From 3a81f654a9f49649fb6755b6e35649f0102a9572 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Thu, 19 Feb 2026 18:17:32 +0100 Subject: all idents as string; move value type; add cow to queries --- common/object/src/lib.rs | 12 ++++---- common/object/src/value.rs | 62 ++++++++++++++++++++++++++++++++------- common/src/node.rs | 6 ++-- database/src/kv/binning.rs | 56 +++++++++++++++++------------------ database/src/lib.rs | 55 +++++----------------------------- database/src/query_ser.rs | 8 +++-- import/src/plugins/musicbrainz.rs | 1 - import/src/plugins/tmdb.rs | 1 + import/src/plugins/trakt.rs | 6 ++-- import/src/plugins/vgmdb.rs | 1 + 10 files changed, 106 insertions(+), 102 deletions(-) diff --git a/common/object/src/lib.rs b/common/object/src/lib.rs index 7d0c203..3e60d58 100644 --- a/common/object/src/lib.rs +++ b/common/object/src/lib.rs @@ -154,14 +154,14 @@ impl<'a> Object<'a> { Some(&values_u8[start as usize..end as usize]) } #[inline] - pub fn get_typed>(&self, index: usize) -> Option { + pub fn get_typed>(&self, index: usize) -> Option { if T::ALIGNED { T::load_aligned(self.get_aligned(index)?) } else { T::load_unaligned(self.get_unaligned(index)?) } } - pub fn get>(&self, tag: TypedTag) -> Option { + pub fn get>(&self, tag: TypedTag) -> Option { self.get_typed(self.find_field(tag.0)?) } pub fn keys(&self) -> KeysIter<'a> { @@ -186,7 +186,7 @@ impl<'a> Object<'a> { } } #[must_use] - pub fn extend + Eq + Ord>( + pub fn extend + Eq + Ord>( &self, tag: TypedTag, values: impl IntoIterator, @@ -318,13 +318,13 @@ pub struct EntriesIter<'a, T> { index: usize, ty: PhantomData, } -impl<'a, T: Value<'a>> Iterator for EntriesIter<'a, T> { +impl<'a, T: ValueLoad<'a>> Iterator for EntriesIter<'a, T> { type Item = (Tag, T); fn next(&mut self) -> Option { if self.index >= self.object.tags.len() { return None; } else { - let value = self.object.get_typed(self.index)?; //? This ends the iterator early if there is any invalid field + let value = self.object.get_typed(self.index)?; let tag = self.object.tags[self.index]; self.index += 1; Some((Tag(tag), value)) @@ -338,7 +338,7 @@ pub struct FieldIter<'a, T> { tag: u32, ty: PhantomData, } -impl<'a, T: Value<'a>> Iterator for FieldIter<'a, T> { +impl<'a, T: ValueLoad<'a>> Iterator for FieldIter<'a, T> { type Item = T; fn next(&mut self) -> Option { if self.index >= self.object.tags.len() { diff --git a/common/object/src/value.rs b/common/object/src/value.rs index 01b0903..3db7e7c 100644 --- a/common/object/src/value.rs +++ b/common/object/src/value.rs @@ -5,8 +5,9 @@ */ use crate::{Object, ObjectBuffer, Tag}; +use std::borrow::Cow; -pub trait Value<'a>: ValueStore + Sized { +pub trait ValueLoad<'a>: ValueStore + Sized { const ALIGNED: bool; fn load_aligned(buf: &'a [u32]) -> Option { let _ = buf; @@ -53,13 +54,54 @@ impl ValueType { } } +#[derive(Debug, Clone)] +pub enum Value<'a> { + Tag(Tag), + U32(u32), + U64(u64), + I64(i64), + String(Cow<'a, str>), + Binary(Cow<'a, [u8]>), +} + +impl<'a> From<&'a str> for Value<'a> { + fn from(value: &'a str) -> Self { + Self::String(value.into()) + } +} +impl From for Value<'static> { + fn from(value: String) -> Self { + Self::String(value.into()) + } +} +impl From for Value<'static> { + fn from(value: u32) -> Self { + Self::U32(value) + } +} +impl From for Value<'static> { + fn from(value: u64) -> Self { + Self::U64(value) + } +} +impl From for Value<'static> { + fn from(value: i64) -> Self { + Self::I64(value) + } +} +impl From for Value<'static> { + fn from(value: Tag) -> Self { + Self::Tag(value) + } +} + pub trait ValueStore { fn get_type(&self) -> ValueType; fn store_aligned(&self, _buf: &mut Vec) {} fn store_unaligned(&self, _buf: &mut Vec) {} fn size(&self) -> usize; } -impl<'a> Value<'a> for &'a str { +impl<'a> ValueLoad<'a> for &'a str { const ALIGNED: bool = false; fn load_unaligned(buf: &'a [u8]) -> Option { str::from_utf8(buf).ok() @@ -76,7 +118,7 @@ impl ValueStore for &str { self.len() } } -impl Value<'_> for u32 { +impl ValueLoad<'_> for u32 { const ALIGNED: bool = true; fn load_aligned(buf: &[u32]) -> Option { buf.get(0).copied().map(u32::from_be) @@ -93,7 +135,7 @@ impl ValueStore for u32 { 4 } } -impl Value<'_> for Tag { +impl ValueLoad<'_> for Tag { const ALIGNED: bool = true; fn load_aligned(buf: &[u32]) -> Option { buf.get(0).copied().map(u32::from_be).map(Tag) @@ -110,7 +152,7 @@ impl ValueStore for Tag { 4 } } -impl Value<'_> for u64 { +impl ValueLoad<'_> for u64 { const ALIGNED: bool = true; fn load_aligned(buf: &[u32]) -> Option { let hi = u32::from_be(*buf.get(0)?) as u64; @@ -130,7 +172,7 @@ impl ValueStore for u64 { 8 } } -impl Value<'_> for f64 { +impl ValueLoad<'_> for f64 { const ALIGNED: bool = true; fn load_aligned(buf: &[u32]) -> Option { if buf.len() < 2 { @@ -156,7 +198,7 @@ impl ValueStore for f64 { 8 } } -impl Value<'_> for i64 { +impl ValueLoad<'_> for i64 { const ALIGNED: bool = true; fn load_aligned(buf: &[u32]) -> Option { u64::load_aligned(buf).map(|x| x as i64) @@ -173,7 +215,7 @@ impl ValueStore for i64 { 8 } } -impl<'a> Value<'a> for Object<'a> { +impl<'a> ValueLoad<'a> for Object<'a> { const ALIGNED: bool = true; fn load_aligned(buf: &'a [u32]) -> Option { Self::load(buf) @@ -204,7 +246,7 @@ impl ValueStore for ObjectBuffer { self.0.len() * 4 } } -impl<'a> Value<'a> for &'a [u8] { +impl<'a> ValueLoad<'a> for &'a [u8] { const ALIGNED: bool = false; fn load_unaligned(buf: &'a [u8]) -> Option { Some(buf) @@ -221,7 +263,7 @@ impl ValueStore for &[u8] { self.len() } } -impl<'a> Value<'a> for () { +impl<'a> ValueLoad<'a> for () { const ALIGNED: bool = true; fn load_aligned(_buf: &'a [u32]) -> Option { Some(()) diff --git a/common/src/node.rs b/common/src/node.rs index 5e52a4c..b02f8e8 100644 --- a/common/src/node.rs +++ b/common/src/node.rs @@ -79,15 +79,15 @@ fields! { IDENT_TRAKT_SHOW: &str = b"trsh"; IDENT_TRAKT_SEASON: &str = b"trse"; IDENT_TRAKT_EPISODE: &str = b"trep"; - IDENT_TRAKT_PERSON: u64 = b"trpe"; + IDENT_TRAKT_PERSON: &str = b"trpe"; IDENT_IMDB: &str = b"imdb"; IDENT_IMDB_PERSON: &str = b"impe"; IDENT_TMDB_SERIES: &str = b"tmse"; IDENT_TMDB_MOVIE: &str = b"tmmv"; - IDENT_TMDB_PERSON: u64 = b"tmpe"; + IDENT_TMDB_PERSON: &str = b"tmpe"; IDENT_TVDB: &str = b"tvdb"; IDENT_OMDB: &str = b"omdb"; - IDENT_VGMDB_ARTIST: u64 = b"vgar"; + IDENT_VGMDB_ARTIST: &str = b"vgar"; IDENT_WIKIDATA: &str = b"wkdt"; } diff --git a/database/src/kv/binning.rs b/database/src/kv/binning.rs index c41d6e9..253bb5c 100644 --- a/database/src/kv/binning.rs +++ b/database/src/kv/binning.rs @@ -51,7 +51,7 @@ impl BinningComponent { } } -impl Filter { +impl Filter<'_> { pub fn get_binnings(&self) -> Vec { self.get_bins_inner() .into_iter() @@ -88,7 +88,7 @@ impl Filter { Filter::Match(path, value) => { vec![vec![(BinningComponent::Match(path.to_owned()), { let mut co = Vec::new(); - value.write_with_len(&mut co); + write_value_with_len(value, &mut co); co })]] } @@ -99,33 +99,31 @@ impl Filter { } } -impl Value { - pub fn write_with_len(&self, out: &mut Vec) { - match self { - Value::Tag(tag) => { - out.extend(4u32.to_be_bytes()); - out.extend(tag.0.to_be_bytes()); - } - Value::U32(x) => { - out.extend(4u32.to_be_bytes()); - out.extend(x.to_be_bytes()); - } - Value::U64(x) => { - out.extend(8u32.to_be_bytes()); - out.extend(x.to_be_bytes()); - } - Value::I64(x) => { - out.extend(8u32.to_be_bytes()); - out.extend(x.to_be_bytes()); - } - Value::String(s) => { - out.extend((s.len() as u32).to_be_bytes()); - out.extend(s.as_bytes()); - } - Value::Binary(s) => { - out.extend((s.len() as u32).to_be_bytes()); - out.extend(s); - } +pub fn write_value_with_len(value: &Value, out: &mut Vec) { + match value { + Value::Tag(tag) => { + out.extend(4u32.to_be_bytes()); + out.extend(tag.0.to_be_bytes()); + } + Value::U32(x) => { + out.extend(4u32.to_be_bytes()); + out.extend(x.to_be_bytes()); + } + Value::U64(x) => { + out.extend(8u32.to_be_bytes()); + out.extend(x.to_be_bytes()); + } + Value::I64(x) => { + out.extend(8u32.to_be_bytes()); + out.extend(x.to_be_bytes()); + } + Value::String(s) => { + out.extend((s.len() as u32).to_be_bytes()); + out.extend(s.as_bytes()); + } + Value::Binary(s) => { + out.extend((s.len() as u32).to_be_bytes()); + out.extend(&**s); } } } diff --git a/database/src/lib.rs b/database/src/lib.rs index 2ecf3c6..ea12447 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -9,7 +9,7 @@ pub mod query_ser; pub mod test_shared; use anyhow::Result; -use jellyobject::{ObjectBuffer, Path, Tag}; +use jellyobject::{ObjectBuffer, Path, Value}; pub type RowNum = u64; pub type RowIter = Box)>>>; @@ -33,8 +33,8 @@ pub trait Transaction { } #[derive(Default, Clone)] -pub struct Query { - pub filter: Filter, +pub struct Query<'a> { + pub filter: Filter<'a>, pub sort: Sort, } @@ -70,52 +70,11 @@ pub enum SortOrder { } #[derive(Debug, Clone, Default)] -pub enum Filter { +pub enum Filter<'a> { #[default] True, - All(Vec), - Any(Vec), - Match(Path, Value), + All(Vec>), + Any(Vec>), + Match(Path, Value<'a>), Has(Path), } - -#[derive(Debug, Clone)] -pub enum Value { - Tag(Tag), - U32(u32), - U64(u64), - I64(i64), - String(String), - Binary(Vec), -} - -impl From<&str> for Value { - fn from(value: &str) -> Self { - Self::String(value.to_owned()) - } -} -impl From for Value { - fn from(value: String) -> Self { - Self::String(value) - } -} -impl From for Value { - fn from(value: u32) -> Self { - Self::U32(value) - } -} -impl From for Value { - fn from(value: u64) -> Self { - Self::U64(value) - } -} -impl From for Value { - fn from(value: i64) -> Self { - Self::I64(value) - } -} -impl From for Value { - fn from(value: Tag) -> Self { - Self::Tag(value) - } -} diff --git a/database/src/query_ser.rs b/database/src/query_ser.rs index d51ef64..78eee09 100644 --- a/database/src/query_ser.rs +++ b/database/src/query_ser.rs @@ -4,9 +4,11 @@ Copyright (C) 2026 metamuffin */ -use crate::{Filter, MultiBehaviour, Query, Sort, SortOrder, Value, ValueSort}; +use jellyobject::Value; -impl Query { +use crate::{Filter, MultiBehaviour, Query, Sort, SortOrder, ValueSort}; + +impl Query<'_> { pub fn show(&self) -> String { let mut o = String::new(); if !matches!(self.filter, Filter::True) { @@ -18,7 +20,7 @@ impl Query { o } } -impl Filter { +impl Filter<'_> { pub fn show(&self) -> String { match self { Filter::True => "TRUE".to_string(), diff --git a/import/src/plugins/musicbrainz.rs b/import/src/plugins/musicbrainz.rs index 9f7b08c..4dfd974 100644 --- a/import/src/plugins/musicbrainz.rs +++ b/import/src/plugins/musicbrainz.rs @@ -455,7 +455,6 @@ impl MusicBrainz { if let Some(url) = url && let Some(id) = url.strip_prefix("https://vgmdb.net/artist/") { - let id = id.parse::().context("parse vgmdb id")?; node = node .as_object() .update(NO_IDENTIFIERS, |ids| ids.insert(IDENT_VGMDB_ARTIST, id)) diff --git a/import/src/plugins/tmdb.rs b/import/src/plugins/tmdb.rs index dfdf020..ab0a679 100644 --- a/import/src/plugins/tmdb.rs +++ b/import/src/plugins/tmdb.rs @@ -324,6 +324,7 @@ impl Tmdb { else { return Ok(()); }; + let id = id.parse()?; let images = self.person_image(&ct.ic.cache, id, ct.rt)?; let Some(prof) = images.profiles.first() else { diff --git a/import/src/plugins/trakt.rs b/import/src/plugins/trakt.rs index 021ffe9..1d01436 100644 --- a/import/src/plugins/trakt.rs +++ b/import/src/plugins/trakt.rs @@ -524,9 +524,11 @@ impl Trakt { c = c.as_object().insert(NO_VISIBILITY, VISI_VISIBLE); c = c.as_object().insert(NO_TITLE, &ap.person.name); c = c.as_object().update(NO_IDENTIFIERS, |ids| { - let mut ids = ids.insert(IDENT_TRAKT_PERSON, traktid); + let mut ids = ids.insert(IDENT_TRAKT_PERSON, &traktid.to_string()); if let Some(tmdbid) = ap.person.ids.tmdb { - ids = ids.as_object().insert(IDENT_TMDB_PERSON, tmdbid); + ids = ids + .as_object() + .insert(IDENT_TMDB_PERSON, &tmdbid.to_string()); } if let Some(imdbid) = &ap.person.ids.imdb { ids = ids.as_object().insert(IDENT_IMDB_PERSON, imdbid); diff --git a/import/src/plugins/vgmdb.rs b/import/src/plugins/vgmdb.rs index 8221692..b7630d2 100644 --- a/import/src/plugins/vgmdb.rs +++ b/import/src/plugins/vgmdb.rs @@ -156,6 +156,7 @@ impl ImportPlugin for Vgmdb { else { return Ok(()); }; + let id = id.parse()?; let Some(image) = self.get_artist_image(&ct.ic.cache, id, ct.rt)? else { return Ok(()); -- cgit v1.3