dt-sql-parser 4.5.1-beta.2 → 4.5.1

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.
@@ -333,6 +333,8 @@ import { FunctionIdentifierContext } from "./HiveSqlParser.js";
333
333
  import { PrincipalIdentifierContext } from "./HiveSqlParser.js";
334
334
  import { NonReservedContext } from "./HiveSqlParser.js";
335
335
  import { Sql11ReservedKeywordsUsedAsFunctionNameContext } from "./HiveSqlParser.js";
336
+ import { ConfigPropertyContext } from "./HiveSqlParser.js";
337
+ import { ConfigPropertyPartContext } from "./HiveSqlParser.js";
336
338
  import { ConfigPropertiesItemContext } from "./HiveSqlParser.js";
337
339
  import { ResourcePlanDdlStatementsContext } from "./HiveSqlParser.js";
338
340
  import { MappingTypesContext } from "./HiveSqlParser.js";
@@ -3710,6 +3712,26 @@ export declare class HiveSqlParserListener implements ParseTreeListener {
3710
3712
  * @param ctx the parse tree
3711
3713
  */
3712
3714
  exitSql11ReservedKeywordsUsedAsFunctionName?: (ctx: Sql11ReservedKeywordsUsedAsFunctionNameContext) => void;
3715
+ /**
3716
+ * Enter a parse tree produced by `HiveSqlParser.configProperty`.
3717
+ * @param ctx the parse tree
3718
+ */
3719
+ enterConfigProperty?: (ctx: ConfigPropertyContext) => void;
3720
+ /**
3721
+ * Exit a parse tree produced by `HiveSqlParser.configProperty`.
3722
+ * @param ctx the parse tree
3723
+ */
3724
+ exitConfigProperty?: (ctx: ConfigPropertyContext) => void;
3725
+ /**
3726
+ * Enter a parse tree produced by `HiveSqlParser.configPropertyPart`.
3727
+ * @param ctx the parse tree
3728
+ */
3729
+ enterConfigPropertyPart?: (ctx: ConfigPropertyPartContext) => void;
3730
+ /**
3731
+ * Exit a parse tree produced by `HiveSqlParser.configPropertyPart`.
3732
+ * @param ctx the parse tree
3733
+ */
3734
+ exitConfigPropertyPart?: (ctx: ConfigPropertyPartContext) => void;
3713
3735
  /**
3714
3736
  * Enter a parse tree produced by `HiveSqlParser.configPropertiesItem`.
3715
3737
  * @param ctx the parse tree
@@ -333,6 +333,8 @@ import { FunctionIdentifierContext } from "./HiveSqlParser.js";
333
333
  import { PrincipalIdentifierContext } from "./HiveSqlParser.js";
334
334
  import { NonReservedContext } from "./HiveSqlParser.js";
335
335
  import { Sql11ReservedKeywordsUsedAsFunctionNameContext } from "./HiveSqlParser.js";
336
+ import { ConfigPropertyContext } from "./HiveSqlParser.js";
337
+ import { ConfigPropertyPartContext } from "./HiveSqlParser.js";
336
338
  import { ConfigPropertiesItemContext } from "./HiveSqlParser.js";
337
339
  import { ResourcePlanDdlStatementsContext } from "./HiveSqlParser.js";
338
340
  import { MappingTypesContext } from "./HiveSqlParser.js";
@@ -2373,6 +2375,18 @@ export declare class HiveSqlParserVisitor<Result> extends AbstractParseTreeVisit
2373
2375
  * @return the visitor result
2374
2376
  */
2375
2377
  visitSql11ReservedKeywordsUsedAsFunctionName?: (ctx: Sql11ReservedKeywordsUsedAsFunctionNameContext) => Result;
2378
+ /**
2379
+ * Visit a parse tree produced by `HiveSqlParser.configProperty`.
2380
+ * @param ctx the parse tree
2381
+ * @return the visitor result
2382
+ */
2383
+ visitConfigProperty?: (ctx: ConfigPropertyContext) => Result;
2384
+ /**
2385
+ * Visit a parse tree produced by `HiveSqlParser.configPropertyPart`.
2386
+ * @param ctx the parse tree
2387
+ * @return the visitor result
2388
+ */
2389
+ visitConfigPropertyPart?: (ctx: ConfigPropertyPartContext) => Result;
2376
2390
  /**
2377
2391
  * Visit a parse tree produced by `HiveSqlParser.configPropertiesItem`.
2378
2392
  * @param ctx the parse tree
@@ -7,7 +7,7 @@ import { ErrorListener, ParseError } from './parseErrorListener';
7
7
  import SemanticContextCollector from './semanticContextCollector';
8
8
  import type { SplitListener } from './splitListener';
9
9
  import { TextSlice } from './textAndWord';
10
- import { CaretPosition, LOCALE_TYPE, SemanticCollectOptions, Suggestions } from './types';
10
+ import { CaretPosition, LOCALE_TYPE, SemanticCollectOptions, SuggestionOptions, Suggestions } from './types';
11
11
  export declare const SQL_SPLIT_SYMBOL_TEXT = ";";
12
12
  /**
13
13
  * Basic SQL class, every sql needs extends it.
@@ -21,6 +21,7 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
21
21
  protected _parseTree: PRC | null;
22
22
  protected _parsedInput: string;
23
23
  protected _parseErrors: ParseError[];
24
+ private _statementStartTokenTypes;
24
25
  /** members for cache end */
25
26
  private _errorListener;
26
27
  /**
@@ -44,6 +45,7 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
44
45
  * @param caretTokenIndex tokenIndex of caretPosition
45
46
  */
46
47
  protected abstract processCandidates(candidates: CandidatesCollection, allTokens: Token[], caretTokenIndex: number): Suggestions<Token>;
48
+ protected getCandidateTokenRanges(candidates: CandidatesCollection, candidateStartTokenIndex: number, allTokens: Token[], caretTokenIndex: number): Token[];
47
49
  /**
48
50
  * Get a new splitListener instance.
49
51
  */
@@ -166,13 +168,24 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
166
168
  tokenIndexOffset: number;
167
169
  newTokenIndex: number;
168
170
  } | null;
