oxide_sql_core/migrations/dialect/
sqlite.rs1use super::MigrationDialect;
4use crate::ast::DataType;
5use crate::migrations::operation::{
6 AlterColumnChange, AlterColumnOp, DropIndexOp, RenameColumnOp, RenameTableOp,
7};
8use crate::schema::RustTypeMapping;
9
10#[derive(Debug, Clone, Copy, Default)]
12pub struct SqliteDialect;
13
14impl SqliteDialect {
15 #[must_use]
17 pub const fn new() -> Self {
18 Self
19 }
20}
21
22impl MigrationDialect for SqliteDialect {
23 fn name(&self) -> &'static str {
24 "sqlite"
25 }
26
27 fn map_data_type(&self, dt: &DataType) -> String {
28 match dt {
30 DataType::Smallint | DataType::Integer | DataType::Bigint => "INTEGER".to_string(),
31 DataType::Real | DataType::Double => "REAL".to_string(),
32 DataType::Decimal { .. } | DataType::Numeric { .. } => "REAL".to_string(),
33 DataType::Char(_) | DataType::Varchar(_) | DataType::Text => "TEXT".to_string(),
34 DataType::Blob | DataType::Binary(_) | DataType::Varbinary(_) => "BLOB".to_string(),
35 DataType::Date | DataType::Time | DataType::Timestamp | DataType::Datetime => {
36 "TEXT".to_string()
37 }
38 DataType::Boolean => "INTEGER".to_string(), DataType::Custom(name) => name.clone(),
40 }
41 }
42
43 fn autoincrement_keyword(&self) -> String {
44 " AUTOINCREMENT".to_string()
45 }
46
47 fn rename_table(&self, op: &RenameTableOp) -> String {
48 format!(
49 "ALTER TABLE {} RENAME TO {}",
50 self.quote_identifier(&op.old_name),
51 self.quote_identifier(&op.new_name)
52 )
53 }
54
55 fn rename_column(&self, op: &RenameColumnOp) -> String {
56 format!(
58 "ALTER TABLE {} RENAME COLUMN {} TO {}",
59 self.quote_identifier(&op.table),
60 self.quote_identifier(&op.old_name),
61 self.quote_identifier(&op.new_name)
62 )
63 }
64
65 fn alter_column(&self, op: &AlterColumnOp) -> String {
66 match &op.change {
70 AlterColumnChange::SetDataType(_) => {
71 format!(
72 "-- SQLite does not support ALTER COLUMN TYPE directly for {}.{}; \
73 table recreation required",
74 op.table, op.column
75 )
76 }
77 AlterColumnChange::SetNullable(_) => {
78 format!(
79 "-- SQLite does not support ALTER COLUMN NULL/NOT NULL directly for {}.{}; \
80 table recreation required",
81 op.table, op.column
82 )
83 }
84 AlterColumnChange::SetDefault(default) => {
85 format!(
87 "-- SQLite does not support ALTER COLUMN SET DEFAULT directly for {}.{}; \
88 would set to: {}",
89 op.table,
90 op.column,
91 self.render_default(default)
92 )
93 }
94 AlterColumnChange::DropDefault => {
95 format!(
96 "-- SQLite does not support ALTER COLUMN DROP DEFAULT directly for {}.{}; \
97 table recreation required",
98 op.table, op.column
99 )
100 }
101 AlterColumnChange::SetUnique(_) => {
102 format!(
103 "-- SQLite does not support ALTER COLUMN UNIQUE directly for {}.{}; \
104 table recreation required",
105 op.table, op.column
106 )
107 }
108 AlterColumnChange::SetAutoincrement(_) => {
109 format!(
110 "-- SQLite does not support ALTER autoincrement for {}.{}; \
111 table recreation required",
112 op.table, op.column
113 )
114 }
115 }
116 }
117
118 fn drop_index(&self, op: &DropIndexOp) -> String {
119 let mut sql = String::from("DROP INDEX ");
120 if op.if_exists {
121 sql.push_str("IF EXISTS ");
122 }
123 sql.push_str(&self.quote_identifier(&op.name));
125 sql
126 }
127
128 fn drop_foreign_key(&self, op: &super::super::operation::DropForeignKeyOp) -> String {
129 format!(
131 "-- SQLite does not support DROP CONSTRAINT; \
132 table recreation required to remove foreign key {} from {}",
133 op.name, op.table
134 )
135 }
136}
137
138impl RustTypeMapping for SqliteDialect {
139 fn map_type(&self, rust_type: &str) -> DataType {
140 match rust_type {
141 "bool" => DataType::Integer,
142 "i8" | "i16" | "u8" | "u16" | "i32" | "u32" => DataType::Integer,
143 "i64" | "u64" | "i128" | "u128" | "isize" | "usize" => DataType::Bigint,
144 "f32" => DataType::Real,
145 "f64" => DataType::Double,
146 "String" => DataType::Text,
147 "Vec<u8>" => DataType::Blob,
148 s if s.contains("DateTime") => DataType::Text,
149 s if s.contains("NaiveDate") => DataType::Text,
150 _ => DataType::Text, }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::migrations::column_builder::{bigint, boolean, timestamp, varchar};
159 use crate::migrations::operation::{DropTableOp, Operation};
160 use crate::migrations::table_builder::CreateTableBuilder;
161
162 #[test]
163 fn test_sqlite_data_types() {
164 let dialect = SqliteDialect::new();
165 assert_eq!(dialect.map_data_type(&DataType::Integer), "INTEGER");
166 assert_eq!(dialect.map_data_type(&DataType::Bigint), "INTEGER");
167 assert_eq!(dialect.map_data_type(&DataType::Text), "TEXT");
168 assert_eq!(dialect.map_data_type(&DataType::Varchar(Some(255))), "TEXT");
169 assert_eq!(dialect.map_data_type(&DataType::Blob), "BLOB");
170 assert_eq!(dialect.map_data_type(&DataType::Boolean), "INTEGER");
171 assert_eq!(dialect.map_data_type(&DataType::Timestamp), "TEXT");
172 }
173
174 #[test]
175 fn test_create_table_sql() {
176 let dialect = SqliteDialect::new();
177 let op = CreateTableBuilder::new()
178 .name("users")
179 .column(bigint("id").primary_key().autoincrement().build())
180 .column(varchar("username", 255).not_null().unique().build())
181 .column(varchar("email", 255).build())
182 .column(
183 timestamp("created_at")
184 .not_null()
185 .default_expr("CURRENT_TIMESTAMP")
186 .build(),
187 )
188 .build();
189
190 let sql = dialect.create_table(&op);
191 assert!(sql.contains("CREATE TABLE \"users\""));
192 assert!(sql.contains("\"id\" INTEGER PRIMARY KEY AUTOINCREMENT"));
193 assert!(sql.contains("\"username\" TEXT NOT NULL UNIQUE"));
194 assert!(sql.contains("DEFAULT CURRENT_TIMESTAMP"));
195 }
196
197 #[test]
198 fn test_drop_table_sql() {
199 let dialect = SqliteDialect::new();
200
201 let op = DropTableOp {
202 name: "users".to_string(),
203 if_exists: false,
204 cascade: false,
205 };
206 assert_eq!(dialect.drop_table(&op), "DROP TABLE \"users\"");
207
208 let op = DropTableOp {
209 name: "users".to_string(),
210 if_exists: true,
211 cascade: false,
212 };
213 assert_eq!(dialect.drop_table(&op), "DROP TABLE IF EXISTS \"users\"");
214 }
215
216 #[test]
217 fn test_add_column_sql() {
218 let dialect = SqliteDialect::new();
219 let op = Operation::add_column(
220 "users",
221 boolean("active").not_null().default_bool(true).build(),
222 );
223
224 if let Operation::AddColumn(add_op) = op {
225 let sql = dialect.add_column(&add_op);
226 assert!(sql.contains("ALTER TABLE \"users\" ADD COLUMN"));
227 assert!(sql.contains("\"active\" INTEGER NOT NULL DEFAULT TRUE"));
228 }
229 }
230
231 #[test]
232 fn test_rename_table_sql() {
233 let dialect = SqliteDialect::new();
234 let op = RenameTableOp {
235 old_name: "old_users".to_string(),
236 new_name: "users".to_string(),
237 };
238 assert_eq!(
239 dialect.rename_table(&op),
240 "ALTER TABLE \"old_users\" RENAME TO \"users\""
241 );
242 }
243
244 #[test]
245 fn test_rename_column_sql() {
246 let dialect = SqliteDialect::new();
247 let op = RenameColumnOp {
248 table: "users".to_string(),
249 old_name: "name".to_string(),
250 new_name: "full_name".to_string(),
251 };
252 assert_eq!(
253 dialect.rename_column(&op),
254 "ALTER TABLE \"users\" RENAME COLUMN \"name\" TO \"full_name\""
255 );
256 }
257}