use std::hash::{BuildHasher, Hash, Hasher};
use ahash::RandomState;
use serde_json::Value as JsonValue;
use crate::{
db::{OrderByClause, WhereOperator, where_clause::WhereClause},
schema::{QueryDefinition, SqlProjectionHint},
};
const SEED_K0: u64 = 0x5241_4953_454F_4E31; const SEED_K1: u64 = 0x4652_4149_5345_514C; const SEED_K2: u64 = 0x4341_4348_454B_4559; const SEED_K3: u64 = 0x5632_5F43_4143_4845;
fn new_hasher() -> impl Hasher {
RandomState::with_seeds(SEED_K0, SEED_K1, SEED_K2, SEED_K3).build_hasher()
}
#[must_use]
pub fn generate_cache_key(
query: &str,
variables: &JsonValue,
where_clause: Option<&WhereClause>,
schema_version: &str,
) -> u64 {
let mut h = new_hasher();
h.write(b"q:");
h.write(query.as_bytes());
h.write(b"\0v:");
hash_json_value(&mut h, variables);
h.write(b"\0w:");
if let Some(wc) = where_clause {
h.write_u8(1);
hash_where_clause(&mut h, wc);
} else {
h.write_u8(0);
}
h.write(b"\0s:");
h.write(schema_version.as_bytes());
h.finish()
}
#[must_use]
pub fn generate_view_query_key(
view: &str,
where_clause: Option<&WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&[OrderByClause]>,
schema_version: &str,
) -> u64 {
let mut h = new_hasher();
h.write(b"v:");
h.write(view.as_bytes());
h.write(b"\0w:");
if let Some(wc) = where_clause {
h.write_u8(1);
hash_where_clause(&mut h, wc);
} else {
h.write_u8(0);
}
h.write(b"\0l:");
match limit {
Some(l) => {
h.write_u8(1);
h.write_u32(l);
},
None => h.write_u8(0),
}
h.write(b"\0o:");
match offset {
Some(o) => {
h.write_u8(1);
h.write_u32(o);
},
None => h.write_u8(0),
}
h.write(b"\0b:");
hash_order_by(&mut h, order_by);
h.write(b"\0s:");
h.write(schema_version.as_bytes());
h.finish()
}
#[must_use]
pub fn generate_projection_query_key(
view: &str,
projection: Option<&SqlProjectionHint>,
where_clause: Option<&WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&[OrderByClause]>,
schema_version: &str,
) -> u64 {
let mut h = new_hasher();
h.write(b"p:");
h.write(view.as_bytes());
h.write(b"\0j:");
match projection {
Some(p) => {
h.write_u8(1);
h.write(p.projection_template.as_bytes());
},
None => h.write_u8(0),
}
h.write(b"\0w:");
if let Some(wc) = where_clause {
h.write_u8(1);
hash_where_clause(&mut h, wc);
} else {
h.write_u8(0);
}
h.write(b"\0l:");
match limit {
Some(l) => {
h.write_u8(1);
h.write_u32(l);
},
None => h.write_u8(0),
}
h.write(b"\0o:");
match offset {
Some(o) => {
h.write_u8(1);
h.write_u32(o);
},
None => h.write_u8(0),
}
h.write(b"\0b:");
hash_order_by(&mut h, order_by);
h.write(b"\0s:");
h.write(schema_version.as_bytes());
h.finish()
}
fn hash_json_value(h: &mut impl Hasher, value: &JsonValue) {
match value {
JsonValue::Null => h.write_u8(0),
JsonValue::Bool(b) => {
h.write_u8(1);
b.hash(h);
},
JsonValue::Number(n) => {
h.write_u8(2);
h.write(n.to_string().as_bytes());
},
JsonValue::String(s) => {
h.write_u8(3);
h.write(s.as_bytes());
},
JsonValue::Array(arr) => {
h.write_u8(4);
h.write_usize(arr.len());
for item in arr {
hash_json_value(h, item);
}
},
JsonValue::Object(map) => {
h.write_u8(5);
h.write_usize(map.len());
let mut keys: Vec<&String> = map.keys().collect();
keys.sort_unstable();
for key in keys {
h.write(key.as_bytes());
hash_json_value(h, &map[key]);
}
},
}
}
fn hash_where_clause(h: &mut impl Hasher, clause: &WhereClause) {
match clause {
WhereClause::Field {
path,
operator,
value,
} => {
h.write_u8(b'F');
h.write_usize(path.len());
for segment in path {
h.write(segment.as_bytes());
h.write_u8(0); }
hash_where_operator(h, operator);
hash_json_value(h, value);
},
WhereClause::And(clauses) => {
h.write_u8(b'A');
h.write_usize(clauses.len());
for c in clauses {
hash_where_clause(h, c);
}
},
WhereClause::Or(clauses) => {
h.write_u8(b'O');
h.write_usize(clauses.len());
for c in clauses {
hash_where_clause(h, c);
}
},
WhereClause::Not(inner) => {
h.write_u8(b'N');
hash_where_clause(h, inner);
},
_ => {
h.write_u8(b'?');
h.write(format!("{clause:?}").as_bytes());
},
}
}
fn hash_where_operator(h: &mut impl Hasher, op: &WhereOperator) {
std::mem::discriminant(op).hash(h);
if let WhereOperator::Extended(inner) = op {
let inner_str = format!("{inner:?}");
h.write(inner_str.as_bytes());
}
}
fn hash_order_by(h: &mut impl Hasher, order_by: Option<&[OrderByClause]>) {
match order_by.filter(|c| !c.is_empty()) {
Some(clauses) => {
h.write_u8(1);
h.write_usize(clauses.len());
for clause in clauses {
let key = clause.storage_key();
h.write(key.as_bytes());
h.write_u8(clause.direction as u8);
}
},
None => h.write_u8(0),
}
}
#[must_use]
pub fn extract_accessed_views(query_def: &QueryDefinition) -> Vec<String> {
let mut views = Vec::new();
if let Some(sql_source) = &query_def.sql_source {
views.push(sql_source.clone());
}
views.extend(query_def.additional_views.iter().cloned());
views
}
#[cfg(test)]
#[must_use]
pub fn verify_deterministic(query: &str, variables: &JsonValue, schema_version: &str) -> bool {
let key1 = generate_cache_key(query, variables, None, schema_version);
let key2 = generate_cache_key(query, variables, None, schema_version);
key1 == key2
}