sql_composer/driver.rs
1//! Trait interface for database drivers and helpers for bind value resolution.
2//!
3//! Driver crates implement [`ComposerConnection`] (sync) or
4//! [`ComposerConnectionAsync`] (async) for their connection types.
5//! This module contains no database dependencies — only the interface.
6
7use std::collections::BTreeMap;
8
9use crate::composer::{ComposedSql, Composer};
10use crate::error::{Error, Result};
11use crate::types::Template;
12
13/// Trait for synchronous database drivers that can compose and prepare SQL.
14///
15/// Each driver crate implements this for its connection type, providing the
16/// bridge between sql-composer's template system and the database's API.
17///
18/// # Example
19///
20/// ```ignore
21/// let (sql, values) = conn.compose(&composer, &template, bind_values!("id" => [1]))?;
22/// let mut stmt = conn.prepare(&sql)?;
23/// let rows = stmt.query_map(params_from_iter(values.iter().map(|v| v.as_ref())), |row| { ... })?;
24/// ```
25pub trait ComposerConnection {
26 /// The database-specific value type for bind parameters.
27 ///
28 /// e.g. `Box<dyn rusqlite::types::ToSql>` or `Box<dyn duckdb::ToSql>`
29 type Value;
30
31 /// The composed SQL string (callers use this to prepare statements).
32 type Statement;
33
34 /// The error type for this driver.
35 type Error: From<Error>;
36
37 /// Compose a template with bind values, returning prepared SQL and ordered values.
38 ///
39 /// Takes the composer, a parsed template, and a map of named bind values.
40 /// Resolves bind parameter order and returns the SQL string with ordered
41 /// values ready for execution.
42 #[allow(clippy::type_complexity)]
43 fn compose(
44 &self,
45 composer: &Composer,
46 template: &Template,
47 values: BTreeMap<String, Vec<Self::Value>>,
48 ) -> std::result::Result<(Self::Statement, Vec<Self::Value>), Self::Error>;
49}
50
51/// Async version of [`ComposerConnection`] for async database drivers
52/// (e.g. tokio-postgres, mysql_async).
53pub trait ComposerConnectionAsync {
54 /// The database-specific value type for bind parameters.
55 type Value;
56
57 /// The composed SQL string.
58 type Statement;
59
60 /// The error type for this driver.
61 type Error: From<Error>;
62
63 /// Compose a template with bind values asynchronously.
64 #[allow(clippy::type_complexity)]
65 fn compose(
66 &self,
67 composer: &Composer,
68 template: &Template,
69 values: BTreeMap<String, Vec<Self::Value>>,
70 ) -> impl std::future::Future<
71 Output = std::result::Result<(Self::Statement, Vec<Self::Value>), Self::Error>,
72 > + Send;
73}
74
75/// Given a [`ComposedSql`] with ordered bind param names and a map of named values,
76/// produce the ordered value vector matching placeholder order.
77///
78/// For single-value bindings, each name maps to one value.
79/// For multi-value bindings (e.g. IN clauses), the composed SQL already has
80/// the correct number of placeholders per binding name, so we flatten all
81/// values for each occurrence.
82pub fn resolve_values<V>(
83 composed: &ComposedSql,
84 values: &mut BTreeMap<String, Vec<V>>,
85) -> Result<Vec<V>> {
86 let mut result = Vec::with_capacity(composed.bind_params.len());
87
88 for name in &composed.bind_params {
89 let vs = values
90 .get_mut(name)
91 .ok_or_else(|| Error::MissingBinding { name: name.clone() })?;
92
93 if vs.is_empty() {
94 return Err(Error::MissingBinding { name: name.clone() });
95 }
96
97 // Take the first value — each placeholder in bind_params corresponds
98 // to exactly one value. Multi-value bindings have been expanded by
99 // compose_with_values() so each placeholder gets its own entry.
100 result.push(vs.remove(0));
101 }
102
103 Ok(result)
104}
105
106/// Build a `BTreeMap<String, Vec<V>>` of named bind values.
107///
108/// # Example
109///
110/// ```
111/// use sql_composer::bind_values;
112///
113/// let values: std::collections::BTreeMap<String, Vec<i32>> = bind_values!(
114/// "user_id" => [42],
115/// "status" => [1, 2, 3],
116/// );
117/// assert_eq!(values["user_id"], vec![42]);
118/// assert_eq!(values["status"], vec![1, 2, 3]);
119/// ```
120#[macro_export]
121macro_rules! bind_values {
122 ($($key:literal => [$($value:expr),+ $(,)?]),+ $(,)?) => {{
123 let mut map = std::collections::BTreeMap::new();
124 $(
125 map.insert($key.to_string(), vec![$($value),+]);
126 )+
127 map
128 }};
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_resolve_values_basic() {
137 let composed = ComposedSql {
138 sql: "SELECT * FROM t WHERE a = $1 AND b = $2".into(),
139 bind_params: vec!["a".into(), "b".into()],
140 };
141 let mut values: BTreeMap<String, Vec<&str>> = BTreeMap::new();
142 values.insert("a".into(), vec!["hello"]);
143 values.insert("b".into(), vec!["world"]);
144
145 let result = resolve_values(&composed, &mut values).unwrap();
146 assert_eq!(result, vec!["hello", "world"]);
147 }
148
149 #[test]
150 fn test_resolve_values_missing_binding() {
151 let composed = ComposedSql {
152 sql: "SELECT * FROM t WHERE a = $1".into(),
153 bind_params: vec!["missing".into()],
154 };
155 let mut values: BTreeMap<String, Vec<&str>> = BTreeMap::new();
156
157 let err = resolve_values(&composed, &mut values).unwrap_err();
158 assert!(matches!(err, Error::MissingBinding { ref name } if name == "missing"));
159 }
160
161 #[test]
162 fn test_resolve_values_multi_value_expanded() {
163 // After compose_with_values, a multi-value binding like ids=[1,2,3]
164 // produces bind_params = ["ids", "ids", "ids"] with placeholders $1, $2, $3.
165 let composed = ComposedSql {
166 sql: "SELECT * FROM t WHERE id IN ($1, $2, $3)".into(),
167 bind_params: vec!["ids".into(), "ids".into(), "ids".into()],
168 };
169 let mut values: BTreeMap<String, Vec<i32>> = BTreeMap::new();
170 values.insert("ids".into(), vec![10, 20, 30]);
171
172 let result = resolve_values(&composed, &mut values).unwrap();
173 assert_eq!(result, vec![10, 20, 30]);
174 }
175
176 #[test]
177 fn test_bind_values_macro() {
178 let values: BTreeMap<String, Vec<i32>> = bind_values!(
179 "a" => [1, 2],
180 "b" => [3],
181 );
182 assert_eq!(values["a"], vec![1, 2]);
183 assert_eq!(values["b"], vec![3]);
184 }
185}