use crate::error::{BindError, DriverError, Result};
use lazy_regex::regex;
use regex::Captures;
use serde::Serialize;
use serde_json::{Map, Value};
use std::collections::HashMap;
type BindResult = std::result::Result<String, BindError>;
pub fn bind<T>(query: &str, params: T) -> Result<String>
where
T: Serialize,
{
Ok(serde_json::to_value(params)
.map_err(BindError::DeserializeError)
.and_then(|val| parametrize_query(query, val))
.map_err(DriverError::BindError)?)
}
#[inline]
fn parametrize_query(query: &str, val: Value) -> BindResult {
match val {
Value::Object(o) => do_param_binding(query, gen_map_params(o)),
Value::Array(a) => do_param_binding(query, gen_seq_params(a)),
_ => Err(BindError::SerializeError),
}
}
#[inline]
fn do_param_binding(query: &str, map: HashMap<String, String>) -> BindResult {
let re = regex!(r"\\(:\w+)|[:\w]:\w+|:\w+:|:(\w+)");
let mut result = Ok(String::new());
let q = re.replace_all(query, |cap: &Captures| {
cap.get(2)
.map(|m| match map.get(m.as_str()) {
Some(k) => k.as_str(),
None => {
result = Err(BindError::MappingError(cap[0].to_owned()));
""
}
})
.or_else(|| cap.get(1).map(|m| &query[m.range()]))
.unwrap_or(&query[cap.get(0).unwrap().range()])
});
result.and(Ok(q.into_owned()))
}
#[inline]
fn gen_map_params(params: Map<String, Value>) -> HashMap<String, String> {
params
.into_iter()
.map(|(k, v)| (k, into_sql_param(v)))
.collect()
}
#[inline]
fn gen_seq_params(params: Vec<Value>) -> HashMap<String, String> {
params
.into_iter()
.enumerate()
.map(|(i, v)| (i.to_string(), into_sql_param(v)))
.collect()
}
#[inline]
fn into_sql_param(val: Value) -> String {
match val {
Value::Null => "NULL".to_owned(),
Value::String(s) => ["'", &s.replace('\'', "''"), "'"].concat(),
Value::Number(n) => n.to_string(),
Value::Bool(b) => match b {
true => "1".to_owned(),
false => "0".to_owned(),
},
Value::Array(a) => {
let iter = a.into_iter().map(into_sql_param);
build_param_list(iter)
}
Value::Object(o) => {
let iter = o.into_iter().map(|(_, v)| into_sql_param(v));
build_param_list(iter)
}
}
}
#[inline]
fn build_param_list<I>(iter: I) -> String
where
I: Iterator<Item = String>,
{
let mut str_params = "(".to_string();
iter.for_each(|s| {
str_params.push_str(&s);
str_params.push_str(", ");
});
str_params.pop();
str_params.pop();
str_params.push(')');
str_params
}