diff options
Diffstat (limited to 'database')
| -rw-r--r-- | database/src/kv/index.rs | 1 | ||||
| -rw-r--r-- | database/src/kv/index_key.rs | 35 | ||||
| -rw-r--r-- | database/src/kv/mod.rs | 39 | ||||
| -rw-r--r-- | database/src/lib.rs | 7 | ||||
| -rw-r--r-- | database/src/query_ser.rs | 94 | ||||
| -rw-r--r-- | database/src/test_shared.rs | 3 |
6 files changed, 172 insertions, 7 deletions
diff --git a/database/src/kv/index.rs b/database/src/kv/index.rs index 81a4f55..23b8349 100644 --- a/database/src/kv/index.rs +++ b/database/src/kv/index.rs @@ -63,7 +63,6 @@ pub fn iter_index<'a>( prefix: Vec<u8>, sort: &Sort, ) -> Result<Box<dyn Iterator<Item = Result<(RowNum, Vec<u8>)>> + 'a>> { - eprintln!("{prefix:?}"); Ok(match sort { Sort::None => Box::new( PrefixIterator { diff --git a/database/src/kv/index_key.rs b/database/src/kv/index_key.rs index a4b2f01..adfc3e8 100644 --- a/database/src/kv/index_key.rs +++ b/database/src/kv/index_key.rs @@ -12,7 +12,7 @@ use crate::{ }; use jellyobject::{Path, Tag}; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct IndexKey(pub Binning, pub SortKey); #[derive(Debug, Hash, PartialEq, Eq)] @@ -45,7 +45,7 @@ impl IndexKey { b = &b[4..]; // remove subtree prefix let binning = Binning::read(&mut b); let sort = SortKey::read(&mut b); - assert!(b.is_empty()); + assert!(b.is_empty(), "index key deser left-over bytes"); Self(binning, sort) } } @@ -85,6 +85,7 @@ impl Binning { impl SortKey { fn read(b: &mut &[u8]) -> Self { let ty = b[0]; + *b = &b[1..]; match ty { 0 => SortKey::None, 1 => SortKey::Count, @@ -108,6 +109,7 @@ impl SortKey { SortKey::None => out.push(0), 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, @@ -118,6 +120,7 @@ impl SortKey { }); } SortKey::Text(path) => { + out.push(3); write_path(path, out); } } @@ -141,3 +144,31 @@ fn read_path(b: &mut &[u8]) -> Path { } 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)])), + )); + } +} diff --git a/database/src/kv/mod.rs b/database/src/kv/mod.rs index 8d42ec7..903fc46 100644 --- a/database/src/kv/mod.rs +++ b/database/src/kv/mod.rs @@ -15,7 +15,7 @@ pub mod sort; use std::borrow::Cow; use crate::{ - Database, Query, RowNum, Transaction, + DEBUG_TAGREG, Database, Query, RowNum, Transaction, kv::{ helpers::{read_counter, write_counter}, index::{iter_index, read_count_index, update_index}, @@ -25,7 +25,8 @@ use crate::{ }, }; use anyhow::{Result, anyhow}; -use jellyobject::ObjectBuffer; +use jellyobject::{ObjectBuffer, inspect::Inspector}; +use log::{debug, info}; pub type SubtreeNum = u32; @@ -44,6 +45,9 @@ impl<T: jellykv::Store> Database for T { impl Transaction for &mut dyn jellykv::Transaction { fn insert(&mut self, entry: ObjectBuffer) -> Result<RowNum> { + if let Some(tr) = DEBUG_TAGREG.get() { + debug!("insert {:?}", Inspector(tr, entry.as_object())) + } let mut id_counter = read_counter(*self, &T_ROW_COUNTER.to_be_bytes(), 0)?; let row = id_counter; id_counter += 1; @@ -97,6 +101,7 @@ impl Transaction for &mut dyn jellykv::Transaction { &'a mut self, query: Query, ) -> Result<Box<dyn Iterator<Item = Result<(RowNum, Vec<u8>)>> + 'a>> { + debug!("query: {}", query.show_debug()); let mut prefixes = Vec::new(); for (binning, mut prefix) in query.filter.get_bins() { let ik = IndexKey(binning, query.sort.key()); @@ -151,6 +156,7 @@ fn alloc_subtree(txn: &mut dyn jellykv::Transaction) -> Result<SubtreeNum> { } fn create_index(txn: &mut dyn jellykv::Transaction, is: SubtreeNum, ik: &IndexKey) -> Result<()> { + info!("creating new index {is}: {ik:?}"); let rowkeys = PrefixIterator { inner: txn.iter(&T_ROWS.to_be_bytes(), false)?, prefix: Cow::Borrowed(&T_ROWS.to_be_bytes()), @@ -246,7 +252,7 @@ mod test { } #[test] - pub fn query_match() -> Result<()> { + pub fn query_match_int() -> Result<()> { let db = jellykv::memory::new(); let mut rows = [0, 0, 0]; @@ -271,4 +277,31 @@ mod test { assert_eq!(result, Some(rows[0])); Ok(()) } + + #[test] + pub fn query_match_str() -> Result<()> { + let db = jellykv::memory::new(); + + let mut rows = [0, 0, 0]; + let mut result = None; + + db.transaction(&mut |txn| { + rows = [ + txn.insert(new_bob())?, + txn.insert(new_alice())?, + txn.insert(new_charlie())?, + ]; + Ok(()) + })?; + db.transaction(&mut |txn| { + result = txn.query_single(Query { + filter: Filter::Match(Path(vec![NAME.0]), "Alice".as_bytes().to_vec()), + sort: Sort::None, + })?; + Ok(()) + })?; + + assert_eq!(result, Some(rows[1])); + Ok(()) + } } diff --git a/database/src/lib.rs b/database/src/lib.rs index c4b2d47..c09340b 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -4,11 +4,16 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ pub mod kv; +pub mod query_ser; #[cfg(test)] pub mod test_shared; +use std::sync::OnceLock; + use anyhow::Result; -use jellyobject::{ObjectBuffer, Path}; +use jellyobject::{ObjectBuffer, Path, Registry}; + +pub static DEBUG_TAGREG: OnceLock<Registry> = OnceLock::new(); pub type RowNum = u64; pub type RowIter = Box<dyn Iterator<Item = Result<(RowNum, Vec<u8>)>>>; diff --git a/database/src/query_ser.rs b/database/src/query_ser.rs new file mode 100644 index 0000000..7698fdc --- /dev/null +++ b/database/src/query_ser.rs @@ -0,0 +1,94 @@ +/* + 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::{DEBUG_TAGREG, Filter, Query, Sort}; +use jellyobject::{Path, Registry, Tag}; +use std::any::TypeId; + +impl Query { + pub fn show_debug(&self) -> String { + if let Some(r) = DEBUG_TAGREG.get() { + self.show(r) + } else { + "[debug tag registry disabled]".to_string() + } + } + pub fn show(&self, reg: &Registry) -> String { + let mut o = String::new(); + if !matches!(self.filter, Filter::True) { + o += &format!("FILTER {}", self.filter.show(reg)) + } + if !matches!(self.sort, Sort::None) { + o += &format!("SORT {}", self.sort.show(reg)) + } + o + } +} +impl Filter { + pub fn show(&self, reg: &Registry) -> String { + match self { + Filter::True => "TRUE".to_string(), + Filter::All(filters) => format!( + "({})", + filters + .iter() + .map(|f| f.show(reg)) + .collect::<Vec<_>>() + .join(" AND ") + ), + Filter::Any(filters) => format!( + "({})", + filters + .iter() + .map(|f| f.show(reg)) + .collect::<Vec<_>>() + .join(" OR ") + ), + Filter::Match(path, value) => { + format!( + "{} = {}", + show_path(reg, path), + show_value(reg, *path.0.last().unwrap(), value) + ) + } + Filter::Has(path) => show_path(reg, path), + } + } +} +impl Sort { + pub fn show(&self, reg: &Registry) -> String { + match self { + Sort::None => "NONE".to_string(), + Sort::Value(vs) => { + todo!() + } + Sort::TextSearch(path, value) => { + format!("TEXT SEARCH {} = {value:?}", show_path(reg, path),) + } + } + } +} + +fn show_path(reg: &Registry, path: &Path) -> String { + path.0 + .iter() + .map(|s| reg.name(*s)) + .collect::<Vec<_>>() + .join(".") +} +fn show_value(reg: &Registry, tag: Tag, value: &[u8]) -> String { + if let Some(info) = reg.info(tag) + && let Some(ty) = info.r#type + { + if ty == TypeId::of::<&str>() { + format!("{:?}", str::from_utf8(value).unwrap()) + } else { + format!("{value:?}") + } + } else { + format!("{value:?}") + } +} diff --git a/database/src/test_shared.rs b/database/src/test_shared.rs index 82e9ba4..8216113 100644 --- a/database/src/test_shared.rs +++ b/database/src/test_shared.rs @@ -7,9 +7,12 @@ use jellyobject::{ObjectBuffer, Registry, fields}; use std::sync::LazyLock; +use crate::DEBUG_TAGREG; + pub static TAGREG: LazyLock<Registry> = LazyLock::new(|| { let mut reg = Registry::default(); register_fields(&mut reg); + DEBUG_TAGREG.set(reg.clone()).ok().unwrap(); reg }); fields! { |