sql_composer_rusqlite/
lib.rs1pub 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#[derive(Debug, thiserror::Error)]
43pub enum Error {
44 #[error(transparent)]
46 Composer(#[from] sql_composer::Error),
47
48 #[error(transparent)]
50 Rusqlite(#[from] rusqlite::Error),
51}
52
53pub struct SqliteConnection(pub rusqlite::Connection);
58
59impl SqliteConnection {
60 pub fn open_in_memory() -> Result<Self, rusqlite::Error> {
62 rusqlite::Connection::open_in_memory().map(Self)
63 }
64
65 pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self, rusqlite::Error> {
67 rusqlite::Connection::open(path).map(Self)
68 }
69
70 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}