1use crate::connection::SqliteConnection;
2use crate::converters;
3use crate::statement_renderer::SqliteStatementRenderer;
4use crate::transaction::SqliteTransaction;
5use crate::sql_generate;
6use crate::default_value_converter::DefaultValueConverter;
7use crate::type_map::TypeMap;
8use datasources::{
9 downcast_connection, Column, Command, Connection, DatasourceError, Driver, Information,
10 IsolationLevel, Mapping, Result, RoutineParameter, StatementTextRenderer, Transaction,
11 TypeConverter, Value,
12};
13use std::collections::HashMap;
14use std::sync::{Arc, LazyLock};
15
16pub struct SqliteDriver;
17
18impl SqliteDriver {
19 pub const QUOTE_LEFT: &'static str = sql_generate::QUOTE_LEFT;
20 pub const QUOTE_RIGHT: &'static str = sql_generate::QUOTE_RIGHT;
21 pub const PROCEDURE_PAYLOAD_TYPE: &'static str = sql_generate::PROCEDURE_PAYLOAD_TYPE;
22
23 pub fn apply_keywords(sql: &str, keyword: &dyn Fn(&str) -> String) -> String {
24 sql_generate::apply_keywords(sql, keyword)
25 }
26
27 pub fn statement_text_renderer() -> &'static StatementTextRenderer {
28 static DRIVER: SqliteDriver = SqliteDriver;
29 Driver::statement_renderer(&DRIVER).expect("sqlite")
30 }
31
32 pub fn types() -> &'static TypeMap {
33 &*sql_generate::TYPES
34 }
35
36 pub fn defaults() -> &'static DefaultValueConverter {
37 &sql_generate::DEFAULTS
38 }
39
40 pub fn convert_type(field: &datasources::SpecField) -> Result<String> {
41 sql_generate::convert_type(field)
42 }
43
44 pub fn convert_default(spec_type: &str, text: &str) -> Result<Option<String>> {
45 sql_generate::convert_default(spec_type, text)
46 }
47
48 pub fn quote(name: &str) -> String {
49 format!("\"{name}\"")
50 }
51
52 pub fn native_type(column: &Column) -> String {
53 column
54 .field
55 .as_ref()
56 .and_then(|field| sql_generate::TYPES.convert_field(field).ok())
57 .unwrap_or_else(|| {
58 column_sql(&column.type_name)
59 .map(str::to_string)
60 .unwrap_or_else(|| column.type_name.clone())
61 })
62 }
63
64 pub fn native_default(column: &Column) -> String {
65 let Some(default) = &column.default else {
66 return String::new();
67 };
68 let Some(field) = &column.field else {
69 return default.value.clone();
70 };
71 sql_generate::DEFAULTS
72 .convert(&field.type_name, &default.value)
73 .ok()
74 .flatten()
75 .unwrap_or_else(|| default.value.clone())
76 }
77
78 pub fn utc_now() -> String {
79 sql_generate::DEFAULTS
80 .convert("datetime", "UtcNow")
81 .ok()
82 .flatten()
83 .unwrap_or_default()
84 }
85
86 pub fn native_param(parameter: &RoutineParameter) -> String {
87 if parameter.r#type == "payload" {
88 return Self::PROCEDURE_PAYLOAD_TYPE.to_string();
89 }
90 parameter
91 .field
92 .as_ref()
93 .and_then(|field| sql_generate::TYPES.convert_field(field).ok())
94 .unwrap_or_else(|| parameter.r#type.clone())
95 }
96}
97
98static STATEMENTS: LazyLock<StatementTextRenderer> =
99 LazyLock::new(|| {
100 StatementTextRenderer::new(Some(Box::new(SqliteStatementRenderer)), None)
101 .with_keyword_apply(sql_generate::apply_keywords)
102 });
103
104static INFORMATION: LazyLock<Information> = LazyLock::new(|| {
105 Information::new(
106 [
107 "Embedded SQL database for local development and tests.",
108 "Full connection, command, transaction, and repository support.",
109 ],
110 [
111 "Overrides SelectInsertedIdStatement to SELECT last_insert_rowid() AS {column}",
112 "Overrides CreateTableStatement type mapping (int→INTEGER, bool→BOOLEAN, uuid/string/datetime→TEXT, decimal→NUMERIC, float→REAL, binary→BLOB)",
113 ],
114 [] as [&str; 0],
115 )
116});
117
118pub(crate) fn column_sql(name: &str) -> Option<&'static str> {
119 Some(match name {
120 "int" => "INTEGER",
121 "bool" => "BOOLEAN",
122 "uuid" | "string" | "datetime" => "TEXT",
123 "decimal" => "NUMERIC",
124 "float" => "REAL",
125 "binary" => "BLOB",
126 _ => return None,
127 })
128}
129
130impl Driver for SqliteDriver {
131 fn connection(&self, options: Option<&HashMap<String, String>>) -> Arc<dyn Connection> {
132 SqliteConnection::from_options(options)
133 }
134
135 fn command(&self, connection: Arc<dyn Connection>) -> Arc<dyn Command> {
136 connection.create_command()
137 }
138
139 fn transaction(
140 &self,
141 connection: Arc<dyn Connection>,
142 isolation: IsolationLevel,
143 ) -> Arc<dyn Transaction> {
144 let conn = downcast_connection::<SqliteConnection>(connection).expect("sqlite");
145 Arc::new(SqliteTransaction::new(conn.share(), isolation))
146 }
147
148 fn row_type_converters(
149 &self,
150 ) -> HashMap<String, Box<dyn TypeConverter<Value, Value> + Send + Sync>> {
151 converters::row_type_converters()
152 }
153
154 fn column_type(&self, name: &str) -> Result<String> {
155 column_sql(name)
156 .map(str::to_string)
157 .ok_or_else(|| DatasourceError::message(format!("Unknown sqlite type \"{name}\"")))
158 }
159
160 fn type_converter(&self) -> Option<&dyn Mapping<String, String>> {
161 Some(&*sql_generate::TYPES)
162 }
163
164 fn default_value_converter(&self) -> &dyn datasources::DefaultValueConverter {
165 Self::defaults()
166 }
167
168 fn statement_renderer(&self) -> Option<&StatementTextRenderer> {
169 Some(&STATEMENTS)
170 }
171
172 fn information(&self) -> Option<&Information> {
173 Some(&INFORMATION)
174 }
175}
176
177datasources::export_driver!(datasources_sqlite_driver, SqliteDriver);
178
179#[cfg(test)]
180mod tests {
181 use super::SqliteDriver;
182 use datasources::Driver;
183
184 #[test]
185 fn maps_column_types() {
186 let driver = SqliteDriver;
187 assert_eq!(driver.column_type("int").unwrap(), "INTEGER");
188 assert_eq!(driver.column_type("uuid").unwrap(), "TEXT");
189 assert_eq!(driver.column_type("string").unwrap(), "TEXT");
190 assert_eq!(driver.column_type("bool").unwrap(), "BOOLEAN");
191 assert_eq!(driver.column_type("decimal").unwrap(), "NUMERIC");
192 assert_eq!(driver.column_type("float").unwrap(), "REAL");
193 assert_eq!(driver.column_type("datetime").unwrap(), "TEXT");
194 assert_eq!(driver.column_type("binary").unwrap(), "BLOB");
195 assert!(driver
196 .column_type("xml")
197 .unwrap_err()
198 .to_string()
199 .contains("Unknown sqlite type \"xml\""));
200 }
201
202 #[test]
203 fn maps_property_targets() {
204 use datasources::converters::RowTypeConverter;
205 use datasources::Value;
206 use std::collections::HashMap;
207
208 let driver = SqliteDriver;
209 let columns = HashMap::from([
210 ("Key".into(), "uuid".into()),
211 ("Active".into(), "boolean".into()),
212 ("Login".into(), "string".into()),
213 ("Created".into(), "datetime".into()),
214 ]);
215 let converter = RowTypeConverter::from(|| driver.row_type_converters(), &columns);
216 let row = HashMap::from([
217 (
218 "Key".into(),
219 Value::from("11111111-1111-1111-1111-111111111111"),
220 ),
221 ("Active".into(), Value::Int(1)),
222 ("Login".into(), Value::from("ada")),
223 ("Created".into(), Value::from("2020-01-02T03:04:05.000Z")),
224 ]);
225 let converted = converter.convert(&row).unwrap();
226 assert_eq!(
227 converted.get("Key"),
228 Some(&Value::from("11111111-1111-1111-1111-111111111111"))
229 );
230 assert_eq!(converted.get("Active"), Some(&Value::Bool(true)));
231 assert_eq!(converted.get("Login"), Some(&Value::from("ada")));
232 assert!(matches!(converted.get("Created"), Some(Value::DateTime(_))));
233 let stored = converter.reverse_convert(&converted).unwrap();
234 assert_eq!(
235 stored.get("Key"),
236 Some(&Value::from("11111111-1111-1111-1111-111111111111"))
237 );
238 assert_eq!(stored.get("Active"), Some(&Value::Int(1)));
239 assert_eq!(stored.get("Login"), Some(&Value::from("ada")));
240 assert_eq!(
241 stored.get("Created"),
242 Some(&Value::from("2020-01-02T03:04:05.000Z"))
243 );
244 }
245}