aboutsummaryrefslogtreecommitdiff
path: root/database/src
diff options
context:
space:
mode:
Diffstat (limited to 'database/src')
-rw-r--r--database/src/kv/index.rs1
-rw-r--r--database/src/kv/index_key.rs35
-rw-r--r--database/src/kv/mod.rs39
-rw-r--r--database/src/lib.rs7
-rw-r--r--database/src/query_ser.rs94
-rw-r--r--database/src/test_shared.rs3
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! {