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}