1use std::collections::BTreeMap;
10use std::fmt;
11use std::hash::{Hash, Hasher};
12
13use rindle::{canonicalize_wire_number_lits, Ast};
14use serde_json::Value;
15
16#[derive(Debug)]
19pub struct QueryKeyError(pub serde_json::Error);
20
21impl fmt::Display for QueryKeyError {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "query key canonicalization failed: {}", self.0)
24 }
25}
26
27impl std::error::Error for QueryKeyError {}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33pub enum StreamMode {
34 Normalized,
35 Flat,
36}
37
38impl StreamMode {
39 pub(crate) fn as_str(self) -> &'static str {
40 match self {
41 StreamMode::Normalized => "normalized",
42 StreamMode::Flat => "flat",
43 }
44 }
45}
46
47#[derive(Clone, Debug, Eq)]
49pub struct QueryKey {
50 schema_version: String,
51 stream_mode: StreamMode,
52 visibility_key: String,
53 canonical_ast: Vec<u8>,
54 fingerprint: u64,
55}
56
57impl QueryKey {
58 pub fn new(
59 schema_version: impl Into<String>,
60 stream_mode: StreamMode,
61 ast: &Ast,
62 visibility_key: impl Into<String>,
63 ) -> Result<QueryKey, QueryKeyError> {
64 let schema_version = schema_version.into();
65 let visibility_key = visibility_key.into();
66 let mut ast = ast.clone();
72 canonicalize_wire_number_lits(&mut ast);
73 let canonical_ast = canonical_ast_bytes(&ast)?;
74
75 let mut hasher = Fnv1a64::new();
76 hash_field(&mut hasher, schema_version.as_bytes());
77 hash_field(&mut hasher, stream_mode.as_str().as_bytes());
78 hash_field(&mut hasher, visibility_key.as_bytes());
79 hash_field(&mut hasher, &canonical_ast);
80 let fingerprint = hasher.finish();
81
82 Ok(QueryKey {
83 schema_version,
84 stream_mode,
85 visibility_key,
86 canonical_ast,
87 fingerprint,
88 })
89 }
90
91 pub fn schema_version(&self) -> &str {
92 &self.schema_version
93 }
94
95 pub fn stream_mode(&self) -> StreamMode {
96 self.stream_mode
97 }
98
99 pub fn visibility_key(&self) -> &str {
100 &self.visibility_key
101 }
102
103 pub fn canonical_ast(&self) -> &[u8] {
104 &self.canonical_ast
105 }
106
107 pub fn fingerprint(&self) -> u64 {
108 self.fingerprint
109 }
110
111 pub fn fingerprint_hex(&self) -> String {
112 format!("{:016x}", self.fingerprint)
113 }
114}
115
116impl PartialEq for QueryKey {
117 fn eq(&self, other: &Self) -> bool {
118 self.schema_version == other.schema_version
119 && self.stream_mode == other.stream_mode
120 && self.visibility_key == other.visibility_key
121 && self.canonical_ast == other.canonical_ast
122 }
123}
124
125impl Hash for QueryKey {
126 fn hash<H: Hasher>(&self, state: &mut H) {
127 self.schema_version.hash(state);
128 self.stream_mode.hash(state);
129 self.visibility_key.hash(state);
130 self.canonical_ast.hash(state);
131 }
132}
133
134pub fn canonical_ast_bytes(ast: &Ast) -> Result<Vec<u8>, QueryKeyError> {
138 let mut value = serde_json::to_value(ast).map_err(QueryKeyError)?;
139 normalize_select_arrays(&mut value);
140 let mut out = Vec::new();
141 write_canonical_json(&value, &mut out)?;
142 Ok(out)
143}
144
145pub(crate) fn normalize_select_arrays(value: &mut Value) {
153 match value {
154 Value::Object(map) => {
155 if let Some(Value::Array(items)) = map.get_mut("select") {
156 if items.iter().all(Value::is_string) {
157 items.sort_by(|a, b| a.as_str().cmp(&b.as_str()));
158 items.dedup();
159 }
160 }
161 for item in map.values_mut() {
162 normalize_select_arrays(item);
163 }
164 }
165 Value::Array(items) => {
166 for item in items.iter_mut() {
167 normalize_select_arrays(item);
168 }
169 }
170 _ => {}
171 }
172}
173
174pub(crate) fn write_canonical_json(value: &Value, out: &mut Vec<u8>) -> Result<(), QueryKeyError> {
175 match value {
176 Value::Null => out.extend_from_slice(b"null"),
177 Value::Bool(v) => out.extend_from_slice(if *v { b"true" } else { b"false" }),
178 Value::Number(n) => out.extend_from_slice(n.to_string().as_bytes()),
179 Value::String(s) => {
180 let quoted = serde_json::to_string(s).map_err(QueryKeyError)?;
181 out.extend_from_slice(quoted.as_bytes());
182 }
183 Value::Array(items) => {
184 out.push(b'[');
185 for (idx, item) in items.iter().enumerate() {
186 if idx > 0 {
187 out.push(b',');
188 }
189 write_canonical_json(item, out)?;
190 }
191 out.push(b']');
192 }
193 Value::Object(map) => {
194 let sorted: BTreeMap<_, _> = map.iter().collect();
195 out.push(b'{');
196 for (idx, (key, item)) in sorted.iter().enumerate() {
197 if idx > 0 {
198 out.push(b',');
199 }
200 let quoted = serde_json::to_string(key).map_err(QueryKeyError)?;
201 out.extend_from_slice(quoted.as_bytes());
202 out.push(b':');
203 write_canonical_json(item, out)?;
204 }
205 out.push(b'}');
206 }
207 }
208 Ok(())
209}
210
211pub(crate) fn hash_field(hasher: &mut Fnv1a64, bytes: &[u8]) {
212 hasher.write(&(bytes.len() as u64).to_le_bytes());
213 hasher.write(bytes);
214}
215
216pub(crate) struct Fnv1a64(u64);
217
218impl Fnv1a64 {
219 pub(crate) fn new() -> Fnv1a64 {
220 Fnv1a64(0xcbf29ce484222325)
221 }
222}
223
224impl Hasher for Fnv1a64 {
225 fn write(&mut self, bytes: &[u8]) {
226 for b in bytes {
227 self.0 ^= u64::from(*b);
228 self.0 = self.0.wrapping_mul(0x100000001b3);
229 }
230 }
231
232 fn finish(&self) -> u64 {
233 self.0
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use rindle::table;
241
242 fn key(ast: &Ast) -> QueryKey {
243 QueryKey::new("v1", StreamMode::Normalized, ast, "vis").expect("key builds")
244 }
245
246 #[test]
247 fn select_column_order_does_not_fork_the_key() {
248 let a = table("issue").select("title").select("priority").build();
251 let b = table("issue").select("priority").select("title").build();
252 assert_eq!(key(&a), key(&b));
253 assert_eq!(key(&a).fingerprint(), key(&b).fingerprint());
254 }
255
256 #[test]
257 fn duplicate_selected_columns_are_deduped() {
258 let a = table("issue").select("title").select("title").build();
259 let b = table("issue").select("title").build();
260 assert_eq!(key(&a), key(&b));
261 }
262
263 #[test]
264 fn different_projections_get_distinct_keys() {
265 let one = table("issue").select("title").build();
268 let two = table("issue").select("title").select("priority").build();
269 assert_ne!(one, two);
270 }
271
272 #[test]
273 fn int_and_integral_number_spellings_share_a_key() {
274 let int = table("issue").r#where("x", 1i64).build();
278 let num = table("issue").r#where("x", 1.0f64).build();
279 let frac = table("issue").r#where("x", 1.5f64).build();
280 assert_eq!(key(&int), key(&num));
281 assert_ne!(key(&int), key(&frac));
282 }
283
284 #[test]
285 fn star_is_distinct_from_an_explicit_list() {
286 let star = table("issue").build();
290 let explicit = table("issue").select("id").select("title").build();
291 assert_ne!(star, explicit);
292 }
293}