From a40553d79cfce8a573490acf726f780a9ee099ef Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sat, 3 Jan 2026 17:05:34 -0500 Subject: [PATCH] ide: support references column constraints & `select t.* from t;` --- PLAN.md | 20 +++++ crates/squawk_ide/src/classify.rs | 26 ++++++ crates/squawk_ide/src/goto_definition.rs | 80 +++++++++++++++++++ crates/squawk_ide/src/resolve.rs | 24 ++++-- .../src/rules/constraint_missing_not_valid.rs | 3 +- .../src/rules/prefer_robust_stmts.rs | 4 +- .../src/generated/syntax_kind.rs | 1 + crates/squawk_parser/src/grammar.rs | 22 ++--- .../snapshots/tests__alter_domain_ok.snap | 18 +++-- .../tests__alter_foreign_table_ok.snap | 18 +++-- .../snapshots/tests__alter_table_err.snap | 9 ++- .../snapshots/tests__alter_table_ok.snap | 45 ++++++----- .../snapshots/tests__create_domain_ok.snap | 9 ++- .../tests__create_foreign_table_ok.snap | 18 +++-- .../snapshots/tests__create_table_ok.snap | 27 ++++--- .../tests__create_table_pg17_ok.snap | 18 +++-- .../squawk_syntax/src/ast/generated/nodes.rs | 79 ++++++++++-------- crates/squawk_syntax/src/ast/node_ext.rs | 2 +- crates/squawk_syntax/src/postgresql.ungram | 17 ++-- ...ntax__test__alter_table_ok_validation.snap | 18 +++-- 20 files changed, 316 insertions(+), 142 deletions(-) diff --git a/PLAN.md b/PLAN.md index ba431566..0b9d4b5e 100644 --- a/PLAN.md +++ b/PLAN.md @@ -590,6 +590,26 @@ FROM ( WHERE total_amount > 1000; ``` +### Rule: implicit column name in references + +```sql +create table u( + id int primary key +); +create table t( + u_id int references u +-- ^quickfix +); + +-- becomes + +create table t( + u_id int references u(id) +); +``` + +and vice versa + ### Rule: aggregate free `having` condition ```sql diff --git a/crates/squawk_ide/src/classify.rs b/crates/squawk_ide/src/classify.rs index a11f4334..9196e3d9 100644 --- a/crates/squawk_ide/src/classify.rs +++ b/crates/squawk_ide/src/classify.rs @@ -66,6 +66,7 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option let mut in_constraint_include_clause = false; let mut in_constraint_where_clause = false; let mut in_partition_item = false; + let mut in_set_null_columns = false; // TODO: can we combine this if and the one that follows? if let Some(parent) = name_ref.syntax().parent() @@ -115,6 +116,7 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option // ^^^ .is_some_and(|field_name_ref| field_name_ref.syntax() == name_ref.syntax()) // we're not inside a call expr + && field_expr.star_token().is_none() && field_expr .syntax() .parent() @@ -220,6 +222,9 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option { return Some(NameRefClass::Tablespace); } + if ast::SetNullColumns::can_cast(ancestor.kind()) { + in_set_null_columns = true; + } if let Some(foreign_key) = ast::ForeignKeyConstraint::cast(ancestor.clone()) { if in_column_list { // TODO: ast is too "flat" here, we need a unique node for to @@ -241,10 +246,31 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option { return Some(NameRefClass::ForeignKeyLocalColumn); } + if in_set_null_columns { + return Some(NameRefClass::ForeignKeyLocalColumn); + } } else { return Some(NameRefClass::ForeignKeyTable); } } + if let Some(references_constraint) = ast::ReferencesConstraint::cast(ancestor.clone()) { + if let Some(column_ref) = references_constraint.column() + && column_ref + .syntax() + .text_range() + .contains_range(name_ref.syntax().text_range()) + { + return Some(NameRefClass::ForeignKeyColumn); + } + if let Some(path) = references_constraint.table() + && path + .syntax() + .text_range() + .contains_range(name_ref.syntax().text_range()) + { + return Some(NameRefClass::ForeignKeyTable); + } + } if ast::CheckConstraint::can_cast(ancestor.kind()) { return Some(NameRefClass::CheckConstraintColumn); } diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index fa815f1e..111aa635 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -465,6 +465,72 @@ create table bar( "); } + #[test] + fn goto_foreign_key_on_delete_set_null_column() { + assert_snapshot!(goto(" +create table users ( + user_id integer not null, + primary key (user_id) +); + +create table posts ( + post_id integer not null, + author_id integer, + primary key (post_id), + foreign key (author_id) references users on delete set null (author_id$0) +); +"), @r" + ╭▸ + 9 │ author_id integer, + │ ───────── 2. destination + 10 │ primary key (post_id), + 11 │ foreign key (author_id) references users on delete set null (author_id) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_references_constraint_table() { + assert_snapshot!(goto(" +create table t ( + id serial primary key +); + +create table u ( + id serial primary key, + t_id int references t$0 +); +"), @r" + ╭▸ + 2 │ create table t ( + │ ─ 2. destination + ‡ + 8 │ t_id int references t + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_references_constraint_column() { + assert_snapshot!(goto(" +create table t ( + id serial primary key +); + +create table u ( + id serial primary key, + t_id int references t(id$0) +); +"), @r" + ╭▸ + 3 │ id serial primary key + │ ── 2. destination + ‡ + 8 │ t_id int references t(id) + ╰╴ ─ 1. source + "); + } + #[test] fn goto_foreign_key_references_column() { assert_snapshot!(goto(" @@ -2918,6 +2984,20 @@ select t$0 from t; "); } + #[test] + fn goto_select_table_star_expansion() { + assert_snapshot!(goto(" +create table t(id int, a int); +select t$0.* from t; +"), @r" + ╭▸ + 2 │ create table t(id int, a int); + │ ─ 2. destination + 3 │ select t.* from t; + ╰╴ ─ 1. source + "); + } + #[test] fn goto_select_table_as_column_with_schema() { assert_snapshot!(goto(" diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index e854d1e7..6effbb88 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -121,22 +121,29 @@ pub(crate) fn resolve_name_ref( resolve_tablespace(binder, &tablespace_name).map(|ptr| smallvec![ptr]) } NameRefClass::ForeignKeyTable => { - let foreign_key = name_ref - .syntax() - .ancestors() - .find_map(ast::ForeignKeyConstraint::cast)?; - let path = foreign_key.path()?; + let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; let table_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); resolve_table(binder, &table_name, &schema, position).map(|ptr| smallvec![ptr]) } NameRefClass::ForeignKeyColumn => { - let foreign_key = name_ref + // TODO: the ast is too flat here + let path = if let Some(foreign_key) = name_ref + .syntax() + .ancestors() + .find_map(ast::ForeignKeyConstraint::cast) + { + foreign_key.path()? + } else if let Some(references_constraint) = name_ref .syntax() .ancestors() - .find_map(ast::ForeignKeyConstraint::cast)?; - let path = foreign_key.path()?; + .find_map(ast::ReferencesConstraint::cast) + { + references_constraint.table()? + } else { + return None; + }; let column_name = Name::from_node(name_ref); resolve_column_for_path(binder, &path, column_name).map(|ptr| smallvec![ptr]) } @@ -678,6 +685,7 @@ fn resolve_select_qualified_column_table( let explicit_schema = if field_expr .field() .is_some_and(|f| f.syntax() == name_ref.syntax()) + && field_expr.star_token().is_none() { // if we're at the field `bar` in `foo.bar` if let ast::Expr::NameRef(schema_name_ref) = field_expr.base()? { diff --git a/crates/squawk_linter/src/rules/constraint_missing_not_valid.rs b/crates/squawk_linter/src/rules/constraint_missing_not_valid.rs index d999c2a1..de634466 100644 --- a/crates/squawk_linter/src/rules/constraint_missing_not_valid.rs +++ b/crates/squawk_linter/src/rules/constraint_missing_not_valid.rs @@ -69,7 +69,8 @@ fn not_valid_validate_in_transaction( ast::AlterTableAction::AddConstraint(add_constraint) => { if add_constraint.not_valid().is_some() && let Some(constraint) = add_constraint.constraint() - && let Some(constraint_name) = constraint.name() + && let Some(constraint_name) = + constraint.constraint_name().and_then(|c| c.name()) { not_valid_names.insert(Identifier::new(&constraint_name.text())); } diff --git a/crates/squawk_linter/src/rules/prefer_robust_stmts.rs b/crates/squawk_linter/src/rules/prefer_robust_stmts.rs index 9d04cbcd..11081130 100644 --- a/crates/squawk_linter/src/rules/prefer_robust_stmts.rs +++ b/crates/squawk_linter/src/rules/prefer_robust_stmts.rs @@ -91,7 +91,9 @@ pub(crate) fn prefer_robust_stmts(ctx: &mut Linter, parse: &Parse) { } ast::AlterTableAction::AddConstraint(add_constraint) => { let constraint = add_constraint.constraint(); - if let Some(constraint_name) = constraint.and_then(|x| x.name()) + if let Some(constraint_name) = constraint + .and_then(|x| x.constraint_name()) + .and_then(|x| x.name()) && let Some(constraint) = constraint_names .get_mut(&Identifier::new(constraint_name.text().as_str())) && *constraint == Constraint::Dropped diff --git a/crates/squawk_parser/src/generated/syntax_kind.rs b/crates/squawk_parser/src/generated/syntax_kind.rs index c22ad4c6..629ec380 100644 --- a/crates/squawk_parser/src/generated/syntax_kind.rs +++ b/crates/squawk_parser/src/generated/syntax_kind.rs @@ -652,6 +652,7 @@ pub enum SyntaxKind { CONSTRAINT_INCLUDE_CLAUSE, CONSTRAINT_INDEX_METHOD, CONSTRAINT_INDEX_TABLESPACE, + CONSTRAINT_NAME, COPY, COPY_OPTION, COPY_OPTION_LIST, diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index a6c0e6f0..8126bdcb 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -3760,9 +3760,7 @@ fn opt_column_constraint(p: &mut Parser<'_>) -> Option { return None; } let m = p.start(); - if p.eat(CONSTRAINT_KW) { - name(p); - } + opt_constraint_name(p); match opt_constraint_inner(p) { Some(kind) => { opt_constraint_option_list(p); @@ -4087,9 +4085,7 @@ fn table_constraint(p: &mut Parser<'_>) -> CompletedMarker { assert!(p.at_ts(TABLE_CONSTRAINT_FIRST)); let m = p.start(); // [ CONSTRAINT constraint_name ] - if p.eat(CONSTRAINT_KW) { - name(p); - } + opt_constraint_name(p); // CHECK ( expression ) [ NO INHERIT ] let kind = match p.current() { CHECK_KW => { @@ -7167,9 +7163,7 @@ fn alter_domain_action(p: &mut Parser<'_>) -> Option { // { NOT NULL | CHECK (expression) } fn domain_constraint(p: &mut Parser<'_>) { let m = p.start(); - if p.eat(CONSTRAINT_KW) { - name(p); - } + opt_constraint_name(p); if p.eat(NOT_KW) { p.expect(NULL_KW); m.complete(p, NOT_NULL_CONSTRAINT); @@ -7186,6 +7180,16 @@ fn domain_constraint(p: &mut Parser<'_>) { } } +fn opt_constraint_name(p: &mut Parser<'_>) { + let m = p.start(); + if p.eat(CONSTRAINT_KW) { + name(p); + m.complete(p, CONSTRAINT_NAME); + } else { + m.abandon(p); + } +} + // ALTER DEFAULT PRIVILEGES // [ FOR { ROLE | USER } target_role [, ...] ] // [ IN SCHEMA schema_name [, ...] ] diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_domain_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_domain_ok.snap index 023d7214..52d1d013 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_domain_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_domain_ok.snap @@ -103,10 +103,11 @@ SOURCE_FILE ADD_KW "add" WHITESPACE " " CHECK_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "c" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "c" WHITESPACE " " CHECK_KW "check" WHITESPACE " " @@ -169,10 +170,11 @@ SOURCE_FILE ADD_KW "add" WHITESPACE " " CHECK_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "a" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "a" WHITESPACE " " CHECK_KW "check" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_foreign_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_foreign_table_ok.snap index 576ad611..48c52b5a 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_foreign_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_foreign_table_ok.snap @@ -1055,10 +1055,11 @@ SOURCE_FILE ADD_KW "add" WHITESPACE " " CHECK_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "c" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "c" WHITESPACE " " CHECK_KW "check" WHITESPACE " " @@ -1096,10 +1097,11 @@ SOURCE_FILE ADD_KW "add" WHITESPACE " " CHECK_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "c" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "c" WHITESPACE " " CHECK_KW "check" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_table_err.snap b/crates/squawk_parser/tests/snapshots/tests__alter_table_err.snap index 37080ba0..56597b55 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_table_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_table_err.snap @@ -103,10 +103,11 @@ SOURCE_FILE ADD_KW "add" WHITESPACE " " NOT_NULL_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "id_not_null" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "id_not_null" WHITESPACE " " NOT_KW "not" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap index ec2bf97b..38003817 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap @@ -2247,10 +2247,11 @@ SOURCE_FILE ADD_KW "add" WHITESPACE " " UNIQUE_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "some_name" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "some_name" WHITESPACE " " UNIQUE_KW "unique" WHITESPACE " " @@ -3192,10 +3193,11 @@ SOURCE_FILE ADD_KW "add" WHITESPACE " " UNIQUE_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "c" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "c" WHITESPACE " " UNIQUE_KW "unique" WHITESPACE " " @@ -3223,10 +3225,11 @@ SOURCE_FILE ADD_KW "add" WHITESPACE " " PRIMARY_KEY_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "c" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "c" WHITESPACE " " PRIMARY_KW "primary" WHITESPACE " " @@ -3258,10 +3261,11 @@ SOURCE_FILE ADD_KW "add" WHITESPACE " " CHECK_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "foo_not_null" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "foo_not_null" WHITESPACE "\n " CHECK_KW "check" WHITESPACE " " @@ -3868,10 +3872,11 @@ SOURCE_FILE ADD_KW "add" WHITESPACE " " FOREIGN_KEY_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "foo_bar_id_fkey" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "foo_bar_id_fkey" WHITESPACE " " FOREIGN_KW "foreign" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__create_domain_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_domain_ok.snap index 01de4547..6de4ce0f 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_domain_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_domain_ok.snap @@ -60,10 +60,11 @@ SOURCE_FILE STRING "'fooooo'" WHITESPACE "\n " CHECK_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "c" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "c" WHITESPACE " " CHECK_KW "check" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__create_foreign_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_foreign_table_ok.snap index 3249b5d0..def07967 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_foreign_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_foreign_table_ok.snap @@ -104,10 +104,11 @@ SOURCE_FILE IDENT "\"fr_FR\"" WHITESPACE "\n " NOT_NULL_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "c" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "c" WHITESPACE "\n " NOT_KW "not" WHITESPACE " " @@ -169,10 +170,11 @@ SOURCE_FILE COMMA "," WHITESPACE "\n " CHECK_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "fooo" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "fooo" WHITESPACE "\n " CHECK_KW "check" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap index ec3cdf14..df443d20 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap @@ -1533,10 +1533,11 @@ SOURCE_FILE INT_KW "int" WHITESPACE " " NULL_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "foo" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "foo" WHITESPACE " " NULL_KW "null" WHITESPACE "\n" @@ -1646,10 +1647,11 @@ SOURCE_FILE COMMA "," WHITESPACE "\n " CHECK_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "foo" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "foo" WHITESPACE " " CHECK_KW "check" WHITESPACE " " @@ -2393,10 +2395,11 @@ SOURCE_FILE COMMA "," WHITESPACE "\n " CHECK_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "foo" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "foo" WHITESPACE " " CHECK_KW "check" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__create_table_pg17_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_table_pg17_ok.snap index c766d818..1dc4a3cc 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_table_pg17_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_table_pg17_ok.snap @@ -182,10 +182,11 @@ SOURCE_FILE COMMA "," WHITESPACE "\n " FOREIGN_KEY_CONSTRAINT - CONSTRAINT_KW "CONSTRAINT" - WHITESPACE " " - NAME - IDENT "order_address" + CONSTRAINT_NAME + CONSTRAINT_KW "CONSTRAINT" + WHITESPACE " " + NAME + IDENT "order_address" WHITESPACE " " FOREIGN_KW "FOREIGN" WHITESPACE " " @@ -336,10 +337,11 @@ SOURCE_FILE COMMA "," WHITESPACE "\n " PRIMARY_KEY_CONSTRAINT - CONSTRAINT_KW "constraint" - WHITESPACE " " - NAME - IDENT "pk" + CONSTRAINT_NAME + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME + IDENT "pk" WHITESPACE " \n " PRIMARY_KW "primary" WHITESPACE " " diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index a1df1a70..6895d384 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -2414,11 +2414,11 @@ pub struct CheckConstraint { } impl CheckConstraint { #[inline] - pub fn expr(&self) -> Option { + pub fn constraint_name(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn name(&self) -> Option { + pub fn expr(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -2437,10 +2437,6 @@ impl CheckConstraint { pub fn check_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::CHECK_KW) } - #[inline] - pub fn constraint_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::CONSTRAINT_KW) - } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -2963,6 +2959,21 @@ impl ConstraintIndexTablespace { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ConstraintName { + pub(crate) syntax: SyntaxNode, +} +impl ConstraintName { + #[inline] + pub fn name(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn constraint_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::CONSTRAINT_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Copy { pub(crate) syntax: SyntaxNode, @@ -7287,7 +7298,7 @@ impl ExcludeConstraint { support::child(&self.syntax) } #[inline] - pub fn name(&self) -> Option { + pub fn constraint_name(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -7295,10 +7306,6 @@ impl ExcludeConstraint { support::child(&self.syntax) } #[inline] - pub fn constraint_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::CONSTRAINT_KW) - } - #[inline] pub fn exclude_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::EXCLUDE_KW) } @@ -7671,11 +7678,11 @@ pub struct ForeignKeyConstraint { } impl ForeignKeyConstraint { #[inline] - pub fn match_type(&self) -> Option { + pub fn constraint_name(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn name(&self) -> Option { + pub fn match_type(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -7691,10 +7698,6 @@ impl ForeignKeyConstraint { support::child(&self.syntax) } #[inline] - pub fn constraint_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::CONSTRAINT_KW) - } - #[inline] pub fn foreign_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::FOREIGN_KW) } @@ -12213,7 +12216,7 @@ impl PrimaryKeyConstraint { support::child(&self.syntax) } #[inline] - pub fn name(&self) -> Option { + pub fn constraint_name(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -12225,10 +12228,6 @@ impl PrimaryKeyConstraint { support::child(&self.syntax) } #[inline] - pub fn constraint_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::CONSTRAINT_KW) - } - #[inline] pub fn key_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::KEY_KW) } @@ -12472,15 +12471,15 @@ pub struct ReferencesConstraint { } impl ReferencesConstraint { #[inline] - pub fn match_type(&self) -> Option { + pub fn column(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn name(&self) -> Option { + pub fn constraint_name(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn name_ref(&self) -> Option { + pub fn match_type(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -12492,7 +12491,7 @@ impl ReferencesConstraint { support::child(&self.syntax) } #[inline] - pub fn path(&self) -> Option { + pub fn table(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -12504,10 +12503,6 @@ impl ReferencesConstraint { support::token(&self.syntax, SyntaxKind::R_PAREN) } #[inline] - pub fn constraint_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::CONSTRAINT_KW) - } - #[inline] pub fn references_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::REFERENCES_KW) } @@ -15328,7 +15323,7 @@ impl UniqueConstraint { support::child(&self.syntax) } #[inline] - pub fn name(&self) -> Option { + pub fn constraint_name(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -15344,10 +15339,6 @@ impl UniqueConstraint { support::child(&self.syntax) } #[inline] - pub fn constraint_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::CONSTRAINT_KW) - } - #[inline] pub fn unique_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::UNIQUE_KW) } @@ -18963,6 +18954,24 @@ impl AstNode for ConstraintIndexTablespace { &self.syntax } } +impl AstNode for ConstraintName { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::CONSTRAINT_NAME + } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} impl AstNode for Copy { #[inline] fn can_cast(kind: SyntaxKind) -> bool { diff --git a/crates/squawk_syntax/src/ast/node_ext.rs b/crates/squawk_syntax/src/ast/node_ext.rs index 29196172..9ec68236 100644 --- a/crates/squawk_syntax/src/ast/node_ext.rs +++ b/crates/squawk_syntax/src/ast/node_ext.rs @@ -40,7 +40,7 @@ use super::support; impl ast::Constraint { #[inline] - pub fn name(&self) -> Option { + pub fn constraint_name(&self) -> Option { support::child(self.syntax()) } } diff --git a/crates/squawk_syntax/src/postgresql.ungram b/crates/squawk_syntax/src/postgresql.ungram index e8f326b6..e17490c1 100644 --- a/crates/squawk_syntax/src/postgresql.ungram +++ b/crates/squawk_syntax/src/postgresql.ungram @@ -574,8 +574,11 @@ Role = RoleList = Role (',' Role)* +ConstraintName = + 'constraint' Name + CheckConstraint = - ('constraint' Name) + ConstraintName? 'check' '(' Expr ')' NoInherit? @@ -621,7 +624,7 @@ NullsNotDistinct = 'nulls' 'not' 'distinct' UniqueConstraint = - ('constraint' Name) + ConstraintName? 'unique' ( UsingIndex @@ -629,7 +632,7 @@ UniqueConstraint = ) PrimaryKeyConstraint = - ('constraint' Name) + ConstraintName? 'primary' 'key' (UsingIndex | ColumnList PartitionItemList?) SetNullColumns = @@ -677,7 +680,7 @@ MatchType = ForeignKeyConstraint = - ('constraint' Name) + ConstraintName? 'foreign' 'key' from_columns:ColumnList 'references' Path to_columns:ColumnList MatchType? OnDeleteAction? @@ -721,8 +724,8 @@ GeneratedConstraint = ('always' 'as' '(' Expr ')' 'stored' | ('always' | 'by' 'default') 'as' 'identity' SequenceOptionList? ) ReferencesConstraint = - ('constraint' Name) - 'references' Path '(' NameRef ')' + ConstraintName? + 'references' table:Path ('(' column:NameRef ')')? MatchType? OnDeleteAction? OnUpdateAction? @@ -1734,7 +1737,7 @@ WhereConditionClause = 'where' '(' Expr ')' ExcludeConstraint = - ('constraint' Name) + ConstraintName? 'exclude' ConstraintIndexMethod? ConstraintExclusionList WhereConditionClause? diff --git a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__alter_table_ok_validation.snap b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__alter_table_ok_validation.snap index f7f2adbc..9310cc72 100644 --- a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__alter_table_ok_validation.snap +++ b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__alter_table_ok_validation.snap @@ -25,10 +25,11 @@ SOURCE_FILE@0..470 ADD_KW@95..98 "add" WHITESPACE@98..99 " " CHECK_CONSTRAINT@99..233 - CONSTRAINT_KW@99..109 "constraint" - WHITESPACE@109..110 " " - NAME@110..136 - IDENT@110..136 "widget_config_schema_ ..." + CONSTRAINT_NAME@99..136 + CONSTRAINT_KW@99..109 "constraint" + WHITESPACE@109..110 " " + NAME@110..136 + IDENT@110..136 "widget_config_schema_ ..." WHITESPACE@136..137 " " CHECK_KW@137..142 "check" WHITESPACE@142..143 " " @@ -90,10 +91,11 @@ SOURCE_FILE@0..470 ADD_KW@283..286 "add" WHITESPACE@286..287 " " CHECK_CONSTRAINT@287..459 - CONSTRAINT_KW@287..297 "constraint" - WHITESPACE@297..298 " " - NAME@298..343 - IDENT@298..343 "widget_instance_confi ..." + CONSTRAINT_NAME@287..343 + CONSTRAINT_KW@287..297 "constraint" + WHITESPACE@297..298 " " + NAME@298..343 + IDENT@298..343 "widget_instance_confi ..." WHITESPACE@343..344 " " CHECK_KW@344..349 "check" WHITESPACE@349..350 " "