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 }
102 }
103
104 fn drop_index(&self, op: &DropIndexOp) -> String {
105 let mut sql = String::from("DROP INDEX ");
106 if op.if_exists {
107 sql.push_str("IF EXISTS ");
108 }
109 sql.push_str(&self.quote_identifier(&op.name));
111 sql
112 }
113
114 fn drop_foreign_key(&self, op: &super::super::operation::DropForeignKeyOp) -> String {
115 format!(
117 "-- SQLite does not support DROP CONSTRAINT; \
118 table recreation required to remove foreign key {} from {}",
119 op.name, op.table
120 )
121 }
122}
123
124impl RustTypeMapping for SqliteDialect {
125 fn map_type(&self, rust_type: &str) -> DataType {
126 match rust_type {
127 "bool" => DataType::Integer,
128 "i8" | "i16" | "u8" | "u16" | "i32" | "u32" => DataType::Integer,
129 "i64" | "u64" | "i128" | "u128" | "isize" | "usize" => DataType::Bigint,
130 "f32" => DataType::Real,
131 "f64" => DataType::Double,
132 "String" => DataType::Text,
133 "Vec<u8>" => DataType::Blob,
134 s if s.contains("DateTime") => DataType::Text,
135 s if s.contains("NaiveDate") => DataType::Text,
136 _ => DataType::Text, }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::migrations::column_builder::{bigint, boolean, timestamp, varchar};
145 use crate::migrations::operation::{DropTableOp, Operation};
146 use crate::migrations::table_builder::CreateTableBuilder;
147
148 #[test]
149 fn test_sqlite_data_types() {
150 let dialect = SqliteDialect::new();
151 assert_eq!(dialect.map_data_type(&DataType::Integer), "INTEGER");
152 assert_eq!(dialect.map_data_type(&DataType::Bigint), "INTEGER");
153 assert_eq!(dialect.map_data_type(&DataType::Text), "TEXT");
154 assert_eq!(dialect.map_data_type(&DataType::Varchar(Some(255))), "TEXT");
155 assert_eq!(dialect.map_data_type(&DataType::Blob), "BLOB");
156 assert_eq!(dialect.map_data_type(&DataType::Boolean), "INTEGER");
157 assert_eq!(dialect.map_data_type(&DataType::Timestamp), "TEXT");
158 }
159
160 #[test]
161 fn test_create_table_sql() {
162 let dialect = SqliteDialect::new();
163 let op = CreateTableBuilder::new()
164 .name("users")
165 .column(bigint("id").primary_key().autoincrement().build())
166 .column(varchar("username", 255).not_null().unique().build())
167 .column(varchar("email", 255).build())
168 .column(
169 timestamp("created_at")
170 .not_null()
171 .default_expr("CURRENT_TIMESTAMP")
172 .build(),
173 )
174 .build();
175
176 let sql = dialect.create_table(&op);
177 assert!(sql.contains("CREATE TABLE \"users\""));
178 assert!(sql.contains("\"id\" INTEGER PRIMARY KEY AUTOINCREMENT"));
179 assert!(sql.contains("\"username\" TEXT NOT NULL UNIQUE"));
180 assert!(sql.contains("DEFAULT CURRENT_TIMESTAMP"));
181 }
182
183 #[test]
184 fn test_drop_table_sql() {
185 let dialect = SqliteDialect::new();
186
187 let op = DropTableOp {
188 name: "users".to_string(),
189 if_exists: false,
190 cascade: false,
191 };
192 assert_eq!(dialect.drop_table(&op), "DROP TABLE \"users\"");
193
194 let op = DropTableOp {
195 name: "users".to_string(),
196 if_exists: true,
197 cascade: false,
198 };
199 assert_eq!(dialect.drop_table(&op), "DROP TABLE IF EXISTS \"users\"");
200 }
201
202 #[test]
203 fn test_add_column_sql() {
204 let dialect = SqliteDialect::new();
205 let op = Operation::add_column(
206 "users",
207 boolean("active").not_null().default_bool(true).build(),
208 );
209
210 if let Operation::AddColumn(add_op) = op {
211 let sql = dialect.add_column(&add_op);
212 assert!(sql.contains("ALTER TABLE \"users\" ADD COLUMN"));
213 assert!(sql.contains("\"active\" INTEGER NOT NULL DEFAULT TRUE"));
214 }
215 }
216
217 #[test]
218 fn test_rename_table_sql() {
219 let dialect = SqliteDialect::new();
220 let op = RenameTableOp {
221 old_name: "old_users".to_string(),
222 new_name: "users".to_string(),
223 };
224 assert_eq!(
225 dialect.rename_table(&op),
226 "ALTER TABLE \"old_users\" RENAME TO \"users\""
227 );
228 }
229
230 #[test]
231 fn test_rename_column_sql() {
232 let dialect = SqliteDialect::new();
233 let op = RenameColumnOp {
234 table: "users".to_string(),
235 old_name: "name".to_string(),
236 new_name: "full_name".to_string(),
237 };
238 assert_eq!(
239 dialect.rename_column(&op),
240 "ALTER TABLE \"users\" RENAME COLUMN \"name\" TO \"full_name\""
241 );
242 }
243}