From de5b626fc224824fa2a4efc856a9e3e4b6615272 Mon Sep 17 00:00:00 2001 From: liuxy0551 Date: Mon, 11 May 2026 17:34:57 +0800 Subject: [PATCH] feat: add generic SQL language support --- README-zh_CN.md | 2 + README.md | 2 + package.json | 5 +- pnpm-lock.yaml | 10 +- src/all.contributions.ts | 1 + src/common/constants.ts | 3 +- src/languageFeatures.ts | 4 +- src/languages/generic/generic.contribution.ts | 18 ++ src/languages/generic/generic.snippet.ts | 76 +++++ src/languages/generic/generic.ts | 266 ++++++++++++++++++ src/languages/generic/generic.worker.ts | 11 + src/languages/generic/genericWorker.ts | 17 ++ src/setupLanguageFeatures.ts | 2 + src/snippets.ts | 1 + website/src/consts/index.ts | 3 +- website/src/languages/index.ts | 12 + website/src/languages/languageWorker.ts | 4 + 17 files changed, 427 insertions(+), 10 deletions(-) create mode 100644 src/languages/generic/generic.contribution.ts create mode 100644 src/languages/generic/generic.snippet.ts create mode 100644 src/languages/generic/generic.ts create mode 100644 src/languages/generic/generic.worker.ts create mode 100644 src/languages/generic/genericWorker.ts diff --git a/README-zh_CN.md b/README-zh_CN.md index aab75a00..fec76452 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -39,6 +39,7 @@ Monaco SQL Languages 是一个基于 Monaco Editor 的 SQL 语言项目,从 [m - Trino (Presto) - PostgreSQL - Impala +- GenericSQL
@@ -72,6 +73,7 @@ npm install monaco-sql-languages import 'monaco-sql-languages/esm/languages/trino/trino.contribution'; import 'monaco-sql-languages/esm/languages/pgsql/pgsql.contribution'; import 'monaco-sql-languages/esm/languages/impala/impala.contribution'; + import 'monaco-sql-languages/esm/languages/generic/generic.contribution'; // 或者你可以通过下面的方式一次性导入所有的语言功能 // import 'monaco-sql-languages/esm/all.contributions'; diff --git a/README.md b/README.md index 49268932..e7264c32 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ This project is based on the SQL language project of Monaco Editor, which was fo - Trino (Presto) - PostgreSQL - Impala +- GenericSQL
@@ -71,6 +72,7 @@ npm install monaco-sql-languages import 'monaco-sql-languages/esm/languages/trino/trino.contribution'; import 'monaco-sql-languages/esm/languages/pgsql/pgsql.contribution'; import 'monaco-sql-languages/esm/languages/impala/impala.contribution'; + import 'monaco-sql-languages/esm/languages/generic/generic.contribution'; // Or you can import all language contributions at once. // import 'monaco-sql-languages/esm/all.contributions'; diff --git a/package.json b/package.json index ee3ccfe1..8e2b4eeb 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "postgresql", "flink", "trino", - "impala" + "impala", + "generic" ], "repository": { "type": "git", @@ -84,6 +85,6 @@ ] }, "dependencies": { - "dt-sql-parser": "4.4.2" + "dt-sql-parser": "4.5.0-beta.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ce50c9d..5d0cb567 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: dt-sql-parser: - specifier: 4.4.2 - version: 4.4.2(antlr4ng-cli@1.0.7) + specifier: 4.5.0-beta.2 + version: 4.5.0-beta.2(antlr4ng-cli@1.0.7) devDependencies: '@commitlint/cli': specifier: ^17.7.2 @@ -714,8 +714,8 @@ packages: resolution: {integrity: sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==} engines: {node: '>=6'} - dt-sql-parser@4.4.2: - resolution: {integrity: sha512-NpfSeCetxDICs5NNOHKO/e/PTQ/EFnGW0QBo11e5nmuWqVRACZkArcBH9/LKnoe1Mdtql45U5h0aU9GOqhBLWg==} + dt-sql-parser@4.5.0-beta.2: + resolution: {integrity: sha512-mf2ja7dMFPIk1P5/LzvcRk6mxoRDjdwokITTwklKEWpiNngj5Y/GVlfVb+v3/XekWShPMqrorbRxhxbkuyTA+g==} engines: {node: '>=18'} eastasianwidth@0.2.0: @@ -2729,7 +2729,7 @@ snapshots: find-up: 3.0.0 minimatch: 3.1.2 - dt-sql-parser@4.4.2(antlr4ng-cli@1.0.7): + dt-sql-parser@4.5.0-beta.2(antlr4ng-cli@1.0.7): dependencies: antlr4-c3: 3.3.7(antlr4ng-cli@1.0.7) antlr4ng: 2.0.11(antlr4ng-cli@1.0.7) diff --git a/src/all.contributions.ts b/src/all.contributions.ts index 8989152f..6b43abe3 100644 --- a/src/all.contributions.ts +++ b/src/all.contributions.ts @@ -5,3 +5,4 @@ import './languages/trino/trino.contribution'; import './languages/mysql/mysql.contribution'; import './languages/pgsql/pgsql.contribution'; import './languages/impala/impala.contribution'; +import './languages/generic/generic.contribution'; diff --git a/src/common/constants.ts b/src/common/constants.ts index c8f7d8ee..eb8ab9ea 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -37,5 +37,6 @@ export enum LanguageIdEnum { PG = 'pgsql', SPARK = 'sparksql', TRINO = 'trinosql', - IMPALA = 'impalasql' + IMPALA = 'impalasql', + GENERIC = 'genericsql' } diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index d19a1960..d3877b37 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -255,7 +255,9 @@ export class DefinitionAdapter implements languages.Def startIndex: -1, endIndex: -1, startColumn: -1, - endColumn: -1 + endColumn: -1, + startTokenIndex: -1, + endTokenIndex: -1 }; const curEntity = entities?.find((entity: EntityContext) => { const entityPosition = entity.position; diff --git a/src/languages/generic/generic.contribution.ts b/src/languages/generic/generic.contribution.ts new file mode 100644 index 00000000..f474843d --- /dev/null +++ b/src/languages/generic/generic.contribution.ts @@ -0,0 +1,18 @@ +import { registerLanguage } from '../../_.contribution'; +import { LanguageIdEnum } from '../../common/constants'; +import { setupLanguageFeatures } from '../../setupLanguageFeatures'; + +registerLanguage({ + id: LanguageIdEnum.GENERIC, + extensions: ['.genericsql'], + aliases: ['GenericSQL', 'generic', 'Generic'], + loader: () => import('./generic') +}); + +setupLanguageFeatures(LanguageIdEnum.GENERIC, { + completionItems: true, + diagnostics: false, + references: false, + definitions: false, + hover: false +}); diff --git a/src/languages/generic/generic.snippet.ts b/src/languages/generic/generic.snippet.ts new file mode 100644 index 00000000..5c803966 --- /dev/null +++ b/src/languages/generic/generic.snippet.ts @@ -0,0 +1,76 @@ +import type { CompletionSnippetOption } from 'src/monaco.contribution'; + +export const genericSnippets: CompletionSnippetOption[] = [ + { + label: 'select', + prefix: 'SELECT', + body: ['SELECT ${2:column1}, ${3:column2} FROM ${1:table_name};\n$4'] + }, + { + label: 'select-join', + prefix: 'SELECT-JOIN', + body: [ + 'SELECT ${8:column1} FROM ${1:table_name1} ${2:t1}', + '${3:LEFT} JOIN ${4:table_name2} ${5:t2} ON ${2:t1}.${6:column1} = ${5:t2}.${7:column2};\n$9' + ] + }, + { + label: 'select-order-by', + prefix: 'SELECT-ORDER-BY', + body: [ + 'SELECT ${2:column1}, ${3:column2} FROM ${1:table_name} ORDER BY ${4:column1} ${5:desc};\n$6' + ] + }, + { + label: 'select-group-by', + prefix: 'SELECT-GROUP-BY', + body: ['SELECT ${2:column1}, COUNT(*) FROM ${1:table_name} GROUP BY ${2:column1};\n$3'] + }, + { + label: 'insert', + prefix: 'INSERT-INTO', + body: [ + 'INSERT INTO ${1:table_name} (${2:column1}, ${3:column2})', + 'SELECT ${4:column1}, ${5:column2} FROM ${6:source_table};\n$7' + ] + }, + { + label: 'update', + prefix: 'UPDATE', + body: [ + 'UPDATE ${1:table_name}', + 'SET ${2:column1} = ${3:value1}', + 'WHERE ${4:column2} = ${5:value2};\n$6' + ] + }, + { + label: 'delete', + prefix: 'DELETE', + body: ['DELETE FROM ${1:table_name}', 'WHERE ${2:column1} = ${3:value1};\n$4'] + }, + { + label: 'create-table', + prefix: 'CREATE-TABLE', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name} (', + '\t${2:column1} ${3:INT} PRIMARY KEY,', + '\t${4:column2} ${5:VARCHAR(100)} NOT NULL', + ');\n$6' + ] + }, + { + label: 'alter-table-add', + prefix: 'ALTER-TABLE-ADD', + body: ['ALTER TABLE ${1:table_name} ADD COLUMN ${2:column_name} ${3:INT};\n$4'] + }, + { + label: 'alter-table-drop', + prefix: 'ALTER-TABLE-DROP', + body: ['ALTER TABLE ${1:table_name} DROP COLUMN ${2:column_name};\n$3'] + }, + { + label: 'drop-table', + prefix: 'DROP-TABLE', + body: ['DROP TABLE IF EXISTS ${1:table_name};\n$2'] + } +]; diff --git a/src/languages/generic/generic.ts b/src/languages/generic/generic.ts new file mode 100644 index 00000000..ccf3a548 --- /dev/null +++ b/src/languages/generic/generic.ts @@ -0,0 +1,266 @@ +import type { languages } from '../../fillers/monaco-editor-core'; +import { TokenClassConsts } from '../../common/constants'; + +export const conf: languages.LanguageConfiguration = { + comments: { + lineComment: '--', + blockComment: ['/*', '*/'] + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + { open: '`', close: '`' } + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + { open: '`', close: '`' } + ] +}; + +export const language = { + defaultToken: '', + tokenPostfix: '.sql', + ignoreCase: true, + brackets: [ + { open: '[', close: ']', token: TokenClassConsts.DELIMITER_SQUARE }, + { open: '(', close: ')', token: TokenClassConsts.DELIMITER_PAREN }, + { open: '{', close: '}', token: TokenClassConsts.DELIMITER_CURLY } + ], + keywords: [ + 'ADD', + 'ALL', + 'ALTER', + 'AND', + 'AS', + 'ASC', + 'BETWEEN', + 'BY', + 'CASE', + 'CAST', + 'CHECK', + 'COALESCE', + 'COLUMN', + 'CONSTRAINT', + 'CREATE', + 'CROSS', + 'DEFAULT', + 'DELETE', + 'DESC', + 'DISTINCT', + 'DROP', + 'ELSE', + 'END', + 'ESCAPE', + 'EXCEPT', + 'EXISTS', + 'FALSE', + 'FIRST', + 'FOREIGN', + 'FROM', + 'FULL', + 'GROUP', + 'HAVING', + 'IF', + 'IN', + 'INNER', + 'INSERT', + 'INTERSECT', + 'INTO', + 'IS', + 'JOIN', + 'KEY', + 'LAST', + 'LEFT', + 'LIKE', + 'LIMIT', + 'NOT', + 'NULL', + 'NULLIF', + 'NULLS', + 'OFFSET', + 'ON', + 'OR', + 'ORDER', + 'OUTER', + 'PRIMARY', + 'RECURSIVE', + 'REFERENCES', + 'RENAME', + 'RIGHT', + 'SELECT', + 'SET', + 'TABLE', + 'THEN', + 'TO', + 'TRUE', + 'UNION', + 'UNIQUE', + 'UPDATE', + 'VALUES', + 'WHEN', + 'WHERE', + 'WITH' + ], + operators: [ + 'AND', + 'BETWEEN', + 'IN', + 'LIKE', + 'NOT', + 'EXISTS', + 'OR', + 'IS', + 'UNION', + 'INTERSECT', + 'EXCEPT', + 'JOIN', + 'CROSS', + 'INNER', + 'OUTER', + 'FULL', + 'LEFT', + 'RIGHT' + ], + builtinFunctions: [ + 'AVG', + 'COUNT', + 'FIRST_VALUE', + 'LAG', + 'LAST_VALUE', + 'LEAD', + 'MAX', + 'MIN', + 'NTH_VALUE', + 'NTILE', + 'PERCENT_RANK', + 'RANK', + 'ROW_NUMBER', + 'SUM', + 'STDDEV', + 'STDDEV_POP', + 'STDDEV_SAMP', + 'VAR_POP', + 'VAR_SAMP', + 'VARIANCE' + ], + builtinVariables: [ + // Not supported + ], + typeKeywords: [ + 'BOOLEAN', + 'TINYINT', + 'SMALLINT', + 'INT', + 'INTEGER', + 'BIGINT', + 'FLOAT', + 'DOUBLE', + 'DECIMAL', + 'NUMERIC', + 'VARCHAR', + 'CHAR', + 'TEXT', + 'DATE', + 'TIME', + 'TIMESTAMP', + 'BINARY', + 'VARBINARY' + ], + scopeKeywords: ['CASE', 'END', 'WHEN', 'THEN', 'ELSE'], + pseudoColumns: [ + // Not supported + ], + tokenizer: { + root: [ + { include: '@comments' }, + { include: '@whitespace' }, + { include: '@pseudoColumns' }, + { include: '@customParams' }, + { include: '@numbers' }, + { include: '@strings' }, + { include: '@complexIdentifiers' }, + { include: '@scopes' }, + [/[:;,.]/, TokenClassConsts.DELIMITER], + [/[\(\)\[\]\{\}]/, '@brackets'], + [ + /[\w@]+/, + { + cases: { + '@scopeKeywords': TokenClassConsts.KEYWORD_SCOPE, + '@operators': TokenClassConsts.OPERATOR_KEYWORD, + '@typeKeywords': TokenClassConsts.TYPE, + '@builtinVariables': TokenClassConsts.VARIABLE, + '@builtinFunctions': TokenClassConsts.PREDEFINED, + '@keywords': TokenClassConsts.KEYWORD, + '@default': TokenClassConsts.IDENTIFIER + } + } + ], + [/[<>=!%&+\-*/|~^]/, TokenClassConsts.OPERATOR_SYMBOL] + ], + whitespace: [[/[\s\t\r\n]+/, TokenClassConsts.WHITE]], + comments: [ + [/--+.*/, TokenClassConsts.COMMENT], + [/\/\*/, { token: TokenClassConsts.COMMENT_QUOTE, next: '@comment' }] + ], + comment: [ + [/[^*/]+/, TokenClassConsts.COMMENT], + [/\*\//, { token: TokenClassConsts.COMMENT_QUOTE, next: '@pop' }], + [/./, TokenClassConsts.COMMENT] + ], + pseudoColumns: [ + [ + /[$][A-Za-z_][\w@#$]*/, + { + cases: { + '@pseudoColumns': TokenClassConsts.PREDEFINED, + '@default': TokenClassConsts.IDENTIFIER + } + } + ] + ], + customParams: [ + [/\${[A-Za-z0-9._-]*}/, TokenClassConsts.VARIABLE], + [/\@\@{[A-Za-z0-9._-]*}/, TokenClassConsts.VARIABLE] + ], + numbers: [ + [/[$][+-]*\d*(\.\d*)?/, TokenClassConsts.NUMBER], + [/((\d+(\.\d*)?)|(\.\d+))([eE][\-+]?\d+)?/, TokenClassConsts.NUMBER] + ], + strings: [[/'/, { token: TokenClassConsts.STRING, next: '@string' }]], + string: [ + [/[^']+/, TokenClassConsts.STRING_ESCAPE], + [/''/, TokenClassConsts.STRING], + [/'/, { token: TokenClassConsts.STRING, next: '@pop' }] + ], + complexIdentifiers: [ + [/`/, { token: TokenClassConsts.IDENTIFIER_QUOTE, next: '@quotedIdentifier' }], + [/"/, { token: TokenClassConsts.IDENTIFIER_QUOTE, next: '@doubleQuotedIdentifier' }] + ], + quotedIdentifier: [ + [/[^`]+/, TokenClassConsts.IDENTIFIER_QUOTE], + [/``/, TokenClassConsts.IDENTIFIER_QUOTE], + [/`/, { token: TokenClassConsts.IDENTIFIER_QUOTE, next: '@pop' }] + ], + doubleQuotedIdentifier: [ + [/[^"]+/, TokenClassConsts.IDENTIFIER_QUOTE], + [/""/, TokenClassConsts.IDENTIFIER_QUOTE], + [/"/, { token: TokenClassConsts.IDENTIFIER_QUOTE, next: '@pop' }] + ], + scopes: [ + // Not supported + ] + } +}; diff --git a/src/languages/generic/generic.worker.ts b/src/languages/generic/generic.worker.ts new file mode 100644 index 00000000..e5b8289c --- /dev/null +++ b/src/languages/generic/generic.worker.ts @@ -0,0 +1,11 @@ +import { worker } from '../../fillers/monaco-editor-core'; +import * as EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js'; +import { ICreateData } from '../../baseSQLWorker'; +import { GenericSQLWorker } from './genericWorker'; + +self.onmessage = () => { + // ignore the first message + EditorWorker.initialize((ctx: worker.IWorkerContext, createData: ICreateData) => { + return new GenericSQLWorker(ctx, createData); + }); +}; diff --git a/src/languages/generic/genericWorker.ts b/src/languages/generic/genericWorker.ts new file mode 100644 index 00000000..776f95e6 --- /dev/null +++ b/src/languages/generic/genericWorker.ts @@ -0,0 +1,17 @@ +import { worker } from '../../fillers/monaco-editor-core'; +import { GenericSQL } from 'dt-sql-parser/dist/parser/generic'; +import { BaseSQLWorker, ICreateData } from '../../baseSQLWorker'; + +export class GenericSQLWorker extends BaseSQLWorker { + protected _ctx: worker.IWorkerContext; + protected parser: GenericSQL; + constructor(ctx: worker.IWorkerContext, createData: ICreateData) { + super(ctx, createData); + this._ctx = ctx; + this.parser = new GenericSQL(); + } +} + +export function create(ctx: worker.IWorkerContext, createData: ICreateData): GenericSQLWorker { + return new GenericSQLWorker(ctx, createData); +} diff --git a/src/setupLanguageFeatures.ts b/src/setupLanguageFeatures.ts index 6e965958..3ef5ef18 100644 --- a/src/setupLanguageFeatures.ts +++ b/src/setupLanguageFeatures.ts @@ -109,6 +109,8 @@ function getDefaultSnippets(languageId: LanguageIdEnum) { return snippets.sparkSnippets; case LanguageIdEnum.TRINO: return snippets.trinoSnippets; + case LanguageIdEnum.GENERIC: + return snippets.genericSnippets; default: return []; } diff --git a/src/snippets.ts b/src/snippets.ts index 997e8f28..4174ed3f 100644 --- a/src/snippets.ts +++ b/src/snippets.ts @@ -5,3 +5,4 @@ export { pgsqlSnippets } from './languages/pgsql/pgsql.snippet'; export { sparkSnippets } from './languages/spark/spark.snippet'; export { mysqlSnippets } from './languages/mysql/mysql.snippet'; export { impalaSnippets } from './languages/impala/impala.snippet'; +export { genericSnippets } from './languages/generic/generic.snippet'; diff --git a/website/src/consts/index.ts b/website/src/consts/index.ts index 1cb3fe23..02d38d6e 100644 --- a/website/src/consts/index.ts +++ b/website/src/consts/index.ts @@ -38,7 +38,8 @@ export const SQL_LANGUAGES = [ 'MySQL', 'PGSQL', 'TrinoSQL', - 'ImpalaSQL' + 'ImpalaSQL', + 'GenericSQL' ]; export const defaultLanguage = SQL_LANGUAGES[0]; diff --git a/website/src/languages/index.ts b/website/src/languages/index.ts index fc52add3..685edca9 100644 --- a/website/src/languages/index.ts +++ b/website/src/languages/index.ts @@ -137,3 +137,15 @@ setupLanguageFeatures(LanguageIdEnum.IMPALA, { hover: true, preprocessCode }); + +setupLanguageFeatures(LanguageIdEnum.GENERIC, { + completionItems: { + enable: true, + completionService + }, + diagnostics: false, + references: true, + definitions: true, + hover: true, + preprocessCode +}); diff --git a/website/src/languages/languageWorker.ts b/website/src/languages/languageWorker.ts index 53bb10bf..688550bc 100644 --- a/website/src/languages/languageWorker.ts +++ b/website/src/languages/languageWorker.ts @@ -9,6 +9,7 @@ import PGSQLWorker from 'monaco-sql-languages/esm/languages/pgsql/pgsql.worker?w import MySQLWorker from 'monaco-sql-languages/esm/languages/mysql/mysql.worker?worker'; import TrinoSQLWorker from 'monaco-sql-languages/esm/languages/trino/trino.worker?worker'; import ImpalaSQLWorker from 'monaco-sql-languages/esm/languages/impala/impala.worker?worker'; +import GenericSQLWorker from 'monaco-sql-languages/esm/languages/generic/generic.worker?worker'; (globalThis as any).MonacoEnvironment = { getWorker(_: any, label: string) { @@ -33,6 +34,9 @@ import ImpalaSQLWorker from 'monaco-sql-languages/esm/languages/impala/impala.wo if (label === LanguageIdEnum.IMPALA) { return new ImpalaSQLWorker(); } + if (label === LanguageIdEnum.GENERIC) { + return new GenericSQLWorker(); + } return new EditorWorker(); } };