/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin */ use crate::{Filter, Value}; use jellyobject::{Object, Path}; /// Sorted list of components to bin objects by filtered values. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Binning(pub Vec); #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] pub enum BinningComponent { Has(Path), Match(Path), } impl Binning { pub fn new(mut comps: Vec) -> Self { comps.sort(); Self(comps) } pub fn apply(&self, ob: &Object, keys: &mut Vec>) { for f in &self.0 { f.apply(ob, keys); } } } impl BinningComponent { pub fn apply(&self, ob: &Object, keys: &mut Vec>) { match self { BinningComponent::Has(path) => { if path.get_matching_value(ob).is_none() { keys.clear(); } } BinningComponent::Match(path) => { let mut new_out = Vec::new(); for value in path.get_matching_values(ob) { for mut co in keys.clone() { co.extend((value.len() as u32).to_be_bytes()); co.extend(value); new_out.push(co); } } *keys = new_out; } } } } impl Filter<'_> { pub fn get_binnings(&self) -> Vec { self.get_bins_inner() .into_iter() .map(|e| Binning(e.into_iter().map(|(e, _)| e).collect())) .collect() } pub fn get_bins(&self) -> Vec<(Binning, Vec)> { self.get_bins_inner() .into_iter() .map(|e| { let (a, b): (Vec, Vec>) = e.into_iter().unzip(); (Binning(a), b.into_iter().flatten().collect()) }) .collect() } fn get_bins_inner(&self) -> Vec)>> { match self { Filter::True => vec![vec![]], Filter::All(filters) => { let mut o = vec![vec![]]; for filter in filters { let mut new_o = Vec::new(); for par in filter.get_bins_inner() { for mut prev in o.clone() { prev.extend(par.clone()); new_o.push(prev); } } o = new_o; } o } Filter::Any(filters) => filters.iter().flat_map(|f| f.get_bins_inner()).collect(), Filter::Match(path, value) => { vec![vec![(BinningComponent::Match(path.to_owned()), { let mut co = Vec::new(); write_value_with_len(value, &mut co); co })]] } Filter::Has(path) => { vec![vec![(BinningComponent::Has(path.to_owned()), vec![])]] } } } } pub fn write_value_with_len(value: &Value, out: &mut Vec) { match value { Value::Tag(tag) => { out.extend(4u32.to_be_bytes()); out.extend(tag.0.to_be_bytes()); } Value::U32(x) => { out.extend(4u32.to_be_bytes()); out.extend(x.to_be_bytes()); } Value::U64(x) => { out.extend(8u32.to_be_bytes()); out.extend(x.to_be_bytes()); } Value::I64(x) => { out.extend(8u32.to_be_bytes()); out.extend(x.to_be_bytes()); } Value::String(s) => { out.extend((s.len() as u32).to_be_bytes()); out.extend(s.as_bytes()); } Value::Binary(s) => { out.extend((s.len() as u32).to_be_bytes()); out.extend(&**s); } } } #[cfg(test)] mod test { use jellyobject::{Path, Tag}; use crate::{ Filter, kv::binning::{Binning, BinningComponent}, }; #[test] fn all() { let f = Filter::All(vec![ Filter::Has(Path(vec![Tag(0)])), Filter::Has(Path(vec![Tag(1)])), ]); let bins = vec![Binning(vec![ BinningComponent::Has(Path(vec![Tag(0)])), BinningComponent::Has(Path(vec![Tag(1)])), ])]; assert_eq!(f.get_binnings(), bins) } #[test] fn any() { let f = Filter::Any(vec![ Filter::Has(Path(vec![Tag(0)])), Filter::Has(Path(vec![Tag(1)])), ]); let bins = vec![ Binning::new(vec![BinningComponent::Has(Path(vec![Tag(0)]))]), Binning::new(vec![BinningComponent::Has(Path(vec![Tag(1)]))]), ]; assert_eq!(f.get_binnings(), bins) } #[test] fn nested() { let f = Filter::All(vec![ Filter::Any(vec![ Filter::Has(Path(vec![Tag(0)])), Filter::Has(Path(vec![Tag(1)])), ]), Filter::Any(vec![ Filter::Has(Path(vec![Tag(2)])), Filter::Has(Path(vec![Tag(3)])), ]), ]); let bins = vec![ Binning::new(vec![ BinningComponent::Has(Path(vec![Tag(0)])), BinningComponent::Has(Path(vec![Tag(2)])), ]), Binning::new(vec![ BinningComponent::Has(Path(vec![Tag(1)])), BinningComponent::Has(Path(vec![Tag(2)])), ]), Binning::new(vec![ BinningComponent::Has(Path(vec![Tag(0)])), BinningComponent::Has(Path(vec![Tag(3)])), ]), Binning::new(vec![ BinningComponent::Has(Path(vec![Tag(1)])), BinningComponent::Has(Path(vec![Tag(3)])), ]), ]; assert_eq!(f.get_binnings(), bins) } }