Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/squawk_fmt/tests/after/create_schema.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ input_file: crates/squawk_fmt/tests/before/create_schema.sql
---
create schema app;

create schema تست;

create schema if not exists
extraordinarily_long_application_reporting_and_data_warehouse_schema
authorization extraordinarily_long_application_reporting_and_data_warehouse_owner_role;
Expand Down
2 changes: 2 additions & 0 deletions crates/squawk_fmt/tests/before/create_schema.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
create schema app;

create schema تست;

create schema if not exists extraordinarily_long_application_reporting_and_data_warehouse_schema authorization extraordinarily_long_application_reporting_and_data_warehouse_owner_role;

create schema authorization current_user;
Expand Down
9 changes: 8 additions & 1 deletion crates/squawk_parser/src/grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3400,6 +3400,10 @@ fn with_query_clause(p: &mut Parser<'_>) -> Option<CompletedMarker> {
let m = p.start();
p.expect(WITH_KW);
p.eat(RECURSIVE_KW);
if p.at_ts(WITH_FOLLOW) {
p.error("expected common table expression");
return Some(m.complete(p, WITH_CLAUSE));
}
while !p.at(EOF) {
with_query(p);
if p.at(COMMA) && p.nth_at_ts(1, WITH_FOLLOW) {
Expand All @@ -3409,8 +3413,11 @@ fn with_query_clause(p: &mut Parser<'_>) -> Option<CompletedMarker> {
if !p.eat(COMMA) {
if p.at_ts(WITH_FOLLOW) || (p.at(L_PAREN) && p.nth_at_ts(1, PAREN_SELECT_FIRST)) {
break;
} else {
} else if p.at_ts(NAME_REF_FIRST) {
p.error("missing comma");
} else {
p.err_and_bump("missing comma");
break;
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions crates/squawk_parser/tests/data/err/select_cte.sql
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ values (2);

-- WITHOUT OVERLAPS is only valid w/ PRIMARY KEY or UNIQUE constraint
with t (a without overlaps) as (select 1) select * from t;

-- missing common table expression
with select 1;
19 changes: 19 additions & 0 deletions crates/squawk_parser/tests/snapshots/tests__select_cte_err.snap
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,21 @@ SOURCE_FILE
PATH_SEGMENT_REF
IDENT "t"
SEMICOLON ";"
WHITESPACE "\n\n"
COMMENT "-- missing common table expression"
WHITESPACE "\n"
SELECT
WITH_CLAUSE
WITH_KW "with"
WHITESPACE " "
SELECT_CLAUSE
SELECT_KW "select"
WHITESPACE " "
TARGET_LIST
TARGET
LITERAL
INT_NUMBER "1"
SEMICOLON ";"
WHITESPACE "\n"
---
error[syntax-error]: unexpected comma
Expand Down Expand Up @@ -491,3 +506,7 @@ error[syntax-error]: WITHOUT OVERLAPS is only allowed in PRIMARY KEY and UNIQUE
╭▸
47 │ with t (a without overlaps) as (select 1) select * from t;
╰╴ ━
error[syntax-error]: expected common table expression
╭▸
50 │ with select 1;
╰╴ ━
18 changes: 15 additions & 3 deletions crates/squawk_syntax/src/ast/node_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,9 +1000,21 @@ impl ast::PathSegmentRef {
}

pub fn is_quoted_name_node(node: &SyntaxNode) -> bool {
let text = node.text();
let first = text.char_at(0.into());
let second = text.char_at(1.into());
let mut first = None;
let mut second = None;
node.text().for_each_chunk(|chunk| {
if second.is_some() {
return;
}
for ch in chunk.chars() {
if first.is_none() {
first = Some(ch);
} else {
second = Some(ch);
break;
}
}
});
matches!(
(first, second),
(Some('u' | 'U'), Some('"')) | (Some('"'), Some(_))
Expand Down
9 changes: 5 additions & 4 deletions crates/squawk_syntax/src/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ pub fn unquote_ident(node: &SyntaxNode) -> Option<String> {

// see: https://www.postgresql.org/docs/18/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
match chars.next() {
Some(c) if c.is_lowercase() || c == '_' => {}
Some(c) if c.is_ascii_lowercase() || !c.is_ascii() || c == '_' => {}
_ => return None,
}

for c in chars {
if c.is_lowercase() || c.is_ascii_digit() || c == '_' || c == '$' {
if c.is_ascii_lowercase() || !c.is_ascii() || c.is_ascii_digit() || c == '_' || c == '$' {
continue;
}
return None;
Expand All @@ -90,12 +90,12 @@ pub fn needs_quoting(text: &str) -> bool {
let mut chars = text.chars();

match chars.next() {
Some(c) if c.is_lowercase() || c == '_' => {}
Some(c) if c.is_ascii_lowercase() || !c.is_ascii() || c == '_' => {}
_ => return true,
}

for c in chars {
if c.is_lowercase() || c.is_ascii_digit() || c == '_' || c == '$' {
if c.is_ascii_lowercase() || !c.is_ascii() || c.is_ascii_digit() || c == '_' || c == '$' {
continue;
}
return true;
Expand Down Expand Up @@ -205,6 +205,7 @@ mod tests {
assert_snapshot!(quote_ident("col_name"), @"col_name");
assert_snapshot!(quote_ident("users"), @"users");
assert_snapshot!(quote_ident("t2$"), @"t2$");
assert_snapshot!(quote_ident("تست"), @"تست");
}

#[test]
Expand Down
Loading