Skip to content

Commit 7cd5021

Browse files
authored
parser: add slice expr node to syntax tree (#707)
1 parent e96ba8d commit 7cd5021

File tree

9 files changed

+231
-12
lines changed

9 files changed

+231
-12
lines changed

PLAN.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,36 @@ SELECT customer_id, merchant_id, request_id, count(*) from t
439439
group by all;
440440
```
441441

442+
and an action to go backwards:
443+
444+
```sql
445+
SELECT customer_id, merchant_id, request_id, count(*) from t
446+
group by all;
447+
-- ^action:expand-group-by-all-names
448+
```
449+
450+
becomes:
451+
452+
```sql
453+
SELECT customer_id, merchant_id, request_id, count(*) from t
454+
group by customer_id, merchant_id, request_id;
455+
```
456+
457+
or with column numbers:
458+
459+
```sql
460+
SELECT customer_id, merchant_id, request_id, count(*) from t
461+
group by all;
462+
-- ^action:expand-group-by-all-numbers
463+
```
464+
465+
becomes:
466+
467+
```sql
468+
SELECT customer_id, merchant_id, request_id, count(*) from t
469+
group by 1, 2, 3;
470+
```
471+
442472
### Rule: unused column
443473

444474
```sql
@@ -1004,6 +1034,64 @@ select foo, "a" from t;
10041034
select foo, 'a' from t;
10051035
```
10061036

1037+
### Quick Fix: Quote and Unquote columns, tables, etc.
1038+
1039+
```sql
1040+
select "x" from "t";
1041+
-- ^ Quick Fix: unquote
1042+
```
1043+
1044+
gives
1045+
1046+
```sql
1047+
select x from "t";
1048+
```
1049+
1050+
and vice versa:
1051+
1052+
```sql
1053+
select x from "t";
1054+
-- ^ Quick Fix: quote
1055+
```
1056+
1057+
gives:
1058+
1059+
Note: we have to be mindful of casing here,
1060+
1061+
```sql
1062+
select "x" from "t";
1063+
```
1064+
1065+
Note: there's some gotchas with this that we need to handle:
1066+
1067+
```sql
1068+
-- okay
1069+
with t("X") as (select 1)
1070+
select "X" from t;
1071+
1072+
-- err
1073+
with t("X") as (select 1)
1074+
select X from t;
1075+
1076+
-- err
1077+
with t("X") as (select 1)
1078+
select x from t;
1079+
```
1080+
1081+
or invalid column names:
1082+
1083+
```sql
1084+
-- ok
1085+
with t("a-b") as (select 1)
1086+
select "a-b" from t;
1087+
1088+
-- err
1089+
with t("a-b") as (select 1)
1090+
select a-b from t;
1091+
-- Query 1 ERROR at Line 2: ERROR: column "a" does not exist
1092+
-- LINE 2: select a-b from t;
1093+
```
1094+
10071095
### Quick Fix: in array
10081096

10091097
```sql

crates/squawk_parser/src/generated/syntax_kind.rs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/squawk_parser/src/grammar.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2120,14 +2120,14 @@ fn index_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
21202120
if p.eat(COLON) {
21212121
// foo[:]
21222122
if p.eat(R_BRACK) {
2123-
return m.complete(p, INDEX_EXPR);
2123+
return m.complete(p, SLICE_EXPR);
21242124
} else {
21252125
// foo[:b]
21262126
if expr(p).is_none() {
21272127
p.error("expected an expression");
21282128
}
21292129
p.expect(R_BRACK);
2130-
return m.complete(p, INDEX_EXPR);
2130+
return m.complete(p, SLICE_EXPR);
21312131
}
21322132
}
21332133
// foo[a]
@@ -2139,12 +2139,14 @@ fn index_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
21392139
if p.eat(COLON) {
21402140
// foo[a:]
21412141
if p.eat(R_BRACK) {
2142-
return m.complete(p, INDEX_EXPR);
2142+
return m.complete(p, SLICE_EXPR);
21432143
}
21442144
// foo[a:b]
21452145
if expr(p).is_none() {
21462146
p.error("expected an expression");
21472147
}
2148+
p.expect(R_BRACK);
2149+
return m.complete(p, SLICE_EXPR);
21482150
}
21492151
p.expect(R_BRACK);
21502152
}

crates/squawk_parser/tests/snapshots/tests__insert_ok.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -728,8 +728,8 @@ SOURCE_FILE
728728
COMMA ","
729729
WHITESPACE " "
730730
COLUMN
731-
INDEX_EXPR
732-
INDEX_EXPR
731+
SLICE_EXPR
732+
SLICE_EXPR
733733
NAME_REF
734734
IDENT "board"
735735
L_BRACK "["

crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,8 +1416,8 @@ SOURCE_FILE
14161416
WHITESPACE " "
14171417
TARGET_LIST
14181418
TARGET
1419-
INDEX_EXPR
1420-
INDEX_EXPR
1419+
SLICE_EXPR
1420+
SLICE_EXPR
14211421
NAME_REF
14221422
IDENT "b"
14231423
L_BRACK "["
@@ -1444,8 +1444,8 @@ SOURCE_FILE
14441444
WHITESPACE " "
14451445
TARGET_LIST
14461446
TARGET
1447-
INDEX_EXPR
1448-
INDEX_EXPR
1447+
SLICE_EXPR
1448+
SLICE_EXPR
14491449
NAME_REF
14501450
IDENT "c"
14511451
L_BRACK "["
@@ -1468,8 +1468,8 @@ SOURCE_FILE
14681468
WHITESPACE " "
14691469
TARGET_LIST
14701470
TARGET
1471-
INDEX_EXPR
1472-
INDEX_EXPR
1471+
SLICE_EXPR
1472+
SLICE_EXPR
14731473
NAME_REF
14741474
IDENT "schedule"
14751475
L_BRACK "["
@@ -3931,7 +3931,7 @@ SOURCE_FILE
39313931
WHITESPACE " "
39323932
TARGET_LIST
39333933
TARGET
3934-
INDEX_EXPR
3934+
SLICE_EXPR
39353935
LITERAL
39363936
POSITIONAL_PARAM "$1"
39373937
L_BRACK "["

crates/squawk_syntax/src/ast/generated/nodes.rs

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/squawk_syntax/src/ast/node_ext.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,34 @@ impl ast::IndexExpr {
7878
}
7979
}
8080

81+
impl ast::SliceExpr {
82+
#[inline]
83+
pub fn base(&self) -> Option<ast::Expr> {
84+
support::children(&self.syntax).next()
85+
}
86+
87+
#[inline]
88+
pub fn start(&self) -> Option<ast::Expr> {
89+
// With `select x[1:]`, we have two exprs, `x` and `1`.
90+
// We skip over the first one, and then we want the second one, but we
91+
// want to make sure we don't choose the end expr if instead we had:
92+
// `select x[:1]`
93+
let colon = self.colon_token()?;
94+
support::children(&self.syntax)
95+
.skip(1)
96+
.find(|expr: &ast::Expr| expr.syntax().text_range().end() <= colon.text_range().start())
97+
}
98+
99+
#[inline]
100+
pub fn end(&self) -> Option<ast::Expr> {
101+
// We want to make sure we get the last expr after the `:` which is the
102+
// end of the slice, i.e., `2` in: `select x[:2]`
103+
let colon = self.colon_token()?;
104+
support::children(&self.syntax)
105+
.find(|expr: &ast::Expr| expr.syntax().text_range().start() >= colon.text_range().end())
106+
}
107+
}
108+
81109
impl ast::BetweenExpr {
82110
#[inline]
83111
pub fn target(&self) -> Option<ast::Expr> {

crates/squawk_syntax/src/lib.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,55 @@ fn index_expr() {
509509
assert_eq!(index.syntax().text(), "bar");
510510
}
511511

512+
#[test]
513+
fn slice_expr() {
514+
use insta::assert_snapshot;
515+
let source_code = "
516+
select x[1:2], x[2:], x[:3], x[:];
517+
";
518+
let parse = SourceFile::parse(source_code);
519+
assert!(parse.errors().is_empty());
520+
let file: SourceFile = parse.tree();
521+
let stmt = file.stmts().next().unwrap();
522+
let ast::Stmt::Select(select) = stmt else {
523+
unreachable!()
524+
};
525+
let select_clause = select.select_clause().unwrap();
526+
let mut targets = select_clause.target_list().unwrap().targets();
527+
528+
let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
529+
unreachable!()
530+
};
531+
assert_snapshot!(slice.syntax(), @"x[1:2]");
532+
assert_eq!(slice.base().unwrap().syntax().text(), "x");
533+
assert_eq!(slice.start().unwrap().syntax().text(), "1");
534+
assert_eq!(slice.end().unwrap().syntax().text(), "2");
535+
536+
let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
537+
unreachable!()
538+
};
539+
assert_snapshot!(slice.syntax(), @"x[2:]");
540+
assert_eq!(slice.base().unwrap().syntax().text(), "x");
541+
assert_eq!(slice.start().unwrap().syntax().text(), "2");
542+
assert!(slice.end().is_none());
543+
544+
let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
545+
unreachable!()
546+
};
547+
assert_snapshot!(slice.syntax(), @"x[:3]");
548+
assert_eq!(slice.base().unwrap().syntax().text(), "x");
549+
assert!(slice.start().is_none());
550+
assert_eq!(slice.end().unwrap().syntax().text(), "3");
551+
552+
let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
553+
unreachable!()
554+
};
555+
assert_snapshot!(slice.syntax(), @"x[:]");
556+
assert_eq!(slice.base().unwrap().syntax().text(), "x");
557+
assert!(slice.start().is_none());
558+
assert!(slice.end().is_none());
559+
}
560+
512561
#[test]
513562
fn field_expr() {
514563
let source_code = "

0 commit comments

Comments
 (0)