Skip to content

Commit 84c1008

Browse files
committed
feat(flink): add flink expression column
1 parent d613f21 commit 84c1008

File tree

10 files changed

+2910
-2707
lines changed

10 files changed

+2910
-2707
lines changed

src/grammar/flink/FlinkSqlParser.g4

+12-8
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ columnName
189189
| {this.shouldMatchEmpty()}?
190190
;
191191

192+
columnNamePath
193+
: uid
194+
;
195+
192196
columnNameList
193197
: LR_BRACKET columnName (COMMA columnName)* RR_BRACKET
194198
;
@@ -451,9 +455,9 @@ queryStatement
451455
: valuesCaluse
452456
| withClause queryStatement
453457
| LR_BRACKET queryStatement RR_BRACKET
454-
| left=queryStatement operator=(KW_INTERSECT | KW_UNION | KW_EXCEPT) KW_ALL? right=queryStatement orderByCaluse? limitClause?
455-
| selectClause orderByCaluse? limitClause?
456-
| selectStatement orderByCaluse? limitClause?
458+
| left=queryStatement operator=(KW_INTERSECT | KW_UNION | KW_EXCEPT) KW_ALL? right=queryStatement orderByClause? limitClause?
459+
| selectClause orderByClause? limitClause?
460+
| selectStatement orderByClause? limitClause?
457461
;
458462

459463
valuesCaluse
@@ -626,15 +630,15 @@ namedWindow
626630
;
627631

628632
windowSpec
629-
: name=errorCapturingIdentifier? LR_BRACKET partitionByClause? orderByCaluse? windowFrame? RR_BRACKET
633+
: name=errorCapturingIdentifier? LR_BRACKET partitionByClause? orderByClause? windowFrame? RR_BRACKET
630634
;
631635

632636
matchRecognizeClause
633-
: KW_MATCH_RECOGNIZE LR_BRACKET partitionByClause? orderByCaluse? measuresClause? outputMode? afterMatchStrategy? patternDefination?
637+
: KW_MATCH_RECOGNIZE LR_BRACKET partitionByClause? orderByClause? measuresClause? outputMode? afterMatchStrategy? patternDefination?
634638
patternVariablesDefination RR_BRACKET (KW_AS? identifier)?
635639
;
636640

637-
orderByCaluse
641+
orderByClause
638642
: KW_ORDER KW_BY orderItemDefition (COMMA orderItemDefition)*
639643
;
640644

@@ -763,7 +767,7 @@ primaryExpression
763767
// | identifier '->' expression #lambda
764768
// | '(' identifier (',' identifier)+ ')' '->' expression #lambda
765769
| value=primaryExpression LS_BRACKET index=valueExpression RS_BRACKET # subscript
766-
| identifier # columnReference
770+
| columnNamePath # columnReference
767771
| dereferenceDefinition # dereference
768772
| LR_BRACKET expression RR_BRACKET # parenthesizedExpression
769773
| KW_CURRENT_TIMESTAMP # dateFunctionExpression
@@ -1216,4 +1220,4 @@ nonReservedKeywords
12161220
| KW_WEEK
12171221
| KW_YEARS
12181222
| KW_ZONE
1219-
;
1223+
;

src/lib/flink/FlinkSqlParser.interp

+3-2
Large diffs are not rendered by default.

src/lib/flink/FlinkSqlParser.ts

+2,744-2,688
Large diffs are not rendered by default.

