-
Notifications
You must be signed in to change notification settings - Fork 62
add rules to ban domains with constraints #418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
69c04c0
7b502f0
816d21e
93c984b
7d840eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| --- | ||
| id: ban-alter-domain-with-add-constraint | ||
| title: ban-alter-domain-with-add-constraint | ||
| --- | ||
|
|
||
| ## problem | ||
|
|
||
| Postgres [domains][], which associate a data type with an optional check constraint, have poor support for online migrations | ||
| when associated with a check constraint. | ||
|
|
||
| [domains]: https://www.postgresql.org/docs/current/sql-createdomain.html | ||
|
|
||
| The purpose of domains is to make the named type-plus-constraint reusable, but this means that any change to the domain's constraint | ||
| requires _all_ columns that use the domain to be revalidated. And, because Postgres can't reason well about arbitrary constraints, | ||
| they increase the chances of a change requiring an expensive table rewrite. | ||
|
|
||
| A couple relevant quotes from a Postgres developer include: | ||
|
|
||
| > No, that's not going to work: coercing to a domain that has any | ||
| > constraints is considered to require a rewrite. | ||
|
|
||
| And: | ||
|
|
||
| > In any case, the point remains that domains are pretty inefficient | ||
| > compared to native types like varchar(12); partly because the system | ||
| > can’t reason very well about arbitrary check constraints as compared | ||
| > to simple length constraints, and partly because the whole feature | ||
| > just isn’t implemented very completely or efficiently. So you’ll be | ||
| > paying *a lot* for some hypothetical future savings. | ||
|
|
||
|
|
||
| ## solution | ||
|
|
||
| Either avoid domains altogether, or (most importantly) avoid adding constraints to domains. Instead, put the [constraint][] | ||
| on the desired column(s) directly. | ||
|
|
||
| [constraint]: https://www.postgresql.org/docs/current/sql-createdomain.html | ||
|
|
||
| ## links | ||
|
|
||
| [The mailing list thread from which the above quotes are sourced](https://www.postgresql.org/message-id/flat/CADVWZZKjhV9fLpewPdQMZx7V6kvGJViwMEDrPAv9m50rGeK9UA%40mail.gmail.com) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| --- | ||
| id: ban-create-domain-with-constraint | ||
| title: ban-create-domain-with-constraint | ||
| --- | ||
|
|
||
| ## problem | ||
|
|
||
| Postgres [domains][], which associate a data type with an optional check constraint, have poor support for online migrations | ||
| when associated with a check constraint. | ||
|
|
||
| [domains]: https://www.postgresql.org/docs/current/sql-createdomain.html | ||
|
|
||
| The purpose of domains is to make the named type-plus-constraint reusable, but this means that any change to the domain's constraint | ||
| requires _all_ columns that use the domain to be revalidated. And, because Postgres can't reason well about arbitrary constraints, | ||
| they increase the chances of a change requiring an expensive table rewrite. | ||
|
|
||
| A couple relevant quotes from a Postgres developer include: | ||
|
|
||
| > No, that's not going to work: coercing to a domain that has any | ||
| > constraints is considered to require a rewrite. | ||
|
|
||
| And: | ||
|
|
||
| > In any case, the point remains that domains are pretty inefficient | ||
| > compared to native types like varchar(12); partly because the system | ||
| > can’t reason very well about arbitrary check constraints as compared | ||
| > to simple length constraints, and partly because the whole feature | ||
| > just isn’t implemented very completely or efficiently. So you’ll be | ||
| > paying *a lot* for some hypothetical future savings. | ||
|
|
||
|
|
||
| ## solution | ||
|
|
||
| Either avoid domains altogether, or (most importantly) avoid adding constraints to domains. Instead, put the [constraint][] | ||
| on the desired column(s) directly. | ||
|
|
||
| [constraint]: https://www.postgresql.org/docs/current/sql-createdomain.html | ||
|
|
||
| ## links | ||
|
|
||
| [The mailing list thread from which the above quotes are sourced](https://www.postgresql.org/message-id/flat/CADVWZZKjhV9fLpewPdQMZx7V6kvGJViwMEDrPAv9m50rGeK9UA%40mail.gmail.com) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| use crate::{ | ||
| versions::Version, | ||
| violations::{RuleViolation, RuleViolationKind}, | ||
| }; | ||
|
|
||
| use squawk_parser::ast::{RawStmt, Stmt}; | ||
|
|
||
| #[must_use] | ||
| pub fn ban_alter_domain_with_add_constraint( | ||
| tree: &[RawStmt], | ||
| _pg_version: Option<Version>, | ||
| _assume_in_transaction: bool, | ||
| ) -> Vec<RuleViolation> { | ||
| let mut errs = vec![]; | ||
| for raw_stmt in tree { | ||
| match &raw_stmt.stmt { | ||
| Stmt::AlterDomainStmt(stmt) if stmt.subtype == "C" => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i couldn't find direct documentation, or even a clear place to cite in the
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah the api is pretty wonky at the moment, I'm working on a new parser that should make this easier when it's done! preview of it at (using wasm): |
||
| errs.push(RuleViolation::new( | ||
| RuleViolationKind::BanAlterDomainWithAddConstraint, | ||
| raw_stmt.into(), | ||
| None, | ||
| )); | ||
| } | ||
| _ => continue, | ||
| } | ||
| } | ||
| errs | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test_rules { | ||
| use crate::{ | ||
| check_sql_with_rule, | ||
| violations::{RuleViolation, RuleViolationKind}, | ||
| }; | ||
|
|
||
| use insta::assert_debug_snapshot; | ||
|
|
||
| fn lint_sql(sql: &str) -> Vec<RuleViolation> { | ||
| check_sql_with_rule( | ||
| sql, | ||
| &RuleViolationKind::BanAlterDomainWithAddConstraint, | ||
| None, | ||
| false, | ||
| ) | ||
| .unwrap() | ||
| } | ||
|
|
||
| #[test] | ||
| fn ban_alter_domain_without_add_constraint_is_ok() { | ||
| let sql = r" | ||
| ALTER DOMAIN domain_name_1 SET DEFAULT 1; | ||
| ALTER DOMAIN domain_name_2 SET NOT NULL; | ||
| ALTER DOMAIN domain_name_3 DROP CONSTRAINT other_domain_name; | ||
| ALTER DOMAIN domain_name_4 RENAME CONSTRAINT constraint_name TO other_constraint_name; | ||
| ALTER DOMAIN domain_name_5 RENAME TO other_domain_name; | ||
| ALTER DOMAIN domain_name_6 VALIDATE CONSTRAINT constraint_name; | ||
| ALTER DOMAIN domain_name_7 OWNER TO you; | ||
| ALTER DOMAIN domain_name_8 SET SCHEMA foo; | ||
| "; | ||
| assert_eq!(lint_sql(sql), vec![]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn ban_alter_domain_with_add_constraint_works() { | ||
| let sql = r" | ||
| ALTER DOMAIN domain_name ADD CONSTRAINT constraint_name CHECK (value > 0); | ||
| "; | ||
| assert_debug_snapshot!(lint_sql(sql)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| use crate::{ | ||
| versions::Version, | ||
| violations::{RuleViolation, RuleViolationKind}, | ||
| }; | ||
|
|
||
| use squawk_parser::ast::{RawStmt, Stmt}; | ||
|
|
||
| #[must_use] | ||
| pub fn ban_create_domain_with_constraint( | ||
| tree: &[RawStmt], | ||
| _pg_version: Option<Version>, | ||
| _assume_in_transaction: bool, | ||
| ) -> Vec<RuleViolation> { | ||
| let mut errs = vec![]; | ||
| for raw_stmt in tree { | ||
| match &raw_stmt.stmt { | ||
| Stmt::CreateDomainStmt(stmt) if !stmt.constraints.is_empty() => { | ||
| errs.push(RuleViolation::new( | ||
| RuleViolationKind::BanCreateDomainWithConstraint, | ||
| raw_stmt.into(), | ||
| None, | ||
| )); | ||
| } | ||
| _ => continue, | ||
| } | ||
| } | ||
| errs | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test_rules { | ||
| use crate::{ | ||
| check_sql_with_rule, | ||
| violations::{RuleViolation, RuleViolationKind}, | ||
| }; | ||
| use insta::assert_debug_snapshot; | ||
|
|
||
| fn lint_sql(sql: &str) -> Vec<RuleViolation> { | ||
| check_sql_with_rule( | ||
| sql, | ||
| &RuleViolationKind::BanCreateDomainWithConstraint, | ||
| None, | ||
| false, | ||
| ) | ||
| .unwrap() | ||
| } | ||
|
|
||
| #[test] | ||
| fn ban_create_domain_without_constraint_is_ok() { | ||
| let sql = r" | ||
| CREATE DOMAIN domain_name_1 AS TEXT; | ||
| CREATE DOMAIN domain_name_2 AS CHARACTER VARYING; | ||
| "; | ||
| assert_eq!(lint_sql(sql), vec![]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn ban_create_domain_with_constraint_works() { | ||
| let sql = r" | ||
| CREATE DOMAIN domain_name_3 AS NUMERIC(15,5) CHECK (value > 0); | ||
| "; | ||
| assert_debug_snapshot!(lint_sql(sql)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| --- | ||
| source: linter/src/rules/ban_alter_domain_with_add_constraint.rs | ||
| expression: lint_sql(sql) | ||
|
|
||
| --- | ||
| [ | ||
| RuleViolation { | ||
| kind: BanAlterDomainWithAddConstraint, | ||
| span: Span { | ||
| start: 0, | ||
| len: Some( | ||
| 79, | ||
| ), | ||
| }, | ||
| messages: [ | ||
| Note( | ||
| "Domains with constraints have poor support for online migrations", | ||
| ), | ||
| ], | ||
| }, | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| --- | ||
| source: linter/src/rules/ban_create_domain_with_constraint.rs | ||
| expression: lint_sql(sql) | ||
|
|
||
| --- | ||
| [ | ||
| RuleViolation { | ||
| kind: BanCreateDomainWithConstraint, | ||
| span: Span { | ||
| start: 0, | ||
| len: Some( | ||
| 67, | ||
| ), | ||
| }, | ||
| messages: [ | ||
| Note( | ||
| "Domains with constraints have poor support for online migrations", | ||
| ), | ||
| ], | ||
| }, | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both of these docs are very thorough, thank you!