diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-01-14 20:28:54 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-01-14 20:28:54 +0100 |
| commit | abf25f340c11111369b69c13c34d8fed9d4f0da8 (patch) | |
| tree | 1e913a4affe3303a17a222c8215a51424d3b71b6 /database/src | |
| parent | b5ff460e938779be4eeab292c2cc1d436b93c137 (diff) | |
| download | jellything-abf25f340c11111369b69c13c34d8fed9d4f0da8.tar jellything-abf25f340c11111369b69c13c34d8fed9d4f0da8.tar.bz2 jellything-abf25f340c11111369b69c13c34d8fed9d4f0da8.tar.zst | |
db binning and sorts
Diffstat (limited to 'database/src')
| -rw-r--r-- | database/src/filter/binning.rs | 52 | ||||
| -rw-r--r-- | database/src/filter/mod.rs | 106 | ||||
| -rw-r--r-- | database/src/indices/rating.rs | 41 | ||||
| -rw-r--r-- | database/src/indices/reference.rs | 71 | ||||
| -rw-r--r-- | database/src/lib.rs | 42 | ||||
| -rw-r--r-- | database/src/query.rs | 43 | ||||
| -rw-r--r-- | database/src/sort/mod.rs (renamed from database/src/indices/mod.rs) | 5 | ||||
| -rw-r--r-- | database/src/sort/none.rs | 50 | ||||
| -rw-r--r-- | database/src/sort/value.rs | 86 | ||||
| -rw-r--r-- | database/src/table.rs | 2 |
10 files changed, 344 insertions, 154 deletions
diff --git a/database/src/filter/binning.rs b/database/src/filter/binning.rs new file mode 100644 index 0000000..977d5d7 --- /dev/null +++ b/database/src/filter/binning.rs @@ -0,0 +1,52 @@ +/* + 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 <metamuffin.org> +*/ + +use jellycommon::jellyobject::{Object, path::Path}; + +/// Sorted list of components to bin objects by filterable values. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Binning(Vec<BinningComponent>); + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] +pub enum BinningComponent { + Has(Path), + Match(Path), +} + +impl Binning { + pub fn new(mut comps: Vec<BinningComponent>) -> Self { + comps.sort(); + Self(comps) + } + pub fn apply(&self, ob: Object<'_>, keys: &mut Vec<Vec<u8>>) { + for f in &self.0 { + f.apply(ob, keys); + } + } +} +impl BinningComponent { + pub fn apply(&self, ob: Object<'_>, keys: &mut Vec<Vec<u8>>) { + match self { + BinningComponent::Has(path) => { + let has = path.get_matching_value(ob).is_some(); + for co in keys { + co.push(has as u8) + } + } + 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((co.len() as u32).to_be_bytes()); + co.extend(value); + new_out.push(co); + } + } + *keys = new_out; + } + } + } +} diff --git a/database/src/filter/mod.rs b/database/src/filter/mod.rs new file mode 100644 index 0000000..30a22de --- /dev/null +++ b/database/src/filter/mod.rs @@ -0,0 +1,106 @@ +/* + 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 <metamuffin.org> +*/ +pub mod binning; + +use crate::{ + filter::binning::{Binning, BinningComponent}, + query::Filter, +}; + +impl Filter { + pub fn get_bins(&self) -> Vec<Binning> { + fn recurse(f: &Filter) -> Vec<Vec<BinningComponent>> { + match f { + Filter::All(filters) => { + let mut o = vec![vec![]]; + for filter in filters { + let mut new_o = Vec::new(); + for par in recurse(filter) { + 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| recurse(f)).collect(), + Filter::Match(path, _) => vec![vec![BinningComponent::Match(path.to_owned())]], + Filter::Has(path) => vec![vec![BinningComponent::Has(path.to_owned())]], + } + } + recurse(self).into_iter().map(Binning::new).collect() + } +} + +#[cfg(test)] +mod test { + use crate::{ + filter::binning::{Binning, BinningComponent}, + query::Filter, + }; + use jellycommon::jellyobject::{Tag, path::Path}; + + #[test] + fn all() { + let f = Filter::All(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)])), + BinningComponent::Has(Path(vec![Tag(1)])), + ])]; + assert_eq!(f.get_bins(), 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_bins(), 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_bins(), bins) + } +} diff --git a/database/src/indices/rating.rs b/database/src/indices/rating.rs deleted file mode 100644 index 003d327..0000000 --- a/database/src/indices/rating.rs +++ /dev/null @@ -1,41 +0,0 @@ -/* - 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 <metamuffin.org> -*/ - -use crate::{ - backends::WriteTransaction, - indices::Index, - table::{RowNum, TableNum}, -}; -use anyhow::Result; -use bytemuck::NoUninit; -use jellycommon::jellyobject::{Object, Tag, TypedTag}; - -#[derive(Clone)] -pub struct RatingIndex { - id: TableNum, - tag: TypedTag<Object<'static>>, -} - -#[repr(C)] -#[derive(NoUninit, Clone, Copy)] -struct Key(TableNum, Tag, u32, u64, RowNum); - -impl RatingIndex { - pub fn new(id: TableNum, tag: TypedTag<Object<'static>>) -> Self { - Self { id, tag } - } -} -impl Index for RatingIndex { - fn add(&self, db: &mut dyn WriteTransaction, id: RowNum, val: Object) -> Result<()> { - todo!() - } - fn remove(&self, db: &mut dyn WriteTransaction, id: RowNum, val: Object) -> Result<()> { - todo!() - } - fn compare(&self, before: Object, after: Object) -> bool { - todo!() - } -} diff --git a/database/src/indices/reference.rs b/database/src/indices/reference.rs deleted file mode 100644 index 66efa24..0000000 --- a/database/src/indices/reference.rs +++ /dev/null @@ -1,71 +0,0 @@ -/* - 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 <metamuffin.org> -*/ - -use crate::{ - backends::{ReadTransaction, WriteTransaction}, - indices::Index, - prefix_iterator::PrefixIterator, - table::{RowNum, TableNum}, -}; -use anyhow::Result; -use bytemuck::{NoUninit, bytes_of}; -use jellycommon::jellyobject::{Object, TypedTag}; - -pub struct ReferenceIndex { - id: TableNum, - tag: TypedTag<RowNum>, -} - -#[repr(C)] -#[derive(NoUninit, Clone, Copy)] -pub struct Key(TableNum, RowNum, RowNum); - -impl ReferenceIndex { - pub fn new(id: TableNum, tag: TypedTag<RowNum>) -> Self { - Self { id, tag } - } - pub fn lookup<'a>( - &self, - db: &'a dyn ReadTransaction, - to: RowNum, - ) -> Result<PrefixIterator<'a>> { - let mut prefix = Vec::new(); - prefix.extend(self.id.to_be_bytes()); - prefix.extend(to.to_be_bytes()); - Ok(PrefixIterator { - inner: db.iter(&prefix, false)?, - prefix: prefix.into(), - }) - } -} -impl Index for ReferenceIndex { - fn add(&self, db: &mut dyn WriteTransaction, id: RowNum, val: Object) -> Result<()> { - for to in val.iter(self.tag) { - db.set(bytes_of(&Key(self.id, to, id)), &[])?; - } - Ok(()) - } - fn remove(&self, db: &mut dyn WriteTransaction, id: RowNum, val: Object) -> Result<()> { - for to in val.iter(self.tag) { - db.del(bytes_of(&Key(self.id, to, id)))?; - } - Ok(()) - } - fn compare(&self, before: Object, after: Object) -> bool { - let mut before = before.iter(self.tag); - let mut after = after.iter(self.tag); - loop { - let b = before.next(); - let a = after.next(); - if a.is_none() && b.is_none() { - break true; - } - if a != b { - break false; - } - } - } -} diff --git a/database/src/lib.rs b/database/src/lib.rs index df314b6..9c372b4 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -4,46 +4,10 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ pub mod backends; -pub mod indices; +pub mod sort; pub mod prefix_iterator; pub mod table; +pub mod query; +pub mod filter; pub type Pad32 = u32; - -use jellycommon::jellyobject::Tag; - -pub struct Query { - pub filter: Filter, - pub sort: Sort, -} - -pub enum Sort { - None, - Value(Vec<ValueSortComponent>), - TextSearch(Path, String), -} -pub struct ValueSortComponent { - pub order: SortOrder, - pub path: Path, - pub multi: MultiBehaviour, - pub offset: Option<Vec<u8>>, -} -pub enum MultiBehaviour { - First, - ForEach, - Max, - Min, - Count, -} -pub enum SortOrder { - Ascending, - Descending, -} -pub enum Filter { - And(Vec<Filter>), - Or(Vec<Filter>), - Match(Path, Vec<u8>), - Has(Path), -} - -pub struct Path(pub Vec<Tag>); diff --git a/database/src/query.rs b/database/src/query.rs new file mode 100644 index 0000000..7d5c11c --- /dev/null +++ b/database/src/query.rs @@ -0,0 +1,43 @@ +/* + 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 <metamuffin.org> +*/ + +use jellycommon::jellyobject::path::Path; + +pub struct Query { + pub filter: Filter, + pub sort: Sort, +} + +pub enum Sort { + None, + Value(ValueSortComponent), + TextSearch(Path, String), +} +pub struct ValueSortComponent { + pub order: SortOrder, + pub path: Path, + pub multi: MultiBehaviour, + pub offset: Option<Vec<u8>>, +} +pub enum MultiBehaviour { + First, + ForEach, + Max, + Min, + Count, +} +pub enum SortOrder { + Ascending, + Descending, +} + +#[derive(Debug, Clone)] +pub enum Filter { + All(Vec<Filter>), + Any(Vec<Filter>), + Match(Path, Vec<u8>), + Has(Path), +} diff --git a/database/src/indices/mod.rs b/database/src/sort/mod.rs index 2d8642a..6473f5d 100644 --- a/database/src/indices/mod.rs +++ b/database/src/sort/mod.rs @@ -8,12 +8,13 @@ use crate::{backends::WriteTransaction, table::RowNum}; use anyhow::Result; use jellycommon::jellyobject::Object; -pub mod reference; -pub mod rating; +pub mod none; +pub mod value; pub trait Index { fn add(&self, db: &mut dyn WriteTransaction, row: RowNum, val: Object) -> Result<()>; fn remove(&self, db: &mut dyn WriteTransaction, row: RowNum, val: Object) -> Result<()>; + /// Might return true if objects are identical for this index; false if not or uncertain fn compare(&self, before: Object, after: Object) -> bool { let _ = (before, after); false diff --git a/database/src/sort/none.rs b/database/src/sort/none.rs new file mode 100644 index 0000000..b17cb29 --- /dev/null +++ b/database/src/sort/none.rs @@ -0,0 +1,50 @@ +/* + 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 <metamuffin.org> +*/ + +use crate::{ + backends::WriteTransaction, + filter::binning::Binning, + sort::Index, + table::{RowNum, TableNum}, +}; +use anyhow::Result; +use jellycommon::jellyobject::Object; + +pub struct UnsortedIndex { + id: TableNum, + binning: Binning, +} + +impl UnsortedIndex { + pub fn new(id: TableNum, binning: Binning) -> Self { + Self { id, binning } + } + fn keys(&self, id: RowNum, ob: Object) -> Vec<Vec<u8>> { + let mut keys = vec![self.id.to_be_bytes().to_vec()]; + self.binning.apply(ob, &mut keys); + for k in &mut keys { + k.extend(id.to_ne_bytes()); + } + keys + } +} +impl Index for UnsortedIndex { + fn add(&self, db: &mut dyn WriteTransaction, id: RowNum, ob: Object) -> Result<()> { + for key in self.keys(id, ob) { + db.set(&key, &[])?; + } + Ok(()) + } + fn remove(&self, db: &mut dyn WriteTransaction, id: RowNum, ob: Object) -> Result<()> { + for key in self.keys(id, ob) { + db.del(&key)?; + } + Ok(()) + } + fn compare(&self, before: Object, after: Object) -> bool { + self.keys(0, before) == self.keys(0, after) + } +} diff --git a/database/src/sort/value.rs b/database/src/sort/value.rs new file mode 100644 index 0000000..0381fe0 --- /dev/null +++ b/database/src/sort/value.rs @@ -0,0 +1,86 @@ +/* + 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 <metamuffin.org> +*/ + +use crate::{ + backends::WriteTransaction, + filter::binning::Binning, + query::{MultiBehaviour, ValueSortComponent}, + sort::Index, + table::{RowNum, TableNum}, +}; +use anyhow::Result; +use jellycommon::jellyobject::Object; + +pub struct ValueIndex { + id: TableNum, + binning: Binning, + sort: ValueSortComponent, +} + +impl ValueIndex { + pub fn new(id: TableNum, binning: Binning, sort: ValueSortComponent) -> Self { + Self { id, binning, sort } + } + fn keys(&self, id: RowNum, ob: Object) -> Vec<Vec<u8>> { + let mut keys = vec![self.id.to_be_bytes().to_vec()]; + self.binning.apply(ob, &mut keys); + self.sort.apply(ob, &mut keys); + for k in &mut keys { + k.extend(id.to_ne_bytes()); + } + keys + } +} +impl Index for ValueIndex { + fn add(&self, db: &mut dyn WriteTransaction, id: RowNum, ob: Object) -> Result<()> { + for key in self.keys(id, ob) { + db.set(&key, &[])?; + } + Ok(()) + } + fn remove(&self, db: &mut dyn WriteTransaction, id: RowNum, ob: Object) -> Result<()> { + for key in self.keys(id, ob) { + db.del(&key)?; + } + Ok(()) + } + fn compare(&self, before: Object, after: Object) -> bool { + self.keys(0, before) == self.keys(0, after) + } +} +impl ValueSortComponent { + fn apply(&self, ob: Object, keys: &mut Vec<Vec<u8>>) { + match self.multi { + MultiBehaviour::First => { + if let Some(val) = self.path.get_matching_value(ob) { + for k in keys.iter_mut() { + k.extend(val); + } + } else { + keys.clear(); + } + } + MultiBehaviour::ForEach => { + let mut keys_out = Vec::new(); + for val in self.path.get_matching_values(ob) { + for mut k in keys.clone() { + k.extend(val); + keys_out.push(k); + } + } + *keys = keys_out + } + MultiBehaviour::Max => todo!(), + MultiBehaviour::Min => todo!(), + MultiBehaviour::Count => { + let count = self.path.get_matching_values(ob).len() as u32; + for k in keys.iter_mut() { + k.extend(count.to_be_bytes()); + } + } + } + } +} diff --git a/database/src/table.rs b/database/src/table.rs index 16b9ed5..a48dd7e 100644 --- a/database/src/table.rs +++ b/database/src/table.rs @@ -6,7 +6,7 @@ use crate::{ backends::{ReadTransaction, WriteTransaction}, - indices::Index, + sort::Index, }; use anyhow::Result; use jellycommon::jellyobject::ObjectBuffer; |