Repository: crate/cratedb-sqlparse (language: python)

Issue:
Change error handling strategy and add verbose errors
## Summary of the changes / Why this is an improvement
Issue #2

This is one of the first pieces required to finish the feature and its only implemented in Python for early feedback/validation. 

Main changes of PR:
- Throwing antlr4 parse errors as exceptions is now only enabled by `raise_exception` API influenced by [DRF Validators](https://www.django-rest-framework.org/api-guide/exceptions/#validationerror)

- Default behaviour is not to raise an exception, the reason is that it forces clients to try/catch errors, when you already work with CrateDB in the HTTP protocol you are used to check the json response, rather than catching exceptions, this change of behaviour aims to make integrating with this library as similar as possible as with CrateDB.

- Implements an `ExceptionCollectorListener` that collects all errors that are caught, (remember that we use `parser.statements`, to support running several queries on the frontends, so several errors can happen simultaneously)

- Adds more verbose errors 

**_The main issue I would like help with the review:_** <-------------------------------------
- Whenever we collect the errors I just add the errors to a list for further processing:
```python
class ExceptionCollectorListener(ErrorListener):
    """
    Error listener that collects all errors into errors for further processing.
    Based partially on https://github.com/antlr/antlr4/issues/396
    """

    def __init__(self):
        self.errors = []

    def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
        error = ParsingException(
            msg=msg,
            offending_token=offendingSymbol,
            e=e,
            query=e.ctx.parser.getTokenStream().getText(e.ctx.start, e.offendingToken.tokenIndex),
        )

        self.errors.append(error)

```


we lose the information on which statement the error belongs to, I tried with different ErrorStrategies and Error listeners but to no avail, also didn't find anything online.

My best effort is to try to match the error's offendingToken query, to any of the parsed statements:

```python
# Shortened for clarity purposes.
def find_suitable_error(statement, errors):
    for error in errors[:]:
        if error.query == statement.query:
            statement.exception = error
            errors.pop(errors.index(error))
```
There are a couple of edge cases that I covered but in short, it works reasonable well, even though I'm not a fan of it, maybe you folks way more experience in `antlr4 ` have an idea?

## Checklist

 - [x] Link to issue this PR refers to (if applicable): 
 - [x] [CLA](https://crate.io/community/contribute/cla/) is signed

Add `with_properties` and `interpolated_properties`
## Summary of the changes / Why this is an improvement

This PR allows for extracting properties defined by `[ WITH ( parameter [= value] [, ... ] ) ]` statements.

For example:

```python
from cratedb_sqlparse import sqlparse

stmt = sqlparse("""
    CREATE TABLE doc.tbl12 (A TEXT) WITH (
      "allocation.max_retries" = 5,
      "blocks.metadata" = false
    );
""")[0]

print(stmt.metadata)
# Metadata(schema='doc', table_name='tbl12', interpolated_properties={}, with_properties={'allocation.max_retries': '5', 'blocks.metadata': 'false'})
```

It also handles the special case of what I call, `interpolated values` and #31 if you have a better name recommendation, please go ahead :P

## Checklist

 - [x] Link to issue this PR refers to (if applicable): 
 - [x] [CLA](https://crate.io/community/contribute/cla/) is signed

Add verbose options to both python and javascript target to include query and approximate position of the error.
From https://github.com/crate/crate/issues/15826

Steps:
- Implement a new ErrorListener that displays the query with the offending token marked both in javascript and python targets.
- Add the Error to every parsed statement. An error could be an object with the following properties:
```
{
   "error": "ParseError",
   "original_query": '...",
   "original_query_with_offending_token": "...",
   "offending_token": "...",
   "error_message": "...",
   "col": 1,
   "line": 1
}
```

We should be able to disable this by:

```python
sqlparse('SELECT 1;', raise_exception=True)
```

Missing: Javascript target
Migrate cratedb_sqlparse from javascript to typescript
Right now `cratedb_sqlparse` does not support typescript, so every typescript project that wants to use this library needs to define their own types, leading to possible type error(s). We should migrate it to typescript (since it is also compatible with plain javascript).


Discussion / hints:
cc @proddata I'd love your feedback as well as any input on this
I read through it and generally like the idea of not raising an exception. However, I'm not sure about the defaults.

Regarding the "main issue," I don't have a strong opinion as I lack details on the mechanics. If the approach works, though, 👍.


Honestly I have never liked the typescript DX, I would much rather [generate the typescript types from javascript](https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html), so users get the benefit of typescript and not port the project to ts.

Give me a few days while I finish the lats updates #38, I'll try to generate all the types, if I don't manage to create something that satisfy you, I'll give in and migrate to/learn typescript. 



Fix patch:
diff --git a/cratedb_sqlparse_js/README.md b/cratedb_sqlparse_js/README.md
index 6019bd6..36e7f43 100644
--- a/cratedb_sqlparse_js/README.md
+++ b/cratedb_sqlparse_js/README.md
@@ -9,12 +9,12 @@
 ![NPM Unpacked Size](https://img.shields.io/npm/unpacked-size/@cratedb/cratedb-sqlparse)
 ![NPM Type Definitions](https://img.shields.io/npm/types/@cratedb/cratedb-sqlparse)
 
-
 CrateDB SQL Parser for JavaScript, compiled from antlr4 JavaScript compile target.
 
 ### Simple usage
+
 ```javascript
-import { sqlparse } from "@cratedb/cratedb-sqlparse";
+import {sqlparse} from "@cratedb/cratedb-sqlparse";
 
 const query = `
 SELECT * FROM SYS.SHARDS;
@@ -37,27 +37,197 @@ console.log(queries[0].original_query)
 ```
 
 ### CrateDB version
+
 You can programmatically check the CrateDB version the package was compiled for in `index.js`
 
 ```javascript
-import { __cratedb_version__ } from "@cratedb/cratedb-sqlparse";
+import {__cratedb_version__} from "@cratedb/cratedb-sqlparse";
 
 console.log(__cratedb_version__)
-// 5.6.4
+// 5.7.2
 ```
 
 ### Features
-Currently, we support the same features as CrateDB java's parser:
+
+Currently, the parser supports a subset of the features of CrateDB's Java/ANTLR parser:
+
 - First class CrateDB SQL dialect support.
 - Input is case-insensitive.
-- Native errors as exceptions.
+- Native errors as exceptions or as objects.
 - Dollar strings.
+- Tables
+- Properties and parametrized properties.
+
+### Exceptions and errors.
+
+By default, exceptions are stored in `statement.exception`.
 
-Optional features:
+```javascript
+import {sqlparse} from "@cratedb/cratedb-sqlparse";
 
-### Errors
-Errors are thrown as 'ParseError' e.g.
+const query = `
+SELECT COUNT(*) FROM doc.tbl f HERE f.id = 1;
 
-```text
-ParseError: line2:9 mismatched input 'ROM' expecting {<EOF>, ';'}
+INSERT INTO doc.tbl VALUES (1, 23, 4);
+`
+const statements = sqlparse(query)
+const stmt = statements[0]
+
+if (stmt.exception) {
+    console.log(stmt.exception.errorMessage)
+    // [line 2:43 mismatched input 'HERE' expecting {<EOF>, ';'}]
+
+    console.log(stmt.exception.errorMessageVerbose)
+    //      SELECT COUNT(*) FROM doc.tbl f HERE f.id = 1;
+    //                                     ^^^^
+    //      INSERT INTO doc.tbl VALUES (1, 23, 4);
+}
+
+console.log(stmt.exception)
+
+// ParseError: mismatched input 'HERE' expecting {<EOF>, ';'}
+//     at ExceptionCollectorListener.syntaxError (file:///home/surister/PycharmProjects/cratedb-sqlparse/cratedb_sqlparse_js/cratedb_sqlparse/parser.js:115:23)
+//     at file:///home/surister/PycharmProjects/cratedb-sqlparse/cratedb_sqlparse_js/node_modules/antlr4/dist/antlr4.node.mjs:1:42125
+//     at Array.map (<anonymous>)
+//     at wt.syntaxError (file:///home/surister/PycharmProjects/cratedb-sqlparse/cratedb_sqlparse_js/node_modules/antlr4/dist/antlr4.node.mjs:1:42115)
+//     at SqlBaseParser.notifyErrorListeners (file:///home/surister/PycharmProjects/cratedb-sqlparse/cratedb_sqlparse_js/node_modules/antlr4/dist/antlr4.node.mjs:1:102085)
+//     at Ce.reportInputMismatch (file:///home/surister/PycharmProjects/cratedb-sqlparse/cratedb_sqlparse_js/node_modules/antlr4/dist/antlr4.node.mjs:1:90577)
+//     at Ce.reportError (file:///home/surister/PycharmProjects/cratedb-sqlparse/cratedb_sqlparse_js/node_modules/antlr4/dist/antlr4.node.mjs:1:88813)
+//     at SqlBaseParser.statements (file:///home/surister/PycharmProjects/cratedb-sqlparse/cratedb_sqlparse_js/cratedb_sqlparse/generated_parser/SqlBaseParser.js:1345:28)
+//     at sqlparse (file:///home/surister/PycharmProjects/cratedb-sqlparse/cratedb_sqlparse_js/cratedb_sqlparse/parser.js:207:25)
+//     at file:///home/surister/PycharmProjects/cratedb-sqlparse/cratedb_sqlparse_js/t.js:4:14 {
+//   query: 'SELECT COUNT(*) FROM doc.tbl f HERE',
+//   msg: "mismatched input 'HERE' expecting {<EOF>, ';'}",
+//   offendingToken: bt {
+//     source: [ [SqlBaseLexer], [CaseInsensitiveStream] ],
+//     type: 322,
+//     channel: 0,
+//     start: 32,
+//     stop: 35,
+//     tokenIndex: 16,
+//     line: 2,
+//     column: 31,
+//     _text: null
+//   },
+//   line: 2,
+//   column: 31,
+//   errorMessage: "[line 2:31 mismatched input 'HERE' expecting {<EOF>, ';'}]",
+//   errorMessageVerbose: '\n' +
+//     'SELECT COUNT(*) FROM doc.tbl f HERE f.id = 1;\n' +
+//     '                               ^^^^\n' +
+//     '\n' +
+//     'INSERT INTO doc.tbl VALUES (1, 23, 4);\n'
+// }
 ```
+
+In some situations, you might want sqlparse to throw an error.
+
+You can set `raise_exception` to `true`
+
+```javascript
+import {sqlparse} from "@cratedb/cratedb-sqlparse";
+
+let stmt = sqlparse('SELECT COUNT(*) FROM doc.tbl f WHERE .id = 1;', true);
+
+//         throw new ParseError(
+//            ^
+//
+// ParseError: no viable alternative at input 'SELECT COUNT(*) FROM doc.tbl f WHERE .'
+```
+
+Catch the exception:
+
+```javascript
+import {sqlparse} from "@cratedb/cratedb-sqlparse";
+
+try {
+    sqlparse('SELECT COUNT(*) FROM doc.tbl f WHERE .id = 1;', true)
+} catch (e) {
+    console.log(e)
+}
+```
+
+> [!NOTE]
+> It will only raise the first exception it finds, even if you pass in several statements.
+
+### Query metadata
+
+Query metadata can be read with `statement.metadata`
+
+```javascript
+import {sqlparse} from "@cratedb/cratedb-sqlparse";
+
+const stmt = sqlparse("SELECT A, B FROM doc.tbl12")[0]
+
+console.log(stmt.metadata);
+
+// Metadata {
+//   tables: [ Table { name: 'tbl12', schema: 'doc' } ],
+//   parameterizedProperties: {},
+//   withProperties: {}
+// }
+
+```
+
+#### Query properties
+
+Properties defined within a `WITH` statement, `statement.metadata.withProperties:`.
+
+```javascript
+import {sqlparse} from "@cratedb/cratedb-sqlparse";
+
+
+const stmt = sqlparse(`
+    CREATE TABLE doc.tbl12 (A TEXT) WITH (
+      "allocation.max_retries" = 5,
+      "blocks.metadata" = false
+    );
+`)[0]
+
+console.log(stmt.metadata);
+
+// Metadata {
+//   tables: [ Table { name: 'tbl12', schema: 'doc' } ],
+//   parameterizedProperties: {},
+//   withProperties: { 'allocation.max_retries': '5', 'blocks.metadata': 'false' }
+// }
+```
+
+#### Table name
+```javascript
+console.log(stmt.metadata.tables)
+// [ Table { name: 'tbl12', schema: 'doc' } ]
+
+table = stmt.metadata.tables[0]
+
+console.log(table.schema, table.name, table.fqn)
+// doc tbl12 "doc"."tbl12"
+```
+
+#### Parameterized properties
+
+Parameterized properties are properties without a real defined value, marked with a dollar string,  `metadata.parameterized_properties`
+
+```javascript
+import {sqlparse} from "@cratedb/cratedb-sqlparse";
+
+const stmt = sqlparse(`
+    CREATE TABLE doc.tbl12 (A TEXT) WITH (
+    "allocation.max_retries" = 5,
+    "blocks.metadata" = $1
+);
+`)[0]
+
+console.log(stmt.metadata)
+
+// Metadata {
+//   tables: [ Table { name: 'tbl12', schema: 'doc', fqn: '"doc"."tbl12"' } ],
+//   parameterizedProperties: { 'blocks.metadata': '$1' },
+//   withProperties: { 'allocation.max_retries': '5', 'blocks.metadata': '$1' }
+// }
+```
+
+In this case, `blocks.metadata` will be in `with_properties` and `parameterized_properties` as well.
+
+For values to be picked up they need to start with a dollar `'$'` and be preceded by integers, e.g. `'$1'` or `'$123'`.
+`'$123abc'` would not be valid.
\ No newline at end of file
diff --git a/cratedb_sqlparse_js/cratedb_sqlparse/AstBuilder.js b/cratedb_sqlparse_js/cratedb_sqlparse/AstBuilder.js
new file mode 100644
index 0000000..151b7f1
--- /dev/null
+++ b/cratedb_sqlparse_js/cratedb_sqlparse/AstBuilder.js
@@ -0,0 +1,98 @@
+import SqlBaseParserVisitor from "./generated_parser/SqlBaseParserVisitor.js";
+import SqlBaseParser from "./generated_parser/SqlBaseParser.js";
+import {Statement} from "./parser.js"
+import {Table} from "./models.js"
+
+
+/**
+ *
+ * @param {string} text
+ * @returns {Boolean}
+ */
+function isDigit(text) {
+    return text.split('').every(char => char >= '0' && char <= '9');
+}
+
+
+export class AstBuilder extends SqlBaseParserVisitor {
+//     The class implements the antlr4 visitor pattern similar to how we do it in CrateDB
+//     https://github.com/crate/crate/blob/master/libs/sql-parser/src/main/java/io/crate/sql/parser/AstBuilder.java
+//
+//     The biggest difference is that in CrateDB, `AstBuilder`, visitor methods
+//     return a specialized Statement visitor.
+//
+//     Sqlparse just extracts whatever data it needs from the context and injects it to the current
+//     visited statement, enriching its metadata.
+
+    /**
+     *
+     * @param {Object} node
+     * @returns {(string|null)}
+     */
+    getText(node) {
+        if (node) {
+            return node.getText().replaceAll("'", "").replaceAll('"', "")
+        }
+        return null
+    }
+
+    /**
+     *
+     * @param {Statement} stmt
+     */
+    enrich(stmt) {
+        this.stmt = stmt
+        this.visit(this.stmt.ctx)
+    }
+
+    /**
+     *
+     * @param {SqlBaseParser.TableNameContext} ctx
+     */
+    visitTableName(ctx) {
+        const fqn = ctx.qname()
+        const parts = this.getText(fqn).split(".")
+
+        let schema = null
+        let name = null;
+        if (parts.length === 1) {
+            name = parts[0]
+        } else {
+            schema = parts[0]
+            name = parts[1]
+        }
+
+        this.stmt.metadata.tables.push(
+            new Table(name, schema)
+        )
+    }
+
+    /**
+     *
+     * @param {SqlBaseParser.GenericPropertiesContext} ctx
+     */
+    visitGenericProperties(ctx) {
+        const nodeProperties = ctx.genericProperty()
+        const properties = {}
+        const parameterizedProperties = {}
+
+        for (const property of nodeProperties) {
+            let key = this.getText(property.ident())
+            let value = this.getText(property.expr())
+
+            properties[key] = value
+
+            if (value && value[0] === '$') {
+                // It might be a parameterized value, e.g. '$1'
+                if (isDigit(value.slice(1))) {
+                    parameterizedProperties[key] = value
+                }
+            }
+
+            this.stmt.metadata.withProperties = properties
+            this.stmt.metadata.parameterizedProperties = parameterizedProperties
+
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/cratedb_sqlparse_js/cratedb_sqlparse/generated_parser/SqlBaseParserVisitor.js b/cratedb_sqlparse_js/cratedb_sqlparse/generated_parser/SqlBaseParserVisitor.js
new file mode 100644
index 0000000..3c050de
--- /dev/null
+++ b/cratedb_sqlparse_js/cratedb_sqlparse/generated_parser/SqlBaseParserVisitor.js
@@ -0,0 +1,1702 @@
+// Generated from SqlBaseParser.g4 by ANTLR 4.13.1
+// jshint ignore: start
+import antlr4 from 'antlr4';
+
+// This class defines a complete generic visitor for a parse tree produced by SqlBaseParser.
+
+export default class SqlBaseParserVisitor extends antlr4.tree.ParseTreeVisitor {
+
+	// Visit a parse tree produced by SqlBaseParser#statements.
+	visitStatements(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#singleStatement.
+	visitSingleStatement(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#singleExpression.
+	visitSingleExpression(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#default.
+	visitDefault(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#begin.
+	visitBegin(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#startTransaction.
+	visitStartTransaction(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#commit.
+	visitCommit(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#explain.
+	visitExplain(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#optimize.
+	visitOptimize(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#refreshTable.
+	visitRefreshTable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#update.
+	visitUpdate(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#delete.
+	visitDelete(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#showTransaction.
+	visitShowTransaction(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#showCreateTable.
+	visitShowCreateTable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#showTables.
+	visitShowTables(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#showSchemas.
+	visitShowSchemas(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#showColumns.
+	visitShowColumns(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#showSessionParameter.
+	visitShowSessionParameter(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alter.
+	visitAlter(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#resetGlobal.
+	visitResetGlobal(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#setTransaction.
+	visitSetTransaction(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#setSessionAuthorization.
+	visitSetSessionAuthorization(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#resetSessionAuthorization.
+	visitResetSessionAuthorization(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#set.
+	visitSet(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#setGlobal.
+	visitSetGlobal(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#setTimeZone.
+	visitSetTimeZone(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#kill.
+	visitKill(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#insert.
+	visitInsert(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#restore.
+	visitRestore(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#copyFrom.
+	visitCopyFrom(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#copyTo.
+	visitCopyTo(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#drop.
+	visitDrop(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#grantPrivilege.
+	visitGrantPrivilege(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#denyPrivilege.
+	visitDenyPrivilege(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#revokePrivilege.
+	visitRevokePrivilege(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#create.
+	visitCreate(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#deallocate.
+	visitDeallocate(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#analyze.
+	visitAnalyze(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#discard.
+	visitDiscard(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#declare.
+	visitDeclare(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#fetch.
+	visitFetch(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#close.
+	visitClose(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropBlobTable.
+	visitDropBlobTable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropTable.
+	visitDropTable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropAlias.
+	visitDropAlias(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropRepository.
+	visitDropRepository(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropSnapshot.
+	visitDropSnapshot(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropFunction.
+	visitDropFunction(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropRole.
+	visitDropRole(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropView.
+	visitDropView(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropAnalyzer.
+	visitDropAnalyzer(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropPublication.
+	visitDropPublication(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropSubscription.
+	visitDropSubscription(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropServer.
+	visitDropServer(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropForeignTable.
+	visitDropForeignTable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropUserMapping.
+	visitDropUserMapping(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#addColumn.
+	visitAddColumn(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropColumn.
+	visitDropColumn(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropCheckConstraint.
+	visitDropCheckConstraint(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterTableProperties.
+	visitAlterTableProperties(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterBlobTableProperties.
+	visitAlterBlobTableProperties(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterTableOpenClose.
+	visitAlterTableOpenClose(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterTableRenameTable.
+	visitAlterTableRenameTable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterTableRenameColumn.
+	visitAlterTableRenameColumn(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterTableReroute.
+	visitAlterTableReroute(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterClusterRerouteRetryFailed.
+	visitAlterClusterRerouteRetryFailed(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterClusterSwapTable.
+	visitAlterClusterSwapTable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterClusterDecommissionNode.
+	visitAlterClusterDecommissionNode(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterClusterGCDanglingArtifacts.
+	visitAlterClusterGCDanglingArtifacts(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterRole.
+	visitAlterRole(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterPublication.
+	visitAlterPublication(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterSubscription.
+	visitAlterSubscription(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#queryOptParens.
+	visitQueryOptParens(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#query.
+	visitQuery(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#queryNoWith.
+	visitQueryNoWith(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#limitClause.
+	visitLimitClause(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#offsetClause.
+	visitOffsetClause(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#queryTermDefault.
+	visitQueryTermDefault(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#setOperation.
+	visitSetOperation(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#setQuant.
+	visitSetQuant(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#sortItem.
+	visitSortItem(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#defaultQuerySpec.
+	visitDefaultQuerySpec(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#valuesRelation.
+	visitValuesRelation(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#selectSingle.
+	visitSelectSingle(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#selectAll.
+	visitSelectAll(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#where.
+	visitWhere(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#returning.
+	visitReturning(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#filter.
+	visitFilter(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#relationDefault.
+	visitRelationDefault(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#joinRelation.
+	visitJoinRelation(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#joinType.
+	visitJoinType(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#joinCriteria.
+	visitJoinCriteria(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#aliasedRelation.
+	visitAliasedRelation(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#tableRelation.
+	visitTableRelation(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#subqueryRelation.
+	visitSubqueryRelation(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#parenthesizedRelation.
+	visitParenthesizedRelation(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#tableWithPartition.
+	visitTableWithPartition(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#tableName.
+	visitTableName(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#tableFunction.
+	visitTableFunction(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#aliasedColumns.
+	visitAliasedColumns(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#with.
+	visitWith(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#namedQuery.
+	visitNamedQuery(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#expr.
+	visitExpr(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#logicalNot.
+	visitLogicalNot(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#booleanDefault.
+	visitBooleanDefault(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#match.
+	visitMatch(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#logicalBinary.
+	visitLogicalBinary(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#predicated.
+	visitPredicated(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#comparison.
+	visitComparison(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#quantifiedComparison.
+	visitQuantifiedComparison(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#between.
+	visitBetween(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#inList.
+	visitInList(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#inSubquery.
+	visitInSubquery(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#like.
+	visitLike(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#arrayLike.
+	visitArrayLike(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#nullPredicate.
+	visitNullPredicate(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#distinctFrom.
+	visitDistinctFrom(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#bitwiseBinary.
+	visitBitwiseBinary(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#valueExpressionDefault.
+	visitValueExpressionDefault(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#concatenation.
+	visitConcatenation(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#fromStringLiteralCast.
+	visitFromStringLiteralCast(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#arithmeticBinary.
+	visitArithmeticBinary(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#arithmeticUnary.
+	visitArithmeticUnary(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#subqueryExpressionDefault.
+	visitSubqueryExpressionDefault(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dereference.
+	visitDereference(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnReference.
+	visitColumnReference(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#atTimezone.
+	visitAtTimezone(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#subscript.
+	visitSubscript(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#recordSubscript.
+	visitRecordSubscript(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#explicitFunctionDefault.
+	visitExplicitFunctionDefault(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#doubleColonCast.
+	visitDoubleColonCast(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#defaultParamOrLiteral.
+	visitDefaultParamOrLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#functionCall.
+	visitFunctionCall(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#nestedExpression.
+	visitNestedExpression(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#arraySlice.
+	visitArraySlice(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#exists.
+	visitExists(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#emptyArray.
+	visitEmptyArray(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#specialDateTimeFunction.
+	visitSpecialDateTimeFunction(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#currentSchema.
+	visitCurrentSchema(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#currentUser.
+	visitCurrentUser(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#sessionUser.
+	visitSessionUser(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#left.
+	visitLeft(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#right.
+	visitRight(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#substring.
+	visitSubstring(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#trim.
+	visitTrim(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#extract.
+	visitExtract(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#cast.
+	visitCast(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#simpleCase.
+	visitSimpleCase(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#searchedCase.
+	visitSearchedCase(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#ifCase.
+	visitIfCase(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#arraySubquery.
+	visitArraySubquery(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#subqueryExpression.
+	visitSubqueryExpression(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#simpleLiteral.
+	visitSimpleLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#arrayLiteral.
+	visitArrayLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#objectLiteral.
+	visitObjectLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#parameterOrSimpleLiteral.
+	visitParameterOrSimpleLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#parameterExpression.
+	visitParameterExpression(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#intAsLiteral.
+	visitIntAsLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#nullAsLiteral.
+	visitNullAsLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#integerParamOrLiteralDoubleColonCast.
+	visitIntegerParamOrLiteralDoubleColonCast(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#integerParamOrLiteralCast.
+	visitIntegerParamOrLiteralCast(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#parameterOrIdent.
+	visitParameterOrIdent(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#parameterOrString.
+	visitParameterOrString(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#positionalParameter.
+	visitPositionalParameter(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#parameterPlaceholder.
+	visitParameterPlaceholder(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#nullLiteral.
+	visitNullLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#escapedCharsStringLiteral.
+	visitEscapedCharsStringLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dollarQuotedStringLiteral.
+	visitDollarQuotedStringLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#stringLiteral.
+	visitStringLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#bitString.
+	visitBitString(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#subscriptSafe.
+	visitSubscriptSafe(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#cmpOp.
+	visitCmpOp(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#setCmpQuantifier.
+	visitSetCmpQuantifier(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#whenClause.
+	visitWhenClause(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#namedWindow.
+	visitNamedWindow(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#over.
+	visitOver(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#windowDefinition.
+	visitWindowDefinition(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#windowFrame.
+	visitWindowFrame(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#unboundedFrame.
+	visitUnboundedFrame(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#currentRowBound.
+	visitCurrentRowBound(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#boundedFrame.
+	visitBoundedFrame(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#qnames.
+	visitQnames(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#qname.
+	visitQname(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#spaceSeparatedIdents.
+	visitSpaceSeparatedIdents(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#identWithOrWithoutValue.
+	visitIdentWithOrWithoutValue(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#idents.
+	visitIdents(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#ident.
+	visitIdent(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#unquotedIdentifier.
+	visitUnquotedIdentifier(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#digitIdentifier.
+	visitDigitIdentifier(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#quotedIdentifier.
+	visitQuotedIdentifier(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#backQuotedIdentifier.
+	visitBackQuotedIdentifier(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#stringLiteralOrIdentifier.
+	visitStringLiteralOrIdentifier(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#stringLiteralOrIdentifierOrQname.
+	visitStringLiteralOrIdentifierOrQname(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#numericLiteral.
+	visitNumericLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#intervalLiteral.
+	visitIntervalLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#intervalField.
+	visitIntervalField(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#booleanLiteral.
+	visitBooleanLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#decimalLiteral.
+	visitDecimalLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#integerLiteral.
+	visitIntegerLiteral(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#objectKeyValue.
+	visitObjectKeyValue(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#insertSource.
+	visitInsertSource(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#onConflict.
+	visitOnConflict(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#conflictTarget.
+	visitConflictTarget(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#values.
+	visitValues(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columns.
+	visitColumns(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#assignment.
+	visitAssignment(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createTable.
+	visitCreateTable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createTableAs.
+	visitCreateTableAs(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createForeignTable.
+	visitCreateForeignTable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createBlobTable.
+	visitCreateBlobTable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createRepository.
+	visitCreateRepository(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createSnapshot.
+	visitCreateSnapshot(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createAnalyzer.
+	visitCreateAnalyzer(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createFunction.
+	visitCreateFunction(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createUserMapping.
+	visitCreateUserMapping(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createRole.
+	visitCreateRole(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createView.
+	visitCreateView(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createPublication.
+	visitCreatePublication(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createSubscription.
+	visitCreateSubscription(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#createServer.
+	visitCreateServer(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#mappedUser.
+	visitMappedUser(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#kvOptions.
+	visitKvOptions(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#kvOption.
+	visitKvOption(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#functionArgument.
+	visitFunctionArgument(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#tableOnly.
+	visitTableOnly(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#tableWithPartitionDefault.
+	visitTableWithPartitionDefault(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#alterSubscriptionMode.
+	visitAlterSubscriptionMode(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#partitionedByOrClusteredInto.
+	visitPartitionedByOrClusteredInto(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#partitionedBy.
+	visitPartitionedBy(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#clusteredBy.
+	visitClusteredBy(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#blobClusteredInto.
+	visitBlobClusteredInto(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnDefinitionDefault.
+	visitColumnDefinitionDefault(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#primaryKeyConstraintTableLevel.
+	visitPrimaryKeyConstraintTableLevel(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#indexDefinition.
+	visitIndexDefinition(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#tableCheckConstraint.
+	visitTableCheckConstraint(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnDefinition.
+	visitColumnDefinition(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#addColumnDefinition.
+	visitAddColumnDefinition(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#dropColumnDefinition.
+	visitDropColumnDefinition(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#rerouteMoveShard.
+	visitRerouteMoveShard(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#rerouteAllocateReplicaShard.
+	visitRerouteAllocateReplicaShard(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#reroutePromoteReplica.
+	visitReroutePromoteReplica(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#rerouteCancelShard.
+	visitRerouteCancelShard(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#objectDataType.
+	visitObjectDataType(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#maybeParametrizedDataType.
+	visitMaybeParametrizedDataType(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#arrayDataType.
+	visitArrayDataType(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#definedDataTypeDefault.
+	visitDefinedDataTypeDefault(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#identDataType.
+	visitIdentDataType(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#definedDataType.
+	visitDefinedDataType(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#objectTypeDefinition.
+	visitObjectTypeDefinition(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnConstraintPrimaryKey.
+	visitColumnConstraintPrimaryKey(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnConstraintNotNull.
+	visitColumnConstraintNotNull(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnConstraintNull.
+	visitColumnConstraintNull(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnIndexConstraint.
+	visitColumnIndexConstraint(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnIndexOff.
+	visitColumnIndexOff(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnStorageDefinition.
+	visitColumnStorageDefinition(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnDefaultConstraint.
+	visitColumnDefaultConstraint(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnGeneratedConstraint.
+	visitColumnGeneratedConstraint(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#columnCheckConstraint.
+	visitColumnCheckConstraint(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#primaryKeyContraint.
+	visitPrimaryKeyContraint(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#checkConstraint.
+	visitCheckConstraint(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#withGenericProperties.
+	visitWithGenericProperties(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#genericProperties.
+	visitGenericProperties(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#genericProperty.
+	visitGenericProperty(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#explainOptions.
+	visitExplainOptions(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#explainOption.
+	visitExplainOption(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#matchPredicateIdents.
+	visitMatchPredicateIdents(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#matchPredicateIdent.
+	visitMatchPredicateIdent(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#analyzerElement.
+	visitAnalyzerElement(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#tokenizer.
+	visitTokenizer(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#tokenFilters.
+	visitTokenFilters(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#charFilters.
+	visitCharFilters(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#namedProperties.
+	visitNamedProperties(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#tableWithPartitions.
+	visitTableWithPartitions(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#setGlobalAssignment.
+	visitSetGlobalAssignment(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#setExpr.
+	visitSetExpr(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#on.
+	visitOn(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#securable.
+	visitSecurable(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#transactionMode.
+	visitTransactionMode(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#isolationLevel.
+	visitIsolationLevel(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#direction.
+	visitDirection(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#declareCursorParams.
+	visitDeclareCursorParams(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+	// Visit a parse tree produced by SqlBaseParser#nonReserved.
+	visitNonReserved(ctx) {
+	  return this.visitChildren(ctx);
+	}
+
+
+
+}
\ No newline at end of file
diff --git a/cratedb_sqlparse_js/cratedb_sqlparse/index.js b/cratedb_sqlparse_js/cratedb_sqlparse/index.js
index 692a1f4..b7b308b 100644
--- a/cratedb_sqlparse_js/cratedb_sqlparse/index.js
+++ b/cratedb_sqlparse_js/cratedb_sqlparse/index.js
@@ -1,3 +1,3 @@
 import {sqlparse} from "./parser.js";
 export {sqlparse};
-export const __cratedb_version__ = "5.6.4"
+export const __cratedb_version__ = "5.7.2"
diff --git a/cratedb_sqlparse_js/cratedb_sqlparse/models.js b/cratedb_sqlparse_js/cratedb_sqlparse/models.js
new file mode 100644
index 0000000..bd3042d
--- /dev/null
+++ b/cratedb_sqlparse_js/cratedb_sqlparse/models.js
@@ -0,0 +1,48 @@
+/**
+ * Represents the metadata of the query, the actual interesting parts of the query such as:
+ * table, schema, columns, options...
+ */
+export class Metadata {
+
+    /**
+     * @param {Table[]} tables - The referenced tables in the query.
+     * @param {object} parameterizedProperties - The properties whose value can be used to inject parameters, they start with '$', example: `CREATE TABLE a (b text) WITH ("allocation.max_retries" = $1)`
+     * @param {object} withProperties - SQL properties, defined after a `WITH` statement. Example: `CREATE TABLE a (b text) WITH ("allocation.max_retries" = 5)`
+     */
+    constructor(tables, parameterizedProperties, withProperties) {
+        this.tables = tables || []
+        this.parameterizedProperties = parameterizedProperties || {}
+        this.withProperties = withProperties || {}
+    }
+}
+
+/**
+ *
+ * @param {string} text
+ * @param {string} quoted_with
+ * @return {string}
+ */
+function quoted(text, quoted_with = '"') {
+    return quoted_with + text + quoted_with
+}
+
+export class Table {
+    /**
+     *
+     * @param {string} name
+     * @param {string} schema
+     * @property {string} fqn - Full qualified name, example: "sys"."shards"
+     */
+    constructor(name, schema) {
+        this.name = name
+        this.schema = schema
+        this.fqn = this._getFqn()
+    }
+
+    /**
+     * @return {string} - The full qualified name, quoted.
+     */
+    _getFqn() {
+        return (this.schema ? quoted(this.schema) + "." : "") + quoted(this.name)
+    }
+}
\ No newline at end of file
diff --git a/cratedb_sqlparse_js/cratedb_sqlparse/parser.js b/cratedb_sqlparse_js/cratedb_sqlparse/parser.js
index 90c6ef0..072f5f8 100644
--- a/cratedb_sqlparse_js/cratedb_sqlparse/parser.js
+++ b/cratedb_sqlparse_js/cratedb_sqlparse/parser.js
@@ -1,14 +1,14 @@
 import SqlBaseLexer from "./generated_parser/SqlBaseLexer.js";
 import SqlBaseParser from "./generated_parser/SqlBaseParser.js";
 import {CommonTokenStream, ErrorListener, InputStream, Interval, Token} from "antlr4";
-
+import {AstBuilder} from "./AstBuilder.js";
+import {Metadata} from "./models.js"
 function BEGIN_DOLLAR_QUOTED_STRING_action(localctx, actionIndex) {
     if (actionIndex === 0) {
         this.tags.push(this.text);
     }
 }
 
-
 function END_DOLLAR_QUOTED_STRING_action(localctx, actionIndex) {
     if (actionIndex === 1) {
         this.tags.pop();
@@ -28,6 +28,67 @@ SqlBaseLexer.prototype.END_DOLLAR_QUOTED_STRING_sempred = END_DOLLAR_QUOTED_STRI
 
 export class ParseError extends Error {
     name = 'ParseError'
+
+    /**
+     *
+     * @param {string} query
+     * @param {string} msg
+     * @param {object} offending_token
+     * @param {object} e
+     * @member {string} errorMessage
+     * @member {string} errorMessageVerbose
+     */
+    constructor(query, msg, offending_token, e) {
+        super(msg);
+        this.query = query;
+        this.msg = msg;
+        this.offendingToken = offending_token;
+        this.line = this.getLine();
+        this.column = this.getColumn();
+        this.errorMessage = this._getErrorMessage();
+        this.errorMessageVerbose = this.getOriginalQueryWithErrorMarked()
+    }
+
+    _getErrorMessage() {
+        return `[line ${this.line}:${this.column} ${this.message}]`
+    }
+
+    /**
+     *
+     * @returns {Number}
+     */
+    getColumn() {
+        return this.offendingToken.column
+    }
+
+    /**
+     *
+     * @returns {Number}
+     */
+    getLine() {
+        return this.offendingToken.line
+    }
+
+    /**
+     *
+     * @returns {string}
+     */
+    getOriginalQueryWithErrorMarked() {
+        const query = this.offendingToken.source[1].strdata
+        const offendingTokenText = query.substring(this.offendingToken.start, this.offendingToken.stop + 1)
+        const queryLines = query.split("\n")
+        const offendingLine = queryLines[this.getLine() - 1]
+        const newLineOffset = offendingLine.indexOf(offendingTokenText)
+
+        const newline = (
+            offendingLine
+            + "\n"
+            + (" ".repeat(newLineOffset) + "^".repeat(this.offendingToken.stop - this.offendingToken.start + 1))
+        )
+        queryLines[this.line - 1] = newline
+
+        return queryLines.join("\n")
+    }
 }
 
 class CaseInsensitiveStream extends InputStream {
@@ -41,27 +102,108 @@ class CaseInsensitiveStream extends InputStream {
 }
 
 class ExceptionErrorListener extends ErrorListener {
+    errors = []
     syntaxError(recognizer, offendingSymbol, line, column, msg, e) {
-        throw new ParseError(`line${line}:${column} ${msg}`);
+        throw new ParseError(
+            e.ctx.parser.getTokenStream().getText(new Interval(
+                e.ctx.start,
+                e.offendingToken.tokenIndex)
+            ),
+            msg,
+            offendingSymbol,
+            e
+        )
     }
 }
 
-class Statement {
-    constructor(ctx) {
+class ExceptionCollectorListener extends ErrorListener {
+    constructor() {
+        super();
+        this.errors = [];
+    }
+
+    syntaxError(recognizer, offendingSymbol, line, column, msg, e) {
+        super.syntaxError(recognizer, offendingSymbol, line, column, msg, e);
+        const error = new ParseError(
+            e.ctx.parser.getTokenStream().getText(new Interval(
+                e.ctx.start,
+                e.offendingToken.tokenIndex)
+            ),
+            msg,
+            offendingSymbol,
+            e
+        )
+        this.errors.push(error)
+    }
+}
+
+
+/*
+* Represents a CrateDB SQL statement.
+* */
+export class Statement {
+
+    /**
+     *
+     * @member {Statement} query
+     * @member {string} originalQuery
+     * @member {Metadata} metadata
+     * @member {string} type - The type of query, example: 'SELECT'
+     * @member {string} tree
+     * @param {object} ctx
+     * @param {ParseError} exception
+     */
+    constructor(ctx, exception) {
         this.query = ctx.parser.getTokenStream().getText(
             new Interval(
                 ctx.start.tokenIndex,
                 ctx.stop.tokenIndex,
             )
         )
-        this.original_query = ctx.parser.getTokenStream().getText()
-        this.tree = ctx.toStringTree(null, ctx.parser)
-        this.type = ctx.start.text
-        this.ctx = ctx
+        this.originalQuery = ctx.parser.getTokenStream().getText();
+        this.tree = ctx.toStringTree(null, ctx.parser);
+        this.type = ctx.start.text;
+        this.ctx = ctx;
+        this.exception = exception || null;
+        this.metadata = new Metadata();
+    }
+}
+
+/**
+ *
+ * @param {string} string
+ * @returns {string}
+ */
+function trim(string) {
+    return string.replace(/^\s+|\s+$/gm, '');
+}
+
+
+function findSuitableError(statement, errors) {
+    for (const error of errors) {
+        let errorQuery = error.query;
+
+        if (errorQuery.endsWith(";")) {
+            errorQuery = errorQuery.substring(0, errorQuery.length - 1);
+        }
+
+        errorQuery = trim(errorQuery);
+
+        // If a good match error_query contains statement.query
+        if (errorQuery.includes(statement.query)) {
+            statement.exception = error;
+            errors.splice(errors.indexOf(error), 1);
+        }
     }
 }
 
-export function sqlparse(query) {
+/**
+ *
+ * @param {string} query
+ * @param {Boolean} raise_exception
+ * @returns {Statement[]}
+ */
+export function sqlparse(query, raise_exception = false) {
     const input = new CaseInsensitiveStream(query);
     const lexer = new SqlBaseLexer(input);
     lexer.removeErrorListeners();
@@ -69,9 +211,45 @@ export function sqlparse(query) {
 
     const parser = new SqlBaseParser(stream);
     parser.removeErrorListeners();
-    parser.addErrorListener(new ExceptionErrorListener());
+
+    const errorListener = raise_exception ? new ExceptionErrorListener() : new ExceptionCollectorListener()
+
+    parser.addErrorListener(errorListener);
 
     const tree = parser.statements();
 
-    return tree.children.filter((children) => children instanceof SqlBaseParser.StatementContext).map((children) => new Statement(children))
-}
+    const statementsContext = tree.children.filter((children) => children instanceof SqlBaseParser.StatementContext)
+
+    let statements = []
+    for (const statementContext of statementsContext) {
+        let stmt = new Statement(statementContext)
+        findSuitableError(stmt, errorListener.errors)
+        statements.push(stmt)
+    }
+
+    if (errorListener.errors.length === 1) {
+        // Fixme, what if there are two unassigned errors ?
+        // can that even be possible?
+        let error = errorListener.errors[0]
+
+        for (const stmt of statements) {
+            if (stmt.exception === null && stmt.query.includes(error.query)) {
+                stmt.exception = error
+                break;
+            }
+        }
+    }
+
+    if (errorListener.errors.length > 1) {
+        console.error("Could not match errors to queries, too much ambiguity, please report it opening an issue with the query.")
+    }
+
+
+    const stmtEnricher = new AstBuilder()
+
+    for (const stmt of statements) {
+        stmtEnricher.enrich(stmt)
+    }
+
+    return statements
+}
\ No newline at end of file
diff --git a/cratedb_sqlparse_js/package.json b/cratedb_sqlparse_js/package.json
index cd9b086..ece1f0c 100644
--- a/cratedb_sqlparse_js/package.json
+++ b/cratedb_sqlparse_js/package.json
@@ -32,6 +32,7 @@
   ],
   "main": "./dist/sqlparse.umd.cjs",
   "module": "./dist/sqlparse.js",
+  "types": "./dist/index.d.ts",
   "exports": {
     ".": {
       "import": "./dist/sqlparse.js",
@@ -42,11 +43,14 @@
     "antlr4": "^4.13.1-patch-1"
   },
   "devDependencies": {
+    "jsdoc": "^4.0.3",
+    "terser": "^5.31.1",
     "vite": "^5.3.2",
     "vitest": "^1.6.0"
   },
   "scripts": {
     "test": "vitest run",
-    "build": "vite build"
+    "build": "vite build && tsc",
+    "docs": "jsdoc *"
   }
 }
diff --git a/cratedb_sqlparse_js/tsconfig.json b/cratedb_sqlparse_js/tsconfig.json
new file mode 100644
index 0000000..c5f90d2
--- /dev/null
+++ b/cratedb_sqlparse_js/tsconfig.json
@@ -0,0 +1,10 @@
+{
+  "include": ["cratedb_sqlparse/**/*"],
+  "compilerOptions": {
+    "allowJs": true,
+    "declaration": true,
+    "emitDeclarationOnly": true,
+    "outDir": "./dist",
+    "declarationMap": true,
+  }
+}
\ No newline at end of file
diff --git a/cratedb_sqlparse_js/vite.config.js b/cratedb_sqlparse_js/vite.config.js
index 248b0ff..15d0572 100644
--- a/cratedb_sqlparse_js/vite.config.js
+++ b/cratedb_sqlparse_js/vite.config.js
@@ -9,6 +9,7 @@ import packageJson from './package.json';
 
 export default defineConfig({
     build: {
+        minify: "terser",
         lib: {
             // Could also be a dictionary or array of multiple entry points
             entry: resolve(__dirname, 'cratedb_sqlparse/index.js'),
@@ -17,7 +18,8 @@ export default defineConfig({
         }, rollupOptions: {
             // make sure to externalize deps that shouldn't be bundled
             // into your library
-            external: [], output: {},
+            external: [],
+            output: {},
         },
     },
 })
\ No newline at end of file


Test patch:
diff --git a/cratedb_sqlparse_js/tests/enricher.test.js b/cratedb_sqlparse_js/tests/enricher.test.js
new file mode 100644
index 0000000..9b09e14
--- /dev/null
+++ b/cratedb_sqlparse_js/tests/enricher.test.js
@@ -0,0 +1,45 @@
+import {expect, test} from 'vitest'
+import {sqlparse} from "../cratedb_sqlparse/index.js";
+import {Metadata} from "../cratedb_sqlparse/models.js";
+
+test('Statement has metadata', () => {
+    const query = "SELECT 1; SELECT 2;"
+    const stmts = sqlparse(query);
+
+    for (const stmt of stmts) {
+        expect(stmt.metadata).not.toBeNull
+        expect(stmt.metadata).toBeInstanceOf(Metadata)
+    }
+})
+
+test("Statement's metadata has Tables", () => {
+    const query = "SELECT _ FROM a.b, d"
+    const stmt = sqlparse(query)[0]
+
+    expect(stmt.metadata.tables).not.toBeNull
+    expect(stmt.metadata.tables[0].schema).toBe('a')
+    expect(stmt.metadata.tables[0].name).toBe('b')
+    expect(stmt.metadata.tables[0].fqn).toBe('"a"."b"')
+
+    expect(stmt.metadata.tables[1].schema).toBeNull()
+    expect(stmt.metadata.tables[1].name).toBe('d')
+    expect(stmt.metadata.tables[1].fqn).toBe('"d"')
+})
+
+test("Statement's metadata correctly gets parameterized properties", ()=>{
+    const query = "CREATE TABLE tbl (A TEXT) WITH ('key' = $1, 'key2' = '$2')"
+    const stmt = sqlparse(query)[0]
+    const expected = { key: '$1', key2: '$2' }
+
+    expect(stmt.metadata.withProperties).toStrictEqual(expected)
+    expect(stmt.metadata.parameterizedProperties).toStrictEqual(expected)
+})
+
+test("Statement's metadata correctly gets with properties", () => {
+    const query = "CREATE TABLE tbl (A TEXT) WITH ('key' = 'val', 'key2' = 2, 'key3' = true)"
+    const stmt = sqlparse(query)[0]
+    const expected = {key: 'val', key2: '2', key3: 'true'}
+
+    expect(stmt.metadata.withProperties).toStrictEqual(expected)
+    expect(stmt.metadata.parameterizedProperties).toStrictEqual({})
+})
diff --git a/cratedb_sqlparse_js/tests/exceptions.test.js b/cratedb_sqlparse_js/tests/exceptions.test.js
new file mode 100644
index 0000000..d2ba4d5
--- /dev/null
+++ b/cratedb_sqlparse_js/tests/exceptions.test.js
@@ -0,0 +1,73 @@
+import {expect, test} from 'vitest'
+import {sqlparse} from "../cratedb_sqlparse/index.js";
+import {ParseError} from "../cratedb_sqlparse/parser.js";
+
+test('ParsingError is raised when raise_exception=true', () => {
+    expect(() => sqlparse('some non sql string', true)).toThrowError(ParseError)
+})
+
+test('Errors are NOT raised on a correct statement', () => {
+    expect(() => sqlparse('SELECT COUNT(*) FROM doc.tbl f WHERE f.id = 1', true)).not.toThrowError()
+})
+
+test('Error should be collected and not thrown by default', () => {
+    const stmts = sqlparse('SLCT 2;')
+    expect(() => stmts).not.toThrowError()
+})
+
+test('Several Errors should be collected and not thrown by default', () => {
+    const stmts = sqlparse(`
+        SELECT A FROM tbl1 where;
+        SELECT 1;
+        SELECT D, A FROM tbl1 WHERE;
+    `)
+    expect(stmts).length(3)
+    expect(stmts[0].exception).not.toBeNull();
+    expect(stmts[1].exception).toBeNull();
+    expect(stmts[2].exception).not.toBeNull();
+})
+
+test('Several Errors should be collected and not thrown by default 2, special case*', () => {
+    // This query is an special case, see parser
+    const stmts = sqlparse(`       
+        SELEC 1;
+        SELECT A, B, C, D FROM tbl1;
+        SELECT D, A FROM tbl1 WHERE;
+        `)
+
+    expect(stmts).length(3)
+    expect(stmts[0].exception).not.toBeNull();
+    expect(stmts[1].exception).toBeNull();
+    expect(stmts[2].exception).not.toBeNull();
+})
+
+test('Several Errors should be collected and not thrown by default 3', () => {
+    // language=SQL format=false
+const stmts = sqlparse(`
+        SELECT 1;
+        SELECT A, B, C, D FROM tbl1;
+        INSERT INTO doc.tbl VALUES (1, 2, 'three', ['four']);
+    `)
+
+    expect(stmts).length(3)
+    expect(stmts[0].exception).toBeNull();
+    expect(stmts[1].exception).toBeNull();
+    expect(stmts[2].exception).toBeNull();
+})
+
+test('Exception message is correct', () => {
+    const stmts = sqlparse(`         
+        SELEC 1;
+        SELECT A, B, C, D FROM tbl1;
+        SELECT D, A FROM tbl1 WHERE;`
+    )
+    const expectedMessage = "[line 2:8 mismatched input 'SELEC' expecting {'SELECT', 'DEALLOCATE', 'FETCH', 'END', 'WITH', 'CREATE', 'ALTER', 'KILL', 'CLOSE', 'BEGIN', 'START', 'COMMIT', 'ANALYZE', 'DISCARD', 'EXPLAIN', 'SHOW', 'OPTIMIZE', 'REFRESH', 'RESTORE', 'DROP', 'INSERT', 'VALUES', 'DELETE', 'UPDATE', 'SET', 'RESET', 'COPY', 'GRANT', 'DENY', 'REVOKE', 'DECLARE'}]"
+    const expectedMessageVerbose = "         \n" +
+        "        SELEC 1;\n" +
+        "        ^^^^^\n" +
+        "        SELECT A, B, C, D FROM tbl1;\n" +
+        "        SELECT D, A FROM tbl1 WHERE;"
+
+    expect(stmts[0].exception.errorMessage).toBe(expectedMessage)
+    expect(stmts[0].exception.getOriginalQueryWithErrorMarked()).toBe(expectedMessageVerbose)
+})
\ No newline at end of file
diff --git a/cratedb_sqlparse_js/tests/lexer.test.js b/cratedb_sqlparse_js/tests/lexer.test.js
new file mode 100644
index 0000000..804b3e4
--- /dev/null
+++ b/cratedb_sqlparse_js/tests/lexer.test.js
@@ -0,0 +1,53 @@
+import {expect, test} from 'vitest'
+import {sqlparse} from "../cratedb_sqlparse/index.js";
+
+test('sqlparse parses one statement', () => {
+    const QUERY = 'SELECT 1;'
+    const queries = sqlparse(QUERY)
+    expect(queries.length).toBe(1)
+
+    const stmts = queries[0]
+    expect(stmts.query).toBe(QUERY.replace(';', ''));
+    expect(stmts.originalQuery).toBe(QUERY)
+    expect(stmts.type).toBe('SELECT')
+});
+
+test('sqlparse parses several statements', () => {
+    const QUERY = `    
+    SELECT 1;
+    INSERT INTO doc.tbl VALUES (1,2,3,4,5,6);
+    DROP TABLE doc.tbl1`
+
+    const stmts = sqlparse(QUERY)
+
+    expect(stmts.length).toBe(3)
+    expect(stmts[0].type).toBe('SELECT')
+    expect(stmts[1].type).toBe('INSERT')
+    expect(stmts[2].type).toBe('DROP')
+})
+
+test('sqlparse supports dollar string', () => {
+    const QUERY = "SELECT $$crate's performance$$"
+    const stmts = sqlparse(QUERY)[0]
+    expect(stmts.query).toBe(QUERY)
+})
+
+test('Test multiquery edge case', ()=>{
+    // Test for https://github.com/crate/cratedb-sqlparse/issues/28,
+    // if this ends up parsing 3 statements, we can change this test,
+    // it's here, so we can programmatically track if the behavior changes.
+    const QUERY = `
+    SELECT A FROM tbl1 where ;
+    SELEC 1;
+    SELECT D, A FROM tbl1 WHERE;`
+
+    const stmts = sqlparse(QUERY)
+    expect(stmts.length).toBe(1)
+})
+
+test('sqlparse is case insensitive', ()=>{
+    const QUERY = 'inSerT InTo doc.Tbl1 Values (1)'
+    const stmts = sqlparse(QUERY)
+    expect(stmts[0].originalQuery).toBe(QUERY)
+    expect(stmts[0].query).toBe(QUERY)
+})
\ No newline at end of file
diff --git a/cratedb_sqlparse_js/tests/parser.test.js b/cratedb_sqlparse_js/tests/parser.test.js
deleted file mode 100644
index 5e9fac4..0000000
--- a/cratedb_sqlparse_js/tests/parser.test.js
+++ /dev/null
@@ -1,46 +0,0 @@
-
-import {expect, test} from 'vitest'
-import {sqlparse} from "../cratedb_sqlparse/index.js";
-
-test('sqlparse parses one statement', () => {
-    const ORIGINAL_QUERY = 'SELECT 1;'
-    const queries = sqlparse(ORIGINAL_QUERY)
-    expect(queries.length).toBe(1)
-
-    const query = queries[0]
-    expect(query.query).toBe(ORIGINAL_QUERY.replace(';', ''));
-    expect(query.original_query).toBe(ORIGINAL_QUERY)
-    expect(query.type).toBe('SELECT')
-});
-
-test('sqlparse parses several statements', () => {
-    const ORIGINAL_QUERY = `    
-    SELECT 1;
-    INSERT INTO doc.tbl VALUES (1,2,3,4,5,6);
-    DROP TABLE doc.tbl1`
-
-    const queries = sqlparse(ORIGINAL_QUERY)
-    expect(queries.length).toBe(3)
-
-    expect(queries[0].type).toBe('SELECT')
-    expect(queries[1].type).toBe('INSERT')
-    expect(queries[2].type).toBe('DROP')
-})
-
-test('sqlparse supports dollar string', () => {
-    const ORIGINAL_QUERY = "SELECT $$crate's performance$$"
-    const query = sqlparse(ORIGINAL_QUERY)[0]
-    expect(query.query).toBe(ORIGINAL_QUERY)
-})
-
-test('sqlparse raises ParsingError', ()=>{
-    const ORIGINAL_QUERY = "some non sql string"
-    expect(()=> sqlparse(ORIGINAL_QUERY)).toThrowError()
-})
-
-test('sqlparse is case insensitive', ()=>{
-    const ORIGINAL_QUERY = 'inSerT InTo doc.Tbl1 Values (1)'
-    const query = sqlparse(ORIGINAL_QUERY)
-    expect(query[0].original_query).toBe(ORIGINAL_QUERY)
-    expect(query[0].query).toBe(ORIGINAL_QUERY)
-})
\ No newline at end of file