dt-sql-parser 4.1.2-beta.1 → 4.3.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.
Files changed (83) hide show
  1. package/README.md +51 -0
  2. package/dist/lib/flink/FlinkSqlLexer.d.ts +2 -2
  3. package/dist/lib/flink/FlinkSqlLexer.js +21 -20
  4. package/dist/lib/flink/FlinkSqlParser.d.ts +178 -168
  5. package/dist/lib/flink/FlinkSqlParser.js +2799 -2741
  6. package/dist/lib/flink/FlinkSqlParserListener.d.ts +11 -0
  7. package/dist/lib/flink/FlinkSqlParserVisitor.d.ts +7 -0
  8. package/dist/lib/hive/HiveSqlParser.d.ts +241 -221
  9. package/dist/lib/hive/HiveSqlParser.js +6229 -5639
  10. package/dist/lib/hive/HiveSqlParserListener.d.ts +22 -0
  11. package/dist/lib/hive/HiveSqlParserVisitor.d.ts +14 -0
  12. package/dist/lib/impala/ImpalaSqlLexer.d.ts +2 -2
  13. package/dist/lib/impala/ImpalaSqlLexer.js +5 -5
  14. package/dist/lib/impala/ImpalaSqlParser.d.ts +147 -109
  15. package/dist/lib/impala/ImpalaSqlParser.js +3448 -3234
  16. package/dist/lib/impala/ImpalaSqlParserListener.d.ts +44 -0
  17. package/dist/lib/impala/ImpalaSqlParserVisitor.d.ts +28 -0
  18. package/dist/lib/mysql/MySqlLexer.d.ts +2 -2
  19. package/dist/lib/mysql/MySqlLexer.js +43 -43
  20. package/dist/lib/mysql/MySqlParser.d.ts +2 -2
  21. package/dist/lib/mysql/MySqlParser.js +42 -42
  22. package/dist/lib/postgresql/PostgreSqlLexer.d.ts +14 -15
  23. package/dist/lib/postgresql/PostgreSqlLexer.js +2077 -2092
  24. package/dist/lib/postgresql/PostgreSqlParser.d.ts +152 -131
  25. package/dist/lib/postgresql/PostgreSqlParser.js +10052 -9925
  26. package/dist/lib/postgresql/PostgreSqlParserListener.d.ts +22 -0
  27. package/dist/lib/postgresql/PostgreSqlParserVisitor.d.ts +14 -0
  28. package/dist/lib/spark/SparkSqlLexer.d.ts +2 -2
  29. package/dist/lib/spark/SparkSqlLexer.js +6 -6
  30. package/dist/lib/spark/SparkSqlParser.d.ts +391 -623
  31. package/dist/lib/spark/SparkSqlParser.js +6749 -8109
  32. package/dist/lib/spark/SparkSqlParserListener.d.ts +38 -326
  33. package/dist/lib/spark/SparkSqlParserVisitor.d.ts +24 -207
  34. package/dist/lib/trino/TrinoSqlLexer.d.ts +2 -2
  35. package/dist/lib/trino/TrinoSqlLexer.js +5 -5
  36. package/dist/lib/trino/TrinoSqlListener.d.ts +44 -0
  37. package/dist/lib/trino/TrinoSqlParser.d.ts +182 -153
  38. package/dist/lib/trino/TrinoSqlParser.js +4758 -4599
  39. package/dist/lib/trino/TrinoSqlVisitor.d.ts +28 -0
  40. package/dist/parser/common/basicSQL.d.ts +43 -9
  41. package/dist/parser/common/basicSQL.js +147 -40
  42. package/dist/parser/common/semanticContextCollector.d.ts +50 -0
  43. package/dist/parser/common/semanticContextCollector.js +202 -0
  44. package/dist/parser/common/tokenUtils.d.ts +12 -0
  45. package/dist/parser/common/tokenUtils.js +38 -0
  46. package/dist/parser/common/types.d.ts +30 -1
  47. package/dist/parser/common/types.js +11 -0
  48. package/dist/parser/flink/flinkErrorListener.js +14 -2
  49. package/dist/parser/flink/flinkSemanticContextCollector.d.ts +9 -0
  50. package/dist/parser/flink/flinkSemanticContextCollector.js +14 -0
  51. package/dist/parser/flink/index.d.ts +4 -2
  52. package/dist/parser/flink/index.js +32 -14
  53. package/dist/parser/hive/hiveErrorListener.js +9 -3
  54. package/dist/parser/hive/hiveSemanticContextCollector.d.ts +9 -0
  55. package/dist/parser/hive/hiveSemanticContextCollector.js +14 -0
  56. package/dist/parser/hive/index.d.ts +4 -2
  57. package/dist/parser/hive/index.js +29 -14
  58. package/dist/parser/impala/ImpalaErrorListener.js +8 -2
  59. package/dist/parser/impala/impalaSemanticContextCollector.d.ts +9 -0
  60. package/dist/parser/impala/impalaSemanticContextCollector.js +14 -0
  61. package/dist/parser/impala/index.d.ts +4 -2
  62. package/dist/parser/impala/index.js +21 -14
  63. package/dist/parser/mysql/index.d.ts +4 -2
  64. package/dist/parser/mysql/index.js +10 -14
  65. package/dist/parser/mysql/mysqlErrorListener.js +6 -2
  66. package/dist/parser/mysql/mysqlSemanticContextCollector.d.ts +9 -0
  67. package/dist/parser/mysql/mysqlSemanticContextCollector.js +14 -0
  68. package/dist/parser/postgresql/index.d.ts +4 -2
  69. package/dist/parser/postgresql/index.js +23 -14
  70. package/dist/parser/postgresql/postgreErrorListener.js +9 -3
  71. package/dist/parser/postgresql/postgreSemanticContextCollector.d.ts +9 -0
  72. package/dist/parser/postgresql/postgreSemanticContextCollector.js +14 -0
  73. package/dist/parser/spark/index.d.ts +4 -2
  74. package/dist/parser/spark/index.js +26 -14
  75. package/dist/parser/spark/sparkErrorListener.js +9 -3
  76. package/dist/parser/spark/sparkSemanticContextCollector.d.ts +9 -0
  77. package/dist/parser/spark/sparkSemanticContextCollector.js +14 -0
  78. package/dist/parser/trino/index.d.ts +4 -2
  79. package/dist/parser/trino/index.js +22 -14
  80. package/dist/parser/trino/trinoErrorListener.js +9 -3
  81. package/dist/parser/trino/trinoSemanticContextCollector.d.ts +9 -0
  82. package/dist/parser/trino/trinoSemanticContextCollector.js +14 -0
  83. 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, tokenIndexOffset: number): Suggestions<Token>;
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 a minimum boundary parser near tokenIndex.
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 tokenIndex start from which index to minimize the boundary.
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, tokenIndex: number, originParseTree?: ParserRuleContext | null): {
126
- parser: P;
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 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
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
- getMinimumParserInfo(input, tokenIndex, originParseTree) {
165
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
166
- if (arguments.length <= 2) {
167
- this.parseWithCache(input);
168
- originParseTree = this._parseTree;
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
- const splitListener = this.splitListener;
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 < tokenIndex && isPrevCtxValid) {
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 > 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 : input.length - 1;
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
- tokenIndex = tokenIndex - tokenIndexOffset;
223
- /**
224
- * Reparse the input fragment,
225
- * and c3 will collect candidates in the newly generated parseTree.
226
- */
227
- const inputSlice = input.slice(startIndex, stopIndex);
228
- const lexer = this.createLexer(inputSlice);
229
- lexer.removeErrorListeners();
230
- const tokenStream = new CommonTokenStream(lexer);
231
- tokenStream.fill();
232
- const parser = this.createParserFromTokenStream(tokenStream);
233
- parser.interpreter.predictionMode = PredictionMode.SLL;
234
- parser.removeErrorListeners();
235
- parser.buildParseTrees = true;
236
- parser.errorHandler = new ErrorStrategy();
237
- sqlParserIns = parser;
238
- parseTree = parser.program();
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: tokenIndex,
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
- const allTokens = this.getAllTokens(input);
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 minimumParser = this.getMinimumParserInfo(input, caretTokenIndex);
262
- if (!minimumParser)
329
+ const inputInfo = this.getMinimumInputInfo(input, caretTokenIndex, this._parseTree);
330
+ if (!inputInfo)
263
331
  return null;
264
- const { parser: sqlParserIns, tokenIndexOffset, newTokenIndex, parseTree: c3Context, } = minimumParser;
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(newTokenIndex, c3Context);
268
- const originalSuggestions = this.processCandidates(candidates, allTokens, newTokenIndex, tokenIndexOffset);
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;