Skip to content

Commit dbb9781

Browse files
authored
parser: upgrade grammar with various fixes (#716)
1 parent 23a9a1c commit dbb9781

File tree

134 files changed

+16907
-9710
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+16907
-9710
lines changed

PLAN.md

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,15 @@ support SQL embedded in other languages
4747

4848
parse and warn, helps with copy pasting examples
4949

50+
https://www.enterprisedb.com/blog/psqls-scripting-language-turing-complete-or-fibonacci-psql
51+
5052
### Other Dialects
5153

5254
support Trino, BigQuery, Aurora DSLQ, etc.
5355

54-
### Check `format()` calls
56+
### Validations
57+
58+
#### Check `format()` calls
5559

5660
https://www.postgresql.org/docs/18/functions-string.html#FUNCTIONS-STRING-FORMAT
5761

@@ -63,6 +67,47 @@ SELECT format('Hello %s %s', 'World');
6367
-- error ^^
6468
```
6569

70+
#### Warn about invalid column notation
71+
72+
```sql
73+
with t as (select 1 as data)
74+
select (data).id from t;
75+
```
76+
77+
postgres says:
78+
79+
```
80+
Query 1 ERROR at Line 40: : ERROR: column notation .id applied to type integer, which is not a composite type
81+
LINE 2: select (data).id from t;
82+
^
83+
```
84+
85+
#### Warn about invalid missing column notation
86+
87+
```sql
88+
create type f as (
89+
id integer,
90+
name text
91+
);
92+
with t as (select (1, 'a')::f as data)
93+
select data.id from t;
94+
```
95+
96+
postgres says:
97+
98+
```
99+
Query 1 ERROR at Line 41: : ERROR: missing FROM-clause entry for table "data"
100+
LINE 2: select data.id from t;
101+
^
102+
```
103+
104+
we should suggest an autofix to:
105+
106+
```sql
107+
with t as (select (1, 'a')::f as data)
108+
select (data).id from t;
109+
```
110+
66111
### Formatter
67112

68113
```shell
@@ -137,6 +182,10 @@ sql for benchmarks maybe?
137182

138183
https://github.com/cedardb/DOOMQL
139184

185+
- rrule_plpgsql
186+
187+
https://github.com/sirrodgepodge/rrule_plpgsql
188+
140189
### CLI
141190

142191
from `deno`
@@ -589,7 +638,7 @@ via: https://www.postgresql.org/docs/17/sql-createview.html
589638
590639
https://www.postgresql.org/docs/17/sql-dropgroup.html
591640
592-
### Rule: `create table as` to `select into`
641+
### Rule: `select into` to `create table as`
593642
594643
```sql
595644
SELECT * INTO films_recent FROM films WHERE date_prod >= '2002-01-01';

crates/squawk_ide/src/expand_selection.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,19 @@ const DELIMITED_LIST_KINDS: &[SyntaxKind] = &[
4040
SyntaxKind::COLUMN_LIST,
4141
SyntaxKind::CONFLICT_INDEX_ITEM_LIST,
4242
SyntaxKind::CONSTRAINT_EXCLUSION_LIST,
43+
SyntaxKind::DROP_OP_CLASS_OPTION_LIST,
44+
SyntaxKind::FDW_OPTION_LIST,
4345
SyntaxKind::FUNCTION_SIG_LIST,
4446
SyntaxKind::GROUP_BY_LIST,
4547
SyntaxKind::JSON_TABLE_COLUMN_LIST,
48+
SyntaxKind::OPERATOR_CLASS_OPTION_LIST,
4649
SyntaxKind::OPTION_ITEM_LIST,
50+
SyntaxKind::OP_SIG_LIST,
4751
SyntaxKind::PARAM_LIST,
4852
SyntaxKind::PARTITION_ITEM_LIST,
53+
SyntaxKind::RETURNING_OPTION_LIST,
54+
SyntaxKind::REVOKE_COMMAND_LIST,
55+
SyntaxKind::ROLE_LIST,
4956
SyntaxKind::ROW_LIST,
5057
SyntaxKind::SET_COLUMN_LIST,
5158
SyntaxKind::SET_EXPR_LIST,
@@ -540,9 +547,11 @@ $0
540547
#[test]
541548
fn list_variants() {
542549
let delimited_ws_list_kinds = &[
550+
SyntaxKind::CREATE_DATABASE_OPTION_LIST,
543551
SyntaxKind::FUNC_OPTION_LIST,
544552
SyntaxKind::ROLE_OPTION_LIST,
545553
SyntaxKind::SEQUENCE_OPTION_LIST,
554+
SyntaxKind::TRIGGER_EVENT_LIST,
546555
SyntaxKind::XML_COLUMN_OPTION_LIST,
547556
SyntaxKind::WHEN_CLAUSE_LIST,
548557
];

crates/squawk_linter/src/analyze.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ pub fn possibly_slow_stmt(stmt: &ast::Stmt) -> bool {
205205
| ast::Stmt::SetConstraints(_)
206206
| ast::Stmt::SetRole(_)
207207
| ast::Stmt::SetSessionAuth(_)
208+
| ast::Stmt::ResetSessionAuth(_)
208209
| ast::Stmt::SetTransaction(_)
209210
| ast::Stmt::Show(_)
210211
| ast::Stmt::Table(_)

crates/squawk_linter/src/ignore.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,19 @@ fn comment_body(token: &SyntaxToken) -> Option<(&str, TextRange)> {
2222
let range = token.text_range();
2323
if token.kind() == SyntaxKind::COMMENT {
2424
let text = token.text();
25-
if let Some(trimmed) = text.strip_prefix("--") {
26-
if let Some(start) = range.start().checked_add(2.into()) {
27-
let end = range.end();
28-
let updated_range = TextRange::new(start, end);
29-
return Some((trimmed, updated_range));
30-
}
25+
if let Some(trimmed) = text.strip_prefix("--")
26+
&& let Some(start) = range.start().checked_add(2.into())
27+
{
28+
let end = range.end();
29+
let updated_range = TextRange::new(start, end);
30+
return Some((trimmed, updated_range));
3131
}
32-
if let Some(trimmed) = text.strip_prefix("/*").and_then(|x| x.strip_suffix("*/")) {
33-
if let Some(start) = range.start().checked_add(2.into()) {
34-
if let Some(end) = range.end().checked_sub(2.into()) {
35-
let updated_range = TextRange::new(start, end);
36-
return Some((trimmed, updated_range));
37-
}
38-
}
32+
if let Some(trimmed) = text.strip_prefix("/*").and_then(|x| x.strip_suffix("*/"))
33+
&& let Some(start) = range.start().checked_add(2.into())
34+
&& let Some(end) = range.end().checked_sub(2.into())
35+
{
36+
let updated_range = TextRange::new(start, end);
37+
return Some((trimmed, updated_range));
3938
}
4039
}
4140
None

crates/squawk_linter/src/rules/adding_field_with_default.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ fn is_non_volatile_or_const(expr: &ast::Expr) -> bool {
2323
ast::Expr::Literal(_) => true,
2424
ast::Expr::ArrayExpr(_) => true,
2525
ast::Expr::BinExpr(bin_expr) => {
26-
if let Some(lhs) = bin_expr.lhs() {
27-
if let Some(rhs) = bin_expr.rhs() {
28-
return is_non_volatile_or_const(&lhs) && is_non_volatile_or_const(&rhs);
29-
}
26+
if let Some(lhs) = bin_expr.lhs()
27+
&& let Some(rhs) = bin_expr.rhs()
28+
{
29+
return is_non_volatile_or_const(&lhs) && is_non_volatile_or_const(&rhs);
3030
}
3131
false
3232
}

crates/squawk_linter/src/rules/ban_char_field.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,14 @@ fn check_path_type(ctx: &mut Linter, path_type: ast::PathType) {
4040
.path()
4141
.and_then(|x| x.segment())
4242
.and_then(|x| x.name_ref())
43+
&& is_char_type(name_ref.text())
4344
{
44-
if is_char_type(name_ref.text()) {
45-
let fix = create_fix(name_ref.syntax().text_range(), path_type.arg_list());
46-
ctx.report(Violation::for_node(
47-
Rule::BanCharField,
48-
"Using `character` is likely a mistake and should almost always be replaced by `text` or `varchar`.".into(),
49-
path_type.syntax(),
50-
).fix(Some(fix)));
51-
}
45+
let fix = create_fix(name_ref.syntax().text_range(), path_type.arg_list());
46+
ctx.report(Violation::for_node(
47+
Rule::BanCharField,
48+
"Using `character` is likely a mistake and should almost always be replaced by `text` or `varchar`.".into(),
49+
path_type.syntax(),
50+
).fix(Some(fix)));
5251
}
5352
}
5453

crates/squawk_linter/src/rules/constraint_missing_not_valid.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,11 @@ fn not_valid_validate_in_transaction(
6767
}
6868
}
6969
ast::AlterTableAction::AddConstraint(add_constraint) => {
70-
if add_constraint.not_valid().is_some() {
71-
if let Some(constraint) = add_constraint.constraint() {
72-
if let Some(constraint_name) = constraint.name() {
73-
not_valid_names
74-
.insert(Identifier::new(&constraint_name.text()));
75-
}
76-
}
70+
if add_constraint.not_valid().is_some()
71+
&& let Some(constraint) = add_constraint.constraint()
72+
&& let Some(constraint_name) = constraint.name()
73+
{
74+
not_valid_names.insert(Identifier::new(&constraint_name.text()));
7775
}
7876
}
7977
_ => (),

crates/squawk_linter/src/rules/prefer_robust_stmts.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,13 @@ pub(crate) fn prefer_robust_stmts(ctx: &mut Linter, parse: &Parse<SourceFile>) {
9191
}
9292
ast::AlterTableAction::AddConstraint(add_constraint) => {
9393
let constraint = add_constraint.constraint();
94-
if let Some(constraint_name) = constraint.and_then(|x| x.name()) {
95-
let name_text = constraint_name.text();
96-
let name = Identifier::new(name_text.as_str());
97-
if let Some(constraint) = constraint_names.get_mut(&name) {
98-
if *constraint == Constraint::Dropped {
99-
*constraint = Constraint::Added;
100-
continue;
101-
}
102-
}
94+
if let Some(constraint_name) = constraint.and_then(|x| x.name())
95+
&& let Some(constraint) = constraint_names
96+
.get_mut(&Identifier::new(constraint_name.text().as_str()))
97+
&& *constraint == Constraint::Dropped
98+
{
99+
*constraint = Constraint::Added;
100+
continue;
103101
}
104102
(ActionErrorMessage::None, None)
105103
}

crates/squawk_linter/src/rules/renaming_table.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub(crate) fn renaming_table(ctx: &mut Linter, parse: &Parse<SourceFile>) {
1010
for stmt in file.stmts() {
1111
if let ast::Stmt::AlterTable(alter_table) = stmt {
1212
for action in alter_table.actions() {
13-
if let ast::AlterTableAction::RenameTable(rename_table) = action {
13+
if let ast::AlterTableAction::RenameTo(rename_table) = action {
1414
ctx.report(Violation::for_node(
1515
Rule::RenamingTable,
1616
"Renaming a table may break existing clients.".into(),

crates/squawk_linter/src/rules/require_timeout_settings.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ pub(crate) fn require_timeout_settings(ctx: &mut Linter, parse: &Parse<SourceFil
5959
if path.qualifier().is_some() {
6060
continue;
6161
}
62-
if let Some(segment) = path.segment() {
63-
if let Some(name_ref) = segment.name_ref() {
64-
let name_ident = Identifier::new(name_ref.text().as_str());
65-
if name_ident == Identifier::new("lock_timeout") {
66-
lock_timeout = ReportOnce::Present;
67-
} else if name_ident == Identifier::new("statement_timeout") {
68-
stmt_timeout = ReportOnce::Present;
69-
}
62+
if let Some(segment) = path.segment()
63+
&& let Some(name_ref) = segment.name_ref()
64+
{
65+
let name_ident = Identifier::new(name_ref.text().as_str());
66+
if name_ident == Identifier::new("lock_timeout") {
67+
lock_timeout = ReportOnce::Present;
68+
} else if name_ident == Identifier::new("statement_timeout") {
69+
stmt_timeout = ReportOnce::Present;
7070
}
7171
}
7272
}

0 commit comments

Comments
 (0)