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}