dt-sql-parser 4.1.0-beta.6 → 4.1.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/lib/flink/FlinkSqlParser.d.ts +2 -6
- package/dist/lib/flink/FlinkSqlParser.js +1232 -1289
- package/dist/parser/common/basicSQL.d.ts +31 -1
- package/dist/parser/common/basicSQL.js +67 -22
- package/dist/parser/common/parseErrorListener.d.ts +5 -3
- package/dist/parser/common/parseErrorListener.js +6 -2
- package/dist/parser/common/semanticContextCollector.d.ts +51 -0
- package/dist/parser/common/semanticContextCollector.js +202 -0
- package/dist/parser/common/types.d.ts +25 -0
- package/dist/parser/common/types.js +7 -0
- package/dist/parser/flink/flinkErrorListener.d.ts +1 -4
- package/dist/parser/flink/flinkErrorListener.js +14 -5
- package/dist/parser/flink/flinkSemanticContextCollector.d.ts +9 -0
- package/dist/parser/flink/flinkSemanticContextCollector.js +14 -0
- package/dist/parser/flink/index.d.ts +3 -1
- package/dist/parser/flink/index.js +7 -2
- package/dist/parser/hive/hiveErrorListener.d.ts +1 -4
- package/dist/parser/hive/hiveErrorListener.js +14 -5
- package/dist/parser/hive/hiveSemanticContextCollector.d.ts +9 -0
- package/dist/parser/hive/hiveSemanticContextCollector.js +14 -0
- package/dist/parser/hive/index.d.ts +3 -1
- package/dist/parser/hive/index.js +7 -2
- package/dist/parser/impala/ImpalaErrorListener.d.ts +1 -4
- package/dist/parser/impala/ImpalaErrorListener.js +14 -5
- package/dist/parser/impala/impalaSemanticContextCollector.d.ts +9 -0
- package/dist/parser/impala/impalaSemanticContextCollector.js +14 -0
- package/dist/parser/impala/index.d.ts +3 -1
- package/dist/parser/impala/index.js +7 -2
- package/dist/parser/mysql/index.d.ts +3 -1
- package/dist/parser/mysql/index.js +7 -2
- package/dist/parser/mysql/mysqlErrorListener.d.ts +1 -4
- package/dist/parser/mysql/mysqlErrorListener.js +14 -5
- package/dist/parser/mysql/mysqlSemanticContextCollector.d.ts +9 -0
- package/dist/parser/mysql/mysqlSemanticContextCollector.js +14 -0
- package/dist/parser/postgresql/index.d.ts +3 -1
- package/dist/parser/postgresql/index.js +7 -2
- package/dist/parser/postgresql/postgreErrorListener.d.ts +1 -4
- package/dist/parser/postgresql/postgreErrorListener.js +14 -5
- package/dist/parser/postgresql/postgreSemanticContextCollector.d.ts +9 -0
- package/dist/parser/postgresql/postgreSemanticContextCollector.js +14 -0
- package/dist/parser/spark/index.d.ts +3 -1
- package/dist/parser/spark/index.js +7 -2
- package/dist/parser/spark/sparkErrorListener.d.ts +1 -4
- package/dist/parser/spark/sparkErrorListener.js +14 -5
- package/dist/parser/spark/sparkSemanticContextCollector.d.ts +9 -0
- package/dist/parser/spark/sparkSemanticContextCollector.js +14 -0
- package/dist/parser/trino/index.d.ts +3 -1
- package/dist/parser/trino/index.js +7 -2
- package/dist/parser/trino/trinoErrorListener.d.ts +1 -4
- package/dist/parser/trino/trinoErrorListener.js +14 -5
- package/dist/parser/trino/trinoSemanticContextCollector.d.ts +9 -0
- package/dist/parser/trino/trinoSemanticContextCollector.js +14 -0
- package/package.json +1 -1
|
@@ -2,11 +2,12 @@ import { Lexer, Token, CommonTokenStream, CharStream, ParserRuleContext, ParseTr
|
|
|
2
2
|
import { CandidatesCollection } from 'antlr4-c3';
|
|
3
3
|
import { SQLParserBase } from '../../lib/SQLParserBase';
|
|
4
4
|
import { TextSlice } from './textAndWord';
|
|
5
|
-
import { CaretPosition, LOCALE_TYPE, Suggestions } from './types';
|
|
5
|
+
import { CaretPosition, LOCALE_TYPE, SemanticCollectOptions, Suggestions } from './types';
|
|
6
6
|
import { ParseError, ErrorListener } from './parseErrorListener';
|
|
7
7
|
import type { SplitListener } from './splitListener';
|
|
8
8
|
import type { EntityCollector } from './entityCollector';
|
|
9
9
|
import { EntityContext } from './entityCollector';
|
|
10
|
+
import SemanticContextCollector from './semanticContextCollector';
|
|
10
11
|
/**
|
|
11
12
|
* Basic SQL class, every sql needs extends it.
|
|
12
13
|
*/
|
|
@@ -56,6 +57,10 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
|
|
|
56
57
|
*/
|
|
57
58
|
protected abstract createEntityCollector(input: string, caretTokenIndex?: number): EntityCollector;
|
|
58
59
|
locale: LOCALE_TYPE;
|
|
60
|
+
/**
|
|
61
|
+
* Get a new semanticContextCollector instance.
|
|
62
|
+
*/
|
|
63
|
+
protected abstract createSemanticContextCollector(input: string, caretPosition: CaretPosition, allTokens: Token[], options?: SemanticCollectOptions): SemanticContextCollector;
|
|
59
64
|
/**
|
|
60
65
|
* Create an antlr4 lexer from input.
|
|
61
66
|
* @param input string
|
|
@@ -94,6 +99,10 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
|
|
|
94
99
|
* @returns syntax errors
|
|
95
100
|
*/
|
|
96
101
|
validate(input: string): ParseError[];
|
|
102
|
+
/**
|
|
103
|
+
* Get the input string that has been parsed.
|
|
104
|
+
*/
|
|
105
|
+
getParsedInput(): string;
|
|
97
106
|
/**
|
|
98
107
|
* Get all Tokens of input string,'<EOF>' is not included.
|
|
99
108
|
* @param input source string
|
|
@@ -111,6 +120,19 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
|
|
|
111
120
|
* @param input source string
|
|
112
121
|
*/
|
|
113
122
|
splitSQLByStatement(input: string): TextSlice[] | null;
|
|
123
|
+
/**
|
|
124
|
+
* Get a minimum boundary parser near tokenIndex.
|
|
125
|
+
* @param input source string.
|
|
126
|
+
* @param tokenIndex start from which index to minimize the boundary.
|
|
127
|
+
* @param originParseTree the parse tree need to be minimized, default value is the result of parsing `input`.
|
|
128
|
+
* @returns minimum parser info
|
|
129
|
+
*/
|
|
130
|
+
getMinimumParserInfo(input: string, tokenIndex: number, originParseTree?: ParserRuleContext | null): {
|
|
131
|
+
parser: P;
|
|
132
|
+
parseTree: ParserRuleContext;
|
|
133
|
+
tokenIndexOffset: number;
|
|
134
|
+
newTokenIndex: number;
|
|
135
|
+
} | null;
|
|
114
136
|
/**
|
|
115
137
|
* Get suggestions of syntax and token at caretPosition
|
|
116
138
|
* @param input source string
|
|
@@ -119,4 +141,12 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
|
|
|
119
141
|
*/
|
|
120
142
|
getSuggestionAtCaretPosition(input: string, caretPosition: CaretPosition): Suggestions | null;
|
|
121
143
|
getAllEntities(input: string, caretPosition?: CaretPosition): EntityContext[] | null;
|
|
144
|
+
/**
|
|
145
|
+
* Get semantic context infos
|
|
146
|
+
* @param input source string
|
|
147
|
+
* @param caretPosition caret position, such as cursor position
|
|
148
|
+
* @param options semantic context options
|
|
149
|
+
* @returns analyzed semantic context
|
|
150
|
+
*/
|
|
151
|
+
getSemanticContextAtCaretPosition(input: string, caretPosition: CaretPosition, options?: SemanticCollectOptions): import("./types").SemanticContext;
|
|
122
152
|
}
|
|
@@ -109,6 +109,12 @@ export class BasicSQL {
|
|
|
109
109
|
this.parseWithCache(input);
|
|
110
110
|
return this._parseErrors;
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Get the input string that has been parsed.
|
|
114
|
+
*/
|
|
115
|
+
getParsedInput() {
|
|
116
|
+
return this._parsedInput;
|
|
117
|
+
}
|
|
112
118
|
/**
|
|
113
119
|
* Get all Tokens of input string,'<EOF>' is not included.
|
|
114
120
|
* @param input source string
|
|
@@ -149,31 +155,31 @@ export class BasicSQL {
|
|
|
149
155
|
return res;
|
|
150
156
|
}
|
|
151
157
|
/**
|
|
152
|
-
* Get
|
|
153
|
-
* @param input source string
|
|
154
|
-
* @param
|
|
155
|
-
* @
|
|
158
|
+
* Get a minimum boundary parser near tokenIndex.
|
|
159
|
+
* @param input source string.
|
|
160
|
+
* @param tokenIndex start from which index to minimize the boundary.
|
|
161
|
+
* @param originParseTree the parse tree need to be minimized, default value is the result of parsing `input`.
|
|
162
|
+
* @returns minimum parser info
|
|
156
163
|
*/
|
|
157
|
-
|
|
164
|
+
getMinimumParserInfo(input, tokenIndex, originParseTree) {
|
|
158
165
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const allTokens = this.getAllTokens(input);
|
|
165
|
-
let caretTokenIndex = findCaretTokenIndex(caretPosition, allTokens);
|
|
166
|
-
let c3Context = this._parseTree;
|
|
167
|
-
let tokenIndexOffset = 0;
|
|
168
|
-
if (!caretTokenIndex && caretTokenIndex !== 0)
|
|
166
|
+
if (arguments.length <= 2) {
|
|
167
|
+
this.parseWithCache(input);
|
|
168
|
+
originParseTree = this._parseTree;
|
|
169
|
+
}
|
|
170
|
+
if (!originParseTree || !(input === null || input === void 0 ? void 0 : input.length))
|
|
169
171
|
return null;
|
|
172
|
+
const splitListener = this.splitListener;
|
|
170
173
|
/**
|
|
171
174
|
* Split sql by statement.
|
|
172
175
|
* Try to collect candidates in as small a range as possible.
|
|
173
176
|
*/
|
|
174
|
-
this.listen(splitListener,
|
|
177
|
+
this.listen(splitListener, originParseTree);
|
|
175
178
|
const statementCount = (_a = splitListener.statementsContext) === null || _a === void 0 ? void 0 : _a.length;
|
|
176
179
|
const statementsContext = splitListener.statementsContext;
|
|
180
|
+
let tokenIndexOffset = 0;
|
|
181
|
+
let sqlParserIns = this._parser;
|
|
182
|
+
let parseTree = originParseTree;
|
|
177
183
|
// If there are multiple statements.
|
|
178
184
|
if (statementCount > 1) {
|
|
179
185
|
/**
|
|
@@ -194,12 +200,12 @@ export class BasicSQL {
|
|
|
194
200
|
*/
|
|
195
201
|
const isPrevCtxValid = index === 0 || !((_b = statementsContext[index - 1]) === null || _b === void 0 ? void 0 : _b.exception);
|
|
196
202
|
const isNextCtxValid = index === statementCount - 1 || !((_c = statementsContext[index + 1]) === null || _c === void 0 ? void 0 : _c.exception);
|
|
197
|
-
if (ctx.stop && ctx.stop.tokenIndex <
|
|
203
|
+
if (ctx.stop && ctx.stop.tokenIndex < tokenIndex && isPrevCtxValid) {
|
|
198
204
|
startStatement = ctx;
|
|
199
205
|
}
|
|
200
206
|
if (ctx.start &&
|
|
201
207
|
!stopStatement &&
|
|
202
|
-
ctx.start.tokenIndex >
|
|
208
|
+
ctx.start.tokenIndex > tokenIndex &&
|
|
203
209
|
isNextCtxValid) {
|
|
204
210
|
stopStatement = ctx;
|
|
205
211
|
break;
|
|
@@ -213,7 +219,7 @@ export class BasicSQL {
|
|
|
213
219
|
* compared to the tokenIndex in the whole input
|
|
214
220
|
*/
|
|
215
221
|
tokenIndexOffset = (_j = (_h = startStatement === null || startStatement === void 0 ? void 0 : startStatement.start) === null || _h === void 0 ? void 0 : _h.tokenIndex) !== null && _j !== void 0 ? _j : 0;
|
|
216
|
-
|
|
222
|
+
tokenIndex = tokenIndex - tokenIndexOffset;
|
|
217
223
|
/**
|
|
218
224
|
* Reparse the input fragment,
|
|
219
225
|
* and c3 will collect candidates in the newly generated parseTree.
|
|
@@ -229,12 +235,37 @@ export class BasicSQL {
|
|
|
229
235
|
parser.buildParseTrees = true;
|
|
230
236
|
parser.errorHandler = new ErrorStrategy();
|
|
231
237
|
sqlParserIns = parser;
|
|
232
|
-
|
|
238
|
+
parseTree = parser.program();
|
|
233
239
|
}
|
|
240
|
+
return {
|
|
241
|
+
parser: sqlParserIns,
|
|
242
|
+
parseTree,
|
|
243
|
+
tokenIndexOffset,
|
|
244
|
+
newTokenIndex: tokenIndex,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get suggestions of syntax and token at caretPosition
|
|
249
|
+
* @param input source string
|
|
250
|
+
* @param caretPosition caret position, such as cursor position
|
|
251
|
+
* @returns suggestion
|
|
252
|
+
*/
|
|
253
|
+
getSuggestionAtCaretPosition(input, caretPosition) {
|
|
254
|
+
this.parseWithCache(input);
|
|
255
|
+
if (!this._parseTree)
|
|
256
|
+
return null;
|
|
257
|
+
const allTokens = this.getAllTokens(input);
|
|
258
|
+
let caretTokenIndex = findCaretTokenIndex(caretPosition, allTokens);
|
|
259
|
+
if (!caretTokenIndex && caretTokenIndex !== 0)
|
|
260
|
+
return null;
|
|
261
|
+
const minimumParser = this.getMinimumParserInfo(input, caretTokenIndex);
|
|
262
|
+
if (!minimumParser)
|
|
263
|
+
return null;
|
|
264
|
+
const { parser: sqlParserIns, tokenIndexOffset, newTokenIndex, parseTree: c3Context, } = minimumParser;
|
|
234
265
|
const core = new CodeCompletionCore(sqlParserIns);
|
|
235
266
|
core.preferredRules = this.preferredRules;
|
|
236
|
-
const candidates = core.collectCandidates(
|
|
237
|
-
const originalSuggestions = this.processCandidates(candidates, allTokens,
|
|
267
|
+
const candidates = core.collectCandidates(newTokenIndex, c3Context);
|
|
268
|
+
const originalSuggestions = this.processCandidates(candidates, allTokens, newTokenIndex, tokenIndexOffset);
|
|
238
269
|
const syntaxSuggestions = originalSuggestions.syntax.map((syntaxCtx) => {
|
|
239
270
|
const wordRanges = syntaxCtx.wordRanges.map((token) => {
|
|
240
271
|
return tokenToWord(token, this._parsedInput);
|
|
@@ -269,4 +300,18 @@ export class BasicSQL {
|
|
|
269
300
|
// parser.entityCollecting = false;
|
|
270
301
|
return collectListener.getEntities();
|
|
271
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Get semantic context infos
|
|
305
|
+
* @param input source string
|
|
306
|
+
* @param caretPosition caret position, such as cursor position
|
|
307
|
+
* @param options semantic context options
|
|
308
|
+
* @returns analyzed semantic context
|
|
309
|
+
*/
|
|
310
|
+
getSemanticContextAtCaretPosition(input, caretPosition, options) {
|
|
311
|
+
const allTokens = this.getAllTokens(input);
|
|
312
|
+
const parseTree = this.parseWithCache(input);
|
|
313
|
+
const statementContextListener = this.createSemanticContextCollector(input, caretPosition, allTokens, options);
|
|
314
|
+
this.listen(statementContextListener, parseTree);
|
|
315
|
+
return statementContextListener.semanticContext;
|
|
316
|
+
}
|
|
272
317
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Token, Recognizer, ANTLRErrorListener, RecognitionException, ATNSimulator, Parser } from 'antlr4ng';
|
|
2
|
-
import {
|
|
2
|
+
import { BasicSQL } from './basicSQL';
|
|
3
3
|
/**
|
|
4
4
|
* Converted from {@link SyntaxError}.
|
|
5
5
|
*/
|
|
@@ -32,8 +32,10 @@ export interface SyntaxError {
|
|
|
32
32
|
export type ErrorListener = (parseError: ParseError, originalError: SyntaxError) => void;
|
|
33
33
|
export declare abstract class ParseErrorListener implements ANTLRErrorListener {
|
|
34
34
|
private _errorListener;
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
protected preferredRules: Set<number>;
|
|
36
|
+
protected get locale(): import("./types").LOCALE_TYPE;
|
|
37
|
+
protected parserContext: BasicSQL;
|
|
38
|
+
constructor(errorListener: ErrorListener, parserContext: BasicSQL, preferredRules: Set<number>);
|
|
37
39
|
reportAmbiguity(): void;
|
|
38
40
|
reportAttemptingFullContext(): void;
|
|
39
41
|
reportContextSensitivity(): void;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Token, LexerNoViableAltException, InputMismatchException, NoViableAltException, } from 'antlr4ng';
|
|
2
2
|
import { transform } from './transform';
|
|
3
3
|
export class ParseErrorListener {
|
|
4
|
-
|
|
5
|
-
this.locale
|
|
4
|
+
get locale() {
|
|
5
|
+
return this.parserContext.locale;
|
|
6
|
+
}
|
|
7
|
+
constructor(errorListener, parserContext, preferredRules) {
|
|
8
|
+
this.parserContext = parserContext;
|
|
9
|
+
this.preferredRules = preferredRules;
|
|
6
10
|
this._errorListener = errorListener;
|
|
7
11
|
}
|
|
8
12
|
reportAmbiguity() { }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ErrorNode, ParserRuleContext, TerminalNode, Token } from 'antlr4ng';
|
|
2
|
+
import { CaretPosition, SemanticCollectOptions, SemanticContext } from '../common/types';
|
|
3
|
+
export declare const SQL_SPLIT_SYMBOL_TEXT = ";";
|
|
4
|
+
declare abstract class SemanticContextCollector {
|
|
5
|
+
constructor(_input: string, caretPosition: CaretPosition, allTokens: Token[], options?: SemanticCollectOptions);
|
|
6
|
+
readonly options: SemanticCollectOptions;
|
|
7
|
+
private _tokenIndex;
|
|
8
|
+
private _allTokens;
|
|
9
|
+
/**
|
|
10
|
+
* If current caret position is in a beginning of statement semantics, it needs to follow some cases:
|
|
11
|
+
* @case1 there is no statement node with an error before the current statement in the parse tree;
|
|
12
|
+
*
|
|
13
|
+
* @case2 if it is an uncomplete keyword, it will be parsed as an `ErrorNode`
|
|
14
|
+
* and need be a direct child node of `program`;
|
|
15
|
+
*
|
|
16
|
+
* @case3 if it is a complete keyword, the parsed TerminalNode or ErrorNode should be
|
|
17
|
+
* the first leaf node of current statement rule;
|
|
18
|
+
*
|
|
19
|
+
* @case4 if it is whiteSpace in caret position, we can't visit it in antlr4 listener,
|
|
20
|
+
* so we find the first unhidden token before the whiteSpace token, and the unhidden token
|
|
21
|
+
* should be the last leaf node of statement its belongs to;
|
|
22
|
+
*
|
|
23
|
+
* @case5 if the previous token is split symbol like `;`, ignore case1 and forcefully judged as beginning of statement.
|
|
24
|
+
*/
|
|
25
|
+
private _isStatementBeginning;
|
|
26
|
+
/**
|
|
27
|
+
* Prev tokenIndex that not white space before current tokenIndex or caret position
|
|
28
|
+
*/
|
|
29
|
+
private _prevTokenIndex;
|
|
30
|
+
get semanticContext(): SemanticContext;
|
|
31
|
+
abstract getWhiteSpaceRuleType(): number;
|
|
32
|
+
abstract getStatementRuleType(): number;
|
|
33
|
+
private prevStatementHasError;
|
|
34
|
+
/**
|
|
35
|
+
* Most root rule is `program`.
|
|
36
|
+
*/
|
|
37
|
+
private isRootRule;
|
|
38
|
+
/**
|
|
39
|
+
* link to @case4
|
|
40
|
+
* It should be called in each language's own `enterStatement`.
|
|
41
|
+
*/
|
|
42
|
+
protected visitStatement(ctx: ParserRuleContext): void;
|
|
43
|
+
/**
|
|
44
|
+
* Uncomplete keyword will be error node
|
|
45
|
+
*/
|
|
46
|
+
visitErrorNode(node: ErrorNode): void;
|
|
47
|
+
visitTerminal(node: TerminalNode): void;
|
|
48
|
+
enterEveryRule(_node: ParserRuleContext): void;
|
|
49
|
+
exitEveryRule(_node: ParserRuleContext): void;
|
|
50
|
+
}
|
|
51
|
+
export default SemanticContextCollector;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { ErrorNode, ParserRuleContext, Token } from 'antlr4ng';
|
|
2
|
+
import { findCaretTokenIndex } from '../common/findCaretTokenIndex';
|
|
3
|
+
import { SqlSplitStrategy, } from '../common/types';
|
|
4
|
+
export const SQL_SPLIT_SYMBOL_TEXT = ';';
|
|
5
|
+
class SemanticContextCollector {
|
|
6
|
+
constructor(_input, caretPosition, allTokens, options) {
|
|
7
|
+
this.options = {
|
|
8
|
+
sqlSplitStrategy: SqlSplitStrategy.STRICT,
|
|
9
|
+
};
|
|
10
|
+
this._allTokens = [];
|
|
11
|
+
/**
|
|
12
|
+
* If current caret position is in a beginning of statement semantics, it needs to follow some cases:
|
|
13
|
+
* @case1 there is no statement node with an error before the current statement in the parse tree;
|
|
14
|
+
*
|
|
15
|
+
* @case2 if it is an uncomplete keyword, it will be parsed as an `ErrorNode`
|
|
16
|
+
* and need be a direct child node of `program`;
|
|
17
|
+
*
|
|
18
|
+
* @case3 if it is a complete keyword, the parsed TerminalNode or ErrorNode should be
|
|
19
|
+
* the first leaf node of current statement rule;
|
|
20
|
+
*
|
|
21
|
+
* @case4 if it is whiteSpace in caret position, we can't visit it in antlr4 listener,
|
|
22
|
+
* so we find the first unhidden token before the whiteSpace token, and the unhidden token
|
|
23
|
+
* should be the last leaf node of statement its belongs to;
|
|
24
|
+
*
|
|
25
|
+
* @case5 if the previous token is split symbol like `;`, ignore case1 and forcefully judged as beginning of statement.
|
|
26
|
+
*/
|
|
27
|
+
this._isStatementBeginning = false;
|
|
28
|
+
// If caretPosition token is whiteSpace, tokenIndex may be undefined.
|
|
29
|
+
const tokenIndex = findCaretTokenIndex(caretPosition, allTokens);
|
|
30
|
+
if (tokenIndex !== undefined) {
|
|
31
|
+
this._tokenIndex = tokenIndex;
|
|
32
|
+
}
|
|
33
|
+
this._allTokens = allTokens;
|
|
34
|
+
this.options = Object.assign(Object.assign({}, this.options), options);
|
|
35
|
+
if (allTokens === null || allTokens === void 0 ? void 0 : allTokens.length) {
|
|
36
|
+
let i = tokenIndex ? tokenIndex - 1 : allTokens.length - 1;
|
|
37
|
+
/**
|
|
38
|
+
* Link to @case4 and @case5
|
|
39
|
+
* Find the previous unhidden token.
|
|
40
|
+
* If can't find tokenIndex or current token is whiteSpace at caretPosition,
|
|
41
|
+
* prevTokenIndex is useful to help us determine if it is beginning of statement.
|
|
42
|
+
*/
|
|
43
|
+
while (i >= 0) {
|
|
44
|
+
if (allTokens[i].channel !== Token.HIDDEN_CHANNEL &&
|
|
45
|
+
(allTokens[i].line < caretPosition.lineNumber ||
|
|
46
|
+
(allTokens[i].line === caretPosition.lineNumber &&
|
|
47
|
+
allTokens[i].column < caretPosition.column))) {
|
|
48
|
+
this._prevTokenIndex = allTokens[i].tokenIndex;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
i--;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* We can directly conclude beginning of statement semantics when current token is
|
|
55
|
+
* the first token of tokenStream or the previous token is semicolon
|
|
56
|
+
*/
|
|
57
|
+
if (tokenIndex === 0 ||
|
|
58
|
+
i === -1 ||
|
|
59
|
+
(this._prevTokenIndex &&
|
|
60
|
+
this._allTokens[this._prevTokenIndex].text === SQL_SPLIT_SYMBOL_TEXT)) {
|
|
61
|
+
this._isStatementBeginning = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
get semanticContext() {
|
|
66
|
+
return {
|
|
67
|
+
isStatementBeginning: this._isStatementBeginning,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
prevStatementHasError(node) {
|
|
71
|
+
let parent = node.parent;
|
|
72
|
+
if (!parent)
|
|
73
|
+
return false;
|
|
74
|
+
const currentNodeIndex = parent.children.findIndex((child) => child === node);
|
|
75
|
+
if (currentNodeIndex <= 0)
|
|
76
|
+
return false;
|
|
77
|
+
for (let i = currentNodeIndex - 1; i >= 0; i--) {
|
|
78
|
+
const prevNode = parent.children[i];
|
|
79
|
+
if (prevNode instanceof ErrorNode ||
|
|
80
|
+
(prevNode instanceof ParserRuleContext && prevNode.exception !== null))
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Most root rule is `program`.
|
|
87
|
+
*/
|
|
88
|
+
isRootRule(node) {
|
|
89
|
+
return node instanceof ParserRuleContext && (node === null || node === void 0 ? void 0 : node.parent) === null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* link to @case4
|
|
93
|
+
* It should be called in each language's own `enterStatement`.
|
|
94
|
+
*/
|
|
95
|
+
visitStatement(ctx) {
|
|
96
|
+
var _a, _b, _c;
|
|
97
|
+
if (this.options.sqlSplitStrategy === SqlSplitStrategy.STRICT)
|
|
98
|
+
return;
|
|
99
|
+
const isWhiteSpaceToken = this._tokenIndex === undefined ||
|
|
100
|
+
((_a = this._allTokens[this._tokenIndex]) === null || _a === void 0 ? void 0 : _a.type) === this.getWhiteSpaceRuleType() ||
|
|
101
|
+
// PostgreSQL whiteSpace not inlcudes '\n' symbol
|
|
102
|
+
((_b = this._allTokens[this._tokenIndex]) === null || _b === void 0 ? void 0 : _b.text) === '\n';
|
|
103
|
+
const isPrevTokenEndOfStatement = this._prevTokenIndex && ((_c = ctx.stop) === null || _c === void 0 ? void 0 : _c.tokenIndex) === this._prevTokenIndex;
|
|
104
|
+
if (isWhiteSpaceToken && isPrevTokenEndOfStatement && ctx.exception === null) {
|
|
105
|
+
this._isStatementBeginning = !this.prevStatementHasError(ctx)
|
|
106
|
+
? true
|
|
107
|
+
: this._isStatementBeginning;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Uncomplete keyword will be error node
|
|
112
|
+
*/
|
|
113
|
+
visitErrorNode(node) {
|
|
114
|
+
var _a, _b, _c, _d;
|
|
115
|
+
if (node.symbol.tokenIndex !== this._tokenIndex ||
|
|
116
|
+
this._isStatementBeginning ||
|
|
117
|
+
this.options.sqlSplitStrategy === SqlSplitStrategy.STRICT)
|
|
118
|
+
return;
|
|
119
|
+
let parent = node.parent;
|
|
120
|
+
let currentNode = node;
|
|
121
|
+
/**
|
|
122
|
+
* Link to @case2
|
|
123
|
+
* The error node is a direct child node of the program node
|
|
124
|
+
*/
|
|
125
|
+
if (this.isRootRule(parent)) {
|
|
126
|
+
this._isStatementBeginning = !this.prevStatementHasError(currentNode);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Link to @case3
|
|
131
|
+
* Error node must be the first leaf node of the statement parse tree.
|
|
132
|
+
**/
|
|
133
|
+
while (parent !== null && parent.ruleIndex !== this.getStatementRuleType()) {
|
|
134
|
+
if (((_a = parent.children) === null || _a === void 0 ? void 0 : _a[0]) !== currentNode) {
|
|
135
|
+
this._isStatementBeginning = false;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
currentNode = parent;
|
|
139
|
+
parent = currentNode.parent;
|
|
140
|
+
}
|
|
141
|
+
let isStatementBeginning = true;
|
|
142
|
+
/**
|
|
143
|
+
* Link to @case1
|
|
144
|
+
* Previous statement must have no exception
|
|
145
|
+
*/
|
|
146
|
+
if ((parent === null || parent === void 0 ? void 0 : parent.ruleIndex) === this.getStatementRuleType()) {
|
|
147
|
+
const programRule = parent.parent;
|
|
148
|
+
const currentStatementRuleIndex = ((_b = programRule === null || programRule === void 0 ? void 0 : programRule.children) === null || _b === void 0 ? void 0 : _b.findIndex((node) => node === parent)) || -1;
|
|
149
|
+
if (currentStatementRuleIndex > 0) {
|
|
150
|
+
/**
|
|
151
|
+
* When you typed a keyword and doesn't match any rule, you will get a EOF error,
|
|
152
|
+
* For example, just typed 'CREATE', 'INSERT'.
|
|
153
|
+
*/
|
|
154
|
+
const isStatementEOF = ((_d = (_c = parent.exception) === null || _c === void 0 ? void 0 : _c.offendingToken) === null || _d === void 0 ? void 0 : _d.text) === '<EOF>';
|
|
155
|
+
isStatementBeginning =
|
|
156
|
+
this.prevStatementHasError(parent) && !isStatementEOF
|
|
157
|
+
? false
|
|
158
|
+
: isStatementBeginning;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
this._isStatementBeginning = isStatementBeginning;
|
|
162
|
+
}
|
|
163
|
+
visitTerminal(node) {
|
|
164
|
+
var _a, _b;
|
|
165
|
+
if (node.symbol.tokenIndex !== this._tokenIndex ||
|
|
166
|
+
this._isStatementBeginning ||
|
|
167
|
+
this.options.sqlSplitStrategy === SqlSplitStrategy.STRICT)
|
|
168
|
+
return;
|
|
169
|
+
let currentNode = node;
|
|
170
|
+
let parent = node.parent;
|
|
171
|
+
/**
|
|
172
|
+
* Link to @case3
|
|
173
|
+
* Current terminal node must be the first leaf node of the statement parse tree.
|
|
174
|
+
**/
|
|
175
|
+
while (parent !== null && parent.ruleIndex !== this.getStatementRuleType()) {
|
|
176
|
+
if (((_a = parent.children) === null || _a === void 0 ? void 0 : _a[0]) !== currentNode) {
|
|
177
|
+
this._isStatementBeginning = false;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
currentNode = parent;
|
|
181
|
+
parent = currentNode.parent;
|
|
182
|
+
}
|
|
183
|
+
let isStatementBeginning = true;
|
|
184
|
+
/**
|
|
185
|
+
* Link to @case1
|
|
186
|
+
* Previous statement must have no exception
|
|
187
|
+
*/
|
|
188
|
+
if ((parent === null || parent === void 0 ? void 0 : parent.ruleIndex) === this.getStatementRuleType()) {
|
|
189
|
+
const programRule = parent.parent;
|
|
190
|
+
const currentStatementRuleIndex = ((_b = programRule === null || programRule === void 0 ? void 0 : programRule.children) === null || _b === void 0 ? void 0 : _b.findIndex((node) => node === parent)) || -1;
|
|
191
|
+
if (currentStatementRuleIndex > 0) {
|
|
192
|
+
isStatementBeginning = this.prevStatementHasError(parent)
|
|
193
|
+
? false
|
|
194
|
+
: isStatementBeginning;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
this._isStatementBeginning = isStatementBeginning;
|
|
198
|
+
}
|
|
199
|
+
enterEveryRule(_node) { }
|
|
200
|
+
exitEveryRule(_node) { }
|
|
201
|
+
}
|
|
202
|
+
export default SemanticContextCollector;
|
|
@@ -64,3 +64,28 @@ export interface Suggestions<T = WordRange> {
|
|
|
64
64
|
readonly keywords: string[];
|
|
65
65
|
}
|
|
66
66
|
export type LOCALE_TYPE = 'zh_CN' | 'en_US';
|
|
67
|
+
export interface SemanticContext {
|
|
68
|
+
isStatementBeginning: boolean;
|
|
69
|
+
}
|
|
70
|
+
export declare enum SqlSplitStrategy {
|
|
71
|
+
/** Only end the statement with semicolon symbol */
|
|
72
|
+
STRICT = 0,
|
|
73
|
+
/** Based on parse tree to split statements */
|
|
74
|
+
LOOSE = 1
|
|
75
|
+
}
|
|
76
|
+
export interface SemanticCollectOptions {
|
|
77
|
+
/**
|
|
78
|
+
* `sqlSplitStrategy` will affects the result of `isStatementBeginning`;
|
|
79
|
+
*
|
|
80
|
+
* For example:
|
|
81
|
+
*
|
|
82
|
+
* The sql is "select id from t1 create\<cart_position\>"
|
|
83
|
+
*
|
|
84
|
+
* - `SqlSplitStrategy.STRICT`: split symbol `;` is missing after select statement so that it considerd as one statement, and `isStatementBeginning` is false
|
|
85
|
+
*
|
|
86
|
+
* - `SqlSplitStrategy.LOOSE`: in parse tree, it will parse to "select id from t1" and "create" two single statement, so `isStatementBeginning` is true
|
|
87
|
+
*
|
|
88
|
+
* @default SqlSplitStrategy.STRICT
|
|
89
|
+
*/
|
|
90
|
+
sqlSplitStrategy?: SqlSplitStrategy;
|
|
91
|
+
}
|
|
@@ -32,3 +32,10 @@ export var EntityContextType;
|
|
|
32
32
|
/** column name that will be created */
|
|
33
33
|
EntityContextType["COLUMN_CREATE"] = "columnCreate";
|
|
34
34
|
})(EntityContextType || (EntityContextType = {}));
|
|
35
|
+
export var SqlSplitStrategy;
|
|
36
|
+
(function (SqlSplitStrategy) {
|
|
37
|
+
/** Only end the statement with semicolon symbol */
|
|
38
|
+
SqlSplitStrategy[SqlSplitStrategy["STRICT"] = 0] = "STRICT";
|
|
39
|
+
/** Based on parse tree to split statements */
|
|
40
|
+
SqlSplitStrategy[SqlSplitStrategy["LOOSE"] = 1] = "LOOSE";
|
|
41
|
+
})(SqlSplitStrategy || (SqlSplitStrategy = {}));
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ParseErrorListener } from '../common/parseErrorListener';
|
|
2
2
|
import { Parser, Token } from 'antlr4ng';
|
|
3
|
-
import { LOCALE_TYPE } from '../common/types';
|
|
4
3
|
export declare class FlinkErrorListener extends ParseErrorListener {
|
|
5
|
-
private preferredRules;
|
|
6
4
|
private objectNames;
|
|
7
|
-
constructor(errorListener: ErrorListener, preferredRules: Set<number>, locale: LOCALE_TYPE);
|
|
8
5
|
getExpectedText(parser: Parser, token: Token): string;
|
|
9
6
|
}
|
|
@@ -2,8 +2,8 @@ import { CodeCompletionCore } from 'antlr4-c3';
|
|
|
2
2
|
import { ParseErrorListener } from '../common/parseErrorListener';
|
|
3
3
|
import { FlinkSqlParser } from '../../lib/flink/FlinkSqlParser';
|
|
4
4
|
export class FlinkErrorListener extends ParseErrorListener {
|
|
5
|
-
constructor(
|
|
6
|
-
super(
|
|
5
|
+
constructor() {
|
|
6
|
+
super(...arguments);
|
|
7
7
|
this.objectNames = new Map([
|
|
8
8
|
[FlinkSqlParser.RULE_catalogPath, 'catalog'],
|
|
9
9
|
[FlinkSqlParser.RULE_catalogPathCreate, 'catalog'],
|
|
@@ -18,18 +18,27 @@ export class FlinkErrorListener extends ParseErrorListener {
|
|
|
18
18
|
[FlinkSqlParser.RULE_columnName, 'column'],
|
|
19
19
|
[FlinkSqlParser.RULE_columnNameCreate, 'column'],
|
|
20
20
|
]);
|
|
21
|
-
this.preferredRules = preferredRules;
|
|
22
21
|
}
|
|
23
22
|
getExpectedText(parser, token) {
|
|
24
23
|
var _a;
|
|
25
24
|
let expectedText = '';
|
|
25
|
+
const input = this.parserContext.getParsedInput();
|
|
26
|
+
/**
|
|
27
|
+
* Get the program context.
|
|
28
|
+
* When called error listener, `this._parseTree` is still `undefined`,
|
|
29
|
+
* so we can't use cached parseTree in `getMinimumParserInfo`
|
|
30
|
+
*/
|
|
26
31
|
let currentContext = (_a = parser.context) !== null && _a !== void 0 ? _a : undefined;
|
|
27
32
|
while (currentContext === null || currentContext === void 0 ? void 0 : currentContext.parent) {
|
|
28
33
|
currentContext = currentContext.parent;
|
|
29
34
|
}
|
|
30
|
-
const
|
|
35
|
+
const parserInfo = this.parserContext.getMinimumParserInfo(input, token.tokenIndex, currentContext);
|
|
36
|
+
if (!parserInfo)
|
|
37
|
+
return '';
|
|
38
|
+
const { parser: c3Parser, newTokenIndex, parseTree: c3Context } = parserInfo;
|
|
39
|
+
const core = new CodeCompletionCore(c3Parser);
|
|
31
40
|
core.preferredRules = this.preferredRules;
|
|
32
|
-
const candidates = core.collectCandidates(
|
|
41
|
+
const candidates = core.collectCandidates(newTokenIndex, c3Context);
|
|
33
42
|
if (candidates.rules.size) {
|
|
34
43
|
const result = [];
|
|
35
44
|
// get expectedText as collect rules first
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FlinkSqlParserListener } from '../../lib';
|
|
2
|
+
import { SingleStatementContext } from '../../lib/flink/FlinkSqlParser';
|
|
3
|
+
import SemanticContextCollector from '../common/semanticContextCollector';
|
|
4
|
+
declare class FlinkSemanticContextCollector extends SemanticContextCollector implements FlinkSqlParserListener {
|
|
5
|
+
getWhiteSpaceRuleType(): number;
|
|
6
|
+
getStatementRuleType(): number;
|
|
7
|
+
enterSingleStatement(ctx: SingleStatementContext): void;
|
|
8
|
+
}
|
|
9
|
+
export { FlinkSemanticContextCollector };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FlinkSqlParser } from '../../lib/flink/FlinkSqlParser';
|
|
2
|
+
import SemanticContextCollector from '../common/semanticContextCollector';
|
|
3
|
+
class FlinkSemanticContextCollector extends SemanticContextCollector {
|
|
4
|
+
getWhiteSpaceRuleType() {
|
|
5
|
+
return FlinkSqlParser.SPACE;
|
|
6
|
+
}
|
|
7
|
+
getStatementRuleType() {
|
|
8
|
+
return FlinkSqlParser.RULE_singleStatement;
|
|
9
|
+
}
|
|
10
|
+
enterSingleStatement(ctx) {
|
|
11
|
+
this.visitStatement(ctx);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export { FlinkSemanticContextCollector };
|
|
@@ -2,12 +2,13 @@ import { CharStream, CommonTokenStream, Token } from 'antlr4ng';
|
|
|
2
2
|
import { CandidatesCollection } from 'antlr4-c3';
|
|
3
3
|
import { FlinkSqlLexer } from '../../lib/flink/FlinkSqlLexer';
|
|
4
4
|
import { FlinkSqlParser, ProgramContext } from '../../lib/flink/FlinkSqlParser';
|
|
5
|
-
import { Suggestions } from '../common/types';
|
|
5
|
+
import { CaretPosition, SemanticCollectOptions, Suggestions } from '../common/types';
|
|
6
6
|
import { BasicSQL } from '../common/basicSQL';
|
|
7
7
|
import { FlinkSqlSplitListener } from './flinkSplitListener';
|
|
8
8
|
import { FlinkEntityCollector } from './flinkEntityCollector';
|
|
9
9
|
import { ErrorListener } from '../common/parseErrorListener';
|
|
10
10
|
import { FlinkErrorListener } from './flinkErrorListener';
|
|
11
|
+
import { FlinkSemanticContextCollector } from './flinkSemanticContextCollector';
|
|
11
12
|
export { FlinkSqlSplitListener, FlinkEntityCollector };
|
|
12
13
|
export declare class FlinkSQL extends BasicSQL<FlinkSqlLexer, ProgramContext, FlinkSqlParser> {
|
|
13
14
|
protected createLexerFromCharStream(charStreams: CharStream): FlinkSqlLexer;
|
|
@@ -16,5 +17,6 @@ export declare class FlinkSQL extends BasicSQL<FlinkSqlLexer, ProgramContext, Fl
|
|
|
16
17
|
protected get splitListener(): FlinkSqlSplitListener;
|
|
17
18
|
protected createErrorListener(_errorListener: ErrorListener): FlinkErrorListener;
|
|
18
19
|
protected createEntityCollector(input: string, caretTokenIndex?: number): FlinkEntityCollector;
|
|
20
|
+
protected createSemanticContextCollector(input: string, caretPosition: CaretPosition, allTokens: Token[], options?: SemanticCollectOptions): FlinkSemanticContextCollector;
|
|
19
21
|
protected processCandidates(candidates: CandidatesCollection, allTokens: Token[], caretTokenIndex: number, tokenIndexOffset: number): Suggestions<Token>;
|
|
20
22
|
}
|