Skip to main content

sql_schema/
name_gen.rs

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