src/lib/flink/FlinkSqlParserListener.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { ColumnOptionDefinitionContext } from "./FlinkSqlParser.js";
3535
import { PhysicalColumnDefinitionContext } from "./FlinkSqlParser.js";
3636
import { ColumnNameCreateContext } from "./FlinkSqlParser.js";
3737
import { ColumnNameContext } from "./FlinkSqlParser.js";
38+
import { ColumnNamePathContext } from "./FlinkSqlParser.js";
3839
import { ColumnNameListContext } from "./FlinkSqlParser.js";
3940
import { ColumnTypeContext } from "./FlinkSqlParser.js";
4041
import { LengthOneDimensionContext } from "./FlinkSqlParser.js";
@@ -123,7 +124,7 @@ import { WindowClauseContext } from "./FlinkSqlParser.js";
123124
import { NamedWindowContext } from "./FlinkSqlParser.js";
124125
import { WindowSpecContext } from "./FlinkSqlParser.js";
125126
import { MatchRecognizeClauseContext } from "./FlinkSqlParser.js";
126-
import { OrderByCaluseContext } from "./FlinkSqlParser.js";
127+
import { OrderByClauseContext } from "./FlinkSqlParser.js";
127128
import { OrderItemDefitionContext } from "./FlinkSqlParser.js";
128129
import { LimitClauseContext } from "./FlinkSqlParser.js";
129130
import { PartitionByClauseContext } from "./FlinkSqlParser.js";
@@ -497,6 +498,16 @@ export class FlinkSqlParserListener implements ParseTreeListener {
497498
* @param ctx the parse tree
498499
*/
499500
exitColumnName?: (ctx: ColumnNameContext) => void;
501+
/**
502+
* Enter a parse tree produced by `FlinkSqlParser.columnNamePath`.
503+
* @param ctx the parse tree
504+
*/
505+
enterColumnNamePath?: (ctx: ColumnNamePathContext) => void;
506+
/**
507+
* Exit a parse tree produced by `FlinkSqlParser.columnNamePath`.
508+
* @param ctx the parse tree
509+
*/
510+
exitColumnNamePath?: (ctx: ColumnNamePathContext) => void;
500511
/**
501512
* Enter a parse tree produced by `FlinkSqlParser.columnNameList`.
502513
* @param ctx the parse tree
@@ -1382,15 +1393,15 @@ export class FlinkSqlParserListener implements ParseTreeListener {
13821393
*/
13831394
exitMatchRecognizeClause?: (ctx: MatchRecognizeClauseContext) => void;
13841395
/**
1385-
* Enter a parse tree produced by `FlinkSqlParser.orderByCaluse`.
1396+
* Enter a parse tree produced by `FlinkSqlParser.orderByClause`.
13861397
* @param ctx the parse tree
13871398
*/
1388-
enterOrderByCaluse?: (ctx: OrderByCaluseContext) => void;
1399+
enterOrderByClause?: (ctx: OrderByClauseContext) => void;
13891400
/**
1390-
* Exit a parse tree produced by `FlinkSqlParser.orderByCaluse`.
1401+
* Exit a parse tree produced by `FlinkSqlParser.orderByClause`.
13911402
* @param ctx the parse tree
13921403
*/
1393-
exitOrderByCaluse?: (ctx: OrderByCaluseContext) => void;
1404+
exitOrderByClause?: (ctx: OrderByClauseContext) => void;
13941405
/**
13951406
* Enter a parse tree produced by `FlinkSqlParser.orderItemDefition`.
13961407
* @param ctx the parse tree

src/lib/flink/FlinkSqlParserVisitor.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { ColumnOptionDefinitionContext } from "./FlinkSqlParser.js";
3535
import { PhysicalColumnDefinitionContext } from "./FlinkSqlParser.js";
3636
import { ColumnNameCreateContext } from "./FlinkSqlParser.js";
3737
import { ColumnNameContext } from "./FlinkSqlParser.js";
38+
import { ColumnNamePathContext } from "./FlinkSqlParser.js";
3839
import { ColumnNameListContext } from "./FlinkSqlParser.js";
3940
import { ColumnTypeContext } from "./FlinkSqlParser.js";
4041
import { LengthOneDimensionContext } from "./FlinkSqlParser.js";
@@ -123,7 +124,7 @@ import { WindowClauseContext } from "./FlinkSqlParser.js";
123124
import { NamedWindowContext } from "./FlinkSqlParser.js";
124125
import { WindowSpecContext } from "./FlinkSqlParser.js";
125126
import { MatchRecognizeClauseContext } from "./FlinkSqlParser.js";
126-
import { OrderByCaluseContext } from "./FlinkSqlParser.js";
127+
import { OrderByClauseContext } from "./FlinkSqlParser.js";
127128
import { OrderItemDefitionContext } from "./FlinkSqlParser.js";
128129
import { LimitClauseContext } from "./FlinkSqlParser.js";
129130
import { PartitionByClauseContext } from "./FlinkSqlParser.js";
@@ -392,6 +393,12 @@ export class FlinkSqlParserVisitor<Result> extends AbstractParseTreeVisitor<Resu
392393
* @return the visitor result
393394
*/
394395
visitColumnName?: (ctx: ColumnNameContext) => Result;
396+
/**
397+
* Visit a parse tree produced by `FlinkSqlParser.columnNamePath`.
398+
* @param ctx the parse tree
399+
* @return the visitor result
400+
*/
401+
visitColumnNamePath?: (ctx: ColumnNamePathContext) => Result;
395402
/**
396403
* Visit a parse tree produced by `FlinkSqlParser.columnNameList`.
397404
* @param ctx the parse tree
@@ -923,11 +930,11 @@ export class FlinkSqlParserVisitor<Result> extends AbstractParseTreeVisitor<Resu
923930
*/
924931
visitMatchRecognizeClause?: (ctx: MatchRecognizeClauseContext) => Result;
925932
/**
926-
* Visit a parse tree produced by `FlinkSqlParser.orderByCaluse`.
933+
* Visit a parse tree produced by `FlinkSqlParser.orderByClause`.
927934
* @param ctx the parse tree
928935
* @return the visitor result
929936
*/
930-
visitOrderByCaluse?: (ctx: OrderByCaluseContext) => Result;
937+
visitOrderByClause?: (ctx: OrderByClauseContext) => Result;
931938
/**
932939
* Visit a parse tree produced by `FlinkSqlParser.orderItemDefition`.
933940
* @param ctx the parse tree

src/parser/flink/flinkErrorListener.ts

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class FlinkErrorListener extends ParseErrorListener {
1919
[FlinkSqlParser.RULE_functionName, 'function'],
2020
[FlinkSqlParser.RULE_functionNameCreate, 'function'],
2121
[FlinkSqlParser.RULE_columnName, 'column'],
22+
[FlinkSqlParser.RULE_columnNamePath, 'column'],
2223
[FlinkSqlParser.RULE_columnNameCreate, 'column'],
2324
]);
2425

@@ -51,6 +52,7 @@ export class FlinkErrorListener extends ParseErrorListener {
5152
case FlinkSqlParser.RULE_viewPath:
5253
case FlinkSqlParser.RULE_functionName:
5354
case FlinkSqlParser.RULE_columnName:
55+
case FlinkSqlParser.RULE_columnNamePath:
5456
case FlinkSqlParser.RULE_catalogPath: {
5557
result.push(`{existing}${name}`);
5658
break;

src/parser/flink/index.ts

+14
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export class FlinkSQL extends BasicSQL<FlinkSqlLexer, ProgramContext, FlinkSqlPa
3232
FlinkSqlParser.RULE_functionName, // functionName
3333
FlinkSqlParser.RULE_functionNameCreate, // functionName that will be created
3434
FlinkSqlParser.RULE_columnName,
35+
FlinkSqlParser.RULE_columnNamePath,
3536
FlinkSqlParser.RULE_columnNameCreate,
3637
]);
3738

@@ -110,6 +111,19 @@ export class FlinkSQL extends BasicSQL<FlinkSqlLexer, ProgramContext, FlinkSqlPa
110111
syntaxContextType = EntityContextType.COLUMN_CREATE;
111112
break;
112113
}
114+
case FlinkSqlParser.RULE_columnNamePath: {
115+
if (
116+
candidateRule.ruleList.includes(FlinkSqlParser.RULE_selectClause) ||
117+
candidateRule.ruleList.includes(FlinkSqlParser.RULE_whereClause) ||
118+
candidateRule.ruleList.includes(FlinkSqlParser.RULE_groupByClause) ||
119+
candidateRule.ruleList.includes(FlinkSqlParser.RULE_limitClause) ||
120+
candidateRule.ruleList.includes(FlinkSqlParser.RULE_whenClause) ||
121+
candidateRule.ruleList.includes(FlinkSqlParser.RULE_havingClause)
122+
) {
123+
syntaxContextType = EntityContextType.COLUMN;
124+
}
125+
break;
126+
}
113127
default:
114128
break;
115129
}

test/parser/flink/errorListener.test.ts

+17
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const sql3 = `DROP VIEW IF EXIsST aaa aaa`;
77
const sql4 = `SELECT * froma aaa`;
88
const sql5 = `CREATE VIEW `;
99
const sql6 = `DROP CATALOG `;
10+
const sql7 = `SELECT SUM(amount) FROM Orders GROUP BY length(users) HAVING SUM( `;
1011

1112
describe('FlinkSQL validate invalid sql and test msg', () => {
1213
const flink = new FlinkSQL();
@@ -64,6 +65,14 @@ describe('FlinkSQL validate invalid sql and test msg', () => {
6465
);
6566
});
6667

68+
test('validate unComplete sql7', () => {
69+
const errors = flink.validate(sql7);
70+
expect(errors.length).toBe(1);
71+
expect(errors[0].message).toBe(
72+
`Statement is incomplete, expecting an existing function or an existing column or a keyword`
73+
);
74+
});
75+
6776
test('validate random text cn', () => {
6877
flink.locale = 'zh_CN';
6978
const errors = flink.validate(randomText);
@@ -97,4 +106,12 @@ describe('FlinkSQL validate invalid sql and test msg', () => {
97106
expect(errors.length).toBe(1);
98107
expect(errors[0].message).toBe(`'aaa' 在此位置无效,期望一个存在的column或者一个关键字`);
99108
});
109+
110+
test('validate unComplete sql7 cn', () => {
111+
const errors = flink.validate(sql7);
112+
expect(errors.length).toBe(1);
113+
expect(errors[0].message).toBe(
114+
`语句不完整,期望一个存在的function或者一个存在的column或者一个关键字`
115+
);
116+
});
100117
});

test/parser/flink/suggestion/fixtures/syntaxSuggestion.sql

+7-1
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,10 @@ INSERT INTO tb (col, tb.c );
3838

3939
CREATE TABLE yourTable (ts TIMESTAMP(3), WATERMARK FOR );
4040

41-
CREATE TABLE newTable ( );
41+
CREATE TABLE newTable ( );
42+
43+
SELECT SUM(amount) FROM Orders GROUP BY length(users) HAVING SUM(amount) > 50;
44+
45+
SELECT * FROM Orders ORDER BY orderTime LIMIT length(order_id);
46+
47+
SELECT age CASE WHEN age < 18 THEN 1 ELSE 0 END AS is_minor FROM dt_catalog.dt_db.subscriptions;

test/parser/flink/suggestion/syntaxSuggestion.test.ts

+85
Original file line numberDiff line numberDiff line change
@@ -374,4 +374,89 @@ describe('Flink SQL Syntax Suggestion', () => {
374374
expect(suggestion).not.toBeUndefined();
375375
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]);
376376
});
377+
378+
test('Select expression column', () => {
379+
const pos: CaretPosition = {
380+
lineNumber: 43,
381+
column: 18,
382+
};
383+
const syntaxes = flink.getSuggestionAtCaretPosition(
384+
commentOtherLine(syntaxSql, pos.lineNumber),
385+
pos
386+
)?.syntax;
387+
const suggestion = syntaxes?.find(
388+
(syn) => syn.syntaxContextType === EntityContextType.COLUMN
389+
);
390+
391+
expect(suggestion).not.toBeUndefined();
392+
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['amount']);
393+
});
394+
395+
test('Group by expression column', () => {
396+
const pos: CaretPosition = {
397+
lineNumber: 43,
398+
column: 53,
399+
};
400+
const syntaxes = flink.getSuggestionAtCaretPosition(
401+
commentOtherLine(syntaxSql, pos.lineNumber),
402+
pos
403+
)?.syntax;
404+
const suggestion = syntaxes?.find(
405+
(syn) => syn.syntaxContextType === EntityContextType.COLUMN
406+
);
407+
408+
expect(suggestion).not.toBeUndefined();
409+
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['users']);
410+
});
411+
412+
test('Having expression column', () => {
413+
const pos: CaretPosition = {
414+
lineNumber: 43,
415+
column: 72,
416+
};
417+
const syntaxes = flink.getSuggestionAtCaretPosition(
418+
commentOtherLine(syntaxSql, pos.lineNumber),
419+
pos
420+
)?.syntax;
421+
const suggestion = syntaxes?.find(
422+
(syn) => syn.syntaxContextType === EntityContextType.COLUMN
423+
);
424+
425+
expect(suggestion).not.toBeUndefined();
426+
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['amount']);
427+
});
428+
429+
test('Limit by expression column', () => {
430+
const pos: CaretPosition = {
431+
lineNumber: 45,
432+
column: 62,
433+
};
434+
const syntaxes = flink.getSuggestionAtCaretPosition(
435+
commentOtherLine(syntaxSql, pos.lineNumber),
436+
pos
437+
)?.syntax;
438+
const suggestion = syntaxes?.find(
439+
(syn) => syn.syntaxContextType === EntityContextType.COLUMN
440+
);
441+
442+
expect(suggestion).not.toBeUndefined();
443+
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['order_id']);
444+
});
445+
446+
test('When by expression column', () => {
447+
const pos: CaretPosition = {
448+
lineNumber: 47,
449+
column: 25,
450+
};
451+
const syntaxes = flink.getSuggestionAtCaretPosition(
452+
commentOtherLine(syntaxSql, pos.lineNumber),
453+
pos
454+
)?.syntax;
455+
const suggestion = syntaxes?.find(
456+
(syn) => syn.syntaxContextType === EntityContextType.COLUMN
457+
);
458+
459+
expect(suggestion).not.toBeUndefined();
460+
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['age']);
461+
});
377462
});

0 commit comments

Comments
 (0)