171
+ /**
172
+ * Get the minimum statement tree for collecting completion candidates
173
+ * Each supported grammar exposes one top-level statement per direct program child
174
+ * Keep the lookup shallow to avoid selecting nested statements or subqueries
175
+ */
176
+ private getSuggestionParseTree;
177
+ /**
178
+ * Collect candidates for the current statement and remove new-statement-only keywords
179
+ */
180
+ private collectSuggestionCandidates;
169
181
  /**
170
182
  * Get suggestions of syntax and token at caretPosition
171
183
  * @param input source string
172
184
  * @param caretPosition caret position, such as cursor position
185
+ * @param options suggestion options
173
186
  * @returns suggestion
174
187
  */
175
- getSuggestionAtCaretPosition(input: string, caretPosition: CaretPosition): Suggestions | null;
188
+ getSuggestionAtCaretPosition(input: string, caretPosition: CaretPosition, options?: SuggestionOptions): Suggestions | null;
176
189
  getAllEntities(input: string, caretPosition?: CaretPosition): EntityContext[] | null;
177
190
  /**
178
191
  * Get semantic context infos
@@ -1,5 +1,5 @@
1
1
  import { CodeCompletionCore } from 'antlr4-c3';
2
- import { CharStreams, CommonTokenStream, ParseTreeWalker, PredictionMode, } from 'antlr4ng';
2
+ import { CharStreams, CommonTokenStream, ParserRuleContext, ParseTreeWalker, PredictionMode, Token, } from 'antlr4ng';
3
3
  import { ErrorStrategy } from './errorStrategy';
4
4
  import { findCaretTokenIndex } from './findCaretTokenIndex';
5
5
  import { ctxToText, tokenToWord } from './textAndWord';
@@ -10,12 +10,56 @@ export const SQL_SPLIT_SYMBOL_TEXT = ';';
10
10
  export class BasicSQL {
11
11
  constructor() {
12
12
  this._parseErrors = [];
13
+ this._statementStartTokenTypes = null;
13
14
  /** members for cache end */
14
15
  this._errorListener = (error) => {
15
16
  this._parseErrors.push(error);
16
17
  };
17
18
  this.locale = 'en_US';
18
19
  }
