1pub mod create;
17pub mod ddl;
18pub mod diff;
19pub mod expected;
20pub mod introspect;
21pub mod migrate;
22
23pub use create::{CreateTable, SchemaBuilder};
24pub use ddl::{
25 DdlGenerator, MysqlDdlGenerator, PostgresDdlGenerator, SqliteDdlGenerator,
26 generator_for_dialect,
27};
28pub use expected::{
29 ModelSchema, ModelTuple, expected_schema, normalize_sql_type, table_schema_from_fields,
30 table_schema_from_model,
31};
32pub use introspect::{
33 CheckConstraintInfo, ColumnInfo, DatabaseSchema, Dialect, ForeignKeyInfo, IndexInfo,
34 Introspector, ParsedSqlType, TableInfo, UniqueConstraintInfo,
35};
36pub use migrate::{Migration, MigrationFormat, MigrationRunner, MigrationStatus, MigrationWriter};
37
38use asupersync::{Cx, Outcome};
39use sqlmodel_core::{Connection, Model, quote_ident};
40
41pub fn create_table<M: Model>() -> CreateTable<M> {
58 CreateTable::new()
59}
60
61pub async fn create_all<C: Connection>(
66 cx: &Cx,
67 conn: &C,
68 schemas: &[&str],
69) -> Outcome<(), sqlmodel_core::Error> {
70 for sql in schemas {
71 match conn.execute(cx, sql, &[]).await {
72 Outcome::Ok(_) => continue,
73 Outcome::Err(e) => return Outcome::Err(e),
74 Outcome::Cancelled(r) => return Outcome::Cancelled(r),
75 Outcome::Panicked(p) => return Outcome::Panicked(p),
76 }
77 }
78 Outcome::Ok(())
79}
80
81pub async fn drop_table<C: Connection>(
83 cx: &Cx,
84 conn: &C,
85 table_name: &str,
86 if_exists: bool,
87) -> Outcome<(), sqlmodel_core::Error> {
88 let sql = if if_exists {
89 format!("DROP TABLE IF EXISTS {}", quote_ident(table_name))
90 } else {
91 format!("DROP TABLE {}", quote_ident(table_name))
92 };
93
94 conn.execute(cx, &sql, &[]).await.map(|_| ())
95}
96
97pub fn drop_table_sql(table_name: &str, if_exists: bool) -> String {
101 if if_exists {
102 format!("DROP TABLE IF EXISTS {}", quote_ident(table_name))
103 } else {
104 format!("DROP TABLE {}", quote_ident(table_name))
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
117 fn test_drop_table_sql_simple() {
118 let sql = drop_table_sql("users", true);
119 assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
120
121 let sql = drop_table_sql("heroes", false);
122 assert_eq!(sql, "DROP TABLE \"heroes\"");
123 }
124
125 #[test]
126 fn test_drop_table_sql_with_keyword_name() {
127 let sql = drop_table_sql("order", true);
129 assert_eq!(sql, "DROP TABLE IF EXISTS \"order\"");
130
131 let sql = drop_table_sql("select", true);
132 assert_eq!(sql, "DROP TABLE IF EXISTS \"select\"");
133
134 let sql = drop_table_sql("user", true);
135 assert_eq!(sql, "DROP TABLE IF EXISTS \"user\"");
136 }
137
138 #[test]
139 fn test_drop_table_sql_with_embedded_quotes() {
140 let sql = drop_table_sql("my\"table", true);
142 assert_eq!(sql, "DROP TABLE IF EXISTS \"my\"\"table\"");
143
144 let sql = drop_table_sql("test\"\"name", false);
145 assert_eq!(sql, "DROP TABLE \"test\"\"\"\"name\"");
146
147 let sql = drop_table_sql("\"", true);
149 assert_eq!(sql, "DROP TABLE IF EXISTS \"\"\"\"");
150 }
151
152 #[test]
153 fn test_drop_table_sql_with_spaces() {
154 let sql = drop_table_sql("my table", true);
155 assert_eq!(sql, "DROP TABLE IF EXISTS \"my table\"");
156 }
157
158 #[test]
159 fn test_drop_table_sql_with_unicode() {
160 let sql = drop_table_sql("用户", true);
161 assert_eq!(sql, "DROP TABLE IF EXISTS \"用户\"");
162
163 let sql = drop_table_sql("tâble_émoji_🦀", false);
164 assert_eq!(sql, "DROP TABLE \"tâble_émoji_🦀\"");
165 }
166
167 #[test]
168 fn test_drop_table_sql_edge_cases() {
169 let sql = drop_table_sql("", true);
171 assert_eq!(sql, "DROP TABLE IF EXISTS \"\"");
172
173 let sql = drop_table_sql("x", true);
175 assert_eq!(sql, "DROP TABLE IF EXISTS \"x\"");
176
177 let sql = drop_table_sql("123table", true);
179 assert_eq!(sql, "DROP TABLE IF EXISTS \"123table\"");
180
181 let sql = drop_table_sql("table-with-dashes", true);
183 assert_eq!(sql, "DROP TABLE IF EXISTS \"table-with-dashes\"");
184
185 let sql = drop_table_sql("table.with.dots", true);
186 assert_eq!(sql, "DROP TABLE IF EXISTS \"table.with.dots\"");
187 }
188
189 #[test]
190 fn test_drop_table_sql_sql_injection_attempt_neutralized() {
191 let malicious = "users\"; DROP TABLE secrets; --";
193 let sql = drop_table_sql(malicious, true);
194 assert_eq!(
196 sql,
197 "DROP TABLE IF EXISTS \"users\"\"; DROP TABLE secrets; --\""
198 );
199 assert!(sql.starts_with("DROP TABLE IF EXISTS \""));
201 assert!(sql.ends_with('"'));
202 let quote_count = sql.matches('"').count();
204 assert_eq!(quote_count, 4);
205 }
206}