diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-02-05 20:31:55 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-02-05 20:31:55 +0100 |
| commit | 65ca3f3450d0067668111f6e13cc3089768c9efe (patch) | |
| tree | 89dceed4f711d25ff2763e18a4be7e1a59e79507 /database | |
| parent | 1af0468788c0a592a76398206e6c7479384853ec (diff) | |
| download | jellything-65ca3f3450d0067668111f6e13cc3089768c9efe.tar jellything-65ca3f3450d0067668111f6e13cc3089768c9efe.tar.bz2 jellything-65ca3f3450d0067668111f6e13cc3089768c9efe.tar.zst | |
remove read/write distinction for kv transactions; traitify database
Diffstat (limited to 'database')
| -rw-r--r-- | database/src/filter/binning.rs | 52 | ||||
| -rw-r--r-- | database/src/kv/binning.rs (renamed from database/src/filter/mod.rs) | 59 | ||||
| -rw-r--r-- | database/src/kv/counters.rs (renamed from database/src/counters.rs) | 14 | ||||
| -rw-r--r-- | database/src/kv/mod.rs | 217 | ||||
| -rw-r--r-- | database/src/kv/prefix_iterator.rs (renamed from database/src/prefix_iterator.rs) | 0 | ||||
| -rw-r--r-- | database/src/kv/sort/mod.rs (renamed from database/src/sort/mod.rs) | 14 | ||||
| -rw-r--r-- | database/src/kv/sort/none.rs | 50 | ||||
| -rw-r--r-- | database/src/kv/sort/value.rs (renamed from database/src/sort/value.rs) | 51 | ||||
| -rw-r--r-- | database/src/lib.rs | 71 | ||||
| -rw-r--r-- | database/src/query.rs | 51 | ||||
| -rw-r--r-- | database/src/sort/none.rs | 53 | ||||
| -rw-r--r-- | database/src/table.rs | 184 |
12 files changed, 413 insertions, 403 deletions
diff --git a/database/src/filter/binning.rs b/database/src/filter/binning.rs deleted file mode 100644 index 3265e1a..0000000 --- a/database/src/filter/binning.rs +++ /dev/null @@ -1,52 +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 jellyobject::{Object, Path}; - -/// Sorted list of components to bin objects by filterable values. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Binning(Vec<BinningComponent>); - -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -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/kv/binning.rs index c40e4c0..7bec294 100644 --- a/database/src/filter/mod.rs +++ b/database/src/kv/binning.rs @@ -3,12 +3,55 @@ 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, -}; +use jellyobject::{Object, Path}; + +use crate::Filter; + +/// Sorted list of components to bin objects by filterable values. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Binning(Vec<BinningComponent>); + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +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; + } + } + } +} impl Filter { pub fn get_bins(&self) -> Vec<Binning> { @@ -40,12 +83,10 @@ impl Filter { #[cfg(test)] mod test { - use crate::{ - filter::binning::{Binning, BinningComponent}, - query::Filter, - }; use jellyobject::{Path, Tag}; + use crate::{Filter, kv::binning::{Binning, BinningComponent}}; + #[test] fn all() { let f = Filter::All(vec![ diff --git a/database/src/counters.rs b/database/src/kv/counters.rs index 17e2de8..fae7b42 100644 --- a/database/src/counters.rs +++ b/database/src/kv/counters.rs @@ -4,12 +4,10 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ use crate::{ - filter::binning::Binning, - query::Query, - table::{RowNum, TableNum}, + Query, RowNum, + kv::{self, TableNum, binning::Binning}, }; use anyhow::Result; -use jellykv::{ReadTransaction, WriteTransaction}; use jellyobject::Object; use std::collections::HashMap; @@ -17,7 +15,7 @@ pub(crate) struct Counters(pub HashMap<Binning, TableNum>); impl Counters { pub fn update( &self, - txn: &mut dyn WriteTransaction, + txn: &mut dyn jellykv::Transaction, ob: Object<'_>, remove: bool, ) -> Result<()> { @@ -37,7 +35,7 @@ impl Counters { } Ok(()) } - pub fn count(&self, txn: &dyn ReadTransaction, query: &Query) -> Result<Option<u64>> { + pub fn count(&self, txn: &dyn jellykv::Transaction, query: &Query) -> Result<Option<u64>> { let mut total = 0; for binning in query.filter.get_bins() { let Some(b) = self.0.get(&binning) else { @@ -49,10 +47,10 @@ impl Counters { } } -pub fn write_counter(txn: &mut dyn WriteTransaction, t: TableNum, value: u64) -> Result<()> { +pub fn write_counter(txn: &mut dyn jellykv::Transaction, t: TableNum, value: u64) -> Result<()> { txn.set(&t.to_be_bytes(), &value.to_be_bytes()) } -pub fn read_counter(txn: &dyn ReadTransaction, t: TableNum) -> Result<u64> { +pub fn read_counter(txn: &dyn jellykv::Transaction, t: TableNum) -> Result<u64> { Ok(txn .get(&t.to_be_bytes())? .map(|k| k.as_slice().try_into().map(RowNum::from_be_bytes).ok()) diff --git a/database/src/kv/mod.rs b/database/src/kv/mod.rs new file mode 100644 index 0000000..b4bd000 --- /dev/null +++ b/database/src/kv/mod.rs @@ -0,0 +1,217 @@ +/* + 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; +pub mod counters; +pub mod prefix_iterator; +pub mod sort; + +use crate::{ + Database, Query, RowIter, RowNum, Transaction, + kv::{binning::Binning, sort::SortKey}, +}; +use anyhow::Result; +use jellyobject::ObjectBuffer; + +pub type IndexKey = (Binning, SortKey); +pub type TableNum = u32; + +impl<T: jellykv::Store> Database for T { + fn transaction(&self, f: &mut dyn FnMut(&mut dyn Transaction) -> Result<()>) -> Result<()> { + todo!() + } +} + +impl<T: jellykv::Transaction> Transaction for T { + fn insert(&self, entry: ObjectBuffer) -> Result<RowNum> { + todo!() + } + + fn get(&self, row: RowNum) -> Result<Option<ObjectBuffer>> { + todo!() + } + + fn remove(&self, row: RowNum) -> Result<()> { + todo!() + } + + fn update(&self, row: RowNum, entry: ObjectBuffer) -> Result<()> { + todo!() + } + + fn query(&self, query: Query) -> Result<RowIter> { + todo!() + } + + fn query_single(&self, query: Query) -> Result<Option<RowNum>> { + todo!() + } + + fn count(&self, query: Query) -> Result<u64> { + todo!() + } +} + +// pub struct Table { +// id: TableNum, +// pub(crate) counters: HashMap<Binning, TableNum>, +// pub(crate) indices: HashMap<IndexKey, TableNum>, +// } +// impl Table { +// pub fn new(id: u32) -> Self { +// Self { +// id, +// counters: HashMap::new(), +// indices: HashMap::new(), +// } +// } +// fn key(&self, row: RowNum) -> Vec<u8> { +// let mut key = Vec::new(); +// key.extend(self.id.to_be_bytes()); +// key.extend(row.to_be_bytes()); +// key +// } +// pub fn iter(&self, txn: &dyn ReadTransaction) -> Result<impl Iterator<Item = Result<RowNum>>> { +// Ok(PrefixIterator { +// inner: txn.iter(&self.id.to_be_bytes(), false)?, +// prefix: self.id.to_be_bytes().to_vec().into(), +// } +// .map(|r| r.map(|r| RowNum::from_be_bytes(r.try_into().unwrap())))) +// } +// pub fn insert(&self, txn: &mut dyn WriteTransaction, entry: ObjectBuffer) -> Result<RowNum> { +// let mut id_counter = read_counter(txn, self.id)?; +// let row = id_counter; +// id_counter += 1; +// write_counter(txn, self.id, id_counter)?; + +// txn.set(&self.key(row), bytemuck::cast_slice(entry.0.as_slice()))?; + +// let ob = entry.as_object(); +// // for idx in self.indices.values() { +// // idx.add(txn, row, ob)?; +// // } + +// Ok(row) +// } +// pub fn get(&self, txn: &dyn ReadTransaction, row: RowNum) -> Result<Option<ObjectBuffer>> { +// Ok(txn.get(&self.key(row))?.map(ObjectBuffer::from)) +// } +// pub fn remove(&self, db: &mut dyn WriteTransaction, row: RowNum) -> Result<bool> { +// let Some(entry) = self.get(db, row)? else { +// return Ok(false); +// }; +// let ob = entry.as_object(); +// // for index in self.indices.values() { +// // index.remove(db, row, ob)?; +// // } +// db.del(&self.key(row))?; +// Ok(true) +// } +// pub fn update( +// &self, +// txn: &mut dyn WriteTransaction, +// row: RowNum, +// entry: ObjectBuffer, +// ) -> Result<()> { +// let before = self +// .get(txn, row)? +// .ok_or(anyhow!("row to update missing"))?; +// let before = before.as_object(); +// let after = entry.as_object(); + +// txn.set(&self.key(row), bytemuck::cast_slice(entry.0.as_slice()))?; + +// // for index in self.indices.values() { +// // if !index.compare(before, after) { +// // index.remove(txn, row, before)?; +// // index.add(txn, row, after)?; +// // } +// // } + +// Ok(()) +// } +// pub fn query(&self, txn: &dyn ReadTransaction, query: Query) -> Result<RowIter> { +// // query +// // .filter +// // .get_bins() +// // .into_iter() +// // .flat_map(|b| { +// // let ikey = (b, query.sort.key()); +// // self.indices.get(&ikey) +// // }) +// // .map(|i| i.query(txn, &query.sort)) +// todo!() +// } +// pub fn query_single(&self, txn: &dyn ReadTransaction, query: Query) -> Result<Option<RowNum>> { +// self.query(txn, query)?.next().transpose() +// } +// pub fn count(&self, txn: &dyn ReadTransaction, query: Query) -> Result<u64> { +// let mut total = 0; +// for b in query.filter.get_bins() { +// if let Some(&c) = self.counters.get(&b) { +// total += read_counter(txn, c)?; +// } +// } +// Ok(total) +// } +// } + +// #[cfg(test)] +// mod test { +// use { +// table::Table, +// test_shared::{NAME, new_bob}, +// }; +// use anyhow::Result; +// use jellykv::Database; + +// #[test] +// pub fn insert_get() -> Result<()> { +// let db = jellykv::memory::new(); +// let table = Table::new(5); + +// let mut bob_row = 0; +// db.write_transaction(&mut |txn| { +// bob_row = table.insert(txn, new_bob())?; +// Ok(()) +// })?; + +// let mut bob = None; +// db.read_transaction(&mut |txn| { +// bob = table.get(txn, bob_row)?; +// Ok(()) +// })?; + +// assert_eq!(bob.unwrap().as_object().get(NAME).unwrap(), "Bob"); +// Ok(()) +// } + +// #[test] +// pub fn update() -> Result<()> { +// let db = jellykv::memory::new(); +// let table = Table::new(5); + +// let mut bob_row = 0; +// let mut bob = None; + +// db.write_transaction(&mut |txn| { +// bob_row = table.insert(txn, new_bob())?; +// Ok(()) +// })?; +// db.write_transaction(&mut |txn| { +// let better_bob = new_bob().as_object().insert(NAME, "Better Bob"); +// table.update(txn, bob_row, better_bob)?; +// Ok(()) +// })?; +// db.read_transaction(&mut |txn| { +// bob = table.get(txn, bob_row)?; +// Ok(()) +// })?; + +// assert_eq!(bob.unwrap().as_object().get(NAME).unwrap(), "Better Bob"); +// Ok(()) +// } +// } diff --git a/database/src/prefix_iterator.rs b/database/src/kv/prefix_iterator.rs index 9a73558..9a73558 100644 --- a/database/src/prefix_iterator.rs +++ b/database/src/kv/prefix_iterator.rs diff --git a/database/src/sort/mod.rs b/database/src/kv/sort/mod.rs index 58d8eff..403ba73 100644 --- a/database/src/sort/mod.rs +++ b/database/src/kv/sort/mod.rs @@ -4,13 +4,9 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{ - query::{MultiBehaviour, Sort}, - table::IndexKey, -}; -use anyhow::Result; -use jellykv::WriteTransaction; -use jellyobject::{Object, Path}; +use jellyobject::Path; + +use crate::{MultiBehaviour, Sort}; pub mod none; pub mod value; @@ -31,7 +27,3 @@ impl Sort { } } } - -pub fn index_add(txn: &mut dyn WriteTransaction, ik: &IndexKey, ob: &Object) -> Result<()> { - -} diff --git a/database/src/kv/sort/none.rs b/database/src/kv/sort/none.rs new file mode 100644 index 0000000..b4d5db2 --- /dev/null +++ b/database/src/kv/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::{ + RowNum, + kv::{TableNum, binning::Binning}, +}; +use 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) +// } +// fn query(&self, txn: &mut dyn ReadTransaction, _sort: &Sort) -> Result<RowIter> { +// todo!() +// } +// } diff --git a/database/src/sort/value.rs b/database/src/kv/sort/value.rs index 6e42fd7..0d4ceb7 100644 --- a/database/src/sort/value.rs +++ b/database/src/kv/sort/value.rs @@ -4,15 +4,12 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ +use jellyobject::Object; + use crate::{ - filter::binning::Binning, - query::{MultiBehaviour, Sort, ValueSort}, - sort::Index, - table::{RowIter, RowNum, TableNum}, + MultiBehaviour, RowNum, ValueSort, + kv::{TableNum, binning::Binning}, }; -use anyhow::Result; -use jellykv::{ReadTransaction, WriteTransaction}; -use jellyobject::Object; pub struct ValueIndex { id: TableNum, @@ -34,26 +31,26 @@ impl ValueIndex { 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) - } - fn query(&self, txn: &mut dyn ReadTransaction, sort: &Sort) -> Result<RowIter> { - todo!() - } -} +// 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) +// } +// fn query(&self, txn: &mut dyn ReadTransaction, sort: &Sort) -> Result<RowIter> { +// todo!() +// } +// } impl ValueSort { fn apply(&self, ob: Object, keys: &mut Vec<Vec<u8>>) { match self.multi { diff --git a/database/src/lib.rs b/database/src/lib.rs index 00e6905..f6f3fa9 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -3,15 +3,70 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -pub mod filter; -pub mod prefix_iterator; -pub mod query; -pub mod sort; -pub mod table; +pub mod kv; #[cfg(test)] pub mod test_shared; -pub mod counters; -pub type Pad32 = u32; +use anyhow::Result; +use jellyobject::{ObjectBuffer, Path}; -pub use jellykv as kv; +pub type RowNum = u64; +pub type RowIter = Box<dyn Iterator<Item = Result<RowNum>>>; + +pub trait Database { + fn transaction(&self, f: &mut dyn FnMut(&mut dyn Transaction) -> Result<()>) -> Result<()>; +} + +pub trait Transaction { + fn insert(&self, entry: ObjectBuffer) -> Result<RowNum>; + fn get(&self, row: RowNum) -> Result<Option<ObjectBuffer>>; + fn remove(&self, row: RowNum) -> Result<()>; + fn update(&self, row: RowNum, entry: ObjectBuffer) -> Result<()>; + fn query(&self, query: Query) -> Result<RowIter>; + fn query_single(&self, query: Query) -> Result<Option<RowNum>>; + fn count(&self, query: Query) -> Result<u64>; +} + +#[derive(Default)] +pub struct Query { + pub filter: Filter, + pub sort: Sort, +} + +#[derive(Default)] +pub enum Sort { + #[default] + None, + Value(ValueSort), + TextSearch(Path, String), +} + +pub struct ValueSort { + pub order: SortOrder, + pub path: Path, + pub multi: MultiBehaviour, + pub offset: Option<Vec<u8>>, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum MultiBehaviour { + First, + ForEach, + Max, + Min, + Count, +} +pub enum SortOrder { + Ascending, + Descending, +} + +#[derive(Debug, Clone, Default)] +pub enum Filter { + #[default] + True, + All(Vec<Filter>), + Any(Vec<Filter>), + Match(Path, Vec<u8>), + Has(Path), +} diff --git a/database/src/query.rs b/database/src/query.rs deleted file mode 100644 index 002a872..0000000 --- a/database/src/query.rs +++ /dev/null @@ -1,51 +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 jellyobject::Path; - -#[derive(Default)] -pub struct Query { - pub filter: Filter, - pub sort: Sort, -} - -#[derive(Default)] -pub enum Sort { - #[default] - None, - Value(ValueSort), - TextSearch(Path, String), -} - -pub struct ValueSort { - pub order: SortOrder, - pub path: Path, - pub multi: MultiBehaviour, - pub offset: Option<Vec<u8>>, -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum MultiBehaviour { - First, - ForEach, - Max, - Min, - Count, -} -pub enum SortOrder { - Ascending, - Descending, -} - -#[derive(Debug, Clone, Default)] -pub enum Filter { - #[default] - True, - All(Vec<Filter>), - Any(Vec<Filter>), - Match(Path, Vec<u8>), - Has(Path), -} diff --git a/database/src/sort/none.rs b/database/src/sort/none.rs deleted file mode 100644 index efaa7f8..0000000 --- a/database/src/sort/none.rs +++ /dev/null @@ -1,53 +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::{ - filter::binning::Binning, - query::Sort, - table::{RowIter, RowNum, TableNum}, -}; -use anyhow::Result; -use jellykv::{ReadTransaction, WriteTransaction}; -use 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) - } - fn query(&self, txn: &mut dyn ReadTransaction, _sort: &Sort) -> Result<RowIter> { - todo!() - } -} diff --git a/database/src/table.rs b/database/src/table.rs deleted file mode 100644 index ee8d66b..0000000 --- a/database/src/table.rs +++ /dev/null @@ -1,184 +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::{ - counters::{read_counter, write_counter}, - filter::binning::Binning, - prefix_iterator::PrefixIterator, - query::Query, - sort::{Index, SortKey}, -}; -use anyhow::{Result, anyhow}; -use jellykv::{ReadTransaction, WriteTransaction}; -use jellyobject::ObjectBuffer; -use std::collections::HashMap; - -pub type TableNum = u32; -pub type RowNum = u64; - -pub type RowIter = Box<dyn Iterator<Item = Result<RowNum>>>; -pub type IndexKey = (Binning, SortKey); - -pub struct Table { - id: TableNum, - pub(crate) counters: HashMap<Binning, TableNum>, - pub(crate) indices: HashMap<IndexKey, TableNum>, -} -impl Table { - pub fn new(id: u32) -> Self { - Self { - id, - counters: HashMap::new(), - indices: HashMap::new(), - } - } - fn key(&self, row: RowNum) -> Vec<u8> { - let mut key = Vec::new(); - key.extend(self.id.to_be_bytes()); - key.extend(row.to_be_bytes()); - key - } - pub fn iter(&self, txn: &dyn ReadTransaction) -> Result<impl Iterator<Item = Result<RowNum>>> { - Ok(PrefixIterator { - inner: txn.iter(&self.id.to_be_bytes(), false)?, - prefix: self.id.to_be_bytes().to_vec().into(), - } - .map(|r| r.map(|r| RowNum::from_be_bytes(r.try_into().unwrap())))) - } - pub fn insert(&self, txn: &mut dyn WriteTransaction, entry: ObjectBuffer) -> Result<RowNum> { - let mut id_counter = read_counter(txn, self.id)?; - let row = id_counter; - id_counter += 1; - write_counter(txn, self.id, id_counter)?; - - txn.set(&self.key(row), bytemuck::cast_slice(entry.0.as_slice()))?; - - let ob = entry.as_object(); - for idx in self.indices.values() { - idx.add(txn, row, ob)?; - } - - Ok(row) - } - pub fn get(&self, txn: &dyn ReadTransaction, row: RowNum) -> Result<Option<ObjectBuffer>> { - Ok(txn.get(&self.key(row))?.map(ObjectBuffer::from)) - } - pub fn remove(&self, db: &mut dyn WriteTransaction, row: RowNum) -> Result<bool> { - let Some(entry) = self.get(db, row)? else { - return Ok(false); - }; - let ob = entry.as_object(); - for index in self.indices.values() { - index.remove(db, row, ob)?; - } - db.del(&self.key(row))?; - Ok(true) - } - pub fn update( - &self, - txn: &mut dyn WriteTransaction, - row: RowNum, - entry: ObjectBuffer, - ) -> Result<()> { - let before = self - .get(txn, row)? - .ok_or(anyhow!("row to update missing"))?; - let before = before.as_object(); - let after = entry.as_object(); - - txn.set(&self.key(row), bytemuck::cast_slice(entry.0.as_slice()))?; - - for index in self.indices.values() { - if !index.compare(before, after) { - index.remove(txn, row, before)?; - index.add(txn, row, after)?; - } - } - - Ok(()) - } - pub fn query(&self, txn: &dyn ReadTransaction, query: Query) -> Result<RowIter> { - // query - // .filter - // .get_bins() - // .into_iter() - // .flat_map(|b| { - // let ikey = (b, query.sort.key()); - // self.indices.get(&ikey) - // }) - // .map(|i| i.query(txn, &query.sort)) - todo!() - } - pub fn query_single(&self, txn: &dyn ReadTransaction, query: Query) -> Result<Option<RowNum>> { - self.query(txn, query)?.next().transpose() - } - pub fn count(&self, txn: &dyn ReadTransaction, query: Query) -> Result<u64> { - let mut total = 0; - for b in query.filter.get_bins() { - if let Some(&c) = self.counters.get(&b) { - total += read_counter(txn, c)?; - } - } - Ok(total) - } -} - -#[cfg(test)] -mod test { - use crate::{ - table::Table, - test_shared::{NAME, new_bob}, - }; - use anyhow::Result; - use jellykv::Database; - - #[test] - pub fn insert_get() -> Result<()> { - let db = jellykv::memory::new(); - let table = Table::new(5); - - let mut bob_row = 0; - db.write_transaction(&mut |txn| { - bob_row = table.insert(txn, new_bob())?; - Ok(()) - })?; - - let mut bob = None; - db.read_transaction(&mut |txn| { - bob = table.get(txn, bob_row)?; - Ok(()) - })?; - - assert_eq!(bob.unwrap().as_object().get(NAME).unwrap(), "Bob"); - Ok(()) - } - - #[test] - pub fn update() -> Result<()> { - let db = jellykv::memory::new(); - let table = Table::new(5); - - let mut bob_row = 0; - let mut bob = None; - - db.write_transaction(&mut |txn| { - bob_row = table.insert(txn, new_bob())?; - Ok(()) - })?; - db.write_transaction(&mut |txn| { - let better_bob = new_bob().as_object().insert(NAME, "Better Bob"); - table.update(txn, bob_row, better_bob)?; - Ok(()) - })?; - db.read_transaction(&mut |txn| { - bob = table.get(txn, bob_row)?; - Ok(()) - })?; - - assert_eq!(bob.unwrap().as_object().get(NAME).unwrap(), "Better Bob"); - Ok(()) - } -} |