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
19 changes: 16 additions & 3 deletions crates/squawk_parser/src/plpgsql_grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ fn opt_block(p: &mut Parser) {
opt_exception_section(p);
p.expect(END_KW);
opt_label_name_ref(p);
// TODO: add validation, sometimes this is required
p.eat(SEMICOLON);
m.complete(p, PLPGSQL_BLOCK);
}
Expand Down Expand Up @@ -236,16 +235,26 @@ fn decl_datatype(p: &mut Parser) {
fn path_name_ref(p: &mut Parser) {
assert!(at_path(p).is_some());
let m = p.start();
name(p, PATH_SEGMENT_REF);
path_segment_ref(p);
let mut path = m.complete(p, PATH_REF);
while !p.at(EOF) && p.at(DOT) {
let m = path.precede(p);
p.bump(DOT);
name(p, PATH_SEGMENT_REF);
path_segment_ref(p);
path = m.complete(p, PATH_REF);
}
}

fn path_segment_ref(p: &mut Parser) {
let m = p.start();
let unicode_ident = p.at(IDENT);
p.bump_any();
if unicode_ident && p.eat(UESCAPE_KW) {
p.expect(STRING);
}
m.complete(p, PATH_SEGMENT_REF);
}

fn at_percent_datatype(p: &Parser) -> bool {
let Some(n) = at_path(p) else {
return false;
Expand All @@ -256,7 +265,11 @@ fn at_percent_datatype(p: &Parser) -> bool {
fn at_path(p: &Parser) -> Option<usize> {
let mut n = 0;
while !p.nth_at(n, EOF) && at_name(p, n) {
let unicode_ident = p.nth_at(n, IDENT);
n += 1;
if unicode_ident && p.nth_at(n, UESCAPE_KW) && p.nth_at(n + 1, STRING) {
n += 2;
}
if !p.nth_at(n, DOT) {
return Some(n);
}
Expand Down
16 changes: 16 additions & 0 deletions crates/squawk_parser/tests/data/plpgsql/err/block.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
do $$
begin
begin
null;
end
end
$$;

do $$
begin
<<inner>>
begin
null;
end inner
end
$$;
8 changes: 8 additions & 0 deletions crates/squawk_parser/tests/data/plpgsql/err/percent_type.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
do $$
declare
a U&"tbl" UESCAPE '!'%type;
b U&"tbl" UESCAPE '!'%rowtype;
begin
null;
end
$$;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ declare
j table%rowtype;
k select%type;
l $1%type;
m U&"int4" UESCAPE '!';
begin
null;
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
source: crates/squawk_syntax/src/test.rs
input_file: crates/squawk_parser/tests/data/plpgsql/err/block.sql
---
PLPGSQL@0..35
WHITESPACE@0..1 "\n"
PLPGSQL_BLOCK@1..34
BEGIN_KW@1..6 "begin"
WHITESPACE@6..9 "\n "
PLPGSQL_BODY@9..30
PLPGSQL_BLOCK@9..30
BEGIN_KW@9..14 "begin"
WHITESPACE@14..19 "\n "
PLPGSQL_BODY@19..24
PLPGSQL_NULL_STMT@19..24
NULL_KW@19..23 "null"
SEMICOLON@23..24 ";"
WHITESPACE@24..27 "\n "
END_KW@27..30 "end"
WHITESPACE@30..31 "\n"
END_KW@31..34 "end"
WHITESPACE@34..35 "\n"
---
PLPGSQL@0..53
WHITESPACE@0..1 "\n"
PLPGSQL_BLOCK@1..52
BEGIN_KW@1..6 "begin"
WHITESPACE@6..9 "\n "
PLPGSQL_BODY@9..48
PLPGSQL_BLOCK@9..48
PLPGSQL_LABEL@9..18
LESS_LESS@9..11 "<<"
PLPGSQL_LABEL_NAME@11..16
INNER_KW@11..16 "inner"
GREATER_GREATER@16..18 ">>"
WHITESPACE@18..21 "\n "
BEGIN_KW@21..26 "begin"
WHITESPACE@26..31 "\n "
PLPGSQL_BODY@31..36
PLPGSQL_NULL_STMT@31..36
NULL_KW@31..35 "null"
SEMICOLON@35..36 ";"
WHITESPACE@36..39 "\n "
END_KW@39..42 "end"
WHITESPACE@42..43 " "
PLPGSQL_LABEL_NAME_REF@43..48
INNER_KW@43..48 "inner"
WHITESPACE@48..49 "\n"
END_KW@49..52 "end"
WHITESPACE@52..53 "\n"

error[syntax-error]: Missing semicolon after block
╭▸
5 │ end
╰╴ ━
error[syntax-error]: Missing semicolon after block
╭▸
14 │ end inner
╰╴ ━
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
source: crates/squawk_syntax/src/test.rs
input_file: crates/squawk_parser/tests/data/plpgsql/err/percent_type.sql
---
PLPGSQL@0..90
WHITESPACE@0..1 "\n"
PLPGSQL_BLOCK@1..89
PLPGSQL_DECLARE_SECTION@1..71
DECLARE_KW@1..8 "declare"
WHITESPACE@8..11 "\n "
PLPGSQL_VAR_DECL@11..38
PLPGSQL_VAR_NAME@11..12
IDENT@11..12 "a"
WHITESPACE@12..13 " "
PERCENT_TYPE@13..37
PATH_REF@13..32
PATH_SEGMENT_REF@13..32
IDENT@13..20 "U&\"tbl\""
WHITESPACE@20..21 " "
UESCAPE_KW@21..28 "UESCAPE"
WHITESPACE@28..29 " "
STRING@29..32 "'!'"
PERCENT_TYPE_CLAUSE@32..37
PERCENT@32..33 "%"
TYPE_KW@33..37 "type"
SEMICOLON@37..38 ";"
WHITESPACE@38..41 "\n "
PLPGSQL_VAR_DECL@41..71
PLPGSQL_VAR_NAME@41..42
IDENT@41..42 "b"
WHITESPACE@42..43 " "
PLPGSQL_PERCENT_ROWTYPE@43..70
PATH_REF@43..62
PATH_SEGMENT_REF@43..62
IDENT@43..50 "U&\"tbl\""
WHITESPACE@50..51 " "
UESCAPE_KW@51..58 "UESCAPE"
WHITESPACE@58..59 " "
STRING@59..62 "'!'"
PERCENT@62..63 "%"
ROWTYPE_KW@63..70 "rowtype"
SEMICOLON@70..71 ";"
WHITESPACE@71..72 "\n"
BEGIN_KW@72..77 "begin"
WHITESPACE@77..80 "\n "
PLPGSQL_BODY@80..85
PLPGSQL_NULL_STMT@80..85
NULL_KW@80..84 "null"
SEMICOLON@84..85 ";"
WHITESPACE@85..86 "\n"
END_KW@86..89 "end"
WHITESPACE@89..90 "\n"

error[syntax-error]: UESCAPE is not allowed before %TYPE
╭▸
3 │ a U&"tbl" UESCAPE '!'%type;
╰╴ ━
error[syntax-error]: UESCAPE is not allowed before %ROWTYPE
╭▸
4 │ b U&"tbl" UESCAPE '!'%rowtype;
╰╴ ━
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
source: crates/squawk_syntax/src/test.rs
input_file: crates/squawk_parser/tests/data/plpgsql/ok/percent_type.sql
---
PLPGSQL@0..265
PLPGSQL@0..291
WHITESPACE@0..1 "\n"
PLPGSQL_BLOCK@1..264
PLPGSQL_DECLARE_SECTION@1..246
PLPGSQL_BLOCK@1..290
PLPGSQL_DECLARE_SECTION@1..272
DECLARE_KW@1..8 "declare"
WHITESPACE@8..11 "\n "
PLPGSQL_VAR_DECL@11..17
Expand Down Expand Up @@ -205,13 +205,27 @@ PLPGSQL@0..265
PERCENT@240..241 "%"
TYPE_KW@241..245 "type"
SEMICOLON@245..246 ";"
WHITESPACE@246..247 "\n"
BEGIN_KW@247..252 "begin"
WHITESPACE@252..255 "\n "
PLPGSQL_BODY@255..260
PLPGSQL_NULL_STMT@255..260
NULL_KW@255..259 "null"
SEMICOLON@259..260 ";"
WHITESPACE@260..261 "\n"
END_KW@261..264 "end"
WHITESPACE@264..265 "\n"
WHITESPACE@246..249 "\n "
PLPGSQL_VAR_DECL@249..272
PLPGSQL_VAR_NAME@249..250
IDENT@249..250 "m"
WHITESPACE@250..251 " "
PATH_TYPE@251..271
PATH_REF@251..271
PATH_SEGMENT_REF@251..271
IDENT@251..259 "U&\"int4\""
WHITESPACE@259..260 " "
UESCAPE_KW@260..267 "UESCAPE"
WHITESPACE@267..268 " "
STRING@268..271 "'!'"
SEMICOLON@271..272 ";"
WHITESPACE@272..273 "\n"
BEGIN_KW@273..278 "begin"
WHITESPACE@278..281 "\n "
PLPGSQL_BODY@281..286
PLPGSQL_NULL_STMT@281..286
NULL_KW@281..285 "null"
SEMICOLON@285..286 ";"
WHITESPACE@286..287 "\n"
END_KW@287..290 "end"
WHITESPACE@290..291 "\n"
58 changes: 58 additions & 0 deletions crates/squawk_syntax/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ pub(crate) fn validate(root: &SyntaxNode, errors: &mut Vec<SyntaxError>) {
ast::NonStandardParam(it) => validate_non_standard_param(it, errors),
ast::ParenFromItem(it) => validate_paren_from_item(it, errors),
ast::PartitionForValuesWith(it) => validate_hash_partition_bounds(it, errors),
ast::PercentType(it) => validate_plpgsql_percent_type(
it.syntax(),
it.percent_type_clause().and_then(|it| it.percent_token()),
"%TYPE",
errors,
),
ast::PlpgsqlBlock(it) => validate_plpgsql_block(it, errors),
ast::PlpgsqlCaseStmt(it) => validate_no_bare_case(it.subject(), errors),
ast::PlpgsqlCompOptionPrintStrictParams(it) => {
validate_print_strict_params(it, errors)
Expand All @@ -63,6 +70,12 @@ pub(crate) fn validate(root: &SyntaxNode, errors: &mut Vec<SyntaxError>) {
ast::PlpgsqlForIStmt(it) => validate_for_i_single_var(it, errors),
ast::PlpgsqlForQueryStmt(it) => validate_for_query_no_reverse(it, errors),
ast::PlpgsqlIfStmt(it) => validate_no_bare_case(it.cond(), errors),
ast::PlpgsqlPercentRowtype(it) => validate_plpgsql_percent_type(
it.syntax(),
it.percent_token(),
"%ROWTYPE",
errors,
),
ast::RelationFromItem(it) => validate_relation_from_item(it, errors),
ast::RuleStmtList(it) => validate_rule_stmt_list(it, errors),
ast::Select(it) => validate_select(it, errors),
Expand Down Expand Up @@ -357,6 +370,51 @@ fn validate_atomic_body(it: ast::AtomicBody, acc: &mut Vec<SyntaxError>) {
}
}

// -- err
// declare x U&"tbl" UESCAPE '!'%type;
// declare x U&"tbl" UESCAPE '!'%rowtype;
fn validate_plpgsql_percent_type(
syntax: &SyntaxNode,
percent: Option<SyntaxToken>,
percent_type: &str,
acc: &mut Vec<SyntaxError>,
) {
if !syntax
.parent()
.is_some_and(|parent| matches!(parent.kind(), PLPGSQL_VAR_DECL | PLPGSQL_CURSOR_ARG))
{
return;
}
let has_uescape = syntax
.descendants_with_tokens()
.any(|element| element.kind() == UESCAPE_KW);
if !has_uescape {
return;
}
let Some(percent) = percent else {
return;
};
acc.push(SyntaxError::new(
format!("UESCAPE is not allowed before {percent_type}"),
percent.text_range(),
));
}

fn validate_plpgsql_block(it: ast::PlpgsqlBlock, acc: &mut Vec<SyntaxError>) {
if it.semicolon_token().is_some()
|| it
.syntax()
.parent()
.is_none_or(|parent| parent.kind() != PLPGSQL_BODY)
{
return;
}
acc.push(SyntaxError::new(
"Missing semicolon after block",
TextRange::empty(it.syntax().text_range().end()),
));
}

fn validate_print_strict_params(
it: ast::PlpgsqlCompOptionPrintStrictParams,
acc: &mut Vec<SyntaxError>,
Expand Down
Loading