Skip to content

Commit df10b28

Browse files
authored
feat(adding_foreign_key_constraint): allow when create table in transaction #703 (#713)
rel: #703
1 parent eff6fe0 commit df10b28

File tree

1 file changed

+77
-38
lines changed

1 file changed

+77
-38
lines changed

crates/squawk_linter/src/rules/adding_foreign_key_constraint.rs

Lines changed: 77 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,75 @@
11
use squawk_syntax::{
22
Parse, SourceFile,
33
ast::{self, AstNode},
4+
identifier::Identifier,
45
};
56

6-
use crate::{Linter, Rule, Violation};
7+
use crate::{
8+
Linter, Rule, Violation, rules::constraint_missing_not_valid::tables_created_in_transaction,
9+
};
710

811
pub(crate) fn adding_foreign_key_constraint(ctx: &mut Linter, parse: &Parse<SourceFile>) {
912
let message = "Adding a foreign key constraint requires a table scan and a `SHARE ROW EXCLUSIVE` lock on both tables, which blocks writes to each table.";
1013
let help = "Add `NOT VALID` to the constraint in one transaction and then VALIDATE the constraint in a separate transaction.";
1114
let file = parse.tree();
15+
let tables_created = tables_created_in_transaction(ctx.settings.assume_in_transaction, &file);
1216
// TODO: use match_ast! like in #api_walkthrough
1317
for stmt in file.stmts() {
1418
if let ast::Stmt::AlterTable(alter_table) = stmt {
15-
for action in alter_table.actions() {
16-
match action {
17-
ast::AlterTableAction::AddConstraint(add_constraint) => {
18-
if add_constraint.not_valid().is_some() {
19-
// Adding foreign key is okay when NOT VALID is specified.
20-
continue;
21-
}
22-
if let Some(constraint) = add_constraint.constraint() {
23-
if matches!(
24-
constraint,
25-
ast::Constraint::ForeignKeyConstraint(_)
26-
| ast::Constraint::ReferencesConstraint(_)
27-
) {
28-
ctx.report(
29-
Violation::for_node(
30-
Rule::AddingForeignKeyConstraint,
31-
message.into(),
32-
constraint.syntax(),
19+
if let Some(table_name) = alter_table
20+
.relation_name()
21+
.and_then(|x| x.path())
22+
.and_then(|x| x.segment())
23+
.and_then(|x| x.name_ref())
24+
{
25+
for action in alter_table.actions() {
26+
match action {
27+
ast::AlterTableAction::AddConstraint(add_constraint) => {
28+
if add_constraint.not_valid().is_some()
29+
|| tables_created.contains(&Identifier::new(&table_name.text()))
30+
{
31+
// Adding foreign key is okay when:
32+
// - NOT VALID is specified.
33+
// - The table is created in the same transaction
34+
continue;
35+
}
36+
if let Some(constraint) = add_constraint.constraint() {
37+
if matches!(
38+
constraint,
39+
ast::Constraint::ForeignKeyConstraint(_)
40+
| ast::Constraint::ReferencesConstraint(_)
41+
) {
42+
ctx.report(
43+
Violation::for_node(
44+
Rule::AddingForeignKeyConstraint,
45+
message.into(),
46+
constraint.syntax(),
47+
)
48+
.help(help),
3349
)
34-
.help(help),
35-
)
50+
}
3651
}
3752
}
38-
}
39-
ast::AlterTableAction::AddColumn(add_column) => {
40-
for constraint in add_column.constraints() {
41-
if matches!(
42-
constraint,
43-
ast::Constraint::ForeignKeyConstraint(_)
44-
| ast::Constraint::ReferencesConstraint(_)
45-
) {
46-
ctx.report(
47-
Violation::for_node(
48-
Rule::AddingForeignKeyConstraint,
49-
message.into(),
50-
constraint.syntax(),
53+
ast::AlterTableAction::AddColumn(add_column) => {
54+
for constraint in add_column.constraints() {
55+
if matches!(
56+
constraint,
57+
ast::Constraint::ForeignKeyConstraint(_)
58+
| ast::Constraint::ReferencesConstraint(_)
59+
) {
60+
ctx.report(
61+
Violation::for_node(
62+
Rule::AddingForeignKeyConstraint,
63+
message.into(),
64+
constraint.syntax(),
65+
)
66+
.help(help),
5167
)
52-
.help(help),
53-
)
68+
}
5469
}
5570
}
71+
_ => continue,
5672
}
57-
_ => continue,
5873
}
5974
}
6075
}
@@ -64,7 +79,7 @@ pub(crate) fn adding_foreign_key_constraint(ctx: &mut Linter, parse: &Parse<Sour
6479
#[cfg(test)]
6580
mod test {
6681
use crate::Rule;
67-
use crate::test_utils::lint;
82+
use crate::test_utils::{lint, lint_with_assume_in_transaction};
6883

6984
#[test]
7085
fn create_table_with_foreign_key_constraint() {
@@ -86,6 +101,30 @@ mod test {
86101
assert!(errors.is_empty());
87102
}
88103

104+
#[test]
105+
fn alter_table_foreign_key_assume_transaction() {
106+
let sql = r#"
107+
CREATE TABLE "emails" ("id" UUID NOT NULL, "user_id" UUID NOT NULL);
108+
ALTER TABLE "emails" ADD CONSTRAINT "fk_user" FOREIGN KEY ("user_id") REFERENCES "users" ("id");
109+
"#;
110+
111+
let errors = lint_with_assume_in_transaction(sql, Rule::AddingForeignKeyConstraint);
112+
assert!(errors.is_empty());
113+
}
114+
115+
#[test]
116+
fn alter_table_foreign_key_in_transaction() {
117+
let sql = r#"
118+
BEGIN;
119+
CREATE TABLE "emails" ("id" UUID NOT NULL, "user_id" UUID NOT NULL);
120+
ALTER TABLE "emails" ADD CONSTRAINT "fk_user" FOREIGN KEY ("user_id") REFERENCES "users" ("id");
121+
COMMIT;
122+
"#;
123+
124+
let errors = lint(sql, Rule::AddingForeignKeyConstraint);
125+
assert!(errors.is_empty());
126+
}
127+
89128
#[test]
90129
fn add_foreign_key_constraint_not_valid_validate() {
91130
let sql = r#"

0 commit comments

Comments
 (0)