1use std::collections::HashMap;
45
46use arrow_array::cast::AsArray;
47use arrow_array::types::{
48 Float64Type, Int32Type, Int64Type, TimestampNanosecondType, UInt8Type, UInt16Type, UInt32Type,
49 UInt64Type,
50};
51use arrow_array::{Array, RecordBatch};
52use arrow_schema::DataType;
53
54use crate::query::Op;
55use crate::schema::{ATTRS, AttrType};
56
57pub const ZONE_IDX: &str = "zone.idx";
59
60const MAGIC: [u8; 4] = *b"MZON";
61const VERSION: u8 = 1;
62const HEADER: usize = 16;
64const ENTRY: usize = 40;
66
67const MAX_KEYS: usize = 4096;
75
76#[derive(Clone, Copy, Debug, PartialEq)]
83pub struct Range {
84 pub int_min: i64,
85 pub int_max: i64,
86 pub dbl_min: f64,
87 pub dbl_max: f64,
88}
89
90impl Range {
91 const EMPTY: Range = Range {
93 int_min: i64::MAX,
94 int_max: i64::MIN,
95 dbl_min: f64::INFINITY,
96 dbl_max: f64::NEG_INFINITY,
97 };
98
99 pub const ANY: Range = Range {
103 int_min: i64::MIN,
104 int_max: i64::MAX,
105 dbl_min: f64::NEG_INFINITY,
106 dbl_max: f64::INFINITY,
107 };
108
109 fn int(&mut self, v: i64) {
110 self.int_min = self.int_min.min(v);
111 self.int_max = self.int_max.max(v);
112 }
113
114 fn float(&mut self, v: f64) {
119 if !v.is_nan() {
120 self.dbl_min = self.dbl_min.min(v);
121 self.dbl_max = self.dbl_max.max(v);
122 }
123 }
124}
125
126pub struct Probe {
135 pub key: u64,
136 pub op: Op,
137 pub int: Option<i64>,
138 pub float: Option<f64>,
139}
140
141impl Probe {
142 pub fn maybe(&self, m: &Map) -> bool {
146 match m.get(self.key) {
147 None => false,
148 Some(r) => {
149 self.int
150 .is_some_and(|t| reachable(self.op, r.int_min, r.int_max, t))
151 || self
152 .float
153 .is_some_and(|t| reachable(self.op, r.dbl_min, r.dbl_max, t))
154 }
155 }
156 }
157}
158
159fn reachable<T: PartialOrd + Copy>(op: Op, min: T, max: T, target: T) -> bool {
166 match op {
167 Op::Eq => min <= target && target <= max,
168 Op::Lt => min < target,
169 Op::Lte => min <= target,
170 Op::Gt => max > target,
171 Op::Gte => max >= target,
172 Op::Ne | Op::Contains => true,
173 }
174}
175
176#[derive(Default)]
178pub struct Builder {
179 keys: HashMap<u64, Range>,
180 full: bool,
181}
182
183impl Builder {
184 fn at(&mut self, key: u64) -> Option<&mut Range> {
185 if !self.keys.contains_key(&key) && self.keys.len() >= MAX_KEYS {
186 self.full = true;
187 return None;
188 }
189 Some(self.keys.entry(key).or_insert(Range::EMPTY))
190 }
191
192 pub fn int(&mut self, key: u64, v: i64) {
193 if let Some(r) = self.at(key) {
194 r.int(v);
195 }
196 }
197
198 pub fn float(&mut self, key: u64, v: f64) {
199 if let Some(r) = self.at(key) {
200 r.float(v);
201 }
202 }
203
204 pub fn any(&mut self, key: u64) {
206 if let Some(r) = self.at(key) {
207 *r = Range::ANY;
208 }
209 }
210
211 pub fn build(&self) -> Option<Vec<u8>> {
214 if self.full || self.keys.is_empty() {
215 return None;
216 }
217 let mut entries: Vec<(&u64, &Range)> = self.keys.iter().collect();
220 entries.sort_unstable_by_key(|(k, _)| **k);
221
222 let mut body = Vec::with_capacity(entries.len() * ENTRY);
223 for (k, r) in entries {
224 body.extend_from_slice(&k.to_le_bytes());
225 body.extend_from_slice(&r.int_min.to_le_bytes());
226 body.extend_from_slice(&r.int_max.to_le_bytes());
227 body.extend_from_slice(&r.dbl_min.to_le_bytes());
228 body.extend_from_slice(&r.dbl_max.to_le_bytes());
229 }
230
231 let mut out = Vec::with_capacity(HEADER + body.len());
232 out.extend_from_slice(&MAGIC);
233 out.push(VERSION);
234 out.extend_from_slice(&[0, 0, 0]);
235 out.extend_from_slice(&(self.keys.len() as u32).to_le_bytes());
236 out.extend_from_slice(&crc32fast::hash(&body).to_le_bytes());
237 out.extend_from_slice(&body);
238 Some(out)
239 }
240}
241
242pub struct Map<'a> {
244 body: &'a [u8],
245 n: usize,
246}
247
248impl<'a> Map<'a> {
249 pub fn open(file: &'a [u8]) -> Option<Map<'a>> {
252 if file.len() < HEADER || file[..4] != MAGIC || file[4] != VERSION {
253 return None;
254 }
255 let n = u32::from_le_bytes(file[8..12].try_into().expect("4 bytes")) as usize;
256 let crc = u32::from_le_bytes(file[12..16].try_into().expect("4 bytes"));
257 let body = &file[HEADER..];
258 if n == 0 || body.len() != n * ENTRY || crc32fast::hash(body) != crc {
259 return None;
260 }
261 Some(Map { body, n })
262 }
263
264 fn key_at(&self, i: usize) -> u64 {
265 u64::from_le_bytes(self.body[i * ENTRY..][..8].try_into().expect("8 bytes"))
266 }
267
268 fn get(&self, key: u64) -> Option<Range> {
271 let (mut lo, mut hi) = (0usize, self.n);
272 while lo < hi {
273 let mid = lo + (hi - lo) / 2;
274 match self.key_at(mid).cmp(&key) {
275 std::cmp::Ordering::Less => lo = mid + 1,
276 std::cmp::Ordering::Greater => hi = mid,
277 std::cmp::Ordering::Equal => {
278 lo = mid;
279 break;
280 }
281 }
282 }
283 if lo >= self.n || self.key_at(lo) != key {
284 return None;
285 }
286 let i = lo;
287 let f = |off: usize| {
288 self.body[i * ENTRY + off..][..8]
289 .try_into()
290 .expect("8 bytes")
291 };
292 Some(Range {
293 int_min: i64::from_le_bytes(f(8)),
294 int_max: i64::from_le_bytes(f(16)),
295 dbl_min: f64::from_le_bytes(f(24)),
296 dbl_max: f64::from_le_bytes(f(32)),
297 })
298 }
299}
300
301pub fn attr_key(key: &str) -> u64 {
308 mix(crate::identity::hash64(key.as_bytes()) ^ 0xa77b_a77b_a77b_a77b)
309}
310
311pub fn field_key(name: &str) -> u64 {
313 mix(crate::identity::hash64(name.as_bytes()) ^ 0xf1e1_f1e1_f1e1_f1e1)
314}
315
316fn mix(mut x: u64) -> u64 {
319 x ^= x >> 30;
320 x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9);
321 x ^= x >> 27;
322 x = x.wrapping_mul(0x94d0_49bb_1331_11eb);
323 x ^ (x >> 31)
324}
325
326pub fn index(tables: &[(&'static str, RecordBatch)]) -> Option<Vec<u8>> {
333 let mut b = Builder::default();
334 if let Some((_, root)) = tables.first() {
335 index_root(&mut b, root);
336 }
337 for (_, t) in tables {
338 if std::sync::Arc::ptr_eq(&t.schema(), &ATTRS) {
339 index_attrs(&mut b, t);
340 }
341 }
342 b.build()
343}
344
345fn index_root(b: &mut Builder, root: &RecordBatch) {
352 macro_rules! ints {
353 ($t:ty, $col:expr, $key:expr) => {{
354 let a = $col.as_primitive::<$t>();
355 for i in 0..a.len() {
356 if !a.is_null(i) {
357 b.int($key, a.value(i) as i64);
358 }
359 }
360 }};
361 }
362
363 for (f, col) in root.schema().fields().iter().zip(root.columns()) {
364 let key = field_key(f.name());
365 match col.data_type() {
366 DataType::Timestamp(_, _) => ints!(TimestampNanosecondType, col, key),
367 DataType::Int64 => ints!(Int64Type, col, key),
368 DataType::Int32 => ints!(Int32Type, col, key),
369 DataType::UInt64 => ints!(UInt64Type, col, key),
370 DataType::UInt32 => ints!(UInt32Type, col, key),
371 DataType::UInt16 => ints!(UInt16Type, col, key),
372 DataType::UInt8 => ints!(UInt8Type, col, key),
373 DataType::Float64 => {
374 let a = col.as_primitive::<Float64Type>();
375 for i in 0..a.len() {
376 if !a.is_null(i) {
377 b.float(key, a.value(i));
378 }
379 }
380 }
381 _ => {}
387 }
388 }
389}
390
391fn index_attrs(b: &mut Builder, t: &RecordBatch) {
394 let dict = t.column(1).as_dictionary::<UInt16Type>();
395 let names = dict.values().as_string::<i32>();
396 let codes = dict.keys().values();
397 let types = t.column(2).as_primitive::<UInt8Type>().values();
398 let strs = crate::attrs::str_column(t);
399 let ints = t.column(4).as_primitive::<Int64Type>();
400 let doubles = t.column(5).as_primitive::<Float64Type>();
401
402 const STR: u8 = AttrType::Str as u8;
403 const INT: u8 = AttrType::Int as u8;
404 const DOUBLE: u8 = AttrType::Double as u8;
405
406 let hashes: Vec<u64> = (0..names.len()).map(|i| attr_key(names.value(i))).collect();
410
411 for row in 0..t.num_rows() {
412 let key = hashes[codes[row] as usize];
413 match types[row] {
414 INT => b.int(key, ints.value(row)),
415 DOUBLE => b.float(key, doubles.value(row)),
416 STR => match strs.value(row).parse::<f64>() {
417 Ok(v) => b.float(key, v),
421 Err(_) => b.any(key),
424 },
425 _ => {}
429 }
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use std::sync::Arc;
436
437 use arrow_array::{Float64Array, Int64Array, RecordBatch, StringArray, UInt64Array};
438 use arrow_schema::{Field, Schema};
439
440 use super::*;
441
442 fn map_of(b: &Builder) -> Vec<u8> {
443 b.build().expect("something to write")
444 }
445
446 fn probe(key: u64, op: Op, int: Option<i64>, float: Option<f64>) -> Probe {
447 Probe {
448 key,
449 op,
450 int,
451 float,
452 }
453 }
454
455 #[test]
456 fn a_range_answers_the_five_ordered_operators_and_nothing_else() {
457 let mut b = Builder::default();
458 let k = attr_key("http.status_code");
459 b.int(k, 200);
460 b.int(k, 404);
461 let bytes = map_of(&b);
462 let m = Map::open(&bytes).expect("readable");
463
464 let ask = |op, t: i64| probe(k, op, Some(t), Some(t as f64)).maybe(&m);
465 assert!(ask(Op::Eq, 200) && ask(Op::Eq, 300) && !ask(Op::Eq, 500));
466 assert!(ask(Op::Gte, 404) && !ask(Op::Gte, 405));
467 assert!(ask(Op::Gt, 403) && !ask(Op::Gt, 404));
468 assert!(ask(Op::Lte, 200) && !ask(Op::Lte, 199));
469 assert!(ask(Op::Lt, 201) && !ask(Op::Lt, 200));
470 assert!(ask(Op::Ne, 200) && ask(Op::Contains, 999));
472 }
473
474 #[test]
475 fn a_key_the_block_never_saw_prunes_and_an_unreadable_file_does_not() {
476 let mut b = Builder::default();
477 b.int(attr_key("present"), 1);
478 let bytes = map_of(&b);
479 let m = Map::open(&bytes).expect("readable");
480 assert!(probe(attr_key("present"), Op::Gte, Some(0), Some(0.0)).maybe(&m));
481 assert!(!probe(attr_key("absent"), Op::Gte, Some(0), Some(0.0)).maybe(&m));
482
483 assert!(Map::open(&[]).is_none());
485 assert!(Map::open(&bytes[..HEADER]).is_none());
486 let mut torn = bytes.clone();
487 torn.pop();
488 assert!(Map::open(&torn).is_none());
489 let mut flipped = bytes.clone();
490 *flipped.last_mut().expect("non-empty") ^= 0xff;
491 assert!(Map::open(&flipped).is_none(), "the crc has to catch this");
492 let mut version = bytes.clone();
493 version[4] = 2;
494 assert!(Map::open(&version).is_none());
495 }
496
497 #[test]
498 fn an_integer_past_two_to_the_fifty_three_keeps_its_own_number_line() {
499 let v = (1i64 << 53) + 1;
503 let mut b = Builder::default();
504 let k = attr_key("bytes");
505 b.int(k, v);
506 let bytes = map_of(&b);
507 let m = Map::open(&bytes).expect("readable");
508 assert!(probe(k, Op::Gte, Some(v), Some(v as f64)).maybe(&m));
509 assert!(!probe(k, Op::Gt, Some(v), Some(v as f64)).maybe(&m));
510 }
511
512 #[test]
513 fn a_fractional_scalar_cannot_reach_an_integer_only_key() {
514 let mut b = Builder::default();
515 let k = attr_key("retries");
516 b.int(k, 3);
517 let bytes = map_of(&b);
518 let m = Map::open(&bytes).expect("readable");
519 assert!(!probe(k, Op::Eq, None, Some(3.5)).maybe(&m));
522 assert!(!probe(k, Op::Lt, None, Some(3.5)).maybe(&m));
523 }
524
525 #[test]
526 fn text_that_parses_is_a_number_and_text_that_does_not_gives_up_the_key() {
527 let attrs = |vals: Vec<&str>| {
528 let mut a = crate::attrs::AttrsBuilder::new("t");
529 for v in vals {
530 a.append(
531 0,
532 "code",
533 Some(&mira_proto::common::v1::AnyValue {
534 value: Some(mira_proto::common::v1::any_value::Value::StringValue(
535 v.into(),
536 )),
537 }),
538 )
539 .expect("appends");
540 }
541 vec![("t", a.finish().expect("finishes"))]
542 };
543
544 let k = attr_key("code");
545 let numeric = index(&attrs(vec!["200", "503"])).expect("a map");
546 let m = Map::open(&numeric).expect("readable");
547 assert!(probe(k, Op::Gte, Some(500), Some(500.0)).maybe(&m));
548 assert!(!probe(k, Op::Gt, Some(503), Some(503.0)).maybe(&m));
549
550 let mixed = index(&attrs(vec!["200", "unset"])).expect("a map");
553 let m = Map::open(&mixed).expect("readable");
554 assert!(probe(k, Op::Gt, Some(9999), Some(9999.0)).maybe(&m));
555 }
556
557 #[test]
558 fn the_root_tables_numeric_columns_are_in_it_and_the_others_are_not() {
559 let schema = Arc::new(Schema::new(vec![
560 Field::new("duration_nano", DataType::UInt64, false),
561 Field::new("count", DataType::Int64, true),
562 Field::new("ratio", DataType::Float64, false),
563 Field::new("body", DataType::Utf8, false),
564 ]));
565 let root = RecordBatch::try_new(
566 schema,
567 vec![
568 Arc::new(UInt64Array::from(vec![10u64, 2_000_000_000])),
569 Arc::new(Int64Array::from(vec![None, None] as Vec<Option<i64>>)),
571 Arc::new(Float64Array::from(vec![0.25, f64::NAN])),
572 Arc::new(StringArray::from(vec!["a", "b"])),
573 ],
574 )
575 .expect("a batch");
576
577 let bytes = index(&[("root", root)]).expect("a map");
578 let m = Map::open(&bytes).expect("readable");
579
580 let d = field_key("duration_nano");
581 assert!(probe(d, Op::Gt, Some(1_000_000_000), Some(1e9)).maybe(&m));
582 assert!(!probe(d, Op::Gt, Some(2_000_000_000), Some(2e9)).maybe(&m));
583
584 assert!(!probe(field_key("count"), Op::Gte, Some(0), Some(0.0)).maybe(&m));
587 assert!(!probe(field_key("body"), Op::Gte, Some(0), Some(0.0)).maybe(&m));
588
589 let r = field_key("ratio");
591 assert!(probe(r, Op::Lte, None, Some(0.25)).maybe(&m));
592 assert!(!probe(r, Op::Gt, None, Some(0.25)).maybe(&m));
593 }
594
595 #[test]
596 fn too_many_keys_writes_nothing_rather_than_a_map_nobody_wants() {
597 let mut b = Builder::default();
598 for i in 0..=MAX_KEYS {
599 b.int(attr_key(&format!("k{i}")), i as i64);
600 }
601 assert!(b.build().is_none(), "over the cap, so no file");
602 assert!(Builder::default().build().is_none(), "nothing to say");
603 }
604}