Skip to main content

sql_composer_rusqlite/
lib.rs

1//! rusqlite driver for sql-composer.
2//!
3//! Provides [`SqliteConnection`], a thin wrapper around [`rusqlite::Connection`]
4//! that implements [`ComposerConnection`] for composing SQL templates with bind
5//! values against SQLite databases.
6//!
7//! # Example
8//!
9//! ```no_run
10//! use sql_composer::composer::Composer;
11//! use sql_composer::driver::ComposerConnection;
12//! use sql_composer::parser::parse_template;
13//! use sql_composer::types::{Dialect, TemplateSource};
14//! use sql_composer::bind_values;
15//! use sql_composer_rusqlite::SqliteConnection;
16//!
17//! let conn = SqliteConnection::open_in_memory().unwrap();
18//! conn.execute("CREATE TABLE users (id INTEGER, name TEXT)", []).unwrap();
19//!
20//! let input = "SELECT * FROM users WHERE id = :bind(user_id)";
21//! let template = parse_template(input, TemplateSource::Literal("example".into())).unwrap();
22//! let composer = Composer::new(Dialect::Sqlite);
23//!
24//! let values = bind_values!("user_id" => [Box::new(1i32) as Box<dyn rusqlite::types::ToSql>]);
25//! let (sql, params) = conn.compose(&composer, &template, values).unwrap();
26//!
27//! let refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|v| v.as_ref()).collect();
28//! let mut stmt = conn.prepare(&sql).unwrap();
29//! let _rows = stmt.query(refs.as_slice()).unwrap();
30//! ```
31
32pub use rusqlite;
33
34use std::collections::BTreeMap;
35use std::ops::{Deref, DerefMut};
36
37use sql_composer::composer::Composer;
38use sql_composer::driver::{self, ComposerConnection};
39use sql_composer::types::Template;
40
41/// Error type for sql-composer-rusqlite operations.
42#[derive(Debug, thiserror::Error)]
43pub enum Error {
44    /// An error from the sql-composer core.
45    #[error(transparent)]
46    Composer(#[from] sql_composer::Error),
47
48    /// An error from rusqlite.
49    #[error(transparent)]
50    Rusqlite(#[from] rusqlite::Error),
51}
52
53/// A wrapper around [`rusqlite::Connection`] that implements [`ComposerConnection`].
54///
55/// Dereferences to the inner `rusqlite::Connection`, so all native methods
56/// are available directly.
57pub struct SqliteConnection(pub rusqlite::Connection);
58
59impl SqliteConnection {
60    /// Open an in-memory SQLite database.
61    pub fn open_in_memory() -> Result<Self, rusqlite::Error> {
62        rusqlite::Connection::open_in_memory().map(Self)
63    }
64
65    /// Open a SQLite database at the given path.
66    pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self, rusqlite::Error> {
67        rusqlite::Connection::open(path).map(Self)
68    }
69
70    /// Wrap an existing `rusqlite::Connection`.
71    pub fn from_connection(conn: rusqlite::Connection) -> Self {
72        Self(conn)
73    }
74}
75
76impl Deref for SqliteConnection {
77    type Target = rusqlite::Connection;
78
79    fn deref(&self) -> &Self::Target {
80        &self.0
81    }
82}
83
84impl DerefMut for SqliteConnection {
85    fn deref_mut(&mut self) -> &mut Self::Target {
86        &mut self.0
87    }
88}
89
90impl ComposerConnection for SqliteConnection {
91    type Value = Box<dyn rusqlite::types::ToSql>;
92    type Statement = String;
93    type Error = Error;
94
95    fn compose(
96        &self,
97        composer: &Composer,
98        template: &Template,
99        mut values: BTreeMap<String, Vec<Self::Value>>,
100    ) -> Result<(String, Vec<Self::Value>), Error> {
101        let composed = composer.compose_with_values(template, &values)?;
102        let ordered = driver::resolve_values(&composed, &mut values)?;
103        Ok((composed.sql, ordered))
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use sql_composer::bind_values;
111    use sql_composer::parser::parse_template;
112    use sql_composer::types::{Dialect, TemplateSource};
113
114    fn boxed(v: impl rusqlite::types::ToSql + 'static) -> Box<dyn rusqlite::types::ToSql> {
115        Box::new(v)
116    }
117
118    #[test]
119    fn test_compose_and_query() {
120        let conn = SqliteConnection::open_in_memory().unwrap();
121        conn.execute(
122            "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)",
123            [],
124        )
125        .unwrap();
126        conn.execute("INSERT INTO users (id, name) VALUES (1, 'Alice')", [])
127            .unwrap();
128        conn.execute("INSERT INTO users (id, name) VALUES (2, 'Bob')", [])
129            .unwrap();
130
131        let input = "SELECT id, name FROM users WHERE id = :bind(user_id)";
132        let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
133        let composer = Composer::new(Dialect::Sqlite);
134
135        let values = bind_values!("user_id" => [boxed(1i32)]);
136        let (sql, params) = conn.compose(&composer, &template, values).unwrap();
137
138        assert_eq!(sql, "SELECT id, name FROM users WHERE id = ?1");
139
140        let refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|v| v.as_ref()).collect();
141        let mut stmt = conn.prepare(&sql).unwrap();
142        let rows: Vec<(i32, String)> = stmt
143            .query_map(refs.as_slice(), |row| Ok((row.get(0)?, row.get(1)?)))
144            .unwrap()
145            .collect::<Result<Vec<_>, _>>()
146            .unwrap();
147
148        assert_eq!(rows, vec![(1, "Alice".to_string())]);
149    }
150
151    #[test]
152    fn test_compose_multi_value_in_clause() {
153        let conn = SqliteConnection::open_in_memory().unwrap();
154        conn.execute(
155            "CREATE TABLE items (id INTEGER PRIMARY KEY, label TEXT NOT NULL)",
156            [],
157        )
158        .unwrap();
159        conn.execute("INSERT INTO items (id, label) VALUES (1, 'a')", [])
160            .unwrap();
161        conn.execute("INSERT INTO items (id, label) VALUES (2, 'b')", [])
162            .unwrap();
163        conn.execute("INSERT INTO items (id, label) VALUES (3, 'c')", [])
164            .unwrap();
165
166        let input = "SELECT id, label FROM items WHERE id IN (:bind(ids)) ORDER BY id";
167        let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
168        let composer = Composer::new(Dialect::Sqlite);
169
170        let values = bind_values!("ids" => [boxed(1i32), boxed(3i32)]);
171        let (sql, params) = conn.compose(&composer, &template, values).unwrap();
172
173        assert_eq!(
174            sql,
175            "SELECT id, label FROM items WHERE id IN (?1, ?2) ORDER BY id"
176        );
177
178        let refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|v| v.as_ref()).collect();
179        let mut stmt = conn.prepare(&sql).unwrap();
180        let rows: Vec<(i32, String)> = stmt
181            .query_map(refs.as_slice(), |row| Ok((row.get(0)?, row.get(1)?)))
182            .unwrap()
183            .collect::<Result<Vec<_>, _>>()
184            .unwrap();
185
186        assert_eq!(rows, vec![(1, "a".to_string()), (3, "c".to_string())]);
187    }
188
189    #[test]
190    fn test_compose_returns_correct_sql() {
191        let conn = SqliteConnection::open_in_memory().unwrap();
192
193        let input = "SELECT :bind(a) AS col_1, :bind(b) AS col_2";
194        let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
195        let composer = Composer::new(Dialect::Sqlite);
196
197        let values = bind_values!(
198            "a" => [boxed("hello")],
199            "b" => [boxed("world")]
200        );
201        let (sql, params) = conn.compose(&composer, &template, values).unwrap();
202
203        assert_eq!(sql, "SELECT ?1 AS col_1, ?2 AS col_2");
204        assert_eq!(params.len(), 2);
205    }
206}