Skip to content

Commit bc01ca1

Browse files
committed
feat(analyser): creating enum
1 parent 6ed1eca commit bc01ca1

File tree

20 files changed

+347
-32
lines changed

20 files changed

+347
-32
lines changed

crates/pgt_analyser/src/lint/safety.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod ban_drop_table;
1616
pub mod ban_truncate_cascade;
1717
pub mod changing_column_type;
1818
pub mod constraint_missing_not_valid;
19+
pub mod creating_enum;
1920
pub mod disallow_unique_constraint;
2021
pub mod multiple_alter_table;
2122
pub mod prefer_big_int;
@@ -31,4 +32,4 @@ pub mod renaming_table;
3132
pub mod require_concurrent_index_creation;
3233
pub mod require_concurrent_index_deletion;
3334
pub mod transaction_nesting;
34-
declare_lint_group! { pub Safety { name : "safety" , rules : [self :: add_serial_column :: AddSerialColumn , self :: adding_field_with_default :: AddingFieldWithDefault , self :: adding_foreign_key_constraint :: AddingForeignKeyConstraint , self :: adding_not_null_field :: AddingNotNullField , self :: adding_primary_key_constraint :: AddingPrimaryKeyConstraint , self :: adding_required_field :: AddingRequiredField , self :: ban_char_field :: BanCharField , self :: ban_concurrent_index_creation_in_transaction :: BanConcurrentIndexCreationInTransaction , self :: ban_drop_column :: BanDropColumn , self :: ban_drop_database :: BanDropDatabase , self :: ban_drop_not_null :: BanDropNotNull , self :: ban_drop_table :: BanDropTable , self :: ban_truncate_cascade :: BanTruncateCascade , self :: changing_column_type :: ChangingColumnType , self :: constraint_missing_not_valid :: ConstraintMissingNotValid , self :: disallow_unique_constraint :: DisallowUniqueConstraint , self :: multiple_alter_table :: MultipleAlterTable , self :: prefer_big_int :: PreferBigInt , self :: prefer_bigint_over_int :: PreferBigintOverInt , self :: prefer_bigint_over_smallint :: PreferBigintOverSmallint , self :: prefer_identity :: PreferIdentity , self :: prefer_jsonb :: PreferJsonb , self :: prefer_robust_stmts :: PreferRobustStmts , self :: prefer_text_field :: PreferTextField , self :: prefer_timestamptz :: PreferTimestamptz , self :: renaming_column :: RenamingColumn , self :: renaming_table :: RenamingTable , self :: require_concurrent_index_creation :: RequireConcurrentIndexCreation , self :: require_concurrent_index_deletion :: RequireConcurrentIndexDeletion , self :: transaction_nesting :: TransactionNesting ,] } }
35+
declare_lint_group! { pub Safety { name : "safety" , rules : [self :: add_serial_column :: AddSerialColumn , self :: adding_field_with_default :: AddingFieldWithDefault , self :: adding_foreign_key_constraint :: AddingForeignKeyConstraint , self :: adding_not_null_field :: AddingNotNullField , self :: adding_primary_key_constraint :: AddingPrimaryKeyConstraint , self :: adding_required_field :: AddingRequiredField , self :: ban_char_field :: BanCharField , self :: ban_concurrent_index_creation_in_transaction :: BanConcurrentIndexCreationInTransaction , self :: ban_drop_column :: BanDropColumn , self :: ban_drop_database :: BanDropDatabase , self :: ban_drop_not_null :: BanDropNotNull , self :: ban_drop_table :: BanDropTable , self :: ban_truncate_cascade :: BanTruncateCascade , self :: changing_column_type :: ChangingColumnType , self :: constraint_missing_not_valid :: ConstraintMissingNotValid , self :: creating_enum :: CreatingEnum , self :: disallow_unique_constraint :: DisallowUniqueConstraint , self :: multiple_alter_table :: MultipleAlterTable , self :: prefer_big_int :: PreferBigInt , self :: prefer_bigint_over_int :: PreferBigintOverInt , self :: prefer_bigint_over_smallint :: PreferBigintOverSmallint , self :: prefer_identity :: PreferIdentity , self :: prefer_jsonb :: PreferJsonb , self :: prefer_robust_stmts :: PreferRobustStmts , self :: prefer_text_field :: PreferTextField , self :: prefer_timestamptz :: PreferTimestamptz , self :: renaming_column :: RenamingColumn , self :: renaming_table :: RenamingTable , self :: require_concurrent_index_creation :: RequireConcurrentIndexCreation , self :: require_concurrent_index_deletion :: RequireConcurrentIndexDeletion , self :: transaction_nesting :: TransactionNesting ,] } }
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use pgt_analyse::{Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule};
2+
use pgt_console::markup;
3+
use pgt_diagnostics::Severity;
4+
5+
declare_lint_rule! {
6+
/// Creating enum types is not recommended for new applications.
7+
///
8+
/// Enumerated types have several limitations that make them difficult to work with in production:
9+
///
10+
/// - Removing values from an enum requires complex migrations and is not supported directly
11+
/// - Adding values to an enum requires an ACCESS EXCLUSIVE lock in some PostgreSQL versions
12+
/// - Associating additional data with enum values is impossible without restructuring
13+
/// - Renaming enum values requires careful migration planning
14+
///
15+
/// A lookup table with a foreign key constraint provides more flexibility and is easier to maintain.
16+
///
17+
/// ## Examples
18+
///
19+
/// ### Invalid
20+
///
21+
/// ```sql,expect_diagnostic
22+
/// CREATE TYPE document_type AS ENUM ('invoice', 'receipt', 'other');
23+
/// ```
24+
///
25+
/// ### Valid
26+
///
27+
/// ```sql
28+
/// -- Use a lookup table instead
29+
/// CREATE TABLE document_type (
30+
/// type_name TEXT PRIMARY KEY
31+
/// );
32+
/// INSERT INTO document_type VALUES ('invoice'), ('receipt'), ('other');
33+
/// ```
34+
///
35+
pub CreatingEnum {
36+
version: "next",
37+
name: "creatingEnum",
38+
severity: Severity::Warning,
39+
recommended: false,
40+
sources: &[RuleSource::Eugene("W13")],
41+
}
42+
}
43+
44+
impl Rule for CreatingEnum {
45+
type Options = ();
46+
47+
fn run(ctx: &RuleContext<Self>) -> Vec<RuleDiagnostic> {
48+
let mut diagnostics = Vec::new();
49+
50+
if let pgt_query::NodeEnum::CreateEnumStmt(stmt) = &ctx.stmt() {
51+
let type_name = get_type_name(&stmt.type_name);
52+
53+
diagnostics.push(
54+
RuleDiagnostic::new(
55+
rule_category!(),
56+
None,
57+
markup! {
58+
"Creating enum type "<Emphasis>{type_name}</Emphasis>" is not recommended."
59+
},
60+
)
61+
.detail(None, "Enum types are difficult to modify: removing values requires complex migrations, and associating additional data with values is not possible.")
62+
.note("Consider using a lookup table with a foreign key constraint instead, which provides more flexibility and easier maintenance."),
63+
);
64+
}
65+
66+
diagnostics
67+
}
68+
}
69+
70+
fn get_type_name(type_name_nodes: &[pgt_query::protobuf::Node]) -> String {
71+
type_name_nodes
72+
.iter()
73+
.filter_map(|n| {
74+
if let Some(pgt_query::NodeEnum::String(s)) = &n.node {
75+
Some(s.sval.as_str())
76+
} else {
77+
None
78+
}
79+
})
80+
.collect::<Vec<_>>()
81+
.join(".")
82+
}

