sql_schema/
name_gen.rs

1use sqlparser::ast::{
2    AlterTableOperation, AlterType, ColumnDef, CreateIndex, CreateTable, ObjectName, ObjectType,
3    Statement,
4};
5
6use crate::SyntaxTree;
7
8#[bon::builder(finish_fn = build)]
9pub fn generate_name(
10    #[builder(start_fn)] tree: &SyntaxTree,
11    max_len: Option<usize>,
12) -> Option<String> {
13    let mut parts = tree
14        .0
15        .iter()
16        .filter_map(|s| match s {
17            Statement::CreateTable(CreateTable { name, .. }) => Some(format!("create_{name}")),
18            Statement::AlterTable {
19                name, operations, ..
20            } => alter_table_name(name, operations),
21            Statement::Drop {
22                object_type, names, ..
23            } => {
24                let object_type = match object_type {
25                    ObjectType::Table => String::new(),
26                    _ => object_type.to_string().to_lowercase() + "_",
27                };
28                let names = names
29                    .iter()
30                    .map(ToString::to_string)
31                    .collect::<Vec<String>>()
32                    .join("_and_");
33                Some(format!("drop_{object_type}{names}"))
34            }
35            Statement::CreateType { name, .. } => Some(format!("create_type_{name}")),
36            Statement::AlterType(AlterType { name, .. }) => Some(format!("alter_type_{name}")),
37            Statement::CreateIndex(CreateIndex {
38                name, table_name, ..
39            }) => {
40                let name = name.as_ref().map(|n| format!("_{n}")).unwrap_or_default();
41                Some(format!("create_{table_name}{name}"))
42            }
43            _ => None,
44        })
45        .collect::<Vec<_>>();
46
47    let mut suffix = None;
48    let mut name = parts.join("__");
49    let max_len = max_len.unwrap_or(50);
50    while name.len() > max_len {
51        suffix = Some("etc");
52        parts.pop();
53        name = parts.join("__");
54    }
55
56    if let Some(suffix) = suffix {
57        name = format!("{name}__{suffix}");
58    }
59
60    if name.is_empty() {
61        None
62    } else {
63        Some(name)
64    }
65}
66
67fn alter_table_name(name: &ObjectName, operations: &[AlterTableOperation]) -> Option<String> {
68    let mut table_verb = "alter";
69    let ops = operations
70        .iter()
71        .filter_map(|op| match op {
72            AlterTableOperation::AddColumn {
73                column_def: ColumnDef { name, .. },
74                ..
75            } => Some(format!("add_{name}")),
76            AlterTableOperation::DropColumn { column_name, .. } => {
77                Some(format!("drop_{column_name}"))
78            }
79            AlterTableOperation::RenameColumn {
80                old_column_name,
81                new_column_name,
82            } => Some(format!("rename_{old_column_name}_to_{new_column_name}")),
83            AlterTableOperation::AlterColumn { column_name, .. } => {
84                Some(format!("alter_{column_name}"))
85            }
86            AlterTableOperation::RenameTable { table_name } => {
87                table_verb = "rename";
88                Some(format!("to_{table_name}"))
89            }
90            _ => None,
91        })
92        .collect::<Vec<_>>();
93
94    Some(if ops.is_empty() || ops.len() > 2 {
95        format!("{table_verb}_{name}")
96    } else {
97        format!("{table_verb}_{name}_{}", ops.join("_"))
98    })
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[derive(Debug)]
106    struct TestCase {
107        sql: &'static str,
108        name: &'static str,
109    }
110
111    fn run_test_case(tc: &TestCase) {
112        let tree = SyntaxTree::builder().sql(tc.sql).build().unwrap();
113        let actual = generate_name(&tree).build();
114        assert_eq!(actual, Some(tc.name.to_owned()), "{tc:?}");
115    }
116
117    fn run_test_cases(test_cases: Vec<TestCase>) {
118        test_cases.iter().for_each(run_test_case);
119    }
120
121    #[test]
122    fn test_generate_name() {
123        run_test_cases(vec![
124            TestCase {
125                sql: "CREATE TABLE foo(bar TEXT);",
126                name: "create_foo",
127            },
128            TestCase {
129                sql: "CREATE TABLE foo(bar TEXT); CREATE TABLE bar(foo TEXT);",
130                name: "create_foo__create_bar",
131            },
132            TestCase {
133                sql: "CREATE TABLE foo(bar TEXT); CREATE TABLE bar(foo TEXT); CREATE TABLE baz(id INT); CREATE TABLE some_really_long_name(id INT);",
134                name: "create_foo__create_bar__create_baz__etc",
135            },
136            TestCase {
137                sql: "ALTER TABLE foo DROP COLUMN bar;",
138                name: "alter_foo_drop_bar",
139            },
140            TestCase {
141                sql: "ALTER TABLE foo ADD COLUMN bar TEXT;",
142                name: "alter_foo_add_bar",
143            },
144            TestCase {
145                sql: "ALTER TABLE foo ALTER COLUMN bar SET DATA TYPE INT;",
146                name: "alter_foo_alter_bar",
147            },
148            TestCase {
149                sql: "ALTER TABLE foo RENAME bar TO id;",
150                name: "alter_foo_rename_bar_to_id",
151            },
152            TestCase {
153                sql: "ALTER TABLE foo RENAME TO bar;",
154                name: "rename_foo_to_bar",
155            },
156            TestCase {
157                sql: "DROP TABLE foo;",
158                name: "drop_foo",
159            },
160            TestCase {
161                sql: "CREATE TYPE status AS ENUM('one', 'two', 'three');",
162                name: "create_type_status",
163            },
164            TestCase {
165                sql: "DROP TYPE status;",
166                name: "drop_type_status",
167            },
168            TestCase {
169                sql: "CREATE UNIQUE INDEX title_idx ON films (title);",
170                name: "create_films_title_idx",
171            },
172            TestCase {
173                sql: "DROP INDEX title_idx",
174                name: "drop_index_title_idx",
175            },
176        ]);
177    }
178}