use rustlavel_core::Json;
use rustlavel_db::Value;
use std::hash::{Hash, Hasher};
pub const PREFIX: &str = "rl:mc";
pub fn entity(table: &str, key: &str) -> String {
format!("{PREFIX}:e:{table}:{key}")
}
pub fn query(table: &str, fingerprint: u64) -> String {
format!("{PREFIX}:q:{table}:{fingerprint:016x}")
}
pub fn generation(table: &str) -> String {
format!("{PREFIX}:g:{table}")
}
pub fn fingerprint(sql: &str, bindings: &[Value]) -> u64 {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
sql.hash(&mut hasher);
for value in bindings {
Json::from(value.clone()).to_string().hash(&mut hasher);
}
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_three_shapes_do_not_collide_with_each_other() {
assert_eq!(entity("users", "7"), "rl:mc:e:users:7");
assert_eq!(generation("users"), "rl:mc:g:users");
assert!(query("users", 1).starts_with("rl:mc:q:users:"));
assert_ne!(entity("users", "7"), query("users", 7));
}
#[test]
fn the_bindings_are_part_of_the_fingerprint() {
let sql = "select * from users where role = ?";
let admin = fingerprint(sql, &[Value::from("admin")]);
let support = fingerprint(sql, &[Value::from("support")]);
assert_ne!(admin, support, "two different filters shared a cache key");
assert_eq!(admin, fingerprint(sql, &[Value::from("admin")]), "not stable");
assert_ne!(admin, fingerprint("select 1", &[Value::from("admin")]));
}
#[test]
fn a_number_and_its_text_are_different_bindings() {
let sql = "select * from users where id = ?";
assert_ne!(
fingerprint(sql, &[Value::from(7i64)]),
fingerprint(sql, &[Value::from("7")]),
);
}
#[test]
fn the_order_of_the_bindings_matters() {
let sql = "select * from users where a = ? and b = ?";
assert_ne!(
fingerprint(sql, &[Value::from("x"), Value::from("y")]),
fingerprint(sql, &[Value::from("y"), Value::from("x")]),
);
}
}