use crate::util::fnv1a_hash;
pub fn predicate_class(canonical_filter_sql: &str, collection: &str) -> u64 {
let normalized = normalize_predicate_text(canonical_filter_sql);
let mut buf = Vec::with_capacity(collection.len() + normalized.len() + 1);
buf.extend_from_slice(collection.as_bytes());
buf.push(b'\x00');
buf.extend_from_slice(normalized.as_bytes());
fnv1a_hash(&buf)
}
fn normalize_predicate_text(sql: &str) -> String {
let mut out = String::with_capacity(sql.len());
let chars: Vec<char> = sql.chars().collect();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
if c == '\'' {
out.push_str("str");
i += 1;
while i < chars.len() {
if chars[i] == '\'' {
i += 1;
if i < chars.len() && chars[i] == '\'' {
i += 1;
} else {
break;
}
} else {
i += 1;
}
}
continue;
}
if c.is_ascii_digit() || (c == '-' && i + 1 < chars.len() && chars[i + 1].is_ascii_digit())
{
let mut is_float = false;
i += 1; while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
if chars[i] == '.' {
is_float = true;
}
i += 1;
}
if is_float {
out.push_str("f64");
} else {
out.push_str("i64");
}
continue;
}
out.push(c);
i += 1;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same_shape_different_literal_same_hash() {
let h1 = predicate_class("WHERE balance > 1000", "accounts");
let h2 = predicate_class("WHERE balance > 9999", "accounts");
assert_eq!(h1, h2);
}
#[test]
fn different_field_different_hash() {
let h1 = predicate_class("WHERE balance > 1000", "accounts");
let h2 = predicate_class("WHERE age > 1000", "accounts");
assert_ne!(h1, h2);
}
#[test]
fn different_collection_different_hash() {
let h1 = predicate_class("WHERE x > 1", "col_a");
let h2 = predicate_class("WHERE x > 1", "col_b");
assert_ne!(h1, h2);
}
#[test]
fn string_literals_normalized() {
let h1 = predicate_class("WHERE name = 'alice'", "users");
let h2 = predicate_class("WHERE name = 'bob'", "users");
assert_eq!(h1, h2);
}
#[test]
fn float_literals_normalized() {
let h1 = predicate_class("WHERE score > 1.5", "items");
let h2 = predicate_class("WHERE score > 9.9", "items");
assert_eq!(h1, h2);
}
}