crates/pgt_analyser/src/options.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub type BanTruncateCascade =
2525
pub type ChangingColumnType =
2626
<lint::safety::changing_column_type::ChangingColumnType as pgt_analyse::Rule>::Options;
2727
pub type ConstraintMissingNotValid = < lint :: safety :: constraint_missing_not_valid :: ConstraintMissingNotValid as pgt_analyse :: Rule > :: Options ;
28+
pub type CreatingEnum = <lint::safety::creating_enum::CreatingEnum as pgt_analyse::Rule>::Options;
2829
pub type DisallowUniqueConstraint = < lint :: safety :: disallow_unique_constraint :: DisallowUniqueConstraint as pgt_analyse :: Rule > :: Options ;
2930
pub type MultipleAlterTable =
3031
<lint::safety::multiple_alter_table::MultipleAlterTable as pgt_analyse::Rule>::Options;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- expect_only_lint/safety/creatingEnum
2+
CREATE TYPE document_type AS ENUM ('invoice', 'receipt', 'other');
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
source: crates/pgt_analyser/tests/rules_tests.rs
3+
expression: snapshot
4+
snapshot_kind: text
5+
---
6+
# Input
7+
```
8+
-- expect_only_lint/safety/creatingEnum
9+
CREATE TYPE document_type AS ENUM ('invoice', 'receipt', 'other');
10+
```
11+
12+
# Diagnostics
13+
lint/safety/creatingEnum ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
14+
15+
× Creating enum type document_type is not recommended.
16+
17+
i Enum types are difficult to modify: removing values requires complex migrations, and associating additional data with values is not possible.
18+
19+
i Consider using a lookup table with a foreign key constraint instead, which provides more flexibility and easier maintenance.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- expect_only_lint/safety/creatingEnum
2+
-- Simple enum with two values
3+
CREATE TYPE status AS ENUM ('active', 'inactive');
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: crates/pgt_analyser/tests/rules_tests.rs
3+
expression: snapshot
4+
snapshot_kind: text
5+
---
6+
# Input
7+
```
8+
-- expect_only_lint/safety/creatingEnum
9+
-- Simple enum with two values
10+
CREATE TYPE status AS ENUM ('active', 'inactive');
11+
12+
```
13+
14+
# Diagnostics
15+
lint/safety/creatingEnum ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
16+
17+
× Creating enum type status is not recommended.
18+
19+
i Enum types are difficult to modify: removing values requires complex migrations, and associating additional data with values is not possible.
20+
21+
i Consider using a lookup table with a foreign key constraint instead, which provides more flexibility and easier maintenance.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- Valid: Creating a regular table (not an enum)
2+
-- expect_no_diagnostics
3+
CREATE TABLE users (
4+
id INT PRIMARY KEY,
5+
name TEXT NOT NULL
6+
);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
source: crates/pgt_analyser/tests/rules_tests.rs
3+
expression: snapshot
4+
snapshot_kind: text
5+
---
6+
# Input
7+
```
8+
-- Valid: Creating a regular table (not an enum)
9+
-- expect_no_diagnostics
10+
CREATE TABLE users (
11+
id INT PRIMARY KEY,
12+
name TEXT NOT NULL
13+
);
14+
15+
```
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- Valid: Using a lookup table instead of enum
2+
-- expect_no_diagnostics
3+
CREATE TABLE document_type (
4+
type_name TEXT PRIMARY KEY
5+
);
6+
7+
INSERT INTO document_type VALUES ('invoice'), ('receipt'), ('other');
8+
9+
CREATE TABLE document (
10+
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
11+
type TEXT REFERENCES document_type(type_name)
12+
);

0 commit comments

Comments
 (0)