diff --git a/crates/squawk_ide/src/expand_selection.rs b/crates/squawk_ide/src/expand_selection.rs index a0eb72fe3..5c396f063 100644 --- a/crates/squawk_ide/src/expand_selection.rs +++ b/crates/squawk_ide/src/expand_selection.rs @@ -71,6 +71,8 @@ const DELIMITED_LIST_KINDS: &[SyntaxKind] = &[ SyntaxKind::PARAM_LIST, SyntaxKind::PARTITION_ITEM_LIST, SyntaxKind::PARTITION_LIST, + SyntaxKind::PLPGSQL_CURSOR_ARG_LIST, + SyntaxKind::PLPGSQL_DIAG_ITEM_LIST, SyntaxKind::TABLE_NAME_REF_LIST, SyntaxKind::REINDEX_OPTION_LIST, SyntaxKind::RELATION_LIST, diff --git a/crates/squawk_ide/src/folding_ranges.rs b/crates/squawk_ide/src/folding_ranges.rs index 21bd6f750..b18777ec2 100644 --- a/crates/squawk_ide/src/folding_ranges.rs +++ b/crates/squawk_ide/src/folding_ranges.rs @@ -128,7 +128,8 @@ fn fold_kind(kind: SyntaxKind) -> Option { SyntaxKind::ARG_LIST | SyntaxKind::TABLE_ARG_LIST | SyntaxKind::RETURN_TABLE_ARG_LIST - | SyntaxKind::PARAM_LIST => Some(FoldKind::ArgList), + | SyntaxKind::PARAM_LIST + | SyntaxKind::PLPGSQL_CURSOR_ARG_LIST => Some(FoldKind::ArgList), SyntaxKind::ARRAY_EXPR => Some(FoldKind::Array), SyntaxKind::CALL_EXPR => Some(FoldKind::FunctionCall), SyntaxKind::JOIN => Some(FoldKind::Join), @@ -169,6 +170,7 @@ fn fold_kind(kind: SyntaxKind) -> Option { | SyntaxKind::OP_SIG_LIST | SyntaxKind::PARTITION_ITEM_LIST | SyntaxKind::PARTITION_LIST + | SyntaxKind::PLPGSQL_DIAG_ITEM_LIST | SyntaxKind::TABLE_NAME_REF_LIST | SyntaxKind::REINDEX_OPTION_LIST | SyntaxKind::RELATION_LIST diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index 4ecb596e4..951f6dd82 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -232,6 +232,7 @@ pub fn goto_definition(db: &dyn Db, position: InFile) -> SmallVec<[Loc resolve::resolve_name_ref(db, InFile::new(definition_file, &name_ref)) }) } + ast::AnyNameRef::PlpgsqlLabelNameRef(_) | ast::AnyNameRef::PlpgsqlVarNameRef(_) => None, ast::AnyNameRef::PolicyRef(name_ref) => resolve_in_files(db, file, |definition_file| { resolve::resolve_policy_ref(db, InFile::new(definition_file, &name_ref)) }), diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index a3583ac23..3c0e330e8 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -227,6 +227,12 @@ pub fn hover(db: &dyn Db, position: InFile) -> Option { | ast::AnyName::OptionItemValueName(_) | ast::AnyName::ParamNameRef(_) | ast::AnyName::PathSegmentRef(_) + | ast::AnyName::PlpgsqlCondition(_) + | ast::AnyName::PlpgsqlLabelName(_) + | ast::AnyName::PlpgsqlLabelNameRef(_) + | ast::AnyName::PlpgsqlOptionValue(_) + | ast::AnyName::PlpgsqlVarName(_) + | ast::AnyName::PlpgsqlVarNameRef(_) | ast::AnyName::PolicyRef(_) | ast::AnyName::PreparedStatementRef(_) | ast::AnyName::PropertyName(_) diff --git a/crates/squawk_parser/src/generated/syntax_kind.rs b/crates/squawk_parser/src/generated/syntax_kind.rs index dd613d901..bddb33867 100644 --- a/crates/squawk_parser/src/generated/syntax_kind.rs +++ b/crates/squawk_parser/src/generated/syntax_kind.rs @@ -1504,7 +1504,73 @@ pub enum SyntaxKind { PERCENT_TYPE_CLAUSE, PERIOD_COLUMN, PLPGSQL, + PLPGSQL_ALIAS_DECL, + PLPGSQL_ALIAS_TARGET, + PLPGSQL_ASSERT_STMT, + PLPGSQL_ASSIGN_STMT, + PLPGSQL_ASSIGN_TARGET, + PLPGSQL_BLOCK, + PLPGSQL_BODY, + PLPGSQL_CALL_STMT, + PLPGSQL_CASE_STMT, + PLPGSQL_CASE_WHEN, + PLPGSQL_COMMIT_STMT, + PLPGSQL_COMP_OPTION_DUMP, + PLPGSQL_COMP_OPTION_PRINT_STRICT_PARAMS, + PLPGSQL_COMP_OPTION_VARIABLE_CONFLICT, + PLPGSQL_CONDITION, + PLPGSQL_CONTINUE_STMT, + PLPGSQL_CURSOR_ARG, + PLPGSQL_CURSOR_ARG_LIST, + PLPGSQL_CURSOR_DECL, + PLPGSQL_DECLARE_SECTION, + PLPGSQL_DIAG_AREA, + PLPGSQL_DIAG_ITEM, + PLPGSQL_DIAG_ITEM_LIST, + PLPGSQL_DIAG_KIND, + PLPGSQL_DIAG_TARGET, + PLPGSQL_DO_STMT, + PLPGSQL_ELSE_CLAUSE, + PLPGSQL_ELSIF_CLAUSE, + PLPGSQL_EXCEPTION_HANDLER, + PLPGSQL_EXCEPTION_SECTION, + PLPGSQL_EXIT_STMT, + PLPGSQL_EXIT_WHEN, + PLPGSQL_GET_DIAG_STMT, + PLPGSQL_IF_STMT, + PLPGSQL_LABEL, + PLPGSQL_LABEL_NAME, + PLPGSQL_LABEL_NAME_REF, + PLPGSQL_LOOP_STMT, + PLPGSQL_NOT_NULL, PLPGSQL_NULL_STMT, + PLPGSQL_OPTION_VALUE, + PLPGSQL_PERCENT_ROWTYPE, + PLPGSQL_PERFORM_STMT, + PLPGSQL_RAISE_LEVEL, + PLPGSQL_RAISE_MESSAGE, + PLPGSQL_RAISE_OPTION_COLUMN, + PLPGSQL_RAISE_OPTION_CONSTRAINT, + PLPGSQL_RAISE_OPTION_DATATYPE, + PLPGSQL_RAISE_OPTION_DETAIL, + PLPGSQL_RAISE_OPTION_ERRCODE, + PLPGSQL_RAISE_OPTION_HINT, + PLPGSQL_RAISE_OPTION_MESSAGE, + PLPGSQL_RAISE_OPTION_SCHEMA, + PLPGSQL_RAISE_OPTION_TABLE, + PLPGSQL_RAISE_STMT, + PLPGSQL_RAISE_USING_CLAUSE, + PLPGSQL_RETURN_NEXT_STMT, + PLPGSQL_RETURN_QUERY_EXECUTE_STMT, + PLPGSQL_RETURN_QUERY_STMT, + PLPGSQL_RETURN_STMT, + PLPGSQL_ROLLBACK_STMT, + PLPGSQL_USING_CLAUSE, + PLPGSQL_VAR_DECL, + PLPGSQL_VAR_INIT, + PLPGSQL_VAR_NAME, + PLPGSQL_VAR_NAME_REF, + PLPGSQL_WHILE_STMT, POLICY, POLICY_COMMAND, POLICY_COMMAND_ALL, diff --git a/crates/squawk_parser/src/generated/token_sets.rs b/crates/squawk_parser/src/generated/token_sets.rs index 5033790dd..b83c36016 100644 --- a/crates/squawk_parser/src/generated/token_sets.rs +++ b/crates/squawk_parser/src/generated/token_sets.rs @@ -2362,3 +2362,31 @@ pub(crate) const RESERVED_KEYWORDS: TokenSet = TokenSet::new(&[ SyntaxKind::WINDOW_KW, SyntaxKind::WITH_KW, ]); + +pub(crate) const PLPGSQL_RESERVED_KEYWORDS: TokenSet = TokenSet::new(&[ + SyntaxKind::ALL_KW, + SyntaxKind::BEGIN_KW, + SyntaxKind::BY_KW, + SyntaxKind::CASE_KW, + SyntaxKind::DECLARE_KW, + SyntaxKind::ELSE_KW, + SyntaxKind::END_KW, + SyntaxKind::FOR_KW, + SyntaxKind::FROM_KW, + SyntaxKind::IF_KW, + SyntaxKind::IN_KW, + SyntaxKind::INTO_KW, + SyntaxKind::NOT_KW, + SyntaxKind::NULL_KW, + SyntaxKind::OR_KW, + SyntaxKind::THEN_KW, + SyntaxKind::TO_KW, + SyntaxKind::USING_KW, + SyntaxKind::WHEN_KW, +]); + +pub(crate) const PLPGSQL_RESERVED_CONTEXTUAL_KEYWORDS: TokenSet = TokenSet::new(&[ + SyntaxKind::FOREACH_KW, + SyntaxKind::LOOP_KW, + SyntaxKind::WHILE_KW, +]); diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index a90ede43c..30e33331f 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -166,7 +166,7 @@ fn is_select_marker(cm: &CompletedMarker) -> bool { } #[derive(Clone, Copy, PartialEq, Eq)] -enum ExprKind { +pub(crate) enum ExprKind { Other, Select, } @@ -2323,7 +2323,7 @@ fn path_for_qualifier( } } -fn opt_percent_type(p: &mut Parser<'_>) -> Option { +pub(crate) fn opt_percent_type(p: &mut Parser<'_>) -> Option { if p.at(PERCENT) && p.nth_at(1, TYPE_KW) { let m = p.start(); p.bump(PERCENT); @@ -2334,7 +2334,7 @@ fn opt_percent_type(p: &mut Parser<'_>) -> Option { } } -fn opt_array_bound(p: &mut Parser<'_>) -> bool { +pub(crate) fn opt_array_bound(p: &mut Parser<'_>) -> bool { if !p.at(L_BRACK) { return false; } @@ -2547,7 +2547,7 @@ fn type_name(p: &mut Parser<'_>) { } } -fn func_type(p: &mut Parser<'_>) { +pub(crate) fn func_type(p: &mut Parser<'_>) { if opt_type_name_with(p, true, true).is_none() { p.error("expected type name"); } @@ -2997,7 +2997,7 @@ fn field_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { m.complete(p, FIELD_EXPR) } -fn expr(p: &mut Parser<'_>) -> Option<(CompletedMarker, ExprKind)> { +pub(crate) fn expr(p: &mut Parser<'_>) -> Option<(CompletedMarker, ExprKind)> { expr_bp(p, 1, &Restrictions::default()) } @@ -3539,6 +3539,29 @@ fn select(p: &mut Parser, m: Option, r: &SelectRestrictions) -> Option) -> CompletedMarker { + assert!(p.nth_at_contextual_kw(0, PERFORM_KW)); + let m = p.start(); + let clause = p.start(); + // Postgres rewrites this to `select` before passing to the sql parser, but + // we can't do that + p.bump_remap(PERFORM_KW); + opt_select_all_or_distinct(p); + opt_target_list(p); + clause.complete(p, SELECT_CLAUSE); + select_tail(p, m, &SelectRestrictions::default(), SELECT) +} + +fn select_tail( + p: &mut Parser, + m: Marker, + r: &SelectRestrictions, + out_kind: SyntaxKind, +) -> CompletedMarker { + let mut out_kind = out_kind; if opt_into_clause(p).is_some() { out_kind = SELECT_INTO; } @@ -3550,20 +3573,20 @@ fn select(p: &mut Parser, m: Option, r: &SelectRestrictions) -> Option) -> bool { p.at(PERIOD_KW) && p.nth_at_ts(1, NAME_REF_FIRST) } -fn accessors(p: &mut Parser<'_>) { +pub(crate) fn accessors(p: &mut Parser<'_>) { while !p.at(EOF) { match p.current() { DOT => field_accessor(p), @@ -4796,7 +4819,7 @@ fn accessors(p: &mut Parser<'_>) { } } -fn field_accessor(p: &mut Parser<'_>) { +pub(crate) fn field_accessor(p: &mut Parser<'_>) { assert!(p.at(DOT)); let m = p.start(); p.bump(DOT); @@ -6266,7 +6289,7 @@ const COL_LABEL_FIRST: TokenSet = TokenSet::new(&[IDENT]) .union(TYPE_FUNC_NAME_KEYWORDS) .union(RESERVED_KEYWORDS); -const NAME_FIRST: TokenSet = TokenSet::new(&[IDENT]) +pub(crate) const NAME_FIRST: TokenSet = TokenSet::new(&[IDENT]) .union(UNRESERVED_KEYWORDS) .union(COL_NAME_KEYWORD_FIRST); @@ -7735,7 +7758,7 @@ fn commit(p: &mut Parser<'_>) -> CompletedMarker { m.complete(p, kind) } -fn opt_chain_clause(p: &mut Parser<'_>) { +pub(crate) fn opt_chain_clause(p: &mut Parser<'_>) { if !p.at(AND_KW) { return; } @@ -7833,7 +7856,7 @@ fn begin(p: &mut Parser<'_>) -> CompletedMarker { } // Sconst -fn opt_string_literal(p: &mut Parser<'_>) -> Option { +pub(crate) fn opt_string_literal(p: &mut Parser<'_>) -> Option { if p.at_ts(STRING_FIRST) { literal(p) } else { @@ -7960,14 +7983,15 @@ fn rollback(p: &mut Parser<'_>) -> CompletedMarker { } #[derive(Default)] -struct StmtRestrictions { +pub(crate) struct StmtRestrictions { begin_end_allowed: bool, semi_allowed: bool, } -fn stmt(p: &mut Parser, r: &StmtRestrictions) -> Option { +pub(crate) fn stmt(p: &mut Parser, r: &StmtRestrictions) -> Option { match (p.current(), p.nth(1)) { - (SEMICOLON, _) => Some(empty_stmt(p)), + (SEMICOLON, _) if r.semi_allowed => Some(empty_stmt(p)), + (SEMICOLON, _) => None, (ABORT_KW, _) => Some(rollback(p)), (ALTER_KW, AGGREGATE_KW) => Some(alter_aggregate(p)), (ALTER_KW, COLLATION_KW) => Some(alter_collation(p)), @@ -8020,7 +8044,7 @@ fn stmt(p: &mut Parser, r: &StmtRestrictions) -> Option { (ALTER_KW, VIEW_KW) => Some(alter_view(p)), (ANALYZE_KW | ANALYSE_KW, _) => Some(analyze(p)), (BEGIN_KW, _) if r.begin_end_allowed => Some(begin(p)), - (CALL_KW, _) => Some(call(p)), + (CALL_KW, _) => Some(call(p, r.semi_allowed)), (CHECKPOINT_KW, _) => Some(checkpoint(p)), (CLOSE_KW, _) => Some(close(p)), (CLUSTER_KW, _) => Some(cluster(p)), @@ -8120,7 +8144,7 @@ fn stmt(p: &mut Parser, r: &StmtRestrictions) -> Option { (DECLARE_KW, _) => Some(declare(p)), (DELETE_KW, _) => Some(delete(p, None, r.semi_allowed)), (DISCARD_KW, _) => Some(discard(p)), - (DO_KW, _) => Some(do_(p)), + (DO_KW, _) => Some(do_(p, r.semi_allowed)), (DROP_KW, ACCESS_KW) => Some(drop_access_method(p)), (DROP_KW, AGGREGATE_KW) => Some(drop_aggregate(p)), (DROP_KW, CAST_KW) => Some(drop_cast(p)), @@ -15987,17 +16011,36 @@ fn opt_do_language(p: &mut Parser<'_>) { } // DO [ LANGUAGE lang_name ] code -fn do_(p: &mut Parser<'_>) -> CompletedMarker { +fn do_(p: &mut Parser<'_>, semi_allowed: bool) -> CompletedMarker { assert!(p.at(DO_KW)); let m = p.start(); p.bump(DO_KW); opt_do_language(p); string_literal(p); opt_do_language(p); - p.eat(SEMICOLON); + if semi_allowed { + p.eat(SEMICOLON); + } m.complete(p, DO) } +pub(crate) fn opt_cursor_scroll(p: &mut Parser<'_>) { + match p.current() { + NO_KW => { + let m = p.start(); + p.bump(NO_KW); + p.expect(SCROLL_KW); + m.complete(p, NO_SCROLL); + } + SCROLL_KW => { + let m = p.start(); + p.bump(SCROLL_KW); + m.complete(p, SCROLL); + } + _ => (), + } +} + // DECLARE name [ BINARY ] [ ASENSITIVE | INSENSITIVE ] [ [ NO ] SCROLL ] // CURSOR [ { WITH | WITHOUT } HOLD ] FOR query fn declare(p: &mut Parser<'_>) -> CompletedMarker { @@ -16022,20 +16065,7 @@ fn declare(p: &mut Parser<'_>) -> CompletedMarker { _ => (), } // [ [ NO ] SCROLL ] - match p.current() { - NO_KW => { - let m = p.start(); - p.bump(NO_KW); - p.expect(SCROLL_KW); - m.complete(p, NO_SCROLL); - } - SCROLL_KW => { - let m = p.start(); - p.bump(SCROLL_KW); - m.complete(p, SCROLL); - } - _ => (), - } + opt_cursor_scroll(p); p.expect(CURSOR_KW); // [ { WITH | WITHOUT } HOLD ] match p.current() { @@ -16716,7 +16746,7 @@ fn preparable_stmt(p: &mut Parser<'_>) { // https://www.postgresql.org/docs/17/sql-call.html // CALL name ( [ argument ] [, ...] ) -fn call(p: &mut Parser<'_>) -> CompletedMarker { +fn call(p: &mut Parser<'_>, semi_allowed: bool) -> CompletedMarker { assert!(p.at(CALL_KW)); let m = p.start(); p.bump(CALL_KW); @@ -16726,7 +16756,9 @@ fn call(p: &mut Parser<'_>) -> CompletedMarker { } else { p.error("expected L_PAREN"); } - p.eat(SEMICOLON); + if semi_allowed { + p.eat(SEMICOLON); + } m.complete(p, CALL) } @@ -19522,7 +19554,7 @@ fn attribute_list(p: &mut Parser<'_>) { m.complete(p, ATTRIBUTE_LIST); } -fn opt_collate(p: &mut Parser<'_>) -> Option { +pub(crate) fn opt_collate(p: &mut Parser<'_>) -> Option { let m = p.start(); if p.eat(COLLATE_KW) { collation_ref(p); diff --git a/crates/squawk_parser/src/lib.rs b/crates/squawk_parser/src/lib.rs index ea767f9f2..5ffc613d8 100644 --- a/crates/squawk_parser/src/lib.rs +++ b/crates/squawk_parser/src/lib.rs @@ -587,7 +587,6 @@ impl<'t> Parser<'t> { /// Advances the parser by one token, remapping its kind. /// This is useful to create contextual keywords from /// identifiers. - #[expect(dead_code, reason = "used by the PL/pgSQL grammar")] pub(crate) fn bump_remap(&mut self, kind: SyntaxKind) { if self.nth(0) == SyntaxKind::EOF { // FIXME: panic!? @@ -597,7 +596,6 @@ impl<'t> Parser<'t> { } /// Checks if the nth token is contextual keyword `kw`. - #[expect(dead_code, reason = "used by the PL/pgSQL grammar")] pub(crate) fn nth_at_contextual_kw(&self, n: usize, kw: SyntaxKind) -> bool { self.inp.contextual_kind(self.pos + n) == kw } @@ -671,6 +669,12 @@ impl<'t> Parser<'t> { kinds.contains(self.nth(n)) } + /// Checks if the nth token is a contextual keyword in `kinds`. + #[must_use] + pub(crate) fn nth_at_contextual_ts(&self, n: usize, kinds: TokenSet) -> bool { + kinds.contains(self.inp.contextual_kind(self.pos + n)) + } + #[must_use] pub(crate) fn nth_at(&self, n: usize, kind: SyntaxKind) -> bool { match kind { diff --git a/crates/squawk_parser/src/plpgsql_grammar.rs b/crates/squawk_parser/src/plpgsql_grammar.rs index b9749ca4c..1b0fd696a 100644 --- a/crates/squawk_parser/src/plpgsql_grammar.rs +++ b/crates/squawk_parser/src/plpgsql_grammar.rs @@ -1,17 +1,976 @@ -use crate::{Parser, syntax_kind::SyntaxKind::*}; +use crate::{ + Parser, SyntaxKind, + generated::token_sets::{ + ALL_KEYWORDS, PLPGSQL_RESERVED_CONTEXTUAL_KEYWORDS, PLPGSQL_RESERVED_KEYWORDS, + }, + grammar, + syntax_kind::SyntaxKind::*, + token_set::TokenSet, +}; pub(crate) fn plpgsql_entry_point(p: &mut Parser) { let m = p.start(); + while !p.at(EOF) && p.at(POUND) { + comp_option(p); + } while !p.at(EOF) { - if p.at(NULL_KW) && p.nth_at(1, SEMICOLON) { - let m = p.start(); - p.bump(NULL_KW); - p.bump(SEMICOLON); - m.complete(p, PLPGSQL_NULL_STMT); + if at_block_start(p) { + opt_block(p); } else { - let kind = p.current(); - p.err_and_bump(&format!("expected a statement, found {kind:?}")); + temp_unknown(p, "expected a block"); } } m.complete(p, PLPGSQL); } + +const VARIABLE_CONFLICT_VALUES: [SyntaxKind; 3] = [ERROR_KW, USE_VARIABLE_KW, USE_COLUMN_KW]; + +fn comp_option(p: &mut Parser) { + assert!(p.at(POUND)); + let m = p.start(); + p.bump(POUND); + let kind = if p.at(OPTION_KW) { + p.bump(OPTION_KW); + expect_contextual_kw(p, DUMP_KW); + PLPGSQL_COMP_OPTION_DUMP + } else if p.nth_at_contextual_kw(0, VARIABLE_CONFLICT_KW) { + p.bump_remap(VARIABLE_CONFLICT_KW); + variable_conflict_value(p); + PLPGSQL_COMP_OPTION_VARIABLE_CONFLICT + } else if p.nth_at_contextual_kw(0, PRINT_STRICT_PARAMS_KW) { + p.bump_remap(PRINT_STRICT_PARAMS_KW); + option_value(p); + PLPGSQL_COMP_OPTION_PRINT_STRICT_PARAMS + } else { + p.error("expected OPTION, PRINT_STRICT_PARAMS, or VARIABLE_CONFLICT"); + ERROR + }; + m.complete(p, kind); +} + +fn variable_conflict_value(p: &mut Parser) { + match VARIABLE_CONFLICT_VALUES + .into_iter() + .find(|&kw| at_maybe_contextual_kw(p, 0, kw)) + { + Some(kw) => bump_maybe_contextual_kw(p, kw), + None => p.error("expected ERROR, USE_VARIABLE, or USE_COLUMN"), + } +} + +fn option_value(p: &mut Parser) { + if !at_name(p, 0) { + p.error(format!("expected an option value, got {:?}", p.current())); + return; + } + name(p, PLPGSQL_OPTION_VALUE); +} + +const BLOCK_FIRST: TokenSet = TokenSet::new(&[BEGIN_KW, DECLARE_KW]); + +fn opt_block(p: &mut Parser) { + if !at_block_start(p) { + return; + } + let m = p.start(); + opt_label(p); + opt_declare_section(p); + p.expect(BEGIN_KW); + body(p, BodyKind::Block); + 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); +} + +// <> +fn opt_label(p: &mut Parser) { + if !at_label(p) { + return; + } + let m = p.start(); + p.bump(LESS_LESS); + name(p, PLPGSQL_LABEL_NAME); + p.bump(GREATER_GREATER); + m.complete(p, PLPGSQL_LABEL); +} + +fn opt_label_name_ref(p: &mut Parser) { + if !at_name(p, 0) { + return; + } + name(p, PLPGSQL_LABEL_NAME_REF); +} + +fn name(p: &mut Parser, kind: SyntaxKind) { + let m = p.start(); + p.bump_any(); + m.complete(p, kind); +} + +fn opt_declare_section(p: &mut Parser) { + if !p.at(DECLARE_KW) { + return; + } + let m = p.start(); + p.bump(DECLARE_KW); + while !p.at(EOF) && !p.at(BEGIN_KW) { + if at_alias_decl(p) { + alias_decl(p); + } else if at_cursor_decl(p) { + cursor_decl(p); + } else if at_var_decl(p) { + var_decl(p); + } else { + temp_unknown(p, "expected a declaration"); + } + } + m.complete(p, PLPGSQL_DECLARE_SECTION); +} + +fn var_decl(p: &mut Parser) { + let m = p.start(); + name(p, PLPGSQL_VAR_NAME); + if p.nth_at_contextual_kw(0, CONSTANT_KW) { + p.bump_remap(CONSTANT_KW); + } + decl_datatype(p); + grammar::opt_collate(p); + opt_not_null(p); + opt_var_init(p); + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_VAR_DECL); +} + +fn alias_decl(p: &mut Parser) { + let m = p.start(); + name(p, PLPGSQL_VAR_NAME); + p.bump_remap(ALIAS_KW); + p.expect(FOR_KW); + alias_target(p); + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_ALIAS_DECL); +} + +fn alias_target(p: &mut Parser) { + let m = p.start(); + if at_path(p).is_some() { + path_name_ref(p); + } else { + p.error(format!("expected an alias target, got {:?}", p.current())); + } + m.complete(p, PLPGSQL_ALIAS_TARGET); +} + +fn cursor_decl(p: &mut Parser) { + let m = p.start(); + name(p, PLPGSQL_VAR_NAME); + grammar::opt_cursor_scroll(p); + p.expect(CURSOR_KW); + if p.at(L_PAREN) { + cursor_arg_list(p); + } + if !p.eat(IS_KW) { + p.expect(FOR_KW); + } + if grammar::stmt(p, &grammar::StmtRestrictions::default()).is_none() { + p.error("expected a query"); + } + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_CURSOR_DECL); +} + +fn cursor_arg_list(p: &mut Parser) { + assert!(p.at(L_PAREN)); + let m = p.start(); + // TODO: use delimited + p.bump(L_PAREN); + if p.at(R_PAREN) { + p.error("expected at least one cursor argument"); + } + while !p.at(EOF) && !p.at(R_PAREN) { + cursor_arg(p); + if !p.eat(COMMA) { + break; + } + } + p.expect(R_PAREN); + m.complete(p, PLPGSQL_CURSOR_ARG_LIST); +} + +fn cursor_arg(p: &mut Parser) { + let m = p.start(); + if at_name(p, 0) { + name(p, PLPGSQL_VAR_NAME); + decl_datatype(p); + } else { + p.error(format!( + "expected a cursor argument name, got {:?}", + p.current() + )); + } + m.complete(p, PLPGSQL_CURSOR_ARG); +} + +fn decl_datatype(p: &mut Parser) { + if !at_percent_datatype(p) { + grammar::func_type(p); + return; + } + let m = p.start(); + path_name_ref(p); + let kind = if grammar::opt_percent_type(p).is_some() { + PERCENT_TYPE + } else { + p.bump(PERCENT); + p.bump_remap(ROWTYPE_KW); + PLPGSQL_PERCENT_ROWTYPE + }; + p.eat(ARRAY_KW); + while !p.at(EOF) && grammar::opt_array_bound(p) {} + m.complete(p, kind); +} + +fn path_name_ref(p: &mut Parser) { + assert!(at_path(p).is_some()); + let m = p.start(); + name(p, PATH_SEGMENT_REF); + 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 = m.complete(p, PATH_REF); + } +} + +fn at_percent_datatype(p: &Parser) -> bool { + let Some(n) = at_path(p) else { + return false; + }; + p.nth_at(n, PERCENT) && (p.nth_at(n + 1, TYPE_KW) || p.nth_at_contextual_kw(n + 1, ROWTYPE_KW)) +} + +fn at_path(p: &Parser) -> Option { + let mut n = 0; + while !p.nth_at(n, EOF) && at_name(p, n) { + n += 1; + if !p.nth_at(n, DOT) { + return Some(n); + } + n += 1; + } + None +} + +fn opt_not_null(p: &mut Parser) { + if !p.at(NOT_KW) { + return; + } + let m = p.start(); + p.bump(NOT_KW); + p.expect(NULL_KW); + m.complete(p, PLPGSQL_NOT_NULL); +} + +fn opt_var_init(p: &mut Parser) { + if !p.at(COLON_EQ) && !p.at(EQ) && !p.at(DEFAULT_KW) { + return; + } + let m = p.start(); + if !p.eat(COLON_EQ) && !p.eat(EQ) { + p.bump(DEFAULT_KW); + } + expr(p); + m.complete(p, PLPGSQL_VAR_INIT); +} + +fn expr(p: &mut Parser) { + if grammar::expr(p).is_none() { + p.error("expected an expression"); + } +} + +#[derive(Clone, Copy, PartialEq)] +enum BodyKind { + Block, + ExceptionHandler, + IfThen, + IfElse, + CaseWhen, + CaseElse, + Loop, +} + +fn body(p: &mut Parser, kind: BodyKind) { + let m = p.start(); + while !p.at(EOF) && !at_body_end(p, kind) { + stmt(p); + } + m.complete(p, PLPGSQL_BODY); +} + +fn stmt(p: &mut Parser) { + if at_block_start(p) { + opt_block(p); + } else if at_loop_start(p) { + loop_stmt(p); + } else if at_assign_stmt(p) { + assign_stmt(p); + } else if at_exit_stmt(p) { + exit_stmt(p); + } else if p.at(CALL_KW) { + call_stmt(p); + } else if p.at(DO_KW) { + do_stmt(p); + } else if p.nth_at_contextual_kw(0, PERFORM_KW) { + perform_stmt(p); + } else if p.at(RETURN_KW) { + return_stmt(p); + } else if p.nth_at_contextual_kw(0, ASSERT_KW) { + assert_stmt(p); + } else if p.nth_at_contextual_kw(0, RAISE_KW) { + raise_stmt(p); + } else if at_get_diag_stmt(p) { + get_diag_stmt(p); + } else if at_transaction_stmt(p) { + transaction_stmt(p); + } else if p.at(CASE_KW) { + case_stmt(p); + } else if p.at(IF_KW) { + if_stmt(p); + } else if p.at(NULL_KW) && p.nth_at(1, SEMICOLON) { + let m = p.start(); + p.bump(NULL_KW); + p.bump(SEMICOLON); + m.complete(p, PLPGSQL_NULL_STMT); + } else { + temp_unknown(p, "expected a statement"); + } +} + +fn call_stmt(p: &mut Parser) { + assert!(p.at(CALL_KW)); + let m = p.start(); + match grammar::stmt(p, &grammar::StmtRestrictions::default()).map(|x| x.kind()) { + Some(CALL) => (), + _ => { + p.error("expected a CALL statement"); + } + } + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_CALL_STMT); +} + +fn do_stmt(p: &mut Parser) { + assert!(p.at(DO_KW)); + let m = p.start(); + match grammar::stmt(p, &grammar::StmtRestrictions::default()).map(|x| x.kind()) { + Some(DO) => (), + _ => { + p.error("expected a DO statement"); + } + } + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_DO_STMT); +} + +fn perform_stmt(p: &mut Parser) { + assert!(p.nth_at_contextual_kw(0, PERFORM_KW)); + let m = p.start(); + match grammar::perform_select(p).kind() { + SELECT | SELECT_INTO | COMPOUND_SELECT => (), + _ => { + p.error("expected a SELECT statement"); + } + } + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_PERFORM_STMT); +} + +fn return_stmt(p: &mut Parser) { + assert!(p.at(RETURN_KW)); + let m = p.start(); + p.bump(RETURN_KW); + let composite = p.nth_at(1, DOT); + let kind = if p.at(NEXT_KW) && !composite { + p.bump(NEXT_KW); + if !p.at(SEMICOLON) { + expr(p); + } + PLPGSQL_RETURN_NEXT_STMT + } else if p.nth_at_contextual_kw(0, QUERY_KW) && !composite && p.nth_at(1, EXECUTE_KW) { + p.bump_remap(QUERY_KW); + p.bump(EXECUTE_KW); + expr(p); + opt_using_clause(p); + PLPGSQL_RETURN_QUERY_EXECUTE_STMT + } else if p.nth_at_contextual_kw(0, QUERY_KW) && !composite { + p.bump_remap(QUERY_KW); + if grammar::stmt(p, &grammar::StmtRestrictions::default()).is_none() { + p.error("expected a query"); + } + PLPGSQL_RETURN_QUERY_STMT + } else { + if !p.at(SEMICOLON) { + expr(p); + } + PLPGSQL_RETURN_STMT + }; + p.expect(SEMICOLON); + m.complete(p, kind); +} + +fn assert_stmt(p: &mut Parser) { + assert!(p.nth_at_contextual_kw(0, ASSERT_KW)); + let m = p.start(); + p.bump_remap(ASSERT_KW); + expr(p); + if p.eat(COMMA) { + expr(p); + } + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_ASSERT_STMT); +} + +fn opt_using_clause(p: &mut Parser) { + if !p.at(USING_KW) { + return; + } + let m = p.start(); + p.bump(USING_KW); + expr(p); + while !p.at(EOF) && p.eat(COMMA) { + expr(p); + } + m.complete(p, PLPGSQL_USING_CLAUSE); +} + +const RAISE_LEVELS: [SyntaxKind; 6] = [ + EXCEPTION_KW, + WARNING_KW, + NOTICE_KW, + INFO_KW, + LOG_KW, + DEBUG_KW, +]; + +const RAISE_OPTIONS: [(SyntaxKind, SyntaxKind); 9] = [ + (ERRCODE_KW, PLPGSQL_RAISE_OPTION_ERRCODE), + (MESSAGE_KW, PLPGSQL_RAISE_OPTION_MESSAGE), + (DETAIL_KW, PLPGSQL_RAISE_OPTION_DETAIL), + (HINT_KW, PLPGSQL_RAISE_OPTION_HINT), + (COLUMN_KW, PLPGSQL_RAISE_OPTION_COLUMN), + (CONSTRAINT_KW, PLPGSQL_RAISE_OPTION_CONSTRAINT), + (DATATYPE_KW, PLPGSQL_RAISE_OPTION_DATATYPE), + (TABLE_KW, PLPGSQL_RAISE_OPTION_TABLE), + (SCHEMA_KW, PLPGSQL_RAISE_OPTION_SCHEMA), +]; + +fn raise_stmt(p: &mut Parser) { + assert!(p.nth_at_contextual_kw(0, RAISE_KW)); + let m = p.start(); + p.bump_remap(RAISE_KW); + if !p.at(SEMICOLON) { + opt_raise_level(p); + if opt_raise_message(p) { + while !p.at(EOF) && p.eat(COMMA) { + expr(p); + } + } else if !p.at(USING_KW) { + condition(p); + } + opt_raise_using_clause(p); + } + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_RAISE_STMT); +} + +fn opt_raise_level(p: &mut Parser) { + let Some(kw) = RAISE_LEVELS + .into_iter() + .find(|&kw| at_maybe_contextual_kw(p, 0, kw)) + else { + return; + }; + let m = p.start(); + bump_maybe_contextual_kw(p, kw); + m.complete(p, PLPGSQL_RAISE_LEVEL); +} + +fn opt_raise_message(p: &mut Parser) -> bool { + let Some(literal) = grammar::opt_string_literal(p) else { + return false; + }; + literal.precede(p).complete(p, PLPGSQL_RAISE_MESSAGE); + true +} + +fn opt_raise_using_clause(p: &mut Parser) { + if !p.at(USING_KW) { + return; + } + let m = p.start(); + p.bump(USING_KW); + raise_option(p); + while !p.at(EOF) && p.eat(COMMA) { + raise_option(p); + } + m.complete(p, PLPGSQL_RAISE_USING_CLAUSE); +} + +fn raise_option(p: &mut Parser) { + let m = p.start(); + let kind = match RAISE_OPTIONS + .into_iter() + .find(|&(kw, _)| at_maybe_contextual_kw(p, 0, kw)) + { + Some((kw, kind)) => { + bump_maybe_contextual_kw(p, kw); + kind + } + None => { + p.error("unrecognized RAISE statement option"); + ERROR + } + }; + if !p.eat(COLON_EQ) { + p.expect(EQ); + } + expr(p); + m.complete(p, kind); +} + +fn transaction_stmt(p: &mut Parser) { + assert!(at_transaction_stmt(p)); + let m = p.start(); + let kind = if p.eat(COMMIT_KW) { + PLPGSQL_COMMIT_STMT + } else { + p.bump(ROLLBACK_KW); + PLPGSQL_ROLLBACK_STMT + }; + grammar::opt_chain_clause(p); + p.expect(SEMICOLON); + m.complete(p, kind); +} + +const DIAG_ITEM_KINDS: [SyntaxKind; 13] = [ + ROW_COUNT_KW, + PG_ROUTINE_OID_KW, + PG_CONTEXT_KW, + PG_EXCEPTION_DETAIL_KW, + PG_EXCEPTION_HINT_KW, + PG_EXCEPTION_CONTEXT_KW, + COLUMN_NAME_KW, + CONSTRAINT_NAME_KW, + PG_DATATYPE_NAME_KW, + MESSAGE_TEXT_KW, + TABLE_NAME_KW, + SCHEMA_NAME_KW, + RETURNED_SQLSTATE_KW, +]; + +fn get_diag_stmt(p: &mut Parser) { + assert!(at_get_diag_stmt(p)); + let m = p.start(); + p.bump_remap(GET_KW); + opt_diag_area(p); + expect_contextual_kw(p, DIAGNOSTICS_KW); + diag_item_list(p); + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_GET_DIAG_STMT); +} + +fn opt_diag_area(p: &mut Parser) { + if !p.at(CURRENT_KW) && !p.nth_at_contextual_kw(0, STACKED_KW) { + return; + } + let m = p.start(); + if !p.eat(CURRENT_KW) { + p.bump_remap(STACKED_KW); + } + m.complete(p, PLPGSQL_DIAG_AREA); +} + +fn diag_item_list(p: &mut Parser) { + let m = p.start(); + diag_item(p); + while !p.at(EOF) && p.eat(COMMA) { + diag_item(p); + } + m.complete(p, PLPGSQL_DIAG_ITEM_LIST); +} + +fn diag_item(p: &mut Parser) { + let m = p.start(); + if at_name(p, 0) { + diag_target(p); + if !p.eat(COLON_EQ) { + p.expect(EQ); + } + diag_kind(p); + } else { + p.error(format!( + "expected a diagnostics target, got {:?}", + p.current() + )); + } + m.complete(p, PLPGSQL_DIAG_ITEM); +} + +fn diag_target(p: &mut Parser) { + assert!(at_name(p, 0)); + let m = p.start(); + name(p, PLPGSQL_VAR_NAME_REF); + while !p.at(EOF) && p.at(DOT) { + grammar::field_accessor(p); + } + if p.at(L_BRACK) { + let m = p.start(); + p.error("a GET DIAGNOSTICS target can't be subscripted"); + grammar::accessors(p); + m.complete(p, ERROR); + } + m.complete(p, PLPGSQL_DIAG_TARGET); +} + +fn diag_kind(p: &mut Parser) { + let m = p.start(); + match DIAG_ITEM_KINDS + .into_iter() + .find(|&kw| p.nth_at_contextual_kw(0, kw)) + { + Some(kw) => p.bump_remap(kw), + None => { + p.error("unrecognized GET DIAGNOSTICS item"); + if at_name(p, 0) { + p.bump_any(); + } + } + } + m.complete(p, PLPGSQL_DIAG_KIND); +} + +fn assign_stmt(p: &mut Parser) { + assert!(at_assign_stmt(p)); + let m = p.start(); + assign_target(p); + if !p.eat(COLON_EQ) { + p.expect(EQ); + } + expr(p); + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_ASSIGN_STMT); +} + +fn assign_target(p: &mut Parser) { + let m = p.start(); + name(p, PLPGSQL_VAR_NAME_REF); + grammar::accessors(p); + m.complete(p, PLPGSQL_ASSIGN_TARGET); +} + +fn if_stmt(p: &mut Parser) { + assert!(p.at(IF_KW)); + let m = p.start(); + p.bump(IF_KW); + expr(p); + p.expect(THEN_KW); + body(p, BodyKind::IfThen); + while !p.at(EOF) && opt_elsif_clause(p) {} + opt_else_clause(p, BodyKind::IfElse); + p.expect(END_KW); + p.expect(IF_KW); + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_IF_STMT); +} + +fn case_stmt(p: &mut Parser) { + assert!(p.at(CASE_KW)); + let m = p.start(); + p.bump(CASE_KW); + if !p.at(WHEN_KW) { + expr(p); + if !p.at(WHEN_KW) { + p.error("expected `when`"); + } + } + while !p.at(EOF) && p.at(WHEN_KW) { + case_when(p); + } + opt_else_clause(p, BodyKind::CaseElse); + p.expect(END_KW); + p.expect(CASE_KW); + p.expect(SEMICOLON); + m.complete(p, PLPGSQL_CASE_STMT); +} + +fn case_when(p: &mut Parser) { + assert!(p.at(WHEN_KW)); + let m = p.start(); + p.bump(WHEN_KW); + expr(p); + while !p.at(EOF) && p.eat(COMMA) { + expr(p); + } + p.expect(THEN_KW); + body(p, BodyKind::CaseWhen); + m.complete(p, PLPGSQL_CASE_WHEN); +} + +fn loop_stmt(p: &mut Parser) { + assert!(at_loop_start(p)); + let m = p.start(); + opt_label(p); + let kind = if p.nth_at_contextual_kw(0, WHILE_KW) { + p.bump_remap(WHILE_KW); + expr(p); + PLPGSQL_WHILE_STMT + } else { + PLPGSQL_LOOP_STMT + }; + expect_contextual_kw(p, LOOP_KW); + body(p, BodyKind::Loop); + p.expect(END_KW); + expect_contextual_kw(p, LOOP_KW); + opt_label_name_ref(p); + p.expect(SEMICOLON); + m.complete(p, kind); +} + +fn exit_stmt(p: &mut Parser) { + assert!(at_exit_stmt(p)); + let m = p.start(); + let kind = if p.eat(CONTINUE_KW) { + PLPGSQL_CONTINUE_STMT + } else { + p.bump_remap(EXIT_KW); + PLPGSQL_EXIT_STMT + }; + opt_label_name_ref(p); + opt_exit_when(p); + p.expect(SEMICOLON); + m.complete(p, kind); +} + +fn opt_exit_when(p: &mut Parser) { + if !p.at(WHEN_KW) { + return; + } + let m = p.start(); + p.bump(WHEN_KW); + expr(p); + m.complete(p, PLPGSQL_EXIT_WHEN); +} + +fn expect_contextual_kw(p: &mut Parser, kw: SyntaxKind) { + if p.nth_at_contextual_kw(0, kw) { + p.bump_remap(kw); + } else { + p.error(format!("expected {kw:?}")); + } +} + +fn opt_elsif_clause(p: &mut Parser) -> bool { + if !at_elsif(p) { + return false; + } + let m = p.start(); + if p.nth_at_contextual_kw(0, ELSEIF_KW) { + p.bump_remap(ELSEIF_KW); + } else { + p.bump_remap(ELSIF_KW); + } + expr(p); + p.expect(THEN_KW); + body(p, BodyKind::IfThen); + m.complete(p, PLPGSQL_ELSIF_CLAUSE); + true +} + +fn opt_else_clause(p: &mut Parser, kind: BodyKind) { + if !p.at(ELSE_KW) { + return; + } + let m = p.start(); + p.bump(ELSE_KW); + body(p, kind); + m.complete(p, PLPGSQL_ELSE_CLAUSE); +} + +// TODO: remove this once we get all the ast nodes working +fn temp_unknown(p: &mut Parser, message: &str) { + let m = p.start(); + p.error(format!("{message}, got {:?}", p.current())); + + let mut depth = 0; + while !p.at(EOF) { + if depth == 0 && p.at(SEMICOLON) { + break; + } + if p.at(BEGIN_KW) { + depth += 1; + } else if p.at(END_KW) && depth > 0 { + depth -= 1; + } + p.bump_any(); + } + p.eat(SEMICOLON); + m.complete(p, ERROR); +} + +fn opt_exception_section(p: &mut Parser) { + if !p.nth_at_contextual_kw(0, EXCEPTION_KW) { + return; + } + let m = p.start(); + p.bump_remap(EXCEPTION_KW); + while !p.at(EOF) && p.at(WHEN_KW) { + exception_handler(p); + } + m.complete(p, PLPGSQL_EXCEPTION_SECTION); +} + +fn exception_handler(p: &mut Parser) { + assert!(p.at(WHEN_KW)); + let m = p.start(); + // TODO: use delimited + p.bump(WHEN_KW); + condition(p); + while !p.at(EOF) && p.eat(OR_KW) { + condition(p); + } + p.expect(THEN_KW); + body(p, BodyKind::ExceptionHandler); + m.complete(p, PLPGSQL_EXCEPTION_HANDLER); +} + +fn condition(p: &mut Parser) { + let m = p.start(); + if at_name(p, 0) { + let sqlstate = p.nth_at_contextual_kw(0, SQLSTATE_KW); + p.bump_any(); + if sqlstate { + p.expect(STRING); + } + } else { + p.error(format!("expected a condition name, got {:?}", p.current())); + } + m.complete(p, PLPGSQL_CONDITION); +} + +fn at_alias_decl(p: &Parser) -> bool { + at_name(p, 0) && p.nth_at_contextual_kw(1, ALIAS_KW) +} + +fn at_cursor_decl(p: &Parser) -> bool { + at_name(p, 0) && (p.nth_at(1, CURSOR_KW) || p.nth_at(1, SCROLL_KW) || p.nth_at(1, NO_KW)) +} + +fn at_var_decl(p: &Parser) -> bool { + at_name(p, 0) +} + +fn at_block_start(p: &Parser) -> bool { + p.at_ts(BLOCK_FIRST) || at_block_label(p) +} + +fn at_block_label(p: &Parser) -> bool { + at_label(p) && p.nth_at_ts(5, BLOCK_FIRST) +} + +fn at_label(p: &Parser) -> bool { + p.at(LESS_LESS) && at_name(p, 2) && p.nth_at(3, GREATER_GREATER) +} + +fn at_loop_start(p: &Parser) -> bool { + at_loop_kw(p, 0) || (at_label(p) && at_loop_kw(p, 5)) +} + +fn at_loop_kw(p: &Parser, n: usize) -> bool { + p.nth_at_contextual_kw(n, LOOP_KW) || p.nth_at_contextual_kw(n, WHILE_KW) +} + +fn at_assign_stmt(p: &Parser) -> bool { + at_assign_target(p) + && (p.nth_at(1, COLON_EQ) || p.nth_at(1, EQ) || p.nth_at(1, L_BRACK) || p.nth_at(1, DOT)) +} + +fn at_assign_target(p: &Parser) -> bool { + at_name(p, 0) && (p.at_ts(grammar::NAME_FIRST) || p.at(POSITIONAL_PARAM)) +} + +fn at_get_diag_stmt(p: &Parser) -> bool { + p.nth_at_contextual_kw(0, GET_KW) && !p.nth_at(1, DOT) +} + +fn at_transaction_stmt(p: &Parser) -> bool { + p.at(COMMIT_KW) || p.at(ROLLBACK_KW) +} + +fn at_exit_stmt(p: &Parser) -> bool { + p.at(CONTINUE_KW) || p.nth_at_contextual_kw(0, EXIT_KW) +} + +fn at_name(p: &Parser, n: usize) -> bool { + if p.nth_at(n, IDENT) { + return !p.nth_at_contextual_ts(n, PLPGSQL_RESERVED_CONTEXTUAL_KEYWORDS); + } + if p.nth_at(n, POSITIONAL_PARAM) { + return true; + } + p.nth_at_ts(n, ALL_KEYWORDS) && !p.nth_at_ts(n, PLPGSQL_RESERVED_KEYWORDS) +} + +fn at_maybe_contextual_kw(p: &Parser, n: usize, kw: SyntaxKind) -> bool { + p.nth_at(n, kw) || p.nth_at_contextual_kw(n, kw) +} + +fn bump_maybe_contextual_kw(p: &mut Parser, kw: SyntaxKind) { + if p.at(kw) { + p.bump(kw); + } else { + p.bump_remap(kw); + } +} + +fn at_elsif(p: &Parser) -> bool { + p.nth_at_contextual_kw(0, ELSIF_KW) || p.nth_at_contextual_kw(0, ELSEIF_KW) +} + +fn at_body_end(p: &Parser, kind: BodyKind) -> bool { + if at_block_end(p) { + return true; + } + match kind { + BodyKind::Block => p.nth_at_contextual_kw(0, EXCEPTION_KW), + BodyKind::ExceptionHandler => p.nth_at_contextual_kw(0, EXCEPTION_KW) || p.at(WHEN_KW), + BodyKind::IfThen => at_end_if(p) || p.at(ELSE_KW) || at_elsif(p), + BodyKind::IfElse => at_end_if(p), + BodyKind::CaseWhen => at_end_case(p) || p.at(WHEN_KW) || p.at(ELSE_KW), + BodyKind::CaseElse => at_end_case(p), + BodyKind::Loop => at_end_loop(p), + } +} + +fn at_end_if(p: &Parser) -> bool { + p.at(END_KW) && p.nth_at(1, IF_KW) +} + +fn at_end_case(p: &Parser) -> bool { + p.at(END_KW) && p.nth_at(1, CASE_KW) +} + +fn at_end_loop(p: &Parser) -> bool { + p.at(END_KW) && p.nth_at_contextual_kw(1, LOOP_KW) +} + +fn at_block_end(p: &Parser) -> bool { + if !p.at(END_KW) { + return false; + } + !p.nth_at(1, IF_KW) && !p.nth_at(1, CASE_KW) && !p.nth_at_contextual_kw(1, LOOP_KW) +} diff --git a/crates/squawk_parser/tests/data/plpgsql/err/alias_decl.sql b/crates/squawk_parser/tests/data/plpgsql/err/alias_decl.sql new file mode 100644 index 000000000..2f1427532 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/alias_decl.sql @@ -0,0 +1,9 @@ +do $$ +declare + a alias for 5; + b alias 1; + c int; +begin + null; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/assert_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/assert_stmt.sql new file mode 100644 index 000000000..89f1309f0 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/assert_stmt.sql @@ -0,0 +1,6 @@ +do $$ +begin + assert; + assert 1 = 1, ; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/assign_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/assign_stmt.sql new file mode 100644 index 000000000..820c21b07 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/assign_stmt.sql @@ -0,0 +1,9 @@ +do $$ +begin + x := ; + a[1 := 2; + x 1; + all := 1; + select := 1; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/call_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/call_stmt.sql new file mode 100644 index 000000000..0b83a6338 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/call_stmt.sql @@ -0,0 +1,7 @@ +do $$ +begin + call; + call p1(1, 2) + call p2 1; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/case_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/case_stmt.sql new file mode 100644 index 000000000..157367783 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/case_stmt.sql @@ -0,0 +1,27 @@ +do $$ +declare + x int := 1; +begin + case x + else + null; + end case; + + case x + when 1 then + null; + end case + + case case when x = 1 then 1 end + when 1 then + null; + end case; + + case + when case when x = 1 then true end then + null; + when x = 2, case when x = 3 then true end then + null; + end case; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/comp_option.sql b/crates/squawk_parser/tests/data/plpgsql/err/comp_option.sql new file mode 100644 index 000000000..6cf1e6bcc --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/comp_option.sql @@ -0,0 +1,41 @@ +do $$ +#option dumpp +begin end $$; + +do $$ +#foo bar +begin end $$; + +do $$ +#variable_conflict +begin end $$; + +do $$ +#variable_conflict use_default +begin end $$; + +-- a number is a syntax error +do $$ +#print_strict_params 1 +begin end $$; + +-- must be on/off +do $$ +#print_strict_params banana +begin end $$; + +-- a quoted value keeps its case, so this one doesn't match `on` +do $$ +#print_strict_params "ON" +begin end $$; + +-- `loop` is PL/pgSQL-reserved +do $$ +#print_strict_params loop +begin end $$; + +-- options only come before the block +do $$ +declare x int; +#option dump +begin end $$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/cursor_decl.sql b/crates/squawk_parser/tests/data/plpgsql/err/cursor_decl.sql new file mode 100644 index 000000000..2d715de60 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/cursor_decl.sql @@ -0,0 +1,10 @@ +do $$ +declare + a cursor () for select 1; + b no cursor for select 1; + c cursor select 1; + d int; +begin + null; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/do_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/do_stmt.sql new file mode 100644 index 000000000..9ddafb871 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/do_stmt.sql @@ -0,0 +1,6 @@ +do $$ +begin + do := 1; + do; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/get_diag_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/get_diag_stmt.sql new file mode 100644 index 000000000..e3e48a210 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/get_diag_stmt.sql @@ -0,0 +1,11 @@ +do $$ +declare + x int; +begin + get diagnostics x = bogus; + get diagnostics x = "row_count"; + get diagnostics x row_count; + get diagnostics x[1] = row_count; + get diagnostics; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/if_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/if_stmt.sql new file mode 100644 index 000000000..ee7392019 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/if_stmt.sql @@ -0,0 +1,24 @@ +do $$ +begin + if 1 = 1 + null; + end if; + + if 1 = 1 then + null; + else + null; + elsif 1 = 2 then + null; + end if; + + -- `expr_until_then` ends the condition at the case's own `then` + if case when true then 1 end = 1 then + null; + elsif 1 = case when true then 1 end then + null; + end if; + + null; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/loop_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/loop_stmt.sql new file mode 100644 index 000000000..f73adf656 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/loop_stmt.sql @@ -0,0 +1,17 @@ +do $$ +begin + loop + null; + end; + + while 1 = 1 + null; + end loop; + + loop + null; + end loop + + exit when +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/missing_end.sql b/crates/squawk_parser/tests/data/plpgsql/err/missing_end.sql new file mode 100644 index 000000000..3fa0e7e71 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/missing_end.sql @@ -0,0 +1,4 @@ +do $$ +begin + null; +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/perform_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/perform_stmt.sql new file mode 100644 index 000000000..59817d2ad --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/perform_stmt.sql @@ -0,0 +1,10 @@ +do $$ +begin + perform from; + perform 1 into x; + perform 1 into strict x; + perform with x as (select 1) select * from x; + perform table onecol; + perform values (1); +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/raise_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/raise_stmt.sql new file mode 100644 index 000000000..89531d7f3 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/raise_stmt.sql @@ -0,0 +1,9 @@ +do $$ +begin + raise notice 'x' using bogus = 1; + raise notice 'x' using errcode; + raise notice 'x' using errcode = ; + raise sqlstate; + raise exception; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/return_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/return_stmt.sql new file mode 100644 index 000000000..7ed7ef8c0 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/return_stmt.sql @@ -0,0 +1,9 @@ +do $$ +begin + return next[1]; + return query; + return query execute; + return query execute 'select 1' using; + return 1 +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/transaction_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/transaction_stmt.sql new file mode 100644 index 000000000..36cbe4b3b --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/err/transaction_stmt.sql @@ -0,0 +1,9 @@ +do $$ +begin + commit work; + commit prepared 'x'; + rollback to savepoint s; + commit and; + commit chain; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/err/unrecognized_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/err/unrecognized_stmt.sql deleted file mode 100644 index 117ecaadf..000000000 --- a/crates/squawk_parser/tests/data/plpgsql/err/unrecognized_stmt.sql +++ /dev/null @@ -1 +0,0 @@ -begin null; end diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/alias_decl.sql b/crates/squawk_parser/tests/data/plpgsql/ok/alias_decl.sql new file mode 100644 index 000000000..130e6fd0f --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/alias_decl.sql @@ -0,0 +1,13 @@ +do $$ +<> +declare + a alias for new; + b alias for $1; + c alias for blk.x; + d alias for pg_catalog.pg_class.relname; + e alias for "my var"; + f alias for value; +begin + null; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/assert_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/assert_stmt.sql new file mode 100644 index 000000000..f71af202d --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/assert_stmt.sql @@ -0,0 +1,10 @@ +do $$ +declare + assert int := 1; +begin + assert true; + assert 1 = 1, 'boom'; + assert (select count(*) from onecol) > 0, 'empty: ' || 'x'; + assert := 2; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/assign_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/assign_stmt.sql new file mode 100644 index 000000000..7bea14842 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/assign_stmt.sql @@ -0,0 +1,19 @@ +do $$ +declare + a int[]; + r record; +begin + x := 1; + x = 1; + a[1] := 2; + a[1:2] := array[1,2]; + a[1:2].i := array[11,12]; + r.f1 := 3; + r.c1[1].i := 11; + $1 := 4; + values := 5; + perform := 6; + x := case when r.f1 = 1 then 'a' else 'b' end; + x := (select max(f1) from onecol); +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/block.sql b/crates/squawk_parser/tests/data/plpgsql/ok/block.sql new file mode 100644 index 000000000..326ccbd10 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/block.sql @@ -0,0 +1,20 @@ +do $$ +begin + begin + null; + end; +end +$$; + +create function f() returns int language plpgsql as $$ +begin + null; +end +$$; + +do $$ +declare +begin + null; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/call_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/call_stmt.sql new file mode 100644 index 000000000..dc035111a --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/call_stmt.sql @@ -0,0 +1,10 @@ +do $$ +declare + call int := 1; +begin + call transaction_test1(); + call p1(1, 2); + call p2(a => 1, b := 2); + call := 2; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/case_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/case_stmt.sql new file mode 100644 index 000000000..08d4f44a5 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/case_stmt.sql @@ -0,0 +1,39 @@ +do $$ +declare + x int := 1; +begin + case x + when 1 then + null; + end case; + + case x + when 1, 2 then + null; + when 3 then + null; + else + null; + end case; + + case + when x = 1 then + case x + when 1 then + null; + end case; + when x = 2 then + null; + else + end case; + + case (case when x = 1 then 1 end) + when 1 then + end case; + + case coalesce(case when x = 1 then 1 end, 0) + when 1 then + null; + end case; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/comp_option.sql b/crates/squawk_parser/tests/data/plpgsql/ok/comp_option.sql new file mode 100644 index 000000000..c00627c95 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/comp_option.sql @@ -0,0 +1,42 @@ +do $$ +#option dump +begin end $$; + +do $$ +#variable_conflict error +begin end $$; + +do $$ +#variable_conflict use_variable +begin end $$; + +do $$ +#variable_conflict use_column +begin end $$; + +do $$ +#print_strict_params on +begin end $$; + +do $$ +#print_strict_params off +begin end $$; + +-- unquoted values are down-cased, so these are the same option +do $$ +#print_strict_params ON +begin end $$; + +do $$ +#print_strict_params "on" +begin end $$; + +-- whitespace after the `#`, and more than one option, are both fine +do $$ # option dump +#variable_conflict use_variable +#print_strict_params on +#variable_conflict use_column +begin end $$; + +-- no newline required before the block +do $$ #option dump begin end $$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/cursor_decl.sql b/crates/squawk_parser/tests/data/plpgsql/ok/cursor_decl.sql new file mode 100644 index 000000000..77f913442 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/cursor_decl.sql @@ -0,0 +1,12 @@ +do $$ +declare + a cursor for select 1; + b scroll cursor for select f1 from int4_tbl; + c no scroll cursor for select f1 from int4_tbl; + d cursor (r1 integer, r2 integer) for select * from generate_series(r1, r2) i; + e cursor (p1 int4_tbl.f1%type) is select 1; + cursor cursor for table t; +begin + null; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/do_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/do_stmt.sql new file mode 100644 index 000000000..bbfea178e --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/do_stmt.sql @@ -0,0 +1,7 @@ +do $$ +begin + do $q$ begin null; end $q$; + do language plpgsql $q$ begin null; end $q$; + do $q$ begin null; end $q$ language plpgsql; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/exception.sql b/crates/squawk_parser/tests/data/plpgsql/ok/exception.sql new file mode 100644 index 000000000..e323ec061 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/exception.sql @@ -0,0 +1,22 @@ +do $$ +begin + null; +exception + when others then + null; + when division_by_zero or sqlstate '22012' then + null; +end +$$; + +do $$ +<> +begin + begin + null; + exception + when no_data_found then + null; + end; +end lbl; +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/get_diag_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/get_diag_stmt.sql new file mode 100644 index 000000000..918e5ff7a --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/get_diag_stmt.sql @@ -0,0 +1,49 @@ +do $$ +declare + get int; + x int; + y text; + "table" int; + r record; +begin + get diagnostics x = row_count; + get current diagnostics x = row_count; + get diagnostics x := row_count; + get diagnostics X = ROW_COUNT; + get diagnostics get = row_count, x = pg_routine_oid; + get diagnostics "table" = row_count; + get diagnostics y = pg_context; + r := row(1); + get diagnostics r.f1 = row_count; + get := 1; +exception when others then + get stacked diagnostics + y = returned_sqlstate, + y = message_text, + y = pg_exception_detail, + y = pg_exception_hint, + y = pg_exception_context, + y = column_name, + y = constraint_name, + y = pg_datatype_name, + y = table_name, + y = schema_name; +end +$$; + +-- `current` and `stacked` name variables here, so they can't also head the area +do $$ +declare + current int; + stacked int; +begin + get diagnostics current = row_count; + get diagnostics stacked = row_count; +end +$$; + +create function get_diag(a int) returns void as $$ +begin + get diagnostics $1 = row_count; +end +$$ language plpgsql; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/if_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/if_stmt.sql new file mode 100644 index 000000000..d7249c442 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/if_stmt.sql @@ -0,0 +1,40 @@ +do $$ +declare + x int := 1; +begin + if x > 0 then + null; + end if; + + if x = 1 then + null; + else + null; + end if; + + if x = 1 then + if x = 2 then + null; + end if; + elsif x = 2 then + null; + elseif x = 3 then + null; + elsif (case when x = 4 then true else false end) then + null; + else + null; + end if; + + if x is null then + end if; + + if coalesce(case when x = 1 then 1 end, 0) = 1 then + null; + end if; + + if (array[case when x = 1 then 1 end])[1] = 1 then + null; + end if; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/label.sql b/crates/squawk_parser/tests/data/plpgsql/ok/label.sql new file mode 100644 index 000000000..013a04c4e --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/label.sql @@ -0,0 +1,17 @@ +do $$ +<> +begin + <> + declare + begin + null; + end inner; +end outer; +$$; + +do $$ +<> +begin + null; +end; +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/loop_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/loop_stmt.sql new file mode 100644 index 000000000..db8fc3d5f --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/loop_stmt.sql @@ -0,0 +1,37 @@ +do $$ +declare + i int := 0; +begin + loop + exit; + end loop; + + loop + exit when i > 10; + continue when i = 2; + continue; + end loop; + + while i < 10 loop + null; + end loop; + + while case when i = 1 then true else false end loop + null; + end loop; + + <> + loop + <> + while i < 10 loop + continue outer when i = 2; + exit inner; + end loop inner; + end loop outer; + + <> + begin + exit blk; + end blk; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/null_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/null_stmt.sql index ff464d15b..d9054f2ae 100644 --- a/crates/squawk_parser/tests/data/plpgsql/ok/null_stmt.sql +++ b/crates/squawk_parser/tests/data/plpgsql/ok/null_stmt.sql @@ -1 +1,6 @@ -null; +do $$ +begin + null; + null; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/percent_type.sql b/crates/squawk_parser/tests/data/plpgsql/ok/percent_type.sql new file mode 100644 index 000000000..7d60d1fa8 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/percent_type.sql @@ -0,0 +1,19 @@ +do $$ +declare + v int; + a v%type; + b v%type[]; + c v%type[1][3]; + d v%type array; + e v%type array[1]; + f foo%rowtype; + g notice%rowtype; + h foo.bar.baz%rowtype; + i pg_catalog.pg_class%rowtype[]; + j table%rowtype; + k select%type; + l $1%type; +begin + null; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/perform_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/perform_stmt.sql new file mode 100644 index 000000000..c464ebe39 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/perform_stmt.sql @@ -0,0 +1,14 @@ +do $$ +declare + perform int := 1; +begin + perform 1; + perform; + perform distinct f1 from onecol; + perform * from onecol order by 1 limit 1; + perform 1 union select 2; + perform (select max(f1) from onecol); + perform f1 from onecol where f1 > 0 group by f1 having count(*) > 1; + perform := 2; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/raise_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/raise_stmt.sql new file mode 100644 index 000000000..1c8a95f9d --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/raise_stmt.sql @@ -0,0 +1,22 @@ +do $$ +declare + raise int := 1; +begin + raise; + raise notice 'plain'; + raise notice 'fmt % %', 1, 2; + raise 'no level'; + raise exception 'boom' using errcode := '22012'; + raise warning using message = 'm', detail = 'd', hint = 'h'; + raise exception using column = 'c', constraint = 'k', datatype = 'd', + table = 't', schema = 's'; + raise division_by_zero; + raise sqlstate '22012'; + raise debug 'dbg'; + raise log 'lg'; + raise info 'nf'; + raise notice E'esc'; + raise using errcode = '22012'; + raise := 2; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/return_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/return_stmt.sql new file mode 100644 index 000000000..183f08b36 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/return_stmt.sql @@ -0,0 +1,20 @@ +do $$ +declare + return int := 1; + next rec; +begin + return; + return 1; + return (select max(f1) from onecol); + return next; + return next 1; + return next.x; + return query select 1; + return query values (1), (2); + return query insert into t values (1) returning a; + return query execute 'select 1'; + return query execute 'select $1' using 7; + return query execute 'select $1, $2' using 7, 8; + return := 2; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/transaction_stmt.sql b/crates/squawk_parser/tests/data/plpgsql/ok/transaction_stmt.sql new file mode 100644 index 000000000..0d3d85111 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/transaction_stmt.sql @@ -0,0 +1,15 @@ +do $$ +declare + commit int := 1; + rollback int := 1; +begin + commit; + commit and chain; + commit and no chain; + rollback; + rollback and chain; + rollback and no chain; + commit := 2; + rollback := 3; +end +$$; diff --git a/crates/squawk_parser/tests/data/plpgsql/ok/var_decl.sql b/crates/squawk_parser/tests/data/plpgsql/ok/var_decl.sql new file mode 100644 index 000000000..172604d64 --- /dev/null +++ b/crates/squawk_parser/tests/data/plpgsql/ok/var_decl.sql @@ -0,0 +1,11 @@ +do $$ +declare + a int; + b constant text := 'x'; + c numeric(10, 2) default 0; + d text collate "C" not null = 'y'; + e some_table.some_col%type; +begin + null; +end +$$; diff --git a/crates/squawk_parser/tests/snapshots/tests__plpgsql_null_stmt_ok.snap b/crates/squawk_parser/tests/snapshots/tests__plpgsql_null_stmt_ok.snap deleted file mode 100644 index a9bf40886..000000000 --- a/crates/squawk_parser/tests/snapshots/tests__plpgsql_null_stmt_ok.snap +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: crates/squawk_parser/tests/tests.rs -input_file: crates/squawk_parser/tests/data/plpgsql/ok/null_stmt.sql ---- -PLPGSQL - PLPGSQL_NULL_STMT - NULL_KW "null" - SEMICOLON ";" - WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__plpgsql_unrecognized_stmt_err.snap b/crates/squawk_parser/tests/snapshots/tests__plpgsql_unrecognized_stmt_err.snap deleted file mode 100644 index a9562d8d2..000000000 --- a/crates/squawk_parser/tests/snapshots/tests__plpgsql_unrecognized_stmt_err.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/squawk_parser/tests/tests.rs -input_file: crates/squawk_parser/tests/data/plpgsql/err/unrecognized_stmt.sql ---- -PLPGSQL - ERROR - BEGIN_KW "begin" - WHITESPACE " " - PLPGSQL_NULL_STMT - NULL_KW "null" - SEMICOLON ";" - WHITESPACE " " - ERROR - END_KW "end" - WHITESPACE "\n" ---- -error[syntax-error]: expected a statement, found BEGIN_KW - ╭▸ -1 │ begin null; end - ╰╴━ -error[syntax-error]: expected a statement, found END_KW - ╭▸ -1 │ begin null; end - ╰╴ ━ diff --git a/crates/squawk_parser/tests/tests.rs b/crates/squawk_parser/tests/tests.rs index 1bcea277d..162437dd7 100644 --- a/crates/squawk_parser/tests/tests.rs +++ b/crates/squawk_parser/tests/tests.rs @@ -64,62 +64,6 @@ fn parser_err(fixture: Fixture<&str>) { ); } -#[dir_test( - dir: "$CARGO_MANIFEST_DIR/tests/data/plpgsql/ok", - glob: "*.sql", -)] -fn plpgsql_ok(fixture: Fixture<&str>) { - let content = fixture.content(); - let absolute_fixture_path = Utf8Path::new(fixture.path()); - let input_file = absolute_fixture_path; - let test_name = absolute_fixture_path - .file_name() - .and_then(|x| x.strip_suffix(".sql")) - .unwrap(); - - let (parsed, errors) = parse(content, EntryPoint::Plpgsql); - - with_settings!({ - omit_expression => true, - input_file => input_file, - }, { - assert_snapshot!(format!("plpgsql_{test_name}_ok"), parsed); - }); - - assert!( - errors.is_none(), - "tests defined in `plpgsql/ok` can't have parser errors." - ); -} - -#[dir_test( - dir: "$CARGO_MANIFEST_DIR/tests/data/plpgsql/err", - glob: "*.sql", -)] -fn plpgsql_err(fixture: Fixture<&str>) { - let content = fixture.content(); - let absolute_fixture_path = Utf8Path::new(fixture.path()); - let input_file = absolute_fixture_path; - let test_name = absolute_fixture_path - .file_name() - .and_then(|x| x.strip_suffix(".sql")) - .unwrap(); - - let (parsed, errors) = parse(content, EntryPoint::Plpgsql); - - with_settings!({ - omit_expression => true, - input_file => input_file, - }, { - assert_snapshot!(format!("plpgsql_{test_name}_err"), parsed); - }); - - assert!( - errors.is_some(), - "tests defined in `plpgsql/err` must have parser errors." - ); -} - #[dir_test( dir: "$CARGO_MANIFEST_DIR/../../postgres/regression_suite", glob: "*.sql", @@ -192,14 +136,9 @@ fn bom_after_start_is_an_ident_char() { #[must_use] fn parse_text(text: &str) -> (String, Option) { - parse(text, EntryPoint::SourceFile) -} - -#[must_use] -fn parse(text: &str, entry_point: EntryPoint) -> (String, Option) { let lexed = LexedStr::new(text); let input = lexed.to_input(); - let output = entry_point.parse(&input); + let output = EntryPoint::SourceFile.parse(&input); let mut buf = String::new(); let mut errors: Vec<(std::ops::Range, String)> = Vec::new(); diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index c57660f90..9545aba27 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -19319,6 +19319,10 @@ pub struct PathSegmentRef { pub(crate) syntax: SyntaxNode, } impl PathSegmentRef { + #[inline] + pub fn positional_param_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::POSITIONAL_PARAM) + } #[inline] pub fn ident_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::IDENT) @@ -19349,6 +19353,10 @@ pub struct PercentType { pub(crate) syntax: SyntaxNode, } impl PercentType { + #[inline] + pub fn array_bounds(&self) -> AstChildren { + support::children(&self.syntax) + } #[inline] pub fn path_ref(&self) -> Option { support::child(&self.syntax) @@ -19358,6 +19366,10 @@ impl PercentType { support::child(&self.syntax) } #[inline] + pub fn array_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ARRAY_KW) + } + #[inline] pub fn setof_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::SETOF_KW) } @@ -19399,269 +19411,396 @@ pub struct Plpgsql { } impl Plpgsql { #[inline] - pub fn stmts(&self) -> AstChildren { + pub fn block(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn comp_options(&self) -> AstChildren { support::children(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PlpgsqlNullStmt { +pub struct PlpgsqlAliasDecl { pub(crate) syntax: SyntaxNode, } -impl PlpgsqlNullStmt { +impl PlpgsqlAliasDecl { + #[inline] + pub fn name(&self) -> Option { + support::child(&self.syntax) + } #[inline] pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::SEMICOLON) } #[inline] - pub fn null_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::NULL_KW) + pub fn alias_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ALIAS_KW) + } + #[inline] + pub fn for_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::FOR_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Policy { +pub struct PlpgsqlAliasTarget { pub(crate) syntax: SyntaxNode, } -impl Policy { +impl PlpgsqlAliasTarget { #[inline] - pub fn ident_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IDENT) + pub fn path_ref(&self) -> Option { + support::child(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PolicyCommand { +pub struct PlpgsqlAssertStmt { pub(crate) syntax: SyntaxNode, } -impl PolicyCommand { +impl PlpgsqlAssertStmt { #[inline] - pub fn command(&self) -> Option { - support::child(&self.syntax) + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) } #[inline] - pub fn for_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::FOR_KW) + pub fn assert_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ASSERT_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PolicyCommandAll { +pub struct PlpgsqlAssignStmt { pub(crate) syntax: SyntaxNode, } -impl PolicyCommandAll { +impl PlpgsqlAssignStmt { #[inline] - pub fn all_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::ALL_KW) + pub fn colon_eq_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::COLON_EQ) + } + #[inline] + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) + } + #[inline] + pub fn eq_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::EQ) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PolicyCommandDelete { +pub struct PlpgsqlAssignTarget { pub(crate) syntax: SyntaxNode, } -impl PolicyCommandDelete { +impl PlpgsqlAssignTarget { #[inline] - pub fn delete_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::DELETE_KW) + pub fn accessors(&self) -> AstChildren { + support::children(&self.syntax) + } + #[inline] + pub fn name(&self) -> Option { + support::child(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PolicyCommandInsert { +pub struct PlpgsqlBlock { pub(crate) syntax: SyntaxNode, } -impl PolicyCommandInsert { +impl PlpgsqlBlock { #[inline] - pub fn insert_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::INSERT_KW) + pub fn body(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn declare_section(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn end_label(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn exception(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn label(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) + } + #[inline] + pub fn begin_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::BEGIN_KW) + } + #[inline] + pub fn end_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::END_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PolicyCommandSelect { +pub struct PlpgsqlBody { pub(crate) syntax: SyntaxNode, } -impl PolicyCommandSelect { +impl PlpgsqlBody { #[inline] - pub fn select_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SELECT_KW) + pub fn stmts(&self) -> AstChildren { + support::children(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PolicyCommandUpdate { +pub struct PlpgsqlCallStmt { pub(crate) syntax: SyntaxNode, } -impl PolicyCommandUpdate { +impl PlpgsqlCallStmt { #[inline] - pub fn update_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::UPDATE_KW) + pub fn call(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PolicyRef { +pub struct PlpgsqlCaseStmt { pub(crate) syntax: SyntaxNode, } -impl PolicyRef { +impl PlpgsqlCaseStmt { #[inline] - pub fn ident_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IDENT) + pub fn else_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn subject(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn whens(&self) -> AstChildren { + support::children(&self.syntax) + } + #[inline] + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) + } + #[inline] + pub fn case_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::CASE_KW) + } + #[inline] + pub fn end_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::END_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PolicyRenameTo { +pub struct PlpgsqlCaseWhen { pub(crate) syntax: SyntaxNode, } -impl PolicyRenameTo { +impl PlpgsqlCaseWhen { #[inline] - pub fn policy(&self) -> Option { + pub fn body(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn rename_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::RENAME_KW) + pub fn conds(&self) -> AstChildren { + support::children(&self.syntax) } #[inline] - pub fn to_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::TO_KW) + pub fn then_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::THEN_KW) + } + #[inline] + pub fn when_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::WHEN_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PolicyRoles { +pub struct PlpgsqlCommitStmt { pub(crate) syntax: SyntaxNode, } -impl PolicyRoles { +impl PlpgsqlCommitStmt { #[inline] - pub fn role_ref_list(&self) -> Option { + pub fn chain_clause(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn to_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::TO_KW) + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) + } + #[inline] + pub fn commit_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::COMMIT_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PortionFromTo { +pub struct PlpgsqlCompOptionDump { pub(crate) syntax: SyntaxNode, } -impl PortionFromTo { +impl PlpgsqlCompOptionDump { #[inline] - pub fn from_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::FROM_KW) + pub fn pound_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::POUND) } #[inline] - pub fn to_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::TO_KW) + pub fn dump_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::DUMP_KW) + } + #[inline] + pub fn option_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::OPTION_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PortionRange { +pub struct PlpgsqlCompOptionPrintStrictParams { pub(crate) syntax: SyntaxNode, } -impl PortionRange { +impl PlpgsqlCompOptionPrintStrictParams { #[inline] - pub fn expr(&self) -> Option { + pub fn option_value(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn l_paren_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::L_PAREN) + pub fn pound_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::POUND) } #[inline] - pub fn r_paren_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::R_PAREN) + pub fn print_strict_params_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::PRINT_STRICT_PARAMS_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PositionFn { +pub struct PlpgsqlCompOptionVariableConflict { pub(crate) syntax: SyntaxNode, } -impl PositionFn { +impl PlpgsqlCompOptionVariableConflict { #[inline] - pub fn l_paren_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::L_PAREN) + pub fn pound_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::POUND) } #[inline] - pub fn r_paren_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::R_PAREN) + pub fn error_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ERROR_KW) } #[inline] - pub fn in_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IN_KW) + pub fn use_column_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::USE_COLUMN_KW) } #[inline] - pub fn position_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::POSITION_KW) + pub fn use_variable_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::USE_VARIABLE_KW) + } + #[inline] + pub fn variable_conflict_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::VARIABLE_CONFLICT_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PostfixExpr { +pub struct PlpgsqlCondition { pub(crate) syntax: SyntaxNode, } -impl PostfixExpr { +impl PlpgsqlCondition { #[inline] - pub fn expr(&self) -> Option { - support::child(&self.syntax) + pub fn positional_param_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::POSITIONAL_PARAM) + } + #[inline] + pub fn string_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::STRING) + } + #[inline] + pub fn ident_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::IDENT) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrefixExpr { +pub struct PlpgsqlContinueStmt { pub(crate) syntax: SyntaxNode, } -impl PrefixExpr { +impl PlpgsqlContinueStmt { #[inline] - pub fn expr(&self) -> Option { + pub fn label(&self) -> Option { support::child(&self.syntax) } + #[inline] + pub fn when_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) + } + #[inline] + pub fn continue_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::CONTINUE_KW) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Prepare { +pub struct PlpgsqlCursorArg { pub(crate) syntax: SyntaxNode, } -impl Prepare { - #[inline] - pub fn name(&self) -> Option { - support::child(&self.syntax) - } +impl PlpgsqlCursorArg { #[inline] - pub fn param_list(&self) -> Option { + pub fn name(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn stmt(&self) -> Option { + pub fn ty(&self) -> Option { support::child(&self.syntax) } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PlpgsqlCursorArgList { + pub(crate) syntax: SyntaxNode, +} +impl PlpgsqlCursorArgList { #[inline] - pub fn semicolon_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SEMICOLON) + pub fn plpgsql_cursor_args(&self) -> AstChildren { + support::children(&self.syntax) } #[inline] - pub fn as_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::AS_KW) + pub fn l_paren_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::L_PAREN) } #[inline] - pub fn prepare_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PREPARE_KW) + pub fn r_paren_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::R_PAREN) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrepareTransaction { +pub struct PlpgsqlCursorDecl { pub(crate) syntax: SyntaxNode, } -impl PrepareTransaction { +impl PlpgsqlCursorDecl { #[inline] - pub fn literal(&self) -> Option { + pub fn args(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn name(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn query(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn scroll(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -19669,856 +19808,1023 @@ impl PrepareTransaction { support::token(&self.syntax, SyntaxKind::SEMICOLON) } #[inline] - pub fn prepare_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PREPARE_KW) + pub fn cursor_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::CURSOR_KW) } #[inline] - pub fn transaction_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::TRANSACTION_KW) + pub fn for_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::FOR_KW) + } + #[inline] + pub fn is_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::IS_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PreparedStatement { +pub struct PlpgsqlDeclareSection { pub(crate) syntax: SyntaxNode, } -impl PreparedStatement { +impl PlpgsqlDeclareSection { #[inline] - pub fn ident_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IDENT) + pub fn decls(&self) -> AstChildren { + support::children(&self.syntax) + } + #[inline] + pub fn declare_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::DECLARE_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PreparedStatementRef { +pub struct PlpgsqlDiagArea { pub(crate) syntax: SyntaxNode, } -impl PreparedStatementRef { +impl PlpgsqlDiagArea { #[inline] - pub fn ident_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IDENT) + pub fn current_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::CURRENT_KW) + } + #[inline] + pub fn stacked_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::STACKED_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PreserveRows { +pub struct PlpgsqlDiagItem { pub(crate) syntax: SyntaxNode, } -impl PreserveRows { +impl PlpgsqlDiagItem { #[inline] - pub fn preserve_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PRESERVE_KW) + pub fn kind(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn rows_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::ROWS_KW) + pub fn colon_eq_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::COLON_EQ) + } + #[inline] + pub fn eq_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::EQ) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PreserveWhitespace { +pub struct PlpgsqlDiagItemList { pub(crate) syntax: SyntaxNode, } -impl PreserveWhitespace { - #[inline] - pub fn preserve_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PRESERVE_KW) - } +impl PlpgsqlDiagItemList { #[inline] - pub fn whitespace_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::WHITESPACE_KW) + pub fn plpgsql_diag_items(&self) -> AstChildren { + support::children(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrimaryKeyConstraint { +pub struct PlpgsqlDiagKind { pub(crate) syntax: SyntaxNode, } -impl PrimaryKeyConstraint { +impl PlpgsqlDiagKind { #[inline] - pub fn constraint_name_clause(&self) -> Option { - support::child(&self.syntax) + pub fn column_name_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::COLUMN_NAME_KW) } #[inline] - pub fn constraint_options(&self) -> AstChildren { - support::children(&self.syntax) + pub fn constraint_name_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::CONSTRAINT_NAME_KW) } #[inline] - pub fn index_parameters(&self) -> Option { - support::child(&self.syntax) + pub fn message_text_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::MESSAGE_TEXT_KW) } #[inline] - pub fn using_index(&self) -> Option { - support::child(&self.syntax) + pub fn pg_context_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::PG_CONTEXT_KW) } #[inline] - pub fn key_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::KEY_KW) + pub fn pg_datatype_name_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::PG_DATATYPE_NAME_KW) } #[inline] - pub fn primary_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PRIMARY_KW) + pub fn pg_exception_context_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::PG_EXCEPTION_CONTEXT_KW) } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Prior { - pub(crate) syntax: SyntaxNode, -} -impl Prior { #[inline] - pub fn prior_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PRIOR_KW) + pub fn pg_exception_detail_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::PG_EXCEPTION_DETAIL_KW) } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeAllFunctionsInSchema { - pub(crate) syntax: SyntaxNode, -} -impl PrivilegeAllFunctionsInSchema { #[inline] - pub fn schema_refs(&self) -> AstChildren { - support::children(&self.syntax) + pub fn pg_exception_hint_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::PG_EXCEPTION_HINT_KW) } #[inline] - pub fn all_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::ALL_KW) + pub fn pg_routine_oid_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::PG_ROUTINE_OID_KW) } #[inline] - pub fn functions_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::FUNCTIONS_KW) + pub fn returned_sqlstate_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::RETURNED_SQLSTATE_KW) } #[inline] - pub fn in_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IN_KW) + pub fn row_count_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ROW_COUNT_KW) } #[inline] - pub fn schema_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SCHEMA_KW) + pub fn schema_name_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SCHEMA_NAME_KW) + } + #[inline] + pub fn table_name_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::TABLE_NAME_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeAllProceduresInSchema { +pub struct PlpgsqlDiagTarget { pub(crate) syntax: SyntaxNode, } -impl PrivilegeAllProceduresInSchema { +impl PlpgsqlDiagTarget { #[inline] - pub fn schema_refs(&self) -> AstChildren { + pub fn field_accessors(&self) -> AstChildren { support::children(&self.syntax) } #[inline] - pub fn all_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::ALL_KW) - } - #[inline] - pub fn in_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IN_KW) - } - #[inline] - pub fn procedures_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PROCEDURES_KW) - } - #[inline] - pub fn schema_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SCHEMA_KW) + pub fn name(&self) -> Option { + support::child(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeAllRoutinesInSchema { +pub struct PlpgsqlDoStmt { pub(crate) syntax: SyntaxNode, } -impl PrivilegeAllRoutinesInSchema { - #[inline] - pub fn schema_refs(&self) -> AstChildren { - support::children(&self.syntax) - } +impl PlpgsqlDoStmt { #[inline] - pub fn all_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::ALL_KW) + pub fn do_(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn in_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IN_KW) + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PlpgsqlElseClause { + pub(crate) syntax: SyntaxNode, +} +impl PlpgsqlElseClause { #[inline] - pub fn routines_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::ROUTINES_KW) + pub fn body(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn schema_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SCHEMA_KW) + pub fn else_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ELSE_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeAllSequencesInSchema { +pub struct PlpgsqlElsifClause { pub(crate) syntax: SyntaxNode, } -impl PrivilegeAllSequencesInSchema { +impl PlpgsqlElsifClause { #[inline] - pub fn schema_refs(&self) -> AstChildren { - support::children(&self.syntax) + pub fn body(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn all_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::ALL_KW) + pub fn cond(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn in_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IN_KW) + pub fn elseif_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ELSEIF_KW) } #[inline] - pub fn schema_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SCHEMA_KW) + pub fn elsif_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ELSIF_KW) } #[inline] - pub fn sequences_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SEQUENCES_KW) + pub fn then_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::THEN_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeAllTablesInSchema { +pub struct PlpgsqlExceptionHandler { pub(crate) syntax: SyntaxNode, } -impl PrivilegeAllTablesInSchema { - #[inline] - pub fn schema_refs(&self) -> AstChildren { - support::children(&self.syntax) - } +impl PlpgsqlExceptionHandler { #[inline] - pub fn all_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::ALL_KW) + pub fn body(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn in_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IN_KW) + pub fn conditions(&self) -> AstChildren { + support::children(&self.syntax) } #[inline] - pub fn schema_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SCHEMA_KW) + pub fn then_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::THEN_KW) } #[inline] - pub fn tables_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::TABLES_KW) + pub fn when_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::WHEN_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeDatabase { +pub struct PlpgsqlExceptionSection { pub(crate) syntax: SyntaxNode, } -impl PrivilegeDatabase { +impl PlpgsqlExceptionSection { #[inline] - pub fn database_refs(&self) -> AstChildren { + pub fn handlers(&self) -> AstChildren { support::children(&self.syntax) } #[inline] - pub fn database_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::DATABASE_KW) + pub fn exception_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::EXCEPTION_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeDefault { +pub struct PlpgsqlExitStmt { pub(crate) syntax: SyntaxNode, } -impl PrivilegeDefault { +impl PlpgsqlExitStmt { #[inline] - pub fn relation_name_refs(&self) -> AstChildren { - support::children(&self.syntax) + pub fn label(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn when_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) + } + #[inline] + pub fn exit_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::EXIT_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeDomain { +pub struct PlpgsqlExitWhen { pub(crate) syntax: SyntaxNode, } -impl PrivilegeDomain { +impl PlpgsqlExitWhen { #[inline] - pub fn domain_refs(&self) -> AstChildren { - support::children(&self.syntax) + pub fn cond(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn domain_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::DOMAIN_KW) + pub fn when_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::WHEN_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeForeignDataWrapper { +pub struct PlpgsqlGetDiagStmt { pub(crate) syntax: SyntaxNode, } -impl PrivilegeForeignDataWrapper { +impl PlpgsqlGetDiagStmt { #[inline] - pub fn foreign_data_wrapper_refs(&self) -> AstChildren { - support::children(&self.syntax) + pub fn area(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn data_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::DATA_KW) + pub fn items(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn foreign_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::FOREIGN_KW) + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) } #[inline] - pub fn wrapper_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::WRAPPER_KW) + pub fn diagnostics_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::DIAGNOSTICS_KW) + } + #[inline] + pub fn get_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::GET_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeForeignServer { +pub struct PlpgsqlIfStmt { pub(crate) syntax: SyntaxNode, } -impl PrivilegeForeignServer { +impl PlpgsqlIfStmt { #[inline] - pub fn server_refs(&self) -> AstChildren { + pub fn cond(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn else_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn elsifs(&self) -> AstChildren { support::children(&self.syntax) } #[inline] - pub fn foreign_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::FOREIGN_KW) + pub fn then_body(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn server_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SERVER_KW) + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) + } + #[inline] + pub fn end_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::END_KW) + } + #[inline] + pub fn if_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::IF_KW) + } + #[inline] + pub fn then_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::THEN_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeFunction { +pub struct PlpgsqlLabel { pub(crate) syntax: SyntaxNode, } -impl PrivilegeFunction { +impl PlpgsqlLabel { #[inline] - pub fn function_sig_list(&self) -> Option { + pub fn name(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn function_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::FUNCTION_KW) + pub fn less_less_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::LESS_LESS) + } + #[inline] + pub fn greater_greater_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::GREATER_GREATER) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeLanguage { +pub struct PlpgsqlLabelName { pub(crate) syntax: SyntaxNode, } -impl PrivilegeLanguage { +impl PlpgsqlLabelName { #[inline] - pub fn language_refs(&self) -> AstChildren { - support::children(&self.syntax) + pub fn positional_param_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::POSITIONAL_PARAM) } #[inline] - pub fn language_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::LANGUAGE_KW) + pub fn ident_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::IDENT) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeLargeObject { +pub struct PlpgsqlLabelNameRef { pub(crate) syntax: SyntaxNode, } -impl PrivilegeLargeObject { +impl PlpgsqlLabelNameRef { #[inline] - pub fn literals(&self) -> AstChildren { - support::children(&self.syntax) - } - #[inline] - pub fn large_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::LARGE_KW) + pub fn positional_param_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::POSITIONAL_PARAM) } #[inline] - pub fn object_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::OBJECT_KW) + pub fn ident_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::IDENT) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeParameter { +pub struct PlpgsqlLoopStmt { pub(crate) syntax: SyntaxNode, } -impl PrivilegeParameter { +impl PlpgsqlLoopStmt { #[inline] - pub fn config_parameter_refs(&self) -> AstChildren { - support::children(&self.syntax) + pub fn body(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn parameter_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PARAMETER_KW) + pub fn end_label(&self) -> Option { + support::child(&self.syntax) } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeProcedure { - pub(crate) syntax: SyntaxNode, -} -impl PrivilegeProcedure { #[inline] - pub fn procedure_sig_list(&self) -> Option { + pub fn label(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn procedure_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PROCEDURE_KW) + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) + } + #[inline] + pub fn end_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::END_KW) + } + #[inline] + pub fn loop_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::LOOP_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegePropertyGraph { +pub struct PlpgsqlNotNull { pub(crate) syntax: SyntaxNode, } -impl PrivilegePropertyGraph { - #[inline] - pub fn property_graph_refs(&self) -> AstChildren { - support::children(&self.syntax) - } +impl PlpgsqlNotNull { #[inline] - pub fn graph_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::GRAPH_KW) + pub fn not_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NOT_KW) } #[inline] - pub fn property_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PROPERTY_KW) + pub fn null_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NULL_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeRoutine { +pub struct PlpgsqlNullStmt { pub(crate) syntax: SyntaxNode, } -impl PrivilegeRoutine { +impl PlpgsqlNullStmt { #[inline] - pub fn routine_sig_list(&self) -> Option { - support::child(&self.syntax) + pub fn semicolon_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::SEMICOLON) } #[inline] - pub fn routine_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::ROUTINE_KW) + pub fn null_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NULL_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeSchema { +pub struct PlpgsqlOptionValue { pub(crate) syntax: SyntaxNode, } -impl PrivilegeSchema { +impl PlpgsqlOptionValue { #[inline] - pub fn schema_refs(&self) -> AstChildren { - support::children(&self.syntax) + pub fn positional_param_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::POSITIONAL_PARAM) } #[inline] - pub fn schema_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SCHEMA_KW) + pub fn ident_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::IDENT) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeSequence { +pub struct PlpgsqlPercentRowtype { pub(crate) syntax: SyntaxNode, } -impl PrivilegeSequence { +impl PlpgsqlPercentRowtype { #[inline] - pub fn sequence_refs(&self) -> AstChildren { + pub fn array_bounds(&self) -> AstChildren { support::children(&self.syntax) } #[inline] - pub fn sequence_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::SEQUENCE_KW) + pub fn path_ref(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn percent_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::PERCENT) + } + #[inline] + pub fn array_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ARRAY_KW) + } + #[inline] + pub fn rowtype_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ROWTYPE_KW) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PrivilegeTable { +pub struct PlpgsqlPerformStmt { pub(crate) syntax: SyntaxNode, } -impl PrivilegeTable { +impl PlpgsqlPerformStmt { #[inline] - pub fn relation_name_refs(&self) -> AstChildren { - support::children(&self.syntax) + pub fn compound_select(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn table_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::TABLE_KW) + pub fn select(&self) -> Option