Skip to content

Commit c97ac12

Browse files
committed
feat(postgresql): add pg expression column
1 parent 82a439e commit c97ac12

File tree

10 files changed

+10219
-9888
lines changed

10 files changed

+10219
-9888
lines changed

src/grammar/postgresql/PostgreSqlParser.g4

+29-26
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,27 @@
11
/*
2-
* [The "MIT license"]
3-
* Copyright (C) 2014 Sam Harwell, Tunnel Vision Laboratories, LLC
4-
*
5-
* Permission is hereby granted, free of charge, to any person obtaining a copy
6-
* of this software and associated documentation files (the "Software"), to deal
7-
* in the Software without restriction, including without limitation the rights
8-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9-
* copies of the Software, and to permit persons to whom the Software is
2+
* [The "MIT license"] Copyright (C) 2014 Sam Harwell, Tunnel Vision Laboratories, LLC
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
5+
* associated documentation files (the "Software"), to deal in the Software without restriction,
6+
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
7+
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
108
* furnished to do so, subject to the following conditions:
11-
*
12-
* 1. The above copyright notice and this permission notice shall be included in
13-
* all copies or substantial portions of the Software.
14-
* 2. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17-
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19-
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20-
* DEALINGS IN THE SOFTWARE.
21-
* 3. Except as contained in this notice, the name of Tunnel Vision
22-
* Laboratories, LLC. shall not be used in advertising or otherwise to
23-
* promote the sale, use or other dealings in this Software without prior
24-
* written authorization from Tunnel Vision Laboratories, LLC.
9+
*
10+
* 1. The above copyright notice and this permission notice shall be included in all copies or
11+
* substantial portions of the Software. 2. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
12+
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
14+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
15+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16+
* DEALINGS IN THE SOFTWARE. 3. Except as contained in this notice, the name of Tunnel Vision
17+
* Laboratories, LLC. shall not be used in advertising or otherwise to promote the sale, use or
18+
* other dealings in this Software without prior written authorization from Tunnel Vision
19+
* Laboratories, LLC.
2520
*/
2621

2722
/**
28-
* This file is an adaptation of antlr's sql/postgresql/PostgreSQLParser.g4 grammar.
29-
* Reference: https://github.com/antlr/grammars-v4/blob/master/sql/postgresql/PostgreSQLParser.g4
23+
* This file is an adaptation of antlr's sql/postgresql/PostgreSQLParser.g4 grammar. Reference:
24+
* https://github.com/antlr/grammars-v4/blob/master/sql/postgresql/PostgreSQLParser.g4
3025
*/
3126

