use std::collections::HashMap;
use crate::errors::Result;
use crate::value::Value;
pub trait RowIndex {
fn lookup(&self, value: &Value) -> Vec<usize>;
fn lookup_rows(&self, value: &Value) -> Option<Result<Vec<Value>>> {
let _ = value;
None
}
}
#[derive(Clone, Debug, Default)]
pub struct HashIndex {
buckets: HashMap<String, Vec<usize>>,
count: usize,
}
impl HashIndex {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, value: &Value, position: usize) {
if let Some(key) = index_key(value) {
self.buckets.entry(key).or_default().push(position);
self.count += 1;
}
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn positions(&self, value: &Value) -> &[usize] {
index_key(value)
.and_then(|key| self.buckets.get(&key))
.map_or(&[][..], Vec::as_slice)
}
}
impl RowIndex for HashIndex {
fn lookup(&self, value: &Value) -> Vec<usize> {
self.positions(value).to_vec()
}
}
pub fn index_key(v: &Value) -> Option<String> {
let mut out = String::new();
push_key(v, &mut out).then_some(out)
}
fn push_key(v: &Value, out: &mut String) -> bool {
match v {
Value::Undefined | Value::Null => out.push('_'),
Value::Bool(true) => out.push('t'),
Value::Bool(false) => out.push('f'),
Value::Number(n) => {
if n.is_nan() {
return false;
}
out.push('n');
out.push_str(&v.to_string());
}
Value::Str(s) => {
out.push('s');
out.push_str(s);
}
Value::Array(items) => {
out.push_str("a[");
for item in items {
if !push_nested(item, out) {
return false;
}
}
out.push(']');
}
Value::Object(o) => {
let mut entries: Vec<(&str, &Value)> = o.iter().collect();
entries.sort_by_key(|(a, _)| *a);
out.push_str("o{");
for (name, value) in entries {
out.push_str(&name.len().to_string());
out.push(':');
out.push_str(name);
out.push('=');
if !push_nested(value, out) {
return false;
}
}
out.push('}');
}
Value::Range(r) => {
out.push('r');
out.push(if r.exclusive_end { 'x' } else { 'i' });
for bound in [&r.lo, &r.hi] {
match bound {
None => out.push('-'),
Some(b) => {
if !push_nested(b, out) {
return false;
}
}
}
}
}
}
true
}
fn push_nested(v: &Value, out: &mut String) -> bool {
let mut inner = String::new();
if !push_key(v, &mut inner) {
return false;
}
out.push_str(&inner.len().to_string());
out.push(':');
out.push_str(&inner);
true
}
pub fn intersect_positions(a: &[usize], b: &[usize]) -> Vec<usize> {
let mut out = Vec::with_capacity(a.len().min(b.len()));
let (mut i, mut j) = (0, 0);
while i < a.len() && j < b.len() {
match a[i].cmp(&b[j]) {
std::cmp::Ordering::Less => i += 1,
std::cmp::Ordering::Greater => j += 1,
std::cmp::Ordering::Equal => {
out.push(a[i]);
i += 1;
j += 1;
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn num(n: f64) -> Value {
Value::Number(n)
}
#[test]
fn buckets_follow_equality_and_keep_row_order() {
let mut idx = HashIndex::new();
let keys = [
num(1.0),
Value::Null,
num(-0.0),
Value::Undefined,
num(0.0),
num(f64::NAN),
num(1.0),
];
for (i, k) in keys.iter().enumerate() {
idx.add(k, i);
}
assert_eq!(idx.len(), 6, "the NaN row is not indexed");
assert_eq!(idx.positions(&num(1.0)), &[0, 6]);
assert_eq!(idx.lookup(&Value::Null), vec![1, 3]);
assert_eq!(idx.positions(&Value::Undefined), &[1, 3]);
assert_eq!(idx.positions(&num(0.0)), &[2, 4]);
assert_eq!(idx.positions(&num(-0.0)), &[2, 4]);
assert!(idx.positions(&num(f64::NAN)).is_empty());
assert!(idx.lookup(&Value::Str("1".to_owned())).is_empty());
}
#[test]
fn intersect_is_an_ordered_merge() {
assert_eq!(intersect_positions(&[0, 1, 3, 5], &[1, 2, 3, 6]), [1, 3]);
assert_eq!(intersect_positions(&[], &[1]), Vec::<usize>::new());
assert_eq!(intersect_positions(&[1], &[]), Vec::<usize>::new());
assert_eq!(intersect_positions(&[2, 4], &[2, 4]), [2, 4]);
}
}