Skip to main content

datasources_sqlite/
driver.rs

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 statement_renderer(&self) -> Option<&StatementTextRenderer> {
165        Some(&STATEMENTS)
166    }
167
168    fn information(&self) -> Option<&Information> {
169        Some(&INFORMATION)
170    }
171}
172
173datasources::export_driver!(datasources_sqlite_driver, SqliteDriver);
174
175#[cfg(test)]
176mod tests {
177    use super::SqliteDriver;
178    use datasources::Driver;
179
180    #[test]
181    fn maps_column_types() {
182        let driver = SqliteDriver;
183        assert_eq!(driver.column_type("int").unwrap(), "INTEGER");
184        assert_eq!(driver.column_type("uuid").unwrap(), "TEXT");
185        assert_eq!(driver.column_type("string").unwrap(), "TEXT");
186        assert_eq!(driver.column_type("bool").unwrap(), "BOOLEAN");
187        assert_eq!(driver.column_type("decimal").unwrap(), "NUMERIC");
188        assert_eq!(driver.column_type("float").unwrap(), "REAL");
189        assert_eq!(driver.column_type("datetime").unwrap(), "TEXT");
190        assert_eq!(driver.column_type("binary").unwrap(), "BLOB");
191        assert!(driver
192            .column_type("xml")
193            .unwrap_err()
194            .to_string()
195            .contains("Unknown sqlite type \"xml\""));
196    }
197
198    #[test]
199    fn maps_property_targets() {
200        use datasources::converters::RowTypeConverter;
201        use datasources::Value;
202        use std::collections::HashMap;
203
204        let driver = SqliteDriver;
205        let columns = HashMap::from([
206            ("Key".into(), "uuid".into()),
207            ("Active".into(), "boolean".into()),
208            ("Login".into(), "string".into()),
209            ("Created".into(), "datetime".into()),
210        ]);
211        let converter = RowTypeConverter::from(|| driver.row_type_converters(), &columns);
212        let row = HashMap::from([
213            (
214                "Key".into(),
215                Value::from("11111111-1111-1111-1111-111111111111"),
216            ),
217            ("Active".into(), Value::Int(1)),
218            ("Login".into(), Value::from("ada")),
219            ("Created".into(), Value::from("2020-01-02T03:04:05.000Z")),
220        ]);
221        let converted = converter.convert(&row).unwrap();
222        assert!(matches!(converted.get("Key"), Some(Value::Uuid(_))));
223        assert_eq!(converted.get("Active"), Some(&Value::Bool(true)));
224        assert_eq!(converted.get("Login"), Some(&Value::from("ada")));
225        assert!(matches!(converted.get("Created"), Some(Value::DateTime(_))));
226        let stored = converter.reverse_convert(&converted).unwrap();
227        assert_eq!(
228            stored.get("Key"),
229            Some(&Value::from("11111111-1111-1111-1111-111111111111"))
230        );
231        assert_eq!(stored.get("Active"), Some(&Value::Int(1)));
232        assert_eq!(stored.get("Login"), Some(&Value::from("ada")));
233        assert_eq!(
234            stored.get("Created"),
235            Some(&Value::from("2020-01-02T03:04:05.000Z"))
236        );
237    }
238}