diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 52464bf36..9d042118c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,25 +1,28 @@ --- -name: Bug report +name: SQL Parser Error about: Create a report to help us improve -title: '' -labels: '' +title: 'JSQLParser Version : RDBMS : failing feature description' +labels: 'Parser Error', 'Feature Request', 'Documentation', 'Java API', 'RDBMS support' assignees: '' --- -**Describe the bug** -A clear and concise description of what the bug is. +**Failing SQL Feature** +- Brief description of the failing SQL feature +- Example: `WITH ROLLUP` can't be parsed -**To Reproduce** -Steps to reproduce the behavior: -1. Example SQL -2. Parsing this SQL using JSqlParser with this statements -3. Exception +**SQL Example** +- Simplified Query Example, focusing on the failing feature +```sql +-- Replace with your ACTUAL example +select 1 +from dual +``` -**Expected behavior** -A clear and concise description of what you expected to happen. - -**System** - - Database you are using -- Java Version +**Software Information** - JSqlParser version +- Database (e. g. Oracle, MS SQL Server, H2, PostgreSQL, IBM DB2 ) + +**Tips** +Please write in English and avoid Screenshots (as we can't copy and paste content from it). +[Try your example online with the latest JSQLParser](http://217.160.215.75:8080/jsqlformatter/demo.html) and share the link in the error report. diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..b9134ea9a --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,8 @@ +changelog: + categories: + - title: Bugs solved + labels: + - "bug" + - title: Changes and new Features + labels: + - "*" \ No newline at end of file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 000000000..92846f274 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,30 @@ +# 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 + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + java: [8, 11] + 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 + - name: Build with Maven + run: mvn -B package --file pom.xml diff --git a/README.md b/README.md index 03aa05949..f1dbc3715 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # JSqlParser -[![Build Status](https://travis-ci.com/JSQLParser/JSqlParser.svg?branch=master)](https://travis-ci.com/JSQLParser/JSqlParser) [![Coverage Status](https://coveralls.io/repos/JSQLParser/JSqlParser/badge.svg?branch=master)](https://coveralls.io/r/JSQLParser/JSqlParser?branch=master) +![Build Status](https://github.com/JSQLParser/JSqlParser/actions/workflows/maven.yml/badge.svg) + +[![Build Status (Legacy)](https://travis-ci.com/JSQLParser/JSqlParser.svg?branch=master)](https://travis-ci.com/JSQLParser/JSqlParser) [![Coverage Status](https://coveralls.io/repos/JSQLParser/JSqlParser/badge.svg?branch=master)](https://coveralls.io/r/JSQLParser/JSqlParser?branch=master) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/6f9a2d7eb98f45969749e101322634a1)](https://www.codacy.com/gh/JSQLParser/JSqlParser/dashboard?utm_source=github.com&utm_medium=referral&utm_content=JSQLParser/JSqlParser&utm_campaign=Badge_Grade) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.jsqlparser/jsqlparser/badge.svg)](http://maven-badges.herokuapp.com/maven-central/com.github.jsqlparser/jsqlparser) [![Javadocs](https://www.javadoc.io/badge/com.github.jsqlparser/jsqlparser.svg)](https://www.javadoc.io/doc/com.github.jsqlparser/jsqlparser) @@ -22,7 +24,7 @@ Please provide feedback on: * API changes: extend visitor with return values (https://github.com/JSQLParser/JSqlParser/issues/901) ## News -* Released version **4.3** of JSqlParser +* Released version **4.4** of JSqlParser * The array parsing is the default behaviour. Square bracket quotation has to be enabled using a parser flag (**CCJSqlParser.withSquareBracketQuotation**). * due to an API change the version will be 3.0 @@ -50,17 +52,9 @@ To help JSqlParser's development you are encouraged to provide **Please write in English, since it's the language most of the dev team knows.** -Also I would like to know about needed examples or documentation stuff. - -## Extensions in the latest SNAPSHOT version 4.4 +Any requests for examples or any particular documentation will be most welcome. -* support for **timestamp with local time zone** -* improved support for quoted identifiers in casts -* support for **top with ties** -* support for operators **<->** and **<#>** -* improvement of test methods -* validation bugfixes -* Json function Improvements and Bugfix #1506 +## Extensions in the latest SNAPSHOT version 4.5 Additionally, we have fixed many errors and improved the code quality and the test coverage. @@ -68,6 +62,21 @@ Additionally, we have fixed many errors and improved the code quality and the te * [Release Notes](https://github.com/JSQLParser/JSqlParser/releases) * Modifications before GitHub's release tagging are listed in the [Older Releases](https://github.com/JSQLParser/JSqlParser/wiki/Older-Releases) page. +* UnsupportedStatement support instead of throwing Exceptions +* support for **RETURNING** clause of a **DELETE** statement +* Add support for `... ALTER COLUMN ... DROP DEFAULT` +* `INSERT` supports `SetOperations` (e. g. `INSERT INTO ... SELECT ... FROM ... UNION SELECT ... FROM ...`), those `SetOperations` are used both for `SELECT` and `VALUES` clauses (API change) in order to simplify the Grammar +* `(WITH ... SELECT ...)` statements within brackets are now supported +* Postgres `NATURAL { INNER | LEFT | RIGHT } JOIN` support +* extended support for Hive dialect `GROUPING SETS` +* support for Postgresql **drop** function +* support table option **character set** and **index** options +* support Postgresql optional **TABLE** in **TRUNCATE** +* support for `ANALYZE mytable` +* PostgreSQL `INSERT INTO ... ON CONFLICT ... DO ...` statements +* implement Parser Timeout Feature, e. g. `CCJSqlParserUtil.parse(sqlStr, parser -> parser.withTimeOut(60000));` +* extended support Postgres' `Extract( field FROM source)` where `field` is a String instead of a Keyword +* support for `DROP column IF EXISTS` ## Building from the sources @@ -84,7 +93,7 @@ gradle build The project requires the following to build: - Maven (or Gradle) -- JDK 8 or later. The jar will target JDK 8, but the version of the maven-compiler-plugin that JsqlParser uses requires JDK 8+ +- JDK 8 or later. The JAR will target JDK 8, but the version of the maven-compiler-plugin that JSqlParser uses requires JDK 8+ This will produce the jsqlparser-VERSION.jar file in the `target/` directory (`build/libs/jsqlparser-VERSION.jar` in case of Gradle). @@ -114,7 +123,7 @@ This is a valid piece of source code: ## Maven Repository -JSQLParser is deployed at sonatypes open source maven repository. +JSQLParser is deployed at Sonatype open source maven repository. Starting from now I will deploy there. The first snapshot version there will be 0.8.5-SNAPSHOT. To use it this is the repository configuration: @@ -129,14 +138,14 @@ To use it this is the repository configuration: ``` -This repositories releases will be synched to maven central. Snapshots remain at sonatype. +These repository releases will be synchronised to Maven Central. Snapshots remain at Sonatype. And this is the dependency declaration in your pom: ```xml com.github.jsqlparser jsqlparser - 4.2 + 4.4 ``` diff --git a/build.gradle b/build.gradle index 127a97f70..036eae1a5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,18 @@ plugins { id 'java' id 'maven-publish' - id "ca.coglinc2.javacc" version "3.0.0" + id "ca.coglinc2.javacc" version "latest.release" id 'jacoco' - id "com.github.spotbugs" version "4.7.2" + id "com.github.spotbugs" version "latest.release" id 'pmd' id 'checkstyle' // download the RR tools which have no Maven Repository - id "de.undercouch.download" version "4.1.2" + id "de.undercouch.download" version "latest.release" } group = 'com.github.jsqlparser' -version = '4.4-SNAPSHOT' +version = '4.5-SNAPSHOT' description = 'JSQLParser library' java.sourceCompatibility = JavaVersion.VERSION_1_8 @@ -23,29 +23,29 @@ repositories { maven { url = uri('https://repo.maven.apache.org/maven2/') } + maven { url "https://plugins.gradle.org/m2/" } } dependencies { - testImplementation 'commons-io:commons-io:2.11.0' - testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.3.1' - testImplementation 'org.assertj:assertj-core:3.22.0' - testImplementation 'org.apache.commons:commons-lang3:3.12.0' - testImplementation 'com.h2database:h2:2.1.210' + testImplementation 'commons-io:commons-io:2.+' + testImplementation 'org.mockito:mockito-core:4.+' + testImplementation 'org.assertj:assertj-core:3.+' + testImplementation 'org.hamcrest:hamcrest-core:2.+' + testImplementation 'org.apache.commons:commons-lang3:3.+' + testImplementation 'com.h2database:h2:2.+' // for JaCoCo Reports - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' - + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.+' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.+' + // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter - testImplementation 'org.mockito:mockito-junit-jupiter:4.3.1' - + testImplementation 'org.mockito:mockito-junit-jupiter:4.+' + // enforce latest version of JavaCC - javacc 'net.java.dev.javacc:javacc:7.0.10' - + javacc 'net.java.dev.javacc:javacc:7.0.11' } compileJavacc { @@ -58,7 +58,6 @@ java { spotbugs pmd - } jacoco { @@ -115,7 +114,7 @@ jacocoTestCoverageVerification { limit { counter = 'LINE' value = 'MISSEDCOUNT' - maximum = 5513 + maximum = 5700 } excludes = [ 'net.sf.jsqlparser.util.validation.*', @@ -165,7 +164,7 @@ spotbugsMain { spotbugs { // fail only on P1 and without the net.sf.jsqlparser.parser.* - excludeFilter = file("spotBugsExcludeFilter.xml") + excludeFilter = file("config/spotbugs/spotBugsExcludeFilter.xml") // do not run over the test, although we should do that eventually spotbugsTest.enabled = false @@ -173,7 +172,7 @@ spotbugs { pmd { consoleOutput = false - toolVersion = "6.36.0" + toolVersion = "6.46.0" sourceSets = [sourceSets.main] @@ -182,7 +181,7 @@ pmd { //rulesMinimumPriority = 1 - ruleSetFiles = files("ruleset.xml") + ruleSetFiles = files("config/pmd/ruleset.xml") pmdMain { excludes = [ @@ -192,7 +191,7 @@ pmd { } checkstyle { - toolVersion "8.45.1" + toolVersion "9.2" sourceSets = [sourceSets.main, sourceSets.test] configFile =rootProject.file('config/checkstyle/checkstyle.xml') } @@ -254,6 +253,7 @@ task renderRR() { publishing { publications { maven(MavenPublication) { + artifactId 'jsqlparser' from(components.java) } } diff --git a/eclipse-java-google-style.xml b/config/formatter/eclipse-java-google-style.xml similarity index 100% rename from eclipse-java-google-style.xml rename to config/formatter/eclipse-java-google-style.xml diff --git a/ruleset.xml b/config/pmd/ruleset.xml similarity index 96% rename from ruleset.xml rename to config/pmd/ruleset.xml index 1d06a9911..dcecbde00 100644 --- a/ruleset.xml +++ b/config/pmd/ruleset.xml @@ -68,8 +68,17 @@ under the License. - - + + + + + @@ -103,7 +112,7 @@ under the License. - + diff --git a/spotBugsExcludeFilter.xml b/config/spotbugs/spotBugsExcludeFilter.xml similarity index 100% rename from spotBugsExcludeFilter.xml rename to config/spotbugs/spotBugsExcludeFilter.xml diff --git a/pom.xml b/pom.xml index 84d90684e..391c297de 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.github.jsqlparser jsqlparser - 4.4 + 4.5 JSQLParser library 2004 @@ -97,7 +97,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.4 + jsqlparser-4.5 @@ -114,7 +114,7 @@ - ${project.basedir}/ruleset.xml + ${project.basedir}/config/pmd/ruleset.xml **/*Bean.java @@ -216,7 +216,7 @@ net.java.dev.javacc javacc - 7.0.10 + 7.0.11 @@ -625,7 +625,7 @@ UTF-8 - 6.36.0 + 6.46.0 JSqlParser parses an SQL statement and translate it into a hierarchy of Java classes. diff --git a/src/main/java/net/sf/jsqlparser/expression/BinaryExpression.java b/src/main/java/net/sf/jsqlparser/expression/BinaryExpression.java index cf87acfb3..848783206 100644 --- a/src/main/java/net/sf/jsqlparser/expression/BinaryExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/BinaryExpression.java @@ -19,7 +19,6 @@ public abstract class BinaryExpression extends ASTNodeAccessImpl implements Expr private Expression leftExpression; private Expression rightExpression; - // private boolean not = false; public BinaryExpression() { } @@ -50,17 +49,6 @@ public void setRightExpression(Expression expression) { rightExpression = expression; } - // public void setNot() { - // not = true; - // } - // - // public void removeNot() { - // not = false; - // } - // - // public boolean isNot() { - // return not; - // } @Override public String toString() { return // (not ? "NOT " : "") + diff --git a/src/main/java/net/sf/jsqlparser/expression/StringValue.java b/src/main/java/net/sf/jsqlparser/expression/StringValue.java index 08e464d00..6c8c2f401 100644 --- a/src/main/java/net/sf/jsqlparser/expression/StringValue.java +++ b/src/main/java/net/sf/jsqlparser/expression/StringValue.java @@ -31,7 +31,7 @@ public StringValue() { public StringValue(String escapedValue) { // removing "'" at the start and at the end - if (escapedValue.startsWith("'") && escapedValue.endsWith("'")) { + if (escapedValue.length() >= 2 && escapedValue.startsWith("'") && escapedValue.endsWith("'")) { value = escapedValue.substring(1, escapedValue.length() - 1); return; } diff --git a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java index 22bd1eb04..75cff8d2b 100644 --- a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java +++ b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java @@ -28,11 +28,25 @@ public P withSquareBracketQuotation(boolean allowSquareBracketQuotation) { public P withAllowComplexParsing(boolean allowComplexParsing) { return withFeature(Feature.allowComplexParsing, allowComplexParsing); } + + public P withUnsupportedStatements(boolean allowUnsupportedStatements) { + return withFeature(Feature.allowUnsupportedStatements, allowUnsupportedStatements); + } + + public P withTimeOut(int timeOutMillSeconds) { + return withFeature(Feature.timeOut, timeOutMillSeconds); + } + public P withFeature(Feature f, boolean enabled) { getConfiguration().setValue(f, enabled); return me(); } + public P withFeature(Feature f, int value) { + getConfiguration().setValue(f, value); + return me(); + } + public abstract FeatureConfiguration getConfiguration(); public abstract P me(); @@ -41,6 +55,10 @@ public boolean getAsBoolean(Feature f) { return getConfiguration().getAsBoolean(f); } + public Integer getAsInteger(Feature f) { + return getConfiguration().getAsInteger(f); + } + public void setErrorRecovery(boolean errorRecovery) { this.errorRecovery = errorRecovery; } diff --git a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java index ca0874c19..16b703576 100644 --- a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java +++ b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java @@ -12,9 +12,16 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statements; @@ -23,6 +30,8 @@ * * @author toben */ + +@SuppressWarnings("PMD.CyclomaticComplexity") public final class CCJSqlParserUtil { public final static int ALLOWED_NESTING_DEPTH = 10; @@ -54,13 +63,25 @@ public static Statement parse(String sql) throws JSQLParserException { * @throws JSQLParserException */ public static Statement parse(String sql, Consumer consumer) throws JSQLParserException { - boolean allowComplexParsing = getNestingDepth(sql)<=ALLOWED_NESTING_DEPTH; - - CCJSqlParser parser = newParser(sql).withAllowComplexParsing(allowComplexParsing); - if (consumer != null) { - consumer.accept(parser); + Statement statement = null; + + // first, try to parse fast and simple + try { + CCJSqlParser parser = newParser(sql).withAllowComplexParsing(false); + if (consumer != null) { + consumer.accept(parser); + } + statement = parseStatement(parser); + } catch (JSQLParserException ex) { + if (getNestingDepth(sql)<=ALLOWED_NESTING_DEPTH) { + CCJSqlParser parser = newParser(sql).withAllowComplexParsing(true); + if (consumer != null) { + consumer.accept(parser); + } + statement = parseStatement(parser); + } } - return parseStatement(parser); + return statement; } public static CCJSqlParser newParser(String sql) { @@ -112,24 +133,44 @@ public static Expression parseExpression(String expression, boolean allowPartial }); } - public static Expression parseExpression(String expression, boolean allowPartialParse, Consumer consumer) throws JSQLParserException { - boolean allowComplexParsing = getNestingDepth(expression)<=ALLOWED_NESTING_DEPTH; - - CCJSqlParser parser = newParser(expression).withAllowComplexParsing(allowComplexParsing); - if (consumer != null) { - consumer.accept(parser); - } + @SuppressWarnings("PMD.CyclomaticComplexity") + public static Expression parseExpression(String expressionStr, boolean allowPartialParse, Consumer consumer) throws JSQLParserException { + Expression expression = null; + + // first, try to parse fast and simple try { - Expression expr = parser.Expression(); - if (!allowPartialParse && parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) { - throw new JSQLParserException("could only parse partial expression " + expr.toString()); + CCJSqlParser parser = newParser(expressionStr).withAllowComplexParsing(false); + if (consumer != null) { + consumer.accept(parser); + } + try { + expression = parser.Expression(); + if (parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) { + throw new JSQLParserException("could only parse partial expression " + expression.toString()); + } + } catch (ParseException ex) { + throw new JSQLParserException(ex); + } + } catch (JSQLParserException ex1) { + // when fast simple parsing fails, try complex parsing but only if it has a chance to succeed + if (getNestingDepth(expressionStr)<=ALLOWED_NESTING_DEPTH) { + CCJSqlParser parser = newParser(expressionStr).withAllowComplexParsing(true); + if (consumer != null) { + consumer.accept(parser); + } + try { + expression = parser.Expression(); + if (!allowPartialParse && parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) { + throw new JSQLParserException("could only parse partial expression " + expression.toString()); + } + } catch (JSQLParserException ex) { + throw ex; + } catch (ParseException ex) { + throw new JSQLParserException(ex); + } } - return expr; - } catch (JSQLParserException ex) { - throw ex; - } catch (ParseException ex) { - throw new JSQLParserException(ex); } + return expression; } /** @@ -158,24 +199,43 @@ public static Expression parseCondExpression(String condExpr, boolean allowParti }); } - public static Expression parseCondExpression(String condExpr, boolean allowPartialParse, Consumer consumer) throws JSQLParserException { - boolean allowComplexParsing = getNestingDepth(condExpr)<=ALLOWED_NESTING_DEPTH; - - CCJSqlParser parser = newParser(condExpr).withAllowComplexParsing(allowComplexParsing); - if (consumer != null) { - consumer.accept(parser); - } + @SuppressWarnings("PMD.CyclomaticComplexity") + public static Expression parseCondExpression(String conditionalExpressionStr, boolean allowPartialParse, Consumer consumer) throws JSQLParserException { + Expression expression = null; + + // first, try to parse fast and simple try { - Expression expr = parser.Expression(); - if (!allowPartialParse && parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) { - throw new JSQLParserException("could only parse partial expression " + expr.toString()); + CCJSqlParser parser = newParser(conditionalExpressionStr).withAllowComplexParsing(false); + if (consumer != null) { + consumer.accept(parser); + } + try { + expression = parser.Expression(); + if (parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) { + throw new JSQLParserException("could only parse partial expression " + expression.toString()); + } + } catch (ParseException ex) { + throw new JSQLParserException(ex); + } + } catch (JSQLParserException ex1) { + if (getNestingDepth(conditionalExpressionStr)<=ALLOWED_NESTING_DEPTH) { + CCJSqlParser parser = newParser(conditionalExpressionStr).withAllowComplexParsing(true); + if (consumer != null) { + consumer.accept(parser); + } + try { + expression = parser.Expression(); + if (!allowPartialParse && parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) { + throw new JSQLParserException("could only parse partial expression " + expression.toString()); + } + } catch (JSQLParserException ex) { + throw ex; + } catch (ParseException ex) { + throw new JSQLParserException(ex); + } } - return expr; - } catch (JSQLParserException ex) { - throw ex; - } catch (ParseException ex) { - throw new JSQLParserException(ex); } + return expression; } /** @@ -184,11 +244,25 @@ public static Expression parseCondExpression(String condExpr, boolean allowParti * @throws JSQLParserException */ public static Statement parseStatement(CCJSqlParser parser) throws JSQLParserException { + Statement statement = null; try { - return parser.Statement(); + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Future future = executorService.submit(new Callable() { + @Override + public Statement call() throws Exception { + return parser.Statement(); + } + }); + executorService.shutdown(); + + statement = future.get( parser.getConfiguration().getAsInteger(Feature.timeOut), TimeUnit.MILLISECONDS); + } catch (TimeoutException ex) { + parser.interrupted = true; + throw new JSQLParserException("Time out occurred.", ex); } catch (Exception ex) { throw new JSQLParserException(ex); } + return statement; } /** @@ -197,10 +271,35 @@ public static Statement parseStatement(CCJSqlParser parser) throws JSQLParserExc * @return the statements parsed */ public static Statements parseStatements(String sqls) throws JSQLParserException { - boolean allowComplexParsing = getNestingDepth(sqls)<=ALLOWED_NESTING_DEPTH; - - CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(allowComplexParsing); - return parseStatements(parser); + return parseStatements(sqls, null); + } + + /** + * Parse a statement list. + * + * @return the statements parsed + */ + public static Statements parseStatements(String sqls, Consumer consumer) throws JSQLParserException { + Statements statements = null; + + // first, try to parse fast and simple + try { + CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(false); + if (consumer != null) { + consumer.accept(parser); + } + statements = parseStatements(parser); + } catch (JSQLParserException ex) { + // when fast simple parsing fails, try complex parsing but only if it has a chance to succeed + if (getNestingDepth(sqls)<=ALLOWED_NESTING_DEPTH) { + CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(true); + if (consumer != null) { + consumer.accept(parser); + } + statements = parseStatements(parser); + } + } + return statements; } /** @@ -209,11 +308,25 @@ public static Statements parseStatements(String sqls) throws JSQLParserException * @throws JSQLParserException */ public static Statements parseStatements(CCJSqlParser parser) throws JSQLParserException { + Statements statements = null; try { - return parser.Statements(); + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Future future = executorService.submit(new Callable() { + @Override + public Statements call() throws Exception { + return parser.Statements(); + } + }); + executorService.shutdown(); + + statements = future.get( parser.getConfiguration().getAsInteger(Feature.timeOut) , TimeUnit.MILLISECONDS); + } catch (TimeoutException ex) { + parser.interrupted = true; + throw new JSQLParserException("Time out occurred.", ex); } catch (Exception ex) { throw new JSQLParserException(ex); } + return statements; } public static void streamStatements(StatementListener listener, InputStream is, String encoding) throws JSQLParserException { diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java index 7c8b2f23a..1193a1f01 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -28,6 +28,7 @@ import net.sf.jsqlparser.statement.UseStatement; import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; +import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; import net.sf.jsqlparser.statement.create.function.CreateFunction; import net.sf.jsqlparser.statement.create.index.CreateIndex; @@ -394,6 +395,14 @@ public enum Feature { */ alterIndex, + + /** + * SQL "ANALYZE" statement is allowed + * + * @see Analyze + */ + analyze, + /** * SQL "TRUNCATE" statement is allowed * @@ -725,7 +734,15 @@ public enum Feature { * allows complex expression parameters or named parameters for functions * will be switched off, when deep nesting of functions is detected */ - allowComplexParsing(true) + allowComplexParsing(true), + + /** + * allows passing through Unsupported Statements as a plain List of Tokens + * needs to be switched off, when VALIDATING statements or parsing blocks + */ + allowUnsupportedStatements(false), + + timeOut( 6000) ; private Object value; diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/FeatureConfiguration.java b/src/main/java/net/sf/jsqlparser/parser/feature/FeatureConfiguration.java index 117194a21..a8aceb5ce 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/FeatureConfiguration.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/FeatureConfiguration.java @@ -58,7 +58,11 @@ public Object getValue(Feature feature) { } public boolean getAsBoolean(Feature f) { - return Boolean.valueOf(String.valueOf(getValue(f))); + return Boolean.parseBoolean(String.valueOf(getValue(f))); + } + + public Integer getAsInteger(Feature f) { + return Integer.valueOf(String.valueOf(getValue(f))); } public String getAsString(Feature f) { diff --git a/src/main/java/net/sf/jsqlparser/statement/OutputClause.java b/src/main/java/net/sf/jsqlparser/statement/OutputClause.java new file mode 100644 index 000000000..19cda4ca6 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/OutputClause.java @@ -0,0 +1,104 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import net.sf.jsqlparser.expression.UserVariable; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.SelectItem; + +import java.util.List; +import java.util.Objects; + +/** + * T-SQL Output Clause + * + * @see OUTPUT Clause (Transact-SQL) + * + *
+ * <OUTPUT_CLAUSE> ::=
+ * {
+ *     [ OUTPUT <dml_select_list> INTO { @table_variable | output_table } [ ( column_list ) ] ]
+ *     [ OUTPUT <dml_select_list> ]
+ * }
+ * <dml_select_list> ::=
+ * { <column_name> | scalar_expression } [ [AS] column_alias_identifier ]
+ *     [ ,...n ]
+ *
+ * <column_name> ::=
+ * { DELETED | INSERTED | from_table_name } . { * | column_name }
+ *     | $action
+ * 
+ */ +public class OutputClause { + List selectItemList; + UserVariable tableVariable; + Table outputTable; + List columnList; + + public OutputClause(List selectItemList, UserVariable tableVariable, Table outputTable, List columnList) { + this.selectItemList = Objects.requireNonNull(selectItemList, "The Select List of the Output Clause must not be null."); + this.tableVariable = tableVariable; + this.outputTable = outputTable; + this.columnList = columnList; + } + + public List getSelectItemList() { + return selectItemList; + } + + public void setSelectItemList(List selectItemList) { + this.selectItemList = selectItemList; + } + + public UserVariable getTableVariable() { + return tableVariable; + } + + public void setTableVariable(UserVariable tableVariable) { + this.tableVariable = tableVariable; + } + + public Table getOutputTable() { + return outputTable; + } + + public void setOutputTable(Table outputTable) { + this.outputTable = outputTable; + } + + public List getColumnList() { + return columnList; + } + + public void setColumnList(List columnList) { + this.columnList = columnList; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append(" OUTPUT "); + PlainSelect.appendStringListTo(builder, selectItemList, true, false); + + if (tableVariable != null) { + builder.append(" INTO ").append(tableVariable); + } else if (outputTable != null) { + builder.append(" INTO ").append(outputTable); + } + + PlainSelect.appendStringListTo(builder, columnList, true, false); + + return builder.append(" "); + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java index 3d36ed824..1326540db 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java @@ -14,6 +14,7 @@ import net.sf.jsqlparser.statement.alter.AlterSystemStatement; import net.sf.jsqlparser.statement.alter.RenameTableStatement; import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; +import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.schema.CreateSchema; @@ -37,7 +38,9 @@ import net.sf.jsqlparser.statement.values.ValuesStatement; public interface StatementVisitor { - + + void visit(Analyze analyze); + void visit(SavepointStatement savepointStatement); void visit(RollbackStatement rollbackStatement); @@ -120,4 +123,6 @@ public interface StatementVisitor { void visit(PurgeStatement purgeStatement); void visit(AlterSystemStatement alterSystemStatement); + + void visit(UnsupportedStatement unsupportedStatement); } diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java index 9f0966031..1ce1330fe 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java @@ -14,6 +14,7 @@ import net.sf.jsqlparser.statement.alter.AlterSystemStatement; import net.sf.jsqlparser.statement.alter.RenameTableStatement; import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; +import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.schema.CreateSchema; @@ -199,6 +200,11 @@ public void visit(CreateFunctionalStatement createFunctionalStatement) { public void visit(CreateSynonym createSynonym) { } + @Override + public void visit(Analyze analyze) { + + } + @Override public void visit(SavepointStatement savepointStatement) { //@todo: do something usefull here @@ -234,4 +240,9 @@ public void visit(PurgeStatement purgeStatement) { @Override public void visit(AlterSystemStatement alterSystemStatement) { } + + @Override + public void visit(UnsupportedStatement unsupportedStatement) { + + } } diff --git a/src/main/java/net/sf/jsqlparser/statement/UnsupportedStatement.java b/src/main/java/net/sf/jsqlparser/statement/UnsupportedStatement.java new file mode 100644 index 000000000..372204c06 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/UnsupportedStatement.java @@ -0,0 +1,54 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2021 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ + +package net.sf.jsqlparser.statement; + +import java.util.List; +import java.util.Objects; + +/** + * + * @author Andreas Reichel + */ + +public class UnsupportedStatement implements Statement { + private List declarations; + + public UnsupportedStatement(List declarations) { + this.declarations = Objects.requireNonNull(declarations, "The List of Tokens must not be null."); + } + + @Override + public void accept(StatementVisitor statementVisitor) { + statementVisitor.visit(this); + } + + @SuppressWarnings({"PMD.MissingBreakInSwitch", "PMD.SwitchStmtsShouldHaveDefault", "PMD.CyclomaticComplexity"}) + public StringBuilder appendTo(StringBuilder builder) { + int i=0; + for (String s:declarations) { + if (i>0) { + builder.append(" "); + } + builder.append(s); + i++; + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + public boolean isEmpty() { + return declarations.isEmpty(); + } +} 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 6f2d4739d..b537b4f03 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -40,6 +40,8 @@ public class AlterExpression { private List colDataTypeList; private List columnDropNotNullList; + private List columnDropDefaultList; + private List pkColumns; private List ukColumns; private String ukName; @@ -239,6 +241,13 @@ public void addColDropNotNull(ColumnDropNotNull columnDropNotNull) { columnDropNotNullList.add(columnDropNotNull); } + public void addColDropDefault(ColumnDropDefault columnDropDefault) { + if (columnDropDefaultList == null) { + columnDropDefaultList = new ArrayList<>(); + } + columnDropDefaultList.add(columnDropDefault); + } + public List getFkSourceColumns() { return fkSourceColumns; } @@ -403,6 +412,9 @@ public String toString() { if (hasColumn) { b.append("COLUMN "); } + if (usingIfExists) { + b.append("IF EXISTS "); + } if (operation == AlterOperation.RENAME) { b.append(columnOldName).append(" TO "); } @@ -425,20 +437,11 @@ public String toString() { b.append(")"); } } else if (getColumnDropNotNullList() != null) { - if (operation == AlterOperation.CHANGE) { - if (optionalSpecifier != null) { - b.append(optionalSpecifier).append(" "); - } - b.append(columnOldName).append(" "); - } else if (columnDropNotNullList.size() > 1) { - b.append("("); - } else { - b.append("COLUMN "); - } + b.append("COLUMN "); b.append(PlainSelect.getStringList(columnDropNotNullList)); - if (columnDropNotNullList.size() > 1) { - b.append(")"); - } + } else if ( columnDropDefaultList != null && !columnDropDefaultList.isEmpty() ) { + b.append("COLUMN "); + b.append(PlainSelect.getStringList(columnDropDefaultList)); } else if (constraintName != null) { b.append("CONSTRAINT "); if (usingIfExists) { @@ -731,4 +734,22 @@ public String toString() { return columnName + " DROP" + (withNot ? " NOT " : " ") + "NULL"; } } + + public static final class ColumnDropDefault { + + private final String columnName; + + public ColumnDropDefault(String columnName) { + this.columnName = columnName; + } + + public String getColumnName() { + return columnName; + } + + @Override + public String toString() { + return columnName + " DROP DEFAULT"; + } + } } diff --git a/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java b/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java new file mode 100644 index 000000000..35373ae09 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java @@ -0,0 +1,42 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.analyze; + +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class Analyze implements Statement { + + private Table table; + + @Override + public void accept(StatementVisitor statementVisitor) { + statementVisitor.visit(this); + } + + public Table getTable() { + return table; + } + + public void setTable(Table table) { + this.table = table; + } + + @Override + public String toString() { + return "ANALYZE " + table.toString(); + } + + public Analyze withTable(Table table) { + this.setTable(table); + return this; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/delete/Delete.java b/src/main/java/net/sf/jsqlparser/statement/delete/Delete.java index 8fcf8c553..ac811b018 100644 --- a/src/main/java/net/sf/jsqlparser/statement/delete/Delete.java +++ b/src/main/java/net/sf/jsqlparser/statement/delete/Delete.java @@ -20,12 +20,14 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.OracleHint; import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.OutputClause; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.Limit; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.SelectItem; import net.sf.jsqlparser.statement.select.WithItem; public class Delete implements Statement { @@ -43,6 +45,29 @@ public class Delete implements Statement { private DeleteModifierPriority modifierPriority; private boolean modifierIgnore; private boolean modifierQuick; + private List returningExpressionList = null; + private OutputClause outputClause; + + public OutputClause getOutputClause() { + return outputClause; + } + + public void setOutputClause(OutputClause outputClause) { + this.outputClause = outputClause; + } + + public List getReturningExpressionList() { + return returningExpressionList; + } + + public void setReturningExpressionList(List returningExpressionList) { + this.returningExpressionList = returningExpressionList; + } + + public Delete withReturningExpressionList(List returningExpressionList) { + this.returningExpressionList = returningExpressionList; + return this; + } public List getWithItemsList() { return withItemsList; @@ -181,6 +206,11 @@ public String toString() { .collect(joining(", "))); } + if (outputClause!=null) { + outputClause.appendTo(b); + } + + if (hasFrom) { b.append(" FROM"); } @@ -214,6 +244,12 @@ public String toString() { if (limit != null) { b.append(limit); } + + if (getReturningExpressionList() != null) { + b.append(" RETURNING ").append(PlainSelect. + getStringList(getReturningExpressionList(), true, false)); + } + return b.toString(); } diff --git a/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java b/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java index 33dbd3bf3..8a39353bb 100644 --- a/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java +++ b/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java @@ -12,7 +12,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; @@ -24,6 +26,7 @@ public class Drop implements Statement { private String type; private Table name; private List parameters; + private Map> typeToParameters = new HashMap<>(); private boolean ifExists = false; @Override @@ -63,11 +66,23 @@ public void setIfExists(boolean ifExists) { this.ifExists = ifExists; } + public Map> getTypeToParameters() { + return typeToParameters; + } + + public void setTypeToParameters(Map> typeToParameters) { + this.typeToParameters = typeToParameters; + } + @Override public String toString() { String sql = "DROP " + type + " " + (ifExists ? "IF EXISTS " : "") + name.toString(); + if (type.equals("FUNCTION")) { + sql += formatFuncParams(getParamsByType("FUNCTION")); + } + if (parameters != null && !parameters.isEmpty()) { sql += " " + PlainSelect.getStringList(parameters); } @@ -75,6 +90,17 @@ public String toString() { return sql; } + public static String formatFuncParams(List params) { + if (params == null) { + return ""; + } + return params.isEmpty() ? "()" : PlainSelect.getStringList(params, true, true); + } + + public List getParamsByType(String type) { + return typeToParameters.get(type); + } + public Drop withIfExists(boolean ifExists) { this.setIfExists(ifExists); return this; diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/ConflictActionType.java b/src/main/java/net/sf/jsqlparser/statement/insert/ConflictActionType.java new file mode 100644 index 000000000..3461ab8a7 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/insert/ConflictActionType.java @@ -0,0 +1,14 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.insert; + +public enum ConflictActionType { + DO_NOTHING, DO_UPDATE +} diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java index 64be4e8ed..2b96bc390 100644 --- a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java +++ b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java @@ -15,17 +15,24 @@ import java.util.Iterator; import java.util.List; import java.util.Optional; + import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.OracleHint; +import net.sf.jsqlparser.expression.RowConstructor; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.ItemsList; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.OutputClause; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; -import net.sf.jsqlparser.statement.select.SelectExpressionItem; +import net.sf.jsqlparser.statement.select.SelectBody; +import net.sf.jsqlparser.statement.select.SelectItem; +import net.sf.jsqlparser.statement.select.SetOperationList; import net.sf.jsqlparser.statement.select.WithItem; +import net.sf.jsqlparser.statement.values.ValuesStatement; @SuppressWarnings({"PMD.CyclomaticComplexity"}) public class Insert implements Statement { @@ -33,25 +40,31 @@ public class Insert implements Statement { private Table table; private OracleHint oracleHint = null; private List columns; - private ItemsList itemsList; - private boolean useValues = true; private Select select; - private boolean useSelectBrackets = true; private boolean useDuplicate = false; private List duplicateUpdateColumns; private List duplicateUpdateExpressionList; private InsertModifierPriority modifierPriority = null; private boolean modifierIgnore = false; - private boolean returningAllColumns = false; - - private List returningExpressionList = null; + private List returningExpressionList = null; private boolean useSet = false; private List setColumns; private List setExpressionList; private List withItemsList; + private OutputClause outputClause; + private InsertConflictTarget conflictTarget; + private InsertConflictAction conflictAction; + + public OutputClause getOutputClause() { + return outputClause; + } + public void setOutputClause(OutputClause outputClause) { + this.outputClause = outputClause; + } + @Override public void accept(StatementVisitor statementVisitor) { statementVisitor.visit(this); @@ -86,35 +99,48 @@ public void setColumns(List list) { * * @return the values of the insert */ + @Deprecated public ItemsList getItemsList() { - return itemsList; + if (select!=null) { + SelectBody selectBody = select.getSelectBody(); + if (selectBody instanceof SetOperationList) { + SetOperationList setOperationList = (SetOperationList) selectBody; + List selects = setOperationList.getSelects(); + + if (selects.size() == 1) { + SelectBody selectBody1 = selects.get(0); + if (selectBody1 instanceof ValuesStatement) { + ValuesStatement valuesStatement = (ValuesStatement) selectBody1; + if (valuesStatement.getExpressions() instanceof ExpressionList) { + ExpressionList expressionList = (ExpressionList) valuesStatement.getExpressions(); + + if (expressionList.getExpressions().size() == 1 && expressionList.getExpressions().get(0) instanceof RowConstructor) { + RowConstructor rowConstructor = (RowConstructor) expressionList.getExpressions().get(0); + return rowConstructor.getExprList(); + } else { + return expressionList; + } + } else { + return valuesStatement.getExpressions(); + } + } + } + } + } + return null; } - public void setItemsList(ItemsList list) { - itemsList = list; - } + @Deprecated public boolean isUseValues() { - return useValues; - } - - public void setUseValues(boolean useValues) { - this.useValues = useValues; - } - - public boolean isReturningAllColumns() { - return returningAllColumns; + return select!=null && select.getSelectBody() instanceof ValuesStatement; } - public void setReturningAllColumns(boolean returningAllColumns) { - this.returningAllColumns = returningAllColumns; - } - - public List getReturningExpressionList() { + public List getReturningExpressionList() { return returningExpressionList; } - public void setReturningExpressionList(List returningExpressionList) { + public void setReturningExpressionList(List returningExpressionList) { this.returningExpressionList = returningExpressionList; } @@ -126,12 +152,9 @@ public void setSelect(Select select) { this.select = select; } + @Deprecated public boolean isUseSelectBrackets() { - return useSelectBrackets; - } - - public void setUseSelectBrackets(boolean useSelectBrackets) { - this.useSelectBrackets = useSelectBrackets; + return false; } public boolean isUseDuplicate() { @@ -206,6 +229,32 @@ public void setWithItemsList(List withItemsList) { this.withItemsList = withItemsList; } + public InsertConflictTarget getConflictTarget() { + return conflictTarget; + } + + public void setConflictTarget(InsertConflictTarget conflictTarget) { + this.conflictTarget = conflictTarget; + } + + public Insert withConflictTarget(InsertConflictTarget conflictTarget) { + setConflictTarget(conflictTarget); + return this; + } + + public InsertConflictAction getConflictAction() { + return conflictAction; + } + + public void setConflictAction(InsertConflictAction conflictAction) { + this.conflictAction = conflictAction; + } + + public Insert withConflictAction(InsertConflictAction conflictAction) { + setConflictAction(conflictAction); + return this; + } + @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public String toString() { @@ -233,25 +282,15 @@ public String toString() { if (columns != null) { sql.append(PlainSelect.getStringList(columns, true, true)).append(" "); } - - if (useValues) { - sql.append("VALUES "); + + if (outputClause !=null) { + sql.append(outputClause.toString()); } - if (itemsList != null) { - sql.append(itemsList); - } else { - if (useSelectBrackets) { - sql.append("("); - } - if (select != null) { - sql.append(select); - } - if (useSelectBrackets) { - sql.append(")"); - } + if (select != null) { + sql.append(select); } - + if (useSet) { sql.append("SET "); for (int i = 0; i < getSetColumns().size(); i++) { @@ -274,9 +313,16 @@ public String toString() { } } - if (isReturningAllColumns()) { - sql.append(" RETURNING *"); - } else if (getReturningExpressionList() != null) { + if (conflictAction!=null) { + sql.append(" ON CONFLICT"); + + if (conflictTarget!=null) { + conflictTarget.appendTo(sql); + } + conflictAction.appendTo(sql); + } + + if (getReturningExpressionList() != null) { sql.append(" RETURNING ").append(PlainSelect. getStringList(getReturningExpressionList(), true, false)); } @@ -288,22 +334,12 @@ public Insert withWithItemsList(List withList) { this.withItemsList = withList; return this; } - - public Insert withUseValues(boolean useValues) { - this.setUseValues(useValues); - return this; - } public Insert withSelect(Select select) { this.setSelect(select); return this; } - public Insert withUseSelectBrackets(boolean useSelectBrackets) { - this.setUseSelectBrackets(useSelectBrackets); - return this; - } - public Insert withUseDuplicate(boolean useDuplicate) { this.setUseDuplicate(useDuplicate); return this; @@ -329,12 +365,7 @@ public Insert withModifierIgnore(boolean modifierIgnore) { return this; } - public Insert withReturningAllColumns(boolean returningAllColumns) { - this.setReturningAllColumns(returningAllColumns); - return this; - } - - public Insert withReturningExpressionList(List returningExpressionList) { + public Insert withReturningExpressionList(List returningExpressionList) { this.setReturningExpressionList(returningExpressionList); return this; } @@ -369,11 +400,6 @@ public Insert withSetColumns(List columns) { return this; } - public Insert withItemsList(ItemsList itemsList) { - this.setItemsList(itemsList); - return this; - } - public Insert addColumns(Column... columns) { List collection = Optional.ofNullable(getColumns()).orElseGet(ArrayList::new); Collections.addAll(collection, columns); @@ -410,14 +436,14 @@ public Insert addDuplicateUpdateExpressionList(Collection return this.withDuplicateUpdateExpressionList(collection); } - public Insert addReturningExpressionList(SelectExpressionItem... returningExpressionList) { - List collection = Optional.ofNullable(getReturningExpressionList()).orElseGet(ArrayList::new); + public Insert addReturningExpressionList(SelectItem... returningExpressionList) { + List collection = Optional.ofNullable(getReturningExpressionList()).orElseGet(ArrayList::new); Collections.addAll(collection, returningExpressionList); return this.withReturningExpressionList(collection); } - public Insert addReturningExpressionList(Collection returningExpressionList) { - List collection = Optional.ofNullable(getReturningExpressionList()).orElseGet(ArrayList::new); + public Insert addReturningExpressionList(Collection returningExpressionList) { + List collection = Optional.ofNullable(getReturningExpressionList()).orElseGet(ArrayList::new); collection.addAll(returningExpressionList); return this.withReturningExpressionList(collection); } diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/InsertConflictAction.java b/src/main/java/net/sf/jsqlparser/statement/insert/InsertConflictAction.java new file mode 100644 index 000000000..32d9313eb --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/insert/InsertConflictAction.java @@ -0,0 +1,108 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.insert; + +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.update.UpdateSet; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; + +/** + * https://www.postgresql.org/docs/current/sql-insert.html + *
+ * conflict_action is one of:
+ *
+ *     DO NOTHING
+ *     DO UPDATE SET { column_name = { expression | DEFAULT } |
+ *                     ( column_name [, ...] ) = [ ROW ] ( { expression | DEFAULT } [, ...] ) |
+ *                     ( column_name [, ...] ) = ( sub-SELECT )
+ *                   } [, ...]
+ *               [ WHERE condition ]
+ * 
+ */ + +public class InsertConflictAction { + ConflictActionType conflictActionType; + + private final ArrayList updateSets = new ArrayList<>(); + Expression whereExpression; + + public InsertConflictAction(ConflictActionType conflictActionType) { + this.conflictActionType = Objects.requireNonNull(conflictActionType, "The Conflict Action Type is mandatory and must not be Null."); + } + + public ConflictActionType getConflictActionType() { + return conflictActionType; + } + + public void setConflictActionType(ConflictActionType conflictActionType) { + this.conflictActionType = Objects.requireNonNull(conflictActionType, "The Conflict Action Type is mandatory and must not be Null."); + } + + public InsertConflictAction withConflictActionType(ConflictActionType conflictActionType) { + setConflictActionType(conflictActionType); + return this; + } + + public InsertConflictAction addUpdateSet(Column column, Expression expression) { + this.updateSets.add(new UpdateSet(column, expression)); + return this; + } + + public InsertConflictAction addUpdateSet(UpdateSet updateSet) { + this.updateSets.add(updateSet); + return this; + } + + public InsertConflictAction withUpdateSets(Collection updateSets) { + this.updateSets.clear(); + this.updateSets.addAll(updateSets); + return this; + } + + public Expression getWhereExpression() { + return whereExpression; + } + + public void setWhereExpression(Expression whereExpression) { + this.whereExpression = whereExpression; + } + + public InsertConflictAction withWhereExpression(Expression whereExpression) { + setWhereExpression(whereExpression); + return this; + } + + @SuppressWarnings("PMD.SwitchStmtsShouldHaveDefault") + public StringBuilder appendTo(StringBuilder builder) { + switch (conflictActionType) { + case DO_NOTHING: + builder.append(" DO NOTHING"); + break; + case DO_UPDATE: + builder.append(" DO UPDATE "); + UpdateSet.appendUpdateSetsTo(builder, updateSets); + + if (whereExpression!=null) { + builder.append(" WHERE ").append(whereExpression); + } + break; + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/InsertConflictTarget.java b/src/main/java/net/sf/jsqlparser/statement/insert/InsertConflictTarget.java new file mode 100644 index 000000000..0f8e9808f --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/insert/InsertConflictTarget.java @@ -0,0 +1,121 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.insert; + +import net.sf.jsqlparser.expression.Expression; + +/** + * https://www.postgresql.org/docs/current/sql-insert.html + *
+ * conflict_target can be one of:
+ *
+ *     ( { index_column_name | ( index_expression ) } [ COLLATE collation ] [ opclass ] [, ...] ) [ WHERE index_predicate ]
+ *     ON CONSTRAINT constraint_name
+ * 
+ * Currently, COLLATE is not supported yet. + */ +public class InsertConflictTarget { + + String indexColumnName; + Expression indexExpression; + Expression whereExpression; + String constraintName; + + public InsertConflictTarget(String indexColumnName, Expression indexExpression, Expression whereExpression, String constraintName) { + this.indexColumnName = indexColumnName; + this.indexExpression = indexExpression; + + this.whereExpression = whereExpression; + this.constraintName = constraintName; + } + + public String getIndexColumnName() { + return indexColumnName; + } + + public void setIndexColumnName(String indexColumnName) { + this.indexColumnName = indexColumnName; + this.indexExpression = null; + } + + public InsertConflictTarget withIndexColumnName(String indexColumnName) { + setIndexColumnName(indexColumnName); + return this; + } + + public Expression getIndexExpression() { + return indexExpression; + } + + public void setIndexExpression(Expression indexExpression) { + this.indexExpression = indexExpression; + this.indexColumnName = null; + } + + public InsertConflictTarget withIndexExpression(Expression indexExpression) { + setIndexExpression(indexExpression); + return this; + } + + public Expression getWhereExpression() { + return whereExpression; + } + + public void setWhereExpression(Expression whereExpression) { + this.whereExpression = whereExpression; + } + + public InsertConflictTarget withWhereExpression(Expression whereExpression) { + setWhereExpression(whereExpression); + return this; + } + + public String getConstraintName() { + return constraintName; + } + + public void setConstraintName(String constraintName) { + this.constraintName = constraintName; + } + + public InsertConflictTarget withConstraintName(String constraintName) { + setConstraintName(constraintName); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + if (constraintName==null) { + builder.append(" ( "); + + //@todo: Index Expression is not supported yet + //if (indexColumnName != null) { + builder.append(indexColumnName); + //} else { + // builder.append(" ( ").append(indexExpression).append(" )"); + //} + builder.append(" "); + + //@todo: Collate is not supported yet + + builder.append(") "); + + if (whereExpression != null) { + builder.append(" WHERE ").append(whereExpression); + } + } else { + builder.append(" ON CONSTRAINT ").append(constraintName); + } + return builder; + } + + public String toString() { + return appendTo(new StringBuilder()).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/select/GroupByElement.java b/src/main/java/net/sf/jsqlparser/statement/select/GroupByElement.java index 4cc9c25c9..15c4f17c5 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/GroupByElement.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/GroupByElement.java @@ -95,7 +95,14 @@ public String toString() { if (groupByExpressions.isUsingBrackets()) { b.append(" )"); } - } else if (groupingSets.size() > 0) { + } else if (groupByExpressions.isUsingBrackets()) { + b.append("()"); + } + + if (groupingSets.size() > 0) { + if (b.charAt(b.length() - 1) != ' ') { + b.append(' '); + } b.append("GROUPING SETS ("); boolean first = true; for (Object o : groupingSets) { @@ -112,10 +119,6 @@ public String toString() { } } b.append(")"); - } else { - if (groupByExpressions.isUsingBrackets()) { - b.append("()"); - } } return b.toString(); diff --git a/src/main/java/net/sf/jsqlparser/statement/select/Join.java b/src/main/java/net/sf/jsqlparser/statement/select/Join.java index 4c6d74ffc..7c4200bef 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/Join.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/Join.java @@ -304,10 +304,12 @@ public String toString() { } else if (isSimple()) { builder.append(rightItem); } else { + if (isNatural()) { + builder.append("NATURAL "); + } + if (isRight()) { builder.append("RIGHT "); - } else if (isNatural()) { - builder.append("NATURAL "); } else if (isFull()) { builder.append("FULL "); } else if (isLeft()) { 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 ff1a174a9..6fc59b926 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java @@ -532,28 +532,38 @@ public static String getStringList(List list) { * @return comma separated list of the elements in the list */ public static String getStringList(List list, boolean useComma, boolean useBrackets) { - StringBuilder ans = new StringBuilder(); - String comma = ","; - if (!useComma) { - comma = ""; - } + return appendStringListTo(new StringBuilder(), list, useComma, useBrackets).toString(); + } + + /** + * Append the toString out put of the objects in the List (that can be comma + * separated). If the List is null or empty an empty string is returned. + * + * @param list list of objects with toString methods + * @param useComma true if the list has to be comma separated + * @param useBrackets true if the list has to be enclosed in brackets + * @return comma separated list of the elements in the list + */ + public static StringBuilder appendStringListTo(StringBuilder builder, List list, boolean useComma, boolean useBrackets) { if (list != null) { + String comma = useComma ? "," : ""; + if (useBrackets) { - ans.append("("); + builder.append("("); } - for (int i = 0; i < list.size(); i++) { - ans.append(list.get(i)).append( i < list.size() - 1 - ? comma + " " - : "" ); + int size = list.size(); + for (int i = 0; i < size; i++) { + builder.append(list.get(i)).append( i < size - 1 + ? comma + " " + : "" ); } if (useBrackets) { - ans.append(")"); + builder.append(")"); } } - - return ans.toString(); + return builder; } public PlainSelect withMySqlSqlCalcFoundRows(boolean mySqlCalcFoundRows) { diff --git a/src/main/java/net/sf/jsqlparser/statement/select/Select.java b/src/main/java/net/sf/jsqlparser/statement/select/Select.java index 7886f44f7..67a002777 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/Select.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/Select.java @@ -23,6 +23,8 @@ public class Select implements Statement { private SelectBody selectBody; private List withItemsList; + private boolean useWithBrackets = false; + @Override public void accept(StatementVisitor statementVisitor) { statementVisitor.visit(this); @@ -41,11 +43,27 @@ public void setSelectBody(SelectBody body) { selectBody = body; } + public void setUsingWithBrackets(boolean useWithBrackets) { + this.useWithBrackets = useWithBrackets; + } + + public Select withUsingWithBrackets(boolean useWithBrackets) { + this.useWithBrackets = useWithBrackets; + return this; + } + + public boolean isUsingWithBrackets() { + return this.useWithBrackets; + } + @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public String toString() { StringBuilder retval = new StringBuilder(); if (withItemsList != null && !withItemsList.isEmpty()) { + if (useWithBrackets) { + retval.append("( "); + } retval.append("WITH "); for (Iterator iter = withItemsList.iterator(); iter.hasNext();) { WithItem withItem = iter.next(); @@ -57,6 +75,9 @@ public String toString() { } } retval.append(selectBody); + if (withItemsList != null && !withItemsList.isEmpty() && useWithBrackets) { + retval.append(" )"); + } return retval.toString(); } diff --git a/src/main/java/net/sf/jsqlparser/statement/truncate/Truncate.java b/src/main/java/net/sf/jsqlparser/statement/truncate/Truncate.java index 67d064249..9848c7799 100644 --- a/src/main/java/net/sf/jsqlparser/statement/truncate/Truncate.java +++ b/src/main/java/net/sf/jsqlparser/statement/truncate/Truncate.java @@ -18,6 +18,9 @@ public class Truncate implements Statement { private Table table; boolean cascade; // to support TRUNCATE TABLE ... CASCADE + boolean tableToken; // to support TRUNCATE without TABLE + boolean only; // to support TRUNCATE with ONLY + @Override public void accept(StatementVisitor statementVisitor) { statementVisitor.visit(this); @@ -41,10 +44,42 @@ public void setCascade(boolean c) { @Override public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("TRUNCATE"); + if (tableToken) { + sb.append(" TABLE"); + } + if (only) { + sb.append(" ONLY"); + } + sb.append(" "); + sb.append(table); + if (cascade) { - return "TRUNCATE TABLE " + table + " CASCADE"; + sb.append( " CASCADE"); } - return "TRUNCATE TABLE " + table; + return sb.toString(); + } + + public boolean isTableToken() { + return tableToken; + } + + public void setTableToken(boolean hasTable) { + this.tableToken = hasTable; + } + + public boolean isOnly() { + return only; + } + + public void setOnly(boolean only) { + this.only = only; + } + + public Truncate withTableToken(boolean hasTableToken){ + this.setTableToken(hasTableToken); + return this; } public Truncate withTable(Table table) { @@ -56,4 +91,9 @@ public Truncate withCascade(boolean cascade) { this.setCascade(cascade); return this; } + public Truncate withOnly(boolean only) { + this.setOnly(only); + return this; + } } + diff --git a/src/main/java/net/sf/jsqlparser/statement/update/Update.java b/src/main/java/net/sf/jsqlparser/statement/update/Update.java index 33f703bb4..0da3fd3a4 100644 --- a/src/main/java/net/sf/jsqlparser/statement/update/Update.java +++ b/src/main/java/net/sf/jsqlparser/statement/update/Update.java @@ -19,6 +19,7 @@ import net.sf.jsqlparser.expression.OracleHint; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.OutputClause; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; import net.sf.jsqlparser.statement.select.*; @@ -36,11 +37,20 @@ public class Update implements Statement { private OracleHint oracleHint = null; private List orderByElements; private Limit limit; - private boolean returningAllColumns = false; - private List returningExpressionList = null; + private List returningExpressionList = null; private UpdateModifierPriority modifierPriority; private boolean modifierIgnore; + private OutputClause outputClause; + + public OutputClause getOutputClause() { + return outputClause; + } + + public void setOutputClause(OutputClause outputClause) { + this.outputClause = outputClause; + } + public ArrayList getUpdateSets() { return updateSets; } @@ -219,19 +229,11 @@ public Limit getLimit() { return limit; } - public boolean isReturningAllColumns() { - return returningAllColumns; - } - - public void setReturningAllColumns(boolean returningAllColumns) { - this.returningAllColumns = returningAllColumns; - } - - public List getReturningExpressionList() { + public List getReturningExpressionList() { return returningExpressionList; } - public void setReturningExpressionList(List returningExpressionList) { + public void setReturningExpressionList(List returningExpressionList) { this.returningExpressionList = returningExpressionList; } @@ -251,7 +253,6 @@ public void setModifierIgnore(boolean modifierIgnore) { this.modifierIgnore = modifierIgnore; } - @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.ExcessiveMethodLength"}) public String toString() { @@ -285,47 +286,13 @@ public String toString() { } } } - b.append(" SET "); - int j = 0; - for (UpdateSet updateSet : updateSets) { - if (j > 0) { - b.append(", "); - } + UpdateSet.appendUpdateSetsTo(b, updateSets); - if (updateSet.usingBracketsForColumns) { - b.append("("); - } - - for (int i = 0; i < updateSet.columns.size(); i++) { - if (i > 0) { - b.append(", "); - } - b.append(updateSet.columns.get(i)); - } - - if (updateSet.usingBracketsForColumns) { - b.append(")"); - } - - b.append(" = "); - - if (updateSet.usingBracketsForValues) { - b.append("("); - } - - for (int i = 0; i < updateSet.expressions.size(); i++) { - if (i > 0) { - b.append(", "); - } - b.append(updateSet.expressions.get(i)); - } - if (updateSet.usingBracketsForValues) { - b.append(")"); - } - - j++; + if (outputClause != null) { + outputClause.appendTo(b); } + if (fromItem != null) { b.append(" FROM ").append(fromItem); if (joins != null) { @@ -350,9 +317,7 @@ public String toString() { b.append(limit); } - if (isReturningAllColumns()) { - b.append(" RETURNING *"); - } else if (getReturningExpressionList() != null) { + if (getReturningExpressionList() != null) { b.append(" RETURNING ").append(PlainSelect. getStringList(getReturningExpressionList(), true, false)); } @@ -405,12 +370,7 @@ public Update withLimit(Limit limit) { return this; } - public Update withReturningAllColumns(boolean returningAllColumns) { - this.setReturningAllColumns(returningAllColumns); - return this; - } - - public Update withReturningExpressionList(List returningExpressionList) { + public Update withReturningExpressionList(List returningExpressionList) { this.setReturningExpressionList(returningExpressionList); return this; } @@ -430,36 +390,36 @@ public Update withExpressions(List expressions) { return this; } - public Update withModifierPriority(UpdateModifierPriority modifierPriority){ + public Update withModifierPriority(UpdateModifierPriority modifierPriority) { this.setModifierPriority(modifierPriority); return this; } - public Update withModifierIgnore(boolean modifierIgnore){ + public Update withModifierIgnore(boolean modifierIgnore) { this.setModifierIgnore(modifierIgnore); return this; } public Update addColumns(Column... columns) { - List collection = Optional.ofNullable(getColumns()).orElseGet(ArrayList::new); + List collection = new ArrayList<>(Optional.ofNullable(getColumns()).orElseGet(ArrayList::new)); Collections.addAll(collection, columns); return this.withColumns(collection); } public Update addColumns(Collection columns) { - List collection = Optional.ofNullable(getColumns()).orElseGet(ArrayList::new); + List collection = new ArrayList<>(Optional.ofNullable(getColumns()).orElseGet(ArrayList::new)); collection.addAll(columns); return this.withColumns(collection); } public Update addExpressions(Expression... expressions) { - List collection = Optional.ofNullable(getExpressions()).orElseGet(ArrayList::new); + List collection = new ArrayList<>(Optional.ofNullable(getExpressions()).orElseGet(ArrayList::new)); Collections.addAll(collection, expressions); return this.withExpressions(collection); } public Update addExpressions(Collection expressions) { - List collection = Optional.ofNullable(getExpressions()).orElseGet(ArrayList::new); + List collection = new ArrayList<>(Optional.ofNullable(getExpressions()).orElseGet(ArrayList::new)); collection.addAll(expressions); return this.withExpressions(collection); } @@ -500,14 +460,14 @@ public Update addOrderByElements(Collection orderByEle return this.withOrderByElements(collection); } - public Update addReturningExpressionList(SelectExpressionItem... returningExpressionList) { - List collection = Optional.ofNullable(getReturningExpressionList()).orElseGet(ArrayList::new); + public Update addReturningExpressionList(SelectItem... returningExpressionList) { + List collection = Optional.ofNullable(getReturningExpressionList()).orElseGet(ArrayList::new); Collections.addAll(collection, returningExpressionList); return this.withReturningExpressionList(collection); } - public Update addReturningExpressionList(Collection returningExpressionList) { - List collection = Optional.ofNullable(getReturningExpressionList()).orElseGet(ArrayList::new); + public Update addReturningExpressionList(Collection returningExpressionList) { + List collection = Optional.ofNullable(getReturningExpressionList()).orElseGet(ArrayList::new); collection.addAll(returningExpressionList); return this.withReturningExpressionList(collection); } diff --git a/src/main/java/net/sf/jsqlparser/statement/update/UpdateSet.java b/src/main/java/net/sf/jsqlparser/statement/update/UpdateSet.java index 3e6da1ba1..a25df6e5e 100644 --- a/src/main/java/net/sf/jsqlparser/statement/update/UpdateSet.java +++ b/src/main/java/net/sf/jsqlparser/statement/update/UpdateSet.java @@ -14,6 +14,7 @@ import net.sf.jsqlparser.schema.Column; import java.util.ArrayList; +import java.util.Collection; import java.util.Objects; public class UpdateSet { @@ -84,4 +85,55 @@ public void add(ExpressionList expressionList) { expressions.addAll(expressionList.getExpressions()); } + public final static StringBuilder appendUpdateSetsTo(StringBuilder builder, Collection updateSets) { + builder.append(" SET "); + + int j = 0; + for (UpdateSet updateSet : updateSets) { + updateSet.appendTo(builder, j); + j++; + } + return builder; + } + + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPath"}) + StringBuilder appendTo(StringBuilder builder, int j) { + if (j > 0) { + builder.append(", "); + } + + if (usingBracketsForColumns) { + builder.append("("); + } + + for (int i = 0; i < columns.size(); i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(columns.get(i)); + } + + if (usingBracketsForColumns) { + builder.append(")"); + } + + builder.append(" = "); + + if (usingBracketsForValues) { + builder.append("("); + } + + for (int i = 0; i < expressions.size(); i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(expressions.get(i)); + } + if (usingBracketsForValues) { + builder.append(")"); + } + + return builder; + } + } diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index 32b316b58..945b163a1 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -27,6 +27,7 @@ import net.sf.jsqlparser.statement.alter.AlterSystemStatement; import net.sf.jsqlparser.statement.alter.RenameTableStatement; import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; +import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.schema.CreateSchema; @@ -686,6 +687,9 @@ public void visit(Replace replace) { } } + public void visit(Analyze analyze) { + visit(analyze.getTable()); + } @Override public void visit(Drop drop) { visit(drop.getName()); @@ -1032,6 +1036,11 @@ public void visit(AlterSystemStatement alterSystemStatement) { // no tables involved in this statement } + @Override + public void visit(UnsupportedStatement unsupportedStatement) { + // no tables involved in this statement + } + @Override public void visit(GeometryDistance geometryDistance) { visitBinaryExpression(geometryDistance); diff --git a/src/main/java/net/sf/jsqlparser/util/cnfexpression/CNFConverter.java b/src/main/java/net/sf/jsqlparser/util/cnfexpression/CNFConverter.java index 1238b7229..fbb4e8499 100644 --- a/src/main/java/net/sf/jsqlparser/util/cnfexpression/CNFConverter.java +++ b/src/main/java/net/sf/jsqlparser/util/cnfexpression/CNFConverter.java @@ -213,7 +213,6 @@ public class CNFConverter { // notice temp1 will be settled as the root and temp2 will be // settled as the dummy root. private boolean isUsed = false; - private CloneHelper clone = new CloneHelper(); private class Mule { @@ -234,9 +233,8 @@ public static Expression convertToCNF(Expression expr) { } /** - * this method takes an expression tree and converts that into a CNF form. Notice the 5 steps - * shown above will turn into 5 different methods. For the sake of testing, I set them public. - * return the converted expression. + * this method takes an expression tree and converts that into a CNF form. Notice the 5 steps shown above will turn + * into 5 different methods. For the sake of testing, I set them public. return the converted expression. * * @param express the original expression tree. */ @@ -260,21 +258,21 @@ private Expression convert(Expression express) } /** - * this is the first step that rebuild the expression tree. Use the standard specified in the - * above class. Traverse the original tree recursively and rebuild the tree from that. + * this is the first step that rebuild the expression tree. Use the standard specified in the above class. Traverse + * the original tree recursively and rebuild the tree from that. * * @param express the original expression tree. */ private void reorder(Expression express) { - root = clone.modify(express); + root = CloneHelper.modify(express); List list = new ArrayList(); list.add(root); dummy = new MultiAndExpression(list); } /** - * This method is used to deal with pushing not operators down. Since it needs an extra - * parameter, I will create a new method to handle this. + * This method is used to deal with pushing not operators down. Since it needs an extra parameter, I will create a + * new method to handle this. */ private void pushNotDown() { /* set the two temp parameters to their staring point. */ @@ -290,11 +288,10 @@ private void pushNotDown() { } /** - * This method is the helper function to push not operators down. traverse the tree thoroughly, - * when we meet the not operator. We only need to consider these three operators: - * MultiAndOperator, MultiOrOperator, NotOperator. Handle them in a seperate way. when we finish - * the traverse, the expression tree will have all the not operators pushed as downwards as they - * could. In the method, I use two global variables: temp1 and temp2 to traverse the expression + * This method is the helper function to push not operators down. traverse the tree thoroughly, when we meet the not + * operator. We only need to consider these three operators: MultiAndOperator, MultiOrOperator, NotOperator. Handle + * them in a seperate way. when we finish the traverse, the expression tree will have all the not operators pushed + * as downwards as they could. In the method, I use two global variables: temp1 and temp2 to traverse the expression * tree. Notice that temp2 will always be the parent of temp1. * * @param index the index of the children appeared in parents array. @@ -322,8 +319,8 @@ private void pushNot(int index) { } /** - * This function mainly deals with pushing not operators down. check the child. If it is not a - * logic operator(and or or). stop at that point. Else use De Morgan law to push not downwards. + * This function mainly deals with pushing not operators down. check the child. If it is not a logic operator(and or + * or). stop at that point. Else use De Morgan law to push not downwards. * * @param index the index of the children appeared in parents array. */ @@ -386,10 +383,9 @@ private void handleNot(int index) { } /** - * This method serves as dealing with the third step. It is used to put all the adjacent same - * multi operators together. BFS the tree and do it node by node. In the end we will get the - * tree where all the same multi operators store in the same odd level of the tree or in the - * same even level of the tree. + * This method serves as dealing with the third step. It is used to put all the adjacent same multi operators + * together. BFS the tree and do it node by node. In the end we will get the tree where all the same multi operators + * store in the same odd level of the tree or in the same even level of the tree. */ @SuppressWarnings({"PMD.CyclomaticComplexity"}) private void gather() { @@ -468,10 +464,10 @@ private void gather() { } /** - * First, BFS the tree and gather all the or operators and their parents into a stack. Next, pop - * them out and push the and operators under the or operators upwards(if there are). Do this - * level by level, which means during each level we will call the gather() method to make the - * tree uniform. When we move out of the stack. The expression tree shall be in CNF form. + * First, BFS the tree and gather all the or operators and their parents into a stack. Next, pop them out and push + * the and operators under the or operators upwards(if there are). Do this level by level, which means during each + * level we will call the gather() method to make the tree uniform. When we move out of the stack. The expression + * tree shall be in CNF form. */ private void pushAndUp() { Queue queue = new LinkedList(); @@ -517,12 +513,11 @@ private void pushAndUp() { } /** - * This helper function is used to deal with pushing and up: generally, pop the top element out - * of the stack, use BFS to traverse the tree and push and up. It will case the expression tree - * to have the and as the new root and multiple or as the children. Push them on the queue and - * repeat the same process until the newly generated or operator does not have any and operators - * in it(which means no elements will be added into the queue). when one level is finished, - * regroup the tree. Do this until the stack is empty, the result will be the expression in CNF + * This helper function is used to deal with pushing and up: generally, pop the top element out of the stack, use + * BFS to traverse the tree and push and up. It will case the expression tree to have the and as the new root and + * multiple or as the children. Push them on the queue and repeat the same process until the newly generated or + * operator does not have any and operators in it(which means no elements will be added into the queue). when one + * level is finished, regroup the tree. Do this until the stack is empty, the result will be the expression in CNF * form. * * @param stack the stack stores a list of combined data. @@ -570,7 +565,7 @@ private void pushAnd(Stack stack) { MultiAndExpression newand = new MultiAndExpression(list); parents.setChild(parents.getIndex(children), newand); for (int i = 0; i < and.size(); i++) { - Expression temp = clone.shallowCopy(children); + Expression temp = CloneHelper.shallowCopy(children); MultipleExpression mtemp = (MultipleExpression) temp; mtemp.addChild(mtemp.size(), and.getChild(i)); newand.addChild(i, mtemp); @@ -581,11 +576,10 @@ private void pushAnd(Stack stack) { } /** - * This is the final step of the CNF conversion: now we have the Expression tree that has one - * multiple and expression with a list of multiple or expression as the child. So we need to - * convert the multiple expression back to the binary counterparts. Note the converted tree is - * left inclined. Also I attach a parenthesis node before the or expression that is attached to - * the and expression to make the generated result resembles the CNF form. + * This is the final step of the CNF conversion: now we have the Expression tree that has one multiple and + * expression with a list of multiple or expression as the child. So we need to convert the multiple expression back + * to the binary counterparts. Note the converted tree is left inclined. Also I attach a parenthesis node before the + * or expression that is attached to the and expression to make the generated result resembles the CNF form. */ private void changeBack() { if (!(root instanceof MultiAndExpression)) { @@ -593,9 +587,9 @@ private void changeBack() { } MultipleExpression temp = (MultipleExpression) root; for (int i = 0; i < temp.size(); i++) { - temp.setChild(i, clone.changeBack(true, temp.getChild(i))); + temp.setChild(i, CloneHelper.changeBack(true, temp.getChild(i))); } - root = clone.changeBack(false, temp); + root = CloneHelper.changeBack(false, temp); } } diff --git a/src/main/java/net/sf/jsqlparser/util/cnfexpression/CloneHelper.java b/src/main/java/net/sf/jsqlparser/util/cnfexpression/CloneHelper.java index 8b3e3347d..0936fab66 100644 --- a/src/main/java/net/sf/jsqlparser/util/cnfexpression/CloneHelper.java +++ b/src/main/java/net/sf/jsqlparser/util/cnfexpression/CloneHelper.java @@ -18,18 +18,16 @@ import net.sf.jsqlparser.expression.operators.conditional.OrExpression; /** - * This class is mainly used for handling the cloning of an expression tree. - * Note this is the shallow copy of the tree. That means I do not modify - * or copy the expression other than these expressions: - * AND, OR, NOT, (), MULTI-AND, MULTI-OR. - * Since the CNF conversion only change the condition part of the tree. + * This class is mainly used for handling the cloning of an expression tree. Note this is the shallow copy of the tree. + * That means I do not modify or copy the expression other than these expressions: AND, OR, NOT, (), MULTI-AND, + * MULTI-OR. Since the CNF conversion only change the condition part of the tree. * * @author messfish * */ class CloneHelper { - public Expression modify(Expression express) { + public static Expression modify(Expression express) { if (express instanceof NotExpression) { return new NotExpression(modify(((NotExpression) express).getExpression())); } @@ -40,7 +38,7 @@ public Expression modify(Expression express) { } if (express instanceof AndExpression) { AndExpression and = (AndExpression) express; - List list = new ArrayList(); + List list = new ArrayList<>(); list.add(modify(and.getLeftExpression())); list.add(modify(and.getRightExpression())); MultiAndExpression result = new MultiAndExpression(list); @@ -51,7 +49,7 @@ public Expression modify(Expression express) { } if (express instanceof OrExpression) { OrExpression or = (OrExpression) express; - List list = new ArrayList(); + List list = new ArrayList<>(); list.add(modify(or.getLeftExpression())); list.add(modify(or.getRightExpression())); MultiOrExpression result = new MultiOrExpression(list); @@ -71,16 +69,16 @@ public Expression modify(Expression express) { } /** - * This method is used to copy the expression which happens at step four. I only copy the - * conditional expressions since the CNF only changes the conditional part. + * This method is used to copy the expression which happens at step four. I only copy the conditional expressions + * since the CNF only changes the conditional part. * * @param express the expression that will be copied. * @return the copied expression. */ - public Expression shallowCopy(Expression express) { + public static Expression shallowCopy(Expression express) { if (express instanceof MultipleExpression) { MultipleExpression multi = (MultipleExpression) express; - List list = new ArrayList(); + List list = new ArrayList<>(); for (int i = 0; i < multi.size(); i++) { list.add(shallowCopy(multi.getChild(i))); } @@ -95,32 +93,54 @@ public Expression shallowCopy(Expression express) { } /** - * This helper method is used to change the multiple expression into the binary form, - * respectively and return the root of the expression tree. + * This helper method is used to change the multiple expression into the binary form, respectively and return the + * root of the expression tree. * * @param isMultiOr variable tells whether the expression is or. * @param exp the expression that needs to be converted. * @return the root of the expression tree. */ - public Expression changeBack(Boolean isMultiOr, Expression exp) { + public static Expression changeBack(Boolean isMultiOr, Expression exp) { if (!(exp instanceof MultipleExpression)) { return exp; } - MultipleExpression changed = (MultipleExpression) exp; - Expression result = changed.getChild(0); - for (int i = 1; i < changed.size(); i++) { - Expression left = result; - Expression right = changed.getChild(i); - if (isMultiOr) { - result = new OrExpression(left, right); - } else { - result = new AndExpression(left, right); + + List result = ((MultipleExpression) exp).getList(); + while (result.size() > 1) { + List compressed = new ArrayList<>(); + for (int i = 0; i < result.size(); i = i + 2) { + Expression left = result.get(i); + Expression right = i + 1 < result.size() ? result.get(i + 1) : null; + + if (isMultiOr) { + compressed.add(right != null ? new OrExpression(left, right) : left); + } else { + compressed.add(right != null ? new AndExpression(left, right) : left); + } } + result = compressed; } if (isMultiOr) { - return new Parenthesis(result); + return new Parenthesis(result.get(0)); + } else { + return result.get(0); } - return result; + +// MultipleExpression changed = (MultipleExpression) exp; +// Expression result = changed.getChild(0); +// for (int i = 1; i < changed.size(); i++) { +// Expression left = result; +// Expression right = changed.getChild(i); +// if (isMultiOr) { +// result = new OrExpression(left, right); +// } else { +// result = new AndExpression(left, right); +// } +// } +// if (isMultiOr) { +// return new Parenthesis(result); +// } +// return result; } } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java index c2830f4ee..e6f425bd4 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java @@ -17,6 +17,7 @@ import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.delete.Delete; import net.sf.jsqlparser.statement.select.Join; +import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.WithItem; public class DeleteDeParser extends AbstractDeParser { @@ -60,6 +61,11 @@ public void deParse(Delete delete) { buffer.append( delete.getTables().stream().map(Table::getFullyQualifiedName).collect(joining(", ", " ", ""))); } + + if (delete.getOutputClause()!=null) { + delete.getOutputClause().appendTo(buffer); + } + if (delete.isHasFrom()) { buffer.append(" FROM"); } @@ -91,6 +97,11 @@ public void deParse(Delete delete) { new LimitDeparser(buffer).deParse(delete.getLimit()); } + if (delete.getReturningExpressionList() != null) { + buffer.append(" RETURNING ").append(PlainSelect. + getStringList(delete.getReturningExpressionList(), true, false)); + } + } public ExpressionVisitor getExpressionVisitor() { diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java index efdd29d47..0ec7c9a19 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java @@ -28,6 +28,10 @@ public void deParse(Drop drop) { buffer.append(" ").append(drop.getName()); + if (drop.getType().equals("FUNCTION")) { + buffer.append(Drop.formatFuncParams(drop.getParamsByType("FUNCTION"))); + } + if (drop.getParameters() != null && !drop.getParameters().isEmpty()) { buffer.append(" ").append(PlainSelect.getStringList(drop.getParameters())); } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/GroupByDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/GroupByDeParser.java index d39a1d934..839ae1adb 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/GroupByDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/GroupByDeParser.java @@ -51,6 +51,9 @@ public void deParse(GroupByElement groupBy) { buffer.append(" )"); } if (!groupBy.getGroupingSets().isEmpty()) { + if (buffer.charAt(buffer.length() - 1) != ' ') { + buffer.append(' '); + } buffer.append("GROUPING SETS ("); boolean first = true; for (Object o : groupBy.getGroupingSets()) { diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java index 5cb975ee6..3b2bcf3b3 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java @@ -19,7 +19,7 @@ import net.sf.jsqlparser.expression.operators.relational.NamedExpressionList; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.insert.Insert; -import net.sf.jsqlparser.statement.select.SelectExpressionItem; +import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.SelectVisitor; import net.sf.jsqlparser.statement.select.SubSelect; import net.sf.jsqlparser.statement.select.WithItem; @@ -76,14 +76,14 @@ public void deParse(Insert insert) { } buffer.append(")"); } - - if (insert.getItemsList() != null) { - insert.getItemsList().accept(this); + + if (insert.getOutputClause() != null) { + buffer.append(insert.getOutputClause().toString()); } if (insert.getSelect() != null) { buffer.append(" "); - if (insert.isUseSelectBrackets()) { + if (insert.getSelect().isUsingWithBrackets()) { buffer.append("("); } if (insert.getSelect().getWithItemsList() != null) { @@ -94,7 +94,7 @@ public void deParse(Insert insert) { buffer.append(" "); } insert.getSelect().getSelectBody().accept(selectVisitor); - if (insert.isUseSelectBrackets()) { + if (insert.getSelect().isUsingWithBrackets()) { buffer.append(")"); } } @@ -129,17 +129,19 @@ public void deParse(Insert insert) { } } - if (insert.isReturningAllColumns()) { - buffer.append(" RETURNING *"); - } else if (insert.getReturningExpressionList() != null) { - buffer.append(" RETURNING "); - for (Iterator iter = insert.getReturningExpressionList().iterator(); iter - .hasNext();) { - buffer.append(iter.next().toString()); - if (iter.hasNext()) { - buffer.append(", "); - } + //@todo: Accept some Visitors for the involved Expressions + if (insert.getConflictAction()!=null) { + buffer.append(" ON CONFLICT"); + + if (insert.getConflictTarget()!=null) { + insert.getConflictTarget().appendTo(buffer); } + insert.getConflictAction().appendTo(buffer); + } + + if (insert.getReturningExpressionList() != null) { + buffer.append(" RETURNING ").append(PlainSelect. + getStringList(insert.getReturningExpressionList(), true, false)); } } 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 055f610a4..7889a3369 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -401,10 +401,12 @@ public void deparseJoin(Join join) { buffer.append(", "); } else { + if (join.isNatural()) { + buffer.append(" NATURAL"); + } + if (join.isRight()) { buffer.append(" RIGHT"); - } else if (join.isNatural()) { - buffer.append(" NATURAL"); } else if (join.isFull()) { buffer.append(" FULL"); } else if (join.isLeft()) { 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 81412eb4a..f10651f22 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java @@ -29,12 +29,14 @@ import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.UnsupportedStatement; import net.sf.jsqlparser.statement.UseStatement; import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.AlterSession; import net.sf.jsqlparser.statement.alter.AlterSystemStatement; import net.sf.jsqlparser.statement.alter.RenameTableStatement; import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; +import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.schema.CreateSchema; @@ -142,6 +144,9 @@ public void visit(Select select) { expressionDeParser.setBuffer(buffer); selectDeParser.setExpressionVisitor(expressionDeParser); if (select.getWithItemsList() != null && !select.getWithItemsList().isEmpty()) { + if (select.isUsingWithBrackets()) { + buffer.append("( "); + } buffer.append("WITH "); for (Iterator iter = select.getWithItemsList().iterator(); iter.hasNext();) { WithItem withItem = iter.next(); @@ -153,15 +158,27 @@ public void visit(Select select) { } } select.getSelectBody().accept(selectDeParser); + if (select.isUsingWithBrackets()) { + buffer.append(" )"); + } } @Override public void visit(Truncate truncate) { - buffer.append("TRUNCATE TABLE "); + buffer.append("TRUNCATE"); + if (truncate.isTableToken()) { + buffer.append(" TABLE"); + } + if (truncate.isOnly()) { + buffer.append(" ONLY"); + } + buffer.append(" "); buffer.append(truncate.getTable()); + if (truncate.getCascade()) { - buffer.append(" CASCADE"); + buffer.append( " CASCADE"); } + } @Override @@ -175,6 +192,11 @@ public void visit(Update update) { } + public void visit(Analyze analyzer) { + buffer.append("ANALYZE "); + buffer.append(analyzer.getTable()); + } + @Override public void visit(Alter alter) { AlterDeParser alterDeParser = new AlterDeParser(buffer); @@ -373,4 +395,9 @@ public void visit(PurgeStatement purgeStatement) { public void visit(AlterSystemStatement alterSystemStatement) { alterSystemStatement.appendTo(buffer); } + + @Override + public void visit(UnsupportedStatement unsupportedStatement) { + unsupportedStatement.appendTo(buffer); + } } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java index 77d952568..da32c8f66 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java @@ -16,7 +16,7 @@ import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.OrderByVisitor; -import net.sf.jsqlparser.statement.select.SelectExpressionItem; +import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.WithItem; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.update.UpdateSet; @@ -103,6 +103,11 @@ public void deParse(Update update) { j++; } + + if (update.getOutputClause()!=null) { + update.getOutputClause().appendTo(buffer); + } + if (update.getFromItem() != null) { buffer.append(" FROM ").append(update.getFromItem()); if (update.getJoins() != null) { @@ -127,17 +132,9 @@ public void deParse(Update update) { new LimitDeparser(buffer).deParse(update.getLimit()); } - if (update.isReturningAllColumns()) { - buffer.append(" RETURNING *"); - } else if (update.getReturningExpressionList() != null) { - buffer.append(" RETURNING "); - for (Iterator iter = update.getReturningExpressionList().iterator(); iter - .hasNext();) { - buffer.append(iter.next().toString()); - if (iter.hasNext()) { - buffer.append(", "); - } - } + if (update.getReturningExpressionList() != null) { + buffer.append(" RETURNING ").append(PlainSelect. + getStringList(update.getReturningExpressionList(), true, false)); } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/H2Version.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/H2Version.java index a5ff52606..9cd733acf 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/H2Version.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/H2Version.java @@ -100,6 +100,7 @@ public enum H2Version implements Version { // http://h2database.com/html/commands.html#insert Feature.insert, Feature.insertValues, + Feature.values, Feature.insertFromSelect, // http://h2database.com/html/commands.html#update Feature.update, @@ -136,7 +137,8 @@ public enum H2Version implements Version { // http://www.h2database.com/html/commands.html#grant_role Feature.grant, // http://h2database.com/html/commands.html#commit - Feature.commit)); + Feature.commit + )); private Set features; private String versionString; diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java index b241ffde5..6a1cba1a4 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java @@ -60,7 +60,7 @@ public enum MariaDbVersion implements Version { Feature.withItem, Feature.withItemRecursive, // https://mariadb.com/kb/en/insert/ - Feature.insert, Feature.insertValues, + Feature.insert, Feature.insertValues, Feature.values, Feature.insertFromSelect, Feature.insertModifierPriority, Feature.insertModifierIgnore, Feature.insertUseSet, Feature.insertUseDuplicateKeyUpdate, Feature.insertReturningExpressionList, diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java index cf30655d4..c13abaa3e 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java @@ -54,6 +54,7 @@ public enum MySqlVersion implements Version { // https://dev.mysql.com/doc/refman/8.0/en/insert.html Feature.insert, Feature.insertValues, + Feature.values, Feature.insertFromSelect, Feature.insertUseSet, Feature.insertModifierPriority, Feature.insertModifierIgnore, Feature.insertUseDuplicateKeyUpdate, diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java index 62fe19bb6..3c970bf65 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java @@ -88,6 +88,7 @@ public enum OracleVersion implements Version { // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/INSERT.html Feature.insert, Feature.insertValues, + Feature.values, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/INSERT.html // see "single_table_insert" Feature.insertFromSelect, diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java index 3f439bd41..32e62cca7 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java @@ -109,6 +109,7 @@ public enum PostgresqlVersion implements Version { // https://www.postgresql.org/docs/current/sql-insert.html Feature.insert, Feature.insertValues, + Feature.values, Feature.insertFromSelect, Feature.insertReturningAll, Feature.insertReturningExpressionList, // https://www.postgresql.org/docs/current/sql-update.html diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/SQLVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/SQLVersion.java index b6eda6455..3b8266c83 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/SQLVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/SQLVersion.java @@ -29,10 +29,13 @@ public enum SQLVersion implements Version { Feature.jdbcParameter, Feature.jdbcNamedParameter, // common features + Feature.setOperation, Feature.select, Feature.selectGroupBy, Feature.function, Feature.insert, + Feature.insertFromSelect, Feature.insertValues, + Feature.values, Feature.update, Feature.delete, Feature.truncate, diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java new file mode 100644 index 000000000..82864b7ff --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java @@ -0,0 +1,25 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.validation.validator; + +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.analyze.Analyze; +import net.sf.jsqlparser.util.validation.ValidationCapability; +import net.sf.jsqlparser.util.validation.metadata.NamedObject; + +public class AnalyzeValidator extends AbstractValidator{ + @Override + public void validate(Analyze analyze) { + for (ValidationCapability c : getCapabilities()) { + validateFeature(c, Feature.analyze); + validateName(c, NamedObject.table, analyze.getTable().getFullyQualifiedName(), true); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/DeleteValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/DeleteValidator.java index 5188cefd9..7e07eb546 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/DeleteValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/DeleteValidator.java @@ -28,6 +28,7 @@ public void validate(Delete delete) { validateOptionalFeature(c, delete.getJoins(), Feature.deleteJoin); validateOptionalFeature(c, delete.getLimit(), Feature.deleteLimit); validateOptionalFeature(c, delete.getOrderByElements(), Feature.deleteOrderBy); + validateOptionalFeature(c, delete.getReturningExpressionList(), Feature.insertReturningExpressionList); } SelectValidator v = getValidator(SelectValidator.class); @@ -46,6 +47,10 @@ public void validate(Delete delete) { getValidator(LimitValidator.class).validate(delete.getLimit()); } + if (isNotEmpty(delete.getReturningExpressionList())) { + delete.getReturningExpressionList().forEach(c -> c .accept(v)); + } + } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java index fd994af55..59fb966fe 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java @@ -29,7 +29,6 @@ public void validate(Insert insert) { validateOptionalFeature(c, insert.getSelect(), Feature.insertFromSelect); validateFeature(c, insert.isUseSet(), Feature.insertUseSet); validateFeature(c, insert.isUseDuplicate(), Feature.insertUseDuplicateKeyUpdate); - validateFeature(c, insert.isReturningAllColumns(), Feature.insertReturningAll); validateOptionalFeature(c, insert.getReturningExpressionList(), Feature.insertReturningExpressionList); } @@ -60,8 +59,8 @@ public void validate(Insert insert) { } if (isNotEmpty(insert.getReturningExpressionList())) { - ExpressionValidator v = getValidator(ExpressionValidator.class); - insert.getReturningExpressionList().forEach(c -> c.getExpression().accept(v)); + SelectValidator v = getValidator(SelectValidator.class); + insert.getReturningExpressionList().forEach(c -> c .accept(v)); } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java index 921aad626..f6cfc121d 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java @@ -27,12 +27,14 @@ import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.UnsupportedStatement; import net.sf.jsqlparser.statement.UseStatement; import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.AlterSession; import net.sf.jsqlparser.statement.alter.AlterSystemStatement; import net.sf.jsqlparser.statement.alter.RenameTableStatement; import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; +import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; import net.sf.jsqlparser.statement.create.function.CreateFunction; import net.sf.jsqlparser.statement.create.index.CreateIndex; @@ -267,6 +269,11 @@ public void visit(CreateSynonym createSynonym) { getValidator(CreateSynonymValidator.class).validate(createSynonym); } + @Override + public void visit(Analyze analyze) { + getValidator(AnalyzeValidator.class).validate(analyze); + } + @Override public void visit(SavepointStatement savepointStatement) { //TODO: not yet implemented @@ -303,4 +310,9 @@ public void visit(PurgeStatement purgeStatement) { public void visit(AlterSystemStatement alterSystemStatement) { //TODO: not yet implemented } + + @Override + public void visit(UnsupportedStatement unsupportedStatement) { + + } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/UpdateValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/UpdateValidator.java index 6bfb3264a..491168243 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/UpdateValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/UpdateValidator.java @@ -9,10 +9,7 @@ */ package net.sf.jsqlparser.util.validation.validator; -import java.util.stream.Collectors; - import net.sf.jsqlparser.parser.feature.Feature; -import net.sf.jsqlparser.statement.select.SelectExpressionItem; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.util.validation.ValidationCapability; @@ -31,9 +28,7 @@ public void validate(Update update) { validateFeature(c, update.isUseSelect(), Feature.updateUseSelect); validateOptionalFeature(c, update.getOrderByElements(), Feature.updateOrderBy); validateOptionalFeature(c, update.getLimit(), Feature.updateLimit); - if (isNotEmpty(update.getReturningExpressionList()) || update.isReturningAllColumns()) { - validateFeature(c, Feature.updateReturning); - } + validateOptionalFeature(c, update.getReturningExpressionList(), Feature.updateReturning); } validateOptionalFromItem(update.getTable()); @@ -62,9 +57,9 @@ public void validate(Update update) { getValidator(LimitValidator.class).validate(update.getLimit()); } - if (update.getReturningExpressionList() != null) { - validateOptionalExpressions(update.getReturningExpressionList().stream() - .map(SelectExpressionItem::getExpression).collect(Collectors.toList())); + if (isNotEmpty(update.getReturningExpressionList())) { + SelectValidator v = getValidator(SelectValidator.class); + update.getReturningExpressionList().forEach(c -> c.accept(v)); } } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 1f26d4068..16cdb9469 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -18,12 +18,13 @@ options { // FORCE_LA_CHECK = true; UNICODE_INPUT = true; JAVA_TEMPLATE_TYPE = "modern"; - JDK_VERSION = "1.7"; + JDK_VERSION = "1.8"; TOKEN_EXTENDS = "BaseToken"; COMMON_TOKEN_ACTION = true; NODE_DEFAULT_VOID = true; TRACK_TOKENS = true; VISITOR = true; + GRAMMAR_ENCODING = "UTF-8"; } PARSER_BEGIN(CCJSqlParser) @@ -37,6 +38,7 @@ import net.sf.jsqlparser.expression.operators.conditional.*; import net.sf.jsqlparser.expression.operators.relational.*; import net.sf.jsqlparser.schema.*; import net.sf.jsqlparser.statement.*; +import net.sf.jsqlparser.statement.analyze.*; import net.sf.jsqlparser.statement.alter.*; import net.sf.jsqlparser.statement.alter.sequence.*; import net.sf.jsqlparser.statement.comment.*; @@ -69,7 +71,8 @@ import java.util.*; public class CCJSqlParser extends AbstractJSqlParser { public int bracketsCounter = 0; public int caseCounter = 0; - + public boolean interrupted = false; + public CCJSqlParser withConfiguration(FeatureConfiguration configuration) { token_source.configuration = configuration; return this; @@ -168,6 +171,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -314,6 +318,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | "> | | +| | | | @@ -479,7 +484,7 @@ TOKEN: | <#LETTER: ["$","A"-"Z","_","#","a"-"z","\u00a2"-"\u00a5","\u00aa","\u00b5","\u00ba","\u00c0"-"\u00d6","\u00d8"-"\u00f6","\u00f8"-"\u021f","\u0222"-"\u0233","\u0250"-"\u02ad","\u02b0"-"\u02b8","\u02bb"-"\u02c1","\u02d0"-"\u02d1","\u02e0"-"\u02e4","\u02ee","\u037a","\u0386","\u0388"-"\u038a","\u038c","\u038e"-"\u03a1","\u03a3"-"\u03ce","\u03d0"-"\u03d7","\u03da"-"\u03f3","\u0400"-"\u0481","\u048c"-"\u04c4","\u04c7"-"\u04c8","\u04cb"-"\u04cc","\u04d0"-"\u04f5","\u04f8"-"\u04f9","\u0531"-"\u0556","\u0559","\u0561"-"\u0587","\u05d0"-"\u05ea","\u05f0"-"\u05f2","\u0621"-"\u063a","\u0640"-"\u064a","\u0671"-"\u06d3","\u06d5","\u06e5"-"\u06e6","\u06fa"-"\u06fc","\u0710","\u0712"-"\u072c","\u0780"-"\u07a5","\u0905"-"\u0939","\u093d","\u0950","\u0958"-"\u0961","\u0985"-"\u098c","\u098f"-"\u0990","\u0993"-"\u09a8","\u09aa"-"\u09b0","\u09b2","\u09b6"-"\u09b9","\u09dc"-"\u09dd","\u09df"-"\u09e1","\u09f0"-"\u09f3","\u0a05"-"\u0a0a","\u0a0f"-"\u0a10","\u0a13"-"\u0a28","\u0a2a"-"\u0a30","\u0a32"-"\u0a33","\u0a35"-"\u0a36","\u0a38"-"\u0a39","\u0a59"-"\u0a5c","\u0a5e","\u0a72"-"\u0a74","\u0a85"-"\u0a8b","\u0a8d","\u0a8f"-"\u0a91","\u0a93"-"\u0aa8","\u0aaa"-"\u0ab0","\u0ab2"-"\u0ab3","\u0ab5"-"\u0ab9","\u0abd","\u0ad0","\u0ae0","\u0b05"-"\u0b0c","\u0b0f"-"\u0b10","\u0b13"-"\u0b28","\u0b2a"-"\u0b30","\u0b32"-"\u0b33","\u0b36"-"\u0b39","\u0b3d","\u0b5c"-"\u0b5d","\u0b5f"-"\u0b61","\u0b85"-"\u0b8a","\u0b8e"-"\u0b90","\u0b92"-"\u0b95","\u0b99"-"\u0b9a","\u0b9c","\u0b9e"-"\u0b9f","\u0ba3"-"\u0ba4","\u0ba8"-"\u0baa","\u0bae"-"\u0bb5","\u0bb7"-"\u0bb9","\u0c05"-"\u0c0c","\u0c0e"-"\u0c10","\u0c12"-"\u0c28","\u0c2a"-"\u0c33","\u0c35"-"\u0c39","\u0c60"-"\u0c61","\u0c85"-"\u0c8c","\u0c8e"-"\u0c90","\u0c92"-"\u0ca8","\u0caa"-"\u0cb3","\u0cb5"-"\u0cb9","\u0cde","\u0ce0"-"\u0ce1","\u0d05"-"\u0d0c","\u0d0e"-"\u0d10","\u0d12"-"\u0d28","\u0d2a"-"\u0d39","\u0d60"-"\u0d61","\u0d85"-"\u0d96","\u0d9a"-"\u0db1","\u0db3"-"\u0dbb","\u0dbd","\u0dc0"-"\u0dc6","\u0e01"-"\u0e30","\u0e32"-"\u0e33","\u0e3f"-"\u0e46","\u0e81"-"\u0e82","\u0e84","\u0e87"-"\u0e88","\u0e8a","\u0e8d","\u0e94"-"\u0e97","\u0e99"-"\u0e9f","\u0ea1"-"\u0ea3","\u0ea5","\u0ea7","\u0eaa"-"\u0eab","\u0ead"-"\u0eb0","\u0eb2"-"\u0eb3","\u0ebd","\u0ec0"-"\u0ec4","\u0ec6","\u0edc"-"\u0edd","\u0f00","\u0f40"-"\u0f47","\u0f49"-"\u0f6a","\u0f88"-"\u0f8b","\u1000"-"\u1021","\u1023"-"\u1027","\u1029"-"\u102a","\u1050"-"\u1055","\u10a0"-"\u10c5","\u10d0"-"\u10f6","\u1100"-"\u1159","\u115f"-"\u11a2","\u11a8"-"\u11f9","\u1200"-"\u1206","\u1208"-"\u1246","\u1248","\u124a"-"\u124d","\u1250"-"\u1256","\u1258","\u125a"-"\u125d","\u1260"-"\u1286","\u1288","\u128a"-"\u128d","\u1290"-"\u12ae","\u12b0","\u12b2"-"\u12b5","\u12b8"-"\u12be","\u12c0","\u12c2"-"\u12c5","\u12c8"-"\u12ce","\u12d0"-"\u12d6","\u12d8"-"\u12ee","\u12f0"-"\u130e","\u1310","\u1312"-"\u1315","\u1318"-"\u131e","\u1320"-"\u1346","\u1348"-"\u135a","\u13a0"-"\u13f4","\u1401"-"\u166c","\u166f"-"\u1676","\u1681"-"\u169a","\u16a0"-"\u16ea","\u1780"-"\u17b3","\u17db","\u1820"-"\u1877","\u1880"-"\u18a8","\u1e00"-"\u1e9b","\u1ea0"-"\u1ef9","\u1f00"-"\u1f15","\u1f18"-"\u1f1d","\u1f20"-"\u1f45","\u1f48"-"\u1f4d","\u1f50"-"\u1f57","\u1f59","\u1f5b","\u1f5d","\u1f5f"-"\u1f7d","\u1f80"-"\u1fb4","\u1fb6"-"\u1fbc","\u1fbe","\u1fc2"-"\u1fc4","\u1fc6"-"\u1fcc","\u1fd0"-"\u1fd3","\u1fd6"-"\u1fdb","\u1fe0"-"\u1fec","\u1ff2"-"\u1ff4","\u1ff6"-"\u1ffc","\u203f"-"\u2040","\u207f","\u20a0"-"\u20af","\u2102","\u2107","\u210a"-"\u2113","\u2115","\u2119"-"\u211d","\u2124","\u2126","\u2128","\u212a"-"\u212d","\u212f"-"\u2131","\u2133"-"\u2139","\u2160"-"\u2183","\u3005"-"\u3007","\u3021"-"\u3029","\u3031"-"\u3035","\u3038"-"\u303a","\u3041"-"\u3094","\u309d"-"\u309e","\u30a1"-"\u30fe","\u3105"-"\u312c","\u3131"-"\u318e","\u31a0"-"\u31b7","\u3400"-"\u4db5","\u4e00"-"\u9fa5","\ua000"-"\ua48c","\uac00"-"\ud7a3","\uf900"-"\ufa2d","\ufb00"-"\ufb06","\ufb13"-"\ufb17","\ufb1d","\ufb1f"-"\ufb28","\ufb2a"-"\ufb36","\ufb38"-"\ufb3c","\ufb3e","\ufb40"-"\ufb41","\ufb43"-"\ufb44","\ufb46"-"\ufbb1","\ufbd3"-"\ufd3d","\ufd50"-"\ufd8f","\ufd92"-"\ufdc7","\ufdf0"-"\ufdfb","\ufe33"-"\ufe34","\ufe4d"-"\ufe4f","\ufe69","\ufe70"-"\ufe72","\ufe74","\ufe76"-"\ufefc","\uff04","\uff21"-"\uff3a","\uff3f","\uff41"-"\uff5a","\uff65"-"\uffbe","\uffc2"-"\uffc7","\uffca"-"\uffcf","\uffd2"-"\uffd7","\uffda"-"\uffdc","\uffe0"-"\uffe1","\uffe5"-"\uffe6"]> | <#PART_LETTER: ["\u0000"-"\b","\u000e"-"\u001b","$","#","@","0"-"9","A"-"Z","_","a"-"z","\u007f"-"\u009f","\u00a2"-"\u00a5","\u00aa","\u00b5","\u00ba","\u00c0"-"\u00d6","\u00d8"-"\u00f6","\u00f8"-"\u021f","\u0222"-"\u0233","\u0250"-"\u02ad","\u02b0"-"\u02b8","\u02bb"-"\u02c1","\u02d0"-"\u02d1","\u02e0"-"\u02e4","\u02ee","\u0300"-"\u034e","\u0360"-"\u0362","\u037a","\u0386","\u0388"-"\u038a","\u038c","\u038e"-"\u03a1","\u03a3"-"\u03ce","\u03d0"-"\u03d7","\u03da"-"\u03f3","\u0400"-"\u0481","\u0483"-"\u0486","\u048c"-"\u04c4","\u04c7"-"\u04c8","\u04cb"-"\u04cc","\u04d0"-"\u04f5","\u04f8"-"\u04f9","\u0531"-"\u0556","\u0559","\u0561"-"\u0587","\u0591"-"\u05a1","\u05a3"-"\u05b9","\u05bb"-"\u05bd","\u05bf","\u05c1"-"\u05c2","\u05c4","\u05d0"-"\u05ea","\u05f0"-"\u05f2","\u0621"-"\u063a","\u0640"-"\u0655","\u0660"-"\u0669","\u0670"-"\u06d3","\u06d5"-"\u06dc","\u06df"-"\u06e8","\u06ea"-"\u06ed","\u06f0"-"\u06fc","\u070f"-"\u072c","\u0730"-"\u074a","\u0780"-"\u07b0","\u0901"-"\u0903","\u0905"-"\u0939","\u093c"-"\u094d","\u0950"-"\u0954","\u0958"-"\u0963","\u0966"-"\u096f","\u0981"-"\u0983","\u0985"-"\u098c","\u098f"-"\u0990","\u0993"-"\u09a8","\u09aa"-"\u09b0","\u09b2","\u09b6"-"\u09b9","\u09bc","\u09be"-"\u09c4","\u09c7"-"\u09c8","\u09cb"-"\u09cd","\u09d7","\u09dc"-"\u09dd","\u09df"-"\u09e3","\u09e6"-"\u09f3","\u0a02","\u0a05"-"\u0a0a","\u0a0f"-"\u0a10","\u0a13"-"\u0a28","\u0a2a"-"\u0a30","\u0a32"-"\u0a33","\u0a35"-"\u0a36","\u0a38"-"\u0a39","\u0a3c","\u0a3e"-"\u0a42","\u0a47"-"\u0a48","\u0a4b"-"\u0a4d","\u0a59"-"\u0a5c","\u0a5e","\u0a66"-"\u0a74","\u0a81"-"\u0a83","\u0a85"-"\u0a8b","\u0a8d","\u0a8f"-"\u0a91","\u0a93"-"\u0aa8","\u0aaa"-"\u0ab0","\u0ab2"-"\u0ab3","\u0ab5"-"\u0ab9","\u0abc"-"\u0ac5","\u0ac7"-"\u0ac9","\u0acb"-"\u0acd","\u0ad0","\u0ae0","\u0ae6"-"\u0aef","\u0b01"-"\u0b03","\u0b05"-"\u0b0c","\u0b0f"-"\u0b10","\u0b13"-"\u0b28","\u0b2a"-"\u0b30","\u0b32"-"\u0b33","\u0b36"-"\u0b39","\u0b3c"-"\u0b43","\u0b47"-"\u0b48","\u0b4b"-"\u0b4d","\u0b56"-"\u0b57","\u0b5c"-"\u0b5d","\u0b5f"-"\u0b61","\u0b66"-"\u0b6f","\u0b82"-"\u0b83","\u0b85"-"\u0b8a","\u0b8e"-"\u0b90","\u0b92"-"\u0b95","\u0b99"-"\u0b9a","\u0b9c","\u0b9e"-"\u0b9f","\u0ba3"-"\u0ba4","\u0ba8"-"\u0baa","\u0bae"-"\u0bb5","\u0bb7"-"\u0bb9","\u0bbe"-"\u0bc2","\u0bc6"-"\u0bc8","\u0bca"-"\u0bcd","\u0bd7","\u0be7"-"\u0bef","\u0c01"-"\u0c03","\u0c05"-"\u0c0c","\u0c0e"-"\u0c10","\u0c12"-"\u0c28","\u0c2a"-"\u0c33","\u0c35"-"\u0c39","\u0c3e"-"\u0c44","\u0c46"-"\u0c48","\u0c4a"-"\u0c4d","\u0c55"-"\u0c56","\u0c60"-"\u0c61","\u0c66"-"\u0c6f","\u0c82"-"\u0c83","\u0c85"-"\u0c8c","\u0c8e"-"\u0c90","\u0c92"-"\u0ca8","\u0caa"-"\u0cb3","\u0cb5"-"\u0cb9","\u0cbe"-"\u0cc4","\u0cc6"-"\u0cc8","\u0cca"-"\u0ccd","\u0cd5"-"\u0cd6","\u0cde","\u0ce0"-"\u0ce1","\u0ce6"-"\u0cef","\u0d02"-"\u0d03","\u0d05"-"\u0d0c","\u0d0e"-"\u0d10","\u0d12"-"\u0d28","\u0d2a"-"\u0d39","\u0d3e"-"\u0d43","\u0d46"-"\u0d48","\u0d4a"-"\u0d4d","\u0d57","\u0d60"-"\u0d61","\u0d66"-"\u0d6f","\u0d82"-"\u0d83","\u0d85"-"\u0d96","\u0d9a"-"\u0db1","\u0db3"-"\u0dbb","\u0dbd","\u0dc0"-"\u0dc6","\u0dca","\u0dcf"-"\u0dd4","\u0dd6","\u0dd8"-"\u0ddf","\u0df2"-"\u0df3","\u0e01"-"\u0e3a","\u0e3f"-"\u0e4e","\u0e50"-"\u0e59","\u0e81"-"\u0e82","\u0e84","\u0e87"-"\u0e88","\u0e8a","\u0e8d","\u0e94"-"\u0e97","\u0e99"-"\u0e9f","\u0ea1"-"\u0ea3","\u0ea5","\u0ea7","\u0eaa"-"\u0eab","\u0ead"-"\u0eb9","\u0ebb"-"\u0ebd","\u0ec0"-"\u0ec4","\u0ec6","\u0ec8"-"\u0ecd","\u0ed0"-"\u0ed9","\u0edc"-"\u0edd","\u0f00","\u0f18"-"\u0f19","\u0f20"-"\u0f29","\u0f35","\u0f37","\u0f39","\u0f3e"-"\u0f47","\u0f49"-"\u0f6a","\u0f71"-"\u0f84","\u0f86"-"\u0f8b","\u0f90"-"\u0f97","\u0f99"-"\u0fbc","\u0fc6","\u1000"-"\u1021","\u1023"-"\u1027","\u1029"-"\u102a","\u102c"-"\u1032","\u1036"-"\u1039","\u1040"-"\u1049","\u1050"-"\u1059","\u10a0"-"\u10c5","\u10d0"-"\u10f6","\u1100"-"\u1159","\u115f"-"\u11a2","\u11a8"-"\u11f9","\u1200"-"\u1206","\u1208"-"\u1246","\u1248","\u124a"-"\u124d","\u1250"-"\u1256","\u1258","\u125a"-"\u125d","\u1260"-"\u1286","\u1288","\u128a"-"\u128d","\u1290"-"\u12ae","\u12b0","\u12b2"-"\u12b5","\u12b8"-"\u12be","\u12c0","\u12c2"-"\u12c5","\u12c8"-"\u12ce","\u12d0"-"\u12d6","\u12d8"-"\u12ee","\u12f0"-"\u130e","\u1310","\u1312"-"\u1315","\u1318"-"\u131e","\u1320"-"\u1346","\u1348"-"\u135a","\u1369"-"\u1371","\u13a0"-"\u13f4","\u1401"-"\u166c","\u166f"-"\u1676","\u1681"-"\u169a","\u16a0"-"\u16ea","\u1780"-"\u17d3","\u17db","\u17e0"-"\u17e9","\u180b"-"\u180e","\u1810"-"\u1819","\u1820"-"\u1877","\u1880"-"\u18a9","\u1e00"-"\u1e9b","\u1ea0"-"\u1ef9","\u1f00"-"\u1f15","\u1f18"-"\u1f1d","\u1f20"-"\u1f45","\u1f48"-"\u1f4d","\u1f50"-"\u1f57","\u1f59","\u1f5b","\u1f5d","\u1f5f"-"\u1f7d","\u1f80"-"\u1fb4","\u1fb6"-"\u1fbc","\u1fbe","\u1fc2"-"\u1fc4","\u1fc6"-"\u1fcc","\u1fd0"-"\u1fd3","\u1fd6"-"\u1fdb","\u1fe0"-"\u1fec","\u1ff2"-"\u1ff4","\u1ff6"-"\u1ffc","\u200c"-"\u200f","\u202a"-"\u202e","\u203f"-"\u2040","\u206a"-"\u206f","\u207f","\u20a0"-"\u20af","\u20d0"-"\u20dc","\u20e1","\u2102","\u2107","\u210a"-"\u2113","\u2115","\u2119"-"\u211d","\u2124","\u2126","\u2128","\u212a"-"\u212d","\u212f"-"\u2131","\u2133"-"\u2139","\u2160"-"\u2183","\u3005"-"\u3007","\u3021"-"\u302f","\u3031"-"\u3035","\u3038"-"\u303a","\u3041"-"\u3094","\u3099"-"\u309a","\u309d"-"\u309e","\u30a1"-"\u30fe","\u3105"-"\u312c","\u3131"-"\u318e","\u31a0"-"\u31b7","\u3400"-"\u4db5","\u4e00"-"\u9fa5","\ua000"-"\ua48c","\uac00"-"\ud7a3","\uf900"-"\ufa2d","\ufb00"-"\ufb06","\ufb13"-"\ufb17","\ufb1d"-"\ufb28","\ufb2a"-"\ufb36","\ufb38"-"\ufb3c","\ufb3e","\ufb40"-"\ufb41","\ufb43"-"\ufb44","\ufb46"-"\ufbb1","\ufbd3"-"\ufd3d","\ufd50"-"\ufd8f","\ufd92"-"\ufdc7","\ufdf0"-"\ufdfb","\ufe20"-"\ufe23","\ufe33"-"\ufe34","\ufe4d"-"\ufe4f","\ufe69","\ufe70"-"\ufe72","\ufe74","\ufe76"-"\ufefc","\ufeff","\uff04","\uff10"-"\uff19","\uff21"-"\uff3a","\uff3f","\uff41"-"\uff5a","\uff65"-"\uffbe","\uffc2"-"\uffc7","\uffca"-"\uffcf","\uffd2"-"\uffd7","\uffda"-"\uffdc","\uffe0"-"\uffe1","\uffe5"-"\uffe6","\ufff9"-"\ufffb"]> | < S_CHAR_LITERAL: (["U","E","N","R","B"]|"RB"|"_utf8")? (("'" ( | ~["'", "\\", "\n", "\r"] )* "'") | ("'" ("''" | ~["'"])* "'")) > -| < S_QUOTED_IDENTIFIER: "\"" (~["\n","\r","\""])* "\"" | "$$" (~["\n","\r","\""])* "$$" | ("`" (~["\n","\r","`"])+ "`") | ( "[" ~["0"-"9","]"] (~["\n","\r","]"])* "]" ) > +| < S_QUOTED_IDENTIFIER: "\"" (~["\n","\r","\""])* "\"" | "$$" (~["\n","\r","\""])* "$$" | ("`" (~["\n","\r","`"])+ "`") | ( "[" (~["\n","\r","]"])* "]" ) > { if ( !configuration.getAsBoolean(Feature.allowSquareBracketQuotation) && matchedToken.image.charAt(0) == '[' ) { matchedToken.image = "["; for (int i=0;i { ifElseStatement.setUsingSemicolonForIfStatement(true); } ] [ LOOKAHEAD(2) - stm2 = SingleStatement() { ifElseStatement.setElseStatement(stm2); } + stm2 = SingleStatement() { ifElseStatement.setElseStatement(stm2); } [ { ifElseStatement.setUsingSemicolonForElseStatement(true); }] ] @@ -518,6 +523,8 @@ Statement Statement() #Statement: [ ] ) + | + LOOKAHEAD( { getAsBoolean(Feature.allowUnsupportedStatements) } ) stm = UnsupportedStatement() } catch (ParseException e) { if (errorRecovery) { parseErrors.add(e); @@ -540,8 +547,29 @@ Statement SingleStatement() : { try { ( + LOOKAHEAD(2) ( + "(" with=WithList() + ( + stm = Select( with ) { ( (Select) stm).setUsingWithBrackets(true); } + + /* @todo: unsure, if we need to implement those + since DMLs in brackets do not really make any sense + | + stm = Insert( with ) { ( (Insert) stm).setUsingWithBrackets(true); } + | + stm = Update( with ) { ( (Update) stm).setUsingWithBrackets(true); } + | + stm = Delete( with ) { ( (Delete) stm).setUsingWithBrackets(true); } + | + stm = Merge( with ) { ( (Merge) stm).setUsingWithBrackets(true); } + + */ + ) + ")" + ) + | ( - [ with=WithList() { } ] + [ with=WithList() ] ( stm = Select( with ) | @@ -554,86 +582,88 @@ Statement SingleStatement() : stm = Merge( with) ) ) - | - stm = Upsert() - | - LOOKAHEAD(3) - stm = Replace() - | - LOOKAHEAD(2) - stm = AlterTable() - | - LOOKAHEAD(2) - stm = AlterSession() - | - LOOKAHEAD(CreateFunctionStatement()) - stm = CreateFunctionStatement() - | - LOOKAHEAD(CreateIndex()) - stm = CreateIndex() - | - LOOKAHEAD(CreateSchema()) - stm = CreateSchema() - | - LOOKAHEAD(CreateSequence()) - stm = CreateSequence() - | - LOOKAHEAD(CreateSynonym()) - stm = CreateSynonym() - | - LOOKAHEAD(CreateTable()) - stm = CreateTable() - | - LOOKAHEAD(CreateView()) - stm = CreateView() - | - LOOKAHEAD(AlterView()) - stm = AlterView() - | - LOOKAHEAD(AlterSequence()) - stm = AlterSequence() - | - stm = Drop() - | - stm = Truncate() - | - stm = Execute() - | - stm = Set() - | - stm = RenameTableStatement() - | - stm = Reset() - | - LOOKAHEAD(ShowColumns()) - stm = ShowColumns() - | - LOOKAHEAD(ShowTables()) - stm = ShowTables() - | - stm = Show() - | - stm = Use() - | - stm = SavepointStatement() - | - stm = RollbackStatement() - | - stm = Commit() - | - stm = Comment() - | - stm = Describe() - | - stm = Explain() - | - stm = Declare() - | - stm = Grant() - | - stm = PurgeStatement() - | - stm = AlterSystemStatement() + | + stm = Upsert() + | + LOOKAHEAD(3) + stm = Replace() + | + LOOKAHEAD(2) + stm = AlterTable() + | + LOOKAHEAD(2) + stm = AlterSession() + | + LOOKAHEAD(CreateFunctionStatement()) + stm = CreateFunctionStatement() + | + LOOKAHEAD(CreateIndex()) + stm = CreateIndex() + | + LOOKAHEAD(CreateSchema()) + stm = CreateSchema() + | + LOOKAHEAD(CreateSequence()) + stm = CreateSequence() + | + LOOKAHEAD(CreateSynonym()) + stm = CreateSynonym() + | + LOOKAHEAD(CreateTable()) + stm = CreateTable() + | + LOOKAHEAD(CreateView()) + stm = CreateView() + | + LOOKAHEAD(AlterView()) + stm = AlterView() + | + LOOKAHEAD(AlterSequence()) + stm = AlterSequence() + | + stm = Drop() + | + stm = Analyze() + | + stm = Truncate() + | + stm = Execute() + | + stm = Set() + | + stm = RenameTableStatement() + | + stm = Reset() + | + LOOKAHEAD(ShowColumns()) + stm = ShowColumns() + | + LOOKAHEAD(ShowTables()) + stm = ShowTables() + | + stm = Show() + | + stm = Use() + | + stm = SavepointStatement() + | + stm = RollbackStatement() + | + stm = Commit() + | + stm = Comment() + | + stm = Describe() + | + stm = Explain() + | + stm = Declare() + | + stm = Grant() + | + stm = PurgeStatement() + | + stm = AlterSystemStatement() ) { return stm; } } catch (ParseException e) { @@ -654,10 +684,27 @@ Block Block() #Block : { } { -()* + ()* try { - (stm = SingleStatement() | stm = Block()) { list.add(stm); } - ( (stm = SingleStatement() | stm = Block()) { list.add(stm); } )* + ( + ( + stm = SingleStatement() + | stm = Block() + ) + + { list.add(stm); } + ) + + ( + ( + ( + stm = SingleStatement() + | stm = Block() + ) + + { list.add(stm); } + ) + )* } catch (ParseException e) { if (errorRecovery) { parseErrors.add(e); @@ -666,11 +713,13 @@ Block Block() #Block : { throw e; } } + { stmts.setStatements(list); block.setStatements(stmts); } - + + [LOOKAHEAD(2) ] { return block; } @@ -706,33 +755,40 @@ Statements Statements() #Statements : { [ LOOKAHEAD(2) ] ) { list.add(stm); } + | + LOOKAHEAD( { getAsBoolean(Feature.allowUnsupportedStatements) } ) stm = UnsupportedStatement() + { if ( !((UnsupportedStatement) stm).isEmpty() ) list.add(stm); } ) - - ( - { if (stm2!=null) - ifElseStatement.setUsingSemicolonForElseStatement(true); - else if (ifElseStatement!=null) - ifElseStatement.setUsingSemicolonForIfStatement(true); } + ( + { if (stm2!=null) + ifElseStatement.setUsingSemicolonForElseStatement(true); + else if (ifElseStatement!=null) + ifElseStatement.setUsingSemicolonForIfStatement(true); } [ ( - condition=Condition() + condition=Condition() stm = SingleStatement() { ifElseStatement = new IfElseStatement(condition, stm); } - [ LOOKAHEAD(2) + [ LOOKAHEAD(2) [ { ifElseStatement.setUsingSemicolonForIfStatement(true); } ] - stm2 = SingleStatement() { ifElseStatement.setElseStatement(stm2); } + stm2 = SingleStatement() { ifElseStatement.setElseStatement(stm2); } ] { list.add( ifElseStatement ); } ) | ( - stm = SingleStatement() + stm = SingleStatement() | stm = Block() [ LOOKAHEAD(2) ] ) { list.add(stm); } - ] + | + // For any reason, we can't LOOKAHEAD( { getAsBoolean(Feature.allowUnsupportedStatements) } ) here + // As it will result in a Stack Overflow + stm = UnsupportedStatement() + { if ( !((UnsupportedStatement) stm).isEmpty() ) list.add(stm); } + ] )* } catch (ParseException e) { @@ -1106,7 +1162,7 @@ ShowStatement Show(): { ValuesStatement Values(): { ItemsList itemsList; } { - + ( | ) itemsList = SimpleExpressionList(false) @@ -1132,10 +1188,12 @@ Update Update( List with ): Limit limit = null; List orderByElements; boolean useColumnsBrackets = false; - List returning = null; + List returning = null; Token tk = null; UpdateModifierPriority modifierPriority = null; boolean modifierIgnore = false; + + OutputClause outputClause = null; } { { update.setOracleHint(getOracleHint()); } @@ -1186,21 +1244,19 @@ Update Update( List with ): ) ) - [ + [ outputClause = OutputClause() {update.setOutputClause(outputClause); } ] + + [ fromItem=FromItem() joins=JoinsList() ] - [ where=WhereClause() { update.setWhere(where); } ] + [ where=WhereClause() { update.setWhere(where); } ] - [ orderByElements = OrderByElements() { update.setOrderByElements(orderByElements); } ] - [ limit = PlainLimit() { update.setLimit(limit); } ] - [ ( - "*" { update.setReturningAllColumns(true); } - | returning=ListExpressionItem() - ) - ] + [ orderByElements = OrderByElements() { update.setOrderByElements(orderByElements); } ] + [ limit = PlainLimit() { update.setLimit(limit); } ] + [ returning=SelectItemsList() ] - { + { return update.withWithItemsList(with) .withTable(table) .withStartJoins(startJoins) @@ -1209,7 +1265,7 @@ Update Update( List with ): .withModifierPriority(modifierPriority) .withModifierIgnore(modifierIgnore) .withReturningExpressionList(returning); - } + } } Replace Replace(): @@ -1287,14 +1343,9 @@ Insert Insert( List with ): Table table = null; Column tableColumn = null; List columns = new ArrayList(); - List primaryExpList = new ArrayList(); - ItemsList itemsList = null; Expression exp = null; - MultiExpressionList multiExpr = null; - List returning = null; + List returning = null; Select select = null; - boolean useValues = true; - boolean useSelectBrackets = false; boolean useDuplicate = false; List duplicateUpdateColumns = null; List duplicateUpdateExpressionList = null; @@ -1306,6 +1357,11 @@ Insert Insert( List with ): List setExpressionList = new ArrayList(); String name = null; boolean useAs = false; + OutputClause outputClause = null; + + UpdateSet updateSet = null; + InsertConflictTarget conflictTarget = null; + InsertConflictAction conflictAction = null; } { { insert.setOracleHint(getOracleHint()); } @@ -1318,40 +1374,13 @@ Insert Insert( List with ): [ [ { useAs = true; } ] name=RelObjectNameWithoutValue() { table.setAlias(new Alias(name,useAs)); }] - [LOOKAHEAD(2) "(" tableColumn=Column() { columns.add(tableColumn); } ("," tableColumn=Column() { columns.add(tableColumn); } )* ")" ] - ( - LOOKAHEAD(2) [ | ] "(" exp=SimpleExpression() { primaryExpList.add(exp); } - ("," exp=SimpleExpression() { primaryExpList.add(exp); } )* ")" { itemsList = new ExpressionList(primaryExpList); } - ("," "(" exp=SimpleExpression() { - if (multiExpr==null) { - multiExpr=new MultiExpressionList(); - multiExpr.addExpressionList((ExpressionList)itemsList); - itemsList = multiExpr; - } - primaryExpList = new ArrayList(); - primaryExpList.add(exp); } - ("," exp=SimpleExpression() { primaryExpList.add(exp); } )* ")" { multiExpr.addExpressionList(primaryExpList); } )* - - | + [LOOKAHEAD(2) "(" tableColumn=Column() { columns.add(tableColumn); } ("," tableColumn=Column() { columns.add(tableColumn); } )* ")" ] - ( - LOOKAHEAD(2) "(" { useSelectBrackets = true; } - { insert.setUseValues(false); } - select = SelectWithWithItems( ) - ")" - | - { insert.setUseValues(false); } - select = SelectWithWithItems( ) - ) - - | + [ outputClause = OutputClause() { insert.setOutputClause(outputClause); } ] - + ( ( - { - useSet = true; - insert.setUseValues(false); - } + { useSet = true; } tableColumn=Column() "=" exp=SimpleExpression() { setColumns = new ArrayList(); @@ -1363,9 +1392,11 @@ Insert Insert( List with ): { setColumns.add(tableColumn); setExpressionList.add(exp); } )* ) + | + select = SelectWithWithItems( ) ) - [ + [ LOOKAHEAD(2) { useDuplicate = true; } tableColumn=Column() "=" exp=SimpleExpression() { @@ -1378,20 +1409,19 @@ Insert Insert( List with ): { duplicateUpdateColumns.add(tableColumn); duplicateUpdateExpressionList.add(exp); } )*] - - [ ( - "*" { insert.setReturningAllColumns(true); } - | returning=ListExpressionItem() - ) + [ + + [ conflictTarget = InsertConflictTarget() ] + conflictAction = InsertConflictAction() { insert.withConflictTarget(conflictTarget).setConflictAction(conflictAction); } ] + [ returning=SelectItemsList() ] + { if (!columns.isEmpty()) { insert.setColumns(columns); } return insert.withWithItemsList(with) - .withItemsList(itemsList) - .withUseSelectBrackets(useSelectBrackets) .withSelect(select) .withTable(table) .withUseDuplicate(useDuplicate) @@ -1406,6 +1436,148 @@ Insert Insert( List with ): } } +InsertConflictTarget InsertConflictTarget(): +{ + String indexColumnName = null; + Expression indexExpression = null; + Expression whereExpression = null; + String constraintName = null ; +} +{ + ( + ( + "(" + indexColumnName = RelObjectNameExt2() +// | +// ( +// "(" indexExpression = Expression() ")" +// ) + + ")" + [ whereExpression = WhereClause() ] + ) + | + ( + constraintName = RelObjectNameExt2() + ) + ) + + { return new InsertConflictTarget(indexColumnName, indexExpression, whereExpression, constraintName); } +} + +InsertConflictAction InsertConflictAction(): +{ + InsertConflictAction conflictAction; + ArrayList updateSets = new ArrayList(); + UpdateSet updateSet = null; + Expression whereExpression = null; + + Column tableColumn = null; + SubSelect subSelect; + Expression valueExpression = null; + ExpressionList expressionList; +} +{ + ( + LOOKAHEAD(2) ( + { conflictAction = new InsertConflictAction( ConflictActionType.DO_NOTHING ); } + ) + | + ( + { conflictAction = new InsertConflictAction( ConflictActionType.DO_UPDATE ); } + ( + LOOKAHEAD(3) tableColumn=Column() + "=" valueExpression=SimpleExpression() { + updateSet = new UpdateSet(); + updateSet.add(tableColumn); + updateSet.add(valueExpression); + updateSets.add(updateSet); + } + ( + "," + tableColumn=Column() + "=" valueExpression=SimpleExpression() { + updateSet = new UpdateSet(); + updateSet.add(tableColumn); + updateSet.add(valueExpression); + updateSets.add(updateSet); + } + )* + | + ( + { updateSet = new UpdateSet(); updateSets.add(updateSet); } + + [ LOOKAHEAD(2) "(" { updateSet.setUsingBracketsForColumns(true); } ] + tableColumn=Column() { updateSet.add(tableColumn); } + ( LOOKAHEAD(2) "," tableColumn=Column() { updateSet.add(tableColumn); } )* + [ LOOKAHEAD(2) ")" ] + + "=" + + ( + LOOKAHEAD(3) subSelect=SubSelect() { updateSet.add(subSelect.withUseBrackets(false)); } + | + LOOKAHEAD(3) "(" expressionList = ComplexExpressionList() { updateSet.setUsingBracketsForValues(true); updateSet.add(expressionList); } ")" + | + valueExpression = Expression() { updateSet.add(valueExpression); } + ) + + ( + "," { updateSet = new UpdateSet(); updateSets.add(updateSet); } + + [ LOOKAHEAD(2) "(" { updateSet.setUsingBracketsForColumns(true); } ] + tableColumn=Column() { updateSet.add(tableColumn); } + ( LOOKAHEAD(2) "," tableColumn=Column() { updateSet.add(tableColumn); } )* + [ LOOKAHEAD(2) ")" ] + + "=" + + ( + LOOKAHEAD(3) subSelect=SubSelect() { updateSet.add(subSelect.withUseBrackets(false)); } + | + LOOKAHEAD(3) "(" expressionList = ComplexExpressionList() { updateSet.setUsingBracketsForValues(true); updateSet.add(expressionList); } ")" + | + valueExpression = Expression() { updateSet.add(valueExpression); } + ) + ) * + ) + ) + + [ whereExpression = WhereClause() ] + ) + ) + + { return conflictAction + .withUpdateSets(updateSets) + .withWhereExpression(whereExpression); } +} + +OutputClause OutputClause(): +{ + List selectItemList = null; + UserVariable tableVariable = null; + Table outputTable = null; + List columnList = null; +} +{ + + selectItemList = SelectItemsList() + [ + ( + tableVariable = UserVariable() + | + outputTable = Table() + ) + [ + LOOKAHEAD(2) columnList = ColumnsNamesList() + ] + ] + + { + return new OutputClause(selectItemList, tableVariable, outputTable, columnList); + } +} + Upsert Upsert(): { Upsert upsert = new Upsert(); @@ -1499,6 +1671,9 @@ Delete Delete( List with ): DeleteModifierPriority modifierPriority = null; boolean modifierIgnore = false; boolean modifierQuick = false; + + List returning = null; + OutputClause outputClause = null; } { { delete.setOracleHint(getOracleHint()); } @@ -1507,6 +1682,7 @@ Delete Delete( List with ): [ { modifierIgnore = true; }] [LOOKAHEAD(4) (table=TableWithAlias() { tables.add(table); } ("," table=TableWithAlias() { tables.add(table); } )* + [ outputClause = OutputClause() {delete.setOutputClause(outputClause); } ] | ) { hasFrom = true; }] [ LOOKAHEAD(3) table=TableWithAlias() joins=JoinsList() ] @@ -1515,6 +1691,8 @@ Delete Delete( List with ): [where=WhereClause() { delete.setWhere(where); } ] [orderByElements = OrderByElements() { delete.setOrderByElements(orderByElements); } ] [limit=PlainLimit() {delete.setLimit(limit); } ] + + [ returning=SelectItemsList() ] { if (joins != null && joins.size() > 0) { delete.setJoins(joins); @@ -1526,7 +1704,8 @@ Delete Delete( List with ): .withUsingList(usingList) .withModifierPriority(modifierPriority) .withModifierIgnore(modifierIgnore) - .withModifierQuick(modifierQuick); + .withModifierQuick(modifierQuick) + .withReturningExpressionList(returning); } } @@ -1661,7 +1840,7 @@ String RelObjectNameWithoutValue() : | tk= | tk= | tk= - | tk= | tk= + | tk= | tk= | tk= /* Keywords for ALTER SESSION */ /* | tk= */ | tk= | tk= @@ -1675,6 +1854,8 @@ String RelObjectNameWithoutValue() : | tk= | tk= | tk= + + | tk= ) { return tk.image; } @@ -1687,7 +1868,7 @@ String RelObjectName() : { Token tk = null; String result = null; } { (result = RelObjectNameWithoutValue() - | tk= | tk= | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= ) { @@ -1772,9 +1953,11 @@ Select SelectWithWithItems( ): List with = null; } { - [ with=WithList() { } ] select = Select( with ) + (LOOKAHEAD(2) ( "(" with=WithList() select = Select( with ) ")" { select.withUsingWithBrackets(true); } ) + | + ( [with=WithList()] select = Select( with ) )) { - return select; + return select; } } @@ -2071,7 +2254,7 @@ SelectExpressionItem SelectExpressionItem(): } { expression=Expression() { selectExpressionItem = new SelectExpressionItem(); selectExpressionItem.setExpression(expression); } - [alias=Alias() { selectExpressionItem.setAlias(alias); }] { return selectExpressionItem; } + [ LOOKAHEAD(2) alias=Alias() { selectExpressionItem.setAlias(alias); }] { return selectExpressionItem; } } SelectItem SelectItem() #SelectItem: @@ -2528,24 +2711,37 @@ Join JoinerExpression() #JoinerExpression: } { - + [ { join.setNatural(true); } ] [ - { join.setLeft(true); } [ { join.setSemi(true); } | { join.setOuter(true); } ] - | ( { join.setRight(true); } - | { join.setFull(true); } - ) [ { join.setOuter(true); } ] - | { join.setInner(true); } - | { join.setNatural(true); } - | { join.setCross(true); } - | { join.setOuter(true); } + ( + { join.setLeft(true); } [ { join.setSemi(true); } | { join.setOuter(true); } ] + | + ( + { join.setRight(true); } + | + { join.setFull(true); } + ) [ { join.setOuter(true); } ] + | + { join.setInner(true); } + ) + | + { join.setCross(true); } + | + { join.setOuter(true); } ] - ( | "," { join.setSimple(true); } ( { join.setOuter(true); } )? - | { join.setStraight(true); } | {join.setApply(true); } ) - - right=FromItem() + ( + + | + "," { join.setSimple(true); } ( { join.setOuter(true); } )? + | + { join.setStraight(true); } + | + {join.setApply(true); } + ) + right=FromItem() [ LOOKAHEAD(2) ( @@ -2683,6 +2879,19 @@ GroupByElement GroupByColumnReferences(): ( LOOKAHEAD(2) ( "(" ")" { groupBy.withUsingBrackets(true); } + ( + LOOKAHEAD(2) ( + "(" + ( LOOKAHEAD(2) "(" ")" { groupBy.addGroupingSet(new ExpressionList()); } + | LOOKAHEAD(3) "(" list = SimpleExpressionList(true) ")" { groupBy.addGroupingSet(list); } + | expr = SimpleExpression() { groupBy.addGroupingSet(expr); } ) + + ( "," ( LOOKAHEAD(2) "(" ")" { groupBy.addGroupingSet(new ExpressionList()); } + | LOOKAHEAD(3) "(" list = SimpleExpressionList(true) ")" { groupBy.addGroupingSet(list); } + | expr = SimpleExpression() { groupBy.addGroupingSet(expr); } ) )* + ")" + ) + )? ) | LOOKAHEAD(2) ( @@ -2699,6 +2908,19 @@ GroupByElement GroupByColumnReferences(): | LOOKAHEAD(2) ( list = ComplexExpressionList() { groupBy.setGroupByExpressionList(list.withUsingBrackets(false)); } + ( + LOOKAHEAD(2) ( + "(" + ( LOOKAHEAD(2) "(" ")" { groupBy.addGroupingSet(new ExpressionList()); } + | LOOKAHEAD(3) "(" list = SimpleExpressionList(true) ")" { groupBy.addGroupingSet(list); } + | expr = SimpleExpression() { groupBy.addGroupingSet(expr); } ) + + ( "," ( LOOKAHEAD(2) "(" ")" { groupBy.addGroupingSet(new ExpressionList()); } + | LOOKAHEAD(3) "(" list = SimpleExpressionList(true) ")" { groupBy.addGroupingSet(list); } + | expr = SimpleExpression() { groupBy.addGroupingSet(expr); } ) )* + ")" + ) + )? ) ) { @@ -3356,7 +3578,7 @@ ExpressionList SimpleExpressionList(boolean outerBrackets) #ExpressionList: } { expr=SimpleExpression() { expressions.add(expr); } - ( LOOKAHEAD(2) "," expr=SimpleExpression() { expressions.add(expr); } )* + ( LOOKAHEAD(2, {!interrupted} ) "," expr=SimpleExpression() { expressions.add(expr); } )* { retval.setExpressions(expressions); return retval; @@ -3376,7 +3598,7 @@ ExpressionList ComplexExpressionList() #ExpressionList: ) { expressions.add(expr); } ( - LOOKAHEAD(2) "," + LOOKAHEAD(2, {!interrupted}) "," ( LOOKAHEAD(2) expr=OracleNamedFunctionParameter() | expr=Expression() @@ -3717,37 +3939,35 @@ Expression PrimaryExpression() #PrimaryExpression: ( { retval = new NullValue(); } - | LOOKAHEAD(3) retval=CaseWhenExpression() + | LOOKAHEAD(3, {!interrupted}) retval=CaseWhenExpression() | retval = SimpleJdbcParameter() - | LOOKAHEAD(2) retval=JdbcNamedParameter() + | LOOKAHEAD(2, {!interrupted}) retval=JdbcNamedParameter() | retval=UserVariable() - | LOOKAHEAD(2) retval=NumericBind() + | LOOKAHEAD(2, {!interrupted}) retval=NumericBind() - | LOOKAHEAD(3) retval=ExtractExpression() + | LOOKAHEAD(3, {!interrupted}) retval=ExtractExpression() | retval=MySQLGroupConcat() | retval=XMLSerializeExpr() - | LOOKAHEAD(JsonExpression()) retval=JsonExpression() + | LOOKAHEAD(JsonExpression(), {!interrupted}) retval=JsonExpression() - | LOOKAHEAD(Function()) retval=Function() [ LOOKAHEAD(2) retval = AnalyticExpression( (Function) retval ) ] + | LOOKAHEAD(JsonFunction(), {!interrupted}) retval = JsonFunction() - | LOOKAHEAD(JsonFunction()) retval = JsonFunction() - - | LOOKAHEAD(JsonAggregateFunction()) retval = JsonAggregateFunction() + | LOOKAHEAD(JsonAggregateFunction(), {!interrupted}) retval = JsonAggregateFunction() /* | LOOKAHEAD(FunctionWithCondParams()) retval = FunctionWithCondParams() */ - | LOOKAHEAD(FullTextSearch()) retval = FullTextSearch() - + | LOOKAHEAD(FullTextSearch(), {!interrupted}) retval = FullTextSearch() + | LOOKAHEAD(Function(), {!interrupted}) retval=Function() [ LOOKAHEAD(2) retval = AnalyticExpression( (Function) retval ) ] - | LOOKAHEAD(2) retval = IntervalExpression() { dateExpressionAllowed = false; } + | LOOKAHEAD(2, {!interrupted}) retval = IntervalExpression() { dateExpressionAllowed = false; } | token= { retval = new DoubleValue(token.image); } @@ -3755,26 +3975,26 @@ Expression PrimaryExpression() #PrimaryExpression: | token= { retval = new HexValue(token.image); } - | LOOKAHEAD(2) retval=CastExpression() + | LOOKAHEAD(2, {!interrupted}) retval=CastExpression() - | LOOKAHEAD(2) retval=TryCastExpression() + | LOOKAHEAD(2, {!interrupted}) retval=TryCastExpression() //| LOOKAHEAD(2) retval=RowConstructor() // support timestamp expressions | (token= | token=) { retval = new TimeKeyExpression(token.image); } - | LOOKAHEAD(2) retval=DateTimeLiteralExpression() + | LOOKAHEAD(2, {!interrupted}) retval=DateTimeLiteralExpression() - | LOOKAHEAD(2) retval=ArrayConstructor(true) + | LOOKAHEAD(2, {!interrupted}) retval=ArrayConstructor(true) - | LOOKAHEAD(2) retval = NextValExpression() + | LOOKAHEAD(2, {!interrupted}) retval = NextValExpression() | retval=ConnectByRootOperator() - | LOOKAHEAD(2) { retval = new AllValue(); } + | LOOKAHEAD(2, {!interrupted}) { retval = new AllValue(); } - | LOOKAHEAD(2) retval=Column() + | LOOKAHEAD(2, {!interrupted}) retval=Column() | token= { retval = new StringValue(token.image); linkAST(retval,jjtThis); } @@ -3784,30 +4004,17 @@ Expression PrimaryExpression() #PrimaryExpression: | "{ts" token= "}" { retval = new TimestampValue(token.image); } - | LOOKAHEAD("(" retval=SubSelect() ")") "(" retval=SubSelect() ")" + | LOOKAHEAD("(" retval=SubSelect() ")", {!interrupted} ) "(" retval=SubSelect() ")" | ( - ( - (LOOKAHEAD({getAsBoolean(Feature.allowComplexParsing)}) "(" list = ComplexExpressionList() ")" - { - if (list.getExpressions().size() == 1) { - retval = new Parenthesis(list.getExpressions().get(0)); - } else { - retval = new RowConstructor().withExprList(list); - } + "(" ( LOOKAHEAD( { getAsBoolean(Feature.allowComplexParsing) && !interrupted } ) list = ComplexExpressionList() | list = SimpleExpressionList(true) ) ")" + { + if (list.getExpressions().size() == 1) { + retval = new Parenthesis(list.getExpressions().get(0)); + } else { + retval = new RowConstructor().withExprList(list); } - ) - - | ("(" list = SimpleExpressionList(true) ")" - { - if (list.getExpressions().size() == 1) { - retval = new Parenthesis(list.getExpressions().get(0)); - } else { - retval = new RowConstructor().withExprList(list); - } - } - ) - ) + } ["." tmp=RelObjectNameExt() { retval = new RowGetExpression(retval, tmp); }] ) ) @@ -4332,13 +4539,14 @@ WindowOffset WindowOffset(): ExtractExpression ExtractExpression() : { ExtractExpression retval = new ExtractExpression(); - String token = null; + String fieldName = null; + Token token = null; Expression expr = null; } { "(" - token=RelObjectName() { retval.setName(token); } + ( fieldName=RelObjectName() { retval.setName(fieldName); } | token= { retval.setName(token.image); } ) expr=SimpleExpression() { retval.setExpression(expr); } ")" @@ -4543,28 +4751,6 @@ FullTextSearch FullTextSearch() : { } } -/* Function FunctionWithCondParams() #Function: { - Function retval = new Function(); - String funcName = null; - ExpressionList expressionList = null; - Token token = null; -} -{ - (token = | token = | token = ) { funcName=token.image; } - - "(" - expressionList=ComplexExpressionList() - ")" - - { - retval.setParameters(expressionList); - retval.setName(funcName); - linkAST(retval,jjtThis); - return retval; - } -} */ - - Function Function() #Function: { Function retval = new Function(); @@ -4961,8 +5147,10 @@ CreateTable CreateTable(): sk3=RelObjectName() /* colNames=ColumnsNamesList() */ colNames = ColumnNamesWithParamsList() + { idxSpec.clear(); } + ( parameter=CreateParameter() { idxSpec.addAll(parameter); } )* { - index = new Index().withType(tk.image).withName(sk3).withColumns(colNames); + index = new Index().withType(tk.image).withName(sk3).withColumns(colNames).withIndexSpec(new ArrayList(idxSpec)); indexes.add(index); } ) @@ -4978,9 +5166,10 @@ CreateTable CreateTable(): ) /* colNames=ColumnsNamesList() */ colNames = ColumnNamesWithParamsList() + { idxSpec.clear(); } ( parameter=CreateParameter() { idxSpec.addAll(parameter); } )* { - index.withColumns(colNames).withIndexSpec(idxSpec); + index.withColumns(colNames).withIndexSpec(new ArrayList(idxSpec)); indexes.add(index); } // reset Token to null forcefullly @@ -4994,13 +5183,14 @@ CreateTable CreateTable(): sk3=RelObjectName() /* colNames=ColumnsNamesList() */ colNames = ColumnNamesWithParamsList() + { idxSpec.clear(); } ( parameter=CreateParameter() { idxSpec.addAll(parameter); } )* { index = new Index() .withType((tk!=null?tk.image + " ":"") + (tk3!=null?tk3.image + " ":"") + tk2.image) .withName(sk3) .withColumns(colNames) - .withIndexSpec(idxSpec); + .withIndexSpec(new ArrayList(idxSpec)); indexes.add(index); } ) @@ -5129,7 +5319,7 @@ ColDataType ColDataType(): [LOOKAHEAD(2) "(" {tk2 =null;} ( ( ( tk= [ LOOKAHEAD(2) (tk2= | tk2=) ] ) | tk= | tk= | tk= ) { argumentsStringList.add(tk.image + (tk2!=null?" " + tk2.image:"")); } ["," {/*argumentsStringList.add(",");*/}] )* ")"] [( "[" {tk=null;} [ tk= ] { array.add(tk!=null?Integer.valueOf(tk.image):null); } "]" )+ { colDataType.setArrayData(array); } ] - [LOOKAHEAD(2) tk= { colDataType.setCharacterSet(tk.image); } ] + [LOOKAHEAD(2) (tk= | tk=) { colDataType.setCharacterSet(tk.image); } ] { if (argumentsStringList.size() > 0) @@ -5138,6 +5328,21 @@ ColDataType ColDataType(): } } +Analyze Analyze(): +{ + Analyze analyze = new Analyze(); + Table table = null; +} +{ + + table=Table() + + { + analyze.setTable(table); + return analyze; + } +} + CreateView CreateView(): { CreateView createView = new CreateView(); @@ -5309,6 +5514,10 @@ List CreateParameter(): | tk= { param.add(tk.image); } | + tk= { param.add(tk.image); } + | + (tk= tk2=) { param.add(tk.image); param.add(tk2.image);} + | ( exp=ArrayConstructor(true)) { param.add(exp.toString()); } | tk="::" colDataType = ColDataType() { param.add(tk.image); param.add(colDataType.toString()); } @@ -5380,12 +5589,55 @@ List ColumnsNamesList(): } } +String FuncArgsListItem(): +{ + Token tk = null; + String argName = null; + String argType = null; +} +{ + ( + LOOKAHEAD(2) ( + argName = RelObjectName() + argType = RelObjectName() + [ "(" tk = ")" { argType = argType + "(" + tk.image + ")"; } ] + ) + | + ( + argType = RelObjectName() + [ "(" tk = ")" { argType = argType + "(" + tk.image + ")"; } ] + ) + ) + { + return argName != null ? String.format("%s %s", argName, argType) : argType; + } +} + +List FuncArgsList(): +{ + List retval = null; + String img = null; +} +{ + "(" + { retval = new ArrayList(); } + [ + img=FuncArgsListItem() { retval.add(img); } + ( "," img=FuncArgsListItem() { retval.add(img); } )* + ] + ")" + { + return retval; + } +} + Drop Drop(): { Drop drop = new Drop(); Token tk = null; Table name; List dropArgs = new ArrayList(); + List funcArgs = null; } { @@ -5401,17 +5653,25 @@ Drop Drop(): tk= | tk= + | + tk= ) { drop.setType(tk.image); } [ LOOKAHEAD(2) {drop.setIfExists(true);} ] name = Table() { drop.setName(name); } + [ funcArgs = FuncArgsList() ] ((tk= | tk= | tk= ) { dropArgs.add(tk.image); })* { - if (dropArgs.size() > 0) + if (dropArgs.size() > 0) { drop.setParameters(dropArgs); + } + if (drop.getType().equals("FUNCTION")) { + drop.getTypeToParameters().put("FUNCTION", funcArgs); + } + return drop; } } @@ -5422,7 +5682,15 @@ Truncate Truncate(): Table table; } { - +/** +* TRUNCATE can be followed directly by the table name in Postgresql +* See: https://www.postgresql.org/docs/current/sql-truncate.html +* +* TRUNCATE [ TABLE ] [ ONLY ] name [ * ] [, ... ] +* [ RESTART IDENTITY | CONTINUE IDENTITY ] [ CASCADE | RESTRICT ] +* +*/ + [LOOKAHEAD(2) {truncate.setTableToken(true);}] [ {truncate.setOnly(true);}] table=Table() { truncate.setTable(table); truncate.setCascade(false); } [ {truncate.setCascade(true);} ] { return truncate; @@ -5466,6 +5734,21 @@ AlterExpression.ColumnDropNotNull AlterExpressionColumnDropNotNull(): } } +AlterExpression.ColumnDropDefault AlterExpressionColumnDropDefault(): +{ + String columnName = null; + boolean withNot = false; + ColDataType dataType = null; + List columnSpecs = null; + List parameter = null; +} +{ + columnName = RelObjectName() + { + return new AlterExpression.ColumnDropDefault(columnName); + } +} + List AlterExpressionConstraintState(): { List retval = new ArrayList(); @@ -5521,6 +5804,7 @@ AlterExpression AlterExpression(): Table fkTable = null; AlterExpression.ColumnDataType alterExpressionColumnDataType = null; AlterExpression.ColumnDropNotNull alterExpressionColumnDropNotNull = null; + AlterExpression.ColumnDropDefault alterExpressionColumnDropDefault = null; ReferentialAction.Action action = null; // for captureRest() @@ -5548,10 +5832,22 @@ AlterExpression AlterExpression(): [ sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }] ) | - LOOKAHEAD(3) ( (LOOKAHEAD(2) { alterExp.hasColumn(true); })? - (LOOKAHEAD(2) alterExpressionColumnDataType = AlterExpressionColumnDataType() { alterExp.addColDataType(alterExpressionColumnDataType); } - | - alterExpressionColumnDropNotNull = AlterExpressionColumnDropNotNull() { alterExp.addColDropNotNull( alterExpressionColumnDropNotNull); }) + LOOKAHEAD(3) ( + ( LOOKAHEAD(2) { alterExp.hasColumn(true); } )? + + ( + LOOKAHEAD(2) alterExpressionColumnDataType = AlterExpressionColumnDataType() { + alterExp.addColDataType(alterExpressionColumnDataType); + } + | + LOOKAHEAD(3) alterExpressionColumnDropNotNull = AlterExpressionColumnDropNotNull() { + alterExp.addColDropNotNull( alterExpressionColumnDropNotNull); + } + | + alterExpressionColumnDropDefault = AlterExpressionColumnDropDefault() { + alterExp.addColDropDefault( alterExpressionColumnDropDefault); + } + ) ) | ( @@ -5668,13 +5964,9 @@ AlterExpression AlterExpression(): ) ) | - ( - { - alterExp.setOperation(AlterOperation.CHANGE); - } - ( - { alterExp.hasColumn(true); alterExp.setOptionalSpecifier("COLUMN"); } | {} - ) + ( + { alterExp.setOperation(AlterOperation.CHANGE); } + [ { alterExp.hasColumn(true); alterExp.setOptionalSpecifier("COLUMN"); } ] ( (tk= | tk=) alterExpressionColumnDataType = AlterExpressionColumnDataType() { alterExp.withColumnOldName(tk.image).addColDataType(alterExpressionColumnDataType); } @@ -5699,6 +5991,7 @@ AlterExpression AlterExpression(): | ( ( LOOKAHEAD(2) { alterExp.hasColumn(true); } )? + [ { alterExp.setUsingIfExists(true); } ] (tk= | tk=) { alterExp.setColumnName(tk.image); } [ "INVALIDATE" { alterExp.addParameters("INVALIDATE"); } ] @@ -5750,7 +6043,7 @@ AlterExpression AlterExpression(): sk3 = RelObjectName() {alterExp.addParameters(sk3); } ) | - LOOKAHEAD(2) { alterExp.setOperation(AlterOperation.RENAME); alterExp.hasColumn(true);} + LOOKAHEAD(2) { alterExp.setOperation(AlterOperation.RENAME); } [ { alterExp.hasColumn(true);} ] ( tk= | tk= ) { alterExp.setColOldName(tk.image); } (tk2= | tk2=) { alterExp.setColumnName(tk2.image); } @@ -5921,10 +6214,6 @@ AlterSystemStatement AlterSystemStatement(): "DISCONNECT" "SESSION" { operation = AlterSystemOperation.DISCONNECT_SESSION; } ) | - ( - "DISCONNECT SESSION" { operation = AlterSystemOperation.DISCONNECT_SESSION; } - ) - | ( "KILL SESSION" { operation = AlterSystemOperation.KILL_SESSION; } ) @@ -6322,6 +6611,17 @@ Synonym Synonym() #Synonym : } } +UnsupportedStatement UnsupportedStatement(): +{ + List tokens = new LinkedList(); +} +{ + tokens=captureUnsupportedStatementDeclaration() + { + return new UnsupportedStatement(tokens); + } +} + JAVACODE List captureRest() { List tokens = new LinkedList(); @@ -6336,3 +6636,19 @@ List captureRest() { } return tokens; } + +JAVACODE +List captureUnsupportedStatementDeclaration() { + List tokens = new LinkedList(); + Token tok; + + while(true) { + tok = getToken(1); + if( tok.kind == EOF || tok.kind== ST_SEMICOLON || tok.kind== K_END ) { + break; + } + tokens.add(tok.image); + tok = getNextToken(); + } + return tokens; +} diff --git a/src/test/java/net/sf/jsqlparser/expression/StringValueTest.java b/src/test/java/net/sf/jsqlparser/expression/StringValueTest.java index 7f089c3ea..7eb59fbf4 100644 --- a/src/test/java/net/sf/jsqlparser/expression/StringValueTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/StringValueTest.java @@ -57,4 +57,10 @@ private void checkStringValue(String original, String expectedValue, String expe assertEquals(expectedValue, v.getValue()); assertEquals(expectedPrefix, v.getPrefix()); } + + @Test + public void testIssue1566EmptyStringValue() { + StringValue v = new StringValue("'"); + assertEquals("'", v.getValue()); + } } diff --git a/src/test/java/net/sf/jsqlparser/expression/operators/relational/LikeExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/operators/relational/LikeExpressionTest.java index 6a3771b66..bafbbf7fb 100644 --- a/src/test/java/net/sf/jsqlparser/expression/operators/relational/LikeExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/operators/relational/LikeExpressionTest.java @@ -9,10 +9,14 @@ */ package net.sf.jsqlparser.expression.operators.relational; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + /** * * @author Tobias Warneke (t.warneke@gmx.net) @@ -25,4 +29,15 @@ public void testLikeNotIssue660() { assertFalse(instance.isNot()); assertTrue(instance.withNot(true).isNot()); } + + @Test + public void testSetEscapeAndGetStringExpression() throws JSQLParserException { + LikeExpression instance = (LikeExpression) CCJSqlParserUtil.parseExpression("name LIKE 'J%$_%'"); + // escape character should be $ + Expression instance2 = new StringValue("$"); + instance.setEscape(instance2); + + // match all records with names that start with letter ’J’ and have the ’_’ character in them + assertEquals("name LIKE 'J%$_%' ESCAPE '$'", instance.toString()); + } } diff --git a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java index 8604046b3..2c210e163 100644 --- a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java +++ b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java @@ -14,6 +14,8 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeoutException; + import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; @@ -26,7 +28,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; public class CCJSqlParserUtilTest { @@ -160,7 +165,10 @@ public void accept(Statement statement) { } @Test + @Disabled public void testParseStatementsFail() throws Exception { + // This will not fail, but always return the Unsupported Statements + // Since we can't LOOKAHEAD in the Statements() production assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parseStatements("select * from dual;WHATEVER!!")); } @@ -253,16 +261,74 @@ public void testParseStatementIssue1250() throws Exception { Statement result = CCJSqlParserUtil.parse("Select test.* from (Select * from sch.PERSON_TABLE // root test\n) as test"); assertEquals("SELECT test.* FROM (SELECT * FROM sch.PERSON_TABLE) AS test", result.toString()); } - + @Test public void testCondExpressionIssue1482() throws JSQLParserException { Expression expr = CCJSqlParserUtil.parseCondExpression("test_table_enum.f1_enum IN ('TEST2'::test.test_enum)", false); assertEquals("test_table_enum.f1_enum IN ('TEST2'::test.test_enum)", expr.toString()); } - + @Test public void testCondExpressionIssue1482_2() throws JSQLParserException { Expression expr = CCJSqlParserUtil.parseCondExpression("test_table_enum.f1_enum IN ('TEST2'::test.\"test_enum\")", false); assertEquals("test_table_enum.f1_enum IN ('TEST2'::test.\"test_enum\")", expr.toString()); } + + @Test + public void testTimeOutIssue1582() throws InterruptedException { + // This statement is INVALID on purpose + // There are crafted INTO keywords in order to make it fail but only after a long time (40 seconds plus) + + String sqlStr = "" + + "select\n" + + " t0.operatienr\n" + + " , case\n" + + " when\n" + + " case when (t0.vc_begintijd_operatie is null or lpad((extract('hours' into t0.vc_begintijd_operatie::timestamp))::text,2,'0') ||':'|| lpad(extract('minutes' from t0.vc_begintijd_operatie::timestamp)::text,2,'0') = '00:00') then null\n" + + " else (greatest(((extract('hours' into (t0.vc_eindtijd_operatie::timestamp-t0.vc_begintijd_operatie::timestamp))*60 + extract('minutes' from (t0.vc_eindtijd_operatie::timestamp-t0.vc_begintijd_operatie::timestamp)))/60)::numeric(12,2),0))*60\n" + + " end = 0 then null\n" + + " else '25. Meer dan 4 uur'\n" + + " end \n" + + " as snijtijd_interval"; + + // With DEFAULT TIMEOUT 6 Seconds, we expect the statement to timeout normally + // A TimeoutException wrapped into a Parser Exception should be thrown + assertThrows(TimeoutException.class, new Executable() { + @Override + public void execute() throws Throwable { + try { + CCJSqlParserUtil.parse(sqlStr); + } catch (JSQLParserException ex) { + Throwable cause = ((JSQLParserException) ex).getCause(); + if (cause != null) { + throw cause; + } else { + throw ex; + } + } + } + }); + + // With custom TIMEOUT 60 Seconds, we expect the statement to not timeout but to fail instead + // No TimeoutException wrapped into a Parser Exception must be thrown + // Instead we expect a Parser Exception only + assertThrows(JSQLParserException.class, new Executable() { + @Override + public void execute() throws Throwable { + try { + CCJSqlParserUtil.parse(sqlStr, parser -> { + parser.withTimeOut(10000); + parser.withAllowComplexParsing(false); + }); + } catch (JSQLParserException ex) { + Throwable cause = ((JSQLParserException) ex).getCause(); + if (cause instanceof TimeoutException) { + throw cause; + } else { + throw ex; + } + } + } + }); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java b/src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java new file mode 100644 index 000000000..9a5f515df --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java @@ -0,0 +1,75 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.select.Select; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; + +public class UnsupportedStatementTest { + @Test + public void testSingleUnsupportedStatement() throws JSQLParserException { + String sqlStr = "this is an unsupported statement"; + + assertSqlCanBeParsedAndDeparsed(sqlStr, true, parser -> parser.withUnsupportedStatements(true) ); + + Assertions.assertThrowsExactly(JSQLParserException.class, new Executable() { + @Override + public void execute() throws Throwable { + CCJSqlParserUtil.parse(sqlStr, parser -> parser.withUnsupportedStatements(false) ); + } + }); + } + + @Test + public void testUnsupportedStatementsFirstInBlock() throws JSQLParserException { + String sqlStr = "This is an unsupported statement; Select * from dual; Select * from dual;"; + + Statements statements = CCJSqlParserUtil.parseStatements(sqlStr, parser -> parser.withUnsupportedStatements(true)); + Assertions.assertEquals(3, statements.getStatements().size()); + Assertions.assertInstanceOf(UnsupportedStatement.class, statements.getStatements().get(0)); + Assertions.assertInstanceOf(Select.class, statements.getStatements().get(1)); + Assertions.assertInstanceOf(Select.class, statements.getStatements().get(2)); + + Assertions.assertThrowsExactly(JSQLParserException.class, new Executable() { + @Override + public void execute() throws Throwable { + CCJSqlParserUtil.parseStatements(sqlStr, parser -> parser.withUnsupportedStatements(false) ); + } + }); + } + + @Test + public void testUnsupportedStatementsMiddleInBlock() throws JSQLParserException { + String sqlStr = "Select * from dual; This is an unsupported statement; Select * from dual;"; + + Statements statements = CCJSqlParserUtil.parseStatements(sqlStr, parser -> parser.withUnsupportedStatements(true)); + Assertions.assertEquals(3, statements.getStatements().size()); + + Assertions.assertInstanceOf(Select.class, statements.getStatements().get(0)); + Assertions.assertInstanceOf(UnsupportedStatement.class, statements.getStatements().get(1)); + Assertions.assertInstanceOf(Select.class, statements.getStatements().get(2)); + +// This will not fail, but always return the Unsupported Statements +// Since we can't LOOKAHEAD in the Statements() production + +// Assertions.assertThrowsExactly(JSQLParserException.class, new Executable() { +// @Override +// public void execute() throws Throwable { +// CCJSqlParserUtil.parseStatements(sqlStr, parser -> parser.withUnsupportedStatements(false) ); +// } +// }); + } +} 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 376514ef6..0b233ba4b 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -131,22 +131,22 @@ public void testAlterTableAddUniqueConstraint() throws JSQLParserException { } @Test - public void testAlterTableForgeignKey2() throws JSQLParserException { + public void testAlterTableForeignKey2() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("ALTER TABLE test ADD FOREIGN KEY (user_id) REFERENCES ra_user (id)"); } @Test - public void testAlterTableForgeignKey3() throws JSQLParserException { + public void testAlterTableForeignKey3() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("ALTER TABLE test ADD FOREIGN KEY (user_id) REFERENCES ra_user (id) ON DELETE RESTRICT"); } @Test - public void testAlterTableForgeignKey4() throws JSQLParserException { + public void testAlterTableForeignKey4() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("ALTER TABLE test ADD FOREIGN KEY (user_id) REFERENCES ra_user (id) ON DELETE SET NULL"); } @Test - public void testAlterTableForgeignWithFkSchema() throws JSQLParserException { + public void testAlterTableForeignWithFkSchema() throws JSQLParserException { final String FK_SCHEMA_NAME = "my_schema"; final String FK_TABLE_NAME = "ra_user"; String sql = "ALTER TABLE test ADD FOREIGN KEY (user_id) REFERENCES " + FK_SCHEMA_NAME + "." + FK_TABLE_NAME + " (id) ON DELETE SET NULL"; @@ -471,6 +471,7 @@ public void testAlterTableAlterColumnDropNotNullIssue918() throws JSQLParserExce @Test public void testAlterTableRenameColumn() throws JSQLParserException { + // With Column Keyword String sql = "ALTER TABLE \"test_table\" RENAME COLUMN \"test_column\" TO \"test_c\""; assertSqlCanBeParsedAndDeparsed(sql); @@ -479,6 +480,10 @@ public void testAlterTableRenameColumn() throws JSQLParserException { assertEquals(expression.getOperation(), AlterOperation.RENAME); assertEquals(expression.getColOldName(), "\"test_column\""); assertEquals(expression.getColumnName(), "\"test_c\""); + + // Without Column Keyword + sql = "ALTER TABLE \"test_table\" RENAME \"test_column\" TO \"test_c\""; + assertSqlCanBeParsedAndDeparsed(sql); } @Test @@ -776,4 +781,26 @@ public void testAlterTableChangeColumnDropNotNull() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("ALTER TABLE a MODIFY (COLUMN b DROP NOT NULL, COLUMN c DROP NOT NULL)", true); } + @Test + public void testAlterTableChangeColumnDropDefault() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("ALTER TABLE a MODIFY COLUMN b DROP DEFAULT", true); + assertSqlCanBeParsedAndDeparsed("ALTER TABLE a MODIFY (COLUMN b DROP DEFAULT, COLUMN c DROP DEFAULT)", true); + assertSqlCanBeParsedAndDeparsed("ALTER TABLE a MODIFY (COLUMN b DROP NOT NULL, COLUMN b DROP DEFAULT)", true); + assertSqlCanBeParsedAndDeparsed("ALTER TABLE a MODIFY (COLUMN b DROP DEFAULT, COLUMN b DROP NOT NULL)", true); + } + + @Test + public void testAlterTableDropColumnIfExists() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("ALTER TABLE test DROP COLUMN IF EXISTS name"); + } + + @Test + public void testAlterTableDropMultipleColumnsIfExists() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("ALTER TABLE test DROP COLUMN IF EXISTS name, DROP COLUMN IF EXISTS surname"); + } + + @Test + public void testAlterTableDropMultipleColumnsIfExistsWithParams() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("ALTER TABLE test DROP COLUMN IF EXISTS name CASCADE, DROP COLUMN IF EXISTS surname CASCADE"); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java b/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java new file mode 100644 index 000000000..087bc4ebb --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java @@ -0,0 +1,41 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.analyze; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.schema.Table; +import org.junit.jupiter.api.Test; + +import java.io.StringReader; + +import static net.sf.jsqlparser.test.TestUtils.assertDeparse; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AnalyzeTest { + + private final CCJSqlParserManager parserManager = new CCJSqlParserManager(); + + @Test + public void testAnalyze() throws JSQLParserException { + String statement = "ANALYZE mytab"; + Analyze parsed = (Analyze) parserManager.parse(new StringReader(statement)); + assertEquals("mytab", parsed.getTable().getFullyQualifiedName()); + assertEquals(statement, "" + parsed); + + assertDeparse(new Analyze().withTable(new Table("mytab")), statement); + } + + @Test + public void testAnalyze2() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("ANALYZE mytable"); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java index 8c5dc0648..6522f40d3 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java @@ -170,7 +170,6 @@ public void testCreateTableUniqueConstraint() throws JSQLParserException { CreateTable createTable = (CreateTable) CCJSqlParserUtil.parseStatements(sqlStr).getStatements().get(0); - System.out.println(createTable.toString()); } @Test @@ -857,4 +856,53 @@ public void testCreateUnionIssue1309() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed( "CREATE TABLE temp.abc AS (SELECT c FROM t1) UNION (SELECT c FROM t2)"); } + + @Test + public void testCreateTableBinaryIssue1518() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("CREATE TABLE `s` (`a` enum ('a', 'b', 'c') CHARACTER SET binary COLLATE binary)"); + } + + @Test + public void testCreateTableIssue1488() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("CREATE TABLE u_call_record (\n" + + "card_user_id int(11) NOT NULL,\n" + + "device_id int(11) NOT NULL,\n" + + "call_start_at int(11) NOT NULL DEFAULT CURRENT_TIMESTAMP(11),\n" + + "card_user_name varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,\n" + + "sim_id varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,\n" + + "called_number varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,\n" + + "called_nickname varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,\n" + + "talk_time smallint(8) NULL DEFAULT NULL,\n" + + "area_name varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,\n" + + "area_service_id int(11) NULL DEFAULT NULL,\n" + + "operator_id int(4) NULL DEFAULT NULL,\n" + + "status tinyint(4) NULL DEFAULT NULL,\n" + + "create_at timestamp NULL DEFAULT NULL,\n" + + "place_user varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,\n" + + "PRIMARY KEY (card_user_id, device_id, call_start_at) USING BTREE,\n" + + "INDEX ucr_index_area_name(area_name) USING BTREE,\n" + + "INDEX ucr_index_area_service_id(area_service_id) USING BTREE,\n" + + "INDEX ucr_index_called_number(called_number) USING BTREE,\n" + + "INDEX ucr_index_create_at(create_at) USING BTREE,\n" + + "INDEX ucr_index_operator_id(operator_id) USING BTREE,\n" + + "INDEX ucr_index_place_user(place_user) USING BTREE,\n" + + "INDEX ucr_index_sim_id(sim_id) USING BTREE,\n" + + "INDEX ucr_index_status(status) USING BTREE,\n" + + "INDEX ucr_index_talk_time(talk_time) USING BTREE\n" + + ") ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic", true); + } + + @Test + public void testCreateTableBinaryIssue1596() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("CREATE TABLE student2 (" + + "id int (10) NOT NULL COMMENT 'ID', " + + "name varchar (20) COLLATE utf8mb4_bin DEFAULT NULL, " + + "birth year (4) DEFAULT NULL, " + + "department varchar (20) COLLATE utf8mb4_bin DEFAULT NULL, " + + "address varchar (50) COLLATE utf8mb4_bin DEFAULT NULL, " + + "PRIMARY KEY (id), " + + "UNIQUE KEY id (id), " + + "INDEX name (name) USING BTREE" + + ") ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin"); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/delete/DeleteTest.java b/src/test/java/net/sf/jsqlparser/statement/delete/DeleteTest.java index f3b944002..7c32fe3ec 100644 --- a/src/test/java/net/sf/jsqlparser/statement/delete/DeleteTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/delete/DeleteTest.java @@ -178,4 +178,42 @@ public void testDeleteMultipleModifiers() throws JSQLParserException { assertTrue(delete2.isModifierIgnore()); assertTrue(delete2.isModifierQuick()); } + + @Test + public void testDeleteReturningIssue1527() throws JSQLParserException { + String statement = "delete from t returning *"; + assertSqlCanBeParsedAndDeparsed(statement, true); + + statement = "delete from products\n" + + " WHERE price <= 99.99\n" + + " RETURNING name, price AS new_price"; + assertSqlCanBeParsedAndDeparsed(statement, true); + } + @Test + public void testDeleteOutputClause() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "DELETE Sales.ShoppingCartItem OUTPUT DELETED.* FROM Sales" + , true + ); + + assertSqlCanBeParsedAndDeparsed( + "DELETE Sales.ShoppingCartItem OUTPUT Sales.ShoppingCartItem FROM Sales" + , true + ); + + assertSqlCanBeParsedAndDeparsed( + "DELETE Production.ProductProductPhoto \n" + + "OUTPUT DELETED.ProductID, \n" + + " p.Name, \n" + + " p.ProductModelID, \n" + + " DELETED.ProductPhotoID \n" + + " INTO @MyTableVar \n" + + "FROM Production.ProductProductPhoto AS ph \n" + + "JOIN Production.Product as p \n" + + " ON ph.ProductID = p.ProductID \n" + + " WHERE p.ProductModelID BETWEEN 120 and 130" + , true + ); + + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java b/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java index 5811f1101..2b33eb817 100644 --- a/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java @@ -97,4 +97,29 @@ public void testOracleMultiColumnDrop() throws JSQLParserException { //assertSqlCanBeParsedAndDeparsed("ALTER TABLE foo DROP (bar, baz)"); assertSqlCanBeParsedAndDeparsed("ALTER TABLE foo DROP (bar, baz) CASCADE"); } + + @Test + public void testUniqueFunctionDrop() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("DROP FUNCTION myFunc"); + } + + @Test + public void testZeroArgDropFunction() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("DROP FUNCTION myFunc()"); + } + + @Test + public void testDropFunctionWithSimpleType() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("DROP FUNCTION myFunc(integer, varchar)"); + } + + @Test + public void testDropFunctionWithNameAndType() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("DROP FUNCTION myFunc(amount integer, name varchar)"); + } + + @Test + public void testDropFunctionWithNameAndParameterizedType() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("DROP FUNCTION myFunc(amount integer, name varchar(255))"); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java b/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java index c7f25e940..b5c8f590c 100644 --- a/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java @@ -9,32 +9,39 @@ */ package net.sf.jsqlparser.statement.insert; -import java.io.StringReader; -import java.util.Arrays; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.DoubleValue; +import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.JdbcParameter; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList; import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.select.AllColumns; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.update.UpdateSet; +import net.sf.jsqlparser.statement.values.ValuesStatement; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.StringReader; +import java.util.Arrays; + import static net.sf.jsqlparser.test.TestUtils.assertDeparse; import static net.sf.jsqlparser.test.TestUtils.assertOracleHintExists; import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static net.sf.jsqlparser.test.TestUtils.assertStatementCanBeDeparsedAs; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; public class InsertTest { @@ -56,13 +63,21 @@ public void testRegularInsert() throws JSQLParserException { getValue()); assertEquals(234, ((LongValue) ((ExpressionList) insert.getItemsList()).getExpressions(). get(2)).getValue()); - assertEquals(statement, "" + insert); + assertEquals(statement, insert.toString()); - assertDeparse(new Insert().withTable(new Table("mytable")) - .addColumns(Arrays.asList(new Column("col1"), new Column("col2"), new Column("col3"))) - .withItemsList(new ExpressionList(new JdbcParameter(), new StringValue("sadfsd"), - new LongValue().withValue(234))), - statement); + ExpressionList expressionList =new ExpressionList( + new JdbcParameter() + , new StringValue("sadfsd") + , new LongValue().withValue(234) + ); + + Select select = new Select().withSelectBody(new ValuesStatement().withExpressions(expressionList)); + + Insert insert2 = new Insert().withTable(new Table("mytable")) + .withColumns(Arrays.asList(new Column("col1"), new Column("col2"), new Column("col3"))) + .withSelect(select); + + assertDeparse(insert2, statement); statement = "INSERT INTO myschema.mytable VALUES (?, ?, 2.3)"; insert = (Insert) parserManager.parse(new StringReader(statement)); @@ -82,9 +97,9 @@ public void testInsertWithKeywordValue() throws JSQLParserException { assertEquals("mytable", insert.getTable().getName()); assertEquals(1, insert.getColumns().size()); assertEquals("col1", insert.getColumns().get(0).getColumnName()); - assertEquals("val1", - ((StringValue) ((ExpressionList) insert.getItemsList()).getExpressions().get(0)). - getValue()); + assertEquals("('val1')", + (((ExpressionList) insert.getItemsList()).getExpressions().get(0)). + toString()); assertEquals("INSERT INTO mytable (col1) VALUES ('val1')", insert.toString()); } @@ -103,11 +118,11 @@ public void testInsertFromSelect() throws JSQLParserException { assertEquals("mytable2", ((Table) ((PlainSelect) insert.getSelect().getSelectBody()).getFromItem()).getName()); - // toString uses brakets + // toString uses brackets String statementToString = "INSERT INTO mytable (col1, col2, col3) SELECT * FROM mytable2"; assertEquals(statementToString, "" + insert); - assertDeparse(new Insert().withUseValues(false).withUseSelectBrackets(false).withTable(new Table("mytable")) + assertDeparse(new Insert().withTable(new Table("mytable")) .addColumns(new Column("col1"), new Column("col2"), new Column("col3")) .withSelect(new Select().withSelectBody( new PlainSelect().addSelectItems(new AllColumns()).withFromItem(new Table("mytable2")))), @@ -135,7 +150,6 @@ public void testInsertValuesWithDuplicateElimination() throws JSQLParserExceptio Insert insert = (Insert) parserManager.parse(new StringReader(statement)); assertEquals("TEST", insert.getTable().getName()); assertEquals(2, insert.getColumns().size()); - assertTrue(insert.isUseValues()); assertEquals("ID", insert.getColumns().get(0).getColumnName()); assertEquals("COUNTER", insert.getColumns().get(1).getColumnName()); assertEquals(2, ((ExpressionList) insert.getItemsList()).getExpressions().size()); @@ -175,15 +189,24 @@ public void testInsertFromSetWithDuplicateElimination() throws JSQLParserExcepti public void testInsertMultiRowValue() throws JSQLParserException { String statement = "INSERT INTO mytable (col1, col2) VALUES (a, b), (d, e)"; assertSqlCanBeParsedAndDeparsed(statement); - assertDeparse(new Insert().withTable(new Table("mytable")) + + MultiExpressionList multiExpressionList = new MultiExpressionList() + .addExpressionLists( new ExpressionList().addExpressions(new Column("a")).addExpressions(new Column("b"))) + .addExpressionLists( new ExpressionList().addExpressions(new Column("d")).addExpressions(new Column("e"))); + + Select select = new Select().withSelectBody(new ValuesStatement().withExpressions(multiExpressionList)); + + Insert insert = new Insert().withTable(new Table("mytable")) .withColumns(Arrays.asList(new Column("col1"), new Column("col2"))) - .withItemsList(new MultiExpressionList().addExpressionLists( - new ExpressionList().addExpressions(Arrays.asList(new Column("a"), new Column("b"))), - new ExpressionList(new Column("d"), new Column("e")))), - statement); + .withSelect(select); + + assertDeparse(insert, statement); } @Test + @Disabled + //@todo: Clarify, if and why this test is supposed to fail and if it is the Parser's job to decide + //What if col1 and col2 are Array Columns? public void testInsertMultiRowValueDifferent() throws JSQLParserException { try { assertSqlCanBeParsedAndDeparsed("INSERT INTO mytable (col1, col2) VALUES (a, b), (d, e, c)"); @@ -234,8 +257,8 @@ public void testInsertSelect() throws JSQLParserException { @Test public void testInsertWithSelect() throws JSQLParserException { - assertSqlCanBeParsedAndDeparsed("INSERT INTO mytable (mycolumn) WITH a AS (SELECT mycolumn FROM mytable) SELECT mycolumn FROM a"); - assertSqlCanBeParsedAndDeparsed("INSERT INTO mytable (mycolumn) (WITH a AS (SELECT mycolumn FROM mytable) SELECT mycolumn FROM a)"); + assertSqlCanBeParsedAndDeparsed("INSERT INTO mytable (mycolumn) WITH a AS (SELECT mycolumn FROM mytable) SELECT mycolumn FROM a", true); + assertSqlCanBeParsedAndDeparsed("INSERT INTO mytable (mycolumn) (WITH a AS (SELECT mycolumn FROM mytable) SELECT mycolumn FROM a)", true); } @Test @@ -386,4 +409,153 @@ public void testInsertTableArrays4() throws JSQLParserException { public void testKeywordDefaultIssue1470() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("INSERT INTO mytable (col1, col2, col3) VALUES (?, 'sadfsd', default)"); } + + @Test + public void testInsertUnionSelectIssue1491() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "insert into table1 (tf1,tf2,tf2)\n" + + "select sf1,sf2,sf3 from s1\n" + + "union\n" + + "select rf1,rf2,rf2 from r1\n" + , true + ); + + assertSqlCanBeParsedAndDeparsed( + "insert into table1 (tf1,tf2,tf2)\n" + + "( select sf1,sf2,sf3 from s1\n" + + "union\n" + + "select rf1,rf2,rf2 from r1\n)" + , true + ); + + assertSqlCanBeParsedAndDeparsed( + "insert into table1 (tf1,tf2,tf2)\n" + + "(select sf1,sf2,sf3 from s1)" + + "union " + + "(select rf1,rf2,rf2 from r1)" + , true + ); + + assertSqlCanBeParsedAndDeparsed( + "insert into table1 (tf1,tf2,tf2)\n" + + "((select sf1,sf2,sf3 from s1)" + + "union " + + "(select rf1,rf2,rf2 from r1))" + , true + ); + + assertSqlCanBeParsedAndDeparsed( + "(with a as (select * from dual) select * from a)" + , true + ); + } + + @Test + public void testInsertOutputClause() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO dbo.EmployeeSales (LastName, FirstName, CurrentSales) \n" + + " OUTPUT INSERTED.EmployeeID,\n" + + " INSERTED.LastName, \n" + + " INSERTED.FirstName, \n" + + " INSERTED.CurrentSales,\n" + + " INSERTED.ProjectedSales\n" + + " INTO @MyTableVar \n" + + " SELECT c.LastName, c.FirstName, sp.SalesYTD \n" + + " FROM Sales.SalesPerson AS sp \n" + + " INNER JOIN Person.Person AS c \n" + + " ON sp.BusinessEntityID = c.BusinessEntityID \n" + + " WHERE sp.BusinessEntityID LIKE '2%' \n" + + " ORDER BY c.LastName, c.FirstName" + , true + ); + } + + // Samples taken from: https://www.postgresql.org/docs/current/sql-insert.html + @Test + public void testInsertOnConflictIssue1551() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO distributors (did, dname)\n" + + " VALUES (5, 'Gizmo Transglobal'), (6, 'Associated Computing, Inc')\n" + + " ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname\n" + , true + ); + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO distributors (did, dname) VALUES (7, 'Redline GmbH')\n" + + " ON CONFLICT (did) DO NOTHING" + , true + ); + + assertSqlCanBeParsedAndDeparsed( + "-- Don't update existing distributors based in a certain ZIP code\n" + + "INSERT INTO distributors AS d (did, dname) VALUES (8, 'Anvil Distribution')\n" + + " ON CONFLICT (did) DO UPDATE\n" + + " SET dname = EXCLUDED.dname || ' (formerly ' || d.dname || ')'\n" + + " WHERE d.zipcode <> '21201'" + , true + ); + + assertSqlCanBeParsedAndDeparsed( + "-- Name a constraint directly in the statement (uses associated\n" + + "-- index to arbitrate taking the DO NOTHING action)\n" + + "INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')\n" + + " ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING" + , true + ); + + assertSqlCanBeParsedAndDeparsed( + "-- This statement could infer a partial unique index on \"did\"\n" + + "-- with a predicate of \"WHERE is_active\", but it could also\n" + + "-- just use a regular unique constraint on \"did\"\n" + + "INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')\n" + + " ON CONFLICT (did) WHERE is_active DO NOTHING" + , true + ); + } + + @Test + public void insertOnConflictObjectsTest() throws JSQLParserException { + String sqlStr = + "WITH a ( a, b , c ) \n" + + "AS (SELECT 1 , 2 , 3 )\n" + + "insert into test\n" + + "select * from a"; + Insert insert = (Insert) CCJSqlParserUtil.parse( sqlStr ); + + Expression whereExpression = CCJSqlParserUtil.parseExpression("a=1", false); + Expression valueExpression = CCJSqlParserUtil.parseExpression("b/2", false); + + InsertConflictTarget conflictTarget = new InsertConflictTarget("a", null, null, null); + insert.setConflictTarget(conflictTarget); + + InsertConflictAction conflictAction = new InsertConflictAction(ConflictActionType.DO_NOTHING); + insert.setConflictAction(conflictAction); + + assertStatementCanBeDeparsedAs(insert, sqlStr + " ON CONFLICT " + conflictTarget.toString() + conflictAction.toString(), true); + + conflictTarget = new InsertConflictTarget(null, null, null, "testConstraint"); + conflictTarget = conflictTarget.withWhereExpression(whereExpression); + assertNotNull(conflictTarget.withConstraintName("a").getConstraintName()); + conflictTarget.setIndexExpression(valueExpression); + assertNotNull(conflictTarget.getIndexExpression()); + assertNotNull(conflictTarget.withIndexColumnName("b").getIndexColumnName()); + + assertNull(conflictTarget.withIndexExpression(valueExpression).getIndexColumnName()); + assertNotNull(conflictTarget.withWhereExpression(whereExpression).getWhereExpression()); + + conflictAction = new InsertConflictAction(ConflictActionType.DO_UPDATE); + conflictAction.addUpdateSet(new Column().withColumnName("a"), valueExpression); + + UpdateSet updateSet=new UpdateSet(); + updateSet.add(new Column().withColumnName("b")); + updateSet.add(valueExpression); + conflictAction=conflictAction.addUpdateSet(updateSet); + + assertNotNull( conflictAction.withWhereExpression(whereExpression).getWhereExpression() ); + assertEquals(ConflictActionType.DO_UPDATE, conflictAction.getConflictActionType()); + + insert = insert.withConflictTarget(conflictTarget).withConflictAction(conflictAction); + + assertStatementCanBeDeparsedAs(insert, sqlStr + " ON CONFLICT " + conflictTarget.toString() + conflictAction.toString(), true); + + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/HiveTest.java b/src/test/java/net/sf/jsqlparser/statement/select/HiveTest.java index 742d7c87f..8abc10494 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/HiveTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/HiveTest.java @@ -46,4 +46,14 @@ public void testLeftSemiJoin() throws Exception { assertSqlCanBeParsedAndDeparsed(sql, true); } + + @Test + public void testGroupByGroupingSets() throws Exception { + String sql = "SELECT\n" + + " C1, C2, C3, MAX(Value)\n" + + "FROM\n" + + " Sometable\n" + + "GROUP BY C1, C2, C3 GROUPING SETS ((C1, C2), (C1, C2, C3), ())"; + assertSqlCanBeParsedAndDeparsed(sql, true); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java b/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java index 5eb514d4d..ee6fc7bc2 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java @@ -131,9 +131,12 @@ public void testRecursiveBracketExpressionIssue1019() { assertEquals("IF(1=1, IF(1=1, IF(1=1, 1, 2), 2), 2)", buildRecursiveBracketExpression("IF(1=1, $1, 2)", "1", 2)); } + // maxDepth = 10 collides with the Parser Timeout = 6 seconds + // temporarily restrict it to maxDepth = 6 for the moment + // @todo: implement methods to set the Parser Timeout explicitly and on demand @Test public void testRecursiveBracketExpressionIssue1019_2() throws JSQLParserException { - doIncreaseOfParseTimeTesting("IF(1=1, $1, 2)", "1", 10); + doIncreaseOfParseTimeTesting("IF(1=1, $1, 2)", "1", 8); } @Test diff --git a/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java b/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java new file mode 100644 index 000000000..7eccca477 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java @@ -0,0 +1,44 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.test.TestUtils; +import org.junit.jupiter.api.Test; + +public class PostgresTest { + @Test + public void testExtractFunction() throws JSQLParserException { + String sqlStr = "SELECT EXTRACT(HOUR FROM TIMESTAMP '2001-02-16 20:38:40')"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + sqlStr = "SELECT EXTRACT('HOUR' FROM TIMESTAMP '2001-02-16 20:38:40')"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + sqlStr = "SELECT EXTRACT('HOURS' FROM TIMESTAMP '2001-02-16 20:38:40')"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + public void testExtractFunctionIssue1582() throws JSQLParserException { + String sqlStr = "" + + "select\n" + + " t0.operatienr\n" + + " , case\n" + + " when\n" + + " case when (t0.vc_begintijd_operatie is null or lpad((extract('hours' from t0.vc_begintijd_operatie::timestamp))::text,2,'0') ||':'|| lpad(extract('minutes' from t0.vc_begintijd_operatie::timestamp)::text,2,'0') = '00:00') then null\n" + + " else (greatest(((extract('hours' from (t0.vc_eindtijd_operatie::timestamp-t0.vc_begintijd_operatie::timestamp))*60 + extract('minutes' from (t0.vc_eindtijd_operatie::timestamp-t0.vc_begintijd_operatie::timestamp)))/60)::numeric(12,2),0))*60\n" + + " end = 0 then null\n" + + " else '25. Meer dan 4 uur'\n" + + " end \n" + + " as snijtijd_interval"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectASTTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectASTTest.java index 495d4365e..de5dbf2c7 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectASTTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectASTTest.java @@ -9,6 +9,8 @@ */ package net.sf.jsqlparser.statement.select; +import java.util.ArrayList; +import java.util.List; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserDefaultVisitor; import net.sf.jsqlparser.parser.CCJSqlParserTreeConstants; @@ -17,6 +19,7 @@ import net.sf.jsqlparser.parser.Token; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.Statement; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; @@ -179,4 +182,39 @@ public Object visit(SimpleNode node, Object data) { assertEquals(30, subSelectStart.beginColumn); assertEquals(49, subSelectEnd.endColumn); } + + @Test + public void testSelectASTExtractWithCommentsIssue1580() throws JSQLParserException { + String sql = "SELECT /* testcomment */ \r\n a, b FROM -- testcomment2 \r\n mytable \r\n order by b, c"; + SimpleNode root = (SimpleNode) CCJSqlParserUtil.parseAST(sql); + List comments = new ArrayList<>(); + + root.jjtAccept(new CCJSqlParserDefaultVisitor() { + @Override + public Object visit(SimpleNode node, Object data) { + if (node.jjtGetFirstToken().specialToken != null) { + //needed since for different nodes we got the same first token + if (!comments.contains(node.jjtGetFirstToken().specialToken)) { + comments.add(node.jjtGetFirstToken().specialToken); + } + } + return super.visit(node, data); + } + }, null); + + assertThat(comments) + .extracting(token -> token.image) + .containsExactly("/* testcomment */", "-- testcomment2 "); + } + + @Test + public void testSelectASTExtractWithCommentsIssue1580_2() throws JSQLParserException { + String sql = "/* I want this comment */\n" + + "SELECT order_detail_id, quantity\n" + + "/* But ignore this one safely */\n" + + "FROM order_details;"; + SimpleNode root = (SimpleNode) CCJSqlParserUtil.parseAST(sql); + + assertThat(root.jjtGetFirstToken().specialToken.image).isEqualTo("/* I want this comment */"); + } } 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 301891f70..14536699e 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -15,6 +15,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import net.sf.jsqlparser.parser.CCJSqlParser; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; @@ -35,6 +40,8 @@ import net.sf.jsqlparser.statement.StatementVisitorAdapter; import net.sf.jsqlparser.statement.Statements; import static net.sf.jsqlparser.test.TestUtils.*; + +import net.sf.jsqlparser.test.MemoryLeakVerifier; import org.apache.commons.io.IOUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -43,8 +50,12 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; @@ -1025,7 +1036,6 @@ public void testDistinctWithFollowingBrackets() throws JSQLParserException { assertThat(selectBody.getDistinct()) .isNotNull() .hasFieldOrPropertyWithValue("onSelectItems", null); - assertThat(selectBody.getSelectItems().get(0).toString()) .isEqualTo("(phone)"); } @@ -1878,6 +1888,12 @@ public void testBrackets2() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(stmt, false, parser -> parser.withSquareBracketQuotation(true)); } + + @Test + public void testIssue1595() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT [id] FROM [guest].[12tableName]", false, + parser -> parser.withSquareBracketQuotation(true)); + } @Test public void testBrackets3() throws JSQLParserException { @@ -2490,6 +2506,12 @@ public void testMultiValueIn4() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(stmt); } + @Test + public void selectIsolationKeywordsAsAlias() throws JSQLParserException { + String stmt = "SELECT col FROM tbl cs"; + assertSqlCanBeParsedAndDeparsed(stmt); + } + @Test public void testMultiValueInBinds() throws JSQLParserException { String stmt = "SELECT * FROM mytable WHERE (a, b) IN ((?, ?), (?, ?))"; @@ -5043,6 +5065,133 @@ public void testIgnoreNullsForWindowFunctionsIssue1429() throws JSQLParserExcept assertSqlCanBeParsedAndDeparsed("SELECT lag(mydata) IGNORE NULLS OVER (ORDER BY sortorder) AS previous_status FROM mytable"); } + @Test + @Timeout(1000) + public void testPerformanceIssue1438() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("" + + "SELECT \t* FROM TABLE_1 t1\n" + + "WHERE\n" + + "\t(((t1.COL1 = 'VALUE2' )\n" + + "\t\tAND (t1.CAL2 = 'VALUE2' ))\n" + + "\t\tAND (((1 = 1 )\n" + + "\t\t\tAND ((((((t1.id IN (940550 ,940600 ,940650 ,940700 ,940750 ,940800 ,940850 ,940900 ,940950 ,941000 ,941050 ,941100 ,941150 ,941200 ,941250 ,941300 ,941350 ,941400 ,941450 ,941500 ,941550 ,941600 ,941650 ,941700 ,941750 ,941800 ,941850 ,941900 ,941950 ,942000 ,942050 ,942100 ,942150 ,942200 ,942250 ,942300 ,942350 ,942400 ,942450 ,942500 ,942550 ,942600 ,942650 ,942700 ,942750 ,942800 ,942850 ,942900 ,942950 ,943000 ,943050 ,943100 ,943150 ,943200 ,943250 ,943300 ,943350 ,943400 ,943450 ,943500 ,943550 ,943600 ,943650 ,943700 ,943750 ,943800 ,943850 ,943900 ,943950 ,944000 ,944050 ,944100 ,944150 ,944200 ,944250 ,944300 ,944350 ,944400 ,944450 ,944500 ,944550 ,944600 ,944650 ,944700 ,944750 ,944800 ,944850 ,944900 ,944950 ,945000 ,945050 ,945100 ,945150 ,945200 ,945250 ,945300 ))\n" + + "\t\t\t\tOR (t1.id IN (945350 ,945400 ,945450 ,945500 ,945550 ,945600 ,945650 ,945700 ,945750 ,945800 ,945850 ,945900 ,945950 ,946000 ,946050 ,946100 ,946150 ,946200 ,946250 ,946300 ,946350 ,946400 ,946450 ,946500 ,946550 ,946600 ,946650 ,946700 ,946750 ,946800 ,946850 ,946900 ,946950 ,947000 ,947050 ,947100 ,947150 ,947200 ,947250 ,947300 ,947350 ,947400 ,947450 ,947500 ,947550 ,947600 ,947650 ,947700 ,947750 ,947800 ,947850 ,947900 ,947950 ,948000 ,948050 ,948100 ,948150 ,948200 ,948250 ,948300 ,948350 ,948400 ,948450 ,948500 ,948550 ,948600 ,948650 ,948700 ,948750 ,948800 ,948850 ,948900 ,948950 ,949000 ,949050 ,949100 ,949150 ,949200 ,949250 ,949300 ,949350 ,949400 ,949450 ,949500 ,949550 ,949600 ,949650 ,949700 ,949750 ,949800 ,949850 ,949900 ,949950 ,950000 ,950050 ,950100 )))\n" + + "\t\t\t\tOR (t1.id IN (950150 ,950200 ,950250 ,950300 ,950350 ,950400 ,950450 ,950500 ,950550 ,950600 ,950650 ,950700 ,950750 ,950800 ,950850 ,950900 ,950950 ,951000 ,951050 ,951100 ,951150 ,951200 ,951250 ,951300 ,951350 ,951400 ,951450 ,951500 ,951550 ,951600 ,951650 ,951700 ,951750 ,951800 ,951850 ,951900 ,951950 ,952000 ,952050 ,952100 ,952150 ,952200 ,952250 ,952300 ,952350 ,952400 ,952450 ,952500 ,952550 ,952600 ,952650 ,952700 ,952750 ,952800 ,952850 ,952900 ,952950 ,953000 ,953050 ,953100 ,953150 ,953200 ,953250 ,953300 ,953350 ,953400 ,953450 ,953500 ,953550 ,953600 ,953650 ,953700 )))\n" + + "\t\t\t\tOR (t1.id IN (953750 ,953800 ,953850 ,953900 ,953950 ,954000 ,954050 ,954100 ,954150 ,954200 ,954250 ,954300 ,954350 ,954400 ,954450 ,954500 ,954550 ,954600 ,954650 ,954700 ,954750 ,954800 ,954850 ,954900 ,954950 ,955000 ,955050 ,955100 ,955150 ,955200 ,955250 ,955300 ,955350 ,955400 ,955450 ,955500 ,955550 ,955600 ,955650 ,955700 ,955750 ,955800 ,955850 ,955900 ,955950 ,956000 ,956050 ,956100 ,956150 ,956200 ,956250 ,956300 ,956350 ,956400 ,956450 ,956500 ,956550 ,956600 ,956650 ,956700 ,956750 ,956800 ,956850 ,956900 ,956950 ,957000 ,957050 ,957100 ,957150 ,957200 ,957250 ,957300 )))\n" + + "\t\t\t\tOR (t1.id IN (944100, 944150, 944200, 944250, 944300, 944350, 944400, 944450, 944500, 944550, 944600, 944650, 944700, 944750, 944800, 944850, 944900, 944950, 945000 )))\n" + + "\t\t\t\tOR (t1.id IN (957350 ,957400 ,957450 ,957500 ,957550 ,957600 ,957650 ,957700 ,957750 ,957800 ,957850 ,957900 ,957950 ,958000 ,958050 ,958100 ,958150 ,958200 ,958250 ,958300 ,958350 ,958400 ,958450 ,958500 ,958550 ,958600 ,958650 ,958700 ,958750 ,958800 ,958850 ,958900 ,958950 ,959000 ,959050 ,959100 ,959150 ,959200 ,959250 ,959300 ,959350 ,959400 ,959450 ,959500 ,959550 ,959600 ,959650 ,959700 ,959750 ,959800 ,959850 ,959900 ,959950 ,960000 ,960050 ,960100 ,960150 ,960200 ,960250 ,960300 ,960350 ,960400 ,960450 ,960500 ,960550 ,960600 ,960650 ,960700 ,960750 ,960800 ,960850 ,960900 ,960950 ,961000 ,961050 ,961100 ,961150 ,961200 ,961250 ,961300 ,961350 ,961400 ,961450 ,961500 ,961550 ,961600 ,961650 ,961700 ,961750 ,961800 ,961850 ,961900 ,961950 ,962000 ,962050 ,962100 ))))\n" + + "\t\t\t\tOR (t1.id IN (962150 ,962200 ,962250 ,962300 ,962350 ,962400 ,962450 ,962500 ,962550 ,962600 ,962650 ,962700 ,962750 ,962800 ,962850 ,962900 ,962950 ,963000 ,963050 ,963100 ,963150 ,963200 ,963250 ,963300 ,963350 ,963400 ,963450 ,963500 ,963550 ,963600 ,963650 ,963700 ,963750 ,963800 ,963850 ,963900 ,963950 ,964000 ,964050 ,964100 ,964150 ,964200 ,964250 ,964300 ,964350 ,964400 ,964450 ,964500 ,964550 ,964600 ,964650 ,964700 ,964750 ,964800 ,964850 ,964900 ,964950 ,965000 ,965050 ,965100 ,965150 ,965200 ,965250 ,965300 ,965350 ,965400 ,965450 ,965500 ))))\n" + + "\tAND t1.COL3 IN (\n" + + "\t SELECT\n" + + "\t\t t2.COL3\n" + + "\t FROM\n" + + "\t\t TABLE_6 t6,\n" + + "\t\t TABLE_1 t5,\n" + + "\t\t TABLE_4 t4,\n" + + "\t\t TABLE_3 t3,\n" + + "\t\t TABLE_1 t2\n" + + "\t WHERE\n" + + "\t\t (((((((t5.CAL3 = T6.id)\n" + + "\t\t\t AND (t5.CAL5 = t6.CAL5))\n" + + "\t\t\t AND (t5.CAL1 = t6.CAL1))\n" + + "\t\t\t AND (t3.CAL1 IN (108500)))\n" + + "\t\t\t AND (t5.id = t2.id))\n" + + "\t\t\t AND NOT ((t6.CAL6 IN ('VALUE'))))\n" + + "\t\t\t AND ((t2.id = t3.CAL2)\n" + + "\t\t\t\t AND (t4.id = t3.CAL3))))\n" + + "ORDER BY\n" + + "\tt1.id ASC", true); + } + + @Test + @Timeout(1000) + public void testPerformanceIssue1397() throws Exception { + String sqlStr = IOUtils.toString( SelectTest.class.getResource( "/net/sf/jsqlparser/statement/select/performanceIssue1397.sql" ), Charset.defaultCharset() ); + assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + /** + * The purpose of the test is to run into a timeout and to stop the parser when this happens. + * We provide an INVALID statement for this purpose, which will fail the SIMPLE parse + * and then hang with COMPLEX parsing until the timeout occurs. + * + * We repeat that test multiple times and want to see no stale references to the Parser after timeout. + * + * @throws JSQLParserException + */ + @Test + public void testParserInterruptedByTimeout() { + String sqlStr = "" + + "SELECT \t* FROM TABLE_1 t1\n" + + "WHERE\n" + + "\t(((t1.COL1 = 'VALUE2' )\n" + + "\t\tAND (t1.CAL2 = 'VALUE2' ))\n" + + "\t\tAND (((1 = 1 )\n" + + "\t\t\tAND ((((((t1.id IN (940550 ,940600 ,940650 ,940700 ,940750 ,940800 ,940850 ,940900 ,940950 ,941000 ,941050 ,941100 ,941150 ,941200 ,941250 ,941300 ,941350 ,941400 ,941450 ,941500 ,941550 ,941600 ,941650 ,941700 ,941750 ,941800 ,941850 ,941900 ,941950 ,942000 ,942050 ,942100 ,942150 ,942200 ,942250 ,942300 ,942350 ,942400 ,942450 ,942500 ,942550 ,942600 ,942650 ,942700 ,942750 ,942800 ,942850 ,942900 ,942950 ,943000 ,943050 ,943100 ,943150 ,943200 ,943250 ,943300 ,943350 ,943400 ,943450 ,943500 ,943550 ,943600 ,943650 ,943700 ,943750 ,943800 ,943850 ,943900 ,943950 ,944000 ,944050 ,944100 ,944150 ,944200 ,944250 ,944300 ,944350 ,944400 ,944450 ,944500 ,944550 ,944600 ,944650 ,944700 ,944750 ,944800 ,944850 ,944900 ,944950 ,945000 ,945050 ,945100 ,945150 ,945200 ,945250 ,945300 ))\n" + + "\t\t\t\tOR (t1.id IN (945350 ,945400 ,945450 ,945500 ,945550 ,945600 ,945650 ,945700 ,945750 ,945800 ,945850 ,945900 ,945950 ,946000 ,946050 ,946100 ,946150 ,946200 ,946250 ,946300 ,946350 ,946400 ,946450 ,946500 ,946550 ,946600 ,946650 ,946700 ,946750 ,946800 ,946850 ,946900 ,946950 ,947000 ,947050 ,947100 ,947150 ,947200 ,947250 ,947300 ,947350 ,947400 ,947450 ,947500 ,947550 ,947600 ,947650 ,947700 ,947750 ,947800 ,947850 ,947900 ,947950 ,948000 ,948050 ,948100 ,948150 ,948200 ,948250 ,948300 ,948350 ,948400 ,948450 ,948500 ,948550 ,948600 ,948650 ,948700 ,948750 ,948800 ,948850 ,948900 ,948950 ,949000 ,949050 ,949100 ,949150 ,949200 ,949250 ,949300 ,949350 ,949400 ,949450 ,949500 ,949550 ,949600 ,949650 ,949700 ,949750 ,949800 ,949850 ,949900 ,949950 ,950000 ,950050 ,950100 )))\n" + + "\t\t\t\tOR (t1.id IN (950150 ,950200 ,950250 ,950300 ,950350 ,950400 ,950450 ,950500 ,950550 ,950600 ,950650 ,950700 ,950750 ,950800 ,950850 ,950900 ,950950 ,951000 ,951050 ,951100 ,951150 ,951200 ,951250 ,951300 ,951350 ,951400 ,951450 ,951500 ,951550 ,951600 ,951650 ,951700 ,951750 ,951800 ,951850 ,951900 ,951950 ,952000 ,952050 ,952100 ,952150 ,952200 ,952250 ,952300 ,952350 ,952400 ,952450 ,952500 ,952550 ,952600 ,952650 ,952700 ,952750 ,952800 ,952850 ,952900 ,952950 ,953000 ,953050 ,953100 ,953150 ,953200 ,953250 ,953300 ,953350 ,953400 ,953450 ,953500 ,953550 ,953600 ,953650 ,953700 )))\n" + + "\t\t\t\tOR (t1.id IN (953750 ,953800 ,953850 ,953900 ,953950 ,954000 ,954050 ,954100 ,954150 ,954200 ,954250 ,954300 ,954350 ,954400 ,954450 ,954500 ,954550 ,954600 ,954650 ,954700 ,954750 ,954800 ,954850 ,954900 ,954950 ,955000 ,955050 ,955100 ,955150 ,955200 ,955250 ,955300 ,955350 ,955400 ,955450 ,955500 ,955550 ,955600 ,955650 ,955700 ,955750 ,955800 ,955850 ,955900 ,955950 ,956000 ,956050 ,956100 ,956150 ,956200 ,956250 ,956300 ,956350 ,956400 ,956450 ,956500 ,956550 ,956600 ,956650 ,956700 ,956750 ,956800 ,956850 ,956900 ,956950 ,957000 ,957050 ,957100 ,957150 ,957200 ,957250 ,957300 )))\n" + + "\t\t\t\tOR (t1.id IN (944100, 944150, 944200, 944250, 944300, 944350, 944400, 944450, 944500, 944550, 944600, 944650, 944700, 944750, 944800, 944850, 944900, 944950, 945000 )))\n" + + "\t\t\t\tOR (t1.id IN (957350 ,957400 ,957450 ,957500 ,957550 ,957600 ,957650 ,957700 ,957750 ,957800 ,957850 ,957900 ,957950 ,958000 ,958050 ,958100 ,958150 ,958200 ,958250 ,958300 ,958350 ,958400 ,958450 ,958500 ,958550 ,958600 ,958650 ,958700 ,958750 ,958800 ,958850 ,958900 ,958950 ,959000 ,959050 ,959100 ,959150 ,959200 ,959250 ,959300 ,959350 ,959400 ,959450 ,959500 ,959550 ,959600 ,959650 ,959700 ,959750 ,959800 ,959850 ,959900 ,959950 ,960000 ,960050 ,960100 ,960150 ,960200 ,960250 ,960300 ,960350 ,960400 ,960450 ,960500 ,960550 ,960600 ,960650 ,960700 ,960750 ,960800 ,960850 ,960900 ,960950 ,961000 ,961050 ,961100 ,961150 ,961200 ,961250 ,961300 ,961350 ,961400 ,961450 ,961500 ,961550 ,961600 ,961650 ,961700 ,961750 ,961800 ,961850 ,961900 ,961950 ,962000 ,962050 ,962100 ))))\n" + + "\t\t\t\tOR (t1.id IN (962150 ,962200 ,962250 ,962300 ,962350 ,962400 ,962450 ,962500 ,962550 ,962600 ,962650 ,962700 ,962750 ,962800 ,962850 ,962900 ,962950 ,963000 ,963050 ,963100 ,963150 ,963200 ,963250 ,963300 ,963350 ,963400 ,963450 ,963500 ,963550 ,963600 ,963650 ,963700 ,963750 ,963800 ,963850 ,963900 ,963950 ,964000 ,964050 ,964100 ,964150 ,964200 ,964250 ,964300 ,964350 ,964400 ,964450 ,964500 ,964550 ,964600 ,964650 ,964700 ,964750 ,964800 ,964850 ,964900 ,964950 ,965000 ,965050 ,965100 ,965150 ,965200 ,965250 ,965300 ,965350 ,965400 ,965450 ,965500 ))))\n" + + "\tAND t1.COL3 IN (\n" + + "\t SELECT\n" + + "\t\t t2.COL3\n" + + "\t FROM\n" + + "\t\t TABLE_6 t6,\n" + + "\t\t TABLE_1 t5,\n" + + "\t\t TABLE_4 t4,\n" + + "\t\t TABLE_3 t3,\n" + + "\t\t TABLE_1 t2\n" + + "\t WHERE\n" + + "\t\t (((((((t5.CAL3 = T6.id)\n" + + "\t\t\t AND (t5.CAL5 = t6.CAL5))\n" + + "\t\t\t AND (t5.CAL1 = t6.CAL1))\n" + + "\t\t\t AND (t3.CAL1 IN (108500)))\n" + + "\t\t\t AND (t5.id = t2.id))\n" + + "\t\t\t AND NOT ((t6.CAL6 IN ('VALUE'))))\n" + + "\t\t\t AND ((t2.id = t3.CAL2)\n" + + "\t\t\t\t AND (t4.id = t3.CAL3))))\n" + + // add two redundant unmatched brackets in order to make the Simple Parser fail + // and the complex parser stuck + " )) \n" + + "ORDER BY\n" + + "\tt1.id ASC"; + + MemoryLeakVerifier verifier = new MemoryLeakVerifier(); + + int parallelThreads = Runtime.getRuntime().availableProcessors() + 1; + ExecutorService executorService = Executors.newFixedThreadPool(parallelThreads); + + for (int i=0; i 0) { message = message.substring(0, pos); diff --git a/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateTest.java b/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateTest.java index c4c780b9c..036aa64ad 100644 --- a/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateTest.java @@ -10,12 +10,15 @@ package net.sf.jsqlparser.statement.truncate; import java.io.StringReader; + import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserManager; import net.sf.jsqlparser.schema.Table; + import static net.sf.jsqlparser.test.TestUtils.assertDeparse; import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; public class TruncateTest { @@ -39,20 +42,57 @@ public void testTruncate() throws Exception { statement = "TRUNCATE TABLE mytab CASCADE"; truncate = (Truncate) parserManager.parse(new StringReader(statement)); assertEquals(statement, truncate.toString()); + + statement = "TRUNCATE TABLE ONLY mytab CASCADE"; + truncate = (Truncate) parserManager.parse(new StringReader(statement)); + assertEquals(statement, truncate.toString()); + } + + @Test + public void testTruncatePostgresqlWithoutTableName() throws Exception { + String statement = "TRUncATE myschema.mytab"; + Truncate truncate = (Truncate) parserManager.parse(new StringReader(statement)); + assertEquals("myschema", truncate.getTable().getSchemaName()); + assertEquals("myschema.mytab", truncate.getTable().getFullyQualifiedName()); + assertEquals("TRUNCATE MYSCHEMA.MYTAB", truncate.toString().toUpperCase()); + + statement = "TRUncATE mytab"; + truncate = (Truncate) parserManager.parse(new StringReader(statement)); + assertEquals("mytab", truncate.getTable().getName()); + assertEquals("TRUNCATE MYTAB", truncate.toString().toUpperCase()); + + statement = "TRUNCATE mytab CASCADE"; + truncate = (Truncate) parserManager.parse(new StringReader(statement)); + assertEquals("TRUNCATE MYTAB CASCADE", truncate.toString().toUpperCase()); } @Test public void testTruncateDeparse() throws JSQLParserException { String statement = "TRUNCATE TABLE foo"; assertSqlCanBeParsedAndDeparsed(statement); - assertDeparse(new Truncate().withTable(new Table("foo")), statement); + assertDeparse(new Truncate() + .withTable(new Table("foo")) + .withTableToken(true), statement); } @Test public void testTruncateCascadeDeparse() throws JSQLParserException { String statement = "TRUNCATE TABLE foo CASCADE"; assertSqlCanBeParsedAndDeparsed(statement); - assertDeparse(new Truncate().withTable(new Table("foo")).withCascade(true), statement); + assertDeparse(new Truncate() + .withTable(new Table("foo")) + .withTableToken(true) + .withCascade(true), statement); } + @Test + public void testTruncateOnlyDeparse() throws JSQLParserException { + String statement = "TRUNCATE TABLE ONLY foo CASCADE"; + assertSqlCanBeParsedAndDeparsed(statement); + assertDeparse(new Truncate() + .withTable(new Table("foo")) + .withCascade(true) + .withTableToken(true) + .withOnly(true), statement); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/update/UpdateTest.java b/src/test/java/net/sf/jsqlparser/statement/update/UpdateTest.java index e0044f828..f06de1401 100644 --- a/src/test/java/net/sf/jsqlparser/statement/update/UpdateTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/update/UpdateTest.java @@ -11,12 +11,14 @@ import java.io.StringReader; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.DoubleValue; import net.sf.jsqlparser.expression.JdbcParameter; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals; import net.sf.jsqlparser.parser.CCJSqlParserManager; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; import static net.sf.jsqlparser.test.TestUtils.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -26,12 +28,12 @@ public class UpdateTest { - private static CCJSqlParserManager parserManager = new CCJSqlParserManager(); + private static final CCJSqlParserManager PARSER_MANAGER = new CCJSqlParserManager(); @Test public void testUpdate() throws JSQLParserException { String statement = "UPDATE mytable set col1='as', col2=?, col3=565 Where o >= 3"; - Update update = (Update) parserManager.parse(new StringReader(statement)); + Update update = (Update) PARSER_MANAGER.parse(new StringReader(statement)); assertEquals("mytable", update.getTable().toString()); assertEquals(3, update.getUpdateSets().size()); assertEquals("col1", update.getUpdateSets().get(0).getColumns().get(0).getColumnName()); @@ -47,7 +49,7 @@ public void testUpdate() throws JSQLParserException { @Test public void testUpdateWAlias() throws JSQLParserException { String statement = "UPDATE table1 A SET A.columna = 'XXX' WHERE A.cod_table = 'YYY'"; - Update update = (Update) parserManager.parse(new StringReader(statement)); + Update update = (Update) PARSER_MANAGER.parse(new StringReader(statement)); } @Test @@ -113,7 +115,7 @@ public void testUpdateWithReturningList() throws JSQLParserException { @Test public void testUpdateDoesNotAllowLimitOffset() { String statement = "UPDATE table1 A SET A.columna = 'XXX' WHERE A.cod_table = 'YYY' LIMIT 3,4"; - assertThrows(JSQLParserException.class, () -> parserManager.parse(new StringReader(statement))); + assertThrows(JSQLParserException.class, () -> PARSER_MANAGER.parse(new StringReader(statement))); } @Test @@ -271,4 +273,48 @@ public void testUpdateMultipleModifiers() throws JSQLParserException { assertEquals(update.getModifierPriority(), UpdateModifierPriority.LOW_PRIORITY); assertTrue(update.isModifierIgnore()); } + + @Test + public void testUpdateOutputClause() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "UPDATE /* TOP (10) */ HumanResources.Employee \n" + + "SET VacationHours = VacationHours * 1.25, \n" + + " ModifiedDate = GETDATE() \n" + + "OUTPUT inserted.BusinessEntityID, \n" + + " deleted.VacationHours, \n" + + " inserted.VacationHours, \n" + + " inserted.ModifiedDate \n" + + "INTO @MyTableVar", + true + ); + + assertSqlCanBeParsedAndDeparsed( + "UPDATE Production.WorkOrder \n" + + "SET ScrapReasonID = 4 \n" + + "OUTPUT deleted.ScrapReasonID, \n" + + " inserted.ScrapReasonID, \n" + + " inserted.WorkOrderID, \n" + + " inserted.ProductID, \n" + + " p.Name \n" + + " INTO @MyTestVar \n" + + "FROM Production.WorkOrder AS wo \n" + + " INNER JOIN Production.Product AS p \n" + + " ON wo.ProductID = p.ProductID \n" + + " AND wo.ScrapReasonID= 16 \n" + + " AND p.ProductID = 733", + true + ); + } + + @Test + public void testUpdateSetsIssue1590() throws JSQLParserException { + Update update = (Update) CCJSqlParserUtil.parse("update mytable set a=5 where b = 2"); + assertEquals(1, update.getUpdateSets().size()); + update.addColumns(new Column("y")); + update.addExpressions(new DoubleValue("6")); + update.getUpdateSets().get(0).setUsingBracketsForColumns(true); + update.getUpdateSets().get(0).setUsingBracketsForValues(true); + + assertEquals("UPDATE mytable SET (a, y) = (5, 6) WHERE b = 2", update.toString()); + } } diff --git a/src/test/java/net/sf/jsqlparser/test/MemoryLeakVerifier.java b/src/test/java/net/sf/jsqlparser/test/MemoryLeakVerifier.java new file mode 100644 index 000000000..5784e5d95 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/test/MemoryLeakVerifier.java @@ -0,0 +1,116 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.test; + +/* ==================================================================== + Taken from Apache POI, with a big thanks. + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * A simple utility class that can verify that objects have been successfully garbage collected. + * + * Usage is something like + * + * private final MemoryLeakVerifier verifier = new MemoryLeakVerifier(); + + {@literal}After + void tearDown() { + verifier.assertGarbageCollected(); + } + + {@literal}Test + void someTest() { + ... + verifier.addObject(object); + } + + * + * This will verify at the end of the test if the object is actually removed by the + * garbage collector or if it lingers in memory for some reason. + * + * Idea taken from http://stackoverflow.com/a/7410460/411846 + */ +public class MemoryLeakVerifier { + private static final int MAX_GC_ITERATIONS = 50; + private static final int GC_SLEEP_TIME = 100; + + private final List> references = new ArrayList<>(); + + public void addObject(Object object) { + references.add(new WeakReference<>(object)); + } + + /** + * Attempts to perform a full garbage collection so that all weak references will be removed. Usually only + * a single GC is required, but there have been situations where some unused memory is not cleared up on the + * first pass. This method performs a full garbage collection and then validates that the weak reference + * now has been cleared. If it hasn't then the thread will sleep for 100 milliseconds and then retry up to + * 50 more times. If after this the object still has not been collected then the assertion will fail. + * + * Based upon the method described in: http://www.javaworld.com/javaworld/javatips/jw-javatip130.html + */ + public void assertGarbageCollected() { + assertGarbageCollected(MAX_GC_ITERATIONS); + } + + /** + * Used only for testing the class itself where we would like to fail faster than 5 seconds + * @param maxIterations The number of times a GC will be invoked until a possible memory leak is reported + */ + void assertGarbageCollected(int maxIterations) { + try { + for (WeakReference ref : references) { + assertGarbageCollected(ref, maxIterations); + } + } catch (InterruptedException e) { + // just ensure that we quickly return when the thread is interrupted + } + } + + private static void assertGarbageCollected(WeakReference ref, int maxIterations) throws InterruptedException { + Runtime runtime = Runtime.getRuntime(); + for (int i = 0; i < maxIterations; i++) { + runtime.runFinalization(); + runtime.gc(); + if (ref.get() == null) { + break; + } + + // Pause for a while and then go back around the loop to try again... + //EventQueue.invokeAndWait(Procedure.NoOp); // Wait for the AWT event queue to have completed processing + Thread.sleep(GC_SLEEP_TIME); + } + + assertNull(ref.get(), "Object should not exist after " + MAX_GC_ITERATIONS + " collections, but still had: " + ref.get()); + } +} + diff --git a/src/test/java/net/sf/jsqlparser/util/cnfexpression/CNFTest.java b/src/test/java/net/sf/jsqlparser/util/cnfexpression/CNFTest.java index 984b5c232..d7b1f288d 100644 --- a/src/test/java/net/sf/jsqlparser/util/cnfexpression/CNFTest.java +++ b/src/test/java/net/sf/jsqlparser/util/cnfexpression/CNFTest.java @@ -9,9 +9,12 @@ */ package net.sf.jsqlparser.util.cnfexpression; +import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class CNFTest { @@ -328,4 +331,69 @@ public void test5() throws Exception { assertEquals(expected.toString(), result.toString()); } + @Test + public void testStackOverflowIssue1576() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression( + "((3.0 >= 4.0 AND 5.0 <= 6.0) OR " + + "(7.0 < 8.0 AND 9.0 > 10.0) OR " + + "(11.0 = 11.0 AND 19.0 > 20.0) OR " + + "(17.0 = 14.0 AND 19.0 > 17.0) OR " + + "(17.0 = 18.0 AND 20.0 > 20.0) OR " + + "(17.0 = 16.0 AND 19.0 > 20.0) OR " + + "(17.0 = 18.0 AND 19.0 > 20.0) OR " + + "(17.0 = 18.0 AND 19.0 > 20.0) OR " + + "(17.0 = 22.0 AND 19.0 > 20.0) OR " + + "(18.0 = 18.0 AND 22.0 > 20.0) OR " + + "(17.0 = 18.0 AND 19.0 > 20.0) OR " + + "(18.0 = 18.0 AND 22.0 > 20.0) OR " + + "(18.0 = 19.0 AND 22.0 > 20.0) OR " + + "(17.0 = 18.0 AND 19.0 > 20.0))" + ); + Expression result = CNFConverter.convertToCNF(expr); + assertThat(result).asString().hasSize(3448827); + } + + + @Test + @Disabled + public void testStackOverflowIssue1576_veryLarge() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression( + "((3.0 >= 4.0 AND 5.0 <= 6.0) OR " + + "(7.0 < 8.0 AND 9.0 > 10.0) OR " + + "(11.0 = 11.0 AND 19.0 > 20.0) OR " + + "(17.0 = 14.0 AND 19.0 > 17.0) OR " + + "(17.0 = 18.0 AND 20.0 > 20.0) OR " + + "(17.0 = 16.0 AND 19.0 > 20.0) OR " + + "(17.0 = 18.0 AND 19.0 > 20.0) OR " + + "(17.0 = 18.0 AND 19.0 > 20.0) OR " + + "(17.0 = 22.0 AND 19.0 > 20.0) OR " + + "(18.0 = 18.0 AND 22.0 > 20.0) OR " + + "(17.0 = 18.0 AND 19.0 > 20.0) OR " + + "(18.0 = 18.0 AND 22.0 > 20.0) OR " + + "(18.0 = 19.0 AND 22.0 > 20.0) OR " + + "(117.0 = 22.0 AND 19.0 > 20.0) OR " + + "(118.0 = 18.0 AND 22.0 > 20.0) OR " + + "(117.0 = 18.0 AND 19.0 > 20.0) OR " + //+ "(118.0 = 18.0 AND 22.0 > 20.0) OR " + //+ "(118.0 = 19.0 AND 22.0 > 20.0) OR " + + "(17.0 = 18.0 AND 19.0 > 20.0))" + ); + Expression result = CNFConverter.convertToCNF(expr); + assertThat(result).asString().hasSize(33685499); + } + + @Test + public void testStackOverflowIssue1576_2() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression( + "((3.0 >= 4.0 AND 5.0 <= 6.0) OR " + + "(7.0 < 8.0 AND 9.0 > 10.0) OR " + + "(11.0 = 11.0 AND 19.0 > 20.0) OR " + + "(17.0 = 14.0 AND 19.0 > 17.0) OR " + + "(17.0 = 18.0 AND 20.0 > 20.0) OR " + + "(17.0 = 16.0 AND 19.0 > 20.0))" + ); + + Expression result = CNFConverter.convertToCNF(expr); + assertThat(result).asString().isEqualTo("(3.0 >= 4.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (3.0 >= 4.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 7.0 < 8.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 11.0 = 11.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 17.0 = 14.0 OR 20.0 > 20.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 17.0 = 18.0 OR 19.0 > 20.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 17.0 = 16.0) AND (5.0 <= 6.0 OR 9.0 > 10.0 OR 19.0 > 20.0 OR 19.0 > 17.0 OR 20.0 > 20.0 OR 19.0 > 20.0)"); + } } diff --git a/src/test/java/net/sf/jsqlparser/util/cnfexpression/CloneHelperTest.java b/src/test/java/net/sf/jsqlparser/util/cnfexpression/CloneHelperTest.java new file mode 100644 index 000000000..ee320d51a --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/cnfexpression/CloneHelperTest.java @@ -0,0 +1,60 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.cnfexpression; + +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import static java.util.stream.Collectors.toList; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.Parenthesis; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + +/** + * + * @author tw + */ +public class CloneHelperTest { + + @Test + public void testChangeBack() { + MultipleExpression ors = transform( Arrays.asList("a>b", "5=a", "b=c", "a>c")); + Expression expr = CloneHelper.changeBack(true, ors); + assertThat(expr).isInstanceOf(Parenthesis.class); + assertThat(expr.toString()).isEqualTo("(a > b OR 5 = a OR b = c OR a > c)"); + } + + @Test + public void testChangeBackOddNumberOfExpressions() { + MultipleExpression ors = transform( Arrays.asList("a>b", "5=a", "b=c", "a>c", "e b OR 5 = a OR b = c OR a > c OR e < f)"); + } + + private static MultipleExpression transform(List expressions) { + return new MultiOrExpression( + expressions.stream() + .map(expr -> { + try { + return CCJSqlParserUtil.parseCondExpression(expr); + } catch (JSQLParserException ex) { + Logger.getLogger(CloneHelperTest.class.getName()).log(Level.SEVERE, null, ex); + return null; + } + }) + .collect(toList())); + } + +} diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/InsertValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/InsertValidatorTest.java index d9b74658e..f5fe80f79 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/validator/InsertValidatorTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/InsertValidatorTest.java @@ -28,8 +28,13 @@ public void testValidationInsert() throws JSQLParserException { @Test public void testValidationInsertNotAllowed() throws JSQLParserException { String sql = "INSERT INTO tab1 (a, b, c) VALUES (5, 'val', ?)"; - validateNotAllowed(sql, 1, 1, FeaturesAllowed.SELECT.copy().add(FeaturesAllowed.JDBC), Feature.insert, - Feature.insertValues); + validateNotAllowed(sql, 1, 1, FeaturesAllowed.SELECT.copy().add(FeaturesAllowed.JDBC) + , Feature.insertValues + , Feature.setOperation + , Feature.insertFromSelect + , Feature.values + , Feature.insert + ); } @Test diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/compound_statements01.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/compound_statements01.sql new file mode 100644 index 000000000..0fce1148e --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/compound_statements01.sql @@ -0,0 +1,32 @@ +--- +-- #%L +-- JSQLParser library +-- %% +-- Copyright (C) 2004 - 2022 JSQLParser +-- %% +-- Dual licensed under GNU LGPL 2.1 or Apache License 2.0 +-- #L% +--- + DECLARE + PK_NAME VARCHAR(200); + + BEGIN + EXECUTE IMMEDIATE ('CREATE SEQUENCE "untitled_table3_seq"'); + + SELECT + cols.column_name INTO PK_NAME + FROM + all_constraints cons, + all_cons_columns cols + WHERE + cons.constraint_type = 'P' + AND cons.constraint_name = cols.constraint_name + AND cons.owner = cols.owner + AND cols.table_name = 'untitled_table3'; + + execute immediate ( + 'create or replace trigger "untitled_table3_autoinc_trg" BEFORE INSERT on "untitled_table3" for each row declare checking number := 1; begin if (:new."' || PK_NAME || '" is null) then while checking >= 1 loop select "untitled_table3_seq".nextval into :new."' || PK_NAME || '" from dual; select count("' || PK_NAME || '") into checking from "untitled_table3" where "' || PK_NAME || '" = :new."' || PK_NAME || '"; end loop; end if; end;' + ); + END + +--@FAILURE: Encountered unexpected token: "PK_NAME" recorded first on May 27, 2022, 10:27:41 PM \ No newline at end of file diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/compound_statements02.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/compound_statements02.sql new file mode 100644 index 000000000..ec47ee889 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/compound_statements02.sql @@ -0,0 +1,30 @@ +--- +-- #%L +-- JSQLParser library +-- %% +-- Copyright (C) 2004 - 2022 JSQLParser +-- %% +-- Dual licensed under GNU LGPL 2.1 or Apache License 2.0 +-- #L% +--- +DECLARE + n_emp_id EMPLOYEES.EMPLOYEE_ID%TYPE := &emp_id1; + BEGIN + DECLARE + n_emp_id employees.employee_id%TYPE := &emp_id2; + v_name employees.first_name%TYPE; + BEGIN + SELECT first_name, CASE foo WHEN 'a' THEN 1 ELSE 2 END CASE as other + INTO v_name + FROM employees + WHERE employee_id = n_emp_id; + + DBMS_OUTPUT.PUT_LINE('First name of employee ' || n_emp_id || + ' is ' || v_name); + EXCEPTION + WHEN no_data_found THEN + DBMS_OUTPUT.PUT_LINE('Employee ' || n_emp_id || ' not found'); + END; + END + +--@FAILURE: Encountered unexpected token: "n_emp_id" recorded first on May 27, 2022, 10:29:48 PM \ No newline at end of file diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/compound_statements03.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/compound_statements03.sql new file mode 100644 index 000000000..12e993aad --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/compound_statements03.sql @@ -0,0 +1,17 @@ +--- +-- #%L +-- JSQLParser library +-- %% +-- Copyright (C) 2004 - 2022 JSQLParser +-- %% +-- Dual licensed under GNU LGPL 2.1 or Apache License 2.0 +-- #L% +--- +BEGIN + SELECT + cols.column_name INTO :variable + FROM + example_table; + END + +--@FAILURE: Encountered unexpected token: "BEGIN" "BEGIN" recorded first on May 27, 2022, 10:29:48 PM \ No newline at end of file diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/keywordasidentifier04.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/keywordasidentifier04.sql index c65966c73..0535515f1 100644 --- a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/keywordasidentifier04.sql +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/keywordasidentifier04.sql @@ -14,4 +14,4 @@ union all from v$backup_piece bp) ---@FAILURE: Encountered unexpected token: "." "." recorded first on Aug 3, 2021, 7:20:08 AM \ No newline at end of file +--@SUCCESSFULLY_PARSED_AND_DEPARSED first on 3 Jun 2022, 18:48:09 \ No newline at end of file diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/performanceIssue1397.sql b/src/test/resources/net/sf/jsqlparser/statement/select/performanceIssue1397.sql new file mode 100644 index 000000000..173a34d4b --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/performanceIssue1397.sql @@ -0,0 +1,19 @@ +--- +-- #%L +-- JSQLParser library +-- %% +-- Copyright (C) 2004 - 2021 JSQLParser +-- %% +-- Dual licensed under GNU LGPL 2.1 or Apache License 2.0 +-- #L% +--- +SELECT "TABLE1"."LABEL" , + (CASE WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 002 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 005 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 2021 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 000 - Alternative Capacitor Devices', 'Registration Q4 001 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 002 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 006 - Alternative Capacitor Devices - Stuff Sample')) THEN 'Alternative Capacitor Devices' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Entry Alt Propane', 'Registration Q2 001 - Entry Alt Propane', 'Registration Q2 002 - Entry Alt Propane', 'Registration Q4 000 - Entry Alt Propane', 'Registration Q4 001 - Entry Alt Propane', 'Registration Q4 002 - Entry Alt Propane', 'Registration Q4 005 - Camper Yeah - Entry Alt')) THEN 'Entry Alt Propane' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Entry Amps', 'Registration Q2 000 - Entry Amps', 'Registration Q2 001 - Entry Amps', 'Registration Q2 002 - Entry Amps', 'Registration Q2 005 - Entry Amps', 'Registration Q4 000 - Entry Amps', 'Registration Q4 001 - Entry Amps', 'Registration Q4 002 - Entry Amps', 'Registration Q4 005 - Entry Amps', 'Registration Q4 006 - Entry Amps')) THEN 'Entry Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Fullsize Amp Alt', 'Registration Q2 001 - Fullsize Amp Alt', 'Registration Q2 002 - Fullsize Amp Alt', 'Registration Q2 005 - Fullsize Amp Alt', 'Registration Q2 2021 - Fullsize Amp Alt', 'Registration Q4 000 - Fullsize Amp Alt', 'Registration Q4 001 - Fullsize Amp Alt', 'Registration Q4 002 - Fullsize Amp Alt', 'Registration Q4 005 - Fullsize Amp Alt', 'Registration Q4 006 - Fullsize Amp Alt')) THEN 'Fullsize Amp Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Purple Device Makes - 450 Ratings', 'Registration Q2 000 - Purple Device Makes - 450 Ratings', 'Registration Q2 001 - Purple Device Makes - 450 Ratings', 'Registration Q2 002 - Purple Device Makes - 450 Ratings', 'Registration Q2 005 - Purple Device Makes - 450 Ratings', 'Registration Q2 2021 - Purple Device Makes - 450 Ratings', 'Registration Q4 000 - Purple Device Makes - 450 Ratings', 'Registration Q4 001 - Purple Device Makes - 450 Ratings', 'Registration Q4 002 - Purple Device Makes - 450 Ratings', 'Registration Q4 005 - Purple Device Makes - 450 Ratings', 'Registration Q4 006 - Purple Device Makes - 450 Ratings')) THEN 'Purple Device Makes' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 001 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 002 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q4 000 - Purple Device Makes Non-Waterproof - Any-American')) THEN 'Purple Device Makes Non-Waterproof - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Waterproof Speakers', 'Registration Q2 001 - Waterproof Speakers', 'Registration Q2 002 - Waterproof Speakers', 'Registration Q2 005 - Waterproof Speakers', 'Registration Q2 2021 - Waterproof Speakers', 'Registration Q4 000 - Waterproof Speakers', 'Registration Q4 001 - Waterproof Speakers', 'Registration Q4 002 - Waterproof Speakers', 'Registration Q4 005 - Waterproof Speakers', 'Registration Q4 006 - Waterproof Speakers')) THEN 'Waterproof Speakers' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Waterproof Makes - 450 Ratings', 'Registration Q2 001 - Waterproof Makes - 450 Ratings', 'Registration Q2 002 - Waterproof Makes - 450 Ratings', 'Registration Q2 005 - Waterproof Makes - 450 Ratings', 'Registration Q2 2021 - Waterproof Makes - 450 Ratings', 'Registration Q4 000 - Waterproof Makes - 450 Ratings', 'Registration Q4 001 - Waterproof Makes - 450 Ratings', 'Registration Q4 002 - Waterproof Makes - 450 Ratings', 'Registration Q4 005 - Waterproof Makes - 450 Ratings', 'Registration Q4 006 - Waterproof Makes - 450 Ratings')) THEN 'Waterproof Makes' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Waterproof Propane Redsize', 'Registration Q2 002 - Waterproof Propane Redsize', 'Registration Q2 005 - Waterproof Propane Redsize', 'Registration Q2 2021 - Waterproof Propane Redsize', 'Registration Q4 001 - Waterproof Propane Redsize', 'Registration Q4 002 - Waterproof Propane Redsize', 'Registration Q4 005 - Waterproof Propane Redsize', 'Registration Q4 006 - Waterproof Propane Redsize')) THEN 'Waterproof Propane Redsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Waterproof Propane Small', 'Registration Q2 002 - Waterproof Propane Small', 'Registration Q2 005 - Waterproof Propane Small', 'Registration Q4 001 - Waterproof Propane Small', 'Registration Q4 002 - Waterproof Propane Small', 'Registration Q4 005 - Waterproof Propane Small', 'Registration Q4 006 - Waterproof Propane Small')) THEN 'Waterproof Propane Small' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Red-Junk - Waterproof', 'Registration Q2 001 - Red-Junk - Waterproof', 'Registration Q2 002 - Red-Junk - Waterproof', 'Registration Q2 005 - Red-Junk - Waterproof', 'Registration Q2 2021 - Red-Junk - Waterproof', 'Registration Q4 000 - Red-Junk - Waterproof', 'Registration Q4 001 - Red-Junk - Waterproof', 'Registration Q4 002 - Red-Junk - Waterproof', 'Registration Q4 005 - Red-Junk - Waterproof', 'Registration Q4 006 - Red-Junk - Waterproof')) THEN 'Red-Junk Waterproof' WHEN ("TABLE1"."DESCRIPTION" = 'Registration Q2 2021 - Redsize Amps') THEN 'Redsize Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Near-Entry Waterproof', 'Registration Q2 001 - Near-Entry Waterproof', 'Registration Q2 002 - Near-Entry Waterproof', 'Registration Q2 005 - Near-Entry Waterproof', 'Registration Q2 2021 - Near-Entry Waterproof', 'Registration Q4 000 - Near-Entry Waterproof', 'Registration Q4 001 - Near-Entry Waterproof', 'Registration Q4 002 - Near-Entry Waterproof', 'Registration Q4 005 - Near-Entry Waterproof', 'Registration Q4 006 - Near-Entry Waterproof')) THEN 'Near-Entry Waterproof' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Home Theaters - Smooth', 'Registration Q2 001 - Home Theaters - Smooth', 'Registration Q2 002 - Home Theaters - Smooth', 'Registration Q2 005 - Home Theaters - Smooth', 'Registration Q2 2021 - Home Theaters - Smooth', 'Registration Q4 000 - Home Theaters - Smooth', 'Registration Q4 001 - Home Theaters - Smooth', 'Registration Q4 002 - Home Theaters - Smooth', 'Registration Q4 005 - Home Theaters - Smooth', 'Registration Q4 006 - Home Theaters - Smooth')) THEN 'Home Theaters - Smooth' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Home Theaters - Fullsize', 'Registration Q2 001 - Home Theaters - Fullsize', 'Registration Q2 002 - Home Theaters - Fullsize', 'Registration Q2 005 - Home Theaters - Fullsize', 'Registration Q2 2021 - Home Theaters - Fullsize', 'Registration Q4 000 - Home Theaters - Fullsize', 'Registration Q4 001 - Home Theaters - Fullsize', 'Registration Q4 002 - Home Theaters - Fullsize', 'Registration Q4 005 - Home Theaters - Fullsize', 'Registration Q4 006 - Home Theaters - Fullsize')) THEN 'Home Theaters - Fullsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Home Theaters - Fullsize - Half Full', 'Registration Q2 001 - Home Theaters - Fullsize - Half Full', 'Registration Q2 002 - Home Theaters - Fullsize - Half Full', 'Registration Q2 005 - Home Theaters - Fullsize - Half Full', 'Registration Q2 2021 - Home Theaters - Fullsize - Half Full', 'Registration Q4 000 - Home Theaters - Fullsize - Half Full', 'Registration Q4 001 - Home Theaters - Fullsize - Half Full', 'Registration Q4 002 - Home Theaters - Fullsize - Half Full', 'Registration Q4 005 - Home Theaters - Fullsize - Half Full')) THEN 'Home Theaters - Fullsize - Half Full' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Small Amps', 'Registration Q1 001 - Small Amps', 'Registration Q1 002 - Small Amps', 'Registration Q1 005 - Small Amps', 'Registration Q1 006 - Small Amps', 'Registration Q1 2021 - Small Amps', 'Registration Q2 000 - Small Amps', 'Registration Q2 001 - Small Amps', 'Registration Q2 002 - Small Amps', 'Registration Q2 005 - Small Amps', 'Registration Q2 2021 - Small Amps', 'Registration Q3 000 - Small Amps', 'Registration Q3 001 - Small Amps', 'Registration Q3 002 - Small Amps', 'Registration Q3 006 - Small Amps', 'Registration Q4 000 - Small Amps', 'Registration Q4 001 - Small Amps', 'Registration Q4 002 - Small Amps', 'Registration Q4 005 - Small Amps', 'Registration Q4 006 - Small Amps')) THEN 'Small Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Small-Entry Amps - Any-American', 'Registration Q2 001 - Small-Entry Amps - Any-American', 'Registration Q2 002 - Small-Entry Amps - Any-American', 'Registration Q4 000 - Small-Entry Amps - Any-American')) THEN 'Small-Entry Amps - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 005 - Camper Yeah - Entry Alt', 'Registration Q4 006 - Camper Yeah - Entry Alt')) THEN 'Camper Yeah - Entry Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 2021 - Camper Yeah - Fullsize', 'Registration Q4 006 - Camper Yeah - Fullsize')) THEN 'Camper Yeah - Fullsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Camper Yeah - Redsize', 'Registration Q2 001 - Camper Yeah - Redsize', 'Registration Q2 002 - Camper Yeah - Redsize', 'Registration Q2 005 - Camper Yeah - Redsize', 'Registration Q2 2021 - Camper Yeah - Redsize', 'Registration Q4 000 - Camper Yeah - Redsize', 'Registration Q4 001 - Camper Yeah - Redsize', 'Registration Q4 002 - Camper Yeah - Redsize', 'Registration Q4 005 - Camper Yeah - Redsize', 'Registration Q4 006 - Camper Yeah - Redsize')) THEN 'Camper Yeah - Redsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Camper Yeah - Small', 'Registration Q1 001 - Camper Yeah - Small', 'Registration Q1 002 - Camper Yeah - Small', 'Registration Q1 005 - Camper Yeah - Small', 'Registration Q1 006 - Camper Yeah - Small', 'Registration Q1 2021 - Camper Yeah - Small', 'Registration Q2 000 - Camper Yeah - Small', 'Registration Q2 001 - Camper Yeah - Small', 'Registration Q2 002 - Camper Yeah - Small', 'Registration Q2 005 - Camper Yeah - Small', 'Registration Q2 2021 - Camper Yeah - Small', 'Registration Q3 000 - Camper Yeah - Small', 'Registration Q3 001 - Camper Yeah - Small', 'Registration Q3 002 - Camper Yeah - Small', 'Registration Q3 006 - Camper Yeah - Small', 'Registration Q4 000 - Camper Yeah - Small', 'Registration Q4 001 - Camper Yeah - Small', 'Registration Q4 002 - Camper Yeah - Small', 'Registration Q4 005 - Camper Yeah - Small', 'Registration Q4 006 - Camper Yeah - Small')) THEN 'Camper Yeah - Small' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Camper Yeah - Small - Any-American', 'Registration Q2 001 - Camper Yeah - Small - Any-American', 'Registration Q2 002 - Camper Yeah - Small - Any-American', 'Registration Q4 000 - Camper Yeah - Small - Any-American')) THEN 'Camper Yeah - Small - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Camper Yeah - Premium Redsize Alt', 'Registration Q4 000 - Camper Yeah - Premium Redsize Alt')) THEN 'Camper Yeah Premium Redsize Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 002 - Camper Wagon', 'Registration Q2 005 - Camper Wagon', 'Registration Q2 2021 - Camper Wagon', 'Registration Q4 002 - Camper Wagon', 'Registration Q4 005 - Camper Wagon', 'Registration Q4 006 - Camper Wagon')) THEN 'Camper Wagon' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 005 - Campers Amps', 'Registration Q2 2021 - Campers Amps', 'Registration Q4 005 - Campers Amps', 'Registration Q4 006 - Campers Amps')) THEN 'Campers Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Campery', 'Registration Q2 001 - Campery', 'Registration Q2 002 - Campery', 'Registration Q2 005 - Campery', 'Registration Q2 2021 - Campery', 'Registration Q4 000 - Campery', 'Registration Q4 001 - Campery', 'Registration Q4 002 - Campery', 'Registration Q4 005 - Campery', 'Registration Q4 006 - Campery')) THEN 'Campery' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Upper Reddle Alt', 'Registration Q1 001 - Upper Reddle Alt', 'Registration Q1 002 - Upper Reddle Alt', 'Registration Q1 005 - Upper Reddle Alt', 'Registration Q1 006 - Upper Reddle Alt', 'Registration Q1 2021 - Upper Reddle Alt', 'Registration Q2 000 - Upper Reddle Alt', 'Registration Q2 001 - Upper Reddle Alt', 'Registration Q2 002 - Upper Reddle Alt', 'Registration Q2 005 - Upper Reddle Alt', 'Registration Q3 000 - Upper Reddle Alt', 'Registration Q3 001 - Upper Reddle Alt', 'Registration Q3 002 - Upper Reddle Alt', 'Registration Q3 006 - Upper Reddle Alt', 'Registration Q4 000 - Upper Reddle Alt', 'Registration Q4 001 - Upper Reddle Alt', 'Registration Q4 002 - Upper Reddle Alt', 'Registration Q4 005 - Upper Reddle Alt', 'Registration Q4 006 - Upper Reddle Alt')) THEN 'Upper Reddle Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Upper Reddle Alt - Any-American', 'Registration Q2 001 - Upper Reddle Alt - Any-American', 'Registration Q2 002 - Upper Reddle Alt - Any-American', 'Registration Q4 000 - Upper Reddle Alt - Any-American')) THEN 'Upper Reddle Alt - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Fires - Smooth', 'Registration Q2 001 - Fires - Smooth', 'Registration Q2 002 - Fires - Smooth', 'Registration Q2 005 - Fires - Smooth', 'Registration Q2 2021 - Fires - Smooth', 'Registration Q4 000 - Fires - Smooth', 'Registration Q4 001 - Fires - Smooth', 'Registration Q4 002 - Fires - Smooth', 'Registration Q4 005 - Fires - Smooth', 'Registration Q4 006 - Fires - Smooth')) THEN 'Fires - Smooth' ELSE "TABLE1"."DESCRIPTION" END) AS "SEGMENT", + (CASE WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Entry Amps', 'Registration Q1 000 - Purple Device Makes - 450 Ratings', 'Registration Q1 000 - Small Amps', 'Registration Q1 000 - Camper Yeah - Small', 'Registration Q1 000 - Upper Reddle Alt')) THEN '000 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Entry Alt Propane', 'Registration Q2 000 - Entry Amps', 'Registration Q2 000 - Fullsize Amp Alt', 'Registration Q2 000 - Purple Device Makes - 450 Ratings', 'Registration Q2 000 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 000 - Waterproof Speakers', 'Registration Q2 000 - Waterproof Makes - 450 Ratings', 'Registration Q2 000 - Red-Junk - Waterproof', 'Registration Q2 000 - Near-Entry Waterproof', 'Registration Q2 000 - Home Theaters - Smooth', 'Registration Q2 000 - Home Theaters - Fullsize', 'Registration Q2 000 - Home Theaters - Fullsize - Half Full', 'Registration Q2 000 - Small Amps', 'Registration Q2 000 - Small-Entry Amps - Any-American', 'Registration Q2 000 - Camper Yeah - Redsize', 'Registration Q2 000 - Camper Yeah - Premium Redsize Alt', 'Registration Q2 000 - Camper Yeah - Small', 'Registration Q2 000 - Camper Yeah - Small - Any-American', 'Registration Q2 000 - Campery', 'Registration Q2 000 - Upper Reddle Alt', 'Registration Q2 000 - Upper Reddle Alt - Any-American', 'Registration Q2 000 - Fires - Smooth')) THEN '000 Q2' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q3 000 - Small Amps', 'Registration Q3 000 - Camper Yeah - Small', 'Registration Q3 000 - Upper Reddle Alt')) THEN '000 Q3' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q4 000 - Alternative Capacitor Devices', 'Registration Q4 000 - Entry Alt Propane', 'Registration Q4 000 - Entry Amps', 'Registration Q4 000 - Fullsize Amp Alt', 'Registration Q4 000 - Purple Device Makes - 450 Ratings', 'Registration Q4 000 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q4 000 - Waterproof Speakers', 'Registration Q4 000 - Waterproof Makes - 450 Ratings', 'Registration Q4 000 - Red-Junk - Waterproof', 'Registration Q4 000 - Near-Entry Waterproof', 'Registration Q4 000 - Home Theaters - Smooth', 'Registration Q4 000 - Home Theaters - Fullsize', 'Registration Q4 000 - Home Theaters - Fullsize - Half Full', 'Registration Q4 000 - Small Amps', 'Registration Q4 000 - Small-Entry Amps - Any-American', 'Registration Q4 000 - Camper Yeah - Redsize', 'Registration Q4 000 - Camper Yeah - Premium Redsize Alt', 'Registration Q4 000 - Camper Yeah - Small', 'Registration Q4 000 - Camper Yeah - Small - Any-American', 'Registration Q4 000 - Campery', 'Registration Q4 000 - Upper Reddle Alt', 'Registration Q4 000 - Upper Reddle Alt - Any-American', 'Registration Q4 000 - Fires - Smooth')) THEN '000 Q4' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 001 - Small Amps', 'Registration Q1 001 - Camper Yeah - Small', 'Registration Q1 001 - Upper Reddle Alt')) THEN '001 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 001 - Entry Alt Propane', 'Registration Q2 001 - Entry Amps', 'Registration Q2 001 - Fullsize Amp Alt', 'Registration Q2 001 - Purple Device Makes - 450 Ratings', 'Registration Q2 001 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 001 - Waterproof Speakers', 'Registration Q2 001 - Waterproof Makes - 450 Ratings', 'Registration Q2 001 - Waterproof Propane Redsize', 'Registration Q2 001 - Waterproof Propane Small', 'Registration Q2 001 - Red-Junk - Waterproof', 'Registration Q2 001 - Near-Entry Waterproof', 'Registration Q2 001 - Home Theaters - Smooth', 'Registration Q2 001 - Home Theaters - Fullsize', 'Registration Q2 001 - Home Theaters - Fullsize - Half Full', 'Registration Q2 001 - Small Amps', 'Registration Q2 001 - Small-Entry Amps - Any-American', 'Registration Q2 001 - Camper Yeah - Redsize', 'Registration Q2 001 - Camper Yeah - Small', 'Registration Q2 001 - Camper Yeah - Small - Any-American', 'Registration Q2 001 - Campery', 'Registration Q2 001 - Upper Reddle Alt', 'Registration Q2 001 - Upper Reddle Alt - Any-American', 'Registration Q2 001 - Fires - Smooth')) THEN '001 Q2' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q3 001 - Small Amps', 'Registration Q3 001 - Camper Yeah - Small', 'Registration Q3 001 - Upper Reddle Alt')) THEN '001 Q3' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q4 001 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 001 - Entry Alt Propane', 'Registration Q4 001 - Entry Amps', 'Registration Q4 001 - Fullsize Amp Alt', 'Registration Q4 001 - Purple Device Makes - 450 Ratings', 'Registration Q4 001 - Waterproof Speakers', 'Registration Q4 001 - Waterproof Makes - 450 Ratings', 'Registration Q4 001 - Waterproof Propane Redsize', 'Registration Q4 001 - Waterproof Propane Small', 'Registration Q4 001 - Red-Junk - Waterproof', 'Registration Q4 001 - Near-Entry Waterproof', 'Registration Q4 001 - Home Theaters - Smooth', 'Registration Q4 001 - Home Theaters - Fullsize', 'Registration Q4 001 - Home Theaters - Fullsize - Half Full', 'Registration Q4 001 - Small Amps', 'Registration Q4 001 - Camper Yeah - Redsize', 'Registration Q4 001 - Camper Yeah - Small', 'Registration Q4 001 - Campery', 'Registration Q4 001 - Upper Reddle Alt', 'Registration Q4 001 - Fires - Smooth')) THEN '001 Q4' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 002 - Small Amps', 'Registration Q1 002 - Camper Yeah - Small', 'Registration Q1 002 - Upper Reddle Alt')) THEN '002 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 002 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 002 - Entry Alt Propane', 'Registration Q2 002 - Entry Amps', 'Registration Q2 002 - Fullsize Amp Alt', 'Registration Q2 002 - Purple Device Makes - 450 Ratings', 'Registration Q2 002 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 002 - Waterproof Speakers', 'Registration Q2 002 - Waterproof Makes - 450 Ratings', 'Registration Q2 002 - Waterproof Propane Redsize', 'Registration Q2 002 - Waterproof Propane Small', 'Registration Q2 002 - Red-Junk - Waterproof', 'Registration Q2 002 - Near-Entry Waterproof', 'Registration Q2 002 - Home Theaters - Smooth', 'Registration Q2 002 - Home Theaters - Fullsize', 'Registration Q2 002 - Home Theaters - Fullsize - Half Full', 'Registration Q2 002 - Small Amps', 'Registration Q2 002 - Small-Entry Amps - Any-American', 'Registration Q2 002 - Camper Yeah - Redsize', 'Registration Q2 002 - Camper Yeah - Small', 'Registration Q2 002 - Camper Yeah - Small - Any-American', 'Registration Q2 002 - Camper Wagon', 'Registration Q2 002 - Campery', 'Registration Q2 002 - Upper Reddle Alt', 'Registration Q2 002 - Upper Reddle Alt - Any-American', 'Registration Q2 002 - Fires - Smooth')) THEN '002 Q2' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q3 002 - Small Amps', 'Registration Q3 002 - Camper Yeah - Small', 'Registration Q3 002 - Upper Reddle Alt')) THEN '002 Q3' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q4 002 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 002 - Entry Alt Propane', 'Registration Q4 002 - Entry Amps', 'Registration Q4 002 - Fullsize Amp Alt', 'Registration Q4 002 - Purple Device Makes - 450 Ratings', 'Registration Q4 002 - Waterproof Speakers', 'Registration Q4 002 - Waterproof Makes - 450 Ratings', 'Registration Q4 002 - Waterproof Propane Redsize', 'Registration Q4 002 - Waterproof Propane Small', 'Registration Q4 002 - Red-Junk - Waterproof', 'Registration Q4 002 - Near-Entry Waterproof', 'Registration Q4 002 - Home Theaters - Smooth', 'Registration Q4 002 - Home Theaters - Fullsize', 'Registration Q4 002 - Home Theaters - Fullsize - Half Full', 'Registration Q4 002 - Small Amps', 'Registration Q4 002 - Camper Yeah - Redsize', 'Registration Q4 002 - Camper Yeah - Small', 'Registration Q4 002 - Camper Wagon', 'Registration Q4 002 - Campery', 'Registration Q4 002 - Upper Reddle Alt', 'Registration Q4 002 - Fires - Smooth')) THEN '002 Q4' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 005 - Small Amps', 'Registration Q1 005 - Camper Yeah - Small', 'Registration Q1 005 - Upper Reddle Alt')) THEN '005 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 005 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 005 - Entry Amps', 'Registration Q2 005 - Fullsize Amp Alt', 'Registration Q2 005 - Purple Device Makes - 450 Ratings', 'Registration Q2 005 - Waterproof Speakers', 'Registration Q2 005 - Waterproof Makes - 450 Ratings', 'Registration Q2 005 - Waterproof Propane Redsize', 'Registration Q2 005 - Waterproof Propane Small', 'Registration Q2 005 - Red-Junk - Waterproof', 'Registration Q2 005 - Near-Entry Waterproof', 'Registration Q2 005 - Home Theaters - Smooth', 'Registration Q2 005 - Home Theaters - Fullsize', 'Registration Q2 005 - Home Theaters - Fullsize - Half Full', 'Registration Q2 005 - Small Amps', 'Registration Q2 005 - Camper Yeah - Entry Alt', 'Registration Q2 005 - Camper Yeah - Redsize', 'Registration Q2 005 - Camper Yeah - Small', 'Registration Q2 005 - Camper Wagon', 'Registration Q2 005 - Campers Amps', 'Registration Q2 005 - Campery', 'Registration Q2 005 - Upper Reddle Alt', 'Registration Q2 005 - Fires - Smooth')) THEN '005 Q2' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q4 005 - Entry Amps', 'Registration Q4 005 - Fullsize Amp Alt', 'Registration Q4 005 - Purple Device Makes - 450 Ratings', 'Registration Q4 005 - Waterproof Speakers', 'Registration Q4 005 - Waterproof Makes - 450 Ratings', 'Registration Q4 005 - Waterproof Propane Redsize', 'Registration Q4 005 - Waterproof Propane Small', 'Registration Q4 005 - Red-Junk - Waterproof', 'Registration Q4 005 - Near-Entry Waterproof', 'Registration Q4 005 - Home Theaters - Smooth', 'Registration Q4 005 - Home Theaters - Fullsize', 'Registration Q4 005 - Home Theaters - Fullsize - Half Full', 'Registration Q4 005 - Small Amps', 'Registration Q4 005 - Camper Yeah - Entry Alt', 'Registration Q4 005 - Camper Yeah - Redsize', 'Registration Q4 005 - Camper Yeah - Small', 'Registration Q4 005 - Camper Wagon', 'Registration Q4 005 - Campers Amps', 'Registration Q4 005 - Campery', 'Registration Q4 005 - Upper Reddle Alt', 'Registration Q4 005 - Fires - Smooth')) THEN '005 Q4' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 006 - Small Amps', 'Registration Q1 006 - Camper Yeah - Small', 'Registration Q1 006 - Upper Reddle Alt')) THEN '006 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q3 006 - Small Amps', 'Registration Q3 006 - Camper Yeah - Small', 'Registration Q3 006 - Upper Reddle Alt')) THEN '006 Q3' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q4 006 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 006 - Entry Amps', 'Registration Q4 006 - Fullsize Amp Alt', 'Registration Q4 006 - Purple Device Makes - 450 Ratings', 'Registration Q4 006 - Waterproof Speakers', 'Registration Q4 006 - Waterproof Makes - 450 Ratings', 'Registration Q4 006 - Waterproof Propane Redsize', 'Registration Q4 006 - Waterproof Propane Small', 'Registration Q4 006 - Red-Junk - Waterproof', 'Registration Q4 006 - Near-Entry Waterproof', 'Registration Q4 006 - Home Theaters - Smooth', 'Registration Q4 006 - Home Theaters - Fullsize', 'Registration Q4 006 - Small Amps', 'Registration Q4 006 - Camper Yeah - Entry Alt', 'Registration Q4 006 - Camper Yeah - Fullsize', 'Registration Q4 006 - Camper Yeah - Redsize', 'Registration Q4 006 - Camper Yeah - Small', 'Registration Q4 006 - Camper Wagon', 'Registration Q4 006 - Campers Amps', 'Registration Q4 006 - Campery', 'Registration Q4 006 - Upper Reddle Alt', 'Registration Q4 006 - Fires - Smooth')) THEN '006 Q4' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 2021 - Small Amps', 'Registration Q1 2021 - Camper Yeah - Small', 'Registration Q1 2021 - Upper Reddle Alt')) THEN '2021 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 2021 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 2021 - Fullsize Amp Alt', 'Registration Q2 2021 - Purple Device Makes - 450 Ratings', 'Registration Q2 2021 - Waterproof Speakers', 'Registration Q2 2021 - Waterproof Makes - 450 Ratings', 'Registration Q2 2021 - Waterproof Propane Redsize', 'Registration Q2 2021 - Red-Junk - Waterproof', 'Registration Q2 2021 - Redsize Amps', 'Registration Q2 2021 - Near-Entry Waterproof', 'Registration Q2 2021 - Home Theaters - Smooth', 'Registration Q2 2021 - Home Theaters - Fullsize', 'Registration Q2 2021 - Home Theaters - Fullsize - Half Full', 'Registration Q2 2021 - Small Amps', 'Registration Q2 2021 - Camper Yeah - Fullsize', 'Registration Q2 2021 - Camper Yeah - Redsize', 'Registration Q2 2021 - Camper Yeah - Small', 'Registration Q2 2021 - Camper Wagon', 'Registration Q2 2021 - Campers Amps', 'Registration Q2 2021 - Campery', 'Registration Q2 2021 - Fires - Smooth')) THEN '2021 Q2' ELSE "TABLE1"."DESCRIPTION" END) AS "Study_Quarter/Year", + COUNT(DISTINCT "TABLE2"."ID") AS "ctd:ID:ok" +FROM "SCHEMA1"."TABLE2" "TABLE2" + INNER JOIN "SCHEMA1"."TABLE1" "TABLE1" ON ("TABLE2"."ID" = "TABLE1"."ID") +WHERE (((CASE WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 002 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 005 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 2021 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 000 - Alternative Capacitor Devices', 'Registration Q4 001 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 002 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 006 - Alternative Capacitor Devices - Stuff Sample')) THEN 'Alternative Capacitor Devices' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Entry Alt Propane', 'Registration Q2 001 - Entry Alt Propane', 'Registration Q2 002 - Entry Alt Propane', 'Registration Q4 000 - Entry Alt Propane', 'Registration Q4 001 - Entry Alt Propane', 'Registration Q4 002 - Entry Alt Propane', 'Registration Q4 005 - Camper Yeah - Entry Alt')) THEN 'Entry Alt Propane' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Entry Amps', 'Registration Q2 000 - Entry Amps', 'Registration Q2 001 - Entry Amps', 'Registration Q2 002 - Entry Amps', 'Registration Q2 005 - Entry Amps', 'Registration Q4 000 - Entry Amps', 'Registration Q4 001 - Entry Amps', 'Registration Q4 002 - Entry Amps', 'Registration Q4 005 - Entry Amps', 'Registration Q4 006 - Entry Amps')) THEN 'Entry Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Fullsize Amp Alt', 'Registration Q2 001 - Fullsize Amp Alt', 'Registration Q2 002 - Fullsize Amp Alt', 'Registration Q2 005 - Fullsize Amp Alt', 'Registration Q2 2021 - Fullsize Amp Alt', 'Registration Q4 000 - Fullsize Amp Alt', 'Registration Q4 001 - Fullsize Amp Alt', 'Registration Q4 002 - Fullsize Amp Alt', 'Registration Q4 005 - Fullsize Amp Alt', 'Registration Q4 006 - Fullsize Amp Alt')) THEN 'Fullsize Amp Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Purple Device Makes - 450 Ratings', 'Registration Q2 000 - Purple Device Makes - 450 Ratings', 'Registration Q2 001 - Purple Device Makes - 450 Ratings', 'Registration Q2 002 - Purple Device Makes - 450 Ratings', 'Registration Q2 005 - Purple Device Makes - 450 Ratings', 'Registration Q2 2021 - Purple Device Makes - 450 Ratings', 'Registration Q4 000 - Purple Device Makes - 450 Ratings', 'Registration Q4 001 - Purple Device Makes - 450 Ratings', 'Registration Q4 002 - Purple Device Makes - 450 Ratings', 'Registration Q4 005 - Purple Device Makes - 450 Ratings', 'Registration Q4 006 - Purple Device Makes - 450 Ratings')) THEN 'Purple Device Makes' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 001 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 002 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q4 000 - Purple Device Makes Non-Waterproof - Any-American')) THEN 'Purple Device Makes Non-Waterproof - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Waterproof Speakers', 'Registration Q2 001 - Waterproof Speakers', 'Registration Q2 002 - Waterproof Speakers', 'Registration Q2 005 - Waterproof Speakers', 'Registration Q2 2021 - Waterproof Speakers', 'Registration Q4 000 - Waterproof Speakers', 'Registration Q4 001 - Waterproof Speakers', 'Registration Q4 002 - Waterproof Speakers', 'Registration Q4 005 - Waterproof Speakers', 'Registration Q4 006 - Waterproof Speakers')) THEN 'Waterproof Speakers' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Waterproof Makes - 450 Ratings', 'Registration Q2 001 - Waterproof Makes - 450 Ratings', 'Registration Q2 002 - Waterproof Makes - 450 Ratings', 'Registration Q2 005 - Waterproof Makes - 450 Ratings', 'Registration Q2 2021 - Waterproof Makes - 450 Ratings', 'Registration Q4 000 - Waterproof Makes - 450 Ratings', 'Registration Q4 001 - Waterproof Makes - 450 Ratings', 'Registration Q4 002 - Waterproof Makes - 450 Ratings', 'Registration Q4 005 - Waterproof Makes - 450 Ratings', 'Registration Q4 006 - Waterproof Makes - 450 Ratings')) THEN 'Waterproof Makes' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Waterproof Propane Redsize', 'Registration Q2 002 - Waterproof Propane Redsize', 'Registration Q2 005 - Waterproof Propane Redsize', 'Registration Q2 2021 - Waterproof Propane Redsize', 'Registration Q4 001 - Waterproof Propane Redsize', 'Registration Q4 002 - Waterproof Propane Redsize', 'Registration Q4 005 - Waterproof Propane Redsize', 'Registration Q4 006 - Waterproof Propane Redsize')) THEN 'Waterproof Propane Redsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Waterproof Propane Small', 'Registration Q2 002 - Waterproof Propane Small', 'Registration Q2 005 - Waterproof Propane Small', 'Registration Q4 001 - Waterproof Propane Small', 'Registration Q4 002 - Waterproof Propane Small', 'Registration Q4 005 - Waterproof Propane Small', 'Registration Q4 006 - Waterproof Propane Small')) THEN 'Waterproof Propane Small' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Red-Junk - Waterproof', 'Registration Q2 001 - Red-Junk - Waterproof', 'Registration Q2 002 - Red-Junk - Waterproof', 'Registration Q2 005 - Red-Junk - Waterproof', 'Registration Q2 2021 - Red-Junk - Waterproof', 'Registration Q4 000 - Red-Junk - Waterproof', 'Registration Q4 001 - Red-Junk - Waterproof', 'Registration Q4 002 - Red-Junk - Waterproof', 'Registration Q4 005 - Red-Junk - Waterproof', 'Registration Q4 006 - Red-Junk - Waterproof')) THEN 'Red-Junk Waterproof' WHEN ("TABLE1"."DESCRIPTION" = 'Registration Q2 2021 - Redsize Amps') THEN 'Redsize Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Near-Entry Waterproof', 'Registration Q2 001 - Near-Entry Waterproof', 'Registration Q2 002 - Near-Entry Waterproof', 'Registration Q2 005 - Near-Entry Waterproof', 'Registration Q2 2021 - Near-Entry Waterproof', 'Registration Q4 000 - Near-Entry Waterproof', 'Registration Q4 001 - Near-Entry Waterproof', 'Registration Q4 002 - Near-Entry Waterproof', 'Registration Q4 005 - Near-Entry Waterproof', 'Registration Q4 006 - Near-Entry Waterproof')) THEN 'Near-Entry Waterproof' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Home Theaters - Smooth', 'Registration Q2 001 - Home Theaters - Smooth', 'Registration Q2 002 - Home Theaters - Smooth', 'Registration Q2 005 - Home Theaters - Smooth', 'Registration Q2 2021 - Home Theaters - Smooth', 'Registration Q4 000 - Home Theaters - Smooth', 'Registration Q4 001 - Home Theaters - Smooth', 'Registration Q4 002 - Home Theaters - Smooth', 'Registration Q4 005 - Home Theaters - Smooth', 'Registration Q4 006 - Home Theaters - Smooth')) THEN 'Home Theaters - Smooth' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Home Theaters - Fullsize', 'Registration Q2 001 - Home Theaters - Fullsize', 'Registration Q2 002 - Home Theaters - Fullsize', 'Registration Q2 005 - Home Theaters - Fullsize', 'Registration Q2 2021 - Home Theaters - Fullsize', 'Registration Q4 000 - Home Theaters - Fullsize', 'Registration Q4 001 - Home Theaters - Fullsize', 'Registration Q4 002 - Home Theaters - Fullsize', 'Registration Q4 005 - Home Theaters - Fullsize', 'Registration Q4 006 - Home Theaters - Fullsize')) THEN 'Home Theaters - Fullsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Home Theaters - Fullsize - Half Full', 'Registration Q2 001 - Home Theaters - Fullsize - Half Full', 'Registration Q2 002 - Home Theaters - Fullsize - Half Full', 'Registration Q2 005 - Home Theaters - Fullsize - Half Full', 'Registration Q2 2021 - Home Theaters - Fullsize - Half Full', 'Registration Q4 000 - Home Theaters - Fullsize - Half Full', 'Registration Q4 001 - Home Theaters - Fullsize - Half Full', 'Registration Q4 002 - Home Theaters - Fullsize - Half Full', 'Registration Q4 005 - Home Theaters - Fullsize - Half Full')) THEN 'Home Theaters - Fullsize - Half Full' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Small Amps', 'Registration Q1 001 - Small Amps', 'Registration Q1 002 - Small Amps', 'Registration Q1 005 - Small Amps', 'Registration Q1 006 - Small Amps', 'Registration Q1 2021 - Small Amps', 'Registration Q2 000 - Small Amps', 'Registration Q2 001 - Small Amps', 'Registration Q2 002 - Small Amps', 'Registration Q2 005 - Small Amps', 'Registration Q2 2021 - Small Amps', 'Registration Q3 000 - Small Amps', 'Registration Q3 001 - Small Amps', 'Registration Q3 002 - Small Amps', 'Registration Q3 006 - Small Amps', 'Registration Q4 000 - Small Amps', 'Registration Q4 001 - Small Amps', 'Registration Q4 002 - Small Amps', 'Registration Q4 005 - Small Amps', 'Registration Q4 006 - Small Amps')) THEN 'Small Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Small-Entry Amps - Any-American', 'Registration Q2 001 - Small-Entry Amps - Any-American', 'Registration Q2 002 - Small-Entry Amps - Any-American', 'Registration Q4 000 - Small-Entry Amps - Any-American')) THEN 'Small-Entry Amps - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 005 - Camper Yeah - Entry Alt', 'Registration Q4 006 - Camper Yeah - Entry Alt')) THEN 'Camper Yeah - Entry Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 2021 - Camper Yeah - Fullsize', 'Registration Q4 006 - Camper Yeah - Fullsize')) THEN 'Camper Yeah - Fullsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Camper Yeah - Redsize', 'Registration Q2 001 - Camper Yeah - Redsize', 'Registration Q2 002 - Camper Yeah - Redsize', 'Registration Q2 005 - Camper Yeah - Redsize', 'Registration Q2 2021 - Camper Yeah - Redsize', 'Registration Q4 000 - Camper Yeah - Redsize', 'Registration Q4 001 - Camper Yeah - Redsize', 'Registration Q4 002 - Camper Yeah - Redsize', 'Registration Q4 005 - Camper Yeah - Redsize', 'Registration Q4 006 - Camper Yeah - Redsize')) THEN 'Camper Yeah - Redsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Camper Yeah - Small', 'Registration Q1 001 - Camper Yeah - Small', 'Registration Q1 002 - Camper Yeah - Small', 'Registration Q1 005 - Camper Yeah - Small', 'Registration Q1 006 - Camper Yeah - Small', 'Registration Q1 2021 - Camper Yeah - Small', 'Registration Q2 000 - Camper Yeah - Small', 'Registration Q2 001 - Camper Yeah - Small', 'Registration Q2 002 - Camper Yeah - Small', 'Registration Q2 005 - Camper Yeah - Small', 'Registration Q2 2021 - Camper Yeah - Small', 'Registration Q3 000 - Camper Yeah - Small', 'Registration Q3 001 - Camper Yeah - Small', 'Registration Q3 002 - Camper Yeah - Small', 'Registration Q3 006 - Camper Yeah - Small', 'Registration Q4 000 - Camper Yeah - Small', 'Registration Q4 001 - Camper Yeah - Small', 'Registration Q4 002 - Camper Yeah - Small', 'Registration Q4 005 - Camper Yeah - Small', 'Registration Q4 006 - Camper Yeah - Small')) THEN 'Camper Yeah - Small' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Camper Yeah - Small - Any-American', 'Registration Q2 001 - Camper Yeah - Small - Any-American', 'Registration Q2 002 - Camper Yeah - Small - Any-American', 'Registration Q4 000 - Camper Yeah - Small - Any-American')) THEN 'Camper Yeah - Small - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Camper Yeah - Premium Redsize Alt', 'Registration Q4 000 - Camper Yeah - Premium Redsize Alt')) THEN 'Camper Yeah Premium Redsize Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 002 - Camper Wagon', 'Registration Q2 005 - Camper Wagon', 'Registration Q2 2021 - Camper Wagon', 'Registration Q4 002 - Camper Wagon', 'Registration Q4 005 - Camper Wagon', 'Registration Q4 006 - Camper Wagon')) THEN 'Camper Wagon' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 005 - Campers Amps', 'Registration Q2 2021 - Campers Amps', 'Registration Q4 005 - Campers Amps', 'Registration Q4 006 - Campers Amps')) THEN 'Campers Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Campery', 'Registration Q2 001 - Campery', 'Registration Q2 002 - Campery', 'Registration Q2 005 - Campery', 'Registration Q2 2021 - Campery', 'Registration Q4 000 - Campery', 'Registration Q4 001 - Campery', 'Registration Q4 002 - Campery', 'Registration Q4 005 - Campery', 'Registration Q4 006 - Campery')) THEN 'Campery' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Upper Reddle Alt', 'Registration Q1 001 - Upper Reddle Alt', 'Registration Q1 002 - Upper Reddle Alt', 'Registration Q1 005 - Upper Reddle Alt', 'Registration Q1 006 - Upper Reddle Alt', 'Registration Q1 2021 - Upper Reddle Alt', 'Registration Q2 000 - Upper Reddle Alt', 'Registration Q2 001 - Upper Reddle Alt', 'Registration Q2 002 - Upper Reddle Alt', 'Registration Q2 005 - Upper Reddle Alt', 'Registration Q3 000 - Upper Reddle Alt', 'Registration Q3 001 - Upper Reddle Alt', 'Registration Q3 002 - Upper Reddle Alt', 'Registration Q3 006 - Upper Reddle Alt', 'Registration Q4 000 - Upper Reddle Alt', 'Registration Q4 001 - Upper Reddle Alt', 'Registration Q4 002 - Upper Reddle Alt', 'Registration Q4 005 - Upper Reddle Alt', 'Registration Q4 006 - Upper Reddle Alt')) THEN 'Upper Reddle Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Upper Reddle Alt - Any-American', 'Registration Q2 001 - Upper Reddle Alt - Any-American', 'Registration Q2 002 - Upper Reddle Alt - Any-American', 'Registration Q4 000 - Upper Reddle Alt - Any-American')) THEN 'Upper Reddle Alt - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Fires - Smooth', 'Registration Q2 001 - Fires - Smooth', 'Registration Q2 002 - Fires - Smooth', 'Registration Q2 005 - Fires - Smooth', 'Registration Q2 2021 - Fires - Smooth', 'Registration Q4 000 - Fires - Smooth', 'Registration Q4 001 - Fires - Smooth', 'Registration Q4 002 - Fires - Smooth', 'Registration Q4 005 - Fires - Smooth', 'Registration Q4 006 - Fires - Smooth')) THEN 'Fires - Smooth' ELSE "TABLE1"."DESCRIPTION" END) >= 'Alternative Capacitor Devices') AND ((CASE WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 002 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 005 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 2021 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 000 - Alternative Capacitor Devices', 'Registration Q4 001 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 002 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 006 - Alternative Capacitor Devices - Stuff Sample')) THEN 'Alternative Capacitor Devices' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Entry Alt Propane', 'Registration Q2 001 - Entry Alt Propane', 'Registration Q2 002 - Entry Alt Propane', 'Registration Q4 000 - Entry Alt Propane', 'Registration Q4 001 - Entry Alt Propane', 'Registration Q4 002 - Entry Alt Propane', 'Registration Q4 005 - Camper Yeah - Entry Alt')) THEN 'Entry Alt Propane' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Entry Amps', 'Registration Q2 000 - Entry Amps', 'Registration Q2 001 - Entry Amps', 'Registration Q2 002 - Entry Amps', 'Registration Q2 005 - Entry Amps', 'Registration Q4 000 - Entry Amps', 'Registration Q4 001 - Entry Amps', 'Registration Q4 002 - Entry Amps', 'Registration Q4 005 - Entry Amps', 'Registration Q4 006 - Entry Amps')) THEN 'Entry Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Fullsize Amp Alt', 'Registration Q2 001 - Fullsize Amp Alt', 'Registration Q2 002 - Fullsize Amp Alt', 'Registration Q2 005 - Fullsize Amp Alt', 'Registration Q2 2021 - Fullsize Amp Alt', 'Registration Q4 000 - Fullsize Amp Alt', 'Registration Q4 001 - Fullsize Amp Alt', 'Registration Q4 002 - Fullsize Amp Alt', 'Registration Q4 005 - Fullsize Amp Alt', 'Registration Q4 006 - Fullsize Amp Alt')) THEN 'Fullsize Amp Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Purple Device Makes - 450 Ratings', 'Registration Q2 000 - Purple Device Makes - 450 Ratings', 'Registration Q2 001 - Purple Device Makes - 450 Ratings', 'Registration Q2 002 - Purple Device Makes - 450 Ratings', 'Registration Q2 005 - Purple Device Makes - 450 Ratings', 'Registration Q2 2021 - Purple Device Makes - 450 Ratings', 'Registration Q4 000 - Purple Device Makes - 450 Ratings', 'Registration Q4 001 - Purple Device Makes - 450 Ratings', 'Registration Q4 002 - Purple Device Makes - 450 Ratings', 'Registration Q4 005 - Purple Device Makes - 450 Ratings', 'Registration Q4 006 - Purple Device Makes - 450 Ratings')) THEN 'Purple Device Makes' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 001 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 002 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q4 000 - Purple Device Makes Non-Waterproof - Any-American')) THEN 'Purple Device Makes Non-Waterproof - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Waterproof Speakers', 'Registration Q2 001 - Waterproof Speakers', 'Registration Q2 002 - Waterproof Speakers', 'Registration Q2 005 - Waterproof Speakers', 'Registration Q2 2021 - Waterproof Speakers', 'Registration Q4 000 - Waterproof Speakers', 'Registration Q4 001 - Waterproof Speakers', 'Registration Q4 002 - Waterproof Speakers', 'Registration Q4 005 - Waterproof Speakers', 'Registration Q4 006 - Waterproof Speakers')) THEN 'Waterproof Speakers' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Waterproof Makes - 450 Ratings', 'Registration Q2 001 - Waterproof Makes - 450 Ratings', 'Registration Q2 002 - Waterproof Makes - 450 Ratings', 'Registration Q2 005 - Waterproof Makes - 450 Ratings', 'Registration Q2 2021 - Waterproof Makes - 450 Ratings', 'Registration Q4 000 - Waterproof Makes - 450 Ratings', 'Registration Q4 001 - Waterproof Makes - 450 Ratings', 'Registration Q4 002 - Waterproof Makes - 450 Ratings', 'Registration Q4 005 - Waterproof Makes - 450 Ratings', 'Registration Q4 006 - Waterproof Makes - 450 Ratings')) THEN 'Waterproof Makes' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Waterproof Propane Redsize', 'Registration Q2 002 - Waterproof Propane Redsize', 'Registration Q2 005 - Waterproof Propane Redsize', 'Registration Q2 2021 - Waterproof Propane Redsize', 'Registration Q4 001 - Waterproof Propane Redsize', 'Registration Q4 002 - Waterproof Propane Redsize', 'Registration Q4 005 - Waterproof Propane Redsize', 'Registration Q4 006 - Waterproof Propane Redsize')) THEN 'Waterproof Propane Redsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Waterproof Propane Small', 'Registration Q2 002 - Waterproof Propane Small', 'Registration Q2 005 - Waterproof Propane Small', 'Registration Q4 001 - Waterproof Propane Small', 'Registration Q4 002 - Waterproof Propane Small', 'Registration Q4 005 - Waterproof Propane Small', 'Registration Q4 006 - Waterproof Propane Small')) THEN 'Waterproof Propane Small' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Red-Junk - Waterproof', 'Registration Q2 001 - Red-Junk - Waterproof', 'Registration Q2 002 - Red-Junk - Waterproof', 'Registration Q2 005 - Red-Junk - Waterproof', 'Registration Q2 2021 - Red-Junk - Waterproof', 'Registration Q4 000 - Red-Junk - Waterproof', 'Registration Q4 001 - Red-Junk - Waterproof', 'Registration Q4 002 - Red-Junk - Waterproof', 'Registration Q4 005 - Red-Junk - Waterproof', 'Registration Q4 006 - Red-Junk - Waterproof')) THEN 'Red-Junk Waterproof' WHEN ("TABLE1"."DESCRIPTION" = 'Registration Q2 2021 - Redsize Amps') THEN 'Redsize Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Near-Entry Waterproof', 'Registration Q2 001 - Near-Entry Waterproof', 'Registration Q2 002 - Near-Entry Waterproof', 'Registration Q2 005 - Near-Entry Waterproof', 'Registration Q2 2021 - Near-Entry Waterproof', 'Registration Q4 000 - Near-Entry Waterproof', 'Registration Q4 001 - Near-Entry Waterproof', 'Registration Q4 002 - Near-Entry Waterproof', 'Registration Q4 005 - Near-Entry Waterproof', 'Registration Q4 006 - Near-Entry Waterproof')) THEN 'Near-Entry Waterproof' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Home Theaters - Smooth', 'Registration Q2 001 - Home Theaters - Smooth', 'Registration Q2 002 - Home Theaters - Smooth', 'Registration Q2 005 - Home Theaters - Smooth', 'Registration Q2 2021 - Home Theaters - Smooth', 'Registration Q4 000 - Home Theaters - Smooth', 'Registration Q4 001 - Home Theaters - Smooth', 'Registration Q4 002 - Home Theaters - Smooth', 'Registration Q4 005 - Home Theaters - Smooth', 'Registration Q4 006 - Home Theaters - Smooth')) THEN 'Home Theaters - Smooth' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Home Theaters - Fullsize', 'Registration Q2 001 - Home Theaters - Fullsize', 'Registration Q2 002 - Home Theaters - Fullsize', 'Registration Q2 005 - Home Theaters - Fullsize', 'Registration Q2 2021 - Home Theaters - Fullsize', 'Registration Q4 000 - Home Theaters - Fullsize', 'Registration Q4 001 - Home Theaters - Fullsize', 'Registration Q4 002 - Home Theaters - Fullsize', 'Registration Q4 005 - Home Theaters - Fullsize', 'Registration Q4 006 - Home Theaters - Fullsize')) THEN 'Home Theaters - Fullsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Home Theaters - Fullsize - Half Full', 'Registration Q2 001 - Home Theaters - Fullsize - Half Full', 'Registration Q2 002 - Home Theaters - Fullsize - Half Full', 'Registration Q2 005 - Home Theaters - Fullsize - Half Full', 'Registration Q2 2021 - Home Theaters - Fullsize - Half Full', 'Registration Q4 000 - Home Theaters - Fullsize - Half Full', 'Registration Q4 001 - Home Theaters - Fullsize - Half Full', 'Registration Q4 002 - Home Theaters - Fullsize - Half Full', 'Registration Q4 005 - Home Theaters - Fullsize - Half Full')) THEN 'Home Theaters - Fullsize - Half Full' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Small Amps', 'Registration Q1 001 - Small Amps', 'Registration Q1 002 - Small Amps', 'Registration Q1 005 - Small Amps', 'Registration Q1 006 - Small Amps', 'Registration Q1 2021 - Small Amps', 'Registration Q2 000 - Small Amps', 'Registration Q2 001 - Small Amps', 'Registration Q2 002 - Small Amps', 'Registration Q2 005 - Small Amps', 'Registration Q2 2021 - Small Amps', 'Registration Q3 000 - Small Amps', 'Registration Q3 001 - Small Amps', 'Registration Q3 002 - Small Amps', 'Registration Q3 006 - Small Amps', 'Registration Q4 000 - Small Amps', 'Registration Q4 001 - Small Amps', 'Registration Q4 002 - Small Amps', 'Registration Q4 005 - Small Amps', 'Registration Q4 006 - Small Amps')) THEN 'Small Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Small-Entry Amps - Any-American', 'Registration Q2 001 - Small-Entry Amps - Any-American', 'Registration Q2 002 - Small-Entry Amps - Any-American', 'Registration Q4 000 - Small-Entry Amps - Any-American')) THEN 'Small-Entry Amps - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 005 - Camper Yeah - Entry Alt', 'Registration Q4 006 - Camper Yeah - Entry Alt')) THEN 'Camper Yeah - Entry Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 2021 - Camper Yeah - Fullsize', 'Registration Q4 006 - Camper Yeah - Fullsize')) THEN 'Camper Yeah - Fullsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Camper Yeah - Redsize', 'Registration Q2 001 - Camper Yeah - Redsize', 'Registration Q2 002 - Camper Yeah - Redsize', 'Registration Q2 005 - Camper Yeah - Redsize', 'Registration Q2 2021 - Camper Yeah - Redsize', 'Registration Q4 000 - Camper Yeah - Redsize', 'Registration Q4 001 - Camper Yeah - Redsize', 'Registration Q4 002 - Camper Yeah - Redsize', 'Registration Q4 005 - Camper Yeah - Redsize', 'Registration Q4 006 - Camper Yeah - Redsize')) THEN 'Camper Yeah - Redsize' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Camper Yeah - Small', 'Registration Q1 001 - Camper Yeah - Small', 'Registration Q1 002 - Camper Yeah - Small', 'Registration Q1 005 - Camper Yeah - Small', 'Registration Q1 006 - Camper Yeah - Small', 'Registration Q1 2021 - Camper Yeah - Small', 'Registration Q2 000 - Camper Yeah - Small', 'Registration Q2 001 - Camper Yeah - Small', 'Registration Q2 002 - Camper Yeah - Small', 'Registration Q2 005 - Camper Yeah - Small', 'Registration Q2 2021 - Camper Yeah - Small', 'Registration Q3 000 - Camper Yeah - Small', 'Registration Q3 001 - Camper Yeah - Small', 'Registration Q3 002 - Camper Yeah - Small', 'Registration Q3 006 - Camper Yeah - Small', 'Registration Q4 000 - Camper Yeah - Small', 'Registration Q4 001 - Camper Yeah - Small', 'Registration Q4 002 - Camper Yeah - Small', 'Registration Q4 005 - Camper Yeah - Small', 'Registration Q4 006 - Camper Yeah - Small')) THEN 'Camper Yeah - Small' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Camper Yeah - Small - Any-American', 'Registration Q2 001 - Camper Yeah - Small - Any-American', 'Registration Q2 002 - Camper Yeah - Small - Any-American', 'Registration Q4 000 - Camper Yeah - Small - Any-American')) THEN 'Camper Yeah - Small - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Camper Yeah - Premium Redsize Alt', 'Registration Q4 000 - Camper Yeah - Premium Redsize Alt')) THEN 'Camper Yeah Premium Redsize Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 002 - Camper Wagon', 'Registration Q2 005 - Camper Wagon', 'Registration Q2 2021 - Camper Wagon', 'Registration Q4 002 - Camper Wagon', 'Registration Q4 005 - Camper Wagon', 'Registration Q4 006 - Camper Wagon')) THEN 'Camper Wagon' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 005 - Campers Amps', 'Registration Q2 2021 - Campers Amps', 'Registration Q4 005 - Campers Amps', 'Registration Q4 006 - Campers Amps')) THEN 'Campers Amps' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Campery', 'Registration Q2 001 - Campery', 'Registration Q2 002 - Campery', 'Registration Q2 005 - Campery', 'Registration Q2 2021 - Campery', 'Registration Q4 000 - Campery', 'Registration Q4 001 - Campery', 'Registration Q4 002 - Campery', 'Registration Q4 005 - Campery', 'Registration Q4 006 - Campery')) THEN 'Campery' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Upper Reddle Alt', 'Registration Q1 001 - Upper Reddle Alt', 'Registration Q1 002 - Upper Reddle Alt', 'Registration Q1 005 - Upper Reddle Alt', 'Registration Q1 006 - Upper Reddle Alt', 'Registration Q1 2021 - Upper Reddle Alt', 'Registration Q2 000 - Upper Reddle Alt', 'Registration Q2 001 - Upper Reddle Alt', 'Registration Q2 002 - Upper Reddle Alt', 'Registration Q2 005 - Upper Reddle Alt', 'Registration Q3 000 - Upper Reddle Alt', 'Registration Q3 001 - Upper Reddle Alt', 'Registration Q3 002 - Upper Reddle Alt', 'Registration Q3 006 - Upper Reddle Alt', 'Registration Q4 000 - Upper Reddle Alt', 'Registration Q4 001 - Upper Reddle Alt', 'Registration Q4 002 - Upper Reddle Alt', 'Registration Q4 005 - Upper Reddle Alt', 'Registration Q4 006 - Upper Reddle Alt')) THEN 'Upper Reddle Alt' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Upper Reddle Alt - Any-American', 'Registration Q2 001 - Upper Reddle Alt - Any-American', 'Registration Q2 002 - Upper Reddle Alt - Any-American', 'Registration Q4 000 - Upper Reddle Alt - Any-American')) THEN 'Upper Reddle Alt - Any-American' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Fires - Smooth', 'Registration Q2 001 - Fires - Smooth', 'Registration Q2 002 - Fires - Smooth', 'Registration Q2 005 - Fires - Smooth', 'Registration Q2 2021 - Fires - Smooth', 'Registration Q4 000 - Fires - Smooth', 'Registration Q4 001 - Fires - Smooth', 'Registration Q4 002 - Fires - Smooth', 'Registration Q4 005 - Fires - Smooth', 'Registration Q4 006 - Fires - Smooth')) THEN 'Fires - Smooth' ELSE "TABLE1"."DESCRIPTION" END) <= 'Fires - Smooth') AND ((CASE WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 000 - Entry Amps', 'Registration Q1 000 - Purple Device Makes - 450 Ratings', 'Registration Q1 000 - Small Amps', 'Registration Q1 000 - Camper Yeah - Small', 'Registration Q1 000 - Upper Reddle Alt')) THEN '000 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 000 - Entry Alt Propane', 'Registration Q2 000 - Entry Amps', 'Registration Q2 000 - Fullsize Amp Alt', 'Registration Q2 000 - Purple Device Makes - 450 Ratings', 'Registration Q2 000 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 000 - Waterproof Speakers', 'Registration Q2 000 - Waterproof Makes - 450 Ratings', 'Registration Q2 000 - Red-Junk - Waterproof', 'Registration Q2 000 - Near-Entry Waterproof', 'Registration Q2 000 - Home Theaters - Smooth', 'Registration Q2 000 - Home Theaters - Fullsize', 'Registration Q2 000 - Home Theaters - Fullsize - Half Full', 'Registration Q2 000 - Small Amps', 'Registration Q2 000 - Small-Entry Amps - Any-American', 'Registration Q2 000 - Camper Yeah - Redsize', 'Registration Q2 000 - Camper Yeah - Premium Redsize Alt', 'Registration Q2 000 - Camper Yeah - Small', 'Registration Q2 000 - Camper Yeah - Small - Any-American', 'Registration Q2 000 - Campery', 'Registration Q2 000 - Upper Reddle Alt', 'Registration Q2 000 - Upper Reddle Alt - Any-American', 'Registration Q2 000 - Fires - Smooth')) THEN '000 Q2' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q3 000 - Small Amps', 'Registration Q3 000 - Camper Yeah - Small', 'Registration Q3 000 - Upper Reddle Alt')) THEN '000 Q3' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q4 000 - Alternative Capacitor Devices', 'Registration Q4 000 - Entry Alt Propane', 'Registration Q4 000 - Entry Amps', 'Registration Q4 000 - Fullsize Amp Alt', 'Registration Q4 000 - Purple Device Makes - 450 Ratings', 'Registration Q4 000 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q4 000 - Waterproof Speakers', 'Registration Q4 000 - Waterproof Makes - 450 Ratings', 'Registration Q4 000 - Red-Junk - Waterproof', 'Registration Q4 000 - Near-Entry Waterproof', 'Registration Q4 000 - Home Theaters - Smooth', 'Registration Q4 000 - Home Theaters - Fullsize', 'Registration Q4 000 - Home Theaters - Fullsize - Half Full', 'Registration Q4 000 - Small Amps', 'Registration Q4 000 - Small-Entry Amps - Any-American', 'Registration Q4 000 - Camper Yeah - Redsize', 'Registration Q4 000 - Camper Yeah - Premium Redsize Alt', 'Registration Q4 000 - Camper Yeah - Small', 'Registration Q4 000 - Camper Yeah - Small - Any-American', 'Registration Q4 000 - Campery', 'Registration Q4 000 - Upper Reddle Alt', 'Registration Q4 000 - Upper Reddle Alt - Any-American', 'Registration Q4 000 - Fires - Smooth')) THEN '000 Q4' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 001 - Small Amps', 'Registration Q1 001 - Camper Yeah - Small', 'Registration Q1 001 - Upper Reddle Alt')) THEN '001 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 001 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 001 - Entry Alt Propane', 'Registration Q2 001 - Entry Amps', 'Registration Q2 001 - Fullsize Amp Alt', 'Registration Q2 001 - Purple Device Makes - 450 Ratings', 'Registration Q2 001 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 001 - Waterproof Speakers', 'Registration Q2 001 - Waterproof Makes - 450 Ratings', 'Registration Q2 001 - Waterproof Propane Redsize', 'Registration Q2 001 - Waterproof Propane Small', 'Registration Q2 001 - Red-Junk - Waterproof', 'Registration Q2 001 - Near-Entry Waterproof', 'Registration Q2 001 - Home Theaters - Smooth', 'Registration Q2 001 - Home Theaters - Fullsize', 'Registration Q2 001 - Home Theaters - Fullsize - Half Full', 'Registration Q2 001 - Small Amps', 'Registration Q2 001 - Small-Entry Amps - Any-American', 'Registration Q2 001 - Camper Yeah - Redsize', 'Registration Q2 001 - Camper Yeah - Small', 'Registration Q2 001 - Camper Yeah - Small - Any-American', 'Registration Q2 001 - Campery', 'Registration Q2 001 - Upper Reddle Alt', 'Registration Q2 001 - Upper Reddle Alt - Any-American', 'Registration Q2 001 - Fires - Smooth')) THEN '001 Q2' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q3 001 - Small Amps', 'Registration Q3 001 - Camper Yeah - Small', 'Registration Q3 001 - Upper Reddle Alt')) THEN '001 Q3' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q4 001 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 001 - Entry Alt Propane', 'Registration Q4 001 - Entry Amps', 'Registration Q4 001 - Fullsize Amp Alt', 'Registration Q4 001 - Purple Device Makes - 450 Ratings', 'Registration Q4 001 - Waterproof Speakers', 'Registration Q4 001 - Waterproof Makes - 450 Ratings', 'Registration Q4 001 - Waterproof Propane Redsize', 'Registration Q4 001 - Waterproof Propane Small', 'Registration Q4 001 - Red-Junk - Waterproof', 'Registration Q4 001 - Near-Entry Waterproof', 'Registration Q4 001 - Home Theaters - Smooth', 'Registration Q4 001 - Home Theaters - Fullsize', 'Registration Q4 001 - Home Theaters - Fullsize - Half Full', 'Registration Q4 001 - Small Amps', 'Registration Q4 001 - Camper Yeah - Redsize', 'Registration Q4 001 - Camper Yeah - Small', 'Registration Q4 001 - Campery', 'Registration Q4 001 - Upper Reddle Alt', 'Registration Q4 001 - Fires - Smooth')) THEN '001 Q4' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 002 - Small Amps', 'Registration Q1 002 - Camper Yeah - Small', 'Registration Q1 002 - Upper Reddle Alt')) THEN '002 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 002 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 002 - Entry Alt Propane', 'Registration Q2 002 - Entry Amps', 'Registration Q2 002 - Fullsize Amp Alt', 'Registration Q2 002 - Purple Device Makes - 450 Ratings', 'Registration Q2 002 - Purple Device Makes Non-Waterproof - Any-American', 'Registration Q2 002 - Waterproof Speakers', 'Registration Q2 002 - Waterproof Makes - 450 Ratings', 'Registration Q2 002 - Waterproof Propane Redsize', 'Registration Q2 002 - Waterproof Propane Small', 'Registration Q2 002 - Red-Junk - Waterproof', 'Registration Q2 002 - Near-Entry Waterproof', 'Registration Q2 002 - Home Theaters - Smooth', 'Registration Q2 002 - Home Theaters - Fullsize', 'Registration Q2 002 - Home Theaters - Fullsize - Half Full', 'Registration Q2 002 - Small Amps', 'Registration Q2 002 - Small-Entry Amps - Any-American', 'Registration Q2 002 - Camper Yeah - Redsize', 'Registration Q2 002 - Camper Yeah - Small', 'Registration Q2 002 - Camper Yeah - Small - Any-American', 'Registration Q2 002 - Camper Wagon', 'Registration Q2 002 - Campery', 'Registration Q2 002 - Upper Reddle Alt', 'Registration Q2 002 - Upper Reddle Alt - Any-American', 'Registration Q2 002 - Fires - Smooth')) THEN '002 Q2' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q3 002 - Small Amps', 'Registration Q3 002 - Camper Yeah - Small', 'Registration Q3 002 - Upper Reddle Alt')) THEN '002 Q3' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q4 002 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 002 - Entry Alt Propane', 'Registration Q4 002 - Entry Amps', 'Registration Q4 002 - Fullsize Amp Alt', 'Registration Q4 002 - Purple Device Makes - 450 Ratings', 'Registration Q4 002 - Waterproof Speakers', 'Registration Q4 002 - Waterproof Makes - 450 Ratings', 'Registration Q4 002 - Waterproof Propane Redsize', 'Registration Q4 002 - Waterproof Propane Small', 'Registration Q4 002 - Red-Junk - Waterproof', 'Registration Q4 002 - Near-Entry Waterproof', 'Registration Q4 002 - Home Theaters - Smooth', 'Registration Q4 002 - Home Theaters - Fullsize', 'Registration Q4 002 - Home Theaters - Fullsize - Half Full', 'Registration Q4 002 - Small Amps', 'Registration Q4 002 - Camper Yeah - Redsize', 'Registration Q4 002 - Camper Yeah - Small', 'Registration Q4 002 - Camper Wagon', 'Registration Q4 002 - Campery', 'Registration Q4 002 - Upper Reddle Alt', 'Registration Q4 002 - Fires - Smooth')) THEN '002 Q4' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 005 - Small Amps', 'Registration Q1 005 - Camper Yeah - Small', 'Registration Q1 005 - Upper Reddle Alt')) THEN '005 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 005 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 005 - Entry Amps', 'Registration Q2 005 - Fullsize Amp Alt', 'Registration Q2 005 - Purple Device Makes - 450 Ratings', 'Registration Q2 005 - Waterproof Speakers', 'Registration Q2 005 - Waterproof Makes - 450 Ratings', 'Registration Q2 005 - Waterproof Propane Redsize', 'Registration Q2 005 - Waterproof Propane Small', 'Registration Q2 005 - Red-Junk - Waterproof', 'Registration Q2 005 - Near-Entry Waterproof', 'Registration Q2 005 - Home Theaters - Smooth', 'Registration Q2 005 - Home Theaters - Fullsize', 'Registration Q2 005 - Home Theaters - Fullsize - Half Full', 'Registration Q2 005 - Small Amps', 'Registration Q2 005 - Camper Yeah - Entry Alt', 'Registration Q2 005 - Camper Yeah - Redsize', 'Registration Q2 005 - Camper Yeah - Small', 'Registration Q2 005 - Camper Wagon', 'Registration Q2 005 - Campers Amps', 'Registration Q2 005 - Campery', 'Registration Q2 005 - Upper Reddle Alt', 'Registration Q2 005 - Fires - Smooth')) THEN '005 Q2' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q4 005 - Entry Amps', 'Registration Q4 005 - Fullsize Amp Alt', 'Registration Q4 005 - Purple Device Makes - 450 Ratings', 'Registration Q4 005 - Waterproof Speakers', 'Registration Q4 005 - Waterproof Makes - 450 Ratings', 'Registration Q4 005 - Waterproof Propane Redsize', 'Registration Q4 005 - Waterproof Propane Small', 'Registration Q4 005 - Red-Junk - Waterproof', 'Registration Q4 005 - Near-Entry Waterproof', 'Registration Q4 005 - Home Theaters - Smooth', 'Registration Q4 005 - Home Theaters - Fullsize', 'Registration Q4 005 - Home Theaters - Fullsize - Half Full', 'Registration Q4 005 - Small Amps', 'Registration Q4 005 - Camper Yeah - Entry Alt', 'Registration Q4 005 - Camper Yeah - Redsize', 'Registration Q4 005 - Camper Yeah - Small', 'Registration Q4 005 - Camper Wagon', 'Registration Q4 005 - Campers Amps', 'Registration Q4 005 - Campery', 'Registration Q4 005 - Upper Reddle Alt', 'Registration Q4 005 - Fires - Smooth')) THEN '005 Q4' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 006 - Small Amps', 'Registration Q1 006 - Camper Yeah - Small', 'Registration Q1 006 - Upper Reddle Alt')) THEN '006 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q3 006 - Small Amps', 'Registration Q3 006 - Camper Yeah - Small', 'Registration Q3 006 - Upper Reddle Alt')) THEN '006 Q3' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q4 006 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q4 006 - Entry Amps', 'Registration Q4 006 - Fullsize Amp Alt', 'Registration Q4 006 - Purple Device Makes - 450 Ratings', 'Registration Q4 006 - Waterproof Speakers', 'Registration Q4 006 - Waterproof Makes - 450 Ratings', 'Registration Q4 006 - Waterproof Propane Redsize', 'Registration Q4 006 - Waterproof Propane Small', 'Registration Q4 006 - Red-Junk - Waterproof', 'Registration Q4 006 - Near-Entry Waterproof', 'Registration Q4 006 - Home Theaters - Smooth', 'Registration Q4 006 - Home Theaters - Fullsize', 'Registration Q4 006 - Small Amps', 'Registration Q4 006 - Camper Yeah - Entry Alt', 'Registration Q4 006 - Camper Yeah - Fullsize', 'Registration Q4 006 - Camper Yeah - Redsize', 'Registration Q4 006 - Camper Yeah - Small', 'Registration Q4 006 - Camper Wagon', 'Registration Q4 006 - Campers Amps', 'Registration Q4 006 - Campery', 'Registration Q4 006 - Upper Reddle Alt', 'Registration Q4 006 - Fires - Smooth')) THEN '006 Q4' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q1 2021 - Small Amps', 'Registration Q1 2021 - Camper Yeah - Small', 'Registration Q1 2021 - Upper Reddle Alt')) THEN '2021 Q1' WHEN ("TABLE1"."DESCRIPTION" IN ('Registration Q2 2021 - Alternative Capacitor Devices - Stuff Sample', 'Registration Q2 2021 - Fullsize Amp Alt', 'Registration Q2 2021 - Purple Device Makes - 450 Ratings', 'Registration Q2 2021 - Waterproof Speakers', 'Registration Q2 2021 - Waterproof Makes - 450 Ratings', 'Registration Q2 2021 - Waterproof Propane Redsize', 'Registration Q2 2021 - Red-Junk - Waterproof', 'Registration Q2 2021 - Redsize Amps', 'Registration Q2 2021 - Near-Entry Waterproof', 'Registration Q2 2021 - Home Theaters - Smooth', 'Registration Q2 2021 - Home Theaters - Fullsize', 'Registration Q2 2021 - Home Theaters - Fullsize - Half Full', 'Registration Q2 2021 - Small Amps', 'Registration Q2 2021 - Camper Yeah - Fullsize', 'Registration Q2 2021 - Camper Yeah - Redsize', 'Registration Q2 2021 - Camper Yeah - Small', 'Registration Q2 2021 - Camper Wagon', 'Registration Q2 2021 - Campers Amps', 'Registration Q2 2021 - Campery', 'Registration Q2 2021 - Fires - Smooth')) THEN '2021 Q2' ELSE "TABLE1"."DESCRIPTION" END) = '2021 Q2') AND ((CASE WHEN ("TABLE1"."CODE" = 'No Answer') THEN 0 ELSE 1 END) <> 0) AND ("TABLE1"."DESCRIPTION" = 'Familiar With (G1)')) +GROUP BY 1, + 2, + 3