Skip to main content

sql_schema/
name_gen.rs

1use sqlparser::ast::{
2    AlterTable, AlterTableOperation, AlterType, ColumnDef, CreateIndex, CreateTable, ObjectName,
3    ObjectType, RenameTableNameKind, 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(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_names, .. } => Some(format!(
77                "drop_{}",
78                column_names
79                    .iter()
80                    .map(|ident| ident.value.clone())
81                    .collect::<Vec<_>>()
82                    .join("_")
83            )),
84            AlterTableOperation::RenameColumn {
85                old_column_name,
86                new_column_name,
87            } => Some(format!("rename_{old_column_name}_to_{new_column_name}")),
88            AlterTableOperation::AlterColumn { column_name, .. } => {
89                Some(format!("alter_{column_name}"))
90            }
91            AlterTableOperation::RenameTable { table_name } => {
92                table_verb = "rename";
93                Some(format!(
94                    "to_{table_name}",
95                    table_name = match table_name {
96                        RenameTableNameKind::As(name) => name,
97                        RenameTableNameKind::To(name) => name,
98                    }
99                ))
100            }
101            _ => None,
102        })
103        .collect::<Vec<_>>();
104
105    Some(if ops.is_empty() || ops.len() > 2 {
106        format!("{table_verb}_{name}")
107    } else {
108        format!("{table_verb}_{name}_{}", ops.join("_"))
109    })
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[derive(Debug)]
117    struct TestCase {
118        sql: &'static str,
119        name: &'static str,
120    }
121
122    fn run_test_case(tc: &TestCase) {
123        let tree = SyntaxTree::builder().sql(tc.sql).build().unwrap();
124        let actual = generate_name(&tree).build();
125        assert_eq!(actual, Some(tc.name.to_owned()), "{tc:?}");
126    }
127
128    fn run_test_cases(test_cases: Vec<TestCase>) {
129        test_cases.iter().for_each(run_test_case);
130    }
131
132    #[test]
133    fn test_generate_name() {
134        run_test_cases(vec![
135            TestCase {
136                sql: "CREATE TABLE foo(bar TEXT);",
137                name: "create_foo",
138            },
139            TestCase {
140                sql: "CREATE TABLE foo(bar TEXT); CREATE TABLE bar(foo TEXT);",
141                name: "create_foo__create_bar",
142            },
143            TestCase {
144                sql: "CREATE TABLE foo(bar TEXT); CREATE TABLE bar(foo TEXT); CREATE TABLE baz(id INT); CREATE TABLE some_really_long_name(id INT);",
145                name: "create_foo__create_bar__create_baz__etc",
146            },
147            TestCase {
148                sql: "ALTER TABLE foo DROP COLUMN bar;",
149                name: "alter_foo_drop_bar",
150            },
151            TestCase {
152                sql: "ALTER TABLE foo ADD COLUMN bar TEXT;",
153                name: "alter_foo_add_bar",
154            },
155            TestCase {
156                sql: "ALTER TABLE foo ALTER COLUMN bar SET DATA TYPE INT;",
157                name: "alter_foo_alter_bar",
158            },
159            TestCase {
160                sql: "ALTER TABLE foo RENAME bar TO id;",
161                name: "alter_foo_rename_bar_to_id",
162            },
163            TestCase {
164                sql: "ALTER TABLE foo RENAME TO bar;",
165                name: "rename_foo_to_bar",
166            },
167            TestCase {
168                sql: "DROP TABLE foo;",
169                name: "drop_foo",
170            },
171            TestCase {
172                sql: "CREATE TYPE status AS ENUM('one', 'two', 'three');",
173                name: "create_type_status",
174            },
175            TestCase {
176                sql: "DROP TYPE status;",
177                name: "drop_type_status",
178            },
179            TestCase {
180                sql: "CREATE UNIQUE INDEX title_idx ON films (title);",
181                name: "create_films_title_idx",
182            },
183            TestCase {
184                sql: "DROP INDEX title_idx",
185                name: "drop_index_title_idx",
186            },
187        ]);
188    }
189}