20
+ getCandidateTokenRanges(candidates, candidateStartTokenIndex, allTokens, caretTokenIndex) {
21
+ var _a;
22
+ // antlr4-c3 may return both entity and alias candidates; use the nearest candidate's start index as boundary
23
+ const endTokenIndex = Array.from(candidates.rules.values()).reduce((nearestStartTokenIndex, candidateRule) => {
24
+ if (candidateRule.startTokenIndex <= candidateStartTokenIndex) {
25
+ return nearestStartTokenIndex;
26
+ }
27
+ return Math.min(nearestStartTokenIndex, candidateRule.startTokenIndex);
28
+ }, caretTokenIndex + 1);
29
+ const previousVisibleToken = allTokens
30
+ .slice(candidateStartTokenIndex, endTokenIndex)
31
+ .reverse()
32
+ .find((token) => token.channel === Token.DEFAULT_CHANNEL);
33
+ const visibleTokenIndexes = allTokens
34
+ .slice(endTokenIndex, caretTokenIndex + 1)
35
+ .reduce((indexes, token, offset) => {
36
+ if (token.channel === Token.DEFAULT_CHANNEL) {
37
+ indexes.push(endTokenIndex + offset);
38
+ }
39
+ return indexes;
40
+ }, []);
41
+ const firstVisibleToken = allTokens[visibleTokenIndexes[0]];
42
+ let rangeEndTokenIndex = endTokenIndex;
43
+ // candidate boundary may fall inside a multi-level qualified name; extend along the identifier and dot chain
44
+ if ((previousVisibleToken === null || previousVisibleToken === void 0 ? void 0 : previousVisibleToken.text) === '.' || (firstVisibleToken === null || firstVisibleToken === void 0 ? void 0 : firstVisibleToken.text) === '.') {
45
+ let visibleTokenOffset = 0;
46
+ if ((previousVisibleToken === null || previousVisibleToken === void 0 ? void 0 : previousVisibleToken.text) === '.' && firstVisibleToken) {
47
+ rangeEndTokenIndex = visibleTokenIndexes[visibleTokenOffset] + 1;
48
+ visibleTokenOffset += 1;
49
+ }
50
+ while (((_a = allTokens[visibleTokenIndexes[visibleTokenOffset]]) === null || _a === void 0 ? void 0 : _a.text) === '.') {
51
+ rangeEndTokenIndex = visibleTokenIndexes[visibleTokenOffset] + 1;
52
+ visibleTokenOffset += 1;
53
+ if (visibleTokenOffset < visibleTokenIndexes.length) {
54
+ rangeEndTokenIndex = visibleTokenIndexes[visibleTokenOffset] + 1;
55
+ visibleTokenOffset += 1;
56
+ }
57
+ }
58
+ }
59
+ return allTokens
60
+ .slice(candidateStartTokenIndex, rangeEndTokenIndex)
61
+ .filter((token) => token.channel === Token.DEFAULT_CHANNEL);
62
+ }
19
63
  /**
20
64
  * Create an antlr4 lexer from input.
21
65
  * @param input string
@@ -384,13 +428,77 @@ export class BasicSQL {
384
428
  newTokenIndex: caretTokenIndex,
385
429
  };
386
430
  }
431
+ /**
432
+ * Get the minimum statement tree for collecting completion candidates
433
+ * Each supported grammar exposes one top-level statement per direct program child
434
+ * Keep the lookup shallow to avoid selecting nested statements or subqueries
435
+ */
436
+ getSuggestionParseTree(parseTree, caretTokenIndex) {
437
+ var _a, _b, _c;
438
+ const children = parseTree.children;
439
+ if (!(children === null || children === void 0 ? void 0 : children.length))
440
+ return parseTree;
441
+ for (let index = children.length - 1; index >= 0; index--) {
442
+ const child = children[index];
443
+ if (!(child instanceof ParserRuleContext))
444
+ continue;
445
+ const startTokenIndex = (_a = child.start) === null || _a === void 0 ? void 0 : _a.tokenIndex;
446
+ const stopTokenIndex = (_b = child.stop) === null || _b === void 0 ? void 0 : _b.tokenIndex;
447
+ if (startTokenIndex === undefined ||
448
+ stopTokenIndex === undefined ||
449
+ startTokenIndex > caretTokenIndex)
450
+ continue;
451
+ // Use the current statement tree when the caret is inside it
452
+ if (stopTokenIndex >= caretTokenIndex)
453
+ return child;
454
+ // Keep using the current statement until it ends with a semicolon
455
+ return ((_c = child.stop) === null || _c === void 0 ? void 0 : _c.text) === SQL_SPLIT_SYMBOL_TEXT ? parseTree : child;
456
+ }
457
+ return parseTree;
458
+ }
459
+ /**
460
+ * Collect candidates for the current statement and remove new-statement-only keywords
461
+ */
462
+ collectSuggestionCandidates(parser, parseTree, caretTokenIndex) {
463
+ const core = new CodeCompletionCore(parser);
464
+ core.preferredRules = this.preferredRules;
465
+ const candidates = core.collectCandidates(caretTokenIndex, parseTree);
466
+ const suggestionParseTree = this.getSuggestionParseTree(parseTree, caretTokenIndex);
467
+ if (suggestionParseTree === parseTree)
468
+ return candidates;
469
+ // Keep the program candidates for outer rule paths and use statement candidates for isolation
470
+ const statementCore = new CodeCompletionCore(parser);
471
+ statementCore.preferredRules = this.preferredRules;
472
+ const statementCandidates = statementCore.collectCandidates(caretTokenIndex, suggestionParseTree);
473
+ if (this._statementStartTokenTypes === null) {
474
+ const statementStartCore = new CodeCompletionCore(parser);
475
+ statementStartCore.preferredRules = this.preferredRules;
476
+ const statementStartCandidates = statementStartCore.collectCandidates(0, parseTree);
477
+ this._statementStartTokenTypes = new Set(statementStartCandidates.tokens.keys());
478
+ }
479
+ const tokens = new Map(candidates.tokens);
480
+ for (const tokenType of this._statementStartTokenTypes) {
481
+ if (!statementCandidates.tokens.has(tokenType)) {
482
+ tokens.delete(tokenType);
483
+ }
484
+ else if (tokens.has(tokenType)) {
485
+ // Use the current statement follow-list to preserve valid combined keywords
486
+ tokens.set(tokenType, statementCandidates.tokens.get(tokenType));
487
+ }
488
+ }
489
+ return {
490
+ rules: candidates.rules,
491
+ tokens,
492
+ };
493
+ }
387
494
  /**
388
495
  * Get suggestions of syntax and token at caretPosition
389
496
  * @param input source string
390
497
  * @param caretPosition caret position, such as cursor position
498
+ * @param options suggestion options
391
499
  * @returns suggestion
392
500
  */
