use std::collections::BTreeMap;
use crate::composer::{ComposedSql, Composer};
use crate::error::{Error, Result};
use crate::types::Template;
pub trait ComposerConnection {
type Value;
type Statement;
type Error: From<Error>;
#[allow(clippy::type_complexity)]
fn compose(
&self,
composer: &Composer,
template: &Template,
values: BTreeMap<String, Vec<Self::Value>>,
) -> std::result::Result<(Self::Statement, Vec<Self::Value>), Self::Error>;
}
pub trait ComposerConnectionAsync {
type Value;
type Statement;
type Error: From<Error>;
#[allow(clippy::type_complexity)]
fn compose(
&self,
composer: &Composer,
template: &Template,
values: BTreeMap<String, Vec<Self::Value>>,
) -> impl std::future::Future<
Output = std::result::Result<(Self::Statement, Vec<Self::Value>), Self::Error>,
> + Send;
}
pub fn resolve_values<V>(
composed: &ComposedSql,
values: &mut BTreeMap<String, Vec<V>>,
) -> Result<Vec<V>> {
let mut result = Vec::with_capacity(composed.bind_params.len());
for name in &composed.bind_params {
let vs = values
.get_mut(name)
.ok_or_else(|| Error::MissingBinding { name: name.clone() })?;
if vs.is_empty() {
return Err(Error::MissingBinding { name: name.clone() });
}
result.push(vs.remove(0));
}
Ok(result)
}
#[macro_export]
macro_rules! bind_values {
($($key:literal => [$($value:expr),+ $(,)?]),+ $(,)?) => {{
let mut map = std::collections::BTreeMap::new();
$(
map.insert($key.to_string(), vec![$($value),+]);
)+
map
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolve_values_basic() {
let composed = ComposedSql {
sql: "SELECT * FROM t WHERE a = $1 AND b = $2".into(),
bind_params: vec!["a".into(), "b".into()],
};
let mut values: BTreeMap<String, Vec<&str>> = BTreeMap::new();
values.insert("a".into(), vec!["hello"]);
values.insert("b".into(), vec!["world"]);
let result = resolve_values(&composed, &mut values).unwrap();
assert_eq!(result, vec!["hello", "world"]);
}
#[test]
fn test_resolve_values_missing_binding() {
let composed = ComposedSql {
sql: "SELECT * FROM t WHERE a = $1".into(),
bind_params: vec!["missing".into()],
};
let mut values: BTreeMap<String, Vec<&str>> = BTreeMap::new();
let err = resolve_values(&composed, &mut values).unwrap_err();
assert!(matches!(err, Error::MissingBinding { ref name } if name == "missing"));
}
#[test]
fn test_resolve_values_multi_value_expanded() {
let composed = ComposedSql {
sql: "SELECT * FROM t WHERE id IN ($1, $2, $3)".into(),
bind_params: vec!["ids".into(), "ids".into(), "ids".into()],
};
let mut values: BTreeMap<String, Vec<i32>> = BTreeMap::new();
values.insert("ids".into(), vec![10, 20, 30]);
let result = resolve_values(&composed, &mut values).unwrap();
assert_eq!(result, vec![10, 20, 30]);
}
#[test]
fn test_bind_values_macro() {
let values: BTreeMap<String, Vec<i32>> = bind_values!(
"a" => [1, 2],
"b" => [3],
);
assert_eq!(values["a"], vec![1, 2]);
assert_eq!(values["b"], vec![3]);
}
}