use std::hash::Hasher;
use crate::msgpack_scan::extract_field;
fn extract_key_range(doc: &[u8], key: &str) -> Option<(usize, usize)> {
extract_field(doc, 0, key).or_else(|| {
key.rsplit_once('.')
.and_then(|(_, field)| extract_field(doc, 0, field))
})
}
pub fn partition_hash<S: AsRef<str>>(doc: &[u8], keys: &[S]) -> u64 {
partition_hash_seeded(doc, keys, 0)
}
pub fn partition_hash_seeded<S: AsRef<str>>(doc: &[u8], keys: &[S], seed: u64) -> u64 {
let mut hasher = std::hash::DefaultHasher::new();
hasher.write_u64(seed);
for key in keys {
if let Some((start, end)) = extract_key_range(doc, key.as_ref()) {
hasher.write(&doc[start..end]);
} else {
hasher.write_u8(0xc0);
}
}
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
fn row(fields: &[(&str, serde_json::Value)]) -> Vec<u8> {
let mut map = serde_json::Map::new();
for (k, v) in fields {
map.insert((*k).to_string(), v.clone());
}
nodedb_types::json_to_msgpack(&serde_json::Value::Object(map)).expect("encode row")
}
#[test]
fn equal_key_bytes_hash_identically() {
let a = row(&[("k", serde_json::json!(7)), ("v", serde_json::json!("a"))]);
let b = row(&[("k", serde_json::json!(7)), ("v", serde_json::json!("b"))]);
assert_eq!(
partition_hash(&a, &["k"]),
partition_hash(&b, &["k"]),
"equal key bytes must co-locate regardless of other columns"
);
}
#[test]
fn distinct_keys_usually_differ() {
let a = row(&[("k", serde_json::json!(1))]);
let b = row(&[("k", serde_json::json!(2))]);
assert_ne!(partition_hash(&a, &["k"]), partition_hash(&b, &["k"]));
}
#[test]
fn missing_field_uses_nil_sentinel() {
let present = row(&[("other", serde_json::json!(1))]);
let other = row(&[("zzz", serde_json::json!(9))]);
assert_eq!(
partition_hash(&present, &["k"]),
partition_hash(&other, &["k"])
);
}
#[test]
fn dotted_key_falls_back_to_trailing_segment() {
let r = row(&[("id", serde_json::json!(5))]);
assert_eq!(
partition_hash(&r, &["t.id"]),
partition_hash(&r, &["id"]),
"qualified key resolves to the unqualified column"
);
}
#[test]
fn seed_changes_routing() {
let r = row(&[("k", serde_json::json!(3))]);
assert_ne!(
partition_hash_seeded(&r, &["k"], 0),
partition_hash_seeded(&r, &["k"], 1),
"a different seed must redistribute the same key"
);
}
}