3227
/**
@@ -2409,7 +2404,7 @@ primaryExpression
24092404
| explicit_row
24102405
| OPEN_PAREN expression COMMA expr_list CLOSE_PAREN
24112406
| row KW_OVERLAPS row
2412-
| qualified_name
2407+
| column_name_path
24132408
| primaryExpression TYPECAST typename
24142409
| (PLUS | MINUS) primaryExpression
24152410
| primaryExpression qual_op primaryExpression?
@@ -2504,6 +2499,10 @@ window_clause
25042499
: KW_WINDOW window_definition (COMMA window_definition)*
25052500
;
25062501

2502+
having_clause
2503+
: KW_HAVING expression
2504+
;
2505+
25072506
window_definition
25082507
: colid KW_AS window_specification
25092508
;
@@ -2743,6 +2742,10 @@ column_name
27432742
| {this.shouldMatchEmpty()}? # columnNameMatch
27442743
;
27452744

2745+
column_name_path
2746+
: colid opt_indirection
2747+
;
2748+
27462749
column_name_create
27472750
: colid # columnNameCreate
27482751
;
@@ -3631,5 +3634,5 @@ any_identifier
36313634
;
36323635

36333636
sql_expression
3634-
: target_list? into_clause? from_clause? where_clause? group_clause? (KW_HAVING expression)? window_clause?
3637+
: target_list? into_clause? from_clause? where_clause? group_clause? having_clause? window_clause?
36353638
;

src/lib/postgresql/PostgreSqlParser.interp

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

src/lib/postgresql/PostgreSqlParser.ts

+9,978-9,859
Large diffs are not rendered by default.

src/lib/postgresql/PostgreSqlParserListener.ts

+22
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ import { Document_or_contentContext } from "./PostgreSqlParser.js";
374374
import { Xmlexists_argumentContext } from "./PostgreSqlParser.js";
375375
import { Xml_passing_mechContext } from "./PostgreSqlParser.js";
376376
import { Window_clauseContext } from "./PostgreSqlParser.js";
377+
import { Having_clauseContext } from "./PostgreSqlParser.js";
377378
import { Window_definitionContext } from "./PostgreSqlParser.js";
378379
import { Over_clauseContext } from "./PostgreSqlParser.js";
379380
import { Window_specificationContext } from "./PostgreSqlParser.js";
@@ -425,6 +426,7 @@ import { ProcedureNameContext } from "./PostgreSqlParser.js";
425426
import { ProcedureNameCreateContext } from "./PostgreSqlParser.js";
426427
import { ColumnNameContext } from "./PostgreSqlParser.js";
427428
import { ColumnNameMatchContext } from "./PostgreSqlParser.js";
429+
import { Column_name_pathContext } from "./PostgreSqlParser.js";
428430
import { ColumnNameCreateContext } from "./PostgreSqlParser.js";
429431
import { FunctionNameCreateContext } from "./PostgreSqlParser.js";
430432
import { FunctionNameContext } from "./PostgreSqlParser.js";
@@ -4209,6 +4211,16 @@ export class PostgreSqlParserListener implements ParseTreeListener {
42094211
* @param ctx the parse tree
42104212
*/
42114213
exitWindow_clause?: (ctx: Window_clauseContext) => void;
4214+
/**
4215+
* Enter a parse tree produced by `PostgreSqlParser.having_clause`.
4216+
* @param ctx the parse tree
4217+
*/
4218+
enterHaving_clause?: (ctx: Having_clauseContext) => void;
4219+
/**
4220+
* Exit a parse tree produced by `PostgreSqlParser.having_clause`.
4221+
* @param ctx the parse tree
4222+
*/
4223+
exitHaving_clause?: (ctx: Having_clauseContext) => void;
42124224
/**
42134225
* Enter a parse tree produced by `PostgreSqlParser.window_definition`.
42144226
* @param ctx the parse tree
@@ -4753,6 +4765,16 @@ export class PostgreSqlParserListener implements ParseTreeListener {
47534765
* @param ctx the parse tree
47544766
*/
47554767
exitColumnNameMatch?: (ctx: ColumnNameMatchContext) => void;
4768+
/**
4769+
* Enter a parse tree produced by `PostgreSqlParser.column_name_path`.
4770+
* @param ctx the parse tree
4771+
*/
4772+
enterColumn_name_path?: (ctx: Column_name_pathContext) => void;
4773+
/**
4774+
* Exit a parse tree produced by `PostgreSqlParser.column_name_path`.
4775+
* @param ctx the parse tree
4776+
*/
4777+
exitColumn_name_path?: (ctx: Column_name_pathContext) => void;
47564778
/**
47574779
* Enter a parse tree produced by the `columnNameCreate`
47584780
* labeled alternative in `PostgreSqlParser.column_name_create`.

src/lib/postgresql/PostgreSqlParserVisitor.ts

+14
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ import { Document_or_contentContext } from "./PostgreSqlParser.js";
374374
import { Xmlexists_argumentContext } from "./PostgreSqlParser.js";
375375
import { Xml_passing_mechContext } from "./PostgreSqlParser.js";
376376
import { Window_clauseContext } from "./PostgreSqlParser.js";
377+
import { Having_clauseContext } from "./PostgreSqlParser.js";
377378
import { Window_definitionContext } from "./PostgreSqlParser.js";
378379
import { Over_clauseContext } from "./PostgreSqlParser.js";
379380
import { Window_specificationContext } from "./PostgreSqlParser.js";
@@ -425,6 +426,7 @@ import { ProcedureNameContext } from "./PostgreSqlParser.js";
425426
import { ProcedureNameCreateContext } from "./PostgreSqlParser.js";
426427
import { ColumnNameContext } from "./PostgreSqlParser.js";
427428
import { ColumnNameMatchContext } from "./PostgreSqlParser.js";
429+
import { Column_name_pathContext } from "./PostgreSqlParser.js";
428430
import { ColumnNameCreateContext } from "./PostgreSqlParser.js";
429431
import { FunctionNameCreateContext } from "./PostgreSqlParser.js";
430432
import { FunctionNameContext } from "./PostgreSqlParser.js";
@@ -2722,6 +2724,12 @@ export class PostgreSqlParserVisitor<Result> extends AbstractParseTreeVisitor<Re
27222724
* @return the visitor result
27232725
*/
27242726
visitWindow_clause?: (ctx: Window_clauseContext) => Result;
2727+
/**
2728+
* Visit a parse tree produced by `PostgreSqlParser.having_clause`.
2729+
* @param ctx the parse tree
2730+
* @return the visitor result
2731+
*/
2732+
visitHaving_clause?: (ctx: Having_clauseContext) => Result;
27252733
/**
27262734
* Visit a parse tree produced by `PostgreSqlParser.window_definition`.
27272735
* @param ctx the parse tree
@@ -3045,6 +3053,12 @@ export class PostgreSqlParserVisitor<Result> extends AbstractParseTreeVisitor<Re
30453053
* @return the visitor result
30463054
*/
30473055
visitColumnNameMatch?: (ctx: ColumnNameMatchContext) => Result;
3056+
/**
3057+
* Visit a parse tree produced by `PostgreSqlParser.column_name_path`.
3058+
* @param ctx the parse tree
3059+
* @return the visitor result
3060+
*/
3061+
visitColumn_name_path?: (ctx: Column_name_pathContext) => Result;
30483062
/**
30493063
* Visit a parse tree produced by the `columnNameCreate`
30503064
* labeled alternative in `PostgreSqlParser.column_name_create`.

src/parser/postgresql/index.ts

+15
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export class PostgreSQL extends BasicSQL<PostgreSqlLexer, ProgramContext, Postgr
3737
PostgreSqlParser.RULE_procedure_name, // procedure name
3838
PostgreSqlParser.RULE_column_name_create, // column name that will be created
3939
PostgreSqlParser.RULE_column_name, // column name
40+
PostgreSqlParser.RULE_column_name_path, // column name
4041
]);
4142

4243
protected get splitListener() {
@@ -126,6 +127,20 @@ export class PostgreSQL extends BasicSQL<PostgreSqlLexer, ProgramContext, Postgr
126127
syntaxContextType = EntityContextType.COLUMN;
127128
break;
128129
}
130+
case PostgreSqlParser.RULE_column_name_path: {
131+
if (
132+
candidateRule.ruleList.includes(PostgreSqlParser.RULE_group_clause) ||
133+
candidateRule.ruleList.includes(PostgreSqlParser.RULE_sort_clause) ||
134+
candidateRule.ruleList.includes(PostgreSqlParser.RULE_limit_clause) ||
135+
candidateRule.ruleList.includes(PostgreSqlParser.RULE_where_clause) ||
136+
candidateRule.ruleList.includes(PostgreSqlParser.RULE_having_clause) ||
137+
candidateRule.ruleList.includes(PostgreSqlParser.RULE_window_clause) ||
138+
candidateRule.ruleList.includes(PostgreSqlParser.RULE_triggerwhen)
139+
) {
140+
syntaxContextType = EntityContextType.COLUMN;
141+
}
142+
break;
143+
}
129144
default:
130145
break;
131146
}

src/parser/postgresql/postgreErrorListener.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export class PostgreSqlErrorListener extends ParseErrorListener {
1414
[PostgreSqlParser.RULE_function_name, 'function'],
1515
[PostgreSqlParser.RULE_function_name_create, 'function'],
1616
[PostgreSqlParser.RULE_column_name, 'column'],
17+
[PostgreSqlParser.RULE_column_name_path, 'column'],
1718
[PostgreSqlParser.RULE_column_name_create, 'column'],
1819
[PostgreSqlParser.RULE_schema_name_create, 'schema'],
1920
[PostgreSqlParser.RULE_schema_name, 'schema'],
@@ -63,8 +64,11 @@ export class PostgreSqlErrorListener extends ParseErrorListener {
6364
case PostgreSqlParser.RULE_view_name:
6465
case PostgreSqlParser.RULE_database_name:
6566
case PostgreSqlParser.RULE_procedure_name:
66-
case PostgreSqlParser.RULE_column_name: {
67-
result.push(`{existing}${name}`);
67+
case PostgreSqlParser.RULE_column_name:
68+
case PostgreSqlParser.RULE_column_name_path: {
69+
if (!result.includes(`{existing}${name}`)) {
70+
result.push(`{existing}${name}`);
71+
}
6872
break;
6973
}
7074
case PostgreSqlParser.RULE_table_name_create:

test/parser/postgresql/errorListener.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const sql1 = `ALTER EVENT`;
55
const sql2 = `CREATE FUNCTION `;
66
const sql3 = `SELECT name, altitude FROM ONLY cities WHERE `;
77
const sql4 = `DROP PROCEDURE name1 a`;
8+
const sql5 = `SELECT * FROM db.tbs GROUP BY sum( `;
89

910
describe('PostgreSQL validate invalid sql and test msg', () => {
1011
const pgSQL = new PostgreSQL();
@@ -45,6 +46,14 @@ describe('PostgreSQL validate invalid sql and test msg', () => {
4546
);
4647
});
4748

49+
test('validate unComplete sql5', () => {
50+
const errors = pgSQL.validate(sql5);
51+
expect(errors.length).toBe(1);
52+
expect(errors[0].message).toBe(
53+
`Statement is incomplete, expecting an existing column or an existing function or a keyword`
54+
);
55+
});
56+
4857
test('validate random text cn', () => {
4958
pgSQL.locale = 'zh_CN';
5059
const errors = pgSQL.validate(randomText);

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

+10
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,13 @@ SELECT * FROM db.tbs GROUP BY (col1, col2) ORDER BY col3;
7979
TRUNCATE TABLE ;
8080

8181
TRUNCATE TABLE t1;
82+
83+
SELECT * FROM db.tbs GROUP BY sum(length(col1+col2)) ORDER BY length(sum(col1/clo2));
84+
85+
VALUES (1, '3'), (3, 'sdsd') ORDER BY sort_expression ASC LIMIT id = 1;
86+
87+
CREATE OR REPLACE RULE name AS ON SELECT TO table_name WHERE length(y+x) = 3 DO INSTEAD NOTHING;
88+
89+
WITH query_name (id) AS (SELECT id FROM table_expression) SELECT DISTINCT ON (col1) random() AS name1 FROM table_expression WHERE name1=name1 GROUP BY id HAVING sum(len+y) < interval '5 hours' WINDOW w AS (PARTITION BY depname ORDER BY salary DESC) EXCEPT (SELECT * FROM others) ORDER BY salary USING > NULLS FIRST OFFSET start FETCH NEXT ROW ONLY FOR KEY SHARE OF table_name NOWAIT;
90+
91+
CREATE CONSTRAINT TRIGGER trig_name INSTEAD OF INSERT OR UPDATE ON table_name FROM referenced_table_name WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE PROCEDURE function_name ();

0 commit comments

Comments
 (0)