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 datasources::{
7    downcast_connection, Command, Connection, DatasourceError, DefaultValueConverter,
8    DeterministicTypeConverter, Driver, Information, IsolationLevel, Mapping, Result,
9    StatementTextRenderer, Transaction, TypeConverter, Value,
10};
11use std::collections::HashMap;
12use std::sync::{Arc, LazyLock};
13
14pub struct SqliteDriver;
15
16impl SqliteDriver {
17    pub const QUOTE_LEFT: &'static str = sql_generate::QUOTE_LEFT;
18    pub const QUOTE_RIGHT: &'static str = sql_generate::QUOTE_RIGHT;
19
20    pub fn statement_text_renderer() -> &'static StatementTextRenderer {
21        static DRIVER: SqliteDriver = SqliteDriver;
22        Driver::statement_renderer(&DRIVER).expect("sqlite")
23    }
24
25    pub fn types() -> &'static DeterministicTypeConverter {
26        &*sql_generate::TYPES
27    }
28
29    pub fn defaults() -> &'static DefaultValueConverter {
30        &sql_generate::DEFAULTS
31    }
32}
33
34static STATEMENTS: LazyLock<StatementTextRenderer> =
35    LazyLock::new(|| StatementTextRenderer::new(Some(Box::new(SqliteStatementRenderer)), None));
36
37static INFORMATION: LazyLock<Information> = LazyLock::new(|| {
38    Information::new(
39        [
40            "Embedded SQL database for local development and tests.",
41            "Full connection, command, transaction, and repository support.",
42        ],
43        [
44            "Overrides SelectInsertedIdStatement to SELECT last_insert_rowid() AS {column}",
45            "Overrides CreateTableStatement type mapping (int→INTEGER, bool→BOOLEAN, uuid/string/datetime→TEXT, decimal→NUMERIC, float→REAL, binary→BLOB)",
46        ],
47        [] as [&str; 0],
48    )
49});
50
51pub(crate) fn column_sql(name: &str) -> Option<&'static str> {
52    Some(match name {
53        "int" => "INTEGER",
54        "bool" => "BOOLEAN",
55        "uuid" | "string" | "datetime" => "TEXT",
56        "decimal" => "NUMERIC",
57        "float" => "REAL",
58        "binary" => "BLOB",
59        _ => return None,
60    })
61}
62
63impl Driver for SqliteDriver {
64    fn connection(&self, options: Option<&HashMap<String, String>>) -> Arc<dyn Connection> {
65        SqliteConnection::from_options(options)
66    }
67
68    fn command(&self, connection: Arc<dyn Connection>) -> Arc<dyn Command> {
69        connection.create_command()
70    }
71
72    fn transaction(
73        &self,
74        connection: Arc<dyn Connection>,
75        isolation: IsolationLevel,
76    ) -> Arc<dyn Transaction> {
77        let conn = downcast_connection::<SqliteConnection>(connection).expect("sqlite");
78        Arc::new(SqliteTransaction::new(conn.share(), isolation))
79    }
80
81    fn row_type_converters(
82        &self,
83    ) -> HashMap<String, Box<dyn TypeConverter<Value, Value> + Send + Sync>> {
84        converters::row_type_converters()
85    }
86
87    fn column_type(&self, name: &str) -> Result<String> {
88        column_sql(name)
89            .map(str::to_string)
90            .ok_or_else(|| DatasourceError::message(format!("Unknown sqlite type \"{name}\"")))
91    }
92
93    fn type_converter(&self) -> Option<&dyn Mapping<String, String>> {
94        Some(&*sql_generate::TYPES)
95    }
96
97    fn default_value_converter(&self) -> Option<&DefaultValueConverter> {
98        Some(&sql_generate::DEFAULTS)
99    }
100
101    fn statement_renderer(&self) -> Option<&StatementTextRenderer> {
102        Some(&STATEMENTS)
103    }
104
105    fn information(&self) -> Option<&Information> {
106        Some(&INFORMATION)
107    }
108}
109
110datasources::export_driver!(datasources_sqlite_driver, SqliteDriver);
111
112#[cfg(test)]
113mod tests {
114    use super::SqliteDriver;
115    use datasources::Driver;
116
117    #[test]
118    fn maps_column_types() {
119        let driver = SqliteDriver;
120        assert_eq!(driver.column_type("int").unwrap(), "INTEGER");
121        assert_eq!(driver.column_type("uuid").unwrap(), "TEXT");
122        assert_eq!(driver.column_type("string").unwrap(), "TEXT");
123        assert_eq!(driver.column_type("bool").unwrap(), "BOOLEAN");
124        assert_eq!(driver.column_type("decimal").unwrap(), "NUMERIC");
125        assert_eq!(driver.column_type("float").unwrap(), "REAL");
126        assert_eq!(driver.column_type("datetime").unwrap(), "TEXT");
127        assert_eq!(driver.column_type("binary").unwrap(), "BLOB");
128        assert!(driver
129            .column_type("xml")
130            .unwrap_err()
131            .to_string()
132            .contains("Unknown sqlite type \"xml\""));
133    }
134
135    #[test]
136    fn maps_property_targets() {
137        use datasources::converters::RowTypeConverter;
138        use datasources::Value;
139        use std::collections::HashMap;
140
141        let driver = SqliteDriver;
142        let columns = HashMap::from([
143            ("Key".into(), "uuid".into()),
144            ("Active".into(), "boolean".into()),
145            ("Login".into(), "string".into()),
146            ("Created".into(), "datetime".into()),
147        ]);
148        let converter = RowTypeConverter::from(|| driver.row_type_converters(), &columns);
149        let row = HashMap::from([
150            (
151                "Key".into(),
152                Value::from("11111111-1111-1111-1111-111111111111"),
153            ),
154            ("Active".into(), Value::Int(1)),
155            ("Login".into(), Value::from("ada")),
156            ("Created".into(), Value::from("2020-01-02T03:04:05.000Z")),
157        ]);
158        let converted = converter.convert(&row).unwrap();
159        assert!(matches!(converted.get("Key"), Some(Value::Uuid(_))));
160        assert_eq!(converted.get("Active"), Some(&Value::Bool(true)));
161        assert_eq!(converted.get("Login"), Some(&Value::from("ada")));
162        assert!(matches!(converted.get("Created"), Some(Value::DateTime(_))));
163        let stored = converter.reverse_convert(&converted).unwrap();
164        assert_eq!(
165            stored.get("Key"),
166            Some(&Value::from("11111111-1111-1111-1111-111111111111"))
167        );
168        assert_eq!(stored.get("Active"), Some(&Value::Int(1)));
169        assert_eq!(stored.get("Login"), Some(&Value::from("ada")));
170        assert_eq!(
171            stored.get("Created"),
172            Some(&Value::from("2020-01-02T03:04:05.000Z"))
173        );
174    }
175}