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