From bca216cd7444f49e91e16311e952913cb790db70 Mon Sep 17 00:00:00 2001 From: Remi Cattiau Date: Wed, 14 Feb 2024 17:56:24 -0800 Subject: [PATCH 1/7] feat: add cloudevent base module --- .github/workflows/release-please.yml | 1 + .../src/services/asyncjobservice.spec.ts | 9 +- packages/cloudevents/bunfig.toml | 3 + packages/cloudevents/jest.config.cjs | 18 + packages/cloudevents/package.json | 59 + .../src/models/filters/abstract.ts | 35 + .../src/models/filters/index.spec.ts | 209 ++ .../cloudevents/src/models/filters/index.ts | 42 + .../src/models/filters/interfaces.ts | 0 .../cloudevents/src/models/filters/logical.ts | 80 + .../src/models/filters/sql.spec.ts | 444 ++++ .../cloudevents/src/models/filters/sql.ts | 888 ++++++++ .../src/models/filters/sql/CESQLLexer.g4} | 28 +- .../src/models/filters/sql/CESQLParser.g4 | 62 + .../src/models/filters/sql/CESQLParser.interp | 87 + .../src/models/filters/sql/CESQLParser.tokens | 59 + .../filters/sql/CESQLParserLexer.interp | 122 ++ .../filters/sql/CESQLParserLexer.tokens | 59 + .../models/filters/sql/CESQLParserLexer.ts | 231 +++ .../models/filters/sql/CESQLParserListener.ts | 357 ++++ .../models/filters/sql/CESQLParserParser.ts | 1677 +++++++++++++++ .../models/filters/sql/CESQLParserVisitor.ts | 240 +++ .../cloudevents/src/models/filters/string.ts | 145 ++ .../src/models/subscription.spec.ts | 48 + .../cloudevents/src/models/subscription.ts | 235 +++ packages/cloudevents/tsconfig.json | 26 + packages/cloudevents/tsconfig.test.json | 16 + packages/core/package.json | 1 + packages/core/src/core.spec.ts | 4 +- packages/core/src/core.ts | 60 +- packages/core/src/index.ts | 2 +- packages/core/src/models/aclmodel.spec.ts | 20 +- packages/core/src/models/aclmodel.ts | 39 +- packages/core/src/models/coremodel.spec.ts | 9 +- packages/core/src/models/coremodel.ts | 312 +-- packages/core/src/models/ownermodel.spec.ts | 20 +- packages/core/src/models/ownermodel.ts | 8 +- packages/core/src/models/relations.spec.ts | 4 +- packages/core/src/models/relations.ts | 4 +- packages/core/src/models/rolemodel.ts | 3 + packages/core/src/models/user.ts | 27 +- packages/core/src/services/binary.spec.ts | 1 - packages/core/src/services/binary.ts | 10 +- packages/core/src/services/cloudbinary.ts | 4 +- packages/core/src/services/cryptoservice.ts | 61 +- packages/core/src/services/domainservice.ts | 362 +++- .../src/services/invitationservice.spec.ts | 4 +- .../core/src/services/invitationservice.ts | 2 +- packages/core/src/services/oauth.ts | 10 +- packages/core/src/services/service.ts | 32 +- packages/core/src/stores/aliasstore.spec.ts | 83 - packages/core/src/stores/aliasstore.ts | 263 --- packages/core/src/stores/file.ts | 2 +- packages/core/src/stores/memory.spec.ts | 4 +- packages/core/src/stores/memory.ts | 2 +- packages/core/src/stores/store.spec.ts | 13 +- packages/core/src/stores/store.ts | 799 ++------ .../src/stores/webdaql/WebdaQLLexer.interp | 119 -- .../src/stores/webdaql/WebdaQLLexer.tokens | 57 - .../core/src/stores/webdaql/WebdaQLLexer.ts | 319 --- .../core/src/stores/webdaql/WebdaQLParser.g4 | 58 - .../src/stores/webdaql/WebdaQLParser.interp | 88 - .../src/stores/webdaql/WebdaQLParser.tokens | 57 - .../stores/webdaql/WebdaQLParserLexer.interp | 119 -- .../stores/webdaql/WebdaQLParserLexer.tokens | 57 - .../src/stores/webdaql/WebdaQLParserLexer.ts | 321 --- .../stores/webdaql/WebdaQLParserListener.ts | 352 ---- .../src/stores/webdaql/WebdaQLParserParser.ts | 1815 ----------------- .../stores/webdaql/WebdaQLParserVisitor.ts | 237 --- .../core/src/stores/webdaql/query.spec.ts | 206 -- packages/core/src/stores/webdaql/query.ts | 820 -------- packages/core/src/utils/context.spec.ts | 14 +- packages/core/src/utils/context.ts | 252 ++- packages/core/src/utils/cookie.ts | 5 +- packages/core/src/utils/session.ts | 15 +- packages/core/tsconfig.json | 2 +- .../elasticsearch/src/elasticsearchservice.ts | 8 +- packages/gcp/webda.module.json | 26 +- packages/runtime/src/utils/iterators.ts | 30 +- packages/shell/src/code/compiler.ts | 29 +- sample-app/webda.module.json | 4 +- 81 files changed, 6151 insertions(+), 6204 deletions(-) create mode 100644 packages/cloudevents/bunfig.toml create mode 100644 packages/cloudevents/jest.config.cjs create mode 100644 packages/cloudevents/package.json create mode 100644 packages/cloudevents/src/models/filters/abstract.ts create mode 100644 packages/cloudevents/src/models/filters/index.spec.ts create mode 100644 packages/cloudevents/src/models/filters/index.ts create mode 100644 packages/cloudevents/src/models/filters/interfaces.ts create mode 100644 packages/cloudevents/src/models/filters/logical.ts create mode 100644 packages/cloudevents/src/models/filters/sql.spec.ts create mode 100644 packages/cloudevents/src/models/filters/sql.ts rename packages/{core/src/stores/webdaql/WebdaQLLexer.g4 => cloudevents/src/models/filters/sql/CESQLLexer.g4} (82%) create mode 100644 packages/cloudevents/src/models/filters/sql/CESQLParser.g4 create mode 100644 packages/cloudevents/src/models/filters/sql/CESQLParser.interp create mode 100644 packages/cloudevents/src/models/filters/sql/CESQLParser.tokens create mode 100644 packages/cloudevents/src/models/filters/sql/CESQLParserLexer.interp create mode 100644 packages/cloudevents/src/models/filters/sql/CESQLParserLexer.tokens create mode 100644 packages/cloudevents/src/models/filters/sql/CESQLParserLexer.ts create mode 100644 packages/cloudevents/src/models/filters/sql/CESQLParserListener.ts create mode 100644 packages/cloudevents/src/models/filters/sql/CESQLParserParser.ts create mode 100644 packages/cloudevents/src/models/filters/sql/CESQLParserVisitor.ts create mode 100644 packages/cloudevents/src/models/filters/string.ts create mode 100644 packages/cloudevents/src/models/subscription.spec.ts create mode 100644 packages/cloudevents/src/models/subscription.ts create mode 100644 packages/cloudevents/tsconfig.json create mode 100644 packages/cloudevents/tsconfig.test.json delete mode 100644 packages/core/src/stores/aliasstore.spec.ts delete mode 100644 packages/core/src/stores/aliasstore.ts delete mode 100644 packages/core/src/stores/webdaql/WebdaQLLexer.interp delete mode 100644 packages/core/src/stores/webdaql/WebdaQLLexer.tokens delete mode 100644 packages/core/src/stores/webdaql/WebdaQLLexer.ts delete mode 100644 packages/core/src/stores/webdaql/WebdaQLParser.g4 delete mode 100644 packages/core/src/stores/webdaql/WebdaQLParser.interp delete mode 100644 packages/core/src/stores/webdaql/WebdaQLParser.tokens delete mode 100644 packages/core/src/stores/webdaql/WebdaQLParserLexer.interp delete mode 100644 packages/core/src/stores/webdaql/WebdaQLParserLexer.tokens delete mode 100644 packages/core/src/stores/webdaql/WebdaQLParserLexer.ts delete mode 100644 packages/core/src/stores/webdaql/WebdaQLParserListener.ts delete mode 100644 packages/core/src/stores/webdaql/WebdaQLParserParser.ts delete mode 100644 packages/core/src/stores/webdaql/WebdaQLParserVisitor.ts delete mode 100644 packages/core/src/stores/webdaql/query.spec.ts delete mode 100644 packages/core/src/stores/webdaql/query.ts diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 5ab0c93b9..68e0e21fa 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -2,6 +2,7 @@ on: push: branches: - main + - release-* workflow_dispatch: permissions: diff --git a/packages/async/src/services/asyncjobservice.spec.ts b/packages/async/src/services/asyncjobservice.spec.ts index 36dfc317a..43008b7f4 100644 --- a/packages/async/src/services/asyncjobservice.spec.ts +++ b/packages/async/src/services/asyncjobservice.spec.ts @@ -43,8 +43,13 @@ class AsyncJobServiceTest extends WebdaTest { }; await service.worker().cancel(); // @ts-ignore - service.runners = []; - assert.rejects(() => service.worker(), /AsyncJobService.worker requires runners/); + service.queue = { + // @ts-ignore + consume: callback => new CancelablePromise() + }; + await service.worker().cancel(); + service["runners"] = []; + await assert.rejects(() => service.worker(), /AsyncJobService.worker requires runners/); } /** * Return a good initialized service diff --git a/packages/cloudevents/bunfig.toml b/packages/cloudevents/bunfig.toml new file mode 100644 index 000000000..1d296cc71 --- /dev/null +++ b/packages/cloudevents/bunfig.toml @@ -0,0 +1,3 @@ +[test] +coverageSkipTestFiles = true +coverage = true \ No newline at end of file diff --git a/packages/cloudevents/jest.config.cjs b/packages/cloudevents/jest.config.cjs new file mode 100644 index 000000000..e5c34667b --- /dev/null +++ b/packages/cloudevents/jest.config.cjs @@ -0,0 +1,18 @@ +module.exports = { + roots: ["src"], + preset: "ts-jest/presets/default-esm", + testMatch: ["**/?(*.)+(spec|test).+(ts|tsx|js)"], + transform: { + "^.+\\.(ts|tsx)$": [ + "ts-jest", + { + tsconfig: "tsconfig.test.json", + useESM: true + } + ] + }, + collectCoverage: true, + collectCoverageFrom: ["src/**/*.ts", "!src/models/filters/sql/CESQL*.ts"], + coverageReporters: ["json", "html", "text"], + extensionsToTreatAsEsm: [".ts"] +}; diff --git a/packages/cloudevents/package.json b/packages/cloudevents/package.json new file mode 100644 index 000000000..3a565b230 --- /dev/null +++ b/packages/cloudevents/package.json @@ -0,0 +1,59 @@ +{ + "name": "@webda/cloudevents", + "version": "3.0.0", + "description": "Discovery and Subscriptions for CloudEvents", + "keywords": [ + "cloudevents", + "webda" + ], + "author": "Remi Cattiau ", + "repository": "git://github.com/loopingz/webda.io.git", + "main": "lib/index.js", + "typings": "lib/index.d.ts", + "scripts": { + "build": "tsc-esm", + "build:module": "webda build", + "build:watch": "webda build --watch", + "grammar": "antlr4ts -visitor src/stores/webdaql/WebdaQLLexer.g4 src/stores/webdaql/WebdaQLParser.g4 && yarn run lint:fix", + "pretest": "npm run build", + "lint": "prettier --check src/**/*", + "lint:fix": "prettier --write src/**/*", + "test:coverage": "NODE_OPTIONS='--no-warnings --experimental-vm-modules' jest", + "test": "bun test --coverage" + }, + "dependencies": { + "cloudevents": "^8.0.0" + }, + "devDependencies": { + "@types/node": "18.11.13", + "jest": "^29.7.0", + "ts-jest": "^29.1.2" + }, + "files": [ + "lib" + ], + "c8": { + "report-dir": "./reports", + "reporter": [ + "html", + "lcov", + "json", + "text" + ], + "exclude": [ + "src/test.ts", + "**/*.spec.ts", + "test/**/*", + "*/stores/webdaql/WebdaQL*" + ], + "excludeNodeModules": true + }, + "homepage": "https://webda.io", + "publishConfig": { + "access": "public" + }, + "type": "module", + "engines": { + "node": ">=18.0.0" + } +} diff --git a/packages/cloudevents/src/models/filters/abstract.ts b/packages/cloudevents/src/models/filters/abstract.ts new file mode 100644 index 000000000..b86d6e6b4 --- /dev/null +++ b/packages/cloudevents/src/models/filters/abstract.ts @@ -0,0 +1,35 @@ +import { CloudEvent } from "cloudevents"; + +/** + * Representation of subscription filtering + */ +export interface Filter {} + +/** + * Implementation of a defined filter + */ +export abstract class FilterImplementation { + /** + * Definition from the spec + */ + definition: T; + constructor(definition: T) { + this.definition = definition; + } + + /** + * Return true if it match the filter, false otherwise + * + * @param event to filter + */ + abstract match(event: CloudEvent): boolean; + + /** + * Option to return an optimized version of the filter + * + * For example a LEFT(type, 4) = "com." can be optimized to PREFIX(type, "com.") + */ + optimize(): FilterImplementation { + return this; + } +} diff --git a/packages/cloudevents/src/models/filters/index.spec.ts b/packages/cloudevents/src/models/filters/index.spec.ts new file mode 100644 index 000000000..255f20cbc --- /dev/null +++ b/packages/cloudevents/src/models/filters/index.spec.ts @@ -0,0 +1,209 @@ +import { expect, test } from "@jest/globals"; +import { CloudEvent } from "cloudevents"; +import { FiltersHelper } from "."; + +test("PrefixFilter", () => { + let event = new CloudEvent({ type: "com.test", source: "unit-test", data: {} }); + + expect( + FiltersHelper.get({ + prefix: { + type: "com.test" + } + }).match(event) + ).toBe(true); + + expect( + FiltersHelper.get({ + prefix: { + type: "test" + } + }).match(event) + ).toBe(false); + + expect( + FiltersHelper.get({ + prefix: { + source: "unit-" + } + }).match(event) + ).toBe(true); +}); + +test("SuffixFilter", () => { + let event = new CloudEvent({ type: "com.test", source: "unit-test", data: {} }); + + expect( + FiltersHelper.get({ + suffix: { + type: "com.test" + } + }).match(event) + ).toBe(true); + + expect( + FiltersHelper.get({ + suffix: { + type: "com." + } + }).match(event) + ).toBe(false); + + expect( + FiltersHelper.get({ + suffix: { + source: "-test" + } + }).match(event) + ).toBe(true); +}); + +test("ExactFilter", () => { + let event = new CloudEvent({ type: "com.test", source: "unit-test", data: {} }); + + expect( + FiltersHelper.get({ + exact: { + type: "com.test" + } + }).match(event) + ).toBe(true); + + expect( + FiltersHelper.get({ + exact: { + type: "com." + } + }).match(event) + ).toBe(false); + + expect( + FiltersHelper.get({ + exact: { + source: "-test" + } + }).match(event) + ).toBe(false); +}); + +test("AllFilter", () => { + let event = new CloudEvent({ type: "com.test", source: "unit-test", data: {} }); + + expect( + FiltersHelper.get({ + all: [ + { + suffix: { + type: "com.test" + } + }, + { + exact: { + source: "unit-test" + } + } + ] + }).match(event) + ).toBe(true); + + expect( + FiltersHelper.get({ + all: [ + { + suffix: { + type: "com2.test" + } + }, + { + exact: { + source: "unit-test" + } + } + ] + }).match(event) + ).toBe(false); +}); + +test("AnyFilter", () => { + let event = new CloudEvent({ type: "com.test", source: "unit-test", data: {} }); + + expect( + FiltersHelper.get({ + any: [ + { + suffix: { + type: "com.test" + } + }, + { + exact: { + source: "unit-test" + } + } + ] + }).match(event) + ).toBe(true); + + expect( + FiltersHelper.get({ + any: [ + { + suffix: { + type: "com2.test" + } + }, + { + exact: { + source: "unit-test" + } + } + ] + }).match(event) + ).toBe(true); + + expect( + FiltersHelper.get({ + any: [ + { + suffix: { + type: "com2.test" + } + }, + { + exact: { + source: "unittest" + } + } + ] + }).match(event) + ).toBe(false); +}); + +test("Common errors", () => { + let event = new CloudEvent({ type: "com.test", source: "unit-test", data: {} }); + + expect(() => + FiltersHelper.get({ + unknown: { + type: "com.test" + } + }).match(event) + ).toThrow(/Unsupported filter type 'unknown'/); + + expect(() => + FiltersHelper.get({ + prefix: { + type: "com.test", + source: "unit-test" + } + }).match(event) + ).toThrow(/Filter only accept one property filtering/); + + expect( + FiltersHelper.get({ + suffix: { + type2: "com2.test" + } + }).match(event) + ).toBe(false); +}); diff --git a/packages/cloudevents/src/models/filters/index.ts b/packages/cloudevents/src/models/filters/index.ts new file mode 100644 index 000000000..140679670 --- /dev/null +++ b/packages/cloudevents/src/models/filters/index.ts @@ -0,0 +1,42 @@ +//import RegexEscape from "regex-escape"; +import { Filter, FilterImplementation } from "./abstract"; +import { AllFilterImplementation, AnyFilterImplementation } from "./logical"; +import { SqlFilterImplementation } from "./sql"; +import { ExactFilterImplementation, PrefixFilterImplementation, SuffixFilterImplementation } from "./string"; + +export * from "./abstract"; + +interface FilterImplementationConstructor { + new (definition: any): FilterImplementation; +} + +/** + * Filter Implementation registry + */ +const FilterImplementations: { [key: string]: FilterImplementationConstructor } = { + exact: ExactFilterImplementation, + prefix: PrefixFilterImplementation, + suffix: SuffixFilterImplementation, + all: AllFilterImplementation, + any: AnyFilterImplementation, + sql: SqlFilterImplementation +}; + +/** + * Retrieve an FilterImplementation object based on the + * definition + */ +export class FiltersHelper { + /** + * Get the filter implementation + * @param filter + * @returns + */ + static get(filter: Filter): FilterImplementation { + let type = Object.keys(filter).pop(); + if (type === undefined || !FilterImplementations[type]) { + throw new Error(`Unsupported filter type '${type}'`); + } + return new FilterImplementations[type](filter).optimize(); + } +} diff --git a/packages/cloudevents/src/models/filters/interfaces.ts b/packages/cloudevents/src/models/filters/interfaces.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/cloudevents/src/models/filters/logical.ts b/packages/cloudevents/src/models/filters/logical.ts new file mode 100644 index 000000000..7ed23b8f6 --- /dev/null +++ b/packages/cloudevents/src/models/filters/logical.ts @@ -0,0 +1,80 @@ +import { CloudEvent } from "cloudevents"; +import { FiltersHelper } from "."; +import { Filter, FilterImplementation } from "./abstract"; + +/** + * Use of this MUST include one nested filter expression, where the result of this + * filter expression is the inverse of the result of the nested expression. In other words, + * if the nested expression evaluated to true, then the not filter expression's result is false. + */ +export interface NotFilter { + not: Filter; +} + +/** + * Use of this MUST include one nested array of filter expressions, where at least one nested + * filter expressions MUST evaluate to true in order for the any filter expression to be true. + */ +export interface AnyFilter { + any: Filter[]; +} + +/** + * Use of this MUST include a nested array of filter expressions, where all nested filter + * expressions MUST evaluate to true in order for the all filter expression to be true. + * + * Note: there MUST be at least one filter expression in the array. + */ +export interface AllFilter { + all: Filter[]; +} + +/** + * Use of this MUST include a nested array of filter expressions, where all nested filter + * expressions MUST evaluate to true in order for the all filter expression to be true. + * + * Note: there MUST be at least one filter expression in the array. + */ +export class AllFilterImplementation extends FilterImplementation { + filters: FilterImplementation[]; + constructor(definition: AllFilter) { + super(definition); + this.filters = this.definition.all.map(f => FiltersHelper.get(f)); + } + /** + * @override + */ + match(event: CloudEvent): boolean { + for (let filter of this.filters) { + if (!filter.match(event)) { + return false; + } + } + return true; + } +} + +/** + * Use of this MUST include one nested array of filter expressions, where at least one nested + * filter expressions MUST evaluate to true in order for the any filter expression to be true. + * + * Note: there MUST be at least one filter expression in the array. + */ +export class AnyFilterImplementation extends FilterImplementation { + filters: FilterImplementation[]; + constructor(definition: AnyFilter) { + super(definition); + this.filters = definition.any.map(f => FiltersHelper.get(f)); + } + /** + * @override + */ + match(event: CloudEvent): boolean { + for (let filter of this.filters) { + if (filter.match(event)) { + return true; + } + } + return false; + } +} diff --git a/packages/cloudevents/src/models/filters/sql.spec.ts b/packages/cloudevents/src/models/filters/sql.spec.ts new file mode 100644 index 000000000..94a059c40 --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql.spec.ts @@ -0,0 +1,444 @@ +import { expect, test } from "@jest/globals"; +import { CloudEvent } from "cloudevents"; +import { FiltersHelper } from "."; +import { SqlFilterImplementation } from "./sql"; + +const event: CloudEvent = new CloudEvent({ type: "com.test", source: "unit-test", data: {} }); +const event2: CloudEvent = new CloudEvent({ + type: "com.test", + subject: "plop", + source: "unit-test", + custom1: " Plop ", + data: {} +}); + +test("SQLFilter", () => { + expect( + FiltersHelper.get({ + sql: "EXISTS subject" + }).match(event) + ).toBe(false); + + expect( + FiltersHelper.get({ + sql: "EXISTS subject" + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `subject = "plop"` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `subject = "plopi"` + }).match(event2) + ).toBe(false); +}); + +test("SQLFilter Sets", () => { + expect( + FiltersHelper.get({ + sql: `subject IN ("plopi", "plop")` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `subject NOT IN ("plopi", "plop")` + }).match(event2) + ).toBe(false); + + expect( + FiltersHelper.get({ + sql: `NOT subject IN ("plopi", "plop")` + }).match(event2) + ).toBe(false); + + expect( + FiltersHelper.get({ + sql: `NOT subject NOT IN ("plopi", "plop")` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `subject IN ("plopi", "plopa")` + }).match(event2) + ).toBe(false); +}); + +test("SQLFilter Arithmetic", () => { + expect( + FiltersHelper.get({ + sql: "1 + 2 > 1" + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: "1 + 2 > 3" + }).match(event2) + ).toBe(false); + + expect( + FiltersHelper.get({ + sql: "1 + 2 < 1" + }).match(event2) + ).toBe(false); + + expect( + FiltersHelper.get({ + sql: "1 / 2 < 1" + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: "(1 - 1) * 2 < 1" + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: "1 * 2 >= -1" + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: "1 - 2 = -1" + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: "1 - 2 <= -1" + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: "1 - 3 <= -1" + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: "1 - 1 <= -1" + }).match(event2) + ).toBe(false); + + expect( + FiltersHelper.get({ + sql: "10 % 3 = 1" + }).match(event2) + ).toBe(true); +}); + +test("SQLFilter Logic Expression", () => { + expect( + FiltersHelper.get({ + sql: `subject = "plop" AND FALSE` + }).match(event2) + ).toBe(false); + + expect( + FiltersHelper.get({ + sql: `TRUE XOR TRUE` + }).match(event2) + ).toBe(false); + + expect( + FiltersHelper.get({ + sql: `subject = "plop" XOR FALSE` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `subject != "plop" XOR TRUE` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `subject != "plop" OR TRUE` + }).match(event2) + ).toBe(true); +}); + +test("SQLFilter functions", () => { + expect( + FiltersHelper.get({ + sql: `UPPER(subject) = 'PLOP'` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `LENGTH(subject) = 4` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `UPPER(LEFT(subject, 2)) = 'PL'` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `UPPER(RIGHT(subject, 2)) = 'OP'` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `ABS(-1) = 1` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `LOWER(custom1) = ' plop '` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `LOWER(custom1) = ''` + }).match(event) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `SUBSTRING(custom1, 1) = 'Plop '` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `SUBSTRING(TRIM(custom1), 0, 2) = 'Pl'` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `CONCAT("Hello", "World") = 'HelloWorld'` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `CONCAT_WS(" ", "Hello", "World") = 'Hello World'` + }).match(event2) + ).toBe(true); +}); + +test("SQLFilter converters", () => { + expect( + FiltersHelper.get({ + sql: `BOOL('true')` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `TRUE` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `BOOL('True')` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `BOOL('true2')` + }).match(event2) + ).toBe(false); + expect( + FiltersHelper.get({ + sql: `IS_BOOL(BOOL(TRUE))` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `BOOL(TRUE)` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `BOOL(12)` + }).match(event2) + ).toBe(false); + expect( + FiltersHelper.get({ + sql: `IS_INT(12)` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `IS_INT('plop')` + }).match(event2) + ).toBe(false); + expect( + FiltersHelper.get({ + sql: `STRING('plop') = 'plop'` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `STRING(122) = '122'` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `STRING(TRUE) = 'TRUE'` + }).match(event2) + ).toBe(true); + // INT now + expect( + FiltersHelper.get({ + sql: `INT('plop') = 0` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `INT('122') = 122` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `INT(122) = 122` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `INT(TRUE) = 0` + }).match(event2) + ).toBe(true); +}); + +test("SQLFilter Like", () => { + expect( + FiltersHelper.get({ + sql: `subject LIKE "p%p"` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `subject LIKE "pl_p"` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `subject LIKE "p_p"` + }).match(event2) + ).toBe(false); + expect( + FiltersHelper.get({ + sql: `"te{plop}st" LIKE "te{plop}_t"` + }).match(event2) + ).toBe(true); + + expect( + FiltersHelper.get({ + sql: `subject NOT LIKE "p%p"` + }).match(event2) + ).toBe(false); + expect( + FiltersHelper.get({ + sql: `subject NOT LIKE "pl_p"` + }).match(event2) + ).toBe(false); + expect( + FiltersHelper.get({ + sql: `subject NOT LIKE "p_p"` + }).match(event2) + ).toBe(true); + expect( + FiltersHelper.get({ + sql: `"te{plop}st" NOT LIKE "te{plop}_t"` + }).match(event2) + ).toBe(false); +}); + +test("SQLFilter errors", () => { + expect(() => + FiltersHelper.get({ + sql: `PLOP(-1) = 1` + }).match(event) + ).toThrow(/Unknown function: 'PLOP'/); + + // One argument methods + ["TRIM", "LENGTH", "UPPER", "ABS", "LOWER", "BOOL", "INT", "STRING", "IS_INT", "IS_BOOL"].forEach(method => { + expect(() => + FiltersHelper.get({ + sql: `${method}("plop", "test")` + }).match(event) + ).toThrow(/Too many arguments/); + }); + // Two arguments methods + ["LEFT", "RIGHT"].forEach(method => { + expect(() => + FiltersHelper.get({ + sql: `${method}("plop", "test", 3)` + }).match(event) + ).toThrow(/Wrong arguments count/); + }); + + expect(() => + FiltersHelper.get({ + sql: `SUBSTRING("plop", 0, 3, 4)` + }).match(event) + ).toThrow(/Wrong arguments count/); + + expect(() => + FiltersHelper.get({ + sql: `SUBSTRING("plop")` + }).match(event) + ).toThrow(/Wrong arguments count/); +}); + +test("SQLFilter optimization", () => { + expect(FiltersHelper.get({ sql: "RIGHT(topic, 4) = 'plop'" }).definition).toStrictEqual({ + suffix: { + topic: "plop" + } + }); + expect(FiltersHelper.get({ sql: "LEFT(subject, 10) = 'cloudevent'" }).definition).toStrictEqual({ + prefix: { + subject: "cloudevent" + } + }); +}); + +test("SQLFilter COV", () => { + // SQL should be optimized and compute when it can be + expect(new SqlFilterImplementation({ sql: '"TEST" = TRUE OR 1 > 3' }).query.toString()).toBe("FALSE"); + expect(new SqlFilterImplementation({ sql: "1 <= 3" }).query.toString()).toBe("TRUE"); + expect(new SqlFilterImplementation({ sql: 'subject = "TEST" OR topic > 3 OR sub = TRUE' }).query.toString()).toBe( + 'subject = "TEST" OR topic > 3 OR sub = TRUE' + ); + expect(new SqlFilterImplementation({ sql: "subject + 3 = 4 OR topic * 3 > 3" }).query.toString()).toBe( + "subject + 3 = 4 OR topic * 3 > 3" + ); + expect(new SqlFilterImplementation({ sql: 'subject NOT IN ["TEST", "TEST2"]' }).query.toString()).toBe( + 'subject NOT IN ("TEST","TEST2")' + ); + expect(new SqlFilterImplementation({ sql: 'subject IN [topic, "TEST2"]' }).query.toString()).toBe( + 'subject IN (topic,"TEST2")' + ); + expect(new SqlFilterImplementation({ sql: 'UPPER(subject) LIKE "W?BDA"' }).query.toString()).toBe( + 'UPPER(subject) LIKE "W?BDA"' + ); + + expect(new SqlFilterImplementation({ sql: 'NOT (UPPER(subject) LIKE "W?BDA")' }).query.toString()).toBe( + 'NOT UPPER(subject) LIKE "W?BDA"' + ); + expect(new SqlFilterImplementation({ sql: "EXISTS plop" }).query.toString()).toBe("EXISTS plop"); + expect(new SqlFilterImplementation({ sql: "-subject" }).query.toString()).toBe("-subject"); +}); diff --git a/packages/cloudevents/src/models/filters/sql.ts b/packages/cloudevents/src/models/filters/sql.ts new file mode 100644 index 000000000..4261cfc7c --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql.ts @@ -0,0 +1,888 @@ +import { ANTLRInputStream, CommonTokenStream } from "antlr4ts"; +import { AbstractParseTreeVisitor } from "antlr4ts/tree"; +import { CloudEvent } from "cloudevents"; +import { Filter, FilterImplementation } from "./abstract"; +import { CESQLParserLexer } from "./sql/CESQLParserLexer"; +import { + BinaryAdditiveExpressionContext, + BinaryComparisonExpressionContext, + BinaryLogicExpressionContext, + BinaryMultiplicativeExpressionContext, + BooleanLiteralContext, + CESQLParserParser, + CesqlContext, + ExistsExpressionContext, + FunctionInvocationExpressionContext, + FunctionParameterListContext, + IdentifierContext, + InExpressionContext, + IntegerLiteralContext, + LikeExpressionContext, + SetExpressionContext, + StringLiteralContext, + SubExpressionContext, + UnaryLogicExpressionContext, + UnaryNumericExpressionContext +} from "./sql/CESQLParserParser"; +import { CESQLParserVisitor } from "./sql/CESQLParserVisitor"; +import { ExactFilterImplementation, PrefixFilterImplementation, SuffixFilterImplementation } from "./string"; + +/** + * Use of this MUST have a string value, representing a CloudEvents SQL Expression. + * The filter result MUST be true if the result value of the expression, coerced to boolean, + * equals to the TRUE boolean value, otherwise MUST be false if an error occurred while + * evaluating the expression or if the result value, coerced to boolean, equals to the FALSE + * boolean value. + * + * Implementations SHOULD reject subscriptions with invalid CloudEvents SQL expressions. + */ +export interface SqlFilter { + sql: string; +} + +/** + * Function name defined by the specification + */ +type FunctionName = + | "ABS" + | "LENGTH" + | "CONCAT" + | "CONCAT_WS" + | "LOWER" + | "UPPER" + | "LEFT" + | "RIGHT" + | "TRIM" + | "BOOL" + | "INT" + | "SUBSTRING" + | "STRING" + | "IS_BOOL" + | "IS_INT"; + +/** + * Functions implementation + */ +const functions: { [key in FunctionName]: (...args: any[]) => any } = { + ABS: (...args: number[]) => { + if (args.length > 1) { + throw new Error("Too many arguments"); + } + return Math.abs(args[0]); + }, + LENGTH: (...args: string[]) => { + if (args.length > 1) { + throw new Error("Too many arguments"); + } + return (args[0] || "").length; + }, + CONCAT: (...args: string[]) => { + return args.join(""); + }, + CONCAT_WS: (...args: string[]) => { + let merger = args.shift(); + return args.join(merger); + }, + LOWER: (...args: string[]) => { + if (args.length > 1) { + throw new Error("Too many arguments"); + } + return (args[0] || "").toLowerCase(); + }, + UPPER: (...args: string[]) => { + if (args.length > 1) { + throw new Error("Too many arguments"); + } + return (args[0] || "").toUpperCase(); + }, + TRIM: (...args: string[]) => { + if (args.length > 1) { + throw new Error("Too many arguments"); + } + return (args[0] || "").trim(); + }, + LEFT: (...args: any[]) => { + if (args.length !== 2) { + throw new Error("Wrong arguments count"); + } + return (args[0] || "").substr(0, args[1]); + }, + RIGHT: (...args: any[]) => { + if (args.length !== 2) { + throw new Error("Wrong arguments count"); + } + return (args[0] || "").substr(args[0].length - args[1]); + }, + SUBSTRING: (...args: any[]) => { + if (args.length > 3 || args.length < 2) { + throw new Error("Wrong arguments count"); + } + return (args[0] || "").substring(args[1], args[2]); + }, + INT: (...args: any[]) => { + if (args.length > 1) { + throw new Error("Too many arguments"); + } + switch (typeof args[0]) { + case "number": + return args[0]; + case "string": + let res = Number.parseInt(args[0]); + if (!Number.isNaN(res)) { + return res; + } + } + return 0; + }, + BOOL: (...args) => { + if (args.length > 1) { + throw new Error("Too many arguments"); + } + if (typeof args[0] === "string") { + return args[0].toLowerCase() === "true"; + } else if (typeof args[0] === "boolean") { + return args[0]; + } + return false; + }, + STRING: (...args) => { + if (args.length > 1) { + throw new Error("Too many arguments"); + } + if (typeof args[0] === "boolean") { + return args[0].toString().toUpperCase(); + } + return args[0].toString(); + }, + IS_BOOL: (...args) => { + if (args.length > 1) { + throw new Error("Too many arguments"); + } + return typeof args[0] === "boolean"; + }, + IS_INT: (...args) => { + if (args.length > 1) { + throw new Error("Too many arguments"); + } + return typeof args[0] === "number"; + } +}; + +/** + * Represent the query expression or subset + */ +export abstract class Expression { + protected deps: Expression[] = []; + constructor() {} + + /** + * Evaluate the expression for the target object + * @param target to evaluate + */ + abstract eval(target: any): any; + /** + * Return the representation of the expression + */ + abstract toString(): string; + + /** + * Resolve the expression if possible + * + * If the expression only contains static values then it can be resolved + * @returns + */ + resolve(): Expression { + if (this.deps.length && this.deps.every(d => d instanceof ValueExpression)) { + return new ValueExpression(this.eval({})); + } + return this; + } +} + +/** + * Represent an attribute expression + */ +class AttributeExpression extends Expression { + constructor(public readonly attribute: string) { + super(); + } + + /** + * @override + */ + eval(target: any): any { + return target[this.attribute]; + } + + /** + * @override + */ + toString(): string { + return this.attribute; + } +} + +/** + * Represent a function expression + */ +class FunctionExpression extends Expression { + public readonly args: Expression[]; + /** + * + * @param functionName to execute + * @param args to call with the function + */ + constructor( + public readonly functionName: keyof typeof functions, + ...args: Expression[] + ) { + super(); + if (!functions[functionName]) { + throw new Error(`Unknown function: '${functionName}'`); + } + this.deps.push(...args); + this.args = args; + } + + /** + * @override + */ + eval(target: any) { + return functions[this.functionName](...this.args.map(a => a.eval(target))); + } + + /** + * @override + */ + toString(): string { + return `${this.functionName}(${this.args.map(a => a.toString()).join(",")})`; + } +} + +/** + * Represent a unary logic expression + */ +class UnaryLogicExpression extends Expression { + /** + * Invert the result of the child expression + * @param child + */ + constructor(public readonly child: Expression) { + super(); + this.deps.push(child); + } + + /** + * @override + */ + eval(target) { + return !this.deps[0].eval(target); + } + + /** + * @override + */ + toString(): string { + return `NOT ${this.deps[0].toString()}`; + } +} + +/** + * Represent a unary numeric expression + */ +class UnaryNumericExpression extends Expression { + /** + * Negative the result of the child expression + * @param child + */ + constructor(public readonly child: Expression) { + super(); + this.deps.push(child); + } + + /** + * @override + */ + eval(target) { + return -1 * this.child.eval(target); + } + + /** + * @override + */ + toString(): string { + return `-${this.child.toString()}`; + } +} + +/** + * Represent a LIKE expression + */ +class LikeExpression extends Expression { + regexp: RegExp; + /** + * + * @param left part of the expression + * @param right part of the expression the regexp with % and _ + * @param invert if NOT LIKE is used + */ + constructor( + public readonly left: Expression, + public readonly right: string, + public readonly invert: boolean = false + ) { + super(); + this.deps.push(left); + this.regexp = new RegExp( + right + .substring(1, right.length - 1) + .replace(/([^\\])%/g, "$1.*") + .replace(/([^\\])_/g, "$1.?") + ); + } + + /** + * @override + */ + eval(target) { + let res = this.regexp.exec(this.left.eval(target)) !== null; + if (this.invert) { + return !res; + } + return res; + } + + /** + * @override + */ + toString(): string { + return `${this.left.toString()} ${this.invert ? "NOT " : ""}LIKE ${this.right}`; + } +} + +/** + * Represent a EXISTS expression + */ +class ExistsExpression extends Expression { + /** + * + * @param property to check + */ + constructor(public readonly property: string) { + super(); + } + + /** + * @override + */ + eval(target) { + return target[this.property] !== undefined; + } + + /** + * @override + */ + toString(): string { + return `EXISTS ${this.property}`; + } +} + +/** + * Represent a IN expression + */ +class InExpression extends Expression { + staticContent: any[]; + /** + * + * @param left part of the expression + * @param right part of the expression + * @param invert if NOT IN + */ + constructor( + public readonly left: Expression, + public readonly right: Expression[], + public readonly invert: boolean = false + ) { + super(); + this.deps.push(left, ...right); + this.staticContent = right.every(d => d instanceof ValueExpression) ? right.map(r => r.eval({})) : undefined; + } + + /** + * @override + */ + eval(target: any) { + let right = this.staticContent || this.right.map(r => r.eval(target)); + if (this.invert) { + return !right.includes(this.left.eval(target)); + } + return right.includes(this.left.eval(target)); + } + + /** + * @override + */ + toString(): string { + return `${this.left.toString()} ${this.invert ? "NOT " : ""}IN (${this.right.map(r => r.toString()).join(",")})`; + } +} + +/** + * Represent a binary multiplicative expression + */ +class BinaryMultiplicativeExpression extends Expression { + /** + * + * @param left part of the expression + * @param right part of the expression + * @param operator + */ + constructor( + public readonly left: Expression, + public readonly right: Expression, + public readonly operator: "*" | "/" | "%" + ) { + super(); + this.deps.push(left, right); + } + + /** + * @override + */ + eval(target: any) { + switch (this.operator) { + case "*": + return this.left.eval(target) * this.right.eval(target); + case "/": + return this.left.eval(target) / this.right.eval(target); + case "%": + return this.left.eval(target) % this.right.eval(target); + } + } + + /** + * @override + */ + toString(): string { + return `${this.left.toString()} ${this.operator} ${this.right.toString()}`; + } +} + +/** + * Represent a binary additive expression + */ +class BinaryAdditiveExpression extends Expression { + /** + * + * @param left part of the expression + * @param right part of the expression + * @param operator + */ + constructor( + public readonly left: Expression, + public readonly right: Expression, + public readonly operator: "+" | "-" + ) { + super(); + this.deps.push(left, right); + } + + /** + * @override + */ + eval(target: any) { + return this.left.eval(target) + this.right.eval(target) * (this.operator === "+" ? 1 : -1); + } + + /** + * @override + */ + toString(): string { + return `${this.left.toString()} + ${this.right.toString()}`; + } +} + +/** + * Represent a binary comparison expression + */ +class BinaryComparisonExpression extends Expression { + /** + * + * @param left part of the expression + * @param right part of the expression + * @param operator to use + */ + constructor( + public readonly left: Expression, + public readonly right: Expression, + public readonly operator: ">" | "<" | ">=" | "<=" | "!=" | "=" + ) { + super(); + this.deps.push(left, right); + } + + /** + * @override + */ + eval(target: any) { + switch (this.operator) { + case ">": + return this.left.eval(target) > this.right.eval(target); + case "<": + return this.left.eval(target) < this.right.eval(target); + case ">=": + return this.left.eval(target) >= this.right.eval(target); + case "<=": + return this.left.eval(target) <= this.right.eval(target); + case "!=": + return this.left.eval(target) != this.right.eval(target); + case "=": + return this.left.eval(target) == this.right.eval(target); + } + } + + /** + * @override + */ + toString(): string { + return `${this.left.toString()} ${this.operator} ${this.right.toString()}`; + } +} + +/** + * Represent a binary logic expression + */ +class BinaryLogicExpression extends Expression { + /** + * + * @param left part of the expression + * @param right part of the expression + * @param operator to use + */ + constructor( + public readonly left: Expression, + public readonly right: Expression, + public readonly operator: "AND" | "OR" | "XOR" + ) { + super(); + this.deps.push(left, right); + } + + /** + * @override + */ + eval(target: any) { + switch (this.operator) { + case "OR": + return this.left.eval(target) || this.right.eval(target); + case "XOR": + return this.left.eval(target) ? !this.right.eval(target) : this.right.eval(target); + case "AND": + return this.left.eval(target) && this.right.eval(target); + } + } + + /** + * @override + */ + toString(): string { + return `${this.left.toString()} ${this.operator} ${this.right.toString()}`; + } +} + +/** + * Represent a value expression + */ +export class ValueExpression extends Expression { + /** + * Value represented by the expression + * @param value + */ + constructor(public readonly value: any) { + super(); + } + + /** + * @override + */ + eval(target: any) { + return this.value; + } + + /** + * @override + */ + toString(): string { + if (typeof this.value === "string") { + return `"${this.value}"`; + } else if (typeof this.value === "boolean") { + return this.value.toString().toUpperCase(); + } + return this.value.toString(); + } +} + +/** + * Create Expression based on the parsed token + * + * Expression allow to optimize and split between Query and Filter + */ +export class CESQLExpressionBuilder extends AbstractParseTreeVisitor implements CESQLParserVisitor { + /** + * By default accepting it + * @returns + */ + protected defaultResult() { + return new ValueExpression(true); + } + + /** + * @override + */ + visitCesql(ctx: CesqlContext): Expression { + return this.visit(ctx.children[0]); + } + + /** + * @override + */ + visitFunctionInvocationExpression(ctx: FunctionInvocationExpressionContext): Expression { + let args = []; + for (let i = 1; i < ctx.childCount; i += 2) { + args.push(...((this.visit(ctx.children[i])))); + } + return new FunctionExpression(ctx.children[0].text, ...args).resolve(); + } + + /** + * @override + */ + visitUnaryLogicExpression(ctx: UnaryLogicExpressionContext): Expression { + return new UnaryLogicExpression(this.getChildrenResults(ctx)[1]).resolve(); + } + + /** + * @override + */ + visitUnaryNumericExpression(ctx: UnaryNumericExpressionContext): Expression { + return new UnaryNumericExpression(this.getChildrenResults(ctx)[1]).resolve(); + } + + /** + * @override + */ + visitLikeExpression(ctx: LikeExpressionContext): Expression { + const [left, regexp] = [this.visit(ctx.children[0]), ctx.children[ctx.children.length - 1].text]; + return new LikeExpression(left, regexp, ctx.childCount === 4).resolve(); + } + + /** + * @override + */ + visitExistsExpression(ctx: ExistsExpressionContext): Expression { + let results = this.getChildrenResults(ctx); + return new ExistsExpression(ctx.children[1].text).resolve(); + } + + /** + * @override + */ + visitInExpression(ctx: InExpressionContext): Expression { + let results = this.getChildrenResults(ctx); + if (ctx.childCount === 4) { + return new InExpression(results[0], results[3], true).resolve(); + } + return new InExpression(results[0], results[2]).resolve(); + } + + /** + * @override + */ + visitBinaryMultiplicativeExpression(ctx: BinaryMultiplicativeExpressionContext): Expression { + let [left, op, right] = [this.visit(ctx.children[0]), ctx.children[1].text, this.visit(ctx.children[2])]; + /* istanbul ignore next */ + if (!["*", "/", "%"].includes(op)) { + throw new Error("Not implemented"); + } + return new BinaryMultiplicativeExpression(left, right, <"*" | "/" | "%">op).resolve(); + } + + /** + * @override + */ + visitBinaryAdditiveExpression(ctx: BinaryAdditiveExpressionContext): Expression { + let [left, op, right] = [this.visit(ctx.children[0]), ctx.children[1].text, this.visit(ctx.children[2])]; + /* istanbul ignore next */ + if (!["+", "-"].includes(op)) { + throw new Error("Not implemented"); + } + return new BinaryAdditiveExpression(left, right, <"+" | "-">op).resolve(); + } + + /** + * Retrieve the children results + * @param ctx + * @returns + */ + getChildrenResults(ctx: any) { + return ctx.children.map((c: any) => this.visit(c)); + } + + /** + * + * @param ctx + * @returns + */ + visitBinaryComparisonExpression(ctx: BinaryComparisonExpressionContext): Expression { + let [left, op, right] = [this.visit(ctx.children[0]), ctx.children[1].text, this.visit(ctx.children[2])]; + /* istanbul ignore next */ + if (!["<", ">", "<=", ">=", "!=", "="].includes(op)) { + throw new Error("Not implemented"); + } + return new BinaryComparisonExpression(left, right, op as any).resolve(); + } + + /** + * @override + */ + visitBinaryLogicExpression(ctx: BinaryLogicExpressionContext): Expression { + let [left, op, right] = [this.visit(ctx.children[0]), ctx.children[1].text, this.visit(ctx.children[2])]; + /* istanbul ignore next */ + if (!["AND", "OR", "XOR"].includes(op)) { + throw new Error("Not implemented"); + } + return new BinaryLogicExpression(left, right, <"AND" | "OR" | "XOR">op).resolve(); + } + + /** + * @override + */ + visitSubExpression(ctx: SubExpressionContext): Expression { + return this.visit(ctx.children[1]); + } + + /** + * @override + */ + visitIdentifier(ctx: IdentifierContext): any { + return new AttributeExpression(ctx.text); + } + + /** + * @override + */ + visitBooleanLiteral(ctx: BooleanLiteralContext): Expression { + return new ValueExpression(ctx.text === "TRUE"); + } + + /** + * @override + */ + visitStringLiteral(ctx: StringLiteralContext): Expression { + // Remove quotes + return new ValueExpression(ctx.text.substr(1, ctx.text.length - 2)); + } + + /** + * @override + */ + visitIntegerLiteral(ctx: IntegerLiteralContext): Expression { + return new ValueExpression(Number(ctx.text)); + } + + /** + * @override + */ + visitFunctionParameterList(ctx: FunctionParameterListContext): Expression[] { + // Only take the odd part LBRACKET exp1 COMMA exp2 RBRACKET + return this.getChildrenResults(ctx).filter((_: any, i: number) => i % 2); + } + + /** + * @override + */ + visitSetExpression(ctx: SetExpressionContext): any[] { + // Only take the odd part LBRACKET exp1 COMMA exp2 RBRACKET + return this.getChildrenResults(ctx) + .filter((_: any, i: number) => i % 2) + .map(e => e.resolve()); + } +} + +/** + * SQL Filter implementation + */ +export class SqlFilterImplementation extends FilterImplementation { + lexer: CESQLParserLexer; + tree: any; + query: Expression; + /** + * + * @param definition {sql: "CESQL Expression"} + */ + constructor(definition: SqlFilter) { + super(definition); + this.lexer = new CESQLParserLexer(new ANTLRInputStream(definition.sql)); + let tokenStream = new CommonTokenStream(this.lexer); + let parser = new CESQLParserParser(tokenStream); + + // Parse the input, where `compilationUnit` is whatever entry point you defined + this.tree = parser.cesql(); + this.query = new CESQLExpressionBuilder().visit(this.tree); + // Display query post optimization + // console.log("SQL", definition.sql, " => ", this.query.toString()); + } + + /** + * Check if a cloudevent match the filter + * @param event + * @returns + */ + match(event: CloudEvent): boolean { + return this.query.eval(event); + } + + /** + * Some filter can be optimized into a simpler form + * + * Samples: + * LEFT(a, 3) = "foo" => prefix: {a: "foo"} + * RIGHT(a, 3) = "foo" => suffix: {a: "foo"} + * a = "foo" => exact: {a: "foo"} + * + * @returns + */ + optimize(): FilterImplementation { + if ( + this.query instanceof BinaryComparisonExpression && + this.query.operator === "=" && + this.query.right instanceof ValueExpression + ) { + if (this.query.left instanceof AttributeExpression) { + return new ExactFilterImplementation({ + exact: { + [this.query.left.attribute]: this.query.right.value + } + }); + } else if ( + this.query.left instanceof FunctionExpression && + this.query.left.args[0] instanceof AttributeExpression + ) { + if (this.query.left.functionName === "LEFT") { + return new PrefixFilterImplementation({ + prefix: { + [this.query.left.args[0].attribute]: this.query.right.value + } + }); + } else if (this.query.left.functionName === "RIGHT") { + return new SuffixFilterImplementation({ + suffix: { + [this.query.left.args[0].attribute]: this.query.right.value + } + }); + } + } + } + return this; + } +} diff --git a/packages/core/src/stores/webdaql/WebdaQLLexer.g4 b/packages/cloudevents/src/models/filters/sql/CESQLLexer.g4 similarity index 82% rename from packages/core/src/stores/webdaql/WebdaQLLexer.g4 rename to packages/cloudevents/src/models/filters/sql/CESQLLexer.g4 index 1eb35e6e0..e783083a6 100644 --- a/packages/core/src/stores/webdaql/WebdaQLLexer.g4 +++ b/packages/cloudevents/src/models/filters/sql/CESQLLexer.g4 @@ -1,4 +1,4 @@ -lexer grammar WebdaQLLexer; +lexer grammar CESQLLexer; // NOTE: // This grammar is case-sensitive, although CESQL keywords are case-insensitive. @@ -24,8 +24,6 @@ RR_BRACKET: ')'; COMMA: ','; SINGLE_QUOTE_SYMB: '\''; DOUBLE_QUOTE_SYMB: '"'; -LR_SQ_BRACKET: '['; -RR_SQ_BRACKET: ']'; fragment QUOTE_SYMB : SINGLE_QUOTE_SYMB | DOUBLE_QUOTE_SYMB @@ -36,6 +34,16 @@ fragment QUOTE_SYMB AND: 'AND'; OR: 'OR'; +XOR: 'XOR'; +NOT: 'NOT'; + +// - Arithmetics + +STAR: '*'; +DIVIDE: '/'; +MODULE: '%'; +PLUS: '+'; +MINUS: '-'; // - Comparison @@ -44,28 +52,20 @@ NOT_EQUAL: '!='; GREATER: '>'; GREATER_OR_EQUAL: '>='; LESS: '<'; +LESS_GREATER: '<>'; LESS_OR_EQUAL: '<='; // Like, exists, in LIKE: 'LIKE'; +EXISTS: 'EXISTS'; IN: 'IN'; -CONTAINS: 'CONTAINS'; // Booleans TRUE: 'TRUE'; FALSE: 'FALSE'; -// Limit -LIMIT: 'LIMIT'; -OFFSET: 'OFFSET'; - -// Order by -ORDER_BY: 'ORDER BY'; -ASC: 'ASC'; -DESC: 'DESC'; - // Literals DQUOTED_STRING_LITERAL: DQUOTA_STRING; @@ -75,5 +75,5 @@ INTEGER_LITERAL: INT_DIGIT+; // Identifiers IDENTIFIER: [a-zA-Z]+; -IDENTIFIER_WITH_NUMBER: [a-zA-Z0-9._]+; +IDENTIFIER_WITH_NUMBER: [a-zA-Z0-9]+; FUNCTION_IDENTIFIER_WITH_UNDERSCORE: [A-Z] [A-Z_]*; \ No newline at end of file diff --git a/packages/cloudevents/src/models/filters/sql/CESQLParser.g4 b/packages/cloudevents/src/models/filters/sql/CESQLParser.g4 new file mode 100644 index 000000000..71ef6a3a0 --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql/CESQLParser.g4 @@ -0,0 +1,62 @@ +grammar CESQLParser; + +import CESQLLexer; + +// Entrypoint +cesql: expression EOF; + +// Structure of operations, function invocations and expression +expression + : functionIdentifier functionParameterList #functionInvocationExpression + // unary operators are the highest priority + | NOT expression #unaryLogicExpression + | MINUS expression # unaryNumericExpression + // LIKE, EXISTS and IN takes precedence over all the other binary operators + | expression NOT? LIKE stringLiteral #likeExpression + | EXISTS identifier #existsExpression + | expression NOT? IN setExpression #inExpression + // Numeric operations + | expression (STAR | DIVIDE | MODULE) expression #binaryMultiplicativeExpression + | expression (PLUS | MINUS) expression #binaryAdditiveExpression + // Comparison operations + | expression (EQUAL | NOT_EQUAL | LESS_GREATER | GREATER_OR_EQUAL | LESS_OR_EQUAL | LESS | GREATER) expression #binaryComparisonExpression + // Logic operations + | expression (AND | OR | XOR) expression #binaryLogicExpression + // Subexpressions and atoms + | LR_BRACKET expression RR_BRACKET #subExpression + | atom #atomExpression + ; + +atom + : booleanLiteral #booleanAtom + | integerLiteral #integerAtom + | stringLiteral #stringAtom + | identifier #identifierAtom + ; + +// Identifiers + +identifier + : (IDENTIFIER | IDENTIFIER_WITH_NUMBER) + ; +functionIdentifier + : (IDENTIFIER | FUNCTION_IDENTIFIER_WITH_UNDERSCORE) + ; + +// Literals + +booleanLiteral: (TRUE | FALSE); +stringLiteral: (DQUOTED_STRING_LITERAL | SQUOTED_STRING_LITERAL); +integerLiteral: INTEGER_LITERAL; + +// Functions + +functionParameterList + : LR_BRACKET ( expression ( COMMA expression )* )? RR_BRACKET + ; + +// Sets + +setExpression + : LR_BRACKET expression ( COMMA expression )* RR_BRACKET // Empty sets are not allowed + ; \ No newline at end of file diff --git a/packages/cloudevents/src/models/filters/sql/CESQLParser.interp b/packages/cloudevents/src/models/filters/sql/CESQLParser.interp new file mode 100644 index 000000000..6dbee9577 --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql/CESQLParser.interp @@ -0,0 +1,87 @@ +token literal names: +null +null +'(' +')' +',' +'\'' +'"' +'AND' +'OR' +'XOR' +'NOT' +'*' +'/' +'%' +'+' +'-' +'=' +'!=' +'>' +'>=' +'<' +'<>' +'<=' +'LIKE' +'EXISTS' +'IN' +'TRUE' +'FALSE' +null +null +null +null +null +null + +token symbolic names: +null +SPACE +LR_BRACKET +RR_BRACKET +COMMA +SINGLE_QUOTE_SYMB +DOUBLE_QUOTE_SYMB +AND +OR +XOR +NOT +STAR +DIVIDE +MODULE +PLUS +MINUS +EQUAL +NOT_EQUAL +GREATER +GREATER_OR_EQUAL +LESS +LESS_GREATER +LESS_OR_EQUAL +LIKE +EXISTS +IN +TRUE +FALSE +DQUOTED_STRING_LITERAL +SQUOTED_STRING_LITERAL +INTEGER_LITERAL +IDENTIFIER +IDENTIFIER_WITH_NUMBER +FUNCTION_IDENTIFIER_WITH_UNDERSCORE + +rule names: +cesql +expression +atom +identifier +functionIdentifier +booleanLiteral +stringLiteral +integerLiteral +functionParameterList +setExpression + + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 35, 112, 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, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 41, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 57, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 63, 10, 3, 3, 3, 3, 3, 7, 3, 67, 10, 3, 12, 3, 14, 3, 70, 11, 3, 3, 4, 3, 4, 3, 4, 3, 4, 5, 4, 76, 10, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 7, 10, 92, 10, 10, 12, 10, 14, 10, 95, 11, 10, 5, 10, 97, 10, 10, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 11, 7, 11, 105, 10, 11, 12, 11, 14, 11, 108, 11, 11, 3, 11, 3, 11, 3, 11, 2, 2, 3, 4, 12, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 12, 2, 14, 2, 16, 2, 18, 2, 20, 2, 2, 10, 3, 2, 13, 15, 3, 2, 16, 17, 3, 2, 18, 24, 3, 2, 9, 11, 3, 2, 33, 34, 4, 2, 33, 33, 35, 35, 3, 2, 28, 29, 3, 2, 30, 31, 2, 120, 2, 22, 3, 2, 2, 2, 4, 40, 3, 2, 2, 2, 6, 75, 3, 2, 2, 2, 8, 77, 3, 2, 2, 2, 10, 79, 3, 2, 2, 2, 12, 81, 3, 2, 2, 2, 14, 83, 3, 2, 2, 2, 16, 85, 3, 2, 2, 2, 18, 87, 3, 2, 2, 2, 20, 100, 3, 2, 2, 2, 22, 23, 5, 4, 3, 2, 23, 24, 7, 2, 2, 3, 24, 3, 3, 2, 2, 2, 25, 26, 8, 3, 1, 2, 26, 27, 5, 10, 6, 2, 27, 28, 5, 18, 10, 2, 28, 41, 3, 2, 2, 2, 29, 30, 7, 12, 2, 2, 30, 41, 5, 4, 3, 13, 31, 32, 7, 17, 2, 2, 32, 41, 5, 4, 3, 12, 33, 34, 7, 26, 2, 2, 34, 41, 5, 8, 5, 2, 35, 36, 7, 4, 2, 2, 36, 37, 5, 4, 3, 2, 37, 38, 7, 5, 2, 2, 38, 41, 3, 2, 2, 2, 39, 41, 5, 6, 4, 2, 40, 25, 3, 2, 2, 2, 40, 29, 3, 2, 2, 2, 40, 31, 3, 2, 2, 2, 40, 33, 3, 2, 2, 2, 40, 35, 3, 2, 2, 2, 40, 39, 3, 2, 2, 2, 41, 68, 3, 2, 2, 2, 42, 43, 12, 8, 2, 2, 43, 44, 9, 2, 2, 2, 44, 67, 5, 4, 3, 9, 45, 46, 12, 7, 2, 2, 46, 47, 9, 3, 2, 2, 47, 67, 5, 4, 3, 8, 48, 49, 12, 6, 2, 2, 49, 50, 9, 4, 2, 2, 50, 67, 5, 4, 3, 7, 51, 52, 12, 5, 2, 2, 52, 53, 9, 5, 2, 2, 53, 67, 5, 4, 3, 5, 54, 56, 12, 11, 2, 2, 55, 57, 7, 12, 2, 2, 56, 55, 3, 2, 2, 2, 56, 57, 3, 2, 2, 2, 57, 58, 3, 2, 2, 2, 58, 59, 7, 25, 2, 2, 59, 67, 5, 14, 8, 2, 60, 62, 12, 9, 2, 2, 61, 63, 7, 12, 2, 2, 62, 61, 3, 2, 2, 2, 62, 63, 3, 2, 2, 2, 63, 64, 3, 2, 2, 2, 64, 65, 7, 27, 2, 2, 65, 67, 5, 20, 11, 2, 66, 42, 3, 2, 2, 2, 66, 45, 3, 2, 2, 2, 66, 48, 3, 2, 2, 2, 66, 51, 3, 2, 2, 2, 66, 54, 3, 2, 2, 2, 66, 60, 3, 2, 2, 2, 67, 70, 3, 2, 2, 2, 68, 66, 3, 2, 2, 2, 68, 69, 3, 2, 2, 2, 69, 5, 3, 2, 2, 2, 70, 68, 3, 2, 2, 2, 71, 76, 5, 12, 7, 2, 72, 76, 5, 16, 9, 2, 73, 76, 5, 14, 8, 2, 74, 76, 5, 8, 5, 2, 75, 71, 3, 2, 2, 2, 75, 72, 3, 2, 2, 2, 75, 73, 3, 2, 2, 2, 75, 74, 3, 2, 2, 2, 76, 7, 3, 2, 2, 2, 77, 78, 9, 6, 2, 2, 78, 9, 3, 2, 2, 2, 79, 80, 9, 7, 2, 2, 80, 11, 3, 2, 2, 2, 81, 82, 9, 8, 2, 2, 82, 13, 3, 2, 2, 2, 83, 84, 9, 9, 2, 2, 84, 15, 3, 2, 2, 2, 85, 86, 7, 32, 2, 2, 86, 17, 3, 2, 2, 2, 87, 96, 7, 4, 2, 2, 88, 93, 5, 4, 3, 2, 89, 90, 7, 6, 2, 2, 90, 92, 5, 4, 3, 2, 91, 89, 3, 2, 2, 2, 92, 95, 3, 2, 2, 2, 93, 91, 3, 2, 2, 2, 93, 94, 3, 2, 2, 2, 94, 97, 3, 2, 2, 2, 95, 93, 3, 2, 2, 2, 96, 88, 3, 2, 2, 2, 96, 97, 3, 2, 2, 2, 97, 98, 3, 2, 2, 2, 98, 99, 7, 5, 2, 2, 99, 19, 3, 2, 2, 2, 100, 101, 7, 4, 2, 2, 101, 106, 5, 4, 3, 2, 102, 103, 7, 6, 2, 2, 103, 105, 5, 4, 3, 2, 104, 102, 3, 2, 2, 2, 105, 108, 3, 2, 2, 2, 106, 104, 3, 2, 2, 2, 106, 107, 3, 2, 2, 2, 107, 109, 3, 2, 2, 2, 108, 106, 3, 2, 2, 2, 109, 110, 7, 5, 2, 2, 110, 21, 3, 2, 2, 2, 11, 40, 56, 62, 66, 68, 75, 93, 96, 106] \ No newline at end of file diff --git a/packages/cloudevents/src/models/filters/sql/CESQLParser.tokens b/packages/cloudevents/src/models/filters/sql/CESQLParser.tokens new file mode 100644 index 000000000..913f0bc71 --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql/CESQLParser.tokens @@ -0,0 +1,59 @@ +SPACE=1 +LR_BRACKET=2 +RR_BRACKET=3 +COMMA=4 +SINGLE_QUOTE_SYMB=5 +DOUBLE_QUOTE_SYMB=6 +AND=7 +OR=8 +XOR=9 +NOT=10 +STAR=11 +DIVIDE=12 +MODULE=13 +PLUS=14 +MINUS=15 +EQUAL=16 +NOT_EQUAL=17 +GREATER=18 +GREATER_OR_EQUAL=19 +LESS=20 +LESS_GREATER=21 +LESS_OR_EQUAL=22 +LIKE=23 +EXISTS=24 +IN=25 +TRUE=26 +FALSE=27 +DQUOTED_STRING_LITERAL=28 +SQUOTED_STRING_LITERAL=29 +INTEGER_LITERAL=30 +IDENTIFIER=31 +IDENTIFIER_WITH_NUMBER=32 +FUNCTION_IDENTIFIER_WITH_UNDERSCORE=33 +'('=2 +')'=3 +','=4 +'\''=5 +'"'=6 +'AND'=7 +'OR'=8 +'XOR'=9 +'NOT'=10 +'*'=11 +'/'=12 +'%'=13 +'+'=14 +'-'=15 +'='=16 +'!='=17 +'>'=18 +'>='=19 +'<'=20 +'<>'=21 +'<='=22 +'LIKE'=23 +'EXISTS'=24 +'IN'=25 +'TRUE'=26 +'FALSE'=27 diff --git a/packages/cloudevents/src/models/filters/sql/CESQLParserLexer.interp b/packages/cloudevents/src/models/filters/sql/CESQLParserLexer.interp new file mode 100644 index 000000000..d6f339e22 --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql/CESQLParserLexer.interp @@ -0,0 +1,122 @@ +token literal names: +null +null +'(' +')' +',' +'\'' +'"' +'AND' +'OR' +'XOR' +'NOT' +'*' +'/' +'%' +'+' +'-' +'=' +'!=' +'>' +'>=' +'<' +'<>' +'<=' +'LIKE' +'EXISTS' +'IN' +'TRUE' +'FALSE' +null +null +null +null +null +null + +token symbolic names: +null +SPACE +LR_BRACKET +RR_BRACKET +COMMA +SINGLE_QUOTE_SYMB +DOUBLE_QUOTE_SYMB +AND +OR +XOR +NOT +STAR +DIVIDE +MODULE +PLUS +MINUS +EQUAL +NOT_EQUAL +GREATER +GREATER_OR_EQUAL +LESS +LESS_GREATER +LESS_OR_EQUAL +LIKE +EXISTS +IN +TRUE +FALSE +DQUOTED_STRING_LITERAL +SQUOTED_STRING_LITERAL +INTEGER_LITERAL +IDENTIFIER +IDENTIFIER_WITH_NUMBER +FUNCTION_IDENTIFIER_WITH_UNDERSCORE + +rule names: +SPACE +ID_LITERAL +DQUOTA_STRING +SQUOTA_STRING +INT_DIGIT +FN_LITERAL +LR_BRACKET +RR_BRACKET +COMMA +SINGLE_QUOTE_SYMB +DOUBLE_QUOTE_SYMB +QUOTE_SYMB +AND +OR +XOR +NOT +STAR +DIVIDE +MODULE +PLUS +MINUS +EQUAL +NOT_EQUAL +GREATER +GREATER_OR_EQUAL +LESS +LESS_GREATER +LESS_OR_EQUAL +LIKE +EXISTS +IN +TRUE +FALSE +DQUOTED_STRING_LITERAL +SQUOTED_STRING_LITERAL +INTEGER_LITERAL +IDENTIFIER +IDENTIFIER_WITH_NUMBER +FUNCTION_IDENTIFIER_WITH_UNDERSCORE + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 35, 237, 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, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 3, 2, 6, 2, 83, 10, 2, 13, 2, 14, 2, 84, 3, 2, 3, 2, 3, 3, 6, 3, 90, 10, 3, 13, 3, 14, 3, 91, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 100, 10, 4, 12, 4, 14, 4, 103, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 7, 5, 113, 10, 5, 12, 5, 14, 5, 116, 11, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 7, 7, 124, 10, 7, 12, 7, 14, 7, 127, 11, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 5, 13, 141, 10, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 36, 3, 36, 3, 37, 6, 37, 217, 10, 37, 13, 37, 14, 37, 218, 3, 38, 6, 38, 222, 10, 38, 13, 38, 14, 38, 223, 3, 39, 6, 39, 227, 10, 39, 13, 39, 14, 39, 228, 3, 40, 3, 40, 7, 40, 233, 10, 40, 12, 40, 14, 40, 236, 11, 40, 2, 2, 2, 41, 3, 2, 3, 5, 2, 2, 7, 2, 2, 9, 2, 2, 11, 2, 2, 13, 2, 2, 15, 2, 4, 17, 2, 5, 19, 2, 6, 21, 2, 7, 23, 2, 8, 25, 2, 2, 27, 2, 9, 29, 2, 10, 31, 2, 11, 33, 2, 12, 35, 2, 13, 37, 2, 14, 39, 2, 15, 41, 2, 16, 43, 2, 17, 45, 2, 18, 47, 2, 19, 49, 2, 20, 51, 2, 21, 53, 2, 22, 55, 2, 23, 57, 2, 24, 59, 2, 25, 61, 2, 26, 63, 2, 27, 65, 2, 28, 67, 2, 29, 69, 2, 30, 71, 2, 31, 73, 2, 32, 75, 2, 33, 77, 2, 34, 79, 2, 35, 3, 2, 10, 5, 2, 11, 12, 15, 15, 34, 34, 5, 2, 50, 59, 67, 92, 99, 124, 4, 2, 36, 36, 94, 94, 4, 2, 41, 41, 94, 94, 3, 2, 50, 59, 3, 2, 67, 92, 4, 2, 67, 92, 97, 97, 4, 2, 67, 92, 99, 124, 2, 244, 2, 3, 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, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 2, 77, 3, 2, 2, 2, 2, 79, 3, 2, 2, 2, 3, 82, 3, 2, 2, 2, 5, 89, 3, 2, 2, 2, 7, 93, 3, 2, 2, 2, 9, 106, 3, 2, 2, 2, 11, 119, 3, 2, 2, 2, 13, 121, 3, 2, 2, 2, 15, 128, 3, 2, 2, 2, 17, 130, 3, 2, 2, 2, 19, 132, 3, 2, 2, 2, 21, 134, 3, 2, 2, 2, 23, 136, 3, 2, 2, 2, 25, 140, 3, 2, 2, 2, 27, 142, 3, 2, 2, 2, 29, 146, 3, 2, 2, 2, 31, 149, 3, 2, 2, 2, 33, 153, 3, 2, 2, 2, 35, 157, 3, 2, 2, 2, 37, 159, 3, 2, 2, 2, 39, 161, 3, 2, 2, 2, 41, 163, 3, 2, 2, 2, 43, 165, 3, 2, 2, 2, 45, 167, 3, 2, 2, 2, 47, 169, 3, 2, 2, 2, 49, 172, 3, 2, 2, 2, 51, 174, 3, 2, 2, 2, 53, 177, 3, 2, 2, 2, 55, 179, 3, 2, 2, 2, 57, 182, 3, 2, 2, 2, 59, 185, 3, 2, 2, 2, 61, 190, 3, 2, 2, 2, 63, 197, 3, 2, 2, 2, 65, 200, 3, 2, 2, 2, 67, 205, 3, 2, 2, 2, 69, 211, 3, 2, 2, 2, 71, 213, 3, 2, 2, 2, 73, 216, 3, 2, 2, 2, 75, 221, 3, 2, 2, 2, 77, 226, 3, 2, 2, 2, 79, 230, 3, 2, 2, 2, 81, 83, 9, 2, 2, 2, 82, 81, 3, 2, 2, 2, 83, 84, 3, 2, 2, 2, 84, 82, 3, 2, 2, 2, 84, 85, 3, 2, 2, 2, 85, 86, 3, 2, 2, 2, 86, 87, 8, 2, 2, 2, 87, 4, 3, 2, 2, 2, 88, 90, 9, 3, 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, 6, 3, 2, 2, 2, 93, 101, 7, 36, 2, 2, 94, 95, 7, 94, 2, 2, 95, 100, 11, 2, 2, 2, 96, 97, 7, 36, 2, 2, 97, 100, 7, 36, 2, 2, 98, 100, 10, 4, 2, 2, 99, 94, 3, 2, 2, 2, 99, 96, 3, 2, 2, 2, 99, 98, 3, 2, 2, 2, 100, 103, 3, 2, 2, 2, 101, 99, 3, 2, 2, 2, 101, 102, 3, 2, 2, 2, 102, 104, 3, 2, 2, 2, 103, 101, 3, 2, 2, 2, 104, 105, 7, 36, 2, 2, 105, 8, 3, 2, 2, 2, 106, 114, 7, 41, 2, 2, 107, 108, 7, 94, 2, 2, 108, 113, 11, 2, 2, 2, 109, 110, 7, 41, 2, 2, 110, 113, 7, 41, 2, 2, 111, 113, 10, 5, 2, 2, 112, 107, 3, 2, 2, 2, 112, 109, 3, 2, 2, 2, 112, 111, 3, 2, 2, 2, 113, 116, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 114, 115, 3, 2, 2, 2, 115, 117, 3, 2, 2, 2, 116, 114, 3, 2, 2, 2, 117, 118, 7, 41, 2, 2, 118, 10, 3, 2, 2, 2, 119, 120, 9, 6, 2, 2, 120, 12, 3, 2, 2, 2, 121, 125, 9, 7, 2, 2, 122, 124, 9, 8, 2, 2, 123, 122, 3, 2, 2, 2, 124, 127, 3, 2, 2, 2, 125, 123, 3, 2, 2, 2, 125, 126, 3, 2, 2, 2, 126, 14, 3, 2, 2, 2, 127, 125, 3, 2, 2, 2, 128, 129, 7, 42, 2, 2, 129, 16, 3, 2, 2, 2, 130, 131, 7, 43, 2, 2, 131, 18, 3, 2, 2, 2, 132, 133, 7, 46, 2, 2, 133, 20, 3, 2, 2, 2, 134, 135, 7, 41, 2, 2, 135, 22, 3, 2, 2, 2, 136, 137, 7, 36, 2, 2, 137, 24, 3, 2, 2, 2, 138, 141, 5, 21, 11, 2, 139, 141, 5, 23, 12, 2, 140, 138, 3, 2, 2, 2, 140, 139, 3, 2, 2, 2, 141, 26, 3, 2, 2, 2, 142, 143, 7, 67, 2, 2, 143, 144, 7, 80, 2, 2, 144, 145, 7, 70, 2, 2, 145, 28, 3, 2, 2, 2, 146, 147, 7, 81, 2, 2, 147, 148, 7, 84, 2, 2, 148, 30, 3, 2, 2, 2, 149, 150, 7, 90, 2, 2, 150, 151, 7, 81, 2, 2, 151, 152, 7, 84, 2, 2, 152, 32, 3, 2, 2, 2, 153, 154, 7, 80, 2, 2, 154, 155, 7, 81, 2, 2, 155, 156, 7, 86, 2, 2, 156, 34, 3, 2, 2, 2, 157, 158, 7, 44, 2, 2, 158, 36, 3, 2, 2, 2, 159, 160, 7, 49, 2, 2, 160, 38, 3, 2, 2, 2, 161, 162, 7, 39, 2, 2, 162, 40, 3, 2, 2, 2, 163, 164, 7, 45, 2, 2, 164, 42, 3, 2, 2, 2, 165, 166, 7, 47, 2, 2, 166, 44, 3, 2, 2, 2, 167, 168, 7, 63, 2, 2, 168, 46, 3, 2, 2, 2, 169, 170, 7, 35, 2, 2, 170, 171, 7, 63, 2, 2, 171, 48, 3, 2, 2, 2, 172, 173, 7, 64, 2, 2, 173, 50, 3, 2, 2, 2, 174, 175, 7, 64, 2, 2, 175, 176, 7, 63, 2, 2, 176, 52, 3, 2, 2, 2, 177, 178, 7, 62, 2, 2, 178, 54, 3, 2, 2, 2, 179, 180, 7, 62, 2, 2, 180, 181, 7, 64, 2, 2, 181, 56, 3, 2, 2, 2, 182, 183, 7, 62, 2, 2, 183, 184, 7, 63, 2, 2, 184, 58, 3, 2, 2, 2, 185, 186, 7, 78, 2, 2, 186, 187, 7, 75, 2, 2, 187, 188, 7, 77, 2, 2, 188, 189, 7, 71, 2, 2, 189, 60, 3, 2, 2, 2, 190, 191, 7, 71, 2, 2, 191, 192, 7, 90, 2, 2, 192, 193, 7, 75, 2, 2, 193, 194, 7, 85, 2, 2, 194, 195, 7, 86, 2, 2, 195, 196, 7, 85, 2, 2, 196, 62, 3, 2, 2, 2, 197, 198, 7, 75, 2, 2, 198, 199, 7, 80, 2, 2, 199, 64, 3, 2, 2, 2, 200, 201, 7, 86, 2, 2, 201, 202, 7, 84, 2, 2, 202, 203, 7, 87, 2, 2, 203, 204, 7, 71, 2, 2, 204, 66, 3, 2, 2, 2, 205, 206, 7, 72, 2, 2, 206, 207, 7, 67, 2, 2, 207, 208, 7, 78, 2, 2, 208, 209, 7, 85, 2, 2, 209, 210, 7, 71, 2, 2, 210, 68, 3, 2, 2, 2, 211, 212, 5, 7, 4, 2, 212, 70, 3, 2, 2, 2, 213, 214, 5, 9, 5, 2, 214, 72, 3, 2, 2, 2, 215, 217, 5, 11, 6, 2, 216, 215, 3, 2, 2, 2, 217, 218, 3, 2, 2, 2, 218, 216, 3, 2, 2, 2, 218, 219, 3, 2, 2, 2, 219, 74, 3, 2, 2, 2, 220, 222, 9, 9, 2, 2, 221, 220, 3, 2, 2, 2, 222, 223, 3, 2, 2, 2, 223, 221, 3, 2, 2, 2, 223, 224, 3, 2, 2, 2, 224, 76, 3, 2, 2, 2, 225, 227, 9, 3, 2, 2, 226, 225, 3, 2, 2, 2, 227, 228, 3, 2, 2, 2, 228, 226, 3, 2, 2, 2, 228, 229, 3, 2, 2, 2, 229, 78, 3, 2, 2, 2, 230, 234, 9, 7, 2, 2, 231, 233, 9, 8, 2, 2, 232, 231, 3, 2, 2, 2, 233, 236, 3, 2, 2, 2, 234, 232, 3, 2, 2, 2, 234, 235, 3, 2, 2, 2, 235, 80, 3, 2, 2, 2, 236, 234, 3, 2, 2, 2, 15, 2, 84, 91, 99, 101, 112, 114, 125, 140, 218, 223, 228, 234, 3, 8, 2, 2] \ No newline at end of file diff --git a/packages/cloudevents/src/models/filters/sql/CESQLParserLexer.tokens b/packages/cloudevents/src/models/filters/sql/CESQLParserLexer.tokens new file mode 100644 index 000000000..913f0bc71 --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql/CESQLParserLexer.tokens @@ -0,0 +1,59 @@ +SPACE=1 +LR_BRACKET=2 +RR_BRACKET=3 +COMMA=4 +SINGLE_QUOTE_SYMB=5 +DOUBLE_QUOTE_SYMB=6 +AND=7 +OR=8 +XOR=9 +NOT=10 +STAR=11 +DIVIDE=12 +MODULE=13 +PLUS=14 +MINUS=15 +EQUAL=16 +NOT_EQUAL=17 +GREATER=18 +GREATER_OR_EQUAL=19 +LESS=20 +LESS_GREATER=21 +LESS_OR_EQUAL=22 +LIKE=23 +EXISTS=24 +IN=25 +TRUE=26 +FALSE=27 +DQUOTED_STRING_LITERAL=28 +SQUOTED_STRING_LITERAL=29 +INTEGER_LITERAL=30 +IDENTIFIER=31 +IDENTIFIER_WITH_NUMBER=32 +FUNCTION_IDENTIFIER_WITH_UNDERSCORE=33 +'('=2 +')'=3 +','=4 +'\''=5 +'"'=6 +'AND'=7 +'OR'=8 +'XOR'=9 +'NOT'=10 +'*'=11 +'/'=12 +'%'=13 +'+'=14 +'-'=15 +'='=16 +'!='=17 +'>'=18 +'>='=19 +'<'=20 +'<>'=21 +'<='=22 +'LIKE'=23 +'EXISTS'=24 +'IN'=25 +'TRUE'=26 +'FALSE'=27 diff --git a/packages/cloudevents/src/models/filters/sql/CESQLParserLexer.ts b/packages/cloudevents/src/models/filters/sql/CESQLParserLexer.ts new file mode 100644 index 000000000..64fa09670 --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql/CESQLParserLexer.ts @@ -0,0 +1,231 @@ +// Generated from src/models/filters/sql/CESQLParser.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 CESQLParserLexer extends Lexer { + public static readonly SPACE = 1; + public static readonly LR_BRACKET = 2; + public static readonly RR_BRACKET = 3; + public static readonly COMMA = 4; + public static readonly SINGLE_QUOTE_SYMB = 5; + public static readonly DOUBLE_QUOTE_SYMB = 6; + public static readonly AND = 7; + public static readonly OR = 8; + public static readonly XOR = 9; + public static readonly NOT = 10; + public static readonly STAR = 11; + public static readonly DIVIDE = 12; + public static readonly MODULE = 13; + public static readonly PLUS = 14; + public static readonly MINUS = 15; + public static readonly EQUAL = 16; + public static readonly NOT_EQUAL = 17; + public static readonly GREATER = 18; + public static readonly GREATER_OR_EQUAL = 19; + public static readonly LESS = 20; + public static readonly LESS_GREATER = 21; + public static readonly LESS_OR_EQUAL = 22; + public static readonly LIKE = 23; + public static readonly EXISTS = 24; + public static readonly IN = 25; + public static readonly TRUE = 26; + public static readonly FALSE = 27; + public static readonly DQUOTED_STRING_LITERAL = 28; + public static readonly SQUOTED_STRING_LITERAL = 29; + public static readonly INTEGER_LITERAL = 30; + public static readonly IDENTIFIER = 31; + public static readonly IDENTIFIER_WITH_NUMBER = 32; + public static readonly FUNCTION_IDENTIFIER_WITH_UNDERSCORE = 33; + + // 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[] = [ + "SPACE", "ID_LITERAL", "DQUOTA_STRING", "SQUOTA_STRING", "INT_DIGIT", + "FN_LITERAL", "LR_BRACKET", "RR_BRACKET", "COMMA", "SINGLE_QUOTE_SYMB", + "DOUBLE_QUOTE_SYMB", "QUOTE_SYMB", "AND", "OR", "XOR", "NOT", "STAR", + "DIVIDE", "MODULE", "PLUS", "MINUS", "EQUAL", "NOT_EQUAL", "GREATER", + "GREATER_OR_EQUAL", "LESS", "LESS_GREATER", "LESS_OR_EQUAL", "LIKE", "EXISTS", + "IN", "TRUE", "FALSE", "DQUOTED_STRING_LITERAL", "SQUOTED_STRING_LITERAL", + "INTEGER_LITERAL", "IDENTIFIER", "IDENTIFIER_WITH_NUMBER", "FUNCTION_IDENTIFIER_WITH_UNDERSCORE", + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, undefined, "'('", "')'", "','", "'''", "'\"'", "'AND'", "'OR'", + "'XOR'", "'NOT'", "'*'", "'/'", "'%'", "'+'", "'-'", "'='", "'!='", "'>'", + "'>='", "'<'", "'<>'", "'<='", "'LIKE'", "'EXISTS'", "'IN'", "'TRUE'", + "'FALSE'", + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, "SPACE", "LR_BRACKET", "RR_BRACKET", "COMMA", "SINGLE_QUOTE_SYMB", + "DOUBLE_QUOTE_SYMB", "AND", "OR", "XOR", "NOT", "STAR", "DIVIDE", "MODULE", + "PLUS", "MINUS", "EQUAL", "NOT_EQUAL", "GREATER", "GREATER_OR_EQUAL", + "LESS", "LESS_GREATER", "LESS_OR_EQUAL", "LIKE", "EXISTS", "IN", "TRUE", + "FALSE", "DQUOTED_STRING_LITERAL", "SQUOTED_STRING_LITERAL", "INTEGER_LITERAL", + "IDENTIFIER", "IDENTIFIER_WITH_NUMBER", "FUNCTION_IDENTIFIER_WITH_UNDERSCORE", + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(CESQLParserLexer._LITERAL_NAMES, CESQLParserLexer._SYMBOLIC_NAMES, []); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return CESQLParserLexer.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + + constructor(input: CharStream) { + super(input); + this._interp = new LexerATNSimulator(CESQLParserLexer._ATN, this); + } + + // @Override + public get grammarFileName(): string { return "CESQLParser.g4"; } + + // @Override + public get ruleNames(): string[] { return CESQLParserLexer.ruleNames; } + + // @Override + public get serializedATN(): string { return CESQLParserLexer._serializedATN; } + + // @Override + public get channelNames(): string[] { return CESQLParserLexer.channelNames; } + + // @Override + public get modeNames(): string[] { return CESQLParserLexer.modeNames; } + + public static readonly _serializedATN: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02#\xED\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\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12" + + "\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t\x17" + + "\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t\x1C" + + "\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04\"\t\"\x04" + + "#\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(\t(\x03\x02\x06\x02S\n\x02" + + "\r\x02\x0E\x02T\x03\x02\x03\x02\x03\x03\x06\x03Z\n\x03\r\x03\x0E\x03[" + + "\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x07\x04d\n\x04\f\x04" + + "\x0E\x04g\v\x04\x03\x04\x03\x04\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05" + + "\x03\x05\x07\x05q\n\x05\f\x05\x0E\x05t\v\x05\x03\x05\x03\x05\x03\x06\x03" + + "\x06\x03\x07\x03\x07\x07\x07|\n\x07\f\x07\x0E\x07\x7F\v\x07\x03\b\x03" + + "\b\x03\t\x03\t\x03\n\x03\n\x03\v\x03\v\x03\f\x03\f\x03\r\x03\r\x05\r\x8D" + + "\n\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x0F\x03\x10\x03" + + "\x10\x03\x10\x03\x10\x03\x11\x03\x11\x03\x11\x03\x11\x03\x12\x03\x12\x03" + + "\x13\x03\x13\x03\x14\x03\x14\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17\x03" + + "\x17\x03\x18\x03\x18\x03\x18\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1A\x03" + + "\x1B\x03\x1B\x03\x1C\x03\x1C\x03\x1C\x03\x1D\x03\x1D\x03\x1D\x03\x1E\x03" + + "\x1E\x03\x1E\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03" + + "\x1F\x03\x1F\x03 \x03 \x03 \x03!\x03!\x03!\x03!\x03!\x03\"\x03\"\x03\"" + + "\x03\"\x03\"\x03\"\x03#\x03#\x03$\x03$\x03%\x06%\xD9\n%\r%\x0E%\xDA\x03" + + "&\x06&\xDE\n&\r&\x0E&\xDF\x03\'\x06\'\xE3\n\'\r\'\x0E\'\xE4\x03(\x03(" + + "\x07(\xE9\n(\f(\x0E(\xEC\v(\x02\x02\x02)\x03\x02\x03\x05\x02\x02\x07\x02" + + "\x02\t\x02\x02\v\x02\x02\r\x02\x02\x0F\x02\x04\x11\x02\x05\x13\x02\x06" + + "\x15\x02\x07\x17\x02\b\x19\x02\x02\x1B\x02\t\x1D\x02\n\x1F\x02\v!\x02" + + "\f#\x02\r%\x02\x0E\'\x02\x0F)\x02\x10+\x02\x11-\x02\x12/\x02\x131\x02" + + "\x143\x02\x155\x02\x167\x02\x179\x02\x18;\x02\x19=\x02\x1A?\x02\x1BA\x02" + + "\x1CC\x02\x1DE\x02\x1EG\x02\x1FI\x02 K\x02!M\x02\"O\x02#\x03\x02\n\x05" + + "\x02\v\f\x0F\x0F\"\"\x05\x022;C\\c|\x04\x02$$^^\x04\x02))^^\x03\x022;" + + "\x03\x02C\\\x04\x02C\\aa\x04\x02C\\c|\x02\xF4\x02\x03\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\x1B\x03\x02\x02\x02" + + "\x02\x1D\x03\x02\x02\x02\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02" + + "\x02#\x03\x02\x02\x02\x02%\x03\x02\x02\x02\x02\'\x03\x02\x02\x02\x02)" + + "\x03\x02\x02\x02\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02" + + "\x02\x02\x021\x03\x02\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02" + + "\x027\x03\x02\x02\x02\x029\x03\x02\x02\x02\x02;\x03\x02\x02\x02\x02=\x03" + + "\x02\x02\x02\x02?\x03\x02\x02\x02\x02A\x03\x02\x02\x02\x02C\x03\x02\x02" + + "\x02\x02E\x03\x02\x02\x02\x02G\x03\x02\x02\x02\x02I\x03\x02\x02\x02\x02" + + "K\x03\x02\x02\x02\x02M\x03\x02\x02\x02\x02O\x03\x02\x02\x02\x03R\x03\x02" + + "\x02\x02\x05Y\x03\x02\x02\x02\x07]\x03\x02\x02\x02\tj\x03\x02\x02\x02" + + "\vw\x03\x02\x02\x02\ry\x03\x02\x02\x02\x0F\x80\x03\x02\x02\x02\x11\x82" + + "\x03\x02\x02\x02\x13\x84\x03\x02\x02\x02\x15\x86\x03\x02\x02\x02\x17\x88" + + "\x03\x02\x02\x02\x19\x8C\x03\x02\x02\x02\x1B\x8E\x03\x02\x02\x02\x1D\x92" + + "\x03\x02\x02\x02\x1F\x95\x03\x02\x02\x02!\x99\x03\x02\x02\x02#\x9D\x03" + + "\x02\x02\x02%\x9F\x03\x02\x02\x02\'\xA1\x03\x02\x02\x02)\xA3\x03\x02\x02" + + "\x02+\xA5\x03\x02\x02\x02-\xA7\x03\x02\x02\x02/\xA9\x03\x02\x02\x021\xAC" + + "\x03\x02\x02\x023\xAE\x03\x02\x02\x025\xB1\x03\x02\x02\x027\xB3\x03\x02" + + "\x02\x029\xB6\x03\x02\x02\x02;\xB9\x03\x02\x02\x02=\xBE\x03\x02\x02\x02" + + "?\xC5\x03\x02\x02\x02A\xC8\x03\x02\x02\x02C\xCD\x03\x02\x02\x02E\xD3\x03" + + "\x02\x02\x02G\xD5\x03\x02\x02\x02I\xD8\x03\x02\x02\x02K\xDD\x03\x02\x02" + + "\x02M\xE2\x03\x02\x02\x02O\xE6\x03\x02\x02\x02QS\t\x02\x02\x02RQ\x03\x02" + + "\x02\x02ST\x03\x02\x02\x02TR\x03\x02\x02\x02TU\x03\x02\x02\x02UV\x03\x02" + + "\x02\x02VW\b\x02\x02\x02W\x04\x03\x02\x02\x02XZ\t\x03\x02\x02YX\x03\x02" + + "\x02\x02Z[\x03\x02\x02\x02[Y\x03\x02\x02\x02[\\\x03\x02\x02\x02\\\x06" + + "\x03\x02\x02\x02]e\x07$\x02\x02^_\x07^\x02\x02_d\v\x02\x02\x02`a\x07$" + + "\x02\x02ad\x07$\x02\x02bd\n\x04\x02\x02c^\x03\x02\x02\x02c`\x03\x02\x02" + + "\x02cb\x03\x02\x02\x02dg\x03\x02\x02\x02ec\x03\x02\x02\x02ef\x03\x02\x02" + + "\x02fh\x03\x02\x02\x02ge\x03\x02\x02\x02hi\x07$\x02\x02i\b\x03\x02\x02" + + "\x02jr\x07)\x02\x02kl\x07^\x02\x02lq\v\x02\x02\x02mn\x07)\x02\x02nq\x07" + + ")\x02\x02oq\n\x05\x02\x02pk\x03\x02\x02\x02pm\x03\x02\x02\x02po\x03\x02" + + "\x02\x02qt\x03\x02\x02\x02rp\x03\x02\x02\x02rs\x03\x02\x02\x02su\x03\x02" + + "\x02\x02tr\x03\x02\x02\x02uv\x07)\x02\x02v\n\x03\x02\x02\x02wx\t\x06\x02" + + "\x02x\f\x03\x02\x02\x02y}\t\x07\x02\x02z|\t\b\x02\x02{z\x03\x02\x02\x02" + + "|\x7F\x03\x02\x02\x02}{\x03\x02\x02\x02}~\x03\x02\x02\x02~\x0E\x03\x02" + + "\x02\x02\x7F}\x03\x02\x02\x02\x80\x81\x07*\x02\x02\x81\x10\x03\x02\x02" + + "\x02\x82\x83\x07+\x02\x02\x83\x12\x03\x02\x02\x02\x84\x85\x07.\x02\x02" + + "\x85\x14\x03\x02\x02\x02\x86\x87\x07)\x02\x02\x87\x16\x03\x02\x02\x02" + + "\x88\x89\x07$\x02\x02\x89\x18\x03\x02\x02\x02\x8A\x8D\x05\x15\v\x02\x8B" + + "\x8D\x05\x17\f\x02\x8C\x8A\x03\x02\x02\x02\x8C\x8B\x03\x02\x02\x02\x8D" + + "\x1A\x03\x02\x02\x02\x8E\x8F\x07C\x02\x02\x8F\x90\x07P\x02\x02\x90\x91" + + "\x07F\x02\x02\x91\x1C\x03\x02\x02\x02\x92\x93\x07Q\x02\x02\x93\x94\x07" + + "T\x02\x02\x94\x1E\x03\x02\x02\x02\x95\x96\x07Z\x02\x02\x96\x97\x07Q\x02" + + "\x02\x97\x98\x07T\x02\x02\x98 \x03\x02\x02\x02\x99\x9A\x07P\x02\x02\x9A" + + "\x9B\x07Q\x02\x02\x9B\x9C\x07V\x02\x02\x9C\"\x03\x02\x02\x02\x9D\x9E\x07" + + ",\x02\x02\x9E$\x03\x02\x02\x02\x9F\xA0\x071\x02\x02\xA0&\x03\x02\x02\x02" + + "\xA1\xA2\x07\'\x02\x02\xA2(\x03\x02\x02\x02\xA3\xA4\x07-\x02\x02\xA4*" + + "\x03\x02\x02\x02\xA5\xA6\x07/\x02\x02\xA6,\x03\x02\x02\x02\xA7\xA8\x07" + + "?\x02\x02\xA8.\x03\x02\x02\x02\xA9\xAA\x07#\x02\x02\xAA\xAB\x07?\x02\x02" + + "\xAB0\x03\x02\x02\x02\xAC\xAD\x07@\x02\x02\xAD2\x03\x02\x02\x02\xAE\xAF" + + "\x07@\x02\x02\xAF\xB0\x07?\x02\x02\xB04\x03\x02\x02\x02\xB1\xB2\x07>\x02" + + "\x02\xB26\x03\x02\x02\x02\xB3\xB4\x07>\x02\x02\xB4\xB5\x07@\x02\x02\xB5" + + "8\x03\x02\x02\x02\xB6\xB7\x07>\x02\x02\xB7\xB8\x07?\x02\x02\xB8:\x03\x02" + + "\x02\x02\xB9\xBA\x07N\x02\x02\xBA\xBB\x07K\x02\x02\xBB\xBC\x07M\x02\x02" + + "\xBC\xBD\x07G\x02\x02\xBD<\x03\x02\x02\x02\xBE\xBF\x07G\x02\x02\xBF\xC0" + + "\x07Z\x02\x02\xC0\xC1\x07K\x02\x02\xC1\xC2\x07U\x02\x02\xC2\xC3\x07V\x02" + + "\x02\xC3\xC4\x07U\x02\x02\xC4>\x03\x02\x02\x02\xC5\xC6\x07K\x02\x02\xC6" + + "\xC7\x07P\x02\x02\xC7@\x03\x02\x02\x02\xC8\xC9\x07V\x02\x02\xC9\xCA\x07" + + "T\x02\x02\xCA\xCB\x07W\x02\x02\xCB\xCC\x07G\x02\x02\xCCB\x03\x02\x02\x02" + + "\xCD\xCE\x07H\x02\x02\xCE\xCF\x07C\x02\x02\xCF\xD0\x07N\x02\x02\xD0\xD1" + + "\x07U\x02\x02\xD1\xD2\x07G\x02\x02\xD2D\x03\x02\x02\x02\xD3\xD4\x05\x07" + + "\x04\x02\xD4F\x03\x02\x02\x02\xD5\xD6\x05\t\x05\x02\xD6H\x03\x02\x02\x02" + + "\xD7\xD9\x05\v\x06\x02\xD8\xD7\x03\x02\x02\x02\xD9\xDA\x03\x02\x02\x02" + + "\xDA\xD8\x03\x02\x02\x02\xDA\xDB\x03\x02\x02\x02\xDBJ\x03\x02\x02\x02" + + "\xDC\xDE\t\t\x02\x02\xDD\xDC\x03\x02\x02\x02\xDE\xDF\x03\x02\x02\x02\xDF" + + "\xDD\x03\x02\x02\x02\xDF\xE0\x03\x02\x02\x02\xE0L\x03\x02\x02\x02\xE1" + + "\xE3\t\x03\x02\x02\xE2\xE1\x03\x02\x02\x02\xE3\xE4\x03\x02\x02\x02\xE4" + + "\xE2\x03\x02\x02\x02\xE4\xE5\x03\x02\x02\x02\xE5N\x03\x02\x02\x02\xE6" + + "\xEA\t\x07\x02\x02\xE7\xE9\t\b\x02\x02\xE8\xE7\x03\x02\x02\x02\xE9\xEC" + + "\x03\x02\x02\x02\xEA\xE8\x03\x02\x02\x02\xEA\xEB\x03\x02\x02\x02\xEBP" + + "\x03\x02\x02\x02\xEC\xEA\x03\x02\x02\x02\x0F\x02T[cepr}\x8C\xDA\xDF\xE4" + + "\xEA\x03\b\x02\x02"; + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!CESQLParserLexer.__ATN) { + CESQLParserLexer.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(CESQLParserLexer._serializedATN)); + } + + return CESQLParserLexer.__ATN; + } + +} + diff --git a/packages/cloudevents/src/models/filters/sql/CESQLParserListener.ts b/packages/cloudevents/src/models/filters/sql/CESQLParserListener.ts new file mode 100644 index 000000000..5681215f3 --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql/CESQLParserListener.ts @@ -0,0 +1,357 @@ +// Generated from src/models/filters/sql/CESQLParser.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; + +import { FunctionInvocationExpressionContext } from "./CESQLParserParser"; +import { UnaryLogicExpressionContext } from "./CESQLParserParser"; +import { UnaryNumericExpressionContext } from "./CESQLParserParser"; +import { LikeExpressionContext } from "./CESQLParserParser"; +import { ExistsExpressionContext } from "./CESQLParserParser"; +import { InExpressionContext } from "./CESQLParserParser"; +import { BinaryMultiplicativeExpressionContext } from "./CESQLParserParser"; +import { BinaryAdditiveExpressionContext } from "./CESQLParserParser"; +import { BinaryComparisonExpressionContext } from "./CESQLParserParser"; +import { BinaryLogicExpressionContext } from "./CESQLParserParser"; +import { SubExpressionContext } from "./CESQLParserParser"; +import { AtomExpressionContext } from "./CESQLParserParser"; +import { BooleanAtomContext } from "./CESQLParserParser"; +import { IntegerAtomContext } from "./CESQLParserParser"; +import { StringAtomContext } from "./CESQLParserParser"; +import { IdentifierAtomContext } from "./CESQLParserParser"; +import { CesqlContext } from "./CESQLParserParser"; +import { ExpressionContext } from "./CESQLParserParser"; +import { AtomContext } from "./CESQLParserParser"; +import { IdentifierContext } from "./CESQLParserParser"; +import { FunctionIdentifierContext } from "./CESQLParserParser"; +import { BooleanLiteralContext } from "./CESQLParserParser"; +import { StringLiteralContext } from "./CESQLParserParser"; +import { IntegerLiteralContext } from "./CESQLParserParser"; +import { FunctionParameterListContext } from "./CESQLParserParser"; +import { SetExpressionContext } from "./CESQLParserParser"; + + +/** + * This interface defines a complete listener for a parse tree produced by + * `CESQLParserParser`. + */ +export interface CESQLParserListener extends ParseTreeListener { + /** + * Enter a parse tree produced by the `functionInvocationExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterFunctionInvocationExpression?: (ctx: FunctionInvocationExpressionContext) => void; + /** + * Exit a parse tree produced by the `functionInvocationExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitFunctionInvocationExpression?: (ctx: FunctionInvocationExpressionContext) => void; + + /** + * Enter a parse tree produced by the `unaryLogicExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterUnaryLogicExpression?: (ctx: UnaryLogicExpressionContext) => void; + /** + * Exit a parse tree produced by the `unaryLogicExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitUnaryLogicExpression?: (ctx: UnaryLogicExpressionContext) => void; + + /** + * Enter a parse tree produced by the `unaryNumericExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterUnaryNumericExpression?: (ctx: UnaryNumericExpressionContext) => void; + /** + * Exit a parse tree produced by the `unaryNumericExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitUnaryNumericExpression?: (ctx: UnaryNumericExpressionContext) => void; + + /** + * Enter a parse tree produced by the `likeExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterLikeExpression?: (ctx: LikeExpressionContext) => void; + /** + * Exit a parse tree produced by the `likeExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitLikeExpression?: (ctx: LikeExpressionContext) => void; + + /** + * Enter a parse tree produced by the `existsExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterExistsExpression?: (ctx: ExistsExpressionContext) => void; + /** + * Exit a parse tree produced by the `existsExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitExistsExpression?: (ctx: ExistsExpressionContext) => void; + + /** + * Enter a parse tree produced by the `inExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterInExpression?: (ctx: InExpressionContext) => void; + /** + * Exit a parse tree produced by the `inExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitInExpression?: (ctx: InExpressionContext) => void; + + /** + * Enter a parse tree produced by the `binaryMultiplicativeExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterBinaryMultiplicativeExpression?: (ctx: BinaryMultiplicativeExpressionContext) => void; + /** + * Exit a parse tree produced by the `binaryMultiplicativeExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitBinaryMultiplicativeExpression?: (ctx: BinaryMultiplicativeExpressionContext) => void; + + /** + * Enter a parse tree produced by the `binaryAdditiveExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterBinaryAdditiveExpression?: (ctx: BinaryAdditiveExpressionContext) => void; + /** + * Exit a parse tree produced by the `binaryAdditiveExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitBinaryAdditiveExpression?: (ctx: BinaryAdditiveExpressionContext) => void; + + /** + * Enter a parse tree produced by the `binaryComparisonExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterBinaryComparisonExpression?: (ctx: BinaryComparisonExpressionContext) => void; + /** + * Exit a parse tree produced by the `binaryComparisonExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitBinaryComparisonExpression?: (ctx: BinaryComparisonExpressionContext) => void; + + /** + * Enter a parse tree produced by the `binaryLogicExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterBinaryLogicExpression?: (ctx: BinaryLogicExpressionContext) => void; + /** + * Exit a parse tree produced by the `binaryLogicExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitBinaryLogicExpression?: (ctx: BinaryLogicExpressionContext) => void; + + /** + * Enter a parse tree produced by the `subExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterSubExpression?: (ctx: SubExpressionContext) => void; + /** + * Exit a parse tree produced by the `subExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitSubExpression?: (ctx: SubExpressionContext) => void; + + /** + * Enter a parse tree produced by the `atomExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterAtomExpression?: (ctx: AtomExpressionContext) => void; + /** + * Exit a parse tree produced by the `atomExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitAtomExpression?: (ctx: AtomExpressionContext) => void; + + /** + * Enter a parse tree produced by the `booleanAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + */ + enterBooleanAtom?: (ctx: BooleanAtomContext) => void; + /** + * Exit a parse tree produced by the `booleanAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + */ + exitBooleanAtom?: (ctx: BooleanAtomContext) => void; + + /** + * Enter a parse tree produced by the `integerAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + */ + enterIntegerAtom?: (ctx: IntegerAtomContext) => void; + /** + * Exit a parse tree produced by the `integerAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + */ + exitIntegerAtom?: (ctx: IntegerAtomContext) => void; + + /** + * Enter a parse tree produced by the `stringAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + */ + enterStringAtom?: (ctx: StringAtomContext) => void; + /** + * Exit a parse tree produced by the `stringAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + */ + exitStringAtom?: (ctx: StringAtomContext) => void; + + /** + * Enter a parse tree produced by the `identifierAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + */ + enterIdentifierAtom?: (ctx: IdentifierAtomContext) => void; + /** + * Exit a parse tree produced by the `identifierAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + */ + exitIdentifierAtom?: (ctx: IdentifierAtomContext) => void; + + /** + * Enter a parse tree produced by `CESQLParserParser.cesql`. + * @param ctx the parse tree + */ + enterCesql?: (ctx: CesqlContext) => void; + /** + * Exit a parse tree produced by `CESQLParserParser.cesql`. + * @param ctx the parse tree + */ + exitCesql?: (ctx: CesqlContext) => void; + + /** + * Enter a parse tree produced by `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + enterExpression?: (ctx: ExpressionContext) => void; + /** + * Exit a parse tree produced by `CESQLParserParser.expression`. + * @param ctx the parse tree + */ + exitExpression?: (ctx: ExpressionContext) => void; + + /** + * Enter a parse tree produced by `CESQLParserParser.atom`. + * @param ctx the parse tree + */ + enterAtom?: (ctx: AtomContext) => void; + /** + * Exit a parse tree produced by `CESQLParserParser.atom`. + * @param ctx the parse tree + */ + exitAtom?: (ctx: AtomContext) => void; + + /** + * Enter a parse tree produced by `CESQLParserParser.identifier`. + * @param ctx the parse tree + */ + enterIdentifier?: (ctx: IdentifierContext) => void; + /** + * Exit a parse tree produced by `CESQLParserParser.identifier`. + * @param ctx the parse tree + */ + exitIdentifier?: (ctx: IdentifierContext) => void; + + /** + * Enter a parse tree produced by `CESQLParserParser.functionIdentifier`. + * @param ctx the parse tree + */ + enterFunctionIdentifier?: (ctx: FunctionIdentifierContext) => void; + /** + * Exit a parse tree produced by `CESQLParserParser.functionIdentifier`. + * @param ctx the parse tree + */ + exitFunctionIdentifier?: (ctx: FunctionIdentifierContext) => void; + + /** + * Enter a parse tree produced by `CESQLParserParser.booleanLiteral`. + * @param ctx the parse tree + */ + enterBooleanLiteral?: (ctx: BooleanLiteralContext) => void; + /** + * Exit a parse tree produced by `CESQLParserParser.booleanLiteral`. + * @param ctx the parse tree + */ + exitBooleanLiteral?: (ctx: BooleanLiteralContext) => void; + + /** + * Enter a parse tree produced by `CESQLParserParser.stringLiteral`. + * @param ctx the parse tree + */ + enterStringLiteral?: (ctx: StringLiteralContext) => void; + /** + * Exit a parse tree produced by `CESQLParserParser.stringLiteral`. + * @param ctx the parse tree + */ + exitStringLiteral?: (ctx: StringLiteralContext) => void; + + /** + * Enter a parse tree produced by `CESQLParserParser.integerLiteral`. + * @param ctx the parse tree + */ + enterIntegerLiteral?: (ctx: IntegerLiteralContext) => void; + /** + * Exit a parse tree produced by `CESQLParserParser.integerLiteral`. + * @param ctx the parse tree + */ + exitIntegerLiteral?: (ctx: IntegerLiteralContext) => void; + + /** + * Enter a parse tree produced by `CESQLParserParser.functionParameterList`. + * @param ctx the parse tree + */ + enterFunctionParameterList?: (ctx: FunctionParameterListContext) => void; + /** + * Exit a parse tree produced by `CESQLParserParser.functionParameterList`. + * @param ctx the parse tree + */ + exitFunctionParameterList?: (ctx: FunctionParameterListContext) => void; + + /** + * Enter a parse tree produced by `CESQLParserParser.setExpression`. + * @param ctx the parse tree + */ + enterSetExpression?: (ctx: SetExpressionContext) => void; + /** + * Exit a parse tree produced by `CESQLParserParser.setExpression`. + * @param ctx the parse tree + */ + exitSetExpression?: (ctx: SetExpressionContext) => void; +} + diff --git a/packages/cloudevents/src/models/filters/sql/CESQLParserParser.ts b/packages/cloudevents/src/models/filters/sql/CESQLParserParser.ts new file mode 100644 index 000000000..cbb4b30c4 --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql/CESQLParserParser.ts @@ -0,0 +1,1677 @@ +// Generated from src/models/filters/sql/CESQLParser.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 { CESQLParserListener } from "./CESQLParserListener"; +import { CESQLParserVisitor } from "./CESQLParserVisitor"; + + +export class CESQLParserParser extends Parser { + public static readonly SPACE = 1; + public static readonly LR_BRACKET = 2; + public static readonly RR_BRACKET = 3; + public static readonly COMMA = 4; + public static readonly SINGLE_QUOTE_SYMB = 5; + public static readonly DOUBLE_QUOTE_SYMB = 6; + public static readonly AND = 7; + public static readonly OR = 8; + public static readonly XOR = 9; + public static readonly NOT = 10; + public static readonly STAR = 11; + public static readonly DIVIDE = 12; + public static readonly MODULE = 13; + public static readonly PLUS = 14; + public static readonly MINUS = 15; + public static readonly EQUAL = 16; + public static readonly NOT_EQUAL = 17; + public static readonly GREATER = 18; + public static readonly GREATER_OR_EQUAL = 19; + public static readonly LESS = 20; + public static readonly LESS_GREATER = 21; + public static readonly LESS_OR_EQUAL = 22; + public static readonly LIKE = 23; + public static readonly EXISTS = 24; + public static readonly IN = 25; + public static readonly TRUE = 26; + public static readonly FALSE = 27; + public static readonly DQUOTED_STRING_LITERAL = 28; + public static readonly SQUOTED_STRING_LITERAL = 29; + public static readonly INTEGER_LITERAL = 30; + public static readonly IDENTIFIER = 31; + public static readonly IDENTIFIER_WITH_NUMBER = 32; + public static readonly FUNCTION_IDENTIFIER_WITH_UNDERSCORE = 33; + public static readonly RULE_cesql = 0; + public static readonly RULE_expression = 1; + public static readonly RULE_atom = 2; + public static readonly RULE_identifier = 3; + public static readonly RULE_functionIdentifier = 4; + public static readonly RULE_booleanLiteral = 5; + public static readonly RULE_stringLiteral = 6; + public static readonly RULE_integerLiteral = 7; + public static readonly RULE_functionParameterList = 8; + public static readonly RULE_setExpression = 9; + // tslint:disable:no-trailing-whitespace + public static readonly ruleNames: string[] = [ + "cesql", "expression", "atom", "identifier", "functionIdentifier", "booleanLiteral", + "stringLiteral", "integerLiteral", "functionParameterList", "setExpression", + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, undefined, "'('", "')'", "','", "'''", "'\"'", "'AND'", "'OR'", + "'XOR'", "'NOT'", "'*'", "'/'", "'%'", "'+'", "'-'", "'='", "'!='", "'>'", + "'>='", "'<'", "'<>'", "'<='", "'LIKE'", "'EXISTS'", "'IN'", "'TRUE'", + "'FALSE'", + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, "SPACE", "LR_BRACKET", "RR_BRACKET", "COMMA", "SINGLE_QUOTE_SYMB", + "DOUBLE_QUOTE_SYMB", "AND", "OR", "XOR", "NOT", "STAR", "DIVIDE", "MODULE", + "PLUS", "MINUS", "EQUAL", "NOT_EQUAL", "GREATER", "GREATER_OR_EQUAL", + "LESS", "LESS_GREATER", "LESS_OR_EQUAL", "LIKE", "EXISTS", "IN", "TRUE", + "FALSE", "DQUOTED_STRING_LITERAL", "SQUOTED_STRING_LITERAL", "INTEGER_LITERAL", + "IDENTIFIER", "IDENTIFIER_WITH_NUMBER", "FUNCTION_IDENTIFIER_WITH_UNDERSCORE", + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(CESQLParserParser._LITERAL_NAMES, CESQLParserParser._SYMBOLIC_NAMES, []); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return CESQLParserParser.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + // @Override + public get grammarFileName(): string { return "CESQLParser.g4"; } + + // @Override + public get ruleNames(): string[] { return CESQLParserParser.ruleNames; } + + // @Override + public get serializedATN(): string { return CESQLParserParser._serializedATN; } + + protected createFailedPredicateException(predicate?: string, message?: string): FailedPredicateException { + return new FailedPredicateException(this, predicate, message); + } + + constructor(input: TokenStream) { + super(input); + this._interp = new ParserATNSimulator(CESQLParserParser._ATN, this); + } + // @RuleVersion(0) + public cesql(): CesqlContext { + let _localctx: CesqlContext = new CesqlContext(this._ctx, this.state); + this.enterRule(_localctx, 0, CESQLParserParser.RULE_cesql); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 20; + this.expression(0); + this.state = 21; + this.match(CESQLParserParser.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; + } + + public expression(): ExpressionContext; + public expression(_p: number): ExpressionContext; + // @RuleVersion(0) + public expression(_p?: number): ExpressionContext { + if (_p === undefined) { + _p = 0; + } + + let _parentctx: ParserRuleContext = this._ctx; + let _parentState: number = this.state; + let _localctx: ExpressionContext = new ExpressionContext(this._ctx, _parentState); + let _prevctx: ExpressionContext = _localctx; + let _startState: number = 2; + this.enterRecursionRule(_localctx, 2, CESQLParserParser.RULE_expression, _p); + let _la: number; + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 38; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 0, this._ctx) ) { + case 1: + { + _localctx = new FunctionInvocationExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + + this.state = 24; + this.functionIdentifier(); + this.state = 25; + this.functionParameterList(); + } + break; + + case 2: + { + _localctx = new UnaryLogicExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 27; + this.match(CESQLParserParser.NOT); + this.state = 28; + this.expression(11); + } + break; + + case 3: + { + _localctx = new UnaryNumericExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 29; + this.match(CESQLParserParser.MINUS); + this.state = 30; + this.expression(10); + } + break; + + case 4: + { + _localctx = new ExistsExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 31; + this.match(CESQLParserParser.EXISTS); + this.state = 32; + this.identifier(); + } + break; + + case 5: + { + _localctx = new SubExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 33; + this.match(CESQLParserParser.LR_BRACKET); + this.state = 34; + this.expression(0); + this.state = 35; + this.match(CESQLParserParser.RR_BRACKET); + } + break; + + case 6: + { + _localctx = new AtomExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 37; + this.atom(); + } + break; + } + this._ctx._stop = this._input.tryLT(-1); + this.state = 66; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 4, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + if (this._parseListeners != null) { + this.triggerExitRuleEvent(); + } + _prevctx = _localctx; + { + this.state = 64; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 3, this._ctx) ) { + case 1: + { + _localctx = new BinaryMultiplicativeExpressionContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, CESQLParserParser.RULE_expression); + this.state = 40; + if (!(this.precpred(this._ctx, 6))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 6)"); + } + this.state = 41; + _la = this._input.LA(1); + if (!((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << CESQLParserParser.STAR) | (1 << CESQLParserParser.DIVIDE) | (1 << CESQLParserParser.MODULE))) !== 0))) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 42; + this.expression(7); + } + break; + + case 2: + { + _localctx = new BinaryAdditiveExpressionContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, CESQLParserParser.RULE_expression); + this.state = 43; + if (!(this.precpred(this._ctx, 5))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 5)"); + } + this.state = 44; + _la = this._input.LA(1); + if (!(_la === CESQLParserParser.PLUS || _la === CESQLParserParser.MINUS)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 45; + this.expression(6); + } + break; + + case 3: + { + _localctx = new BinaryComparisonExpressionContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, CESQLParserParser.RULE_expression); + this.state = 46; + if (!(this.precpred(this._ctx, 4))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 4)"); + } + this.state = 47; + _la = this._input.LA(1); + if (!((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << CESQLParserParser.EQUAL) | (1 << CESQLParserParser.NOT_EQUAL) | (1 << CESQLParserParser.GREATER) | (1 << CESQLParserParser.GREATER_OR_EQUAL) | (1 << CESQLParserParser.LESS) | (1 << CESQLParserParser.LESS_GREATER) | (1 << CESQLParserParser.LESS_OR_EQUAL))) !== 0))) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 48; + this.expression(5); + } + break; + + case 4: + { + _localctx = new BinaryLogicExpressionContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, CESQLParserParser.RULE_expression); + this.state = 49; + if (!(this.precpred(this._ctx, 3))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 3)"); + } + this.state = 50; + _la = this._input.LA(1); + if (!((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << CESQLParserParser.AND) | (1 << CESQLParserParser.OR) | (1 << CESQLParserParser.XOR))) !== 0))) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 51; + this.expression(3); + } + break; + + case 5: + { + _localctx = new LikeExpressionContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, CESQLParserParser.RULE_expression); + this.state = 52; + if (!(this.precpred(this._ctx, 9))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 9)"); + } + this.state = 54; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === CESQLParserParser.NOT) { + { + this.state = 53; + this.match(CESQLParserParser.NOT); + } + } + + this.state = 56; + this.match(CESQLParserParser.LIKE); + this.state = 57; + this.stringLiteral(); + } + break; + + case 6: + { + _localctx = new InExpressionContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, CESQLParserParser.RULE_expression); + this.state = 58; + if (!(this.precpred(this._ctx, 7))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 7)"); + } + this.state = 60; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === CESQLParserParser.NOT) { + { + this.state = 59; + this.match(CESQLParserParser.NOT); + } + } + + this.state = 62; + this.match(CESQLParserParser.IN); + this.state = 63; + this.setExpression(); + } + break; + } + } + } + this.state = 68; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 4, 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.unrollRecursionContexts(_parentctx); + } + return _localctx; + } + // @RuleVersion(0) + public atom(): AtomContext { + let _localctx: AtomContext = new AtomContext(this._ctx, this.state); + this.enterRule(_localctx, 4, CESQLParserParser.RULE_atom); + try { + this.state = 73; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case CESQLParserParser.TRUE: + case CESQLParserParser.FALSE: + _localctx = new BooleanAtomContext(_localctx); + this.enterOuterAlt(_localctx, 1); + { + this.state = 69; + this.booleanLiteral(); + } + break; + case CESQLParserParser.INTEGER_LITERAL: + _localctx = new IntegerAtomContext(_localctx); + this.enterOuterAlt(_localctx, 2); + { + this.state = 70; + this.integerLiteral(); + } + break; + case CESQLParserParser.DQUOTED_STRING_LITERAL: + case CESQLParserParser.SQUOTED_STRING_LITERAL: + _localctx = new StringAtomContext(_localctx); + this.enterOuterAlt(_localctx, 3); + { + this.state = 71; + this.stringLiteral(); + } + break; + case CESQLParserParser.IDENTIFIER: + case CESQLParserParser.IDENTIFIER_WITH_NUMBER: + _localctx = new IdentifierAtomContext(_localctx); + this.enterOuterAlt(_localctx, 4); + { + this.state = 72; + this.identifier(); + } + 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 identifier(): IdentifierContext { + let _localctx: IdentifierContext = new IdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 6, CESQLParserParser.RULE_identifier); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 75; + _la = this._input.LA(1); + if (!(_la === CESQLParserParser.IDENTIFIER || _la === CESQLParserParser.IDENTIFIER_WITH_NUMBER)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + 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 functionIdentifier(): FunctionIdentifierContext { + let _localctx: FunctionIdentifierContext = new FunctionIdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 8, CESQLParserParser.RULE_functionIdentifier); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 77; + _la = this._input.LA(1); + if (!(_la === CESQLParserParser.IDENTIFIER || _la === CESQLParserParser.FUNCTION_IDENTIFIER_WITH_UNDERSCORE)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + 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 booleanLiteral(): BooleanLiteralContext { + let _localctx: BooleanLiteralContext = new BooleanLiteralContext(this._ctx, this.state); + this.enterRule(_localctx, 10, CESQLParserParser.RULE_booleanLiteral); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 79; + _la = this._input.LA(1); + if (!(_la === CESQLParserParser.TRUE || _la === CESQLParserParser.FALSE)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + 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 stringLiteral(): StringLiteralContext { + let _localctx: StringLiteralContext = new StringLiteralContext(this._ctx, this.state); + this.enterRule(_localctx, 12, CESQLParserParser.RULE_stringLiteral); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 81; + _la = this._input.LA(1); + if (!(_la === CESQLParserParser.DQUOTED_STRING_LITERAL || _la === CESQLParserParser.SQUOTED_STRING_LITERAL)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + 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 integerLiteral(): IntegerLiteralContext { + let _localctx: IntegerLiteralContext = new IntegerLiteralContext(this._ctx, this.state); + this.enterRule(_localctx, 14, CESQLParserParser.RULE_integerLiteral); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 83; + this.match(CESQLParserParser.INTEGER_LITERAL); + } + } + 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 functionParameterList(): FunctionParameterListContext { + let _localctx: FunctionParameterListContext = new FunctionParameterListContext(this._ctx, this.state); + this.enterRule(_localctx, 16, CESQLParserParser.RULE_functionParameterList); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 85; + this.match(CESQLParserParser.LR_BRACKET); + this.state = 94; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (((((_la - 2)) & ~0x1F) === 0 && ((1 << (_la - 2)) & ((1 << (CESQLParserParser.LR_BRACKET - 2)) | (1 << (CESQLParserParser.NOT - 2)) | (1 << (CESQLParserParser.MINUS - 2)) | (1 << (CESQLParserParser.EXISTS - 2)) | (1 << (CESQLParserParser.TRUE - 2)) | (1 << (CESQLParserParser.FALSE - 2)) | (1 << (CESQLParserParser.DQUOTED_STRING_LITERAL - 2)) | (1 << (CESQLParserParser.SQUOTED_STRING_LITERAL - 2)) | (1 << (CESQLParserParser.INTEGER_LITERAL - 2)) | (1 << (CESQLParserParser.IDENTIFIER - 2)) | (1 << (CESQLParserParser.IDENTIFIER_WITH_NUMBER - 2)) | (1 << (CESQLParserParser.FUNCTION_IDENTIFIER_WITH_UNDERSCORE - 2)))) !== 0)) { + { + this.state = 86; + this.expression(0); + this.state = 91; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === CESQLParserParser.COMMA) { + { + { + this.state = 87; + this.match(CESQLParserParser.COMMA); + this.state = 88; + this.expression(0); + } + } + this.state = 93; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + } + } + + this.state = 96; + this.match(CESQLParserParser.RR_BRACKET); + } + } + 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 setExpression(): SetExpressionContext { + let _localctx: SetExpressionContext = new SetExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 18, CESQLParserParser.RULE_setExpression); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 98; + this.match(CESQLParserParser.LR_BRACKET); + this.state = 99; + this.expression(0); + this.state = 104; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === CESQLParserParser.COMMA) { + { + { + this.state = 100; + this.match(CESQLParserParser.COMMA); + this.state = 101; + this.expression(0); + } + } + this.state = 106; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 107; + this.match(CESQLParserParser.RR_BRACKET); + } + } + 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 sempred(_localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { + switch (ruleIndex) { + case 1: + return this.expression_sempred(_localctx as ExpressionContext, predIndex); + } + return true; + } + private expression_sempred(_localctx: ExpressionContext, predIndex: number): boolean { + switch (predIndex) { + case 0: + return this.precpred(this._ctx, 6); + + case 1: + return this.precpred(this._ctx, 5); + + case 2: + return this.precpred(this._ctx, 4); + + case 3: + return this.precpred(this._ctx, 3); + + case 4: + return this.precpred(this._ctx, 9); + + case 5: + return this.precpred(this._ctx, 7); + } + return true; + } + + public static readonly _serializedATN: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03#p\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\x03\x02\x03\x02\x03\x02\x03\x03" + + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x05\x03)\n\x03\x03\x03\x03\x03" + + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x03\x03\x03\x03\x03\x03\x05\x039\n\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x05\x03?\n\x03\x03\x03\x03\x03\x07\x03C\n\x03\f\x03\x0E\x03F\v\x03\x03" + + "\x04\x03\x04\x03\x04\x03\x04\x05\x04L\n\x04\x03\x05\x03\x05\x03\x06\x03" + + "\x06\x03\x07\x03\x07\x03\b\x03\b\x03\t\x03\t\x03\n\x03\n\x03\n\x03\n\x07" + + "\n\\\n\n\f\n\x0E\n_\v\n\x05\na\n\n\x03\n\x03\n\x03\v\x03\v\x03\v\x03\v" + + "\x07\vi\n\v\f\v\x0E\vl\v\v\x03\v\x03\v\x03\v\x02\x02\x03\x04\f\x02\x02" + + "\x04\x02\x06\x02\b\x02\n\x02\f\x02\x0E\x02\x10\x02\x12\x02\x14\x02\x02" + + "\n\x03\x02\r\x0F\x03\x02\x10\x11\x03\x02\x12\x18\x03\x02\t\v\x03\x02!" + + "\"\x04\x02!!##\x03\x02\x1C\x1D\x03\x02\x1E\x1F\x02x\x02\x16\x03\x02\x02" + + "\x02\x04(\x03\x02\x02\x02\x06K\x03\x02\x02\x02\bM\x03\x02\x02\x02\nO\x03" + + "\x02\x02\x02\fQ\x03\x02\x02\x02\x0ES\x03\x02\x02\x02\x10U\x03\x02\x02" + + "\x02\x12W\x03\x02\x02\x02\x14d\x03\x02\x02\x02\x16\x17\x05\x04\x03\x02" + + "\x17\x18\x07\x02\x02\x03\x18\x03\x03\x02\x02\x02\x19\x1A\b\x03\x01\x02" + + "\x1A\x1B\x05\n\x06\x02\x1B\x1C\x05\x12\n\x02\x1C)\x03\x02\x02\x02\x1D" + + "\x1E\x07\f\x02\x02\x1E)\x05\x04\x03\r\x1F \x07\x11\x02\x02 )\x05\x04\x03" + + "\f!\"\x07\x1A\x02\x02\")\x05\b\x05\x02#$\x07\x04\x02\x02$%\x05\x04\x03" + + "\x02%&\x07\x05\x02\x02&)\x03\x02\x02\x02\')\x05\x06\x04\x02(\x19\x03\x02" + + "\x02\x02(\x1D\x03\x02\x02\x02(\x1F\x03\x02\x02\x02(!\x03\x02\x02\x02(" + + "#\x03\x02\x02\x02(\'\x03\x02\x02\x02)D\x03\x02\x02\x02*+\f\b\x02\x02+" + + ",\t\x02\x02\x02,C\x05\x04\x03\t-.\f\x07\x02\x02./\t\x03\x02\x02/C\x05" + + "\x04\x03\b01\f\x06\x02\x0212\t\x04\x02\x022C\x05\x04\x03\x0734\f\x05\x02" + + "\x0245\t\x05\x02\x025C\x05\x04\x03\x0568\f\v\x02\x0279\x07\f\x02\x028" + + "7\x03\x02\x02\x0289\x03\x02\x02\x029:\x03\x02\x02\x02:;\x07\x19\x02\x02" + + ";C\x05\x0E\b\x02<>\f\t\x02\x02=?\x07\f\x02\x02>=\x03\x02\x02\x02>?\x03" + + "\x02\x02\x02?@\x03\x02\x02\x02@A\x07\x1B\x02\x02AC\x05\x14\v\x02B*\x03" + + "\x02\x02\x02B-\x03\x02\x02\x02B0\x03\x02\x02\x02B3\x03\x02\x02\x02B6\x03" + + "\x02\x02\x02B<\x03\x02\x02\x02CF\x03\x02\x02\x02DB\x03\x02\x02\x02DE\x03" + + "\x02\x02\x02E\x05\x03\x02\x02\x02FD\x03\x02\x02\x02GL\x05\f\x07\x02HL" + + "\x05\x10\t\x02IL\x05\x0E\b\x02JL\x05\b\x05\x02KG\x03\x02\x02\x02KH\x03" + + "\x02\x02\x02KI\x03\x02\x02\x02KJ\x03\x02\x02\x02L\x07\x03\x02\x02\x02" + + "MN\t\x06\x02\x02N\t\x03\x02\x02\x02OP\t\x07\x02\x02P\v\x03\x02\x02\x02" + + "QR\t\b\x02\x02R\r\x03\x02\x02\x02ST\t\t\x02\x02T\x0F\x03\x02\x02\x02U" + + "V\x07 \x02\x02V\x11\x03\x02\x02\x02W`\x07\x04\x02\x02X]\x05\x04\x03\x02" + + "YZ\x07\x06\x02\x02Z\\\x05\x04\x03\x02[Y\x03\x02\x02\x02\\_\x03\x02\x02" + + "\x02][\x03\x02\x02\x02]^\x03\x02\x02\x02^a\x03\x02\x02\x02_]\x03\x02\x02" + + "\x02`X\x03\x02\x02\x02`a\x03\x02\x02\x02ab\x03\x02\x02\x02bc\x07\x05\x02" + + "\x02c\x13\x03\x02\x02\x02de\x07\x04\x02\x02ej\x05\x04\x03\x02fg\x07\x06" + + "\x02\x02gi\x05\x04\x03\x02hf\x03\x02\x02\x02il\x03\x02\x02\x02jh\x03\x02" + + "\x02\x02jk\x03\x02\x02\x02km\x03\x02\x02\x02lj\x03\x02\x02\x02mn\x07\x05" + + "\x02\x02n\x15\x03\x02\x02\x02\v(8>BDK]`j"; + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!CESQLParserParser.__ATN) { + CESQLParserParser.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(CESQLParserParser._serializedATN)); + } + + return CESQLParserParser.__ATN; + } + +} + +export class CesqlContext extends ParserRuleContext { + public expression(): ExpressionContext { + return this.getRuleContext(0, ExpressionContext); + } + public EOF(): TerminalNode { return this.getToken(CESQLParserParser.EOF, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return CESQLParserParser.RULE_cesql; } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterCesql) { + listener.enterCesql(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitCesql) { + listener.exitCesql(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitCesql) { + return visitor.visitCesql(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class ExpressionContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return CESQLParserParser.RULE_expression; } + public copyFrom(ctx: ExpressionContext): void { + super.copyFrom(ctx); + } +} +export class FunctionInvocationExpressionContext extends ExpressionContext { + public functionIdentifier(): FunctionIdentifierContext { + return this.getRuleContext(0, FunctionIdentifierContext); + } + public functionParameterList(): FunctionParameterListContext { + return this.getRuleContext(0, FunctionParameterListContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterFunctionInvocationExpression) { + listener.enterFunctionInvocationExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitFunctionInvocationExpression) { + listener.exitFunctionInvocationExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitFunctionInvocationExpression) { + return visitor.visitFunctionInvocationExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class UnaryLogicExpressionContext extends ExpressionContext { + public NOT(): TerminalNode { return this.getToken(CESQLParserParser.NOT, 0); } + public expression(): ExpressionContext { + return this.getRuleContext(0, ExpressionContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterUnaryLogicExpression) { + listener.enterUnaryLogicExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitUnaryLogicExpression) { + listener.exitUnaryLogicExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitUnaryLogicExpression) { + return visitor.visitUnaryLogicExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class UnaryNumericExpressionContext extends ExpressionContext { + public MINUS(): TerminalNode { return this.getToken(CESQLParserParser.MINUS, 0); } + public expression(): ExpressionContext { + return this.getRuleContext(0, ExpressionContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterUnaryNumericExpression) { + listener.enterUnaryNumericExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitUnaryNumericExpression) { + listener.exitUnaryNumericExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitUnaryNumericExpression) { + return visitor.visitUnaryNumericExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class LikeExpressionContext extends ExpressionContext { + public expression(): ExpressionContext { + return this.getRuleContext(0, ExpressionContext); + } + public LIKE(): TerminalNode { return this.getToken(CESQLParserParser.LIKE, 0); } + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext); + } + public NOT(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.NOT, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterLikeExpression) { + listener.enterLikeExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitLikeExpression) { + listener.exitLikeExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitLikeExpression) { + return visitor.visitLikeExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class ExistsExpressionContext extends ExpressionContext { + public EXISTS(): TerminalNode { return this.getToken(CESQLParserParser.EXISTS, 0); } + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterExistsExpression) { + listener.enterExistsExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitExistsExpression) { + listener.exitExistsExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitExistsExpression) { + return visitor.visitExistsExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class InExpressionContext extends ExpressionContext { + public expression(): ExpressionContext { + return this.getRuleContext(0, ExpressionContext); + } + public IN(): TerminalNode { return this.getToken(CESQLParserParser.IN, 0); } + public setExpression(): SetExpressionContext { + return this.getRuleContext(0, SetExpressionContext); + } + public NOT(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.NOT, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterInExpression) { + listener.enterInExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitInExpression) { + listener.exitInExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitInExpression) { + return visitor.visitInExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class BinaryMultiplicativeExpressionContext extends ExpressionContext { + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public STAR(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.STAR, 0); } + public DIVIDE(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.DIVIDE, 0); } + public MODULE(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.MODULE, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterBinaryMultiplicativeExpression) { + listener.enterBinaryMultiplicativeExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitBinaryMultiplicativeExpression) { + listener.exitBinaryMultiplicativeExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitBinaryMultiplicativeExpression) { + return visitor.visitBinaryMultiplicativeExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class BinaryAdditiveExpressionContext extends ExpressionContext { + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public PLUS(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.PLUS, 0); } + public MINUS(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.MINUS, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterBinaryAdditiveExpression) { + listener.enterBinaryAdditiveExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitBinaryAdditiveExpression) { + listener.exitBinaryAdditiveExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitBinaryAdditiveExpression) { + return visitor.visitBinaryAdditiveExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class BinaryComparisonExpressionContext extends ExpressionContext { + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public EQUAL(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.EQUAL, 0); } + public NOT_EQUAL(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.NOT_EQUAL, 0); } + public LESS_GREATER(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.LESS_GREATER, 0); } + public GREATER_OR_EQUAL(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.GREATER_OR_EQUAL, 0); } + public LESS_OR_EQUAL(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.LESS_OR_EQUAL, 0); } + public LESS(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.LESS, 0); } + public GREATER(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.GREATER, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterBinaryComparisonExpression) { + listener.enterBinaryComparisonExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitBinaryComparisonExpression) { + listener.exitBinaryComparisonExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitBinaryComparisonExpression) { + return visitor.visitBinaryComparisonExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class BinaryLogicExpressionContext extends ExpressionContext { + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public AND(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.AND, 0); } + public OR(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.OR, 0); } + public XOR(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.XOR, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterBinaryLogicExpression) { + listener.enterBinaryLogicExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitBinaryLogicExpression) { + listener.exitBinaryLogicExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitBinaryLogicExpression) { + return visitor.visitBinaryLogicExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class SubExpressionContext extends ExpressionContext { + public LR_BRACKET(): TerminalNode { return this.getToken(CESQLParserParser.LR_BRACKET, 0); } + public expression(): ExpressionContext { + return this.getRuleContext(0, ExpressionContext); + } + public RR_BRACKET(): TerminalNode { return this.getToken(CESQLParserParser.RR_BRACKET, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterSubExpression) { + listener.enterSubExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitSubExpression) { + listener.exitSubExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitSubExpression) { + return visitor.visitSubExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class AtomExpressionContext extends ExpressionContext { + public atom(): AtomContext { + return this.getRuleContext(0, AtomContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterAtomExpression) { + listener.enterAtomExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitAtomExpression) { + listener.exitAtomExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitAtomExpression) { + return visitor.visitAtomExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class AtomContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return CESQLParserParser.RULE_atom; } + public copyFrom(ctx: AtomContext): void { + super.copyFrom(ctx); + } +} +export class BooleanAtomContext extends AtomContext { + public booleanLiteral(): BooleanLiteralContext { + return this.getRuleContext(0, BooleanLiteralContext); + } + constructor(ctx: AtomContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterBooleanAtom) { + listener.enterBooleanAtom(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitBooleanAtom) { + listener.exitBooleanAtom(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitBooleanAtom) { + return visitor.visitBooleanAtom(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class IntegerAtomContext extends AtomContext { + public integerLiteral(): IntegerLiteralContext { + return this.getRuleContext(0, IntegerLiteralContext); + } + constructor(ctx: AtomContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterIntegerAtom) { + listener.enterIntegerAtom(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitIntegerAtom) { + listener.exitIntegerAtom(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitIntegerAtom) { + return visitor.visitIntegerAtom(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class StringAtomContext extends AtomContext { + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext); + } + constructor(ctx: AtomContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterStringAtom) { + listener.enterStringAtom(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitStringAtom) { + listener.exitStringAtom(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitStringAtom) { + return visitor.visitStringAtom(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class IdentifierAtomContext extends AtomContext { + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); + } + constructor(ctx: AtomContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterIdentifierAtom) { + listener.enterIdentifierAtom(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitIdentifierAtom) { + listener.exitIdentifierAtom(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitIdentifierAtom) { + return visitor.visitIdentifierAtom(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class IdentifierContext extends ParserRuleContext { + public IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.IDENTIFIER, 0); } + public IDENTIFIER_WITH_NUMBER(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.IDENTIFIER_WITH_NUMBER, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return CESQLParserParser.RULE_identifier; } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterIdentifier) { + listener.enterIdentifier(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitIdentifier) { + listener.exitIdentifier(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitIdentifier) { + return visitor.visitIdentifier(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class FunctionIdentifierContext extends ParserRuleContext { + public IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.IDENTIFIER, 0); } + public FUNCTION_IDENTIFIER_WITH_UNDERSCORE(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.FUNCTION_IDENTIFIER_WITH_UNDERSCORE, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return CESQLParserParser.RULE_functionIdentifier; } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterFunctionIdentifier) { + listener.enterFunctionIdentifier(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitFunctionIdentifier) { + listener.exitFunctionIdentifier(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitFunctionIdentifier) { + return visitor.visitFunctionIdentifier(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class BooleanLiteralContext extends ParserRuleContext { + public TRUE(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.TRUE, 0); } + public FALSE(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.FALSE, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return CESQLParserParser.RULE_booleanLiteral; } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterBooleanLiteral) { + listener.enterBooleanLiteral(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitBooleanLiteral) { + listener.exitBooleanLiteral(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitBooleanLiteral) { + return visitor.visitBooleanLiteral(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class StringLiteralContext extends ParserRuleContext { + public DQUOTED_STRING_LITERAL(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.DQUOTED_STRING_LITERAL, 0); } + public SQUOTED_STRING_LITERAL(): TerminalNode | undefined { return this.tryGetToken(CESQLParserParser.SQUOTED_STRING_LITERAL, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return CESQLParserParser.RULE_stringLiteral; } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterStringLiteral) { + listener.enterStringLiteral(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitStringLiteral) { + listener.exitStringLiteral(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitStringLiteral) { + return visitor.visitStringLiteral(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class IntegerLiteralContext extends ParserRuleContext { + public INTEGER_LITERAL(): TerminalNode { return this.getToken(CESQLParserParser.INTEGER_LITERAL, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return CESQLParserParser.RULE_integerLiteral; } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterIntegerLiteral) { + listener.enterIntegerLiteral(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitIntegerLiteral) { + listener.exitIntegerLiteral(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitIntegerLiteral) { + return visitor.visitIntegerLiteral(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class FunctionParameterListContext extends ParserRuleContext { + public LR_BRACKET(): TerminalNode { return this.getToken(CESQLParserParser.LR_BRACKET, 0); } + public RR_BRACKET(): TerminalNode { return this.getToken(CESQLParserParser.RR_BRACKET, 0); } + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(CESQLParserParser.COMMA); + } else { + return this.getToken(CESQLParserParser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return CESQLParserParser.RULE_functionParameterList; } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterFunctionParameterList) { + listener.enterFunctionParameterList(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitFunctionParameterList) { + listener.exitFunctionParameterList(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitFunctionParameterList) { + return visitor.visitFunctionParameterList(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class SetExpressionContext extends ParserRuleContext { + public LR_BRACKET(): TerminalNode { return this.getToken(CESQLParserParser.LR_BRACKET, 0); } + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public RR_BRACKET(): TerminalNode { return this.getToken(CESQLParserParser.RR_BRACKET, 0); } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(CESQLParserParser.COMMA); + } else { + return this.getToken(CESQLParserParser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return CESQLParserParser.RULE_setExpression; } + // @Override + public enterRule(listener: CESQLParserListener): void { + if (listener.enterSetExpression) { + listener.enterSetExpression(this); + } + } + // @Override + public exitRule(listener: CESQLParserListener): void { + if (listener.exitSetExpression) { + listener.exitSetExpression(this); + } + } + // @Override + public accept(visitor: CESQLParserVisitor): Result { + if (visitor.visitSetExpression) { + return visitor.visitSetExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} + + diff --git a/packages/cloudevents/src/models/filters/sql/CESQLParserVisitor.ts b/packages/cloudevents/src/models/filters/sql/CESQLParserVisitor.ts new file mode 100644 index 000000000..bc1edb0ba --- /dev/null +++ b/packages/cloudevents/src/models/filters/sql/CESQLParserVisitor.ts @@ -0,0 +1,240 @@ +// Generated from src/models/filters/sql/CESQLParser.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor"; + +import { FunctionInvocationExpressionContext } from "./CESQLParserParser"; +import { UnaryLogicExpressionContext } from "./CESQLParserParser"; +import { UnaryNumericExpressionContext } from "./CESQLParserParser"; +import { LikeExpressionContext } from "./CESQLParserParser"; +import { ExistsExpressionContext } from "./CESQLParserParser"; +import { InExpressionContext } from "./CESQLParserParser"; +import { BinaryMultiplicativeExpressionContext } from "./CESQLParserParser"; +import { BinaryAdditiveExpressionContext } from "./CESQLParserParser"; +import { BinaryComparisonExpressionContext } from "./CESQLParserParser"; +import { BinaryLogicExpressionContext } from "./CESQLParserParser"; +import { SubExpressionContext } from "./CESQLParserParser"; +import { AtomExpressionContext } from "./CESQLParserParser"; +import { BooleanAtomContext } from "./CESQLParserParser"; +import { IntegerAtomContext } from "./CESQLParserParser"; +import { StringAtomContext } from "./CESQLParserParser"; +import { IdentifierAtomContext } from "./CESQLParserParser"; +import { CesqlContext } from "./CESQLParserParser"; +import { ExpressionContext } from "./CESQLParserParser"; +import { AtomContext } from "./CESQLParserParser"; +import { IdentifierContext } from "./CESQLParserParser"; +import { FunctionIdentifierContext } from "./CESQLParserParser"; +import { BooleanLiteralContext } from "./CESQLParserParser"; +import { StringLiteralContext } from "./CESQLParserParser"; +import { IntegerLiteralContext } from "./CESQLParserParser"; +import { FunctionParameterListContext } from "./CESQLParserParser"; +import { SetExpressionContext } from "./CESQLParserParser"; + + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by `CESQLParserParser`. + * + * @param The return type of the visit operation. Use `void` for + * operations with no return type. + */ +export interface CESQLParserVisitor extends ParseTreeVisitor { + /** + * Visit a parse tree produced by the `functionInvocationExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitFunctionInvocationExpression?: (ctx: FunctionInvocationExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `unaryLogicExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitUnaryLogicExpression?: (ctx: UnaryLogicExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `unaryNumericExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitUnaryNumericExpression?: (ctx: UnaryNumericExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `likeExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitLikeExpression?: (ctx: LikeExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `existsExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitExistsExpression?: (ctx: ExistsExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `inExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitInExpression?: (ctx: InExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `binaryMultiplicativeExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitBinaryMultiplicativeExpression?: (ctx: BinaryMultiplicativeExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `binaryAdditiveExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitBinaryAdditiveExpression?: (ctx: BinaryAdditiveExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `binaryComparisonExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitBinaryComparisonExpression?: (ctx: BinaryComparisonExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `binaryLogicExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitBinaryLogicExpression?: (ctx: BinaryLogicExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `subExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitSubExpression?: (ctx: SubExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `atomExpression` + * labeled alternative in `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitAtomExpression?: (ctx: AtomExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `booleanAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + * @return the visitor result + */ + visitBooleanAtom?: (ctx: BooleanAtomContext) => Result; + + /** + * Visit a parse tree produced by the `integerAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + * @return the visitor result + */ + visitIntegerAtom?: (ctx: IntegerAtomContext) => Result; + + /** + * Visit a parse tree produced by the `stringAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + * @return the visitor result + */ + visitStringAtom?: (ctx: StringAtomContext) => Result; + + /** + * Visit a parse tree produced by the `identifierAtom` + * labeled alternative in `CESQLParserParser.atom`. + * @param ctx the parse tree + * @return the visitor result + */ + visitIdentifierAtom?: (ctx: IdentifierAtomContext) => Result; + + /** + * Visit a parse tree produced by `CESQLParserParser.cesql`. + * @param ctx the parse tree + * @return the visitor result + */ + visitCesql?: (ctx: CesqlContext) => Result; + + /** + * Visit a parse tree produced by `CESQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitExpression?: (ctx: ExpressionContext) => Result; + + /** + * Visit a parse tree produced by `CESQLParserParser.atom`. + * @param ctx the parse tree + * @return the visitor result + */ + visitAtom?: (ctx: AtomContext) => Result; + + /** + * Visit a parse tree produced by `CESQLParserParser.identifier`. + * @param ctx the parse tree + * @return the visitor result + */ + visitIdentifier?: (ctx: IdentifierContext) => Result; + + /** + * Visit a parse tree produced by `CESQLParserParser.functionIdentifier`. + * @param ctx the parse tree + * @return the visitor result + */ + visitFunctionIdentifier?: (ctx: FunctionIdentifierContext) => Result; + + /** + * Visit a parse tree produced by `CESQLParserParser.booleanLiteral`. + * @param ctx the parse tree + * @return the visitor result + */ + visitBooleanLiteral?: (ctx: BooleanLiteralContext) => Result; + + /** + * Visit a parse tree produced by `CESQLParserParser.stringLiteral`. + * @param ctx the parse tree + * @return the visitor result + */ + visitStringLiteral?: (ctx: StringLiteralContext) => Result; + + /** + * Visit a parse tree produced by `CESQLParserParser.integerLiteral`. + * @param ctx the parse tree + * @return the visitor result + */ + visitIntegerLiteral?: (ctx: IntegerLiteralContext) => Result; + + /** + * Visit a parse tree produced by `CESQLParserParser.functionParameterList`. + * @param ctx the parse tree + * @return the visitor result + */ + visitFunctionParameterList?: (ctx: FunctionParameterListContext) => Result; + + /** + * Visit a parse tree produced by `CESQLParserParser.setExpression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitSetExpression?: (ctx: SetExpressionContext) => Result; +} + diff --git a/packages/cloudevents/src/models/filters/string.ts b/packages/cloudevents/src/models/filters/string.ts new file mode 100644 index 000000000..da359397f --- /dev/null +++ b/packages/cloudevents/src/models/filters/string.ts @@ -0,0 +1,145 @@ +import { CloudEvent } from "cloudevents"; +import { Filter, FilterImplementation } from "./abstract"; + +/** + * Use of this MUST include exactly one nested property, where the key is the name of the + * CloudEvents attribute to be matched, and its value is the String value to use in the comparison. + * To evaluate to true the value of the matching CloudEvents attribute MUST exactly match the value + * String specified (case sensitive). + * + * The attribute name and value specified in the filter express MUST NOT be empty strings. + */ +export interface ExactFilter { + exact: { + [key: string]: string; + }; +} + +/** + * Use of this MUST include exactly one nested property, where the key is the name of the CloudEvents + * attribute to be matched, and its value is the String value to use in the comparison. + * To evaluate to true the value of the matching CloudEvents attribute MUST start with the value + * String specified (case sensitive). + * + * The attribute name and value specified in the filter express MUST NOT be empty strings. + */ +export interface PrefixFilter { + prefix: { + [key: string]: string; + }; +} + +/** + * Use of this MUST include exactly one nested property, where the key is the name of the CloudEvents + * attribute to be matched, and its value is the String value to use in the comparison. + * To evaluate to true the value of the matching CloudEvents attribute MUST end with the value + * String specified (case sensitive). + * + * The attribute name and value specified in the filter express MUST NOT be empty strings. + * + */ export interface SuffixFilter { + suffix: { + [key: string]: string; + }; +} + +/** + * Abstract class to read CloudEvent specified property + */ +export abstract class StringPropertyFilterImplementation extends FilterImplementation { + /** + * Property to read event from + */ + property: string; + /** + * Filter property to read from + */ + filterProperty: string; + constructor(definition: T, filterProperty: string) { + super(definition); + this.filterProperty = filterProperty; + // @ts-ignore + if (Object.keys(definition[filterProperty]).length !== 1) { + throw new Error("Filter only accept one property filtering"); + } + // @ts-ignore + this.property = Object.keys(definition[filterProperty]).pop(); + } + + /** + * @override + */ + match(event: CloudEvent): boolean { + let value: string = event[this.property]; + if (!event[this.property] || typeof value !== "string") { + return false; + } + // @ts-ignore + return this.matchString(value, this.definition[this.filterProperty][this.property]); + } + + /** + * Verify if value match the condition + * + * @param value from the CloudEvent + * @param condition from the filter definition + */ + abstract matchString(value: string, condition: string): boolean; +} + +/** + * Use of this MUST include exactly one nested property, where the key is the name of the + * CloudEvents attribute to be matched, and its value is the String value to use in the comparison. + * To evaluate to true the value of the matching CloudEvents attribute MUST start with the value + * String specified (case sensitive). + */ +export class PrefixFilterImplementation extends StringPropertyFilterImplementation { + constructor(definition: PrefixFilter) { + super(definition, "prefix"); + } + + /** + * @override + */ + matchString(value: string, condition: string) { + return value.startsWith(condition); + } +} + +/** + * Use of this MUST include exactly one nested property, where the key is the name of the + * CloudEvents attribute to be matched, and its value is the String value to use in the comparison. + * To evaluate to true the value of the matching CloudEvents attribute MUST end with the value + * String specified (case sensitive). + */ +export class SuffixFilterImplementation extends StringPropertyFilterImplementation { + constructor(definition: SuffixFilter) { + super(definition, "suffix"); + } + + /** + * @override + */ + matchString(value: string, condition: string) { + return value.endsWith(condition); + } +} + +/** + * Use of this MUST include exactly one nested property, where the key is the name of the CloudEvents + * attribute to be matched, and its value is the String value to use in the comparison. To evaluate + * to true the value of the matching CloudEvents attribute MUST exactly match the value String + * specified (case sensitive). + */ +export class ExactFilterImplementation extends StringPropertyFilterImplementation { + constructor(definition: ExactFilter) { + super(definition, "exact"); + } + + /** + * @override + */ + matchString(value: string, condition: string) { + return value === condition; + } +} diff --git a/packages/cloudevents/src/models/subscription.spec.ts b/packages/cloudevents/src/models/subscription.spec.ts new file mode 100644 index 000000000..3fd5eb84a --- /dev/null +++ b/packages/cloudevents/src/models/subscription.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from "@jest/globals"; +import { CloudEvent } from "cloudevents"; +import { Server } from "http"; +import Subscription from "./subscription"; + +test("Subscription", async () => { + const subscription = new Subscription(); + Object.assign(subscription, { + types: ["test", "test2"], + sink: "test", + filters: [ + { + prefix: { + subject: "test" + } + } + ] + }); + let source = { + type: "test", + source: "unit-test", + data: {}, + id: "test", + subject: "plop", + time: new Date().toISOString(), + specversion: "1.0" + }; + let event: CloudEvent = new CloudEvent(source); + expect(subscription.match(event)).toBe(false); + event = new CloudEvent({ ...source, subject: "test" }); + expect(subscription.match(event)).toBe(true); + subscription.sink = "http://localhost:18181"; + let called = false; + const server = new Server().listen(18181).on("request", (req, res) => { + res.write("OK"); + res.statusCode = 200; + called = true; + res.end(); + }); + await subscription.emit(event); + expect(called).toBe(true); + called = false; + event = new CloudEvent({ ...source, subject: "test", type: "cloud" }); + expect(subscription.match(event)).toBe(false); + await subscription.emit(event); + expect(called).toBe(false); + server.close(); +}); diff --git a/packages/cloudevents/src/models/subscription.ts b/packages/cloudevents/src/models/subscription.ts new file mode 100644 index 000000000..753bf1810 --- /dev/null +++ b/packages/cloudevents/src/models/subscription.ts @@ -0,0 +1,235 @@ +import { CloudEvent, EmitterFunction, emitterFor, httpTransport } from "cloudevents"; +import { Filter, FilterImplementation, FiltersHelper } from "./filters"; + +/** + * For HTTP, the following settings properties SHOULD be supported by all implementations. + */ +export interface HttpSettings { + /** + * A set of key/value pairs that is copied into the HTTP request as custom headers. + */ + headers?: { [key: string]: string }; + /** + * The HTTP method to use for sending the message. This defaults to POST if not set. + */ + method?: "POST" | "PUT"; +} + +/** + * All implementations that support MQTT MUST support the topicname settings. + * All other settings SHOULD be supported. + */ +export interface MQTTSettings { + /** + * The name of the MQTT topic to publish to. + */ + topicname: string; + /** + * MQTT quality of service (QoS) level: 0 (at most once), 1 (at least once), or 2 (exactly once). + * This defaults to 1 if not set. + * + * @default 1 + */ + qos?: number; + /** + * MQTT retain flag: true/false. This defaults to false if not set. + * + * @default false + */ + retain?: boolean; + /** + * MQTT expiry interval, in seconds. This value has no default value and the message will not expire + * if the setting is absent. This setting only applies to MQTT 5.0. + */ + expiry?: number; + /** + * A set of key/value pairs that are copied into the MQTT PUBLISH packet's user property section. + * This setting only applies to MQTT 5.0. + */ + userproperties?: { [key: string]: string }; +} + +/** + * For AMQP, the address property MUST be supported by all implementations and other settings + * properties SHOULD be supported by all implementations. + */ +export interface AMQPSettings { + /** + * The link target node in the AMQP container identified by the sink URI, if not expressed in the + * sink URI's path portion. + */ + address?: string; + /** + * Name to use for the AMQP link. If not set, a random link name is used. + */ + linkname?: string; + /** + * Allows to control the sender's settlement mode, which determines whether transfers are performed + * "settled" (without acknowledgement) or "unsettled" (with acknowledgement). + * + * @default "unsettled" + */ + sendersettlementmode?: "settled" | "unsettled"; + /** + * A set of key/value pairs that are copied into link properties for the send link. + */ + linkproperties?: { [key: string]: string }; +} + +/** + * All implementations that support Apache Kafka MUST support the topicname settings. + * All other settings SHOULD be supported. + */ +export interface KafkaSettings { + /** + * The name of the Kafka topic to publish to. + */ + topicname?: string; + /** + * A partition key extractor expression per the CloudEvents Kafka transport binding specification. + */ + partitionkeyextractor?: string; + /** + * + */ + clientid?: string; + /** + * + */ + acks?: string; +} + +export interface NATSSettings { + /** + * The name of the NATS subject to publish to. + */ + subject: string; +} + +/** + * A subscription manager manages a collection of subscriptions. The upper limit on how many + * subscriptions are supported is implementation specific. + * + * To help explain the subscription resource, the following non-normative pseudo json shows its + * basic structure: + * + */ +export default class Subscription { + id: string = ""; + /** + * Indicates the source to which the subscription is related. When present on a subscribe request, + * all events generated due to this subscription MUST have a CloudEvents source property that + * matches this value. If this property is not present on a subscribe request then there are no + * constraints placed on the CloudEvents source property for the events generated. + * + * If present, MUST be a non-empty URI + */ + source?: string; + /** + * Indicates which types of events the subscriber is interested in receiving. When present on a + * subscribe request, all events generated due to this subscription MUST have a CloudEvents type + * property that matches one of these values. + * + * If present, any value present MUST a non-empty string + * + * @example com.github.pull_request.opened + * @example com.example.object.deleted + */ + types?: string[]; + /** + * A set of key/value pairs that modify the configuration of of the subscription related to the + * event generation process. While this specification places no constraints on the data type of + * the map values. When there is a Discovery Enpoint Service definition defined for the subscription + * manager, then the key MUST be one of the subscriptionconfig keys specified in the Discovery + * Endpoint Service definition. The value MUST conform to the data type specified by the value in + * the subscriptionconfig entry for the key + * + * If present, any "key" used in the map MUST be a non-empty string + */ + config?: { [key: string]: string }; + /** + * Identifier of a delivery protocol. Because of WebSocket tunneling options for AMQP, MQTT and + * other protocols, the URI scheme is not sufficient to identify the protocol. The protocols with + * existing CloudEvents bindings are identified as AMQP, MQTT3, MQTT5, HTTP, KAFKA, and NATS. + * An implementation MAY add support for further protocols. + * + * Value comparisons are case sensitive. + */ + protocol: "HTTP" | "MQTT" | "WEBDA" = "HTTP"; + /** + * A set of settings specific to the selected delivery protocol provider. Options for these + * settings are listed in the following subsection. An subscription manager MAY offer more options. + * See the Protocol Settings section for future details. + */ + protocolsettings?: HttpSettings | MQTTSettings | AMQPSettings | KafkaSettings | NATSSettings = {}; + /** + * The address to which events MUST be sent. The format of the address MUST be valid for the + * protocol specified in the protocol property, or one of the protocol's own transport bindings + * (e.g. AMQP over WebSockets). + * + * @required + */ + sink: string = ""; + /** + * An array of filter expressions that evaluates to true or false. If any filter expression in the + * array evaluates to false, the event MUST NOT be sent to the sink. If all the filter expressions + * in the array evaluates to true, the event MUST be attempted to be delivered. Absence of a filter + * or empty array implies a value of true. + * + * Each filter dialect MUST have a name that is unique within the scope of the subscription manager. + * Each dialect will define the semantics and syntax of the filter expression language. See the + * Filters section for more information. + * + * If a subscription manager does not support filters, or the filter dialect specified in a + * subscription request, then it MUST generate an error and reject the subscription create or + * update request. + */ + filters: Filter[] = []; + /** + * Filter implementation + */ + protected resolvedFilters: FilterImplementation; + /** + * + */ + private emitter: EmitterFunction; + + /** + * Verify that an event match its filters + * @param event + * @returns + */ + match(event: CloudEvent): boolean { + // Need to filter first on types + if (this.types && !this.types.includes(event.type)) { + return false; + } + this.resolvedFilters ??= FiltersHelper.get({ all: this.filters }); + return this.resolvedFilters.match(event); + } + + /** + * Create the emitter for the subscription + * @returns + */ + createEmitter() { + if (this.protocol === "HTTP") { + return emitterFor(httpTransport(this.sink), this.protocolsettings as any); + } + } + + /** + * Emit a cloudevent + * @param event + */ + async emit(event: CloudEvent) { + // Ensure the subscription match the event + if (!this.match(event)) { + return; + } + this.emitter ??= this.createEmitter(); + await this.emitter(event); + } +} + +export { Subscription }; diff --git a/packages/cloudevents/tsconfig.json b/packages/cloudevents/tsconfig.json new file mode 100644 index 000000000..87bfc4256 --- /dev/null +++ b/packages/cloudevents/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": ["es2019", "DOM"], + "module": "es2020", + "outDir": "./lib", + "strict": false, + "declaration": true, + "sourceMap": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "moduleResolution": "node", + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["**/node_modules", "src/**/*.spec.ts"], + "ts-node": { + "transpileOnly": true, + "preferTsExts": true, + "compilerOptions": { + "experimentalDecorators": true + }, + "include": ["src/**/*.spec.ts"], + "exclude": ["src/models/**/*"] + } +} diff --git a/packages/cloudevents/tsconfig.test.json b/packages/cloudevents/tsconfig.test.json new file mode 100644 index 000000000..63bccc884 --- /dev/null +++ b/packages/cloudevents/tsconfig.test.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "outDir": "./lib", + "strict": false, + "declaration": true, + "sourceMap": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "moduleResolution": "node", + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["**/node_modules"] +} diff --git a/packages/core/package.json b/packages/core/package.json index 4e4ae36bc..0dd8259a1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -30,6 +30,7 @@ "ajv-formats": "^2.1.1", "antlr4ts": "^0.5.0-alpha.4", "bcryptjs": "^2.4.3", + "cloudevents": "^8.0.0", "cookie": "^0.6.0", "dateformat": "^5.0.3", "deepmerge-ts": "^5.1.0", diff --git a/packages/core/src/core.spec.ts b/packages/core/src/core.spec.ts index 065764dbc..b6d605f7d 100644 --- a/packages/core/src/core.spec.ts +++ b/packages/core/src/core.spec.ts @@ -9,10 +9,10 @@ import { Bean, CancelablePromise, ConsoleLoggerService, + Context, ContextProvider, CoreModel, MemoryStore, - OperationContext, Route, Service, User, @@ -699,7 +699,7 @@ class CoreTest extends WebdaTest { return undefined; } }; - assert.ok(this.webda["_contextProviders"][0].getContext({}) instanceof OperationContext); + assert.ok(this.webda["_contextProviders"][0].getContext({}) instanceof Context); this.webda.registerContextProvider(provider); this.webda.getService("Registry").stop = async () => { throw new Error("Test"); diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 6327e6d97..882eda1c1 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -6,6 +6,7 @@ import * as events from "events"; import { JSONSchema7 } from "json-schema"; import jsonpath from "jsonpath"; import pkg from "node-machine-id"; +import { AsyncLocalStorage } from "node:async_hooks"; import { OpenAPIV3 } from "openapi-types"; import { Counter, @@ -22,6 +23,7 @@ import { Application, Configuration, Modda } from "./application"; import { BinaryService, ConfigurationService, + Context, ContextProvider, ContextProviderInfo, GlobalContext, @@ -160,7 +162,7 @@ export type RegistryEntry = CoreModel & T; /** * Ensure all events store the context in the same place */ -export interface EventWithContext { +export interface EventWithContext { context: T; } @@ -268,7 +270,7 @@ export type CoreEvents = { * Emitted whenever a new Context is created */ "Webda.NewContext": { - context: OperationContext; + context: Context; info: ContextProviderInfo; }; /** @@ -428,6 +430,11 @@ export class Core extends events.EventEmitter * System context */ protected globalContext: GlobalContext; + + /** + * Store execution context + */ + static asyncLocalStorage = new AsyncLocalStorage<{ context: Context } & any>(); /** * */ @@ -1141,6 +1148,35 @@ export class Core extends events.EventEmitter return this.globalContext; } + /** + * Return the current context or global context + * @returns + */ + public getContext(): T { + return Core.asyncLocalStorage.getStore()?.context || this.globalContext; + } + + /** + * Run this function as system + * + * @param run + * @returns + */ + runAsSystem(run: () => T): T { + return this.runInContext(this.globalContext, run); + } + + /** + * Run this function as user + * @param context + * @param run + * @returns + */ + runInContext(context: Context, run: () => T): T { + const previousContext = Core.asyncLocalStorage.getStore()?.context; + return Core.asyncLocalStorage.run({ context, previousContext }, run); + } + /** * Set the system context * @param context @@ -1213,6 +1249,13 @@ export class Core extends events.EventEmitter return params; } + /** + * Create services singleton + * + * @param services + * @param service + * @returns + */ protected createService(services: any, service: string) { let type = services[service]?.type; if (type === undefined) { @@ -1237,7 +1280,11 @@ export class Core extends events.EventEmitter } } - getBeans() { + /** + * Get webda beans + * @returns + */ + protected getBeans() { // @ts-ignore return process.webdaBeans || {}; } @@ -1379,11 +1426,8 @@ export class Core extends events.EventEmitter * @param info * @returns */ - public async newContext( - info: ContextProviderInfo, - noInit: boolean = false - ): Promise { - let context: OperationContext; + public async newContext(info: ContextProviderInfo, noInit: boolean = false): Promise { + let context: Context; this._contextProviders.find(provider => (context = provider.getContext(info)) !== undefined); if (!noInit) { await context.init(); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 54f2bca79..8098029a2 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -67,4 +67,4 @@ export * from "./utils/session"; export * from "./utils/throttler"; export * from "./utils/waiter"; -export * from "./stores/webdaql/query"; +export * from "../../webdaql/query"; diff --git a/packages/core/src/models/aclmodel.spec.ts b/packages/core/src/models/aclmodel.spec.ts index ce0ae32d3..21297f3a0 100644 --- a/packages/core/src/models/aclmodel.spec.ts +++ b/packages/core/src/models/aclmodel.spec.ts @@ -49,12 +49,11 @@ class AclModelTest { assert.strictEqual(await this.model.canAct(this._ctx, "action"), true); assert.deepStrictEqual(await this.model.getPermissions(this._ctx), ["get", "action"]); await this.model._onGet(); - // @ts-ignore - assert.deepStrictEqual(this.model._permissions, []); - this.model.setContext(this._ctx); - await this.model._onGet(); - // @ts-ignore - assert.deepStrictEqual(this.model._permissions, ["get", "action"]); + assert.deepStrictEqual(this.model["_permissions"], []); + await this._webda.runInContext(this._ctx, async () => { + await this.model._onGet(); + assert.deepStrictEqual(this.model["_permissions"], ["get", "action"]); + }); } @test async multigroups() { @@ -166,9 +165,10 @@ class AclModelTest { } @test async onSave() { - this.model.setContext(this._ctx); - await this.model._onSave(); - assert.deepStrictEqual(this.model.__acl, { "user-uid": "all" }); - assert.strictEqual(this.model._creator, "user-uid"); + await this._webda.runInContext(this._ctx, async () => { + await this.model._onSave(); + assert.deepStrictEqual(this.model.__acl, { "user-uid": "all" }); + assert.strictEqual(this.model._creator, "user-uid"); + }); } } diff --git a/packages/core/src/models/aclmodel.ts b/packages/core/src/models/aclmodel.ts index 257472aaf..d1a376ce9 100644 --- a/packages/core/src/models/aclmodel.ts +++ b/packages/core/src/models/aclmodel.ts @@ -1,4 +1,4 @@ -import { OperationContext, User, WebContext, WebdaError } from "../index"; +import { Context, OperationContext, User, WebContext, WebdaError } from "../index"; import { Action, CoreModel } from "./coremodel"; export type Acl = { [key: string]: string }; @@ -38,7 +38,7 @@ export default class AclModel extends CoreModel { */ async _onGet() { const ctx = this.getContext(); - if (!ctx.isGlobal()) { + if (ctx instanceof OperationContext) { this._permissions = await this.getPermissions(ctx); } else { this._permissions = []; @@ -133,7 +133,24 @@ export default class AclModel extends CoreModel { * GET * @param ctx */ - async _httpGetAcls(ctx: OperationContext) { + async _httpGetAcls( + _ctx: OperationContext< + void, + { + raw: Acl; + resolved: { + permission: string; + actor: { + uuid: string; + name: string; + email: string; + avatar: string; + }; + }[]; + } + > + ) { + // Permissions checked by canAct prior to this call return { raw: this.__acl, resolved: await Promise.all( @@ -154,16 +171,16 @@ export default class AclModel extends CoreModel { * @param ace * @returns */ - async getUserPublicEntry(ace: string) { - return (await User.ref(ace).get())?.toPublicEntry(); + async getUserPublicEntry(ace: string): Promise> { + return >(await User.ref(ace).get())?.toPublicEntry(); } /** * */ - async _httpPutAcls(ctx: OperationContext) { + async _httpPutAcls(ctx: OperationContext) { let acl = await ctx.getInput(); - // This looks like a bad request + // This looks like a bad request TODO Remove by OperationContext validation if (acl.raw) { throw new WebdaError.BadRequest("ACL should have raw field"); } @@ -172,7 +189,7 @@ export default class AclModel extends CoreModel { } // Should cache the user role in the session - getGroups(_ctx: OperationContext, user: User) { + getGroups(_ctx: Context, user: User) { if (!user) { return []; } @@ -191,7 +208,7 @@ export default class AclModel extends CoreModel { * @param user * @returns */ - async getPermissions(ctx: OperationContext, user?: User): Promise { + async getPermissions(ctx: Context, user?: User): Promise { if (!user) { user = await ctx.getCurrentUser(); } @@ -205,7 +222,7 @@ export default class AclModel extends CoreModel { return [...permissions.values()]; } - async hasPermission(ctx: OperationContext, user: User, action: string): Promise { + async hasPermission(ctx: Context, user: User, action: string): Promise { let groups = this.getGroups(ctx, user); for (let i in this.__acl) { if (groups.indexOf(i) >= 0) { @@ -217,7 +234,7 @@ export default class AclModel extends CoreModel { return false; } - async canAct(ctx: OperationContext, action: string): Promise { + async canAct(ctx: Context, action: string): Promise { if (action === "create" && ctx.getCurrentUserId()) { return true; } diff --git a/packages/core/src/models/coremodel.spec.ts b/packages/core/src/models/coremodel.spec.ts index ac28e7ef8..b50db1aa4 100644 --- a/packages/core/src/models/coremodel.spec.ts +++ b/packages/core/src/models/coremodel.spec.ts @@ -3,6 +3,7 @@ import * as assert from "assert"; import * as sinon from "sinon"; import { Action, + Context, Core, CoreModel, Expose, @@ -13,7 +14,6 @@ import { ModelParent, ModelRelated, ModelsMapped, - OperationContext, WebdaError, createModelLinksMap } from ".."; @@ -33,7 +33,7 @@ class TestMask extends CoreModel { side: string; counter: number; - attributePermission(key: string, value: any, mode: "READ" | "WRITE", context?: OperationContext): any { + attributePermission(key: string, value: any, mode: "READ" | "WRITE", context?: Context): any { if (key === "card") { const mask = "---X-XXXX-XXXX-X---"; value = value.padEnd(mask.length, "?"); @@ -185,8 +185,9 @@ class CoreModelTest extends WebdaTest { @test("Verify Context access within output to server") async withContext() { let ctx = await this.newContext(); let task = new Task(); - task.setContext(ctx); - ctx.write(task); + Core.get().runInContext(ctx, async () => { + ctx.write(task); + }); let result = JSON.parse(ctx.getResponseBody()); assert.strictEqual(result._gotContext, true); } diff --git a/packages/core/src/models/coremodel.ts b/packages/core/src/models/coremodel.ts index d2f24eaa4..111f07670 100644 --- a/packages/core/src/models/coremodel.ts +++ b/packages/core/src/models/coremodel.ts @@ -1,16 +1,14 @@ -import { EventEmitter } from "events"; import { JSONSchema7 } from "json-schema"; import util from "util"; import { v4 as uuidv4 } from "uuid"; +import { WebdaQL } from "../../../webdaql/query"; import { ModelGraph, ModelsTree } from "../application"; -import { Core, EventEmitterUtils } from "../core"; +import { Core } from "../core"; import { WebdaError } from "../errors"; -import { EventService } from "../services/asyncevents"; import { BinariesImpl, Binary } from "../services/binary"; import { Service } from "../services/service"; -import { Store, StoreEvents } from "../stores/store"; -import { WebdaQL } from "../stores/webdaql/query"; -import { OperationContext } from "../utils/context"; +import { Store } from "../stores/store"; +import { Context, OperationContext } from "../utils/context"; import { HttpMethodType } from "../utils/httpcontext"; import { Throttler } from "../utils/throttler"; import { @@ -71,7 +69,7 @@ export class CoreModelQuery { */ query( query?: string, - context?: OperationContext + context?: Context ): Promise<{ results: CoreModel[]; continuationToken?: string; @@ -93,12 +91,7 @@ export class CoreModelQuery { * @param callback * @param context */ - async forEach( - callback: (model: any) => Promise, - query?: string, - context?: OperationContext, - parallelism: number = 3 - ) { + async forEach(callback: (model: any) => Promise, query?: string, context?: Context, parallelism: number = 3) { const throttler = new Throttler(); throttler.setConcurrency(parallelism); for await (const model of this.iterate(query, context)) { @@ -112,7 +105,7 @@ export class CoreModelQuery { * @param context * @returns */ - iterate(query?: string, context?: OperationContext) { + iterate(query?: string, context?: Context) { return Core.get().getModelStore(this.getTargetModel()).iterate(this.completeQuery(query), context); } @@ -120,7 +113,7 @@ export class CoreModelQuery { * Get all the objects * @returns */ - async getAll(context?: OperationContext): Promise { + async getAll(context?: Context): Promise { let res = []; for await (const item of this.iterate(this.completeQuery(), context)) { res.push(item); @@ -222,7 +215,7 @@ export interface ExposeParameters { /** * */ -export interface CoreModelDefinition extends EventEmitter { +export interface CoreModelDefinition { new (): T; /** * If the model have some Expose annotation @@ -237,7 +230,7 @@ export interface CoreModelDefinition extends Ev * @param object to load data from * @param context if the data is unsafe from http */ - factory(this: Constructor, object: Partial, context?: OperationContext): T; + factory(this: Constructor, object: Partial, context?: Context): T; /** * Get the model actions */ @@ -277,7 +270,7 @@ export interface CoreModelDefinition extends Ev * Permission query for the model * @param context */ - getPermissionQuery(context?: OperationContext): null | { partial: boolean; query: string }; + getPermissionQuery(context: OperationContext): null | { partial: boolean; query: string }; /** * Reference to an object without doing a DB request yet */ @@ -295,48 +288,9 @@ export interface CoreModelDefinition extends Ev query( query?: string, includeSubclass?: boolean, - context?: OperationContext + context?: Context ): Promise<{ results: T[]; continuationToken?: string }>; - /** - * Listen to events on the model - * @param event - * @param listener - * @param async - */ - on( - this: Constructor, - event: Key, - listener: (evt: StoreEvents[Key]) => any, - async?: boolean - ): this; - /** - * Listen to events on the model asynchronously - * @param event - * @param listener - */ - onAsync( - this: Constructor, - event: Key, - listener: (evt: StoreEvents[Key]) => any - ): this; - /** - * Emit an event for this class - * @param this - * @param event - * @param evt - */ - emit(this: Constructor, event: Key, evt: StoreEvents[Key]); - /** - * Emit an event for this class and wait for all listeners to finish - * @param this - * @param event - * @param evt - */ - emitSync( - this: Constructor, - event: Key, - evt: StoreEvents[Key] - ): Promise; + /** * Return the event on the model that can be listened to by an * external authorized source @@ -348,38 +302,7 @@ export interface CoreModelDefinition extends Ev * @param event * @param context */ - authorizeClientEvent(_event: string, _context: OperationContext, _model?: T): boolean; - - /** - * EventEmitter interface - * @param event - * @param listener - */ - addListener(event: string | symbol, listener: (...args: any[]) => void): this; - /** - * EventEmitter interface - * @param event - * @param listener - */ - once(event: string | symbol, listener: (...args: any[]) => void): this; - /** - * EventEmitter interface - * @param event - * @param listener - */ - removeListener(event: string | symbol, listener: (...args: any[]) => void): this; - /** - * EventEmitter interface - * @param event - * @param listener - */ - off(eventName: string | symbol, listener: (...args: any[]) => void): this; - /** - * EventEmitter interface - * @param event - * @param listener - */ - removeAllListeners(eventName?): this; + authorizeClientEvent(_event: string, _context: Context, _model?: T): boolean; } export type Constructor = []> = new (...args: K) => T; @@ -448,8 +371,8 @@ export class ModelRef { this.uuid = uuid === "" ? undefined : model.completeUid(uuid); this.store = Core.get().getModelStore(model); } - async get(context?: OperationContext): Promise { - return (await this.store.get(this.uuid))?.setContext(context || this.parent?.getContext()); + async get(): Promise { + return await this.store.get(this.uuid); } set(id: string | T) { this.uuid = id instanceof CoreModel ? id.getUuid() : id; @@ -577,8 +500,8 @@ export class ModelRefWithCreate extends ModelRef { * @param withSave * @returns */ - async create(defaultValue: RawModel, context?: OperationContext, withSave: boolean = true): Promise { - let result = new this.model().setContext(context).load(defaultValue, true).setUuid(this.uuid); + async create(defaultValue: RawModel, withSave: boolean = true): Promise { + let result = new this.model().load(defaultValue, true).setUuid(this.uuid); if (withSave) { await result.save(); } @@ -594,8 +517,8 @@ export class ModelRefWithCreate extends ModelRef { * @param context to set on the object * @returns */ - async getOrCreate(defaultValue: RawModel, context?: OperationContext, withSave: boolean = true): Promise { - return (await this.get()) || this.create(defaultValue, context, withSave); + async getOrCreate(defaultValue: RawModel, withSave: boolean = true): Promise { + return (await this.get()) || this.create(defaultValue, withSave); } } @@ -620,8 +543,6 @@ export class ModelRefCustom extends ModelRef { export type ModelRefCustomProperties = ModelRefCustom & K; -export const Emitters: WeakMap, EventEmitter> = new WeakMap(); - /** * Basic Object in Webda * @@ -652,7 +573,7 @@ class CoreModel { * @TJS-ignore */ @NotEnumerable - __ctx: OperationContext; + __ctx: Context; /** * If object is attached to its store * @@ -692,160 +613,6 @@ class CoreModel { .getShortId(Core.get()?.getApplication().getModelFromInstance(this)); } - /** - * Listen to events on the model - * @param event - * @param listener - * @param async - */ - static on( - this: Constructor, - event: Key, - listener: (evt: StoreEvents[Key]) => any, - async: boolean = false - ) { - if (!Emitters.has(this)) { - Emitters.set(this, new EventEmitter()); - } - // TODO Manage async - if (async) { - Core.get() - .getService("AsyncEvents") - .bindAsyncListener((this), event, listener); - } else { - Emitters.get(this).on(event, listener); - } - return this; - } - - /** - * Emit an event for this class - * @param this - * @param event - * @param evt - */ - static emit( - this: Constructor, - event: Key, - evt: StoreEvents[Key] - ) { - let clazz = this; - // @ts-ignore - while (clazz) { - // Emit for all parent class - if (Emitters.has(clazz)) { - EventEmitterUtils.emit(Emitters.get(clazz), event, evt); - } - // @ts-ignore - if (clazz === CoreModel) { - break; - } - clazz = Object.getPrototypeOf(clazz); - } - } - - /** - * Emit an event for this class and wait for all listeners to finish - * @param this - * @param event - * @param evt - */ - static async emitSync( - this: Constructor, - event: Key, - evt: StoreEvents[Key] - ) { - let clazz = this; - let p = []; - // @ts-ignore - while (clazz) { - // Emit for all parent class - if (Emitters.has(clazz)) { - p.push(EventEmitterUtils.emitSync(Emitters.get(clazz), event, evt)); - } - // @ts-ignore - if (clazz === CoreModel) { - break; - } - clazz = Object.getPrototypeOf(clazz); - } - await Promise.all(p); - } - - /** - * Listen to events on the model asynchronously - * @param event - * @param listener - */ - static onAsync(event: Key, listener: (evt: StoreEvents[Key]) => any, queue?: string) { - return this.on(event, listener, true); - } - - /** - * - * @param event - * @param listener - * @returns - */ - static addListener(event: Key, listener: (...args: any[]) => void) { - return this.on(event, listener); - } - - static emitter(method, ...args) { - if (!Emitters.has(this)) { - Emitters.set(this, new EventEmitter()); - } - // @ts-ignore - return Emitters.get(this)[method](...args); - } - - static removeListener(...args) { - return this.emitter("removeListener", ...args); - } - - static off(...args) { - return this.emitter("off", ...args); - } - - static once(...args) { - return this.emitter("once", ...args); - } - - static removeAllListeners(...args) { - return this.emitter("removeAllListeners", ...args); - } - - static setMaxListeners(...args) { - return this.emitter("setMaxListeners", ...args); - } - - static getMaxListeners(...args) { - return this.emitter("getMaxListeners", ...args); - } - - static listeners(...args) { - return this.emitter("listeners", ...args); - } - - static rawListeners(...args) { - return this.emitter("rawListeners", ...args); - } - - static listenerCount(...args) { - return this.emitter("listenerCount", ...args); - } - - static prependListener(...args) { - return this.emitter("prependListener", ...args); - } - - static prependOnceListener(...args) { - return this.emitter("prependOnceListener", ...args); - } - - static eventNames(...args) { - return this.emitter("eventNames", ...args); - } /** * * @returns @@ -868,7 +635,7 @@ class CoreModel { * @param _context * @returns */ - static authorizeClientEvent(_event: string, _context: OperationContext, _model?: CoreModel) { + static authorizeClientEvent(_event: string, _context: Context, _model?: CoreModel) { return false; } @@ -1061,7 +828,7 @@ class CoreModel { this: Constructor, query: string = "", includeSubclass: boolean = true, - context?: OperationContext + context?: Context ): AsyncGenerator { // @ts-ignore return this.store().iterate(this.completeQuery(query, includeSubclass), context); @@ -1077,7 +844,7 @@ class CoreModel { this: Constructor, query: string = "", includeSubclass: boolean = true, - context?: OperationContext + context?: Context ): Promise<{ results: T[]; continuationToken?: string; @@ -1195,7 +962,7 @@ class CoreModel { } async checkAct( - context: OperationContext, + context: Context, action: | "create" | "update" @@ -1219,7 +986,7 @@ class CoreModel { * @returns */ async canAct( - _context: OperationContext, + _context: Context, _action: | "create" | "update" @@ -1246,8 +1013,8 @@ class CoreModel { * Create an object * @returns */ - static factory(this: Constructor, object: Partial, context?: OperationContext): T { - return object instanceof this ? object : new this().setContext(context).load(object, context === undefined); + static factory(this: Constructor, object: Partial, context?: Context): T { + return object instanceof this ? object : new this().load(object, context === undefined); } /** @@ -1303,7 +1070,7 @@ class CoreModel { * @param context * @returns updated value */ - attributePermission(key: string, value: any, mode: "READ" | "WRITE", context?: OperationContext): any { + attributePermission(key: string, value: any, mode: "READ" | "WRITE", context?: Context): any { if (mode === "WRITE") { return key.startsWith("_") ? undefined : value; } else { @@ -1397,21 +1164,13 @@ class CoreModel { } } - /** - * Context of the request - */ - setContext(ctx: OperationContext): this { - this.__ctx = ctx; - return this; - } - /** * Get object context * * Global object does not belong to a request */ - getContext(): T { - return this.__ctx || Core.get().getGlobalContext(); + getContext(): T { + return Core.get().getContext(); } /** @@ -1512,8 +1271,8 @@ class CoreModel { * @param ctx * @param updates */ - async validate(ctx: OperationContext, updates: any, ignoreRequired: boolean = false): Promise { - ctx.getWebda().validateSchema(this, updates, ignoreRequired); + async validate(_ctx: OperationContext, updates: any, ignoreRequired: boolean = false): Promise { + Core.get().validateSchema(this, updates, ignoreRequired); return true; } @@ -1545,10 +1304,9 @@ class CoreModel { * Get a pre typed service * * @param service to retrieve - * WARNING: Only object attached to a store can retrieve service */ getService(service): T { - return this.__store.getService(service); + return Core.get().getService(service); } /** @@ -1696,7 +1454,7 @@ class UuidModel extends CoreModel { /** * @override */ - validate(ctx: OperationContext, updates: any, ignoreRequired?: boolean): Promise { + validate(ctx: OperationContext, updates: any, ignoreRequired?: boolean): Promise { updates.uuid ??= this.generateUid(); return super.validate(ctx, updates, ignoreRequired); } diff --git a/packages/core/src/models/ownermodel.spec.ts b/packages/core/src/models/ownermodel.spec.ts index 3b5f95ad0..d6310771b 100644 --- a/packages/core/src/models/ownermodel.spec.ts +++ b/packages/core/src/models/ownermodel.spec.ts @@ -122,15 +122,17 @@ class OwnerModelTest extends WebdaTest { @test("Query") async queryPermission() { await this.beforeEach(); - this._session.login("fake_user2", "fake_ident"); - let res = await this._taskStore.query("", this._ctx); - assert.deepStrictEqual(res.results.map(r => r.getUuid()).sort(), ["task_public", "task_user2"]); - res = await this._taskStore.query(""); - assert.deepStrictEqual(res.results.length, 4); - res = await this._taskStore.query("uuid = 'task_user1'", this._ctx); - assert.deepStrictEqual(res.results.length, 0); - res = await this._taskStore.query("uuid = 'task_public'", this._ctx); - assert.deepStrictEqual(res.results.length, 1); + await this.webda.runInContext(this._ctx, async () => { + this._session.login("fake_user2", "fake_ident"); + let res = await this._taskStore.query(""); + assert.deepStrictEqual(res.results.map(r => r.getUuid()).sort(), ["task_public", "task_user2"]); + res = await this._taskStore.query(""); + assert.deepStrictEqual(res.results.length, 4); + res = await this._taskStore.query("uuid = 'task_user1'"); + assert.deepStrictEqual(res.results.length, 0); + res = await this._taskStore.query("uuid = 'task_public'"); + assert.deepStrictEqual(res.results.length, 1); + }); } @test("Actions") async actions() { diff --git a/packages/core/src/models/ownermodel.ts b/packages/core/src/models/ownermodel.ts index 4644a3b73..ea0a6c076 100644 --- a/packages/core/src/models/ownermodel.ts +++ b/packages/core/src/models/ownermodel.ts @@ -1,4 +1,4 @@ -import { OperationContext } from "../utils/context"; +import { Context, OperationContext } from "../utils/context"; import { UuidModel } from "./coremodel"; import { ModelLink } from "./relations"; import { User } from "./user"; @@ -34,7 +34,7 @@ export class OwnerModel extends UuidModel { /** * @override */ - validate(ctx: OperationContext, updates: any, ignoreRequired?: boolean): Promise { + validate(ctx: OperationContext, updates: any, ignoreRequired?: boolean): Promise { updates._user ??= this._user?.toString(); updates.uuid ??= this.generateUid(); return super.validate(ctx, updates, ignoreRequired); @@ -51,7 +51,7 @@ export class OwnerModel extends UuidModel { } async canAct( - ctx: OperationContext, + ctx: Context, action: | "create" | "update" @@ -83,7 +83,7 @@ export class OwnerModel extends UuidModel { * @param context * @returns */ - static getPermissionQuery(context?: OperationContext): null | { partial: boolean; query: string } { + static getPermissionQuery(context?: Context): null | { partial: boolean; query: string } { if (!context) { return null; } diff --git a/packages/core/src/models/relations.spec.ts b/packages/core/src/models/relations.spec.ts index 5f071affb..be7f37e34 100644 --- a/packages/core/src/models/relations.spec.ts +++ b/packages/core/src/models/relations.spec.ts @@ -97,8 +97,8 @@ export class ModelDrivenTest extends WebdaTest { const User = this.webda.getModel("User"); // Create a User - let user = await User.ref("user1").getOrCreate({ uuid: "user1" }, undefined, true); - let user2 = await User.ref("user2").getOrCreate({ uuid: "user2" }, undefined, true); + let user = await User.ref("user1").getOrCreate({ uuid: "user1" }, true); + let user2 = await User.ref("user2").getOrCreate({ uuid: "user2" }, true); Contact.factory({ firstName: "test", lastName: "" }); let contact = new Contact().load({ firstName: "test", lastName: "", age: 18 }, true); diff --git a/packages/core/src/models/relations.ts b/packages/core/src/models/relations.ts index d78516ac3..f8471c086 100644 --- a/packages/core/src/models/relations.ts +++ b/packages/core/src/models/relations.ts @@ -78,7 +78,7 @@ export class ModelLink implements ModelLinker { } async get(): Promise { - return (await this.model.ref(this.uuid).get())?.setContext(this.parent?.getContext()); + return await this.model.ref(this.uuid).get(); } set(id: string | T) { this.uuid = typeof id === "string" ? id : id.getUuid(); @@ -307,7 +307,7 @@ export class ModelMapLoaderImplementation { * @returns the model */ async get(): Promise { - return this._model.ref(this.uuid).get(this._parent.getContext()); + return this._model.ref(this.uuid).get(); } } diff --git a/packages/core/src/models/rolemodel.ts b/packages/core/src/models/rolemodel.ts index e87074c5a..242bd535e 100644 --- a/packages/core/src/models/rolemodel.ts +++ b/packages/core/src/models/rolemodel.ts @@ -20,6 +20,9 @@ abstract class RoleModel extends CoreModel { return ctx.getSession().roles; } + /** + * @override + */ async canAct(ctx: OperationContext, action: string): Promise { // If this action doesn't require role if (!this.getRolesMap()[action]) { diff --git a/packages/core/src/models/user.ts b/packages/core/src/models/user.ts index 1aa6d4bfd..62d7a167b 100644 --- a/packages/core/src/models/user.ts +++ b/packages/core/src/models/user.ts @@ -3,6 +3,28 @@ import { CoreModel } from "./coremodel"; import { Ident } from "./ident"; import { ModelsMapped } from "./relations"; +/** + * Public entry for user + */ +export interface UserPublicEntry { + /** + * Display name + */ + displayName?: string; + /** + * UUID of the user + */ + uuid: string; + /** + * Avatar of the user + */ + avatar?: string; + /** + * Email of the user + */ + email?: string; +} + /** * First basic model for User * @class @@ -50,14 +72,13 @@ export class User extends CoreModel { * Return displayable public entry * @returns */ - toPublicEntry(): any { - const res = { + toPublicEntry(): UserPublicEntry { + return { displayName: this.displayName, uuid: this.getUuid(), avatar: this._avatar, email: this.getEmail() }; - return res; } getGroups(): string[] { diff --git a/packages/core/src/services/binary.spec.ts b/packages/core/src/services/binary.spec.ts index fab7f45c7..36fee7c49 100644 --- a/packages/core/src/services/binary.spec.ts +++ b/packages/core/src/services/binary.spec.ts @@ -436,7 +436,6 @@ class BinaryTest extends WebdaTest { if (withLogin) { ctx.getSession().login(user1.getUuid(), "fake"); } - user1.setContext(ctx); return { userStore, binary, user1, ctx }; } diff --git a/packages/core/src/services/binary.ts b/packages/core/src/services/binary.ts index af54558ef..c386ba562 100644 --- a/packages/core/src/services/binary.ts +++ b/packages/core/src/services/binary.ts @@ -7,7 +7,7 @@ import { Readable } from "stream"; import { Core, Counter, WebdaError } from "../index"; import { CoreModel, NotEnumerable } from "../models/coremodel"; import { EventStoreDeleted, MappingService, Store } from "../stores/store"; -import { OperationContext, WebContext } from "../utils/context"; +import { Context, WebContext } from "../utils/context"; import { Service, ServiceParameters } from "./service"; /** @@ -19,7 +19,7 @@ export interface EventBinary { /** * In case the Context is known */ - context?: OperationContext; + context?: Context; } export interface EventBinaryUploadSuccess extends EventBinary { @@ -244,7 +244,7 @@ export class BinaryMap extends BinaryFile { * Current context */ @NotEnumerable - __ctx: OperationContext; + __ctx: Context; /** * Link to the binary store */ @@ -288,7 +288,7 @@ export class BinaryMap extends BinaryFile { * Set the http context * @param ctx */ - setContext(ctx: OperationContext) { + setContext(ctx: Context) { this.__ctx = ctx; } } @@ -666,7 +666,7 @@ export abstract class BinaryService< */ async getRedirectUrlFromObject( binaryMap: BinaryMap, - _context: OperationContext, + _context: Context, _expires: number = 30 ): Promise { return null; diff --git a/packages/core/src/services/cloudbinary.ts b/packages/core/src/services/cloudbinary.ts index 2d4ee7b61..5e2a0e926 100644 --- a/packages/core/src/services/cloudbinary.ts +++ b/packages/core/src/services/cloudbinary.ts @@ -1,5 +1,5 @@ import { join } from "path"; -import { OperationContext } from "../utils/context"; +import { Context } from "../utils/context"; import { BinaryMap, BinaryModel, BinaryParameters, BinaryService } from "./binary"; /** @@ -73,5 +73,5 @@ export abstract class CloudBinary; + abstract getSignedUrlFromMap(map: BinaryMap, expires: number, context: Context): Promise; } diff --git a/packages/core/src/services/cryptoservice.ts b/packages/core/src/services/cryptoservice.ts index ef8a76455..f33527a3c 100644 --- a/packages/core/src/services/cryptoservice.ts +++ b/packages/core/src/services/cryptoservice.ts @@ -1,6 +1,13 @@ -import { createCipheriv, createDecipheriv, createHash, createHmac, generateKeyPairSync, randomBytes } from "crypto"; +import { + createCipheriv, + createDecipheriv, + createHash, + createHmac, + createPublicKey, + generateKeyPairSync, + randomBytes +} from "crypto"; import jwt from "jsonwebtoken"; -import { pem2jwk } from "pem-jwk"; import * as util from "util"; import { Core, OperationContext, RegistryEntry, Store } from "../index"; import { JSONUtils } from "../utils/serializers"; @@ -258,8 +265,8 @@ export default class CryptoService + ) { context.write({ keys: Object.keys(this.keys).map(k => { if (!this.jwks[k]) { @@ -304,7 +353,7 @@ export default class CryptoService i !== "__class") + .forEach(i => { + delete updateObject[i]; + }); + updateObject.setUuid(uuid); + updateObject.load(body, false, false); + await this.patch(updateObject); + object = undefined; + } else { + let updateObject: any = new this._model(); + updateObject.load(body); + // Copy back the _ attributes + Object.keys(object) + .filter(i => i.startsWith("_")) + .forEach(i => { + updateObject[i] = object[i]; + }); + try { + await updateObject.validate(ctx, body); + } catch (err) { + this.log("INFO", "Object invalid", err); + throw new WebdaError.BadRequest("Object is not valid"); + } + + // Add mappers back to + object = await this.update(updateObject); + } + ctx.write(object); + const evt = { + context: ctx, + updates: body, + object: object, + store: this, + method: <"PATCH" | "PUT">ctx.getHttpContext().getMethod() + }; + await Promise.all([object?.__class.emitSync("Store.WebUpdate", evt), this.emitSync("Store.WebUpdate", evt)]); + } + + /** + * Handle GET on object + * + * @param ctx context of the request + */ + @Route("./{uuid}", ["GET"], { + get: { + description: "Retrieve ${modelName} model if permissions allow", + summary: "Retrieve a ${modelName}", + operationId: "get${modelName}", + schemas: { + output: "${modelName}" + }, + responses: { + "200": {}, + "400": { + description: "Object is invalid" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + } + }) + async httpGet(ctx: WebContext) { + let uuid = ctx.parameter("uuid"); + let object = await this.get(uuid, ctx); + await this.emitSync("Store.WebGetNotFound", { + context: ctx, + uuid, + store: this + }); + if (object === undefined || object.__deleted) { + throw new WebdaError.NotFound("Object not found or is deleted"); + } + await object.checkAct(ctx, "get"); + ctx.write(object); + const evt = { + context: ctx, + object: object, + store: this + }; + await Promise.all([this.emitSync("Store.WebGet", evt), object.__class.emitSync("Store.WebGet", evt)]); + ctx.write(object); + } + + /** + * Handle HTTP request + * + * @param ctx context of the request + * @returns + */ + @Route("./{uuid}", ["DELETE"], { + delete: { + operationId: "delete${modelName}", + description: "Delete ${modelName} if the permissions allow", + summary: "Delete a ${modelName}", + responses: { + "204": { + description: "" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + } + }) + async httpDelete(ctx: WebContext) { + let uuid = ctx.parameter("uuid"); + let object = await this.getWebda().runAsSystem(async () => { + const object = await this.get(uuid, ctx); + if (!object || object.__deleted) throw new WebdaError.NotFound("Object not found or is deleted"); + return object; + }); + await object.checkAct(ctx, "delete"); + // http://stackoverflow.com/questions/28684209/huge-delay-on-delete-requests-with-204-response-and-no-content-in-objectve-c# + // IOS don't handle 204 with Content-Length != 0 it seems + // Might still run into: Have trouble to handle the Content-Length on API Gateway so returning an empty object for now + ctx.writeHead(204, { "Content-Length": "0" }); + await this.delete(uuid); + const evt = { + context: ctx, + object_id: uuid, + store: this + }; + await Promise.all([this.emitSync("Store.WebDelete", evt), object.__class.emitSync("Store.WebDelete", evt)]); + } diff --git a/packages/core/src/services/invitationservice.spec.ts b/packages/core/src/services/invitationservice.spec.ts index c5dec6ac3..c870fc129 100644 --- a/packages/core/src/services/invitationservice.spec.ts +++ b/packages/core/src/services/invitationservice.spec.ts @@ -8,14 +8,14 @@ import { Ident } from "../models/ident"; import { ModelMapLoader, ModelMapLoaderImplementation } from "../models/relations"; import { Store } from "../stores/store"; import { WebdaTest } from "../test"; -import { OperationContext } from "../utils/context"; +import { Context } from "../utils/context"; import { Authentication } from "./authentication"; import InvitationService, { InvitationParameters } from "./invitationservice"; import { Mailer } from "./mailer"; class MyCompany extends AclModel { name: string; - async canAct(ctx: OperationContext, action: string) { + async canAct(ctx: Context, action: string) { if (action === "create") { return; } diff --git a/packages/core/src/services/invitationservice.ts b/packages/core/src/services/invitationservice.ts index aa4da20cd..c990c9eb9 100644 --- a/packages/core/src/services/invitationservice.ts +++ b/packages/core/src/services/invitationservice.ts @@ -497,7 +497,7 @@ export default class InvitationService): string[] { + return this.parameters.scope; } /** @@ -460,7 +460,7 @@ export abstract class OAuthService< * * @param ctx */ - abstract handleToken(ctx: OperationContext): Promise; + abstract handleToken(ctx: Context): Promise; /** * Manage the return of a provider diff --git a/packages/core/src/services/service.ts b/packages/core/src/services/service.ts index 22a175e3a..94e69deba 100644 --- a/packages/core/src/services/service.ts +++ b/packages/core/src/services/service.ts @@ -3,14 +3,14 @@ import { deepmerge } from "deepmerge-ts"; import * as events from "events"; import { Constructor, + Context, Core, Counter, EventEmitterUtils, Gauge, Histogram, Logger, - MetricConfiguration, - OperationContext + MetricConfiguration } from "../index"; import { OpenAPIWebdaDefinition } from "../router"; import { HttpMethodType } from "../utils/httpcontext"; @@ -435,7 +435,7 @@ abstract class Service< * @param event * @param context */ - authorizeClientEvent(_event: string, _context: OperationContext): boolean { + authorizeClientEvent(_event: string, _context: Context): boolean { return false; } @@ -584,6 +584,7 @@ abstract class Service< /** * Emit the event with data and wait for Promise to finish if listener returned a Promise + * @deprecated */ emitSync(event: Key, data: E[Key]): Promise { return EventEmitterUtils.emitSync(this, event, data); @@ -601,11 +602,15 @@ abstract class Service< * Type the listener part * @param event * @param listener - * @param queue + * @param clusterWide if true will listen to all events even if they are not emitted by this cluster node * @returns */ - on(event: Key | symbol, listener: (evt: E[Key]) => void): this { - super.on(event, listener); + on(event: Key | symbol, listener: (evt: E[Key]) => void, clusterWide?: boolean): this { + if (clusterWide) { + super.on(event, listener); + } else { + super.on(event, evt => (evt.emitterId ? undefined : listener(evt))); + } return this; } @@ -615,8 +620,19 @@ abstract class Service< * @param callback * @param queue Name of queue to use, can be undefined, queue name are used to define differents priorities */ - onAsync(event: Key, listener: (evt: E[Key]) => void, queue: string = undefined) { - this._webda.getService("AsyncEvents").bindAsyncListener(this, event, listener, queue); + onAsync( + event: Key, + listener: (evt: E[Key]) => void, + queue: string = undefined, + clusterWide?: boolean + ) { + if (clusterWide) { + this._webda.getService("AsyncEvents").bindAsyncListener(this, event, listener, queue); + } else { + this._webda + .getService("AsyncEvents") + .bindAsyncListener(this, event, evt => (evt.emitterId ? undefined : listener(evt)), queue); + } } /** diff --git a/packages/core/src/stores/aliasstore.spec.ts b/packages/core/src/stores/aliasstore.spec.ts deleted file mode 100644 index 6c4e19220..000000000 --- a/packages/core/src/stores/aliasstore.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { suite, test } from "@testdeck/mocha"; -import { CoreModel, MapperService, Store } from "../index"; -import { AliasStore } from "./aliasstore"; -import { StoreTest } from "./store.spec"; - -@suite -class AliasStoreTest extends StoreTest { - async buildWebda() { - await super.buildWebda(); - let services = [ - new AliasStore(this.webda, "aliasUser", { - targetStore: "MemoryIdents", - model: "Webda/User", - idTemplate: "{id}", - expose: { - url: "/alias/users" - } - }), - new AliasStore(this.webda, "aliasIdent", { - targetStore: "MemoryIdents", - model: "WebdaTest/Ident", - idTemplate: "{id}", - expose: { - url: "/alias/idents" - }, - asyncDelete: true - }), - new MapperService(this.webda, "aliasMapper", { - source: "aliasIdent", - targetAttribute: "idents", - target: "aliasUser", - attribute: "_user", - fields: ["type", "_lastUpdate"], - cascade: true - }) - ]; - services.forEach(s => this.registerService(s)); - } - - getIdentStore(): Store { - // Need to slow down the _get - let store = >this.getService("MemoryIdents"); - store.getParameters().forceModel = false; - store.getParameters().strict = false; - // @ts-ignore - let original = store._get.bind(store); - // @ts-ignore - store._get = async (...args) => { - await this.sleep(1); - return original(...args); - }; - return this.getService("aliasIdent"); - } - - getUserStore(): Store { - const store = this.getService("aliasUser"); - // Monkey patch to allow switch from User to PermissionModel during test - store.setModel = (...args) => { - store._model = args[0]; - store._targetStore.setModel(...args); - store._targetStore.getParameters().forceModel = true; - store.getParameters().strict = false; - store._targetStore.getParameters().strict = false; - }; - return store; - } - - async getIndex(): Promise { - return undefined; - } - - @test - async httpCRUD() { - return super.httpCRUD("/alias/users"); - } - - @test - async modelActions() { - return super.modelActions("/alias/idents"); - } -} - -export { AliasStoreTest }; diff --git a/packages/core/src/stores/aliasstore.ts b/packages/core/src/stores/aliasstore.ts deleted file mode 100644 index 8488c60e4..000000000 --- a/packages/core/src/stores/aliasstore.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { CoreModel } from "../models/coremodel"; -import { Inject } from "../services/service"; -import { WebContext } from "../utils/context"; -import { Store, StoreEvents, StoreFindResult, StoreParameters } from "./store"; -import { WebdaQL } from "./webdaql/query"; - -export class AbstractAliasStoreParameters extends StoreParameters { - /** - * Store to alias - */ - targetStore: string; - - constructor(params: any, store: Store) { - super(params, store); - this.strict = true; - } -} - -/** - * AliasStore allow to expose a Store as another route with another model - * This is useful to store small collection in the registry - * - * The RegistryStore will default to a model, then specific models from the - * target collection can be exposed via an endpoint like users - * - */ -export abstract class AbstractAliasStore< - T extends CoreModel, - K extends AliasStoreParameters, - E extends StoreEvents -> extends Store { - @Inject("params:targetStore") - _targetStore: Store; - - /** - * @override - */ - find(query: WebdaQL.Query): Promise> { - let expr = new WebdaQL.AndExpression([new WebdaQL.ComparisonExpression("=", "__type", this._modelType)]); - // Will need to check for uuid query - if (query.filter) { - expr.children.push(query.filter); - } - query.filter = expr; - return this._targetStore.find(query); - } - - /** - * @override - */ - _exists(uid: string): Promise { - return this._targetStore._exists(this.generateUuidFromPublicId(uid)); - } - - /** - * @override - */ - async getAll(list?: string[]): Promise { - if (list) { - return this._targetStore.getAll(list.map(id => this.generateUuidFromPublicId(id))); - } else { - let res = []; - for await (const item of this._targetStore.iterate(`__type = '${this._modelType}'`)) { - res.push(item); - } - // Get all from find - return res; - } - } - - /** - * @override - */ - _removeAttribute( - uuid: string, - attribute: string, - itemWriteCondition?: any, - itemWriteConditionField?: string - ): Promise { - // @ts-ignore - return this._targetStore._removeAttribute( - this.generateUuidFromPublicId(uuid), - attribute, - itemWriteCondition, - itemWriteConditionField - ); - } - - /** - * @override - */ - _save(object: T): Promise { - // @ts-ignore - return this._targetStore._save(object); - } - - /** - * @override - */ - _upsertItemToCollection( - uid: string, - prop: string, - item: any, - index: number, - itemWriteCondition: any, - itemWriteConditionField: string, - updateDate: Date - ): Promise { - // @ts-ignore - return this._targetStore._upsertItemToCollection( - this.generateUuidFromPublicId(uid), - prop, - item, - index, - itemWriteCondition, - itemWriteConditionField, - updateDate - ); - } - - /** - * @override - */ - abstract generateUuidFromPublicId(id: string): string; - - /** - * @override - */ - async _get(uid: string, raiseIfNotFound?: boolean | undefined): Promise { - // @ts-ignore - return this._targetStore._get(this.generateUuidFromPublicId(uid), raiseIfNotFound); - } - - /** - * @override - */ - async _delete(uid: string, writeCondition?: any, itemWriteConditionField?: string | undefined): Promise { - // @ts-ignore - return this._targetStore._delete(this.generateUuidFromPublicId(uid), writeCondition, itemWriteConditionField); - } - - /** - * @override - */ - async _deleteItemFromCollection( - uid: string, - prop: string, - index: number, - itemWriteCondition: any, - itemWriteConditionField: string, - updateDate: Date - ): Promise { - // @ts-ignore - return this._targetStore._deleteItemFromCollection( - this.generateUuidFromPublicId(uid), - prop, - index, - itemWriteCondition, - itemWriteConditionField, - updateDate - ); - } - - /** - * @override - */ - async _incrementAttributes(uid: string, ...args): Promise { - // @ts-ignore - return this._targetStore._incrementAttributes( - this.generateUuidFromPublicId(uid), - // @ts-ignore - ...args - ); - } - - /** - * @override - */ - async _patch( - object: any, - uid: string, - itemWriteCondition?: any, - itemWriteConditionField?: string | undefined - ): Promise { - // @ts-ignore - return this._targetStore._patch( - object, - this.generateUuidFromPublicId(uid), - itemWriteCondition, - itemWriteConditionField - ); - } - - /** - * @override - */ - async _update( - object: any, - uid: string, - itemWriteCondition?: any, - itemWriteConditionField?: string | undefined - ): Promise { - // @ts-ignore - return this._targetStore._update( - object, - this.generateUuidFromPublicId(uid), - itemWriteCondition, - itemWriteConditionField - ); - } - - /** - * @override - */ - httpGet(ctx: WebContext): Promise { - ctx.getParameters().id = this.generateUuidFromPublicId(ctx.getParameters().id); - return super.httpGet(ctx); - } - - /** - * @override - */ - httpAction(ctx: WebContext): Promise { - ctx.getParameters().id = this.generateUuidFromPublicId(ctx.getParameters().id); - return super.httpAction(ctx); - } -} - -export class AliasStoreParameters extends AbstractAliasStoreParameters { - /** - * Store to alias - */ - idTemplate: string; -} -/** - * AliasStore allow to expose a Store as another route with another model - * This is useful to store small collection in the registry - * - * The RegistryStore will default to a model, then specific models from the - * target collection can be exposed via an endpoint like users - * - * @WebdaModda - */ -export class AliasStore< - T extends CoreModel = CoreModel, - K extends AliasStoreParameters = AliasStoreParameters, - E extends StoreEvents = StoreEvents -> extends AbstractAliasStore { - /** - * @override - */ - loadParameters(params: any): StoreParameters { - return new AliasStoreParameters(params, this); - } - - /** - * Use the idTemplate parameter to generate uuid - * @override - */ - generateUuidFromPublicId(id: string): string { - return this.parameters.idTemplate.replace(/\{id\}/g, id); - } -} diff --git a/packages/core/src/stores/file.ts b/packages/core/src/stores/file.ts index f7af2a13f..6cd2fae70 100644 --- a/packages/core/src/stores/file.ts +++ b/packages/core/src/stores/file.ts @@ -1,9 +1,9 @@ import * as fs from "fs"; import * as path from "path"; +import { WebdaQL } from "../../../webdaql/query"; import { CoreModel, FilterAttributes } from "../models/coremodel"; import { JSONUtils } from "../utils/serializers"; import { Store, StoreFindResult, StoreNotFoundError, StoreParameters } from "./store"; -import { WebdaQL } from "./webdaql/query"; class FileStoreParameters extends StoreParameters { /** diff --git a/packages/core/src/stores/memory.spec.ts b/packages/core/src/stores/memory.spec.ts index 68dd29e88..d77a69f67 100644 --- a/packages/core/src/stores/memory.spec.ts +++ b/packages/core/src/stores/memory.spec.ts @@ -2,12 +2,12 @@ import { suite, test } from "@testdeck/mocha"; import * as assert from "assert"; import { existsSync } from "fs"; import sinon from "sinon"; +import { WebdaQL } from "../../../webdaql/query"; import { AggregatorService, CoreModel, Ident, MemoryStore, Store, User, WebdaError } from "../index"; import { HttpContext } from "../utils/httpcontext"; import { FileUtils } from "../utils/serializers"; import { StoreNotFoundError } from "./store"; import { PermissionModel, StoreTest } from "./store.spec"; -import { WebdaQL } from "./webdaql/query"; @suite class MemoryStoreTest extends StoreTest { @@ -88,7 +88,7 @@ class MemoryStoreTest extends StoreTest { let res, offset; let total = 0; do { - res = await userStore.query(`state = 'CA' LIMIT 100 ${offset ? 'OFFSET "' + offset + '"' : ""}`, context); + res = await userStore.query(`state = 'CA' LIMIT 100 ${offset ? 'OFFSET "' + offset + '"' : ""}`); offset = res.continuationToken; total += res.results.length; } while (offset); diff --git a/packages/core/src/stores/memory.ts b/packages/core/src/stores/memory.ts index 2cc22c478..a92df5581 100644 --- a/packages/core/src/stores/memory.ts +++ b/packages/core/src/stores/memory.ts @@ -3,10 +3,10 @@ import { createReadStream, createWriteStream, existsSync } from "node:fs"; import { open } from "node:fs/promises"; import { Readable, Writable } from "node:stream"; import { createGzip } from "node:zlib"; +import { WebdaQL } from "../../../webdaql/query"; import { CoreModel } from "../models/coremodel"; import { GunzipConditional } from "../utils/serializers"; import { Store, StoreFindResult, StoreNotFoundError, StoreParameters, UpdateConditionFailError } from "./store"; -import { WebdaQL } from "./webdaql/query"; interface StorageMap { [key: string]: string; } diff --git a/packages/core/src/stores/store.spec.ts b/packages/core/src/stores/store.spec.ts index 30ce46952..8b8f33640 100644 --- a/packages/core/src/stores/store.spec.ts +++ b/packages/core/src/stores/store.spec.ts @@ -3,7 +3,7 @@ import * as assert from "assert"; import { stub } from "sinon"; import { v4 as uuidv4 } from "uuid"; import { Ident } from "../../test/models/ident"; -import { OperationContext, Store, StoreParameters, User, WebdaError } from "../index"; +import { Context, Core, Store, StoreParameters, User, WebdaError } from "../index"; import { CoreModel } from "../models/coremodel"; import { WebdaTest } from "../test"; import { HttpContext } from "../utils/httpcontext"; @@ -14,7 +14,7 @@ import { StoreEvents, StoreNotFoundError, UpdateConditionFailError } from "./sto */ export class PermissionModel extends CoreModel { order: number; - async canAct(ctx: OperationContext, _action: string): Promise { + async canAct(ctx: Context, _action: string): Promise { if (this.order % 200 < 120) { return false; } @@ -842,10 +842,13 @@ abstract class StoreTest extends WebdaTest { let store = this.getIdentStore(); let model = await store.save({ counter: 1 }); model.saveUpdateCompat = true; - await store.save(model, await this.newContext()); + await Core.get().runInContext(await this.newContext(), async () => { + await store.save(model); + }); model.saveInnerMethod = true; - model.setContext(await this.newContext()); - await model.save(); + await Core.get().runInContext(await this.newContext(), async () => { + await model.save(); + }); let model2 = await store.get(model.getUuid()); store.on("Store.Update", async () => { model2._lastUpdate = new Date(100); diff --git a/packages/core/src/stores/store.ts b/packages/core/src/stores/store.ts index 0da7e590a..64af7d386 100644 --- a/packages/core/src/stores/store.ts +++ b/packages/core/src/stores/store.ts @@ -1,10 +1,9 @@ +import { WebdaQL } from "../../../webdaql/query"; import { Counter, EventWithContext, Histogram, RegistryEntry } from "../core"; import { ConfigurationProvider, MemoryStore, ModelMapLoaderImplementation, Throttler, WebdaError } from "../index"; -import { Constructor, CoreModel, CoreModelDefinition, FilterAttributes, ModelAction } from "../models/coremodel"; -import { Route, Service, ServiceParameters } from "../services/service"; -import { OperationContext, WebContext } from "../utils/context"; -import { HttpMethodType } from "../utils/httpcontext"; -import { WebdaQL } from "./webdaql/query"; +import { Constructor, CoreModel, CoreModelDefinition, FilterAttributes } from "../models/coremodel"; +import { Service, ServiceParameters } from "../services/service"; +import { Context, GlobalContext, OperationContext, WebContext } from "../utils/context"; export class StoreNotFoundError extends WebdaError.CodeError { constructor(uuid: string, storeName: string) { @@ -24,6 +23,11 @@ export class UpdateConditionFailError extends WebdaError.CodeError { } interface EventStore { + /** + * Emitter node + * This is the node that emitted the event + */ + emitterId?: string; /** * Target object */ @@ -39,7 +43,7 @@ interface EventStore { /** * Context of the operation */ - context?: OperationContext; + context?: Context; } /** * Event called before save of an object @@ -211,7 +215,7 @@ export interface EventStoreQuery { /** * Context in which the query was run */ - context: OperationContext; + context: Context; } /** * Event sent when query is resolved @@ -313,56 +317,6 @@ export interface EventStoreWebDelete extends EventWithContext { store: Store; } -// REFACTOR . >= 4 -/** - * @deprecated Store should not be exposed directly anymore - * You should use the DomainService instead - */ -export type StoreExposeParameters = { - /** - * URL endpoint to use to expose REST Resources API - * - * @default service.getName().toLowerCase() - */ - url?: string; - /** - * You can restrict any part of the CRUD - * - * @default {} - */ - restrict?: { - /** - * Do not expose the POST - */ - create?: boolean; - /** - * Do not expose the PUT and PATCH - */ - update?: boolean; - /** - * Do not expose the GET - */ - get?: boolean; - /** - * Do not expose the DELETE - */ - delete?: boolean; - /** - * Do not expose the query endpoint - */ - query?: boolean; - }; - - /** - * For confidentiality sometimes you might prefer to expose query through PUT - * To avoid GET logging - * - * @default "GET" - */ - queryMethod?: "PUT" | "GET"; -}; -// END_REFACTOR - /** * Represent a query result on the Store */ @@ -407,14 +361,6 @@ export class StoreParameters extends ServiceParameters { * async delete */ asyncDelete: boolean; - // REFACTOR . >= 4.0 - /** - * Expose the service to an urls - * - * @deprecated will probably be removed in 4.0 in favor of Expose annotation - */ - expose?: StoreExposeParameters; - // END_REFACTOR /** * Allow to load object that does not have the type data @@ -432,6 +378,14 @@ export class StoreParameters extends ServiceParameters { * @default true */ defaultModel?: boolean; + /** + * Store security behavior + * + * In strict mode the store will only return object available to the current user + * In warning mode the store will return all objects but log a warning if objects is not available to current user + * In legacy mode the store will return all objects without any security check (apart from Web methods) + */ + securityContext: "STRICT" | "WARNING" | "LEGACY"; /** * If set, Store will ignore the __type * @@ -450,36 +404,16 @@ export class StoreParameters extends ServiceParameters { modelAliases?: { [key: string]: string }; /** * Disable default memory cache + * + * If you are running several instances of the same app without the cluster service, you should disable the cache */ noCache?: boolean; constructor(params: any, service: Service) { super(params); this.model ??= "Webda/CoreModel"; - let expose = params.expose; - if (typeof expose == "boolean") { - expose = {}; - expose.url = "/" + service.getName().toLowerCase(); - } else if (typeof expose == "string") { - expose = { - url: expose - }; - } else if (typeof expose == "object" && expose.url == undefined) { - expose.url = "/" + service.getName().toLowerCase(); - } - if (expose) { - expose.restrict = expose.restrict || {}; - this.expose = expose; - this.expose.queryMethod ??= "GET"; - this.url = expose.url; - } - if (params.map) { - throw new Error("Deprecated map usage, use a MapperService"); - } - if (params.index) { - throw new Error("Deprecated index usage, use an AggregatorService"); - } this.strict ??= false; + this.securityContext ??= "STRICT"; this.defaultModel ??= true; this.forceModel ??= false; this.slowQueryThreshold ??= 30000; @@ -587,14 +521,13 @@ abstract class Store< protected _uuidField: string = "uuid"; /** * Add metrics counter - * ' UNION SELECT name, tbl_name as email, "" as col1, "" as col2, "" as col3, "" as col4, "" as col5, "" as col6, "" as col7, "" as col8 FROM sqlite_master -- - * {"email":"' UNION SELECT name as profileImage, tbl_name as email, '' AS column3 FROM sqlite_master --","password":"we"} */ metrics: { cache_invalidations: Counter; operations_total: Counter; slow_queries_total: Counter; cache_hits: Counter; + security_context_warnings: Counter; queries: Histogram; }; @@ -652,9 +585,6 @@ abstract class Store< } } } - if (this.getParameters().expose) { - this.log("WARN", "Exposing a store is not recommended, use a DomainService instead to expose all your CoreModel"); - } } logSlowQuery(_query: string, _reason: string, _time: number) { @@ -686,6 +616,10 @@ abstract class Store< name: "slow_queries", help: "Number of slow queries encountered" }); + this.metrics.security_context_warnings = this.getMetric(Counter, { + name: "security_context_warnings", + help: "Number of security context violation if in WARNING mode" + }); this.metrics.cache_invalidations = this.getMetric(Counter, { name: "cache_invalidations", help: "Number of cache invalidation encountered" @@ -725,7 +659,7 @@ abstract class Store< * @param raiseIfNotFound * @returns */ - async _getFromCache(uuid: string, raiseIfNotFound: boolean = false): Promise { + protected async _getFromCache(uuid: string, raiseIfNotFound: boolean = false): Promise { let res = await this._cacheStore?._get(uuid); if (!res) { res = await this._get(uuid, raiseIfNotFound); @@ -749,132 +683,6 @@ abstract class Store< return this._getFromCache(uid); } - /** - * @override - */ - getUrl(url: string, methods: HttpMethodType[]) { - // If url is absolute - if (url.startsWith("/")) { - return url; - } - - // Parent url to find here - const expose = this.parameters.expose; - if ( - !expose.url || - (url === "." && methods.includes("POST") && expose.restrict.create) || - (url === "./{uuid}" && methods.includes("DELETE") && expose.restrict.delete) || - (url === "./{uuid}" && methods.includes("PATCH") && expose.restrict.update) || - (url === "./{uuid}" && methods.includes("PUT") && expose.restrict.update) || - (url === "./{uuid}" && methods.includes("GET") && expose.restrict.get) || - (url === ".{?q}" && methods.includes("GET") && expose.restrict.query) || - (url === "." && methods.includes("PUT") && expose.restrict.query) - ) { - return undefined; - } - return super.getUrl(url, methods); - } - - static getOpenAPI() {} - /** - * @inheritdoc - */ - initRoutes() { - if (!this.parameters.expose) { - return; - } - super.initRoutes(); - // We enforce ExposeParameters within the constructor - const expose = this.parameters.expose; - this.getWebda().getRouter().registerModelUrl(this.parameters.model, expose.url); - - // Query endpoint - if (!expose.restrict.query) { - let requestBody; - if (expose.queryMethod === "PUT") { - requestBody = { - content: { - "application/json": { - schema: { - properties: { - q: { - type: "string" - } - } - } - } - } - }; - } - this.addRoute(expose.queryMethod === "GET" ? `.{?q}` : ".", [expose.queryMethod], this.httpQuery, { - model: this.parameters.model, - [expose.queryMethod.toLowerCase()]: { - description: `Query on ${this.parameters.model} model with WebdaQL`, - summary: "Query " + this.parameters.model, - operationId: `query${this._model.name}`, - requestBody, - responses: { - "200": { - description: `Retrieve models ${this._model.name}`, - content: { - "application/json": { - schema: { - properties: { - continuationToken: { - type: "string" - }, - results: { - type: "array", - items: { - $ref: `#/components/schemas/${this._model.name}` - } - } - } - } - } - } - }, - "400": { - description: "Query is invalid" - }, - "403": { - description: "You don't have permissions" - } - } - } - }); - } - - // Model actions - if (this._model && this._model.getActions) { - let actions = this._model.getActions(); - Object.keys(actions).forEach(name => { - let action: ModelAction = actions[name]; - action.method ??= name; - if (!action.methods) { - action.methods = ["PUT"]; - } - let executer; - if (action.global) { - // By default will grab the object and then call the action - if (!this._model[action.method]) { - throw Error("Action static method " + action.method + " does not exist"); - } - executer = this.httpGlobalAction; - this.addRoute(`./${name}`, action.methods, executer, action.openapi); - } else { - // By default will grab the object and then call the action - if (!this._model.prototype[action.method]) { - throw Error("Action method " + action.method + " does not exist"); - } - executer = ctx => this.httpAction(ctx, action.method); - - this.addRoute(`./{uuid}/${name}`, action.methods, executer, action.openapi); - } - }); - } - } - /** * OVerwrite the model * Used mainly in test @@ -888,7 +696,7 @@ abstract class Store< /** * We should ignore exception from the store */ - cacheStorePatchException() { + protected cacheStorePatchException() { const replacer = original => { return (...args) => { return original @@ -956,23 +764,11 @@ abstract class Store< object[this._reverseMap[i].property][j] = this._reverseMap[i].mapper.newModel( object[this._reverseMap[i].property][j] ); - object[this._reverseMap[i].property][j].setContext(object.getContext()); } } return object; } - /** - * Get a new model with this data preloaded - * @param object - * @returns - */ - newModel(object: any = {}) { - let result = this.initModel(object); - Object.keys(object).forEach(k => result.__dirty.add(k)); - return result; - } - /** * Add reverse map information * @@ -980,7 +776,7 @@ abstract class Store< * @param cascade * @param store */ - addReverseMap(prop: string, store: MappingService) { + protected addReverseMap(prop: string, store: MappingService) { this._reverseMap.push({ property: prop, mapper: store @@ -1003,6 +799,7 @@ abstract class Store< if (params.length === 0) { return; } + await this.checkContext(uid, "update"); let updateDate = new Date(); this.metrics.operations_total.inc({ operation: "increment" }); await this._incrementAttributes(uid, params, updateDate); @@ -1018,6 +815,31 @@ abstract class Store< return updateDate; } + /** + * Check if a user can act on an object + * @param model + */ + protected checkContext(model: string | CoreModel, action: string): Promise | null { + // We do not want to check in legacy + if (this.parameters.securityContext === "LEGACY") return null; + const context = Context.get(); + // We are running as system + if (context instanceof GlobalContext) return null; + return (async () => { + if (typeof model === "string") { + model = await this._getFromCache(model); + } + if (!model.canAct(Context.get(), action)) { + if (this.parameters.securityContext === "WARNING") { + this.metrics.security_context_warnings.inc(); + this.log("WARN", `Security context violation on ${model.getUuid()} for action ${action}`); + } else { + throw new WebdaError.Unauthorized(`Security context violation on ${model.getUuid()} for action ${action}`); + } + } + })(); + } + /** * Helper function that call incrementAttributes * @param uid @@ -1047,6 +869,7 @@ abstract class Store< itemWriteCondition: any = undefined, itemWriteConditionField: string = this._uuidField ) { + await this.checkContext(uid, "update"); let updateDate = new Date(); this.metrics.operations_total.inc({ operation: "collectionUpsert" }); await this._upsertItemToCollection( @@ -1090,6 +913,7 @@ abstract class Store< itemWriteCondition: any, itemWriteConditionField: string = this._uuidField ) { + await this.checkContext(uid, "update"); let updateDate = new Date(); this.metrics.operations_total.inc({ operation: "collectionDelete" }); await this._deleteItemFromCollection( @@ -1122,14 +946,14 @@ abstract class Store< * @param query * @param context */ - async *iterate(query: string = "", context?: OperationContext): AsyncGenerator { + async *iterate(query: string = "", context?: Context): AsyncGenerator { if (query.includes("OFFSET")) { throw new Error("Cannot contain an OFFSET for iterate method"); } let continuationToken; do { let q = query + (continuationToken !== undefined ? ` OFFSET "${continuationToken}"` : ""); - let page = await this.query(q, context); + let page = await this.query(q); for (let item of page.results) { yield item; } @@ -1137,25 +961,6 @@ abstract class Store< } while (continuationToken); } - // REFACTOR . >= 4 - /** - * Query all the results - * - * - * @param query - * @param context - * @returns - * @deprecated use iterate instead - */ - async queryAll(query: string, context?: OperationContext): Promise { - let res = []; - for await (let item of this.iterate(query, context)) { - res.push(item); - } - return res; - } - // END_REFACTOR - /** * Check that __type Comparison is only used with = and CONTAINS * If CONTAINS is used, move __type to __types @@ -1169,8 +974,9 @@ abstract class Store< * @param query * @param context to apply permission */ - async query(query: string, context?: OperationContext): Promise<{ results: T[]; continuationToken?: string }> { - let permissionQuery = this._model.getPermissionQuery(context); + async query(query: string): Promise<{ results: T[]; continuationToken?: string }> { + const context = this.parameters.securityContext === "LEGACY" ? undefined : Context.get(); + let permissionQuery = context instanceof OperationContext ? this._model.getPermissionQuery(context) : undefined; let partialPermission = true; let fullQuery = query; if (permissionQuery) { @@ -1221,7 +1027,6 @@ abstract class Store< } let subOffsetCount = 0; for (let item of tmpResults.results) { - item.setContext(context); // Because of dynamic filter and permission we need to suboffset the pagination subOffsetCount++; if (subOffsetCount <= secondOffset) { @@ -1280,7 +1085,7 @@ abstract class Store< query = WebdaQL.unsanitize((await ctx.getRequestBody()).q); } try { - ctx.write(await this.query(query, ctx)); + ctx.write(await this.query(query)); } catch (err) { if (err instanceof SyntaxError) { this.log("INFO", "Query syntax error"); @@ -1359,14 +1164,12 @@ abstract class Store< * * Might want to rename to create */ - async save(object, ctx: OperationContext = undefined): Promise { + async save(object): Promise { if (object instanceof this._model && object._creationDate !== undefined && object._lastUpdate !== undefined) { - if (ctx) { - object.setContext(ctx); - } + await this.checkContext(object, "update"); return await object.save(); } - return this.create(object, ctx); + return this.create(object); } /** @@ -1375,8 +1178,9 @@ abstract class Store< * @param ctx * @returns */ - async create(object, ctx: OperationContext = undefined) { + async create(object, ctx: Context = undefined) { object = this.initModel(object); + await this.checkContext(object, "create"); // Dates should be store by the Store if (!object._creationDate) { @@ -1387,9 +1191,6 @@ abstract class Store< const ancestors = this.getWebda().getApplication().getModelHierarchy(object.__type).ancestors; object.__types = [object.__type, ...ancestors].filter(i => i !== "Webda/CoreModel" && i !== "CoreModel"); - if (ctx) { - object.setContext(ctx); - } // Handle object auto listener const evt = { object: object, @@ -1445,7 +1246,7 @@ abstract class Store< * @param condition * @param uid */ - checkUpdateCondition(model: T, conditionField?: CK, condition?: any, uid?: string) { + protected checkUpdateCondition(model: T, conditionField?: CK, condition?: any, uid?: string) { if (conditionField) { // Add toString to manage Date object if (model[conditionField].toString() !== condition.toString()) { @@ -1461,7 +1262,7 @@ abstract class Store< * @param condition * @param uid */ - checkCollectionUpdateCondition>, CK extends keyof T>( + protected checkCollectionUpdateCondition>, CK extends keyof T>( model: T, collection: FK, conditionField?: CK, @@ -1497,6 +1298,7 @@ abstract class Store< condition: any ): Promise { try { + await this.checkContext(uuid, "update"); await this._patch(updates, uuid, condition, conditionField); // CoreModel should also emit this one but cannot do within this context await this.emitStoreEvent("Store.PartialUpdated", { @@ -1586,14 +1388,12 @@ abstract class Store< this.metrics.operations_total.inc({ operation: "get" }); const uuid = object.getUuid ? object.getUuid() : object[this._uuidField]; let load = await this._getFromCache(uuid, true); + await this.checkContext(load, "update"); if (load.__type !== this._modelType && this.parameters.strict) { this.log("WARN", `Object '${uuid}' was not created by this store ${load.__type}:${this._modelType}`); throw new StoreNotFoundError(uuid, this.getName()); } loaded = this.initModel(load); - if (object instanceof CoreModel) { - loaded.setContext(object.getContext()); - } const update = object; const evt = { object: loaded, @@ -1774,41 +1574,43 @@ abstract class Store< patcher: (object: T) => Promise | (() => Promise) | undefined>, batchSize: number = 500 ) { - let status: RegistryEntry<{ - continuationToken?: string; - count: number; - updated: number; - done: boolean; - }> = await this.getWebda().getRegistry().get(`storeMigration.${this.getName()}.${name}`, undefined, {}); - status.count ??= 0; - status.updated ??= 0; - const worker = new Throttler(20); - do { - const res = await this.query( - status.continuationToken ? `LIMIT ${batchSize} OFFSET "${status.continuationToken}"` : `LIMIT ${batchSize}` - ); - status.count += res.results.length; - for (let item of res.results) { - let updated = await patcher(item); - if (updated !== undefined) { - status.updated++; - if (typeof updated === "function") { - worker.queue(updated); - } else { - worker.queue(async () => { - await item.patch(>updated, null); - }); + return this.getWebda().runAsSystem(async () => { + let status: RegistryEntry<{ + continuationToken?: string; + count: number; + updated: number; + done: boolean; + }> = await this.getWebda().getRegistry().get(`storeMigration.${this.getName()}.${name}`, undefined); + status.count ??= 0; + status.updated ??= 0; + const worker = new Throttler(20); + do { + const res = await this.query( + status.continuationToken ? `LIMIT ${batchSize} OFFSET "${status.continuationToken}"` : `LIMIT ${batchSize}` + ); + status.count += res.results.length; + for (let item of res.results) { + let updated = await patcher(item); + if (updated !== undefined) { + status.updated++; + if (typeof updated === "function") { + worker.queue(updated); + } else { + worker.queue(async () => { + await item.patch(>updated, null); + }); + } } } - } - this.log( - "INFO", - `storeMigration.${this.getName()}.${name}: Migrated ${status.count} items: ${status.updated} updated` - ); - status.continuationToken = res.continuationToken; - await worker.wait(); - await status.save(); - } while (status.continuationToken); + this.log( + "INFO", + `storeMigration.${this.getName()}.${name}: Migrated ${status.count} items: ${status.updated} updated` + ); + status.continuationToken = res.continuationToken; + await worker.wait(); + await status.save(); + } while (status.continuationToken); + }); } /** @@ -1824,6 +1626,7 @@ abstract class Store< itemWriteCondition?: any, itemWriteConditionField?: CK ) { + await this.checkContext(uuid, "update"); this.metrics.operations_total.inc({ operation: "attributeDelete" }); await this._removeAttribute(uuid, attribute, itemWriteCondition, itemWriteConditionField); await this.emitStoreEvent("Store.PartialUpdated", { @@ -1893,6 +1696,7 @@ abstract class Store< throw new UpdateConditionFailError(to_delete.getUuid(), writeConditionField, writeCondition); } } + await this.checkContext(to_delete, "delete"); const evt = { object: to_delete, object_id: to_delete.getUuid(), @@ -1956,19 +1760,21 @@ abstract class Store< * @returns {Promise>} */ async getConfiguration(id: string): Promise<{ [key: string]: any }> { - this.metrics.operations_total.inc({ operation: "get" }); - let object = await this._getFromCache(id); - if (!object) { - return undefined; - } - let result: { [key: string]: any } = {}; - for (let i in object) { - if (i === this._uuidField || i === "_lastUpdate" || i.startsWith("_")) { - continue; + return this.getWebda().runAsSystem(async () => { + this.metrics.operations_total.inc({ operation: "get" }); + let object = await this._getFromCache(id); + if (!object) { + return undefined; } - result[i] = object[i]; - } - return result; + let result: { [key: string]: any } = {}; + for (let i in object) { + if (i === this._uuidField || i === "_lastUpdate" || i.startsWith("_")) { + continue; + } + result[i] = object[i]; + } + return result; + }); } /** @@ -1989,7 +1795,7 @@ abstract class Store< * @param {String} uuid to get * @return {Promise} the object retrieved ( can be undefined if not found ) */ - async get(uid: string, ctx: OperationContext = undefined, defaultValue: any = undefined): Promise { + async get(uid: string, defaultValue: any = undefined): Promise { /** @ignore */ if (!uid) { return undefined; @@ -1999,17 +1805,16 @@ abstract class Store< if (!object) { return defaultValue ? this.initModel(defaultValue).setUuid(uid) : undefined; } + await this.checkContext(object, "get"); if (object.__type !== this._modelType && this.parameters.strict) { this.log("WARN", `Object '${uid}' was not created by this store ${object.__type}:${this._modelType}`); return undefined; } object = this.initModel(object); - object.setContext(ctx); const evt = { object: object, object_id: object.getUuid(), - store: this, - context: ctx + store: this }; await Promise.all([this.emitSync("Store.Get", evt), object.__class.emitSync("Store.Get", evt), object._onGet()]); return object; @@ -2101,356 +1906,6 @@ abstract class Store< modelName: this._model.name }; } - - /** - * Handle POST - * @param ctx - */ - @Route(".", ["POST"], { - post: { - description: "The way to create a new ${modelName} model", - summary: "Create a new ${modelName}", - operationId: "create${modelName}", - requestBody: { - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/${modelName}" - } - } - } - }, - responses: { - "200": { - description: "Retrieve model", - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/${modelName}" - } - } - } - }, - "400": { - description: "Object is invalid" - }, - "403": { - description: "You don't have permissions" - }, - "409": { - description: "Object already exists" - } - } - } - }) - async httpCreate(ctx: WebContext) { - return this.operationCreate(ctx, this.parameters.model); - } - - /** - * Create a new object based on the context - * @param ctx - * @param model - */ - async operationCreate(ctx: OperationContext, model: string) { - let body = await ctx.getInput(); - const modelPrototype = this.getWebda().getApplication().getModel(model); - let object = modelPrototype.factory(body, ctx); - object._creationDate = new Date(); - await object.checkAct(ctx, "create"); - try { - await object.validate(ctx, body); - } catch (err) { - this.log("INFO", "Object is not valid", err); - throw new WebdaError.BadRequest("Object is not valid"); - } - if (object[this._uuidField] && (await this.exists(object[this._uuidField]))) { - throw new WebdaError.Conflict("Object already exists"); - } - await this.save(object, ctx); - ctx.write(object); - const evt = { - context: ctx, - values: body, - object: object, - object_id: object.getUuid(), - store: this - }; - await Promise.all([object.__class.emitSync("Store.WebCreate", evt), this.emitSync("Store.WebCreate", evt)]); - } - - /** - * Handle obect action - * @param ctx - */ - async httpAction(ctx: WebContext, actionMethod?: string) { - let action = ctx.getHttpContext().getUrl().split("/").pop(); - actionMethod ??= action; - let object = await this.get(ctx.parameter("uuid"), ctx); - if (object === undefined || object.__deleted) { - throw new WebdaError.NotFound("Object not found or is deleted"); - } - const inputSchema = `${object.__class.getIdentifier(false)}.${action}.input`; - if (this.getWebda().getApplication().hasSchema(inputSchema)) { - const input = await ctx.getInput(); - try { - this.getWebda().validateSchema(inputSchema, input); - } catch (err) { - this.log("INFO", "Object invalid", err); - this.log("INFO", "Object invalid", inputSchema, input, this.getWebda().getApplication().getSchema(inputSchema)); - throw new WebdaError.BadRequest("Body is invalid"); - } - } - await object.checkAct(ctx, action); - const evt = { - action: action, - object: object, - store: this, - context: ctx - }; - await Promise.all([this.emitSync("Store.Action", evt), object.__class.emitSync("Store.Action", evt)]); - const res = await object[actionMethod](ctx); - if (res) { - ctx.write(res); - } - const evtActioned = { - action: action, - object: object, - store: this, - context: ctx, - result: res - }; - await Promise.all([ - this.emitSync("Store.Actioned", evtActioned), - object?.__class.emitSync("Store.Actioned", evtActioned) - ]); - } - - /** - * Handle collection action - * @param ctx - */ - async httpGlobalAction(ctx: WebContext, model: CoreModelDefinition = this._model) { - let action = ctx.getHttpContext().getUrl().split("/").pop(); - const evt = { - action: action, - store: this, - context: ctx, - model - }; - await Promise.all([this.emitSync("Store.Action", evt), model.emitSync("Store.Action", evt)]); - const res = await model[action](ctx); - if (res) { - ctx.write(res); - } - const evtActioned = { - action: action, - store: this, - context: ctx, - result: res, - model - }; - await Promise.all([this.emitSync("Store.Actioned", evtActioned), model?.emitSync("Store.Actioned", evtActioned)]); - } - - /** - * Handle HTTP Update for an object - * - * @param ctx context of the request - */ - @Route("./{uuid}", ["PUT", "PATCH"], { - put: { - description: "Update a ${modelName} if the permissions allow", - summary: "Update a ${modelName}", - operationId: "update${modelName}", - schemas: { - input: "${modelName}", - output: "${modelName}" - }, - responses: { - "200": {}, - "400": { - description: "Object is invalid" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - }, - patch: { - description: "Patch a ${modelName} if the permissions allow", - summary: "Patch a ${modelName}", - operationId: "partialUpdatet${modelName}", - schemas: { - input: "${modelName}" - }, - responses: { - "204": { - description: "" - }, - "400": { - description: "Object is invalid" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - } - }) - async httpUpdate(ctx: WebContext) { - const { uuid } = ctx.getParameters(); - const body = await ctx.getInput(); - body[this._uuidField] = uuid; - let object = await this.get(uuid, ctx); - if (!object || object.__deleted) throw new WebdaError.NotFound("Object not found or is deleted"); - await object.checkAct(ctx, "update"); - if (ctx.getHttpContext().getMethod() === "PATCH") { - try { - await object.validate(ctx, body, true); - } catch (err) { - this.log("INFO", "Object invalid", err, object); - throw new WebdaError.BadRequest("Object is not valid"); - } - let updateObject: any = new this._model(); - // Clean any default attributes from the model - Object.keys(updateObject) - .filter(i => i !== "__class") - .forEach(i => { - delete updateObject[i]; - }); - updateObject.setContext(ctx); - updateObject.setUuid(uuid); - updateObject.load(body, false, false); - await this.patch(updateObject); - object = undefined; - } else { - let updateObject: any = new this._model(); - updateObject.setContext(ctx); - updateObject.load(body); - // Copy back the _ attributes - Object.keys(object) - .filter(i => i.startsWith("_")) - .forEach(i => { - updateObject[i] = object[i]; - }); - try { - await updateObject.validate(ctx, body); - } catch (err) { - this.log("INFO", "Object invalid", err); - throw new WebdaError.BadRequest("Object is not valid"); - } - - // Add mappers back to - object = await this.update(updateObject); - } - ctx.write(object); - const evt = { - context: ctx, - updates: body, - object: object, - store: this, - method: <"PATCH" | "PUT">ctx.getHttpContext().getMethod() - }; - await Promise.all([object?.__class.emitSync("Store.WebUpdate", evt), this.emitSync("Store.WebUpdate", evt)]); - } - - /** - * Handle GET on object - * - * @param ctx context of the request - */ - @Route("./{uuid}", ["GET"], { - get: { - description: "Retrieve ${modelName} model if permissions allow", - summary: "Retrieve a ${modelName}", - operationId: "get${modelName}", - schemas: { - output: "${modelName}" - }, - responses: { - "200": {}, - "400": { - description: "Object is invalid" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - } - }) - async httpGet(ctx: WebContext) { - let uuid = ctx.parameter("uuid"); - let object = await this.get(uuid, ctx); - await this.emitSync("Store.WebGetNotFound", { - context: ctx, - uuid, - store: this - }); - if (object === undefined || object.__deleted) { - throw new WebdaError.NotFound("Object not found or is deleted"); - } - await object.checkAct(ctx, "get"); - ctx.write(object); - const evt = { - context: ctx, - object: object, - store: this - }; - await Promise.all([this.emitSync("Store.WebGet", evt), object.__class.emitSync("Store.WebGet", evt)]); - ctx.write(object); - } - - /** - * Handle HTTP request - * - * @param ctx context of the request - * @returns - */ - @Route("./{uuid}", ["DELETE"], { - delete: { - operationId: "delete${modelName}", - description: "Delete ${modelName} if the permissions allow", - summary: "Delete a ${modelName}", - responses: { - "204": { - description: "" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - } - }) - async httpDelete(ctx: WebContext) { - let uuid = ctx.parameter("uuid"); - let object = await this.get(uuid, ctx); - if (!object || object.__deleted) throw new WebdaError.NotFound("Object not found or is deleted"); - await object.checkAct(ctx, "delete"); - // http://stackoverflow.com/questions/28684209/huge-delay-on-delete-requests-with-204-response-and-no-content-in-objectve-c# - // IOS don't handle 204 with Content-Length != 0 it seems - // Might still run into: Have trouble to handle the Content-Length on API Gateway so returning an empty object for now - ctx.writeHead(204, { "Content-Length": "0" }); - await this.delete(uuid); - const evt = { - context: ctx, - object_id: uuid, - store: this - }; - await Promise.all([this.emitSync("Store.WebDelete", evt), object.__class.emitSync("Store.WebDelete", evt)]); - } - /** * Return the model uuid field */ diff --git a/packages/core/src/stores/webdaql/WebdaQLLexer.interp b/packages/core/src/stores/webdaql/WebdaQLLexer.interp deleted file mode 100644 index ffd6388a2..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLLexer.interp +++ /dev/null @@ -1,119 +0,0 @@ -token literal names: -null -null -'(' -')' -',' -'\'' -'"' -'[' -']' -'AND' -'OR' -'=' -'!=' -'>' -'>=' -'<' -'<=' -'LIKE' -'IN' -'CONTAINS' -'TRUE' -'FALSE' -'LIMIT' -'OFFSET' -'ORDER BY' -'ASC' -'DESC' -null -null -null -null -null -null - -token symbolic names: -null -SPACE -LR_BRACKET -RR_BRACKET -COMMA -SINGLE_QUOTE_SYMB -DOUBLE_QUOTE_SYMB -LR_SQ_BRACKET -RR_SQ_BRACKET -AND -OR -EQUAL -NOT_EQUAL -GREATER -GREATER_OR_EQUAL -LESS -LESS_OR_EQUAL -LIKE -IN -CONTAINS -TRUE -FALSE -LIMIT -OFFSET -ORDER_BY -ASC -DESC -DQUOTED_STRING_LITERAL -SQUOTED_STRING_LITERAL -INTEGER_LITERAL -IDENTIFIER -IDENTIFIER_WITH_NUMBER -FUNCTION_IDENTIFIER_WITH_UNDERSCORE - -rule names: -SPACE -ID_LITERAL -DQUOTA_STRING -SQUOTA_STRING -INT_DIGIT -FN_LITERAL -LR_BRACKET -RR_BRACKET -COMMA -SINGLE_QUOTE_SYMB -DOUBLE_QUOTE_SYMB -LR_SQ_BRACKET -RR_SQ_BRACKET -QUOTE_SYMB -AND -OR -EQUAL -NOT_EQUAL -GREATER -GREATER_OR_EQUAL -LESS -LESS_OR_EQUAL -LIKE -IN -CONTAINS -TRUE -FALSE -LIMIT -OFFSET -ORDER_BY -ASC -DESC -DQUOTED_STRING_LITERAL -SQUOTED_STRING_LITERAL -INTEGER_LITERAL -IDENTIFIER -IDENTIFIER_WITH_NUMBER -FUNCTION_IDENTIFIER_WITH_UNDERSCORE - -channel names: -DEFAULT_TOKEN_CHANNEL -HIDDEN - -mode names: -DEFAULT_MODE - -atn: -[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 34, 251, 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, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 3, 2, 6, 2, 81, 10, 2, 13, 2, 14, 2, 82, 3, 2, 3, 2, 3, 3, 6, 3, 88, 10, 3, 13, 3, 14, 3, 89, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 98, 10, 4, 12, 4, 14, 4, 101, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 7, 5, 111, 10, 5, 12, 5, 14, 5, 114, 11, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 7, 7, 122, 10, 7, 12, 7, 14, 7, 125, 11, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 5, 15, 143, 10, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 35, 3, 35, 3, 36, 6, 36, 231, 10, 36, 13, 36, 14, 36, 232, 3, 37, 6, 37, 236, 10, 37, 13, 37, 14, 37, 237, 3, 38, 6, 38, 241, 10, 38, 13, 38, 14, 38, 242, 3, 39, 3, 39, 7, 39, 247, 10, 39, 12, 39, 14, 39, 250, 11, 39, 2, 2, 2, 40, 3, 2, 3, 5, 2, 2, 7, 2, 2, 9, 2, 2, 11, 2, 2, 13, 2, 2, 15, 2, 4, 17, 2, 5, 19, 2, 6, 21, 2, 7, 23, 2, 8, 25, 2, 9, 27, 2, 10, 29, 2, 2, 31, 2, 11, 33, 2, 12, 35, 2, 13, 37, 2, 14, 39, 2, 15, 41, 2, 16, 43, 2, 17, 45, 2, 18, 47, 2, 19, 49, 2, 20, 51, 2, 21, 53, 2, 22, 55, 2, 23, 57, 2, 24, 59, 2, 25, 61, 2, 26, 63, 2, 27, 65, 2, 28, 67, 2, 29, 69, 2, 30, 71, 2, 31, 73, 2, 32, 75, 2, 33, 77, 2, 34, 3, 2, 11, 5, 2, 11, 12, 15, 15, 34, 34, 5, 2, 50, 59, 67, 92, 99, 124, 4, 2, 36, 36, 94, 94, 4, 2, 41, 41, 94, 94, 3, 2, 50, 59, 3, 2, 67, 92, 4, 2, 67, 92, 97, 97, 4, 2, 67, 92, 99, 124, 7, 2, 48, 48, 50, 59, 67, 92, 97, 97, 99, 124, 2, 258, 2, 3, 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, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 2, 77, 3, 2, 2, 2, 3, 80, 3, 2, 2, 2, 5, 87, 3, 2, 2, 2, 7, 91, 3, 2, 2, 2, 9, 104, 3, 2, 2, 2, 11, 117, 3, 2, 2, 2, 13, 119, 3, 2, 2, 2, 15, 126, 3, 2, 2, 2, 17, 128, 3, 2, 2, 2, 19, 130, 3, 2, 2, 2, 21, 132, 3, 2, 2, 2, 23, 134, 3, 2, 2, 2, 25, 136, 3, 2, 2, 2, 27, 138, 3, 2, 2, 2, 29, 142, 3, 2, 2, 2, 31, 144, 3, 2, 2, 2, 33, 148, 3, 2, 2, 2, 35, 151, 3, 2, 2, 2, 37, 153, 3, 2, 2, 2, 39, 156, 3, 2, 2, 2, 41, 158, 3, 2, 2, 2, 43, 161, 3, 2, 2, 2, 45, 163, 3, 2, 2, 2, 47, 166, 3, 2, 2, 2, 49, 171, 3, 2, 2, 2, 51, 174, 3, 2, 2, 2, 53, 183, 3, 2, 2, 2, 55, 188, 3, 2, 2, 2, 57, 194, 3, 2, 2, 2, 59, 200, 3, 2, 2, 2, 61, 207, 3, 2, 2, 2, 63, 216, 3, 2, 2, 2, 65, 220, 3, 2, 2, 2, 67, 225, 3, 2, 2, 2, 69, 227, 3, 2, 2, 2, 71, 230, 3, 2, 2, 2, 73, 235, 3, 2, 2, 2, 75, 240, 3, 2, 2, 2, 77, 244, 3, 2, 2, 2, 79, 81, 9, 2, 2, 2, 80, 79, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 80, 3, 2, 2, 2, 82, 83, 3, 2, 2, 2, 83, 84, 3, 2, 2, 2, 84, 85, 8, 2, 2, 2, 85, 4, 3, 2, 2, 2, 86, 88, 9, 3, 2, 2, 87, 86, 3, 2, 2, 2, 88, 89, 3, 2, 2, 2, 89, 87, 3, 2, 2, 2, 89, 90, 3, 2, 2, 2, 90, 6, 3, 2, 2, 2, 91, 99, 7, 36, 2, 2, 92, 93, 7, 94, 2, 2, 93, 98, 11, 2, 2, 2, 94, 95, 7, 36, 2, 2, 95, 98, 7, 36, 2, 2, 96, 98, 10, 4, 2, 2, 97, 92, 3, 2, 2, 2, 97, 94, 3, 2, 2, 2, 97, 96, 3, 2, 2, 2, 98, 101, 3, 2, 2, 2, 99, 97, 3, 2, 2, 2, 99, 100, 3, 2, 2, 2, 100, 102, 3, 2, 2, 2, 101, 99, 3, 2, 2, 2, 102, 103, 7, 36, 2, 2, 103, 8, 3, 2, 2, 2, 104, 112, 7, 41, 2, 2, 105, 106, 7, 94, 2, 2, 106, 111, 11, 2, 2, 2, 107, 108, 7, 41, 2, 2, 108, 111, 7, 41, 2, 2, 109, 111, 10, 5, 2, 2, 110, 105, 3, 2, 2, 2, 110, 107, 3, 2, 2, 2, 110, 109, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 115, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 116, 7, 41, 2, 2, 116, 10, 3, 2, 2, 2, 117, 118, 9, 6, 2, 2, 118, 12, 3, 2, 2, 2, 119, 123, 9, 7, 2, 2, 120, 122, 9, 8, 2, 2, 121, 120, 3, 2, 2, 2, 122, 125, 3, 2, 2, 2, 123, 121, 3, 2, 2, 2, 123, 124, 3, 2, 2, 2, 124, 14, 3, 2, 2, 2, 125, 123, 3, 2, 2, 2, 126, 127, 7, 42, 2, 2, 127, 16, 3, 2, 2, 2, 128, 129, 7, 43, 2, 2, 129, 18, 3, 2, 2, 2, 130, 131, 7, 46, 2, 2, 131, 20, 3, 2, 2, 2, 132, 133, 7, 41, 2, 2, 133, 22, 3, 2, 2, 2, 134, 135, 7, 36, 2, 2, 135, 24, 3, 2, 2, 2, 136, 137, 7, 93, 2, 2, 137, 26, 3, 2, 2, 2, 138, 139, 7, 95, 2, 2, 139, 28, 3, 2, 2, 2, 140, 143, 5, 21, 11, 2, 141, 143, 5, 23, 12, 2, 142, 140, 3, 2, 2, 2, 142, 141, 3, 2, 2, 2, 143, 30, 3, 2, 2, 2, 144, 145, 7, 67, 2, 2, 145, 146, 7, 80, 2, 2, 146, 147, 7, 70, 2, 2, 147, 32, 3, 2, 2, 2, 148, 149, 7, 81, 2, 2, 149, 150, 7, 84, 2, 2, 150, 34, 3, 2, 2, 2, 151, 152, 7, 63, 2, 2, 152, 36, 3, 2, 2, 2, 153, 154, 7, 35, 2, 2, 154, 155, 7, 63, 2, 2, 155, 38, 3, 2, 2, 2, 156, 157, 7, 64, 2, 2, 157, 40, 3, 2, 2, 2, 158, 159, 7, 64, 2, 2, 159, 160, 7, 63, 2, 2, 160, 42, 3, 2, 2, 2, 161, 162, 7, 62, 2, 2, 162, 44, 3, 2, 2, 2, 163, 164, 7, 62, 2, 2, 164, 165, 7, 63, 2, 2, 165, 46, 3, 2, 2, 2, 166, 167, 7, 78, 2, 2, 167, 168, 7, 75, 2, 2, 168, 169, 7, 77, 2, 2, 169, 170, 7, 71, 2, 2, 170, 48, 3, 2, 2, 2, 171, 172, 7, 75, 2, 2, 172, 173, 7, 80, 2, 2, 173, 50, 3, 2, 2, 2, 174, 175, 7, 69, 2, 2, 175, 176, 7, 81, 2, 2, 176, 177, 7, 80, 2, 2, 177, 178, 7, 86, 2, 2, 178, 179, 7, 67, 2, 2, 179, 180, 7, 75, 2, 2, 180, 181, 7, 80, 2, 2, 181, 182, 7, 85, 2, 2, 182, 52, 3, 2, 2, 2, 183, 184, 7, 86, 2, 2, 184, 185, 7, 84, 2, 2, 185, 186, 7, 87, 2, 2, 186, 187, 7, 71, 2, 2, 187, 54, 3, 2, 2, 2, 188, 189, 7, 72, 2, 2, 189, 190, 7, 67, 2, 2, 190, 191, 7, 78, 2, 2, 191, 192, 7, 85, 2, 2, 192, 193, 7, 71, 2, 2, 193, 56, 3, 2, 2, 2, 194, 195, 7, 78, 2, 2, 195, 196, 7, 75, 2, 2, 196, 197, 7, 79, 2, 2, 197, 198, 7, 75, 2, 2, 198, 199, 7, 86, 2, 2, 199, 58, 3, 2, 2, 2, 200, 201, 7, 81, 2, 2, 201, 202, 7, 72, 2, 2, 202, 203, 7, 72, 2, 2, 203, 204, 7, 85, 2, 2, 204, 205, 7, 71, 2, 2, 205, 206, 7, 86, 2, 2, 206, 60, 3, 2, 2, 2, 207, 208, 7, 81, 2, 2, 208, 209, 7, 84, 2, 2, 209, 210, 7, 70, 2, 2, 210, 211, 7, 71, 2, 2, 211, 212, 7, 84, 2, 2, 212, 213, 7, 34, 2, 2, 213, 214, 7, 68, 2, 2, 214, 215, 7, 91, 2, 2, 215, 62, 3, 2, 2, 2, 216, 217, 7, 67, 2, 2, 217, 218, 7, 85, 2, 2, 218, 219, 7, 69, 2, 2, 219, 64, 3, 2, 2, 2, 220, 221, 7, 70, 2, 2, 221, 222, 7, 71, 2, 2, 222, 223, 7, 85, 2, 2, 223, 224, 7, 69, 2, 2, 224, 66, 3, 2, 2, 2, 225, 226, 5, 7, 4, 2, 226, 68, 3, 2, 2, 2, 227, 228, 5, 9, 5, 2, 228, 70, 3, 2, 2, 2, 229, 231, 5, 11, 6, 2, 230, 229, 3, 2, 2, 2, 231, 232, 3, 2, 2, 2, 232, 230, 3, 2, 2, 2, 232, 233, 3, 2, 2, 2, 233, 72, 3, 2, 2, 2, 234, 236, 9, 9, 2, 2, 235, 234, 3, 2, 2, 2, 236, 237, 3, 2, 2, 2, 237, 235, 3, 2, 2, 2, 237, 238, 3, 2, 2, 2, 238, 74, 3, 2, 2, 2, 239, 241, 9, 10, 2, 2, 240, 239, 3, 2, 2, 2, 241, 242, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 242, 243, 3, 2, 2, 2, 243, 76, 3, 2, 2, 2, 244, 248, 9, 7, 2, 2, 245, 247, 9, 8, 2, 2, 246, 245, 3, 2, 2, 2, 247, 250, 3, 2, 2, 2, 248, 246, 3, 2, 2, 2, 248, 249, 3, 2, 2, 2, 249, 78, 3, 2, 2, 2, 250, 248, 3, 2, 2, 2, 15, 2, 82, 89, 97, 99, 110, 112, 123, 142, 232, 237, 242, 248, 3, 8, 2, 2] \ No newline at end of file diff --git a/packages/core/src/stores/webdaql/WebdaQLLexer.tokens b/packages/core/src/stores/webdaql/WebdaQLLexer.tokens deleted file mode 100644 index d6427881e..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLLexer.tokens +++ /dev/null @@ -1,57 +0,0 @@ -SPACE=1 -LR_BRACKET=2 -RR_BRACKET=3 -COMMA=4 -SINGLE_QUOTE_SYMB=5 -DOUBLE_QUOTE_SYMB=6 -LR_SQ_BRACKET=7 -RR_SQ_BRACKET=8 -AND=9 -OR=10 -EQUAL=11 -NOT_EQUAL=12 -GREATER=13 -GREATER_OR_EQUAL=14 -LESS=15 -LESS_OR_EQUAL=16 -LIKE=17 -IN=18 -CONTAINS=19 -TRUE=20 -FALSE=21 -LIMIT=22 -OFFSET=23 -ORDER_BY=24 -ASC=25 -DESC=26 -DQUOTED_STRING_LITERAL=27 -SQUOTED_STRING_LITERAL=28 -INTEGER_LITERAL=29 -IDENTIFIER=30 -IDENTIFIER_WITH_NUMBER=31 -FUNCTION_IDENTIFIER_WITH_UNDERSCORE=32 -'('=2 -')'=3 -','=4 -'\''=5 -'"'=6 -'['=7 -']'=8 -'AND'=9 -'OR'=10 -'='=11 -'!='=12 -'>'=13 -'>='=14 -'<'=15 -'<='=16 -'LIKE'=17 -'IN'=18 -'CONTAINS'=19 -'TRUE'=20 -'FALSE'=21 -'LIMIT'=22 -'OFFSET'=23 -'ORDER BY'=24 -'ASC'=25 -'DESC'=26 diff --git a/packages/core/src/stores/webdaql/WebdaQLLexer.ts b/packages/core/src/stores/webdaql/WebdaQLLexer.ts deleted file mode 100644 index bea9a81fb..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLLexer.ts +++ /dev/null @@ -1,319 +0,0 @@ -// Generated from src/stores/webdaql/WebdaQLLexer.g4 by ANTLR 4.9.0-SNAPSHOT - -import { ATN } from "antlr4ts/atn/ATN"; -import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; -import { LexerATNSimulator } from "antlr4ts/atn/LexerATNSimulator"; -import { CharStream } from "antlr4ts/CharStream"; -import { Lexer } from "antlr4ts/Lexer"; -import { Vocabulary } from "antlr4ts/Vocabulary"; -import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; - -import * as Utils from "antlr4ts/misc/Utils"; - -export class WebdaQLLexer extends Lexer { - public static readonly SPACE = 1; - public static readonly LR_BRACKET = 2; - public static readonly RR_BRACKET = 3; - public static readonly COMMA = 4; - public static readonly SINGLE_QUOTE_SYMB = 5; - public static readonly DOUBLE_QUOTE_SYMB = 6; - public static readonly LR_SQ_BRACKET = 7; - public static readonly RR_SQ_BRACKET = 8; - public static readonly AND = 9; - public static readonly OR = 10; - public static readonly EQUAL = 11; - public static readonly NOT_EQUAL = 12; - public static readonly GREATER = 13; - public static readonly GREATER_OR_EQUAL = 14; - public static readonly LESS = 15; - public static readonly LESS_OR_EQUAL = 16; - public static readonly LIKE = 17; - public static readonly IN = 18; - public static readonly CONTAINS = 19; - public static readonly TRUE = 20; - public static readonly FALSE = 21; - public static readonly LIMIT = 22; - public static readonly OFFSET = 23; - public static readonly ORDER_BY = 24; - public static readonly ASC = 25; - public static readonly DESC = 26; - public static readonly DQUOTED_STRING_LITERAL = 27; - public static readonly SQUOTED_STRING_LITERAL = 28; - public static readonly INTEGER_LITERAL = 29; - public static readonly IDENTIFIER = 30; - public static readonly IDENTIFIER_WITH_NUMBER = 31; - public static readonly FUNCTION_IDENTIFIER_WITH_UNDERSCORE = 32; - - // 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[] = [ - "SPACE", - "ID_LITERAL", - "DQUOTA_STRING", - "SQUOTA_STRING", - "INT_DIGIT", - "FN_LITERAL", - "LR_BRACKET", - "RR_BRACKET", - "COMMA", - "SINGLE_QUOTE_SYMB", - "DOUBLE_QUOTE_SYMB", - "LR_SQ_BRACKET", - "RR_SQ_BRACKET", - "QUOTE_SYMB", - "AND", - "OR", - "EQUAL", - "NOT_EQUAL", - "GREATER", - "GREATER_OR_EQUAL", - "LESS", - "LESS_OR_EQUAL", - "LIKE", - "IN", - "CONTAINS", - "TRUE", - "FALSE", - "LIMIT", - "OFFSET", - "ORDER_BY", - "ASC", - "DESC", - "DQUOTED_STRING_LITERAL", - "SQUOTED_STRING_LITERAL", - "INTEGER_LITERAL", - "IDENTIFIER", - "IDENTIFIER_WITH_NUMBER", - "FUNCTION_IDENTIFIER_WITH_UNDERSCORE" - ]; - - private static readonly _LITERAL_NAMES: Array = [ - undefined, - undefined, - "'('", - "')'", - "','", - "'''", - "'\"'", - "'['", - "']'", - "'AND'", - "'OR'", - "'='", - "'!='", - "'>'", - "'>='", - "'<'", - "'<='", - "'LIKE'", - "'IN'", - "'CONTAINS'", - "'TRUE'", - "'FALSE'", - "'LIMIT'", - "'OFFSET'", - "'ORDER BY'", - "'ASC'", - "'DESC'" - ]; - private static readonly _SYMBOLIC_NAMES: Array = [ - undefined, - "SPACE", - "LR_BRACKET", - "RR_BRACKET", - "COMMA", - "SINGLE_QUOTE_SYMB", - "DOUBLE_QUOTE_SYMB", - "LR_SQ_BRACKET", - "RR_SQ_BRACKET", - "AND", - "OR", - "EQUAL", - "NOT_EQUAL", - "GREATER", - "GREATER_OR_EQUAL", - "LESS", - "LESS_OR_EQUAL", - "LIKE", - "IN", - "CONTAINS", - "TRUE", - "FALSE", - "LIMIT", - "OFFSET", - "ORDER_BY", - "ASC", - "DESC", - "DQUOTED_STRING_LITERAL", - "SQUOTED_STRING_LITERAL", - "INTEGER_LITERAL", - "IDENTIFIER", - "IDENTIFIER_WITH_NUMBER", - "FUNCTION_IDENTIFIER_WITH_UNDERSCORE" - ]; - public static readonly VOCABULARY: Vocabulary = new VocabularyImpl( - WebdaQLLexer._LITERAL_NAMES, - WebdaQLLexer._SYMBOLIC_NAMES, - [] - ); - - // @Override - // @NotNull - public get vocabulary(): Vocabulary { - return WebdaQLLexer.VOCABULARY; - } - // tslint:enable:no-trailing-whitespace - - constructor(input: CharStream) { - super(input); - this._interp = new LexerATNSimulator(WebdaQLLexer._ATN, this); - } - - // @Override - public get grammarFileName(): string { - return "WebdaQLLexer.g4"; - } - - // @Override - public get ruleNames(): string[] { - return WebdaQLLexer.ruleNames; - } - - // @Override - public get serializedATN(): string { - return WebdaQLLexer._serializedATN; - } - - // @Override - public get channelNames(): string[] { - return WebdaQLLexer.channelNames; - } - - // @Override - public get modeNames(): string[] { - return WebdaQLLexer.modeNames; - } - - public static readonly _serializedATN: string = - '\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02"\xFB\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\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12" + - "\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t\x17" + - "\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t\x1C" + - '\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04"\t"\x04' + - "#\t#\x04$\t$\x04%\t%\x04&\t&\x04'\t'\x03\x02\x06\x02Q\n\x02\r\x02\x0E" + - "\x02R\x03\x02\x03\x02\x03\x03\x06\x03X\n\x03\r\x03\x0E\x03Y\x03\x04\x03" + - "\x04\x03\x04\x03\x04\x03\x04\x03\x04\x07\x04b\n\x04\f\x04\x0E\x04e\v\x04" + - "\x03\x04\x03\x04\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x07\x05" + - "o\n\x05\f\x05\x0E\x05r\v\x05\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03" + - "\x07\x07\x07z\n\x07\f\x07\x0E\x07}\v\x07\x03\b\x03\b\x03\t\x03\t\x03\n" + - "\x03\n\x03\v\x03\v\x03\f\x03\f\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0F\x03" + - "\x0F\x05\x0F\x8F\n\x0F\x03\x10\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11" + - "\x03\x11\x03\x12\x03\x12\x03\x13\x03\x13\x03\x13\x03\x14\x03\x14\x03\x15" + - "\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17\x03\x17\x03\x17\x03\x18\x03\x18" + - "\x03\x18\x03\x18\x03\x18\x03\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1A" + - "\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1B" + - "\x03\x1B\x03\x1B\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1D" + - "\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E\x03\x1E" + - "\x03\x1E\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F" + - "\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x03 \x03!\x03!\x03!\x03!\x03!" + - '\x03"\x03"\x03#\x03#\x03$\x06$\xE7\n$\r$\x0E$\xE8\x03%\x06%\xEC\n%\r' + - "%\x0E%\xED\x03&\x06&\xF1\n&\r&\x0E&\xF2\x03'\x03'\x07'\xF7\n'\f'" + - "\x0E'\xFA\v'\x02\x02\x02(\x03\x02\x03\x05\x02\x02\x07\x02\x02\t\x02" + - "\x02\v\x02\x02\r\x02\x02\x0F\x02\x04\x11\x02\x05\x13\x02\x06\x15\x02\x07" + - "\x17\x02\b\x19\x02\t\x1B\x02\n\x1D\x02\x02\x1F\x02\v!\x02\f#\x02\r%\x02" + - "\x0E'\x02\x0F)\x02\x10+\x02\x11-\x02\x12/\x02\x131\x02\x143\x02\x155" + - "\x02\x167\x02\x179\x02\x18;\x02\x19=\x02\x1A?\x02\x1BA\x02\x1CC\x02\x1D" + - 'E\x02\x1EG\x02\x1FI\x02 K\x02!M\x02"\x03\x02\v\x05\x02\v\f\x0F\x0F"' + - '"\x05\x022;C\\c|\x04\x02$$^^\x04\x02))^^\x03\x022;\x03\x02C\\\x04\x02' + - "C\\aa\x04\x02C\\c|\x07\x02002;C\\aac|\x02\u0102\x02\x03\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\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02" + - "\x02#\x03\x02\x02\x02\x02%\x03\x02\x02\x02\x02'\x03\x02\x02\x02\x02)" + - "\x03\x02\x02\x02\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02" + - "\x02\x02\x021\x03\x02\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02" + - "\x027\x03\x02\x02\x02\x029\x03\x02\x02\x02\x02;\x03\x02\x02\x02\x02=\x03" + - "\x02\x02\x02\x02?\x03\x02\x02\x02\x02A\x03\x02\x02\x02\x02C\x03\x02\x02" + - "\x02\x02E\x03\x02\x02\x02\x02G\x03\x02\x02\x02\x02I\x03\x02\x02\x02\x02" + - "K\x03\x02\x02\x02\x02M\x03\x02\x02\x02\x03P\x03\x02\x02\x02\x05W\x03\x02" + - "\x02\x02\x07[\x03\x02\x02\x02\th\x03\x02\x02\x02\vu\x03\x02\x02\x02\r" + - "w\x03\x02\x02\x02\x0F~\x03\x02\x02\x02\x11\x80\x03\x02\x02\x02\x13\x82" + - "\x03\x02\x02\x02\x15\x84\x03\x02\x02\x02\x17\x86\x03\x02\x02\x02\x19\x88" + - "\x03\x02\x02\x02\x1B\x8A\x03\x02\x02\x02\x1D\x8E\x03\x02\x02\x02\x1F\x90" + - "\x03\x02\x02\x02!\x94\x03\x02\x02\x02#\x97\x03\x02\x02\x02%\x99\x03\x02" + - "\x02\x02'\x9C\x03\x02\x02\x02)\x9E\x03\x02\x02\x02+\xA1\x03\x02\x02\x02" + - "-\xA3\x03\x02\x02\x02/\xA6\x03\x02\x02\x021\xAB\x03\x02\x02\x023\xAE\x03" + - "\x02\x02\x025\xB7\x03\x02\x02\x027\xBC\x03\x02\x02\x029\xC2\x03\x02\x02" + - "\x02;\xC8\x03\x02\x02\x02=\xCF\x03\x02\x02\x02?\xD8\x03\x02\x02\x02A\xDC" + - "\x03\x02\x02\x02C\xE1\x03\x02\x02\x02E\xE3\x03\x02\x02\x02G\xE6\x03\x02" + - "\x02\x02I\xEB\x03\x02\x02\x02K\xF0\x03\x02\x02\x02M\xF4\x03\x02\x02\x02" + - "OQ\t\x02\x02\x02PO\x03\x02\x02\x02QR\x03\x02\x02\x02RP\x03\x02\x02\x02" + - "RS\x03\x02\x02\x02ST\x03\x02\x02\x02TU\b\x02\x02\x02U\x04\x03\x02\x02" + - "\x02VX\t\x03\x02\x02WV\x03\x02\x02\x02XY\x03\x02\x02\x02YW\x03\x02\x02" + - "\x02YZ\x03\x02\x02\x02Z\x06\x03\x02\x02\x02[c\x07$\x02\x02\\]\x07^\x02" + - "\x02]b\v\x02\x02\x02^_\x07$\x02\x02_b\x07$\x02\x02`b\n\x04\x02\x02a\\" + - "\x03\x02\x02\x02a^\x03\x02\x02\x02a`\x03\x02\x02\x02be\x03\x02\x02\x02" + - "ca\x03\x02\x02\x02cd\x03\x02\x02\x02df\x03\x02\x02\x02ec\x03\x02\x02\x02" + - "fg\x07$\x02\x02g\b\x03\x02\x02\x02hp\x07)\x02\x02ij\x07^\x02\x02jo\v\x02" + - "\x02\x02kl\x07)\x02\x02lo\x07)\x02\x02mo\n\x05\x02\x02ni\x03\x02\x02\x02" + - "nk\x03\x02\x02\x02nm\x03\x02\x02\x02or\x03\x02\x02\x02pn\x03\x02\x02\x02" + - "pq\x03\x02\x02\x02qs\x03\x02\x02\x02rp\x03\x02\x02\x02st\x07)\x02\x02" + - "t\n\x03\x02\x02\x02uv\t\x06\x02\x02v\f\x03\x02\x02\x02w{\t\x07\x02\x02" + - "xz\t\b\x02\x02yx\x03\x02\x02\x02z}\x03\x02\x02\x02{y\x03\x02\x02\x02{" + - "|\x03\x02\x02\x02|\x0E\x03\x02\x02\x02}{\x03\x02\x02\x02~\x7F\x07*\x02" + - "\x02\x7F\x10\x03\x02\x02\x02\x80\x81\x07+\x02\x02\x81\x12\x03\x02\x02" + - "\x02\x82\x83\x07.\x02\x02\x83\x14\x03\x02\x02\x02\x84\x85\x07)\x02\x02" + - "\x85\x16\x03\x02\x02\x02\x86\x87\x07$\x02\x02\x87\x18\x03\x02\x02\x02" + - "\x88\x89\x07]\x02\x02\x89\x1A\x03\x02\x02\x02\x8A\x8B\x07_\x02\x02\x8B" + - "\x1C\x03\x02\x02\x02\x8C\x8F\x05\x15\v\x02\x8D\x8F\x05\x17\f\x02\x8E\x8C" + - "\x03\x02\x02\x02\x8E\x8D\x03\x02\x02\x02\x8F\x1E\x03\x02\x02\x02\x90\x91" + - "\x07C\x02\x02\x91\x92\x07P\x02\x02\x92\x93\x07F\x02\x02\x93 \x03\x02\x02" + - '\x02\x94\x95\x07Q\x02\x02\x95\x96\x07T\x02\x02\x96"\x03\x02\x02\x02\x97' + - "\x98\x07?\x02\x02\x98$\x03\x02\x02\x02\x99\x9A\x07#\x02\x02\x9A\x9B\x07" + - "?\x02\x02\x9B&\x03\x02\x02\x02\x9C\x9D\x07@\x02\x02\x9D(\x03\x02\x02\x02" + - "\x9E\x9F\x07@\x02\x02\x9F\xA0\x07?\x02\x02\xA0*\x03\x02\x02\x02\xA1\xA2" + - "\x07>\x02\x02\xA2,\x03\x02\x02\x02\xA3\xA4\x07>\x02\x02\xA4\xA5\x07?\x02" + - "\x02\xA5.\x03\x02\x02\x02\xA6\xA7\x07N\x02\x02\xA7\xA8\x07K\x02\x02\xA8" + - "\xA9\x07M\x02\x02\xA9\xAA\x07G\x02\x02\xAA0\x03\x02\x02\x02\xAB\xAC\x07" + - "K\x02\x02\xAC\xAD\x07P\x02\x02\xAD2\x03\x02\x02\x02\xAE\xAF\x07E\x02\x02" + - "\xAF\xB0\x07Q\x02\x02\xB0\xB1\x07P\x02\x02\xB1\xB2\x07V\x02\x02\xB2\xB3" + - "\x07C\x02\x02\xB3\xB4\x07K\x02\x02\xB4\xB5\x07P\x02\x02\xB5\xB6\x07U\x02" + - "\x02\xB64\x03\x02\x02\x02\xB7\xB8\x07V\x02\x02\xB8\xB9\x07T\x02\x02\xB9" + - "\xBA\x07W\x02\x02\xBA\xBB\x07G\x02\x02\xBB6\x03\x02\x02\x02\xBC\xBD\x07" + - "H\x02\x02\xBD\xBE\x07C\x02\x02\xBE\xBF\x07N\x02\x02\xBF\xC0\x07U\x02\x02" + - "\xC0\xC1\x07G\x02\x02\xC18\x03\x02\x02\x02\xC2\xC3\x07N\x02\x02\xC3\xC4" + - "\x07K\x02\x02\xC4\xC5\x07O\x02\x02\xC5\xC6\x07K\x02\x02\xC6\xC7\x07V\x02" + - "\x02\xC7:\x03\x02\x02\x02\xC8\xC9\x07Q\x02\x02\xC9\xCA\x07H\x02\x02\xCA" + - "\xCB\x07H\x02\x02\xCB\xCC\x07U\x02\x02\xCC\xCD\x07G\x02\x02\xCD\xCE\x07" + - "V\x02\x02\xCE<\x03\x02\x02\x02\xCF\xD0\x07Q\x02\x02\xD0\xD1\x07T\x02\x02" + - "\xD1\xD2\x07F\x02\x02\xD2\xD3\x07G\x02\x02\xD3\xD4\x07T\x02\x02\xD4\xD5" + - '\x07"\x02\x02\xD5\xD6\x07D\x02\x02\xD6\xD7\x07[\x02\x02\xD7>\x03\x02' + - "\x02\x02\xD8\xD9\x07C\x02\x02\xD9\xDA\x07U\x02\x02\xDA\xDB\x07E\x02\x02" + - "\xDB@\x03\x02\x02\x02\xDC\xDD\x07F\x02\x02\xDD\xDE\x07G\x02\x02\xDE\xDF" + - "\x07U\x02\x02\xDF\xE0\x07E\x02\x02\xE0B\x03\x02\x02\x02\xE1\xE2\x05\x07" + - "\x04\x02\xE2D\x03\x02\x02\x02\xE3\xE4\x05\t\x05\x02\xE4F\x03\x02\x02\x02" + - "\xE5\xE7\x05\v\x06\x02\xE6\xE5\x03\x02\x02\x02\xE7\xE8\x03\x02\x02\x02" + - "\xE8\xE6\x03\x02\x02\x02\xE8\xE9\x03\x02\x02\x02\xE9H\x03\x02\x02\x02" + - "\xEA\xEC\t\t\x02\x02\xEB\xEA\x03\x02\x02\x02\xEC\xED\x03\x02\x02\x02\xED" + - "\xEB\x03\x02\x02\x02\xED\xEE\x03\x02\x02\x02\xEEJ\x03\x02\x02\x02\xEF" + - "\xF1\t\n\x02\x02\xF0\xEF\x03\x02\x02\x02\xF1\xF2\x03\x02\x02\x02\xF2\xF0" + - "\x03\x02\x02\x02\xF2\xF3\x03\x02\x02\x02\xF3L\x03\x02\x02\x02\xF4\xF8" + - "\t\x07\x02\x02\xF5\xF7\t\b\x02\x02\xF6\xF5\x03\x02\x02\x02\xF7\xFA\x03" + - "\x02\x02\x02\xF8\xF6\x03\x02\x02\x02\xF8\xF9\x03\x02\x02\x02\xF9N\x03" + - "\x02\x02\x02\xFA\xF8\x03\x02\x02\x02\x0F\x02RYacnp{\x8E\xE8\xED\xF2\xF8" + - "\x03\b\x02\x02"; - public static __ATN: ATN; - public static get _ATN(): ATN { - if (!WebdaQLLexer.__ATN) { - WebdaQLLexer.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(WebdaQLLexer._serializedATN)); - } - - return WebdaQLLexer.__ATN; - } -} diff --git a/packages/core/src/stores/webdaql/WebdaQLParser.g4 b/packages/core/src/stores/webdaql/WebdaQLParser.g4 deleted file mode 100644 index 9def96985..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLParser.g4 +++ /dev/null @@ -1,58 +0,0 @@ -grammar WebdaQLParser; - -import WebdaQLLexer; - -// Entrypoint -webdaql: expression? orderExpression? limitExpression? offsetExpression? EOF; - -limitExpression: LIMIT integerLiteral; -offsetExpression: OFFSET stringLiteral; -orderFieldExpression: identifier (ASC | DESC)?; -orderExpression: ORDER_BY orderFieldExpression ( COMMA orderFieldExpression )*; - -// Structure of operations, function invocations and expression -expression - : - // LIKE, EXISTS and IN takes precedence over all the other binary operators - identifier LIKE stringLiteral #likeExpression - | identifier IN setExpression #inExpression - | identifier CONTAINS stringLiteral #containsExpression - // Comparison operations - | identifier (EQUAL | NOT_EQUAL | GREATER_OR_EQUAL | LESS_OR_EQUAL | LESS | GREATER) values #binaryComparisonExpression - // Logic operations - | expression AND expression #andLogicExpression - | expression OR expression #orLogicExpression - // Subexpressions and atoms - | LR_BRACKET expression RR_BRACKET #subExpression - | atom #atomExpression - ; - -values - : booleanLiteral #booleanAtom - | integerLiteral #integerAtom - | stringLiteral #stringAtom - ; - -atom - : values #valuesAtom - | identifier #identifierAtom - ; - -// Identifiers - -identifier - : (IDENTIFIER | IDENTIFIER_WITH_NUMBER) - ; - -// Literals - -booleanLiteral: (TRUE | FALSE); -stringLiteral: (DQUOTED_STRING_LITERAL | SQUOTED_STRING_LITERAL); -integerLiteral: INTEGER_LITERAL; - - -// Sets - -setExpression - : LR_SQ_BRACKET values ( COMMA values )* RR_SQ_BRACKET // Empty sets are not allowed - ; \ No newline at end of file diff --git a/packages/core/src/stores/webdaql/WebdaQLParser.interp b/packages/core/src/stores/webdaql/WebdaQLParser.interp deleted file mode 100644 index 26d2431a0..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLParser.interp +++ /dev/null @@ -1,88 +0,0 @@ -token literal names: -null -null -'(' -')' -',' -'\'' -'"' -'[' -']' -'AND' -'OR' -'=' -'!=' -'>' -'>=' -'<' -'<=' -'LIKE' -'IN' -'CONTAINS' -'TRUE' -'FALSE' -'LIMIT' -'OFFSET' -'ORDER BY' -'ASC' -'DESC' -null -null -null -null -null -null - -token symbolic names: -null -SPACE -LR_BRACKET -RR_BRACKET -COMMA -SINGLE_QUOTE_SYMB -DOUBLE_QUOTE_SYMB -LR_SQ_BRACKET -RR_SQ_BRACKET -AND -OR -EQUAL -NOT_EQUAL -GREATER -GREATER_OR_EQUAL -LESS -LESS_OR_EQUAL -LIKE -IN -CONTAINS -TRUE -FALSE -LIMIT -OFFSET -ORDER_BY -ASC -DESC -DQUOTED_STRING_LITERAL -SQUOTED_STRING_LITERAL -INTEGER_LITERAL -IDENTIFIER -IDENTIFIER_WITH_NUMBER -FUNCTION_IDENTIFIER_WITH_UNDERSCORE - -rule names: -webdaql -limitExpression -offsetExpression -orderFieldExpression -orderExpression -expression -values -atom -identifier -booleanLiteral -stringLiteral -integerLiteral -setExpression - - -atn: -[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 34, 125, 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, 5, 2, 30, 10, 2, 3, 2, 5, 2, 33, 10, 2, 3, 2, 5, 2, 36, 10, 2, 3, 2, 5, 2, 39, 10, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 5, 5, 51, 10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 7, 6, 57, 10, 6, 12, 6, 14, 6, 60, 11, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 5, 7, 84, 10, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 7, 7, 92, 10, 7, 12, 7, 14, 7, 95, 11, 7, 3, 8, 3, 8, 3, 8, 5, 8, 100, 10, 8, 3, 9, 3, 9, 5, 9, 104, 10, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 7, 14, 118, 10, 14, 12, 14, 14, 14, 121, 11, 14, 3, 14, 3, 14, 3, 14, 2, 2, 3, 12, 15, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 12, 2, 14, 2, 16, 2, 18, 2, 20, 2, 22, 2, 24, 2, 26, 2, 2, 7, 3, 2, 27, 28, 3, 2, 13, 18, 3, 2, 32, 33, 3, 2, 22, 23, 3, 2, 29, 30, 2, 128, 2, 29, 3, 2, 2, 2, 4, 42, 3, 2, 2, 2, 6, 45, 3, 2, 2, 2, 8, 48, 3, 2, 2, 2, 10, 52, 3, 2, 2, 2, 12, 83, 3, 2, 2, 2, 14, 99, 3, 2, 2, 2, 16, 103, 3, 2, 2, 2, 18, 105, 3, 2, 2, 2, 20, 107, 3, 2, 2, 2, 22, 109, 3, 2, 2, 2, 24, 111, 3, 2, 2, 2, 26, 113, 3, 2, 2, 2, 28, 30, 5, 12, 7, 2, 29, 28, 3, 2, 2, 2, 29, 30, 3, 2, 2, 2, 30, 32, 3, 2, 2, 2, 31, 33, 5, 10, 6, 2, 32, 31, 3, 2, 2, 2, 32, 33, 3, 2, 2, 2, 33, 35, 3, 2, 2, 2, 34, 36, 5, 4, 3, 2, 35, 34, 3, 2, 2, 2, 35, 36, 3, 2, 2, 2, 36, 38, 3, 2, 2, 2, 37, 39, 5, 6, 4, 2, 38, 37, 3, 2, 2, 2, 38, 39, 3, 2, 2, 2, 39, 40, 3, 2, 2, 2, 40, 41, 7, 2, 2, 3, 41, 3, 3, 2, 2, 2, 42, 43, 7, 24, 2, 2, 43, 44, 5, 24, 13, 2, 44, 5, 3, 2, 2, 2, 45, 46, 7, 25, 2, 2, 46, 47, 5, 22, 12, 2, 47, 7, 3, 2, 2, 2, 48, 50, 5, 18, 10, 2, 49, 51, 9, 2, 2, 2, 50, 49, 3, 2, 2, 2, 50, 51, 3, 2, 2, 2, 51, 9, 3, 2, 2, 2, 52, 53, 7, 26, 2, 2, 53, 58, 5, 8, 5, 2, 54, 55, 7, 6, 2, 2, 55, 57, 5, 8, 5, 2, 56, 54, 3, 2, 2, 2, 57, 60, 3, 2, 2, 2, 58, 56, 3, 2, 2, 2, 58, 59, 3, 2, 2, 2, 59, 11, 3, 2, 2, 2, 60, 58, 3, 2, 2, 2, 61, 62, 8, 7, 1, 2, 62, 63, 5, 18, 10, 2, 63, 64, 7, 19, 2, 2, 64, 65, 5, 22, 12, 2, 65, 84, 3, 2, 2, 2, 66, 67, 5, 18, 10, 2, 67, 68, 7, 20, 2, 2, 68, 69, 5, 26, 14, 2, 69, 84, 3, 2, 2, 2, 70, 71, 5, 18, 10, 2, 71, 72, 7, 21, 2, 2, 72, 73, 5, 22, 12, 2, 73, 84, 3, 2, 2, 2, 74, 75, 5, 18, 10, 2, 75, 76, 9, 3, 2, 2, 76, 77, 5, 14, 8, 2, 77, 84, 3, 2, 2, 2, 78, 79, 7, 4, 2, 2, 79, 80, 5, 12, 7, 2, 80, 81, 7, 5, 2, 2, 81, 84, 3, 2, 2, 2, 82, 84, 5, 16, 9, 2, 83, 61, 3, 2, 2, 2, 83, 66, 3, 2, 2, 2, 83, 70, 3, 2, 2, 2, 83, 74, 3, 2, 2, 2, 83, 78, 3, 2, 2, 2, 83, 82, 3, 2, 2, 2, 84, 93, 3, 2, 2, 2, 85, 86, 12, 6, 2, 2, 86, 87, 7, 11, 2, 2, 87, 92, 5, 12, 7, 7, 88, 89, 12, 5, 2, 2, 89, 90, 7, 12, 2, 2, 90, 92, 5, 12, 7, 6, 91, 85, 3, 2, 2, 2, 91, 88, 3, 2, 2, 2, 92, 95, 3, 2, 2, 2, 93, 91, 3, 2, 2, 2, 93, 94, 3, 2, 2, 2, 94, 13, 3, 2, 2, 2, 95, 93, 3, 2, 2, 2, 96, 100, 5, 20, 11, 2, 97, 100, 5, 24, 13, 2, 98, 100, 5, 22, 12, 2, 99, 96, 3, 2, 2, 2, 99, 97, 3, 2, 2, 2, 99, 98, 3, 2, 2, 2, 100, 15, 3, 2, 2, 2, 101, 104, 5, 14, 8, 2, 102, 104, 5, 18, 10, 2, 103, 101, 3, 2, 2, 2, 103, 102, 3, 2, 2, 2, 104, 17, 3, 2, 2, 2, 105, 106, 9, 4, 2, 2, 106, 19, 3, 2, 2, 2, 107, 108, 9, 5, 2, 2, 108, 21, 3, 2, 2, 2, 109, 110, 9, 6, 2, 2, 110, 23, 3, 2, 2, 2, 111, 112, 7, 31, 2, 2, 112, 25, 3, 2, 2, 2, 113, 114, 7, 9, 2, 2, 114, 119, 5, 14, 8, 2, 115, 116, 7, 6, 2, 2, 116, 118, 5, 14, 8, 2, 117, 115, 3, 2, 2, 2, 118, 121, 3, 2, 2, 2, 119, 117, 3, 2, 2, 2, 119, 120, 3, 2, 2, 2, 120, 122, 3, 2, 2, 2, 121, 119, 3, 2, 2, 2, 122, 123, 7, 10, 2, 2, 123, 27, 3, 2, 2, 2, 14, 29, 32, 35, 38, 50, 58, 83, 91, 93, 99, 103, 119] \ No newline at end of file diff --git a/packages/core/src/stores/webdaql/WebdaQLParser.tokens b/packages/core/src/stores/webdaql/WebdaQLParser.tokens deleted file mode 100644 index d6427881e..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLParser.tokens +++ /dev/null @@ -1,57 +0,0 @@ -SPACE=1 -LR_BRACKET=2 -RR_BRACKET=3 -COMMA=4 -SINGLE_QUOTE_SYMB=5 -DOUBLE_QUOTE_SYMB=6 -LR_SQ_BRACKET=7 -RR_SQ_BRACKET=8 -AND=9 -OR=10 -EQUAL=11 -NOT_EQUAL=12 -GREATER=13 -GREATER_OR_EQUAL=14 -LESS=15 -LESS_OR_EQUAL=16 -LIKE=17 -IN=18 -CONTAINS=19 -TRUE=20 -FALSE=21 -LIMIT=22 -OFFSET=23 -ORDER_BY=24 -ASC=25 -DESC=26 -DQUOTED_STRING_LITERAL=27 -SQUOTED_STRING_LITERAL=28 -INTEGER_LITERAL=29 -IDENTIFIER=30 -IDENTIFIER_WITH_NUMBER=31 -FUNCTION_IDENTIFIER_WITH_UNDERSCORE=32 -'('=2 -')'=3 -','=4 -'\''=5 -'"'=6 -'['=7 -']'=8 -'AND'=9 -'OR'=10 -'='=11 -'!='=12 -'>'=13 -'>='=14 -'<'=15 -'<='=16 -'LIKE'=17 -'IN'=18 -'CONTAINS'=19 -'TRUE'=20 -'FALSE'=21 -'LIMIT'=22 -'OFFSET'=23 -'ORDER BY'=24 -'ASC'=25 -'DESC'=26 diff --git a/packages/core/src/stores/webdaql/WebdaQLParserLexer.interp b/packages/core/src/stores/webdaql/WebdaQLParserLexer.interp deleted file mode 100644 index ffd6388a2..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLParserLexer.interp +++ /dev/null @@ -1,119 +0,0 @@ -token literal names: -null -null -'(' -')' -',' -'\'' -'"' -'[' -']' -'AND' -'OR' -'=' -'!=' -'>' -'>=' -'<' -'<=' -'LIKE' -'IN' -'CONTAINS' -'TRUE' -'FALSE' -'LIMIT' -'OFFSET' -'ORDER BY' -'ASC' -'DESC' -null -null -null -null -null -null - -token symbolic names: -null -SPACE -LR_BRACKET -RR_BRACKET -COMMA -SINGLE_QUOTE_SYMB -DOUBLE_QUOTE_SYMB -LR_SQ_BRACKET -RR_SQ_BRACKET -AND -OR -EQUAL -NOT_EQUAL -GREATER -GREATER_OR_EQUAL -LESS -LESS_OR_EQUAL -LIKE -IN -CONTAINS -TRUE -FALSE -LIMIT -OFFSET -ORDER_BY -ASC -DESC -DQUOTED_STRING_LITERAL -SQUOTED_STRING_LITERAL -INTEGER_LITERAL -IDENTIFIER -IDENTIFIER_WITH_NUMBER -FUNCTION_IDENTIFIER_WITH_UNDERSCORE - -rule names: -SPACE -ID_LITERAL -DQUOTA_STRING -SQUOTA_STRING -INT_DIGIT -FN_LITERAL -LR_BRACKET -RR_BRACKET -COMMA -SINGLE_QUOTE_SYMB -DOUBLE_QUOTE_SYMB -LR_SQ_BRACKET -RR_SQ_BRACKET -QUOTE_SYMB -AND -OR -EQUAL -NOT_EQUAL -GREATER -GREATER_OR_EQUAL -LESS -LESS_OR_EQUAL -LIKE -IN -CONTAINS -TRUE -FALSE -LIMIT -OFFSET -ORDER_BY -ASC -DESC -DQUOTED_STRING_LITERAL -SQUOTED_STRING_LITERAL -INTEGER_LITERAL -IDENTIFIER -IDENTIFIER_WITH_NUMBER -FUNCTION_IDENTIFIER_WITH_UNDERSCORE - -channel names: -DEFAULT_TOKEN_CHANNEL -HIDDEN - -mode names: -DEFAULT_MODE - -atn: -[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 34, 251, 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, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 3, 2, 6, 2, 81, 10, 2, 13, 2, 14, 2, 82, 3, 2, 3, 2, 3, 3, 6, 3, 88, 10, 3, 13, 3, 14, 3, 89, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 98, 10, 4, 12, 4, 14, 4, 101, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 7, 5, 111, 10, 5, 12, 5, 14, 5, 114, 11, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 7, 7, 122, 10, 7, 12, 7, 14, 7, 125, 11, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 5, 15, 143, 10, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 35, 3, 35, 3, 36, 6, 36, 231, 10, 36, 13, 36, 14, 36, 232, 3, 37, 6, 37, 236, 10, 37, 13, 37, 14, 37, 237, 3, 38, 6, 38, 241, 10, 38, 13, 38, 14, 38, 242, 3, 39, 3, 39, 7, 39, 247, 10, 39, 12, 39, 14, 39, 250, 11, 39, 2, 2, 2, 40, 3, 2, 3, 5, 2, 2, 7, 2, 2, 9, 2, 2, 11, 2, 2, 13, 2, 2, 15, 2, 4, 17, 2, 5, 19, 2, 6, 21, 2, 7, 23, 2, 8, 25, 2, 9, 27, 2, 10, 29, 2, 2, 31, 2, 11, 33, 2, 12, 35, 2, 13, 37, 2, 14, 39, 2, 15, 41, 2, 16, 43, 2, 17, 45, 2, 18, 47, 2, 19, 49, 2, 20, 51, 2, 21, 53, 2, 22, 55, 2, 23, 57, 2, 24, 59, 2, 25, 61, 2, 26, 63, 2, 27, 65, 2, 28, 67, 2, 29, 69, 2, 30, 71, 2, 31, 73, 2, 32, 75, 2, 33, 77, 2, 34, 3, 2, 11, 5, 2, 11, 12, 15, 15, 34, 34, 5, 2, 50, 59, 67, 92, 99, 124, 4, 2, 36, 36, 94, 94, 4, 2, 41, 41, 94, 94, 3, 2, 50, 59, 3, 2, 67, 92, 4, 2, 67, 92, 97, 97, 4, 2, 67, 92, 99, 124, 7, 2, 48, 48, 50, 59, 67, 92, 97, 97, 99, 124, 2, 258, 2, 3, 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, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 2, 77, 3, 2, 2, 2, 3, 80, 3, 2, 2, 2, 5, 87, 3, 2, 2, 2, 7, 91, 3, 2, 2, 2, 9, 104, 3, 2, 2, 2, 11, 117, 3, 2, 2, 2, 13, 119, 3, 2, 2, 2, 15, 126, 3, 2, 2, 2, 17, 128, 3, 2, 2, 2, 19, 130, 3, 2, 2, 2, 21, 132, 3, 2, 2, 2, 23, 134, 3, 2, 2, 2, 25, 136, 3, 2, 2, 2, 27, 138, 3, 2, 2, 2, 29, 142, 3, 2, 2, 2, 31, 144, 3, 2, 2, 2, 33, 148, 3, 2, 2, 2, 35, 151, 3, 2, 2, 2, 37, 153, 3, 2, 2, 2, 39, 156, 3, 2, 2, 2, 41, 158, 3, 2, 2, 2, 43, 161, 3, 2, 2, 2, 45, 163, 3, 2, 2, 2, 47, 166, 3, 2, 2, 2, 49, 171, 3, 2, 2, 2, 51, 174, 3, 2, 2, 2, 53, 183, 3, 2, 2, 2, 55, 188, 3, 2, 2, 2, 57, 194, 3, 2, 2, 2, 59, 200, 3, 2, 2, 2, 61, 207, 3, 2, 2, 2, 63, 216, 3, 2, 2, 2, 65, 220, 3, 2, 2, 2, 67, 225, 3, 2, 2, 2, 69, 227, 3, 2, 2, 2, 71, 230, 3, 2, 2, 2, 73, 235, 3, 2, 2, 2, 75, 240, 3, 2, 2, 2, 77, 244, 3, 2, 2, 2, 79, 81, 9, 2, 2, 2, 80, 79, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 80, 3, 2, 2, 2, 82, 83, 3, 2, 2, 2, 83, 84, 3, 2, 2, 2, 84, 85, 8, 2, 2, 2, 85, 4, 3, 2, 2, 2, 86, 88, 9, 3, 2, 2, 87, 86, 3, 2, 2, 2, 88, 89, 3, 2, 2, 2, 89, 87, 3, 2, 2, 2, 89, 90, 3, 2, 2, 2, 90, 6, 3, 2, 2, 2, 91, 99, 7, 36, 2, 2, 92, 93, 7, 94, 2, 2, 93, 98, 11, 2, 2, 2, 94, 95, 7, 36, 2, 2, 95, 98, 7, 36, 2, 2, 96, 98, 10, 4, 2, 2, 97, 92, 3, 2, 2, 2, 97, 94, 3, 2, 2, 2, 97, 96, 3, 2, 2, 2, 98, 101, 3, 2, 2, 2, 99, 97, 3, 2, 2, 2, 99, 100, 3, 2, 2, 2, 100, 102, 3, 2, 2, 2, 101, 99, 3, 2, 2, 2, 102, 103, 7, 36, 2, 2, 103, 8, 3, 2, 2, 2, 104, 112, 7, 41, 2, 2, 105, 106, 7, 94, 2, 2, 106, 111, 11, 2, 2, 2, 107, 108, 7, 41, 2, 2, 108, 111, 7, 41, 2, 2, 109, 111, 10, 5, 2, 2, 110, 105, 3, 2, 2, 2, 110, 107, 3, 2, 2, 2, 110, 109, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 115, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 116, 7, 41, 2, 2, 116, 10, 3, 2, 2, 2, 117, 118, 9, 6, 2, 2, 118, 12, 3, 2, 2, 2, 119, 123, 9, 7, 2, 2, 120, 122, 9, 8, 2, 2, 121, 120, 3, 2, 2, 2, 122, 125, 3, 2, 2, 2, 123, 121, 3, 2, 2, 2, 123, 124, 3, 2, 2, 2, 124, 14, 3, 2, 2, 2, 125, 123, 3, 2, 2, 2, 126, 127, 7, 42, 2, 2, 127, 16, 3, 2, 2, 2, 128, 129, 7, 43, 2, 2, 129, 18, 3, 2, 2, 2, 130, 131, 7, 46, 2, 2, 131, 20, 3, 2, 2, 2, 132, 133, 7, 41, 2, 2, 133, 22, 3, 2, 2, 2, 134, 135, 7, 36, 2, 2, 135, 24, 3, 2, 2, 2, 136, 137, 7, 93, 2, 2, 137, 26, 3, 2, 2, 2, 138, 139, 7, 95, 2, 2, 139, 28, 3, 2, 2, 2, 140, 143, 5, 21, 11, 2, 141, 143, 5, 23, 12, 2, 142, 140, 3, 2, 2, 2, 142, 141, 3, 2, 2, 2, 143, 30, 3, 2, 2, 2, 144, 145, 7, 67, 2, 2, 145, 146, 7, 80, 2, 2, 146, 147, 7, 70, 2, 2, 147, 32, 3, 2, 2, 2, 148, 149, 7, 81, 2, 2, 149, 150, 7, 84, 2, 2, 150, 34, 3, 2, 2, 2, 151, 152, 7, 63, 2, 2, 152, 36, 3, 2, 2, 2, 153, 154, 7, 35, 2, 2, 154, 155, 7, 63, 2, 2, 155, 38, 3, 2, 2, 2, 156, 157, 7, 64, 2, 2, 157, 40, 3, 2, 2, 2, 158, 159, 7, 64, 2, 2, 159, 160, 7, 63, 2, 2, 160, 42, 3, 2, 2, 2, 161, 162, 7, 62, 2, 2, 162, 44, 3, 2, 2, 2, 163, 164, 7, 62, 2, 2, 164, 165, 7, 63, 2, 2, 165, 46, 3, 2, 2, 2, 166, 167, 7, 78, 2, 2, 167, 168, 7, 75, 2, 2, 168, 169, 7, 77, 2, 2, 169, 170, 7, 71, 2, 2, 170, 48, 3, 2, 2, 2, 171, 172, 7, 75, 2, 2, 172, 173, 7, 80, 2, 2, 173, 50, 3, 2, 2, 2, 174, 175, 7, 69, 2, 2, 175, 176, 7, 81, 2, 2, 176, 177, 7, 80, 2, 2, 177, 178, 7, 86, 2, 2, 178, 179, 7, 67, 2, 2, 179, 180, 7, 75, 2, 2, 180, 181, 7, 80, 2, 2, 181, 182, 7, 85, 2, 2, 182, 52, 3, 2, 2, 2, 183, 184, 7, 86, 2, 2, 184, 185, 7, 84, 2, 2, 185, 186, 7, 87, 2, 2, 186, 187, 7, 71, 2, 2, 187, 54, 3, 2, 2, 2, 188, 189, 7, 72, 2, 2, 189, 190, 7, 67, 2, 2, 190, 191, 7, 78, 2, 2, 191, 192, 7, 85, 2, 2, 192, 193, 7, 71, 2, 2, 193, 56, 3, 2, 2, 2, 194, 195, 7, 78, 2, 2, 195, 196, 7, 75, 2, 2, 196, 197, 7, 79, 2, 2, 197, 198, 7, 75, 2, 2, 198, 199, 7, 86, 2, 2, 199, 58, 3, 2, 2, 2, 200, 201, 7, 81, 2, 2, 201, 202, 7, 72, 2, 2, 202, 203, 7, 72, 2, 2, 203, 204, 7, 85, 2, 2, 204, 205, 7, 71, 2, 2, 205, 206, 7, 86, 2, 2, 206, 60, 3, 2, 2, 2, 207, 208, 7, 81, 2, 2, 208, 209, 7, 84, 2, 2, 209, 210, 7, 70, 2, 2, 210, 211, 7, 71, 2, 2, 211, 212, 7, 84, 2, 2, 212, 213, 7, 34, 2, 2, 213, 214, 7, 68, 2, 2, 214, 215, 7, 91, 2, 2, 215, 62, 3, 2, 2, 2, 216, 217, 7, 67, 2, 2, 217, 218, 7, 85, 2, 2, 218, 219, 7, 69, 2, 2, 219, 64, 3, 2, 2, 2, 220, 221, 7, 70, 2, 2, 221, 222, 7, 71, 2, 2, 222, 223, 7, 85, 2, 2, 223, 224, 7, 69, 2, 2, 224, 66, 3, 2, 2, 2, 225, 226, 5, 7, 4, 2, 226, 68, 3, 2, 2, 2, 227, 228, 5, 9, 5, 2, 228, 70, 3, 2, 2, 2, 229, 231, 5, 11, 6, 2, 230, 229, 3, 2, 2, 2, 231, 232, 3, 2, 2, 2, 232, 230, 3, 2, 2, 2, 232, 233, 3, 2, 2, 2, 233, 72, 3, 2, 2, 2, 234, 236, 9, 9, 2, 2, 235, 234, 3, 2, 2, 2, 236, 237, 3, 2, 2, 2, 237, 235, 3, 2, 2, 2, 237, 238, 3, 2, 2, 2, 238, 74, 3, 2, 2, 2, 239, 241, 9, 10, 2, 2, 240, 239, 3, 2, 2, 2, 241, 242, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 242, 243, 3, 2, 2, 2, 243, 76, 3, 2, 2, 2, 244, 248, 9, 7, 2, 2, 245, 247, 9, 8, 2, 2, 246, 245, 3, 2, 2, 2, 247, 250, 3, 2, 2, 2, 248, 246, 3, 2, 2, 2, 248, 249, 3, 2, 2, 2, 249, 78, 3, 2, 2, 2, 250, 248, 3, 2, 2, 2, 15, 2, 82, 89, 97, 99, 110, 112, 123, 142, 232, 237, 242, 248, 3, 8, 2, 2] \ No newline at end of file diff --git a/packages/core/src/stores/webdaql/WebdaQLParserLexer.tokens b/packages/core/src/stores/webdaql/WebdaQLParserLexer.tokens deleted file mode 100644 index d6427881e..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLParserLexer.tokens +++ /dev/null @@ -1,57 +0,0 @@ -SPACE=1 -LR_BRACKET=2 -RR_BRACKET=3 -COMMA=4 -SINGLE_QUOTE_SYMB=5 -DOUBLE_QUOTE_SYMB=6 -LR_SQ_BRACKET=7 -RR_SQ_BRACKET=8 -AND=9 -OR=10 -EQUAL=11 -NOT_EQUAL=12 -GREATER=13 -GREATER_OR_EQUAL=14 -LESS=15 -LESS_OR_EQUAL=16 -LIKE=17 -IN=18 -CONTAINS=19 -TRUE=20 -FALSE=21 -LIMIT=22 -OFFSET=23 -ORDER_BY=24 -ASC=25 -DESC=26 -DQUOTED_STRING_LITERAL=27 -SQUOTED_STRING_LITERAL=28 -INTEGER_LITERAL=29 -IDENTIFIER=30 -IDENTIFIER_WITH_NUMBER=31 -FUNCTION_IDENTIFIER_WITH_UNDERSCORE=32 -'('=2 -')'=3 -','=4 -'\''=5 -'"'=6 -'['=7 -']'=8 -'AND'=9 -'OR'=10 -'='=11 -'!='=12 -'>'=13 -'>='=14 -'<'=15 -'<='=16 -'LIKE'=17 -'IN'=18 -'CONTAINS'=19 -'TRUE'=20 -'FALSE'=21 -'LIMIT'=22 -'OFFSET'=23 -'ORDER BY'=24 -'ASC'=25 -'DESC'=26 diff --git a/packages/core/src/stores/webdaql/WebdaQLParserLexer.ts b/packages/core/src/stores/webdaql/WebdaQLParserLexer.ts deleted file mode 100644 index 6360f2c55..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLParserLexer.ts +++ /dev/null @@ -1,321 +0,0 @@ -// Generated from src/stores/webdaql/WebdaQLParser.g4 by ANTLR 4.9.0-SNAPSHOT - -import { ATN } from "antlr4ts/atn/ATN"; -import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; -import { LexerATNSimulator } from "antlr4ts/atn/LexerATNSimulator"; -import { CharStream } from "antlr4ts/CharStream"; -import { Lexer } from "antlr4ts/Lexer"; -import { Vocabulary } from "antlr4ts/Vocabulary"; -import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; - -import * as Utils from "antlr4ts/misc/Utils"; - -export class WebdaQLParserLexer extends Lexer { - public static readonly SPACE = 1; - public static readonly LR_BRACKET = 2; - public static readonly RR_BRACKET = 3; - public static readonly COMMA = 4; - public static readonly SINGLE_QUOTE_SYMB = 5; - public static readonly DOUBLE_QUOTE_SYMB = 6; - public static readonly LR_SQ_BRACKET = 7; - public static readonly RR_SQ_BRACKET = 8; - public static readonly AND = 9; - public static readonly OR = 10; - public static readonly EQUAL = 11; - public static readonly NOT_EQUAL = 12; - public static readonly GREATER = 13; - public static readonly GREATER_OR_EQUAL = 14; - public static readonly LESS = 15; - public static readonly LESS_OR_EQUAL = 16; - public static readonly LIKE = 17; - public static readonly IN = 18; - public static readonly CONTAINS = 19; - public static readonly TRUE = 20; - public static readonly FALSE = 21; - public static readonly LIMIT = 22; - public static readonly OFFSET = 23; - public static readonly ORDER_BY = 24; - public static readonly ASC = 25; - public static readonly DESC = 26; - public static readonly DQUOTED_STRING_LITERAL = 27; - public static readonly SQUOTED_STRING_LITERAL = 28; - public static readonly INTEGER_LITERAL = 29; - public static readonly IDENTIFIER = 30; - public static readonly IDENTIFIER_WITH_NUMBER = 31; - public static readonly FUNCTION_IDENTIFIER_WITH_UNDERSCORE = 32; - - // 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[] = [ - "SPACE", - "ID_LITERAL", - "DQUOTA_STRING", - "SQUOTA_STRING", - "INT_DIGIT", - "FN_LITERAL", - "LR_BRACKET", - "RR_BRACKET", - "COMMA", - "SINGLE_QUOTE_SYMB", - "DOUBLE_QUOTE_SYMB", - "LR_SQ_BRACKET", - "RR_SQ_BRACKET", - "QUOTE_SYMB", - "AND", - "OR", - "EQUAL", - "NOT_EQUAL", - "GREATER", - "GREATER_OR_EQUAL", - "LESS", - "LESS_OR_EQUAL", - "LIKE", - "IN", - "CONTAINS", - "TRUE", - "FALSE", - "LIMIT", - "OFFSET", - "ORDER_BY", - "ASC", - "DESC", - "DQUOTED_STRING_LITERAL", - "SQUOTED_STRING_LITERAL", - "INTEGER_LITERAL", - "IDENTIFIER", - "IDENTIFIER_WITH_NUMBER", - "FUNCTION_IDENTIFIER_WITH_UNDERSCORE" - ]; - - private static readonly _LITERAL_NAMES: Array = [ - undefined, - undefined, - "'('", - "')'", - "','", - "'''", - "'\"'", - "'['", - "']'", - "'AND'", - "'OR'", - "'='", - "'!='", - "'>'", - "'>='", - "'<'", - "'<='", - "'LIKE'", - "'IN'", - "'CONTAINS'", - "'TRUE'", - "'FALSE'", - "'LIMIT'", - "'OFFSET'", - "'ORDER BY'", - "'ASC'", - "'DESC'" - ]; - private static readonly _SYMBOLIC_NAMES: Array = [ - undefined, - "SPACE", - "LR_BRACKET", - "RR_BRACKET", - "COMMA", - "SINGLE_QUOTE_SYMB", - "DOUBLE_QUOTE_SYMB", - "LR_SQ_BRACKET", - "RR_SQ_BRACKET", - "AND", - "OR", - "EQUAL", - "NOT_EQUAL", - "GREATER", - "GREATER_OR_EQUAL", - "LESS", - "LESS_OR_EQUAL", - "LIKE", - "IN", - "CONTAINS", - "TRUE", - "FALSE", - "LIMIT", - "OFFSET", - "ORDER_BY", - "ASC", - "DESC", - "DQUOTED_STRING_LITERAL", - "SQUOTED_STRING_LITERAL", - "INTEGER_LITERAL", - "IDENTIFIER", - "IDENTIFIER_WITH_NUMBER", - "FUNCTION_IDENTIFIER_WITH_UNDERSCORE" - ]; - public static readonly VOCABULARY: Vocabulary = new VocabularyImpl( - WebdaQLParserLexer._LITERAL_NAMES, - WebdaQLParserLexer._SYMBOLIC_NAMES, - [] - ); - - // @Override - // @NotNull - public get vocabulary(): Vocabulary { - return WebdaQLParserLexer.VOCABULARY; - } - // tslint:enable:no-trailing-whitespace - - constructor(input: CharStream) { - super(input); - this._interp = new LexerATNSimulator(WebdaQLParserLexer._ATN, this); - } - - // @Override - public get grammarFileName(): string { - return "WebdaQLParser.g4"; - } - - // @Override - public get ruleNames(): string[] { - return WebdaQLParserLexer.ruleNames; - } - - // @Override - public get serializedATN(): string { - return WebdaQLParserLexer._serializedATN; - } - - // @Override - public get channelNames(): string[] { - return WebdaQLParserLexer.channelNames; - } - - // @Override - public get modeNames(): string[] { - return WebdaQLParserLexer.modeNames; - } - - public static readonly _serializedATN: string = - '\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02"\xFB\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\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12" + - "\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t\x17" + - "\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t\x1C" + - '\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04"\t"\x04' + - "#\t#\x04$\t$\x04%\t%\x04&\t&\x04'\t'\x03\x02\x06\x02Q\n\x02\r\x02\x0E" + - "\x02R\x03\x02\x03\x02\x03\x03\x06\x03X\n\x03\r\x03\x0E\x03Y\x03\x04\x03" + - "\x04\x03\x04\x03\x04\x03\x04\x03\x04\x07\x04b\n\x04\f\x04\x0E\x04e\v\x04" + - "\x03\x04\x03\x04\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x07\x05" + - "o\n\x05\f\x05\x0E\x05r\v\x05\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03" + - "\x07\x07\x07z\n\x07\f\x07\x0E\x07}\v\x07\x03\b\x03\b\x03\t\x03\t\x03\n" + - "\x03\n\x03\v\x03\v\x03\f\x03\f\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0F\x03" + - "\x0F\x05\x0F\x8F\n\x0F\x03\x10\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11" + - "\x03\x11\x03\x12\x03\x12\x03\x13\x03\x13\x03\x13\x03\x14\x03\x14\x03\x15" + - "\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17\x03\x17\x03\x17\x03\x18\x03\x18" + - "\x03\x18\x03\x18\x03\x18\x03\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1A" + - "\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1B" + - "\x03\x1B\x03\x1B\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1D" + - "\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E\x03\x1E" + - "\x03\x1E\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F" + - "\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x03 \x03!\x03!\x03!\x03!\x03!" + - '\x03"\x03"\x03#\x03#\x03$\x06$\xE7\n$\r$\x0E$\xE8\x03%\x06%\xEC\n%\r' + - "%\x0E%\xED\x03&\x06&\xF1\n&\r&\x0E&\xF2\x03'\x03'\x07'\xF7\n'\f'" + - "\x0E'\xFA\v'\x02\x02\x02(\x03\x02\x03\x05\x02\x02\x07\x02\x02\t\x02" + - "\x02\v\x02\x02\r\x02\x02\x0F\x02\x04\x11\x02\x05\x13\x02\x06\x15\x02\x07" + - "\x17\x02\b\x19\x02\t\x1B\x02\n\x1D\x02\x02\x1F\x02\v!\x02\f#\x02\r%\x02" + - "\x0E'\x02\x0F)\x02\x10+\x02\x11-\x02\x12/\x02\x131\x02\x143\x02\x155" + - "\x02\x167\x02\x179\x02\x18;\x02\x19=\x02\x1A?\x02\x1BA\x02\x1CC\x02\x1D" + - 'E\x02\x1EG\x02\x1FI\x02 K\x02!M\x02"\x03\x02\v\x05\x02\v\f\x0F\x0F"' + - '"\x05\x022;C\\c|\x04\x02$$^^\x04\x02))^^\x03\x022;\x03\x02C\\\x04\x02' + - "C\\aa\x04\x02C\\c|\x07\x02002;C\\aac|\x02\u0102\x02\x03\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\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02" + - "\x02#\x03\x02\x02\x02\x02%\x03\x02\x02\x02\x02'\x03\x02\x02\x02\x02)" + - "\x03\x02\x02\x02\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02" + - "\x02\x02\x021\x03\x02\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02" + - "\x027\x03\x02\x02\x02\x029\x03\x02\x02\x02\x02;\x03\x02\x02\x02\x02=\x03" + - "\x02\x02\x02\x02?\x03\x02\x02\x02\x02A\x03\x02\x02\x02\x02C\x03\x02\x02" + - "\x02\x02E\x03\x02\x02\x02\x02G\x03\x02\x02\x02\x02I\x03\x02\x02\x02\x02" + - "K\x03\x02\x02\x02\x02M\x03\x02\x02\x02\x03P\x03\x02\x02\x02\x05W\x03\x02" + - "\x02\x02\x07[\x03\x02\x02\x02\th\x03\x02\x02\x02\vu\x03\x02\x02\x02\r" + - "w\x03\x02\x02\x02\x0F~\x03\x02\x02\x02\x11\x80\x03\x02\x02\x02\x13\x82" + - "\x03\x02\x02\x02\x15\x84\x03\x02\x02\x02\x17\x86\x03\x02\x02\x02\x19\x88" + - "\x03\x02\x02\x02\x1B\x8A\x03\x02\x02\x02\x1D\x8E\x03\x02\x02\x02\x1F\x90" + - "\x03\x02\x02\x02!\x94\x03\x02\x02\x02#\x97\x03\x02\x02\x02%\x99\x03\x02" + - "\x02\x02'\x9C\x03\x02\x02\x02)\x9E\x03\x02\x02\x02+\xA1\x03\x02\x02\x02" + - "-\xA3\x03\x02\x02\x02/\xA6\x03\x02\x02\x021\xAB\x03\x02\x02\x023\xAE\x03" + - "\x02\x02\x025\xB7\x03\x02\x02\x027\xBC\x03\x02\x02\x029\xC2\x03\x02\x02" + - "\x02;\xC8\x03\x02\x02\x02=\xCF\x03\x02\x02\x02?\xD8\x03\x02\x02\x02A\xDC" + - "\x03\x02\x02\x02C\xE1\x03\x02\x02\x02E\xE3\x03\x02\x02\x02G\xE6\x03\x02" + - "\x02\x02I\xEB\x03\x02\x02\x02K\xF0\x03\x02\x02\x02M\xF4\x03\x02\x02\x02" + - "OQ\t\x02\x02\x02PO\x03\x02\x02\x02QR\x03\x02\x02\x02RP\x03\x02\x02\x02" + - "RS\x03\x02\x02\x02ST\x03\x02\x02\x02TU\b\x02\x02\x02U\x04\x03\x02\x02" + - "\x02VX\t\x03\x02\x02WV\x03\x02\x02\x02XY\x03\x02\x02\x02YW\x03\x02\x02" + - "\x02YZ\x03\x02\x02\x02Z\x06\x03\x02\x02\x02[c\x07$\x02\x02\\]\x07^\x02" + - "\x02]b\v\x02\x02\x02^_\x07$\x02\x02_b\x07$\x02\x02`b\n\x04\x02\x02a\\" + - "\x03\x02\x02\x02a^\x03\x02\x02\x02a`\x03\x02\x02\x02be\x03\x02\x02\x02" + - "ca\x03\x02\x02\x02cd\x03\x02\x02\x02df\x03\x02\x02\x02ec\x03\x02\x02\x02" + - "fg\x07$\x02\x02g\b\x03\x02\x02\x02hp\x07)\x02\x02ij\x07^\x02\x02jo\v\x02" + - "\x02\x02kl\x07)\x02\x02lo\x07)\x02\x02mo\n\x05\x02\x02ni\x03\x02\x02\x02" + - "nk\x03\x02\x02\x02nm\x03\x02\x02\x02or\x03\x02\x02\x02pn\x03\x02\x02\x02" + - "pq\x03\x02\x02\x02qs\x03\x02\x02\x02rp\x03\x02\x02\x02st\x07)\x02\x02" + - "t\n\x03\x02\x02\x02uv\t\x06\x02\x02v\f\x03\x02\x02\x02w{\t\x07\x02\x02" + - "xz\t\b\x02\x02yx\x03\x02\x02\x02z}\x03\x02\x02\x02{y\x03\x02\x02\x02{" + - "|\x03\x02\x02\x02|\x0E\x03\x02\x02\x02}{\x03\x02\x02\x02~\x7F\x07*\x02" + - "\x02\x7F\x10\x03\x02\x02\x02\x80\x81\x07+\x02\x02\x81\x12\x03\x02\x02" + - "\x02\x82\x83\x07.\x02\x02\x83\x14\x03\x02\x02\x02\x84\x85\x07)\x02\x02" + - "\x85\x16\x03\x02\x02\x02\x86\x87\x07$\x02\x02\x87\x18\x03\x02\x02\x02" + - "\x88\x89\x07]\x02\x02\x89\x1A\x03\x02\x02\x02\x8A\x8B\x07_\x02\x02\x8B" + - "\x1C\x03\x02\x02\x02\x8C\x8F\x05\x15\v\x02\x8D\x8F\x05\x17\f\x02\x8E\x8C" + - "\x03\x02\x02\x02\x8E\x8D\x03\x02\x02\x02\x8F\x1E\x03\x02\x02\x02\x90\x91" + - "\x07C\x02\x02\x91\x92\x07P\x02\x02\x92\x93\x07F\x02\x02\x93 \x03\x02\x02" + - '\x02\x94\x95\x07Q\x02\x02\x95\x96\x07T\x02\x02\x96"\x03\x02\x02\x02\x97' + - "\x98\x07?\x02\x02\x98$\x03\x02\x02\x02\x99\x9A\x07#\x02\x02\x9A\x9B\x07" + - "?\x02\x02\x9B&\x03\x02\x02\x02\x9C\x9D\x07@\x02\x02\x9D(\x03\x02\x02\x02" + - "\x9E\x9F\x07@\x02\x02\x9F\xA0\x07?\x02\x02\xA0*\x03\x02\x02\x02\xA1\xA2" + - "\x07>\x02\x02\xA2,\x03\x02\x02\x02\xA3\xA4\x07>\x02\x02\xA4\xA5\x07?\x02" + - "\x02\xA5.\x03\x02\x02\x02\xA6\xA7\x07N\x02\x02\xA7\xA8\x07K\x02\x02\xA8" + - "\xA9\x07M\x02\x02\xA9\xAA\x07G\x02\x02\xAA0\x03\x02\x02\x02\xAB\xAC\x07" + - "K\x02\x02\xAC\xAD\x07P\x02\x02\xAD2\x03\x02\x02\x02\xAE\xAF\x07E\x02\x02" + - "\xAF\xB0\x07Q\x02\x02\xB0\xB1\x07P\x02\x02\xB1\xB2\x07V\x02\x02\xB2\xB3" + - "\x07C\x02\x02\xB3\xB4\x07K\x02\x02\xB4\xB5\x07P\x02\x02\xB5\xB6\x07U\x02" + - "\x02\xB64\x03\x02\x02\x02\xB7\xB8\x07V\x02\x02\xB8\xB9\x07T\x02\x02\xB9" + - "\xBA\x07W\x02\x02\xBA\xBB\x07G\x02\x02\xBB6\x03\x02\x02\x02\xBC\xBD\x07" + - "H\x02\x02\xBD\xBE\x07C\x02\x02\xBE\xBF\x07N\x02\x02\xBF\xC0\x07U\x02\x02" + - "\xC0\xC1\x07G\x02\x02\xC18\x03\x02\x02\x02\xC2\xC3\x07N\x02\x02\xC3\xC4" + - "\x07K\x02\x02\xC4\xC5\x07O\x02\x02\xC5\xC6\x07K\x02\x02\xC6\xC7\x07V\x02" + - "\x02\xC7:\x03\x02\x02\x02\xC8\xC9\x07Q\x02\x02\xC9\xCA\x07H\x02\x02\xCA" + - "\xCB\x07H\x02\x02\xCB\xCC\x07U\x02\x02\xCC\xCD\x07G\x02\x02\xCD\xCE\x07" + - "V\x02\x02\xCE<\x03\x02\x02\x02\xCF\xD0\x07Q\x02\x02\xD0\xD1\x07T\x02\x02" + - "\xD1\xD2\x07F\x02\x02\xD2\xD3\x07G\x02\x02\xD3\xD4\x07T\x02\x02\xD4\xD5" + - '\x07"\x02\x02\xD5\xD6\x07D\x02\x02\xD6\xD7\x07[\x02\x02\xD7>\x03\x02' + - "\x02\x02\xD8\xD9\x07C\x02\x02\xD9\xDA\x07U\x02\x02\xDA\xDB\x07E\x02\x02" + - "\xDB@\x03\x02\x02\x02\xDC\xDD\x07F\x02\x02\xDD\xDE\x07G\x02\x02\xDE\xDF" + - "\x07U\x02\x02\xDF\xE0\x07E\x02\x02\xE0B\x03\x02\x02\x02\xE1\xE2\x05\x07" + - "\x04\x02\xE2D\x03\x02\x02\x02\xE3\xE4\x05\t\x05\x02\xE4F\x03\x02\x02\x02" + - "\xE5\xE7\x05\v\x06\x02\xE6\xE5\x03\x02\x02\x02\xE7\xE8\x03\x02\x02\x02" + - "\xE8\xE6\x03\x02\x02\x02\xE8\xE9\x03\x02\x02\x02\xE9H\x03\x02\x02\x02" + - "\xEA\xEC\t\t\x02\x02\xEB\xEA\x03\x02\x02\x02\xEC\xED\x03\x02\x02\x02\xED" + - "\xEB\x03\x02\x02\x02\xED\xEE\x03\x02\x02\x02\xEEJ\x03\x02\x02\x02\xEF" + - "\xF1\t\n\x02\x02\xF0\xEF\x03\x02\x02\x02\xF1\xF2\x03\x02\x02\x02\xF2\xF0" + - "\x03\x02\x02\x02\xF2\xF3\x03\x02\x02\x02\xF3L\x03\x02\x02\x02\xF4\xF8" + - "\t\x07\x02\x02\xF5\xF7\t\b\x02\x02\xF6\xF5\x03\x02\x02\x02\xF7\xFA\x03" + - "\x02\x02\x02\xF8\xF6\x03\x02\x02\x02\xF8\xF9\x03\x02\x02\x02\xF9N\x03" + - "\x02\x02\x02\xFA\xF8\x03\x02\x02\x02\x0F\x02RYacnp{\x8E\xE8\xED\xF2\xF8" + - "\x03\b\x02\x02"; - public static __ATN: ATN; - public static get _ATN(): ATN { - if (!WebdaQLParserLexer.__ATN) { - WebdaQLParserLexer.__ATN = new ATNDeserializer().deserialize( - Utils.toCharArray(WebdaQLParserLexer._serializedATN) - ); - } - - return WebdaQLParserLexer.__ATN; - } -} diff --git a/packages/core/src/stores/webdaql/WebdaQLParserListener.ts b/packages/core/src/stores/webdaql/WebdaQLParserListener.ts deleted file mode 100644 index 35481e9ce..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLParserListener.ts +++ /dev/null @@ -1,352 +0,0 @@ -// Generated from src/stores/webdaql/WebdaQLParser.g4 by ANTLR 4.9.0-SNAPSHOT - -import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; - -import { - AndLogicExpressionContext, - AtomContext, - AtomExpressionContext, - BinaryComparisonExpressionContext, - BooleanAtomContext, - BooleanLiteralContext, - ContainsExpressionContext, - ExpressionContext, - IdentifierAtomContext, - IdentifierContext, - InExpressionContext, - IntegerAtomContext, - IntegerLiteralContext, - LikeExpressionContext, - LimitExpressionContext, - OffsetExpressionContext, - OrderExpressionContext, - OrderFieldExpressionContext, - OrLogicExpressionContext, - SetExpressionContext, - StringAtomContext, - StringLiteralContext, - SubExpressionContext, - ValuesAtomContext, - ValuesContext, - WebdaqlContext -} from "./WebdaQLParserParser"; - -/** - * This interface defines a complete listener for a parse tree produced by - * `WebdaQLParserParser`. - */ -export interface WebdaQLParserListener extends ParseTreeListener { - /** - * Enter a parse tree produced by the `likeExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - enterLikeExpression?: (ctx: LikeExpressionContext) => void; - /** - * Exit a parse tree produced by the `likeExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - exitLikeExpression?: (ctx: LikeExpressionContext) => void; - - /** - * Enter a parse tree produced by the `inExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - enterInExpression?: (ctx: InExpressionContext) => void; - /** - * Exit a parse tree produced by the `inExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - exitInExpression?: (ctx: InExpressionContext) => void; - - /** - * Enter a parse tree produced by the `containsExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - enterContainsExpression?: (ctx: ContainsExpressionContext) => void; - /** - * Exit a parse tree produced by the `containsExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - exitContainsExpression?: (ctx: ContainsExpressionContext) => void; - - /** - * Enter a parse tree produced by the `binaryComparisonExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - enterBinaryComparisonExpression?: (ctx: BinaryComparisonExpressionContext) => void; - /** - * Exit a parse tree produced by the `binaryComparisonExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - exitBinaryComparisonExpression?: (ctx: BinaryComparisonExpressionContext) => void; - - /** - * Enter a parse tree produced by the `andLogicExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - enterAndLogicExpression?: (ctx: AndLogicExpressionContext) => void; - /** - * Exit a parse tree produced by the `andLogicExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - exitAndLogicExpression?: (ctx: AndLogicExpressionContext) => void; - - /** - * Enter a parse tree produced by the `orLogicExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - enterOrLogicExpression?: (ctx: OrLogicExpressionContext) => void; - /** - * Exit a parse tree produced by the `orLogicExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - exitOrLogicExpression?: (ctx: OrLogicExpressionContext) => void; - - /** - * Enter a parse tree produced by the `subExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - enterSubExpression?: (ctx: SubExpressionContext) => void; - /** - * Exit a parse tree produced by the `subExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - exitSubExpression?: (ctx: SubExpressionContext) => void; - - /** - * Enter a parse tree produced by the `atomExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - enterAtomExpression?: (ctx: AtomExpressionContext) => void; - /** - * Exit a parse tree produced by the `atomExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - exitAtomExpression?: (ctx: AtomExpressionContext) => void; - - /** - * Enter a parse tree produced by the `booleanAtom` - * labeled alternative in `WebdaQLParserParser.values`. - * @param ctx the parse tree - */ - enterBooleanAtom?: (ctx: BooleanAtomContext) => void; - /** - * Exit a parse tree produced by the `booleanAtom` - * labeled alternative in `WebdaQLParserParser.values`. - * @param ctx the parse tree - */ - exitBooleanAtom?: (ctx: BooleanAtomContext) => void; - - /** - * Enter a parse tree produced by the `integerAtom` - * labeled alternative in `WebdaQLParserParser.values`. - * @param ctx the parse tree - */ - enterIntegerAtom?: (ctx: IntegerAtomContext) => void; - /** - * Exit a parse tree produced by the `integerAtom` - * labeled alternative in `WebdaQLParserParser.values`. - * @param ctx the parse tree - */ - exitIntegerAtom?: (ctx: IntegerAtomContext) => void; - - /** - * Enter a parse tree produced by the `stringAtom` - * labeled alternative in `WebdaQLParserParser.values`. - * @param ctx the parse tree - */ - enterStringAtom?: (ctx: StringAtomContext) => void; - /** - * Exit a parse tree produced by the `stringAtom` - * labeled alternative in `WebdaQLParserParser.values`. - * @param ctx the parse tree - */ - exitStringAtom?: (ctx: StringAtomContext) => void; - - /** - * Enter a parse tree produced by the `valuesAtom` - * labeled alternative in `WebdaQLParserParser.atom`. - * @param ctx the parse tree - */ - enterValuesAtom?: (ctx: ValuesAtomContext) => void; - /** - * Exit a parse tree produced by the `valuesAtom` - * labeled alternative in `WebdaQLParserParser.atom`. - * @param ctx the parse tree - */ - exitValuesAtom?: (ctx: ValuesAtomContext) => void; - - /** - * Enter a parse tree produced by the `identifierAtom` - * labeled alternative in `WebdaQLParserParser.atom`. - * @param ctx the parse tree - */ - enterIdentifierAtom?: (ctx: IdentifierAtomContext) => void; - /** - * Exit a parse tree produced by the `identifierAtom` - * labeled alternative in `WebdaQLParserParser.atom`. - * @param ctx the parse tree - */ - exitIdentifierAtom?: (ctx: IdentifierAtomContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.webdaql`. - * @param ctx the parse tree - */ - enterWebdaql?: (ctx: WebdaqlContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.webdaql`. - * @param ctx the parse tree - */ - exitWebdaql?: (ctx: WebdaqlContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.limitExpression`. - * @param ctx the parse tree - */ - enterLimitExpression?: (ctx: LimitExpressionContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.limitExpression`. - * @param ctx the parse tree - */ - exitLimitExpression?: (ctx: LimitExpressionContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.offsetExpression`. - * @param ctx the parse tree - */ - enterOffsetExpression?: (ctx: OffsetExpressionContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.offsetExpression`. - * @param ctx the parse tree - */ - exitOffsetExpression?: (ctx: OffsetExpressionContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.orderFieldExpression`. - * @param ctx the parse tree - */ - enterOrderFieldExpression?: (ctx: OrderFieldExpressionContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.orderFieldExpression`. - * @param ctx the parse tree - */ - exitOrderFieldExpression?: (ctx: OrderFieldExpressionContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.orderExpression`. - * @param ctx the parse tree - */ - enterOrderExpression?: (ctx: OrderExpressionContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.orderExpression`. - * @param ctx the parse tree - */ - exitOrderExpression?: (ctx: OrderExpressionContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - enterExpression?: (ctx: ExpressionContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.expression`. - * @param ctx the parse tree - */ - exitExpression?: (ctx: ExpressionContext) => void; - - /** - * Enter a parse tree produced by the `values` - * labeled alternative in `WebdaQLParserParser.expressionexpressionexpressionexpressionexpressionexpressionexpressionexpressionvaluesvaluesvaluesatomatom`. - * @param ctx the parse tree - */ - enterValues?: (ctx: ValuesContext) => void; - /** - * Exit a parse tree produced by the `values` - * labeled alternative in `WebdaQLParserParser.expressionexpressionexpressionexpressionexpressionexpressionexpressionexpressionvaluesvaluesvaluesatomatom`. - * @param ctx the parse tree - */ - exitValues?: (ctx: ValuesContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.atom`. - * @param ctx the parse tree - */ - enterAtom?: (ctx: AtomContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.atom`. - * @param ctx the parse tree - */ - exitAtom?: (ctx: AtomContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.identifier`. - * @param ctx the parse tree - */ - enterIdentifier?: (ctx: IdentifierContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.identifier`. - * @param ctx the parse tree - */ - exitIdentifier?: (ctx: IdentifierContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.booleanLiteral`. - * @param ctx the parse tree - */ - enterBooleanLiteral?: (ctx: BooleanLiteralContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.booleanLiteral`. - * @param ctx the parse tree - */ - exitBooleanLiteral?: (ctx: BooleanLiteralContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.stringLiteral`. - * @param ctx the parse tree - */ - enterStringLiteral?: (ctx: StringLiteralContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.stringLiteral`. - * @param ctx the parse tree - */ - exitStringLiteral?: (ctx: StringLiteralContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.integerLiteral`. - * @param ctx the parse tree - */ - enterIntegerLiteral?: (ctx: IntegerLiteralContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.integerLiteral`. - * @param ctx the parse tree - */ - exitIntegerLiteral?: (ctx: IntegerLiteralContext) => void; - - /** - * Enter a parse tree produced by `WebdaQLParserParser.setExpression`. - * @param ctx the parse tree - */ - enterSetExpression?: (ctx: SetExpressionContext) => void; - /** - * Exit a parse tree produced by `WebdaQLParserParser.setExpression`. - * @param ctx the parse tree - */ - exitSetExpression?: (ctx: SetExpressionContext) => void; -} diff --git a/packages/core/src/stores/webdaql/WebdaQLParserParser.ts b/packages/core/src/stores/webdaql/WebdaQLParserParser.ts deleted file mode 100644 index 6b54e5ecf..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLParserParser.ts +++ /dev/null @@ -1,1815 +0,0 @@ -// Generated from src/stores/webdaql/WebdaQLParser.g4 by ANTLR 4.9.0-SNAPSHOT - -import { ATN } from "antlr4ts/atn/ATN"; -import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; -import { ParserATNSimulator } from "antlr4ts/atn/ParserATNSimulator"; -import { FailedPredicateException } from "antlr4ts/FailedPredicateException"; -import { NoViableAltException } from "antlr4ts/NoViableAltException"; -import { Parser } from "antlr4ts/Parser"; -import { ParserRuleContext } from "antlr4ts/ParserRuleContext"; -import { RecognitionException } from "antlr4ts/RecognitionException"; -import { RuleContext } from "antlr4ts/RuleContext"; -//import { RuleVersion } from "antlr4ts/RuleVersion"; -import { Token } from "antlr4ts/Token"; -import { TokenStream } from "antlr4ts/TokenStream"; -import { TerminalNode } from "antlr4ts/tree/TerminalNode"; -import { Vocabulary } from "antlr4ts/Vocabulary"; -import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; - -import * as Utils from "antlr4ts/misc/Utils"; - -import { WebdaQLParserListener } from "./WebdaQLParserListener"; -import { WebdaQLParserVisitor } from "./WebdaQLParserVisitor"; - -export class WebdaQLParserParser extends Parser { - public static readonly SPACE = 1; - public static readonly LR_BRACKET = 2; - public static readonly RR_BRACKET = 3; - public static readonly COMMA = 4; - public static readonly SINGLE_QUOTE_SYMB = 5; - public static readonly DOUBLE_QUOTE_SYMB = 6; - public static readonly LR_SQ_BRACKET = 7; - public static readonly RR_SQ_BRACKET = 8; - public static readonly AND = 9; - public static readonly OR = 10; - public static readonly EQUAL = 11; - public static readonly NOT_EQUAL = 12; - public static readonly GREATER = 13; - public static readonly GREATER_OR_EQUAL = 14; - public static readonly LESS = 15; - public static readonly LESS_OR_EQUAL = 16; - public static readonly LIKE = 17; - public static readonly IN = 18; - public static readonly CONTAINS = 19; - public static readonly TRUE = 20; - public static readonly FALSE = 21; - public static readonly LIMIT = 22; - public static readonly OFFSET = 23; - public static readonly ORDER_BY = 24; - public static readonly ASC = 25; - public static readonly DESC = 26; - public static readonly DQUOTED_STRING_LITERAL = 27; - public static readonly SQUOTED_STRING_LITERAL = 28; - public static readonly INTEGER_LITERAL = 29; - public static readonly IDENTIFIER = 30; - public static readonly IDENTIFIER_WITH_NUMBER = 31; - public static readonly FUNCTION_IDENTIFIER_WITH_UNDERSCORE = 32; - public static readonly RULE_webdaql = 0; - public static readonly RULE_limitExpression = 1; - public static readonly RULE_offsetExpression = 2; - public static readonly RULE_orderFieldExpression = 3; - public static readonly RULE_orderExpression = 4; - public static readonly RULE_expression = 5; - public static readonly RULE_values = 6; - public static readonly RULE_atom = 7; - public static readonly RULE_identifier = 8; - public static readonly RULE_booleanLiteral = 9; - public static readonly RULE_stringLiteral = 10; - public static readonly RULE_integerLiteral = 11; - public static readonly RULE_setExpression = 12; - // tslint:disable:no-trailing-whitespace - public static readonly ruleNames: string[] = [ - "webdaql", - "limitExpression", - "offsetExpression", - "orderFieldExpression", - "orderExpression", - "expression", - "values", - "atom", - "identifier", - "booleanLiteral", - "stringLiteral", - "integerLiteral", - "setExpression" - ]; - - private static readonly _LITERAL_NAMES: Array = [ - undefined, - undefined, - "'('", - "')'", - "','", - "'''", - "'\"'", - "'['", - "']'", - "'AND'", - "'OR'", - "'='", - "'!='", - "'>'", - "'>='", - "'<'", - "'<='", - "'LIKE'", - "'IN'", - "'CONTAINS'", - "'TRUE'", - "'FALSE'", - "'LIMIT'", - "'OFFSET'", - "'ORDER BY'", - "'ASC'", - "'DESC'" - ]; - private static readonly _SYMBOLIC_NAMES: Array = [ - undefined, - "SPACE", - "LR_BRACKET", - "RR_BRACKET", - "COMMA", - "SINGLE_QUOTE_SYMB", - "DOUBLE_QUOTE_SYMB", - "LR_SQ_BRACKET", - "RR_SQ_BRACKET", - "AND", - "OR", - "EQUAL", - "NOT_EQUAL", - "GREATER", - "GREATER_OR_EQUAL", - "LESS", - "LESS_OR_EQUAL", - "LIKE", - "IN", - "CONTAINS", - "TRUE", - "FALSE", - "LIMIT", - "OFFSET", - "ORDER_BY", - "ASC", - "DESC", - "DQUOTED_STRING_LITERAL", - "SQUOTED_STRING_LITERAL", - "INTEGER_LITERAL", - "IDENTIFIER", - "IDENTIFIER_WITH_NUMBER", - "FUNCTION_IDENTIFIER_WITH_UNDERSCORE" - ]; - public static readonly VOCABULARY: Vocabulary = new VocabularyImpl( - WebdaQLParserParser._LITERAL_NAMES, - WebdaQLParserParser._SYMBOLIC_NAMES, - [] - ); - - // @Override - // @NotNull - public get vocabulary(): Vocabulary { - return WebdaQLParserParser.VOCABULARY; - } - // tslint:enable:no-trailing-whitespace - - // @Override - public get grammarFileName(): string { - return "WebdaQLParser.g4"; - } - - // @Override - public get ruleNames(): string[] { - return WebdaQLParserParser.ruleNames; - } - - // @Override - public get serializedATN(): string { - return WebdaQLParserParser._serializedATN; - } - - protected createFailedPredicateException(predicate?: string, message?: string): FailedPredicateException { - return new FailedPredicateException(this, predicate, message); - } - - constructor(input: TokenStream) { - super(input); - this._interp = new ParserATNSimulator(WebdaQLParserParser._ATN, this); - } - // @RuleVersion(0) - public webdaql(): WebdaqlContext { - let _localctx: WebdaqlContext = new WebdaqlContext(this._ctx, this.state); - this.enterRule(_localctx, 0, WebdaQLParserParser.RULE_webdaql); - let _la: number; - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 27; - this._errHandler.sync(this); - _la = this._input.LA(1); - if ( - (_la & ~0x1f) === 0 && - ((1 << _la) & - ((1 << WebdaQLParserParser.LR_BRACKET) | - (1 << WebdaQLParserParser.TRUE) | - (1 << WebdaQLParserParser.FALSE) | - (1 << WebdaQLParserParser.DQUOTED_STRING_LITERAL) | - (1 << WebdaQLParserParser.SQUOTED_STRING_LITERAL) | - (1 << WebdaQLParserParser.INTEGER_LITERAL) | - (1 << WebdaQLParserParser.IDENTIFIER) | - (1 << WebdaQLParserParser.IDENTIFIER_WITH_NUMBER))) !== - 0 - ) { - { - this.state = 26; - this.expression(0); - } - } - - this.state = 30; - this._errHandler.sync(this); - _la = this._input.LA(1); - if (_la === WebdaQLParserParser.ORDER_BY) { - { - this.state = 29; - this.orderExpression(); - } - } - - this.state = 33; - this._errHandler.sync(this); - _la = this._input.LA(1); - if (_la === WebdaQLParserParser.LIMIT) { - { - this.state = 32; - this.limitExpression(); - } - } - - this.state = 36; - this._errHandler.sync(this); - _la = this._input.LA(1); - if (_la === WebdaQLParserParser.OFFSET) { - { - this.state = 35; - this.offsetExpression(); - } - } - - this.state = 38; - this.match(WebdaQLParserParser.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 limitExpression(): LimitExpressionContext { - let _localctx: LimitExpressionContext = new LimitExpressionContext(this._ctx, this.state); - this.enterRule(_localctx, 2, WebdaQLParserParser.RULE_limitExpression); - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 40; - this.match(WebdaQLParserParser.LIMIT); - this.state = 41; - this.integerLiteral(); - } - } 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 offsetExpression(): OffsetExpressionContext { - let _localctx: OffsetExpressionContext = new OffsetExpressionContext(this._ctx, this.state); - this.enterRule(_localctx, 4, WebdaQLParserParser.RULE_offsetExpression); - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 43; - this.match(WebdaQLParserParser.OFFSET); - this.state = 44; - this.stringLiteral(); - } - } 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 orderFieldExpression(): OrderFieldExpressionContext { - let _localctx: OrderFieldExpressionContext = new OrderFieldExpressionContext(this._ctx, this.state); - this.enterRule(_localctx, 6, WebdaQLParserParser.RULE_orderFieldExpression); - let _la: number; - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 46; - this.identifier(); - this.state = 48; - this._errHandler.sync(this); - _la = this._input.LA(1); - if (_la === WebdaQLParserParser.ASC || _la === WebdaQLParserParser.DESC) { - { - this.state = 47; - _la = this._input.LA(1); - if (!(_la === WebdaQLParserParser.ASC || _la === WebdaQLParserParser.DESC)) { - this._errHandler.recoverInline(this); - } else { - if (this._input.LA(1) === Token.EOF) { - this.matchedEOF = true; - } - - this._errHandler.reportMatch(this); - this.consume(); - } - } - } - } - } 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 orderExpression(): OrderExpressionContext { - let _localctx: OrderExpressionContext = new OrderExpressionContext(this._ctx, this.state); - this.enterRule(_localctx, 8, WebdaQLParserParser.RULE_orderExpression); - let _la: number; - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 50; - this.match(WebdaQLParserParser.ORDER_BY); - this.state = 51; - this.orderFieldExpression(); - this.state = 56; - this._errHandler.sync(this); - _la = this._input.LA(1); - while (_la === WebdaQLParserParser.COMMA) { - { - { - this.state = 52; - this.match(WebdaQLParserParser.COMMA); - this.state = 53; - this.orderFieldExpression(); - } - } - this.state = 58; - 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; - } - - public expression(): ExpressionContext; - public expression(_p: number): ExpressionContext; - // @RuleVersion(0) - public expression(_p?: number): ExpressionContext { - if (_p === undefined) { - _p = 0; - } - - let _parentctx: ParserRuleContext = this._ctx; - let _parentState: number = this.state; - let _localctx: ExpressionContext = new ExpressionContext(this._ctx, _parentState); - let _prevctx: ExpressionContext = _localctx; - let _startState: number = 10; - this.enterRecursionRule(_localctx, 10, WebdaQLParserParser.RULE_expression, _p); - let _la: number; - try { - let _alt: number; - this.enterOuterAlt(_localctx, 1); - { - this.state = 81; - this._errHandler.sync(this); - switch (this.interpreter.adaptivePredict(this._input, 6, this._ctx)) { - case 1: - { - _localctx = new LikeExpressionContext(_localctx); - this._ctx = _localctx; - _prevctx = _localctx; - - this.state = 60; - this.identifier(); - this.state = 61; - this.match(WebdaQLParserParser.LIKE); - this.state = 62; - this.stringLiteral(); - } - break; - - case 2: - { - _localctx = new InExpressionContext(_localctx); - this._ctx = _localctx; - _prevctx = _localctx; - this.state = 64; - this.identifier(); - this.state = 65; - this.match(WebdaQLParserParser.IN); - this.state = 66; - this.setExpression(); - } - break; - - case 3: - { - _localctx = new ContainsExpressionContext(_localctx); - this._ctx = _localctx; - _prevctx = _localctx; - this.state = 68; - this.identifier(); - this.state = 69; - this.match(WebdaQLParserParser.CONTAINS); - this.state = 70; - this.stringLiteral(); - } - break; - - case 4: - { - _localctx = new BinaryComparisonExpressionContext(_localctx); - this._ctx = _localctx; - _prevctx = _localctx; - this.state = 72; - this.identifier(); - this.state = 73; - _la = this._input.LA(1); - if ( - !( - (_la & ~0x1f) === 0 && - ((1 << _la) & - ((1 << WebdaQLParserParser.EQUAL) | - (1 << WebdaQLParserParser.NOT_EQUAL) | - (1 << WebdaQLParserParser.GREATER) | - (1 << WebdaQLParserParser.GREATER_OR_EQUAL) | - (1 << WebdaQLParserParser.LESS) | - (1 << WebdaQLParserParser.LESS_OR_EQUAL))) !== - 0 - ) - ) { - this._errHandler.recoverInline(this); - } else { - if (this._input.LA(1) === Token.EOF) { - this.matchedEOF = true; - } - - this._errHandler.reportMatch(this); - this.consume(); - } - this.state = 74; - this.values(); - } - break; - - case 5: - { - _localctx = new SubExpressionContext(_localctx); - this._ctx = _localctx; - _prevctx = _localctx; - this.state = 76; - this.match(WebdaQLParserParser.LR_BRACKET); - this.state = 77; - this.expression(0); - this.state = 78; - this.match(WebdaQLParserParser.RR_BRACKET); - } - break; - - case 6: - { - _localctx = new AtomExpressionContext(_localctx); - this._ctx = _localctx; - _prevctx = _localctx; - this.state = 80; - this.atom(); - } - break; - } - this._ctx._stop = this._input.tryLT(-1); - this.state = 91; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 8, this._ctx); - while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { - if (_alt === 1) { - if (this._parseListeners != null) { - this.triggerExitRuleEvent(); - } - _prevctx = _localctx; - { - this.state = 89; - this._errHandler.sync(this); - switch (this.interpreter.adaptivePredict(this._input, 7, this._ctx)) { - case 1: - { - _localctx = new AndLogicExpressionContext(new ExpressionContext(_parentctx, _parentState)); - this.pushNewRecursionContext(_localctx, _startState, WebdaQLParserParser.RULE_expression); - this.state = 83; - if (!this.precpred(this._ctx, 4)) { - throw this.createFailedPredicateException("this.precpred(this._ctx, 4)"); - } - this.state = 84; - this.match(WebdaQLParserParser.AND); - this.state = 85; - this.expression(5); - } - break; - - case 2: - { - _localctx = new OrLogicExpressionContext(new ExpressionContext(_parentctx, _parentState)); - this.pushNewRecursionContext(_localctx, _startState, WebdaQLParserParser.RULE_expression); - this.state = 86; - if (!this.precpred(this._ctx, 3)) { - throw this.createFailedPredicateException("this.precpred(this._ctx, 3)"); - } - this.state = 87; - this.match(WebdaQLParserParser.OR); - this.state = 88; - this.expression(4); - } - break; - } - } - } - this.state = 93; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 8, 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.unrollRecursionContexts(_parentctx); - } - return _localctx; - } - // @RuleVersion(0) - public values(): ValuesContext { - let _localctx: ValuesContext = new ValuesContext(this._ctx, this.state); - this.enterRule(_localctx, 12, WebdaQLParserParser.RULE_values); - try { - this.state = 97; - this._errHandler.sync(this); - switch (this._input.LA(1)) { - case WebdaQLParserParser.TRUE: - case WebdaQLParserParser.FALSE: - _localctx = new BooleanAtomContext(_localctx); - this.enterOuterAlt(_localctx, 1); - { - this.state = 94; - this.booleanLiteral(); - } - break; - case WebdaQLParserParser.INTEGER_LITERAL: - _localctx = new IntegerAtomContext(_localctx); - this.enterOuterAlt(_localctx, 2); - { - this.state = 95; - this.integerLiteral(); - } - break; - case WebdaQLParserParser.DQUOTED_STRING_LITERAL: - case WebdaQLParserParser.SQUOTED_STRING_LITERAL: - _localctx = new StringAtomContext(_localctx); - this.enterOuterAlt(_localctx, 3); - { - this.state = 96; - this.stringLiteral(); - } - 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 atom(): AtomContext { - let _localctx: AtomContext = new AtomContext(this._ctx, this.state); - this.enterRule(_localctx, 14, WebdaQLParserParser.RULE_atom); - try { - this.state = 101; - this._errHandler.sync(this); - switch (this._input.LA(1)) { - case WebdaQLParserParser.TRUE: - case WebdaQLParserParser.FALSE: - case WebdaQLParserParser.DQUOTED_STRING_LITERAL: - case WebdaQLParserParser.SQUOTED_STRING_LITERAL: - case WebdaQLParserParser.INTEGER_LITERAL: - _localctx = new ValuesAtomContext(_localctx); - this.enterOuterAlt(_localctx, 1); - { - this.state = 99; - this.values(); - } - break; - case WebdaQLParserParser.IDENTIFIER: - case WebdaQLParserParser.IDENTIFIER_WITH_NUMBER: - _localctx = new IdentifierAtomContext(_localctx); - this.enterOuterAlt(_localctx, 2); - { - this.state = 100; - this.identifier(); - } - 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 identifier(): IdentifierContext { - let _localctx: IdentifierContext = new IdentifierContext(this._ctx, this.state); - this.enterRule(_localctx, 16, WebdaQLParserParser.RULE_identifier); - let _la: number; - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 103; - _la = this._input.LA(1); - if (!(_la === WebdaQLParserParser.IDENTIFIER || _la === WebdaQLParserParser.IDENTIFIER_WITH_NUMBER)) { - this._errHandler.recoverInline(this); - } else { - if (this._input.LA(1) === Token.EOF) { - this.matchedEOF = true; - } - - this._errHandler.reportMatch(this); - this.consume(); - } - } - } 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 booleanLiteral(): BooleanLiteralContext { - let _localctx: BooleanLiteralContext = new BooleanLiteralContext(this._ctx, this.state); - this.enterRule(_localctx, 18, WebdaQLParserParser.RULE_booleanLiteral); - let _la: number; - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 105; - _la = this._input.LA(1); - if (!(_la === WebdaQLParserParser.TRUE || _la === WebdaQLParserParser.FALSE)) { - this._errHandler.recoverInline(this); - } else { - if (this._input.LA(1) === Token.EOF) { - this.matchedEOF = true; - } - - this._errHandler.reportMatch(this); - this.consume(); - } - } - } 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 stringLiteral(): StringLiteralContext { - let _localctx: StringLiteralContext = new StringLiteralContext(this._ctx, this.state); - this.enterRule(_localctx, 20, WebdaQLParserParser.RULE_stringLiteral); - let _la: number; - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 107; - _la = this._input.LA(1); - if ( - !(_la === WebdaQLParserParser.DQUOTED_STRING_LITERAL || _la === WebdaQLParserParser.SQUOTED_STRING_LITERAL) - ) { - this._errHandler.recoverInline(this); - } else { - if (this._input.LA(1) === Token.EOF) { - this.matchedEOF = true; - } - - this._errHandler.reportMatch(this); - this.consume(); - } - } - } 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 integerLiteral(): IntegerLiteralContext { - let _localctx: IntegerLiteralContext = new IntegerLiteralContext(this._ctx, this.state); - this.enterRule(_localctx, 22, WebdaQLParserParser.RULE_integerLiteral); - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 109; - this.match(WebdaQLParserParser.INTEGER_LITERAL); - } - } 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 setExpression(): SetExpressionContext { - let _localctx: SetExpressionContext = new SetExpressionContext(this._ctx, this.state); - this.enterRule(_localctx, 24, WebdaQLParserParser.RULE_setExpression); - let _la: number; - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 111; - this.match(WebdaQLParserParser.LR_SQ_BRACKET); - this.state = 112; - this.values(); - this.state = 117; - this._errHandler.sync(this); - _la = this._input.LA(1); - while (_la === WebdaQLParserParser.COMMA) { - { - { - this.state = 113; - this.match(WebdaQLParserParser.COMMA); - this.state = 114; - this.values(); - } - } - this.state = 119; - this._errHandler.sync(this); - _la = this._input.LA(1); - } - this.state = 120; - this.match(WebdaQLParserParser.RR_SQ_BRACKET); - } - } 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 sempred(_localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { - switch (ruleIndex) { - case 5: - return this.expression_sempred(_localctx as ExpressionContext, predIndex); - } - return true; - } - private expression_sempred(_localctx: ExpressionContext, predIndex: number): boolean { - switch (predIndex) { - case 0: - return this.precpred(this._ctx, 4); - - case 1: - return this.precpred(this._ctx, 3); - } - return true; - } - - public static readonly _serializedATN: string = - '\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03"}\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\x05\x02\x1E\n\x02\x03\x02\x05\x02!\n\x02\x03\x02\x05\x02" + - "$\n\x02\x03\x02\x05\x02'\n\x02\x03\x02\x03\x02\x03\x03\x03\x03\x03\x03" + - "\x03\x04\x03\x04\x03\x04\x03\x05\x03\x05\x05\x053\n\x05\x03\x06\x03\x06" + - "\x03\x06\x03\x06\x07\x069\n\x06\f\x06\x0E\x06<\v\x06\x03\x07\x03\x07\x03" + - "\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03" + - "\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03" + - "\x07\x03\x07\x05\x07T\n\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03" + - "\x07\x07\x07\\\n\x07\f\x07\x0E\x07_\v\x07\x03\b\x03\b\x03\b\x05\bd\n\b" + - "\x03\t\x03\t\x05\th\n\t\x03\n\x03\n\x03\v\x03\v\x03\f\x03\f\x03\r\x03" + - "\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x07\x0Ev\n\x0E\f\x0E\x0E\x0Ey\v\x0E" + - "\x03\x0E\x03\x0E\x03\x0E\x02\x02\x03\f\x0F\x02\x02\x04\x02\x06\x02\b\x02" + - "\n\x02\f\x02\x0E\x02\x10\x02\x12\x02\x14\x02\x16\x02\x18\x02\x1A\x02\x02" + - "\x07\x03\x02\x1B\x1C\x03\x02\r\x12\x03\x02 !\x03\x02\x16\x17\x03\x02\x1D" + - "\x1E\x02\x80\x02\x1D\x03\x02\x02\x02\x04*\x03\x02\x02\x02\x06-\x03\x02" + - "\x02\x02\b0\x03\x02\x02\x02\n4\x03\x02\x02\x02\fS\x03\x02\x02\x02\x0E" + - "c\x03\x02\x02\x02\x10g\x03\x02\x02\x02\x12i\x03\x02\x02\x02\x14k\x03\x02" + - "\x02\x02\x16m\x03\x02\x02\x02\x18o\x03\x02\x02\x02\x1Aq\x03\x02\x02\x02" + - "\x1C\x1E\x05\f\x07\x02\x1D\x1C\x03\x02\x02\x02\x1D\x1E\x03\x02\x02\x02" + - "\x1E \x03\x02\x02\x02\x1F!\x05\n\x06\x02 \x1F\x03\x02\x02\x02 !\x03\x02" + - '\x02\x02!#\x03\x02\x02\x02"$\x05\x04\x03\x02#"\x03\x02\x02\x02#$\x03' + - "\x02\x02\x02$&\x03\x02\x02\x02%'\x05\x06\x04\x02&%\x03\x02\x02\x02&'" + - "\x03\x02\x02\x02'(\x03\x02\x02\x02()\x07\x02\x02\x03)\x03\x03\x02\x02" + - "\x02*+\x07\x18\x02\x02+,\x05\x18\r\x02,\x05\x03\x02\x02\x02-.\x07\x19" + - "\x02\x02./\x05\x16\f\x02/\x07\x03\x02\x02\x0202\x05\x12\n\x0213\t\x02" + - "\x02\x0221\x03\x02\x02\x0223\x03\x02\x02\x023\t\x03\x02\x02\x0245\x07" + - "\x1A\x02\x025:\x05\b\x05\x0267\x07\x06\x02\x0279\x05\b\x05\x0286\x03\x02" + - "\x02\x029<\x03\x02\x02\x02:8\x03\x02\x02\x02:;\x03\x02\x02\x02;\v\x03" + - "\x02\x02\x02<:\x03\x02\x02\x02=>\b\x07\x01\x02>?\x05\x12\n\x02?@\x07\x13" + - "\x02\x02@A\x05\x16\f\x02AT\x03\x02\x02\x02BC\x05\x12\n\x02CD\x07\x14\x02" + - "\x02DE\x05\x1A\x0E\x02ET\x03\x02\x02\x02FG\x05\x12\n\x02GH\x07\x15\x02" + - "\x02HI\x05\x16\f\x02IT\x03\x02\x02\x02JK\x05\x12\n\x02KL\t\x03\x02\x02" + - "LM\x05\x0E\b\x02MT\x03\x02\x02\x02NO\x07\x04\x02\x02OP\x05\f\x07\x02P" + - "Q\x07\x05\x02\x02QT\x03\x02\x02\x02RT\x05\x10\t\x02S=\x03\x02\x02\x02" + - "SB\x03\x02\x02\x02SF\x03\x02\x02\x02SJ\x03\x02\x02\x02SN\x03\x02\x02\x02" + - "SR\x03\x02\x02\x02T]\x03\x02\x02\x02UV\f\x06\x02\x02VW\x07\v\x02\x02W" + - "\\\x05\f\x07\x07XY\f\x05\x02\x02YZ\x07\f\x02\x02Z\\\x05\f\x07\x06[U\x03" + - "\x02\x02\x02[X\x03\x02\x02\x02\\_\x03\x02\x02\x02][\x03\x02\x02\x02]^" + - "\x03\x02\x02\x02^\r\x03\x02\x02\x02_]\x03\x02\x02\x02`d\x05\x14\v\x02" + - "ad\x05\x18\r\x02bd\x05\x16\f\x02c`\x03\x02\x02\x02ca\x03\x02\x02\x02c" + - "b\x03\x02\x02\x02d\x0F\x03\x02\x02\x02eh\x05\x0E\b\x02fh\x05\x12\n\x02" + - "ge\x03\x02\x02\x02gf\x03\x02\x02\x02h\x11\x03\x02\x02\x02ij\t\x04\x02" + - "\x02j\x13\x03\x02\x02\x02kl\t\x05\x02\x02l\x15\x03\x02\x02\x02mn\t\x06" + - "\x02\x02n\x17\x03\x02\x02\x02op\x07\x1F\x02\x02p\x19\x03\x02\x02\x02q" + - "r\x07\t\x02\x02rw\x05\x0E\b\x02st\x07\x06\x02\x02tv\x05\x0E\b\x02us\x03" + - "\x02\x02\x02vy\x03\x02\x02\x02wu\x03\x02\x02\x02wx\x03\x02\x02\x02xz\x03" + - "\x02\x02\x02yw\x03\x02\x02\x02z{\x07\n\x02\x02{\x1B\x03\x02\x02\x02\x0E" + - "\x1D #&2:S[]cgw"; - public static __ATN: ATN; - public static get _ATN(): ATN { - if (!WebdaQLParserParser.__ATN) { - WebdaQLParserParser.__ATN = new ATNDeserializer().deserialize( - Utils.toCharArray(WebdaQLParserParser._serializedATN) - ); - } - - return WebdaQLParserParser.__ATN; - } -} - -export class WebdaqlContext extends ParserRuleContext { - public EOF(): TerminalNode { - return this.getToken(WebdaQLParserParser.EOF, 0); - } - public expression(): ExpressionContext | undefined { - return this.tryGetRuleContext(0, ExpressionContext); - } - public orderExpression(): OrderExpressionContext | undefined { - return this.tryGetRuleContext(0, OrderExpressionContext); - } - public limitExpression(): LimitExpressionContext | undefined { - return this.tryGetRuleContext(0, LimitExpressionContext); - } - public offsetExpression(): OffsetExpressionContext | undefined { - return this.tryGetRuleContext(0, OffsetExpressionContext); - } - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_webdaql; - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterWebdaql) { - listener.enterWebdaql(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitWebdaql) { - listener.exitWebdaql(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitWebdaql) { - return visitor.visitWebdaql(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class LimitExpressionContext extends ParserRuleContext { - public LIMIT(): TerminalNode { - return this.getToken(WebdaQLParserParser.LIMIT, 0); - } - public integerLiteral(): IntegerLiteralContext { - return this.getRuleContext(0, IntegerLiteralContext); - } - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_limitExpression; - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterLimitExpression) { - listener.enterLimitExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitLimitExpression) { - listener.exitLimitExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitLimitExpression) { - return visitor.visitLimitExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class OffsetExpressionContext extends ParserRuleContext { - public OFFSET(): TerminalNode { - return this.getToken(WebdaQLParserParser.OFFSET, 0); - } - public stringLiteral(): StringLiteralContext { - return this.getRuleContext(0, StringLiteralContext); - } - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_offsetExpression; - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterOffsetExpression) { - listener.enterOffsetExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitOffsetExpression) { - listener.exitOffsetExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitOffsetExpression) { - return visitor.visitOffsetExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class OrderFieldExpressionContext extends ParserRuleContext { - public identifier(): IdentifierContext { - return this.getRuleContext(0, IdentifierContext); - } - public ASC(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.ASC, 0); - } - public DESC(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.DESC, 0); - } - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_orderFieldExpression; - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterOrderFieldExpression) { - listener.enterOrderFieldExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitOrderFieldExpression) { - listener.exitOrderFieldExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitOrderFieldExpression) { - return visitor.visitOrderFieldExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class OrderExpressionContext extends ParserRuleContext { - public ORDER_BY(): TerminalNode { - return this.getToken(WebdaQLParserParser.ORDER_BY, 0); - } - public orderFieldExpression(): OrderFieldExpressionContext[]; - public orderFieldExpression(i: number): OrderFieldExpressionContext; - public orderFieldExpression(i?: number): OrderFieldExpressionContext | OrderFieldExpressionContext[] { - if (i === undefined) { - return this.getRuleContexts(OrderFieldExpressionContext); - } else { - return this.getRuleContext(i, OrderFieldExpressionContext); - } - } - public COMMA(): TerminalNode[]; - public COMMA(i: number): TerminalNode; - public COMMA(i?: number): TerminalNode | TerminalNode[] { - if (i === undefined) { - return this.getTokens(WebdaQLParserParser.COMMA); - } else { - return this.getToken(WebdaQLParserParser.COMMA, i); - } - } - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_orderExpression; - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterOrderExpression) { - listener.enterOrderExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitOrderExpression) { - listener.exitOrderExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitOrderExpression) { - return visitor.visitOrderExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class ExpressionContext extends ParserRuleContext { - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_expression; - } - public copyFrom(ctx: ExpressionContext): void { - super.copyFrom(ctx); - } -} -export class LikeExpressionContext extends ExpressionContext { - public identifier(): IdentifierContext { - return this.getRuleContext(0, IdentifierContext); - } - public LIKE(): TerminalNode { - return this.getToken(WebdaQLParserParser.LIKE, 0); - } - public stringLiteral(): StringLiteralContext { - return this.getRuleContext(0, StringLiteralContext); - } - constructor(ctx: ExpressionContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterLikeExpression) { - listener.enterLikeExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitLikeExpression) { - listener.exitLikeExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitLikeExpression) { - return visitor.visitLikeExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} -export class InExpressionContext extends ExpressionContext { - public identifier(): IdentifierContext { - return this.getRuleContext(0, IdentifierContext); - } - public IN(): TerminalNode { - return this.getToken(WebdaQLParserParser.IN, 0); - } - public setExpression(): SetExpressionContext { - return this.getRuleContext(0, SetExpressionContext); - } - constructor(ctx: ExpressionContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterInExpression) { - listener.enterInExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitInExpression) { - listener.exitInExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitInExpression) { - return visitor.visitInExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} -export class ContainsExpressionContext extends ExpressionContext { - public identifier(): IdentifierContext { - return this.getRuleContext(0, IdentifierContext); - } - public CONTAINS(): TerminalNode { - return this.getToken(WebdaQLParserParser.CONTAINS, 0); - } - public stringLiteral(): StringLiteralContext { - return this.getRuleContext(0, StringLiteralContext); - } - constructor(ctx: ExpressionContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterContainsExpression) { - listener.enterContainsExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitContainsExpression) { - listener.exitContainsExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitContainsExpression) { - return visitor.visitContainsExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} -export class BinaryComparisonExpressionContext extends ExpressionContext { - public identifier(): IdentifierContext { - return this.getRuleContext(0, IdentifierContext); - } - public values(): ValuesContext { - return this.getRuleContext(0, ValuesContext); - } - public EQUAL(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.EQUAL, 0); - } - public NOT_EQUAL(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.NOT_EQUAL, 0); - } - public GREATER_OR_EQUAL(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.GREATER_OR_EQUAL, 0); - } - public LESS_OR_EQUAL(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.LESS_OR_EQUAL, 0); - } - public LESS(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.LESS, 0); - } - public GREATER(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.GREATER, 0); - } - constructor(ctx: ExpressionContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterBinaryComparisonExpression) { - listener.enterBinaryComparisonExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitBinaryComparisonExpression) { - listener.exitBinaryComparisonExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitBinaryComparisonExpression) { - return visitor.visitBinaryComparisonExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} -export class AndLogicExpressionContext extends ExpressionContext { - public expression(): ExpressionContext[]; - public expression(i: number): ExpressionContext; - public expression(i?: number): ExpressionContext | ExpressionContext[] { - if (i === undefined) { - return this.getRuleContexts(ExpressionContext); - } else { - return this.getRuleContext(i, ExpressionContext); - } - } - public AND(): TerminalNode { - return this.getToken(WebdaQLParserParser.AND, 0); - } - constructor(ctx: ExpressionContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterAndLogicExpression) { - listener.enterAndLogicExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitAndLogicExpression) { - listener.exitAndLogicExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitAndLogicExpression) { - return visitor.visitAndLogicExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} -export class OrLogicExpressionContext extends ExpressionContext { - public expression(): ExpressionContext[]; - public expression(i: number): ExpressionContext; - public expression(i?: number): ExpressionContext | ExpressionContext[] { - if (i === undefined) { - return this.getRuleContexts(ExpressionContext); - } else { - return this.getRuleContext(i, ExpressionContext); - } - } - public OR(): TerminalNode { - return this.getToken(WebdaQLParserParser.OR, 0); - } - constructor(ctx: ExpressionContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterOrLogicExpression) { - listener.enterOrLogicExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitOrLogicExpression) { - listener.exitOrLogicExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitOrLogicExpression) { - return visitor.visitOrLogicExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} -export class SubExpressionContext extends ExpressionContext { - public LR_BRACKET(): TerminalNode { - return this.getToken(WebdaQLParserParser.LR_BRACKET, 0); - } - public expression(): ExpressionContext { - return this.getRuleContext(0, ExpressionContext); - } - public RR_BRACKET(): TerminalNode { - return this.getToken(WebdaQLParserParser.RR_BRACKET, 0); - } - constructor(ctx: ExpressionContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterSubExpression) { - listener.enterSubExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitSubExpression) { - listener.exitSubExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitSubExpression) { - return visitor.visitSubExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} -export class AtomExpressionContext extends ExpressionContext { - public atom(): AtomContext { - return this.getRuleContext(0, AtomContext); - } - constructor(ctx: ExpressionContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterAtomExpression) { - listener.enterAtomExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitAtomExpression) { - listener.exitAtomExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitAtomExpression) { - return visitor.visitAtomExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class ValuesContext extends ParserRuleContext { - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_values; - } - public copyFrom(ctx: ValuesContext): void { - super.copyFrom(ctx); - } -} -export class BooleanAtomContext extends ValuesContext { - public booleanLiteral(): BooleanLiteralContext { - return this.getRuleContext(0, BooleanLiteralContext); - } - constructor(ctx: ValuesContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterBooleanAtom) { - listener.enterBooleanAtom(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitBooleanAtom) { - listener.exitBooleanAtom(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitBooleanAtom) { - return visitor.visitBooleanAtom(this); - } else { - return visitor.visitChildren(this); - } - } -} -export class IntegerAtomContext extends ValuesContext { - public integerLiteral(): IntegerLiteralContext { - return this.getRuleContext(0, IntegerLiteralContext); - } - constructor(ctx: ValuesContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterIntegerAtom) { - listener.enterIntegerAtom(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitIntegerAtom) { - listener.exitIntegerAtom(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitIntegerAtom) { - return visitor.visitIntegerAtom(this); - } else { - return visitor.visitChildren(this); - } - } -} -export class StringAtomContext extends ValuesContext { - public stringLiteral(): StringLiteralContext { - return this.getRuleContext(0, StringLiteralContext); - } - constructor(ctx: ValuesContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterStringAtom) { - listener.enterStringAtom(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitStringAtom) { - listener.exitStringAtom(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitStringAtom) { - return visitor.visitStringAtom(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class AtomContext extends ParserRuleContext { - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_atom; - } - public copyFrom(ctx: AtomContext): void { - super.copyFrom(ctx); - } -} -export class ValuesAtomContext extends AtomContext { - public values(): ValuesContext { - return this.getRuleContext(0, ValuesContext); - } - constructor(ctx: AtomContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterValuesAtom) { - listener.enterValuesAtom(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitValuesAtom) { - listener.exitValuesAtom(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitValuesAtom) { - return visitor.visitValuesAtom(this); - } else { - return visitor.visitChildren(this); - } - } -} -export class IdentifierAtomContext extends AtomContext { - public identifier(): IdentifierContext { - return this.getRuleContext(0, IdentifierContext); - } - constructor(ctx: AtomContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterIdentifierAtom) { - listener.enterIdentifierAtom(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitIdentifierAtom) { - listener.exitIdentifierAtom(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitIdentifierAtom) { - return visitor.visitIdentifierAtom(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class IdentifierContext extends ParserRuleContext { - public IDENTIFIER(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.IDENTIFIER, 0); - } - public IDENTIFIER_WITH_NUMBER(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.IDENTIFIER_WITH_NUMBER, 0); - } - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_identifier; - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterIdentifier) { - listener.enterIdentifier(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitIdentifier) { - listener.exitIdentifier(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitIdentifier) { - return visitor.visitIdentifier(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class BooleanLiteralContext extends ParserRuleContext { - public TRUE(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.TRUE, 0); - } - public FALSE(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.FALSE, 0); - } - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_booleanLiteral; - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterBooleanLiteral) { - listener.enterBooleanLiteral(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitBooleanLiteral) { - listener.exitBooleanLiteral(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitBooleanLiteral) { - return visitor.visitBooleanLiteral(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class StringLiteralContext extends ParserRuleContext { - public DQUOTED_STRING_LITERAL(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.DQUOTED_STRING_LITERAL, 0); - } - public SQUOTED_STRING_LITERAL(): TerminalNode | undefined { - return this.tryGetToken(WebdaQLParserParser.SQUOTED_STRING_LITERAL, 0); - } - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_stringLiteral; - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterStringLiteral) { - listener.enterStringLiteral(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitStringLiteral) { - listener.exitStringLiteral(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitStringLiteral) { - return visitor.visitStringLiteral(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class IntegerLiteralContext extends ParserRuleContext { - public INTEGER_LITERAL(): TerminalNode { - return this.getToken(WebdaQLParserParser.INTEGER_LITERAL, 0); - } - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_integerLiteral; - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterIntegerLiteral) { - listener.enterIntegerLiteral(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitIntegerLiteral) { - listener.exitIntegerLiteral(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitIntegerLiteral) { - return visitor.visitIntegerLiteral(this); - } else { - return visitor.visitChildren(this); - } - } -} - -export class SetExpressionContext extends ParserRuleContext { - public LR_SQ_BRACKET(): TerminalNode { - return this.getToken(WebdaQLParserParser.LR_SQ_BRACKET, 0); - } - public values(): ValuesContext[]; - public values(i: number): ValuesContext; - public values(i?: number): ValuesContext | ValuesContext[] { - if (i === undefined) { - return this.getRuleContexts(ValuesContext); - } else { - return this.getRuleContext(i, ValuesContext); - } - } - public RR_SQ_BRACKET(): TerminalNode { - return this.getToken(WebdaQLParserParser.RR_SQ_BRACKET, 0); - } - public COMMA(): TerminalNode[]; - public COMMA(i: number): TerminalNode; - public COMMA(i?: number): TerminalNode | TerminalNode[] { - if (i === undefined) { - return this.getTokens(WebdaQLParserParser.COMMA); - } else { - return this.getToken(WebdaQLParserParser.COMMA, i); - } - } - constructor(parent: ParserRuleContext | undefined, invokingState: number) { - super(parent, invokingState); - } - // @Override - public get ruleIndex(): number { - return WebdaQLParserParser.RULE_setExpression; - } - // @Override - public enterRule(listener: WebdaQLParserListener): void { - if (listener.enterSetExpression) { - listener.enterSetExpression(this); - } - } - // @Override - public exitRule(listener: WebdaQLParserListener): void { - if (listener.exitSetExpression) { - listener.exitSetExpression(this); - } - } - // @Override - public accept(visitor: WebdaQLParserVisitor): Result { - if (visitor.visitSetExpression) { - return visitor.visitSetExpression(this); - } else { - return visitor.visitChildren(this); - } - } -} diff --git a/packages/core/src/stores/webdaql/WebdaQLParserVisitor.ts b/packages/core/src/stores/webdaql/WebdaQLParserVisitor.ts deleted file mode 100644 index d41392d8b..000000000 --- a/packages/core/src/stores/webdaql/WebdaQLParserVisitor.ts +++ /dev/null @@ -1,237 +0,0 @@ -// Generated from src/stores/webdaql/WebdaQLParser.g4 by ANTLR 4.9.0-SNAPSHOT - -import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor"; - -import { - AndLogicExpressionContext, - AtomContext, - AtomExpressionContext, - BinaryComparisonExpressionContext, - BooleanAtomContext, - BooleanLiteralContext, - ContainsExpressionContext, - ExpressionContext, - IdentifierAtomContext, - IdentifierContext, - InExpressionContext, - IntegerAtomContext, - IntegerLiteralContext, - LikeExpressionContext, - LimitExpressionContext, - OffsetExpressionContext, - OrderExpressionContext, - OrderFieldExpressionContext, - OrLogicExpressionContext, - SetExpressionContext, - StringAtomContext, - StringLiteralContext, - SubExpressionContext, - ValuesAtomContext, - ValuesContext, - WebdaqlContext -} from "./WebdaQLParserParser"; - -/** - * This interface defines a complete generic visitor for a parse tree produced - * by `WebdaQLParserParser`. - * - * @param The return type of the visit operation. Use `void` for - * operations with no return type. - */ -export interface WebdaQLParserVisitor extends ParseTreeVisitor { - /** - * Visit a parse tree produced by the `likeExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitLikeExpression?: (ctx: LikeExpressionContext) => Result; - - /** - * Visit a parse tree produced by the `inExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitInExpression?: (ctx: InExpressionContext) => Result; - - /** - * Visit a parse tree produced by the `containsExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitContainsExpression?: (ctx: ContainsExpressionContext) => Result; - - /** - * Visit a parse tree produced by the `binaryComparisonExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitBinaryComparisonExpression?: (ctx: BinaryComparisonExpressionContext) => Result; - - /** - * Visit a parse tree produced by the `andLogicExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitAndLogicExpression?: (ctx: AndLogicExpressionContext) => Result; - - /** - * Visit a parse tree produced by the `orLogicExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitOrLogicExpression?: (ctx: OrLogicExpressionContext) => Result; - - /** - * Visit a parse tree produced by the `subExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitSubExpression?: (ctx: SubExpressionContext) => Result; - - /** - * Visit a parse tree produced by the `atomExpression` - * labeled alternative in `WebdaQLParserParser.expression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitAtomExpression?: (ctx: AtomExpressionContext) => Result; - - /** - * Visit a parse tree produced by the `booleanAtom` - * labeled alternative in `WebdaQLParserParser.values`. - * @param ctx the parse tree - * @return the visitor result - */ - visitBooleanAtom?: (ctx: BooleanAtomContext) => Result; - - /** - * Visit a parse tree produced by the `integerAtom` - * labeled alternative in `WebdaQLParserParser.values`. - * @param ctx the parse tree - * @return the visitor result - */ - visitIntegerAtom?: (ctx: IntegerAtomContext) => Result; - - /** - * Visit a parse tree produced by the `stringAtom` - * labeled alternative in `WebdaQLParserParser.values`. - * @param ctx the parse tree - * @return the visitor result - */ - visitStringAtom?: (ctx: StringAtomContext) => Result; - - /** - * Visit a parse tree produced by the `valuesAtom` - * labeled alternative in `WebdaQLParserParser.atom`. - * @param ctx the parse tree - * @return the visitor result - */ - visitValuesAtom?: (ctx: ValuesAtomContext) => Result; - - /** - * Visit a parse tree produced by the `identifierAtom` - * labeled alternative in `WebdaQLParserParser.atom`. - * @param ctx the parse tree - * @return the visitor result - */ - visitIdentifierAtom?: (ctx: IdentifierAtomContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.webdaql`. - * @param ctx the parse tree - * @return the visitor result - */ - visitWebdaql?: (ctx: WebdaqlContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.limitExpression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitLimitExpression?: (ctx: LimitExpressionContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.offsetExpression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitOffsetExpression?: (ctx: OffsetExpressionContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.orderFieldExpression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitOrderFieldExpression?: (ctx: OrderFieldExpressionContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.orderExpression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitOrderExpression?: (ctx: OrderExpressionContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.expression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitExpression?: (ctx: ExpressionContext) => Result; - - /** - * Visit a parse tree produced by the `values` - * labeled alternative in `WebdaQLParserParser.expressionexpressionexpressionexpressionexpressionexpressionexpressionexpressionvaluesvaluesvaluesatomatom`. - * @param ctx the parse tree - * @return the visitor result - */ - visitValues?: (ctx: ValuesContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.atom`. - * @param ctx the parse tree - * @return the visitor result - */ - visitAtom?: (ctx: AtomContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.identifier`. - * @param ctx the parse tree - * @return the visitor result - */ - visitIdentifier?: (ctx: IdentifierContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.booleanLiteral`. - * @param ctx the parse tree - * @return the visitor result - */ - visitBooleanLiteral?: (ctx: BooleanLiteralContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.stringLiteral`. - * @param ctx the parse tree - * @return the visitor result - */ - visitStringLiteral?: (ctx: StringLiteralContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.integerLiteral`. - * @param ctx the parse tree - * @return the visitor result - */ - visitIntegerLiteral?: (ctx: IntegerLiteralContext) => Result; - - /** - * Visit a parse tree produced by `WebdaQLParserParser.setExpression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitSetExpression?: (ctx: SetExpressionContext) => Result; -} diff --git a/packages/core/src/stores/webdaql/query.spec.ts b/packages/core/src/stores/webdaql/query.spec.ts deleted file mode 100644 index cabb75716..000000000 --- a/packages/core/src/stores/webdaql/query.spec.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { suite, test } from "@testdeck/mocha"; -import * as assert from "assert"; -import { WebdaQL } from "./query"; - -const targets = [ - { - test: { - attr1: "plop" - }, - attr2: "OK", - attr3: 13, - attr4: "ok", - attr5: ["test", "plip"] - } -]; - -@suite -class QueryTest { - @test - dev() { - let queryMap = { - "a = 1 AND b = 2 AND c = 3 AND d = 4": "a = 1 AND b = 2 AND c = 3 AND d = 4", - "a = 1 AND (b = 2 AND c = 3) AND d = 4": "a = 1 AND b = 2 AND c = 3 AND d = 4", - "a = 1 AND ((b = 2 AND c = 3) AND d = 4)": "a = 1 AND b = 2 AND c = 3 AND d = 4", - "a = 1 AND ((b = 2 OR c = 3) AND d = 4)": "a = 1 AND ( b = 2 OR c = 3 ) AND d = 4", - "a = 1 AND (b = 2) AND c = 3 AND d = 4": "a = 1 AND b = 2 AND c = 3 AND d = 4", - "a = 1 OR b = 2 OR c = 3 OR d = 4": "a = 1 OR b = 2 OR c = 3 OR d = 4", - 'a = 1 AND (b = 2 OR c = 3) AND d = "plop"': 'a = 1 AND ( b = 2 OR c = 3 ) AND d = "plop"', - "a = 1 OR b = TRUE AND c = FALSE OR d = 'plop'": 'a = 1 OR ( b = TRUE AND c = FALSE ) OR d = "plop"', - "test.attr1 = 'plop'": true, - "test.attr1 = 'plop2'": false, - "test.attr1 = 'plop' AND attr2 IN ['TEST', TRUE, 3]": 'test.attr1 = "plop" AND attr2 IN ["TEST", TRUE, 3]', - "(test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK']) OR attr3 <= 12": null, - "test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK'] OR attr3 <= 12 AND attr4 = 'ok'": null, - "test.attr1 = 'plop' AND (attr2 IN ['TEST', 'OK'] OR attr3 <= 12) AND attr4 = 'ok'": null, - "test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK'] AND attr3 <= 12 AND attr4 = 'ok'": null, - "a = 1 OR b = 2": null, - "a = 1 AND b = 2 OR c = 3 AND d = 4": null, - "a = 1 AND b = 2 OR c = 3 AND d = 4 OR e = 5 AND f = 6": null, - "a = 1 AND (b = 2 OR (c = 3 AND (d = 4 OR e = 5))) AND f = 6": null, - "": true, - 'test.attr1 LIKE "pl_p"': true, - 'test.attr1 LIKE "pl__p"': false, - 'attr3 LIKE "1_"': true, - "attr3 >= 12": true, - "attr3 <= 12": false, - "attr3 != 12": true, - "attr3 > 12": true, - "attr3 < 13": false, - "attr5 CONTAINS 'test'": true, - "attr5 CONTAINS 'test2'": false, - 'test.attr1 LIKE "pl%"': true, - "a = 1 AND b=2 OR a=1 AND b=3": "( a = 1 AND b = 2 ) OR ( a = 1 AND b = 3 )", // TODO Might want to auto-simplify to a = 1 AND b IN [2,3] - "a = 1 ORDER BY a": null, - "a = 1 ORDER BY a DESC, b ASC": null, - "(attr3 >= 12)": true - }; - for (let query in queryMap) { - const validator = new WebdaQL.QueryValidator(query); - assert.strictEqual(validator.displayTree().replace(/\s/g, ""), query.replace(/\s/g, "")); - if (typeof queryMap[query] === "string") { - assert.strictEqual( - validator.getExpression().toString(), - queryMap[query], - `Failed optimization of query: ${query} => ${validator.getExpression().toString()}` - ); - } else if (typeof queryMap[query] === "boolean") { - assert.strictEqual(validator.eval(targets[0]), queryMap[query], `Failed query: ${query}`); - } else { - console.log("QUERY", query); - console.log("OPTIMIZED QUERY", validator.getExpression().toString(), "=>", validator.eval(targets[0])); - } - } - } - - @test - prependQuery() { - assert.strictEqual(WebdaQL.PrependCondition("", "test='plop'"), "test='plop'"); - assert.strictEqual(WebdaQL.PrependCondition("test='plop'", ""), "test='plop'"); - assert.strictEqual(WebdaQL.PrependCondition("test='plip'", "test='plop'"), "(test='plop') AND (test='plip')"); - assert.strictEqual(WebdaQL.PrependCondition("ORDER BY test", "test='plop'"), "test='plop' ORDER BY test"); - assert.strictEqual( - WebdaQL.PrependCondition("ORDER BY test LIMIT 100", "test='plop'"), - "test='plop' ORDER BY test LIMIT 100" - ); - assert.strictEqual( - WebdaQL.PrependCondition("ORDER BY test DESC LIMIT 100", "test='plop'"), - "test='plop' ORDER BY test DESC LIMIT 100" - ); - assert.strictEqual( - WebdaQL.PrependCondition("test='plip' ORDER BY test ASC LIMIT 100", "test='plop'"), - "(test='plop') AND (test='plip') ORDER BY test ASC LIMIT 100" - ); - assert.strictEqual( - WebdaQL.PrependCondition("test='plip' LIMIT 100", "test='plop'"), - "(test='plop') AND (test='plip') LIMIT 100" - ); - assert.strictEqual( - WebdaQL.PrependCondition("test='plip' OFFSET 'test'", "test='plop'"), - "(test='plop') AND (test='plip') OFFSET 'test'" - ); - assert.strictEqual( - WebdaQL.PrependCondition("test='ORDER BY plop' OFFSET 'test'", "test='plop'"), - "(test='plop') AND (test='ORDER BY plop') OFFSET 'test'" - ); - assert.strictEqual( - WebdaQL.PrependCondition('test="ORDER BY plop" LIMIT 100', "test='plop'"), - "(test='plop') AND (test=\"ORDER BY plop\") LIMIT 100" - ); - } - - @test - likeRegexp() { - assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("test"), /test/); - assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("t_est"), /t.{1}est/); - assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("_test"), /.{1}test/); - assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("t\\_est"), /t_est/); - - assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("t%est"), /t.*est/); - assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("%te?st"), /.*te\?st/); - assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("t\\%est"), /t%est/); - assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("t\\eest"), /t\\est/); - } - - @test - simple() { - assert.strictEqual(new WebdaQL.QueryValidator("test.attr1 = 'plop'").eval(targets[0]), true); - assert.strictEqual(new WebdaQL.QueryValidator("test.attr1 = 'plop2'").eval(targets[0]), false); - } - - @test - contains() { - assert.strictEqual(new WebdaQL.QueryValidator("test.attr1 CONTAINS 'plop2'").eval(targets[0]), false); - } - - @test - andQuery() { - assert.strictEqual( - new WebdaQL.QueryValidator("test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK']").eval(targets[0]), - true - ); - } - - @test - orQuery() { - new WebdaQL.QueryValidator("(test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK']) OR attr3 <= 12").eval(targets[0]); - } - - @test - multipleQuery() { - new WebdaQL.QueryValidator("test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK'] OR attr3 <= 12 AND attr4 = 'ok'").eval( - targets[0] - ); - new WebdaQL.QueryValidator("test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK'] AND attr3 <= 12 AND attr4 = 'ok'").eval( - targets[0] - ); - } - - @test - limitOffset() { - let val = new WebdaQL.QueryValidator('LIMIT 10 OFFSET "pl"'); - assert.strictEqual(val.getLimit(), 10); - assert.strictEqual(val.getOffset(), "pl"); - val = new WebdaQL.QueryValidator('OFFSET "pl2"'); - assert.strictEqual(val.getLimit(), 1000); - assert.strictEqual(val.getOffset(), "pl2"); - } - - @test - badQuery() { - assert.throws(() => new WebdaQL.QueryValidator("test lo 'plop'").eval(targets[0])); - } - - @test - setters() { - let target: any = {}; - new WebdaQL.SetterValidator('i = 10 AND j = "12"').eval(target); - assert.strictEqual(target.i, 10); - assert.strictEqual(target.j, "12"); - target = {}; - new WebdaQL.SetterValidator("").eval(target); - assert.strictEqual(Object.keys(target).length, 0); - new WebdaQL.SetterValidator('test.i = 10 AND j.k.l = "12"').eval(target); - assert.strictEqual(target.test.i, 10); - assert.strictEqual(target.j.k.l, "12"); - target = {}; - new WebdaQL.SetterValidator("test.__proto__.test = 10").eval(target); - assert.strictEqual(target.__proto__.test, undefined); - assert.throws(() => new WebdaQL.SetterValidator('i = 10 OR j = "12"').eval(target), SyntaxError); - assert.throws(() => new WebdaQL.SetterValidator('i > 10 AND j = "12"').eval(target), SyntaxError); - } - - @test - partialValidator() { - let validator = new WebdaQL.PartialValidator("attr1 = 'plop' AND attr2 = 'ok'"); - assert.ok(validator.eval({ attr1: "plop" })); - assert.ok(validator.wasPartialMatch()); - assert.ok(!validator.eval({ attr1: "plop" }, false)); - assert.ok(!validator.eval({ attr1: "plop2" })); - assert.ok( - new WebdaQL.PartialValidator( - "attr1 = 'plop' AND attr2 LIKE '?ok' AND attr3 IN ['test'] AND attr4 CONTAINS 'plop'" - ).eval({ attr1: "plop" }) - ); - } -} diff --git a/packages/core/src/stores/webdaql/query.ts b/packages/core/src/stores/webdaql/query.ts deleted file mode 100644 index 8b5b671db..000000000 --- a/packages/core/src/stores/webdaql/query.ts +++ /dev/null @@ -1,820 +0,0 @@ -import { CharStreams, CommonTokenStream, RecognitionException, Recognizer, Token } from "antlr4ts"; -import { AbstractParseTreeVisitor, ParseTree, TerminalNode } from "antlr4ts/tree/index.js"; -import { WebdaQLLexer } from "./WebdaQLLexer"; -import { - AndLogicExpressionContext, - BinaryComparisonExpressionContext, - BooleanLiteralContext, - ContainsExpressionContext, - InExpressionContext, - IntegerLiteralContext, - LikeExpressionContext, - LimitExpressionContext, - OffsetExpressionContext, - OrLogicExpressionContext, - OrderExpressionContext, - OrderFieldExpressionContext, - SetExpressionContext, - StringLiteralContext, - SubExpressionContext, - WebdaQLParserParser, - WebdaqlContext -} from "./WebdaQLParserParser"; -import { WebdaQLParserVisitor } from "./WebdaQLParserVisitor"; - -type value = boolean | string | number; - -/** - * Meta Query Language - * - * - */ -export namespace WebdaQL { - export interface OrderBy { - field: string; - direction: "ASC" | "DESC"; - } - - export function PrependCondition(query: string = "", condition?: string): string { - if (!condition) { - return query; - } - query = query.trim(); - // Find the right part of the query this regex will always be true as all part are optional - const rightPart = query.match( - /(?ORDER BY ([a-zA-Z0-9\._]+( DESC| ASC)?,?)+ ?)?(?OFFSET ["']\w+["'] ?)?(?LIMIT \d+)?$/ - ); - // Remove it if found - query = query.substring(0, query.length - rightPart[0].length).trim(); - // Add the condition to the query now - if (!query) { - query = condition; - } else { - query = `(${condition}) AND (${query})`; - } - // Re-add the right part - if (rightPart[0].length) { - query += " " + rightPart[0]; - } - return query; - } - /** - * Create Expression based on the parsed token - * - * Expression allow to optimize and split between Query and Filter - */ - export class ExpressionBuilder extends AbstractParseTreeVisitor implements WebdaQLParserVisitor { - /** - * Contain the parsed limit - */ - limit: number; - /** - * Contain the parsed offset - */ - offset: string; - orderBy: OrderBy[]; - - /** - * Default result for the override - * @returns - */ - protected defaultResult(): Query { - // An empty AND return true - return { - filter: new AndExpression([]) - }; - } - - /** - * Get offset - * @returns - */ - getOffset(): string { - return this.offset; - } - - /** - * Get limit - * @returns - */ - getLimit(): number { - return this.limit; - } - - /** - * Read the limit - * @param ctx - */ - visitLimitExpression(ctx: LimitExpressionContext) { - this.limit = this.visitIntegerLiteral(ctx.getChild(1)); - } - - /** - * Read the offset if provided - * @param ctx - */ - visitOffsetExpression(ctx: OffsetExpressionContext) { - this.offset = this.visitStringLiteral(ctx.getChild(1)); - } - - /** - * Visit a order field expression - */ - visitOrderFieldExpression(ctx: OrderFieldExpressionContext): OrderBy { - return { - field: ctx.getChild(0).text, - direction: ctx.childCount > 1 ? ctx.getChild(1).text : "ASC" - }; - } - - /** - * Read the order by values - */ - visitOrderExpression(ctx: OrderExpressionContext): void { - this.orderBy = ctx.children - ?.filter(c => c instanceof OrderFieldExpressionContext) - .map((c: OrderFieldExpressionContext) => this.visitOrderFieldExpression(c)); - } - - /** - * Return only AndExpression - * @param ctx - * @returns - */ - visitWebdaql(ctx: WebdaqlContext): Query { - if (ctx.childCount === 1) { - // An empty AND return true - return { - filter: new AndExpression([]) - }; - } - - // To parse offset and limit and order by - for (let i = 1; i < ctx.childCount - 1; i++) { - this.visit(ctx.getChild(i)); - } - // If the first element is a sub expression, it means we have a filter - if (ctx.getChild(0) instanceof SubExpressionContext) { - return { - filter: (this.visit(ctx.getChild(0).getChild(1))) || new AndExpression([]), - limit: this.limit, - continuationToken: this.offset, - orderBy: this.orderBy - }; - } - // Go down one level - if expression empty it means no expression were provided - return { - filter: (this.visit(ctx.getChild(0))) || new AndExpression([]), - limit: this.limit, - continuationToken: this.offset, - orderBy: this.orderBy - }; - } - - /** - * Simplify Logic expression and regroup them - * @param ctx - * @returns - */ - getComparison(ctx: AndLogicExpressionContext | OrLogicExpressionContext): any[] { - const res = []; - let [left, _, right] = ctx.children; - if (right instanceof SubExpressionContext) { - right = right.getChild(1); - } - if (left instanceof SubExpressionContext) { - left = left.getChild(1); - } - if (left instanceof ctx.constructor) { - res.push(...this.getComparison((left))); - } else { - res.push(left); - } - if (right instanceof ctx.constructor) { - res.push(...this.getComparison((right))); - } else { - res.push(right); - } - return res; - } - - /** - * Get the AndExpression, regrouping all the parameters - * - * By default the parser is doing a AND (b AND (c AND d)) creating 3 depth expressions - * This visitor simplify to a AND b AND c AND d with only one Expression - */ - visitAndLogicExpression(ctx: AndLogicExpressionContext): AndExpression { - return new AndExpression(this.getComparison(ctx).map(c => (this.visit(c)))); - } - - /** - * Implement the BinaryComparison with all methods managed - */ - visitBinaryComparisonExpression(ctx: BinaryComparisonExpressionContext) { - const [left, op, right] = ctx.children; - // @ts-ignore - return new ComparisonExpression(op.text, left.text, this.visit(right)); - } - - /** - * Visit each value of the [..., ..., ...] set - */ - visitSetExpression(ctx: SetExpressionContext): value[] { - return (ctx.children.filter((_i, id) => id % 2).map(c => this.visit(c))); - } - - /** - * a LIKE "%A?" - * @param ctx - * @returns - */ - visitLikeExpression(ctx: LikeExpressionContext) { - const [left, _, right] = ctx.children; - let value = (this.visit(right)); - return new ComparisonExpression("LIKE", left.text, value); - } - - /** - * Map the a IN ['b','c'] - */ - visitInExpression(ctx: InExpressionContext) { - const [left, _, right] = ctx.children; - let value = (this.visit(right)); - return new ComparisonExpression("IN", left.text, value); - } - - /** - * Map the a CONTAINS 'b' - */ - visitContainsExpression(ctx: ContainsExpressionContext) { - const [left, _, right] = ctx.children; - let value = (this.visit(right)); - return new ComparisonExpression("CONTAINS", left.text, value); - } - - /** - * Get the OrExpression, regrouping all the parameters - * - * By default the parser is doing a OR (b OR (c OR d)) creating 3 depth expressions - * This visitor simplify to a OR b OR c OR d with only one Expression - */ - visitOrLogicExpression(ctx: OrLogicExpressionContext) { - return new OrExpression(this.getComparison(ctx).map(c => (this.visit(c)))); - } - - /** - * Read the string literal (removing the simple or double bracket) - */ - visitStringLiteral(ctx: StringLiteralContext): string { - return ctx.text.substring(1, ctx.text.length - 1); - } - - /** - * Read the boolean literal - */ - visitBooleanLiteral(ctx: BooleanLiteralContext): boolean { - return "TRUE" === ctx.text; - } - - /** - * Read the number literal - */ - visitIntegerLiteral(ctx: IntegerLiteralContext): number { - return parseInt(ctx.text); - } - } - - /** - * Represent a full Query - */ - export interface Query { - /** - * Filtering part of the expression - */ - filter: Expression; - /** - * Limit value - */ - limit?: number; - /** - * Offset value - */ - continuationToken?: string; - /** - * Order by clause - */ - orderBy?: OrderBy[]; - /** - * Get the string representation of the query - */ - toString(): string; - } - - /** - * Represent the query expression or subset - */ - export abstract class Expression { - operator: T; - - constructor(operator: T) { - this.operator = operator; - } - - /** - * Evaluate the expression for the target object - * @param target to evaluate - */ - abstract eval(target: any): boolean; - /** - * Return the representation of the expression - * @param depth - */ - abstract toString(depth?: number): string; - } - - export type ComparisonOperator = "=" | "<=" | ">=" | "<" | ">" | "!=" | "LIKE" | "IN" | "CONTAINS"; - /** - * Comparison expression - */ - export class ComparisonExpression extends Expression { - /** - * Right side of the comparison - */ - value: value | value[]; - /** - * Attribute to read from the object (split by .) - */ - attribute: string[]; - /** - * - * @param operator of the expression - * @param attribute of the object to read - * @param value - */ - constructor(operator: T, attribute: string, value: value | any[]) { - super(operator); - this.value = value; - this.attribute = attribute.split("."); - } - - static likeToRegex(like: string): RegExp { - return new RegExp( - like - // Prevent common regexp chars - .replace(/\?/g, "\\?") - .replace(/\[/g, "\\[") - .replace(/\{/g, "\\{") - .replace(/\(/g, "\\(") - // Update % and _ to match regex version - .replace(/([^\\])_/g, "$1.{1}") - .replace(/^_/g, ".{1}") - .replace(/\\_/g, "_") - .replace(/([^\\])%/g, "$1.*") - .replace(/^%/g, ".*") - .replace(/\\%/g, "%") - // Replace backslash aswell - .replace(/\\([^?[{(])/g, "\\\\") - ); - } - - /** - * Read the value from the object - * - * @param target - * @returns - */ - static getAttributeValue(target: any, attribute: string[]): any { - let res = target; - for (let i = 0; res && i < attribute.length; i++) { - res = res[attribute[i]]; - } - return res; - } - - /** - * Set the value of the attribute based on the assignment - * - * If used as a Set expression - * @param target - */ - setAttributeValue(target: any) { - // Avoid alteration of prototype for security reason - if (this.attribute.includes("__proto__")) { - return; - } - if (this.operator === "=") { - let res = target; - for (let i = 0; res && i < this.attribute.length - 1; i++) { - res[this.attribute[i]] ??= {}; - res = res[this.attribute[i]]; - } - res[this.attribute[this.attribute.length - 1]] = this.value; - } - } - /** - * @override - */ - eval(target: any): boolean { - const left = ComparisonExpression.getAttributeValue(target, this.attribute); - switch (this.operator) { - case "=": - // ignore strong type on purpose - return left == this.value; - case "<=": - return left <= this.value; - case ">=": - return left >= this.value; - case "<": - return left < this.value; - case ">": - return left > this.value; - case "!=": - return left != this.value; - case "LIKE": - if (typeof left === "string") { - // Grammar definie value as stringLiteral - return left.match(ComparisonExpression.likeToRegex(this.value)) !== null; - } - return left.toString().match(ComparisonExpression.likeToRegex(this.value)) !== null; - case "IN": - return (this.value).includes(left); - case "CONTAINS": - if (Array.isArray(left)) { - return left.includes(this.value); - } - return false; - } - } - - /** - * Return a string represantation of a value - */ - toStringValue(value: value | value[]): string { - if (Array.isArray(value)) { - return `[${value.map(v => this.toStringValue(v)).join(", ")}]`; - } - switch (typeof value) { - case "string": - return `"${value}"`; - case "boolean": - return value.toString().toUpperCase(); - } - return value?.toString(); - } - - /** - * Allow subclass to create different display - */ - toStringAttribute() { - return this.attribute.join("."); - } - - /** - * Allow subclass to create different display - */ - toStringOperator() { - return this.operator; - } - - /** - * @override - */ - toString() { - return `${this.toStringAttribute()} ${this.toStringOperator()} ${this.toStringValue(this.value)}`; - } - } - - /** - * Abstract logic expression (AND|OR) - * - * Could add XOR in the future - */ - export abstract class LogicalExpression extends Expression { - /** - * Contains the members of the logical expression - */ - children: Expression[] = []; - /** - * - * @param operator - * @param children - */ - constructor(operator: T, children: Expression[]) { - super(operator); - this.children = children; - } - - /** - * @override - */ - toString(depth: number = 0) { - if (depth) { - return "( " + this.children.map(c => c.toString(depth + 1)).join(` ${this.operator} `) + " )"; - } - return this.children.map(c => c.toString(depth + 1)).join(` ${this.operator} `); - } - } - - /** - * AND Expression implementation - */ - export class AndExpression extends LogicalExpression<"AND"> { - /** - * @param children Expressions to use for AND - */ - constructor(children: Expression[]) { - super("AND", children); - } - - /** - * @override - */ - eval(target: any): boolean { - for (let child of this.children) { - if (!child.eval(target)) { - return false; - } - } - return true; - } - } - - /** - * OR Expression implementation - */ - export class OrExpression extends LogicalExpression<"OR"> { - /** - * @param children Expressions to use for OR - */ - constructor(children: Expression[]) { - super("OR", children); - } - - /** - * @override - */ - eval(target: any): boolean { - for (let child of this.children) { - if (child.eval(target)) { - return true; - } - } - return this.children.length === 0; - } - } - - /** - * - */ - export class QueryValidator { - protected lexer: WebdaQLLexer; - protected tree: WebdaqlContext; - protected query: Query; - protected builder: ExpressionBuilder; - - constructor( - protected sql: string, - builder: ExpressionBuilder = new ExpressionBuilder() - ) { - this.lexer = new WebdaQLLexer(CharStreams.fromString(sql || "")); - let tokenStream = new CommonTokenStream(this.lexer); - let parser = new WebdaQLParserParser(tokenStream); - parser.removeErrorListeners(); - parser.addErrorListener({ - syntaxError: ( - _recognizer: Recognizer, - _offendingSymbol: Token, - _line: number, - _charPositionInLine: number, - msg: string, - _e: RecognitionException - ) => { - throw new SyntaxError(`${msg} (Query: ${sql})`); - } - }); - // Parse the input, where `compilationUnit` is whatever entry point you defined - this.tree = parser.webdaql(); - this.builder = builder; - this.query = this.builder.visit(this.tree); - } - - /** - * Get offset - * @returns - */ - getOffset(): string { - return this.builder.getOffset() || ""; - } - - /** - * Get limit - * @returns - */ - getLimit(): number { - return this.builder.getLimit() || 1000; - } - - /** - * Get the expression by itself - * @returns - */ - getExpression(): Expression { - return this.query.filter; - } - - /** - * Retrieve parsed query - * @returns - */ - getQuery(): Query { - return { - ...this.query, - // Use displayTree to get the truely executed query - toString: () => this.displayTree() - }; - } - - /** - * Verify if a target fit the expression - * @param target - * @returns - */ - eval(target: any) { - return this.query.filter.eval(target); - } - - /** - * Display parse tree back as query - * @param tree - * @returns - */ - displayTree(tree: ParseTree = this.tree): string { - let res = ""; - for (let i = 0; i < tree.childCount; i++) { - const child = tree.getChild(i); - if (child instanceof TerminalNode) { - if (child.text === "") { - continue; - } - res += child.text.trim() + " "; - } else { - res += this.displayTree(child).trim() + " "; - } - } - return res; - } - } - - /** - * For now reuse same parser - */ - export class SetterValidator extends QueryValidator { - constructor(sql: string) { - super(sql); - // Do one empty run to raise any issue with disallowed expression - this.eval({}); - } - - eval(target: any): boolean { - if (this.query.filter) { - this.assign(target, this.query.filter); - } - return true; - } - - assign(target: any, expression: Expression) { - if (expression instanceof AndExpression) { - expression.children.forEach(c => this.assign(target, c)); - } else if (expression instanceof ComparisonExpression && (expression).operator === "=") { - expression.setAttributeValue(target); - } else { - throw new SyntaxError(`Set Expression can only contain And and assignment expression '='`); - } - } - } - - export class PartialValidator extends QueryValidator { - builder: PartialExpressionBuilder; - - constructor(query: string, builder: PartialExpressionBuilder = new PartialExpressionBuilder()) { - super(query, builder); - } - - /** - * Eval the query - * @param target - * @param partial - * @returns - */ - eval(target: any, partial: boolean = true): boolean { - this.builder.setPartial(partial); - this.builder.setPartialMatch(false); - return this.query.filter.eval(target); - } - - /** - * Return if the result ignored some fields - * @returns - */ - wasPartialMatch(): boolean { - return this.builder.partialMatch; - } - } - - export class PartialComparisonExpression< - T extends ComparisonOperator = ComparisonOperator - > extends ComparisonExpression { - constructor( - protected builder: PartialExpressionBuilder, - op: T, - attribute: string, - value: any - ) { - super(op, attribute, value); - } - - /** - * Override the eval to check if the attribute is present - * if not and we are in partial mode, return true - * - * @param target - * @returns - */ - eval(target: any): boolean { - if (this.builder.partial) { - const left = ComparisonExpression.getAttributeValue(target, this.attribute); - if (left === undefined) { - this.builder.setPartialMatch(true); - return true; - } - } - return super.eval(target); - } - } - - export class PartialExpressionBuilder extends ExpressionBuilder { - /** - * Enforce the partial mode - */ - partial: boolean; - /** - * If eval was called in partial mode - */ - partialMatch: boolean; - - setPartial(partial: boolean) { - this.partial = partial; - } - - setPartialMatch(partial: boolean) { - this.partialMatch = partial; - } - /** - * a LIKE "%A?" - * @param ctx - * @returns - */ - visitLikeExpression(ctx: any) { - const [left, _, right] = ctx.children; - let value = (this.visit(right)); - return new PartialComparisonExpression(this, "LIKE", left.text, value); - } - - /** - * Implement the BinaryComparison with all methods managed - */ - visitBinaryComparisonExpression(ctx: any) { - const [left, op, right] = ctx.children; - // @ts-ignore - return new PartialComparisonExpression(this, op.text, left.text, this.visit(right)); - } - - /** - * Map the a IN ['b','c'] - */ - visitInExpression(ctx: any) { - const [left, _, right] = ctx.children; - let value = (this.visit(right)); - return new PartialComparisonExpression(this, "IN", left.text, value); - } - - /** - * Map the a CONTAINS 'b' - */ - visitContainsExpression(ctx: any) { - const [left, _, right] = ctx.children; - let value = (this.visit(right)); - return new PartialComparisonExpression(this, "CONTAINS", left.text, value); - } - } - - /** - * Remove artifact from sanitize-html inside query - * @param query - * @returns - */ - export function unsanitize(query: string): string { - return query.replace(/</g, "<").replace(/>/g, ">"); - } -} diff --git a/packages/core/src/utils/context.spec.ts b/packages/core/src/utils/context.spec.ts index 9e366dac8..186955ef5 100644 --- a/packages/core/src/utils/context.spec.ts +++ b/packages/core/src/utils/context.spec.ts @@ -1,9 +1,8 @@ import { suite, test } from "@testdeck/mocha"; import * as assert from "assert"; import { Readable } from "stream"; +import { WebdaQL } from "../../../webdaql/query"; import { Core } from "../core"; -import { Service } from "../services/service"; -import { WebdaQL } from "../stores/webdaql/query"; import { WebdaTest } from "../test"; import { OperationContext, SimpleOperationContext, WebContext } from "./context"; import { HttpContext } from "./httpcontext"; @@ -81,8 +80,6 @@ class ContextTest extends WebdaTest { // Get the last lines this.ctx.logIn(); this.ctx.getRoute(); - assert.notStrictEqual(this.ctx.getService("Users"), undefined); - assert.notStrictEqual(this.ctx.getService("Users"), undefined); this.ctx = new WebContextMock(this.webda, new HttpContext("test.webda.io", "GET", "/uritemplate/plop")); this.ctx.setPathParameters({ id: "plop" }); this.ctx.setServiceParameters({ id: "service" }); @@ -97,15 +94,9 @@ class ContextTest extends WebdaTest { this.ctx.session = undefined; assert.strictEqual(this.ctx.getSession(), undefined); assert.strictEqual(this.ctx._promises.length, 0); - this.ctx.addAsyncRequest((async () => {})()); + this.ctx.registerPromise((async () => {})()); assert.strictEqual(this.ctx._promises.length, 1); - let caught = false; - this.ctx.on("error", () => { - caught = true; - }); - this.ctx.emitError("plop"); - assert.ok(caught); this.ctx.log("INFO", "Test"); assert.rejects(() => this.ctx.execute(), /Not Implemented/); @@ -266,7 +257,6 @@ class ContextTest extends WebdaTest { @test generic() { this.ctx.init(); - assert.notStrictEqual(this.ctx.getWebda(), undefined); // @ts-ignore this.ctx.session = undefined; assert.strictEqual(this.ctx.getCurrentUserId(), undefined); diff --git a/packages/core/src/utils/context.ts b/packages/core/src/utils/context.ts index 12e357cbc..91233d1b3 100644 --- a/packages/core/src/utils/context.ts +++ b/packages/core/src/utils/context.ts @@ -1,6 +1,5 @@ import { WorkerLogLevel } from "@webda/workout"; import acceptLanguage from "accept-language"; -import { EventEmitter } from "events"; import * as http from "http"; import sanitize from "sanitize-html"; import { Readable, Writable } from "stream"; @@ -56,26 +55,116 @@ export interface ContextProvider { } /** - * OperationContext is used when call to an operation - * - * @param T type of input for this context - * @param U type of output for this context + * Base Context */ -export class OperationContext extends EventEmitter { - protected static __globalContext: OperationContext; +export abstract class Context { /** * Contain emitting Core */ @NotEnumerable protected _webda: Core; - /** - * Session - */ - protected session: Session; /** * Allow extensions */ protected extensions: { [key: string]: any }; + /** + * Contain all registered promises to this context + */ + @NotEnumerable + _promises: Promise[]; + + /** + * + * @param webda + */ + constructor(webda: Core) { + this._webda = webda; + } + /** + * Get current user id + */ + abstract getCurrentUserId(): string | "system"; + + /** + * Return the current execution context + * @param this + * @returns + */ + static get(): T { + return Core.get().getContext(); + } + /** + * Register a promise with the context + * @param promise + */ + registerPromise(promise) { + this._promises.push(promise); + } + /** + * Get an extension of the context + * @param name of the extension + * @returns extension object + */ + public getExtension(name: string): K { + return this.extensions[name]; + } + + /** + * + * @param name to add + * @param extension object to store + */ + public setExtension(name: string, extension: any): this { + this.extensions[name] = extension; + return this; + } + + /** + * Ensure the whole execution is finished + */ + async end() { + await Promise.all(this._promises); + } + + /** + * Return the user + * @returns + */ + abstract getCurrentUser(): Promise; + + /** + * Proxy for simplification + * @param level + * @param args + */ + log(level: WorkerLogLevel, ...args: any[]) { + this._webda.log(level, ...args); + } + + /** + * Can be used to init the context if needed + * Reading from the input or reaching to a db + * @returns + */ + async init(): Promise { + return this; + } +} + +/** + * OperationContext is used when call to an operation + * It contains the input expected and output expected + * It also have a session and so user + * + * @param T type of input for this context + * @param U type of output for this context + */ +export class OperationContext extends Context { + /** + * Session + */ + protected session: Session; + /** * Contain the sanitized request body if computed */ @@ -91,18 +180,12 @@ export class OperationContext extends EventEmitter { */ private user: User; - /** - * Contain all registered promises to this context - */ - @NotEnumerable - _promises: Promise[]; - /** * @ignore * Used by Webda framework to set the body, session and output stream if known */ constructor(webda: Core, stream: Writable = undefined) { - super(); + super(webda); this.extensions = {}; this._webda = webda; this._promises = []; @@ -118,14 +201,6 @@ export class OperationContext extends EventEmitter { */ @NotEnumerable _stream: Writable; - /** - * Get an extension of the context - * @param name of the extension - * @returns extension object - */ - public getExtension(name: string): K { - return this.extensions[name]; - } /** * For easier compatibility with WebContext @@ -143,31 +218,6 @@ export class OperationContext extends EventEmitter { // Do nothing } - /** - * - * @param name to add - * @param extension object to store - */ - public setExtension(name: string, extension: any): this { - this.extensions[name] = extension; - return this; - } - - /** - * Return the webda - */ - getWebda() { - return this._webda; - } - - /** - * Register a promise with the context - * @param promise - */ - addAsyncRequest(promise) { - this._promises.push(promise); - } - /** * Get output as string, if a OutputStream is provided it will returned null * @returns @@ -187,14 +237,13 @@ export class OperationContext extends EventEmitter { } /** - * Ensure the whole execution is finished + * Get input for the operation + * + * The input is by default sanitized to avoid any XSS attack + * + * @param sanitizedOptions + * @returns */ - async end() { - this.emit("end"); - await Promise.all(this._promises); - this.emit("close"); - } - async getInput( sanitizedOptions: sanitize.IOptions & { defaultValue?: any; raw?: boolean | string[] } = { allowedTags: [], @@ -220,8 +269,8 @@ export class OperationContext extends EventEmitter { }; try { let data = await this.getRawInputAsString( - this.getWebda().getGlobalParams().requestLimit, - this.getWebda().getGlobalParams().requestTimeout + this._webda.getGlobalParams().requestLimit, + this._webda.getGlobalParams().requestTimeout ); if (sanitizedOptions.raw === true) { return JSON.parse(data || sanitizedOptions.defaultValue); @@ -300,15 +349,6 @@ export class OperationContext extends EventEmitter { }); } - /** - * Proxy for simplification - * @param level - * @param args - */ - log(level: WorkerLogLevel, ...args: any[]) { - this._webda.log(level, ...args); - } - /** * Create a new session * @returns @@ -351,10 +391,6 @@ export class OperationContext extends EventEmitter { return true; } - async init(): Promise { - return this; - } - /** * Get the current user from session */ @@ -364,7 +400,7 @@ export class OperationContext extends EventEmitter { } // Caching the answer if (!this.user || refresh) { - this.user = await this._webda.getApplication().getModel("User").ref(this.getCurrentUserId()).get(this); + this.user = await this._webda.getApplication().getModel("User").ref(this.getCurrentUserId()).get(); } return this.user; } @@ -375,36 +411,31 @@ export class OperationContext extends EventEmitter { getCurrentUserId() { return undefined; } - - /** - * Global context is the default Context - * - * Whenever a request is internal to the system - * or not linked to a user request - * @returns - */ - isGlobal() { - return false; - } } -export class GlobalContext extends OperationContext { - session: Session = new Session(); - +/** + * Global Context is used as system context + */ +export class GlobalContext extends Context { constructor(webda: Core) { super(webda); - this.session.login("system", "system"); - // Disable logout - this.session.logout = () => {}; } /** * @override */ - isGlobal() { - return true; + getCurrentUserId(): string { + return "system"; + } + + /** + * @override + */ + getCurrentUser(): Promise { + return undefined; } } + /** * Simple Operation Context with custom input */ @@ -420,7 +451,7 @@ export class SimpleOperationContext extends OperationContext { * @returns */ static async fromContext(context: OperationContext): Promise { - const ctx = new SimpleOperationContext(context.getWebda()); + const ctx = new SimpleOperationContext(Core.get()); ctx.setSession(context.getSession()); ctx.setInput(Buffer.from(JSONUtils.stringify(await context.getInput()))); return ctx; @@ -448,7 +479,7 @@ export class SimpleOperationContext extends OperationContext { * @override */ async getRawInput(limit: number = 1024 * 1024 * 10, _timeout: number = 60000): Promise { - return this.input.slice(0, limit); + return this.input.subarray(0, limit); } } /** @@ -495,7 +526,7 @@ export class WebContext extends OperationContext { * @param httpContext current http context */ public setHttpContext(httpContext: HttpContext) { - this.extensions["http"] = httpContext; + this.setExtension("http", httpContext); this.reinit(); } @@ -653,6 +684,10 @@ export class WebContext extends OperationContext { this._cookie[param] = { name: param, value, options }; } + /** + * Get the cookies to be sent to the client + * @returns + */ getResponseCookies(): Map { return this._cookie; } @@ -705,7 +740,7 @@ export class WebContext extends OperationContext { return this._ended; } this._ended = (async () => { - this.emit("end"); + // TO_REVIEW this.emit("end"); if (this.getExtension("http")) { await this._webda.getService("SessionManager").save(this, this.session); } @@ -718,7 +753,7 @@ export class WebContext extends OperationContext { this._webda.flushHeaders(this); } this._webda.flush(this); - this.emit("close"); + // TO_REVIEW this.emit("close"); })(); return this._ended; } @@ -776,16 +811,6 @@ export class WebContext extends OperationContext { }); } - /** - * Get a service from webda - * - * @see Webda - * @param {String} name of the service - */ - getService(name): K { - return this._webda.getService(name); - } - /** * Get the HTTP stream to output raw data * @returns {*} @@ -815,7 +840,7 @@ export class WebContext extends OperationContext { * Execute the target route */ async execute() { - return this._route._method(this); + return this._webda.runInContext(this, () => this._route._method(this)); } /** @@ -889,6 +914,11 @@ export class WebContext extends OperationContext { this.processParameters(); } + /** + * Override to init session http info + * @param force + * @returns + */ async init(force: boolean = false): Promise { if (this._init && !force) { return this._init; @@ -903,8 +933,4 @@ export class WebContext extends OperationContext { this._init = super.init(); return this._init; } - - emitError(err) { - this.emit("error", err); - } } diff --git a/packages/core/src/utils/cookie.ts b/packages/core/src/utils/cookie.ts index b6f91c420..f5f83ea02 100644 --- a/packages/core/src/utils/cookie.ts +++ b/packages/core/src/utils/cookie.ts @@ -1,4 +1,5 @@ import { CookieSerializeOptions, serialize as cookieSerialize } from "cookie"; +import { Core } from "../core"; import { JWTOptions } from "../services/cryptoservice"; import { WebContext } from "./context"; import { HttpContext } from "./httpcontext"; @@ -123,7 +124,7 @@ export class SecureCookie { } try { - return Object.assign(session, await context.getWebda().getCrypto().jwtVerify(raw, options)); + return Object.assign(session, await Core.get().getCrypto().jwtVerify(raw, options)); } catch (err) { context.log("WARN", "Ignoring bad cookie", `'${raw}'`, "from", context.getHttpContext()); return session; @@ -145,7 +146,7 @@ export class SecureCookie { options?: JWTOptions, cookieOptions?: Partial ) { - let value = await context.getWebda().getCrypto().jwtSign(Object.assign({}, data), options); + let value = await Core.get().getCrypto().jwtSign(Object.assign({}, data), options); this.sendCookie(context, name, value, new CookieOptions(cookieOptions, context.getHttpContext())); } diff --git a/packages/core/src/utils/session.ts b/packages/core/src/utils/session.ts index 557510be0..6112fcfe7 100644 --- a/packages/core/src/utils/session.ts +++ b/packages/core/src/utils/session.ts @@ -1,8 +1,9 @@ +import { Core } from "../core"; import { CoreModel, NotEnumerable } from "../models/coremodel"; import CryptoService, { JWTOptions } from "../services/cryptoservice"; import { DeepPartial, Inject, Service, ServiceParameters } from "../services/service"; import { Store } from "../stores/store"; -import { OperationContext, WebContext } from "./context"; +import { Context, OperationContext, WebContext } from "./context"; import { CookieOptions, SecureCookie } from "./cookie"; /** @@ -13,17 +14,17 @@ export abstract class SessionManager; + abstract load(context: Context): Promise; /** * Save the session within the context * @param context * @param session */ - abstract save(context: OperationContext, session: Session): Promise; + abstract save(context: Context, session: Session): Promise; /** * Create a new session */ - abstract newSession(context: OperationContext): Promise; + abstract newSession(context: Context): Promise; } export class CookieSessionParameters extends ServiceParameters { @@ -80,7 +81,7 @@ export class CookieSessionManager< /** * @override */ - async load(context: OperationContext): Promise { + async load(context: Context): Promise { if (!(context instanceof WebContext)) { return new Session(); } @@ -91,7 +92,7 @@ export class CookieSessionManager< Object.assign(session, (await this.sessionStore.get(cookie.sub))?.session); session.uuid = cookie.sub; } - session.uuid ??= context.getWebda().getUuid("base64"); + session.uuid ??= Core.get().getUuid("base64"); } else { Object.assign(session, cookie); } @@ -108,7 +109,7 @@ export class CookieSessionManager< /** * @override */ - async save(context: OperationContext, session: Session) { + async save(context: Context, session: Session) { if (!(context instanceof WebContext)) { return; } diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 42fbcdfde..6edd846ba 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -12,7 +12,7 @@ "moduleResolution": "node", "skipLibCheck": true }, - "include": ["src/**/*"], + "include": ["src/**/*", "../webdaql"], "exclude": ["**/node_modules"], "ts-node": { "transpileOnly": true, diff --git a/packages/elasticsearch/src/elasticsearchservice.ts b/packages/elasticsearch/src/elasticsearchservice.ts index 6b3fce2e5..592c31742 100644 --- a/packages/elasticsearch/src/elasticsearchservice.ts +++ b/packages/elasticsearch/src/elasticsearchservice.ts @@ -53,7 +53,7 @@ interface IndexParameter { }; } -interface IndexInfo extends IndexParameter { +export interface IndexInfo extends IndexParameter { _store?: Store; _model?: CoreModelDefinition; name: string; @@ -427,7 +427,11 @@ export default class ElasticSearchService< */ checkIndex(index: string): IndexInfo { if (!this.indexes[index]) { - throw new ESUnknownIndexError(index); + let parentIndex = Object.keys(this.indexes).find(i => index.startsWith(i)); + if (!parentIndex) { + throw new ESUnknownIndexError(index); + } + return this.indexes[parentIndex]; } return this.indexes[index]; } diff --git a/packages/gcp/webda.module.json b/packages/gcp/webda.module.json index 2ba0bab12..39b029789 100644 --- a/packages/gcp/webda.module.json +++ b/packages/gcp/webda.module.json @@ -277,7 +277,7 @@ "retry": { "anyOf": [ { - "$ref": "#/definitions/Partial%3Cclass-657338214-822-3488-657338214-0-17392%3E" + "$ref": "#/definitions/Partial%3Cclass-657338214-735-3044-657338214-0-15050%3E" }, { "type": "null" @@ -391,16 +391,7 @@ "currentRetryAttempt": { "type": "number" }, - "shouldRetryFn": {}, - "maxRetryDelay": { - "type": "number" - }, - "retryDelayMultiplier": { - "type": "number" - }, - "totalTimeout": { - "type": "number" - } + "shouldRetryFn": {} } } } @@ -603,13 +594,6 @@ } ], "description": "BigQueryConfig state" - }, - "useTableSchema": { - "type": [ - "boolean", - "null" - ], - "description": "BigQueryConfig useTableSchema" } }, "description": "Properties of a BigQueryConfig." @@ -1239,7 +1223,7 @@ } } }, - "Partial": { + "Partial": { "type": "object", "properties": { "retryCodes": { @@ -1293,9 +1277,7 @@ "retryDelayMultiplier", "maxRetryDelayMillis" ] - }, - "shouldRetryFn": {}, - "getResumptionRequestFn": {} + } } }, "google.pubsub.v1.BigQueryConfig.State": { diff --git a/packages/runtime/src/utils/iterators.ts b/packages/runtime/src/utils/iterators.ts index 65ccbfdf8..ba04510df 100644 --- a/packages/runtime/src/utils/iterators.ts +++ b/packages/runtime/src/utils/iterators.ts @@ -55,6 +55,15 @@ export class EventIterator { this.resolve(); } + /** + * Allow specific override + * @param event + * @param listener + */ + bindListener(event: string, listener: (evt: any) => void) { + this.eventEmitter.on(event, listener); + } + async *iterate() { this.running = true; // using a queue and not the once method in case 2 events are sent successively @@ -72,9 +81,10 @@ export class EventIterator { this.listeners[event] = data => { this.push(event, data); }; + // We might have multiple listeners on the same event // We could optimize by keeping a map but not sure it is better - this.eventEmitter.on(event, this.listeners[event]); + this.bindListener(event, this.listeners[event]); } while (true) { if (this.queue.length === 0) { @@ -167,3 +177,21 @@ export class MergedIterator { } } } + +/** + * Utility class to iterate over an async generator + */ +export class IteratorUtils { + /** + * Iterate over an async generator and return an array + * @param it + * @returns + */ + static async all(it: AsyncGenerator): Promise { + const res = []; + for await (let i of it) { + res.push(i); + } + return res; + } +} diff --git a/packages/shell/src/code/compiler.ts b/packages/shell/src/code/compiler.ts index 03600e4ff..bff4172fc 100644 --- a/packages/shell/src/code/compiler.ts +++ b/packages/shell/src/code/compiler.ts @@ -56,6 +56,7 @@ class WebdaSchemaResults { schemaNode?: ts.Node; link?: string; title?: string; + description?: string; addOpenApi: boolean; }; } = {}; @@ -75,15 +76,19 @@ class WebdaSchemaResults { let schemas = {}; Object.entries(this.store) .sort((a, b) => a[0].localeCompare(b[0])) - .forEach(([name, { schemaNode, link, title, addOpenApi }]) => { + .forEach(([name, { schemaNode, link, title, description, addOpenApi }]) => { if (schemaNode) { schemas[name] = compiler.generateSchema(schemaNode, title || name); - if (addOpenApi && schemas[name]) { - schemas[name].properties ??= {}; - schemas[name].properties["openapi"] = { - type: "object", - additionalProperties: true - }; + if (schemas[name]) { + if (addOpenApi) { + schemas[name].properties ??= {}; + schemas[name].properties["openapi"] = { + type: "object", + additionalProperties: true + }; + } + schemas[name].title ??= title || name; + schemas[name].description ??= description; } } else { schemas[name] = link; @@ -92,12 +97,13 @@ class WebdaSchemaResults { return schemas; } - add(name: string, info?: ts.Node | string, title?: string, addOpenApi: boolean = false) { + add(name: string, info?: ts.Node | string, title?: string, addOpenApi: boolean = false, description?: string) { if (typeof info === "object") { this.store[name] = { name: name, schemaNode: info, title, + description, addOpenApi }; this.byNode.set(info, name); @@ -106,6 +112,7 @@ class WebdaSchemaResults { name: name, link: info, title, + description, addOpenApi }; } @@ -874,7 +881,11 @@ export class Compiler { if (ts.isTypeReferenceNode(schemaNode)) { let decl = schemas.get(this.typeChecker.getTypeFromTypeNode(schemaNode).getSymbol().declarations[0]); if (decl) { - schemas.add(name, decl); + const description = ts + .getJSDocCommentsAndTags(obj) + .filter(i => ts.isJSDoc(obj)) + .shift()?.comment; + schemas.add(name, decl, undefined, false, description); return; } } diff --git a/sample-app/webda.module.json b/sample-app/webda.module.json index e184c79c0..100586cc6 100644 --- a/sample-app/webda.module.json +++ b/sample-app/webda.module.json @@ -1307,7 +1307,7 @@ "description": "URL on which to serve the content" }, "introspection": { - "$ref": "#/definitions/Partial%3Cclass-178760925-2932-3416-178760925-0-9621976353190%3E" + "$ref": "#/definitions/Partial%3Cclass-178760925-2932-3416-178760925-0-9757976353190%3E" }, "openapi": { "type": "object", @@ -1320,7 +1320,7 @@ ], "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Partial": { + "Partial": { "type": "object", "properties": { "type": { From 5be442267cd8c6c4ceeba1082f5540687f7cc07c Mon Sep 17 00:00:00 2001 From: Remi Cattiau Date: Wed, 14 Feb 2024 17:57:51 -0800 Subject: [PATCH 2/7] feat: separate WebdaQL module --- packages/ql/.mocharc.json | 3 + packages/ql/README.md | 3 + packages/ql/package.json | 70 + packages/ql/src/WebdaQLLexer.g4 | 79 + packages/ql/src/WebdaQLLexer.interp | 119 ++ packages/ql/src/WebdaQLLexer.tokens | 57 + packages/ql/src/WebdaQLLexer.ts | 319 ++++ packages/ql/src/WebdaQLParser.g4 | 58 + packages/ql/src/WebdaQLParser.interp | 88 + packages/ql/src/WebdaQLParser.tokens | 57 + packages/ql/src/WebdaQLParserLexer.interp | 119 ++ packages/ql/src/WebdaQLParserLexer.tokens | 57 + packages/ql/src/WebdaQLParserLexer.ts | 321 ++++ packages/ql/src/WebdaQLParserListener.ts | 352 ++++ packages/ql/src/WebdaQLParserParser.ts | 1815 +++++++++++++++++++++ packages/ql/src/WebdaQLParserVisitor.ts | 237 +++ packages/ql/src/query.spec.ts | 213 +++ packages/ql/src/query.ts | 819 ++++++++++ packages/ql/tsconfig.json | 26 + 19 files changed, 4812 insertions(+) create mode 100644 packages/ql/.mocharc.json create mode 100644 packages/ql/README.md create mode 100644 packages/ql/package.json create mode 100644 packages/ql/src/WebdaQLLexer.g4 create mode 100644 packages/ql/src/WebdaQLLexer.interp create mode 100644 packages/ql/src/WebdaQLLexer.tokens create mode 100644 packages/ql/src/WebdaQLLexer.ts create mode 100644 packages/ql/src/WebdaQLParser.g4 create mode 100644 packages/ql/src/WebdaQLParser.interp create mode 100644 packages/ql/src/WebdaQLParser.tokens create mode 100644 packages/ql/src/WebdaQLParserLexer.interp create mode 100644 packages/ql/src/WebdaQLParserLexer.tokens create mode 100644 packages/ql/src/WebdaQLParserLexer.ts create mode 100644 packages/ql/src/WebdaQLParserListener.ts create mode 100644 packages/ql/src/WebdaQLParserParser.ts create mode 100644 packages/ql/src/WebdaQLParserVisitor.ts create mode 100644 packages/ql/src/query.spec.ts create mode 100644 packages/ql/src/query.ts create mode 100644 packages/ql/tsconfig.json diff --git a/packages/ql/.mocharc.json b/packages/ql/.mocharc.json new file mode 100644 index 000000000..72a03424c --- /dev/null +++ b/packages/ql/.mocharc.json @@ -0,0 +1,3 @@ +{ + "node-option": ["experimental-specifier-resolution=node", "loader=ts-node/esm"] +} diff --git a/packages/ql/README.md b/packages/ql/README.md new file mode 100644 index 000000000..87f12d021 --- /dev/null +++ b/packages/ql/README.md @@ -0,0 +1,3 @@ +# @webda/webdaql + +This is a package for WebdaQL, a query language for Webda. diff --git a/packages/ql/package.json b/packages/ql/package.json new file mode 100644 index 000000000..6840a3c1d --- /dev/null +++ b/packages/ql/package.json @@ -0,0 +1,70 @@ +{ + "name": "@webda/ql", + "version": "3.0.0", + "description": "WebdaQL module", + "keywords": [ + "webda" + ], + "author": "Remi Cattiau ", + "repository": "git://github.com/loopingz/webda.io.git", + "main": "lib/query.js", + "typings": "lib/query.d.ts", + "scripts": { + "build": "tsc-esm", + "build:module": "webda build", + "build:watch": "webda build --watch", + "grammar": "antlr4ts -visitor src/WebdaQLLexer.g4 src/WebdaQLParser.g4 && yarn run lint:fix", + "pretest": "npm run build", + "lint": "prettier --check src/**/*", + "lint:fix": "prettier --write src/**/*", + "test": "c8 mocha --recursive --exit --timeout=30000 'src/*.spec.ts'", + "test:debug": "mocha --recursive --exit --no-timeouts 'src/*.spec.ts'" + }, + "dependencies": {}, + "devDependencies": { + "@testdeck/mocha": "^0.3.2", + "@types/node": "18.11.13", + "antlr4ts-cli": "^0.5.0-alpha.4", + "c8": "^9.0.0", + "mocha": "^10.0.0", + "prettier": "^3.0.0", + "sinon": "^17.0.0", + "ts-node": "^10.1.0", + "typescript": "~5.3.2" + }, + "files": [ + "lib", + "webda.module.json" + ], + "c8": { + "report-dir": "./reports", + "reporter": [ + "html", + "lcov", + "json", + "text" + ], + "exclude": [ + "**/*.spec.ts", + "src/WebdaQL*" + ], + "excludeNodeModules": true + }, + "homepage": "https://webda.io", + "publishConfig": { + "access": "public" + }, + "nx": { + "implicitDependencies": [ + "@webda/tsc-esm", + "@webda/workout" + ] + }, + "webda": { + "namespace": "Webda" + }, + "type": "module", + "engines": { + "node": ">=18.0.0" + } +} diff --git a/packages/ql/src/WebdaQLLexer.g4 b/packages/ql/src/WebdaQLLexer.g4 new file mode 100644 index 000000000..1eb35e6e0 --- /dev/null +++ b/packages/ql/src/WebdaQLLexer.g4 @@ -0,0 +1,79 @@ +lexer grammar WebdaQLLexer; + +// NOTE: +// This grammar is case-sensitive, although CESQL keywords are case-insensitive. +// In order to implement case-insensitivity, check out +// https://github.com/antlr/antlr4/blob/master/doc/case-insensitive-lexing.md#custom-character-streams-approach + +// Skip tab, carriage return and newlines + +SPACE: [ \t\r\n]+ -> skip; + +// Fragments for Literal primitives + +fragment ID_LITERAL: [a-zA-Z0-9]+; +fragment DQUOTA_STRING: '"' ( '\\'. | '""' | ~('"'| '\\') )* '"'; +fragment SQUOTA_STRING: '\'' ('\\'. | '\'\'' | ~('\'' | '\\'))* '\''; +fragment INT_DIGIT: [0-9]; +fragment FN_LITERAL: [A-Z] [A-Z_]*; + +// Constructors symbols + +LR_BRACKET: '('; +RR_BRACKET: ')'; +COMMA: ','; +SINGLE_QUOTE_SYMB: '\''; +DOUBLE_QUOTE_SYMB: '"'; +LR_SQ_BRACKET: '['; +RR_SQ_BRACKET: ']'; + +fragment QUOTE_SYMB + : SINGLE_QUOTE_SYMB | DOUBLE_QUOTE_SYMB + ; + +// Operators +// - Logic + +AND: 'AND'; +OR: 'OR'; + +// - Comparison + +EQUAL: '='; +NOT_EQUAL: '!='; +GREATER: '>'; +GREATER_OR_EQUAL: '>='; +LESS: '<'; +LESS_OR_EQUAL: '<='; + +// Like, exists, in + +LIKE: 'LIKE'; +IN: 'IN'; +CONTAINS: 'CONTAINS'; + +// Booleans + +TRUE: 'TRUE'; +FALSE: 'FALSE'; + +// Limit +LIMIT: 'LIMIT'; +OFFSET: 'OFFSET'; + +// Order by +ORDER_BY: 'ORDER BY'; +ASC: 'ASC'; +DESC: 'DESC'; + +// Literals + +DQUOTED_STRING_LITERAL: DQUOTA_STRING; +SQUOTED_STRING_LITERAL: SQUOTA_STRING; +INTEGER_LITERAL: INT_DIGIT+; + +// Identifiers + +IDENTIFIER: [a-zA-Z]+; +IDENTIFIER_WITH_NUMBER: [a-zA-Z0-9._]+; +FUNCTION_IDENTIFIER_WITH_UNDERSCORE: [A-Z] [A-Z_]*; \ No newline at end of file diff --git a/packages/ql/src/WebdaQLLexer.interp b/packages/ql/src/WebdaQLLexer.interp new file mode 100644 index 000000000..ffd6388a2 --- /dev/null +++ b/packages/ql/src/WebdaQLLexer.interp @@ -0,0 +1,119 @@ +token literal names: +null +null +'(' +')' +',' +'\'' +'"' +'[' +']' +'AND' +'OR' +'=' +'!=' +'>' +'>=' +'<' +'<=' +'LIKE' +'IN' +'CONTAINS' +'TRUE' +'FALSE' +'LIMIT' +'OFFSET' +'ORDER BY' +'ASC' +'DESC' +null +null +null +null +null +null + +token symbolic names: +null +SPACE +LR_BRACKET +RR_BRACKET +COMMA +SINGLE_QUOTE_SYMB +DOUBLE_QUOTE_SYMB +LR_SQ_BRACKET +RR_SQ_BRACKET +AND +OR +EQUAL +NOT_EQUAL +GREATER +GREATER_OR_EQUAL +LESS +LESS_OR_EQUAL +LIKE +IN +CONTAINS +TRUE +FALSE +LIMIT +OFFSET +ORDER_BY +ASC +DESC +DQUOTED_STRING_LITERAL +SQUOTED_STRING_LITERAL +INTEGER_LITERAL +IDENTIFIER +IDENTIFIER_WITH_NUMBER +FUNCTION_IDENTIFIER_WITH_UNDERSCORE + +rule names: +SPACE +ID_LITERAL +DQUOTA_STRING +SQUOTA_STRING +INT_DIGIT +FN_LITERAL +LR_BRACKET +RR_BRACKET +COMMA +SINGLE_QUOTE_SYMB +DOUBLE_QUOTE_SYMB +LR_SQ_BRACKET +RR_SQ_BRACKET +QUOTE_SYMB +AND +OR +EQUAL +NOT_EQUAL +GREATER +GREATER_OR_EQUAL +LESS +LESS_OR_EQUAL +LIKE +IN +CONTAINS +TRUE +FALSE +LIMIT +OFFSET +ORDER_BY +ASC +DESC +DQUOTED_STRING_LITERAL +SQUOTED_STRING_LITERAL +INTEGER_LITERAL +IDENTIFIER +IDENTIFIER_WITH_NUMBER +FUNCTION_IDENTIFIER_WITH_UNDERSCORE + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 34, 251, 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, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 3, 2, 6, 2, 81, 10, 2, 13, 2, 14, 2, 82, 3, 2, 3, 2, 3, 3, 6, 3, 88, 10, 3, 13, 3, 14, 3, 89, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 98, 10, 4, 12, 4, 14, 4, 101, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 7, 5, 111, 10, 5, 12, 5, 14, 5, 114, 11, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 7, 7, 122, 10, 7, 12, 7, 14, 7, 125, 11, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 5, 15, 143, 10, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 35, 3, 35, 3, 36, 6, 36, 231, 10, 36, 13, 36, 14, 36, 232, 3, 37, 6, 37, 236, 10, 37, 13, 37, 14, 37, 237, 3, 38, 6, 38, 241, 10, 38, 13, 38, 14, 38, 242, 3, 39, 3, 39, 7, 39, 247, 10, 39, 12, 39, 14, 39, 250, 11, 39, 2, 2, 2, 40, 3, 2, 3, 5, 2, 2, 7, 2, 2, 9, 2, 2, 11, 2, 2, 13, 2, 2, 15, 2, 4, 17, 2, 5, 19, 2, 6, 21, 2, 7, 23, 2, 8, 25, 2, 9, 27, 2, 10, 29, 2, 2, 31, 2, 11, 33, 2, 12, 35, 2, 13, 37, 2, 14, 39, 2, 15, 41, 2, 16, 43, 2, 17, 45, 2, 18, 47, 2, 19, 49, 2, 20, 51, 2, 21, 53, 2, 22, 55, 2, 23, 57, 2, 24, 59, 2, 25, 61, 2, 26, 63, 2, 27, 65, 2, 28, 67, 2, 29, 69, 2, 30, 71, 2, 31, 73, 2, 32, 75, 2, 33, 77, 2, 34, 3, 2, 11, 5, 2, 11, 12, 15, 15, 34, 34, 5, 2, 50, 59, 67, 92, 99, 124, 4, 2, 36, 36, 94, 94, 4, 2, 41, 41, 94, 94, 3, 2, 50, 59, 3, 2, 67, 92, 4, 2, 67, 92, 97, 97, 4, 2, 67, 92, 99, 124, 7, 2, 48, 48, 50, 59, 67, 92, 97, 97, 99, 124, 2, 258, 2, 3, 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, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 2, 77, 3, 2, 2, 2, 3, 80, 3, 2, 2, 2, 5, 87, 3, 2, 2, 2, 7, 91, 3, 2, 2, 2, 9, 104, 3, 2, 2, 2, 11, 117, 3, 2, 2, 2, 13, 119, 3, 2, 2, 2, 15, 126, 3, 2, 2, 2, 17, 128, 3, 2, 2, 2, 19, 130, 3, 2, 2, 2, 21, 132, 3, 2, 2, 2, 23, 134, 3, 2, 2, 2, 25, 136, 3, 2, 2, 2, 27, 138, 3, 2, 2, 2, 29, 142, 3, 2, 2, 2, 31, 144, 3, 2, 2, 2, 33, 148, 3, 2, 2, 2, 35, 151, 3, 2, 2, 2, 37, 153, 3, 2, 2, 2, 39, 156, 3, 2, 2, 2, 41, 158, 3, 2, 2, 2, 43, 161, 3, 2, 2, 2, 45, 163, 3, 2, 2, 2, 47, 166, 3, 2, 2, 2, 49, 171, 3, 2, 2, 2, 51, 174, 3, 2, 2, 2, 53, 183, 3, 2, 2, 2, 55, 188, 3, 2, 2, 2, 57, 194, 3, 2, 2, 2, 59, 200, 3, 2, 2, 2, 61, 207, 3, 2, 2, 2, 63, 216, 3, 2, 2, 2, 65, 220, 3, 2, 2, 2, 67, 225, 3, 2, 2, 2, 69, 227, 3, 2, 2, 2, 71, 230, 3, 2, 2, 2, 73, 235, 3, 2, 2, 2, 75, 240, 3, 2, 2, 2, 77, 244, 3, 2, 2, 2, 79, 81, 9, 2, 2, 2, 80, 79, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 80, 3, 2, 2, 2, 82, 83, 3, 2, 2, 2, 83, 84, 3, 2, 2, 2, 84, 85, 8, 2, 2, 2, 85, 4, 3, 2, 2, 2, 86, 88, 9, 3, 2, 2, 87, 86, 3, 2, 2, 2, 88, 89, 3, 2, 2, 2, 89, 87, 3, 2, 2, 2, 89, 90, 3, 2, 2, 2, 90, 6, 3, 2, 2, 2, 91, 99, 7, 36, 2, 2, 92, 93, 7, 94, 2, 2, 93, 98, 11, 2, 2, 2, 94, 95, 7, 36, 2, 2, 95, 98, 7, 36, 2, 2, 96, 98, 10, 4, 2, 2, 97, 92, 3, 2, 2, 2, 97, 94, 3, 2, 2, 2, 97, 96, 3, 2, 2, 2, 98, 101, 3, 2, 2, 2, 99, 97, 3, 2, 2, 2, 99, 100, 3, 2, 2, 2, 100, 102, 3, 2, 2, 2, 101, 99, 3, 2, 2, 2, 102, 103, 7, 36, 2, 2, 103, 8, 3, 2, 2, 2, 104, 112, 7, 41, 2, 2, 105, 106, 7, 94, 2, 2, 106, 111, 11, 2, 2, 2, 107, 108, 7, 41, 2, 2, 108, 111, 7, 41, 2, 2, 109, 111, 10, 5, 2, 2, 110, 105, 3, 2, 2, 2, 110, 107, 3, 2, 2, 2, 110, 109, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 115, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 116, 7, 41, 2, 2, 116, 10, 3, 2, 2, 2, 117, 118, 9, 6, 2, 2, 118, 12, 3, 2, 2, 2, 119, 123, 9, 7, 2, 2, 120, 122, 9, 8, 2, 2, 121, 120, 3, 2, 2, 2, 122, 125, 3, 2, 2, 2, 123, 121, 3, 2, 2, 2, 123, 124, 3, 2, 2, 2, 124, 14, 3, 2, 2, 2, 125, 123, 3, 2, 2, 2, 126, 127, 7, 42, 2, 2, 127, 16, 3, 2, 2, 2, 128, 129, 7, 43, 2, 2, 129, 18, 3, 2, 2, 2, 130, 131, 7, 46, 2, 2, 131, 20, 3, 2, 2, 2, 132, 133, 7, 41, 2, 2, 133, 22, 3, 2, 2, 2, 134, 135, 7, 36, 2, 2, 135, 24, 3, 2, 2, 2, 136, 137, 7, 93, 2, 2, 137, 26, 3, 2, 2, 2, 138, 139, 7, 95, 2, 2, 139, 28, 3, 2, 2, 2, 140, 143, 5, 21, 11, 2, 141, 143, 5, 23, 12, 2, 142, 140, 3, 2, 2, 2, 142, 141, 3, 2, 2, 2, 143, 30, 3, 2, 2, 2, 144, 145, 7, 67, 2, 2, 145, 146, 7, 80, 2, 2, 146, 147, 7, 70, 2, 2, 147, 32, 3, 2, 2, 2, 148, 149, 7, 81, 2, 2, 149, 150, 7, 84, 2, 2, 150, 34, 3, 2, 2, 2, 151, 152, 7, 63, 2, 2, 152, 36, 3, 2, 2, 2, 153, 154, 7, 35, 2, 2, 154, 155, 7, 63, 2, 2, 155, 38, 3, 2, 2, 2, 156, 157, 7, 64, 2, 2, 157, 40, 3, 2, 2, 2, 158, 159, 7, 64, 2, 2, 159, 160, 7, 63, 2, 2, 160, 42, 3, 2, 2, 2, 161, 162, 7, 62, 2, 2, 162, 44, 3, 2, 2, 2, 163, 164, 7, 62, 2, 2, 164, 165, 7, 63, 2, 2, 165, 46, 3, 2, 2, 2, 166, 167, 7, 78, 2, 2, 167, 168, 7, 75, 2, 2, 168, 169, 7, 77, 2, 2, 169, 170, 7, 71, 2, 2, 170, 48, 3, 2, 2, 2, 171, 172, 7, 75, 2, 2, 172, 173, 7, 80, 2, 2, 173, 50, 3, 2, 2, 2, 174, 175, 7, 69, 2, 2, 175, 176, 7, 81, 2, 2, 176, 177, 7, 80, 2, 2, 177, 178, 7, 86, 2, 2, 178, 179, 7, 67, 2, 2, 179, 180, 7, 75, 2, 2, 180, 181, 7, 80, 2, 2, 181, 182, 7, 85, 2, 2, 182, 52, 3, 2, 2, 2, 183, 184, 7, 86, 2, 2, 184, 185, 7, 84, 2, 2, 185, 186, 7, 87, 2, 2, 186, 187, 7, 71, 2, 2, 187, 54, 3, 2, 2, 2, 188, 189, 7, 72, 2, 2, 189, 190, 7, 67, 2, 2, 190, 191, 7, 78, 2, 2, 191, 192, 7, 85, 2, 2, 192, 193, 7, 71, 2, 2, 193, 56, 3, 2, 2, 2, 194, 195, 7, 78, 2, 2, 195, 196, 7, 75, 2, 2, 196, 197, 7, 79, 2, 2, 197, 198, 7, 75, 2, 2, 198, 199, 7, 86, 2, 2, 199, 58, 3, 2, 2, 2, 200, 201, 7, 81, 2, 2, 201, 202, 7, 72, 2, 2, 202, 203, 7, 72, 2, 2, 203, 204, 7, 85, 2, 2, 204, 205, 7, 71, 2, 2, 205, 206, 7, 86, 2, 2, 206, 60, 3, 2, 2, 2, 207, 208, 7, 81, 2, 2, 208, 209, 7, 84, 2, 2, 209, 210, 7, 70, 2, 2, 210, 211, 7, 71, 2, 2, 211, 212, 7, 84, 2, 2, 212, 213, 7, 34, 2, 2, 213, 214, 7, 68, 2, 2, 214, 215, 7, 91, 2, 2, 215, 62, 3, 2, 2, 2, 216, 217, 7, 67, 2, 2, 217, 218, 7, 85, 2, 2, 218, 219, 7, 69, 2, 2, 219, 64, 3, 2, 2, 2, 220, 221, 7, 70, 2, 2, 221, 222, 7, 71, 2, 2, 222, 223, 7, 85, 2, 2, 223, 224, 7, 69, 2, 2, 224, 66, 3, 2, 2, 2, 225, 226, 5, 7, 4, 2, 226, 68, 3, 2, 2, 2, 227, 228, 5, 9, 5, 2, 228, 70, 3, 2, 2, 2, 229, 231, 5, 11, 6, 2, 230, 229, 3, 2, 2, 2, 231, 232, 3, 2, 2, 2, 232, 230, 3, 2, 2, 2, 232, 233, 3, 2, 2, 2, 233, 72, 3, 2, 2, 2, 234, 236, 9, 9, 2, 2, 235, 234, 3, 2, 2, 2, 236, 237, 3, 2, 2, 2, 237, 235, 3, 2, 2, 2, 237, 238, 3, 2, 2, 2, 238, 74, 3, 2, 2, 2, 239, 241, 9, 10, 2, 2, 240, 239, 3, 2, 2, 2, 241, 242, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 242, 243, 3, 2, 2, 2, 243, 76, 3, 2, 2, 2, 244, 248, 9, 7, 2, 2, 245, 247, 9, 8, 2, 2, 246, 245, 3, 2, 2, 2, 247, 250, 3, 2, 2, 2, 248, 246, 3, 2, 2, 2, 248, 249, 3, 2, 2, 2, 249, 78, 3, 2, 2, 2, 250, 248, 3, 2, 2, 2, 15, 2, 82, 89, 97, 99, 110, 112, 123, 142, 232, 237, 242, 248, 3, 8, 2, 2] \ No newline at end of file diff --git a/packages/ql/src/WebdaQLLexer.tokens b/packages/ql/src/WebdaQLLexer.tokens new file mode 100644 index 000000000..d6427881e --- /dev/null +++ b/packages/ql/src/WebdaQLLexer.tokens @@ -0,0 +1,57 @@ +SPACE=1 +LR_BRACKET=2 +RR_BRACKET=3 +COMMA=4 +SINGLE_QUOTE_SYMB=5 +DOUBLE_QUOTE_SYMB=6 +LR_SQ_BRACKET=7 +RR_SQ_BRACKET=8 +AND=9 +OR=10 +EQUAL=11 +NOT_EQUAL=12 +GREATER=13 +GREATER_OR_EQUAL=14 +LESS=15 +LESS_OR_EQUAL=16 +LIKE=17 +IN=18 +CONTAINS=19 +TRUE=20 +FALSE=21 +LIMIT=22 +OFFSET=23 +ORDER_BY=24 +ASC=25 +DESC=26 +DQUOTED_STRING_LITERAL=27 +SQUOTED_STRING_LITERAL=28 +INTEGER_LITERAL=29 +IDENTIFIER=30 +IDENTIFIER_WITH_NUMBER=31 +FUNCTION_IDENTIFIER_WITH_UNDERSCORE=32 +'('=2 +')'=3 +','=4 +'\''=5 +'"'=6 +'['=7 +']'=8 +'AND'=9 +'OR'=10 +'='=11 +'!='=12 +'>'=13 +'>='=14 +'<'=15 +'<='=16 +'LIKE'=17 +'IN'=18 +'CONTAINS'=19 +'TRUE'=20 +'FALSE'=21 +'LIMIT'=22 +'OFFSET'=23 +'ORDER BY'=24 +'ASC'=25 +'DESC'=26 diff --git a/packages/ql/src/WebdaQLLexer.ts b/packages/ql/src/WebdaQLLexer.ts new file mode 100644 index 000000000..bea9a81fb --- /dev/null +++ b/packages/ql/src/WebdaQLLexer.ts @@ -0,0 +1,319 @@ +// Generated from src/stores/webdaql/WebdaQLLexer.g4 by ANTLR 4.9.0-SNAPSHOT + +import { ATN } from "antlr4ts/atn/ATN"; +import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; +import { LexerATNSimulator } from "antlr4ts/atn/LexerATNSimulator"; +import { CharStream } from "antlr4ts/CharStream"; +import { Lexer } from "antlr4ts/Lexer"; +import { Vocabulary } from "antlr4ts/Vocabulary"; +import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; + +import * as Utils from "antlr4ts/misc/Utils"; + +export class WebdaQLLexer extends Lexer { + public static readonly SPACE = 1; + public static readonly LR_BRACKET = 2; + public static readonly RR_BRACKET = 3; + public static readonly COMMA = 4; + public static readonly SINGLE_QUOTE_SYMB = 5; + public static readonly DOUBLE_QUOTE_SYMB = 6; + public static readonly LR_SQ_BRACKET = 7; + public static readonly RR_SQ_BRACKET = 8; + public static readonly AND = 9; + public static readonly OR = 10; + public static readonly EQUAL = 11; + public static readonly NOT_EQUAL = 12; + public static readonly GREATER = 13; + public static readonly GREATER_OR_EQUAL = 14; + public static readonly LESS = 15; + public static readonly LESS_OR_EQUAL = 16; + public static readonly LIKE = 17; + public static readonly IN = 18; + public static readonly CONTAINS = 19; + public static readonly TRUE = 20; + public static readonly FALSE = 21; + public static readonly LIMIT = 22; + public static readonly OFFSET = 23; + public static readonly ORDER_BY = 24; + public static readonly ASC = 25; + public static readonly DESC = 26; + public static readonly DQUOTED_STRING_LITERAL = 27; + public static readonly SQUOTED_STRING_LITERAL = 28; + public static readonly INTEGER_LITERAL = 29; + public static readonly IDENTIFIER = 30; + public static readonly IDENTIFIER_WITH_NUMBER = 31; + public static readonly FUNCTION_IDENTIFIER_WITH_UNDERSCORE = 32; + + // 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[] = [ + "SPACE", + "ID_LITERAL", + "DQUOTA_STRING", + "SQUOTA_STRING", + "INT_DIGIT", + "FN_LITERAL", + "LR_BRACKET", + "RR_BRACKET", + "COMMA", + "SINGLE_QUOTE_SYMB", + "DOUBLE_QUOTE_SYMB", + "LR_SQ_BRACKET", + "RR_SQ_BRACKET", + "QUOTE_SYMB", + "AND", + "OR", + "EQUAL", + "NOT_EQUAL", + "GREATER", + "GREATER_OR_EQUAL", + "LESS", + "LESS_OR_EQUAL", + "LIKE", + "IN", + "CONTAINS", + "TRUE", + "FALSE", + "LIMIT", + "OFFSET", + "ORDER_BY", + "ASC", + "DESC", + "DQUOTED_STRING_LITERAL", + "SQUOTED_STRING_LITERAL", + "INTEGER_LITERAL", + "IDENTIFIER", + "IDENTIFIER_WITH_NUMBER", + "FUNCTION_IDENTIFIER_WITH_UNDERSCORE" + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, + undefined, + "'('", + "')'", + "','", + "'''", + "'\"'", + "'['", + "']'", + "'AND'", + "'OR'", + "'='", + "'!='", + "'>'", + "'>='", + "'<'", + "'<='", + "'LIKE'", + "'IN'", + "'CONTAINS'", + "'TRUE'", + "'FALSE'", + "'LIMIT'", + "'OFFSET'", + "'ORDER BY'", + "'ASC'", + "'DESC'" + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, + "SPACE", + "LR_BRACKET", + "RR_BRACKET", + "COMMA", + "SINGLE_QUOTE_SYMB", + "DOUBLE_QUOTE_SYMB", + "LR_SQ_BRACKET", + "RR_SQ_BRACKET", + "AND", + "OR", + "EQUAL", + "NOT_EQUAL", + "GREATER", + "GREATER_OR_EQUAL", + "LESS", + "LESS_OR_EQUAL", + "LIKE", + "IN", + "CONTAINS", + "TRUE", + "FALSE", + "LIMIT", + "OFFSET", + "ORDER_BY", + "ASC", + "DESC", + "DQUOTED_STRING_LITERAL", + "SQUOTED_STRING_LITERAL", + "INTEGER_LITERAL", + "IDENTIFIER", + "IDENTIFIER_WITH_NUMBER", + "FUNCTION_IDENTIFIER_WITH_UNDERSCORE" + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl( + WebdaQLLexer._LITERAL_NAMES, + WebdaQLLexer._SYMBOLIC_NAMES, + [] + ); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return WebdaQLLexer.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + constructor(input: CharStream) { + super(input); + this._interp = new LexerATNSimulator(WebdaQLLexer._ATN, this); + } + + // @Override + public get grammarFileName(): string { + return "WebdaQLLexer.g4"; + } + + // @Override + public get ruleNames(): string[] { + return WebdaQLLexer.ruleNames; + } + + // @Override + public get serializedATN(): string { + return WebdaQLLexer._serializedATN; + } + + // @Override + public get channelNames(): string[] { + return WebdaQLLexer.channelNames; + } + + // @Override + public get modeNames(): string[] { + return WebdaQLLexer.modeNames; + } + + public static readonly _serializedATN: string = + '\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02"\xFB\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\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12" + + "\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t\x17" + + "\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t\x1C" + + '\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04"\t"\x04' + + "#\t#\x04$\t$\x04%\t%\x04&\t&\x04'\t'\x03\x02\x06\x02Q\n\x02\r\x02\x0E" + + "\x02R\x03\x02\x03\x02\x03\x03\x06\x03X\n\x03\r\x03\x0E\x03Y\x03\x04\x03" + + "\x04\x03\x04\x03\x04\x03\x04\x03\x04\x07\x04b\n\x04\f\x04\x0E\x04e\v\x04" + + "\x03\x04\x03\x04\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x07\x05" + + "o\n\x05\f\x05\x0E\x05r\v\x05\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03" + + "\x07\x07\x07z\n\x07\f\x07\x0E\x07}\v\x07\x03\b\x03\b\x03\t\x03\t\x03\n" + + "\x03\n\x03\v\x03\v\x03\f\x03\f\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0F\x03" + + "\x0F\x05\x0F\x8F\n\x0F\x03\x10\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11" + + "\x03\x11\x03\x12\x03\x12\x03\x13\x03\x13\x03\x13\x03\x14\x03\x14\x03\x15" + + "\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17\x03\x17\x03\x17\x03\x18\x03\x18" + + "\x03\x18\x03\x18\x03\x18\x03\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1A" + + "\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1B" + + "\x03\x1B\x03\x1B\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1D" + + "\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E\x03\x1E" + + "\x03\x1E\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F" + + "\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x03 \x03!\x03!\x03!\x03!\x03!" + + '\x03"\x03"\x03#\x03#\x03$\x06$\xE7\n$\r$\x0E$\xE8\x03%\x06%\xEC\n%\r' + + "%\x0E%\xED\x03&\x06&\xF1\n&\r&\x0E&\xF2\x03'\x03'\x07'\xF7\n'\f'" + + "\x0E'\xFA\v'\x02\x02\x02(\x03\x02\x03\x05\x02\x02\x07\x02\x02\t\x02" + + "\x02\v\x02\x02\r\x02\x02\x0F\x02\x04\x11\x02\x05\x13\x02\x06\x15\x02\x07" + + "\x17\x02\b\x19\x02\t\x1B\x02\n\x1D\x02\x02\x1F\x02\v!\x02\f#\x02\r%\x02" + + "\x0E'\x02\x0F)\x02\x10+\x02\x11-\x02\x12/\x02\x131\x02\x143\x02\x155" + + "\x02\x167\x02\x179\x02\x18;\x02\x19=\x02\x1A?\x02\x1BA\x02\x1CC\x02\x1D" + + 'E\x02\x1EG\x02\x1FI\x02 K\x02!M\x02"\x03\x02\v\x05\x02\v\f\x0F\x0F"' + + '"\x05\x022;C\\c|\x04\x02$$^^\x04\x02))^^\x03\x022;\x03\x02C\\\x04\x02' + + "C\\aa\x04\x02C\\c|\x07\x02002;C\\aac|\x02\u0102\x02\x03\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\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02" + + "\x02#\x03\x02\x02\x02\x02%\x03\x02\x02\x02\x02'\x03\x02\x02\x02\x02)" + + "\x03\x02\x02\x02\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02" + + "\x02\x02\x021\x03\x02\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02" + + "\x027\x03\x02\x02\x02\x029\x03\x02\x02\x02\x02;\x03\x02\x02\x02\x02=\x03" + + "\x02\x02\x02\x02?\x03\x02\x02\x02\x02A\x03\x02\x02\x02\x02C\x03\x02\x02" + + "\x02\x02E\x03\x02\x02\x02\x02G\x03\x02\x02\x02\x02I\x03\x02\x02\x02\x02" + + "K\x03\x02\x02\x02\x02M\x03\x02\x02\x02\x03P\x03\x02\x02\x02\x05W\x03\x02" + + "\x02\x02\x07[\x03\x02\x02\x02\th\x03\x02\x02\x02\vu\x03\x02\x02\x02\r" + + "w\x03\x02\x02\x02\x0F~\x03\x02\x02\x02\x11\x80\x03\x02\x02\x02\x13\x82" + + "\x03\x02\x02\x02\x15\x84\x03\x02\x02\x02\x17\x86\x03\x02\x02\x02\x19\x88" + + "\x03\x02\x02\x02\x1B\x8A\x03\x02\x02\x02\x1D\x8E\x03\x02\x02\x02\x1F\x90" + + "\x03\x02\x02\x02!\x94\x03\x02\x02\x02#\x97\x03\x02\x02\x02%\x99\x03\x02" + + "\x02\x02'\x9C\x03\x02\x02\x02)\x9E\x03\x02\x02\x02+\xA1\x03\x02\x02\x02" + + "-\xA3\x03\x02\x02\x02/\xA6\x03\x02\x02\x021\xAB\x03\x02\x02\x023\xAE\x03" + + "\x02\x02\x025\xB7\x03\x02\x02\x027\xBC\x03\x02\x02\x029\xC2\x03\x02\x02" + + "\x02;\xC8\x03\x02\x02\x02=\xCF\x03\x02\x02\x02?\xD8\x03\x02\x02\x02A\xDC" + + "\x03\x02\x02\x02C\xE1\x03\x02\x02\x02E\xE3\x03\x02\x02\x02G\xE6\x03\x02" + + "\x02\x02I\xEB\x03\x02\x02\x02K\xF0\x03\x02\x02\x02M\xF4\x03\x02\x02\x02" + + "OQ\t\x02\x02\x02PO\x03\x02\x02\x02QR\x03\x02\x02\x02RP\x03\x02\x02\x02" + + "RS\x03\x02\x02\x02ST\x03\x02\x02\x02TU\b\x02\x02\x02U\x04\x03\x02\x02" + + "\x02VX\t\x03\x02\x02WV\x03\x02\x02\x02XY\x03\x02\x02\x02YW\x03\x02\x02" + + "\x02YZ\x03\x02\x02\x02Z\x06\x03\x02\x02\x02[c\x07$\x02\x02\\]\x07^\x02" + + "\x02]b\v\x02\x02\x02^_\x07$\x02\x02_b\x07$\x02\x02`b\n\x04\x02\x02a\\" + + "\x03\x02\x02\x02a^\x03\x02\x02\x02a`\x03\x02\x02\x02be\x03\x02\x02\x02" + + "ca\x03\x02\x02\x02cd\x03\x02\x02\x02df\x03\x02\x02\x02ec\x03\x02\x02\x02" + + "fg\x07$\x02\x02g\b\x03\x02\x02\x02hp\x07)\x02\x02ij\x07^\x02\x02jo\v\x02" + + "\x02\x02kl\x07)\x02\x02lo\x07)\x02\x02mo\n\x05\x02\x02ni\x03\x02\x02\x02" + + "nk\x03\x02\x02\x02nm\x03\x02\x02\x02or\x03\x02\x02\x02pn\x03\x02\x02\x02" + + "pq\x03\x02\x02\x02qs\x03\x02\x02\x02rp\x03\x02\x02\x02st\x07)\x02\x02" + + "t\n\x03\x02\x02\x02uv\t\x06\x02\x02v\f\x03\x02\x02\x02w{\t\x07\x02\x02" + + "xz\t\b\x02\x02yx\x03\x02\x02\x02z}\x03\x02\x02\x02{y\x03\x02\x02\x02{" + + "|\x03\x02\x02\x02|\x0E\x03\x02\x02\x02}{\x03\x02\x02\x02~\x7F\x07*\x02" + + "\x02\x7F\x10\x03\x02\x02\x02\x80\x81\x07+\x02\x02\x81\x12\x03\x02\x02" + + "\x02\x82\x83\x07.\x02\x02\x83\x14\x03\x02\x02\x02\x84\x85\x07)\x02\x02" + + "\x85\x16\x03\x02\x02\x02\x86\x87\x07$\x02\x02\x87\x18\x03\x02\x02\x02" + + "\x88\x89\x07]\x02\x02\x89\x1A\x03\x02\x02\x02\x8A\x8B\x07_\x02\x02\x8B" + + "\x1C\x03\x02\x02\x02\x8C\x8F\x05\x15\v\x02\x8D\x8F\x05\x17\f\x02\x8E\x8C" + + "\x03\x02\x02\x02\x8E\x8D\x03\x02\x02\x02\x8F\x1E\x03\x02\x02\x02\x90\x91" + + "\x07C\x02\x02\x91\x92\x07P\x02\x02\x92\x93\x07F\x02\x02\x93 \x03\x02\x02" + + '\x02\x94\x95\x07Q\x02\x02\x95\x96\x07T\x02\x02\x96"\x03\x02\x02\x02\x97' + + "\x98\x07?\x02\x02\x98$\x03\x02\x02\x02\x99\x9A\x07#\x02\x02\x9A\x9B\x07" + + "?\x02\x02\x9B&\x03\x02\x02\x02\x9C\x9D\x07@\x02\x02\x9D(\x03\x02\x02\x02" + + "\x9E\x9F\x07@\x02\x02\x9F\xA0\x07?\x02\x02\xA0*\x03\x02\x02\x02\xA1\xA2" + + "\x07>\x02\x02\xA2,\x03\x02\x02\x02\xA3\xA4\x07>\x02\x02\xA4\xA5\x07?\x02" + + "\x02\xA5.\x03\x02\x02\x02\xA6\xA7\x07N\x02\x02\xA7\xA8\x07K\x02\x02\xA8" + + "\xA9\x07M\x02\x02\xA9\xAA\x07G\x02\x02\xAA0\x03\x02\x02\x02\xAB\xAC\x07" + + "K\x02\x02\xAC\xAD\x07P\x02\x02\xAD2\x03\x02\x02\x02\xAE\xAF\x07E\x02\x02" + + "\xAF\xB0\x07Q\x02\x02\xB0\xB1\x07P\x02\x02\xB1\xB2\x07V\x02\x02\xB2\xB3" + + "\x07C\x02\x02\xB3\xB4\x07K\x02\x02\xB4\xB5\x07P\x02\x02\xB5\xB6\x07U\x02" + + "\x02\xB64\x03\x02\x02\x02\xB7\xB8\x07V\x02\x02\xB8\xB9\x07T\x02\x02\xB9" + + "\xBA\x07W\x02\x02\xBA\xBB\x07G\x02\x02\xBB6\x03\x02\x02\x02\xBC\xBD\x07" + + "H\x02\x02\xBD\xBE\x07C\x02\x02\xBE\xBF\x07N\x02\x02\xBF\xC0\x07U\x02\x02" + + "\xC0\xC1\x07G\x02\x02\xC18\x03\x02\x02\x02\xC2\xC3\x07N\x02\x02\xC3\xC4" + + "\x07K\x02\x02\xC4\xC5\x07O\x02\x02\xC5\xC6\x07K\x02\x02\xC6\xC7\x07V\x02" + + "\x02\xC7:\x03\x02\x02\x02\xC8\xC9\x07Q\x02\x02\xC9\xCA\x07H\x02\x02\xCA" + + "\xCB\x07H\x02\x02\xCB\xCC\x07U\x02\x02\xCC\xCD\x07G\x02\x02\xCD\xCE\x07" + + "V\x02\x02\xCE<\x03\x02\x02\x02\xCF\xD0\x07Q\x02\x02\xD0\xD1\x07T\x02\x02" + + "\xD1\xD2\x07F\x02\x02\xD2\xD3\x07G\x02\x02\xD3\xD4\x07T\x02\x02\xD4\xD5" + + '\x07"\x02\x02\xD5\xD6\x07D\x02\x02\xD6\xD7\x07[\x02\x02\xD7>\x03\x02' + + "\x02\x02\xD8\xD9\x07C\x02\x02\xD9\xDA\x07U\x02\x02\xDA\xDB\x07E\x02\x02" + + "\xDB@\x03\x02\x02\x02\xDC\xDD\x07F\x02\x02\xDD\xDE\x07G\x02\x02\xDE\xDF" + + "\x07U\x02\x02\xDF\xE0\x07E\x02\x02\xE0B\x03\x02\x02\x02\xE1\xE2\x05\x07" + + "\x04\x02\xE2D\x03\x02\x02\x02\xE3\xE4\x05\t\x05\x02\xE4F\x03\x02\x02\x02" + + "\xE5\xE7\x05\v\x06\x02\xE6\xE5\x03\x02\x02\x02\xE7\xE8\x03\x02\x02\x02" + + "\xE8\xE6\x03\x02\x02\x02\xE8\xE9\x03\x02\x02\x02\xE9H\x03\x02\x02\x02" + + "\xEA\xEC\t\t\x02\x02\xEB\xEA\x03\x02\x02\x02\xEC\xED\x03\x02\x02\x02\xED" + + "\xEB\x03\x02\x02\x02\xED\xEE\x03\x02\x02\x02\xEEJ\x03\x02\x02\x02\xEF" + + "\xF1\t\n\x02\x02\xF0\xEF\x03\x02\x02\x02\xF1\xF2\x03\x02\x02\x02\xF2\xF0" + + "\x03\x02\x02\x02\xF2\xF3\x03\x02\x02\x02\xF3L\x03\x02\x02\x02\xF4\xF8" + + "\t\x07\x02\x02\xF5\xF7\t\b\x02\x02\xF6\xF5\x03\x02\x02\x02\xF7\xFA\x03" + + "\x02\x02\x02\xF8\xF6\x03\x02\x02\x02\xF8\xF9\x03\x02\x02\x02\xF9N\x03" + + "\x02\x02\x02\xFA\xF8\x03\x02\x02\x02\x0F\x02RYacnp{\x8E\xE8\xED\xF2\xF8" + + "\x03\b\x02\x02"; + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!WebdaQLLexer.__ATN) { + WebdaQLLexer.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(WebdaQLLexer._serializedATN)); + } + + return WebdaQLLexer.__ATN; + } +} diff --git a/packages/ql/src/WebdaQLParser.g4 b/packages/ql/src/WebdaQLParser.g4 new file mode 100644 index 000000000..9def96985 --- /dev/null +++ b/packages/ql/src/WebdaQLParser.g4 @@ -0,0 +1,58 @@ +grammar WebdaQLParser; + +import WebdaQLLexer; + +// Entrypoint +webdaql: expression? orderExpression? limitExpression? offsetExpression? EOF; + +limitExpression: LIMIT integerLiteral; +offsetExpression: OFFSET stringLiteral; +orderFieldExpression: identifier (ASC | DESC)?; +orderExpression: ORDER_BY orderFieldExpression ( COMMA orderFieldExpression )*; + +// Structure of operations, function invocations and expression +expression + : + // LIKE, EXISTS and IN takes precedence over all the other binary operators + identifier LIKE stringLiteral #likeExpression + | identifier IN setExpression #inExpression + | identifier CONTAINS stringLiteral #containsExpression + // Comparison operations + | identifier (EQUAL | NOT_EQUAL | GREATER_OR_EQUAL | LESS_OR_EQUAL | LESS | GREATER) values #binaryComparisonExpression + // Logic operations + | expression AND expression #andLogicExpression + | expression OR expression #orLogicExpression + // Subexpressions and atoms + | LR_BRACKET expression RR_BRACKET #subExpression + | atom #atomExpression + ; + +values + : booleanLiteral #booleanAtom + | integerLiteral #integerAtom + | stringLiteral #stringAtom + ; + +atom + : values #valuesAtom + | identifier #identifierAtom + ; + +// Identifiers + +identifier + : (IDENTIFIER | IDENTIFIER_WITH_NUMBER) + ; + +// Literals + +booleanLiteral: (TRUE | FALSE); +stringLiteral: (DQUOTED_STRING_LITERAL | SQUOTED_STRING_LITERAL); +integerLiteral: INTEGER_LITERAL; + + +// Sets + +setExpression + : LR_SQ_BRACKET values ( COMMA values )* RR_SQ_BRACKET // Empty sets are not allowed + ; \ No newline at end of file diff --git a/packages/ql/src/WebdaQLParser.interp b/packages/ql/src/WebdaQLParser.interp new file mode 100644 index 000000000..26d2431a0 --- /dev/null +++ b/packages/ql/src/WebdaQLParser.interp @@ -0,0 +1,88 @@ +token literal names: +null +null +'(' +')' +',' +'\'' +'"' +'[' +']' +'AND' +'OR' +'=' +'!=' +'>' +'>=' +'<' +'<=' +'LIKE' +'IN' +'CONTAINS' +'TRUE' +'FALSE' +'LIMIT' +'OFFSET' +'ORDER BY' +'ASC' +'DESC' +null +null +null +null +null +null + +token symbolic names: +null +SPACE +LR_BRACKET +RR_BRACKET +COMMA +SINGLE_QUOTE_SYMB +DOUBLE_QUOTE_SYMB +LR_SQ_BRACKET +RR_SQ_BRACKET +AND +OR +EQUAL +NOT_EQUAL +GREATER +GREATER_OR_EQUAL +LESS +LESS_OR_EQUAL +LIKE +IN +CONTAINS +TRUE +FALSE +LIMIT +OFFSET +ORDER_BY +ASC +DESC +DQUOTED_STRING_LITERAL +SQUOTED_STRING_LITERAL +INTEGER_LITERAL +IDENTIFIER +IDENTIFIER_WITH_NUMBER +FUNCTION_IDENTIFIER_WITH_UNDERSCORE + +rule names: +webdaql +limitExpression +offsetExpression +orderFieldExpression +orderExpression +expression +values +atom +identifier +booleanLiteral +stringLiteral +integerLiteral +setExpression + + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 34, 125, 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, 5, 2, 30, 10, 2, 3, 2, 5, 2, 33, 10, 2, 3, 2, 5, 2, 36, 10, 2, 3, 2, 5, 2, 39, 10, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 5, 5, 51, 10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 7, 6, 57, 10, 6, 12, 6, 14, 6, 60, 11, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 5, 7, 84, 10, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 7, 7, 92, 10, 7, 12, 7, 14, 7, 95, 11, 7, 3, 8, 3, 8, 3, 8, 5, 8, 100, 10, 8, 3, 9, 3, 9, 5, 9, 104, 10, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 7, 14, 118, 10, 14, 12, 14, 14, 14, 121, 11, 14, 3, 14, 3, 14, 3, 14, 2, 2, 3, 12, 15, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 12, 2, 14, 2, 16, 2, 18, 2, 20, 2, 22, 2, 24, 2, 26, 2, 2, 7, 3, 2, 27, 28, 3, 2, 13, 18, 3, 2, 32, 33, 3, 2, 22, 23, 3, 2, 29, 30, 2, 128, 2, 29, 3, 2, 2, 2, 4, 42, 3, 2, 2, 2, 6, 45, 3, 2, 2, 2, 8, 48, 3, 2, 2, 2, 10, 52, 3, 2, 2, 2, 12, 83, 3, 2, 2, 2, 14, 99, 3, 2, 2, 2, 16, 103, 3, 2, 2, 2, 18, 105, 3, 2, 2, 2, 20, 107, 3, 2, 2, 2, 22, 109, 3, 2, 2, 2, 24, 111, 3, 2, 2, 2, 26, 113, 3, 2, 2, 2, 28, 30, 5, 12, 7, 2, 29, 28, 3, 2, 2, 2, 29, 30, 3, 2, 2, 2, 30, 32, 3, 2, 2, 2, 31, 33, 5, 10, 6, 2, 32, 31, 3, 2, 2, 2, 32, 33, 3, 2, 2, 2, 33, 35, 3, 2, 2, 2, 34, 36, 5, 4, 3, 2, 35, 34, 3, 2, 2, 2, 35, 36, 3, 2, 2, 2, 36, 38, 3, 2, 2, 2, 37, 39, 5, 6, 4, 2, 38, 37, 3, 2, 2, 2, 38, 39, 3, 2, 2, 2, 39, 40, 3, 2, 2, 2, 40, 41, 7, 2, 2, 3, 41, 3, 3, 2, 2, 2, 42, 43, 7, 24, 2, 2, 43, 44, 5, 24, 13, 2, 44, 5, 3, 2, 2, 2, 45, 46, 7, 25, 2, 2, 46, 47, 5, 22, 12, 2, 47, 7, 3, 2, 2, 2, 48, 50, 5, 18, 10, 2, 49, 51, 9, 2, 2, 2, 50, 49, 3, 2, 2, 2, 50, 51, 3, 2, 2, 2, 51, 9, 3, 2, 2, 2, 52, 53, 7, 26, 2, 2, 53, 58, 5, 8, 5, 2, 54, 55, 7, 6, 2, 2, 55, 57, 5, 8, 5, 2, 56, 54, 3, 2, 2, 2, 57, 60, 3, 2, 2, 2, 58, 56, 3, 2, 2, 2, 58, 59, 3, 2, 2, 2, 59, 11, 3, 2, 2, 2, 60, 58, 3, 2, 2, 2, 61, 62, 8, 7, 1, 2, 62, 63, 5, 18, 10, 2, 63, 64, 7, 19, 2, 2, 64, 65, 5, 22, 12, 2, 65, 84, 3, 2, 2, 2, 66, 67, 5, 18, 10, 2, 67, 68, 7, 20, 2, 2, 68, 69, 5, 26, 14, 2, 69, 84, 3, 2, 2, 2, 70, 71, 5, 18, 10, 2, 71, 72, 7, 21, 2, 2, 72, 73, 5, 22, 12, 2, 73, 84, 3, 2, 2, 2, 74, 75, 5, 18, 10, 2, 75, 76, 9, 3, 2, 2, 76, 77, 5, 14, 8, 2, 77, 84, 3, 2, 2, 2, 78, 79, 7, 4, 2, 2, 79, 80, 5, 12, 7, 2, 80, 81, 7, 5, 2, 2, 81, 84, 3, 2, 2, 2, 82, 84, 5, 16, 9, 2, 83, 61, 3, 2, 2, 2, 83, 66, 3, 2, 2, 2, 83, 70, 3, 2, 2, 2, 83, 74, 3, 2, 2, 2, 83, 78, 3, 2, 2, 2, 83, 82, 3, 2, 2, 2, 84, 93, 3, 2, 2, 2, 85, 86, 12, 6, 2, 2, 86, 87, 7, 11, 2, 2, 87, 92, 5, 12, 7, 7, 88, 89, 12, 5, 2, 2, 89, 90, 7, 12, 2, 2, 90, 92, 5, 12, 7, 6, 91, 85, 3, 2, 2, 2, 91, 88, 3, 2, 2, 2, 92, 95, 3, 2, 2, 2, 93, 91, 3, 2, 2, 2, 93, 94, 3, 2, 2, 2, 94, 13, 3, 2, 2, 2, 95, 93, 3, 2, 2, 2, 96, 100, 5, 20, 11, 2, 97, 100, 5, 24, 13, 2, 98, 100, 5, 22, 12, 2, 99, 96, 3, 2, 2, 2, 99, 97, 3, 2, 2, 2, 99, 98, 3, 2, 2, 2, 100, 15, 3, 2, 2, 2, 101, 104, 5, 14, 8, 2, 102, 104, 5, 18, 10, 2, 103, 101, 3, 2, 2, 2, 103, 102, 3, 2, 2, 2, 104, 17, 3, 2, 2, 2, 105, 106, 9, 4, 2, 2, 106, 19, 3, 2, 2, 2, 107, 108, 9, 5, 2, 2, 108, 21, 3, 2, 2, 2, 109, 110, 9, 6, 2, 2, 110, 23, 3, 2, 2, 2, 111, 112, 7, 31, 2, 2, 112, 25, 3, 2, 2, 2, 113, 114, 7, 9, 2, 2, 114, 119, 5, 14, 8, 2, 115, 116, 7, 6, 2, 2, 116, 118, 5, 14, 8, 2, 117, 115, 3, 2, 2, 2, 118, 121, 3, 2, 2, 2, 119, 117, 3, 2, 2, 2, 119, 120, 3, 2, 2, 2, 120, 122, 3, 2, 2, 2, 121, 119, 3, 2, 2, 2, 122, 123, 7, 10, 2, 2, 123, 27, 3, 2, 2, 2, 14, 29, 32, 35, 38, 50, 58, 83, 91, 93, 99, 103, 119] \ No newline at end of file diff --git a/packages/ql/src/WebdaQLParser.tokens b/packages/ql/src/WebdaQLParser.tokens new file mode 100644 index 000000000..d6427881e --- /dev/null +++ b/packages/ql/src/WebdaQLParser.tokens @@ -0,0 +1,57 @@ +SPACE=1 +LR_BRACKET=2 +RR_BRACKET=3 +COMMA=4 +SINGLE_QUOTE_SYMB=5 +DOUBLE_QUOTE_SYMB=6 +LR_SQ_BRACKET=7 +RR_SQ_BRACKET=8 +AND=9 +OR=10 +EQUAL=11 +NOT_EQUAL=12 +GREATER=13 +GREATER_OR_EQUAL=14 +LESS=15 +LESS_OR_EQUAL=16 +LIKE=17 +IN=18 +CONTAINS=19 +TRUE=20 +FALSE=21 +LIMIT=22 +OFFSET=23 +ORDER_BY=24 +ASC=25 +DESC=26 +DQUOTED_STRING_LITERAL=27 +SQUOTED_STRING_LITERAL=28 +INTEGER_LITERAL=29 +IDENTIFIER=30 +IDENTIFIER_WITH_NUMBER=31 +FUNCTION_IDENTIFIER_WITH_UNDERSCORE=32 +'('=2 +')'=3 +','=4 +'\''=5 +'"'=6 +'['=7 +']'=8 +'AND'=9 +'OR'=10 +'='=11 +'!='=12 +'>'=13 +'>='=14 +'<'=15 +'<='=16 +'LIKE'=17 +'IN'=18 +'CONTAINS'=19 +'TRUE'=20 +'FALSE'=21 +'LIMIT'=22 +'OFFSET'=23 +'ORDER BY'=24 +'ASC'=25 +'DESC'=26 diff --git a/packages/ql/src/WebdaQLParserLexer.interp b/packages/ql/src/WebdaQLParserLexer.interp new file mode 100644 index 000000000..ffd6388a2 --- /dev/null +++ b/packages/ql/src/WebdaQLParserLexer.interp @@ -0,0 +1,119 @@ +token literal names: +null +null +'(' +')' +',' +'\'' +'"' +'[' +']' +'AND' +'OR' +'=' +'!=' +'>' +'>=' +'<' +'<=' +'LIKE' +'IN' +'CONTAINS' +'TRUE' +'FALSE' +'LIMIT' +'OFFSET' +'ORDER BY' +'ASC' +'DESC' +null +null +null +null +null +null + +token symbolic names: +null +SPACE +LR_BRACKET +RR_BRACKET +COMMA +SINGLE_QUOTE_SYMB +DOUBLE_QUOTE_SYMB +LR_SQ_BRACKET +RR_SQ_BRACKET +AND +OR +EQUAL +NOT_EQUAL +GREATER +GREATER_OR_EQUAL +LESS +LESS_OR_EQUAL +LIKE +IN +CONTAINS +TRUE +FALSE +LIMIT +OFFSET +ORDER_BY +ASC +DESC +DQUOTED_STRING_LITERAL +SQUOTED_STRING_LITERAL +INTEGER_LITERAL +IDENTIFIER +IDENTIFIER_WITH_NUMBER +FUNCTION_IDENTIFIER_WITH_UNDERSCORE + +rule names: +SPACE +ID_LITERAL +DQUOTA_STRING +SQUOTA_STRING +INT_DIGIT +FN_LITERAL +LR_BRACKET +RR_BRACKET +COMMA +SINGLE_QUOTE_SYMB +DOUBLE_QUOTE_SYMB +LR_SQ_BRACKET +RR_SQ_BRACKET +QUOTE_SYMB +AND +OR +EQUAL +NOT_EQUAL +GREATER +GREATER_OR_EQUAL +LESS +LESS_OR_EQUAL +LIKE +IN +CONTAINS +TRUE +FALSE +LIMIT +OFFSET +ORDER_BY +ASC +DESC +DQUOTED_STRING_LITERAL +SQUOTED_STRING_LITERAL +INTEGER_LITERAL +IDENTIFIER +IDENTIFIER_WITH_NUMBER +FUNCTION_IDENTIFIER_WITH_UNDERSCORE + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 34, 251, 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, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 3, 2, 6, 2, 81, 10, 2, 13, 2, 14, 2, 82, 3, 2, 3, 2, 3, 3, 6, 3, 88, 10, 3, 13, 3, 14, 3, 89, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 98, 10, 4, 12, 4, 14, 4, 101, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 7, 5, 111, 10, 5, 12, 5, 14, 5, 114, 11, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 7, 7, 122, 10, 7, 12, 7, 14, 7, 125, 11, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 5, 15, 143, 10, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 35, 3, 35, 3, 36, 6, 36, 231, 10, 36, 13, 36, 14, 36, 232, 3, 37, 6, 37, 236, 10, 37, 13, 37, 14, 37, 237, 3, 38, 6, 38, 241, 10, 38, 13, 38, 14, 38, 242, 3, 39, 3, 39, 7, 39, 247, 10, 39, 12, 39, 14, 39, 250, 11, 39, 2, 2, 2, 40, 3, 2, 3, 5, 2, 2, 7, 2, 2, 9, 2, 2, 11, 2, 2, 13, 2, 2, 15, 2, 4, 17, 2, 5, 19, 2, 6, 21, 2, 7, 23, 2, 8, 25, 2, 9, 27, 2, 10, 29, 2, 2, 31, 2, 11, 33, 2, 12, 35, 2, 13, 37, 2, 14, 39, 2, 15, 41, 2, 16, 43, 2, 17, 45, 2, 18, 47, 2, 19, 49, 2, 20, 51, 2, 21, 53, 2, 22, 55, 2, 23, 57, 2, 24, 59, 2, 25, 61, 2, 26, 63, 2, 27, 65, 2, 28, 67, 2, 29, 69, 2, 30, 71, 2, 31, 73, 2, 32, 75, 2, 33, 77, 2, 34, 3, 2, 11, 5, 2, 11, 12, 15, 15, 34, 34, 5, 2, 50, 59, 67, 92, 99, 124, 4, 2, 36, 36, 94, 94, 4, 2, 41, 41, 94, 94, 3, 2, 50, 59, 3, 2, 67, 92, 4, 2, 67, 92, 97, 97, 4, 2, 67, 92, 99, 124, 7, 2, 48, 48, 50, 59, 67, 92, 97, 97, 99, 124, 2, 258, 2, 3, 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, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 2, 77, 3, 2, 2, 2, 3, 80, 3, 2, 2, 2, 5, 87, 3, 2, 2, 2, 7, 91, 3, 2, 2, 2, 9, 104, 3, 2, 2, 2, 11, 117, 3, 2, 2, 2, 13, 119, 3, 2, 2, 2, 15, 126, 3, 2, 2, 2, 17, 128, 3, 2, 2, 2, 19, 130, 3, 2, 2, 2, 21, 132, 3, 2, 2, 2, 23, 134, 3, 2, 2, 2, 25, 136, 3, 2, 2, 2, 27, 138, 3, 2, 2, 2, 29, 142, 3, 2, 2, 2, 31, 144, 3, 2, 2, 2, 33, 148, 3, 2, 2, 2, 35, 151, 3, 2, 2, 2, 37, 153, 3, 2, 2, 2, 39, 156, 3, 2, 2, 2, 41, 158, 3, 2, 2, 2, 43, 161, 3, 2, 2, 2, 45, 163, 3, 2, 2, 2, 47, 166, 3, 2, 2, 2, 49, 171, 3, 2, 2, 2, 51, 174, 3, 2, 2, 2, 53, 183, 3, 2, 2, 2, 55, 188, 3, 2, 2, 2, 57, 194, 3, 2, 2, 2, 59, 200, 3, 2, 2, 2, 61, 207, 3, 2, 2, 2, 63, 216, 3, 2, 2, 2, 65, 220, 3, 2, 2, 2, 67, 225, 3, 2, 2, 2, 69, 227, 3, 2, 2, 2, 71, 230, 3, 2, 2, 2, 73, 235, 3, 2, 2, 2, 75, 240, 3, 2, 2, 2, 77, 244, 3, 2, 2, 2, 79, 81, 9, 2, 2, 2, 80, 79, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 80, 3, 2, 2, 2, 82, 83, 3, 2, 2, 2, 83, 84, 3, 2, 2, 2, 84, 85, 8, 2, 2, 2, 85, 4, 3, 2, 2, 2, 86, 88, 9, 3, 2, 2, 87, 86, 3, 2, 2, 2, 88, 89, 3, 2, 2, 2, 89, 87, 3, 2, 2, 2, 89, 90, 3, 2, 2, 2, 90, 6, 3, 2, 2, 2, 91, 99, 7, 36, 2, 2, 92, 93, 7, 94, 2, 2, 93, 98, 11, 2, 2, 2, 94, 95, 7, 36, 2, 2, 95, 98, 7, 36, 2, 2, 96, 98, 10, 4, 2, 2, 97, 92, 3, 2, 2, 2, 97, 94, 3, 2, 2, 2, 97, 96, 3, 2, 2, 2, 98, 101, 3, 2, 2, 2, 99, 97, 3, 2, 2, 2, 99, 100, 3, 2, 2, 2, 100, 102, 3, 2, 2, 2, 101, 99, 3, 2, 2, 2, 102, 103, 7, 36, 2, 2, 103, 8, 3, 2, 2, 2, 104, 112, 7, 41, 2, 2, 105, 106, 7, 94, 2, 2, 106, 111, 11, 2, 2, 2, 107, 108, 7, 41, 2, 2, 108, 111, 7, 41, 2, 2, 109, 111, 10, 5, 2, 2, 110, 105, 3, 2, 2, 2, 110, 107, 3, 2, 2, 2, 110, 109, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 115, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 116, 7, 41, 2, 2, 116, 10, 3, 2, 2, 2, 117, 118, 9, 6, 2, 2, 118, 12, 3, 2, 2, 2, 119, 123, 9, 7, 2, 2, 120, 122, 9, 8, 2, 2, 121, 120, 3, 2, 2, 2, 122, 125, 3, 2, 2, 2, 123, 121, 3, 2, 2, 2, 123, 124, 3, 2, 2, 2, 124, 14, 3, 2, 2, 2, 125, 123, 3, 2, 2, 2, 126, 127, 7, 42, 2, 2, 127, 16, 3, 2, 2, 2, 128, 129, 7, 43, 2, 2, 129, 18, 3, 2, 2, 2, 130, 131, 7, 46, 2, 2, 131, 20, 3, 2, 2, 2, 132, 133, 7, 41, 2, 2, 133, 22, 3, 2, 2, 2, 134, 135, 7, 36, 2, 2, 135, 24, 3, 2, 2, 2, 136, 137, 7, 93, 2, 2, 137, 26, 3, 2, 2, 2, 138, 139, 7, 95, 2, 2, 139, 28, 3, 2, 2, 2, 140, 143, 5, 21, 11, 2, 141, 143, 5, 23, 12, 2, 142, 140, 3, 2, 2, 2, 142, 141, 3, 2, 2, 2, 143, 30, 3, 2, 2, 2, 144, 145, 7, 67, 2, 2, 145, 146, 7, 80, 2, 2, 146, 147, 7, 70, 2, 2, 147, 32, 3, 2, 2, 2, 148, 149, 7, 81, 2, 2, 149, 150, 7, 84, 2, 2, 150, 34, 3, 2, 2, 2, 151, 152, 7, 63, 2, 2, 152, 36, 3, 2, 2, 2, 153, 154, 7, 35, 2, 2, 154, 155, 7, 63, 2, 2, 155, 38, 3, 2, 2, 2, 156, 157, 7, 64, 2, 2, 157, 40, 3, 2, 2, 2, 158, 159, 7, 64, 2, 2, 159, 160, 7, 63, 2, 2, 160, 42, 3, 2, 2, 2, 161, 162, 7, 62, 2, 2, 162, 44, 3, 2, 2, 2, 163, 164, 7, 62, 2, 2, 164, 165, 7, 63, 2, 2, 165, 46, 3, 2, 2, 2, 166, 167, 7, 78, 2, 2, 167, 168, 7, 75, 2, 2, 168, 169, 7, 77, 2, 2, 169, 170, 7, 71, 2, 2, 170, 48, 3, 2, 2, 2, 171, 172, 7, 75, 2, 2, 172, 173, 7, 80, 2, 2, 173, 50, 3, 2, 2, 2, 174, 175, 7, 69, 2, 2, 175, 176, 7, 81, 2, 2, 176, 177, 7, 80, 2, 2, 177, 178, 7, 86, 2, 2, 178, 179, 7, 67, 2, 2, 179, 180, 7, 75, 2, 2, 180, 181, 7, 80, 2, 2, 181, 182, 7, 85, 2, 2, 182, 52, 3, 2, 2, 2, 183, 184, 7, 86, 2, 2, 184, 185, 7, 84, 2, 2, 185, 186, 7, 87, 2, 2, 186, 187, 7, 71, 2, 2, 187, 54, 3, 2, 2, 2, 188, 189, 7, 72, 2, 2, 189, 190, 7, 67, 2, 2, 190, 191, 7, 78, 2, 2, 191, 192, 7, 85, 2, 2, 192, 193, 7, 71, 2, 2, 193, 56, 3, 2, 2, 2, 194, 195, 7, 78, 2, 2, 195, 196, 7, 75, 2, 2, 196, 197, 7, 79, 2, 2, 197, 198, 7, 75, 2, 2, 198, 199, 7, 86, 2, 2, 199, 58, 3, 2, 2, 2, 200, 201, 7, 81, 2, 2, 201, 202, 7, 72, 2, 2, 202, 203, 7, 72, 2, 2, 203, 204, 7, 85, 2, 2, 204, 205, 7, 71, 2, 2, 205, 206, 7, 86, 2, 2, 206, 60, 3, 2, 2, 2, 207, 208, 7, 81, 2, 2, 208, 209, 7, 84, 2, 2, 209, 210, 7, 70, 2, 2, 210, 211, 7, 71, 2, 2, 211, 212, 7, 84, 2, 2, 212, 213, 7, 34, 2, 2, 213, 214, 7, 68, 2, 2, 214, 215, 7, 91, 2, 2, 215, 62, 3, 2, 2, 2, 216, 217, 7, 67, 2, 2, 217, 218, 7, 85, 2, 2, 218, 219, 7, 69, 2, 2, 219, 64, 3, 2, 2, 2, 220, 221, 7, 70, 2, 2, 221, 222, 7, 71, 2, 2, 222, 223, 7, 85, 2, 2, 223, 224, 7, 69, 2, 2, 224, 66, 3, 2, 2, 2, 225, 226, 5, 7, 4, 2, 226, 68, 3, 2, 2, 2, 227, 228, 5, 9, 5, 2, 228, 70, 3, 2, 2, 2, 229, 231, 5, 11, 6, 2, 230, 229, 3, 2, 2, 2, 231, 232, 3, 2, 2, 2, 232, 230, 3, 2, 2, 2, 232, 233, 3, 2, 2, 2, 233, 72, 3, 2, 2, 2, 234, 236, 9, 9, 2, 2, 235, 234, 3, 2, 2, 2, 236, 237, 3, 2, 2, 2, 237, 235, 3, 2, 2, 2, 237, 238, 3, 2, 2, 2, 238, 74, 3, 2, 2, 2, 239, 241, 9, 10, 2, 2, 240, 239, 3, 2, 2, 2, 241, 242, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 242, 243, 3, 2, 2, 2, 243, 76, 3, 2, 2, 2, 244, 248, 9, 7, 2, 2, 245, 247, 9, 8, 2, 2, 246, 245, 3, 2, 2, 2, 247, 250, 3, 2, 2, 2, 248, 246, 3, 2, 2, 2, 248, 249, 3, 2, 2, 2, 249, 78, 3, 2, 2, 2, 250, 248, 3, 2, 2, 2, 15, 2, 82, 89, 97, 99, 110, 112, 123, 142, 232, 237, 242, 248, 3, 8, 2, 2] \ No newline at end of file diff --git a/packages/ql/src/WebdaQLParserLexer.tokens b/packages/ql/src/WebdaQLParserLexer.tokens new file mode 100644 index 000000000..d6427881e --- /dev/null +++ b/packages/ql/src/WebdaQLParserLexer.tokens @@ -0,0 +1,57 @@ +SPACE=1 +LR_BRACKET=2 +RR_BRACKET=3 +COMMA=4 +SINGLE_QUOTE_SYMB=5 +DOUBLE_QUOTE_SYMB=6 +LR_SQ_BRACKET=7 +RR_SQ_BRACKET=8 +AND=9 +OR=10 +EQUAL=11 +NOT_EQUAL=12 +GREATER=13 +GREATER_OR_EQUAL=14 +LESS=15 +LESS_OR_EQUAL=16 +LIKE=17 +IN=18 +CONTAINS=19 +TRUE=20 +FALSE=21 +LIMIT=22 +OFFSET=23 +ORDER_BY=24 +ASC=25 +DESC=26 +DQUOTED_STRING_LITERAL=27 +SQUOTED_STRING_LITERAL=28 +INTEGER_LITERAL=29 +IDENTIFIER=30 +IDENTIFIER_WITH_NUMBER=31 +FUNCTION_IDENTIFIER_WITH_UNDERSCORE=32 +'('=2 +')'=3 +','=4 +'\''=5 +'"'=6 +'['=7 +']'=8 +'AND'=9 +'OR'=10 +'='=11 +'!='=12 +'>'=13 +'>='=14 +'<'=15 +'<='=16 +'LIKE'=17 +'IN'=18 +'CONTAINS'=19 +'TRUE'=20 +'FALSE'=21 +'LIMIT'=22 +'OFFSET'=23 +'ORDER BY'=24 +'ASC'=25 +'DESC'=26 diff --git a/packages/ql/src/WebdaQLParserLexer.ts b/packages/ql/src/WebdaQLParserLexer.ts new file mode 100644 index 000000000..6360f2c55 --- /dev/null +++ b/packages/ql/src/WebdaQLParserLexer.ts @@ -0,0 +1,321 @@ +// Generated from src/stores/webdaql/WebdaQLParser.g4 by ANTLR 4.9.0-SNAPSHOT + +import { ATN } from "antlr4ts/atn/ATN"; +import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; +import { LexerATNSimulator } from "antlr4ts/atn/LexerATNSimulator"; +import { CharStream } from "antlr4ts/CharStream"; +import { Lexer } from "antlr4ts/Lexer"; +import { Vocabulary } from "antlr4ts/Vocabulary"; +import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; + +import * as Utils from "antlr4ts/misc/Utils"; + +export class WebdaQLParserLexer extends Lexer { + public static readonly SPACE = 1; + public static readonly LR_BRACKET = 2; + public static readonly RR_BRACKET = 3; + public static readonly COMMA = 4; + public static readonly SINGLE_QUOTE_SYMB = 5; + public static readonly DOUBLE_QUOTE_SYMB = 6; + public static readonly LR_SQ_BRACKET = 7; + public static readonly RR_SQ_BRACKET = 8; + public static readonly AND = 9; + public static readonly OR = 10; + public static readonly EQUAL = 11; + public static readonly NOT_EQUAL = 12; + public static readonly GREATER = 13; + public static readonly GREATER_OR_EQUAL = 14; + public static readonly LESS = 15; + public static readonly LESS_OR_EQUAL = 16; + public static readonly LIKE = 17; + public static readonly IN = 18; + public static readonly CONTAINS = 19; + public static readonly TRUE = 20; + public static readonly FALSE = 21; + public static readonly LIMIT = 22; + public static readonly OFFSET = 23; + public static readonly ORDER_BY = 24; + public static readonly ASC = 25; + public static readonly DESC = 26; + public static readonly DQUOTED_STRING_LITERAL = 27; + public static readonly SQUOTED_STRING_LITERAL = 28; + public static readonly INTEGER_LITERAL = 29; + public static readonly IDENTIFIER = 30; + public static readonly IDENTIFIER_WITH_NUMBER = 31; + public static readonly FUNCTION_IDENTIFIER_WITH_UNDERSCORE = 32; + + // 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[] = [ + "SPACE", + "ID_LITERAL", + "DQUOTA_STRING", + "SQUOTA_STRING", + "INT_DIGIT", + "FN_LITERAL", + "LR_BRACKET", + "RR_BRACKET", + "COMMA", + "SINGLE_QUOTE_SYMB", + "DOUBLE_QUOTE_SYMB", + "LR_SQ_BRACKET", + "RR_SQ_BRACKET", + "QUOTE_SYMB", + "AND", + "OR", + "EQUAL", + "NOT_EQUAL", + "GREATER", + "GREATER_OR_EQUAL", + "LESS", + "LESS_OR_EQUAL", + "LIKE", + "IN", + "CONTAINS", + "TRUE", + "FALSE", + "LIMIT", + "OFFSET", + "ORDER_BY", + "ASC", + "DESC", + "DQUOTED_STRING_LITERAL", + "SQUOTED_STRING_LITERAL", + "INTEGER_LITERAL", + "IDENTIFIER", + "IDENTIFIER_WITH_NUMBER", + "FUNCTION_IDENTIFIER_WITH_UNDERSCORE" + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, + undefined, + "'('", + "')'", + "','", + "'''", + "'\"'", + "'['", + "']'", + "'AND'", + "'OR'", + "'='", + "'!='", + "'>'", + "'>='", + "'<'", + "'<='", + "'LIKE'", + "'IN'", + "'CONTAINS'", + "'TRUE'", + "'FALSE'", + "'LIMIT'", + "'OFFSET'", + "'ORDER BY'", + "'ASC'", + "'DESC'" + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, + "SPACE", + "LR_BRACKET", + "RR_BRACKET", + "COMMA", + "SINGLE_QUOTE_SYMB", + "DOUBLE_QUOTE_SYMB", + "LR_SQ_BRACKET", + "RR_SQ_BRACKET", + "AND", + "OR", + "EQUAL", + "NOT_EQUAL", + "GREATER", + "GREATER_OR_EQUAL", + "LESS", + "LESS_OR_EQUAL", + "LIKE", + "IN", + "CONTAINS", + "TRUE", + "FALSE", + "LIMIT", + "OFFSET", + "ORDER_BY", + "ASC", + "DESC", + "DQUOTED_STRING_LITERAL", + "SQUOTED_STRING_LITERAL", + "INTEGER_LITERAL", + "IDENTIFIER", + "IDENTIFIER_WITH_NUMBER", + "FUNCTION_IDENTIFIER_WITH_UNDERSCORE" + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl( + WebdaQLParserLexer._LITERAL_NAMES, + WebdaQLParserLexer._SYMBOLIC_NAMES, + [] + ); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return WebdaQLParserLexer.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + constructor(input: CharStream) { + super(input); + this._interp = new LexerATNSimulator(WebdaQLParserLexer._ATN, this); + } + + // @Override + public get grammarFileName(): string { + return "WebdaQLParser.g4"; + } + + // @Override + public get ruleNames(): string[] { + return WebdaQLParserLexer.ruleNames; + } + + // @Override + public get serializedATN(): string { + return WebdaQLParserLexer._serializedATN; + } + + // @Override + public get channelNames(): string[] { + return WebdaQLParserLexer.channelNames; + } + + // @Override + public get modeNames(): string[] { + return WebdaQLParserLexer.modeNames; + } + + public static readonly _serializedATN: string = + '\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02"\xFB\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\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12" + + "\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t\x17" + + "\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t\x1C" + + '\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04"\t"\x04' + + "#\t#\x04$\t$\x04%\t%\x04&\t&\x04'\t'\x03\x02\x06\x02Q\n\x02\r\x02\x0E" + + "\x02R\x03\x02\x03\x02\x03\x03\x06\x03X\n\x03\r\x03\x0E\x03Y\x03\x04\x03" + + "\x04\x03\x04\x03\x04\x03\x04\x03\x04\x07\x04b\n\x04\f\x04\x0E\x04e\v\x04" + + "\x03\x04\x03\x04\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x07\x05" + + "o\n\x05\f\x05\x0E\x05r\v\x05\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03" + + "\x07\x07\x07z\n\x07\f\x07\x0E\x07}\v\x07\x03\b\x03\b\x03\t\x03\t\x03\n" + + "\x03\n\x03\v\x03\v\x03\f\x03\f\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0F\x03" + + "\x0F\x05\x0F\x8F\n\x0F\x03\x10\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11" + + "\x03\x11\x03\x12\x03\x12\x03\x13\x03\x13\x03\x13\x03\x14\x03\x14\x03\x15" + + "\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17\x03\x17\x03\x17\x03\x18\x03\x18" + + "\x03\x18\x03\x18\x03\x18\x03\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1A" + + "\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1B" + + "\x03\x1B\x03\x1B\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1D" + + "\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E\x03\x1E" + + "\x03\x1E\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F" + + "\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x03 \x03!\x03!\x03!\x03!\x03!" + + '\x03"\x03"\x03#\x03#\x03$\x06$\xE7\n$\r$\x0E$\xE8\x03%\x06%\xEC\n%\r' + + "%\x0E%\xED\x03&\x06&\xF1\n&\r&\x0E&\xF2\x03'\x03'\x07'\xF7\n'\f'" + + "\x0E'\xFA\v'\x02\x02\x02(\x03\x02\x03\x05\x02\x02\x07\x02\x02\t\x02" + + "\x02\v\x02\x02\r\x02\x02\x0F\x02\x04\x11\x02\x05\x13\x02\x06\x15\x02\x07" + + "\x17\x02\b\x19\x02\t\x1B\x02\n\x1D\x02\x02\x1F\x02\v!\x02\f#\x02\r%\x02" + + "\x0E'\x02\x0F)\x02\x10+\x02\x11-\x02\x12/\x02\x131\x02\x143\x02\x155" + + "\x02\x167\x02\x179\x02\x18;\x02\x19=\x02\x1A?\x02\x1BA\x02\x1CC\x02\x1D" + + 'E\x02\x1EG\x02\x1FI\x02 K\x02!M\x02"\x03\x02\v\x05\x02\v\f\x0F\x0F"' + + '"\x05\x022;C\\c|\x04\x02$$^^\x04\x02))^^\x03\x022;\x03\x02C\\\x04\x02' + + "C\\aa\x04\x02C\\c|\x07\x02002;C\\aac|\x02\u0102\x02\x03\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\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02" + + "\x02#\x03\x02\x02\x02\x02%\x03\x02\x02\x02\x02'\x03\x02\x02\x02\x02)" + + "\x03\x02\x02\x02\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02" + + "\x02\x02\x021\x03\x02\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02" + + "\x027\x03\x02\x02\x02\x029\x03\x02\x02\x02\x02;\x03\x02\x02\x02\x02=\x03" + + "\x02\x02\x02\x02?\x03\x02\x02\x02\x02A\x03\x02\x02\x02\x02C\x03\x02\x02" + + "\x02\x02E\x03\x02\x02\x02\x02G\x03\x02\x02\x02\x02I\x03\x02\x02\x02\x02" + + "K\x03\x02\x02\x02\x02M\x03\x02\x02\x02\x03P\x03\x02\x02\x02\x05W\x03\x02" + + "\x02\x02\x07[\x03\x02\x02\x02\th\x03\x02\x02\x02\vu\x03\x02\x02\x02\r" + + "w\x03\x02\x02\x02\x0F~\x03\x02\x02\x02\x11\x80\x03\x02\x02\x02\x13\x82" + + "\x03\x02\x02\x02\x15\x84\x03\x02\x02\x02\x17\x86\x03\x02\x02\x02\x19\x88" + + "\x03\x02\x02\x02\x1B\x8A\x03\x02\x02\x02\x1D\x8E\x03\x02\x02\x02\x1F\x90" + + "\x03\x02\x02\x02!\x94\x03\x02\x02\x02#\x97\x03\x02\x02\x02%\x99\x03\x02" + + "\x02\x02'\x9C\x03\x02\x02\x02)\x9E\x03\x02\x02\x02+\xA1\x03\x02\x02\x02" + + "-\xA3\x03\x02\x02\x02/\xA6\x03\x02\x02\x021\xAB\x03\x02\x02\x023\xAE\x03" + + "\x02\x02\x025\xB7\x03\x02\x02\x027\xBC\x03\x02\x02\x029\xC2\x03\x02\x02" + + "\x02;\xC8\x03\x02\x02\x02=\xCF\x03\x02\x02\x02?\xD8\x03\x02\x02\x02A\xDC" + + "\x03\x02\x02\x02C\xE1\x03\x02\x02\x02E\xE3\x03\x02\x02\x02G\xE6\x03\x02" + + "\x02\x02I\xEB\x03\x02\x02\x02K\xF0\x03\x02\x02\x02M\xF4\x03\x02\x02\x02" + + "OQ\t\x02\x02\x02PO\x03\x02\x02\x02QR\x03\x02\x02\x02RP\x03\x02\x02\x02" + + "RS\x03\x02\x02\x02ST\x03\x02\x02\x02TU\b\x02\x02\x02U\x04\x03\x02\x02" + + "\x02VX\t\x03\x02\x02WV\x03\x02\x02\x02XY\x03\x02\x02\x02YW\x03\x02\x02" + + "\x02YZ\x03\x02\x02\x02Z\x06\x03\x02\x02\x02[c\x07$\x02\x02\\]\x07^\x02" + + "\x02]b\v\x02\x02\x02^_\x07$\x02\x02_b\x07$\x02\x02`b\n\x04\x02\x02a\\" + + "\x03\x02\x02\x02a^\x03\x02\x02\x02a`\x03\x02\x02\x02be\x03\x02\x02\x02" + + "ca\x03\x02\x02\x02cd\x03\x02\x02\x02df\x03\x02\x02\x02ec\x03\x02\x02\x02" + + "fg\x07$\x02\x02g\b\x03\x02\x02\x02hp\x07)\x02\x02ij\x07^\x02\x02jo\v\x02" + + "\x02\x02kl\x07)\x02\x02lo\x07)\x02\x02mo\n\x05\x02\x02ni\x03\x02\x02\x02" + + "nk\x03\x02\x02\x02nm\x03\x02\x02\x02or\x03\x02\x02\x02pn\x03\x02\x02\x02" + + "pq\x03\x02\x02\x02qs\x03\x02\x02\x02rp\x03\x02\x02\x02st\x07)\x02\x02" + + "t\n\x03\x02\x02\x02uv\t\x06\x02\x02v\f\x03\x02\x02\x02w{\t\x07\x02\x02" + + "xz\t\b\x02\x02yx\x03\x02\x02\x02z}\x03\x02\x02\x02{y\x03\x02\x02\x02{" + + "|\x03\x02\x02\x02|\x0E\x03\x02\x02\x02}{\x03\x02\x02\x02~\x7F\x07*\x02" + + "\x02\x7F\x10\x03\x02\x02\x02\x80\x81\x07+\x02\x02\x81\x12\x03\x02\x02" + + "\x02\x82\x83\x07.\x02\x02\x83\x14\x03\x02\x02\x02\x84\x85\x07)\x02\x02" + + "\x85\x16\x03\x02\x02\x02\x86\x87\x07$\x02\x02\x87\x18\x03\x02\x02\x02" + + "\x88\x89\x07]\x02\x02\x89\x1A\x03\x02\x02\x02\x8A\x8B\x07_\x02\x02\x8B" + + "\x1C\x03\x02\x02\x02\x8C\x8F\x05\x15\v\x02\x8D\x8F\x05\x17\f\x02\x8E\x8C" + + "\x03\x02\x02\x02\x8E\x8D\x03\x02\x02\x02\x8F\x1E\x03\x02\x02\x02\x90\x91" + + "\x07C\x02\x02\x91\x92\x07P\x02\x02\x92\x93\x07F\x02\x02\x93 \x03\x02\x02" + + '\x02\x94\x95\x07Q\x02\x02\x95\x96\x07T\x02\x02\x96"\x03\x02\x02\x02\x97' + + "\x98\x07?\x02\x02\x98$\x03\x02\x02\x02\x99\x9A\x07#\x02\x02\x9A\x9B\x07" + + "?\x02\x02\x9B&\x03\x02\x02\x02\x9C\x9D\x07@\x02\x02\x9D(\x03\x02\x02\x02" + + "\x9E\x9F\x07@\x02\x02\x9F\xA0\x07?\x02\x02\xA0*\x03\x02\x02\x02\xA1\xA2" + + "\x07>\x02\x02\xA2,\x03\x02\x02\x02\xA3\xA4\x07>\x02\x02\xA4\xA5\x07?\x02" + + "\x02\xA5.\x03\x02\x02\x02\xA6\xA7\x07N\x02\x02\xA7\xA8\x07K\x02\x02\xA8" + + "\xA9\x07M\x02\x02\xA9\xAA\x07G\x02\x02\xAA0\x03\x02\x02\x02\xAB\xAC\x07" + + "K\x02\x02\xAC\xAD\x07P\x02\x02\xAD2\x03\x02\x02\x02\xAE\xAF\x07E\x02\x02" + + "\xAF\xB0\x07Q\x02\x02\xB0\xB1\x07P\x02\x02\xB1\xB2\x07V\x02\x02\xB2\xB3" + + "\x07C\x02\x02\xB3\xB4\x07K\x02\x02\xB4\xB5\x07P\x02\x02\xB5\xB6\x07U\x02" + + "\x02\xB64\x03\x02\x02\x02\xB7\xB8\x07V\x02\x02\xB8\xB9\x07T\x02\x02\xB9" + + "\xBA\x07W\x02\x02\xBA\xBB\x07G\x02\x02\xBB6\x03\x02\x02\x02\xBC\xBD\x07" + + "H\x02\x02\xBD\xBE\x07C\x02\x02\xBE\xBF\x07N\x02\x02\xBF\xC0\x07U\x02\x02" + + "\xC0\xC1\x07G\x02\x02\xC18\x03\x02\x02\x02\xC2\xC3\x07N\x02\x02\xC3\xC4" + + "\x07K\x02\x02\xC4\xC5\x07O\x02\x02\xC5\xC6\x07K\x02\x02\xC6\xC7\x07V\x02" + + "\x02\xC7:\x03\x02\x02\x02\xC8\xC9\x07Q\x02\x02\xC9\xCA\x07H\x02\x02\xCA" + + "\xCB\x07H\x02\x02\xCB\xCC\x07U\x02\x02\xCC\xCD\x07G\x02\x02\xCD\xCE\x07" + + "V\x02\x02\xCE<\x03\x02\x02\x02\xCF\xD0\x07Q\x02\x02\xD0\xD1\x07T\x02\x02" + + "\xD1\xD2\x07F\x02\x02\xD2\xD3\x07G\x02\x02\xD3\xD4\x07T\x02\x02\xD4\xD5" + + '\x07"\x02\x02\xD5\xD6\x07D\x02\x02\xD6\xD7\x07[\x02\x02\xD7>\x03\x02' + + "\x02\x02\xD8\xD9\x07C\x02\x02\xD9\xDA\x07U\x02\x02\xDA\xDB\x07E\x02\x02" + + "\xDB@\x03\x02\x02\x02\xDC\xDD\x07F\x02\x02\xDD\xDE\x07G\x02\x02\xDE\xDF" + + "\x07U\x02\x02\xDF\xE0\x07E\x02\x02\xE0B\x03\x02\x02\x02\xE1\xE2\x05\x07" + + "\x04\x02\xE2D\x03\x02\x02\x02\xE3\xE4\x05\t\x05\x02\xE4F\x03\x02\x02\x02" + + "\xE5\xE7\x05\v\x06\x02\xE6\xE5\x03\x02\x02\x02\xE7\xE8\x03\x02\x02\x02" + + "\xE8\xE6\x03\x02\x02\x02\xE8\xE9\x03\x02\x02\x02\xE9H\x03\x02\x02\x02" + + "\xEA\xEC\t\t\x02\x02\xEB\xEA\x03\x02\x02\x02\xEC\xED\x03\x02\x02\x02\xED" + + "\xEB\x03\x02\x02\x02\xED\xEE\x03\x02\x02\x02\xEEJ\x03\x02\x02\x02\xEF" + + "\xF1\t\n\x02\x02\xF0\xEF\x03\x02\x02\x02\xF1\xF2\x03\x02\x02\x02\xF2\xF0" + + "\x03\x02\x02\x02\xF2\xF3\x03\x02\x02\x02\xF3L\x03\x02\x02\x02\xF4\xF8" + + "\t\x07\x02\x02\xF5\xF7\t\b\x02\x02\xF6\xF5\x03\x02\x02\x02\xF7\xFA\x03" + + "\x02\x02\x02\xF8\xF6\x03\x02\x02\x02\xF8\xF9\x03\x02\x02\x02\xF9N\x03" + + "\x02\x02\x02\xFA\xF8\x03\x02\x02\x02\x0F\x02RYacnp{\x8E\xE8\xED\xF2\xF8" + + "\x03\b\x02\x02"; + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!WebdaQLParserLexer.__ATN) { + WebdaQLParserLexer.__ATN = new ATNDeserializer().deserialize( + Utils.toCharArray(WebdaQLParserLexer._serializedATN) + ); + } + + return WebdaQLParserLexer.__ATN; + } +} diff --git a/packages/ql/src/WebdaQLParserListener.ts b/packages/ql/src/WebdaQLParserListener.ts new file mode 100644 index 000000000..35481e9ce --- /dev/null +++ b/packages/ql/src/WebdaQLParserListener.ts @@ -0,0 +1,352 @@ +// Generated from src/stores/webdaql/WebdaQLParser.g4 by ANTLR 4.9.0-SNAPSHOT + +import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; + +import { + AndLogicExpressionContext, + AtomContext, + AtomExpressionContext, + BinaryComparisonExpressionContext, + BooleanAtomContext, + BooleanLiteralContext, + ContainsExpressionContext, + ExpressionContext, + IdentifierAtomContext, + IdentifierContext, + InExpressionContext, + IntegerAtomContext, + IntegerLiteralContext, + LikeExpressionContext, + LimitExpressionContext, + OffsetExpressionContext, + OrderExpressionContext, + OrderFieldExpressionContext, + OrLogicExpressionContext, + SetExpressionContext, + StringAtomContext, + StringLiteralContext, + SubExpressionContext, + ValuesAtomContext, + ValuesContext, + WebdaqlContext +} from "./WebdaQLParserParser"; + +/** + * This interface defines a complete listener for a parse tree produced by + * `WebdaQLParserParser`. + */ +export interface WebdaQLParserListener extends ParseTreeListener { + /** + * Enter a parse tree produced by the `likeExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + enterLikeExpression?: (ctx: LikeExpressionContext) => void; + /** + * Exit a parse tree produced by the `likeExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + exitLikeExpression?: (ctx: LikeExpressionContext) => void; + + /** + * Enter a parse tree produced by the `inExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + enterInExpression?: (ctx: InExpressionContext) => void; + /** + * Exit a parse tree produced by the `inExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + exitInExpression?: (ctx: InExpressionContext) => void; + + /** + * Enter a parse tree produced by the `containsExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + enterContainsExpression?: (ctx: ContainsExpressionContext) => void; + /** + * Exit a parse tree produced by the `containsExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + exitContainsExpression?: (ctx: ContainsExpressionContext) => void; + + /** + * Enter a parse tree produced by the `binaryComparisonExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + enterBinaryComparisonExpression?: (ctx: BinaryComparisonExpressionContext) => void; + /** + * Exit a parse tree produced by the `binaryComparisonExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + exitBinaryComparisonExpression?: (ctx: BinaryComparisonExpressionContext) => void; + + /** + * Enter a parse tree produced by the `andLogicExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + enterAndLogicExpression?: (ctx: AndLogicExpressionContext) => void; + /** + * Exit a parse tree produced by the `andLogicExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + exitAndLogicExpression?: (ctx: AndLogicExpressionContext) => void; + + /** + * Enter a parse tree produced by the `orLogicExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + enterOrLogicExpression?: (ctx: OrLogicExpressionContext) => void; + /** + * Exit a parse tree produced by the `orLogicExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + exitOrLogicExpression?: (ctx: OrLogicExpressionContext) => void; + + /** + * Enter a parse tree produced by the `subExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + enterSubExpression?: (ctx: SubExpressionContext) => void; + /** + * Exit a parse tree produced by the `subExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + exitSubExpression?: (ctx: SubExpressionContext) => void; + + /** + * Enter a parse tree produced by the `atomExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + enterAtomExpression?: (ctx: AtomExpressionContext) => void; + /** + * Exit a parse tree produced by the `atomExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + exitAtomExpression?: (ctx: AtomExpressionContext) => void; + + /** + * Enter a parse tree produced by the `booleanAtom` + * labeled alternative in `WebdaQLParserParser.values`. + * @param ctx the parse tree + */ + enterBooleanAtom?: (ctx: BooleanAtomContext) => void; + /** + * Exit a parse tree produced by the `booleanAtom` + * labeled alternative in `WebdaQLParserParser.values`. + * @param ctx the parse tree + */ + exitBooleanAtom?: (ctx: BooleanAtomContext) => void; + + /** + * Enter a parse tree produced by the `integerAtom` + * labeled alternative in `WebdaQLParserParser.values`. + * @param ctx the parse tree + */ + enterIntegerAtom?: (ctx: IntegerAtomContext) => void; + /** + * Exit a parse tree produced by the `integerAtom` + * labeled alternative in `WebdaQLParserParser.values`. + * @param ctx the parse tree + */ + exitIntegerAtom?: (ctx: IntegerAtomContext) => void; + + /** + * Enter a parse tree produced by the `stringAtom` + * labeled alternative in `WebdaQLParserParser.values`. + * @param ctx the parse tree + */ + enterStringAtom?: (ctx: StringAtomContext) => void; + /** + * Exit a parse tree produced by the `stringAtom` + * labeled alternative in `WebdaQLParserParser.values`. + * @param ctx the parse tree + */ + exitStringAtom?: (ctx: StringAtomContext) => void; + + /** + * Enter a parse tree produced by the `valuesAtom` + * labeled alternative in `WebdaQLParserParser.atom`. + * @param ctx the parse tree + */ + enterValuesAtom?: (ctx: ValuesAtomContext) => void; + /** + * Exit a parse tree produced by the `valuesAtom` + * labeled alternative in `WebdaQLParserParser.atom`. + * @param ctx the parse tree + */ + exitValuesAtom?: (ctx: ValuesAtomContext) => void; + + /** + * Enter a parse tree produced by the `identifierAtom` + * labeled alternative in `WebdaQLParserParser.atom`. + * @param ctx the parse tree + */ + enterIdentifierAtom?: (ctx: IdentifierAtomContext) => void; + /** + * Exit a parse tree produced by the `identifierAtom` + * labeled alternative in `WebdaQLParserParser.atom`. + * @param ctx the parse tree + */ + exitIdentifierAtom?: (ctx: IdentifierAtomContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.webdaql`. + * @param ctx the parse tree + */ + enterWebdaql?: (ctx: WebdaqlContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.webdaql`. + * @param ctx the parse tree + */ + exitWebdaql?: (ctx: WebdaqlContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.limitExpression`. + * @param ctx the parse tree + */ + enterLimitExpression?: (ctx: LimitExpressionContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.limitExpression`. + * @param ctx the parse tree + */ + exitLimitExpression?: (ctx: LimitExpressionContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.offsetExpression`. + * @param ctx the parse tree + */ + enterOffsetExpression?: (ctx: OffsetExpressionContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.offsetExpression`. + * @param ctx the parse tree + */ + exitOffsetExpression?: (ctx: OffsetExpressionContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.orderFieldExpression`. + * @param ctx the parse tree + */ + enterOrderFieldExpression?: (ctx: OrderFieldExpressionContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.orderFieldExpression`. + * @param ctx the parse tree + */ + exitOrderFieldExpression?: (ctx: OrderFieldExpressionContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.orderExpression`. + * @param ctx the parse tree + */ + enterOrderExpression?: (ctx: OrderExpressionContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.orderExpression`. + * @param ctx the parse tree + */ + exitOrderExpression?: (ctx: OrderExpressionContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + enterExpression?: (ctx: ExpressionContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.expression`. + * @param ctx the parse tree + */ + exitExpression?: (ctx: ExpressionContext) => void; + + /** + * Enter a parse tree produced by the `values` + * labeled alternative in `WebdaQLParserParser.expressionexpressionexpressionexpressionexpressionexpressionexpressionexpressionvaluesvaluesvaluesatomatom`. + * @param ctx the parse tree + */ + enterValues?: (ctx: ValuesContext) => void; + /** + * Exit a parse tree produced by the `values` + * labeled alternative in `WebdaQLParserParser.expressionexpressionexpressionexpressionexpressionexpressionexpressionexpressionvaluesvaluesvaluesatomatom`. + * @param ctx the parse tree + */ + exitValues?: (ctx: ValuesContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.atom`. + * @param ctx the parse tree + */ + enterAtom?: (ctx: AtomContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.atom`. + * @param ctx the parse tree + */ + exitAtom?: (ctx: AtomContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.identifier`. + * @param ctx the parse tree + */ + enterIdentifier?: (ctx: IdentifierContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.identifier`. + * @param ctx the parse tree + */ + exitIdentifier?: (ctx: IdentifierContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.booleanLiteral`. + * @param ctx the parse tree + */ + enterBooleanLiteral?: (ctx: BooleanLiteralContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.booleanLiteral`. + * @param ctx the parse tree + */ + exitBooleanLiteral?: (ctx: BooleanLiteralContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.stringLiteral`. + * @param ctx the parse tree + */ + enterStringLiteral?: (ctx: StringLiteralContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.stringLiteral`. + * @param ctx the parse tree + */ + exitStringLiteral?: (ctx: StringLiteralContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.integerLiteral`. + * @param ctx the parse tree + */ + enterIntegerLiteral?: (ctx: IntegerLiteralContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.integerLiteral`. + * @param ctx the parse tree + */ + exitIntegerLiteral?: (ctx: IntegerLiteralContext) => void; + + /** + * Enter a parse tree produced by `WebdaQLParserParser.setExpression`. + * @param ctx the parse tree + */ + enterSetExpression?: (ctx: SetExpressionContext) => void; + /** + * Exit a parse tree produced by `WebdaQLParserParser.setExpression`. + * @param ctx the parse tree + */ + exitSetExpression?: (ctx: SetExpressionContext) => void; +} diff --git a/packages/ql/src/WebdaQLParserParser.ts b/packages/ql/src/WebdaQLParserParser.ts new file mode 100644 index 000000000..6b54e5ecf --- /dev/null +++ b/packages/ql/src/WebdaQLParserParser.ts @@ -0,0 +1,1815 @@ +// Generated from src/stores/webdaql/WebdaQLParser.g4 by ANTLR 4.9.0-SNAPSHOT + +import { ATN } from "antlr4ts/atn/ATN"; +import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; +import { ParserATNSimulator } from "antlr4ts/atn/ParserATNSimulator"; +import { FailedPredicateException } from "antlr4ts/FailedPredicateException"; +import { NoViableAltException } from "antlr4ts/NoViableAltException"; +import { Parser } from "antlr4ts/Parser"; +import { ParserRuleContext } from "antlr4ts/ParserRuleContext"; +import { RecognitionException } from "antlr4ts/RecognitionException"; +import { RuleContext } from "antlr4ts/RuleContext"; +//import { RuleVersion } from "antlr4ts/RuleVersion"; +import { Token } from "antlr4ts/Token"; +import { TokenStream } from "antlr4ts/TokenStream"; +import { TerminalNode } from "antlr4ts/tree/TerminalNode"; +import { Vocabulary } from "antlr4ts/Vocabulary"; +import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; + +import * as Utils from "antlr4ts/misc/Utils"; + +import { WebdaQLParserListener } from "./WebdaQLParserListener"; +import { WebdaQLParserVisitor } from "./WebdaQLParserVisitor"; + +export class WebdaQLParserParser extends Parser { + public static readonly SPACE = 1; + public static readonly LR_BRACKET = 2; + public static readonly RR_BRACKET = 3; + public static readonly COMMA = 4; + public static readonly SINGLE_QUOTE_SYMB = 5; + public static readonly DOUBLE_QUOTE_SYMB = 6; + public static readonly LR_SQ_BRACKET = 7; + public static readonly RR_SQ_BRACKET = 8; + public static readonly AND = 9; + public static readonly OR = 10; + public static readonly EQUAL = 11; + public static readonly NOT_EQUAL = 12; + public static readonly GREATER = 13; + public static readonly GREATER_OR_EQUAL = 14; + public static readonly LESS = 15; + public static readonly LESS_OR_EQUAL = 16; + public static readonly LIKE = 17; + public static readonly IN = 18; + public static readonly CONTAINS = 19; + public static readonly TRUE = 20; + public static readonly FALSE = 21; + public static readonly LIMIT = 22; + public static readonly OFFSET = 23; + public static readonly ORDER_BY = 24; + public static readonly ASC = 25; + public static readonly DESC = 26; + public static readonly DQUOTED_STRING_LITERAL = 27; + public static readonly SQUOTED_STRING_LITERAL = 28; + public static readonly INTEGER_LITERAL = 29; + public static readonly IDENTIFIER = 30; + public static readonly IDENTIFIER_WITH_NUMBER = 31; + public static readonly FUNCTION_IDENTIFIER_WITH_UNDERSCORE = 32; + public static readonly RULE_webdaql = 0; + public static readonly RULE_limitExpression = 1; + public static readonly RULE_offsetExpression = 2; + public static readonly RULE_orderFieldExpression = 3; + public static readonly RULE_orderExpression = 4; + public static readonly RULE_expression = 5; + public static readonly RULE_values = 6; + public static readonly RULE_atom = 7; + public static readonly RULE_identifier = 8; + public static readonly RULE_booleanLiteral = 9; + public static readonly RULE_stringLiteral = 10; + public static readonly RULE_integerLiteral = 11; + public static readonly RULE_setExpression = 12; + // tslint:disable:no-trailing-whitespace + public static readonly ruleNames: string[] = [ + "webdaql", + "limitExpression", + "offsetExpression", + "orderFieldExpression", + "orderExpression", + "expression", + "values", + "atom", + "identifier", + "booleanLiteral", + "stringLiteral", + "integerLiteral", + "setExpression" + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, + undefined, + "'('", + "')'", + "','", + "'''", + "'\"'", + "'['", + "']'", + "'AND'", + "'OR'", + "'='", + "'!='", + "'>'", + "'>='", + "'<'", + "'<='", + "'LIKE'", + "'IN'", + "'CONTAINS'", + "'TRUE'", + "'FALSE'", + "'LIMIT'", + "'OFFSET'", + "'ORDER BY'", + "'ASC'", + "'DESC'" + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, + "SPACE", + "LR_BRACKET", + "RR_BRACKET", + "COMMA", + "SINGLE_QUOTE_SYMB", + "DOUBLE_QUOTE_SYMB", + "LR_SQ_BRACKET", + "RR_SQ_BRACKET", + "AND", + "OR", + "EQUAL", + "NOT_EQUAL", + "GREATER", + "GREATER_OR_EQUAL", + "LESS", + "LESS_OR_EQUAL", + "LIKE", + "IN", + "CONTAINS", + "TRUE", + "FALSE", + "LIMIT", + "OFFSET", + "ORDER_BY", + "ASC", + "DESC", + "DQUOTED_STRING_LITERAL", + "SQUOTED_STRING_LITERAL", + "INTEGER_LITERAL", + "IDENTIFIER", + "IDENTIFIER_WITH_NUMBER", + "FUNCTION_IDENTIFIER_WITH_UNDERSCORE" + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl( + WebdaQLParserParser._LITERAL_NAMES, + WebdaQLParserParser._SYMBOLIC_NAMES, + [] + ); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return WebdaQLParserParser.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + // @Override + public get grammarFileName(): string { + return "WebdaQLParser.g4"; + } + + // @Override + public get ruleNames(): string[] { + return WebdaQLParserParser.ruleNames; + } + + // @Override + public get serializedATN(): string { + return WebdaQLParserParser._serializedATN; + } + + protected createFailedPredicateException(predicate?: string, message?: string): FailedPredicateException { + return new FailedPredicateException(this, predicate, message); + } + + constructor(input: TokenStream) { + super(input); + this._interp = new ParserATNSimulator(WebdaQLParserParser._ATN, this); + } + // @RuleVersion(0) + public webdaql(): WebdaqlContext { + let _localctx: WebdaqlContext = new WebdaqlContext(this._ctx, this.state); + this.enterRule(_localctx, 0, WebdaQLParserParser.RULE_webdaql); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 27; + this._errHandler.sync(this); + _la = this._input.LA(1); + if ( + (_la & ~0x1f) === 0 && + ((1 << _la) & + ((1 << WebdaQLParserParser.LR_BRACKET) | + (1 << WebdaQLParserParser.TRUE) | + (1 << WebdaQLParserParser.FALSE) | + (1 << WebdaQLParserParser.DQUOTED_STRING_LITERAL) | + (1 << WebdaQLParserParser.SQUOTED_STRING_LITERAL) | + (1 << WebdaQLParserParser.INTEGER_LITERAL) | + (1 << WebdaQLParserParser.IDENTIFIER) | + (1 << WebdaQLParserParser.IDENTIFIER_WITH_NUMBER))) !== + 0 + ) { + { + this.state = 26; + this.expression(0); + } + } + + this.state = 30; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === WebdaQLParserParser.ORDER_BY) { + { + this.state = 29; + this.orderExpression(); + } + } + + this.state = 33; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === WebdaQLParserParser.LIMIT) { + { + this.state = 32; + this.limitExpression(); + } + } + + this.state = 36; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === WebdaQLParserParser.OFFSET) { + { + this.state = 35; + this.offsetExpression(); + } + } + + this.state = 38; + this.match(WebdaQLParserParser.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 limitExpression(): LimitExpressionContext { + let _localctx: LimitExpressionContext = new LimitExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 2, WebdaQLParserParser.RULE_limitExpression); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 40; + this.match(WebdaQLParserParser.LIMIT); + this.state = 41; + this.integerLiteral(); + } + } 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 offsetExpression(): OffsetExpressionContext { + let _localctx: OffsetExpressionContext = new OffsetExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 4, WebdaQLParserParser.RULE_offsetExpression); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 43; + this.match(WebdaQLParserParser.OFFSET); + this.state = 44; + this.stringLiteral(); + } + } 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 orderFieldExpression(): OrderFieldExpressionContext { + let _localctx: OrderFieldExpressionContext = new OrderFieldExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 6, WebdaQLParserParser.RULE_orderFieldExpression); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 46; + this.identifier(); + this.state = 48; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === WebdaQLParserParser.ASC || _la === WebdaQLParserParser.DESC) { + { + this.state = 47; + _la = this._input.LA(1); + if (!(_la === WebdaQLParserParser.ASC || _la === WebdaQLParserParser.DESC)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + } + } 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 orderExpression(): OrderExpressionContext { + let _localctx: OrderExpressionContext = new OrderExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 8, WebdaQLParserParser.RULE_orderExpression); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 50; + this.match(WebdaQLParserParser.ORDER_BY); + this.state = 51; + this.orderFieldExpression(); + this.state = 56; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === WebdaQLParserParser.COMMA) { + { + { + this.state = 52; + this.match(WebdaQLParserParser.COMMA); + this.state = 53; + this.orderFieldExpression(); + } + } + this.state = 58; + 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; + } + + public expression(): ExpressionContext; + public expression(_p: number): ExpressionContext; + // @RuleVersion(0) + public expression(_p?: number): ExpressionContext { + if (_p === undefined) { + _p = 0; + } + + let _parentctx: ParserRuleContext = this._ctx; + let _parentState: number = this.state; + let _localctx: ExpressionContext = new ExpressionContext(this._ctx, _parentState); + let _prevctx: ExpressionContext = _localctx; + let _startState: number = 10; + this.enterRecursionRule(_localctx, 10, WebdaQLParserParser.RULE_expression, _p); + let _la: number; + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 81; + this._errHandler.sync(this); + switch (this.interpreter.adaptivePredict(this._input, 6, this._ctx)) { + case 1: + { + _localctx = new LikeExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + + this.state = 60; + this.identifier(); + this.state = 61; + this.match(WebdaQLParserParser.LIKE); + this.state = 62; + this.stringLiteral(); + } + break; + + case 2: + { + _localctx = new InExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 64; + this.identifier(); + this.state = 65; + this.match(WebdaQLParserParser.IN); + this.state = 66; + this.setExpression(); + } + break; + + case 3: + { + _localctx = new ContainsExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 68; + this.identifier(); + this.state = 69; + this.match(WebdaQLParserParser.CONTAINS); + this.state = 70; + this.stringLiteral(); + } + break; + + case 4: + { + _localctx = new BinaryComparisonExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 72; + this.identifier(); + this.state = 73; + _la = this._input.LA(1); + if ( + !( + (_la & ~0x1f) === 0 && + ((1 << _la) & + ((1 << WebdaQLParserParser.EQUAL) | + (1 << WebdaQLParserParser.NOT_EQUAL) | + (1 << WebdaQLParserParser.GREATER) | + (1 << WebdaQLParserParser.GREATER_OR_EQUAL) | + (1 << WebdaQLParserParser.LESS) | + (1 << WebdaQLParserParser.LESS_OR_EQUAL))) !== + 0 + ) + ) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 74; + this.values(); + } + break; + + case 5: + { + _localctx = new SubExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 76; + this.match(WebdaQLParserParser.LR_BRACKET); + this.state = 77; + this.expression(0); + this.state = 78; + this.match(WebdaQLParserParser.RR_BRACKET); + } + break; + + case 6: + { + _localctx = new AtomExpressionContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 80; + this.atom(); + } + break; + } + this._ctx._stop = this._input.tryLT(-1); + this.state = 91; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 8, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + if (this._parseListeners != null) { + this.triggerExitRuleEvent(); + } + _prevctx = _localctx; + { + this.state = 89; + this._errHandler.sync(this); + switch (this.interpreter.adaptivePredict(this._input, 7, this._ctx)) { + case 1: + { + _localctx = new AndLogicExpressionContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, WebdaQLParserParser.RULE_expression); + this.state = 83; + if (!this.precpred(this._ctx, 4)) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 4)"); + } + this.state = 84; + this.match(WebdaQLParserParser.AND); + this.state = 85; + this.expression(5); + } + break; + + case 2: + { + _localctx = new OrLogicExpressionContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, WebdaQLParserParser.RULE_expression); + this.state = 86; + if (!this.precpred(this._ctx, 3)) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 3)"); + } + this.state = 87; + this.match(WebdaQLParserParser.OR); + this.state = 88; + this.expression(4); + } + break; + } + } + } + this.state = 93; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 8, 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.unrollRecursionContexts(_parentctx); + } + return _localctx; + } + // @RuleVersion(0) + public values(): ValuesContext { + let _localctx: ValuesContext = new ValuesContext(this._ctx, this.state); + this.enterRule(_localctx, 12, WebdaQLParserParser.RULE_values); + try { + this.state = 97; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case WebdaQLParserParser.TRUE: + case WebdaQLParserParser.FALSE: + _localctx = new BooleanAtomContext(_localctx); + this.enterOuterAlt(_localctx, 1); + { + this.state = 94; + this.booleanLiteral(); + } + break; + case WebdaQLParserParser.INTEGER_LITERAL: + _localctx = new IntegerAtomContext(_localctx); + this.enterOuterAlt(_localctx, 2); + { + this.state = 95; + this.integerLiteral(); + } + break; + case WebdaQLParserParser.DQUOTED_STRING_LITERAL: + case WebdaQLParserParser.SQUOTED_STRING_LITERAL: + _localctx = new StringAtomContext(_localctx); + this.enterOuterAlt(_localctx, 3); + { + this.state = 96; + this.stringLiteral(); + } + 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 atom(): AtomContext { + let _localctx: AtomContext = new AtomContext(this._ctx, this.state); + this.enterRule(_localctx, 14, WebdaQLParserParser.RULE_atom); + try { + this.state = 101; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case WebdaQLParserParser.TRUE: + case WebdaQLParserParser.FALSE: + case WebdaQLParserParser.DQUOTED_STRING_LITERAL: + case WebdaQLParserParser.SQUOTED_STRING_LITERAL: + case WebdaQLParserParser.INTEGER_LITERAL: + _localctx = new ValuesAtomContext(_localctx); + this.enterOuterAlt(_localctx, 1); + { + this.state = 99; + this.values(); + } + break; + case WebdaQLParserParser.IDENTIFIER: + case WebdaQLParserParser.IDENTIFIER_WITH_NUMBER: + _localctx = new IdentifierAtomContext(_localctx); + this.enterOuterAlt(_localctx, 2); + { + this.state = 100; + this.identifier(); + } + 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 identifier(): IdentifierContext { + let _localctx: IdentifierContext = new IdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 16, WebdaQLParserParser.RULE_identifier); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 103; + _la = this._input.LA(1); + if (!(_la === WebdaQLParserParser.IDENTIFIER || _la === WebdaQLParserParser.IDENTIFIER_WITH_NUMBER)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } 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 booleanLiteral(): BooleanLiteralContext { + let _localctx: BooleanLiteralContext = new BooleanLiteralContext(this._ctx, this.state); + this.enterRule(_localctx, 18, WebdaQLParserParser.RULE_booleanLiteral); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 105; + _la = this._input.LA(1); + if (!(_la === WebdaQLParserParser.TRUE || _la === WebdaQLParserParser.FALSE)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } 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 stringLiteral(): StringLiteralContext { + let _localctx: StringLiteralContext = new StringLiteralContext(this._ctx, this.state); + this.enterRule(_localctx, 20, WebdaQLParserParser.RULE_stringLiteral); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 107; + _la = this._input.LA(1); + if ( + !(_la === WebdaQLParserParser.DQUOTED_STRING_LITERAL || _la === WebdaQLParserParser.SQUOTED_STRING_LITERAL) + ) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } 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 integerLiteral(): IntegerLiteralContext { + let _localctx: IntegerLiteralContext = new IntegerLiteralContext(this._ctx, this.state); + this.enterRule(_localctx, 22, WebdaQLParserParser.RULE_integerLiteral); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 109; + this.match(WebdaQLParserParser.INTEGER_LITERAL); + } + } 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 setExpression(): SetExpressionContext { + let _localctx: SetExpressionContext = new SetExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 24, WebdaQLParserParser.RULE_setExpression); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 111; + this.match(WebdaQLParserParser.LR_SQ_BRACKET); + this.state = 112; + this.values(); + this.state = 117; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === WebdaQLParserParser.COMMA) { + { + { + this.state = 113; + this.match(WebdaQLParserParser.COMMA); + this.state = 114; + this.values(); + } + } + this.state = 119; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 120; + this.match(WebdaQLParserParser.RR_SQ_BRACKET); + } + } 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 sempred(_localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { + switch (ruleIndex) { + case 5: + return this.expression_sempred(_localctx as ExpressionContext, predIndex); + } + return true; + } + private expression_sempred(_localctx: ExpressionContext, predIndex: number): boolean { + switch (predIndex) { + case 0: + return this.precpred(this._ctx, 4); + + case 1: + return this.precpred(this._ctx, 3); + } + return true; + } + + public static readonly _serializedATN: string = + '\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03"}\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\x05\x02\x1E\n\x02\x03\x02\x05\x02!\n\x02\x03\x02\x05\x02" + + "$\n\x02\x03\x02\x05\x02'\n\x02\x03\x02\x03\x02\x03\x03\x03\x03\x03\x03" + + "\x03\x04\x03\x04\x03\x04\x03\x05\x03\x05\x05\x053\n\x05\x03\x06\x03\x06" + + "\x03\x06\x03\x06\x07\x069\n\x06\f\x06\x0E\x06<\v\x06\x03\x07\x03\x07\x03" + + "\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03" + + "\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03" + + "\x07\x03\x07\x05\x07T\n\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03" + + "\x07\x07\x07\\\n\x07\f\x07\x0E\x07_\v\x07\x03\b\x03\b\x03\b\x05\bd\n\b" + + "\x03\t\x03\t\x05\th\n\t\x03\n\x03\n\x03\v\x03\v\x03\f\x03\f\x03\r\x03" + + "\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x07\x0Ev\n\x0E\f\x0E\x0E\x0Ey\v\x0E" + + "\x03\x0E\x03\x0E\x03\x0E\x02\x02\x03\f\x0F\x02\x02\x04\x02\x06\x02\b\x02" + + "\n\x02\f\x02\x0E\x02\x10\x02\x12\x02\x14\x02\x16\x02\x18\x02\x1A\x02\x02" + + "\x07\x03\x02\x1B\x1C\x03\x02\r\x12\x03\x02 !\x03\x02\x16\x17\x03\x02\x1D" + + "\x1E\x02\x80\x02\x1D\x03\x02\x02\x02\x04*\x03\x02\x02\x02\x06-\x03\x02" + + "\x02\x02\b0\x03\x02\x02\x02\n4\x03\x02\x02\x02\fS\x03\x02\x02\x02\x0E" + + "c\x03\x02\x02\x02\x10g\x03\x02\x02\x02\x12i\x03\x02\x02\x02\x14k\x03\x02" + + "\x02\x02\x16m\x03\x02\x02\x02\x18o\x03\x02\x02\x02\x1Aq\x03\x02\x02\x02" + + "\x1C\x1E\x05\f\x07\x02\x1D\x1C\x03\x02\x02\x02\x1D\x1E\x03\x02\x02\x02" + + "\x1E \x03\x02\x02\x02\x1F!\x05\n\x06\x02 \x1F\x03\x02\x02\x02 !\x03\x02" + + '\x02\x02!#\x03\x02\x02\x02"$\x05\x04\x03\x02#"\x03\x02\x02\x02#$\x03' + + "\x02\x02\x02$&\x03\x02\x02\x02%'\x05\x06\x04\x02&%\x03\x02\x02\x02&'" + + "\x03\x02\x02\x02'(\x03\x02\x02\x02()\x07\x02\x02\x03)\x03\x03\x02\x02" + + "\x02*+\x07\x18\x02\x02+,\x05\x18\r\x02,\x05\x03\x02\x02\x02-.\x07\x19" + + "\x02\x02./\x05\x16\f\x02/\x07\x03\x02\x02\x0202\x05\x12\n\x0213\t\x02" + + "\x02\x0221\x03\x02\x02\x0223\x03\x02\x02\x023\t\x03\x02\x02\x0245\x07" + + "\x1A\x02\x025:\x05\b\x05\x0267\x07\x06\x02\x0279\x05\b\x05\x0286\x03\x02" + + "\x02\x029<\x03\x02\x02\x02:8\x03\x02\x02\x02:;\x03\x02\x02\x02;\v\x03" + + "\x02\x02\x02<:\x03\x02\x02\x02=>\b\x07\x01\x02>?\x05\x12\n\x02?@\x07\x13" + + "\x02\x02@A\x05\x16\f\x02AT\x03\x02\x02\x02BC\x05\x12\n\x02CD\x07\x14\x02" + + "\x02DE\x05\x1A\x0E\x02ET\x03\x02\x02\x02FG\x05\x12\n\x02GH\x07\x15\x02" + + "\x02HI\x05\x16\f\x02IT\x03\x02\x02\x02JK\x05\x12\n\x02KL\t\x03\x02\x02" + + "LM\x05\x0E\b\x02MT\x03\x02\x02\x02NO\x07\x04\x02\x02OP\x05\f\x07\x02P" + + "Q\x07\x05\x02\x02QT\x03\x02\x02\x02RT\x05\x10\t\x02S=\x03\x02\x02\x02" + + "SB\x03\x02\x02\x02SF\x03\x02\x02\x02SJ\x03\x02\x02\x02SN\x03\x02\x02\x02" + + "SR\x03\x02\x02\x02T]\x03\x02\x02\x02UV\f\x06\x02\x02VW\x07\v\x02\x02W" + + "\\\x05\f\x07\x07XY\f\x05\x02\x02YZ\x07\f\x02\x02Z\\\x05\f\x07\x06[U\x03" + + "\x02\x02\x02[X\x03\x02\x02\x02\\_\x03\x02\x02\x02][\x03\x02\x02\x02]^" + + "\x03\x02\x02\x02^\r\x03\x02\x02\x02_]\x03\x02\x02\x02`d\x05\x14\v\x02" + + "ad\x05\x18\r\x02bd\x05\x16\f\x02c`\x03\x02\x02\x02ca\x03\x02\x02\x02c" + + "b\x03\x02\x02\x02d\x0F\x03\x02\x02\x02eh\x05\x0E\b\x02fh\x05\x12\n\x02" + + "ge\x03\x02\x02\x02gf\x03\x02\x02\x02h\x11\x03\x02\x02\x02ij\t\x04\x02" + + "\x02j\x13\x03\x02\x02\x02kl\t\x05\x02\x02l\x15\x03\x02\x02\x02mn\t\x06" + + "\x02\x02n\x17\x03\x02\x02\x02op\x07\x1F\x02\x02p\x19\x03\x02\x02\x02q" + + "r\x07\t\x02\x02rw\x05\x0E\b\x02st\x07\x06\x02\x02tv\x05\x0E\b\x02us\x03" + + "\x02\x02\x02vy\x03\x02\x02\x02wu\x03\x02\x02\x02wx\x03\x02\x02\x02xz\x03" + + "\x02\x02\x02yw\x03\x02\x02\x02z{\x07\n\x02\x02{\x1B\x03\x02\x02\x02\x0E" + + "\x1D #&2:S[]cgw"; + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!WebdaQLParserParser.__ATN) { + WebdaQLParserParser.__ATN = new ATNDeserializer().deserialize( + Utils.toCharArray(WebdaQLParserParser._serializedATN) + ); + } + + return WebdaQLParserParser.__ATN; + } +} + +export class WebdaqlContext extends ParserRuleContext { + public EOF(): TerminalNode { + return this.getToken(WebdaQLParserParser.EOF, 0); + } + public expression(): ExpressionContext | undefined { + return this.tryGetRuleContext(0, ExpressionContext); + } + public orderExpression(): OrderExpressionContext | undefined { + return this.tryGetRuleContext(0, OrderExpressionContext); + } + public limitExpression(): LimitExpressionContext | undefined { + return this.tryGetRuleContext(0, LimitExpressionContext); + } + public offsetExpression(): OffsetExpressionContext | undefined { + return this.tryGetRuleContext(0, OffsetExpressionContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_webdaql; + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterWebdaql) { + listener.enterWebdaql(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitWebdaql) { + listener.exitWebdaql(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitWebdaql) { + return visitor.visitWebdaql(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class LimitExpressionContext extends ParserRuleContext { + public LIMIT(): TerminalNode { + return this.getToken(WebdaQLParserParser.LIMIT, 0); + } + public integerLiteral(): IntegerLiteralContext { + return this.getRuleContext(0, IntegerLiteralContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_limitExpression; + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterLimitExpression) { + listener.enterLimitExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitLimitExpression) { + listener.exitLimitExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitLimitExpression) { + return visitor.visitLimitExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class OffsetExpressionContext extends ParserRuleContext { + public OFFSET(): TerminalNode { + return this.getToken(WebdaQLParserParser.OFFSET, 0); + } + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_offsetExpression; + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterOffsetExpression) { + listener.enterOffsetExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitOffsetExpression) { + listener.exitOffsetExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitOffsetExpression) { + return visitor.visitOffsetExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class OrderFieldExpressionContext extends ParserRuleContext { + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); + } + public ASC(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.ASC, 0); + } + public DESC(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.DESC, 0); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_orderFieldExpression; + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterOrderFieldExpression) { + listener.enterOrderFieldExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitOrderFieldExpression) { + listener.exitOrderFieldExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitOrderFieldExpression) { + return visitor.visitOrderFieldExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class OrderExpressionContext extends ParserRuleContext { + public ORDER_BY(): TerminalNode { + return this.getToken(WebdaQLParserParser.ORDER_BY, 0); + } + public orderFieldExpression(): OrderFieldExpressionContext[]; + public orderFieldExpression(i: number): OrderFieldExpressionContext; + public orderFieldExpression(i?: number): OrderFieldExpressionContext | OrderFieldExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(OrderFieldExpressionContext); + } else { + return this.getRuleContext(i, OrderFieldExpressionContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(WebdaQLParserParser.COMMA); + } else { + return this.getToken(WebdaQLParserParser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_orderExpression; + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterOrderExpression) { + listener.enterOrderExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitOrderExpression) { + listener.exitOrderExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitOrderExpression) { + return visitor.visitOrderExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class ExpressionContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_expression; + } + public copyFrom(ctx: ExpressionContext): void { + super.copyFrom(ctx); + } +} +export class LikeExpressionContext extends ExpressionContext { + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); + } + public LIKE(): TerminalNode { + return this.getToken(WebdaQLParserParser.LIKE, 0); + } + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterLikeExpression) { + listener.enterLikeExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitLikeExpression) { + listener.exitLikeExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitLikeExpression) { + return visitor.visitLikeExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class InExpressionContext extends ExpressionContext { + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); + } + public IN(): TerminalNode { + return this.getToken(WebdaQLParserParser.IN, 0); + } + public setExpression(): SetExpressionContext { + return this.getRuleContext(0, SetExpressionContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterInExpression) { + listener.enterInExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitInExpression) { + listener.exitInExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitInExpression) { + return visitor.visitInExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class ContainsExpressionContext extends ExpressionContext { + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); + } + public CONTAINS(): TerminalNode { + return this.getToken(WebdaQLParserParser.CONTAINS, 0); + } + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterContainsExpression) { + listener.enterContainsExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitContainsExpression) { + listener.exitContainsExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitContainsExpression) { + return visitor.visitContainsExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class BinaryComparisonExpressionContext extends ExpressionContext { + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); + } + public values(): ValuesContext { + return this.getRuleContext(0, ValuesContext); + } + public EQUAL(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.EQUAL, 0); + } + public NOT_EQUAL(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.NOT_EQUAL, 0); + } + public GREATER_OR_EQUAL(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.GREATER_OR_EQUAL, 0); + } + public LESS_OR_EQUAL(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.LESS_OR_EQUAL, 0); + } + public LESS(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.LESS, 0); + } + public GREATER(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.GREATER, 0); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterBinaryComparisonExpression) { + listener.enterBinaryComparisonExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitBinaryComparisonExpression) { + listener.exitBinaryComparisonExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitBinaryComparisonExpression) { + return visitor.visitBinaryComparisonExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class AndLogicExpressionContext extends ExpressionContext { + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public AND(): TerminalNode { + return this.getToken(WebdaQLParserParser.AND, 0); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterAndLogicExpression) { + listener.enterAndLogicExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitAndLogicExpression) { + listener.exitAndLogicExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitAndLogicExpression) { + return visitor.visitAndLogicExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class OrLogicExpressionContext extends ExpressionContext { + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public OR(): TerminalNode { + return this.getToken(WebdaQLParserParser.OR, 0); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterOrLogicExpression) { + listener.enterOrLogicExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitOrLogicExpression) { + listener.exitOrLogicExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitOrLogicExpression) { + return visitor.visitOrLogicExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class SubExpressionContext extends ExpressionContext { + public LR_BRACKET(): TerminalNode { + return this.getToken(WebdaQLParserParser.LR_BRACKET, 0); + } + public expression(): ExpressionContext { + return this.getRuleContext(0, ExpressionContext); + } + public RR_BRACKET(): TerminalNode { + return this.getToken(WebdaQLParserParser.RR_BRACKET, 0); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterSubExpression) { + listener.enterSubExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitSubExpression) { + listener.exitSubExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitSubExpression) { + return visitor.visitSubExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class AtomExpressionContext extends ExpressionContext { + public atom(): AtomContext { + return this.getRuleContext(0, AtomContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterAtomExpression) { + listener.enterAtomExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitAtomExpression) { + listener.exitAtomExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitAtomExpression) { + return visitor.visitAtomExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class ValuesContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_values; + } + public copyFrom(ctx: ValuesContext): void { + super.copyFrom(ctx); + } +} +export class BooleanAtomContext extends ValuesContext { + public booleanLiteral(): BooleanLiteralContext { + return this.getRuleContext(0, BooleanLiteralContext); + } + constructor(ctx: ValuesContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterBooleanAtom) { + listener.enterBooleanAtom(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitBooleanAtom) { + listener.exitBooleanAtom(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitBooleanAtom) { + return visitor.visitBooleanAtom(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class IntegerAtomContext extends ValuesContext { + public integerLiteral(): IntegerLiteralContext { + return this.getRuleContext(0, IntegerLiteralContext); + } + constructor(ctx: ValuesContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterIntegerAtom) { + listener.enterIntegerAtom(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitIntegerAtom) { + listener.exitIntegerAtom(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitIntegerAtom) { + return visitor.visitIntegerAtom(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class StringAtomContext extends ValuesContext { + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext); + } + constructor(ctx: ValuesContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterStringAtom) { + listener.enterStringAtom(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitStringAtom) { + listener.exitStringAtom(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitStringAtom) { + return visitor.visitStringAtom(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class AtomContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_atom; + } + public copyFrom(ctx: AtomContext): void { + super.copyFrom(ctx); + } +} +export class ValuesAtomContext extends AtomContext { + public values(): ValuesContext { + return this.getRuleContext(0, ValuesContext); + } + constructor(ctx: AtomContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterValuesAtom) { + listener.enterValuesAtom(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitValuesAtom) { + listener.exitValuesAtom(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitValuesAtom) { + return visitor.visitValuesAtom(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class IdentifierAtomContext extends AtomContext { + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); + } + constructor(ctx: AtomContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterIdentifierAtom) { + listener.enterIdentifierAtom(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitIdentifierAtom) { + listener.exitIdentifierAtom(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitIdentifierAtom) { + return visitor.visitIdentifierAtom(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class IdentifierContext extends ParserRuleContext { + public IDENTIFIER(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.IDENTIFIER, 0); + } + public IDENTIFIER_WITH_NUMBER(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.IDENTIFIER_WITH_NUMBER, 0); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_identifier; + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterIdentifier) { + listener.enterIdentifier(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitIdentifier) { + listener.exitIdentifier(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitIdentifier) { + return visitor.visitIdentifier(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class BooleanLiteralContext extends ParserRuleContext { + public TRUE(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.TRUE, 0); + } + public FALSE(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.FALSE, 0); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_booleanLiteral; + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterBooleanLiteral) { + listener.enterBooleanLiteral(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitBooleanLiteral) { + listener.exitBooleanLiteral(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitBooleanLiteral) { + return visitor.visitBooleanLiteral(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class StringLiteralContext extends ParserRuleContext { + public DQUOTED_STRING_LITERAL(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.DQUOTED_STRING_LITERAL, 0); + } + public SQUOTED_STRING_LITERAL(): TerminalNode | undefined { + return this.tryGetToken(WebdaQLParserParser.SQUOTED_STRING_LITERAL, 0); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_stringLiteral; + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterStringLiteral) { + listener.enterStringLiteral(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitStringLiteral) { + listener.exitStringLiteral(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitStringLiteral) { + return visitor.visitStringLiteral(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class IntegerLiteralContext extends ParserRuleContext { + public INTEGER_LITERAL(): TerminalNode { + return this.getToken(WebdaQLParserParser.INTEGER_LITERAL, 0); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_integerLiteral; + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterIntegerLiteral) { + listener.enterIntegerLiteral(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitIntegerLiteral) { + listener.exitIntegerLiteral(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitIntegerLiteral) { + return visitor.visitIntegerLiteral(this); + } else { + return visitor.visitChildren(this); + } + } +} + +export class SetExpressionContext extends ParserRuleContext { + public LR_SQ_BRACKET(): TerminalNode { + return this.getToken(WebdaQLParserParser.LR_SQ_BRACKET, 0); + } + public values(): ValuesContext[]; + public values(i: number): ValuesContext; + public values(i?: number): ValuesContext | ValuesContext[] { + if (i === undefined) { + return this.getRuleContexts(ValuesContext); + } else { + return this.getRuleContext(i, ValuesContext); + } + } + public RR_SQ_BRACKET(): TerminalNode { + return this.getToken(WebdaQLParserParser.RR_SQ_BRACKET, 0); + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(WebdaQLParserParser.COMMA); + } else { + return this.getToken(WebdaQLParserParser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { + return WebdaQLParserParser.RULE_setExpression; + } + // @Override + public enterRule(listener: WebdaQLParserListener): void { + if (listener.enterSetExpression) { + listener.enterSetExpression(this); + } + } + // @Override + public exitRule(listener: WebdaQLParserListener): void { + if (listener.exitSetExpression) { + listener.exitSetExpression(this); + } + } + // @Override + public accept(visitor: WebdaQLParserVisitor): Result { + if (visitor.visitSetExpression) { + return visitor.visitSetExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} diff --git a/packages/ql/src/WebdaQLParserVisitor.ts b/packages/ql/src/WebdaQLParserVisitor.ts new file mode 100644 index 000000000..d41392d8b --- /dev/null +++ b/packages/ql/src/WebdaQLParserVisitor.ts @@ -0,0 +1,237 @@ +// Generated from src/stores/webdaql/WebdaQLParser.g4 by ANTLR 4.9.0-SNAPSHOT + +import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor"; + +import { + AndLogicExpressionContext, + AtomContext, + AtomExpressionContext, + BinaryComparisonExpressionContext, + BooleanAtomContext, + BooleanLiteralContext, + ContainsExpressionContext, + ExpressionContext, + IdentifierAtomContext, + IdentifierContext, + InExpressionContext, + IntegerAtomContext, + IntegerLiteralContext, + LikeExpressionContext, + LimitExpressionContext, + OffsetExpressionContext, + OrderExpressionContext, + OrderFieldExpressionContext, + OrLogicExpressionContext, + SetExpressionContext, + StringAtomContext, + StringLiteralContext, + SubExpressionContext, + ValuesAtomContext, + ValuesContext, + WebdaqlContext +} from "./WebdaQLParserParser"; + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by `WebdaQLParserParser`. + * + * @param The return type of the visit operation. Use `void` for + * operations with no return type. + */ +export interface WebdaQLParserVisitor extends ParseTreeVisitor { + /** + * Visit a parse tree produced by the `likeExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitLikeExpression?: (ctx: LikeExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `inExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitInExpression?: (ctx: InExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `containsExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitContainsExpression?: (ctx: ContainsExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `binaryComparisonExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitBinaryComparisonExpression?: (ctx: BinaryComparisonExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `andLogicExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitAndLogicExpression?: (ctx: AndLogicExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `orLogicExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitOrLogicExpression?: (ctx: OrLogicExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `subExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitSubExpression?: (ctx: SubExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `atomExpression` + * labeled alternative in `WebdaQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitAtomExpression?: (ctx: AtomExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `booleanAtom` + * labeled alternative in `WebdaQLParserParser.values`. + * @param ctx the parse tree + * @return the visitor result + */ + visitBooleanAtom?: (ctx: BooleanAtomContext) => Result; + + /** + * Visit a parse tree produced by the `integerAtom` + * labeled alternative in `WebdaQLParserParser.values`. + * @param ctx the parse tree + * @return the visitor result + */ + visitIntegerAtom?: (ctx: IntegerAtomContext) => Result; + + /** + * Visit a parse tree produced by the `stringAtom` + * labeled alternative in `WebdaQLParserParser.values`. + * @param ctx the parse tree + * @return the visitor result + */ + visitStringAtom?: (ctx: StringAtomContext) => Result; + + /** + * Visit a parse tree produced by the `valuesAtom` + * labeled alternative in `WebdaQLParserParser.atom`. + * @param ctx the parse tree + * @return the visitor result + */ + visitValuesAtom?: (ctx: ValuesAtomContext) => Result; + + /** + * Visit a parse tree produced by the `identifierAtom` + * labeled alternative in `WebdaQLParserParser.atom`. + * @param ctx the parse tree + * @return the visitor result + */ + visitIdentifierAtom?: (ctx: IdentifierAtomContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.webdaql`. + * @param ctx the parse tree + * @return the visitor result + */ + visitWebdaql?: (ctx: WebdaqlContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.limitExpression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitLimitExpression?: (ctx: LimitExpressionContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.offsetExpression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitOffsetExpression?: (ctx: OffsetExpressionContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.orderFieldExpression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitOrderFieldExpression?: (ctx: OrderFieldExpressionContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.orderExpression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitOrderExpression?: (ctx: OrderExpressionContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitExpression?: (ctx: ExpressionContext) => Result; + + /** + * Visit a parse tree produced by the `values` + * labeled alternative in `WebdaQLParserParser.expressionexpressionexpressionexpressionexpressionexpressionexpressionexpressionvaluesvaluesvaluesatomatom`. + * @param ctx the parse tree + * @return the visitor result + */ + visitValues?: (ctx: ValuesContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.atom`. + * @param ctx the parse tree + * @return the visitor result + */ + visitAtom?: (ctx: AtomContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.identifier`. + * @param ctx the parse tree + * @return the visitor result + */ + visitIdentifier?: (ctx: IdentifierContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.booleanLiteral`. + * @param ctx the parse tree + * @return the visitor result + */ + visitBooleanLiteral?: (ctx: BooleanLiteralContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.stringLiteral`. + * @param ctx the parse tree + * @return the visitor result + */ + visitStringLiteral?: (ctx: StringLiteralContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.integerLiteral`. + * @param ctx the parse tree + * @return the visitor result + */ + visitIntegerLiteral?: (ctx: IntegerLiteralContext) => Result; + + /** + * Visit a parse tree produced by `WebdaQLParserParser.setExpression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitSetExpression?: (ctx: SetExpressionContext) => Result; +} diff --git a/packages/ql/src/query.spec.ts b/packages/ql/src/query.spec.ts new file mode 100644 index 000000000..324beec21 --- /dev/null +++ b/packages/ql/src/query.spec.ts @@ -0,0 +1,213 @@ +import { suite, test } from "@testdeck/mocha"; +import * as assert from "assert"; +import * as WebdaQL from "./query"; + +const targets = [ + { + test: { + attr1: "plop" + }, + attr2: "OK", + attr3: 13, + attr4: "ok", + attr5: ["test", "plip"] + } +]; + +@suite +class QueryTest { + @test + dev() { + let queryMap = { + "a = 1 AND b = 2 AND c = 3 AND d = 4": "a = 1 AND b = 2 AND c = 3 AND d = 4", + "a = 1 AND (b = 2 AND c = 3) AND d = 4": "a = 1 AND b = 2 AND c = 3 AND d = 4", + "a = 1 AND ((b = 2 AND c = 3) AND d = 4)": "a = 1 AND b = 2 AND c = 3 AND d = 4", + "a = 1 AND ((b = 2 OR c = 3) AND d = 4)": "a = 1 AND ( b = 2 OR c = 3 ) AND d = 4", + "a = 1 AND (b = 2) AND c = 3 AND d = 4": "a = 1 AND b = 2 AND c = 3 AND d = 4", + "a = 1 OR b = 2 OR c = 3 OR d = 4": "a = 1 OR b = 2 OR c = 3 OR d = 4", + 'a = 1 AND (b = 2 OR c = 3) AND d = "plop"': 'a = 1 AND ( b = 2 OR c = 3 ) AND d = "plop"', + "a = 1 OR b = TRUE AND c = FALSE OR d = 'plop'": 'a = 1 OR ( b = TRUE AND c = FALSE ) OR d = "plop"', + "test.attr1 = 'plop'": true, + "test.attr1 = 'plop2'": false, + "test.attr1 = 'plop' AND attr2 IN ['TEST', TRUE, 3]": 'test.attr1 = "plop" AND attr2 IN ["TEST", TRUE, 3]', + "(test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK']) OR attr3 <= 12": null, + "test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK'] OR attr3 <= 12 AND attr4 = 'ok'": null, + "test.attr1 = 'plop' AND (attr2 IN ['TEST', 'OK'] OR attr3 <= 12) AND attr4 = 'ok'": null, + "test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK'] AND attr3 <= 12 AND attr4 = 'ok'": null, + "a = 1 OR b = 2": null, + "a = 1 AND b = 2 OR c = 3 AND d = 4": null, + "a = 1 AND b = 2 OR c = 3 AND d = 4 OR e = 5 AND f = 6": null, + "a = 1 AND (b = 2 OR (c = 3 AND (d = 4 OR e = 5))) AND f = 6": null, + "": true, + 'test.attr1 LIKE "pl_p"': true, + 'test.attr1 LIKE "pl__p"': false, + 'attr3 LIKE "1_"': true, + "attr3 >= 12": true, + "attr3 <= 12": false, + "attr3 != 12": true, + "attr3 > 12": true, + "attr3 < 13": false, + "attr5 CONTAINS 'test'": true, + "attr5 CONTAINS 'test2'": false, + 'test.attr1 LIKE "pl%"': true, + "a = 1 AND b=2 OR a=1 AND b=3": "( a = 1 AND b = 2 ) OR ( a = 1 AND b = 3 )", // TODO Might want to auto-simplify to a = 1 AND b IN [2,3] + "a = 1 ORDER BY a": null, + "a = 1 ORDER BY a DESC, b ASC": null, + "(attr3 >= 12)": true + }; + for (let query in queryMap) { + const validator = new WebdaQL.QueryValidator(query); + assert.strictEqual(validator.displayTree().replace(/\s/g, ""), query.replace(/\s/g, "")); + if (typeof queryMap[query] === "string") { + assert.strictEqual( + validator.getExpression().toString(), + queryMap[query], + `Failed optimization of query: ${query} => ${validator.getExpression().toString()}` + ); + } else if (typeof queryMap[query] === "boolean") { + assert.strictEqual(validator.eval(targets[0]), queryMap[query], `Failed query: ${query}`); + } else { + console.log("QUERY", query); + console.log("OPTIMIZED QUERY", validator.getExpression().toString(), "=>", validator.eval(targets[0])); + } + } + } + + @test + prependQuery() { + assert.strictEqual(WebdaQL.PrependCondition("", "test='plop'"), "test='plop'"); + assert.strictEqual(WebdaQL.PrependCondition("test='plop'", ""), "test='plop'"); + assert.strictEqual(WebdaQL.PrependCondition("test='plip'", "test='plop'"), "(test='plop') AND (test='plip')"); + assert.strictEqual(WebdaQL.PrependCondition("ORDER BY test", "test='plop'"), "test='plop' ORDER BY test"); + assert.strictEqual( + WebdaQL.PrependCondition("ORDER BY test LIMIT 100", "test='plop'"), + "test='plop' ORDER BY test LIMIT 100" + ); + assert.strictEqual( + WebdaQL.PrependCondition("ORDER BY test DESC LIMIT 100", "test='plop'"), + "test='plop' ORDER BY test DESC LIMIT 100" + ); + assert.strictEqual( + WebdaQL.PrependCondition("test='plip' ORDER BY test ASC LIMIT 100", "test='plop'"), + "(test='plop') AND (test='plip') ORDER BY test ASC LIMIT 100" + ); + assert.strictEqual( + WebdaQL.PrependCondition("test='plip' LIMIT 100", "test='plop'"), + "(test='plop') AND (test='plip') LIMIT 100" + ); + assert.strictEqual( + WebdaQL.PrependCondition("test='plip' OFFSET 'test'", "test='plop'"), + "(test='plop') AND (test='plip') OFFSET 'test'" + ); + assert.strictEqual( + WebdaQL.PrependCondition("test='ORDER BY plop' OFFSET 'test'", "test='plop'"), + "(test='plop') AND (test='ORDER BY plop') OFFSET 'test'" + ); + assert.strictEqual( + WebdaQL.PrependCondition('test="ORDER BY plop" LIMIT 100', "test='plop'"), + "(test='plop') AND (test=\"ORDER BY plop\") LIMIT 100" + ); + } + + @test + likeRegexp() { + assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("test"), /test/); + assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("t_est"), /t.{1}est/); + assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("_test"), /.{1}test/); + assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("t\\_est"), /t_est/); + + assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("t%est"), /t.*est/); + assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("%te?st"), /.*te\?st/); + assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("t\\%est"), /t%est/); + assert.deepStrictEqual(WebdaQL.ComparisonExpression.likeToRegex("t\\eest"), /t\\est/); + } + + @test + simple() { + assert.strictEqual(new WebdaQL.QueryValidator("test.attr1 = 'plop'").eval(targets[0]), true); + assert.strictEqual(new WebdaQL.QueryValidator("test.attr1 = 'plop2'").eval(targets[0]), false); + } + + @test + contains() { + assert.strictEqual(new WebdaQL.QueryValidator("test.attr1 CONTAINS 'plop2'").eval(targets[0]), false); + } + + @test + andQuery() { + assert.strictEqual( + new WebdaQL.QueryValidator("test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK']").eval(targets[0]), + true + ); + } + + @test + orQuery() { + new WebdaQL.QueryValidator("(test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK']) OR attr3 <= 12").eval(targets[0]); + } + + @test + multipleQuery() { + new WebdaQL.QueryValidator("test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK'] OR attr3 <= 12 AND attr4 = 'ok'").eval( + targets[0] + ); + new WebdaQL.QueryValidator("test.attr1 = 'plop' AND attr2 IN ['TEST', 'OK'] AND attr3 <= 12 AND attr4 = 'ok'").eval( + targets[0] + ); + } + + @test + unsanitize() { + assert.strictEqual(WebdaQL.unsanitize("test.attr1 < 12 AND attr2 > 13"), "test.attr1 < 12 AND attr2 > 13"); + } + + @test + limitOffset() { + let val = new WebdaQL.QueryValidator('LIMIT 10 OFFSET "pl"'); + // getQuery + val.getQuery(); + assert.strictEqual(val.getLimit(), 10); + assert.strictEqual(val.getOffset(), "pl"); + val = new WebdaQL.QueryValidator('OFFSET "pl2"'); + assert.strictEqual(val.getLimit(), 1000); + assert.strictEqual(val.getOffset(), "pl2"); + } + + @test + badQuery() { + assert.throws(() => new WebdaQL.QueryValidator("test lo 'plop'").eval(targets[0])); + } + + @test + setters() { + let target: any = {}; + new WebdaQL.SetterValidator('i = 10 AND j = "12"').eval(target); + assert.strictEqual(target.i, 10); + assert.strictEqual(target.j, "12"); + target = {}; + new WebdaQL.SetterValidator("").eval(target); + assert.strictEqual(Object.keys(target).length, 0); + new WebdaQL.SetterValidator('test.i = 10 AND j.k.l = "12"').eval(target); + assert.strictEqual(target.test.i, 10); + assert.strictEqual(target.j.k.l, "12"); + target = {}; + new WebdaQL.SetterValidator("test.__proto__.test = 10").eval(target); + assert.strictEqual(target.__proto__.test, undefined); + assert.throws(() => new WebdaQL.SetterValidator('i = 10 OR j = "12"').eval(target), SyntaxError); + assert.throws(() => new WebdaQL.SetterValidator('i > 10 AND j = "12"').eval(target), SyntaxError); + } + + @test + partialValidator() { + let validator = new WebdaQL.PartialValidator("attr1 = 'plop' AND attr2 = 'ok'"); + assert.ok(validator.eval({ attr1: "plop" })); + assert.ok(validator.wasPartialMatch()); + assert.ok(!validator.eval({ attr1: "plop" }, false)); + assert.ok(!validator.eval({ attr1: "plop2" })); + assert.ok( + new WebdaQL.PartialValidator( + "attr1 = 'plop' AND attr2 LIKE '?ok' AND attr3 IN ['test'] AND attr4 CONTAINS 'plop'" + ).eval({ attr1: "plop" }) + ); + } +} diff --git a/packages/ql/src/query.ts b/packages/ql/src/query.ts new file mode 100644 index 000000000..01280e507 --- /dev/null +++ b/packages/ql/src/query.ts @@ -0,0 +1,819 @@ +import { CharStreams, CommonTokenStream, RecognitionException, Recognizer, Token } from "antlr4ts"; +import { AbstractParseTreeVisitor, ParseTree, TerminalNode } from "antlr4ts/tree/index.js"; +import { WebdaQLLexer } from "./WebdaQLLexer"; +import { + AndLogicExpressionContext, + BinaryComparisonExpressionContext, + BooleanLiteralContext, + ContainsExpressionContext, + InExpressionContext, + IntegerLiteralContext, + LikeExpressionContext, + LimitExpressionContext, + OffsetExpressionContext, + OrLogicExpressionContext, + OrderExpressionContext, + OrderFieldExpressionContext, + SetExpressionContext, + StringLiteralContext, + SubExpressionContext, + WebdaQLParserParser, + WebdaqlContext +} from "./WebdaQLParserParser"; +import { WebdaQLParserVisitor } from "./WebdaQLParserVisitor"; + +type value = boolean | string | number; + +/** + * Meta Query Language + * + * + */ + +export interface OrderBy { + field: string; + direction: "ASC" | "DESC"; +} + +export function PrependCondition(query: string = "", condition?: string): string { + if (!condition) { + return query; + } + query = query.trim(); + // Find the right part of the query this regex will always be true as all part are optional + const rightPart = query.match( + /(?ORDER BY ([a-zA-Z0-9\._]+( DESC| ASC)?,?)+ ?)?(?OFFSET ["']\w+["'] ?)?(?LIMIT \d+)?$/ + ); + // Remove it if found + query = query.substring(0, query.length - rightPart[0].length).trim(); + // Add the condition to the query now + if (!query) { + query = condition; + } else { + query = `(${condition}) AND (${query})`; + } + // Re-add the right part + if (rightPart[0].length) { + query += " " + rightPart[0]; + } + return query; +} +/** + * Create Expression based on the parsed token + * + * Expression allow to optimize and split between Query and Filter + */ +export class ExpressionBuilder extends AbstractParseTreeVisitor implements WebdaQLParserVisitor { + /** + * Contain the parsed limit + */ + limit: number; + /** + * Contain the parsed offset + */ + offset: string; + orderBy: OrderBy[]; + + /** + * Default result for the override + * @returns + */ + protected defaultResult(): Query { + // An empty AND return true + return { + filter: new AndExpression([]) + }; + } + + /** + * Get offset + * @returns + */ + getOffset(): string { + return this.offset; + } + + /** + * Get limit + * @returns + */ + getLimit(): number { + return this.limit; + } + + /** + * Read the limit + * @param ctx + */ + visitLimitExpression(ctx: LimitExpressionContext) { + this.limit = this.visitIntegerLiteral(ctx.getChild(1)); + } + + /** + * Read the offset if provided + * @param ctx + */ + visitOffsetExpression(ctx: OffsetExpressionContext) { + this.offset = this.visitStringLiteral(ctx.getChild(1)); + } + + /** + * Visit a order field expression + */ + visitOrderFieldExpression(ctx: OrderFieldExpressionContext): OrderBy { + return { + field: ctx.getChild(0).text, + direction: ctx.childCount > 1 ? ctx.getChild(1).text : "ASC" + }; + } + + /** + * Read the order by values + */ + visitOrderExpression(ctx: OrderExpressionContext): void { + this.orderBy = ctx.children + ?.filter(c => c instanceof OrderFieldExpressionContext) + .map((c: OrderFieldExpressionContext) => this.visitOrderFieldExpression(c)); + } + + /** + * Return only AndExpression + * @param ctx + * @returns + */ + visitWebdaql(ctx: WebdaqlContext): Query { + if (ctx.childCount === 1) { + // An empty AND return true + return { + filter: new AndExpression([]) + }; + } + + // To parse offset and limit and order by + for (let i = 1; i < ctx.childCount - 1; i++) { + this.visit(ctx.getChild(i)); + } + // If the first element is a sub expression, it means we have a filter + if (ctx.getChild(0) instanceof SubExpressionContext) { + return { + filter: (this.visit(ctx.getChild(0).getChild(1))) || new AndExpression([]), + limit: this.limit, + continuationToken: this.offset, + orderBy: this.orderBy + }; + } + // Go down one level - if expression empty it means no expression were provided + return { + filter: (this.visit(ctx.getChild(0))) || new AndExpression([]), + limit: this.limit, + continuationToken: this.offset, + orderBy: this.orderBy + }; + } + + /** + * Simplify Logic expression and regroup them + * @param ctx + * @returns + */ + getComparison(ctx: AndLogicExpressionContext | OrLogicExpressionContext): any[] { + const res = []; + let [left, _, right] = ctx.children; + if (right instanceof SubExpressionContext) { + right = right.getChild(1); + } + if (left instanceof SubExpressionContext) { + left = left.getChild(1); + } + if (left instanceof ctx.constructor) { + res.push(...this.getComparison((left))); + } else { + res.push(left); + } + if (right instanceof ctx.constructor) { + res.push(...this.getComparison((right))); + } else { + res.push(right); + } + return res; + } + + /** + * Get the AndExpression, regrouping all the parameters + * + * By default the parser is doing a AND (b AND (c AND d)) creating 3 depth expressions + * This visitor simplify to a AND b AND c AND d with only one Expression + */ + visitAndLogicExpression(ctx: AndLogicExpressionContext): AndExpression { + return new AndExpression(this.getComparison(ctx).map(c => (this.visit(c)))); + } + + /** + * Implement the BinaryComparison with all methods managed + */ + visitBinaryComparisonExpression(ctx: BinaryComparisonExpressionContext) { + const [left, op, right] = ctx.children; + // @ts-ignore + return new ComparisonExpression(op.text, left.text, this.visit(right)); + } + + /** + * Visit each value of the [..., ..., ...] set + */ + visitSetExpression(ctx: SetExpressionContext): value[] { + return (ctx.children.filter((_i, id) => id % 2).map(c => this.visit(c))); + } + + /** + * a LIKE "%A?" + * @param ctx + * @returns + */ + visitLikeExpression(ctx: LikeExpressionContext) { + const [left, _, right] = ctx.children; + let value = (this.visit(right)); + return new ComparisonExpression("LIKE", left.text, value); + } + + /** + * Map the a IN ['b','c'] + */ + visitInExpression(ctx: InExpressionContext) { + const [left, _, right] = ctx.children; + let value = (this.visit(right)); + return new ComparisonExpression("IN", left.text, value); + } + + /** + * Map the a CONTAINS 'b' + */ + visitContainsExpression(ctx: ContainsExpressionContext) { + const [left, _, right] = ctx.children; + let value = (this.visit(right)); + return new ComparisonExpression("CONTAINS", left.text, value); + } + + /** + * Get the OrExpression, regrouping all the parameters + * + * By default the parser is doing a OR (b OR (c OR d)) creating 3 depth expressions + * This visitor simplify to a OR b OR c OR d with only one Expression + */ + visitOrLogicExpression(ctx: OrLogicExpressionContext) { + return new OrExpression(this.getComparison(ctx).map(c => (this.visit(c)))); + } + + /** + * Read the string literal (removing the simple or double bracket) + */ + visitStringLiteral(ctx: StringLiteralContext): string { + return ctx.text.substring(1, ctx.text.length - 1); + } + + /** + * Read the boolean literal + */ + visitBooleanLiteral(ctx: BooleanLiteralContext): boolean { + return "TRUE" === ctx.text; + } + + /** + * Read the number literal + */ + visitIntegerLiteral(ctx: IntegerLiteralContext): number { + return parseInt(ctx.text); + } +} + +/** + * Represent a full Query + */ +export interface Query { + /** + * Filtering part of the expression + */ + filter: Expression; + /** + * Limit value + */ + limit?: number; + /** + * Offset value + */ + continuationToken?: string; + /** + * Order by clause + */ + orderBy?: OrderBy[]; + /** + * Get the string representation of the query + */ + toString(): string; +} + +/** + * Represent the query expression or subset + */ +export abstract class Expression { + operator: T; + + constructor(operator: T) { + this.operator = operator; + } + + /** + * Evaluate the expression for the target object + * @param target to evaluate + */ + abstract eval(target: any): boolean; + /** + * Return the representation of the expression + * @param depth + */ + abstract toString(depth?: number): string; +} + +export type ComparisonOperator = "=" | "<=" | ">=" | "<" | ">" | "!=" | "LIKE" | "IN" | "CONTAINS"; +/** + * Comparison expression + */ +export class ComparisonExpression extends Expression { + /** + * Right side of the comparison + */ + value: value | value[]; + /** + * Attribute to read from the object (split by .) + */ + attribute: string[]; + /** + * + * @param operator of the expression + * @param attribute of the object to read + * @param value + */ + constructor(operator: T, attribute: string, value: value | any[]) { + super(operator); + this.value = value; + this.attribute = attribute.split("."); + } + + static likeToRegex(like: string): RegExp { + return new RegExp( + like + // Prevent common regexp chars + .replace(/\?/g, "\\?") + .replace(/\[/g, "\\[") + .replace(/\{/g, "\\{") + .replace(/\(/g, "\\(") + // Update % and _ to match regex version + .replace(/([^\\])_/g, "$1.{1}") + .replace(/^_/g, ".{1}") + .replace(/\\_/g, "_") + .replace(/([^\\])%/g, "$1.*") + .replace(/^%/g, ".*") + .replace(/\\%/g, "%") + // Replace backslash aswell + .replace(/\\([^?[{(])/g, "\\\\") + ); + } + + /** + * Read the value from the object + * + * @param target + * @returns + */ + static getAttributeValue(target: any, attribute: string[]): any { + let res = target; + for (let i = 0; res && i < attribute.length; i++) { + res = res[attribute[i]]; + } + return res; + } + + /** + * Set the value of the attribute based on the assignment + * + * If used as a Set expression + * @param target + */ + setAttributeValue(target: any) { + // Avoid alteration of prototype for security reason + if (this.attribute.includes("__proto__")) { + return; + } + if (this.operator === "=") { + let res = target; + for (let i = 0; res && i < this.attribute.length - 1; i++) { + res[this.attribute[i]] ??= {}; + res = res[this.attribute[i]]; + } + res[this.attribute[this.attribute.length - 1]] = this.value; + } + } + /** + * @override + */ + eval(target: any): boolean { + const left = ComparisonExpression.getAttributeValue(target, this.attribute); + switch (this.operator) { + case "=": + // ignore strong type on purpose + return left == this.value; + case "<=": + return left <= this.value; + case ">=": + return left >= this.value; + case "<": + return left < this.value; + case ">": + return left > this.value; + case "!=": + return left != this.value; + case "LIKE": + if (typeof left === "string") { + // Grammar definie value as stringLiteral + return left.match(ComparisonExpression.likeToRegex(this.value)) !== null; + } + return left.toString().match(ComparisonExpression.likeToRegex(this.value)) !== null; + case "IN": + return (this.value).includes(left); + case "CONTAINS": + if (Array.isArray(left)) { + return left.includes(this.value); + } + return false; + } + } + + /** + * Return a string represantation of a value + */ + toStringValue(value: value | value[]): string { + if (Array.isArray(value)) { + return `[${value.map(v => this.toStringValue(v)).join(", ")}]`; + } + switch (typeof value) { + case "string": + return `"${value}"`; + case "boolean": + return value.toString().toUpperCase(); + } + return value?.toString(); + } + + /** + * Allow subclass to create different display + */ + toStringAttribute() { + return this.attribute.join("."); + } + + /** + * Allow subclass to create different display + */ + toStringOperator() { + return this.operator; + } + + /** + * @override + */ + toString() { + return `${this.toStringAttribute()} ${this.toStringOperator()} ${this.toStringValue(this.value)}`; + } +} + +/** + * Abstract logic expression (AND|OR) + * + * Could add XOR in the future + */ +export abstract class LogicalExpression extends Expression { + /** + * Contains the members of the logical expression + */ + children: Expression[] = []; + /** + * + * @param operator + * @param children + */ + constructor(operator: T, children: Expression[]) { + super(operator); + this.children = children; + } + + /** + * @override + */ + toString(depth: number = 0) { + if (depth) { + return "( " + this.children.map(c => c.toString(depth + 1)).join(` ${this.operator} `) + " )"; + } + return this.children.map(c => c.toString(depth + 1)).join(` ${this.operator} `); + } +} + +/** + * AND Expression implementation + */ +export class AndExpression extends LogicalExpression<"AND"> { + /** + * @param children Expressions to use for AND + */ + constructor(children: Expression[]) { + super("AND", children); + } + + /** + * @override + */ + eval(target: any): boolean { + for (let child of this.children) { + if (!child.eval(target)) { + return false; + } + } + return true; + } +} + +/** + * OR Expression implementation + */ +export class OrExpression extends LogicalExpression<"OR"> { + /** + * @param children Expressions to use for OR + */ + constructor(children: Expression[]) { + super("OR", children); + } + + /** + * @override + */ + eval(target: any): boolean { + for (let child of this.children) { + if (child.eval(target)) { + return true; + } + } + return this.children.length === 0; + } +} + +/** + * + */ +export class QueryValidator { + protected lexer: WebdaQLLexer; + protected tree: WebdaqlContext; + protected query: Query; + protected builder: ExpressionBuilder; + + constructor( + protected sql: string, + builder: ExpressionBuilder = new ExpressionBuilder() + ) { + this.lexer = new WebdaQLLexer(CharStreams.fromString(sql || "")); + let tokenStream = new CommonTokenStream(this.lexer); + let parser = new WebdaQLParserParser(tokenStream); + parser.removeErrorListeners(); + parser.addErrorListener({ + syntaxError: ( + _recognizer: Recognizer, + _offendingSymbol: Token, + _line: number, + _charPositionInLine: number, + msg: string, + _e: RecognitionException + ) => { + throw new SyntaxError(`${msg} (Query: ${sql})`); + } + }); + // Parse the input, where `compilationUnit` is whatever entry point you defined + this.tree = parser.webdaql(); + this.builder = builder; + this.query = this.builder.visit(this.tree); + } + + /** + * Get offset + * @returns + */ + getOffset(): string { + return this.builder.getOffset() || ""; + } + + /** + * Get limit + * @returns + */ + getLimit(): number { + return this.builder.getLimit() || 1000; + } + + /** + * Get the expression by itself + * @returns + */ + getExpression(): Expression { + return this.query.filter; + } + + /** + * Retrieve parsed query + * @returns + */ + getQuery(): Query { + return { + ...this.query, + // Use displayTree to get the truely executed query + toString: () => this.displayTree() + }; + } + + /** + * Verify if a target fit the expression + * @param target + * @returns + */ + eval(target: any) { + return this.query.filter.eval(target); + } + + /** + * Display parse tree back as query + * @param tree + * @returns + */ + displayTree(tree: ParseTree = this.tree): string { + let res = ""; + for (let i = 0; i < tree.childCount; i++) { + const child = tree.getChild(i); + if (child instanceof TerminalNode) { + if (child.text === "") { + continue; + } + res += child.text.trim() + " "; + } else { + res += this.displayTree(child).trim() + " "; + } + } + return res; + } +} + +/** + * For now reuse same parser + */ +export class SetterValidator extends QueryValidator { + constructor(sql: string) { + super(sql); + // Do one empty run to raise any issue with disallowed expression + this.eval({}); + } + + eval(target: any): boolean { + if (this.query.filter) { + this.assign(target, this.query.filter); + } + return true; + } + + assign(target: any, expression: Expression) { + if (expression instanceof AndExpression) { + expression.children.forEach(c => this.assign(target, c)); + } else if (expression instanceof ComparisonExpression && (expression).operator === "=") { + expression.setAttributeValue(target); + } else { + throw new SyntaxError(`Set Expression can only contain And and assignment expression '='`); + } + } +} + +export class PartialValidator extends QueryValidator { + builder: PartialExpressionBuilder; + + constructor(query: string, builder: PartialExpressionBuilder = new PartialExpressionBuilder()) { + super(query, builder); + } + + /** + * Eval the query + * @param target + * @param partial + * @returns + */ + eval(target: any, partial: boolean = true): boolean { + this.builder.setPartial(partial); + this.builder.setPartialMatch(false); + return this.query.filter.eval(target); + } + + /** + * Return if the result ignored some fields + * @returns + */ + wasPartialMatch(): boolean { + return this.builder.partialMatch; + } +} + +export class PartialComparisonExpression< + T extends ComparisonOperator = ComparisonOperator +> extends ComparisonExpression { + constructor( + protected builder: PartialExpressionBuilder, + op: T, + attribute: string, + value: any + ) { + super(op, attribute, value); + } + + /** + * Override the eval to check if the attribute is present + * if not and we are in partial mode, return true + * + * @param target + * @returns + */ + eval(target: any): boolean { + if (this.builder.partial) { + const left = ComparisonExpression.getAttributeValue(target, this.attribute); + if (left === undefined) { + this.builder.setPartialMatch(true); + return true; + } + } + return super.eval(target); + } +} + +export class PartialExpressionBuilder extends ExpressionBuilder { + /** + * Enforce the partial mode + */ + partial: boolean; + /** + * If eval was called in partial mode + */ + partialMatch: boolean; + + setPartial(partial: boolean) { + this.partial = partial; + } + + setPartialMatch(partial: boolean) { + this.partialMatch = partial; + } + /** + * a LIKE "%A?" + * @param ctx + * @returns + */ + visitLikeExpression(ctx: any) { + const [left, _, right] = ctx.children; + let value = (this.visit(right)); + return new PartialComparisonExpression(this, "LIKE", left.text, value); + } + + /** + * Implement the BinaryComparison with all methods managed + */ + visitBinaryComparisonExpression(ctx: any) { + const [left, op, right] = ctx.children; + // @ts-ignore + return new PartialComparisonExpression(this, op.text, left.text, this.visit(right)); + } + + /** + * Map the a IN ['b','c'] + */ + visitInExpression(ctx: any) { + const [left, _, right] = ctx.children; + let value = (this.visit(right)); + return new PartialComparisonExpression(this, "IN", left.text, value); + } + + /** + * Map the a CONTAINS 'b' + */ + visitContainsExpression(ctx: any) { + const [left, _, right] = ctx.children; + let value = (this.visit(right)); + return new PartialComparisonExpression(this, "CONTAINS", left.text, value); + } +} + +/** + * Remove artifact from sanitize-html inside query + * @param query + * @returns + */ +export function unsanitize(query: string): string { + return query.replace(/</g, "<").replace(/>/g, ">"); +} diff --git a/packages/ql/tsconfig.json b/packages/ql/tsconfig.json new file mode 100644 index 000000000..42fbcdfde --- /dev/null +++ b/packages/ql/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": ["es2019", "DOM"], + "module": "es2020", + "outDir": "./lib", + "strict": false, + "declaration": true, + "sourceMap": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "moduleResolution": "node", + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["**/node_modules"], + "ts-node": { + "transpileOnly": true, + "preferTsExts": true, + "compilerOptions": { + "experimentalDecorators": true + }, + "include": ["src/**/*.spec.ts"], + "exclude": ["src/models/**/*"] + } +} From 054439858fa33c46db98e5f17632cb217768bd0a Mon Sep 17 00:00:00 2001 From: Remi Cattiau Date: Sun, 3 Mar 2024 16:27:31 -0800 Subject: [PATCH 3/7] wip --- docs/contribute/Ideas/v4.md | 1 + docs/pages/Concepts/Context.md | 4 +- docs/pages/Concepts/Stores/Stores.md | 124 +- docs/pages/Protocols.md | 26 + .../async/src/services/asyncjobservice.ts | 8 +- .../src/models/subscription.spec.ts | 4 +- .../cloudevents/src/models/subscription.ts | 101 +- packages/core/package.json | 2 + packages/core/src/application.ts | 3 +- packages/core/src/core.ts | 215 +--- packages/core/src/index.ts | 7 - packages/core/src/models/aclmodel.ts | 4 +- packages/core/src/models/coremodel.ts | 406 +------ packages/core/src/models/relations.spec.ts | 7 +- packages/core/src/models/relations.ts | 23 +- packages/core/src/models/rolemodel.ts | 13 +- packages/core/src/queues/pubsubservice.ts | 37 + packages/core/src/router.spec.ts | 209 ---- packages/core/src/router.ts | 520 -------- .../core/src/services/asyncevents.spec.ts | 113 -- packages/core/src/services/asyncevents.ts | 264 ---- packages/core/src/services/authentication.ts | 46 +- packages/core/src/services/binary.ts | 133 +- packages/core/src/services/cloudbinary.ts | 2 +- packages/core/src/services/configuration.ts | 23 +- packages/core/src/services/cron.ts | 2 +- packages/core/src/services/cryptoservice.ts | 3 +- packages/core/src/services/domainservice.ts | 1082 +---------------- .../core/src/services/fileconfiguration.ts | 48 +- .../core/src/services/invitationservice.ts | 44 +- .../src/services/kubernetesconfiguration.ts | 19 +- packages/core/src/services/mailer.ts | 6 +- packages/core/src/services/oauth.ts | 69 +- packages/core/src/services/resource.spec.ts | 125 -- packages/core/src/services/resource.ts | 211 ---- packages/core/src/services/service.ts | 159 +-- packages/core/src/stores/mapper.ts | 3 +- packages/core/src/stores/memory.spec.ts | 4 +- packages/core/src/stores/memory.ts | 2 +- packages/core/src/stores/modelmapper.ts | 3 +- packages/core/src/stores/store.spec.ts | 7 +- packages/core/src/stores/store.ts | 580 ++++++--- packages/core/src/utils/context.spec.ts | 2 +- packages/core/src/utils/context.ts | 2 +- packages/core/src/utils/session.spec.ts | 6 - packages/core/src/utils/session.ts | 34 +- packages/core/src/utils/throttler.spec.ts | 4 +- packages/core/src/utils/throttler.ts | 9 - packages/core/tsconfig.json | 4 +- packages/runtime/src/services/cluster.ts | 2 +- .../src/stores/aggregator.ts | 0 packages/{core => runtime}/src/stores/file.ts | 5 +- packages/tsc-esm/src/lib.spec.ts | 4 + packages/tsc-esm/src/lib.ts | 10 +- 54 files changed, 1034 insertions(+), 3710 deletions(-) delete mode 100644 packages/core/src/router.spec.ts delete mode 100644 packages/core/src/router.ts delete mode 100644 packages/core/src/services/asyncevents.spec.ts delete mode 100644 packages/core/src/services/asyncevents.ts delete mode 100644 packages/core/src/services/resource.spec.ts delete mode 100644 packages/core/src/services/resource.ts rename packages/{core => runtime}/src/stores/aggregator.ts (100%) rename packages/{core => runtime}/src/stores/file.ts (97%) diff --git a/docs/contribute/Ideas/v4.md b/docs/contribute/Ideas/v4.md index 9041e6c18..5c2ae4bc4 100644 --- a/docs/contribute/Ideas/v4.md +++ b/docs/contribute/Ideas/v4.md @@ -15,6 +15,7 @@ Default services: - CryptoService - Registry - EventService +- CacheService - SessionManager - Logger? diff --git a/docs/pages/Concepts/Context.md b/docs/pages/Concepts/Context.md index eb63b8ad7..6df30ac0d 100644 --- a/docs/pages/Concepts/Context.md +++ b/docs/pages/Concepts/Context.md @@ -1,6 +1,8 @@ # Context -The Context is used to expose Session and globally all information aroudn the current operation. +The Context is used to expose Session and globally all information around the current operation. It is the execution Context, it leverages NodeJS [Asynchronous context tracking](https://nodejs.org/api/async_context.html) + +You can access the context from anywhere using the hook `useContext` from `@webda/core`. As we have the ability to execute outside of `http` context the Context is not directly linked to the `http` request. diff --git a/docs/pages/Concepts/Stores/Stores.md b/docs/pages/Concepts/Stores/Stores.md index 9b87786a9..ed4d612d5 100644 --- a/docs/pages/Concepts/Stores/Stores.md +++ b/docs/pages/Concepts/Stores/Stores.md @@ -4,129 +4,43 @@ sidebar_position: 4 # Stores -The store services allow you to store object in a NoSQL database it handles for you mapping between objects. Objects are mapped to a model to allow security policy and schema verification. +The store services allow you to store object in a database it handles for you mapping between objects. Objects are mapped to a model to allow security policy and schema verification. -We have currently File, DynamoDB and MongoDB storage +Available stores are: -:::warning - -If you run several instances of your application, you should have a pub/sub to notify the other instances of the changes or disable cache by setting `disableCache` to `true` +- MemoryStore +- FileStore +- [DynamoDB](./DynamoDB.md) +- [MongoDB](./MongoDB.md) +- [PostgresStore](./PostgresStore.md) -::: +Stores are managing one or several models. -## Expose REST API +Example of a Store managing the model `MyModel` -Inside the configuration you can add a block for expose the store as a REST API +```mermaid -```js title="webda.config.json" -{ - ... - "expose": { - "url": "/storeurl", // By default the URL is the store name in lower case - "restrict": { - "update": true, // Prevent the creation of an object the PUT method wont be exposed - "delete": false // Allow delete for the object - } - } - ... -} ``` -The above configuration will end up creating the following routes: - -- `POST /storeurl` -- `GET /storeurl/{uuid}` -- `DELETE /storeurl/{uuid}` - -You can see that by default, once the store exposed all the methods are available unless you restrict them. - -## Configuring Mapping - -As an example we will use the Users / Idents stores used by the Authentication module. +The configuration would look like -A User has several Idents so in NoSQL we need to deduplicate a part of the Ident object inside an array inside the User object - -The following is the Idents store configuration - -```js title="webda.config.json" +```javascript title="webda.config.json" { ... - "map": { - "Users": { // Target store - "key": "user", // Property inside Ident Object - "target": "idents", // Property on the User Object - "fields": "type", // Fields from the Ident Object ( uuid is added by default ) - "cascade": true // If User object is delete then delete all the linked Idents - } + "stores": { + "mystore": { + "model": "mymodel", + } } -``` - -So if you have a user like - -```javascript -{ - ... - "uuid": "user_01" -} -``` - -Then you save a new Ident object like - -```javascript -{ ... - "uuid": "ident_01", - "user": "user_01", - "type": "Google" } ``` -Once the Ident saved, the User object will look like - -```javascript -{ - ... - "uuid": "user_01", - "idents": [{"uuid":"ident_01","type":"Google"}] - ... -} -``` - -Then if you update the field type on your Ident object the User object will reflect the change, as well as if you delete the ident object it will be removed from the User object. - -If cascade = true, then if you delete the User object, all attached Idents will be delete aswell. - -## Events - -The Stores emit events to let you implement some auto completion of the object if needed or taking any others action even deny the action by throwing an exception - -The store event looks like - -```javascript -{ - 'object': object, - 'store': this -} -``` - -Store.Save: Before saving the object -Store.Saved: After saving the object -Store.Update: Before updating the object -Store.Updated: After updating the object -Store.Delete: Before deleting the object -Store.Deleted: After deleting the object -Store.Get: When getting the object - -### Owner Policy - -POST: Add the current user in the user field of the object -PUT: Verify the current user is the user inside the user field -GET: Verify the current user is the user inside the user field, or a public=true field exists on the object -DELETE: Verify the current user is the user inside the user field +:::warning -### Void policy +If you run several instances of your application, you should have a pub/sub to notify the other instances of the changes or disable cache by setting `disableCache` to `true` -No verification, not recommended at all +::: ## Validation diff --git a/docs/pages/Protocols.md b/docs/pages/Protocols.md index a62b318c2..3ca1849c4 100644 --- a/docs/pages/Protocols.md +++ b/docs/pages/Protocols.md @@ -7,3 +7,29 @@ sidebar_position: 2.091 ## REST ## GraphQL + +# Internal Protocols + +StorageFinder supports a variety of protocols for different storage systems. The following is a list of supported protocols + +`gs://` - Google Cloud Storage +`s3://` - Amazon S3 +`http://` - HTTP +`https://` - HTTPS +`file://` - Local File System + +PubSub service can be used to send and receive messages. The following is a list of supported protocols + +`amqp://` - RabbitMQ +`sqs://` - Amazon SQS +`sns://` - Amazon SNS +`gcp-pubsub://` - Google Cloud PubSub + +CryptoService manage encryption and decryption of data. The following is a list of supported protocols + +`aes://` - AES +`rsa://` - RSA +`pgp://` - PGP +`jwt://` - JWT +`jwe://` - JWE +`jws://` - JWS diff --git a/packages/async/src/services/asyncjobservice.ts b/packages/async/src/services/asyncjobservice.ts index 121be7573..9bd520c0f 100644 --- a/packages/async/src/services/asyncjobservice.ts +++ b/packages/async/src/services/asyncjobservice.ts @@ -2,7 +2,6 @@ import { CancelableLoopPromise, CancelablePromise, CloudBinary, - Constructor, Core, CoreModelDefinition, CronDefinition, @@ -18,9 +17,10 @@ import { SimpleOperationContext, Store, WebContext, - WebdaError, - WebdaQL + WebdaError } from "@webda/core"; +import WebdaQL from "@webda/ql"; +import { Constructor } from "@webda/tsc-esm"; import { WorkerLogLevel } from "@webda/workout"; import axios, { AxiosResponse } from "axios"; import * as crypto from "crypto"; @@ -697,7 +697,7 @@ export default class AsyncJobService { + CronService.loadAnnotations(this.webda.getServices()).forEach(cron => { this.log("INFO", `Schedule cron ${cron.cron}: ${cron.serviceName}.${cron.method}(...) # ${cron.description}`); crontabSchedule(cron.cron, this.getCronExecutor(cron)); }); diff --git a/packages/cloudevents/src/models/subscription.spec.ts b/packages/cloudevents/src/models/subscription.spec.ts index 3fd5eb84a..e405cb980 100644 --- a/packages/cloudevents/src/models/subscription.spec.ts +++ b/packages/cloudevents/src/models/subscription.spec.ts @@ -1,7 +1,9 @@ import { expect, test } from "@jest/globals"; import { CloudEvent } from "cloudevents"; import { Server } from "http"; -import Subscription from "./subscription"; +import { SubscriptionMixIn } from "./subscription"; + +class Subscription extends SubscriptionMixIn(Object) {} test("Subscription", async () => { const subscription = new Subscription(); diff --git a/packages/cloudevents/src/models/subscription.ts b/packages/cloudevents/src/models/subscription.ts index 753bf1810..d8d36575e 100644 --- a/packages/cloudevents/src/models/subscription.ts +++ b/packages/cloudevents/src/models/subscription.ts @@ -114,8 +114,8 @@ export interface NATSSettings { * basic structure: * */ -export default class Subscription { - id: string = ""; +export default interface Subscription { + id: string; /** * Indicates the source to which the subscription is related. When present on a subscribe request, * all events generated due to this subscription MUST have a CloudEvents source property that @@ -155,13 +155,13 @@ export default class Subscription { * * Value comparisons are case sensitive. */ - protocol: "HTTP" | "MQTT" | "WEBDA" = "HTTP"; + protocol: "HTTP" | "MQTT" | "WEBDA"; /** * A set of settings specific to the selected delivery protocol provider. Options for these * settings are listed in the following subsection. An subscription manager MAY offer more options. * See the Protocol Settings section for future details. */ - protocolsettings?: HttpSettings | MQTTSettings | AMQPSettings | KafkaSettings | NATSSettings = {}; + protocolsettings?: HttpSettings | MQTTSettings | AMQPSettings | KafkaSettings | NATSSettings; /** * The address to which events MUST be sent. The format of the address MUST be valid for the * protocol specified in the protocol property, or one of the protocol's own transport bindings @@ -169,7 +169,7 @@ export default class Subscription { * * @required */ - sink: string = ""; + sink: string; /** * An array of filter expressions that evaluates to true or false. If any filter expression in the * array evaluates to false, the event MUST NOT be sent to the sink. If all the filter expressions @@ -184,52 +184,71 @@ export default class Subscription { * subscription request, then it MUST generate an error and reject the subscription create or * update request. */ - filters: Filter[] = []; + filters: Filter[]; +} + +/** + * Subscription Mixin to add default behavior to a subscription + * @param clazz + * @returns + */ +export function SubscriptionMixIn(clazz: any) { /** * Filter implementation */ - protected resolvedFilters: FilterImplementation; - /** - * - */ - private emitter: EmitterFunction; + let resolvedFilters: FilterImplementation; /** - * Verify that an event match its filters - * @param event - * @returns - */ - match(event: CloudEvent): boolean { - // Need to filter first on types - if (this.types && !this.types.includes(event.type)) { - return false; + * Emitter + */ + let emitter: EmitterFunction; + return class extends clazz implements Subscription { + id: string; + source?: string; + types?: string[]; + config?: { [key: string]: string }; + protocol: "HTTP" | "MQTT" | "WEBDA" = "HTTP"; + protocolsettings?: HttpSettings | MQTTSettings | AMQPSettings | KafkaSettings | NATSSettings; + sink: string = ""; + filters: Filter[] = []; + + /** + * Verify that an event match its filters + * @param event + * @returns + */ + match(event: CloudEvent): boolean { + // Need to filter first on types + if (this.types && !this.types.includes(event.type)) { + return false; + } + resolvedFilters ??= FiltersHelper.get({ all: this.filters }); + return resolvedFilters.match(event); } - this.resolvedFilters ??= FiltersHelper.get({ all: this.filters }); - return this.resolvedFilters.match(event); - } - /** - * Create the emitter for the subscription - * @returns - */ - createEmitter() { - if (this.protocol === "HTTP") { - return emitterFor(httpTransport(this.sink), this.protocolsettings as any); + /** + * Create the emitter for the subscription + * @returns + */ + createEmitter() { + if (this.protocol === "HTTP") { + return emitterFor(httpTransport(this.sink), this.protocolsettings as any); + } } - } - /** - * Emit a cloudevent - * @param event - */ - async emit(event: CloudEvent) { - // Ensure the subscription match the event - if (!this.match(event)) { - return; + /** + * Emit a cloudevent + * @param event + */ + async emit(event: CloudEvent) { + // Ensure the subscription match the event + if (!this.match(event)) { + return; + } + emitter ??= this.createEmitter(); + await emitter(event); } - this.emitter ??= this.createEmitter(); - await this.emitter(event); - } + }; } export { Subscription }; diff --git a/packages/core/package.json b/packages/core/package.json index 0dd8259a1..f9be391ab 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -24,6 +24,8 @@ }, "dependencies": { "@types/json-schema": "^7.0.8", + "@webda/ql": "^3.0.0", + "@webda/cloudevents": "^3.0.0", "@webda/workout": "^3.1.3", "accept-language": "^3.0.18", "ajv": "^8.6.2", diff --git a/packages/core/src/application.ts b/packages/core/src/application.ts index 5c5686e12..631262e6c 100644 --- a/packages/core/src/application.ts +++ b/packages/core/src/application.ts @@ -1,9 +1,10 @@ +import { Constructor } from "@webda/tsc-esm"; import { WorkerLogLevel, WorkerOutput } from "@webda/workout"; import * as fs from "fs"; import { JSONSchema7 } from "json-schema"; import { OpenAPIV3 } from "openapi-types"; import * as path from "path"; -import { Constructor, Core, CoreModel, CoreModelDefinition, Service, WebdaError } from "./index"; +import { Core, CoreModel, CoreModelDefinition, Service, WebdaError } from "./index"; import { getCommonJS } from "./utils/esm"; import { FileUtils } from "./utils/serializers"; const { __dirname } = getCommonJS(import.meta.url); diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 882eda1c1..89ad6ba34 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -1,13 +1,14 @@ +import * as WebdaQL from "@webda/ql"; +import { Constructor } from "@webda/tsc-esm"; import { WorkerLogLevel, WorkerOutput } from "@webda/workout"; import Ajv, { ErrorObject } from "ajv"; import addFormats from "ajv-formats"; +import { randomUUID } from "crypto"; import { deepmerge } from "deepmerge-ts"; import * as events from "events"; -import { JSONSchema7 } from "json-schema"; import jsonpath from "jsonpath"; import pkg from "node-machine-id"; import { AsyncLocalStorage } from "node:async_hooks"; -import { OpenAPIV3 } from "openapi-types"; import { Counter, CounterConfiguration, @@ -20,6 +21,7 @@ import { import { Writable } from "stream"; import { v4 as uuidv4 } from "uuid"; import { Application, Configuration, Modda } from "./application"; +import { InstanceCache } from "./cache"; import { BinaryService, ConfigurationService, @@ -34,12 +36,12 @@ import { Service, Store, UnpackedApplication, + UnpackedConfiguration, WebContext, - WebdaError, - WebdaQL + WebdaError } from "./index"; -import { Constructor, CoreModel, CoreModelDefinition } from "./models/coremodel"; -import { RouteInfo, Router } from "./router"; +import { CoreModel, CoreModelDefinition } from "./models/coremodel"; +import { RouteInfo, Router } from "./rest/router"; import CryptoService from "./services/cryptoservice"; import { JSONUtils } from "./utils/serializers"; const { machineIdSync } = pkg; @@ -279,7 +281,15 @@ export type CoreEvents = { "Webda.UpdateContextRoute": { context: WebContext; }; - [key: string]: unknown; + "Webda.Configuration.Loaded": { + configuration: UnpackedConfiguration["services"]; + }; + "Webda.Configuration.Updated": { + configuration: UnpackedConfiguration["services"]; + }; + "Webda.Configuration.Applied": { + configuration: UnpackedConfiguration["services"]; + }; }; type NoSchemaResult = null; @@ -297,7 +307,11 @@ type SchemaInvalidResult = { * @class Core * @category CoreFeatures */ -export class Core extends events.EventEmitter { +export class Core { + /** + * Emitter + */ + protected emitter: events.EventEmitter = new events.EventEmitter(); /** * Webda Services * @hidden @@ -444,8 +458,6 @@ export class Core extends events.EventEmitter * @params {Object} config - The configuration Object, if undefined will load the configuration file */ constructor(application?: Application) { - /** @ignore */ - super(); // Store WebdaCore in process to avoid conflict with import // @ts-ignore Core.singleton = process.webda = this; @@ -578,6 +590,7 @@ export class Core extends events.EventEmitter * @param model * @returns */ + @InstanceCache() getModelStore(modelOrConstructor: Constructor | T): Store { const model = >( (modelOrConstructor instanceof CoreModel ? modelOrConstructor.__class : modelOrConstructor) @@ -699,10 +712,8 @@ export class Core extends events.EventEmitter protected async initService(service: string) { try { this.log("TRACE", "Initializing service", service); - this.services[service]._initTime = Date.now(); await this.services[service].init(); } catch (err) { - this.services[service]._initException = err; this.failedServices[service] = { _initException: err }; this.log("ERROR", "Init service " + service + " failed: " + err.message); this.log("TRACE", err.stack); @@ -723,11 +734,9 @@ export class Core extends events.EventEmitter * * It will resolve Services init method and autolink */ + @InstanceCache() async init() { - if (this._init) { - return this._init; - } - + // Init Core Services if (this.configuration.parameters.configurationService) { try { this.log("INFO", "Create and init ConfigurationService", this.configuration.parameters.configurationService); @@ -768,26 +777,20 @@ export class Core extends events.EventEmitter this._modelBinariesCache.clear(); this.log("TRACE", "Create Webda init promise"); - this._init = (async () => { - await this.initService("Registry"); - await this.initService("CryptoService"); - - // Init services - let service; - let inits = []; - for (service in this.services) { - if ( - this.services[service].init !== undefined && - !this.services[service]._createException && - !this.services[service]._initTime - ) { - inits.push(this.initService(service)); - } + await this.initService("Registry"); + await this.initService("CryptoService"); + + // Init services + let service; + let inits = []; + for (service in this.services) { + // Filter out the services that are already initiated + if (!["Registry", "CryptoService", this.configuration.parameters.configurationService].includes(service)) { + inits.push(this.initService(service)); } - await Promise.all(inits); - await this.emitSync("Webda.Init.Services", this.services); - })(); - return this._init; + } + await Promise.all(inits); + await this.emit("Webda.Init.Services", this.services); } /** @@ -1063,6 +1066,10 @@ export class Core extends events.EventEmitter return result; } + /** + * Get the configuration + * @returns + */ public getConfiguration() { return this.configuration; } @@ -1092,22 +1099,6 @@ export class Core extends events.EventEmitter return this.application.getModel(name); } - /** - * Add to context information and executor based on the http context - */ - public updateContextWithRoute(ctx: WebContext): boolean { - let http = ctx.getHttpContext(); - // Check mapping - let route = this.router.getRouteFromUrl(ctx, http.getMethod(), http.getRelativeUri()); - if (route === undefined) { - return false; - } - ctx.setRoute({ ...this.configuration, ...route }); - ctx.setExecutor(this.getService(route.executor)); - this.emit("Webda.UpdateContextRoute", { context: ctx }); - return true; - } - /** * Flush the headers to the response, no more header modification is possible after that * @@ -1366,6 +1357,10 @@ export class Core extends events.EventEmitter return value; } + /** + * Get current machine id for node identification + * @returns + */ static getMachineId() { try { return process.env["WEBDA_MACHINE_ID"] || machineIdSync(); @@ -1432,7 +1427,7 @@ export class Core extends events.EventEmitter if (!noInit) { await context.init(); } - await this.emitSync("Webda.NewContext", { context, info }); + await this.emit("Webda.NewContext", { context, info }); return context; } @@ -1471,7 +1466,7 @@ export class Core extends events.EventEmitter */ public getUuid(format: "ascii" | "base64" | "hex" | "binary" | "uuid" = "uuid"): string { if (format === "uuid") { - return uuidv4().toString(); + return randomUUID(); } let buffer = Buffer.alloc(16); uuidv4(undefined, buffer); @@ -1483,20 +1478,13 @@ export class Core extends events.EventEmitter return buffer.toString(format); } - /** - * @override - */ - public emit(eventType: K | symbol | string, event?: E[K], ...data: any[]): boolean { - return super.emit(eventType, event, ...data); - } - /** * Emit the event with data and wait for Promise to finish if listener returned a Promise */ - public emitSync(eventType: K | symbol, event?: E[K], ...data: any[]): Promise { + public emit(eventType: K | symbol, event?: E[K], ...data: any[]): Promise { let result; let promises = []; - let listeners = this.listeners(eventType); + let listeners = this.emitter.listeners(eventType); for (let listener of listeners) { result = listener(event, ...data); if (result instanceof Promise) { @@ -1514,7 +1502,7 @@ export class Core extends events.EventEmitter * @returns */ on(event: Key | symbol, listener: (evt: E[Key]) => void): this { - super.on(event, listener); + this.emitter.on(event, listener); return this; } @@ -1556,107 +1544,6 @@ export class Core extends events.EventEmitter return (await Promise.all(this._requestCORSFilters.map(filter => filter.checkRequest(ctx, "CORS")))).some(v => v); } - /** - * Export OpenAPI - * @param skipHidden - * @returns - */ - exportOpenAPI(skipHidden: boolean = true): OpenAPIV3.Document { - let packageInfo = this.application.getPackageDescription(); - let contact: OpenAPIV3.ContactObject; - if (typeof packageInfo.author === "string") { - contact = { - name: packageInfo.author - }; - } else if (packageInfo.author) { - contact = packageInfo.author; - } - let license: OpenAPIV3.LicenseObject; - if (typeof packageInfo.license === "string") { - license = { - name: packageInfo.license - }; - } else if (packageInfo.license) { - license = packageInfo.license; - } - let openapi: OpenAPIV3.Document = deepmerge( - { - openapi: "3.0.3", - info: { - description: packageInfo.description, - version: packageInfo.version || "0.0.0", - title: packageInfo.title || "Webda-based application", - termsOfService: packageInfo.termsOfService, - contact, - license - }, - components: { - schemas: { - Object: { - type: "object" - } - } - }, - paths: {}, - tags: [] - }, - this.application.getConfiguration().openapi || {} - ); - let models = this.application.getModels(); - const schemas = this.application.getSchemas(); - // Copy all input/output from actions - for (let i in schemas) { - if (!(i.endsWith(".input") || i.endsWith(".output"))) { - continue; - } - // @ts-ignore - openapi.components.schemas[i] ??= schemas[i]; - // Not sure how to test following - /* c8 ignore next 5 */ - for (let j in schemas[i].definitions) { - // @ts-ignore - openapi.components.schemas[j] ??= schemas[i].definitions[j]; - } - } - for (let i in models) { - let model = models[i]; - let desc: JSONSchema7 = { - type: "object" - }; - let modelName = model.name || i.split("/").pop(); - let schema = this.application.getSchema(i); - if (schema) { - for (let j in schema.definitions) { - // @ts-ignore - openapi.components.schemas[j] ??= schema.definitions[j]; - } - delete schema.definitions; - desc = schema; - } - // Remove empty required as openapi does not like that - // Our compiler is not generating this anymore but it is additional protection - /* c8 ignore next 3 */ - if (desc.required && desc.required.length === 0) { - delete desc.required; - } - // Remove $schema - delete desc.$schema; - // Rename all #/definitions/ by #/components/schemas/ - openapi.components.schemas[modelName] = JSON.parse( - JSON.stringify(desc).replace(/#\/definitions\//g, "#/components/schemas/") - ); - } - this.router.completeOpenAPI(openapi, skipHidden); - openapi.tags.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); - let paths = {}; - Object.keys(openapi.paths) - .sort() - .forEach(i => (paths[i] = openapi.paths[i])); - openapi.paths = paths; - - return openapi; - } - /** * Get a metric object * diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8098029a2..0241a0129 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -27,7 +27,6 @@ export * from "./queues/pubsubservice"; export * from "./queues/queueservice"; // Services -export * from "./services/asyncevents"; export * from "./services/authentication"; export * from "./services/binary"; export * from "./services/cloudbinary"; // move to runtime @@ -43,13 +42,9 @@ export * from "./services/kubernetesconfiguration"; // move to runtime export * from "./services/mailer"; export * from "./services/notificationservice"; export * from "./services/oauth"; -export * from "./services/resource"; // move to runtime export * from "./services/service"; // Stores -export * from "./stores/aggregator"; // move to runtime -export * from "./stores/aliasstore"; // move to runtime -export * from "./stores/file"; // move to runtime export * from "./stores/mapper"; export * from "./stores/memory"; export * from "./stores/store"; @@ -66,5 +61,3 @@ export * from "./utils/serializers"; export * from "./utils/session"; export * from "./utils/throttler"; export * from "./utils/waiter"; - -export * from "../../webdaql/query"; diff --git a/packages/core/src/models/aclmodel.ts b/packages/core/src/models/aclmodel.ts index d1a376ce9..b4b6e76c2 100644 --- a/packages/core/src/models/aclmodel.ts +++ b/packages/core/src/models/aclmodel.ts @@ -25,8 +25,8 @@ export default class AclModel extends CoreModel { /** * Ensure creator has all permissions by default */ - async _onSave() { - await super._onSave(); + async _onCreate() { + await super._onCreate(); this._creator = this.getContext().getCurrentUserId(); if (Object.keys(this.__acl).length === 0 && this._creator) { this.__acl[this._creator] = "all"; diff --git a/packages/core/src/models/coremodel.ts b/packages/core/src/models/coremodel.ts index 111f07670..3e69a1f61 100644 --- a/packages/core/src/models/coremodel.ts +++ b/packages/core/src/models/coremodel.ts @@ -1,16 +1,18 @@ +import * as WebdaQL from "@webda/ql"; +import { Constructor, FilterAttributes, NotEnumerable } from "@webda/tsc-esm"; +import { randomUUID } from "crypto"; import { JSONSchema7 } from "json-schema"; import util from "util"; -import { v4 as uuidv4 } from "uuid"; -import { WebdaQL } from "../../../webdaql/query"; import { ModelGraph, ModelsTree } from "../application"; import { Core } from "../core"; import { WebdaError } from "../errors"; +import { ModelCreateEvent, ModelDeleteEvent, ModelGetEvent, ModelUpdateEvent } from "../events"; import { BinariesImpl, Binary } from "../services/binary"; import { Service } from "../services/service"; import { Store } from "../stores/store"; import { Context, OperationContext } from "../utils/context"; import { HttpMethodType } from "../utils/httpcontext"; -import { Throttler } from "../utils/throttler"; +import { CoreModelQuery, ModelRef, ModelRefWithCreate } from "./coremodelref"; import { ModelActions, ModelLinksArray, @@ -35,109 +37,6 @@ export function Expose(params: Partial = {}) { }; } -/** - * - */ -export class CoreModelQuery { - @NotEnumerable - private type: string; - @NotEnumerable - private model: CoreModel; - @NotEnumerable - private attribute: string; - @NotEnumerable - private targetModel: CoreModelDefinition; - - constructor(type: string, model: CoreModel, attribute: string) { - this.attribute = attribute; - this.type = type; - this.model = model; - } - - /** - * Retrieve target model definition - * @returns - */ - getTargetModel(): CoreModelDefinition { - this.targetModel ??= Core.get().getModel(this.type); - return this.targetModel; - } - /** - * Query the object - * @param query - * @returns - */ - query( - query?: string, - context?: Context - ): Promise<{ - results: CoreModel[]; - continuationToken?: string; - }> { - return this.getTargetModel().query(this.completeQuery(query), true, context); - } - - /** - * Complete the query with condition - * @param query - * @returns - */ - protected completeQuery(query?: string): string { - return WebdaQL.PrependCondition(query, `${this.attribute} = '${this.model.getUuid()}'`); - } - - /** - * - * @param callback - * @param context - */ - async forEach(callback: (model: any) => Promise, query?: string, context?: Context, parallelism: number = 3) { - const throttler = new Throttler(); - throttler.setConcurrency(parallelism); - for await (const model of this.iterate(query, context)) { - throttler.execute(() => callback(model)); - } - return throttler.wait(); - } - - /** - * Iterate through all - * @param context - * @returns - */ - iterate(query?: string, context?: Context) { - return Core.get().getModelStore(this.getTargetModel()).iterate(this.completeQuery(query), context); - } - - /** - * Get all the objects - * @returns - */ - async getAll(context?: Context): Promise { - let res = []; - for await (const item of this.iterate(this.completeQuery(), context)) { - res.push(item); - } - return res; - } -} - -/** - * Attribute of an object - * - * Filter out methods - */ -export type Attributes = { - [K in keyof T]: T[K] extends Function ? never : K; -}[keyof T]; - -/** - * Filter type keys by type - */ -export type FilterAttributes = { - [L in keyof T]: T[L] extends K ? L : never; -}[keyof T]; - /** * Define an Action on a model * @@ -305,30 +204,6 @@ export interface CoreModelDefinition { authorizeClientEvent(_event: string, _context: Context, _model?: T): boolean; } -export type Constructor = []> = new (...args: K) => T; - -/** - * Make a property hidden from json and schema - * - * This property will not be saved in the store - * Nor it will be exposed in the API - * - * @param target - * @param propertyKey - */ -export function NotEnumerable(target: any, propertyKey: string) { - Object.defineProperty(target, propertyKey, { - set(value) { - Object.defineProperty(this, propertyKey, { - value, - writable: true, - configurable: true - }); - }, - configurable: true - }); -} - const ActionsAnnotated: Map = new Map(); /** * Define an object method as an action @@ -354,195 +229,6 @@ export function Action(options: { methods?: HttpMethodType[]; openapi?: any; nam }; } -export class ModelRef { - @NotEnumerable - protected store: Store; - @NotEnumerable - protected model: CoreModelDefinition; - @NotEnumerable - protected parent: CoreModel; - - constructor( - protected uuid: string, - model: CoreModelDefinition, - parent?: CoreModel - ) { - this.model = model; - this.uuid = uuid === "" ? undefined : model.completeUid(uuid); - this.store = Core.get().getModelStore(model); - } - async get(): Promise { - return await this.store.get(this.uuid); - } - set(id: string | T) { - this.uuid = id instanceof CoreModel ? id.getUuid() : id; - this.parent?.__dirty.add(Object.keys(this.parent).find(k => this.parent[k] === this)); - } - toString(): string { - return this.uuid; - } - toJSON(): string { - return this.uuid; - } - getUuid(): string { - return this.uuid; - } - async deleteItemFromCollection( - prop: FilterAttributes, - index: number, - itemWriteCondition: any, - itemWriteConditionField?: string - ): Promise { - const updateDate = await this.store.deleteItemFromCollection( - this.uuid, - prop, - index, - itemWriteCondition, - itemWriteConditionField - ); - await this.model.emitSync("Store.PartialUpdated", { - object_id: this.uuid, - store: this.store, - updateDate, - partial_update: { - deleteItem: { - property: prop, - index: index - } - } - }); - return this; - } - async upsertItemToCollection( - prop: FilterAttributes, - item: any, - index?: number, - itemWriteCondition?: any, - itemWriteConditionField?: string - ): Promise { - const updateDate = await this.store.upsertItemToCollection( - this.uuid, - prop, - item, - index, - itemWriteCondition, - itemWriteConditionField - ); - await this.model.emitSync("Store.PartialUpdated", { - object_id: this.uuid, - store: this.store, - updateDate, - partial_update: { - addItem: { - value: item, - property: prop, - index: index - } - } - }); - return this; - } - exists(): Promise { - return this.store.exists(this.uuid); - } - delete(): Promise { - return this.store.delete(this.uuid); - } - conditionalPatch(updates: Partial, conditionField: any, condition: any): Promise { - return this.store.conditionalPatch(this.uuid, updates, conditionField, condition); - } - patch(updates: Partial): Promise { - return this.store.conditionalPatch(this.uuid, updates, null, undefined); - } - async setAttribute(attribute: keyof T, value: any): Promise { - await this.store.setAttribute(this.uuid, attribute, value); - return this; - } - async removeAttribute( - attribute: keyof T, - itemWriteCondition?: any, - itemWriteConditionField?: keyof T - ): Promise { - await this.store.removeAttribute(this.uuid, attribute, itemWriteCondition, itemWriteConditionField); - await this.model?.emitSync("Store.PartialUpdated", { - object_id: this.uuid, - store: this.store, - partial_update: { - deleteAttribute: attribute - } - }); - return this; - } - async incrementAttributes( - info: { - property: FilterAttributes; - value: number; - }[] - ): Promise { - const updateDate = await this.store.incrementAttributes(this.uuid, info); - await this.model.emitSync("Store.PartialUpdated", { - object_id: this.uuid, - store: this.store, - updateDate, - partial_update: { - increments: <{ property: string; value: number }[]>info - } - }); - return this; - } -} - -export class ModelRefWithCreate extends ModelRef { - /** - * Allow to create a model - * @param defaultValue - * @param context - * @param withSave - * @returns - */ - async create(defaultValue: RawModel, withSave: boolean = true): Promise { - let result = new this.model().load(defaultValue, true).setUuid(this.uuid); - if (withSave) { - await result.save(); - } - return result; - } - - /** - * Load a model from the known store - * - * @param this the class from which the static is called - * @param id of the object to load - * @param defaultValue if object not found return a default object - * @param context to set on the object - * @returns - */ - async getOrCreate(defaultValue: RawModel, withSave: boolean = true): Promise { - return (await this.get()) || this.create(defaultValue, withSave); - } -} - -export class ModelRefCustom extends ModelRef { - constructor( - public uuid: string, - model: CoreModelDefinition, - data: any, - parent: CoreModel - ) { - super(uuid, model, parent); - Object.assign(this, data); - } - - toJSON(): any { - return this; - } - getUuid(): string { - return this.uuid; - } -} - -export type ModelRefCustomProperties = ModelRefCustom & K; - /** * Basic Object in Webda * @@ -568,20 +254,14 @@ class CoreModel { */ __types: string[]; /** - * Object context - * - * @TJS-ignore - */ - @NotEnumerable - __ctx: Context; - /** - * If object is attached to its store - * * @TJS-ignore */ @NotEnumerable __store: Store; + /** + * Dirty fields + */ @NotEnumerable __dirty: Set = new Set(); @@ -827,11 +507,10 @@ class CoreModel { static iterate( this: Constructor, query: string = "", - includeSubclass: boolean = true, - context?: Context + includeSubclass: boolean = true ): AsyncGenerator { // @ts-ignore - return this.store().iterate(this.completeQuery(query, includeSubclass), context); + return this.store().iterate(this.completeQuery(query, includeSubclass)); } /** @@ -843,14 +522,13 @@ class CoreModel { static async query( this: Constructor, query: string = "", - includeSubclass: boolean = true, - context?: Context + includeSubclass: boolean = true ): Promise<{ results: T[]; continuationToken?: string; }> { // @ts-ignore - return this.store().query(this.completeQuery(query, includeSubclass), context); + return this.store().query(this.completeQuery(query, includeSubclass)); } /** @@ -961,6 +639,11 @@ class CoreModel { return null; } + /** + * Check if an actions is permitted + * @param context + * @param action + */ async checkAct( context: Context, action: @@ -1241,11 +924,7 @@ class CoreModel { async save(full?: boolean | keyof this, ...args: (keyof this)[]): Promise { // If proxy is not used and not field specified call save if ((!util.types.isProxy(this) && full === undefined) || full === true) { - if (!this._creationDate || !this._lastUpdate) { - await this.__store.create(this, this.getContext()); - } else { - await this.__store.update(this); - } + await this.__store.update(this); return this; } const patch: any = { @@ -1283,7 +962,7 @@ class CoreModel { * @returns */ generateUid(_object: any = undefined): string { - return uuidv4().toString(); + return randomUUID(); } /** @@ -1357,6 +1036,13 @@ class CoreModel { */ async _onDeleted() { // Empty to be overriden + return new ModelDeleteEvent( + { + object: this, + object_id: this.getUuid() + }, + this + ).emit(); } /** @@ -1364,20 +1050,31 @@ class CoreModel { */ async _onGet() { // Empty to be overriden + await new ModelGetEvent( + { + object: this + }, + this + ).emit(); } /** * Called when object is about to be saved */ - async _onSave() { - // Empty to be overriden + async _onCreate() { + // TODO } /** * Called when object is saved */ - async _onSaved() { - // Empty to be overriden + async _onCreated() { + await new ModelCreateEvent( + { + object: this + }, + this + ).emit(); } /** @@ -1386,14 +1083,21 @@ class CoreModel { * @param updates to be send */ async _onUpdate(_updates: any) { - // Empty to be overriden + // empty to be overriden } /** * Called when object is updated */ - async _onUpdated() { - // Empty to be overriden + async _onUpdated(updates: any) { + await new ModelUpdateEvent( + { + object: this, + object_id: this.getUuid(), + update: updates + }, + this + ).emit(); } /** @@ -1402,7 +1106,7 @@ class CoreModel { * @param value */ async setAttribute(property: keyof this, value: any) { - await this.getRef().setAttribute(property, value); + await this.ref().setAttribute(property, value); this[property] = value; } @@ -1411,7 +1115,7 @@ class CoreModel { * @param property */ async removeAttribute(property: keyof this) { - await this.getRef().removeAttribute(property); + await this.ref().removeAttribute(property); delete this[property]; } @@ -1428,7 +1132,7 @@ class CoreModel { * Return a model ref * @returns */ - getRef(): ModelRef { + ref(): ModelRef { return new ModelRef(this.getUuid(), this.__class); } @@ -1437,7 +1141,7 @@ class CoreModel { * @param info */ async incrementAttributes(info: { property: string; value: number }[]) { - await this.getRef().incrementAttributes(info); + await this.ref().incrementAttributes(info); for (let inc of info) { this[inc.property] ??= 0; this[inc.property] += inc.value; diff --git a/packages/core/src/models/relations.spec.ts b/packages/core/src/models/relations.spec.ts index be7f37e34..9339b8391 100644 --- a/packages/core/src/models/relations.spec.ts +++ b/packages/core/src/models/relations.spec.ts @@ -10,7 +10,7 @@ import { Binaries, Binary, BinaryService, MemoryBinaryFile } from "../services/b import { ModelMapper } from "../stores/modelmapper"; import { WebdaTest } from "../test"; import { HttpContext } from "../utils/httpcontext"; -import { CoreModel, Emitters } from "./coremodel"; +import { CoreModel } from "./coremodel"; import { ModelLink, ModelLinksArray, @@ -82,11 +82,6 @@ interface ContactInterface extends CoreModel { } @suite export class ModelDrivenTest extends WebdaTest { - after() { - // Ensure we remove all listeners - Object.values(this.webda.getModels()).forEach(m => Emitters.get(m)?.removeAllListeners()); - } - @test async test() { // Init mapper diff --git a/packages/core/src/models/relations.ts b/packages/core/src/models/relations.ts index f8471c086..29a39a617 100644 --- a/packages/core/src/models/relations.ts +++ b/packages/core/src/models/relations.ts @@ -1,14 +1,6 @@ -import { - Attributes, - CoreModel, - CoreModelDefinition, - FilterAttributes, - ModelAction, - ModelRef, - ModelRefCustom, - ModelRefCustomProperties, - NotEnumerable -} from "./coremodel"; +import { Attributes, FilterAttributes, NotEnumerable } from "@webda/tsc-esm"; +import { CoreModel, CoreModelDefinition, ModelAction } from "./coremodel"; +import { ModelRef, ModelRefCustom, ModelRefCustomProperties } from "./coremodelref"; /** * Raw model without methods @@ -17,15 +9,6 @@ export type RawModel = { [K in Attributes]?: T[K] extends object ? RawModel : T[K]; }; -/** - * Methods of an object - * - * Filter out attributes - */ -export type Methods = { - [K in keyof T]: T[K] extends Function ? K : never; -}[keyof T]; - /** * Model loader with a `get` method */ diff --git a/packages/core/src/models/rolemodel.ts b/packages/core/src/models/rolemodel.ts index 242bd535e..07827d975 100644 --- a/packages/core/src/models/rolemodel.ts +++ b/packages/core/src/models/rolemodel.ts @@ -1,4 +1,4 @@ -import { CoreModel, OperationContext, WebdaError } from "../index"; +import { Context, CoreModel, OperationContext, WebdaError } from "../index"; abstract class RoleModel extends CoreModel { abstract getRolesMap(): { [key: string]: string }; @@ -6,8 +6,13 @@ abstract class RoleModel extends CoreModel { return false; } - async getRoles(ctx: OperationContext) { - if (!ctx.getCurrentUserId()) { + /** + * Get the roles of the current user + * @param ctx + * @returns + */ + async getRoles(ctx: Context) { + if (!(ctx instanceof OperationContext)) { throw new WebdaError.Forbidden("No user"); } // If roles are cached in session @@ -16,7 +21,7 @@ abstract class RoleModel extends CoreModel { } let user = await ctx.getCurrentUser(); // Cache roles in session - ctx.getSession().roles = user.getRoles(); + ctx.getSession().roles = user?.getRoles(); return ctx.getSession().roles; } diff --git a/packages/core/src/queues/pubsubservice.ts b/packages/core/src/queues/pubsubservice.ts index 1c5d6b5df..e4ebac1f0 100644 --- a/packages/core/src/queues/pubsubservice.ts +++ b/packages/core/src/queues/pubsubservice.ts @@ -2,6 +2,11 @@ import { Counter, Gauge, Histogram } from "../core"; import { Service, ServiceParameters } from "../services/service"; import { CancelablePromise } from "../utils/waiter"; +/** + * Allow to send events to a queue without creating a service + */ +const Protocols: { [key: string]: (event: any, url: string) => Promise } = {}; + export default abstract class PubSubService< T = any, K extends ServiceParameters = ServiceParameters @@ -77,6 +82,38 @@ export default abstract class PubSubService< eventPrototype?: { new (): T }, onBind?: () => void ): CancelablePromise; + + /** + * Register a protocol + * @param protocol + * @param send + */ + static registerProtocol(protocol: string, send: (event: any, url: string) => Promise) { + Protocols[protocol] = send; + } + + /** + * Protocol is managed by the pubsub + * @param protocol + * @returns + */ + static hasProtocol(protocol: string) { + return !!Protocols[protocol]; + } + + /** + * Send a message to a pubsub or queue + * @param event + * @param url + * @returns + */ + static async send(event: any, url: string) { + const protocol = url.split("://")[0]; + if (Protocols[protocol]) { + return Protocols[protocol](event, url); + } + throw new Error(`Protocol ${protocol} not known, you might forgot to add a webda module`); + } } export { PubSubService }; diff --git a/packages/core/src/router.spec.ts b/packages/core/src/router.spec.ts deleted file mode 100644 index e5322de52..000000000 --- a/packages/core/src/router.spec.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { suite, test } from "@testdeck/mocha"; -import * as assert from "assert"; -import { User } from "./models/user"; -import { RouteInfo } from "./router"; -import { WebdaTest } from "./test"; -import { HttpContext } from "./utils/httpcontext"; - -@suite -class RouterTest extends WebdaTest { - @test - testGetRouteMethodsFromUrl() { - const info: RouteInfo = { methods: ["GET"], executor: "DefinedMailer" }; - this.webda.addRoute("/plop", info); - assert.deepStrictEqual(this.webda.getRouter().getRouteMethodsFromUrl("/"), ["GET", "POST"]); - assert.deepStrictEqual(this.webda.getRouter().getRouteMethodsFromUrl("/plop"), ["GET"]); - this.webda.addRoute("/plop", { methods: ["POST"], executor: "DefinedMailer" }); - assert.deepStrictEqual(this.webda.getRouter().getRouteMethodsFromUrl("/plop"), ["POST", "GET"]); - let call = []; - this.webda.log = (level, ...args) => { - call.push({ level, args }); - }; - this.webda.addRoute("/plop", { methods: ["GET"], executor: "DefinedMailer" }); - assert.deepStrictEqual(call, [ - { level: "TRACE", args: ["Add route GET /plop"] }, - { level: "WARN", args: ["GET /plop overlap with another defined route"] } - ]); - // Should be skipped - this.webda.addRoute("/plop", info); - } - - @test - async testRouterWithPrefix() { - this.webda.addRoute("/test/{uuid}", { methods: ["GET"], executor: "DefinedMailer" }); - this.webda.getGlobalParams().routePrefix = "/reprefix"; - assert.strictEqual(this.webda.getRouter().getFinalUrl("/test/plop"), "/reprefix/test/plop"); - assert.strictEqual(this.webda.getRouter().getFinalUrl("/reprefix/test/plop"), "/reprefix/test/plop"); - assert.strictEqual(this.webda.getRouter().getFinalUrl("//test/plop"), "/test/plop"); - this.webda.getRouter().remapRoutes(); - } - - @test - async testRouteWithPrefix() { - this.webda.addRoute("/test/{uuid}", { methods: ["GET"], executor: "DefinedMailer" }); - let httpContext = new HttpContext("test.webda.io", "GET", "/prefix/test/plop", "https"); - httpContext.setPrefix("/prefix"); - let ctx = await this.webda.newWebContext(httpContext); - this.webda.updateContextWithRoute(ctx); - assert.strictEqual(ctx.getPathParameters().uuid, "plop"); - } - - @test - async testRouteWithOverlap() { - this.webda.addRoute("/test/{uuid}", { methods: ["GET"], executor: "DefinedMailer" }); - this.webda.addRoute("/test/{id}", { methods: ["GET"], executor: "DefinedMailer" }); - let httpContext = new HttpContext("test.webda.io", "GET", "/test/plop", "https"); - let ctx = await this.webda.newWebContext(httpContext); - this.webda.updateContextWithRoute(ctx); - assert.deepStrictEqual(this.webda.getRouter().getRouteMethodsFromUrl("/test/plop"), ["GET"]); - } - - @test - async testRouteWithWeirdSplit() { - this.webda.addRoute("/test/{uuid}at{domain}", { methods: ["GET"], executor: "DefinedMailer" }); - let httpContext = new HttpContext("test.webda.io", "GET", "/test/plopatgoogle", "https"); - let ctx = await this.webda.newWebContext(httpContext); - this.webda.updateContextWithRoute(ctx); - assert.deepStrictEqual(ctx.getPathParameters(), { - uuid: "plop", - domain: "google" - }); - } - - @test - async testRouteWithSubPath() { - this.webda.addRoute("/test/{uuid}", { methods: ["GET"], executor: "DefinedMailer" }); - this.webda.addRoute("/test/{uuid}/users", { methods: ["GET"], executor: "DefinedMailer2" }); - this.webda.addRoute("/test/{puid}/users/{uuid}", { methods: ["GET"], executor: "DefinedMailer3" }); - let httpContext = new HttpContext("test.webda.io", "GET", "/test/plop", "https"); - let ctx = await this.webda.newWebContext(httpContext); - this.webda.updateContextWithRoute(ctx); - assert.deepStrictEqual(ctx.getPathParameters(), { - uuid: "plop" - }); - httpContext = new HttpContext("test.webda.io", "GET", "/test/plop/users", "https"); - ctx = await this.webda.newWebContext(httpContext); - this.webda.updateContextWithRoute(ctx); - assert.deepStrictEqual(ctx.getPathParameters(), { - uuid: "plop" - }); - httpContext = new HttpContext("test.webda.io", "GET", "/test/plop/users/plip", "https"); - ctx = await this.webda.newWebContext(httpContext); - this.webda.updateContextWithRoute(ctx); - assert.deepStrictEqual(ctx.getPathParameters(), { - uuid: "plip", - puid: "plop" - }); - } - - @test - async testRouteWithPath() { - this.webda.addRoute("/test/{+path}", { methods: ["GET"], executor: "DefinedMailer" }); - this.webda.addRoute("/test2/{+path}{?query*}", { methods: ["GET"], executor: "DefinedMailer" }); - let httpContext = new HttpContext("test.webda.io", "GET", "/test/plop/toto/plus", "https"); - let ctx = await this.webda.newWebContext(httpContext); - this.webda.updateContextWithRoute(ctx); - assert.deepStrictEqual(ctx.getPathParameters(), { path: "plop/toto/plus" }); - httpContext = new HttpContext("test.webda.io", "GET", "/test2/plop/toto/plus?query3=12&query2=test,test2", "https"); - ctx = await this.webda.newWebContext(httpContext); - this.webda.updateContextWithRoute(ctx); - assert.deepStrictEqual(ctx.getPathParameters(), { - path: "plop/toto/plus", - query: { - query3: "12", - query2: "test,test2" - } - }); - } - - @test - async testRouteWithEmail() { - this.webda.addRoute("/email/{email}/test", { methods: ["GET"], executor: "DefinedMailer" }); - this.webda.addRoute("/email/callback{?email,test?}", { methods: ["GET"], executor: "DefinedMailer" }); - let httpContext = new HttpContext("test.webda.io", "GET", "/email/test%40webda.io/test", "https"); - let ctx = await this.webda.newWebContext(httpContext); - this.webda.updateContextWithRoute(ctx); - assert.deepStrictEqual(ctx.getPathParameters(), { email: "test@webda.io" }); - httpContext = new HttpContext("test.webda.io", "GET", "/email/callback?email=test%40webda.io", "https"); - ctx = await this.webda.newWebContext(httpContext); - this.webda.updateContextWithRoute(ctx); - assert.deepStrictEqual(ctx.getPathParameters(), { email: "test@webda.io" }); - } - - @test - async testRouteWithQueryParam() { - this.webda.addRoute("/test/plop{?uuid?}", { methods: ["GET"], executor: "DefinedMailer" }); - let httpContext = new HttpContext("test.webda.io", "GET", "/test/plop", "http"); - let ctx = await this.webda.newWebContext(httpContext); - assert.strictEqual(this.webda.updateContextWithRoute(ctx), true); - assert.strictEqual(ctx.getPathParameters().uuid, undefined); - httpContext = new HttpContext("test.webda.io", "GET", "/test/plop?uuid=bouzouf", "http"); - ctx = await this.webda.newWebContext(httpContext); - assert.strictEqual(this.webda.updateContextWithRoute(ctx), true); - assert.strictEqual(ctx.getPathParameters().uuid, "bouzouf"); - this.webda.addRoute("/test/plop2{?params+}", { methods: ["GET"], executor: "DefinedMailer" }); - httpContext = new HttpContext("test.webda.io", "GET", "/test/plop2", "http"); - ctx = await this.webda.newWebContext(httpContext); - assert.strictEqual(this.webda.updateContextWithRoute(ctx), false); - httpContext = new HttpContext("test.webda.io", "GET", "/test/plop2?uuid=plop", "http"); - ctx = await this.webda.newWebContext(httpContext); - assert.strictEqual(this.webda.updateContextWithRoute(ctx), true); - assert.strictEqual(ctx.getPathParameters().params.uuid, "plop"); - } - - @test - completeOpenApi() { - let api = { paths: {}, info: { title: "Plop", version: "1.0" }, openapi: "", tags: [{ name: "test" }] }; - const info: RouteInfo = { - methods: ["GET"], - executor: "DefinedMailer", - openapi: { - tags: ["plop", "test"], - hidden: true, - get: { - schemas: { - output: "test" - } - } - } - }; - this.webda.addRoute("/plop{?*path}", info); - this.webda.getRouter().remapRoutes(); - this.webda.getRouter().completeOpenAPI(api); - assert.strictEqual(api.paths["/plop"], undefined); - this.webda.getRouter().completeOpenAPI(api, false); - assert.notStrictEqual(api.paths["/plop"], undefined); - assert.deepStrictEqual(api.paths["/plop"].get.tags, ["plop", "test"]); - assert.ok(api.tags.filter(f => f.name === "plop").length === 1); - } - - @test - cov() { - const info: RouteInfo = { - methods: ["GET"], - executor: "DefinedMailer", - openapi: { - tags: ["plop", "test"], - hidden: true, - get: { - schemas: { - output: "test" - } - } - } - }; - this.webda.addRoute("/cov", info); - this.webda.addRoute("/cov", info); - this.webda.addRoute("/cov", { ...info, methods: ["PUT"] }); - this.webda.getRouter().removeRoute("/cov", info); - this.webda.getRouter().getRoutes(); - } - - @test - getModelUrl() { - let routes = this.webda.getRouter().getRoutes(); - console.log(routes["/memory/users{?q}"][0].openapi); - let url = this.webda.getRouter().getModelUrl(new User()); - console.log(url); - } -} diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts deleted file mode 100644 index 7c49019eb..000000000 --- a/packages/core/src/router.ts +++ /dev/null @@ -1,520 +0,0 @@ -import { JSONSchema7 } from "json-schema"; -import { OpenAPIV3 } from "openapi-types"; -import uriTemplates from "uri-templates"; -import { Core } from "./core"; -import { CoreModel, CoreModelDefinition } from "./models/coremodel"; -import { WebContext } from "./utils/context"; -import { HttpMethodType } from "./utils/httpcontext"; - -type RecursivePartial = { - [P in keyof T]?: RecursivePartial; -}; - -export interface OpenApiWebdaOperation extends RecursivePartial { - schemas?: { - input?: JSONSchema7 | string; - output?: JSONSchema7 | string; - }; -} -/** - * Define overridable OpenAPI description - */ -export interface OpenAPIWebdaDefinition extends RecursivePartial { - /** - * Do not output for this specific Route - * - * It can still be output with --include-hidden, as it is needed to declare - * the route in API Gateway or any other internal documentation - */ - hidden?: boolean; - /** - * If defined will link to the model schema instead of a generic Object - */ - model?: string; - /** - * Tags defined for all methods - */ - tags?: string[]; - post?: OpenApiWebdaOperation; - put?: OpenApiWebdaOperation; - patch?: OpenApiWebdaOperation; - get?: OpenApiWebdaOperation; -} - -/** - * Route Information default information - */ -export interface RouteInfo { - model?: CoreModelDefinition; - /** - * HTTP Method to expose - */ - methods: HttpMethodType[]; - /** - * Executor name - */ - executor: string; - /** - * Method name on the executor - */ - _method?: string | Function; - /** - * OpenAPI definition - */ - openapi?: OpenAPIWebdaDefinition; - /** - * URI Template parser - */ - _uriTemplateParse?: { fromUri: (uri: string, options?: { strict: boolean }) => any; varNames: any }; - /** - * Query parameters to extract - */ - _queryParams?: { name: string; required: boolean }[]; - /** - * Catch all parameter - */ - _queryCatchAll?: string; - /** - * Hash - */ - hash?: string; - /** - * Intend to override existing - */ - override?: boolean; -} - -/** - * Manage Route resolution - * @category CoreFeatures - */ -export class Router { - protected routes: Map = new Map(); - protected initiated: boolean = false; - protected pathMap: { url: string; config: RouteInfo }[]; - protected webda: Core; - protected models: Map = new Map(); - - constructor(webda: Core) { - this.webda = webda; - } - - /** - * Registration of a model - * @param model - * @param url - */ - registerModelUrl(model: string, url: string) { - this.models.set(model, url); - } - - /** - * Return the route for model - * @param model - * @returns - */ - getModelUrl(model: string | CoreModel) { - if (typeof model !== "string") { - model = this.webda.getApplication().getModelName(model); - } - return this.models.get(model); - } - - /** - * Include prefix to the url if not present - * @param url - * @returns - */ - getFinalUrl(url: string): string { - // We have to replace all @ by %40 as it is allowed in url rfc (https://www.rfc-editor.org/rfc/rfc3986#page-22) - // But disallowed in https://www.rfc-editor.org/rfc/rfc6570#section-3.2.1 - // Similar for / in query string - url = url.replace(/@/g, "%40"); - if (url.includes("?")) { - url = url.substring(0, url.indexOf("?")) + "?" + url.substring(url.indexOf("?") + 1).replace(/\//g, "%2F"); - } - const prefix = this.webda.getGlobalParams().routePrefix || ""; - if (prefix && url.startsWith(prefix)) { - return url; - } - // Absolute url - if (url.startsWith("//")) { - return url.substring(1); - } - return `${prefix}${url}`; - } - - /** - * Return routes - * @returns - */ - getRoutes() { - return this.routes; - } - - /** - * Add a route dynamicaly - * - * @param {String} url of the route can contains dynamic part like {uuid} - * @param {Object} info the type of executor - */ - addRoute(url: string, info: RouteInfo): void { - const finalUrl = this.getFinalUrl(url); - this.webda.log("TRACE", `Add route ${info.methods.join(",")} ${finalUrl}`); - info.openapi ??= {}; - if (this.routes[finalUrl]) { - // If route is already added do not do anything - if (this.routes[finalUrl].includes(info)) { - return; - } - // Check and add warning if same method is used - let methods = this.routes[finalUrl].map((r: RouteInfo) => r.methods).flat(); - info.methods.forEach(m => { - if (methods.indexOf(m) >= 0) { - if (!info.override) { - this.webda.log("WARN", `${m} ${finalUrl} overlap with another defined route`); - } - } - }); - // Last added need to be overriding - this.routes[finalUrl].unshift(info); - } else { - this.routes[finalUrl] = [info]; - } - - if (this.initiated) { - this.remapRoutes(); - } - } - - /** - * Remove a route dynamicly - * - * @param {String} url to remove - */ - removeRoute(url: string, info: RouteInfo = undefined): void { - const finalUrl = this.getFinalUrl(url); - if (!info) { - delete this.routes[finalUrl]; - } else if (this.routes[finalUrl] && this.routes[finalUrl].includes(info)) { - this.routes[finalUrl].splice(this.routes[finalUrl].indexOf(info), 1); - } - - this.remapRoutes(); - } - - /** - * Reinit all routes - * - * It will readd the URITemplates if needed - * Sort all routes again - */ - public remapRoutes() { - // Might need to ensure each routes is prefixed - const prefix = this.webda.getGlobalParams().routePrefix || ""; - if (prefix) { - Object.keys(this.routes) - .filter(k => !k.startsWith(prefix)) - .forEach(k => { - this.routes[this.getFinalUrl(k)] = this.routes[k]; - delete this.routes[k]; - }); - } - - this.initURITemplates(this.routes); - - // Order path desc - this.pathMap = []; - for (let i in this.routes) { - // Might need to trail the query string - this.routes[i].forEach((config: RouteInfo) => { - this.pathMap.push({ - url: i, - config - }); - }); - } - this.pathMap.sort(this.comparePath); - this.initiated = true; - } - - protected comparePath(a, b): number { - // Normal node works with localeCompare but not Lambda... - // Local compare { to a return: 26 on Lambda - let bs = b.url.replace(/\{[^{}]+}/, "{}").split("/"); - let as = a.url.replace(/\{[^{}]+}/, "{}").split("/"); - for (let i in as) { - if (bs[i] === undefined) return -1; - if (as[i] === bs[i]) continue; - if (as[i][0] === "{" && bs[i][0] !== "{") return 1; - if (as[i][0] !== "{" && bs[i][0] === "{") return -1; - return bs[i] < as[i] ? -1 : 1; - } - return 1; - } - - /** - * @hidden - */ - protected initURITemplates(config: Map): void { - // Prepare tbe URI parser - for (let map in config) { - if (map.indexOf("{") !== -1) { - config[map].forEach((e: RouteInfo) => { - let idx = map.indexOf("{?"); - let queryOptional = true; - if (idx >= 0) { - let query = map.substring(idx + 2, map.length - 1); - e._queryParams = []; - query.split(",").forEach(q => { - if (q.endsWith("*")) { - e._queryCatchAll = q.substring(0, q.length - 1); - return; - } else if (q.endsWith("+")) { - e._queryCatchAll = q.substring(0, q.length - 1); - queryOptional = false; - return; - } else if (q.endsWith("?")) { - e._queryParams.push({ name: q.substring(0, q.length - 1), required: false }); - } else { - queryOptional = false; - e._queryParams.push({ name: q, required: true }); - } - }); - // We do not use uri-templates for query parsing - //map = map.substring(0, idx) + "?{+URITemplateQuery}"; - const templates = [uriTemplates(map.substring(0, idx) + "?{+URITemplateQuery}")]; - let pathTemplate = uriTemplates(map.substring(0, idx)); - if (queryOptional) { - templates.push(pathTemplate); - } - e._uriTemplateParse = { - fromUri: (url: string) => { - return templates.reduce((v, t) => (v ? v : t.fromUri(url)), undefined); - }, - varNames: [...pathTemplate.varNames, ...e._queryParams.map(q => q.name)] - }; - } else { - e._uriTemplateParse = uriTemplates(map); - } - }); - } - } - } - - /** - * Get all method for a specific url - * @param config - * @param method - * @param url - */ - getRouteMethodsFromUrl(url): HttpMethodType[] { - const finalUrl = this.getFinalUrl(url); - let methods = new Set(); - for (let i in this.pathMap) { - const routeUrl = this.pathMap[i].url; - const map = this.pathMap[i].config; - - if ( - routeUrl !== finalUrl && - (map._uriTemplateParse === undefined || map._uriTemplateParse.fromUri(finalUrl, { strict: true }) === undefined) - ) { - continue; - } - - map.methods.forEach(m => methods.add(m)); - } - return Array.from(methods); - } - - /** - * Get the route from a method / url - */ - public getRouteFromUrl(ctx: WebContext, method: HttpMethodType, url: string): any { - const finalUrl = this.getFinalUrl(url); - let parameters = this.webda.getConfiguration().parameters; - for (let i in this.pathMap) { - const routeUrl = this.pathMap[i].url; - const map = this.pathMap[i].config; - - // Check method - if (map.methods.indexOf(method) === -1) { - continue; - } - - if (routeUrl === finalUrl) { - ctx.setServiceParameters(parameters); - return map; - } - - if (map._uriTemplateParse === undefined) { - continue; - } - const parse_result = map._uriTemplateParse.fromUri(finalUrl, { strict: true }); - if (parse_result !== undefined) { - let parseUrl = new URL(`http://localhost${finalUrl}`); - if (map._queryCatchAll) { - parse_result[map._queryCatchAll] = {}; - parseUrl.searchParams.forEach((v, k) => { - if (!map._queryParams?.find(q => q.name === k)) { - parse_result[map._queryCatchAll][k] = v; - } - }); - } - // Check for each params - let mandatoryParams = true; - map._queryParams?.forEach(q => { - if (!parseUrl.searchParams.has(q.name)) { - mandatoryParams &&= !q.required; - return; - } - parse_result[q.name] = parseUrl.searchParams.get(q.name); - }); - // Skip if we miss mandatory params - if (!mandatoryParams) { - continue; - } - if (parse_result.URITemplateQuery) { - delete parse_result.URITemplateQuery; - } - ctx.setServiceParameters(parameters); - ctx.setPathParameters(parse_result); - - return map; - } - } - } - - protected getOpenAPISchema(schema) { - if (!schema) { - return { - $ref: "#/components/schemas/Object" - }; - } else if (typeof schema === "string") { - return { - $ref: "#/components/schemas/" + schema - }; - } - return schema; - } - /** - * Add all known routes to paths - * - * @param openapi to complete - * @param skipHidden add hidden routes or not - */ - completeOpenAPI(openapi: OpenAPIV3.Document, skipHidden: boolean = true) { - let hasTag = tag => openapi.tags.find(t => t.name === tag) !== undefined; - for (let i in this.routes) { - this.routes[i].forEach((route: RouteInfo) => { - route.openapi = this.webda - .getApplication() - .replaceVariables(route.openapi || {}, this.webda.getService(route.executor).getOpenApiReplacements()); - if (route.openapi.hidden && skipHidden) { - return; - } - route.openapi.hidden = false; - let path = i; - if (i.indexOf("{?") >= 0) { - path = i.substring(0, i.indexOf("{?")); - } - openapi.paths[path] = openapi.paths[path] || {}; - if (route._uriTemplateParse) { - openapi.paths[path].parameters = []; - route._uriTemplateParse.varNames.forEach(varName => { - const queryParam = route._queryParams?.find(i => i.name === varName); - if (queryParam) { - let name = varName; - if (name.startsWith("*")) { - name = name.substr(1); - } - openapi.paths[path].parameters.push({ - name, - in: "query", - required: queryParam.required, - schema: { - type: "string" - } - }); - return; - } - openapi.paths[path].parameters.push({ - // ^[a-zA-Z0-9._$-]+$] is the official regex of AWS - name: varName.replace(/[^a-zA-Z0-9._$-]/g, ""), - in: "path", - required: true, - schema: { - type: "string" - } - }); - }); - } - route.methods.forEach(method => { - let responses: { [key: string]: OpenAPIV3.ResponseObject }; - let schema: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject; - let description; - let summary; - let operationId; - let requestBody; - let tags = route.openapi.tags ?? []; - // Refactor here - if (route.openapi[method.toLowerCase()]) { - responses = route.openapi[method.toLowerCase()].responses; - schema = this.getOpenAPISchema(route.openapi[method.toLowerCase()].schemas?.output); - description = route.openapi[method.toLowerCase()].description; - summary = route.openapi[method.toLowerCase()].summary; - operationId = route.openapi[method.toLowerCase()].operationId; - tags.push(...(route.openapi[method.toLowerCase()].tags || [])); - requestBody = route.openapi[method.toLowerCase()].requestBody; - } - responses = responses || { - 200: { - description: "Operation success" - } - }; - for (let j in responses) { - // Add default answer - let code = parseInt(j); - if (code < 300 && code >= 200 && !responses[j].description) { - responses[j].description = "Operation success"; - responses[j].content ??= {}; - responses[j].content["application/json"] = { - schema - }; - } - } - // Add the service name if no tags are defined - if (tags.length === 0) { - tags.push(route.executor); - } - let desc: OpenAPIV3.OperationObject = { - tags, - responses: responses, - description, - summary, - operationId, - requestBody - }; - if (method.toLowerCase().startsWith("p") && route.openapi[method.toLowerCase()]?.schemas?.input) { - // Add request schema if exist - desc.requestBody ??= { - content: { - "application/json": { - schema: this.getOpenAPISchema(route.openapi[method.toLowerCase()]?.schemas?.input) - } - } - }; - } - openapi.paths[path][method.toLowerCase()] = desc; - tags - .filter(tag => !hasTag(tag)) - .forEach(tag => - openapi.tags.push({ - name: tag - }) - ); - }); - }); - } - } -} diff --git a/packages/core/src/services/asyncevents.spec.ts b/packages/core/src/services/asyncevents.spec.ts deleted file mode 100644 index 2619e168f..000000000 --- a/packages/core/src/services/asyncevents.spec.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { suite, test } from "@testdeck/mocha"; -import * as assert from "assert"; -import * as sinon from "sinon"; -import { CoreModel } from "../models/coremodel"; -import { Queue } from "../queues/queueservice"; -import { Store } from "../stores/store"; -import { WebdaTest } from "../test"; -import { AsyncEvent, EventService } from "./asyncevents"; - -@suite -class AsyncEventsTest extends WebdaTest { - @test - async simple() { - var users: Store = >this.webda.getService("Users"); - var eventsCount = 0; - var priorityEventsCount = 0; - var defaultQueue: Queue = >this.webda.getService("EventQueue"); - var priorityQueue: Queue = >this.webda.getService("PriorityEventQueue"); - var eventService: EventService = this.webda.getService("AsyncEvents"); - users.onAsync("Store.Saved", () => { - eventsCount++; - }); - users.onAsync( - "Store.Deleted", - () => { - priorityEventsCount++; - }, - "priority" - ); - await users.save({ - uuid: "test", - type: 1 - }); - let size = await defaultQueue.size(); - assert.strictEqual(size, 1); - size = await priorityQueue.size(); - assert.strictEqual(size, 0); - await users.delete("test"); - size = await priorityQueue.size(); - assert.strictEqual(size, 1); - size = await defaultQueue.size(); - assert.strictEqual(size, 1); - // Now that we have queued all messages see if they unqueue correctly - // We need to emulate the worker as it wont stop pulling from the queue - assert.strictEqual(eventsCount, 0); - assert.strictEqual(priorityEventsCount, 0); - let evts = await defaultQueue.receiveMessage(); - // @ts-ignore - await eventService.handleRawEvent(evts[0].Message); - assert.strictEqual(eventsCount, 1); - assert.strictEqual(priorityEventsCount, 0); - evts = await priorityQueue.receiveMessage(); - // @ts-ignore - await eventService.handleRawEvent(evts[0].Message); - assert.strictEqual(eventsCount, 1); - assert.strictEqual(priorityEventsCount, 1); - // Disable async and verify that it directly update now - eventService._async = false; - await users.save({ - uuid: "test", - type: 1 - }); - assert.strictEqual(eventsCount, 2); - assert.strictEqual(priorityEventsCount, 1); - await users.delete("test"); - assert.strictEqual(eventsCount, 2); - assert.strictEqual(priorityEventsCount, 2); - } - - @test worker() { - let eventService: EventService = this.webda.getService("AsyncEvents"); - eventService._queues = { - plop: { - // @ts-ignore - consume: () => "ploper" - }, - default: { - // @ts-ignore - consume: () => "default" - } - }; - assert.strictEqual(eventService.worker("plop"), "ploper"); - assert.strictEqual(eventService.worker(), "default"); - } - - @test async computeParameters() { - let evt = new EventService(this.webda, "none", { sync: false }); - await assert.rejects(() => evt.computeParameters(), /Need at least one queue for async to be ready/); - } - - @test async cov() { - let evt = new EventService(this.webda, "none", { sync: true }); - assert.throws( - () => evt.bindAsyncListener(evt, "plop", undefined, "plop"), - /EventService is not configured for asynchronous/ - ); - await assert.rejects( - // @ts-ignore - () => evt.handleEvent({ getMapper: () => "plop" }), - /Callbacks should not be empty, possible application version mismatch between emitter and worker/ - ); - evt._async = true; - let stub = sinon.spy(this.webda.getService("Users"), "on"); - try { - evt.bindAsyncListener(this.webda.getService("Users"), "plop", () => {}, "priority"); - evt.bindAsyncListener(this.webda.getService("Users"), "plop", () => {}, "priority"); - // Should call 'on' only once - assert.strictEqual(stub.callCount, 1); - } finally { - stub.restore(); - } - } -} diff --git a/packages/core/src/services/asyncevents.ts b/packages/core/src/services/asyncevents.ts deleted file mode 100644 index 5c0c716dc..000000000 --- a/packages/core/src/services/asyncevents.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { CoreModelDefinition } from "../models/coremodel"; -import { Queue } from "../queues/queueservice"; -import { Service, ServiceParameters } from "./service"; - -/** - * AsyncEvent representation - */ -export class AsyncEvent { - /** - * Service emitted the event - */ - service: string; - /** - * Type of event - */ - type: string; - /** - * Payload of the event - */ - payload: any; - /** - * Time - */ - time: Date; - - /** - * Used when serializing a service - */ - static ServiceTag = "#Webda:Service:"; - - constructor(service: string | Service | CoreModelDefinition, type, payload = {}) { - if (service instanceof Service) { - this.service = `service:${service.getName()}`; - } else if (typeof service === "string") { - this.service = service; - } else { - this.service = `model:${service.name}`; - } - this.type = type; - this.payload = payload; - this.time = new Date(); - } - - /** - * Allow payload to contain Service but do not serialize them - * replacing them by a #Webda:Service:${service.getName()} so it - * can be revived - * - * @returns - */ - toJSON() { - return { - ...this, - payload: JSON.stringify(this.payload, (_key: string, value: any) => { - if (value instanceof Service) { - return `${AsyncEvent.ServiceTag}${value.getName()}`; - } - return value; - }) - }; - } - - /** - * Deserialize from the queue, reviving any detected service - * - * @param data - * @param service - * @returns - */ - static fromQueue(data: any, service: Service) { - let evt = new AsyncEvent( - data.service, - data.type, - JSON.parse(data.payload, (_key: string, value: any) => { - if (typeof value === "string" && value.startsWith(AsyncEvent.ServiceTag)) { - return service.getService(value.substring(AsyncEvent.ServiceTag.length)); - } - return value; - }) - ); - evt.time = data.time; - return evt; - } - - /** - * Mapper name - * @returns - */ - getMapper() { - return this.service + "_" + this.type; - } -} - -interface QueueMap { - [key: string]: Queue; -} - -/** - * @inheritdoc - */ -export class EventServiceParameters extends ServiceParameters { - /** - * Queues to post async events to - */ - queues?: { [key: string]: string }; - /** - * Make the event sending asynchronous - */ - sync?: boolean; - - /** - * @inheritdoc - */ - constructor(params: any) { - super(params); - this.queues ??= {}; - this.sync ??= false; - } -} - -/** - * @category CoreServices - * @WebdaModda AsyncEvents - */ -class EventService extends Service { - _callbacks: any = {}; - _queues: QueueMap = {}; - _defaultQueue: string = ""; - _async: boolean; - - /** - * Load parameters - * - * @param params - * @ignore - */ - loadParameters(params: any): ServiceParameters { - return new EventServiceParameters(params); - } - - /** - * @ignore - * Setup the default routes - */ - async computeParameters(): Promise { - Object.keys(this.parameters.queues).forEach(key => { - // Define default as first queue - if (!this._defaultQueue) { - this._defaultQueue = key; - } - this._queues[key] = this.getService(this.parameters.queues[key]); - }); - - this._async = !this.parameters.sync; - // Check we have at least one queue to handle asynchronous - if (this._async && Object.keys(this._queues).length < 1) { - this._webda.log("ERROR", "Need at least one queue for async to be ready", this.parameters); - throw Error("Need at least one queue for async to be ready"); - } - } - - /** - * Bind a asynchronous event - * - * ```mermaid - * sequenceDiagram - * participant S as Service - * participant As as AsyncEventService - * participant Q as Queue - * participant Aw as AsyncEventService Worker - * - * As->>S: Bind event to a sendQueue listener - * activate As - * S->>As: Emit event - * As->>Q: Push the event to the queue - * deactivate As - * Aw->>Q: Consume queue - * Aw->>Aw: Call the original listener - * ``` - * - * @param service - * @param event - * @param callback - * @param queue - */ - bindAsyncListener(service: Service | CoreModelDefinition, event: string, callback, queue?: string) { - if (!this._async) { - throw Error("EventService is not configured for asynchronous"); - } - if (!queue) { - queue = this._defaultQueue; - } - let mapper = new AsyncEvent(service, event).getMapper(); - if (!this._callbacks[mapper]) { - if (service instanceof Service) { - service.on(event, data => this.pushEvent(service, event, queue, data)); - } else { - service.on(event, data => this.pushEvent(service, event, queue, data)); - } - this._callbacks[mapper] = []; - } - this._callbacks[mapper].push(callback); - } - - /** - * Synchronous Listener to proxy to async - * - * @param service - * @param type - * @param queue - * @param payload - * @returns - */ - async pushEvent(service: Service | CoreModelDefinition, type: string, queue: string, payload: any) { - let event = new AsyncEvent(service, type, payload); - if (this._async) { - return this._queues[queue].sendMessage(event); - } else { - return this.handleEvent(event); - } - } - - /** - * Process one event - * - * @param event - * @returns - */ - protected async handleEvent(event: AsyncEvent): Promise { - if (!this._callbacks[event.getMapper()]) { - return Promise.reject( - "Callbacks should not be empty, possible application version mismatch between emitter and worker" - ); - } - let promises = []; - this._callbacks[event.getMapper()].map(executor => { - promises.push(executor(event.payload, event)); - }); - // Need to handle the failure - await Promise.all(promises); - } - - /** - * - * @param eventBody serialized event - * @returns - */ - protected async handleRawEvent(event: AsyncEvent) { - return this.handleEvent(AsyncEvent.fromQueue(event, this)); - } - - /** - * Process asynchronous event on queue - * @param queue - * @returns - */ - worker(queue: string = this._defaultQueue): Promise { - // Avoid loops - this._async = false; - return this._queues[queue].consume(this.handleRawEvent.bind(this)); - } -} - -export { EventService, QueueMap }; diff --git a/packages/core/src/services/authentication.ts b/packages/core/src/services/authentication.ts index 80c322977..5da5290f9 100644 --- a/packages/core/src/services/authentication.ts +++ b/packages/core/src/services/authentication.ts @@ -1,6 +1,8 @@ import bcrypt from "bcryptjs"; import { Counter, EventWithContext } from "../core"; import { WebdaError } from "../errors"; +import { Event } from "../events"; +import { Context } from "../index"; import { CoreModelDefinition } from "../models/coremodel"; import { Ident } from "../models/ident"; import { User } from "../models/user"; @@ -11,6 +13,25 @@ import { HttpContext, HttpMethodType } from "../utils/httpcontext"; import CryptoService from "./cryptoservice"; import { Mailer } from "./mailer"; +export class AuthenticationGetMeEvent extends Event<{ + user: User; + context: Context; +}> { + //static type: "authentication.getme"; + static getType(_source: Function): string { + return "authentication.getme"; + } +} + +export class AuthenticationRegisterEvent extends Event<{ + user: User; + context: Context; + data: any; + identId: string; +}> { + type: "authentication.register"; +} + /** * Emitted when the /me route is called */ @@ -231,10 +252,10 @@ export type AuthenticationEvents = { * @WebdaModda */ -class Authentication< - T extends AuthenticationParameters = AuthenticationParameters, - E extends AuthenticationEvents = AuthenticationEvents -> extends Service { +class Authentication extends Service< + T, + AuthenticationGetMeEvent +> { _identModel: CoreModelDefinition; _userModel: CoreModelDefinition; /** @@ -604,7 +625,7 @@ class Authentication< if (await this._identModel.ref(`${identId}_${provider}`).exists()) { throw new Error("Ident is already known"); } - let ctx = await this._webda.newWebContext(new HttpContext("fake", "GET", "/")); + let ctx = await this.webda.newWebContext(new HttpContext("fake", "GET", "/")); // Pretend we logged in with the ident await this.onIdentLogin(ctx, provider, identId, profile); } @@ -618,12 +639,15 @@ class Authentication< true ); this.metrics.registration.inc(); - await this.emitSync("Authentication.Register", { - user: user, - data: data, - context: ctx, - identId - }); + await new AuthenticationRegisterEvent( + { + user: user, + data: data, + context: ctx, + identId + }, + this + ).emit(); return user; } diff --git a/packages/core/src/services/binary.ts b/packages/core/src/services/binary.ts index c386ba562..9b5ec3249 100644 --- a/packages/core/src/services/binary.ts +++ b/packages/core/src/services/binary.ts @@ -1,12 +1,14 @@ "use strict"; +import { NotEnumerable } from "@webda/tsc-esm"; import * as crypto from "crypto"; import * as fs from "fs"; import * as mime from "mime-types"; import * as path from "path"; import { Readable } from "stream"; -import { Core, Counter, WebdaError } from "../index"; -import { CoreModel, NotEnumerable } from "../models/coremodel"; -import { EventStoreDeleted, MappingService, Store } from "../stores/store"; +import { Event } from "../events"; +import { Core, Counter, FileBinary, WebdaError } from "../index"; +import { CoreModel } from "../models/coremodel"; +import { MappingService, Store } from "../stores/store"; import { Context, WebContext } from "../utils/context"; import { Service, ServiceParameters } from "./service"; @@ -34,6 +36,13 @@ export interface EventBinaryMetadataUpdate extends EventBinaryUploadSuccess { metadata: BinaryMetadata; } +export class BinaryGetEvent extends Event<{ object: FileBinary }> {} +export class BinaryDeleteEvent extends Event<{ object: FileBinary }> {} +export class BinaryUploadSuccessEvent extends Event<{ object: FileBinary; target: CoreModel }> {} +export class BinaryCreateEvent extends Event<{ object: FileBinary; target: CoreModel }> {} +export class BinaryMetadataUpdateEvent extends Event<{ object: FileBinary }> {} +export class BinaryMetadataUpdatedEvent extends Event<{ object: FileBinary }> {} + /** * Emitted if binary does not exist */ @@ -564,11 +573,8 @@ export type BinaryModel = CoreModel & T; * @abstract * @WebdaModda Binary */ -export abstract class BinaryService< - T extends BinaryParameters = BinaryParameters, - E extends BinaryEvents = BinaryEvents - > - extends Service +export abstract class BinaryService + extends Service implements MappingService { _lowercaseMaps: any; @@ -620,11 +626,12 @@ export abstract class BinaryService< } await object.checkAct(context, "get_binary"); const file: BinaryMap = Array.isArray(object[property]) ? object[property][index] : object[property]; - await this.emitSync("Binary.Get", { - object: file, - service: this, - context: context - }); + await new BinaryGetEvent( + { + object: file + }, + this + ).emit(); let url = await this.getRedirectUrlFromObject(file, context); // No url, we return the file if (url === null) { @@ -752,10 +759,12 @@ export abstract class BinaryService< * @emits 'binaryGet' */ async get(info: BinaryMap): Promise { - await this.emitSync("Binary.Get", { - object: info, - service: this - }); + await new BinaryGetEvent( + { + object: info + }, + this + ).emit(); this.metrics.download.inc(); return this._get(info); } @@ -767,10 +776,12 @@ export abstract class BinaryService< * @param {String} filepath to save the binary to */ async downloadTo(info: BinaryMap, filename): Promise { - await this.emitSync("Binary.Get", { - object: info, - service: this - }); + await new BinaryGetEvent( + { + object: info + }, + this + ).emit(); this.metrics.download.inc(); let readStream: any = await this._get(info); let writeStream = fs.createWriteStream(filename); @@ -784,7 +795,7 @@ export abstract class BinaryService< // Stubing the fs module in ESM seems complicated for now /* c8 ignore next 3 */ } catch (err) { - this._webda.log("ERROR", err); + this.webda.log("ERROR", err); } return reject(src); }); @@ -815,16 +826,17 @@ export abstract class BinaryService< this._lowercaseMaps = {}; Object.keys(map).forEach(prop => { this._lowercaseMaps[prop.toLowerCase()] = prop; - let reverseStore = this._webda.getService(prop); + let reverseStore = this.webda.getService(prop); if (reverseStore === undefined || !(reverseStore instanceof Store)) { - this._webda.log("WARN", "Can't setup mapping as store ", prop, " doesn't exist"); + this.webda.log("WARN", "Can't setup mapping as store ", prop, " doesn't exist"); map[prop]["-onerror"] = "NoStore"; return; } for (let i in map[prop]) { - reverseStore.addReverseMap(map[prop][i], this); + reverseStore["addReverseMap"](map[prop][i], this); } // Cascade delete + /* reverseStore.on("Store.Deleted", async (evt: EventStoreDeleted) => { let infos = []; if (evt.object[map[prop]]) { @@ -832,6 +844,7 @@ export abstract class BinaryService< } await Promise.all(infos.map(info => this.cascadeDelete(info, evt.object.getUuid()))); }); + */ }); } @@ -870,21 +883,7 @@ export abstract class BinaryService< if (this.handleBinary(object.__type, property) !== -1) { return; } - // DELETE_IN_4.0.0 - const name = object.getStore().getName(); - let map = this.parameters.map[this._lowercaseMaps[name.toLowerCase()]]; - if (map === undefined) { - throw new Error("Unknown mapping"); - } - if (Array.isArray(map) && map.indexOf(property) === -1) { - throw new Error("Unknown mapping"); - } - /* - // END_DELETE_IN_4.0.0 throw new Error("Unknown mapping"); - // DELETE_IN_4.0.0 - */ - // END_DELETE_IN_4.0.0 } /** @@ -896,11 +895,13 @@ export abstract class BinaryService< if (Array.isArray(object[property]) && object[property].find(i => i.hash === file.hash)) { return; } - await this.emitSync("Binary.UploadSuccess", { - object: file, - service: this, - target: object - }); + await new BinaryUploadSuccessEvent( + { + object: file, + target: object + }, + this + ).emit(); const relations = this.getWebda().getApplication().getRelations(object); const cardinality = (relations.binaries || []).find(p => p.attribute === property)?.cardinality || "MANY"; if (cardinality === "MANY") { @@ -908,11 +909,13 @@ export abstract class BinaryService< } else { await object.getStore().setAttribute(object_uid, property, file); } - await this.emitSync("Binary.Create", { - object: file, - service: this, - target: object - }); + await new BinaryCreateEvent( + { + object: file, + target: object + }, + this + ).emit(); this.metrics.upload.inc(); } @@ -942,10 +945,12 @@ export abstract class BinaryService< } else { object.getStore().removeAttribute(object.getUuid(), property); } - await this.emitSync("Binary.Delete", { - object: info, - service: this - }); + await new BinaryDeleteEvent( + { + object: info + }, + this + ).emit(); this.metrics.delete.inc(); return update; } @@ -1140,7 +1145,7 @@ export abstract class BinaryService< * @returns */ protected getOperationName(): string { - return this._name.toLowerCase() === "binary" ? "" : this._name; + return this.name.toLowerCase() === "binary" ? "" : this.name; } /** @@ -1253,14 +1258,16 @@ export abstract class BinaryService< throw new WebdaError.BadRequest("Metadata is too big: 4kb max"); } let evt = { - service: this, object: file, target: object }; - await this.emitSync("Binary.MetadataUpdate", { - ...evt, - metadata - }); + await new BinaryMetadataUpdateEvent( + { + ...evt, + metadata + }, + this + ).emit(); file.metadata = metadata; // Update mapper on purpose await object.getStore().patch( @@ -1271,7 +1278,13 @@ export abstract class BinaryService< false ); this.metrics.metadataUpdate.inc(); - await this.emitSync("Binary.MetadataUpdated", evt); + await new BinaryMetadataUpdatedEvent( + { + ...evt, + metadata + }, + this + ).emit(); } } } diff --git a/packages/core/src/services/cloudbinary.ts b/packages/core/src/services/cloudbinary.ts index 5e2a0e926..a564cb38a 100644 --- a/packages/core/src/services/cloudbinary.ts +++ b/packages/core/src/services/cloudbinary.ts @@ -59,7 +59,7 @@ export abstract class CloudBinary extends Service { + T extends ConfigurationServiceParameters = ConfigurationServiceParameters +> extends Service { protected serializedConfiguration: any; /** * @@ -134,7 +127,7 @@ export default class ConfigurationService< } // Add webda info - this.watch("$.services", (updates: any) => this._webda.reinit(updates)); + this.watch("$.services", (updates: any) => this.webda.reinit(updates)); return this; } @@ -205,10 +198,11 @@ export default class ConfigurationService< this.log("DEBUG", "Refreshing configuration"); const newConfig = (await this.loadConfiguration()) || this.parameters.default; - this.emit("Configuration.Loaded", newConfig); const serializedConfig = JSON.stringify(newConfig); if (serializedConfig !== this.serializedConfiguration) { - this.emit("Configuration.Applying", newConfig); + this.getWebda().emit("Webda.Configuration.Updated", { + configuration: newConfig + }); this.log("DEBUG", "Apply new configuration"); this.serializedConfiguration = serializedConfig; this.configuration = newConfig; @@ -230,7 +224,9 @@ export default class ConfigurationService< } }); await Promise.all(promises); - this.emit("Configuration.Applied", newConfig); + this.getWebda().emit("Webda.Configuration.Applied", { + configuration: newConfig + }); } // If the ConfigurationProvider cannot trigger we check at interval if (this.interval) { @@ -251,7 +247,6 @@ export default class ConfigurationService< */ async loadAndStoreConfiguration(): Promise<{ [key: string]: any }> { let res = await this.loadConfiguration(); - this.emit("Configuration.Loaded", res); this.serializedConfiguration = JSON.stringify(res); return res; } diff --git a/packages/core/src/services/cron.ts b/packages/core/src/services/cron.ts index ed35182ea..32b1c5dea 100644 --- a/packages/core/src/services/cron.ts +++ b/packages/core/src/services/cron.ts @@ -109,7 +109,7 @@ class CronService extends Service { return; } this._scanned = true; - this.crons.push(...CronService.loadAnnotations(this._webda.getServices())); + this.crons.push(...CronService.loadAnnotations(this.webda.getServices())); } getCrontab() { diff --git a/packages/core/src/services/cryptoservice.ts b/packages/core/src/services/cryptoservice.ts index f33527a3c..3f88051e6 100644 --- a/packages/core/src/services/cryptoservice.ts +++ b/packages/core/src/services/cryptoservice.ts @@ -1,3 +1,4 @@ +import { DeepPartial } from "@webda/tsc-esm"; import { createCipheriv, createDecipheriv, @@ -11,7 +12,7 @@ import jwt from "jsonwebtoken"; import * as util from "util"; import { Core, OperationContext, RegistryEntry, Store } from "../index"; import { JSONUtils } from "../utils/serializers"; -import { DeepPartial, Inject, Route, Service, ServiceParameters } from "./service"; +import { Inject, Route, Service, ServiceParameters } from "./service"; export class SecretString { constructor( diff --git a/packages/core/src/services/domainservice.ts b/packages/core/src/services/domainservice.ts index d8c8f6eb7..6a149769f 100644 --- a/packages/core/src/services/domainservice.ts +++ b/packages/core/src/services/domainservice.ts @@ -1,19 +1,6 @@ +import { DeepPartial } from "@webda/tsc-esm"; import { JSONSchema7 } from "json-schema"; -import { - Application, - Core, - CoreModelDefinition, - DeepPartial, - JSONUtils, - Methods, - ModelAction, - OperationDefinition, - Service, - ServiceParameters, - Store, - WebContext -} from "../index"; -import { OpenAPIWebdaDefinition } from "../router"; +import { Application, CoreModelDefinition, JSONUtils, OperationDefinition, Service, ServiceParameters } from "../index"; import { TransformCase, TransformCaseType } from "../utils/case"; /** @@ -209,49 +196,6 @@ export class DomainServiceParameters extends ServiceParameters { } } -/** - * Swagger static html - */ -const swagger = ` - - - - - - - - -
- - - - - -`; - /** * Domain Service expose all the models as Route and Operations * @@ -337,1025 +281,3 @@ export abstract class DomainService extends DomainService { - /** - * OpenAPI cache - */ - openapiContent: string; - /** - * Override to fallback on isDebug for exposeOpenAPI - * @returns - */ - resolve() { - this.parameters.exposeOpenAPI ??= this.getWebda().isDebug(); - super.resolve(); - if (this.parameters.exposeOpenAPI) { - if (typeof this.parameters.exposeOpenAPI === "string") { - this.addRoute(this.parameters.exposeOpenAPI, ["GET"], this.openapi, { hidden: true }); - } else { - this.addRoute(".", ["GET"], this.openapi, { hidden: true }); - } - } - return this; - } - - /** - * @override - */ - loadParameters(params: DeepPartial): DomainServiceParameters { - return new RESTDomainServiceParameters(params); - } - - /** - * Handle one model and expose it based on the service - * @param model - * @param name - * @param context - * @returns - */ - handleModel(model: CoreModelDefinition, name: string, context: any): boolean { - const depth = context.depth || 0; - const relations = model.getRelations(); - const injectAttribute = relations?.parent?.attribute; - const app = this.getWebda().getApplication(); - - const injector = ( - service: Store, - method: Methods, - type: "SET" | "QUERY" | "GET" | "DELETE", - ...args: any[] - ) => { - return async (context: WebContext) => { - let parentId = `pid.${depth - 1}`; - if (type === "SET" && injectAttribute && depth > 0) { - (await context.getInput())[injectAttribute] = context.getPathParameters()[parentId]; - } else if (type === "QUERY") { - let input = await context.getInput({ defaultValue: { q: "" } }); - let q; - if (context.getHttpContext().getMethod() === "GET") { - q = context.getParameters().q; - } else { - q = input.q; - } - let query = q ? ` AND (${q})` : ""; - if (injectAttribute) { - query = ` AND ${injectAttribute} = "${context.getPathParameters()[parentId]}"` + query; - } - if (args[0] !== 0) { - query = `__types CONTAINS "${model.getIdentifier()}"` + query; - } else if (query.startsWith(" AND ")) { - query = query.substring(5); - } - if (context.getHttpContext().getMethod() === "GET") { - context.getParameters().q = query; - } else { - input.q = query; - } - this.log("TRACE", `Query modified to '${query}' from '${q}' ${args[0]}`); - } - // Complete the uuid if needed - if (context.getParameters().uuid) { - context.getParameters().uuid = model.completeUid(context.getParameters().uuid); - } - await service[method](context, ...args); - }; - }; - - // Update prefix - const prefix = - (context.prefix || (this.parameters.url.endsWith("/") ? this.parameters.url : this.parameters.url + "/")) + - this.transformName(name); - context.prefix = prefix + `/{pid.${depth}}/`; - - // Register the model url - this.getWebda().getRouter().registerModelUrl(app.getModelName(model), prefix); - - let openapi: OpenAPIWebdaDefinition = { - [this.parameters.queryMethod.toLowerCase()]: { - tags: [name], - summary: `Query ${name}`, - operationId: `query${name}`, - requestBody: - this.parameters.queryMethod === "GET" - ? undefined - : { - content: { - "application/json": { - schema: { - properties: { - q: { - type: "string" - } - } - } - } - } - }, - parameters: - this.parameters.queryMethod === "GET" - ? [ - { - name: "q", - in: "query", - description: "Query to execute", - schema: { - type: "string" - } - } - ] - : [], - responses: { - "200": { - description: "Operation success", - content: { - "application/json": { - schema: { - properties: { - continuationToken: { - type: "string" - }, - results: { - type: "array", - items: { - $ref: `#/components/schemas/${model.getIdentifier()}` - } - } - } - } - } - } - }, - "400": { - description: "Query is invalid" - }, - "403": { - description: "You don't have permissions" - } - } - } - }; - model.Expose.restrict.query || - this.addRoute( - `${prefix}${this.parameters.queryMethod === "GET" ? "{?q?}" : ""}`, - [this.parameters.queryMethod], - injector(model.store(), "httpQuery", "QUERY", model.store().handleModel(model)), - openapi - ); - openapi = { - post: { - tags: [name], - summary: `Create ${name}`, - operationId: `create${name}`, - requestBody: { - content: { - "application/json": { - schema: { - $ref: `#/components/schemas/${model.getIdentifier()}` - } - } - } - }, - responses: { - "200": { - description: "Operation success", - content: { - "application/json": { - schema: { - $ref: `#/components/schemas/${model.getIdentifier()}` - } - } - } - }, - "400": { - description: "Invalid input" - }, - "403": { - description: "You don't have permissions" - }, - "409": { - description: "Object already exists" - } - } - } - }; - model.Expose.restrict.create || - this.addRoute( - `${prefix}`, - ["POST"], - injector(model.store(), "operationCreate", "SET", model.getIdentifier()), - openapi - ); - openapi = { - delete: { - tags: [name], - operationId: `delete${name}`, - description: `Delete ${name} if the permissions allow`, - summary: `Delete a ${name}`, - responses: { - "204": { - description: "Operation success" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - } - }; - model.Expose.restrict.delete || - this.addRoute(`${prefix}/{uuid}`, ["DELETE"], injector(model.store(), "httpDelete", "DELETE"), openapi); - let openapiInfo = { - tags: [name], - operationId: `update${name}`, - description: `Update ${name} if the permissions allow`, - summary: `Update a ${name}`, - requestBody: { - content: { - "application/json": { - schema: { - $ref: `#/components/schemas/${model.getIdentifier()}` - } - } - } - }, - responses: { - "204": { - description: "Operation success" - }, - "400": { - description: "Invalid input" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - }; - openapi = { - put: openapiInfo, - patch: openapiInfo - }; - model.Expose.restrict.update || - this.addRoute(`${prefix}/{uuid}`, ["PUT", "PATCH"], injector(model.store(), "httpUpdate", "SET"), openapi); - openapi = { - get: { - tags: [name], - description: `Retrieve ${name} model if permissions allow`, - summary: `Retrieve a ${name}`, - operationId: `get${name}`, - responses: { - "200": { - content: { - "application/json": { - schema: { - $ref: `#/components/schemas/${model.getIdentifier()}` - } - } - } - }, - "400": { - description: "Object is invalid" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - } - }; - model.Expose.restrict.get || - this.addRoute(`${prefix}/{uuid}`, ["GET"], injector(model.store(), "httpGet", "GET"), openapi); - - // Add all actions - // Actions cannot be restricted as its purpose is to be exposed - let actions = model.getActions(); - Object.keys(actions).forEach(actionName => { - let action: ModelAction = actions[actionName]; - openapi = { - ...action.openapi - }; - (action.methods || ["PUT"]).forEach(method => { - openapi[method.toLowerCase()] = { - tags: [name], - ...(action.openapi?.[method.toLowerCase()] ?? {}) - }; - }); - if (app.hasSchema(`${model.getIdentifier(false)}.${actionName}.input`)) { - Object.keys(openapi) - .filter(k => ["get", "post", "put", "patch", "delete"].includes(k)) - .forEach(k => { - openapi[k].requestBody = { - content: { - "application/json": { - schema: { - $ref: `#/components/schemas/${model.getIdentifier(false)}.${actionName}.input` - } - } - } - }; - }); - } - if (app.hasSchema(`${model.getIdentifier(false)}.${actionName}.output`)) { - Object.keys(openapi) - .filter(k => ["get", "post", "put", "patch", "delete"].includes(k)) - .forEach(k => { - openapi[k].responses ??= {}; - openapi[k].responses["200"] ??= {}; - openapi[k].responses["200"].content = { - "application/json": { - schema: { - $ref: `#/components/schemas/${model.getIdentifier(false)}.${actionName}.output` - } - } - }; - }); - } - this.addRoute( - action.global ? `${prefix}/${actionName}` : `${prefix}/{uuid}/${actionName}`, - action.methods || ["PUT"], - async ctx => { - if (action.global) { - return model.store().httpGlobalAction(ctx, model); - } else { - ctx.getParameters().uuid = model.completeUid(ctx.getParameters().uuid); - return model.store().httpAction(ctx, action.method); - } - }, - openapi - ); - }); - - /* - Binaries should expose several methods - If cardinality is ONE - GET to download the binary - POST to upload a binary directly - PUT to upload a binary with challenge - DELETE /{hash} to delete a binary - PUT /{hash} to update metadata - GET /url to get a signed url - - If cardinality is MANY - GET /{index} to download the binary - GET /{index}/url to get a signed url - POST to upload a binary directly - PUT to upload a binary with challenge - DELETE /{index}/{hash} to delete a binary - PUT /{index}/{hash} to update metadata - */ - - (relations.binaries || []).forEach(binary => { - const store = Core.get().getBinaryStore(model, binary.attribute); - const modelInjector = async (ctx: WebContext) => { - ctx.getParameters().model = model.getIdentifier(); - ctx.getParameters().property = binary.attribute; - await store.httpRoute(ctx); - }; - const modelInjectorChallenge = async (ctx: WebContext) => { - ctx.getParameters().model = model.getIdentifier(); - ctx.getParameters().property = binary.attribute; - await store.httpChallenge(ctx); - }; - const modelInjectorGet = async (ctx: WebContext) => { - ctx.getParameters().model = model.getIdentifier(); - ctx.getParameters().property = binary.attribute; - await store.httpGet(ctx); - }; - openapi = { - put: { - tags: [name], - summary: `Upload ${binary.attribute} of ${name} after challenge`, - description: `You will need to call the challenge method first to get a signed url to upload the content\nIf the data is already known then done is returned`, - operationId: `upload${name}${binary.attribute}`, - requestBody: { - content: { - "application/octet-stream": { - schema: { - type: "object", - required: ["hash", "challenge"], - properties: { - hash: { - type: "string", - description: "md5(data)" - }, - challenge: { - type: "string", - description: "md5('Webda' + data)" - }, - size: { - type: "number", - description: "Size of the data" - }, - name: { - type: "string", - description: "Name of the file" - }, - mimetype: { - type: "string", - description: "Mimetype of the file" - }, - metadata: { - type: "object", - description: "Metadata to add to the binary" - } - } - } - } - } - }, - responses: { - "200": { - description: "Operation success", - content: { - "application/octet-stream": { - schema: { - type: "object", - properties: { - done: { - type: "boolean" - }, - url: { - type: "string" - }, - method: { - type: "string" - }, - md5: { - type: "string", - description: - "base64 md5 of the data\nThe next request requires Content-MD5 to be added with this one along with Content-Type: 'application/octet-stream'" - } - } - } - } - } - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - } - }; - this.addRoute(`${prefix}/{uuid}/${binary.attribute}`, ["PUT"], modelInjectorChallenge, openapi); - openapi = { - post: { - tags: [name], - summary: `Upload ${binary.attribute} of ${name} directly`, - description: `You can upload the content directly to the server\nThis is not the recommended way as it wont be able to optimize network traffic`, - operationId: `upload${name}${binary.attribute}`, - requestBody: { - content: { - "application/octet-stream": { - schema: { - type: "string", - format: "binary" - } - } - } - }, - responses: { - "204": { - description: "" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Object does not exist or attachment does not exist" - } - } - } - }; - this.addRoute(`${prefix}/{uuid}/${binary.attribute}`, ["POST"], modelInjector, openapi); - - let rootUrl = `${prefix}/{uuid}/${binary.attribute}`; - if (binary.cardinality === "MANY") { - rootUrl += "/{index}"; - } - - openapi = { - get: { - tags: [name], - summary: `Download ${binary.attribute} of ${name}`, - operationId: `download${name}${binary.attribute}`, - responses: { - "200": { - description: "Operation success", - content: { - "application/octet-stream": { - schema: { - type: "string", - format: "binary" - } - } - } - }, - "302": { - description: "Redirect to the binary" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - } - }; - this.addRoute(`${rootUrl}`, ["GET"], modelInjectorGet, openapi); - openapi = { - delete: { - tags: [name], - summary: `Delete ${binary.attribute} of ${name}`, - responses: { - "204": { - description: "" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Object does not exist or attachment does not exist" - }, - "412": { - description: "Provided hash does not match" - } - } - } - }; - this.addRoute(`${rootUrl}/{hash}`, ["DELETE"], modelInjector, openapi); - openapi = { - put: { - tags: [name], - summary: `Update metadata of ${binary.attribute} of ${name}`, - operationId: `update${name}${binary.attribute}`, - requestBody: { - content: { - "application/json": { - schema: { - type: "object", - description: "Metadata to add to the binary", - additionalProperties: true - } - } - } - }, - responses: { - "204": { - description: "" - }, - "400": { - description: "Invalid input: metadata are limited to 4kb" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Object does not exist or attachment does not exist" - }, - "412": { - description: "Provided hash does not match" - } - } - } - }; - this.addRoute(`${rootUrl}/{hash}`, ["PUT"], modelInjector, openapi); - openapi = { - get: { - tags: [name], - summary: `Get a signed url to download ${binary.attribute} of ${name} with a limited lifetime`, - responses: { - "200": { - description: "Operation success", - content: { - "application/json": { - schema: { - type: "object", - properties: { - Location: { - type: "string", - description: "Signed url to download the binary" - }, - Map: { - $ref: "#/components/schemas/BinaryMap" - } - } - } - } - } - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Object does not exist or attachment does not exist" - } - } - } - }; - this.addRoute(`${rootUrl}/url`, ["GET"], modelInjectorGet, openapi); - }); - - return true; - } - - /** - * Serve the openapi with the swagger-ui - * @param ctx - */ - async openapi(ctx: WebContext) { - this.openapiContent ??= swagger.replace("{{OPENAPI}}", JSON.stringify(this.getWebda().exportOpenAPI(true))); - ctx.write(this.openapiContent); - } -} - - - - - /** - * Handle POST - * @param ctx - */ - @Route(".", ["POST"], { - post: { - description: "The way to create a new ${modelName} model", - summary: "Create a new ${modelName}", - operationId: "create${modelName}", - requestBody: { - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/${modelName}" - } - } - } - }, - responses: { - "200": { - description: "Retrieve model", - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/${modelName}" - } - } - } - }, - "400": { - description: "Object is invalid" - }, - "403": { - description: "You don't have permissions" - }, - "409": { - description: "Object already exists" - } - } - } - }) - async httpCreate(ctx: WebContext) { - return this.operationCreate(ctx, this.parameters.model); - } - - /** - * Create a new object based on the context - * @param ctx - * @param model - */ - async operationCreate(ctx: OperationContext, model: string) { - let body = await ctx.getInput(); - const modelPrototype = this.getWebda().getApplication().getModel(model); - let object = modelPrototype.factory(body, ctx); - object._creationDate = new Date(); - await object.checkAct(ctx, "create"); - try { - await object.validate(ctx, body); - } catch (err) { - this.log("INFO", "Object is not valid", err); - throw new WebdaError.BadRequest("Object is not valid"); - } - if (object[this._uuidField] && (await this.exists(object[this._uuidField]))) { - throw new WebdaError.Conflict("Object already exists"); - } - await this.save(object); - ctx.write(object); - const evt = { - context: ctx, - values: body, - object: object, - object_id: object.getUuid(), - store: this - }; - await Promise.all([object.__class.emitSync("Store.WebCreate", evt), this.emitSync("Store.WebCreate", evt)]); - } - - /** - * Handle object action - * @param ctx - */ - async httpAction(ctx: WebContext, actionMethod?: string) { - let action = ctx.getHttpContext().getUrl().split("/").pop(); - actionMethod ??= action; - let object = await this.get(ctx.parameter("uuid"), ctx); - if (object === undefined || object.__deleted) { - throw new WebdaError.NotFound("Object not found or is deleted"); - } - const inputSchema = `${object.__class.getIdentifier(false)}.${action}.input`; - if (this.getWebda().getApplication().hasSchema(inputSchema)) { - const input = await ctx.getInput(); - try { - this.getWebda().validateSchema(inputSchema, input); - } catch (err) { - this.log("INFO", "Object invalid", err); - this.log("INFO", "Object invalid", inputSchema, input, this.getWebda().getApplication().getSchema(inputSchema)); - throw new WebdaError.BadRequest("Body is invalid"); - } - } - await object.checkAct(ctx, action); - const evt = { - action: action, - object: object, - store: this, - context: ctx - }; - await Promise.all([this.emitSync("Store.Action", evt), object.__class.emitSync("Store.Action", evt)]); - const res = await object[actionMethod](ctx); - if (res) { - ctx.write(res); - } - const evtActioned = { - action: action, - object: object, - store: this, - context: ctx, - result: res - }; - await Promise.all([ - this.emitSync("Store.Actioned", evtActioned), - object?.__class.emitSync("Store.Actioned", evtActioned) - ]); - } - - /** - * Handle collection action - * @param ctx - */ - async httpGlobalAction(ctx: WebContext, model: CoreModelDefinition = this._model) { - let action = ctx.getHttpContext().getUrl().split("/").pop(); - const evt = { - action: action, - store: this, - context: ctx, - model - }; - await Promise.all([this.emitSync("Store.Action", evt), model.emitSync("Store.Action", evt)]); - const res = await model[action](ctx); - if (res) { - ctx.write(res); - } - const evtActioned = { - action: action, - store: this, - context: ctx, - result: res, - model - }; - await Promise.all([this.emitSync("Store.Actioned", evtActioned), model?.emitSync("Store.Actioned", evtActioned)]); - } - - /** - * Handle HTTP Update for an object - * - * @param ctx context of the request - */ - @Route("./{uuid}", ["PUT", "PATCH"], { - put: { - description: "Update a ${modelName} if the permissions allow", - summary: "Update a ${modelName}", - operationId: "update${modelName}", - schemas: { - input: "${modelName}", - output: "${modelName}" - }, - responses: { - "200": {}, - "400": { - description: "Object is invalid" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - }, - patch: { - description: "Patch a ${modelName} if the permissions allow", - summary: "Patch a ${modelName}", - operationId: "partialUpdatet${modelName}", - schemas: { - input: "${modelName}" - }, - responses: { - "204": { - description: "" - }, - "400": { - description: "Object is invalid" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - } - }) - async httpUpdate(ctx: WebContext) { - const { uuid } = ctx.getParameters(); - const body = await ctx.getInput(); - body[this._uuidField] = uuid; - let object = await this.get(uuid, ctx); - if (!object || object.__deleted) throw new WebdaError.NotFound("Object not found or is deleted"); - await object.checkAct(ctx, "update"); - if (ctx.getHttpContext().getMethod() === "PATCH") { - try { - await object.validate(ctx, body, true); - } catch (err) { - this.log("INFO", "Object invalid", err, object); - throw new WebdaError.BadRequest("Object is not valid"); - } - let updateObject: any = new this._model(); - // Clean any default attributes from the model - Object.keys(updateObject) - .filter(i => i !== "__class") - .forEach(i => { - delete updateObject[i]; - }); - updateObject.setUuid(uuid); - updateObject.load(body, false, false); - await this.patch(updateObject); - object = undefined; - } else { - let updateObject: any = new this._model(); - updateObject.load(body); - // Copy back the _ attributes - Object.keys(object) - .filter(i => i.startsWith("_")) - .forEach(i => { - updateObject[i] = object[i]; - }); - try { - await updateObject.validate(ctx, body); - } catch (err) { - this.log("INFO", "Object invalid", err); - throw new WebdaError.BadRequest("Object is not valid"); - } - - // Add mappers back to - object = await this.update(updateObject); - } - ctx.write(object); - const evt = { - context: ctx, - updates: body, - object: object, - store: this, - method: <"PATCH" | "PUT">ctx.getHttpContext().getMethod() - }; - await Promise.all([object?.__class.emitSync("Store.WebUpdate", evt), this.emitSync("Store.WebUpdate", evt)]); - } - - /** - * Handle GET on object - * - * @param ctx context of the request - */ - @Route("./{uuid}", ["GET"], { - get: { - description: "Retrieve ${modelName} model if permissions allow", - summary: "Retrieve a ${modelName}", - operationId: "get${modelName}", - schemas: { - output: "${modelName}" - }, - responses: { - "200": {}, - "400": { - description: "Object is invalid" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - } - }) - async httpGet(ctx: WebContext) { - let uuid = ctx.parameter("uuid"); - let object = await this.get(uuid, ctx); - await this.emitSync("Store.WebGetNotFound", { - context: ctx, - uuid, - store: this - }); - if (object === undefined || object.__deleted) { - throw new WebdaError.NotFound("Object not found or is deleted"); - } - await object.checkAct(ctx, "get"); - ctx.write(object); - const evt = { - context: ctx, - object: object, - store: this - }; - await Promise.all([this.emitSync("Store.WebGet", evt), object.__class.emitSync("Store.WebGet", evt)]); - ctx.write(object); - } - - /** - * Handle HTTP request - * - * @param ctx context of the request - * @returns - */ - @Route("./{uuid}", ["DELETE"], { - delete: { - operationId: "delete${modelName}", - description: "Delete ${modelName} if the permissions allow", - summary: "Delete a ${modelName}", - responses: { - "204": { - description: "" - }, - "403": { - description: "You don't have permissions" - }, - "404": { - description: "Unknown object" - } - } - } - }) - async httpDelete(ctx: WebContext) { - let uuid = ctx.parameter("uuid"); - let object = await this.getWebda().runAsSystem(async () => { - const object = await this.get(uuid, ctx); - if (!object || object.__deleted) throw new WebdaError.NotFound("Object not found or is deleted"); - return object; - }); - await object.checkAct(ctx, "delete"); - // http://stackoverflow.com/questions/28684209/huge-delay-on-delete-requests-with-204-response-and-no-content-in-objectve-c# - // IOS don't handle 204 with Content-Length != 0 it seems - // Might still run into: Have trouble to handle the Content-Length on API Gateway so returning an empty object for now - ctx.writeHead(204, { "Content-Length": "0" }); - await this.delete(uuid); - const evt = { - context: ctx, - object_id: uuid, - store: this - }; - await Promise.all([this.emitSync("Store.WebDelete", evt), object.__class.emitSync("Store.WebDelete", evt)]); - } diff --git a/packages/core/src/services/fileconfiguration.ts b/packages/core/src/services/fileconfiguration.ts index 5db675079..1a0dde0b8 100644 --- a/packages/core/src/services/fileconfiguration.ts +++ b/packages/core/src/services/fileconfiguration.ts @@ -1,5 +1,5 @@ import { existsSync, watchFile } from "fs"; -import { WebdaError } from "../index"; +import { ServiceParameters, WebdaError } from "../index"; import { FileUtils } from "../utils/serializers"; import ConfigurationService, { ConfigurationServiceParameters } from "./configuration"; @@ -10,23 +10,37 @@ import ConfigurationService, { ConfigurationServiceParameters } from "./configur export class FileConfigurationService< T extends ConfigurationServiceParameters = ConfigurationServiceParameters > extends ConfigurationService { - /** @ignore */ - async init(): Promise { - // Do not call super as we diverged - if (!this.parameters.source) { + /** + * @override + */ + loadParameters(params: any): ServiceParameters { + let res = new ConfigurationServiceParameters(params); + if (!res.source) { throw new WebdaError.CodeError("FILE_CONFIGURATION_SOURCE_MISSING", "Need a source for FileConfigurationService"); } // Load it from where it should be - this.parameters.source = this._webda.getAppPath(this.parameters.source); - if (!existsSync(this.parameters.source)) { - throw new WebdaError.CodeError("FILE_CONFIGURATION_SOURCE_MISSING", "Need a source for FileConfigurationService"); + res.source = this.webda.getAppPath(res.source); + if (!existsSync(res.source)) { + if (res.default) { + FileUtils.save(res.default, res.source); + } else { + throw new WebdaError.CodeError( + "FILE_CONFIGURATION_SOURCE_MISSING", + "Need a source for FileConfigurationService" + ); + } } + return res; + } + /** @ignore */ + async init(): Promise { + // Do not call super as we diverged watchFile(this.parameters.source, this.checkUpdate.bind(this)); // Add webda info - this.watch("$.services", (updates: any) => this._webda.reinit(updates)); + this.watch("$.services", (updates: any) => this.webda.reinit(updates)); await this.checkUpdate(); return this; @@ -39,20 +53,4 @@ export class FileConfigurationService< protected async loadConfiguration(): Promise<{ [key: string]: any }> { return FileUtils.load(this.parameters.source); } - - /** - * Read the file and store it - */ - async initConfiguration(): Promise<{ [key: string]: any }> { - this.parameters.source = this._webda.getAppPath(this.parameters.source); - - /** - * Auto-generate file if missing - */ - if (!existsSync(this.parameters.source) && this.parameters.default) { - FileUtils.save(this.parameters.default, this.parameters.source); - } - - return this.loadAndStoreConfiguration(); - } } diff --git a/packages/core/src/services/invitationservice.ts b/packages/core/src/services/invitationservice.ts index c990c9eb9..27276212f 100644 --- a/packages/core/src/services/invitationservice.ts +++ b/packages/core/src/services/invitationservice.ts @@ -1,3 +1,5 @@ +import { DeepPartial } from "@webda/tsc-esm"; +import { LocalSubscription } from "../events"; import { EventAuthenticationRegister, EventWithContext, @@ -9,14 +11,23 @@ import { } from "../index"; import { CoreModel, CoreModelDefinition } from "../models/coremodel"; import { User } from "../models/user"; -import { Authentication } from "./authentication"; +import { Authentication, AuthenticationRegisterEvent } from "./authentication"; import { NotificationService } from "./notificationservice"; -import { DeepPartial, Inject, Service, ServiceParameters } from "./service"; - +import { Inject, Service, ServiceParameters } from "./service"; interface InvitationAnswerBody { accept: boolean; } +type Invitations = { + model: string; + inviter: { + uuid: string; + name: string; + }; + pending: boolean; + metadata: any; +}[]; + interface Invitation { /** * User to invite to target @@ -215,9 +226,9 @@ export default class InvitationService - this.registrationListener(evt) - ); + new LocalSubscription().register(evt => { + this.registrationListener(evt.data); + }); this.model = this.getWebda().getModel(this.parameters.model); const url = this.getParameters().url || `/${this.getWebda().getApplication().getModelPlural(this.parameters.model)}`; @@ -370,7 +381,7 @@ export default class InvitationService, model: CoreModel) { + async answerInvitation(ctx: WebContext, model: CoreModel & { [key: string]: any }) { let body = await ctx.getInput(); // Invitation on the model is gone if (model === undefined) { @@ -406,7 +417,7 @@ export default class InvitationService, model: CoreModel) { + async uninvite(ctx: OperationContext, model: CoreModel & { [key: string]: any }) { const body: Invitation = await ctx.getInput(); body.users ??= []; body.idents ??= []; @@ -615,7 +626,15 @@ export default class InvitationService p.model === model.getUuid()).length) { return; } @@ -681,8 +700,9 @@ export default class InvitationServiceevt.user; + invitedUser[this.parameters.mapAttribute] ??= []; + invitedUser[this.parameters.mapAttribute].push( ...infos .filter(m => m.model !== undefined) .map(m => { diff --git a/packages/core/src/services/kubernetesconfiguration.ts b/packages/core/src/services/kubernetesconfiguration.ts index 684dab5af..f42f9e952 100644 --- a/packages/core/src/services/kubernetesconfiguration.ts +++ b/packages/core/src/services/kubernetesconfiguration.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; import * as path from "path"; -import { WebdaError } from "../index"; +import { ServiceParameters, WebdaError } from "../index"; import { FileUtils } from "../utils/serializers"; import { ConfigurationService, ConfigurationServiceParameters } from "./configuration"; @@ -12,24 +12,31 @@ import { ConfigurationService, ConfigurationServiceParameters } from "./configur */ export class KubernetesConfigurationService extends ConfigurationService { /** - * @ignore + * @override */ - async init(): Promise { + loadParameters(params: any): ServiceParameters { + let res = new ConfigurationServiceParameters(params); // Do not call super as we diverged - if (!this.parameters.source) { + if (!res.source) { throw new WebdaError.CodeError( "KUBE_CONFIGURATION_SOURCE_MISSING", "Need a source for KubernetesConfigurationService" ); } - this.parameters.source = this._webda.getAppPath(this.parameters.source); - if (!fs.existsSync(this.parameters.source)) { + res.source = this.webda.getAppPath(res.source); + if (!fs.existsSync(res.source)) { throw new WebdaError.CodeError( "KUBE_CONFIGURATION_SOURCE_MISSING", "Need a source for KubernetesConfigurationService" ); } + return res; + } + /** + * @ignore + */ + async init(): Promise { // Add webda info this.watch("$.services", (updates: any) => this._webda.reinit(updates)); diff --git a/packages/core/src/services/mailer.ts b/packages/core/src/services/mailer.ts index 9706a7554..8f71c9369 100644 --- a/packages/core/src/services/mailer.ts +++ b/packages/core/src/services/mailer.ts @@ -218,7 +218,7 @@ class Mailer extends AbstractMail _getTemplate(name: string) { if (!this._templates[name]) { if (!this.hasNotification(name)) { - this._webda.log("WARN", "No template found for", name); + this.webda.log("WARN", "No template found for", name); return; } this._templates[name] = new Email({ @@ -252,7 +252,7 @@ class Mailer extends AbstractMail */ async send(options: MailerSendOptions, callback = undefined): Promise { if (this._transporter === undefined) { - this._webda.log("ERROR", "Cannot send email as no transporter is defined"); + this.webda.log("ERROR", "Cannot send email as no transporter is defined"); return Promise.reject("Cannot send email as no transporter is defined"); } if (!options.from) { @@ -283,4 +283,4 @@ class Mailer extends AbstractMail } } -export { Mailer, TemplatesMap, IEmailTemplate }; +export { IEmailTemplate, Mailer, TemplatesMap }; diff --git a/packages/core/src/services/oauth.ts b/packages/core/src/services/oauth.ts index aecc8358b..dee3c8b9c 100644 --- a/packages/core/src/services/oauth.ts +++ b/packages/core/src/services/oauth.ts @@ -1,9 +1,18 @@ -import { Core, Counter } from "../core"; -import { Context, EventWithContext, OperationContext, RequestFilter, WebContext, WebdaError } from "../index"; +import { Counter } from "../core"; +import { Event } from "../events"; +import { Context, OperationContext, RequestFilter, WebContext, WebdaError } from "../index"; import { Authentication } from "./authentication"; import { RegExpStringValidator, Service, ServiceParameters } from "./service"; -export interface EventOAuthToken extends EventWithContext { +export class OAuthCallbackEvent extends Event<{ + provider: string; + type: "token" | "callback"; + [key: string]: any; +}> { + static type: "oauth.callback"; +} + +export class OAuthTokenEvent extends Event<{ /** * Provider from */ @@ -17,6 +26,8 @@ export interface EventOAuthToken extends EventWithContext { * Profile comming from the provider */ [key: string]: any; +}> { + static type: "oauth.token"; } /** @@ -84,10 +95,6 @@ export class OAuthServiceParameters extends ServiceParameters { } } -export type OAuthEvents = { - "OAuth.Callback": EventOAuthToken; -}; - /** * OAuth Session variables */ @@ -115,11 +122,8 @@ export interface OAuthSession { * OAuth service implementing the default OAuth workflow * It is abstract as it does not manage any provider as is */ -export abstract class OAuthService< - T extends OAuthServiceParameters = OAuthServiceParameters, - E extends OAuthEvents = OAuthEvents - > - extends Service +export abstract class OAuthService + extends Service implements RequestFilter { _authenticationService: Authentication; @@ -132,14 +136,6 @@ export abstract class OAuthService< }; authorized_uris: RegExpStringValidator; - /** - * Ensure default parameter url - */ - constructor(webda: Core, name: string, params?: any) { - super(webda, name, params); - this.parameters.url = this.parameters.url || `${this.getDefaultUrl()}`; - } - initMetrics(): void { super.initMetrics(); this.metrics.login = this.getMetric(Counter, { @@ -161,6 +157,7 @@ export abstract class OAuthService< } else { this.authorized_uris = new RegExpStringValidator(result.authorized_uris); } + result.url ??= this.getDefaultUrl(); return result; } @@ -194,7 +191,7 @@ export abstract class OAuthService< */ resolve(): this { super.resolve(); - this._authenticationService = this.getService(this.parameters.authenticationService); + this._authenticationService = this.getService(this.parameters.authenticationService); return this; } @@ -368,12 +365,15 @@ export abstract class OAuthService< private async _token(context: WebContext) { const res = await this.handleToken(context); await this.handleReturn(context, res.identId, res.profile); - await this.emitSync("OAuth.Callback", { - ...res, - type: "token", - provider: this.getName(), - context - }); + new OAuthTokenEvent( + { + ...res, + type: "token", + provider: this.getName(), + context + }, + this + ).emit(); this.metrics.login.inc({ method: "token" }); } @@ -386,12 +386,15 @@ export abstract class OAuthService< private async _callback(ctx: WebContext) { const res = await this.handleCallback(ctx); await this.handleReturn(ctx, res.identId, res.profile); - await this.emitSync("OAuth.Callback", { - ...res, - type: "callback", - provider: this.getName(), - context: ctx - }); + new OAuthCallbackEvent( + { + ...res, + type: "callback", + provider: this.getName(), + context: ctx + }, + this + ).emit(); this.metrics.login.inc({ method: "callback" }); } diff --git a/packages/core/src/services/resource.spec.ts b/packages/core/src/services/resource.spec.ts deleted file mode 100644 index 895d8ee6a..000000000 --- a/packages/core/src/services/resource.spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { suite, test } from "@testdeck/mocha"; -import * as assert from "assert"; -import * as fs from "fs"; -import { WebdaError } from "../errors"; -import { WebdaTest } from "../test"; -import ResourceService, { ResourceServiceParameters } from "./resource"; - -@suite -class ResourceTest extends WebdaTest { - resource: ResourceService; - resourceModel; - ctx; - async before(init: boolean = true) { - await super.before(init); - this.resource = this.getService("ResourceService"); - this.resourceModel = this.getService("ModelsResource"); - this.ctx = await this.newContext(); - } - - @test - async parentFolder() { - let executor = this.getExecutor(this.ctx, "test.webda.io", "GET", "/resources/../config.json"); - assert.notStrictEqual(executor, undefined); - await assert.rejects( - () => executor.execute(this.ctx), - (err: WebdaError.HttpError) => err.getResponseCode() === 401 - ); - } - - @test - params() { - let params = new ResourceServiceParameters({ - url: "/test/", - rootRedirect: true - }); - assert.strictEqual(params.url, "/test/"); - assert.strictEqual(params.folder, "./test/"); - } - - @test - async redirect() { - this.resource.getParameters().rootRedirect = true; - this.webda.getRouter().removeRoute("/"); - assert.ok(this.webda.getRouter().getRouteFromUrl(this.ctx, "GET", "/") === undefined); - this.resource.initRoutes(); - assert.ok(this.webda.getRouter().getRouteFromUrl(this.ctx, "GET", "/") !== undefined); - this.resource._redirect(this.ctx); - assert.strictEqual(this.ctx.getResponseHeaders().Location, "http://test.webda.io/resources/"); - } - - @test - async index() { - let executor = this.getExecutor(this.ctx, "test.webda.io", "GET", "/resources/"); - // index.html does not exist in our case - await assert.rejects( - () => executor.execute(this.ctx), - (err: WebdaError.HttpError) => err.getResponseCode() === 404 - ); - } - - @test - async unknownFile() { - this.getService("ResourceService").getParameters().indexFallback = true; - let executor = this.getExecutor(this.ctx, "test.webda.io", "GET", "/resources/config.unknown.json"); - assert.notStrictEqual(executor, undefined); - await assert.rejects( - () => executor.execute(this.ctx), - (err: WebdaError.HttpError) => err.getResponseCode() === 404 - ); - } - - @test - async jsonFile() { - let executor = this.getExecutor(this.ctx, "test.webda.io", "GET", "/resources/config.json"); - assert.notStrictEqual(executor, undefined); - await executor.execute(this.ctx); - assert.strictEqual(this.ctx.getResponseBody().toString(), fs.readFileSync("./test/config.json").toString()); - assert.strictEqual(this.ctx.getResponseHeaders()["content-type"], "application/json"); - } - - @test - async jsFile() { - let executor = this.getExecutor(this.ctx, "test.webda.io", "GET", "/resources/moddas/voidstore.js"); - assert.notStrictEqual(executor, undefined); - await executor.execute(this.ctx); - assert.strictEqual(this.ctx.getResponseBody().toString(), fs.readFileSync("./test/moddas/voidstore.js").toString()); - assert.strictEqual(this.ctx.getResponseHeaders()["content-type"], "application/javascript"); - } - - @test - async textFile() { - let executor = this.getExecutor(this.ctx, "test.webda.io", "GET", "/resources/data/test.txt"); - assert.notStrictEqual(executor, undefined); - await executor.execute(this.ctx); - assert.strictEqual(this.ctx.getResponseBody().toString(), fs.readFileSync("./test/data/test.txt").toString()); - assert.strictEqual(this.ctx.getResponseHeaders()["content-type"], "text/plain; charset=UTF-8"); - } - - @test - async pngFile() { - let executor = this.getExecutor(this.ctx, "test.webda.io", "GET", "/resources/data/test.png"); - assert.notStrictEqual(executor, undefined); - await executor.execute(this.ctx); - assert.strictEqual(this.ctx.getResponseBody().toString(), fs.readFileSync("./test/data/test.png").toString()); - assert.strictEqual(this.ctx.getResponseHeaders()["content-type"], "image/png"); - } - - // Check Store HTTP mapping - @test - async testStoreHttpMapping() { - let executor = this.getExecutor( - this.ctx, - "test.webda.io", - "GET", - "/templates/PASSPORT_EMAIL_RECOVERY/html.mustache" - ); - assert.notStrictEqual(executor, undefined); - await executor.execute(this.ctx); - assert.strictEqual( - this.ctx.getResponseBody().toString(), - fs.readFileSync("./templates/PASSPORT_EMAIL_RECOVERY/html.mustache").toString() - ); - assert.strictEqual(this.ctx.getResponseHeaders()["content-type"], "application/octet-stream"); - } -} diff --git a/packages/core/src/services/resource.ts b/packages/core/src/services/resource.ts deleted file mode 100644 index 07ce8cee5..000000000 --- a/packages/core/src/services/resource.ts +++ /dev/null @@ -1,211 +0,0 @@ -import * as fs from "fs"; -import * as mime from "mime-types"; -import * as path from "path"; -import { WebdaError } from "../errors"; -import { WebContext } from "../utils/context"; -import { Service, ServiceParameters } from "./service"; - -/** - * ResourceService parameters - */ -export class ResourceServiceParameters extends ServiceParameters { - /** - * URL on which to serve the content - * - * @default "resources" - */ - url?: string; - /** - * Folder to server - * - * @default "." + url - */ - folder?: string; - /** - * Add the / root to redirect to /{url} - * - * @default false - */ - rootRedirect?: boolean; - /** - * Index file - * - * @default index.html - */ - index?: string; - /** - * Return the index file for any unfound resource - * Useful for single page application - * - * @default true - */ - indexFallback?: boolean; - - constructor(params: any) { - super(params); - this.url ??= "resources"; - this.rootRedirect ??= false; - if (!this.url.startsWith("/")) { - this.url = "/" + this.url; - } - if (!this.url.endsWith("/")) { - this.url += "/"; - } - this.folder ??= "." + this.url; - if (!this.folder.endsWith("/")) { - this.folder += "/"; - } - this.index ??= "index.html"; - this.indexFallback ??= true; - } -} - -/** - * This service expose a folder as web - * - * It is the same as `static` on `express` - * - * @category CoreServices - * @WebdaModda - */ -export default class ResourceService< - T extends ResourceServiceParameters = ResourceServiceParameters -> extends Service { - /** - * Resolved path to the folder to serve - */ - _resolved: string; - /** - * If serving just one file - */ - fileOnly: boolean; - - /** - * Load the parameters for a service - */ - loadParameters(params: any): ResourceServiceParameters { - return new ResourceServiceParameters(params); - } - - /** - * Resolve resource folder - */ - computeParameters() { - super.computeParameters(); - this._resolved = path.resolve(this.parameters.folder); - this.fileOnly = !fs.lstatSync(this._resolved).isDirectory(); - } - - /** - * Init the routes - */ - initRoutes() { - this.addRoute(this.parameters.url, ["GET"], this._serve, { - get: { - description: "Get resources", - summary: "Get file", - operationId: "getResource", - responses: { - "200": { - description: "" - }, - "401": { - description: "Illegal resource" - }, - "404": { - description: "File not found" - } - } - } - }); - this.addRoute( - this.parameters.url + "{+resource}", - ["GET"], - this._serve, - { - get: { - description: "Get resources", - summary: "Get file", - operationId: "getResources", - responses: { - "200": { - description: "" - }, - "401": { - description: "Illegal resource" - }, - "404": { - description: "File not found" - } - } - } - }, - true - ); - if (this.parameters.rootRedirect) { - this.addRoute("/", ["GET"], this._redirect, { - get: { - description: "Redirect / to the exposed url", - summary: "Serve resource", - operationId: "redirectRoottoResources", - responses: { - "302": { - description: "" - } - } - } - }); - } - } - - /** - * Handle / request and redirect to the resources folder - * - * @param ctx - */ - _redirect(ctx: WebContext) { - ctx.redirect(ctx.getHttpContext().getAbsoluteUrl(this.parameters.url)); - } - - /** - * Serve the folder by itself, doing the mime detection - * - * @param ctx - */ - _serve(ctx: WebContext) { - let file = this._resolved; - // If resource is not a file - if (!this.fileOnly) { - file = path.join(this._resolved, ctx.parameter("resource") || this.parameters.index); - } - - // Avoid path transversal - if (!path.resolve(file).startsWith(this._resolved)) { - throw new WebdaError.Unauthorized(file); - } - - if (!fs.existsSync(file)) { - file = path.join(this._resolved, this.parameters.index); - // Catch All for SPA - if (!(this.parameters.indexFallback && fs.existsSync(file))) { - throw new WebdaError.NotFound(file); - } - } - let mimetype = mime.lookup(file) || "application/octet-stream"; - if (mimetype.startsWith("text/")) { - mimetype += "; charset=UTF-8"; - } - ctx.writeHead(200, { - "content-type": mimetype, - "content-length": fs.lstatSync(file).size - }); - const stream = fs.createReadStream(file); - return new Promise((resolve, reject) => { - stream.on("error", reject); - stream.on("end", resolve); - stream.pipe(ctx.getStream()); - }); - } -} - -export { ResourceService }; diff --git a/packages/core/src/services/service.ts b/packages/core/src/services/service.ts index 94e69deba..7566cbd88 100644 --- a/packages/core/src/services/service.ts +++ b/packages/core/src/services/service.ts @@ -1,20 +1,10 @@ +import { Constructor, DeepPartial } from "@webda/tsc-esm"; import { WorkerLogLevel } from "@webda/workout"; import { deepmerge } from "deepmerge-ts"; -import * as events from "events"; -import { - Constructor, - Context, - Core, - Counter, - EventEmitterUtils, - Gauge, - Histogram, - Logger, - MetricConfiguration -} from "../index"; -import { OpenAPIWebdaDefinition } from "../router"; +import { EventType, Subscription } from "../events"; +import { Context, Core, Counter, Gauge, Histogram, Logger, MetricConfiguration } from "../index"; +import { OpenAPIWebdaDefinition } from "../rest/router"; import { HttpMethodType } from "../utils/httpcontext"; -import { EventService } from "./asyncevents"; /** * Represent a Inject annotation @@ -269,17 +259,6 @@ export class ServiceParameters { } } -/** - * Create a new type with only optional - */ -export type DeepPartial = { - [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; -}; - -export type PartialModel = { - [P in keyof T]: T[P] extends Function ? T[P] : T[P] extends object ? null | PartialModel : T[P] | null; -}; - export type Events = { [key: string]: unknown; }; @@ -294,27 +273,13 @@ export type Events = { * @abstract * @class Service */ -abstract class Service< - T extends ServiceParameters = ServiceParameters, - E extends Events = Events -> extends events.EventEmitter { - /** - * Webda Core object - */ - protected _webda: Core; - /** - * Service name - */ - protected _name: string; +abstract class Service { /** * Hold the parameters for your service * * It will be bring from the `webda.config.json` */ - protected parameters: T; - _createException: string; - _initTime: number; - _initException: any = undefined; + protected parameters: Readonly; /** * Logger with class context */ @@ -331,12 +296,13 @@ abstract class Service< * @param {String} name - The name of the service * @param {Object} params - The parameters block define in the configuration file */ - constructor(webda: Core, name: string, params: DeepPartial = {}) { - super(); + constructor( + protected webda: Core, + protected name: string, + params: DeepPartial = {} + ) { this.logger = webda ? webda.getLogger(this) : undefined; - this._webda = webda; - this._name = name; - this.parameters = this.loadParameters(params); + this.parameters = Object.freeze(this.loadParameters(params)); } /** @@ -364,7 +330,7 @@ abstract class Service< * Return WebdaCore */ getWebda(): Core { - return this._webda; + return this.webda; } /** @@ -378,7 +344,7 @@ abstract class Service< * Return service representation */ toString() { - return this.parameters.type + "[" + this._name + "]"; + return this.parameters.type + "[" + this.name + "]"; } /** @@ -490,10 +456,10 @@ abstract class Service< if (!finalUrl) { return; } - this._webda.addRoute(finalUrl, { + this.webda.addRoute(finalUrl, { // Create bounded function to keep the context _method: executer.bind(this), - executor: this._name, + executor: this.name, openapi: deepmerge(openapi, this.parameters.openapi || {}), methods, override @@ -532,7 +498,7 @@ abstract class Service< const id = this.getOperationId(j); if (!id) continue; this.log("TRACE", "Adding operation", id, "for bean", this.getName()); - this._webda.registerOperation(j.includes(".") ? j : `${this.getName()}.${j}`, { + this.webda.registerOperation(j.includes(".") ? j : `${this.getName()}.${j}`, { ...operations[j], service: this.getName(), input: `${this.getName()}.${operations[j].method}.input`, @@ -550,7 +516,7 @@ abstract class Service< * @return {String} The export of the strip object ( removed all attribute with _ ) */ toPublicJSON(object: unknown) { - return this._webda.toPublicJSON(object); + return this.webda.toPublicJSON(object); } /** @@ -558,7 +524,7 @@ abstract class Service< * @returns */ toJSON() { - return this._name; + return this.name; } /** @@ -582,72 +548,19 @@ abstract class Service< return this.init(); } - /** - * Emit the event with data and wait for Promise to finish if listener returned a Promise - * @deprecated - */ - emitSync(event: Key, data: E[Key]): Promise { - return EventEmitterUtils.emitSync(this, event, data); - } - - /** - * Override to allow capturing long listeners - * @override - */ - emit(event: Key | symbol, data: E[Key]): boolean { - return EventEmitterUtils.emit(this, event, data); - } - - /** - * Type the listener part - * @param event - * @param listener - * @param clusterWide if true will listen to all events even if they are not emitted by this cluster node - * @returns - */ - on(event: Key | symbol, listener: (evt: E[Key]) => void, clusterWide?: boolean): this { - if (clusterWide) { - super.on(event, listener); - } else { - super.on(event, evt => (evt.emitterId ? undefined : listener(evt))); - } - return this; - } - - /** - * Listen to an event as on(...) would do except that it will be asynchronous - * @param event - * @param callback - * @param queue Name of queue to use, can be undefined, queue name are used to define differents priorities - */ - onAsync( - event: Key, - listener: (evt: E[Key]) => void, - queue: string = undefined, - clusterWide?: boolean - ) { - if (clusterWide) { - this._webda.getService("AsyncEvents").bindAsyncListener(this, event, listener, queue); - } else { - this._webda - .getService("AsyncEvents") - .bindAsyncListener(this, event, evt => (evt.emitterId ? undefined : listener(evt)), queue); - } - } - /** * Return a webda service * @param service name to retrieve */ getService>(service: string): K { - return this._webda.getService(service); + return this.webda.getService(service); } /** * Get service name */ getName(): string { - return this._name; + return this.name; } /** @@ -677,7 +590,35 @@ abstract class Service< */ log(level: WorkerLogLevel, ...args: any[]) { // Add the service name to avoid confusion - this.logger.log(level, `[${this._name}]`, ...args); + this.logger.log(level, `[${this.name}]`, ...args); + } + + /** + * Register for a specific event + * @param event + * @param callback + */ + on(event: EventType | string | (string | EventType)[], callback: Function, name?: string) { + // Capture types + const types = (Array.isArray(event) ? event : [event]).map((e: string | EventType) => { + if (typeof e !== "string") { + return e.getType(this.constructor); + } else { + return e; + } + }); + // If name is not specified we try to capture the stack + if (!name) { + let stack; + Error.captureStackTrace(stack); + console.log(stack); + } + // Create the subscription + new Subscription() + .load({ + types + }) + .register(callback); } } diff --git a/packages/core/src/stores/mapper.ts b/packages/core/src/stores/mapper.ts index 17acebf5c..252614600 100644 --- a/packages/core/src/stores/mapper.ts +++ b/packages/core/src/stores/mapper.ts @@ -1,4 +1,5 @@ -import { CoreModel, ModelRef } from "../models/coremodel"; +import { CoreModel } from "../models/coremodel"; +import { ModelRef } from "../models/coremodelref"; import { Inject, Service, ServiceParameters } from "../services/service"; import { EventStoreDeleted, EventStorePartialUpdated, EventStoreSaved, EventStoreUpdate, Store } from "./store"; diff --git a/packages/core/src/stores/memory.spec.ts b/packages/core/src/stores/memory.spec.ts index d77a69f67..8f6f7c67a 100644 --- a/packages/core/src/stores/memory.spec.ts +++ b/packages/core/src/stores/memory.spec.ts @@ -1,8 +1,8 @@ import { suite, test } from "@testdeck/mocha"; +import * as WebdaQL from "@webda/ql"; import * as assert from "assert"; import { existsSync } from "fs"; import sinon from "sinon"; -import { WebdaQL } from "../../../webdaql/query"; import { AggregatorService, CoreModel, Ident, MemoryStore, Store, User, WebdaError } from "../index"; import { HttpContext } from "../utils/httpcontext"; import { FileUtils } from "../utils/serializers"; @@ -93,8 +93,6 @@ class MemoryStoreTest extends StoreTest { total += res.results.length; } while (offset); assert.strictEqual(total, 100); - assert.rejects(() => userStore.queryAll("state = 'CA' OFFSET 123"), /Cannot contain an OFFSET for queryAll method/); - assert.strictEqual((await userStore.queryAll("state = 'CA' LIMIT 50")).length, 250); return userStore; } diff --git a/packages/core/src/stores/memory.ts b/packages/core/src/stores/memory.ts index a92df5581..9d29e5f9f 100644 --- a/packages/core/src/stores/memory.ts +++ b/packages/core/src/stores/memory.ts @@ -1,9 +1,9 @@ +import * as WebdaQL from "@webda/ql"; import * as crypto from "node:crypto"; import { createReadStream, createWriteStream, existsSync } from "node:fs"; import { open } from "node:fs/promises"; import { Readable, Writable } from "node:stream"; import { createGzip } from "node:zlib"; -import { WebdaQL } from "../../../webdaql/query"; import { CoreModel } from "../models/coremodel"; import { GunzipConditional } from "../utils/serializers"; import { Store, StoreFindResult, StoreNotFoundError, StoreParameters, UpdateConditionFailError } from "./store"; diff --git a/packages/core/src/stores/modelmapper.ts b/packages/core/src/stores/modelmapper.ts index 9f99ee9c6..0cb510f58 100644 --- a/packages/core/src/stores/modelmapper.ts +++ b/packages/core/src/stores/modelmapper.ts @@ -1,4 +1,5 @@ -import { CoreModel, CoreModelDefinition, ModelRef } from "../models/coremodel"; +import { CoreModel, CoreModelDefinition } from "../models/coremodel"; +import { ModelRef } from "../models/coremodelref"; import { Service } from "../services/service"; import { EventStorePartialUpdated, EventStorePatchUpdated } from "./store"; diff --git a/packages/core/src/stores/store.spec.ts b/packages/core/src/stores/store.spec.ts index 8b8f33640..a0626eb38 100644 --- a/packages/core/src/stores/store.spec.ts +++ b/packages/core/src/stores/store.spec.ts @@ -25,12 +25,7 @@ export class PermissionModel extends CoreModel { class StoreParametersTest { @test cov() { - let params = new StoreParameters({ expose: "/plop", lastUpdateField: "bz", creationDateField: "c" }, undefined); - assert.deepStrictEqual(params.expose, { - queryMethod: "GET", - url: "/plop", - restrict: {} - }); + let params = new StoreParameters({ lastUpdateField: "bz", creationDateField: "c" }, undefined); assert.throws(() => new StoreParameters({ map: {} }, undefined), Error); assert.throws(() => new StoreParameters({ index: [] }, undefined), Error); } diff --git a/packages/core/src/stores/store.ts b/packages/core/src/stores/store.ts index 64af7d386..5d518f1a9 100644 --- a/packages/core/src/stores/store.ts +++ b/packages/core/src/stores/store.ts @@ -1,8 +1,10 @@ -import { WebdaQL } from "../../../webdaql/query"; +import * as WebdaQL from "@webda/ql"; +import { Constructor, FilterAttributes } from "@webda/tsc-esm"; import { Counter, EventWithContext, Histogram, RegistryEntry } from "../core"; +import { Event } from "../events"; import { ConfigurationProvider, MemoryStore, ModelMapLoaderImplementation, Throttler, WebdaError } from "../index"; -import { Constructor, CoreModel, CoreModelDefinition, FilterAttributes } from "../models/coremodel"; -import { Service, ServiceParameters } from "../services/service"; +import { CoreModel, CoreModelDefinition } from "../models/coremodel"; +import { Route, Service, ServiceParameters } from "../services/service"; import { Context, GlobalContext, OperationContext, WebContext } from "../utils/context"; export class StoreNotFoundError extends WebdaError.CodeError { @@ -22,6 +24,20 @@ export class UpdateConditionFailError extends WebdaError.CodeError { } } +class StoreQueryEvent extends Event< + { + store: Store; + query: string; + context?: Context; + parsedQuery: WebdaQL.Query; + } & T +> {} + +class StoreQueriedEvent extends StoreQueryEvent<{ + results: CoreModel[]; + continuationToken: string; +}> {} + interface EventStore { /** * Emitter node @@ -487,12 +503,8 @@ export interface MappingService { * } * @category CoreServices */ -abstract class Store< - T extends CoreModel = CoreModel, - K extends StoreParameters = StoreParameters, - E extends StoreEvents = StoreEvents - > - extends Service +abstract class Store + extends Service implements ConfigurationProvider, MappingService { /** @@ -530,6 +542,7 @@ abstract class Store< security_context_warnings: Counter; queries: Histogram; }; + strict: boolean; /** * Load the parameters for a service @@ -549,7 +562,7 @@ abstract class Store< this._modelType = this._model.getIdentifier(); this._uuidField = this._model.getUuidField(); if (!this.parameters.noCache) { - this._cacheStore = new MemoryStore(this._webda, `_${this.getName()}_cache`, { + this._cacheStore = new MemoryStore(this.webda, `_${this.getName()}_cache`, { model: this.parameters.model }); this._cacheStore.computeParameters(); @@ -567,14 +580,15 @@ abstract class Store< // Compute the hierarchy this._modelsHierarchy[this._model.getIdentifier(false)] = 0; this._modelsHierarchy[this._model.getIdentifier()] = 0; + this.strict = this.parameters.strict; // Strict Store only store their model - if (!this.parameters.strict) { + if (!this.strict) { recursive(this._model.getHierarchy().children, 1); } // Add additional models if (this.parameters.additionalModels.length) { // Strict mode is to only allow one model per store - if (this.parameters.strict) { + if (this.strict) { this.log("ERROR", "Cannot add additional models in strict mode"); } else { for (let modelType of this.parameters.additionalModels) { @@ -684,13 +698,13 @@ abstract class Store< } /** - * OVerwrite the model + * Overwrite the model * Used mainly in test */ setModel(model: CoreModelDefinition) { this._model = model; this._cacheStore?.setModel(model); - this.parameters.strict = false; + this.strict = false; } /** @@ -702,7 +716,7 @@ abstract class Store< return original .bind(this._cacheStore, ...args)() .catch(err => { - this.log("TRACE", `Ignoring cache exception ${this._name}: ${err.message}`); + this.log("TRACE", `Ignoring cache exception ${this.name}: ${err.message}`); }); }; }; @@ -720,6 +734,15 @@ abstract class Store< } } + /** + * + * @param object + * @returns + */ + newModel(object: any): T { + return this._model.factory(object); + } + /** * Init a model from the current stored data * @@ -990,12 +1013,15 @@ abstract class Store< parsedQuery.limit = limit; // __type is a special field to filter on the type of the object // Emit the default event - await this.emitSync("Store.Query", { - query, - parsedQuery, - store: this, - context - }); + await new StoreQueryEvent( + { + query, + parsedQuery, + store: this, + context + }, + this + ).emit(true); const result = { results: [], continuationToken: undefined @@ -1063,14 +1089,18 @@ abstract class Store< this.logSlowQuery(query, "", duration); this.metrics.slow_queries_total.inc(); } - await this.emitSync("Store.Queried", { - query, - parsedQuery: parsedQuery, - store: this, - continuationToken: result.continuationToken, - results: result.results, - context - }); + // Emit queried event + await new StoreQueriedEvent( + { + query, + parsedQuery: parsedQuery, + store: this, + continuationToken: result.continuationToken, + results: result.results, + context + }, + this + ).emit(); return result; } @@ -1103,10 +1133,7 @@ abstract class Store< * @param event * @param data */ - async emitStoreEvent( - event: Key, - data: E[Key] & { emitterId?: string } - ): Promise { + async emitStoreEvent(event: string, data: any & { emitterId?: string }): Promise { if (event === "Store.Deleted") { await this._cacheStore?._delete((data).object_id); } else if (event === "Store.PartialUpdated") { @@ -1152,24 +1179,6 @@ abstract class Store< } else if (event === "Store.PatchUpdated") { await this._cacheStore?._patch((data).object, (data).object_id); } - await this.emitSync(event, data); - } - - /** - * Save an object - * - * @param {Object} Object to save - * @param {String} Uuid to use, if not specified take the object.uuid or generate one if not found - * @return {Promise} with saved object - * - * Might want to rename to create - */ - async save(object): Promise { - if (object instanceof this._model && object._creationDate !== undefined && object._lastUpdate !== undefined) { - await this.checkContext(object, "update"); - return await object.save(); - } - return this.create(object); } /** @@ -1178,7 +1187,7 @@ abstract class Store< * @param ctx * @returns */ - async create(object, ctx: Context = undefined) { + async create(object) { object = this.initModel(object); await this.checkContext(object, "create"); @@ -1192,33 +1201,13 @@ abstract class Store< object.__types = [object.__type, ...ancestors].filter(i => i !== "Webda/CoreModel" && i !== "CoreModel"); // Handle object auto listener - const evt = { - object: object, - object_id: object.getUuid(), - store: this, - context: ctx - }; - await Promise.all([ - this.emitSync("Store.Save", evt), - object?.__class.emitSync("Store.Save", evt), - object._onSave() - ]); + await object._onCreate(); - this.metrics.operations_total.inc({ operation: "save" }); + this.metrics.operations_total.inc({ operation: "create" }); let res = await this._save(object); await this._cacheStore?._save(object); object = this.initModel(res); - const evtSaved = { - object: object, - object_id: object.getUuid(), - store: this, - context: ctx - }; - await Promise.all([ - this.emitSync("Store.Saved", evtSaved), - object?.__class.emitSync("Store.Saved", evtSaved), - object._onSaved() - ]); + await object._onCreated(); return object; } @@ -1363,7 +1352,7 @@ abstract class Store< * @return {Promise} with saved object */ async update( - object: any, + updates: any, reverseMap = true, partial = false, conditionField?: CK | null, @@ -1375,37 +1364,27 @@ abstract class Store< // Dont allow to update collections from map if (this._reverseMap != undefined && reverseMap) { for (let i in this._reverseMap) { - if (object[this._reverseMap[i].property] != undefined) { - delete object[this._reverseMap[i].property]; + if (updates[this._reverseMap[i].property] != undefined) { + delete updates[this._reverseMap[i].property]; } } } - if (Object.keys(object).length < 2) { + // If no updates, return undefined + if (Object.keys(updates).length < 2) { return undefined; } - object._lastUpdate = new Date(); + updates._lastUpdate = new Date(); this.metrics.operations_total.inc({ operation: "get" }); - const uuid = object.getUuid ? object.getUuid() : object[this._uuidField]; + const uuid = updates.getUuid ? updates.getUuid() : updates[this._uuidField]; let load = await this._getFromCache(uuid, true); await this.checkContext(load, "update"); - if (load.__type !== this._modelType && this.parameters.strict) { + if (load.__type !== this._modelType && this.strict) { this.log("WARN", `Object '${uuid}' was not created by this store ${load.__type}:${this._modelType}`); throw new StoreNotFoundError(uuid, this.getName()); } loaded = this.initModel(load); - const update = object; - const evt = { - object: loaded, - object_id: loaded.getUuid(), - store: this, - update - }; - await Promise.all([ - this.emitSync(partial ? `Store.PatchUpdate` : `Store.Update`, evt), - object?.__class?.emitSync(partial ? `Store.PatchUpdate` : `Store.Update`, evt), - loaded._onUpdate(object) - ]); + loaded._onUpdate(updates); let res: any; if (conditionField !== null) { @@ -1414,34 +1393,23 @@ abstract class Store< } if (partial) { this.metrics.operations_total.inc({ operation: "partialUpdate" }); - await this._patch(object, uuid, conditionValue, conditionField); - res = object; + await this._patch(updates, uuid, conditionValue, conditionField); + res = updates; } else { // Copy back the mappers for (let i in this._reverseMap) { - object[this._reverseMap[i].property] = loaded[this._reverseMap[i].property]; + updates[this._reverseMap[i].property] = loaded[this._reverseMap[i].property]; } - object = this.initModel(object); + updates = this.initModel(updates); this.metrics.operations_total.inc({ operation: "update" }); - res = await this._update(object, uuid, conditionValue, conditionField); + res = await this._update(updates, uuid, conditionValue, conditionField); } // Reinit save saved = this.initModel({ ...loaded, ...res }); - const evtUpdated = { - object: saved, - object_id: saved.getUuid(), - store: this, - update, - previous: loaded - }; - await Promise.all([ - this.emitStoreEvent(partial ? `Store.PatchUpdated` : `Store.Updated`, evtUpdated), - saved?.__class.emitSync(partial ? `Store.PatchUpdated` : `Store.Updated`, evtUpdated), - saved._onUpdated() - ]); + await saved._onUpdated(updates); return saved; } @@ -1463,7 +1431,7 @@ abstract class Store< this.log("INFO", "Ensuring __type is using its short id form"); const app = this.getWebda().getApplication(); // We need to be laxist for migration - this.parameters.strict = false; + this.strict = false; await this.migration("typesShortId", async item => { if (item.__type !== undefined && item.__type.includes("/")) { const model = app.getWebdaObject("models", item.__type); @@ -1486,7 +1454,7 @@ abstract class Store< async cleanModelAliases() { this.log("INFO", "Ensuring __type is not using any aliases"); // We need to be laxist for migration - this.parameters.strict = false; + this.strict = false; await this.migration("cleanAliases", async item => { if (this.parameters.modelAliases[item.__type]) { this.log("INFO", "Migrating type " + item.__type + " to " + this.parameters.modelAliases[item.__type]); @@ -1504,7 +1472,7 @@ abstract class Store< this.log("INFO", "Ensuring __type is case sensitive from migration from v2.x"); const app = this.getWebda().getApplication(); // We need to be laxist for migration - this.parameters.strict = false; + this.strict = false; await this.migration("typesCase", async item => { if (item.__type !== undefined) { if (!app.hasWebdaObject("models", item.__type, true) && app.hasWebdaObject("models", item.__type, false)) { @@ -1683,7 +1651,7 @@ abstract class Store< if (to_delete === undefined) { return; } - if (to_delete.__type !== this._modelType && this.parameters.strict) { + if (to_delete.__type !== this._modelType && this.strict) { this.log("WARN", `Object '${uid}' was not created by this store ${to_delete.__type}:${this._modelType}`); return; } @@ -1703,11 +1671,7 @@ abstract class Store< store: this }; // Send preevent - await Promise.all([ - this.emitSync("Store.Delete", evt), - to_delete?.__class.emitSync("Store.Delete", evt), - to_delete._onDelete() - ]); + await to_delete._onDelete(); // If async we just tag the object as deleted if (this.parameters.asyncDelete && !sync) { @@ -1732,16 +1696,7 @@ abstract class Store< } // Send post event - const evtDeleted = { - object: to_delete, - object_id: to_delete.getUuid(), - store: this - }; - await Promise.all([ - this.emitStoreEvent("Store.Deleted", evtDeleted), - to_delete.__class.emitSync("Store.Deleted", evtDeleted), - to_delete._onDeleted() - ]); + await to_delete._onDeleted(); } /** @@ -1786,7 +1741,7 @@ abstract class Store< if (await this.exists(uuid)) { return this.update({ ...data, uuid }); } - return this.save(data instanceof CoreModel ? data.setUuid(uuid) : { ...data, uuid }); + return this.create(data instanceof CoreModel ? data.setUuid(uuid) : { ...data, uuid }); } /** @@ -1796,27 +1751,18 @@ abstract class Store< * @return {Promise} the object retrieved ( can be undefined if not found ) */ async get(uid: string, defaultValue: any = undefined): Promise { - /** @ignore */ - if (!uid) { - return undefined; - } this.metrics.operations_total.inc({ operation: "get" }); let object = await this._getFromCache(uid); if (!object) { return defaultValue ? this.initModel(defaultValue).setUuid(uid) : undefined; } await this.checkContext(object, "get"); - if (object.__type !== this._modelType && this.parameters.strict) { + if (object.__type !== this._modelType && this.strict) { this.log("WARN", `Object '${uid}' was not created by this store ${object.__type}:${this._modelType}`); return undefined; } object = this.initModel(object); - const evt = { - object: object, - object_id: object.getUuid(), - store: this - }; - await Promise.all([this.emitSync("Store.Get", evt), object.__class.emitSync("Store.Get", evt), object._onGet()]); + await object._onGet(); return object; } @@ -1925,6 +1871,356 @@ abstract class Store< return this._exists(uid); } + /** + * Handle POST + * @param ctx + */ + @Route(".", ["POST"], { + post: { + description: "The way to create a new ${modelName} model", + summary: "Create a new ${modelName}", + operationId: "create${modelName}", + requestBody: { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/${modelName}" + } + } + } + }, + responses: { + "200": { + description: "Retrieve model", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/${modelName}" + } + } + } + }, + "400": { + description: "Object is invalid" + }, + "403": { + description: "You don't have permissions" + }, + "409": { + description: "Object already exists" + } + } + } + }) + async httpCreate(ctx: WebContext) { + return this.operationCreate(ctx, this.parameters.model); + } + + /** + * Create a new object based on the context + * @param ctx + * @param model + */ + async operationCreate(ctx: OperationContext, model: string) { + let body = await ctx.getInput(); + const modelPrototype = this.getWebda().getApplication().getModel(model); + let object = modelPrototype.factory(body, ctx); + object._creationDate = new Date(); + await object.checkAct(ctx, "create"); + try { + await object.validate(ctx, body); + } catch (err) { + this.log("INFO", "Object is not valid", err); + throw new WebdaError.BadRequest("Object is not valid"); + } + if (object[this._uuidField] && (await this.exists(object[this._uuidField]))) { + throw new WebdaError.Conflict("Object already exists"); + } + await this.create(object); + ctx.write(object); + const evt = { + context: ctx, + values: body, + object: object, + object_id: object.getUuid(), + store: this + }; + await Promise.all([object.__class.emitSync("Store.WebCreate", evt), this.emitSync("Store.WebCreate", evt)]); + } + + /** + * Handle object action + * @param ctx + */ + async httpAction(ctx: WebContext, actionMethod?: string) { + let action = ctx.getHttpContext().getUrl().split("/").pop(); + actionMethod ??= action; + let object = await this.get(ctx.parameter("uuid"), ctx); + if (object === undefined || object.__deleted) { + throw new WebdaError.NotFound("Object not found or is deleted"); + } + const inputSchema = `${object.__class.getIdentifier(false)}.${action}.input`; + if (this.getWebda().getApplication().hasSchema(inputSchema)) { + const input = await ctx.getInput(); + try { + this.getWebda().validateSchema(inputSchema, input); + } catch (err) { + this.log("INFO", "Object invalid", err); + this.log("INFO", "Object invalid", inputSchema, input, this.getWebda().getApplication().getSchema(inputSchema)); + throw new WebdaError.BadRequest("Body is invalid"); + } + } + await object.checkAct(ctx, action); + const evt = { + action: action, + object: object, + store: this, + context: ctx + }; + await Promise.all([this.emitSync("Store.Action", evt), object.__class.emitSync("Store.Action", evt)]); + const res = await object[actionMethod](ctx); + if (res) { + ctx.write(res); + } + const evtActioned = { + action: action, + object: object, + store: this, + context: ctx, + result: res + }; + await Promise.all([ + this.emitSync("Store.Actioned", evtActioned), + object?.__class.emitSync("Store.Actioned", evtActioned) + ]); + } + + /** + * Handle collection action + * @param ctx + */ + async httpGlobalAction(ctx: WebContext, model: CoreModelDefinition = this._model) { + let action = ctx.getHttpContext().getUrl().split("/").pop(); + const evt = { + action: action, + store: this, + context: ctx, + model + }; + await Promise.all([this.emitSync("Store.Action", evt), model.emitSync("Store.Action", evt)]); + const res = await model[action](ctx); + if (res) { + ctx.write(res); + } + const evtActioned = { + action: action, + store: this, + context: ctx, + result: res, + model + }; + await Promise.all([this.emitSync("Store.Actioned", evtActioned), model?.emitSync("Store.Actioned", evtActioned)]); + } + + /** + * Handle HTTP Update for an object + * + * @param ctx context of the request + */ + @Route("./{uuid}", ["PUT", "PATCH"], { + put: { + description: "Update a ${modelName} if the permissions allow", + summary: "Update a ${modelName}", + operationId: "update${modelName}", + schemas: { + input: "${modelName}", + output: "${modelName}" + }, + responses: { + "200": {}, + "400": { + description: "Object is invalid" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + }, + patch: { + description: "Patch a ${modelName} if the permissions allow", + summary: "Patch a ${modelName}", + operationId: "partialUpdatet${modelName}", + schemas: { + input: "${modelName}" + }, + responses: { + "204": { + description: "" + }, + "400": { + description: "Object is invalid" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + } + }) + async httpUpdate(ctx: WebContext) { + const { uuid } = ctx.getParameters(); + const body = await ctx.getInput(); + body[this._uuidField] = uuid; + let object = await this.get(uuid, ctx); + if (!object || object.__deleted) throw new WebdaError.NotFound("Object not found or is deleted"); + await object.checkAct(ctx, "update"); + if (ctx.getHttpContext().getMethod() === "PATCH") { + try { + await object.validate(ctx, body, true); + } catch (err) { + this.log("INFO", "Object invalid", err, object); + throw new WebdaError.BadRequest("Object is not valid"); + } + let updateObject: any = new this._model(); + // Clean any default attributes from the model + Object.keys(updateObject) + .filter(i => i !== "__class") + .forEach(i => { + delete updateObject[i]; + }); + updateObject.setUuid(uuid); + updateObject.load(body, false, false); + await this.patch(updateObject); + object = undefined; + } else { + let updateObject: any = new this._model(); + updateObject.load(body); + // Copy back the _ attributes + Object.keys(object) + .filter(i => i.startsWith("_")) + .forEach(i => { + updateObject[i] = object[i]; + }); + try { + await updateObject.validate(ctx, body); + } catch (err) { + this.log("INFO", "Object invalid", err); + throw new WebdaError.BadRequest("Object is not valid"); + } + + // Add mappers back to + object = await this.update(updateObject); + } + ctx.write(object); + const evt = { + context: ctx, + updates: body, + object: object, + store: this, + method: <"PATCH" | "PUT">ctx.getHttpContext().getMethod() + }; + await Promise.all([object?.__class.emitSync("Store.WebUpdate", evt), this.emitSync("Store.WebUpdate", evt)]); + } + + /** + * Handle GET on object + * + * @param ctx context of the request + */ + @Route("./{uuid}", ["GET"], { + get: { + description: "Retrieve ${modelName} model if permissions allow", + summary: "Retrieve a ${modelName}", + operationId: "get${modelName}", + schemas: { + output: "${modelName}" + }, + responses: { + "200": {}, + "400": { + description: "Object is invalid" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + } + }) + async httpGet(ctx: WebContext) { + let uuid = ctx.parameter("uuid"); + let object = await this.get(uuid, ctx); + await this.emitSync("Store.WebGetNotFound", { + context: ctx, + uuid, + store: this + }); + if (object === undefined || object.__deleted) { + throw new WebdaError.NotFound("Object not found or is deleted"); + } + await object.checkAct(ctx, "get"); + ctx.write(object); + const evt = { + context: ctx, + object: object, + store: this + }; + await Promise.all([this.emitSync("Store.WebGet", evt), object.__class.emitSync("Store.WebGet", evt)]); + ctx.write(object); + } + + /** + * Handle HTTP request + * + * @param ctx context of the request + * @returns + */ + @Route("./{uuid}", ["DELETE"], { + delete: { + operationId: "delete${modelName}", + description: "Delete ${modelName} if the permissions allow", + summary: "Delete a ${modelName}", + responses: { + "204": { + description: "" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + } + }) + async httpDelete(ctx: WebContext) { + let uuid = ctx.parameter("uuid"); + let object = await this.getWebda().runAsSystem(async () => { + const object = await this.get(uuid, ctx); + if (!object || object.__deleted) throw new WebdaError.NotFound("Object not found or is deleted"); + return object; + }); + await object.checkAct(ctx, "delete"); + // http://stackoverflow.com/questions/28684209/huge-delay-on-delete-requests-with-204-response-and-no-content-in-objectve-c# + // IOS don't handle 204 with Content-Length != 0 it seems + // Might still run into: Have trouble to handle the Content-Length on API Gateway so returning an empty object for now + ctx.writeHead(204, { "Content-Length": "0" }); + await this.delete(uuid); + const evt = { + context: ctx, + object_id: uuid, + store: this + }; + await Promise.all([this.emitSync("Store.WebDelete", evt), object.__class.emitSync("Store.WebDelete", evt)]); + } + /** * Search within the store */ diff --git a/packages/core/src/utils/context.spec.ts b/packages/core/src/utils/context.spec.ts index 186955ef5..6f135eb9e 100644 --- a/packages/core/src/utils/context.spec.ts +++ b/packages/core/src/utils/context.spec.ts @@ -1,7 +1,7 @@ import { suite, test } from "@testdeck/mocha"; +import * as WebdaQL from "@webda/ql"; import * as assert from "assert"; import { Readable } from "stream"; -import { WebdaQL } from "../../../webdaql/query"; import { Core } from "../core"; import { WebdaTest } from "../test"; import { OperationContext, SimpleOperationContext, WebContext } from "./context"; diff --git a/packages/core/src/utils/context.ts b/packages/core/src/utils/context.ts index 91233d1b3..f8dd56354 100644 --- a/packages/core/src/utils/context.ts +++ b/packages/core/src/utils/context.ts @@ -1,3 +1,4 @@ +import { NotEnumerable } from "@webda/tsc-esm"; import { WorkerLogLevel } from "@webda/workout"; import acceptLanguage from "accept-language"; import * as http from "http"; @@ -5,7 +6,6 @@ import sanitize from "sanitize-html"; import { Readable, Writable } from "stream"; import { WritableStreamBuffer } from "stream-buffers"; import { Core } from "../core"; -import { NotEnumerable } from "../models/coremodel"; import { User } from "../models/user"; import { Service } from "../services/service"; import { Session, SessionManager } from "../utils/session"; diff --git a/packages/core/src/utils/session.spec.ts b/packages/core/src/utils/session.spec.ts index 32c2caf36..b4f0750e9 100644 --- a/packages/core/src/utils/session.spec.ts +++ b/packages/core/src/utils/session.spec.ts @@ -28,12 +28,6 @@ class SessionStoreTest extends WebdaTest { const store = (this.webda.getServices()["SessionStore"] = await new MemoryStore(this.webda, "SessionStore", {}) .resolve() .init()); - store.getParameters().expose = { url: "url" }; - assert.throws( - () => this.getService("SessionManager").resolve(), - /SessionStore should not be exposed/ - ); - store.getParameters().expose = undefined; this.getService("SessionManager").resolve(); let ctx = await this.newContext(); ctx.getSession().identUsed = "bouzouf"; diff --git a/packages/core/src/utils/session.ts b/packages/core/src/utils/session.ts index 6112fcfe7..992832c2e 100644 --- a/packages/core/src/utils/session.ts +++ b/packages/core/src/utils/session.ts @@ -1,7 +1,8 @@ +import { DeepPartial, NotEnumerable } from "@webda/tsc-esm"; import { Core } from "../core"; -import { CoreModel, NotEnumerable } from "../models/coremodel"; +import { CoreModel } from "../models/coremodel"; import CryptoService, { JWTOptions } from "../services/cryptoservice"; -import { DeepPartial, Inject, Service, ServiceParameters } from "../services/service"; +import { Inject, Service, ServiceParameters } from "../services/service"; import { Store } from "../stores/store"; import { Context, OperationContext, WebContext } from "./context"; import { CookieOptions, SecureCookie } from "./cookie"; @@ -67,17 +68,6 @@ export class CookieSessionManager< return new CookieSessionParameters(params); } - /** - * @override - */ - resolve() { - super.resolve(); - if (this.sessionStore && this.sessionStore.getParameters().expose) { - throw new Error("SessionStore should not be exposed"); - } - return this; - } - /** * @override */ @@ -85,12 +75,11 @@ export class CookieSessionManager< if (!(context instanceof WebContext)) { return new Session(); } - const session = new Session(); + let session = new Session(); let cookie = await SecureCookie.load(this.parameters.cookie.name, context, this.parameters.jwt); if (this.sessionStore) { if (cookie.sub) { - Object.assign(session, (await this.sessionStore.get(cookie.sub))?.session); - session.uuid = cookie.sub; + session = await Session.ref(cookie.sub).get(); } session.uuid ??= Core.get().getUuid("base64"); } else { @@ -115,11 +104,8 @@ export class CookieSessionManager< } // If store is found session info are stored in db if (this.sessionStore) { - await this.sessionStore.save({ - uuid: session.uuid, - session, - ttl: Date.now() + this.parameters.cookie.maxAge * 1000 - }); + session.ttl = Date.now() + this.parameters.cookie.maxAge * 1000; + await session.save(); SecureCookie.save( this.parameters.cookie.name, context, @@ -136,13 +122,17 @@ export class CookieSessionManager< /** * Session */ -export class Session { +export class Session extends CoreModel { @NotEnumerable protected changed: boolean = false; /** * Session uuid */ uuid: string; + /** + * Session time to live + */ + ttl: number; /** * User id */ diff --git a/packages/core/src/utils/throttler.spec.ts b/packages/core/src/utils/throttler.spec.ts index c65479556..3670ddf31 100644 --- a/packages/core/src/utils/throttler.spec.ts +++ b/packages/core/src/utils/throttler.spec.ts @@ -27,9 +27,7 @@ class ThrottlerTest { assert.strictEqual(curs.length, 1, `Currents ${curs}`); t.setConcurrency(3); assert.strictEqual(t.getInProgress().length, 3); - // REFACTOR . >= 4.0.0 | replace("waitForCompletion", "wait") - let p = t.waitForCompletion(); - // END_REFACTOR + let p = t.wait(); resolvers.forEach(r => r()); await new Promise(resolve => setImmediate(resolve)); resolvers.forEach(r => r()); diff --git a/packages/core/src/utils/throttler.ts b/packages/core/src/utils/throttler.ts index 9ea5abdc8..ba45d39a0 100644 --- a/packages/core/src/utils/throttler.ts +++ b/packages/core/src/utils/throttler.ts @@ -123,15 +123,6 @@ export class Throttler { return this._queue.length; } - // REFACTOR . >= 4.0.0 - /** - * @deprecated - */ - async waitForCompletion(): Promise { - return this.wait(); - } - // END_REFACTOR - /** * Wait until every promise resolve * @returns diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 6edd846ba..87bfc4256 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -12,8 +12,8 @@ "moduleResolution": "node", "skipLibCheck": true }, - "include": ["src/**/*", "../webdaql"], - "exclude": ["**/node_modules"], + "include": ["src/**/*"], + "exclude": ["**/node_modules", "src/**/*.spec.ts"], "ts-node": { "transpileOnly": true, "preferTsExts": true, diff --git a/packages/runtime/src/services/cluster.ts b/packages/runtime/src/services/cluster.ts index b99b24122..27f51e577 100644 --- a/packages/runtime/src/services/cluster.ts +++ b/packages/runtime/src/services/cluster.ts @@ -1,7 +1,6 @@ import { Core, CoreModelDefinition, - DeepPartial, Gauge, Inject, PubSubService, @@ -11,6 +10,7 @@ import { Store, WebContext } from "@webda/core"; +import { DeepPartial } from "@webda/tsc-esm"; /** * Message going through our pub/sub diff --git a/packages/core/src/stores/aggregator.ts b/packages/runtime/src/stores/aggregator.ts similarity index 100% rename from packages/core/src/stores/aggregator.ts rename to packages/runtime/src/stores/aggregator.ts diff --git a/packages/core/src/stores/file.ts b/packages/runtime/src/stores/file.ts similarity index 97% rename from packages/core/src/stores/file.ts rename to packages/runtime/src/stores/file.ts index 6cd2fae70..7420da9f6 100644 --- a/packages/core/src/stores/file.ts +++ b/packages/runtime/src/stores/file.ts @@ -1,7 +1,8 @@ +import WebdaQL from "@webda/ql"; +import { FilterAttributes } from "@webda/tsc-esm"; import * as fs from "fs"; import * as path from "path"; -import { WebdaQL } from "../../../webdaql/query"; -import { CoreModel, FilterAttributes } from "../models/coremodel"; +import { CoreModel } from "../models/coremodel"; import { JSONUtils } from "../utils/serializers"; import { Store, StoreFindResult, StoreNotFoundError, StoreParameters } from "./store"; diff --git a/packages/tsc-esm/src/lib.spec.ts b/packages/tsc-esm/src/lib.spec.ts index 6c66de775..4289a648e 100644 --- a/packages/tsc-esm/src/lib.spec.ts +++ b/packages/tsc-esm/src/lib.spec.ts @@ -18,6 +18,8 @@ import { Test } from "./index"; import * as ind from './index'; +import { Test2 } from '..'; +import { Test3 } from '../test3'; import { Bean, Inject, Queue, Service, ServiceParameters, Throttler } from "@webda/core"; import { open } from "node:fs/promises"; import { pipeline } from "node:stream/promises"; @@ -36,6 +38,8 @@ let SinkService = class SinkService extends Service { assert.ok(content.includes('"./index.js"')); assert.ok(content.includes("'./index.js'")); assert.ok(content.includes('"./export.js"')); + assert.ok(content.includes("'..'")); + assert.ok(content.includes("'../test3.js'")); assert.ok(content.includes("'./export.js'")); assert.ok(!content.includes('"node:fs/promises.js"')); assert.ok(!content.includes('"node:stream/promises.js"')); diff --git a/packages/tsc-esm/src/lib.ts b/packages/tsc-esm/src/lib.ts index 6b79b85d3..1bcd01046 100644 --- a/packages/tsc-esm/src/lib.ts +++ b/packages/tsc-esm/src/lib.ts @@ -1,18 +1,20 @@ import { mkdirSync, writeFileSync } from "fs"; import { dirname } from "path"; +export * from "./utils.js"; + export function writer(fileName: string, text: string) { mkdirSync(dirname(fileName), { recursive: true }); // Add the ".js" -> if module writeFileSync( fileName, text - .replace(/^(import [^;]* from "\..*?)(\.js)?";/gm, '$1.js";') - .replace(/^(import [^;]* from '\..*?)(\.js)?';/gm, "$1.js';") + .replace(/^(import [^;]* from "\.[^.]*?)(\.js)?";/gm, '$1.js";') + .replace(/^(import [^;]* from '\.[^.]*?)(\.js)?';/gm, "$1.js';") // BUG: Abusive replace for node module -> shoud use node:fs/promises .replace(/^(import [^;]* from "(?!node:)(@[^/;"]+\/)?[^@/;"]+\/[^;"]*?)(\.js)?";/gm, '$1.js";') .replace(/^(import [^;]* from '(?!node:)(@[^/;']+\/)?[^@/;']+\/[^;']*?)(\.js)?';/gm, "$1.js';") - .replace(/^(export [^;]* from "\..*?)(\.js)?";/gm, '$1.js";') - .replace(/^(export [^;]* from '\..*?)(\.js)?';/gm, "$1.js';") + .replace(/^(export [^;]* from "\.[^.]*?)(\.js)?";/gm, '$1.js";') + .replace(/^(export [^;]* from '\.[^.]*?)(\.js)?';/gm, "$1.js';") ); } From 364c16a797ba6c7d62b25bfdb5ee52f2b100df5e Mon Sep 17 00:00:00 2001 From: Remi Cattiau Date: Sun, 3 Mar 2024 16:27:55 -0800 Subject: [PATCH 4/7] wip --- packages/core/src/rest/domain.ts | 727 ++++++++++++++++++++++++++ packages/core/src/rest/router.spec.ts | 209 ++++++++ packages/core/src/rest/router.ts | 620 ++++++++++++++++++++++ 3 files changed, 1556 insertions(+) create mode 100644 packages/core/src/rest/domain.ts create mode 100644 packages/core/src/rest/router.spec.ts create mode 100644 packages/core/src/rest/router.ts diff --git a/packages/core/src/rest/domain.ts b/packages/core/src/rest/domain.ts new file mode 100644 index 000000000..8de61bff3 --- /dev/null +++ b/packages/core/src/rest/domain.ts @@ -0,0 +1,727 @@ +import { DeepPartial, Methods } from "@webda/tsc-esm"; +import { + Core, + CoreModelDefinition, + DomainService, + DomainServiceParameters, + ModelAction, + Store, + WebContext +} from "../index"; +import { OpenAPIWebdaDefinition } from "./router"; + +/** + * Swagger static html + */ +const swagger = ` + + + + + + + + +
+ + + + + +`; + +/** + * + */ +export class RESTDomainServiceParameters extends DomainServiceParameters { + /** + * Expose the OpenAPI + * If a string is provided it will be used as the url + * + * @default true if debug false otherwise + */ + exposeOpenAPI: boolean | string; + + /** + * Set default url to / + * @param params + */ + constructor(params: any) { + super(params); + this.url ??= "/"; + } +} + +/** + * Expose all models via a REST API + * @WebdaModda + */ +export class RESTDomainService< + T extends RESTDomainServiceParameters = RESTDomainServiceParameters +> extends DomainService { + /** + * OpenAPI cache + */ + openapiContent: string; + /** + * Override to fallback on isDebug for exposeOpenAPI + * @returns + */ + resolve() { + super.resolve(); + if (this.parameters.exposeOpenAPI) { + if (typeof this.parameters.exposeOpenAPI === "string") { + this.addRoute(this.parameters.exposeOpenAPI, ["GET"], this.openapi, { hidden: true }); + } else { + this.addRoute(".", ["GET"], this.openapi, { hidden: true }); + } + } + return this; + } + + /** + * @override + */ + loadParameters(params: DeepPartial): DomainServiceParameters { + let res = new RESTDomainServiceParameters(params); + res.exposeOpenAPI ??= this.getWebda().isDebug(); + return res; + } + + /** + * Handle one model and expose it based on the service + * @param model + * @param name + * @param context + * @returns + */ + handleModel(model: CoreModelDefinition, name: string, context: any): boolean { + const depth = context.depth || 0; + const relations = model.getRelations(); + const injectAttribute = relations?.parent?.attribute; + const app = this.getWebda().getApplication(); + + const injector = ( + service: Store, + method: Methods, + type: "SET" | "QUERY" | "GET" | "DELETE", + ...args: any[] + ) => { + return async (context: WebContext) => { + let parentId = `pid.${depth - 1}`; + if (type === "SET" && injectAttribute && depth > 0) { + (await context.getInput())[injectAttribute] = context.getPathParameters()[parentId]; + } else if (type === "QUERY") { + let input = await context.getInput({ defaultValue: { q: "" } }); + let q; + if (context.getHttpContext().getMethod() === "GET") { + q = context.getParameters().q; + } else { + q = input.q; + } + let query = q ? ` AND (${q})` : ""; + if (injectAttribute) { + query = ` AND ${injectAttribute} = "${context.getPathParameters()[parentId]}"` + query; + } + if (args[0] !== 0) { + query = `__types CONTAINS "${model.getIdentifier()}"` + query; + } else if (query.startsWith(" AND ")) { + query = query.substring(5); + } + if (context.getHttpContext().getMethod() === "GET") { + context.getParameters().q = query; + } else { + input.q = query; + } + this.log("TRACE", `Query modified to '${query}' from '${q}' ${args[0]}`); + } + // Complete the uuid if needed + if (context.getParameters().uuid) { + context.getParameters().uuid = model.completeUid(context.getParameters().uuid); + } + await service[method](context, ...args); + }; + }; + + // Update prefix + const prefix = + (context.prefix || (this.parameters.url.endsWith("/") ? this.parameters.url : this.parameters.url + "/")) + + this.transformName(name); + context.prefix = prefix + `/{pid.${depth}}/`; + + // Register the model url + this.getWebda().getRouter().registerModelUrl(app.getModelName(model), prefix); + + let openapi: OpenAPIWebdaDefinition = { + [this.parameters.queryMethod.toLowerCase()]: { + tags: [name], + summary: `Query ${name}`, + operationId: `query${name}`, + requestBody: + this.parameters.queryMethod === "GET" + ? undefined + : { + content: { + "application/json": { + schema: { + properties: { + q: { + type: "string" + } + } + } + } + } + }, + parameters: + this.parameters.queryMethod === "GET" + ? [ + { + name: "q", + in: "query", + description: "Query to execute", + schema: { + type: "string" + } + } + ] + : [], + responses: { + "200": { + description: "Operation success", + content: { + "application/json": { + schema: { + properties: { + continuationToken: { + type: "string" + }, + results: { + type: "array", + items: { + $ref: `#/components/schemas/${model.getIdentifier()}` + } + } + } + } + } + } + }, + "400": { + description: "Query is invalid" + }, + "403": { + description: "You don't have permissions" + } + } + } + }; + model.Expose.restrict.query || + this.addRoute( + `${prefix}${this.parameters.queryMethod === "GET" ? "{?q?}" : ""}`, + [this.parameters.queryMethod], + injector(model.store(), "httpQuery", "QUERY", model.store().handleModel(model)), + openapi + ); + openapi = { + post: { + tags: [name], + summary: `Create ${name}`, + operationId: `create${name}`, + requestBody: { + content: { + "application/json": { + schema: { + $ref: `#/components/schemas/${model.getIdentifier()}` + } + } + } + }, + responses: { + "200": { + description: "Operation success", + content: { + "application/json": { + schema: { + $ref: `#/components/schemas/${model.getIdentifier()}` + } + } + } + }, + "400": { + description: "Invalid input" + }, + "403": { + description: "You don't have permissions" + }, + "409": { + description: "Object already exists" + } + } + } + }; + model.Expose.restrict.create || + this.addRoute( + `${prefix}`, + ["POST"], + injector(model.store(), "operationCreate", "SET", model.getIdentifier()), + openapi + ); + openapi = { + delete: { + tags: [name], + operationId: `delete${name}`, + description: `Delete ${name} if the permissions allow`, + summary: `Delete a ${name}`, + responses: { + "204": { + description: "Operation success" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + } + }; + model.Expose.restrict.delete || + this.addRoute(`${prefix}/{uuid}`, ["DELETE"], injector(model.store(), "httpDelete", "DELETE"), openapi); + let openapiInfo = { + tags: [name], + operationId: `update${name}`, + description: `Update ${name} if the permissions allow`, + summary: `Update a ${name}`, + requestBody: { + content: { + "application/json": { + schema: { + $ref: `#/components/schemas/${model.getIdentifier()}` + } + } + } + }, + responses: { + "204": { + description: "Operation success" + }, + "400": { + description: "Invalid input" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + }; + openapi = { + put: openapiInfo, + patch: openapiInfo + }; + model.Expose.restrict.update || + this.addRoute(`${prefix}/{uuid}`, ["PUT", "PATCH"], injector(model.store(), "httpUpdate", "SET"), openapi); + openapi = { + get: { + tags: [name], + description: `Retrieve ${name} model if permissions allow`, + summary: `Retrieve a ${name}`, + operationId: `get${name}`, + responses: { + "200": { + content: { + "application/json": { + schema: { + $ref: `#/components/schemas/${model.getIdentifier()}` + } + } + } + }, + "400": { + description: "Object is invalid" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + } + }; + model.Expose.restrict.get || + this.addRoute(`${prefix}/{uuid}`, ["GET"], injector(model.store(), "httpGet", "GET"), openapi); + + // Add all actions + // Actions cannot be restricted as its purpose is to be exposed + let actions = model.getActions(); + Object.keys(actions).forEach(actionName => { + let action: ModelAction = actions[actionName]; + openapi = { + ...action.openapi + }; + (action.methods || ["PUT"]).forEach(method => { + openapi[method.toLowerCase()] = { + tags: [name], + ...(action.openapi?.[method.toLowerCase()] ?? {}) + }; + }); + if (app.hasSchema(`${model.getIdentifier(false)}.${actionName}.input`)) { + Object.keys(openapi) + .filter(k => ["get", "post", "put", "patch", "delete"].includes(k)) + .forEach(k => { + openapi[k].requestBody = { + content: { + "application/json": { + schema: { + $ref: `#/components/schemas/${model.getIdentifier(false)}.${actionName}.input` + } + } + } + }; + }); + } + if (app.hasSchema(`${model.getIdentifier(false)}.${actionName}.output`)) { + Object.keys(openapi) + .filter(k => ["get", "post", "put", "patch", "delete"].includes(k)) + .forEach(k => { + openapi[k].responses ??= {}; + openapi[k].responses["200"] ??= {}; + openapi[k].responses["200"].content = { + "application/json": { + schema: { + $ref: `#/components/schemas/${model.getIdentifier(false)}.${actionName}.output` + } + } + }; + }); + } + this.addRoute( + action.global ? `${prefix}/${actionName}` : `${prefix}/{uuid}/${actionName}`, + action.methods || ["PUT"], + async ctx => { + if (action.global) { + return model.store().httpGlobalAction(ctx, model); + } else { + ctx.getParameters().uuid = model.completeUid(ctx.getParameters().uuid); + return model.store().httpAction(ctx, action.method); + } + }, + openapi + ); + }); + + /* + Binaries should expose several methods + If cardinality is ONE + GET to download the binary + POST to upload a binary directly + PUT to upload a binary with challenge + DELETE /{hash} to delete a binary + PUT /{hash} to update metadata + GET /url to get a signed url + + If cardinality is MANY + GET /{index} to download the binary + GET /{index}/url to get a signed url + POST to upload a binary directly + PUT to upload a binary with challenge + DELETE /{index}/{hash} to delete a binary + PUT /{index}/{hash} to update metadata + */ + + (relations.binaries || []).forEach(binary => { + const store = Core.get().getBinaryStore(model, binary.attribute); + const modelInjector = async (ctx: WebContext) => { + ctx.getParameters().model = model.getIdentifier(); + ctx.getParameters().property = binary.attribute; + await store.httpRoute(ctx); + }; + const modelInjectorChallenge = async (ctx: WebContext) => { + ctx.getParameters().model = model.getIdentifier(); + ctx.getParameters().property = binary.attribute; + await store.httpChallenge(ctx); + }; + const modelInjectorGet = async (ctx: WebContext) => { + ctx.getParameters().model = model.getIdentifier(); + ctx.getParameters().property = binary.attribute; + await store.httpGet(ctx); + }; + openapi = { + put: { + tags: [name], + summary: `Upload ${binary.attribute} of ${name} after challenge`, + description: `You will need to call the challenge method first to get a signed url to upload the content\nIf the data is already known then done is returned`, + operationId: `upload${name}${binary.attribute}`, + requestBody: { + content: { + "application/octet-stream": { + schema: { + type: "object", + required: ["hash", "challenge"], + properties: { + hash: { + type: "string", + description: "md5(data)" + }, + challenge: { + type: "string", + description: "md5('Webda' + data)" + }, + size: { + type: "number", + description: "Size of the data" + }, + name: { + type: "string", + description: "Name of the file" + }, + mimetype: { + type: "string", + description: "Mimetype of the file" + }, + metadata: { + type: "object", + description: "Metadata to add to the binary" + } + } + } + } + } + }, + responses: { + "200": { + description: "Operation success", + content: { + "application/octet-stream": { + schema: { + type: "object", + properties: { + done: { + type: "boolean" + }, + url: { + type: "string" + }, + method: { + type: "string" + }, + md5: { + type: "string", + description: + "base64 md5 of the data\nThe next request requires Content-MD5 to be added with this one along with Content-Type: 'application/octet-stream'" + } + } + } + } + } + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + } + }; + this.addRoute(`${prefix}/{uuid}/${binary.attribute}`, ["PUT"], modelInjectorChallenge, openapi); + openapi = { + post: { + tags: [name], + summary: `Upload ${binary.attribute} of ${name} directly`, + description: `You can upload the content directly to the server\nThis is not the recommended way as it wont be able to optimize network traffic`, + operationId: `upload${name}${binary.attribute}`, + requestBody: { + content: { + "application/octet-stream": { + schema: { + type: "string", + format: "binary" + } + } + } + }, + responses: { + "204": { + description: "" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Object does not exist or attachment does not exist" + } + } + } + }; + this.addRoute(`${prefix}/{uuid}/${binary.attribute}`, ["POST"], modelInjector, openapi); + + let rootUrl = `${prefix}/{uuid}/${binary.attribute}`; + if (binary.cardinality === "MANY") { + rootUrl += "/{index}"; + } + + openapi = { + get: { + tags: [name], + summary: `Download ${binary.attribute} of ${name}`, + operationId: `download${name}${binary.attribute}`, + responses: { + "200": { + description: "Operation success", + content: { + "application/octet-stream": { + schema: { + type: "string", + format: "binary" + } + } + } + }, + "302": { + description: "Redirect to the binary" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Unknown object" + } + } + } + }; + this.addRoute(`${rootUrl}`, ["GET"], modelInjectorGet, openapi); + openapi = { + delete: { + tags: [name], + summary: `Delete ${binary.attribute} of ${name}`, + responses: { + "204": { + description: "" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Object does not exist or attachment does not exist" + }, + "412": { + description: "Provided hash does not match" + } + } + } + }; + this.addRoute(`${rootUrl}/{hash}`, ["DELETE"], modelInjector, openapi); + openapi = { + put: { + tags: [name], + summary: `Update metadata of ${binary.attribute} of ${name}`, + operationId: `update${name}${binary.attribute}`, + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + description: "Metadata to add to the binary", + additionalProperties: true + } + } + } + }, + responses: { + "204": { + description: "" + }, + "400": { + description: "Invalid input: metadata are limited to 4kb" + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Object does not exist or attachment does not exist" + }, + "412": { + description: "Provided hash does not match" + } + } + } + }; + this.addRoute(`${rootUrl}/{hash}`, ["PUT"], modelInjector, openapi); + openapi = { + get: { + tags: [name], + summary: `Get a signed url to download ${binary.attribute} of ${name} with a limited lifetime`, + responses: { + "200": { + description: "Operation success", + content: { + "application/json": { + schema: { + type: "object", + properties: { + Location: { + type: "string", + description: "Signed url to download the binary" + }, + Map: { + $ref: "#/components/schemas/BinaryMap" + } + } + } + } + } + }, + "403": { + description: "You don't have permissions" + }, + "404": { + description: "Object does not exist or attachment does not exist" + } + } + } + }; + this.addRoute(`${rootUrl}/url`, ["GET"], modelInjectorGet, openapi); + }); + + return true; + } + + /** + * Serve the openapi with the swagger-ui + * @param ctx + */ + async openapi(ctx: WebContext) { + this.openapiContent ??= swagger.replace( + "{{OPENAPI}}", + JSON.stringify(this.getWebda().getRouter().exportOpenAPI(true)) + ); + ctx.write(this.openapiContent); + } +} diff --git a/packages/core/src/rest/router.spec.ts b/packages/core/src/rest/router.spec.ts new file mode 100644 index 000000000..e5322de52 --- /dev/null +++ b/packages/core/src/rest/router.spec.ts @@ -0,0 +1,209 @@ +import { suite, test } from "@testdeck/mocha"; +import * as assert from "assert"; +import { User } from "./models/user"; +import { RouteInfo } from "./router"; +import { WebdaTest } from "./test"; +import { HttpContext } from "./utils/httpcontext"; + +@suite +class RouterTest extends WebdaTest { + @test + testGetRouteMethodsFromUrl() { + const info: RouteInfo = { methods: ["GET"], executor: "DefinedMailer" }; + this.webda.addRoute("/plop", info); + assert.deepStrictEqual(this.webda.getRouter().getRouteMethodsFromUrl("/"), ["GET", "POST"]); + assert.deepStrictEqual(this.webda.getRouter().getRouteMethodsFromUrl("/plop"), ["GET"]); + this.webda.addRoute("/plop", { methods: ["POST"], executor: "DefinedMailer" }); + assert.deepStrictEqual(this.webda.getRouter().getRouteMethodsFromUrl("/plop"), ["POST", "GET"]); + let call = []; + this.webda.log = (level, ...args) => { + call.push({ level, args }); + }; + this.webda.addRoute("/plop", { methods: ["GET"], executor: "DefinedMailer" }); + assert.deepStrictEqual(call, [ + { level: "TRACE", args: ["Add route GET /plop"] }, + { level: "WARN", args: ["GET /plop overlap with another defined route"] } + ]); + // Should be skipped + this.webda.addRoute("/plop", info); + } + + @test + async testRouterWithPrefix() { + this.webda.addRoute("/test/{uuid}", { methods: ["GET"], executor: "DefinedMailer" }); + this.webda.getGlobalParams().routePrefix = "/reprefix"; + assert.strictEqual(this.webda.getRouter().getFinalUrl("/test/plop"), "/reprefix/test/plop"); + assert.strictEqual(this.webda.getRouter().getFinalUrl("/reprefix/test/plop"), "/reprefix/test/plop"); + assert.strictEqual(this.webda.getRouter().getFinalUrl("//test/plop"), "/test/plop"); + this.webda.getRouter().remapRoutes(); + } + + @test + async testRouteWithPrefix() { + this.webda.addRoute("/test/{uuid}", { methods: ["GET"], executor: "DefinedMailer" }); + let httpContext = new HttpContext("test.webda.io", "GET", "/prefix/test/plop", "https"); + httpContext.setPrefix("/prefix"); + let ctx = await this.webda.newWebContext(httpContext); + this.webda.updateContextWithRoute(ctx); + assert.strictEqual(ctx.getPathParameters().uuid, "plop"); + } + + @test + async testRouteWithOverlap() { + this.webda.addRoute("/test/{uuid}", { methods: ["GET"], executor: "DefinedMailer" }); + this.webda.addRoute("/test/{id}", { methods: ["GET"], executor: "DefinedMailer" }); + let httpContext = new HttpContext("test.webda.io", "GET", "/test/plop", "https"); + let ctx = await this.webda.newWebContext(httpContext); + this.webda.updateContextWithRoute(ctx); + assert.deepStrictEqual(this.webda.getRouter().getRouteMethodsFromUrl("/test/plop"), ["GET"]); + } + + @test + async testRouteWithWeirdSplit() { + this.webda.addRoute("/test/{uuid}at{domain}", { methods: ["GET"], executor: "DefinedMailer" }); + let httpContext = new HttpContext("test.webda.io", "GET", "/test/plopatgoogle", "https"); + let ctx = await this.webda.newWebContext(httpContext); + this.webda.updateContextWithRoute(ctx); + assert.deepStrictEqual(ctx.getPathParameters(), { + uuid: "plop", + domain: "google" + }); + } + + @test + async testRouteWithSubPath() { + this.webda.addRoute("/test/{uuid}", { methods: ["GET"], executor: "DefinedMailer" }); + this.webda.addRoute("/test/{uuid}/users", { methods: ["GET"], executor: "DefinedMailer2" }); + this.webda.addRoute("/test/{puid}/users/{uuid}", { methods: ["GET"], executor: "DefinedMailer3" }); + let httpContext = new HttpContext("test.webda.io", "GET", "/test/plop", "https"); + let ctx = await this.webda.newWebContext(httpContext); + this.webda.updateContextWithRoute(ctx); + assert.deepStrictEqual(ctx.getPathParameters(), { + uuid: "plop" + }); + httpContext = new HttpContext("test.webda.io", "GET", "/test/plop/users", "https"); + ctx = await this.webda.newWebContext(httpContext); + this.webda.updateContextWithRoute(ctx); + assert.deepStrictEqual(ctx.getPathParameters(), { + uuid: "plop" + }); + httpContext = new HttpContext("test.webda.io", "GET", "/test/plop/users/plip", "https"); + ctx = await this.webda.newWebContext(httpContext); + this.webda.updateContextWithRoute(ctx); + assert.deepStrictEqual(ctx.getPathParameters(), { + uuid: "plip", + puid: "plop" + }); + } + + @test + async testRouteWithPath() { + this.webda.addRoute("/test/{+path}", { methods: ["GET"], executor: "DefinedMailer" }); + this.webda.addRoute("/test2/{+path}{?query*}", { methods: ["GET"], executor: "DefinedMailer" }); + let httpContext = new HttpContext("test.webda.io", "GET", "/test/plop/toto/plus", "https"); + let ctx = await this.webda.newWebContext(httpContext); + this.webda.updateContextWithRoute(ctx); + assert.deepStrictEqual(ctx.getPathParameters(), { path: "plop/toto/plus" }); + httpContext = new HttpContext("test.webda.io", "GET", "/test2/plop/toto/plus?query3=12&query2=test,test2", "https"); + ctx = await this.webda.newWebContext(httpContext); + this.webda.updateContextWithRoute(ctx); + assert.deepStrictEqual(ctx.getPathParameters(), { + path: "plop/toto/plus", + query: { + query3: "12", + query2: "test,test2" + } + }); + } + + @test + async testRouteWithEmail() { + this.webda.addRoute("/email/{email}/test", { methods: ["GET"], executor: "DefinedMailer" }); + this.webda.addRoute("/email/callback{?email,test?}", { methods: ["GET"], executor: "DefinedMailer" }); + let httpContext = new HttpContext("test.webda.io", "GET", "/email/test%40webda.io/test", "https"); + let ctx = await this.webda.newWebContext(httpContext); + this.webda.updateContextWithRoute(ctx); + assert.deepStrictEqual(ctx.getPathParameters(), { email: "test@webda.io" }); + httpContext = new HttpContext("test.webda.io", "GET", "/email/callback?email=test%40webda.io", "https"); + ctx = await this.webda.newWebContext(httpContext); + this.webda.updateContextWithRoute(ctx); + assert.deepStrictEqual(ctx.getPathParameters(), { email: "test@webda.io" }); + } + + @test + async testRouteWithQueryParam() { + this.webda.addRoute("/test/plop{?uuid?}", { methods: ["GET"], executor: "DefinedMailer" }); + let httpContext = new HttpContext("test.webda.io", "GET", "/test/plop", "http"); + let ctx = await this.webda.newWebContext(httpContext); + assert.strictEqual(this.webda.updateContextWithRoute(ctx), true); + assert.strictEqual(ctx.getPathParameters().uuid, undefined); + httpContext = new HttpContext("test.webda.io", "GET", "/test/plop?uuid=bouzouf", "http"); + ctx = await this.webda.newWebContext(httpContext); + assert.strictEqual(this.webda.updateContextWithRoute(ctx), true); + assert.strictEqual(ctx.getPathParameters().uuid, "bouzouf"); + this.webda.addRoute("/test/plop2{?params+}", { methods: ["GET"], executor: "DefinedMailer" }); + httpContext = new HttpContext("test.webda.io", "GET", "/test/plop2", "http"); + ctx = await this.webda.newWebContext(httpContext); + assert.strictEqual(this.webda.updateContextWithRoute(ctx), false); + httpContext = new HttpContext("test.webda.io", "GET", "/test/plop2?uuid=plop", "http"); + ctx = await this.webda.newWebContext(httpContext); + assert.strictEqual(this.webda.updateContextWithRoute(ctx), true); + assert.strictEqual(ctx.getPathParameters().params.uuid, "plop"); + } + + @test + completeOpenApi() { + let api = { paths: {}, info: { title: "Plop", version: "1.0" }, openapi: "", tags: [{ name: "test" }] }; + const info: RouteInfo = { + methods: ["GET"], + executor: "DefinedMailer", + openapi: { + tags: ["plop", "test"], + hidden: true, + get: { + schemas: { + output: "test" + } + } + } + }; + this.webda.addRoute("/plop{?*path}", info); + this.webda.getRouter().remapRoutes(); + this.webda.getRouter().completeOpenAPI(api); + assert.strictEqual(api.paths["/plop"], undefined); + this.webda.getRouter().completeOpenAPI(api, false); + assert.notStrictEqual(api.paths["/plop"], undefined); + assert.deepStrictEqual(api.paths["/plop"].get.tags, ["plop", "test"]); + assert.ok(api.tags.filter(f => f.name === "plop").length === 1); + } + + @test + cov() { + const info: RouteInfo = { + methods: ["GET"], + executor: "DefinedMailer", + openapi: { + tags: ["plop", "test"], + hidden: true, + get: { + schemas: { + output: "test" + } + } + } + }; + this.webda.addRoute("/cov", info); + this.webda.addRoute("/cov", info); + this.webda.addRoute("/cov", { ...info, methods: ["PUT"] }); + this.webda.getRouter().removeRoute("/cov", info); + this.webda.getRouter().getRoutes(); + } + + @test + getModelUrl() { + let routes = this.webda.getRouter().getRoutes(); + console.log(routes["/memory/users{?q}"][0].openapi); + let url = this.webda.getRouter().getModelUrl(new User()); + console.log(url); + } +} diff --git a/packages/core/src/rest/router.ts b/packages/core/src/rest/router.ts new file mode 100644 index 000000000..df2ca7317 --- /dev/null +++ b/packages/core/src/rest/router.ts @@ -0,0 +1,620 @@ +import { deepmerge } from "deepmerge-ts"; +import { JSONSchema7 } from "json-schema"; +import { OpenAPIV3 } from "openapi-types"; +import uriTemplates from "uri-templates"; +import { Core, CoreModel, CoreModelDefinition, HttpMethodType, WebContext } from ".."; + +type RecursivePartial = { + [P in keyof T]?: RecursivePartial; +}; + +export interface OpenApiWebdaOperation extends RecursivePartial { + schemas?: { + input?: JSONSchema7 | string; + output?: JSONSchema7 | string; + }; +} +/** + * Define overridable OpenAPI description + */ +export interface OpenAPIWebdaDefinition extends RecursivePartial { + /** + * Do not output for this specific Route + * + * It can still be output with --include-hidden, as it is needed to declare + * the route in API Gateway or any other internal documentation + */ + hidden?: boolean; + /** + * If defined will link to the model schema instead of a generic Object + */ + model?: string; + /** + * Tags defined for all methods + */ + tags?: string[]; + post?: OpenApiWebdaOperation; + put?: OpenApiWebdaOperation; + patch?: OpenApiWebdaOperation; + get?: OpenApiWebdaOperation; +} + +/** + * Route Information default information + */ +export interface RouteInfo { + model?: CoreModelDefinition; + /** + * HTTP Method to expose + */ + methods: HttpMethodType[]; + /** + * Executor name + */ + executor: string; + /** + * Method name on the executor + */ + _method?: string | Function; + /** + * OpenAPI definition + */ + openapi?: OpenAPIWebdaDefinition; + /** + * URI Template parser + */ + _uriTemplateParse?: { fromUri: (uri: string, options?: { strict: boolean }) => any; varNames: any }; + /** + * Query parameters to extract + */ + _queryParams?: { name: string; required: boolean }[]; + /** + * Catch all parameter + */ + _queryCatchAll?: string; + /** + * Hash + */ + hash?: string; + /** + * Intend to override existing + */ + override?: boolean; +} + +/** + * Manage Route resolution + * @category CoreFeatures + */ +export class Router { + protected routes: Map = new Map(); + protected initiated: boolean = false; + protected pathMap: { url: string; config: RouteInfo }[]; + protected webda: Core; + protected models: Map = new Map(); + + constructor(webda: Core) { + this.webda = webda; + } + + /** + * Registration of a model + * @param model + * @param url + */ + registerModelUrl(model: string, url: string) { + this.models.set(model, url); + } + + /** + * Return the route for model + * @param model + * @returns + */ + getModelUrl(model: string | CoreModel) { + if (typeof model !== "string") { + model = this.webda.getApplication().getModelName(model); + } + return this.models.get(model); + } + + /** + * Include prefix to the url if not present + * @param url + * @returns + */ + getFinalUrl(url: string): string { + // We have to replace all @ by %40 as it is allowed in url rfc (https://www.rfc-editor.org/rfc/rfc3986#page-22) + // But disallowed in https://www.rfc-editor.org/rfc/rfc6570#section-3.2.1 + // Similar for / in query string + url = url.replace(/@/g, "%40"); + if (url.includes("?")) { + url = url.substring(0, url.indexOf("?")) + "?" + url.substring(url.indexOf("?") + 1).replace(/\//g, "%2F"); + } + const prefix = this.webda.getGlobalParams().routePrefix || ""; + if (prefix && url.startsWith(prefix)) { + return url; + } + // Absolute url + if (url.startsWith("//")) { + return url.substring(1); + } + return `${prefix}${url}`; + } + + /** + * Return routes + * @returns + */ + getRoutes() { + return this.routes; + } + + /** + * Add a route dynamicaly + * + * @param {String} url of the route can contains dynamic part like {uuid} + * @param {Object} info the type of executor + */ + addRoute(url: string, info: RouteInfo): void { + const finalUrl = this.getFinalUrl(url); + this.webda.log("TRACE", `Add route ${info.methods.join(",")} ${finalUrl}`); + info.openapi ??= {}; + if (this.routes[finalUrl]) { + // If route is already added do not do anything + if (this.routes[finalUrl].includes(info)) { + return; + } + // Check and add warning if same method is used + let methods = this.routes[finalUrl].map((r: RouteInfo) => r.methods).flat(); + info.methods.forEach(m => { + if (methods.indexOf(m) >= 0) { + if (!info.override) { + this.webda.log("WARN", `${m} ${finalUrl} overlap with another defined route`); + } + } + }); + // Last added need to be overriding + this.routes[finalUrl].unshift(info); + } else { + this.routes[finalUrl] = [info]; + } + + if (this.initiated) { + this.remapRoutes(); + } + } + + /** + * Remove a route dynamicly + * + * @param {String} url to remove + */ + removeRoute(url: string, info: RouteInfo = undefined): void { + const finalUrl = this.getFinalUrl(url); + if (!info) { + delete this.routes[finalUrl]; + } else if (this.routes[finalUrl] && this.routes[finalUrl].includes(info)) { + this.routes[finalUrl].splice(this.routes[finalUrl].indexOf(info), 1); + } + + this.remapRoutes(); + } + + /** + * Reinit all routes + * + * It will readd the URITemplates if needed + * Sort all routes again + */ + public remapRoutes() { + // Might need to ensure each routes is prefixed + const prefix = this.webda.getGlobalParams().routePrefix || ""; + if (prefix) { + Object.keys(this.routes) + .filter(k => !k.startsWith(prefix)) + .forEach(k => { + this.routes[this.getFinalUrl(k)] = this.routes[k]; + delete this.routes[k]; + }); + } + + this.initURITemplates(this.routes); + + // Order path desc + this.pathMap = []; + for (let i in this.routes) { + // Might need to trail the query string + this.routes[i].forEach((config: RouteInfo) => { + this.pathMap.push({ + url: i, + config + }); + }); + } + this.pathMap.sort(this.comparePath); + this.initiated = true; + } + + protected comparePath(a, b): number { + // Normal node works with localeCompare but not Lambda... + // Local compare { to a return: 26 on Lambda + let bs = b.url.replace(/\{[^{}]+}/, "{}").split("/"); + let as = a.url.replace(/\{[^{}]+}/, "{}").split("/"); + for (let i in as) { + if (bs[i] === undefined) return -1; + if (as[i] === bs[i]) continue; + if (as[i][0] === "{" && bs[i][0] !== "{") return 1; + if (as[i][0] !== "{" && bs[i][0] === "{") return -1; + return bs[i] < as[i] ? -1 : 1; + } + return 1; + } + + /** + * @hidden + */ + protected initURITemplates(config: Map): void { + // Prepare tbe URI parser + for (let map in config) { + if (map.indexOf("{") !== -1) { + config[map].forEach((e: RouteInfo) => { + let idx = map.indexOf("{?"); + let queryOptional = true; + if (idx >= 0) { + let query = map.substring(idx + 2, map.length - 1); + e._queryParams = []; + query.split(",").forEach(q => { + if (q.endsWith("*")) { + e._queryCatchAll = q.substring(0, q.length - 1); + return; + } else if (q.endsWith("+")) { + e._queryCatchAll = q.substring(0, q.length - 1); + queryOptional = false; + return; + } else if (q.endsWith("?")) { + e._queryParams.push({ name: q.substring(0, q.length - 1), required: false }); + } else { + queryOptional = false; + e._queryParams.push({ name: q, required: true }); + } + }); + // We do not use uri-templates for query parsing + //map = map.substring(0, idx) + "?{+URITemplateQuery}"; + const templates = [uriTemplates(map.substring(0, idx) + "?{+URITemplateQuery}")]; + let pathTemplate = uriTemplates(map.substring(0, idx)); + if (queryOptional) { + templates.push(pathTemplate); + } + e._uriTemplateParse = { + fromUri: (url: string) => { + return templates.reduce((v, t) => (v ? v : t.fromUri(url)), undefined); + }, + varNames: [...pathTemplate.varNames, ...e._queryParams.map(q => q.name)] + }; + } else { + e._uriTemplateParse = uriTemplates(map); + } + }); + } + } + } + + /** + * Get all method for a specific url + * @param config + * @param method + * @param url + */ + getRouteMethodsFromUrl(url): HttpMethodType[] { + const finalUrl = this.getFinalUrl(url); + let methods = new Set(); + for (let i in this.pathMap) { + const routeUrl = this.pathMap[i].url; + const map = this.pathMap[i].config; + + if ( + routeUrl !== finalUrl && + (map._uriTemplateParse === undefined || map._uriTemplateParse.fromUri(finalUrl, { strict: true }) === undefined) + ) { + continue; + } + + map.methods.forEach(m => methods.add(m)); + } + return Array.from(methods); + } + + /** + * Get the route from a method / url + */ + public getRouteFromUrl(ctx: WebContext, method: HttpMethodType, url: string): any { + const finalUrl = this.getFinalUrl(url); + let parameters = this.webda.getConfiguration().parameters; + for (let i in this.pathMap) { + const routeUrl = this.pathMap[i].url; + const map = this.pathMap[i].config; + + // Check method + if (map.methods.indexOf(method) === -1) { + continue; + } + + if (routeUrl === finalUrl) { + ctx.setServiceParameters(parameters); + return map; + } + + if (map._uriTemplateParse === undefined) { + continue; + } + const parse_result = map._uriTemplateParse.fromUri(finalUrl, { strict: true }); + if (parse_result !== undefined) { + let parseUrl = new URL(`http://localhost${finalUrl}`); + if (map._queryCatchAll) { + parse_result[map._queryCatchAll] = {}; + parseUrl.searchParams.forEach((v, k) => { + if (!map._queryParams?.find(q => q.name === k)) { + parse_result[map._queryCatchAll][k] = v; + } + }); + } + // Check for each params + let mandatoryParams = true; + map._queryParams?.forEach(q => { + if (!parseUrl.searchParams.has(q.name)) { + mandatoryParams &&= !q.required; + return; + } + parse_result[q.name] = parseUrl.searchParams.get(q.name); + }); + // Skip if we miss mandatory params + if (!mandatoryParams) { + continue; + } + if (parse_result.URITemplateQuery) { + delete parse_result.URITemplateQuery; + } + ctx.setServiceParameters(parameters); + ctx.setPathParameters(parse_result); + + return map; + } + } + } + + protected getOpenAPISchema(schema) { + if (!schema) { + return { + $ref: "#/components/schemas/Object" + }; + } else if (typeof schema === "string") { + return { + $ref: "#/components/schemas/" + schema + }; + } + return schema; + } + /** + * Add all known routes to paths + * + * @param openapi to complete + * @param skipHidden add hidden routes or not + */ + completeOpenAPI(openapi: OpenAPIV3.Document, skipHidden: boolean = true) { + let hasTag = tag => openapi.tags.find(t => t.name === tag) !== undefined; + for (let i in this.routes) { + this.routes[i].forEach((route: RouteInfo) => { + route.openapi = this.webda + .getApplication() + .replaceVariables(route.openapi || {}, this.webda.getService(route.executor).getOpenApiReplacements()); + if (route.openapi.hidden && skipHidden) { + return; + } + route.openapi.hidden = false; + let path = i; + if (i.indexOf("{?") >= 0) { + path = i.substring(0, i.indexOf("{?")); + } + openapi.paths[path] = openapi.paths[path] || {}; + if (route._uriTemplateParse) { + openapi.paths[path].parameters = []; + route._uriTemplateParse.varNames.forEach(varName => { + const queryParam = route._queryParams?.find(i => i.name === varName); + if (queryParam) { + let name = varName; + if (name.startsWith("*")) { + name = name.substr(1); + } + openapi.paths[path].parameters.push({ + name, + in: "query", + required: queryParam.required, + schema: { + type: "string" + } + }); + return; + } + openapi.paths[path].parameters.push({ + // ^[a-zA-Z0-9._$-]+$] is the official regex of AWS + name: varName.replace(/[^a-zA-Z0-9._$-]/g, ""), + in: "path", + required: true, + schema: { + type: "string" + } + }); + }); + } + route.methods.forEach(method => { + let responses: { [key: string]: OpenAPIV3.ResponseObject }; + let schema: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject; + let description; + let summary; + let operationId; + let requestBody; + let tags = route.openapi.tags ?? []; + // Refactor here + if (route.openapi[method.toLowerCase()]) { + responses = route.openapi[method.toLowerCase()].responses; + schema = this.getOpenAPISchema(route.openapi[method.toLowerCase()].schemas?.output); + description = route.openapi[method.toLowerCase()].description; + summary = route.openapi[method.toLowerCase()].summary; + operationId = route.openapi[method.toLowerCase()].operationId; + tags.push(...(route.openapi[method.toLowerCase()].tags || [])); + requestBody = route.openapi[method.toLowerCase()].requestBody; + } + responses = responses || { + 200: { + description: "Operation success" + } + }; + for (let j in responses) { + // Add default answer + let code = parseInt(j); + if (code < 300 && code >= 200 && !responses[j].description) { + responses[j].description = "Operation success"; + responses[j].content ??= {}; + responses[j].content["application/json"] = { + schema + }; + } + } + // Add the service name if no tags are defined + if (tags.length === 0) { + tags.push(route.executor); + } + let desc: OpenAPIV3.OperationObject = { + tags, + responses: responses, + description, + summary, + operationId, + requestBody + }; + if (method.toLowerCase().startsWith("p") && route.openapi[method.toLowerCase()]?.schemas?.input) { + // Add request schema if exist + desc.requestBody ??= { + content: { + "application/json": { + schema: this.getOpenAPISchema(route.openapi[method.toLowerCase()]?.schemas?.input) + } + } + }; + } + openapi.paths[path][method.toLowerCase()] = desc; + tags + .filter(tag => !hasTag(tag)) + .forEach(tag => + openapi.tags.push({ + name: tag + }) + ); + }); + }); + } + } + + /** + * Export OpenAPI + * @param skipHidden + * @returns + */ + exportOpenAPI(skipHidden: boolean = true): OpenAPIV3.Document { + const app = Core.get().getApplication(); + let packageInfo = app.getPackageDescription(); + let contact: OpenAPIV3.ContactObject; + if (typeof packageInfo.author === "string") { + contact = { + name: packageInfo.author + }; + } else if (packageInfo.author) { + contact = packageInfo.author; + } + let license: OpenAPIV3.LicenseObject; + if (typeof packageInfo.license === "string") { + license = { + name: packageInfo.license + }; + } else if (packageInfo.license) { + license = packageInfo.license; + } + let openapi: OpenAPIV3.Document = deepmerge( + { + openapi: "3.0.3", + info: { + description: packageInfo.description, + version: packageInfo.version || "0.0.0", + title: packageInfo.title || "Webda-based application", + termsOfService: packageInfo.termsOfService, + contact, + license + }, + components: { + schemas: { + Object: { + type: "object" + } + } + }, + paths: {}, + tags: [] + }, + app.getConfiguration().openapi || {} + ); + let models = app.getModels(); + const schemas = app.getSchemas(); + // Copy all input/output from actions + for (let i in schemas) { + if (!(i.endsWith(".input") || i.endsWith(".output"))) { + continue; + } + // @ts-ignore + openapi.components.schemas[i] ??= schemas[i]; + // Not sure how to test following + /* c8 ignore next 5 */ + for (let j in schemas[i].definitions) { + // @ts-ignore + openapi.components.schemas[j] ??= schemas[i].definitions[j]; + } + } + for (let i in models) { + let model = models[i]; + let desc: JSONSchema7 = { + type: "object" + }; + let modelName = model.name || i.split("/").pop(); + let schema = Core.get().getApplication().getSchema(i); + if (schema) { + for (let j in schema.definitions) { + // @ts-ignore + openapi.components.schemas[j] ??= schema.definitions[j]; + } + delete schema.definitions; + desc = schema; + } + // Remove empty required as openapi does not like that + // Our compiler is not generating this anymore but it is additional protection + /* c8 ignore next 3 */ + if (desc.required && desc.required.length === 0) { + delete desc.required; + } + // Remove $schema + delete desc.$schema; + // Rename all #/definitions/ by #/components/schemas/ + openapi.components.schemas[modelName] = JSON.parse( + JSON.stringify(desc).replace(/#\/definitions\//g, "#/components/schemas/") + ); + } + this.completeOpenAPI(openapi, skipHidden); + openapi.tags.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); + let paths = {}; + Object.keys(openapi.paths) + .sort() + .forEach(i => (paths[i] = openapi.paths[i])); + openapi.paths = paths; + + return openapi; + } +} From 7ce2fdc054f308261033d821b426b0f9c4254c45 Mon Sep 17 00:00:00 2001 From: Remi Cattiau Date: Thu, 28 Mar 2024 09:39:53 -0700 Subject: [PATCH 5/7] feat!: update StorageFinder to use promises to allow GCS/S3 --- packages/core/src/unpackedapplication.ts | 2 +- packages/core/src/utils/serializers.spec.ts | 14 ++--- packages/core/src/utils/serializers.ts | 57 ++++++++++++--------- 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/packages/core/src/unpackedapplication.ts b/packages/core/src/unpackedapplication.ts index 8698f84ff..8a93d3f32 100644 --- a/packages/core/src/unpackedapplication.ts +++ b/packages/core/src/unpackedapplication.ts @@ -227,7 +227,7 @@ export class UnpackedApplication extends Application { if (!fs.existsSync(nodeModules)) { return; } - FileUtils.walk( + FileUtils.walkSync( nodeModules, filepath => { // We filter out the cache of nx diff --git a/packages/core/src/utils/serializers.spec.ts b/packages/core/src/utils/serializers.spec.ts index 01762ff8e..6e1126bb5 100644 --- a/packages/core/src/utils/serializers.spec.ts +++ b/packages/core/src/utils/serializers.spec.ts @@ -244,7 +244,7 @@ plop: test } @test - walker() { + async walker() { try { let res = []; if (!existsSync("./test/link")) { @@ -255,14 +255,14 @@ plop: test symlinkSync("../non-existing", "test/badlink"); } } catch (err) {} - FileUtils.walk("test", f => res.push(f)); + await FileUtils.walk("test", f => res.push(f)); assert.ok( ["test/models/ident.js", "test/jsonutils/mdocs.yaml", "test/data/test.png"] .map(c => res.includes(c)) .reduce((v, c) => v && c, true) ); res = []; - FileUtils.walk("test", f => res.push(f), { + FileUtils.walkSync("test", f => res.push(f), { includeDir: true, followSymlinks: true }); @@ -273,18 +273,18 @@ plop: test } @test - finder() { - let res = FileUtils.find("test", { filterPattern: /Dockerfile/ }); + async finder() { + let res = await FileUtils.find("test", { filterPattern: /Dockerfile/ }); assert.ok(res.includes("test/Dockerfile")); } @test async streams() { - const st = FileUtils.getWriteStream("/tmp/webda.stream"); + const st = await FileUtils.getWriteStream("/tmp/webda.stream"); let p = new Promise(resolve => st.on("finish", resolve)); st.end(); await p; - FileUtils.getReadStream("/tmp/webda.stream"); + await FileUtils.getReadStream("/tmp/webda.stream"); } @test diff --git a/packages/core/src/utils/serializers.ts b/packages/core/src/utils/serializers.ts index 6e818b28e..63a1b61b4 100644 --- a/packages/core/src/utils/serializers.ts +++ b/packages/core/src/utils/serializers.ts @@ -109,19 +109,19 @@ export interface StorageFinder { /** * Recursively browse the path and call processor on each */ - walk(path: string, processor: (filepath: string) => void, options?: WalkerOptionsType, depth?: number): void; + walk(path: string, processor: (filepath: string) => void, options?: WalkerOptionsType, depth?: number): Promise; /** * Find */ - find(currentPath: string, options?: FinderOptionsType): string[]; + find(currentPath: string, options?: FinderOptionsType): Promise; /** * Get a write stream based on the id return by the finder */ - getWriteStream(path: string): Writable; + getWriteStream(path: string): Promise; /** * Get a read stream based on the id return by the finder */ - getReadStream(path: string): Readable; + getReadStream(path: string): Promise; } /** @@ -144,36 +144,26 @@ export const FileUtils: StorageFinder & { save: (object: any, filename: string, publicAudience?: boolean, format?: Format) => void; load: (filename: string, format?: Format) => any; clean: (...files: string[]) => void; + walkSync: (path: string, processor: (filepath: string) => void, options?: WalkerOptionsType, depth?: number) => void; } = { /** * @override */ - getWriteStream: (path: string) => { + getWriteStream: async (path: string) => { return createWriteStream(path); }, - getReadStream: (path: string) => { + getReadStream: async (path: string) => { return createReadStream(path); }, - /** - * Recursively run a process through all descendant files under a path - * - * @param {string} path - * @param {Function} processor Processing function, eg: (filepath: string) => void - * @param {string} currentPath Path to dig - * @param {boolean} options.followSymlinks Follow symlinks which targets a folder - * @param {boolean} options.includeDir Include folders to results transmitted to the processor function - * @param {number} options.maxDepth Maximum depth level to investigate, default is 100 to prevent infinite loops - * @param {number} state.depth Starting level counter - */ - walk: ( + walkSync: ( path: string, processor: (filepath: string) => void, options: WalkerOptionsType = { maxDepth: 100 }, depth: number = 0 - ): void => { + ) => { let files = readdirSync(path); - const fileItemCallback = p => { + const fileItemCallback = async p => { try { const stat = lstatSync(p); if (stat.isDirectory()) { @@ -183,7 +173,7 @@ export const FileUtils: StorageFinder & { // folder found, trying to dig further if (!options.maxDepth || depth < options.maxDepth) { // unless we reached the maximum depth - FileUtils.walk(p, processor, options, depth + 1); + FileUtils.walkSync(p, processor, options, depth + 1); } } else if (stat.isSymbolicLink() && options.followSymlinks) { let realPath; @@ -200,7 +190,7 @@ export const FileUtils: StorageFinder & { // following below if (!options.maxDepth || depth < options.maxDepth) { // unless we reached the maximum depth - FileUtils.walk(options.resolveSymlink ? realPath : p, processor, options, depth + 1); + FileUtils.walkSync(options.resolveSymlink ? realPath : p, processor, options, depth + 1); } } } else { @@ -219,6 +209,25 @@ export const FileUtils: StorageFinder & { }; files.map(f => join(path, f)).forEach(fileItemCallback); }, + /** + * Recursively run a process through all descendant files under a path + * + * @param {string} path + * @param {Function} processor Processing function, eg: (filepath: string) => void + * @param {string} currentPath Path to dig + * @param {boolean} options.followSymlinks Follow symlinks which targets a folder + * @param {boolean} options.includeDir Include folders to results transmitted to the processor function + * @param {number} options.maxDepth Maximum depth level to investigate, default is 100 to prevent infinite loops + * @param {number} state.depth Starting level counter + */ + walk: async ( + path: string, + processor: (filepath: string) => void, + options: WalkerOptionsType = { maxDepth: 100 }, + depth: number = 0 + ): Promise => { + FileUtils.walkSync(path, processor, options, depth); + }, /** * Find files below a provided path, optionally filtered by a RegExp pattern * Without pattern provided, ALL FOUND FILES will be returned by default @@ -230,7 +239,7 @@ export const FileUtils: StorageFinder & { * @param {RegExp} options.filterPattern RegExp pattern to filter, no filter will find all files * @returns */ - find: (currentPath: string, options: FinderOptionsType = { maxDepth: 3 }): string[] => { + find: async (currentPath: string, options: FinderOptionsType = { maxDepth: 3 }) => { let found = []; const processor = (filepath: string) => { if (!options.filterPattern || options.filterPattern.test(filepath)) { @@ -238,7 +247,7 @@ export const FileUtils: StorageFinder & { found.push(filepath); } }; - FileUtils.walk(currentPath, processor, options); + await FileUtils.walk(currentPath, processor, options); return found; }, /** From d620a2852ee7690cf6665169c478cb753daea9aa Mon Sep 17 00:00:00 2001 From: Remi Cattiau Date: Tue, 16 Apr 2024 21:26:43 -0700 Subject: [PATCH 6/7] feat: add iterate declaration --- packages/core/src/models/coremodel.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/models/coremodel.ts b/packages/core/src/models/coremodel.ts index 3e69a1f61..ca8fdf4e4 100644 --- a/packages/core/src/models/coremodel.ts +++ b/packages/core/src/models/coremodel.ts @@ -202,6 +202,12 @@ export interface CoreModelDefinition { * @param context */ authorizeClientEvent(_event: string, _context: Context, _model?: T): boolean; + /** + * Iterate through the model + * @param query + * @param includeSubclass + */ + iterate(query?: string, includeSubclass?: boolean): AsyncGenerator; } const ActionsAnnotated: Map = new Map(); From 74c6ca662139b4775c480a12bb66564c45d2a3d2 Mon Sep 17 00:00:00 2001 From: Remi Cattiau Date: Thu, 18 Jul 2024 19:23:42 -0700 Subject: [PATCH 7/7] wip: other modif --- docs/pages/Concepts/Models/Models.md | 9 ++ docs/pages/Concepts/Services/Events.md | 85 ++++--------------- .../Deployments/Kubernetes/Kubernetes.md | 5 ++ package.json | 2 +- packages/async/README.md | 12 +++ packages/cloudevents/tsconfig.json | 4 +- packages/cloudevents/tsconfig.test.json | 2 +- packages/core/src/core.ts | 2 +- packages/core/src/services/authentication.ts | 21 +++-- .../core/src/services/invitationservice.ts | 34 ++++---- .../src/services/kubernetesconfiguration.ts | 2 +- packages/core/src/services/service.ts | 9 +- packages/core/src/stores/mapper.ts | 19 +++-- packages/core/src/stores/memory.spec.ts | 2 +- packages/core/src/stores/store.ts | 7 ++ packages/core/src/test.ts | 4 +- packages/gcp/src/services/storage.ts | 77 ++++++++++++++++- packages/hawk/src/apikey.ts | 2 +- packages/hawk/src/hawk.ts | 42 ++++----- 19 files changed, 202 insertions(+), 138 deletions(-) diff --git a/docs/pages/Concepts/Models/Models.md b/docs/pages/Concepts/Models/Models.md index b0e2438f5..7c9b3301c 100644 --- a/docs/pages/Concepts/Models/Models.md +++ b/docs/pages/Concepts/Models/Models.md @@ -53,6 +53,15 @@ class MyModel extends CoreModel { } ``` +An action is defined as global if it is on a static method. You can also rename the action by providing a name. + +```js title="src/mymodel.ts" +class MyModel extends CoreModel { + @Action({ name: "myAction" }) + static globalAction() {} +} +``` + ## Model schemas The schema is generated with [ts-json-schema-generator](https://github.com/vega/ts-json-schema-generator) diff --git a/docs/pages/Concepts/Services/Events.md b/docs/pages/Concepts/Services/Events.md index 250308f00..a1ea32850 100644 --- a/docs/pages/Concepts/Services/Events.md +++ b/docs/pages/Concepts/Services/Events.md @@ -1,79 +1,24 @@ # Events -The framework use the EventEmitter for events. +The framework does not use the EventEmitter for events since v4. -`emit()` this will not wait for any promise returned by listeners +We aligned on CloudEvents and its subscription system to allow easier integration with other systems. -`emitSync()` this will wait for resolution on all promises returned by listeners +To emit an event, just define the event as a class and use its `emit` method. +If you await the method then you will wait for its delivery and local listeners to be processed. -We also have a mechanism to listen asynchronously to events. They will then be posted to a Queue for them -to be consumed through a `AsyncEvent.worker()` +To listen to an event you can use the `on`. -```mermaid -sequenceDiagram - participant S as Service - participant As as AsyncEventService - participant Q as Queue - participant Aw as AsyncEventService Worker - As->>S: Bind event to a sendQueue listener - activate As - S->>As: Emit event - As->>Q: Push the event to the queue - deactivate As - Aw->>Q: Consume queue - Aw->>Aw: Call the original listener -``` - -### Webda.Init - -### Webda.Init.Services - -### Webda.Create.Services - -### Webda.NewContext - -### Webda.Request - -### Store.Save - -### Store.Saved - -### Store.Update - -### Store.Updated - -### Store.PartialUpdate - -### Store.PArtialUpdated - -### Store.Delete +Subscription must be named from your service, this allows it to be overriden by configuration to execute asynchronously. -### Store.Deleted - -### Store.Get - -### Store.Find - -### Store.Found - -### Store.WebCreate - -### Store.WebUpdate - -### Store.WebGet - -### Store.WebDelete - -### Store.Action - -### Store.Actionned - -## Runtime Events - -To implement some clients listeners we can allow listeners by uuid +``` +emit -> @Emits() +on(name: string, event: string | Subscription , callback: async () => {}) +``` -addModelListener(model: string, uuid: string) // fullUuid? -removeModelListener(model: string, uuid: string) // fullUuid? +Local -> execute within current node process +PubSub -> execute on all nodes within the cluster +Queue -> execute once by some workers -Get the current map -The Pub/Sub will then send all events for this uuid. +EventService->worker(...subscription: string) -> if Queue subscription +EventService->init() -> will sub to all PubSub/Local on startup diff --git a/docs/pages/Deployments/Kubernetes/Kubernetes.md b/docs/pages/Deployments/Kubernetes/Kubernetes.md index 6f6fecf9f..a19ba5772 100644 --- a/docs/pages/Deployments/Kubernetes/Kubernetes.md +++ b/docs/pages/Deployments/Kubernetes/Kubernetes.md @@ -3,3 +3,8 @@ You can deploy directly to Kubernetes. The CronService is also compatible and translate the `@Cron` annotation into `CronJob` within Kubernetes. + +## Updates + +Check if resources are compatible with next version of Kubernetes. +https://github.com/kubepug/kubepug diff --git a/package.json b/package.json index 6610fad4e..4c02e2aa4 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "prettier-plugin-organize-imports": "^3.0.0", "sinon": "^17.0.0", "ts-node": "^10.1.0", - "typescript": "~5.3.2" + "typescript": "~5.4.5" }, "author": "loopingz", "license": "MIT", diff --git a/packages/async/README.md b/packages/async/README.md index 87cfdadb3..5890c14ce 100644 --- a/packages/async/README.md +++ b/packages/async/README.md @@ -18,7 +18,19 @@ You have different types of runner available. It allows you to launch other language/binary from within your application and capure the output. +## Scheduler + +You can schedule an action to be executed at a specific time. + +```typescript +this.getService("AsyncJobService").scheduleJob( + AsyncAction.createAction("test", "echo", ["Hello World"]), + new Date(Date.now() + 1000) +); +``` + + ## Sponsors