dt-sql-parser 4.1.2-beta.1 → 4.2.0
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/FlinkSqlLexer.d.ts +2 -2
- package/dist/lib/flink/FlinkSqlLexer.js +21 -20
- package/dist/lib/flink/FlinkSqlParser.d.ts +178 -168
- package/dist/lib/flink/FlinkSqlParser.js +2799 -2741
- package/dist/lib/flink/FlinkSqlParserListener.d.ts +11 -0
- package/dist/lib/flink/FlinkSqlParserVisitor.d.ts +7 -0
- package/dist/lib/hive/HiveSqlParser.d.ts +241 -221
- package/dist/lib/hive/HiveSqlParser.js +6229 -5639
- package/dist/lib/hive/HiveSqlParserListener.d.ts +22 -0
- package/dist/lib/hive/HiveSqlParserVisitor.d.ts +14 -0
- package/dist/lib/impala/ImpalaSqlLexer.d.ts +2 -2
- package/dist/lib/impala/ImpalaSqlLexer.js +5 -5
- package/dist/lib/impala/ImpalaSqlParser.d.ts +147 -109
- package/dist/lib/impala/ImpalaSqlParser.js +3448 -3234
- package/dist/lib/impala/ImpalaSqlParserListener.d.ts +44 -0
- package/dist/lib/impala/ImpalaSqlParserVisitor.d.ts +28 -0
- package/dist/lib/mysql/MySqlLexer.d.ts +2 -2
- package/dist/lib/mysql/MySqlLexer.js +43 -43
- package/dist/lib/mysql/MySqlParser.d.ts +2 -2
- package/dist/lib/mysql/MySqlParser.js +42 -42
- package/dist/lib/postgresql/PostgreSqlLexer.d.ts +14 -15
- package/dist/lib/postgresql/PostgreSqlLexer.js +2077 -2092
- package/dist/lib/postgresql/PostgreSqlParser.d.ts +152 -131
- package/dist/lib/postgresql/PostgreSqlParser.js +10052 -9925
- package/dist/lib/postgresql/PostgreSqlParserListener.d.ts +22 -0
- package/dist/lib/postgresql/PostgreSqlParserVisitor.d.ts +14 -0
- package/dist/lib/spark/SparkSqlLexer.d.ts +2 -2
- package/dist/lib/spark/SparkSqlLexer.js +6 -6
- package/dist/lib/spark/SparkSqlParser.d.ts +391 -623
- package/dist/lib/spark/SparkSqlParser.js +6749 -8109
- package/dist/lib/spark/SparkSqlParserListener.d.ts +38 -326
- package/dist/lib/spark/SparkSqlParserVisitor.d.ts +24 -207
- package/dist/lib/trino/TrinoSqlLexer.d.ts +2 -2
- package/dist/lib/trino/TrinoSqlLexer.js +5 -5
- package/dist/lib/trino/TrinoSqlListener.d.ts +44 -0
- package/dist/lib/trino/TrinoSqlParser.d.ts +182 -153
- package/dist/lib/trino/TrinoSqlParser.js +4758 -4599
- package/dist/lib/trino/TrinoSqlVisitor.d.ts +28 -0
- package/dist/parser/common/basicSQL.d.ts +43 -9
- package/dist/parser/common/basicSQL.js +147 -40
- package/dist/parser/common/semanticContextCollector.d.ts +50 -0
- package/dist/parser/common/semanticContextCollector.js +202 -0
- package/dist/parser/common/tokenUtils.d.ts +12 -0
- package/dist/parser/common/tokenUtils.js +38 -0
- package/dist/parser/common/types.d.ts +25 -0
- package/dist/parser/common/types.js +7 -0
- package/dist/parser/flink/flinkErrorListener.js +14 -2
- package/dist/parser/flink/flinkSemanticContextCollector.d.ts +9 -0
- package/dist/parser/flink/flinkSemanticContextCollector.js +14 -0
- package/dist/parser/flink/index.d.ts +4 -2
- package/dist/parser/flink/index.js +22 -14
- package/dist/parser/hive/hiveErrorListener.js +9 -3
- package/dist/parser/hive/hiveSemanticContextCollector.d.ts +9 -0
- package/dist/parser/hive/hiveSemanticContextCollector.js +14 -0
- package/dist/parser/hive/index.d.ts +4 -2
- package/dist/parser/hive/index.js +29 -14
- package/dist/parser/impala/ImpalaErrorListener.js +8 -2
- package/dist/parser/impala/impalaSemanticContextCollector.d.ts +9 -0
- package/dist/parser/impala/impalaSemanticContextCollector.js +14 -0
- package/dist/parser/impala/index.d.ts +4 -2
- package/dist/parser/impala/index.js +21 -14
- package/dist/parser/mysql/index.d.ts +4 -2
- package/dist/parser/mysql/index.js +10 -14
- package/dist/parser/mysql/mysqlErrorListener.js +6 -2
- package/dist/parser/mysql/mysqlSemanticContextCollector.d.ts +9 -0
- package/dist/parser/mysql/mysqlSemanticContextCollector.js +14 -0
- package/dist/parser/postgresql/index.d.ts +4 -2
- package/dist/parser/postgresql/index.js +23 -14
- package/dist/parser/postgresql/postgreErrorListener.js +9 -3
- package/dist/parser/postgresql/postgreSemanticContextCollector.d.ts +9 -0
- package/dist/parser/postgresql/postgreSemanticContextCollector.js +14 -0
- package/dist/parser/spark/index.d.ts +4 -2
- package/dist/parser/spark/index.js +26 -14
- package/dist/parser/spark/sparkErrorListener.js +9 -3
- package/dist/parser/spark/sparkSemanticContextCollector.d.ts +9 -0
- package/dist/parser/spark/sparkSemanticContextCollector.js +14 -0
- package/dist/parser/trino/index.d.ts +4 -2
- package/dist/parser/trino/index.js +22 -14
- package/dist/parser/trino/trinoErrorListener.js +9 -3
- package/dist/parser/trino/trinoSemanticContextCollector.d.ts +9 -0
- package/dist/parser/trino/trinoSemanticContextCollector.js +14 -0
- package/package.json +2 -1
|
@@ -114,7 +114,10 @@ import { InlineTableContext } from "./TrinoSqlParser.js";
|
|
|
114
114
|
import { SubqueryContext } from "./TrinoSqlParser.js";
|
|
115
115
|
import { SortItemContext } from "./TrinoSqlParser.js";
|
|
116
116
|
import { QuerySpecificationContext } from "./TrinoSqlParser.js";
|
|
117
|
+
import { WhereClauseContext } from "./TrinoSqlParser.js";
|
|
118
|
+
import { HavingClauseContext } from "./TrinoSqlParser.js";
|
|
117
119
|
import { GroupByContext } from "./TrinoSqlParser.js";
|
|
120
|
+
import { PartitionByContext } from "./TrinoSqlParser.js";
|
|
118
121
|
import { SingleGroupingSetContext } from "./TrinoSqlParser.js";
|
|
119
122
|
import { RollupContext } from "./TrinoSqlParser.js";
|
|
120
123
|
import { CubeContext } from "./TrinoSqlParser.js";
|
|
@@ -347,6 +350,7 @@ import { CatalogNameCreateContext } from "./TrinoSqlParser.js";
|
|
|
347
350
|
import { FunctionNameContext } from "./TrinoSqlParser.js";
|
|
348
351
|
import { FunctionNameCreateContext } from "./TrinoSqlParser.js";
|
|
349
352
|
import { ColumnRefContext } from "./TrinoSqlParser.js";
|
|
353
|
+
import { ColumnNameContext } from "./TrinoSqlParser.js";
|
|
350
354
|
import { ColumnNameCreateContext } from "./TrinoSqlParser.js";
|
|
351
355
|
import { QualifiedNameContext } from "./TrinoSqlParser.js";
|
|
352
356
|
import { QueryPeriodContext } from "./TrinoSqlParser.js";
|
|
@@ -1160,12 +1164,30 @@ export declare class TrinoSqlVisitor<Result> extends AbstractParseTreeVisitor<Re
|
|
|
1160
1164
|
* @return the visitor result
|
|
1161
1165
|
*/
|
|
1162
1166
|
visitQuerySpecification?: (ctx: QuerySpecificationContext) => Result;
|
|
1167
|
+
/**
|
|
1168
|
+
* Visit a parse tree produced by `TrinoSqlParser.whereClause`.
|
|
1169
|
+
* @param ctx the parse tree
|
|
1170
|
+
* @return the visitor result
|
|
1171
|
+
*/
|
|
1172
|
+
visitWhereClause?: (ctx: WhereClauseContext) => Result;
|
|
1173
|
+
/**
|
|
1174
|
+
* Visit a parse tree produced by `TrinoSqlParser.havingClause`.
|
|
1175
|
+
* @param ctx the parse tree
|
|
1176
|
+
* @return the visitor result
|
|
1177
|
+
*/
|
|
1178
|
+
visitHavingClause?: (ctx: HavingClauseContext) => Result;
|
|
1163
1179
|
/**
|
|
1164
1180
|
* Visit a parse tree produced by `TrinoSqlParser.groupBy`.
|
|
1165
1181
|
* @param ctx the parse tree
|
|
1166
1182
|
* @return the visitor result
|
|
1167
1183
|
*/
|
|
1168
1184
|
visitGroupBy?: (ctx: GroupByContext) => Result;
|
|
1185
|
+
/**
|
|
1186
|
+
* Visit a parse tree produced by `TrinoSqlParser.partitionBy`.
|
|
1187
|
+
* @param ctx the parse tree
|
|
1188
|
+
* @return the visitor result
|
|
1189
|
+
*/
|
|
1190
|
+
visitPartitionBy?: (ctx: PartitionByContext) => Result;
|
|
1169
1191
|
/**
|
|
1170
1192
|
* Visit a parse tree produced by the `singleGroupingSet`
|
|
1171
1193
|
* labeled alternative in `TrinoSqlParser.groupingElement`.
|
|
@@ -2705,6 +2727,12 @@ export declare class TrinoSqlVisitor<Result> extends AbstractParseTreeVisitor<Re
|
|
|
2705
2727
|
* @return the visitor result
|
|
2706
2728
|
*/
|
|
2707
2729
|
visitColumnRef?: (ctx: ColumnRefContext) => Result;
|
|
2730
|
+
/**
|
|
2731
|
+
* Visit a parse tree produced by `TrinoSqlParser.columnName`.
|
|
2732
|
+
* @param ctx the parse tree
|
|
2733
|
+
* @return the visitor result
|
|
2734
|
+
*/
|
|
2735
|
+
visitColumnName?: (ctx: ColumnNameContext) => Result;
|
|
2708
2736
|
/**
|
|
2709
2737
|
* Visit a parse tree produced by `TrinoSqlParser.columnNameCreate`.
|
|
2710
2738
|
* @param ctx the parse tree
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { Lexer, Token, CommonTokenStream, CharStream, ParserRuleContext, ParseTreeListener, ANTLRErrorListener } from 'antlr4ng';
|
|
1
|
+
import { Lexer, Token, CommonTokenStream, CharStream, ParserRuleContext, ParseTreeListener, ANTLRErrorListener, Parser } from 'antlr4ng';
|
|
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';
|
|
11
|
+
export declare const SQL_SPLIT_SYMBOL_TEXT = ";";
|
|
10
12
|
/**
|
|
11
13
|
* Basic SQL class, every sql needs extends it.
|
|
12
14
|
*/
|
|
@@ -38,11 +40,10 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
|
|
|
38
40
|
/**
|
|
39
41
|
* Convert candidates to suggestions
|
|
40
42
|
* @param candidates candidate list
|
|
41
|
-
* @param allTokens all tokens from input
|
|
43
|
+
* @param allTokens slice all tokens from input by tokenIndexOffset
|
|
42
44
|
* @param caretTokenIndex tokenIndex of caretPosition
|
|
43
|
-
* @param tokenIndexOffset offset of the tokenIndex in the candidates compared to the tokenIndex in allTokens
|
|
44
45
|
*/
|
|
45
|
-
protected abstract processCandidates(candidates: CandidatesCollection, allTokens: Token[], caretTokenIndex: number
|
|
46
|
+
protected abstract processCandidates(candidates: CandidatesCollection, allTokens: Token[], caretTokenIndex: number): Suggestions<Token>;
|
|
46
47
|
/**
|
|
47
48
|
* Get a new splitListener instance.
|
|
48
49
|
*/
|
|
@@ -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, allTokens?: Token[], 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
|
|
@@ -116,14 +121,35 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
|
|
|
116
121
|
*/
|
|
117
122
|
splitSQLByStatement(input: string): TextSlice[] | null;
|
|
118
123
|
/**
|
|
119
|
-
* Get
|
|
124
|
+
* Get the smaller range of input
|
|
125
|
+
* @param input string
|
|
126
|
+
* @param allTokens all tokens from input
|
|
127
|
+
* @param tokenIndexOffset offset of the tokenIndex in the range of input
|
|
128
|
+
* @param caretTokenIndex tokenIndex of caretPosition
|
|
129
|
+
* @returns inputSlice: string, caretTokenIndex: number
|
|
130
|
+
*/
|
|
131
|
+
private splitInputBySymbolText;
|
|
132
|
+
/**
|
|
133
|
+
* Get the minimum input string that can be parsed successfully by c3.
|
|
134
|
+
* @param input source string
|
|
135
|
+
* @param caretTokenIndex tokenIndex of caretPosition
|
|
136
|
+
* @param originParseTree origin parseTree
|
|
137
|
+
* @returns MinimumInputInfo
|
|
138
|
+
*/
|
|
139
|
+
getMinimumInputInfo(input: string, caretTokenIndex: number, originParseTree: ParserRuleContext | undefined): {
|
|
140
|
+
input: string;
|
|
141
|
+
tokenIndexOffset: number;
|
|
142
|
+
statementCount: number;
|
|
143
|
+
} | null;
|
|
144
|
+
/**
|
|
145
|
+
* Get a minimum boundary parser near caretTokenIndex.
|
|
120
146
|
* @param input source string.
|
|
121
|
-
* @param
|
|
147
|
+
* @param caretTokenIndex start from which index to minimize the boundary.
|
|
122
148
|
* @param originParseTree the parse tree need to be minimized, default value is the result of parsing `input`.
|
|
123
149
|
* @returns minimum parser info
|
|
124
150
|
*/
|
|
125
|
-
getMinimumParserInfo(input: string,
|
|
126
|
-
parser:
|
|
151
|
+
getMinimumParserInfo(input: string, caretTokenIndex: number, originParseTree: ParserRuleContext | undefined): {
|
|
152
|
+
parser: Parser;
|
|
127
153
|
parseTree: ParserRuleContext;
|
|
128
154
|
tokenIndexOffset: number;
|
|
129
155
|
newTokenIndex: number;
|
|
@@ -136,4 +162,12 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
|
|
|
136
162
|
*/
|
|
137
163
|
getSuggestionAtCaretPosition(input: string, caretPosition: CaretPosition): Suggestions | null;
|
|
138
164
|
getAllEntities(input: string, caretPosition?: CaretPosition): EntityContext[] | null;
|
|
165
|
+
/**
|
|
166
|
+
* Get semantic context infos
|
|
167
|
+
* @param input source string
|
|
168
|
+
* @param caretPosition caret position, such as cursor position
|
|
169
|
+
* @param options semantic context options
|
|
170
|
+
* @returns analyzed semantic context
|
|
171
|
+
*/
|
|
172
|
+
getSemanticContextAtCaretPosition(input: string, caretPosition: CaretPosition, options?: SemanticCollectOptions): import("./types").SemanticContext;
|
|
139
173
|
}
|
|
@@ -3,6 +3,7 @@ import { CodeCompletionCore } from 'antlr4-c3';
|
|
|
3
3
|
import { findCaretTokenIndex } from './findCaretTokenIndex';
|
|
4
4
|
import { ctxToText, tokenToWord } from './textAndWord';
|
|
5
5
|
import { ErrorStrategy } from './errorStrategy';
|
|
6
|
+
export const SQL_SPLIT_SYMBOL_TEXT = ';';
|
|
6
7
|
/**
|
|
7
8
|
* Basic SQL class, every sql needs extends it.
|
|
8
9
|
*/
|
|
@@ -155,31 +156,83 @@ export class BasicSQL {
|
|
|
155
156
|
return res;
|
|
156
157
|
}
|
|
157
158
|
/**
|
|
158
|
-
* Get
|
|
159
|
-
* @param input
|
|
160
|
-
* @param
|
|
161
|
-
* @param
|
|
162
|
-
* @
|
|
159
|
+
* Get the smaller range of input
|
|
160
|
+
* @param input string
|
|
161
|
+
* @param allTokens all tokens from input
|
|
162
|
+
* @param tokenIndexOffset offset of the tokenIndex in the range of input
|
|
163
|
+
* @param caretTokenIndex tokenIndex of caretPosition
|
|
164
|
+
* @returns inputSlice: string, caretTokenIndex: number
|
|
163
165
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
splitInputBySymbolText(input, allTokens, tokenIndexOffset, caretTokenIndex) {
|
|
167
|
+
const tokens = allTokens.slice(tokenIndexOffset);
|
|
168
|
+
/**
|
|
169
|
+
* Set startToken
|
|
170
|
+
*/
|
|
171
|
+
let startToken = null;
|
|
172
|
+
for (let tokenIndex = caretTokenIndex - tokenIndexOffset; tokenIndex >= 0; tokenIndex--) {
|
|
173
|
+
const token = tokens[tokenIndex];
|
|
174
|
+
if ((token === null || token === void 0 ? void 0 : token.text) === SQL_SPLIT_SYMBOL_TEXT) {
|
|
175
|
+
startToken = tokens[tokenIndex + 1];
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (startToken === null) {
|
|
180
|
+
startToken = tokens[0];
|
|
169
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Set stopToken
|
|
184
|
+
*/
|
|
185
|
+
let stopToken = null;
|
|
186
|
+
for (let tokenIndex = caretTokenIndex - tokenIndexOffset; tokenIndex < tokens.length; tokenIndex++) {
|
|
187
|
+
const token = tokens[tokenIndex];
|
|
188
|
+
if ((token === null || token === void 0 ? void 0 : token.text) === SQL_SPLIT_SYMBOL_TEXT) {
|
|
189
|
+
stopToken = token;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (stopToken === null) {
|
|
194
|
+
stopToken = tokens[tokens.length - 1];
|
|
195
|
+
}
|
|
196
|
+
const indexOffset = tokens[0].start;
|
|
197
|
+
let startIndex = startToken.start - indexOffset;
|
|
198
|
+
let stopIndex = stopToken.stop + 1 - indexOffset;
|
|
199
|
+
/**
|
|
200
|
+
* Save offset of the tokenIndex in the range of input
|
|
201
|
+
* compared to the tokenIndex in the whole input
|
|
202
|
+
*/
|
|
203
|
+
const _tokenIndexOffset = startToken.tokenIndex;
|
|
204
|
+
const _caretTokenIndex = caretTokenIndex - _tokenIndexOffset;
|
|
205
|
+
/**
|
|
206
|
+
* Get the smaller range of _input
|
|
207
|
+
*/
|
|
208
|
+
const _input = input.slice(startIndex, stopIndex);
|
|
209
|
+
return {
|
|
210
|
+
inputSlice: _input,
|
|
211
|
+
allTokens: allTokens.slice(_tokenIndexOffset),
|
|
212
|
+
caretTokenIndex: _caretTokenIndex,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get the minimum input string that can be parsed successfully by c3.
|
|
217
|
+
* @param input source string
|
|
218
|
+
* @param caretTokenIndex tokenIndex of caretPosition
|
|
219
|
+
* @param originParseTree origin parseTree
|
|
220
|
+
* @returns MinimumInputInfo
|
|
221
|
+
*/
|
|
222
|
+
getMinimumInputInfo(input, caretTokenIndex, originParseTree) {
|
|
223
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
170
224
|
if (!originParseTree || !(input === null || input === void 0 ? void 0 : input.length))
|
|
171
225
|
return null;
|
|
172
|
-
|
|
226
|
+
let inputSlice = input;
|
|
173
227
|
/**
|
|
174
228
|
* Split sql by statement.
|
|
175
229
|
* Try to collect candidates in as small a range as possible.
|
|
176
230
|
*/
|
|
231
|
+
const splitListener = this.splitListener;
|
|
177
232
|
this.listen(splitListener, originParseTree);
|
|
178
233
|
const statementCount = (_a = splitListener.statementsContext) === null || _a === void 0 ? void 0 : _a.length;
|
|
179
234
|
const statementsContext = splitListener.statementsContext;
|
|
180
235
|
let tokenIndexOffset = 0;
|
|
181
|
-
let sqlParserIns = this._parser;
|
|
182
|
-
let parseTree = originParseTree;
|
|
183
236
|
// If there are multiple statements.
|
|
184
237
|
if (statementCount > 1) {
|
|
185
238
|
/**
|
|
@@ -200,12 +253,12 @@ export class BasicSQL {
|
|
|
200
253
|
*/
|
|
201
254
|
const isPrevCtxValid = index === 0 || !((_b = statementsContext[index - 1]) === null || _b === void 0 ? void 0 : _b.exception);
|
|
202
255
|
const isNextCtxValid = index === statementCount - 1 || !((_c = statementsContext[index + 1]) === null || _c === void 0 ? void 0 : _c.exception);
|
|
203
|
-
if (ctx.stop && ctx.stop.tokenIndex <
|
|
256
|
+
if (ctx.stop && ctx.stop.tokenIndex < caretTokenIndex && isPrevCtxValid) {
|
|
204
257
|
startStatement = ctx;
|
|
205
258
|
}
|
|
206
259
|
if (ctx.start &&
|
|
207
260
|
!stopStatement &&
|
|
208
|
-
ctx.start.tokenIndex >
|
|
261
|
+
ctx.start.tokenIndex > caretTokenIndex &&
|
|
209
262
|
isNextCtxValid) {
|
|
210
263
|
stopStatement = ctx;
|
|
211
264
|
break;
|
|
@@ -213,35 +266,50 @@ export class BasicSQL {
|
|
|
213
266
|
}
|
|
214
267
|
// A boundary consisting of the index of the input.
|
|
215
268
|
const startIndex = (_e = (_d = startStatement === null || startStatement === void 0 ? void 0 : startStatement.start) === null || _d === void 0 ? void 0 : _d.start) !== null && _e !== void 0 ? _e : 0;
|
|
216
|
-
const stopIndex = (_g = (_f = stopStatement === null || stopStatement === void 0 ? void 0 : stopStatement.stop) === null || _f === void 0 ? void 0 : _f.stop) !== null && _g !== void 0 ? _g :
|
|
269
|
+
const stopIndex = (_g = (_f = stopStatement === null || stopStatement === void 0 ? void 0 : stopStatement.stop) === null || _f === void 0 ? void 0 : _f.stop) !== null && _g !== void 0 ? _g : inputSlice.length - 1;
|
|
217
270
|
/**
|
|
218
271
|
* Save offset of the tokenIndex in the range of input
|
|
219
272
|
* compared to the tokenIndex in the whole input
|
|
220
273
|
*/
|
|
221
274
|
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;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
275
|
+
inputSlice = inputSlice.slice(startIndex, stopIndex);
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
input: inputSlice,
|
|
279
|
+
tokenIndexOffset,
|
|
280
|
+
statementCount,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Get a minimum boundary parser near caretTokenIndex.
|
|
285
|
+
* @param input source string.
|
|
286
|
+
* @param caretTokenIndex start from which index to minimize the boundary.
|
|
287
|
+
* @param originParseTree the parse tree need to be minimized, default value is the result of parsing `input`.
|
|
288
|
+
* @returns minimum parser info
|
|
289
|
+
*/
|
|
290
|
+
getMinimumParserInfo(input, caretTokenIndex, originParseTree) {
|
|
291
|
+
if (!originParseTree || !(input === null || input === void 0 ? void 0 : input.length))
|
|
292
|
+
return null;
|
|
293
|
+
const inputInfo = this.getMinimumInputInfo(input, caretTokenIndex, originParseTree);
|
|
294
|
+
if (!inputInfo)
|
|
295
|
+
return null;
|
|
296
|
+
const { input: inputSlice, tokenIndexOffset } = inputInfo;
|
|
297
|
+
caretTokenIndex = caretTokenIndex - tokenIndexOffset;
|
|
298
|
+
let sqlParserIns = this._parser;
|
|
299
|
+
let parseTree = originParseTree;
|
|
300
|
+
/**
|
|
301
|
+
* Reparse the input fragment,
|
|
302
|
+
* and c3 will collect candidates in the newly generated parseTree when input changed.
|
|
303
|
+
*/
|
|
304
|
+
if (inputSlice !== input) {
|
|
305
|
+
sqlParserIns = this.createParser(inputSlice);
|
|
306
|
+
parseTree = sqlParserIns.program();
|
|
239
307
|
}
|
|
240
308
|
return {
|
|
241
309
|
parser: sqlParserIns,
|
|
242
310
|
parseTree,
|
|
243
311
|
tokenIndexOffset,
|
|
244
|
-
newTokenIndex:
|
|
312
|
+
newTokenIndex: caretTokenIndex,
|
|
245
313
|
};
|
|
246
314
|
}
|
|
247
315
|
/**
|
|
@@ -254,18 +322,43 @@ export class BasicSQL {
|
|
|
254
322
|
this.parseWithCache(input);
|
|
255
323
|
if (!this._parseTree)
|
|
256
324
|
return null;
|
|
257
|
-
|
|
325
|
+
let allTokens = this.getAllTokens(input);
|
|
258
326
|
let caretTokenIndex = findCaretTokenIndex(caretPosition, allTokens);
|
|
259
327
|
if (!caretTokenIndex && caretTokenIndex !== 0)
|
|
260
328
|
return null;
|
|
261
|
-
const
|
|
262
|
-
if (!
|
|
329
|
+
const inputInfo = this.getMinimumInputInfo(input, caretTokenIndex, this._parseTree);
|
|
330
|
+
if (!inputInfo)
|
|
263
331
|
return null;
|
|
264
|
-
const {
|
|
332
|
+
const { input: _input, tokenIndexOffset, statementCount } = inputInfo;
|
|
333
|
+
let inputSlice = _input;
|
|
334
|
+
/**
|
|
335
|
+
* Split the inputSlice by separator to get the smaller range of inputSlice.
|
|
336
|
+
*/
|
|
337
|
+
if (inputSlice.includes(SQL_SPLIT_SYMBOL_TEXT)) {
|
|
338
|
+
const { inputSlice: _inputSlice, allTokens: _allTokens, caretTokenIndex: _caretTokenIndex, } = this.splitInputBySymbolText(inputSlice, allTokens, tokenIndexOffset, caretTokenIndex);
|
|
339
|
+
allTokens = _allTokens;
|
|
340
|
+
caretTokenIndex = _caretTokenIndex;
|
|
341
|
+
inputSlice = _inputSlice;
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
if (statementCount > 1) {
|
|
345
|
+
caretTokenIndex = caretTokenIndex - tokenIndexOffset;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
let sqlParserIns = this._parser;
|
|
349
|
+
let parseTree = this._parseTree;
|
|
350
|
+
/**
|
|
351
|
+
* Reparse the input fragment,
|
|
352
|
+
* and c3 will collect candidates in the newly generated parseTree when input changed.
|
|
353
|
+
*/
|
|
354
|
+
if (inputSlice !== input) {
|
|
355
|
+
sqlParserIns = this.createParser(inputSlice);
|
|
356
|
+
parseTree = sqlParserIns.program();
|
|
357
|
+
}
|
|
265
358
|
const core = new CodeCompletionCore(sqlParserIns);
|
|
266
359
|
core.preferredRules = this.preferredRules;
|
|
267
|
-
const candidates = core.collectCandidates(
|
|
268
|
-
const originalSuggestions = this.processCandidates(candidates, allTokens,
|
|
360
|
+
const candidates = core.collectCandidates(caretTokenIndex, parseTree);
|
|
361
|
+
const originalSuggestions = this.processCandidates(candidates, allTokens, caretTokenIndex);
|
|
269
362
|
const syntaxSuggestions = originalSuggestions.syntax.map((syntaxCtx) => {
|
|
270
363
|
const wordRanges = syntaxCtx.wordRanges.map((token) => {
|
|
271
364
|
return tokenToWord(token, this._parsedInput);
|
|
@@ -300,4 +393,18 @@ export class BasicSQL {
|
|
|
300
393
|
// parser.entityCollecting = false;
|
|
301
394
|
return collectListener.getEntities();
|
|
302
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Get semantic context infos
|
|
398
|
+
* @param input source string
|
|
399
|
+
* @param caretPosition caret position, such as cursor position
|
|
400
|
+
* @param options semantic context options
|
|
401
|
+
* @returns analyzed semantic context
|
|
402
|
+
*/
|
|
403
|
+
getSemanticContextAtCaretPosition(input, caretPosition, options) {
|
|
404
|
+
const allTokens = this.getAllTokens(input);
|
|
405
|
+
const parseTree = this.parseWithCache(input);
|
|
406
|
+
const statementContextListener = this.createSemanticContextCollector(input, caretPosition, allTokens, options);
|
|
407
|
+
this.listen(statementContextListener, parseTree);
|
|
408
|
+
return statementContextListener.semanticContext;
|
|
409
|
+
}
|
|
303
410
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ErrorNode, ParserRuleContext, TerminalNode, Token } from 'antlr4ng';
|
|
2
|
+
import { CaretPosition, SemanticCollectOptions, SemanticContext } from '../common/types';
|
|
3
|
+
declare abstract class SemanticContextCollector {
|
|
4
|
+
constructor(_input: string, caretPosition: CaretPosition, allTokens: Token[], options?: SemanticCollectOptions);
|
|
5
|
+
readonly options: SemanticCollectOptions;
|
|
6
|
+
private _tokenIndex;
|
|
7
|
+
private _allTokens;
|
|
8
|
+
/**
|
|
9
|
+
* If current caret position is in a beginning of statement semantics, it needs to follow some cases:
|
|
10
|
+
* @case1 there is no statement node with an error before the current statement in the parse tree;
|
|
11
|
+
*
|
|
12
|
+
* @case2 if it is an uncomplete keyword, it will be parsed as an `ErrorNode`
|
|
13
|
+
* and need be a direct child node of `program`;
|
|
14
|
+
*
|
|
15
|
+
* @case3 if it is a complete keyword, the parsed TerminalNode or ErrorNode should be
|
|
16
|
+
* the first leaf node of current statement rule;
|
|
17
|
+
*
|
|
18
|
+
* @case4 if it is whiteSpace in caret position, we can't visit it in antlr4 listener,
|
|
19
|
+
* so we find the first unhidden token before the whiteSpace token, and the unhidden token
|
|
20
|
+
* should be the last leaf node of statement its belongs to;
|
|
21
|
+
*
|
|
22
|
+
* @case5 if the previous token is split symbol like `;`, ignore case1 and forcefully judged as beginning of statement.
|
|
23
|
+
*/
|
|
24
|
+
private _isStatementBeginning;
|
|
25
|
+
/**
|
|
26
|
+
* Prev tokenIndex that not white space before current tokenIndex or caret position
|
|
27
|
+
*/
|
|
28
|
+
private _prevTokenIndex;
|
|
29
|
+
get semanticContext(): SemanticContext;
|
|
30
|
+
abstract getWhiteSpaceRuleType(): number;
|
|
31
|
+
abstract getStatementRuleType(): number;
|
|
32
|
+
private prevStatementHasError;
|
|
33
|
+
/**
|
|
34
|
+
* Most root rule is `program`.
|
|
35
|
+
*/
|
|
36
|
+
private isRootRule;
|
|
37
|
+
/**
|
|
38
|
+
* link to @case4
|
|
39
|
+
* It should be called in each language's own `enterStatement`.
|
|
40
|
+
*/
|
|
41
|
+
protected visitStatement(ctx: ParserRuleContext): void;
|
|
42
|
+
/**
|
|
43
|
+
* Uncomplete keyword will be error node
|
|
44
|
+
*/
|
|
45
|
+
visitErrorNode(node: ErrorNode): void;
|
|
46
|
+
visitTerminal(node: TerminalNode): void;
|
|
47
|
+
enterEveryRule(_node: ParserRuleContext): void;
|
|
48
|
+
exitEveryRule(_node: ParserRuleContext): void;
|
|
49
|
+
}
|
|
50
|
+
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
|
+
import { SQL_SPLIT_SYMBOL_TEXT } from './basicSQL';
|
|
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;
|