393
- getSuggestionAtCaretPosition(input, caretPosition) {
501
+ getSuggestionAtCaretPosition(input, caretPosition, options) {
394
502
  this.parseWithCache(input);
395
503
  if (!this._parseTree)
396
504
  return null;
@@ -428,11 +536,7 @@ export class BasicSQL {
428
536
  sqlParserIns = this.createParser(inputSlice);
429
537
  parseTree = sqlParserIns.program();
430
538
  }
431
- const core = new CodeCompletionCore(sqlParserIns);
432
- core.preferredRules = this.preferredRules;
433
- // core.showRuleStack = true;
434
- // core.showResult = true;
435
- const candidates = core.collectCandidates(caretTokenIndex, parseTree);
539
+ const candidates = this.collectSuggestionCandidates(sqlParserIns, parseTree, caretTokenIndex);
436
540
  const originalSuggestions = this.processCandidates(candidates, allTokens, caretTokenIndex);
437
541
  const syntaxSuggestions = originalSuggestions.syntax.map((syntaxCtx) => {
438
542
  const wordRanges = syntaxCtx.wordRanges.map((token) => {
@@ -443,9 +547,13 @@ export class BasicSQL {
443
547
  wordRanges,
444
548
  };
445
549
  });
550
+ const keywordFilter = options === null || options === void 0 ? void 0 : options.keywordFilter;
551
+ const keywords = keywordFilter
552
+ ? originalSuggestions.keywords.filter((keyword) => keywordFilter(keyword))
553
+ : originalSuggestions.keywords;
446
554
  return {
447
555
  syntax: syntaxSuggestions,
448
- keywords: originalSuggestions.keywords,
556
+ keywords,
449
557
  };
450
558
  }
451
559
  getAllEntities(input, caretPosition) {
@@ -69,6 +69,15 @@ export interface Suggestions<T = WordRange> {
69
69
  */
70
70
  readonly keywords: string[];
71
71
  }
72
+ /**
73
+ * Suggested information options
74
+ */
75
+ export interface SuggestionOptions {
76
+ /**
77
+ * Return true to keep the keyword, otherwise remove it
78
+ */
79
+ keywordFilter?: (keyword: string) => boolean;
80
+ }
72
81
  export type LOCALE_TYPE = 'zh_CN' | 'en_US';
73
82
  export interface SemanticContext {
74
83
  isStatementBeginning: boolean;
@@ -67,7 +67,7 @@ export class FlinkSQL extends BasicSQL {
67
67
  const keywords = [];
68
68
  for (let candidate of candidates.rules) {
69
69
  const [ruleType, candidateRule] = candidate;
70
- const tokenRanges = allTokens.slice(candidateRule.startTokenIndex, caretTokenIndex + 1);
70
+ const tokenRanges = this.getCandidateTokenRanges(candidates, candidateRule.startTokenIndex, allTokens, caretTokenIndex);
71
71
  let syntaxContextType = void 0;
72
72
  switch (ruleType) {
73
73
  case FlinkSqlParser.RULE_catalogPath: {
@@ -53,7 +53,7 @@ export class GenericSQL extends BasicSQL {
53
53
  const keywords = [];
54
54
  for (const candidate of candidates.rules) {
55
55
  const [ruleType, candidateRule] = candidate;
56
- const tokenRanges = allTokens.slice(candidateRule.startTokenIndex, caretTokenIndex + 1);
56
+ const tokenRanges = this.getCandidateTokenRanges(candidates, candidateRule.startTokenIndex, allTokens, caretTokenIndex);
57
57
  let syntaxContextType = void 0;
58
58
  switch (ruleType) {
59
59
  case GenericSqlParser.RULE_tableName: {
@@ -55,7 +55,7 @@ export class HiveSQL extends BasicSQL {
55
55
  const keywords = [];
56
56
  for (let candidate of candidates.rules) {
57
57
  const [ruleType, candidateRule] = candidate;
58
- const tokenRanges = allTokens.slice(candidateRule.startTokenIndex, caretTokenIndex + 1);
58
+ const tokenRanges = this.getCandidateTokenRanges(candidates, candidateRule.startTokenIndex, allTokens, caretTokenIndex);
59
59
  let syntaxContextType = void 0;
60
60
  switch (ruleType) {
61
61
  case HiveSqlParser.RULE_dbSchemaName: {
@@ -54,7 +54,7 @@ export class ImpalaSQL extends BasicSQL {
54
54
  const keywords = [];
55
55
  for (let candidate of candidates.rules) {
56
56
  const [ruleType, candidateRule] = candidate;
57
- const tokenRanges = allTokens.slice(candidateRule.startTokenIndex, caretTokenIndex + 1);
57
+ const tokenRanges = this.getCandidateTokenRanges(candidates, candidateRule.startTokenIndex, allTokens, caretTokenIndex);
58
58
  let syntaxContextType = void 0;
59
59
  switch (ruleType) {
60
60
  case ImpalaSqlParser.RULE_functionNameCreate: {
@@ -62,7 +62,7 @@ export class MySQL extends BasicSQL {
62
62
  const keywords = [];
63
63
  for (const candidate of candidates.rules) {
64
64
  const [ruleType, candidateRule] = candidate;
65
- const tokenRanges = allTokens.slice(candidateRule.startTokenIndex, caretTokenIndex + 1);
65
+ const tokenRanges = this.getCandidateTokenRanges(candidates, candidateRule.startTokenIndex, allTokens, caretTokenIndex);
66
66
  let syntaxContextType = void 0;
67
67
  switch (ruleType) {
68
68
  case MySqlParser.RULE_databaseName: {
@@ -64,7 +64,7 @@ export class PostgreSQL extends BasicSQL {
64
64
  const keywords = [];
65
65
  for (let candidate of candidates.rules) {
66
66
  const [ruleType, candidateRule] = candidate;
67
- const tokenRanges = allTokens.slice(candidateRule.startTokenIndex, caretTokenIndex + 1);
67
+ const tokenRanges = this.getCandidateTokenRanges(candidates, candidateRule.startTokenIndex, allTokens, caretTokenIndex);
68
68
  let syntaxContextType = void 0;
69
69
  switch (ruleType) {
70
70
  case PostgreSqlParser.RULE_tableNameCreate: {
@@ -59,7 +59,7 @@ export class SparkSQL extends BasicSQL {
59
59
  const keywords = [];
60
60
  for (const candidate of candidates.rules) {
61
61
  const [ruleType, candidateRule] = candidate;
62
- const tokenRanges = allTokens.slice(candidateRule.startTokenIndex, caretTokenIndex + 1);
62
+ const tokenRanges = this.getCandidateTokenRanges(candidates, candidateRule.startTokenIndex, allTokens, caretTokenIndex);
63
63
  let syntaxContextType = void 0;
64
64
  switch (ruleType) {
65
65
  case SparkSqlParser.RULE_namespaceName: {
@@ -56,7 +56,7 @@ export class TrinoSQL extends BasicSQL {
56
56
  const keywords = [];
57
57
  for (let candidate of candidates.rules) {
58
58
  const [ruleType, candidateRule] = candidate;
59
- const tokenRanges = allTokens.slice(candidateRule.startTokenIndex, caretTokenIndex + 1);
59
+ const tokenRanges = this.getCandidateTokenRanges(candidates, candidateRule.startTokenIndex, allTokens, caretTokenIndex);
60
60
  let syntaxContextType = void 0;
61
61
  switch (ruleType) {
62
62
  case TrinoSqlParser.RULE_catalogRef: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dt-sql-parser",
3
- "version": "4.5.1-beta.2",
3
+ "version": "4.5.1",
4
4
  "authors": "DTStack Corporation",
5
5
  "description": "SQL Parsers for BigData, built with antlr4",
6
6
  "keywords": [