use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[derive(Clone, Debug)]
pub struct PreparedStatement {
pub(crate) name: String,
#[allow(dead_code)]
pub(crate) param_count: usize,
}
#[derive(Clone, Debug)]
pub struct PreparedAstQuery {
pub(crate) stmt: PreparedStatement,
pub(crate) params: Vec<Option<Vec<u8>>>,
pub(crate) sql: String,
pub(crate) sql_hash: u64,
}
impl PreparedAstQuery {
#[inline]
pub fn statement_name(&self) -> &str {
self.stmt.name()
}
#[inline]
pub fn param_count(&self) -> usize {
self.params.len()
}
}
impl PreparedStatement {
#[inline]
pub fn from_sql_bytes(sql_bytes: &[u8]) -> Self {
let name = sql_bytes_to_stmt_name(sql_bytes);
let param_count = sql_bytes
.windows(2)
.filter(|w| w[0] == b'$' && w[1].is_ascii_digit())
.count();
Self { name, param_count }
}
#[inline]
pub fn from_sql(sql: &str) -> Self {
Self::from_sql_bytes(sql.as_bytes())
}
#[inline]
pub fn name(&self) -> &str {
&self.name
}
}
#[inline]
pub fn sql_bytes_hash(sql: &[u8]) -> u64 {
let mut hasher = DefaultHasher::new();
sql.hash(&mut hasher);
hasher.finish()
}
#[inline]
pub fn stmt_name_from_hash(hash: u64) -> String {
format!("s{hash:016x}")
}
#[inline]
pub fn sql_bytes_to_stmt_name(sql: &[u8]) -> String {
stmt_name_from_hash(sql_bytes_hash(sql))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stmt_name_from_bytes() {
let sql = b"SELECT id, name FROM users WHERE id = $1";
let name1 = sql_bytes_to_stmt_name(sql);
let name2 = sql_bytes_to_stmt_name(sql);
let hash = sql_bytes_hash(sql);
let name3 = stmt_name_from_hash(hash);
assert_eq!(name1, name2); assert_eq!(name1, name3);
assert!(name1.starts_with("s"));
assert_eq!(name1.len(), 17); }
#[test]
fn test_prepared_statement() {
let stmt = PreparedStatement::from_sql("SELECT * FROM users WHERE id = $1 AND name = $2");
assert_eq!(stmt.param_count, 2);
assert!(stmt.name.starts_with("s"));
}
}