Skip to content

Commit

Permalink
Javascript: Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
surister committed Jul 7, 2024
1 parent 4ff09b1 commit d148041
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 19 deletions.
192 changes: 181 additions & 11 deletions cratedb_sqlparse_js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
8 changes: 4 additions & 4 deletions cratedb_sqlparse_js/cratedb_sqlparse/AstBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export class AstBuilder extends SqlBaseParserVisitor {
* @param {Object} node
* @returns {(String|null)}
*/
getText(node){
if(node){
getText(node) {
if (node) {
return node.getText().replaceAll("'", "").replaceAll('"', "")
}
return null
Expand All @@ -54,7 +54,7 @@ export class AstBuilder extends SqlBaseParserVisitor {

let schema = null
let name = null;
if (parts.length === 1){
if (parts.length === 1) {
name = parts[0]
} else {
schema = parts[0]
Expand All @@ -68,7 +68,7 @@ export class AstBuilder extends SqlBaseParserVisitor {

/**
*
* @param {SqlBaseParser.GenericPropertiesContext} ctx
* @param {SqlBaseParser.GenericPropertiesContext} ctx
*/
visitGenericProperties(ctx) {
const nodeProperties = ctx.genericProperty()
Expand Down
3 changes: 2 additions & 1 deletion cratedb_sqlparse_js/cratedb_sqlparse/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ export class Table {
constructor(name, schema) {
this.name = name
this.schema = schema
this.fqn = this._getFqn()
}

/**
* @return {String} The full qualified name, quoted.
*/
getFqn() {
_getFqn() {
return (this.schema ? quoted(this.schema) + "." : "") + quoted(this.name)
}
}
3 changes: 2 additions & 1 deletion cratedb_sqlparse_js/cratedb_sqlparse/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ export class Statement {
* @property {Metadata} metadata
* @property {String} type
* @property {String} tree
* @property {Object} ctx
* @param {Object} ctx
* @param {ParseError} exception
*/
constructor(ctx, exception) {
this.query = ctx.parser.getTokenStream().getText(
Expand Down
4 changes: 2 additions & 2 deletions cratedb_sqlparse_js/tests/enricher.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ test("Statement's metadata has Tables", () => {
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].getFqn()).toBe('"a"."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].getFqn()).toBe('"d"')
expect(stmt.metadata.tables[1].fqn).toBe('"d"')
})

test("Statement's metadata correctly gets parameterized properties", ()=>{
Expand Down

0 comments on commit d148041

Please sign in to comment.