use std::fmt::Display; /* 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::{ MultiBehaviour, Sort, kv::{ T_INDICES, binning::{Binning, BinningComponent}, }, }; use jellyobject::{Path, Tag}; #[derive(Debug, PartialEq, Eq)] pub struct IndexKey(pub Binning, pub SortKey); #[derive(Debug, Hash, PartialEq, Eq)] pub enum SortKey { None, Count, Random, Value(Path, MultiBehaviour), Text(Path), } impl Sort { pub fn key(&self) -> SortKey { match self { Sort::None => SortKey::None, Sort::Random(_) => SortKey::Random, Sort::Value(vs) => SortKey::Value(vs.path.clone(), vs.multi), Sort::TextSearch(p, _) => SortKey::Text(p.to_owned()), } } } impl IndexKey { pub fn to_bytes(&self) -> Vec { let mut out = Vec::new(); out.extend(T_INDICES.to_be_bytes()); self.0.write(&mut out); self.1.write(&mut out); out } pub fn from_bytes(mut b: &[u8]) -> Self { b = &b[4..]; // remove subtree prefix let binning = Binning::read(&mut b); let sort = SortKey::read(&mut b); assert!(b.is_empty(), "index key deser left-over bytes"); Self(binning, sort) } } impl Binning { fn read(b: &mut &[u8]) -> Self { let len = b[0]; *b = &b[1..]; let mut o = Vec::new(); for _ in 0..len { let ty = b[0]; *b = &b[1..]; o.push(match ty { 0 => BinningComponent::Has(read_path(b)), 1 => BinningComponent::Match(read_path(b)), _ => unreachable!(), }); } Self(o) } fn write(&self, out: &mut Vec) { assert!(out.len() < 256); out.push(self.0.len() as u8); for b in &self.0 { match b { BinningComponent::Has(path) => { out.push(0); write_path(path, out); } BinningComponent::Match(path) => { out.push(1); write_path(path, out); } } } } } impl SortKey { fn read(b: &mut &[u8]) -> Self { let ty = b[0]; *b = &b[1..]; match ty { 0 => SortKey::None, 4 => SortKey::Random, 1 => SortKey::Count, 2 => SortKey::Value(read_path(b), { let mb = b[0]; *b = &b[1..]; match mb { 0 => MultiBehaviour::First, 1 => MultiBehaviour::ForEach, 2 => MultiBehaviour::Max, 3 => MultiBehaviour::Min, 4 => MultiBehaviour::Count, _ => unreachable!(), } }), 3 => SortKey::Text(read_path(b)), _ => unreachable!(), } } fn write(&self, out: &mut Vec) { match self { SortKey::None => out.push(0), SortKey::Random => out.push(4), SortKey::Count => out.push(1), SortKey::Value(path, multi_behaviour) => { out.push(2); write_path(path, out); out.push(match multi_behaviour { MultiBehaviour::First => 0, MultiBehaviour::ForEach => 1, MultiBehaviour::Max => 2, MultiBehaviour::Min => 3, MultiBehaviour::Count => 4, }); } SortKey::Text(path) => { out.push(3); write_path(path, out); } } } } impl Display for IndexKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} | {}", self.0, self.1) } } impl Display for Binning { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for (i, b) in self.0.iter().enumerate() { if i > 0 { write!(f, " ")?; } write!(f, "{b}")?; } Ok(()) } } impl Display for BinningComponent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { BinningComponent::Has(path) => write!(f, "H({path})"), BinningComponent::Match(path) => write!(f, "M({path})"), } } } impl Display for SortKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SortKey::None => write!(f, "none"), SortKey::Random => write!(f, "random"), SortKey::Count => write!(f, "count"), SortKey::Value(path, multi) => write!(f, "value({path}, {multi})"), SortKey::Text(path) => write!(f, "text({path})"), } } } impl Display for MultiBehaviour { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { MultiBehaviour::First => "first", MultiBehaviour::ForEach => "for_each", MultiBehaviour::Max => "max", MultiBehaviour::Min => "min", MultiBehaviour::Count => "count", }) } } fn write_path(path: &Path, out: &mut Vec) { assert!(path.0.len() < 256); out.push(path.0.len() as u8); for c in &path.0 { out.extend(c.0.to_be_bytes()); } } fn read_path(b: &mut &[u8]) -> Path { let len = b[0]; *b = &b[1..]; let mut o = Vec::new(); for _ in 0..len { o.push(Tag(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))); *b = &b[4..]; } Path(o) } #[cfg(test)] mod test { use jellyobject::{Path, Tag}; use crate::kv::{ binning::{Binning, BinningComponent}, index_key::{IndexKey, SortKey}, }; #[test] fn serialize() { let t = |k: IndexKey| { let b = k.to_bytes(); let k2 = IndexKey::from_bytes(&b); assert_eq!(k, k2); }; t(IndexKey( Binning(vec![BinningComponent::Match(Path(vec![Tag(1001)]))]), SortKey::None, )); t(IndexKey( Binning(vec![BinningComponent::Has(Path(vec![Tag(123), Tag(456)]))]), SortKey::Text(Path(vec![Tag(789)])), )); } }