dt-sql-parser 4.5.1-beta.3 → 4.5.2-beta.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/dist/lib/SQLParserBase.d.ts +9 -0
- package/dist/lib/SQLParserBase.js +15 -0
- package/dist/lib/generic/GenericSqlListener.d.ts +11 -0
- package/dist/lib/generic/GenericSqlParser.d.ts +61 -47
- package/dist/lib/generic/GenericSqlParser.js +1047 -949
- package/dist/lib/generic/GenericSqlVisitor.d.ts +7 -0
- package/dist/lib/hive/HiveSqlParser.d.ts +54 -30
- package/dist/lib/hive/HiveSqlParser.js +5633 -5467
- package/dist/lib/hive/HiveSqlParserListener.d.ts +22 -0
- package/dist/lib/hive/HiveSqlParserVisitor.d.ts +14 -0
- package/dist/lib/mysql/MySqlParser.d.ts +34 -9
- package/dist/lib/mysql/MySqlParser.js +5697 -5550
- package/dist/lib/mysql/MySqlParserListener.d.ts +33 -5
- package/dist/lib/mysql/MySqlParserVisitor.d.ts +20 -3
- package/dist/lib/postgresql/PostgreSqlParser.d.ts +200 -166
- package/dist/lib/postgresql/PostgreSqlParser.js +10389 -10136
- package/dist/lib/postgresql/PostgreSqlParserListener.d.ts +31 -5
- package/dist/lib/postgresql/PostgreSqlParserVisitor.d.ts +19 -3
- package/dist/lib/trino/TrinoSqlListener.d.ts +33 -0
- package/dist/lib/trino/TrinoSqlParser.d.ts +157 -115
- package/dist/lib/trino/TrinoSqlParser.js +4775 -4494
- package/dist/lib/trino/TrinoSqlVisitor.d.ts +21 -0
- package/dist/parser/common/basicSQL.d.ts +3 -0
- package/dist/parser/common/basicSQL.js +47 -1
- package/dist/parser/common/entityCollector.d.ts +37 -2
- package/dist/parser/common/entityCollector.js +303 -2
- package/dist/parser/flink/flinkEntityCollector.d.ts +2 -1
- package/dist/parser/flink/flinkEntityCollector.js +4 -0
- package/dist/parser/flink/index.js +1 -1
- package/dist/parser/generic/genericEntityCollector.d.ts +7 -2
- package/dist/parser/generic/genericEntityCollector.js +65 -15
- package/dist/parser/generic/index.js +13 -1
- package/dist/parser/hive/hiveEntityCollector.d.ts +2 -1
- package/dist/parser/hive/hiveEntityCollector.js +5 -0
- package/dist/parser/hive/index.js +1 -1
- package/dist/parser/impala/impalaEntityCollector.d.ts +2 -1
- package/dist/parser/impala/impalaEntityCollector.js +4 -0
- package/dist/parser/impala/index.js +1 -1
- package/dist/parser/mysql/index.js +15 -1
- package/dist/parser/mysql/mysqlEntityCollector.d.ts +3 -1
- package/dist/parser/mysql/mysqlEntityCollector.js +24 -0
- package/dist/parser/postgresql/index.js +3 -2
- package/dist/parser/postgresql/postgreEntityCollector.d.ts +3 -1
- package/dist/parser/postgresql/postgreEntityCollector.js +10 -0
- package/dist/parser/spark/index.js +1 -1
- package/dist/parser/spark/sparkEntityCollector.d.ts +2 -1
- package/dist/parser/spark/sparkEntityCollector.js +5 -0
- package/dist/parser/trino/index.js +4 -2
- package/dist/parser/trino/trinoEntityCollector.d.ts +2 -1
- package/dist/parser/trino/trinoEntityCollector.js +5 -0
- package/package.json +1 -1
|
@@ -138,6 +138,8 @@ import { RelationDefaultContext } from "./TrinoSqlParser.js";
|
|
|
138
138
|
import { JoinRelationContext } from "./TrinoSqlParser.js";
|
|
139
139
|
import { JoinTypeContext } from "./TrinoSqlParser.js";
|
|
140
140
|
import { JoinCriteriaContext } from "./TrinoSqlParser.js";
|
|
141
|
+
import { JoinColumnEqualityContext } from "./TrinoSqlParser.js";
|
|
142
|
+
import { JoinColumnReferenceContext } from "./TrinoSqlParser.js";
|
|
141
143
|
import { SampledRelationContext } from "./TrinoSqlParser.js";
|
|
142
144
|
import { SampleTypeContext } from "./TrinoSqlParser.js";
|
|
143
145
|
import { TrimsSpecificationContext } from "./TrinoSqlParser.js";
|
|
@@ -356,6 +358,7 @@ import { CatalogNameCreateContext } from "./TrinoSqlParser.js";
|
|
|
356
358
|
import { FunctionNameContext } from "./TrinoSqlParser.js";
|
|
357
359
|
import { FunctionNameCreateContext } from "./TrinoSqlParser.js";
|
|
358
360
|
import { ColumnRefContext } from "./TrinoSqlParser.js";
|
|
361
|
+
import { EmptyColumnContext } from "./TrinoSqlParser.js";
|
|
359
362
|
import { ColumnNameContext } from "./TrinoSqlParser.js";
|
|
360
363
|
import { ColumnNameCreateContext } from "./TrinoSqlParser.js";
|
|
361
364
|
import { QualifiedNameContext } from "./TrinoSqlParser.js";
|
|
@@ -1320,6 +1323,18 @@ export declare class TrinoSqlVisitor<Result> extends AbstractParseTreeVisitor<Re
|
|
|
1320
1323
|
* @return the visitor result
|
|
1321
1324
|
*/
|
|
1322
1325
|
visitJoinCriteria?: (ctx: JoinCriteriaContext) => Result;
|
|
1326
|
+
/**
|
|
1327
|
+
* Visit a parse tree produced by `TrinoSqlParser.joinColumnEquality`.
|
|
1328
|
+
* @param ctx the parse tree
|
|
1329
|
+
* @return the visitor result
|
|
1330
|
+
*/
|
|
1331
|
+
visitJoinColumnEquality?: (ctx: JoinColumnEqualityContext) => Result;
|
|
1332
|
+
/**
|
|
1333
|
+
* Visit a parse tree produced by `TrinoSqlParser.joinColumnReference`.
|
|
1334
|
+
* @param ctx the parse tree
|
|
1335
|
+
* @return the visitor result
|
|
1336
|
+
*/
|
|
1337
|
+
visitJoinColumnReference?: (ctx: JoinColumnReferenceContext) => Result;
|
|
1323
1338
|
/**
|
|
1324
1339
|
* Visit a parse tree produced by `TrinoSqlParser.sampledRelation`.
|
|
1325
1340
|
* @param ctx the parse tree
|
|
@@ -2769,6 +2784,12 @@ export declare class TrinoSqlVisitor<Result> extends AbstractParseTreeVisitor<Re
|
|
|
2769
2784
|
* @return the visitor result
|
|
2770
2785
|
*/
|
|
2771
2786
|
visitColumnRef?: (ctx: ColumnRefContext) => Result;
|
|
2787
|
+
/**
|
|
2788
|
+
* Visit a parse tree produced by `TrinoSqlParser.emptyColumn`.
|
|
2789
|
+
* @param ctx the parse tree
|
|
2790
|
+
* @return the visitor result
|
|
2791
|
+
*/
|
|
2792
|
+
visitEmptyColumn?: (ctx: EmptyColumnContext) => Result;
|
|
2772
2793
|
/**
|
|
2773
2794
|
* Visit a parse tree produced by `TrinoSqlParser.columnName`.
|
|
2774
2795
|
* @param ctx the parse tree
|
|
@@ -45,6 +45,7 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
|
|
|
45
45
|
* @param caretTokenIndex tokenIndex of caretPosition
|
|
46
46
|
*/
|
|
47
47
|
protected abstract processCandidates(candidates: CandidatesCollection, allTokens: Token[], caretTokenIndex: number): Suggestions<Token>;
|
|
48
|
+
protected getCandidateTokenRanges(candidates: CandidatesCollection, candidateStartTokenIndex: number, allTokens: Token[], caretTokenIndex: number): Token[];
|
|
48
49
|
/**
|
|
49
50
|
* Get a new splitListener instance.
|
|
50
51
|
*/
|
|
@@ -169,6 +170,8 @@ export declare abstract class BasicSQL<L extends Lexer = Lexer, PRC extends Pars
|
|
|
169
170
|
} | null;
|
|
170
171
|
/**
|
|
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
|
|
172
175
|
*/
|
|
173
176
|
private getSuggestionParseTree;
|
|
174
177
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CodeCompletionCore } from 'antlr4-c3';
|
|
2
|
-
import { CharStreams, CommonTokenStream, ParserRuleContext, 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';
|
|
@@ -17,6 +17,49 @@ export class BasicSQL {
|
|
|
17
17
|
};
|
|
18
18
|
this.locale = 'en_US';
|
|
19
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
|
+
}
|
|
20
63
|
/**
|
|
21
64
|
* Create an antlr4 lexer from input.
|
|
22
65
|
* @param input string
|
|
@@ -387,6 +430,8 @@ export class BasicSQL {
|
|
|
387
430
|
}
|
|
388
431
|
/**
|
|
389
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
|
|
390
435
|
*/
|
|
391
436
|
getSuggestionParseTree(parseTree, caretTokenIndex) {
|
|
392
437
|
var _a, _b, _c;
|
|
@@ -421,6 +466,7 @@ export class BasicSQL {
|
|
|
421
466
|
const suggestionParseTree = this.getSuggestionParseTree(parseTree, caretTokenIndex);
|
|
422
467
|
if (suggestionParseTree === parseTree)
|
|
423
468
|
return candidates;
|
|
469
|
+
// Keep the program candidates for outer rule paths and use statement candidates for isolation
|
|
424
470
|
const statementCore = new CodeCompletionCore(parser);
|
|
425
471
|
statementCore.preferredRules = this.preferredRules;
|
|
426
472
|
const statementCandidates = statementCore.collectCandidates(caretTokenIndex, suggestionParseTree);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ParserRuleContext, Token } from 'antlr4ng';
|
|
1
|
+
import { ErrorNode, ParserRuleContext, Token } from 'antlr4ng';
|
|
2
2
|
import { TextPosition, WordPosition, WordRange } from './textAndWord';
|
|
3
3
|
import { EntityContextType } from './types';
|
|
4
4
|
/**
|
|
@@ -144,10 +144,21 @@ export declare abstract class EntityCollector {
|
|
|
144
144
|
private readonly _allTokens;
|
|
145
145
|
private readonly _caretTokenIndex;
|
|
146
146
|
private readonly _entitiesSet;
|
|
147
|
+
/**
|
|
148
|
+
* Contexts whose source slice contains a parse error.
|
|
149
|
+
* Error recovery can stretch a rule context across a whole clause, so `ctxToText`
|
|
150
|
+
* would return e.g. `JOIN myOwn\nORDER BY 1` where a name is expected.
|
|
151
|
+
*/
|
|
152
|
+
private readonly _errorContexts;
|
|
147
153
|
/** Staging statements that have already entered. */
|
|
148
154
|
private readonly _stmtStack;
|
|
149
155
|
/** Staging entities inside a single statement or clause. */
|
|
150
156
|
private readonly _entityStack;
|
|
157
|
+
/**
|
|
158
|
+
* CTE definitions collected while walking.
|
|
159
|
+
* Visibility is sequential inside the WITH owner's range, and references bind to the nearest preceding CTE.
|
|
160
|
+
*/
|
|
161
|
+
private _cteDefinitions;
|
|
151
162
|
/**
|
|
152
163
|
* The scope depth of the statement containing the caret.
|
|
153
164
|
* This is used to determine the accessibility of entities.
|
|
@@ -164,7 +175,7 @@ export declare abstract class EntityCollector {
|
|
|
164
175
|
* */
|
|
165
176
|
private _rootStmt;
|
|
166
177
|
visitTerminal(): void;
|
|
167
|
-
visitErrorNode(): void;
|
|
178
|
+
visitErrorNode(node: ErrorNode): void;
|
|
168
179
|
enterEveryRule(): void;
|
|
169
180
|
exitEveryRule(): void;
|
|
170
181
|
getRootStmt(): StmtContext | null;
|
|
@@ -215,5 +226,29 @@ export declare abstract class EntityCollector {
|
|
|
215
226
|
protected combineQueryResultStmtEntities(stmtContext: StmtContext, entitiesInsideStmt: EntityContext[]): EntityContext[];
|
|
216
227
|
protected combineFromTableSource(stmtContext: StmtContext, entitiesInsideStmt: EntityContext[]): EntityContext[];
|
|
217
228
|
protected combineCreateTableOrViewStmtEntities(stmtContext: StmtContext, entitiesInsideStmt: EntityContext[]): EntityContext[];
|
|
229
|
+
/**
|
|
230
|
+
* Collect one CTE definition.
|
|
231
|
+
* `endTokenIndex` is the closing token of this item only (not a following CTE).
|
|
232
|
+
* Column names, when present, become the outward schema and do not replace the body query result.
|
|
233
|
+
*/
|
|
234
|
+
protected collectCte(cteCtx: ParserRuleContext, nameCtx: ParserRuleContext | null, bodyCtx: ParserRuleContext | null, endTokenIndex: number, columnNameCtxs?: ParserRuleContext[], columnListCtx?: ParserRuleContext | null): void;
|
|
235
|
+
private cteScopeEndTokenIndex;
|
|
236
|
+
private extendUnclosedScope;
|
|
237
|
+
private finalizeCteDefinitions;
|
|
238
|
+
/**
|
|
239
|
+
* CTE outward schema defaults to the body query result itself.
|
|
240
|
+
* Override when that result's text covers more than the select list.
|
|
241
|
+
*/
|
|
242
|
+
protected adaptOutwardQueryResult(entity: CommonEntityContext): CommonEntityContext;
|
|
243
|
+
protected sourceSlice(startIndex: number, endIndex: number): string;
|
|
244
|
+
private findOutwardQueryResult;
|
|
245
|
+
private columnTokens;
|
|
246
|
+
private createColumnListQueryResultFromTokens;
|
|
247
|
+
private createColumnListQueryResult;
|
|
248
|
+
private wrapOutwardColumns;
|
|
249
|
+
private bindCteReferences;
|
|
250
|
+
private markCteAccessibility;
|
|
251
|
+
/** Nearest preceding CTE whose scope covers `tokenIndex`. */
|
|
252
|
+
private resolveCte;
|
|
218
253
|
}
|
|
219
254
|
export {};
|
|
@@ -9,7 +9,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import { isToken, Token } from 'antlr4ng';
|
|
12
|
+
import { isToken, ParserRuleContext, TerminalNode, Token } from 'antlr4ng';
|
|
13
13
|
import { SimpleStack } from './simpleStack';
|
|
14
14
|
import { ctxToText, isWordRange, tokenToWord, } from './textAndWord';
|
|
15
15
|
import { EntityContextType } from './types';
|
|
@@ -224,24 +224,63 @@ function isEntityInScope(entity, rangeEntity) {
|
|
|
224
224
|
rangeStart <= entityStart &&
|
|
225
225
|
rangeStop >= entityStop);
|
|
226
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Parents of a single CTE item. The scope of that CTE is the query that owns this clause.
|
|
229
|
+
*/
|
|
230
|
+
const CTE_CLAUSE_CONTEXT_NAMES = new Set([
|
|
231
|
+
'WithClauseContext',
|
|
232
|
+
'WithStatementContext',
|
|
233
|
+
'CtesContext',
|
|
234
|
+
'WithContext',
|
|
235
|
+
]);
|
|
236
|
+
function normalizeIdent(text) {
|
|
237
|
+
let value = text.trim();
|
|
238
|
+
if ((value.startsWith('`') && value.endsWith('`') && value.length >= 2) ||
|
|
239
|
+
(value.startsWith('"') && value.endsWith('"') && value.length >= 2) ||
|
|
240
|
+
(value.startsWith('[') && value.endsWith(']') && value.length >= 2)) {
|
|
241
|
+
value = value.slice(1, -1);
|
|
242
|
+
}
|
|
243
|
+
return value.toLowerCase();
|
|
244
|
+
}
|
|
245
|
+
function sameIdent(left, right) {
|
|
246
|
+
return normalizeIdent(left) === normalizeIdent(right);
|
|
247
|
+
}
|
|
227
248
|
/**
|
|
228
249
|
* @todo: Handle alias, includes column alias, table alias, query as alias and so on.
|
|
229
250
|
* @todo: [may be need] Combine the entities in each clause.
|
|
230
251
|
*/
|
|
231
252
|
export class EntityCollector {
|
|
232
253
|
constructor(input, allTokens, caretTokenIndex) {
|
|
254
|
+
/**
|
|
255
|
+
* Contexts whose source slice contains a parse error.
|
|
256
|
+
* Error recovery can stretch a rule context across a whole clause, so `ctxToText`
|
|
257
|
+
* would return e.g. `JOIN myOwn\nORDER BY 1` where a name is expected.
|
|
258
|
+
*/
|
|
259
|
+
this._errorContexts = new Set();
|
|
233
260
|
this._input = input;
|
|
234
261
|
this._allTokens = allTokens || [];
|
|
235
262
|
this._caretTokenIndex = caretTokenIndex !== null && caretTokenIndex !== void 0 ? caretTokenIndex : -1;
|
|
236
263
|
this._entitiesSet = new Set();
|
|
237
264
|
this._stmtStack = new SimpleStack();
|
|
238
265
|
this._entityStack = new SimpleStack();
|
|
266
|
+
this._cteDefinitions = [];
|
|
239
267
|
this._rootStmt = null;
|
|
240
268
|
this._caretStmtScopeDepth = 0;
|
|
241
269
|
this._caretStmt = null;
|
|
242
270
|
}
|
|
243
271
|
visitTerminal() { }
|
|
244
|
-
visitErrorNode() {
|
|
272
|
+
visitErrorNode(node) {
|
|
273
|
+
/**
|
|
274
|
+
* Mark every ancestor. An entity pushed on one of them would carry the
|
|
275
|
+
* error-tainted source slice as its `text`. Entities are always pushed from
|
|
276
|
+
* `exit<Rule>` callbacks, so this runs before the context is used.
|
|
277
|
+
*/
|
|
278
|
+
let parent = node.parent;
|
|
279
|
+
while (parent) {
|
|
280
|
+
this._errorContexts.add(parent);
|
|
281
|
+
parent = parent.parent;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
245
284
|
enterEveryRule() { }
|
|
246
285
|
exitEveryRule() { }
|
|
247
286
|
getRootStmt() {
|
|
@@ -254,11 +293,16 @@ export class EntityCollector {
|
|
|
254
293
|
this._entitiesSet.clear();
|
|
255
294
|
this._stmtStack.clear();
|
|
256
295
|
this._entityStack.clear();
|
|
296
|
+
this._cteDefinitions = [];
|
|
257
297
|
this._rootStmt = null;
|
|
298
|
+
this._errorContexts.clear();
|
|
258
299
|
}
|
|
259
300
|
exitProgram() {
|
|
301
|
+
this.finalizeCteDefinitions();
|
|
302
|
+
this.bindCteReferences();
|
|
260
303
|
const entities = Array.from(this._entitiesSet);
|
|
261
304
|
if (this._caretTokenIndex !== -1) {
|
|
305
|
+
this.markCteAccessibility();
|
|
262
306
|
this.attachAccessibleToEntities(entities);
|
|
263
307
|
}
|
|
264
308
|
this._entityStack.clear();
|
|
@@ -376,6 +420,11 @@ export class EntityCollector {
|
|
|
376
420
|
if (!this._entityStack.isEmpty()) {
|
|
377
421
|
this.combineEntitiesAndAdd(stmtContext);
|
|
378
422
|
}
|
|
423
|
+
// Statement-scoped walks never exit program. Finalize once the outermost statement pops.
|
|
424
|
+
if (this._stmtStack.isEmpty()) {
|
|
425
|
+
this.finalizeCteDefinitions();
|
|
426
|
+
this.bindCteReferences();
|
|
427
|
+
}
|
|
379
428
|
// If the current statement contains the caret and its scope depth equals the caret statement's scope depth
|
|
380
429
|
// then set it as the nearest statement containing the caret
|
|
381
430
|
if (stmtContext.isContainCaret &&
|
|
@@ -386,6 +435,9 @@ export class EntityCollector {
|
|
|
386
435
|
return stmtContext;
|
|
387
436
|
}
|
|
388
437
|
pushEntity(ctx, type, attrInfo, metaInfo) {
|
|
438
|
+
// A context containing a parse error has no usable `text`.
|
|
439
|
+
if (this._errorContexts.has(ctx))
|
|
440
|
+
return null;
|
|
389
441
|
const entityContext = toEntityContext(ctx, type, this._input, this._stmtStack.peek(), attrInfo, metaInfo);
|
|
390
442
|
if (entityContext) {
|
|
391
443
|
if (this._stmtStack.isEmpty()) {
|
|
@@ -545,4 +597,253 @@ export class EntityCollector {
|
|
|
545
597
|
}
|
|
546
598
|
return finalEntities;
|
|
547
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* Collect one CTE definition.
|
|
602
|
+
* `endTokenIndex` is the closing token of this item only (not a following CTE).
|
|
603
|
+
* Column names, when present, become the outward schema and do not replace the body query result.
|
|
604
|
+
*/
|
|
605
|
+
collectCte(cteCtx, nameCtx, bodyCtx, endTokenIndex, columnNameCtxs = [], columnListCtx = null) {
|
|
606
|
+
if (!nameCtx || endTokenIndex < 0)
|
|
607
|
+
return;
|
|
608
|
+
const entity = this.pushEntity(nameCtx, EntityContextType.TABLE, undefined, {
|
|
609
|
+
declareType: TableDeclareType.EXPRESSION,
|
|
610
|
+
});
|
|
611
|
+
if (!entity || !isCommonEntityContext(entity))
|
|
612
|
+
return;
|
|
613
|
+
this._cteDefinitions.push({
|
|
614
|
+
entity,
|
|
615
|
+
name: entity.text,
|
|
616
|
+
body: bodyCtx,
|
|
617
|
+
columnNameCtxs,
|
|
618
|
+
columnListCtx,
|
|
619
|
+
endTokenIndex,
|
|
620
|
+
scopeEndTokenIndex: this.cteScopeEndTokenIndex(cteCtx, endTokenIndex),
|
|
621
|
+
outward: null,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
cteScopeEndTokenIndex(cteCtx, endTokenIndex) {
|
|
625
|
+
var _a, _b, _c, _d, _e;
|
|
626
|
+
let scopeEnd = endTokenIndex;
|
|
627
|
+
let current = cteCtx.parent;
|
|
628
|
+
while (current) {
|
|
629
|
+
if (CTE_CLAUSE_CONTEXT_NAMES.has(current.constructor.name)) {
|
|
630
|
+
scopeEnd = (_c = (_b = (_a = current.parent) === null || _a === void 0 ? void 0 : _a.stop) === null || _b === void 0 ? void 0 : _b.tokenIndex) !== null && _c !== void 0 ? _c : endTokenIndex;
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
current = current.parent;
|
|
634
|
+
}
|
|
635
|
+
if (!current) {
|
|
636
|
+
scopeEnd = (_e = (_d = this._stmtStack.peek()) === null || _d === void 0 ? void 0 : _d.position.endTokenIndex) !== null && _e !== void 0 ? _e : endTokenIndex;
|
|
637
|
+
}
|
|
638
|
+
// An incomplete FROM/JOIN stops the parsed query before the caret.
|
|
639
|
+
// Keep the CTE visible until a semicolon or a closing paren leaves this query.
|
|
640
|
+
return this.extendUnclosedScope(scopeEnd);
|
|
641
|
+
}
|
|
642
|
+
extendUnclosedScope(scopeEnd) {
|
|
643
|
+
var _a;
|
|
644
|
+
let depth = 0;
|
|
645
|
+
let end = scopeEnd;
|
|
646
|
+
for (let i = scopeEnd + 1; i < this._allTokens.length; i++) {
|
|
647
|
+
const token = this._allTokens[i];
|
|
648
|
+
if (token.channel === Token.HIDDEN_CHANNEL || token.type === Token.EOF)
|
|
649
|
+
continue;
|
|
650
|
+
const text = (_a = token.text) !== null && _a !== void 0 ? _a : '';
|
|
651
|
+
if (text === ';')
|
|
652
|
+
break;
|
|
653
|
+
if (text === '(') {
|
|
654
|
+
depth++;
|
|
655
|
+
}
|
|
656
|
+
else if (text === ')') {
|
|
657
|
+
if (depth === 0)
|
|
658
|
+
break;
|
|
659
|
+
depth--;
|
|
660
|
+
}
|
|
661
|
+
end = token.tokenIndex;
|
|
662
|
+
}
|
|
663
|
+
return end;
|
|
664
|
+
}
|
|
665
|
+
finalizeCteDefinitions() {
|
|
666
|
+
this._cteDefinitions.forEach((cte) => {
|
|
667
|
+
if (cte.outward) {
|
|
668
|
+
cte.entity.relatedEntities = [cte.outward];
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const fromColumnList = cte.columnListCtx
|
|
672
|
+
? this.createColumnListQueryResultFromTokens(cte.entity.belongStmt, this.columnTokens(cte.columnListCtx))
|
|
673
|
+
: cte.columnNameCtxs.length
|
|
674
|
+
? this.createColumnListQueryResult(cte.entity.belongStmt, cte.columnNameCtxs)
|
|
675
|
+
: null;
|
|
676
|
+
const found = fromColumnList !== null && fromColumnList !== void 0 ? fromColumnList : this.findOutwardQueryResult(cte.body);
|
|
677
|
+
// Column-list results are already a dedicated object. A body query
|
|
678
|
+
// result is shared with the entity set, so dialects may copy it.
|
|
679
|
+
const outward = fromColumnList !== null && fromColumnList !== void 0 ? fromColumnList : (found ? this.adaptOutwardQueryResult(found) : null);
|
|
680
|
+
cte.outward = outward;
|
|
681
|
+
if (outward) {
|
|
682
|
+
cte.entity.relatedEntities = [outward];
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* CTE outward schema defaults to the body query result itself.
|
|
688
|
+
* Override when that result's text covers more than the select list.
|
|
689
|
+
*/
|
|
690
|
+
adaptOutwardQueryResult(entity) {
|
|
691
|
+
return entity;
|
|
692
|
+
}
|
|
693
|
+
sourceSlice(startIndex, endIndex) {
|
|
694
|
+
return this._input.slice(startIndex, endIndex + 1);
|
|
695
|
+
}
|
|
696
|
+
findOutwardQueryResult(body) {
|
|
697
|
+
var _a, _b, _c, _d;
|
|
698
|
+
if (!(body === null || body === void 0 ? void 0 : body.start) || !body.stop)
|
|
699
|
+
return null;
|
|
700
|
+
const start = body.start.tokenIndex;
|
|
701
|
+
const stop = body.stop.tokenIndex;
|
|
702
|
+
let best = null;
|
|
703
|
+
for (const entity of this._entitiesSet) {
|
|
704
|
+
if (entity.entityContextType !== EntityContextType.QUERY_RESULT ||
|
|
705
|
+
!isCommonEntityContext(entity)) {
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
const tokenStart = entity.position.startTokenIndex;
|
|
709
|
+
const tokenStop = entity.position.endTokenIndex;
|
|
710
|
+
if (tokenStart < start || tokenStop > stop)
|
|
711
|
+
continue;
|
|
712
|
+
if (!best) {
|
|
713
|
+
best = entity;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const bestDepth = (_b = (_a = best.belongStmt) === null || _a === void 0 ? void 0 : _a.scopeDepth) !== null && _b !== void 0 ? _b : 0;
|
|
717
|
+
const depth = (_d = (_c = entity.belongStmt) === null || _c === void 0 ? void 0 : _c.scopeDepth) !== null && _d !== void 0 ? _d : 0;
|
|
718
|
+
if (depth < bestDepth ||
|
|
719
|
+
(depth === bestDepth && tokenStart < best.position.startTokenIndex)) {
|
|
720
|
+
best = entity;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return best;
|
|
724
|
+
}
|
|
725
|
+
columnTokens(listCtx) {
|
|
726
|
+
const tokens = [];
|
|
727
|
+
const skip = new Set(['(', ')', ',', '.', '[', ']']);
|
|
728
|
+
const visit = (ctx) => {
|
|
729
|
+
var _a;
|
|
730
|
+
for (let i = 0; i < ctx.getChildCount(); i++) {
|
|
731
|
+
const child = ctx.getChild(i);
|
|
732
|
+
if (child instanceof ParserRuleContext) {
|
|
733
|
+
visit(child);
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (child instanceof TerminalNode) {
|
|
737
|
+
const text = (_a = child.symbol.text) !== null && _a !== void 0 ? _a : '';
|
|
738
|
+
if (text && !skip.has(text))
|
|
739
|
+
tokens.push(child.symbol);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
visit(listCtx);
|
|
744
|
+
return tokens;
|
|
745
|
+
}
|
|
746
|
+
createColumnListQueryResultFromTokens(belongStmt, tokens) {
|
|
747
|
+
const columns = [];
|
|
748
|
+
tokens.forEach((token) => {
|
|
749
|
+
const word = tokenToWord(token, this._input);
|
|
750
|
+
if (!word.text.trim())
|
|
751
|
+
return;
|
|
752
|
+
columns.push({
|
|
753
|
+
entityContextType: EntityContextType.COLUMN,
|
|
754
|
+
text: word.text,
|
|
755
|
+
position: {
|
|
756
|
+
startIndex: word.startIndex,
|
|
757
|
+
endIndex: word.endIndex,
|
|
758
|
+
line: word.line,
|
|
759
|
+
startColumn: word.startColumn,
|
|
760
|
+
endColumn: word.endColumn,
|
|
761
|
+
startTokenIndex: token.tokenIndex,
|
|
762
|
+
endTokenIndex: token.tokenIndex,
|
|
763
|
+
},
|
|
764
|
+
belongStmt,
|
|
765
|
+
declareType: ColumnDeclareType.LITERAL,
|
|
766
|
+
relatedEntities: null,
|
|
767
|
+
[AttrName.colType]: null,
|
|
768
|
+
[AttrName.comment]: null,
|
|
769
|
+
isAccessible: null,
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
return this.wrapOutwardColumns(belongStmt, columns);
|
|
773
|
+
}
|
|
774
|
+
createColumnListQueryResult(belongStmt, columnNameCtxs) {
|
|
775
|
+
const columns = [];
|
|
776
|
+
columnNameCtxs.forEach((nameCtx) => {
|
|
777
|
+
const column = toEntityContext(nameCtx, EntityContextType.COLUMN, this._input, belongStmt, undefined, { declareType: ColumnDeclareType.LITERAL });
|
|
778
|
+
if (column && isColumnEntityContext(column) && column.text.trim()) {
|
|
779
|
+
columns.push(column);
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
return this.wrapOutwardColumns(belongStmt, columns);
|
|
783
|
+
}
|
|
784
|
+
wrapOutwardColumns(belongStmt, columns) {
|
|
785
|
+
if (!columns.length)
|
|
786
|
+
return null;
|
|
787
|
+
const first = columns[0].position;
|
|
788
|
+
const last = columns[columns.length - 1].position;
|
|
789
|
+
return {
|
|
790
|
+
entityContextType: EntityContextType.QUERY_RESULT,
|
|
791
|
+
text: columns.map((column) => column.text).join(', '),
|
|
792
|
+
position: {
|
|
793
|
+
startIndex: first.startIndex,
|
|
794
|
+
endIndex: last.endIndex,
|
|
795
|
+
line: first.line,
|
|
796
|
+
startColumn: first.startColumn,
|
|
797
|
+
endColumn: last.endColumn,
|
|
798
|
+
startTokenIndex: first.startTokenIndex,
|
|
799
|
+
endTokenIndex: last.endTokenIndex,
|
|
800
|
+
},
|
|
801
|
+
belongStmt,
|
|
802
|
+
relatedEntities: null,
|
|
803
|
+
columns,
|
|
804
|
+
[AttrName.comment]: null,
|
|
805
|
+
isAccessible: null,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
bindCteReferences() {
|
|
809
|
+
this._entitiesSet.forEach((entity) => {
|
|
810
|
+
if (entity.entityContextType !== EntityContextType.TABLE ||
|
|
811
|
+
!isCommonEntityContext(entity) ||
|
|
812
|
+
entity.declareType !== TableDeclareType.LITERAL) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
const cte = this.resolveCte(entity.text, entity.position.startTokenIndex);
|
|
816
|
+
if (!cte)
|
|
817
|
+
return;
|
|
818
|
+
entity.declareType = TableDeclareType.EXPRESSION;
|
|
819
|
+
entity.relatedEntities = cte.outward ? [cte.outward] : null;
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
markCteAccessibility() {
|
|
823
|
+
const caretTokenIndex = this.getPrevNonHiddenTokenIndex(this._caretTokenIndex);
|
|
824
|
+
if (!Number.isFinite(caretTokenIndex)) {
|
|
825
|
+
this._cteDefinitions.forEach((cte) => {
|
|
826
|
+
cte.entity.isAccessible = false;
|
|
827
|
+
});
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
this._cteDefinitions.forEach((cte) => {
|
|
831
|
+
cte.entity.isAccessible = this.resolveCte(cte.name, caretTokenIndex) === cte;
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
/** Nearest preceding CTE whose scope covers `tokenIndex`. */
|
|
835
|
+
resolveCte(name, tokenIndex) {
|
|
836
|
+
let winner = null;
|
|
837
|
+
this._cteDefinitions.forEach((cte) => {
|
|
838
|
+
if (!sameIdent(cte.name, name))
|
|
839
|
+
return;
|
|
840
|
+
if (tokenIndex <= cte.endTokenIndex)
|
|
841
|
+
return;
|
|
842
|
+
if (tokenIndex > cte.scopeEndTokenIndex)
|
|
843
|
+
return;
|
|
844
|
+
if (!winner || cte.endTokenIndex > winner.endTokenIndex)
|
|
845
|
+
winner = cte;
|
|
846
|
+
});
|
|
847
|
+
return winner;
|
|
848
|
+
}
|
|
548
849
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AtomExpressionTableContext, AtomFunctionTableContext, CatalogPathContext, CatalogPathCreateContext, ColumnNameCreateContext, CreateCatalogContext, CreateDatabaseContext, CreateFunctionContext, CreateTableContext, CreateViewContext, DatabasePathContext, DatabasePathCreateContext, FunctionNameCreateContext, InsertStatementContext, QueryStatementContext, SelectExpressionColumnNameContext, SelectListContext, SelectLiteralColumnNameContext, SelectWindowItemColumnNameContext, SqlStatementContext, TableAllColumnsContext, TablePathContext, TablePathCreateContext, ViewPathContext, ViewPathCreateContext } from '../../lib/flink/FlinkSqlParser';
|
|
1
|
+
import { AtomExpressionTableContext, AtomFunctionTableContext, CatalogPathContext, CatalogPathCreateContext, ColumnNameCreateContext, CreateCatalogContext, CreateDatabaseContext, CreateFunctionContext, CreateTableContext, CreateViewContext, DatabasePathContext, DatabasePathCreateContext, FunctionNameCreateContext, InsertStatementContext, QueryStatementContext, SelectExpressionColumnNameContext, SelectListContext, SelectLiteralColumnNameContext, SelectWindowItemColumnNameContext, SqlStatementContext, TableAllColumnsContext, TablePathContext, TablePathCreateContext, ViewPathContext, ViewPathCreateContext, WithItemContext } from '../../lib/flink/FlinkSqlParser';
|
|
2
2
|
import { FlinkSqlParserListener } from '../../lib/flink/FlinkSqlParserListener';
|
|
3
3
|
import { EntityCollector } from '../common/entityCollector';
|
|
4
4
|
export declare class FlinkEntityCollector extends EntityCollector implements FlinkSqlParserListener {
|
|
@@ -11,6 +11,7 @@ export declare class FlinkEntityCollector extends EntityCollector implements Fli
|
|
|
11
11
|
exitAtomFunctionTable(ctx: AtomFunctionTableContext): void;
|
|
12
12
|
exitAtomExpressionTable(ctx: AtomExpressionTableContext): void;
|
|
13
13
|
exitTablePathCreate(ctx: TablePathCreateContext): void;
|
|
14
|
+
exitWithItem(ctx: WithItemContext): void;
|
|
14
15
|
exitViewPath(ctx: ViewPathContext): void;
|
|
15
16
|
exitViewPathCreate(ctx: ViewPathCreateContext): void;
|
|
16
17
|
exitFunctionNameCreate(ctx: FunctionNameCreateContext): void;
|
|
@@ -58,6 +58,10 @@ export class FlinkEntityCollector extends EntityCollector {
|
|
|
58
58
|
},
|
|
59
59
|
]);
|
|
60
60
|
}
|
|
61
|
+
exitWithItem(ctx) {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
this.collectCte(ctx, ctx.withItemName(), ctx.queryStatement(), (_b = (_a = ctx.stop) === null || _a === void 0 ? void 0 : _a.tokenIndex) !== null && _b !== void 0 ? _b : -1, ctx.columnName());
|
|
64
|
+
}
|
|
61
65
|
exitViewPath(ctx) {
|
|
62
66
|
this.pushEntity(ctx, EntityContextType.VIEW);
|
|
63
67
|
}
|
|
@@ -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 =
|
|
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: {
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import type { GenericSqlListener } from '../../lib/generic/GenericSqlListener';
|
|
2
|
-
import { AlterTableContext, ColumnDefinitionContext, ColumnRefContext, CreateTableContext, DeleteContext, DropTableContext, InsertContext, QuerySpecificationContext, QueryStatementContext, SelectItemContext, SingleStatementContext, TableNameContext, TableNameCreateContext, UpdateContext } from '../../lib/generic/GenericSqlParser';
|
|
3
|
-
import { EntityCollector } from '../common/entityCollector';
|
|
2
|
+
import { AlterTableContext, ColumnDefinitionContext, ColumnRefContext, CreateTableContext, DeleteContext, DropTableContext, InsertContext, NamedQueryContext, QuerySpecificationContext, QueryStatementContext, SelectItemContext, SingleStatementContext, TableNameContext, TableNameCreateContext, UpdateContext } from '../../lib/generic/GenericSqlParser';
|
|
3
|
+
import { CommonEntityContext, EntityCollector } from '../common/entityCollector';
|
|
4
4
|
export declare class GenericEntityCollector extends EntityCollector implements GenericSqlListener {
|
|
5
|
+
/** Select-list source text for each query result. The result itself keeps the full statement. */
|
|
6
|
+
private readonly selectListText;
|
|
7
|
+
enterProgram(): void;
|
|
5
8
|
/** ====== Entity Begin */
|
|
6
9
|
exitTableName(ctx: TableNameContext): void;
|
|
10
|
+
exitNamedQuery(ctx: NamedQueryContext): void;
|
|
7
11
|
exitTableNameCreate(ctx: TableNameCreateContext): void;
|
|
8
12
|
exitColumnRef(ctx: ColumnRefContext): void;
|
|
9
13
|
exitColumnDefinition(ctx: ColumnDefinitionContext): void;
|
|
10
14
|
exitQuerySpecification(ctx: QuerySpecificationContext): void;
|
|
15
|
+
protected adaptOutwardQueryResult(entity: CommonEntityContext): CommonEntityContext;
|
|
11
16
|
exitSelectItem(ctx: SelectItemContext): void;
|
|
12
17
|
private isSimpleColumnReference;
|
|
13
18
|
/** ===== Statement begin */
|