From 8a5c2bbad59cb078da3452b8ed29c072156ba753 Mon Sep 17 00:00:00 2001 From: Kacper Kula Date: Mon, 2 Sep 2024 19:40:48 +0100 Subject: [PATCH] feat: adding proper gramar parsing instead of regex. Improving how CSVs are handled and reloaded. --- .gitignore | 1 + SqlSealLang.g4 | 47 ++ package-lock.json | 67 +- package.json | 7 + pnpm-lock.yaml | 82 +++ src/SealObserver.ts | 6 + src/SqlSealCodeblockHandler.ts | 123 ++-- src/database.ts | 40 +- src/grammar/SqlSealLang.interp | 45 ++ src/grammar/SqlSealLang.tokens | 22 + src/grammar/SqlSealLangLexer.interp | 56 ++ src/grammar/SqlSealLangLexer.tokens | 22 + src/grammar/SqlSealLangLexer.ts | 131 ++++ src/grammar/SqlSealLangListener.ts | 109 +++ src/grammar/SqlSealLangParser.ts | 1043 +++++++++++++++++++++++++++ src/grammar/SqlSealLangVisitor.ts | 80 ++ src/grammar/parser.test.ts | 12 + src/grammar/parser.ts | 108 +++ src/models/sync.ts | 64 ++ src/sqlReparseTables.ts | 36 +- src/utils.ts | 47 +- src/utils.unit.test.ts | 8 +- yarn.lock | 35 +- 23 files changed, 2061 insertions(+), 130 deletions(-) create mode 100644 SqlSealLang.g4 create mode 100644 src/grammar/SqlSealLang.interp create mode 100644 src/grammar/SqlSealLang.tokens create mode 100644 src/grammar/SqlSealLangLexer.interp create mode 100644 src/grammar/SqlSealLangLexer.tokens create mode 100644 src/grammar/SqlSealLangLexer.ts create mode 100644 src/grammar/SqlSealLangListener.ts create mode 100644 src/grammar/SqlSealLangParser.ts create mode 100644 src/grammar/SqlSealLangVisitor.ts create mode 100644 src/grammar/parser.test.ts create mode 100644 src/grammar/parser.ts create mode 100644 src/models/sync.ts diff --git a/.gitignore b/.gitignore index 701d912..e7ad3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ better_sqlite3.node better_sqlite3*.node database.sqlite +coverage/ diff --git a/SqlSealLang.g4 b/SqlSealLang.g4 new file mode 100644 index 0000000..cff5792 --- /dev/null +++ b/SqlSealLang.g4 @@ -0,0 +1,47 @@ +grammar SqlSealLang; + +parse + : statement+ EOF + ; + +statement + : tableStatement + | queryStatement + ; + +tableStatement + : TABLE WS+ ID WS* '=' WS* FILE WS* '(' WS* FILE_URL WS* ')' WS* + ; + +queryStatement + : withClause? selectStatement WS* + ; + +withClause + : WITH WS+ cteDefinition (WS* ',' WS* cteDefinition)* WS* + ; + +cteDefinition + : ID WS+ AS WS* '(' WS* selectStatement WS* ')' + ; + +selectStatement + : SELECT WS+ selectBody + ; + +selectBody + : .*? + ; + +TABLE : 'TABLE'; +FILE : 'file'; +WITH : 'WITH'; +AS : 'AS'; +SELECT : 'SELECT'; + +ID : [a-zA-Z_][a-zA-Z_0-9]*; +FILE_URL : [a-zA-Z0-9_./\\-]+; +WS : [ \t\r\n]+; + +// Catch-all rule for any other character +ANY : .; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 48c5117..ca46513 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,32 @@ { "name": "sqlseal", - "version": "0.4.1", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sqlseal", - "version": "0.4.1", + "version": "0.6.0", "license": "MIT", "dependencies": { + "antlr4": "^4.13.2", + "antlr4ts": "0.5.0-alpha.4", "better-sqlite3": "^11.2.1", + "json5": "^2.2.3", "lodash": "^4.17.21", "node-sql-parser": "^5.0.0", "papaparse": "^5.4.1" }, "devDependencies": { + "@jest/globals": "^29.7.0", "@types/better-sqlite3": "^7.6.10", "@types/jest": "^29.5.12", "@types/lodash": "^4.17.7", - "@types/node": "^16.11.6", + "@types/node": "^22.5.0", "@types/papaparse": "^5.3.14", "@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/parser": "5.29.0", + "antlr4ts-cli": "0.5.0-alpha.4", "builtin-modules": "3.3.0", "electron": "^30.0.0", "electron-rebuild": "^3.2.9", @@ -2478,10 +2483,21 @@ "dev": true }, "node_modules/@types/node": { - "version": "16.18.96", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz", - "integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ==", - "dev": true + "version": "22.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.2.tgz", + "integrity": "sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/papaparse": { "version": "5.3.14", @@ -3227,6 +3243,31 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/antlr4": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.2.tgz", + "integrity": "sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=16" + } + }, + "node_modules/antlr4ts": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", + "license": "BSD-3-Clause" + }, + "node_modules/antlr4ts-cli": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmjs.org/antlr4ts-cli/-/antlr4ts-cli-0.5.0-alpha.4.tgz", + "integrity": "sha512-lVPVBTA2CVHRYILSKilL6Jd4hAumhSZZWA7UbQNQrmaSSj7dPmmYaN4bOmZG79cOy0lS00i4LY68JZZjZMWVrw==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "antlr4ts": "antlr4ts" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -6458,7 +6499,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -9131,17 +9171,6 @@ "node": ">=12" } }, - "node_modules/vitepress/node_modules/@types/node": { - "version": "20.12.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", - "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/vitepress/node_modules/@vitejs/plugin-vue": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", diff --git a/package.json b/package.json index 1f4b3ad..98d4fa5 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "node esbuild.config.mjs", "typecheck": "tsc -noEmit -skipLibCheck", "build": " node esbuild.config.mjs production", + "build-grammar": "antlr4ts -o src/grammar -Dlanguage=TypeScript -visitor SqlSealLang.g4", "version": "node version-bump.mjs && git add manifest.json versions.json", "rebuild": "electron-rebuild -f -w better-sqlite3", "docs:dev": "vitepress dev docs", @@ -26,6 +27,8 @@ "@types/papaparse": "^5.3.14", "@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/parser": "5.29.0", + "antlr4ng-cli": "^2.0.0", + "antlr4ts-cli": "0.5.0-alpha.4", "builtin-modules": "3.3.0", "electron": "^30.0.0", "electron-rebuild": "^3.2.9", @@ -42,6 +45,10 @@ "vue": "^3.4.26" }, "dependencies": { + "antlr4": "^4.13.2", + "antlr4-cli": "^4.5.3", + "antlr4ng": "^3.0.4", + "antlr4ts": "0.5.0-alpha.4", "better-sqlite3": "^11.2.1", "json5": "^2.2.3", "lodash": "^4.17.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62085a5..35466a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,18 @@ importers: .: dependencies: + antlr4: + specifier: ^4.13.2 + version: 4.13.2 + antlr4-cli: + specifier: ^4.5.3 + version: 4.5.3 + antlr4ng: + specifier: ^3.0.4 + version: 3.0.4(antlr4ng-cli@2.0.0) + antlr4ts: + specifier: 0.5.0-alpha.4 + version: 0.5.0-alpha.4 better-sqlite3: specifier: ^11.2.1 version: 11.2.1 @@ -48,6 +60,12 @@ importers: '@typescript-eslint/parser': specifier: 5.29.0 version: 5.29.0(eslint@8.57.0)(typescript@4.7.4) + antlr4ng-cli: + specifier: ^2.0.0 + version: 2.0.0 + antlr4ts-cli: + specifier: 0.5.0-alpha.4 + version: 0.5.0-alpha.4 builtin-modules: specifier: 3.3.0 version: 3.3.0 @@ -1201,6 +1219,29 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + antlr4-cli@4.5.3: + resolution: {integrity: sha512-2My0geojGhigXgy9J7/YGYXau8nVjUldxvwttsG/amYtUjTSfc149AQBeBtDsuleApY3C8oQq1RW+8RIum1uEQ==} + + antlr4@4.13.2: + resolution: {integrity: sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg==} + engines: {node: '>=16'} + + antlr4ng-cli@2.0.0: + resolution: {integrity: sha512-oAt5OSSYhRQn1PgahtpAP4Vp3BApCoCqlzX7Q8ZUWWls4hX59ryYuu0t7Hwrnfk796OxP/vgIJaqxdltd/oEvQ==} + hasBin: true + + antlr4ng@3.0.4: + resolution: {integrity: sha512-u1Ww6wVv9hq70E9AaYe5qW3ba8hvnjJdO3ZsKnb3iJWFV/medLEEhbyWwXCvvD2ef0ptdaiIUgmaazS/WE6uyQ==} + peerDependencies: + antlr4ng-cli: ^2.0.0 + + antlr4ts-cli@0.5.0-alpha.4: + resolution: {integrity: sha512-lVPVBTA2CVHRYILSKilL6Jd4hAumhSZZWA7UbQNQrmaSSj7dPmmYaN4bOmZG79cOy0lS00i4LY68JZZjZMWVrw==} + hasBin: true + + antlr4ts@0.5.0-alpha.4: + resolution: {integrity: sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1941,6 +1982,10 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -2614,6 +2659,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} + regexpp@3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} engines: {node: '>=8'} @@ -2716,6 +2765,11 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shelljs@0.7.8: + resolution: {integrity: sha512-/YF5Uk8hcwi7ima04ppkbA4RaRMdPMBfwAvAf8sufYOxsJRtbdoBsT8vGvlb+799BrlGdYrd+oczIA2eN2JdWA==} + engines: {node: '>=0.11.0'} + hasBin: true + shiki@1.14.1: resolution: {integrity: sha512-FujAN40NEejeXdzPt+3sZ3F2dx1U24BY2XTY01+MG8mbxCiA2XukXdcbyMyLAHJ/1AUUnQd1tZlvIjefWWEJeA==} @@ -4281,6 +4335,22 @@ snapshots: ansi-styles@5.2.0: {} + antlr4-cli@4.5.3: + dependencies: + shelljs: 0.7.8 + + antlr4@4.13.2: {} + + antlr4ng-cli@2.0.0: {} + + antlr4ng@3.0.4(antlr4ng-cli@2.0.0): + dependencies: + antlr4ng-cli: 2.0.0 + + antlr4ts-cli@0.5.0-alpha.4: {} + + antlr4ts@0.5.0-alpha.4: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -5157,6 +5227,8 @@ snapshots: ini@1.3.8: {} + interpret@1.4.0: {} + ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -6027,6 +6099,10 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + rechoir@0.6.2: + dependencies: + resolve: 1.22.8 + regexpp@3.2.0: {} require-directory@2.1.1: {} @@ -6131,6 +6207,12 @@ snapshots: shebang-regex@3.0.0: {} + shelljs@0.7.8: + dependencies: + glob: 7.2.3 + interpret: 1.4.0 + rechoir: 0.6.2 + shiki@1.14.1: dependencies: '@shikijs/core': 1.14.1 diff --git a/src/SealObserver.ts b/src/SealObserver.ts index 8f62123..2f3aaa6 100644 --- a/src/SealObserver.ts +++ b/src/SealObserver.ts @@ -40,6 +40,12 @@ export class SealObserver { }); } + hasAnyObserver(tableNames: string[]) { + return tableNames.reduce((acc, t) => + acc || (this.tables.has(t) && this.tables.get(t)!.size > 0), + false) + } + unregisterObserversByTag(tag: string) { if (this.tags.has(tag)) { const observers = this.tags.get(tag); diff --git a/src/SqlSealCodeblockHandler.ts b/src/SqlSealCodeblockHandler.ts index d600046..2795bf0 100644 --- a/src/SqlSealCodeblockHandler.ts +++ b/src/SqlSealCodeblockHandler.ts @@ -6,80 +6,80 @@ import { prefixedIfNotGlobal, updateTables } from "./sqlReparseTables" import { SealObserver } from "./SealObserver" import { SqlSealDatabase } from "./database" import { Logger } from "./logger" +import { ParsedLanguage, parseLanguage, TableStatement } from "./grammar/parser" +import { SyncModel } from "./models/sync" + export class SqlSealCodeblockHandler { get globalTables() { return ['files', 'tags'] } - constructor(private readonly app: App, private readonly db: SqlSealDatabase,private readonly observer: SealObserver, private logger: Logger) { } + syncModel: SyncModel + constructor(private readonly app: App, private readonly db: SqlSealDatabase,private readonly observer: SealObserver, private logger: Logger) { + this.syncModel = new SyncModel(db) + } - tablesConfig: Record = {} + setupTables(tables: ParsedLanguage['tables'], ctx: MarkdownPostProcessorContext) { + tables.forEach(table => { + this.setupTable(table, ctx) + }) + } - getHandler() { - return async (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => { - - displayLoader(el) - await this.db.connect() - this.observer.unregisterObserversByTag(ctx.docId) // Unregister all previous observers. - - const frontmatter = await resolveFrontmatter(ctx, this.app) - const prefix = hashString(ctx.sourcePath) - const regex = /TABLE\s+(.+)\s+=\s+file\(([^)]+)\)/g; - let match - while ((match = regex.exec(source)) !== null) { - const name = match[1]; - const url = match[2]; - this.logger.log('CodedblockHandler. table', name, url) + setupTable({ url, name }: TableStatement, ctx: MarkdownPostProcessorContext) { // UPDATING TABLES IF THEY CHANGED SINCE LAST REGISTER + const prefix = hashString(ctx.sourcePath) const prefixedName = prefixedIfNotGlobal(name, this.globalTables, prefix) - if (this.tablesConfig[prefixedName] === url) { - // We do not need to rework it. Also, it should be watched, right? - // continue - } - if (!!this.tablesConfig[prefixedName]) { - // We need to remove old database as it's pointing to the wrong collection (probably). - } - + const existing = this.syncModel.getSync(ctx.sourcePath, prefixedName) - // FIXME: do not register observer when it's already been registed for this ctx. this.observer.registerObserver(`file:${url}`, async () => { // Update table await this.db.loadDataForDatabaseFromUrl(prefixedName, url, true) + // update when it was updated. + this.syncModel.removeSync(ctx.sourcePath, prefixedName) + this.syncModel.registerSync(ctx.sourcePath, url, prefixedName) + // Fire observers for the table this.observer.fireObservers(`table:${prefixedName}`) }, ctx.docId) - if (this.tablesConfig[prefixedName] !== url) { - this.observer.fireObservers(`file:${url}`) - this.tablesConfig[prefixedName] = url - } else { - // We still need to update the table watchers? - requestAnimationFrame(() => { - this.observer.fireObservers(`table:${prefixedName}`) - }) + + if (existing) { + + if (existing.url === url) { + // Already synced + // TODO: Check if the file was updated after we synced, if so, we should update it. + requestAnimationFrame(() => { + this.observer.fireObservers(`table:${prefixedName}`) + }) + return + } else { + // Old database is no-no. We need to update it. + this.db.db.prepare('DROP TABLE :tablename').run({ + tablename: prefixedName + }) + this.syncModel.removeSync(ctx.sourcePath, prefixedName) + } } + this.observer.fireObservers(`file:${url}`) } - const selectRegexp = /SELECT\s+(.*)/g; // OR WITH? - const selectMatch = selectRegexp.exec(source) - if (selectMatch) { + setupSelect(selectStmt: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) { try { - // FIXME: WAIT FOR TABLES TO BE READDY!!!! - const selectStatement = selectMatch[0] - const { statement, tables } = updateTables(selectStatement, this.globalTables, prefix) + const prefix = hashString(ctx.sourcePath) + const { statement, tables } = updateTables(selectStmt, this.globalTables, prefix) + const frontmatter = resolveFrontmatter(ctx, this.app) const renderSelect = async () => { try { - const stmt = await this.db.db.prepare(statement) - const columns = await stmt.columns().map(column => column.name); - const data = await stmt.all(frontmatter ?? {}) + const stmt = this.db.db.prepare(statement) + const columns = stmt.columns().map(column => column.name); + const data = stmt.all(frontmatter ?? {}) displayData(el, columns, data) } catch (e) { displayError(el, e) } } - // Register observer for each table tables.forEach(table => { const observer = async () => { @@ -88,18 +88,22 @@ export class SqlSealCodeblockHandler { this.observer.unregisterObserversByTag(ctx.docId) } - displayLoader(el) await renderSelect() } this.observer.registerObserver(`table:${table}`, observer, ctx.docId) }) - this.globalTables.forEach(t => this.observer.fireObservers(`table:${t}`)) - - // await renderSelect() <- this should get triggered automatically. - + if (!this.observer.hasAnyObserver(this.globalTables.map(t => `table:${t}`))) { + renderSelect() + } + // Triggering the ones which exist + tables.forEach(t => { + if (this.syncModel.getSync(ctx.sourcePath, t)) { + this.observer.fireObservers(`table:${t}`) + } + }) } catch (e) { if (e instanceof RangeError && ctx && Object.keys(ctx.frontmatter).length === 0) { displayInfo(el, 'Cannot access frontmatter properties in Live Preview Mode. Switch to Reading Mode to see the results.') @@ -108,6 +112,27 @@ export class SqlSealCodeblockHandler { } } } + + getHandler() { + return async (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => { + + displayLoader(el) + await this.db.connect() + this.observer.unregisterObserversByTag(ctx.docId) // Unregister all previous observers. + + // NEW PARSING + try { + const results = parseLanguage(source) + + // FIXME: if not properly parsed, we should display error. + + this.setupTables(results.tables, ctx) + results.queryPart ? this.setupSelect(results.queryPart, el, ctx) : displayInfo(el, `SQLSeal. ${results.tables.map(t => `${t.name} table definition.`).join(' ')}`) + + } catch (e) { + displayError(el, e.toString()) + } + } } } \ No newline at end of file diff --git a/src/database.ts b/src/database.ts index 1bbb073..c0300b0 100644 --- a/src/database.ts +++ b/src/database.ts @@ -4,48 +4,10 @@ import path from 'path' import Papa from 'papaparse' import { prefixedIfNotGlobal } from "./sqlReparseTables" import { camelCase } from 'lodash' -import { fetchBlobData, FieldTypes, predictJson, predictType } from "./utils" +import { dataToCamelCase, fetchBlobData, FieldTypes, predictJson, predictType, toTypeStatements } from "./utils" import os from 'os' import fs from 'fs' -export function isNumeric(str: string) { - if (typeof str != "string") return false // we only process strings! - return !isNaN(str as any) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)... - !isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail -} - -function dataToCamelCase(data: Array>) { - return data.map(d => Object.keys(d).reduce((acc, k) => ({ - ...acc, - [camelCase(k)]: d[k] - }), {})) -} - - -const toTypeStatements = (header: Array, data: Array>) => { - let d: Array> = data - const types: Record> = {} - header.forEach(key => { - const type = predictType(key, data) - if (type === 'REAL' || type === 'INTEGER') { - // converting all data here to text - d = d.map(record => ({ - ...record, - [key]: type === 'REAL' - ? parseFloat(record[key] as string) - : parseInt(record[key] as string) - })) - } - - types[key] = type - }) - - return { - data: d, - types - } -} - export class SqlSealDatabase { private savedDatabases: Record = {} db: Database.Database diff --git a/src/grammar/SqlSealLang.interp b/src/grammar/SqlSealLang.interp new file mode 100644 index 0000000..fb542c0 --- /dev/null +++ b/src/grammar/SqlSealLang.interp @@ -0,0 +1,45 @@ +token literal names: +null +'=' +'(' +')' +',' +'TABLE' +'file' +'WITH' +'AS' +'SELECT' +null +null +null +null + +token symbolic names: +null +null +null +null +null +TABLE +FILE +WITH +AS +SELECT +ID +FILE_URL +WS +ANY + +rule names: +parse +statement +tableStatement +queryStatement +withClause +cteDefinition +selectStatement +selectBody + + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 15, 163, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 3, 2, 6, 2, 20, 10, 2, 13, 2, 14, 2, 21, 3, 2, 3, 2, 3, 3, 3, 3, 5, 3, 28, 10, 3, 3, 4, 3, 4, 6, 4, 32, 10, 4, 13, 4, 14, 4, 33, 3, 4, 3, 4, 7, 4, 38, 10, 4, 12, 4, 14, 4, 41, 11, 4, 3, 4, 3, 4, 7, 4, 45, 10, 4, 12, 4, 14, 4, 48, 11, 4, 3, 4, 3, 4, 7, 4, 52, 10, 4, 12, 4, 14, 4, 55, 11, 4, 3, 4, 3, 4, 7, 4, 59, 10, 4, 12, 4, 14, 4, 62, 11, 4, 3, 4, 3, 4, 7, 4, 66, 10, 4, 12, 4, 14, 4, 69, 11, 4, 3, 4, 3, 4, 7, 4, 73, 10, 4, 12, 4, 14, 4, 76, 11, 4, 3, 5, 5, 5, 79, 10, 5, 3, 5, 3, 5, 7, 5, 83, 10, 5, 12, 5, 14, 5, 86, 11, 5, 3, 6, 3, 6, 6, 6, 90, 10, 6, 13, 6, 14, 6, 91, 3, 6, 3, 6, 7, 6, 96, 10, 6, 12, 6, 14, 6, 99, 11, 6, 3, 6, 3, 6, 7, 6, 103, 10, 6, 12, 6, 14, 6, 106, 11, 6, 3, 6, 7, 6, 109, 10, 6, 12, 6, 14, 6, 112, 11, 6, 3, 6, 7, 6, 115, 10, 6, 12, 6, 14, 6, 118, 11, 6, 3, 7, 3, 7, 6, 7, 122, 10, 7, 13, 7, 14, 7, 123, 3, 7, 3, 7, 7, 7, 128, 10, 7, 12, 7, 14, 7, 131, 11, 7, 3, 7, 3, 7, 7, 7, 135, 10, 7, 12, 7, 14, 7, 138, 11, 7, 3, 7, 3, 7, 7, 7, 142, 10, 7, 12, 7, 14, 7, 145, 11, 7, 3, 7, 3, 7, 3, 8, 3, 8, 6, 8, 151, 10, 8, 13, 8, 14, 8, 152, 3, 8, 3, 8, 3, 9, 7, 9, 158, 10, 9, 12, 9, 14, 9, 161, 11, 9, 3, 9, 3, 159, 2, 2, 10, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 12, 2, 14, 2, 16, 2, 2, 2, 2, 176, 2, 19, 3, 2, 2, 2, 4, 27, 3, 2, 2, 2, 6, 29, 3, 2, 2, 2, 8, 78, 3, 2, 2, 2, 10, 87, 3, 2, 2, 2, 12, 119, 3, 2, 2, 2, 14, 148, 3, 2, 2, 2, 16, 159, 3, 2, 2, 2, 18, 20, 5, 4, 3, 2, 19, 18, 3, 2, 2, 2, 20, 21, 3, 2, 2, 2, 21, 19, 3, 2, 2, 2, 21, 22, 3, 2, 2, 2, 22, 23, 3, 2, 2, 2, 23, 24, 7, 2, 2, 3, 24, 3, 3, 2, 2, 2, 25, 28, 5, 6, 4, 2, 26, 28, 5, 8, 5, 2, 27, 25, 3, 2, 2, 2, 27, 26, 3, 2, 2, 2, 28, 5, 3, 2, 2, 2, 29, 31, 7, 7, 2, 2, 30, 32, 7, 14, 2, 2, 31, 30, 3, 2, 2, 2, 32, 33, 3, 2, 2, 2, 33, 31, 3, 2, 2, 2, 33, 34, 3, 2, 2, 2, 34, 35, 3, 2, 2, 2, 35, 39, 7, 12, 2, 2, 36, 38, 7, 14, 2, 2, 37, 36, 3, 2, 2, 2, 38, 41, 3, 2, 2, 2, 39, 37, 3, 2, 2, 2, 39, 40, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 39, 3, 2, 2, 2, 42, 46, 7, 3, 2, 2, 43, 45, 7, 14, 2, 2, 44, 43, 3, 2, 2, 2, 45, 48, 3, 2, 2, 2, 46, 44, 3, 2, 2, 2, 46, 47, 3, 2, 2, 2, 47, 49, 3, 2, 2, 2, 48, 46, 3, 2, 2, 2, 49, 53, 7, 8, 2, 2, 50, 52, 7, 14, 2, 2, 51, 50, 3, 2, 2, 2, 52, 55, 3, 2, 2, 2, 53, 51, 3, 2, 2, 2, 53, 54, 3, 2, 2, 2, 54, 56, 3, 2, 2, 2, 55, 53, 3, 2, 2, 2, 56, 60, 7, 4, 2, 2, 57, 59, 7, 14, 2, 2, 58, 57, 3, 2, 2, 2, 59, 62, 3, 2, 2, 2, 60, 58, 3, 2, 2, 2, 60, 61, 3, 2, 2, 2, 61, 63, 3, 2, 2, 2, 62, 60, 3, 2, 2, 2, 63, 67, 7, 13, 2, 2, 64, 66, 7, 14, 2, 2, 65, 64, 3, 2, 2, 2, 66, 69, 3, 2, 2, 2, 67, 65, 3, 2, 2, 2, 67, 68, 3, 2, 2, 2, 68, 70, 3, 2, 2, 2, 69, 67, 3, 2, 2, 2, 70, 74, 7, 5, 2, 2, 71, 73, 7, 14, 2, 2, 72, 71, 3, 2, 2, 2, 73, 76, 3, 2, 2, 2, 74, 72, 3, 2, 2, 2, 74, 75, 3, 2, 2, 2, 75, 7, 3, 2, 2, 2, 76, 74, 3, 2, 2, 2, 77, 79, 5, 10, 6, 2, 78, 77, 3, 2, 2, 2, 78, 79, 3, 2, 2, 2, 79, 80, 3, 2, 2, 2, 80, 84, 5, 14, 8, 2, 81, 83, 7, 14, 2, 2, 82, 81, 3, 2, 2, 2, 83, 86, 3, 2, 2, 2, 84, 82, 3, 2, 2, 2, 84, 85, 3, 2, 2, 2, 85, 9, 3, 2, 2, 2, 86, 84, 3, 2, 2, 2, 87, 89, 7, 9, 2, 2, 88, 90, 7, 14, 2, 2, 89, 88, 3, 2, 2, 2, 90, 91, 3, 2, 2, 2, 91, 89, 3, 2, 2, 2, 91, 92, 3, 2, 2, 2, 92, 93, 3, 2, 2, 2, 93, 110, 5, 12, 7, 2, 94, 96, 7, 14, 2, 2, 95, 94, 3, 2, 2, 2, 96, 99, 3, 2, 2, 2, 97, 95, 3, 2, 2, 2, 97, 98, 3, 2, 2, 2, 98, 100, 3, 2, 2, 2, 99, 97, 3, 2, 2, 2, 100, 104, 7, 6, 2, 2, 101, 103, 7, 14, 2, 2, 102, 101, 3, 2, 2, 2, 103, 106, 3, 2, 2, 2, 104, 102, 3, 2, 2, 2, 104, 105, 3, 2, 2, 2, 105, 107, 3, 2, 2, 2, 106, 104, 3, 2, 2, 2, 107, 109, 5, 12, 7, 2, 108, 97, 3, 2, 2, 2, 109, 112, 3, 2, 2, 2, 110, 108, 3, 2, 2, 2, 110, 111, 3, 2, 2, 2, 111, 116, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 113, 115, 7, 14, 2, 2, 114, 113, 3, 2, 2, 2, 115, 118, 3, 2, 2, 2, 116, 114, 3, 2, 2, 2, 116, 117, 3, 2, 2, 2, 117, 11, 3, 2, 2, 2, 118, 116, 3, 2, 2, 2, 119, 121, 7, 12, 2, 2, 120, 122, 7, 14, 2, 2, 121, 120, 3, 2, 2, 2, 122, 123, 3, 2, 2, 2, 123, 121, 3, 2, 2, 2, 123, 124, 3, 2, 2, 2, 124, 125, 3, 2, 2, 2, 125, 129, 7, 10, 2, 2, 126, 128, 7, 14, 2, 2, 127, 126, 3, 2, 2, 2, 128, 131, 3, 2, 2, 2, 129, 127, 3, 2, 2, 2, 129, 130, 3, 2, 2, 2, 130, 132, 3, 2, 2, 2, 131, 129, 3, 2, 2, 2, 132, 136, 7, 4, 2, 2, 133, 135, 7, 14, 2, 2, 134, 133, 3, 2, 2, 2, 135, 138, 3, 2, 2, 2, 136, 134, 3, 2, 2, 2, 136, 137, 3, 2, 2, 2, 137, 139, 3, 2, 2, 2, 138, 136, 3, 2, 2, 2, 139, 143, 5, 14, 8, 2, 140, 142, 7, 14, 2, 2, 141, 140, 3, 2, 2, 2, 142, 145, 3, 2, 2, 2, 143, 141, 3, 2, 2, 2, 143, 144, 3, 2, 2, 2, 144, 146, 3, 2, 2, 2, 145, 143, 3, 2, 2, 2, 146, 147, 7, 5, 2, 2, 147, 13, 3, 2, 2, 2, 148, 150, 7, 11, 2, 2, 149, 151, 7, 14, 2, 2, 150, 149, 3, 2, 2, 2, 151, 152, 3, 2, 2, 2, 152, 150, 3, 2, 2, 2, 152, 153, 3, 2, 2, 2, 153, 154, 3, 2, 2, 2, 154, 155, 5, 16, 9, 2, 155, 15, 3, 2, 2, 2, 156, 158, 11, 2, 2, 2, 157, 156, 3, 2, 2, 2, 158, 161, 3, 2, 2, 2, 159, 160, 3, 2, 2, 2, 159, 157, 3, 2, 2, 2, 160, 17, 3, 2, 2, 2, 161, 159, 3, 2, 2, 2, 24, 21, 27, 33, 39, 46, 53, 60, 67, 74, 78, 84, 91, 97, 104, 110, 116, 123, 129, 136, 143, 152, 159] \ No newline at end of file diff --git a/src/grammar/SqlSealLang.tokens b/src/grammar/SqlSealLang.tokens new file mode 100644 index 0000000..f80b1e1 --- /dev/null +++ b/src/grammar/SqlSealLang.tokens @@ -0,0 +1,22 @@ +T__0=1 +T__1=2 +T__2=3 +T__3=4 +TABLE=5 +FILE=6 +WITH=7 +AS=8 +SELECT=9 +ID=10 +FILE_URL=11 +WS=12 +ANY=13 +'='=1 +'('=2 +')'=3 +','=4 +'TABLE'=5 +'file'=6 +'WITH'=7 +'AS'=8 +'SELECT'=9 diff --git a/src/grammar/SqlSealLangLexer.interp b/src/grammar/SqlSealLangLexer.interp new file mode 100644 index 0000000..71e838b --- /dev/null +++ b/src/grammar/SqlSealLangLexer.interp @@ -0,0 +1,56 @@ +token literal names: +null +'=' +'(' +')' +',' +'TABLE' +'file' +'WITH' +'AS' +'SELECT' +null +null +null +null + +token symbolic names: +null +null +null +null +null +TABLE +FILE +WITH +AS +SELECT +ID +FILE_URL +WS +ANY + +rule names: +T__0 +T__1 +T__2 +T__3 +TABLE +FILE +WITH +AS +SELECT +ID +FILE_URL +WS +ANY + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 15, 82, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 11, 3, 11, 7, 11, 66, 10, 11, 12, 11, 14, 11, 69, 11, 11, 3, 12, 6, 12, 72, 10, 12, 13, 12, 14, 12, 73, 3, 13, 6, 13, 77, 10, 13, 13, 13, 14, 13, 78, 3, 14, 3, 14, 2, 2, 2, 15, 3, 2, 3, 5, 2, 4, 7, 2, 5, 9, 2, 6, 11, 2, 7, 13, 2, 8, 15, 2, 9, 17, 2, 10, 19, 2, 11, 21, 2, 12, 23, 2, 13, 25, 2, 14, 27, 2, 15, 3, 2, 6, 5, 2, 67, 92, 97, 97, 99, 124, 6, 2, 50, 59, 67, 92, 97, 97, 99, 124, 7, 2, 47, 59, 67, 92, 94, 94, 97, 97, 99, 124, 5, 2, 11, 12, 15, 15, 34, 34, 2, 84, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 3, 29, 3, 2, 2, 2, 5, 31, 3, 2, 2, 2, 7, 33, 3, 2, 2, 2, 9, 35, 3, 2, 2, 2, 11, 37, 3, 2, 2, 2, 13, 43, 3, 2, 2, 2, 15, 48, 3, 2, 2, 2, 17, 53, 3, 2, 2, 2, 19, 56, 3, 2, 2, 2, 21, 63, 3, 2, 2, 2, 23, 71, 3, 2, 2, 2, 25, 76, 3, 2, 2, 2, 27, 80, 3, 2, 2, 2, 29, 30, 7, 63, 2, 2, 30, 4, 3, 2, 2, 2, 31, 32, 7, 42, 2, 2, 32, 6, 3, 2, 2, 2, 33, 34, 7, 43, 2, 2, 34, 8, 3, 2, 2, 2, 35, 36, 7, 46, 2, 2, 36, 10, 3, 2, 2, 2, 37, 38, 7, 86, 2, 2, 38, 39, 7, 67, 2, 2, 39, 40, 7, 68, 2, 2, 40, 41, 7, 78, 2, 2, 41, 42, 7, 71, 2, 2, 42, 12, 3, 2, 2, 2, 43, 44, 7, 104, 2, 2, 44, 45, 7, 107, 2, 2, 45, 46, 7, 110, 2, 2, 46, 47, 7, 103, 2, 2, 47, 14, 3, 2, 2, 2, 48, 49, 7, 89, 2, 2, 49, 50, 7, 75, 2, 2, 50, 51, 7, 86, 2, 2, 51, 52, 7, 74, 2, 2, 52, 16, 3, 2, 2, 2, 53, 54, 7, 67, 2, 2, 54, 55, 7, 85, 2, 2, 55, 18, 3, 2, 2, 2, 56, 57, 7, 85, 2, 2, 57, 58, 7, 71, 2, 2, 58, 59, 7, 78, 2, 2, 59, 60, 7, 71, 2, 2, 60, 61, 7, 69, 2, 2, 61, 62, 7, 86, 2, 2, 62, 20, 3, 2, 2, 2, 63, 67, 9, 2, 2, 2, 64, 66, 9, 3, 2, 2, 65, 64, 3, 2, 2, 2, 66, 69, 3, 2, 2, 2, 67, 65, 3, 2, 2, 2, 67, 68, 3, 2, 2, 2, 68, 22, 3, 2, 2, 2, 69, 67, 3, 2, 2, 2, 70, 72, 9, 4, 2, 2, 71, 70, 3, 2, 2, 2, 72, 73, 3, 2, 2, 2, 73, 71, 3, 2, 2, 2, 73, 74, 3, 2, 2, 2, 74, 24, 3, 2, 2, 2, 75, 77, 9, 5, 2, 2, 76, 75, 3, 2, 2, 2, 77, 78, 3, 2, 2, 2, 78, 76, 3, 2, 2, 2, 78, 79, 3, 2, 2, 2, 79, 26, 3, 2, 2, 2, 80, 81, 11, 2, 2, 2, 81, 28, 3, 2, 2, 2, 6, 2, 67, 73, 78, 2] \ No newline at end of file diff --git a/src/grammar/SqlSealLangLexer.tokens b/src/grammar/SqlSealLangLexer.tokens new file mode 100644 index 0000000..f80b1e1 --- /dev/null +++ b/src/grammar/SqlSealLangLexer.tokens @@ -0,0 +1,22 @@ +T__0=1 +T__1=2 +T__2=3 +T__3=4 +TABLE=5 +FILE=6 +WITH=7 +AS=8 +SELECT=9 +ID=10 +FILE_URL=11 +WS=12 +ANY=13 +'='=1 +'('=2 +')'=3 +','=4 +'TABLE'=5 +'file'=6 +'WITH'=7 +'AS'=8 +'SELECT'=9 diff --git a/src/grammar/SqlSealLangLexer.ts b/src/grammar/SqlSealLangLexer.ts new file mode 100644 index 0000000..8a6990d --- /dev/null +++ b/src/grammar/SqlSealLangLexer.ts @@ -0,0 +1,131 @@ +// Generated from SqlSealLang.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ATN } from "antlr4ts/atn/ATN"; +import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; +import { CharStream } from "antlr4ts/CharStream"; +import { Lexer } from "antlr4ts/Lexer"; +import { LexerATNSimulator } from "antlr4ts/atn/LexerATNSimulator"; +import { NotNull } from "antlr4ts/Decorators"; +import { Override } from "antlr4ts/Decorators"; +import { RuleContext } from "antlr4ts/RuleContext"; +import { Vocabulary } from "antlr4ts/Vocabulary"; +import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; + +import * as Utils from "antlr4ts/misc/Utils"; + + +export class SqlSealLangLexer extends Lexer { + public static readonly T__0 = 1; + public static readonly T__1 = 2; + public static readonly T__2 = 3; + public static readonly T__3 = 4; + public static readonly TABLE = 5; + public static readonly FILE = 6; + public static readonly WITH = 7; + public static readonly AS = 8; + public static readonly SELECT = 9; + public static readonly ID = 10; + public static readonly FILE_URL = 11; + public static readonly WS = 12; + public static readonly ANY = 13; + + // tslint:disable:no-trailing-whitespace + public static readonly channelNames: string[] = [ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + ]; + + // tslint:disable:no-trailing-whitespace + public static readonly modeNames: string[] = [ + "DEFAULT_MODE", + ]; + + public static readonly ruleNames: string[] = [ + "T__0", "T__1", "T__2", "T__3", "TABLE", "FILE", "WITH", "AS", "SELECT", + "ID", "FILE_URL", "WS", "ANY", + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, "'='", "'('", "')'", "','", "'TABLE'", "'file'", "'WITH'", + "'AS'", "'SELECT'", + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, undefined, undefined, undefined, undefined, "TABLE", "FILE", + "WITH", "AS", "SELECT", "ID", "FILE_URL", "WS", "ANY", + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(SqlSealLangLexer._LITERAL_NAMES, SqlSealLangLexer._SYMBOLIC_NAMES, []); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return SqlSealLangLexer.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + + constructor(input: CharStream) { + super(input); + this._interp = new LexerATNSimulator(SqlSealLangLexer._ATN, this); + } + + // @Override + public get grammarFileName(): string { return "SqlSealLang.g4"; } + + // @Override + public get ruleNames(): string[] { return SqlSealLangLexer.ruleNames; } + + // @Override + public get serializedATN(): string { return SqlSealLangLexer._serializedATN; } + + // @Override + public get channelNames(): string[] { return SqlSealLangLexer.channelNames; } + + // @Override + public get modeNames(): string[] { return SqlSealLangLexer.modeNames; } + + public static readonly _serializedATN: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02\x0FR\b\x01\x04" + + "\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x04" + + "\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r\t\r" + + "\x04\x0E\t\x0E\x03\x02\x03\x02\x03\x03\x03\x03\x03\x04\x03\x04\x03\x05" + + "\x03\x05\x03\x06\x03\x06\x03\x06\x03\x06\x03\x06\x03\x06\x03\x07\x03\x07" + + "\x03\x07\x03\x07\x03\x07\x03\b\x03\b\x03\b\x03\b\x03\b\x03\t\x03\t\x03" + + "\t\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\v\x03\v\x07\vB\n\v\f" + + "\v\x0E\vE\v\v\x03\f\x06\fH\n\f\r\f\x0E\fI\x03\r\x06\rM\n\r\r\r\x0E\rN" + + "\x03\x0E\x03\x0E\x02\x02\x02\x0F\x03\x02\x03\x05\x02\x04\x07\x02\x05\t" + + "\x02\x06\v\x02\x07\r\x02\b\x0F\x02\t\x11\x02\n\x13\x02\v\x15\x02\f\x17" + + "\x02\r\x19\x02\x0E\x1B\x02\x0F\x03\x02\x06\x05\x02C\\aac|\x06\x022;C\\" + + "aac|\x07\x02/;C\\^^aac|\x05\x02\v\f\x0F\x0F\"\"\x02T\x02\x03\x03\x02\x02" + + "\x02\x02\x05\x03\x02\x02\x02\x02\x07\x03\x02\x02\x02\x02\t\x03\x02\x02" + + "\x02\x02\v\x03\x02\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02\x02\x02" + + "\x02\x11\x03\x02\x02\x02\x02\x13\x03\x02\x02\x02\x02\x15\x03\x02\x02\x02" + + "\x02\x17\x03\x02\x02\x02\x02\x19\x03\x02\x02\x02\x02\x1B\x03\x02\x02\x02" + + "\x03\x1D\x03\x02\x02\x02\x05\x1F\x03\x02\x02\x02\x07!\x03\x02\x02\x02" + + "\t#\x03\x02\x02\x02\v%\x03\x02\x02\x02\r+\x03\x02\x02\x02\x0F0\x03\x02" + + "\x02\x02\x115\x03\x02\x02\x02\x138\x03\x02\x02\x02\x15?\x03\x02\x02\x02" + + "\x17G\x03\x02\x02\x02\x19L\x03\x02\x02\x02\x1BP\x03\x02\x02\x02\x1D\x1E" + + "\x07?\x02\x02\x1E\x04\x03\x02\x02\x02\x1F \x07*\x02\x02 \x06\x03\x02\x02" + + "\x02!\"\x07+\x02\x02\"\b\x03\x02\x02\x02#$\x07.\x02\x02$\n\x03\x02\x02" + + "\x02%&\x07V\x02\x02&\'\x07C\x02\x02\'(\x07D\x02\x02()\x07N\x02\x02)*\x07" + + "G\x02\x02*\f\x03\x02\x02\x02+,\x07h\x02\x02,-\x07k\x02\x02-.\x07n\x02" + + "\x02./\x07g\x02\x02/\x0E\x03\x02\x02\x0201\x07Y\x02\x0212\x07K\x02\x02" + + "23\x07V\x02\x0234\x07J\x02\x024\x10\x03\x02\x02\x0256\x07C\x02\x0267\x07" + + "U\x02\x027\x12\x03\x02\x02\x0289\x07U\x02\x029:\x07G\x02\x02:;\x07N\x02" + + "\x02;<\x07G\x02\x02<=\x07E\x02\x02=>\x07V\x02\x02>\x14\x03\x02\x02\x02" + + "?C\t\x02\x02\x02@B\t\x03\x02\x02A@\x03\x02\x02\x02BE\x03\x02\x02\x02C" + + "A\x03\x02\x02\x02CD\x03\x02\x02\x02D\x16\x03\x02\x02\x02EC\x03\x02\x02" + + "\x02FH\t\x04\x02\x02GF\x03\x02\x02\x02HI\x03\x02\x02\x02IG\x03\x02\x02" + + "\x02IJ\x03\x02\x02\x02J\x18\x03\x02\x02\x02KM\t\x05\x02\x02LK\x03\x02" + + "\x02\x02MN\x03\x02\x02\x02NL\x03\x02\x02\x02NO\x03\x02\x02\x02O\x1A\x03" + + "\x02\x02\x02PQ\v\x02\x02\x02Q\x1C\x03\x02\x02\x02\x06\x02CIN\x02"; + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!SqlSealLangLexer.__ATN) { + SqlSealLangLexer.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(SqlSealLangLexer._serializedATN)); + } + + return SqlSealLangLexer.__ATN; + } + +} + diff --git a/src/grammar/SqlSealLangListener.ts b/src/grammar/SqlSealLangListener.ts new file mode 100644 index 0000000..320fddd --- /dev/null +++ b/src/grammar/SqlSealLangListener.ts @@ -0,0 +1,109 @@ +// Generated from SqlSealLang.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; + +import { ParseContext } from "./SqlSealLangParser"; +import { StatementContext } from "./SqlSealLangParser"; +import { TableStatementContext } from "./SqlSealLangParser"; +import { QueryStatementContext } from "./SqlSealLangParser"; +import { WithClauseContext } from "./SqlSealLangParser"; +import { CteDefinitionContext } from "./SqlSealLangParser"; +import { SelectStatementContext } from "./SqlSealLangParser"; +import { SelectBodyContext } from "./SqlSealLangParser"; + + +/** + * This interface defines a complete listener for a parse tree produced by + * `SqlSealLangParser`. + */ +export interface SqlSealLangListener extends ParseTreeListener { + /** + * Enter a parse tree produced by `SqlSealLangParser.parse`. + * @param ctx the parse tree + */ + enterParse?: (ctx: ParseContext) => void; + /** + * Exit a parse tree produced by `SqlSealLangParser.parse`. + * @param ctx the parse tree + */ + exitParse?: (ctx: ParseContext) => void; + + /** + * Enter a parse tree produced by `SqlSealLangParser.statement`. + * @param ctx the parse tree + */ + enterStatement?: (ctx: StatementContext) => void; + /** + * Exit a parse tree produced by `SqlSealLangParser.statement`. + * @param ctx the parse tree + */ + exitStatement?: (ctx: StatementContext) => void; + + /** + * Enter a parse tree produced by `SqlSealLangParser.tableStatement`. + * @param ctx the parse tree + */ + enterTableStatement?: (ctx: TableStatementContext) => void; + /** + * Exit a parse tree produced by `SqlSealLangParser.tableStatement`. + * @param ctx the parse tree + */ + exitTableStatement?: (ctx: TableStatementContext) => void; + + /** + * Enter a parse tree produced by `SqlSealLangParser.queryStatement`. + * @param ctx the parse tree + */ + enterQueryStatement?: (ctx: QueryStatementContext) => void; + /** + * Exit a parse tree produced by `SqlSealLangParser.queryStatement`. + * @param ctx the parse tree + */ + exitQueryStatement?: (ctx: QueryStatementContext) => void; + + /** + * Enter a parse tree produced by `SqlSealLangParser.withClause`. + * @param ctx the parse tree + */ + enterWithClause?: (ctx: WithClauseContext) => void; + /** + * Exit a parse tree produced by `SqlSealLangParser.withClause`. + * @param ctx the parse tree + */ + exitWithClause?: (ctx: WithClauseContext) => void; + + /** + * Enter a parse tree produced by `SqlSealLangParser.cteDefinition`. + * @param ctx the parse tree + */ + enterCteDefinition?: (ctx: CteDefinitionContext) => void; + /** + * Exit a parse tree produced by `SqlSealLangParser.cteDefinition`. + * @param ctx the parse tree + */ + exitCteDefinition?: (ctx: CteDefinitionContext) => void; + + /** + * Enter a parse tree produced by `SqlSealLangParser.selectStatement`. + * @param ctx the parse tree + */ + enterSelectStatement?: (ctx: SelectStatementContext) => void; + /** + * Exit a parse tree produced by `SqlSealLangParser.selectStatement`. + * @param ctx the parse tree + */ + exitSelectStatement?: (ctx: SelectStatementContext) => void; + + /** + * Enter a parse tree produced by `SqlSealLangParser.selectBody`. + * @param ctx the parse tree + */ + enterSelectBody?: (ctx: SelectBodyContext) => void; + /** + * Exit a parse tree produced by `SqlSealLangParser.selectBody`. + * @param ctx the parse tree + */ + exitSelectBody?: (ctx: SelectBodyContext) => void; +} + diff --git a/src/grammar/SqlSealLangParser.ts b/src/grammar/SqlSealLangParser.ts new file mode 100644 index 0000000..b058cb5 --- /dev/null +++ b/src/grammar/SqlSealLangParser.ts @@ -0,0 +1,1043 @@ +// Generated from SqlSealLang.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ATN } from "antlr4ts/atn/ATN"; +import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; +import { FailedPredicateException } from "antlr4ts/FailedPredicateException"; +import { NotNull } from "antlr4ts/Decorators"; +import { NoViableAltException } from "antlr4ts/NoViableAltException"; +import { Override } from "antlr4ts/Decorators"; +import { Parser } from "antlr4ts/Parser"; +import { ParserRuleContext } from "antlr4ts/ParserRuleContext"; +import { ParserATNSimulator } from "antlr4ts/atn/ParserATNSimulator"; +import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; +import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor"; +import { RecognitionException } from "antlr4ts/RecognitionException"; +import { RuleContext } from "antlr4ts/RuleContext"; +//import { RuleVersion } from "antlr4ts/RuleVersion"; +import { TerminalNode } from "antlr4ts/tree/TerminalNode"; +import { Token } from "antlr4ts/Token"; +import { TokenStream } from "antlr4ts/TokenStream"; +import { Vocabulary } from "antlr4ts/Vocabulary"; +import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; + +import * as Utils from "antlr4ts/misc/Utils"; + +import { SqlSealLangListener } from "./SqlSealLangListener"; +import { SqlSealLangVisitor } from "./SqlSealLangVisitor"; + + +export class SqlSealLangParser extends Parser { + public static readonly T__0 = 1; + public static readonly T__1 = 2; + public static readonly T__2 = 3; + public static readonly T__3 = 4; + public static readonly TABLE = 5; + public static readonly FILE = 6; + public static readonly WITH = 7; + public static readonly AS = 8; + public static readonly SELECT = 9; + public static readonly ID = 10; + public static readonly FILE_URL = 11; + public static readonly WS = 12; + public static readonly ANY = 13; + public static readonly RULE_parse = 0; + public static readonly RULE_statement = 1; + public static readonly RULE_tableStatement = 2; + public static readonly RULE_queryStatement = 3; + public static readonly RULE_withClause = 4; + public static readonly RULE_cteDefinition = 5; + public static readonly RULE_selectStatement = 6; + public static readonly RULE_selectBody = 7; + // tslint:disable:no-trailing-whitespace + public static readonly ruleNames: string[] = [ + "parse", "statement", "tableStatement", "queryStatement", "withClause", + "cteDefinition", "selectStatement", "selectBody", + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, "'='", "'('", "')'", "','", "'TABLE'", "'file'", "'WITH'", + "'AS'", "'SELECT'", + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, undefined, undefined, undefined, undefined, "TABLE", "FILE", + "WITH", "AS", "SELECT", "ID", "FILE_URL", "WS", "ANY", + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(SqlSealLangParser._LITERAL_NAMES, SqlSealLangParser._SYMBOLIC_NAMES, []); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return SqlSealLangParser.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + // @Override + public get grammarFileName(): string { return "SqlSealLang.g4"; } + + // @Override + public get ruleNames(): string[] { return SqlSealLangParser.ruleNames; } + + // @Override + public get serializedATN(): string { return SqlSealLangParser._serializedATN; } + + protected createFailedPredicateException(predicate?: string, message?: string): FailedPredicateException { + return new FailedPredicateException(this, predicate, message); + } + + constructor(input: TokenStream) { + super(input); + this._interp = new ParserATNSimulator(SqlSealLangParser._ATN, this); + } + // @RuleVersion(0) + public parse(): ParseContext { + let _localctx: ParseContext = new ParseContext(this._ctx, this.state); + this.enterRule(_localctx, 0, SqlSealLangParser.RULE_parse); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 17; + this._errHandler.sync(this); + _la = this._input.LA(1); + do { + { + { + this.state = 16; + this.statement(); + } + } + this.state = 19; + this._errHandler.sync(this); + _la = this._input.LA(1); + } while ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << SqlSealLangParser.TABLE) | (1 << SqlSealLangParser.WITH) | (1 << SqlSealLangParser.SELECT))) !== 0)); + this.state = 21; + this.match(SqlSealLangParser.EOF); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public statement(): StatementContext { + let _localctx: StatementContext = new StatementContext(this._ctx, this.state); + this.enterRule(_localctx, 2, SqlSealLangParser.RULE_statement); + try { + this.state = 25; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case SqlSealLangParser.TABLE: + this.enterOuterAlt(_localctx, 1); + { + this.state = 23; + this.tableStatement(); + } + break; + case SqlSealLangParser.WITH: + case SqlSealLangParser.SELECT: + this.enterOuterAlt(_localctx, 2); + { + this.state = 24; + this.queryStatement(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public tableStatement(): TableStatementContext { + let _localctx: TableStatementContext = new TableStatementContext(this._ctx, this.state); + this.enterRule(_localctx, 4, SqlSealLangParser.RULE_tableStatement); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 27; + this.match(SqlSealLangParser.TABLE); + this.state = 29; + this._errHandler.sync(this); + _la = this._input.LA(1); + do { + { + { + this.state = 28; + this.match(SqlSealLangParser.WS); + } + } + this.state = 31; + this._errHandler.sync(this); + _la = this._input.LA(1); + } while (_la === SqlSealLangParser.WS); + this.state = 33; + this.match(SqlSealLangParser.ID); + this.state = 37; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 34; + this.match(SqlSealLangParser.WS); + } + } + this.state = 39; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 40; + this.match(SqlSealLangParser.T__0); + this.state = 44; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 41; + this.match(SqlSealLangParser.WS); + } + } + this.state = 46; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 47; + this.match(SqlSealLangParser.FILE); + this.state = 51; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 48; + this.match(SqlSealLangParser.WS); + } + } + this.state = 53; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 54; + this.match(SqlSealLangParser.T__1); + this.state = 58; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 55; + this.match(SqlSealLangParser.WS); + } + } + this.state = 60; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 61; + this.match(SqlSealLangParser.FILE_URL); + this.state = 65; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 62; + this.match(SqlSealLangParser.WS); + } + } + this.state = 67; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 68; + this.match(SqlSealLangParser.T__2); + this.state = 72; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 69; + this.match(SqlSealLangParser.WS); + } + } + this.state = 74; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public queryStatement(): QueryStatementContext { + let _localctx: QueryStatementContext = new QueryStatementContext(this._ctx, this.state); + this.enterRule(_localctx, 6, SqlSealLangParser.RULE_queryStatement); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 76; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === SqlSealLangParser.WITH) { + { + this.state = 75; + this.withClause(); + } + } + + this.state = 78; + this.selectStatement(); + this.state = 82; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 79; + this.match(SqlSealLangParser.WS); + } + } + this.state = 84; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public withClause(): WithClauseContext { + let _localctx: WithClauseContext = new WithClauseContext(this._ctx, this.state); + this.enterRule(_localctx, 8, SqlSealLangParser.RULE_withClause); + let _la: number; + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 85; + this.match(SqlSealLangParser.WITH); + this.state = 87; + this._errHandler.sync(this); + _la = this._input.LA(1); + do { + { + { + this.state = 86; + this.match(SqlSealLangParser.WS); + } + } + this.state = 89; + this._errHandler.sync(this); + _la = this._input.LA(1); + } while (_la === SqlSealLangParser.WS); + this.state = 91; + this.cteDefinition(); + this.state = 108; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 14, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 95; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 92; + this.match(SqlSealLangParser.WS); + } + } + this.state = 97; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 98; + this.match(SqlSealLangParser.T__3); + this.state = 102; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 99; + this.match(SqlSealLangParser.WS); + } + } + this.state = 104; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 105; + this.cteDefinition(); + } + } + } + this.state = 110; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 14, this._ctx); + } + this.state = 114; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 111; + this.match(SqlSealLangParser.WS); + } + } + this.state = 116; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public cteDefinition(): CteDefinitionContext { + let _localctx: CteDefinitionContext = new CteDefinitionContext(this._ctx, this.state); + this.enterRule(_localctx, 10, SqlSealLangParser.RULE_cteDefinition); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 117; + this.match(SqlSealLangParser.ID); + this.state = 119; + this._errHandler.sync(this); + _la = this._input.LA(1); + do { + { + { + this.state = 118; + this.match(SqlSealLangParser.WS); + } + } + this.state = 121; + this._errHandler.sync(this); + _la = this._input.LA(1); + } while (_la === SqlSealLangParser.WS); + this.state = 123; + this.match(SqlSealLangParser.AS); + this.state = 127; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 124; + this.match(SqlSealLangParser.WS); + } + } + this.state = 129; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 130; + this.match(SqlSealLangParser.T__1); + this.state = 134; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 131; + this.match(SqlSealLangParser.WS); + } + } + this.state = 136; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 137; + this.selectStatement(); + this.state = 141; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === SqlSealLangParser.WS) { + { + { + this.state = 138; + this.match(SqlSealLangParser.WS); + } + } + this.state = 143; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 144; + this.match(SqlSealLangParser.T__2); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public selectStatement(): SelectStatementContext { + let _localctx: SelectStatementContext = new SelectStatementContext(this._ctx, this.state); + this.enterRule(_localctx, 12, SqlSealLangParser.RULE_selectStatement); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 146; + this.match(SqlSealLangParser.SELECT); + this.state = 148; + this._errHandler.sync(this); + _alt = 1; + do { + switch (_alt) { + case 1: + { + { + this.state = 147; + this.match(SqlSealLangParser.WS); + } + } + break; + default: + throw new NoViableAltException(this); + } + this.state = 150; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 20, this._ctx); + } while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER); + this.state = 152; + this.selectBody(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public selectBody(): SelectBodyContext { + let _localctx: SelectBodyContext = new SelectBodyContext(this._ctx, this.state); + this.enterRule(_localctx, 14, SqlSealLangParser.RULE_selectBody); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 157; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 21, this._ctx); + while (_alt !== 1 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1 + 1) { + { + { + this.state = 154; + this.matchWildcard(); + } + } + } + this.state = 159; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 21, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + + public static readonly _serializedATN: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03\x0F\xA3\x04\x02" + + "\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x04\x07" + + "\t\x07\x04\b\t\b\x04\t\t\t\x03\x02\x06\x02\x14\n\x02\r\x02\x0E\x02\x15" + + "\x03\x02\x03\x02\x03\x03\x03\x03\x05\x03\x1C\n\x03\x03\x04\x03\x04\x06" + + "\x04 \n\x04\r\x04\x0E\x04!\x03\x04\x03\x04\x07\x04&\n\x04\f\x04\x0E\x04" + + ")\v\x04\x03\x04\x03\x04\x07\x04-\n\x04\f\x04\x0E\x040\v\x04\x03\x04\x03" + + "\x04\x07\x044\n\x04\f\x04\x0E\x047\v\x04\x03\x04\x03\x04\x07\x04;\n\x04" + + "\f\x04\x0E\x04>\v\x04\x03\x04\x03\x04\x07\x04B\n\x04\f\x04\x0E\x04E\v" + + "\x04\x03\x04\x03\x04\x07\x04I\n\x04\f\x04\x0E\x04L\v\x04\x03\x05\x05\x05" + + "O\n\x05\x03\x05\x03\x05\x07\x05S\n\x05\f\x05\x0E\x05V\v\x05\x03\x06\x03" + + "\x06\x06\x06Z\n\x06\r\x06\x0E\x06[\x03\x06\x03\x06\x07\x06`\n\x06\f\x06" + + "\x0E\x06c\v\x06\x03\x06\x03\x06\x07\x06g\n\x06\f\x06\x0E\x06j\v\x06\x03" + + "\x06\x07\x06m\n\x06\f\x06\x0E\x06p\v\x06\x03\x06\x07\x06s\n\x06\f\x06" + + "\x0E\x06v\v\x06\x03\x07\x03\x07\x06\x07z\n\x07\r\x07\x0E\x07{\x03\x07" + + "\x03\x07\x07\x07\x80\n\x07\f\x07\x0E\x07\x83\v\x07\x03\x07\x03\x07\x07" + + "\x07\x87\n\x07\f\x07\x0E\x07\x8A\v\x07\x03\x07\x03\x07\x07\x07\x8E\n\x07" + + "\f\x07\x0E\x07\x91\v\x07\x03\x07\x03\x07\x03\b\x03\b\x06\b\x97\n\b\r\b" + + "\x0E\b\x98\x03\b\x03\b\x03\t\x07\t\x9E\n\t\f\t\x0E\t\xA1\v\t\x03\t\x03" + + "\x9F\x02\x02\n\x02\x02\x04\x02\x06\x02\b\x02\n\x02\f\x02\x0E\x02\x10\x02" + + "\x02\x02\x02\xB0\x02\x13\x03\x02\x02\x02\x04\x1B\x03\x02\x02\x02\x06\x1D" + + "\x03\x02\x02\x02\bN\x03\x02\x02\x02\nW\x03\x02\x02\x02\fw\x03\x02\x02" + + "\x02\x0E\x94\x03\x02\x02\x02\x10\x9F\x03\x02\x02\x02\x12\x14\x05\x04\x03" + + "\x02\x13\x12\x03\x02\x02\x02\x14\x15\x03\x02\x02\x02\x15\x13\x03\x02\x02" + + "\x02\x15\x16\x03\x02\x02\x02\x16\x17\x03\x02\x02\x02\x17\x18\x07\x02\x02" + + "\x03\x18\x03\x03\x02\x02\x02\x19\x1C\x05\x06\x04\x02\x1A\x1C\x05\b\x05" + + "\x02\x1B\x19\x03\x02\x02\x02\x1B\x1A\x03\x02\x02\x02\x1C\x05\x03\x02\x02" + + "\x02\x1D\x1F\x07\x07\x02\x02\x1E \x07\x0E\x02\x02\x1F\x1E\x03\x02\x02" + + "\x02 !\x03\x02\x02\x02!\x1F\x03\x02\x02\x02!\"\x03\x02\x02\x02\"#\x03" + + "\x02\x02\x02#\'\x07\f\x02\x02$&\x07\x0E\x02\x02%$\x03\x02\x02\x02&)\x03" + + "\x02\x02\x02\'%\x03\x02\x02\x02\'(\x03\x02\x02\x02(*\x03\x02\x02\x02)" + + "\'\x03\x02\x02\x02*.\x07\x03\x02\x02+-\x07\x0E\x02\x02,+\x03\x02\x02\x02" + + "-0\x03\x02\x02\x02.,\x03\x02\x02\x02./\x03\x02\x02\x02/1\x03\x02\x02\x02" + + "0.\x03\x02\x02\x0215\x07\b\x02\x0224\x07\x0E\x02\x0232\x03\x02\x02\x02" + + "47\x03\x02\x02\x0253\x03\x02\x02\x0256\x03\x02\x02\x0268\x03\x02\x02\x02" + + "75\x03\x02\x02\x028<\x07\x04\x02\x029;\x07\x0E\x02\x02:9\x03\x02\x02\x02" + + ";>\x03\x02\x02\x02<:\x03\x02\x02\x02<=\x03\x02\x02\x02=?\x03\x02\x02\x02" + + "><\x03\x02\x02\x02?C\x07\r\x02\x02@B\x07\x0E\x02\x02A@\x03\x02\x02\x02" + + "BE\x03\x02\x02\x02CA\x03\x02\x02\x02CD\x03\x02\x02\x02DF\x03\x02\x02\x02" + + "EC\x03\x02\x02\x02FJ\x07\x05\x02\x02GI\x07\x0E\x02\x02HG\x03\x02\x02\x02" + + "IL\x03\x02\x02\x02JH\x03\x02\x02\x02JK\x03\x02\x02\x02K\x07\x03\x02\x02" + + "\x02LJ\x03\x02\x02\x02MO\x05\n\x06\x02NM\x03\x02\x02\x02NO\x03\x02\x02" + + "\x02OP\x03\x02\x02\x02PT\x05\x0E\b\x02QS\x07\x0E\x02\x02RQ\x03\x02\x02" + + "\x02SV\x03\x02\x02\x02TR\x03\x02\x02\x02TU\x03\x02\x02\x02U\t\x03\x02" + + "\x02\x02VT\x03\x02\x02\x02WY\x07\t\x02\x02XZ\x07\x0E\x02\x02YX\x03\x02" + + "\x02\x02Z[\x03\x02\x02\x02[Y\x03\x02\x02\x02[\\\x03\x02\x02\x02\\]\x03" + + "\x02\x02\x02]n\x05\f\x07\x02^`\x07\x0E\x02\x02_^\x03\x02\x02\x02`c\x03" + + "\x02\x02\x02a_\x03\x02\x02\x02ab\x03\x02\x02\x02bd\x03\x02\x02\x02ca\x03" + + "\x02\x02\x02dh\x07\x06\x02\x02eg\x07\x0E\x02\x02fe\x03\x02\x02\x02gj\x03" + + "\x02\x02\x02hf\x03\x02\x02\x02hi\x03\x02\x02\x02ik\x03\x02\x02\x02jh\x03" + + "\x02\x02\x02km\x05\f\x07\x02la\x03\x02\x02\x02mp\x03\x02\x02\x02nl\x03" + + "\x02\x02\x02no\x03\x02\x02\x02ot\x03\x02\x02\x02pn\x03\x02\x02\x02qs\x07" + + "\x0E\x02\x02rq\x03\x02\x02\x02sv\x03\x02\x02\x02tr\x03\x02\x02\x02tu\x03" + + "\x02\x02\x02u\v\x03\x02\x02\x02vt\x03\x02\x02\x02wy\x07\f\x02\x02xz\x07" + + "\x0E\x02\x02yx\x03\x02\x02\x02z{\x03\x02\x02\x02{y\x03\x02\x02\x02{|\x03" + + "\x02\x02\x02|}\x03\x02\x02\x02}\x81\x07\n\x02\x02~\x80\x07\x0E\x02\x02" + + "\x7F~\x03\x02\x02\x02\x80\x83\x03\x02\x02\x02\x81\x7F\x03\x02\x02\x02" + + "\x81\x82\x03\x02\x02\x02\x82\x84\x03\x02\x02\x02\x83\x81\x03\x02\x02\x02" + + "\x84\x88\x07\x04\x02\x02\x85\x87\x07\x0E\x02\x02\x86\x85\x03\x02\x02\x02" + + "\x87\x8A\x03\x02\x02\x02\x88\x86\x03\x02\x02\x02\x88\x89\x03\x02\x02\x02" + + "\x89\x8B\x03\x02\x02\x02\x8A\x88\x03\x02\x02\x02\x8B\x8F\x05\x0E\b\x02" + + "\x8C\x8E\x07\x0E\x02\x02\x8D\x8C\x03\x02\x02\x02\x8E\x91\x03\x02\x02\x02" + + "\x8F\x8D\x03\x02\x02\x02\x8F\x90\x03\x02\x02\x02\x90\x92\x03\x02\x02\x02" + + "\x91\x8F\x03\x02\x02\x02\x92\x93\x07\x05\x02\x02\x93\r\x03\x02\x02\x02" + + "\x94\x96\x07\v\x02\x02\x95\x97\x07\x0E\x02\x02\x96\x95\x03\x02\x02\x02" + + "\x97\x98\x03\x02\x02\x02\x98\x96\x03\x02\x02\x02\x98\x99\x03\x02\x02\x02" + + "\x99\x9A\x03\x02\x02\x02\x9A\x9B\x05\x10\t\x02\x9B\x0F\x03\x02\x02\x02" + + "\x9C\x9E\v\x02\x02\x02\x9D\x9C\x03\x02\x02\x02\x9E\xA1\x03\x02\x02\x02" + + "\x9F\xA0\x03\x02\x02\x02\x9F\x9D\x03\x02\x02\x02\xA0\x11\x03\x02\x02\x02" + + "\xA1\x9F\x03\x02\x02\x02\x18\x15\x1B!\'.5(visitor: SqlSealLangVisitor): Result { + if (visitor.visitParse) { + return visitor.visitParse(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class StatementContext extends ParserRuleContext { + public tableStatement(): TableStatementContext | undefined { + return this.tryGetRuleContext(0, TableStatementContext); + } + public queryStatement(): QueryStatementContext | undefined { + return this.tryGetRuleContext(0, QueryStatementContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return SqlSealLangParser.RULE_statement; } + // @Override + public enterRule(listener: SqlSealLangListener): void { + if (listener.enterStatement) { + listener.enterStatement(this); + } + } + // @Override + public exitRule(listener: SqlSealLangListener): void { + if (listener.exitStatement) { + listener.exitStatement(this); + } + } + // @Override + public accept(visitor: SqlSealLangVisitor): Result { + if (visitor.visitStatement) { + return visitor.visitStatement(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class TableStatementContext extends ParserRuleContext { + public TABLE(): TerminalNode { return this.getToken(SqlSealLangParser.TABLE, 0); } + public ID(): TerminalNode { return this.getToken(SqlSealLangParser.ID, 0); } + public FILE(): TerminalNode { return this.getToken(SqlSealLangParser.FILE, 0); } + public FILE_URL(): TerminalNode { return this.getToken(SqlSealLangParser.FILE_URL, 0); } + public WS(): TerminalNode[]; + public WS(i: number): TerminalNode; + public WS(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(SqlSealLangParser.WS); + } else { + return this.getToken(SqlSealLangParser.WS, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return SqlSealLangParser.RULE_tableStatement; } + // @Override + public enterRule(listener: SqlSealLangListener): void { + if (listener.enterTableStatement) { + listener.enterTableStatement(this); + } + } + // @Override + public exitRule(listener: SqlSealLangListener): void { + if (listener.exitTableStatement) { + listener.exitTableStatement(this); + } + } + // @Override + public accept(visitor: SqlSealLangVisitor): Result { + if (visitor.visitTableStatement) { + return visitor.visitTableStatement(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class QueryStatementContext extends ParserRuleContext { + public selectStatement(): SelectStatementContext { + return this.getRuleContext(0, SelectStatementContext); + } + public withClause(): WithClauseContext | undefined { + return this.tryGetRuleContext(0, WithClauseContext); + } + public WS(): TerminalNode[]; + public WS(i: number): TerminalNode; + public WS(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(SqlSealLangParser.WS); + } else { + return this.getToken(SqlSealLangParser.WS, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return SqlSealLangParser.RULE_queryStatement; } + // @Override + public enterRule(listener: SqlSealLangListener): void { + if (listener.enterQueryStatement) { + listener.enterQueryStatement(this); + } + } + // @Override + public exitRule(listener: SqlSealLangListener): void { + if (listener.exitQueryStatement) { + listener.exitQueryStatement(this); + } + } + // @Override + public accept(visitor: SqlSealLangVisitor): Result { + if (visitor.visitQueryStatement) { + return visitor.visitQueryStatement(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class WithClauseContext extends ParserRuleContext { + public WITH(): TerminalNode { return this.getToken(SqlSealLangParser.WITH, 0); } + public cteDefinition(): CteDefinitionContext[]; + public cteDefinition(i: number): CteDefinitionContext; + public cteDefinition(i?: number): CteDefinitionContext | CteDefinitionContext[] { + if (i === undefined) { + return this.getRuleContexts(CteDefinitionContext); + } else { + return this.getRuleContext(i, CteDefinitionContext); + } + } + public WS(): TerminalNode[]; + public WS(i: number): TerminalNode; + public WS(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(SqlSealLangParser.WS); + } else { + return this.getToken(SqlSealLangParser.WS, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return SqlSealLangParser.RULE_withClause; } + // @Override + public enterRule(listener: SqlSealLangListener): void { + if (listener.enterWithClause) { + listener.enterWithClause(this); + } + } + // @Override + public exitRule(listener: SqlSealLangListener): void { + if (listener.exitWithClause) { + listener.exitWithClause(this); + } + } + // @Override + public accept(visitor: SqlSealLangVisitor): Result { + if (visitor.visitWithClause) { + return visitor.visitWithClause(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class CteDefinitionContext extends ParserRuleContext { + public ID(): TerminalNode { return this.getToken(SqlSealLangParser.ID, 0); } + public AS(): TerminalNode { return this.getToken(SqlSealLangParser.AS, 0); } + public selectStatement(): SelectStatementContext { + return this.getRuleContext(0, SelectStatementContext); + } + public WS(): TerminalNode[]; + public WS(i: number): TerminalNode; + public WS(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(SqlSealLangParser.WS); + } else { + return this.getToken(SqlSealLangParser.WS, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return SqlSealLangParser.RULE_cteDefinition; } + // @Override + public enterRule(listener: SqlSealLangListener): void { + if (listener.enterCteDefinition) { + listener.enterCteDefinition(this); + } + } + // @Override + public exitRule(listener: SqlSealLangListener): void { + if (listener.exitCteDefinition) { + listener.exitCteDefinition(this); + } + } + // @Override + public accept(visitor: SqlSealLangVisitor): Result { + if (visitor.visitCteDefinition) { + return visitor.visitCteDefinition(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class SelectStatementContext extends ParserRuleContext { + public SELECT(): TerminalNode { return this.getToken(SqlSealLangParser.SELECT, 0); } + public selectBody(): SelectBodyContext { + return this.getRuleContext(0, SelectBodyContext); + } + public WS(): TerminalNode[]; + public WS(i: number): TerminalNode; + public WS(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(SqlSealLangParser.WS); + } else { + return this.getToken(SqlSealLangParser.WS, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return SqlSealLangParser.RULE_selectStatement; } + // @Override + public enterRule(listener: SqlSealLangListener): void { + if (listener.enterSelectStatement) { + listener.enterSelectStatement(this); + } + } + // @Override + public exitRule(listener: SqlSealLangListener): void { + if (listener.exitSelectStatement) { + listener.exitSelectStatement(this); + } + } + // @Override + public accept(visitor: SqlSealLangVisitor): Result { + if (visitor.visitSelectStatement) { + return visitor.visitSelectStatement(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class SelectBodyContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return SqlSealLangParser.RULE_selectBody; } + // @Override + public enterRule(listener: SqlSealLangListener): void { + if (listener.enterSelectBody) { + listener.enterSelectBody(this); + } + } + // @Override + public exitRule(listener: SqlSealLangListener): void { + if (listener.exitSelectBody) { + listener.exitSelectBody(this); + } + } + // @Override + public accept(visitor: SqlSealLangVisitor): Result { + if (visitor.visitSelectBody) { + return visitor.visitSelectBody(this); + } else { + return visitor.visitChildren(this); + } + } +} + + diff --git a/src/grammar/SqlSealLangVisitor.ts b/src/grammar/SqlSealLangVisitor.ts new file mode 100644 index 0000000..a96c2b3 --- /dev/null +++ b/src/grammar/SqlSealLangVisitor.ts @@ -0,0 +1,80 @@ +// Generated from SqlSealLang.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor"; + +import { ParseContext } from "./SqlSealLangParser"; +import { StatementContext } from "./SqlSealLangParser"; +import { TableStatementContext } from "./SqlSealLangParser"; +import { QueryStatementContext } from "./SqlSealLangParser"; +import { WithClauseContext } from "./SqlSealLangParser"; +import { CteDefinitionContext } from "./SqlSealLangParser"; +import { SelectStatementContext } from "./SqlSealLangParser"; +import { SelectBodyContext } from "./SqlSealLangParser"; + + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by `SqlSealLangParser`. + * + * @param The return type of the visit operation. Use `void` for + * operations with no return type. + */ +export interface SqlSealLangVisitor extends ParseTreeVisitor { + /** + * Visit a parse tree produced by `SqlSealLangParser.parse`. + * @param ctx the parse tree + * @return the visitor result + */ + visitParse?: (ctx: ParseContext) => Result; + + /** + * Visit a parse tree produced by `SqlSealLangParser.statement`. + * @param ctx the parse tree + * @return the visitor result + */ + visitStatement?: (ctx: StatementContext) => Result; + + /** + * Visit a parse tree produced by `SqlSealLangParser.tableStatement`. + * @param ctx the parse tree + * @return the visitor result + */ + visitTableStatement?: (ctx: TableStatementContext) => Result; + + /** + * Visit a parse tree produced by `SqlSealLangParser.queryStatement`. + * @param ctx the parse tree + * @return the visitor result + */ + visitQueryStatement?: (ctx: QueryStatementContext) => Result; + + /** + * Visit a parse tree produced by `SqlSealLangParser.withClause`. + * @param ctx the parse tree + * @return the visitor result + */ + visitWithClause?: (ctx: WithClauseContext) => Result; + + /** + * Visit a parse tree produced by `SqlSealLangParser.cteDefinition`. + * @param ctx the parse tree + * @return the visitor result + */ + visitCteDefinition?: (ctx: CteDefinitionContext) => Result; + + /** + * Visit a parse tree produced by `SqlSealLangParser.selectStatement`. + * @param ctx the parse tree + * @return the visitor result + */ + visitSelectStatement?: (ctx: SelectStatementContext) => Result; + + /** + * Visit a parse tree produced by `SqlSealLangParser.selectBody`. + * @param ctx the parse tree + * @return the visitor result + */ + visitSelectBody?: (ctx: SelectBodyContext) => Result; +} + diff --git a/src/grammar/parser.test.ts b/src/grammar/parser.test.ts new file mode 100644 index 0000000..397840a --- /dev/null +++ b/src/grammar/parser.test.ts @@ -0,0 +1,12 @@ +import { describe, it, expect } from '@jest/globals' +import { parseLanguage } from './parser' + + +describe('Parser', () => { + it('should properly parse select only', () => { + expect(parseLanguage('SELECT * FROM files')).toEqual({ + tables: [], + queryPart: 'SELECT * FROM files' + }) + }) +}) \ No newline at end of file diff --git a/src/grammar/parser.ts b/src/grammar/parser.ts new file mode 100644 index 0000000..bf13c70 --- /dev/null +++ b/src/grammar/parser.ts @@ -0,0 +1,108 @@ +import { ANTLRErrorListener, CharStreams, CommonTokenStream, RecognitionException, Recognizer } from 'antlr4ts'; +import { SqlSealLangLexer } from './SqlSealLangLexer'; +import { QueryStatementContext, TableStatementContext, SqlSealLangParser, ParseContext, StatementContext, WithClauseContext, SelectStatementContext } from './SqlSealLangParser'; +import { SqlSealLangVisitor } from './SqlSealLangVisitor'; +import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor'; + +export interface TableStatement { + name: string; + file: string; + url: string; + originalStatement: string; +} + +export interface ParsedLanguage { + tables: TableStatement[]; + queryPart: string | null; +} + + +class ErrorListener implements ANTLRErrorListener { + private errors: string[] = []; + + syntaxError(recognizer: Recognizer, offendingSymbol: any, line: number, charPositionInLine: number, msg: string, e: RecognitionException | undefined): void { + this.errors.push(`Line ${line}:${charPositionInLine} ${msg}`); + } + + hasErrors(): boolean { + return this.errors.length > 0; + } + + getErrors(): string { + return this.errors.join('\n'); + } +} + +export class SqlSealLangVisitorImpl extends AbstractParseTreeVisitor implements SqlSealLangVisitor { + private tables: TableStatement[] = []; + private queryPart: string | null = null; + + defaultResult(): ParsedLanguage { + return { tables: this.tables, queryPart: this.queryPart }; + } + + visitParse(ctx: ParseContext): ParsedLanguage { + ctx.statement().forEach(stmt => this.visit(stmt)); + return this.defaultResult(); + } + + visitStatement(ctx: StatementContext): ParsedLanguage { + if (ctx.tableStatement()) { + this.visitTableStatement(ctx.tableStatement()!); + } else if (ctx.queryStatement()) { + this.visitQueryStatement(ctx.queryStatement()!); + } + return this.defaultResult(); + } + + visitTableStatement(ctx: TableStatementContext): ParsedLanguage { + const tableName = ctx.ID().text; + const fileUrl = ctx.FILE_URL().text; + + // Reconstruct the original table statement with whitespace + const tableStmt = ctx.children!.map(child => child.text).join(''); + + this.tables.push({ + name: tableName, + file: 'file', + url: fileUrl, + originalStatement: tableStmt + }); + return this.defaultResult(); + } + + visitQueryStatement(ctx: QueryStatementContext): ParsedLanguage { + // Reconstruct the original query statement with whitespace and all characters + this.queryPart = ctx.children!.map(child => child.text).join(''); + return this.defaultResult(); + } + + visitWithClause(ctx: WithClauseContext): ParsedLanguage { + // The WITH clause is already included in the queryPart, so we don't need to do anything here + return this.defaultResult(); + } + + visitSelectStatement(ctx: SelectStatementContext): ParsedLanguage { + // The SELECT statement is already included in the queryPart, so we don't need to do anything here + return this.defaultResult(); + } +} + +export function parseLanguage(input: string): ParsedLanguage { + const chars = CharStreams.fromString(input); + const lexer = new SqlSealLangLexer(chars); + const tokens = new CommonTokenStream(lexer); + const parser = new SqlSealLangParser(tokens); + + const errorListener = new ErrorListener(); + parser.removeErrorListeners(); + parser.addErrorListener(errorListener); + + const tree = parser.parse(); + + if (errorListener.hasErrors()) { + throw errorListener.getErrors() + } + const visitor = new SqlSealLangVisitorImpl(); + return visitor.visit(tree); +} \ No newline at end of file diff --git a/src/models/sync.ts b/src/models/sync.ts new file mode 100644 index 0000000..c449fff --- /dev/null +++ b/src/models/sync.ts @@ -0,0 +1,64 @@ +import { SqlSealDatabase } from "src/database"; + +export interface SyncEntry { + filename: string; + url: string; + name: string; + syncedAt: string; + metadata: any; +} + + +export class SyncModel { + tableName = 'sqlseal_sync' + constructor(private db: SqlSealDatabase) { + this.db.connect().then(() => + this.createTableIfNotExists() + ) + } + + createTableIfNotExists() { + this.db.createTable(this.tableName, { + 'filename': 'TEXT', + 'url': 'TEXT', + 'name': 'TEXT', + 'syncedAt': 'TEXT', + 'metadata': 'JSON' + }) + } + + registerSync(filename: string, url: string, tableName: string) { + this.db.insertData(this.tableName, [{ + filename, + url, + name: tableName, + syncedAt: Date.now(), + metadata: { } + }]) + } + + getSync(filename: string, tableName: string) { + const data = this.db.db.prepare(` + SELECT * + FROM sqlseal_sync + WHERE filename = @filename + AND name = @name + `).all({ + filename: filename, + name: tableName, + }) + + if (data) { + return data[0] as SyncEntry + } + return null + } + + removeSync(filename: string, tableName: string) { + this.db.db.prepare('DELETE FROM sqlseal_sync WHERE filename = :filename AND name = :name').run({ + filename: filename, + name: tableName + }) + } + +} \ No newline at end of file diff --git a/src/sqlReparseTables.ts b/src/sqlReparseTables.ts index b604f9d..3d70a79 100644 --- a/src/sqlReparseTables.ts +++ b/src/sqlReparseTables.ts @@ -1,6 +1,5 @@ -import { BaseFrom, Parser, Select } from "node-sql-parser" +import { BaseFrom, From, Parser, Select, With } from "node-sql-parser" import { generatePrefix } from "./hash" -import { table } from "console" const isGlobal = (table: string, globalTables: string[]) => { return globalTables.map(t => @@ -9,7 +8,7 @@ const isGlobal = (table: string, globalTables: string[]) => { } const isBaseFrom = (from: From): from is BaseFrom => { - return !!from['table'] + return Object.keys(from).includes('table') } export const prefixedIfNotGlobal = (tableName: string, globalTables: string[], prefix: string) => { @@ -20,7 +19,27 @@ export const prefixedIfNotGlobal = (tableName: string, globalTables: string[], p return generatePrefix(prefix, tableName) } -const updateSelect = (selectAst: Select, globalTables: string[], prefix: string) => { +const updateSelect = (selectAst: Select, globalTables: string[], prefix: string): { selectAst: Select, tables: string[] } => { + + let cteGlobals: string[] = [] + let withs = selectAst.with + let extraTables: string[] = [] + if (selectAst.with) { + cteGlobals = selectAst.with.map(w => w.name.value) + const updatedWiths = selectAst.with.map(w => { + const { selectAst, tables } = updateSelect(w.stmt.ast, [...cteGlobals, ...globalTables], prefix) + extraTables.push(...tables) + return { + ...w, + stmt: { + ...w.stmt, + ast: selectAst + } + } satisfies With + }) + withs = updatedWiths + } + if (!selectAst.from) { return { selectAst: selectAst, tables: [] } } @@ -29,11 +48,11 @@ const updateSelect = (selectAst: Select, globalTables: string[], prefix: string) const updatedFrom = selectAst.from.map(from => { if(isBaseFrom(from)) { - const t = prefixedIfNotGlobal(from.table, globalTables, prefix) + const t = prefixedIfNotGlobal(from.table, [...cteGlobals, ...globalTables], prefix) tables.push(t) return { ...from, - table: t + table: t, } } return from @@ -42,9 +61,10 @@ const updateSelect = (selectAst: Select, globalTables: string[], prefix: string) return { selectAst: { ...selectAst, - from: updatedFrom + from: updatedFrom, + with: withs }, - tables + tables: [...tables, ...extraTables] } } diff --git a/src/utils.ts b/src/utils.ts index 57e3797..cbe6a32 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,8 @@ import { get } from "https" -import JSON5 from 'json5' +import { parse } from 'json5' import * as fs from 'fs' -import { isNumeric } from "./database"; +import { camelCase } from "lodash"; export const fetchBlobData = async (url: string, filePath: string) => { return new Promise((resolve, reject) => { @@ -90,10 +90,10 @@ export const predictJson = (data: Array>) => { [k]: d[k] } } - const v = d[k].trim() + const v = (d[k] as string).trim() if ((v.at(0) == '[' && v.at(-1) === ']') || (v.at(0) === '{' && v.at(-1) === '}')) { try { - const d = JSON5.parse(v) + const d = parse(v) return { ...o, [k]: d @@ -106,4 +106,43 @@ export const predictJson = (data: Array>) => { [k]: d[k] } }, {})) +} + + +export function isNumeric(str: string) { + if (typeof str != "string") return false // we only process strings! + return !isNaN(str as any) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)... + !isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail +} + +export function dataToCamelCase(data: Array>) { + return data.map(d => Object.keys(d).reduce((acc, k) => ({ + ...acc, + [camelCase(k)]: d[k] + }), {})) +} + + +export const toTypeStatements = (header: Array, data: Array>) => { + let d: Array> = data + const types: Record> = {} + header.forEach(key => { + const type = predictType(key, data) + if (type === 'REAL' || type === 'INTEGER') { + // converting all data here to text + d = d.map(record => ({ + ...record, + [key]: type === 'REAL' + ? parseFloat(record[key] as string) + : parseInt(record[key] as string) + })) + } + + types[key] = type + }) + + return { + data: d, + types + } } \ No newline at end of file diff --git a/src/utils.unit.test.ts b/src/utils.unit.test.ts index 074a0c0..b70b363 100644 --- a/src/utils.unit.test.ts +++ b/src/utils.unit.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from '@jest/globals' -import { predictType } from './utils' +import { predictJson, predictType } from './utils' describe('Utils', () => { @@ -10,4 +10,10 @@ describe('Utils', () => { it('should properly predict JSON type', () => { expect(predictType('xxx', [{ 'xxx': { a: 'b' } }])).toEqual('JSON') }) + + it('should properly predict json by parsing it', () => { + expect(predictJson([{ 'xxx': '["a", "b"]'}])).toEqual([{ + xxx: ['a', 'b'] + }]) + }) }) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 1ec8639..a9f6fc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1035,17 +1035,12 @@ resolved "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz" integrity sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA== -"@types/node@*", "@types/node@^16.11.6": - version "16.18.96" - resolved "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz" - integrity sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ== - -"@types/node@^18.0.0 || >=20.0.0": - version "20.12.7" - resolved "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz" - integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg== +"@types/node@*", "@types/node@^18.0.0 || >=20.0.0", "@types/node@^22.5.0": + version "22.5.2" + resolved "https://registry.npmjs.org/@types/node/-/node-22.5.2.tgz" + integrity sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg== dependencies: - undici-types "~5.26.4" + undici-types "~6.19.2" "@types/node@^20.9.0": version "20.14.15" @@ -1433,6 +1428,21 @@ ansi-styles@^5.0.0: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +antlr4@^4.13.2: + version "4.13.2" + resolved "https://registry.npmjs.org/antlr4/-/antlr4-4.13.2.tgz" + integrity sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg== + +antlr4ts-cli@0.5.0-alpha.4: + version "0.5.0-alpha.4" + resolved "https://registry.npmjs.org/antlr4ts-cli/-/antlr4ts-cli-0.5.0-alpha.4.tgz" + integrity sha512-lVPVBTA2CVHRYILSKilL6Jd4hAumhSZZWA7UbQNQrmaSSj7dPmmYaN4bOmZG79cOy0lS00i4LY68JZZjZMWVrw== + +antlr4ts@0.5.0-alpha.4: + version "0.5.0-alpha.4" + resolved "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz" + integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== + anymatch@^3.0.3: version "3.1.3" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" @@ -4693,6 +4703,11 @@ undici-types@~5.26.4: resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + unique-filename@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz"