use std::collections::HashMap;
#[derive(Debug)]
pub(super) struct BoundQuery {
pub(super) sql: String,
pub(super) binds: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct MissingParam(pub(super) String);
pub(super) fn prepare_enrichment_query(
query: &str,
claims: &HashMap<String, serde_json::Value>,
) -> Result<BoundQuery, MissingParam> {
let mut sql = String::with_capacity(query.len());
let mut binds: Vec<serde_json::Value> = Vec::new();
let mut param_index: HashMap<String, usize> = HashMap::new();
let bytes = query.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'$' && i + 1 < bytes.len() && is_name_start(bytes[i + 1]) {
let start = i + 1;
let mut end = start;
while end < bytes.len() && is_name_char(bytes[end]) {
end += 1;
}
let name = &query[start..end];
let pos = if let Some(&existing) = param_index.get(name) {
existing
} else {
let value = claims.get(name).ok_or_else(|| MissingParam(name.to_owned()))?;
binds.push(value.clone());
let pos = binds.len();
param_index.insert(name.to_owned(), pos);
pos
};
sql.push('$');
sql.push_str(&pos.to_string());
i = end;
} else {
sql.push(char::from(bytes[i]));
i += 1;
}
}
Ok(BoundQuery { sql, binds })
}
const fn is_name_start(b: u8) -> bool {
b.is_ascii_alphabetic() || b == b'_'
}
const fn is_name_char(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}