diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..3f84de5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,27 @@
+---
+name: SQL Parser Error
+about: Create a report to help us improve
+title: 'Parser Version : RDBMS : Failing feature description'
+labels: 'Parser Error', 'Feature Request', 'Documentation', 'Java API', 'RDBMS support'
+assignees: ''
+
+---
+
+**Failing SQL Feature**
+- Brief description of the failing SQL feature
+- Example: `WITH ROLLUP` can't be parsed
+
+**SQL Example**
+- Simplified Query Example, focusing on the failing feature
+```sql
+-- Replace with your ACTUAL example
+select 1
+from dual
+```
+
+**Software Information**
+- Parser 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).
\ No newline at end of file
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 0000000..a0f9ef7
--- /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: [ main ]
+ pull_request:
+ branches: [ main ]
+
+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/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml
new file mode 100644
index 0000000..4906b2c
--- /dev/null
+++ b/.github/workflows/sphinx.yml
@@ -0,0 +1,31 @@
+name: Sphinx Pages
+on: [push, workflow_dispatch]
+permissions: write-all
+jobs:
+ docs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/setup-python@v4
+ - name: Install XSLT Processor
+ run: sudo apt-get install xsltproc sphinx-common
+ - name: Install dependencies
+ run: pip install sphinx_rtd_theme sphinx-book-theme myst_parser sphinx-prompt sphinx_substitution_extensions sphinx_issues sphinx_tabs pygments
+ - name: Checkout project sources
+ uses: actions/checkout@v2
+ with:
+ ref: main
+ fetch-depth: 0
+ - name: Setup Gradle
+ uses: gradle/gradle-build-action@v2
+ - name: Run build with Gradle Wrapper
+ run: gradle sphinx
+ - name: Deploy
+ uses: actions/configure-pages@v2
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v1
+ with:
+ # Upload entire repository
+ path: 'build/site/sphinx'
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v1
diff --git a/.gitignore b/.gitignore
index f9b1a4b..cd19616 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
*.ipr
*.iws
target/
+build/
/var
/*/var/
/presto-product-tests/**/var/
@@ -25,3 +26,8 @@ benchmark_outputs
.mvn/timing.properties
.editorconfig
node_modules
+nb-configuration.xml
+gradle/
+.gradle/
+gradlew
+gradlew.bat
diff --git a/README.md b/README.md
index 185566f..34b7d9d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,53 @@
-# SQL Language Frontend
+# Java SQL:2016 Language Frontend
-A Modern SQL frontend based on SQL16 with extensions for streaming, graph, rich types, etc, including parser, resolver, rewriters, etc.
+[Visit our Website.](https://manticore-projects.github.io/sql/index.html)
+
+
+[](http://maven-badges.herokuapp.com/maven-central/com.facebook.presto/presto-coresql)
+[](https://www.javadoc.io/doc/com.facebook.presto/presto-coresql)
+
+A Modern SQL frontend based on the SQL16 Standard with extensions for streaming, graph, rich types.
+
+ * Comprehensive support for statements:
+
+ - QUERY: ``SELECT ...``
+ - DML: ``INSERT ... INTO ...`` ``UPDATE ...`` ``MERGE ... INTO ...`` ``DELETE ... FROM ...``
+ - DDL: ``CREATE ...`` ``ALTER ...`` ``DROP ...``
+
+ * Nested Expressions (e.g. Sub-Selects)
+ * ``WITH`` clauses
+ * De-Parser for a Statement AST Node, which writes a SQL from Java Objects
+ * Rewriter
+ * Linter
+
+## How to use it
+
+1) Maven Repository
+
+ ```xml
+
+ com.facebook.presto
+ presto-coresql
+ 0.1
+
+ ```
+
+2) Parsing and Unparsing a SQL Statement
+
+ ```java
+ import com.facebook.coresql.parser.AstNode;
+ import com.facebook.coresql.parser.ParserHelper;
+ import com.facebook.coresql.parser.Unparser;
+
+ String sqlStr = "select 1 from dual where a=b";
+ AstNode ast = ParserHelper.parseStatement(sqlStr);
+ String unparsedSqlStr = Unparser.unparse(ast);
+ ```
+
+3) Compile latest version from source
+
+ ```shell
+ git clone https://github.com/kaikalur/sql.git
+ cd sql
+ mvn install
+ ```
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..eeba4b2
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,214 @@
+import se.bjurr.gitchangelog.plugin.gradle.GitChangelogTask
+import java.nio.charset.Charset
+
+plugins {
+ id 'java'
+ id "ca.coglinc2.javacc" version "latest.release"
+ id "com.github.spotbugs" version "latest.release"
+ id 'checkstyle'
+
+ // download the RR tools which have no Maven Repository
+ id "de.undercouch.download" version "latest.release"
+
+ id "se.bjurr.gitchangelog.git-changelog-gradle-plugin" version "latest.release"
+}
+
+allprojects {
+ repositories {
+ mavenLocal()
+ mavenCentral()
+ maven {
+ url 'https://oss.sonatype.org/content/repositories/snapshots'
+ }
+ }
+}
+
+def getVersion = { boolean considerSnapshot ->
+ def major = 0
+ def minor = 0
+ def patch = 0
+ def commit = ""
+ def snapshot =""
+ new ByteArrayOutputStream().withStream { os ->
+ exec {
+ workingDir "$projectDir"
+ args = [
+ "--no-pager"
+ , "describe"
+ , "--tags"
+ , "--always"
+ , "--dirty=-SNAPSHOT"
+ ]
+ executable "git"
+ standardOutput = os
+ }
+ def matcher = os.toString() =~ /(\d*)\.(\d*)-(\d*)-([a-zA-Z\d]*)/
+ matcher.find()
+
+ major = matcher[0][1]
+ minor = matcher[0][2]
+ patch = matcher[0][3]
+ commit = matcher[0][4]
+
+ if (considerSnapshot && os.toString().trim().endsWith("-SNAPSHOT")) {
+ minor++
+ snapshot = "-SNAPSHOT"
+ }
+ }
+ return "${major}.${minor}${snapshot}"
+}
+group = 'com.facebook.presto'
+version = getVersion(true)
+description = 'Java SQL:2016 compliant Parser Suite'
+
+java {
+ withSourcesJar()
+ withJavadocJar()
+}
+
+test {
+ useJUnitPlatform()
+ // set heap size for the test JVM(s)
+ minHeapSize = "128m"
+ maxHeapSize = "1G"
+ jvmArgs << [
+ '-Djunit.jupiter.execution.parallel.enabled=true',
+ '-Djunit.jupiter.execution.parallel.config.strategy=dynamic',
+ '-Djunit.jupiter.execution.parallel.mode.default=concurrent'
+ ]
+
+ finalizedBy check
+}
+
+checkstyle {
+ sourceSets = [sourceSets.main, sourceSets.test]
+}
+
+task renderRR() {
+ dependsOn ':presto-coresql-parser:compileJavacc'
+ doLast {
+ // these WAR files have been provided as a courtesy by Gunther Rademacher
+ // and belong to the RR - Railroad Diagram Generator Project
+ // https://github.com/GuntherRademacher/rr
+ //
+ // Hosting at manticore-projects.com is temporary until a better solution is found
+ // Please do not use these files without Gunther's permission
+ download.run {
+ src 'http://manticore-projects.com/download/convert.war'
+ dest "$buildDir/rr/convert.war"
+ overwrite false
+ }
+
+ download.run {
+ src 'http://manticore-projects.com/download/rr.war'
+ dest "$buildDir/rr/rr.war"
+ overwrite false
+ }
+
+ javaexec {
+ standardOutput = new FileOutputStream("$buildDir/rr/syntax.ebnf")
+ main = "-jar"
+ args = [
+ "$buildDir/rr/convert.war",
+ "${project(':presto-coresql-parser').buildDir}/generated/jjtree/com/facebook/coresql/parser/parser.jj"
+ ]
+ }
+
+ javaexec {
+ main = "-jar"
+ args = [
+ "$buildDir/rr/rr.war",
+ "-noepsilon",
+ "-color:#4D88FF",
+ "-offset:0",
+ "-width:800",
+ //"-png",
+ //"-out:target/rr/PRESTO_SQL_PARSERCC.zip",
+ "-out:$buildDir/rr/syntax.xhtml",
+ "$buildDir/rr/syntax.ebnf"
+ ]
+ }
+
+ //@todo: a Java based solution may be more appropriate here
+ exec {
+ commandLine "sh", "-c", "xsltproc sphinx/xhtml2rst.xsl $buildDir/rr/syntax.xhtml > sphinx/src/syntax.rst"
+ }
+ }
+}
+
+task gitChangelogTask(type: GitChangelogTask) {
+ fromRepo = file("$projectDir")
+ file = new File("${projectDir}/sphinx/src/changelog.rst")
+ //fromRef = "4.0"
+ //toRef = "1.1";
+ templateContent ="""
+************************
+Changelog
+************************
+
+
+{{#tags}}
+{{#ifMatches name "^Unreleased.*"}}
+Latest Changes since |PRESTO_SQL_PARSER_VERSION|
+{{/ifMatches}}
+{{#ifMatches name "^(?!Unreleased).*"}}
+Version {{name}}
+{{/ifMatches}}
+=============================================================
+
+ {{#issues}}
+
+ {{#commits}}
+ {{#ifMatches messageTitle "^(?!Merge).*"}}
+ * **{{{messageTitle}}}**
+
+ {{authorName}}, {{commitDate}}
+ {{/ifMatches}}
+ {{/commits}}
+
+ {{/issues}}
+{{/tags}}
+"""
+}
+
+task sphinx(type: Exec) {
+ dependsOn(gitChangelogTask, renderRR)
+
+ String PROLOG = """
+.. |_| unicode:: U+00A0
+ :trim:
+
+.. |PRESTO_SQL_PARSER_EMAIL| replace:: support@manticore-projects.com
+.. |PRESTO_SQL_PARSER_VERSION| replace:: ${getVersion(false)}
+.. |PRESTO_SQL_PARSER_SNAPSHOT_VERSION| replace:: ${getVersion(true)}
+.. |PRESTO_SQL_PARSER_STABLE_VERSION_LINK| raw:: html
+
+ ${project.name}-${getVersion(false)}.jar
+
+.. |PRESTO_SQL_PARSER_SNAPSHOT_VERSION_LINK| raw:: html
+
+ ${project.name}-${getVersion(true)}.jar
+
+"""
+
+ args = [
+ "-Dproject=Presto SQL Parser"
+ , "-Dcopyright=Sreeni Viswanadha, 2022"
+ , "-Dauthor=Sreeni Viswanadha"
+ , "-Drelease=${getVersion(false)}"
+ , "-Drst_prolog=$PROLOG"
+ , "sphinx/src"
+ , "$buildDir/site/sphinx"
+ ]
+
+ executable "sphinx-build"
+
+ //store the output instead of printing to the console:
+ standardOutput = new ByteArrayOutputStream()
+
+ //extension method stopTomcat.output() can be used to obtain the output:
+ ext.output = {
+ return standardOutput.toString()
+ }
+}
+
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000..e95b987
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,275 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000..e27e45a
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/config/eclipse-formatter/eclipse-airlift.importorder b/config/eclipse-formatter/eclipse-airlift.importorder
new file mode 100644
index 0000000..5700764
--- /dev/null
+++ b/config/eclipse-formatter/eclipse-airlift.importorder
@@ -0,0 +1,6 @@
+#Organize Import Order
+#Thu Jan 24 12:47:53 PST 2013
+3=\#
+2=java
+1=javax
+0=
diff --git a/config/eclipse-formatter/eclipse-airlift.xml b/config/eclipse-formatter/eclipse-airlift.xml
new file mode 100644
index 0000000..fdea458
--- /dev/null
+++ b/config/eclipse-formatter/eclipse-airlift.xml
@@ -0,0 +1,399 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..7325d74
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,5 @@
+org.gradle.jvmargs=-Xmx1G -XX:MaxPermSize=512m -Dfile.encoding=UTF-8
+org.gradle.caching=true
+org.gradle.parallel=true
+org.gradle.configureondemand=true
+
diff --git a/linter/pom.xml b/linter/pom.xml
index 79385b5..b5d516c 100644
--- a/linter/pom.xml
+++ b/linter/pom.xml
@@ -19,15 +19,16 @@
- junit
- junit
- 4.12
+ org.junit.jupiter
+ junit-jupiter
+ 5.8.1
test
- org.testng
- testng
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.8.1
test
diff --git a/linter/src/test/java/com/facebook/coresql/linter/lint/TestMixedAndOr.java b/linter/src/test/java/com/facebook/coresql/linter/lint/TestMixedAndOr.java
index bf93ed9..2b638b7 100644
--- a/linter/src/test/java/com/facebook/coresql/linter/lint/TestMixedAndOr.java
+++ b/linter/src/test/java/com/facebook/coresql/linter/lint/TestMixedAndOr.java
@@ -16,23 +16,23 @@
import com.facebook.coresql.linter.warning.DefaultWarningCollector;
import com.facebook.coresql.linter.warning.WarningCollectorConfig;
import com.facebook.coresql.parser.AstNode;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.Test;
import static com.facebook.coresql.linter.warning.StandardWarningCode.MIXING_AND_OR_WITHOUT_PARENTHESES;
import static com.facebook.coresql.parser.ParserHelper.parseStatement;
-import static org.testng.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestMixedAndOr
{
- private static final LintingVisitor LINTING_VISITOR = new MixedAndOr(new DefaultWarningCollector(new WarningCollectorConfig().setMaxWarnings(1)));
+ private static final LintingVisitor LINTING_VISITOR = new MixedAndOr(
+ new DefaultWarningCollector(new WarningCollectorConfig().setMaxWarnings(1)));
private static final String[] NON_WARNING_SQL_STRINGS = new String[] {
"SELECT (true or false) and false;",
"SELECT true or false or true;",
"SELECT true and false and false;",
"SELECT a FROM T WHERE a.id = 2 or (a.id = 3 and a.age = 73);",
"SELECT a FROM T WHERE (a.id = 2 or a.id = 3) and (a.age = 73 or a.age = 100);",
- "SELECT * from Evaluation e JOIN Value v ON e.CaseNum = v.CaseNum\n" +
- " AND e.FileNum = v.FileNum AND e.ActivityNum = v.ActivityNum;",
+ "SELECT * from Evaluation e JOIN Value v ON e.CaseNum = v.CaseNum\n" + " AND e.FileNum = v.FileNum AND e.ActivityNum = v.ActivityNum;",
"use a.b;",
" SELECT 1;",
"SELECT a FROM T;",
@@ -46,27 +46,22 @@ public class TestMixedAndOr
"CREATE TABLE T AS SELECT TRANSFORM(ARRAY[x], x -> x + 2) AS arra FROM T;",
"INSERT INTO T SELECT TRANSFORM(ARRAY[x], x -> x + 2) AS arra FROM T;",
"SELECT ROW_NUMBER() OVER(PARTITION BY x) FROM T;",
- "SELECT x, SUM(y) OVER (PARTITION BY y ORDER BY 1) AS min\n" +
- "FROM (values ('b',10), ('a', 10)) AS T(x, y)\n;",
- "SELECT\n" +
- " CAST(MAP() AS map>) AS \"bool_tensor_features\";",
+ "SELECT x, SUM(y) OVER (PARTITION BY y ORDER BY 1) AS min\n" + "FROM (values ('b',10), ('a', 10)) AS T(x, y)\n;",
+ "SELECT\n" + " CAST(MAP() AS map>) AS \"bool_tensor_features\";",
"SELECT f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f())))))))))))))))))))))))))))));",
- "SELECT abs, 2 as abs;",
- };
-
+ "SELECT abs, 2 as abs;"};
private static final String[] WARNING_SQL_STRINGS = new String[] {
"SELECT true or false and false;",
"SELECT a FROM T WHERE a.id = 2 or a.id = 3 and a.age = 73;",
- "SELECT a FROM T WHERE (a.id = 2 or a.id = 3) and a.age = 73 or a.age = 100;"
- };
+ "SELECT a FROM T WHERE (a.id = 2 or a.id = 3) and a.age = 73 or a.age = 100;"};
private static void assertHasMixedAndOrWarnings(String statement, int expectedNumWarnings)
{
AstNode ast = parseStatement(statement);
LINTING_VISITOR.lint(ast);
- assertEquals(LINTING_VISITOR.getWarningCollector().getAllWarnings().size(), expectedNumWarnings);
- LINTING_VISITOR.getWarningCollector().getAllWarnings().forEach(x ->
- assertEquals(x.getWarningCode(), MIXING_AND_OR_WITHOUT_PARENTHESES.getWarningCode()));
+ assertEquals(LINTING_VISITOR.getWarningCollector().getAllWarnings().size(), expectedNumWarnings, statement);
+ LINTING_VISITOR.getWarningCollector().getAllWarnings()
+ .forEach(x -> assertEquals(x.getWarningCode(), MIXING_AND_OR_WITHOUT_PARENTHESES.getWarningCode()));
}
@Test
diff --git a/linter/src/test/java/com/facebook/coresql/linter/warning/TestWarningCollector.java b/linter/src/test/java/com/facebook/coresql/linter/warning/TestWarningCollector.java
index f729489..1fe3e3b 100644
--- a/linter/src/test/java/com/facebook/coresql/linter/warning/TestWarningCollector.java
+++ b/linter/src/test/java/com/facebook/coresql/linter/warning/TestWarningCollector.java
@@ -14,10 +14,10 @@
package com.facebook.coresql.linter.warning;
import com.facebook.coresql.parser.AstNode;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.Test;
import static com.facebook.coresql.parser.ParserHelper.parseStatement;
-import static org.testng.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestWarningCollector
{
diff --git a/parser/build.gradle b/parser/build.gradle
new file mode 100644
index 0000000..5fcde87
--- /dev/null
+++ b/parser/build.gradle
@@ -0,0 +1,89 @@
+plugins {
+ id 'java'
+ id "ca.coglinc2.javacc" version "latest.release"
+}
+
+dependencies {
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.+'
+ testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.+'
+ testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.+'
+
+ // for the ASCII Trees
+ testImplementation 'hu.webarticum:tree-printer:+'
+
+ // https://mvnrepository.com/artifact/com.google.inject/guice
+ testImplementation 'com.google.inject:guice:5.1.0'
+
+ // https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api
+ testImplementation 'jakarta.validation:jakarta.validation-api:3.0.2'
+
+ // https://mvnrepository.com/artifact/io.airlift/log
+ testImplementation 'io.airlift:log:206'
+
+ // https://mvnrepository.com/artifact/io.airlift/configuration
+ testImplementation 'io.airlift:configuration:206'
+
+ // https://mvnrepository.com/artifact/io.airlift/bootstrap
+ testImplementation 'io.airlift:bootstrap:206'
+
+ // https://mvnrepository.com/artifact/com.google.guava/guava
+ testImplementation 'com.google.guava:guava:31.1-jre'
+
+ // enforce latest version of JavaCC
+ javacc 'net.java.dev.javacc:javacc:+'
+}
+
+java {
+ withSourcesJar()
+ withJavadocJar()
+}
+
+test {
+ useJUnitPlatform()
+ failFast=true
+ minHeapSize = "128m"
+ maxHeapSize = "1G"
+ jvmArgs << [
+ '-Djunit.jupiter.execution.parallel.enabled=true',
+ '-Djunit.jupiter.execution.parallel.config.strategy=dynamic',
+ '-Djunit.jupiter.execution.parallel.mode.default=concurrent',
+ //'-Xss=256k'
+ ]
+ filter {
+ excludeTestsMatching("IT*")
+ excludeTestsMatching("*IT")
+ excludeTestsMatching("*ITCase")
+ }
+
+ finalizedBy check
+}
+
+tasks.register('optionalTests', Test) {
+ useJUnitPlatform()
+
+ description = 'Runs optional tests.'
+ group = 'verification'
+
+ shouldRunAfter test
+
+ minHeapSize = "128m"
+ maxHeapSize = "1G"
+ jvmArgs << [
+ '-Djunit.jupiter.execution.parallel.enabled=true',
+ '-Djunit.jupiter.execution.parallel.config.strategy=dynamic',
+ '-Djunit.jupiter.execution.parallel.mode.default=concurrent'
+ ]
+ filter {
+ includeTestsMatching("IT*")
+ includeTestsMatching("*IT")
+ includeTestsMatching("*ITCase")
+ }
+
+ testLogging {
+ events "passed"
+ }
+}
+
+compileJavacc {
+ arguments = [grammar_encoding: 'UTF-8', static: 'false' /*, java_template_type: 'modern'*/ ]
+}
diff --git a/parser/grammar/javacc-options-java.txt b/parser/grammar/javacc-options-java.txt
index d598cd2..1aa6deb 100644
--- a/parser/grammar/javacc-options-java.txt
+++ b/parser/grammar/javacc-options-java.txt
@@ -12,6 +12,9 @@ options {
VISITOR = true;
VISITOR_RETURN_TYPE = "void";
VISITOR_DATA_TYPE = "Void";
+ DEBUG_PARSER = false;
+ DEBUG_LOOKAHEAD = false;
+ DEBUG_TOKEN_MANAGER = false;
}
PARSER_BEGIN(SqlParser)
diff --git a/parser/grammar/prepare-javacc-grammar.sh b/parser/grammar/prepare-javacc-grammar.sh
index 4340990..e3046b0 100755
--- a/parser/grammar/prepare-javacc-grammar.sh
+++ b/parser/grammar/prepare-javacc-grammar.sh
@@ -1,4 +1,4 @@
# Concatenate all the fragments into a .jj file.
-gendir='../target/generated-sources/javacc'
+gendir='../main/jjtree/com/facebook/coresql/parser/'
mkdir -p $gendir
-cat javacc-options-java.txt nonreservedwords.txt reservedwords.txt sql-spec.txt presto-extensions.txt lexical-elements.txt > $gendir/parser_tmp.jjt
+cat javacc-options-java.txt nonreservedwords.txt reservedwords.txt sql-spec.txt presto-extensions.txt lexical-elements.txt > $gendir/parser.jjt
diff --git a/parser/main/jjtree/com/facebook/coresql/parser/parser.jjt b/parser/main/jjtree/com/facebook/coresql/parser/parser.jjt
new file mode 100644
index 0000000..0c5fbcc
--- /dev/null
+++ b/parser/main/jjtree/com/facebook/coresql/parser/parser.jjt
@@ -0,0 +1,8321 @@
+options {
+ STATIC = false;
+ LOOKAHEAD=3;
+ IGNORE_CASE=true;
+ UNICODE_INPUT=true;
+ ERROR_REPORTING=false;
+ NODE_DEFAULT_VOID = true;
+ NODE_SCOPE_HOOK = true;
+ NODE_CLASS = "AstNode";
+ NODE_PREFIX = "";
+ MULTI = true;
+ VISITOR = true;
+ VISITOR_RETURN_TYPE = "void";
+ VISITOR_DATA_TYPE = "Void";
+ DEBUG_PARSER = false;
+ DEBUG_LOOKAHEAD = false;
+ DEBUG_TOKEN_MANAGER = false;
+}
+
+PARSER_BEGIN(SqlParser)
+package com.facebook.coresql.parser;
+
+public class SqlParser {
+ private boolean IsIdNonReservedWord() {
+ int kind = getToken(1).kind;
+ if (kind == regular_identifier || kind == delimited_identifier || kind == Unicode_delimited_identifier) return true;
+
+ if (!(kind >= MIN_NON_RESERVED_WORD && kind <= MAX_NON_RESERVED_WORD)) return false; // Not a nonreserved word.
+
+ // Some special cases.
+ switch (kind) {
+ // Some contextual keywords
+ case GROUP:
+ case ORDER:
+ case PARTITION:
+ return getToken(2).kind != BY;
+
+ case LIMIT:
+ return getToken(2).kind != unsigned_integer;
+
+ case ROWS:
+ return getToken(2).kind != BETWEEN;
+
+ // Some builtin functions
+ case TRIM:
+ case POSITION:
+ case MOD:
+ case POWER:
+ case RANK:
+ case ROW_NUMBER:
+ case FLOOR:
+ case MIN:
+ case MAX:
+ case UPPER:
+ case LOWER:
+ case CARDINALITY:
+ case ABS:
+ return getToken(2).kind != lparen;
+
+ default:
+ return true;
+ }
+ }
+
+ private boolean SyncToSemicolon() {
+ while (getToken(1).kind != EOF && getToken(1).kind != SqlParserConstants.semicolon) getNextToken();
+
+ if (getToken(1).kind == semicolon) {
+ getNextToken();
+ }
+
+ return true;
+ }
+
+ private boolean NotEof() {
+ return getToken(1).kind != EOF;
+ }
+
+ public void PushNode(Node node) { jjtree.pushNode(node); }
+ public Node PopNode() { return jjtree.popNode(); }
+
+ void jjtreeOpenNodeScope(Node node) {
+ ((AstNode)node).beginToken = getToken(1);
+ }
+
+ void jjtreeCloseNodeScope(Node node) {
+ AstNode astNode = ((AstNode)node);
+ astNode.endToken = getToken(0);
+ Token t = astNode.beginToken;
+
+ // For some nodes, the node is opened after some children are already created. Reset the begin for those to be
+ // the begin of the left-most child.
+ if (astNode.NumChildren() > 0) {
+ Token t0 = astNode.GetChild(0).beginToken;
+ if (t0.beginLine < t.beginLine || (t0.beginLine == t.beginLine && t0.beginColumn < t.beginColumn)) {
+ astNode.beginToken = t0;
+ }
+ }
+
+ if (astNode.IsNegatableOperator()) {
+ Token t1 = astNode.GetChild(0).endToken;
+
+ if (astNode.Kind() == JJTISNULL) {
+ // IsNull -- see if the penultimate token is NOT
+ while (t1 != null && t1.kind != IS) {
+ t1 = t1.next;
+ }
+
+ if (t1.next.kind == NOT) {
+ astNode.SetNegated(true);
+ }
+ }
+ else if (astNode.NumChildren() > 1) {
+ Token t2 = astNode.GetChild(1).beginToken;
+ while (t1.next != null && t1.next != t2) {
+ if (t1.kind == NOT) {
+ astNode.SetNegated(true);
+ break;
+ }
+ t1 = t1.next;
+ }
+ }
+ }
+ else if (astNode.NumChildren() == 2 && astNode.IsOperator()) {
+ // Hack locate the token just before the first token of the second operator
+ Token t1 = astNode.GetChild(0).endToken;
+ Token t2 = astNode.GetChild(1).beginToken;
+ while (t1.next != null && t1.next != t2) {
+ t1 = t1.next;
+ }
+ astNode.SetOperator(t1.kind);
+ }
+ else if (astNode.NumChildren() == 1 && astNode.IsOperator()) {
+ astNode.SetOperator(astNode.beginToken.kind);
+ }
+ }
+
+ public AstNode getResult()
+ {
+ return (AstNode) jjtree.popNode();
+ }
+ }
+
+PARSER_END(SqlParser)
+
+TOKEN_MGR_DECLS:
+{
+ void setKindToIdentifier(Token t) {
+ t.kind = regular_identifier;
+ }
+
+ void setUnicodeLiteralType(Token t) {
+ t.kind = unicode_literal;
+ }
+
+ void StoreImage(Token matchedToken) {
+ matchedToken.image = image.toString();
+ }
+}
+
+// Temporary entry point
+Node CompilationUnit() #CompilationUnit:
+{}
+{
+ (
+ LOOKAHEAD({ NotEof() })
+ try {
+ direct_SQL_statement()
+ } catch(ParseException pe) {
+ System.err.println("Parse error: " + getToken(1).beginLine + ":" + getToken(1).beginColumn + " at token: " + getToken(1).image);
+ SyncToSemicolon();
+ }
+ )*
+
+
+
+ { return jjtThis; }
+}
+// non_reserved words
+
+SKIP:
+{
+ // Dummy token to get a value range. Will never be mached. And it should be here positionally - before the first non reserved word
+
+}
+
+// This production should be here and moved out. See notes for details on handling non-reserved words.
+void non_reserved_word():
+{}
+{
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ | // Non-standard
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ | // Non-standard
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ | // Non-standard
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+
+ // Non-standard
+ // Changed the following reserved words into non-reserved one as lot of users use them as identifiers.
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |