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}