From 6d76469e303cf6f08c51586845826db0a09f93e6 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 11 Feb 2026 08:50:58 +0000 Subject: [PATCH 1/8] impl Deserialize for Packed & SumTag --- crates/sats/src/algebraic_value.rs | 4 +++- crates/sats/src/sum_value.rs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/sats/src/algebraic_value.rs b/crates/sats/src/algebraic_value.rs index 4013aed00ac..3648ac99db0 100644 --- a/crates/sats/src/algebraic_value.rs +++ b/crates/sats/src/algebraic_value.rs @@ -1,7 +1,7 @@ pub mod de; pub mod ser; -use crate::{AlgebraicType, ArrayValue, ProductValue, SumValue}; +use crate::{impl_deserialize, AlgebraicType, ArrayValue, Deserialize, ProductValue, SumValue}; use core::mem; use core::ops::{Bound, RangeBounds}; use derive_more::From; @@ -117,6 +117,8 @@ pub enum AlgebraicValue { #[repr(Rust, packed)] pub struct Packed(pub T); +impl_deserialize!([T: Deserialize<'de>] Packed, de => <_>::deserialize(de).map(Packed)); + impl From for Packed { fn from(value: T) -> Self { Self(value) diff --git a/crates/sats/src/sum_value.rs b/crates/sats/src/sum_value.rs index 9284a420a86..39c04f7c4fe 100644 --- a/crates/sats/src/sum_value.rs +++ b/crates/sats/src/sum_value.rs @@ -1,4 +1,5 @@ use crate::algebraic_value::AlgebraicValue; +use crate::impl_deserialize; use crate::sum_type::SumType; /// A value of a sum type choosing a specific variant of the type. @@ -34,6 +35,8 @@ impl SumValue { #[repr(transparent)] pub struct SumTag(pub u8); +impl_deserialize!([] SumTag, de => <_>::deserialize(de).map(SumTag)); + #[cfg(feature = "memory-usage")] impl spacetimedb_memory_usage::MemoryUsage for SumTag {} From 584581bbbb8c1a68916367f45e3e2ebbe2580a8e Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 11 Feb 2026 09:19:42 +0000 Subject: [PATCH 2/8] cleanup TypedIndexPointIter and ditch Direct variant --- crates/table/src/table_index/mod.rs | 172 +++++++++--------- .../unique_direct_fixed_cap_index.rs | 7 +- .../src/table_index/unique_direct_index.rs | 25 +-- .../src/table_index/unique_hash_index.rs | 7 +- crates/table/src/table_index/uniquemap.rs | 23 ++- 5 files changed, 109 insertions(+), 125 deletions(-) diff --git a/crates/table/src/table_index/mod.rs b/crates/table/src/table_index/mod.rs index a467f87efc8..99eaa7e6cb6 100644 --- a/crates/table/src/table_index/mod.rs +++ b/crates/table/src/table_index/mod.rs @@ -27,7 +27,7 @@ use self::hash_index::HashIndex; use self::same_key_entry::SameKeyEntryIter; use self::unique_direct_fixed_cap_index::{UniqueDirectFixedCapIndex, UniqueDirectFixedCapIndexRangeIter}; -use self::unique_direct_index::{UniqueDirectIndex, UniqueDirectIndexPointIter, UniqueDirectIndexRangeIter}; +use self::unique_direct_index::{UniqueDirectIndex, UniqueDirectIndexRangeIter}; use self::unique_hash_index::UniqueHashIndex; use super::indexes::RowPointer; use super::table::RowRef; @@ -58,28 +58,24 @@ pub use self::index::{Index, IndexCannotSeekRange, IndexSeekRangeResult, RangedI pub use self::key_size::KeySize; type BtreeIndex = multimap::MultiMap; -type BtreeIndexPointIter<'a> = SameKeyEntryIter<'a>; type BtreeIndexRangeIter<'a, K> = multimap::MultiMapRangeIter<'a, K>; type BtreeUniqueIndex = uniquemap::UniqueMap; -type BtreeUniqueIndexPointIter<'a> = uniquemap::UniqueMapPointIter<'a>; type BtreeUniqueIndexRangeIter<'a, K> = uniquemap::UniqueMapRangeIter<'a, K>; /// A point iterator over a [`TypedIndex`], with a specialized key type. /// /// See module docs for info about specialization. enum TypedIndexPointIter<'a> { - BTree(BtreeIndexPointIter<'a>), - UniqueBTree(BtreeUniqueIndexPointIter<'a>), - UniqueDirect(UniqueDirectIndexPointIter), + NonUnique(SameKeyEntryIter<'a>), + Unique(uniquemap::UniquePointIter), } impl Iterator for TypedIndexPointIter<'_> { type Item = RowPointer; fn next(&mut self) -> Option { match self { - Self::BTree(this) => this.next(), - Self::UniqueBTree(this) => this.next(), - Self::UniqueDirect(this) => this.next(), + Self::NonUnique(this) => this.next(), + Self::Unique(this) => this.next(), } } } @@ -961,85 +957,85 @@ impl TypedIndex { use TypedIndex::*; use TypedIndexPointIter::*; match self { - BtreeBool(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_bool)), - BtreeU8(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_u8)), - BtreeSumTag(this) => BTree(iter_at_type(this, key, as_sum_tag)), - BtreeI8(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_i8)), - BtreeU16(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_u16)), - BtreeI16(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_i16)), - BtreeU32(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_u32)), - BtreeI32(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_i32)), - BtreeU64(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_u64)), - BtreeI64(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_i64)), - BtreeU128(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_u128)), - BtreeI128(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_i128)), - BtreeU256(this) => BTree(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), - BtreeI256(this) => BTree(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), - BtreeF32(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_f32)), - BtreeF64(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_f64)), - BtreeString(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_string)), - BtreeAV(this) => BTree(this.seek_point(key)), - HashBool(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_bool)), - HashU8(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_u8)), - HashSumTag(this) => BTree(iter_at_type(this, key, as_sum_tag)), - HashI8(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_i8)), - HashU16(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_u16)), - HashI16(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_i16)), - HashU32(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_u32)), - HashI32(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_i32)), - HashU64(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_u64)), - HashI64(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_i64)), - HashU128(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_u128)), - HashI128(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_i128)), - HashU256(this) => BTree(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), - HashI256(this) => BTree(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), - HashF32(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_f32)), - HashF64(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_f64)), - HashString(this) => BTree(iter_at_type(this, key, AlgebraicValue::as_string)), - HashAV(this) => BTree(this.seek_point(key)), - UniqueBtreeBool(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_bool)), - UniqueBtreeU8(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_u8)), - UniqueBtreeSumTag(this) => UniqueBTree(iter_at_type(this, key, as_sum_tag)), - UniqueBtreeI8(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_i8)), - UniqueBtreeU16(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_u16)), - UniqueBtreeI16(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_i16)), - UniqueBtreeU32(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_u32)), - UniqueBtreeI32(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_i32)), - UniqueBtreeU64(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_u64)), - UniqueBtreeI64(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_i64)), - UniqueBtreeU128(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_u128)), - UniqueBtreeI128(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_i128)), - UniqueBtreeU256(this) => UniqueBTree(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), - UniqueBtreeI256(this) => UniqueBTree(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), - UniqueBtreeF32(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_f32)), - UniqueBtreeF64(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_f64)), - UniqueBtreeString(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_string)), - UniqueBtreeAV(this) => UniqueBTree(this.seek_point(key)), - - UniqueHashBool(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_bool)), - UniqueHashU8(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_u8)), - UniqueHashSumTag(this) => UniqueBTree(iter_at_type(this, key, as_sum_tag)), - UniqueHashI8(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_i8)), - UniqueHashU16(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_u16)), - UniqueHashI16(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_i16)), - UniqueHashU32(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_u32)), - UniqueHashI32(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_i32)), - UniqueHashU64(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_u64)), - UniqueHashI64(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_i64)), - UniqueHashU128(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_u128)), - UniqueHashI128(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_i128)), - UniqueHashU256(this) => UniqueBTree(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), - UniqueHashI256(this) => UniqueBTree(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), - UniqueHashF32(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_f32)), - UniqueHashF64(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_f64)), - UniqueHashString(this) => UniqueBTree(iter_at_type(this, key, AlgebraicValue::as_string)), - UniqueHashAV(this) => UniqueBTree(this.seek_point(key)), - - UniqueDirectSumTag(this) => UniqueDirect(iter_at_type(this, key, as_sum_tag)), - UniqueDirectU8(this) => UniqueDirect(iter_at_type(this, key, AlgebraicValue::as_u8)), - UniqueDirectU16(this) => UniqueDirect(iter_at_type(this, key, AlgebraicValue::as_u16)), - UniqueDirectU32(this) => UniqueDirect(iter_at_type(this, key, AlgebraicValue::as_u32)), - UniqueDirectU64(this) => UniqueDirect(iter_at_type(this, key, AlgebraicValue::as_u64)), + BtreeBool(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_bool)), + BtreeU8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u8)), + BtreeSumTag(this) => NonUnique(iter_at_type(this, key, as_sum_tag)), + BtreeI8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i8)), + BtreeU16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u16)), + BtreeI16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i16)), + BtreeU32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u32)), + BtreeI32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i32)), + BtreeU64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u64)), + BtreeI64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i64)), + BtreeU128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u128)), + BtreeI128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i128)), + BtreeU256(this) => NonUnique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), + BtreeI256(this) => NonUnique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), + BtreeF32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f32)), + BtreeF64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f64)), + BtreeString(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_string)), + BtreeAV(this) => NonUnique(this.seek_point(key)), + HashBool(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_bool)), + HashU8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u8)), + HashSumTag(this) => NonUnique(iter_at_type(this, key, as_sum_tag)), + HashI8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i8)), + HashU16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u16)), + HashI16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i16)), + HashU32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u32)), + HashI32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i32)), + HashU64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u64)), + HashI64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i64)), + HashU128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u128)), + HashI128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i128)), + HashU256(this) => NonUnique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), + HashI256(this) => NonUnique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), + HashF32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f32)), + HashF64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f64)), + HashString(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_string)), + HashAV(this) => NonUnique(this.seek_point(key)), + UniqueBtreeBool(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_bool)), + UniqueBtreeU8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u8)), + UniqueBtreeSumTag(this) => Unique(iter_at_type(this, key, as_sum_tag)), + UniqueBtreeI8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i8)), + UniqueBtreeU16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u16)), + UniqueBtreeI16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i16)), + UniqueBtreeU32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u32)), + UniqueBtreeI32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i32)), + UniqueBtreeU64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u64)), + UniqueBtreeI64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i64)), + UniqueBtreeU128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u128)), + UniqueBtreeI128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i128)), + UniqueBtreeU256(this) => Unique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), + UniqueBtreeI256(this) => Unique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), + UniqueBtreeF32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f32)), + UniqueBtreeF64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f64)), + UniqueBtreeString(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_string)), + UniqueBtreeAV(this) => Unique(this.seek_point(key)), + + UniqueHashBool(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_bool)), + UniqueHashU8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u8)), + UniqueHashSumTag(this) => Unique(iter_at_type(this, key, as_sum_tag)), + UniqueHashI8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i8)), + UniqueHashU16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u16)), + UniqueHashI16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i16)), + UniqueHashU32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u32)), + UniqueHashI32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i32)), + UniqueHashU64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u64)), + UniqueHashI64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i64)), + UniqueHashU128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u128)), + UniqueHashI128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i128)), + UniqueHashU256(this) => Unique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), + UniqueHashI256(this) => Unique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), + UniqueHashF32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f32)), + UniqueHashF64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f64)), + UniqueHashString(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_string)), + UniqueHashAV(this) => Unique(this.seek_point(key)), + + UniqueDirectSumTag(this) => Unique(iter_at_type(this, key, as_sum_tag)), + UniqueDirectU8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u8)), + UniqueDirectU16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u16)), + UniqueDirectU32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u32)), + UniqueDirectU64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u64)), } } diff --git a/crates/table/src/table_index/unique_direct_fixed_cap_index.rs b/crates/table/src/table_index/unique_direct_fixed_cap_index.rs index 805d70fc83d..c13c12f7105 100644 --- a/crates/table/src/table_index/unique_direct_fixed_cap_index.rs +++ b/crates/table/src/table_index/unique_direct_fixed_cap_index.rs @@ -1,5 +1,6 @@ use super::index::{Index, RangedIndex}; -use super::unique_direct_index::{expose, injest, ToFromUsize, UniqueDirectIndexPointIter, NONE_PTR}; +use super::uniquemap::UniquePointIter; +use super::unique_direct_index::{expose, injest, ToFromUsize, NONE_PTR}; use crate::indexes::RowPointer; use crate::table_index::KeySize; use core::marker::PhantomData; @@ -79,13 +80,13 @@ impl Index for UniqueDirectFixedCapIndex { } type PointIter<'a> - = UniqueDirectIndexPointIter + = UniquePointIter where Self: 'a; fn seek_point(&self, &key: &Self::Key) -> Self::PointIter<'_> { let point = self.array.get(key.to_usize()).copied().filter(|slot| *slot != NONE_PTR); - UniqueDirectIndexPointIter::new(point) + UniquePointIter::new(point) } fn num_keys(&self) -> usize { diff --git a/crates/table/src/table_index/unique_direct_index.rs b/crates/table/src/table_index/unique_direct_index.rs index 6730e642270..cd034e36356 100644 --- a/crates/table/src/table_index/unique_direct_index.rs +++ b/crates/table/src/table_index/unique_direct_index.rs @@ -1,10 +1,10 @@ use super::index::{Despecialize, Index, RangedIndex}; +use super::uniquemap::UniquePointIter; use super::{BtreeUniqueIndex, KeySize}; use crate::indexes::{PageIndex, PageOffset, RowPointer, SquashedOffset}; use core::marker::PhantomData; use core::mem; use core::ops::{Bound, RangeBounds}; -use core::option::IntoIter; use spacetimedb_sats::memory_usage::MemoryUsage; use spacetimedb_sats::sum_value::SumTag; @@ -226,7 +226,7 @@ impl Index for UniqueDirectIndex { } type PointIter<'a> - = UniqueDirectIndexPointIter + = UniquePointIter where Self: 'a; @@ -239,7 +239,7 @@ impl Index for UniqueDirectIndex { .and_then(|x| x.as_ref()) .map(|inner| inner.get(inner_key)) .filter(|slot| *slot != NONE_PTR); - UniqueDirectIndexPointIter::new(point) + UniquePointIter::new(point) } fn num_keys(&self) -> usize { @@ -315,25 +315,6 @@ impl RangedIndex for UniqueDirectIndex { } } -/// An iterator over the potential value in a [`UniqueDirectMap`] for a given key. -pub struct UniqueDirectIndexPointIter { - iter: IntoIter, -} - -impl UniqueDirectIndexPointIter { - pub(super) fn new(point: Option) -> Self { - let iter = point.map(expose).into_iter(); - Self { iter } - } -} - -impl Iterator for UniqueDirectIndexPointIter { - type Item = RowPointer; - fn next(&mut self) -> Option { - self.iter.next() - } -} - /// An iterator over a range of keys in a [`UniqueDirectIndex`]. #[derive(Debug, Clone)] pub struct UniqueDirectIndexRangeIter<'a> { diff --git a/crates/table/src/table_index/unique_hash_index.rs b/crates/table/src/table_index/unique_hash_index.rs index ba572d03085..2415896fa0e 100644 --- a/crates/table/src/table_index/unique_hash_index.rs +++ b/crates/table/src/table_index/unique_hash_index.rs @@ -1,5 +1,5 @@ use super::{Index, KeySize}; -use crate::table_index::uniquemap::UniqueMapPointIter; +use crate::table_index::uniquemap::UniquePointIter; use crate::{indexes::RowPointer, table_index::key_size::KeyBytesStorage}; use core::hash::Hash; use spacetimedb_data_structures::map::hash_map::Entry; @@ -87,12 +87,11 @@ impl Index for UniqueHashIndex { } type PointIter<'a> - = UniqueMapPointIter<'a> + = UniquePointIter where Self: 'a; fn seek_point(&self, point: &Self::Key) -> Self::PointIter<'_> { - let iter = self.map.get(point).into_iter(); - UniqueMapPointIter { iter } + UniquePointIter::new(self.map.get(point).copied()) } } diff --git a/crates/table/src/table_index/uniquemap.rs b/crates/table/src/table_index/uniquemap.rs index fa77885d233..7701dc64fdf 100644 --- a/crates/table/src/table_index/uniquemap.rs +++ b/crates/table/src/table_index/uniquemap.rs @@ -66,13 +66,12 @@ impl Index for UniqueMap { } type PointIter<'a> - = UniqueMapPointIter<'a> + = UniquePointIter where Self: 'a; - fn seek_point(&self, key: &Self::Key) -> Self::PointIter<'_> { - let iter = self.map.get(key).into_iter(); - UniqueMapPointIter { iter } + fn seek_point(&self, point: &Self::Key) -> Self::PointIter<'_> { + UniquePointIter::new(self.map.get(point).copied()) } /// Deletes all entries from the map, leaving it empty. @@ -96,16 +95,24 @@ impl Index for UniqueMap { } /// An iterator over the potential value in a [`UniqueMap`] for a given key. -pub struct UniqueMapPointIter<'a> { +pub struct UniquePointIter { /// The iterator seeking for matching keys in the range. - pub(super) iter: IntoIter<&'a RowPointer>, + pub(super) iter: IntoIter, } -impl<'a> Iterator for UniqueMapPointIter<'a> { +impl UniquePointIter { + /// Returns a new iterator over the possibly found row pointer. + pub fn new(point: Option) -> Self { + let iter = point.into_iter(); + Self { iter } + } +} + +impl Iterator for UniquePointIter { type Item = RowPointer; fn next(&mut self) -> Option { - self.iter.next().copied() + self.iter.next() } } From fe3dd10ca32602d28240c86dfa3b84b69cc5f5b8 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 11 Feb 2026 09:35:25 +0000 Subject: [PATCH 3/8] - UniqueMap -> UniqueBTreeIndex + MultiMap -> BTreeIndex - Btree* -> BTree --- crates/bench/benches/index.rs | 16 +- .../{multimap.rs => btree_index.rs} | 20 +- crates/table/src/table_index/mod.rs | 783 +++++++++--------- .../{uniquemap.rs => unique_btree_index.rs} | 22 +- .../unique_direct_fixed_cap_index.rs | 2 +- .../src/table_index/unique_direct_index.rs | 8 +- .../src/table_index/unique_hash_index.rs | 2 +- 7 files changed, 424 insertions(+), 429 deletions(-) rename crates/table/src/table_index/{multimap.rs => btree_index.rs} (90%) rename crates/table/src/table_index/{uniquemap.rs => unique_btree_index.rs} (84%) diff --git a/crates/bench/benches/index.rs b/crates/bench/benches/index.rs index 553bd304dd4..d288aef290b 100644 --- a/crates/bench/benches/index.rs +++ b/crates/bench/benches/index.rs @@ -1,4 +1,6 @@ -use core::{any::type_name, hash::BuildHasherDefault, hint::black_box, iter::repeat_with, mem, time::Duration}; +use core::{ + any::type_name, hash::BuildHasherDefault, hash::Hash, hint::black_box, iter::repeat_with, mem, time::Duration, +}; use criterion::{ criterion_group, criterion_main, measurement::{Measurement as _, WallTime}, @@ -14,13 +16,9 @@ use rand::{ use spacetimedb_lib::AlgebraicValue; use spacetimedb_sats::{layout::Size, product, u256}; use spacetimedb_table::indexes::{PageIndex, PageOffset, RowPointer, SquashedOffset}; -use spacetimedb_table::table_index::uniquemap::UniqueMap; -use spacetimedb_table::table_index::Index as _; -use spacetimedb_table::table_index::{ - unique_direct_index::{ToFromUsize, UniqueDirectIndex}, - KeySize, -}; -use std::hash::Hash; +use spacetimedb_table::table_index::unique_btree_index::UniqueBTreeIndex; +use spacetimedb_table::table_index::unique_direct_index::{ToFromUsize, UniqueDirectIndex}; +use spacetimedb_table::table_index::{Index as _, KeySize}; fn time(body: impl FnOnce() -> R) -> Duration { let start = WallTime.start(); @@ -201,7 +199,7 @@ trait Index: Clone { } #[derive(Clone)] -struct IBTree>(UniqueMap); +struct IBTree>(UniqueBTreeIndex); impl + Clone + Eq + Hash + Ord> Index for IBTree { type K = K; fn new() -> Self { diff --git a/crates/table/src/table_index/multimap.rs b/crates/table/src/table_index/btree_index.rs similarity index 90% rename from crates/table/src/table_index/multimap.rs rename to crates/table/src/table_index/btree_index.rs index b1ab1361072..a96ec88b5cc 100644 --- a/crates/table/src/table_index/multimap.rs +++ b/crates/table/src/table_index/btree_index.rs @@ -7,7 +7,7 @@ use std::collections::btree_map::{BTreeMap, Range}; /// A multi map that relates a `K` to a *set* of `RowPointer`s. #[derive(Debug, PartialEq, Eq)] -pub struct MultiMap { +pub struct BTreeIndex { /// The map is backed by a `BTreeMap` for relating keys to values. /// /// A value set is stored as a `SmallVec`. @@ -21,7 +21,7 @@ pub struct MultiMap { num_key_bytes: u64, } -impl Default for MultiMap { +impl Default for BTreeIndex { fn default() -> Self { Self { map: <_>::default(), @@ -31,7 +31,7 @@ impl Default for MultiMap { } } -impl MemoryUsage for MultiMap { +impl MemoryUsage for BTreeIndex { fn heap_usage(&self) -> usize { let Self { map, @@ -42,7 +42,7 @@ impl MemoryUsage for MultiMap { } } -impl Index for MultiMap { +impl Index for BTreeIndex { type Key = K; fn clone_structure(&self) -> Self { @@ -118,32 +118,32 @@ impl Index for MultiMap { } } -impl RangedIndex for MultiMap { +impl RangedIndex for BTreeIndex { type RangeIter<'a> - = MultiMapRangeIter<'a, K> + = BTreeIndexRangeIter<'a, K> where Self: 'a; /// Returns an iterator over the multimap that yields all the `V`s /// of the `K`s that fall within the specified `range`. fn seek_range(&self, range: &impl RangeBounds) -> Self::RangeIter<'_> { - MultiMapRangeIter { + BTreeIndexRangeIter { outer: self.map.range((range.start_bound(), range.end_bound())), inner: SameKeyEntry::empty_iter(), } } } -/// An iterator over values in a [`MultiMap`] where the keys are in a certain range. +/// An iterator over values in a [`BTreeIndex`] where the keys are in a certain range. #[derive(Clone)] -pub struct MultiMapRangeIter<'a, K> { +pub struct BTreeIndexRangeIter<'a, K> { /// The outer iterator seeking for matching keys in the range. outer: Range<'a, K, SameKeyEntry>, /// The inner iterator for the value set for a found key. inner: SameKeyEntryIter<'a>, } -impl Iterator for MultiMapRangeIter<'_, K> { +impl Iterator for BTreeIndexRangeIter<'_, K> { type Item = RowPointer; fn next(&mut self) -> Option { diff --git a/crates/table/src/table_index/mod.rs b/crates/table/src/table_index/mod.rs index 99eaa7e6cb6..7b082b7ea27 100644 --- a/crates/table/src/table_index/mod.rs +++ b/crates/table/src/table_index/mod.rs @@ -1,6 +1,6 @@ //! Table indexes with specialized key types. //! -//! Indexes could be implemented as `MultiMap` (and once were), +//! Indexes could be implemented as `BTreeIndex` (and once were), //! but that results in wasted memory and spurious comparisons and branches //! because the keys must always be homogeneous at a more specific type than `AlgebraicValue`. //! @@ -25,7 +25,9 @@ /// Additionally, beyond our btree indices, /// we support direct unique indices, where key are indices into `Vec`s. use self::hash_index::HashIndex; +use self::btree_index::{BTreeIndex, BTreeIndexRangeIter}; use self::same_key_entry::SameKeyEntryIter; +use self::unique_btree_index::{UniqueBTreeIndex, UniqueBTreeIndexRangeIter, UniquePointIter}; use self::unique_direct_fixed_cap_index::{UniqueDirectFixedCapIndex, UniqueDirectFixedCapIndexRangeIter}; use self::unique_direct_index::{UniqueDirectIndex, UniqueDirectIndexRangeIter}; use self::unique_hash_index::UniqueHashIndex; @@ -47,27 +49,22 @@ use spacetimedb_schema::def::IndexAlgorithm; mod hash_index; mod index; mod key_size; -mod multimap; +mod btree_index; mod same_key_entry; +pub mod unique_btree_index; pub mod unique_direct_fixed_cap_index; pub mod unique_direct_index; mod unique_hash_index; -pub mod uniquemap; pub use self::index::{Index, IndexCannotSeekRange, IndexSeekRangeResult, RangedIndex}; pub use self::key_size::KeySize; -type BtreeIndex = multimap::MultiMap; -type BtreeIndexRangeIter<'a, K> = multimap::MultiMapRangeIter<'a, K>; -type BtreeUniqueIndex = uniquemap::UniqueMap; -type BtreeUniqueIndexRangeIter<'a, K> = uniquemap::UniqueMapRangeIter<'a, K>; - /// A point iterator over a [`TypedIndex`], with a specialized key type. /// /// See module docs for info about specialization. enum TypedIndexPointIter<'a> { NonUnique(SameKeyEntryIter<'a>), - Unique(uniquemap::UniquePointIter), + Unique(UniquePointIter), } impl Iterator for TypedIndexPointIter<'_> { @@ -103,44 +100,44 @@ enum TypedIndexRangeIter<'a> { RangeEmpty, // All the non-unique btree index iterators. - BtreeBool(BtreeIndexRangeIter<'a, bool>), - BtreeU8(BtreeIndexRangeIter<'a, u8>), - BtreeSumTag(BtreeIndexRangeIter<'a, SumTag>), - BtreeI8(BtreeIndexRangeIter<'a, i8>), - BtreeU16(BtreeIndexRangeIter<'a, u16>), - BtreeI16(BtreeIndexRangeIter<'a, i16>), - BtreeU32(BtreeIndexRangeIter<'a, u32>), - BtreeI32(BtreeIndexRangeIter<'a, i32>), - BtreeU64(BtreeIndexRangeIter<'a, u64>), - BtreeI64(BtreeIndexRangeIter<'a, i64>), - BtreeU128(BtreeIndexRangeIter<'a, Packed>), - BtreeI128(BtreeIndexRangeIter<'a, Packed>), - BtreeU256(BtreeIndexRangeIter<'a, u256>), - BtreeI256(BtreeIndexRangeIter<'a, i256>), - BtreeF32(BtreeIndexRangeIter<'a, F32>), - BtreeF64(BtreeIndexRangeIter<'a, F64>), - BtreeString(BtreeIndexRangeIter<'a, Box>), - BtreeAV(BtreeIndexRangeIter<'a, AlgebraicValue>), + BTreeBool(BTreeIndexRangeIter<'a, bool>), + BTreeU8(BTreeIndexRangeIter<'a, u8>), + BTreeSumTag(BTreeIndexRangeIter<'a, SumTag>), + BTreeI8(BTreeIndexRangeIter<'a, i8>), + BTreeU16(BTreeIndexRangeIter<'a, u16>), + BTreeI16(BTreeIndexRangeIter<'a, i16>), + BTreeU32(BTreeIndexRangeIter<'a, u32>), + BTreeI32(BTreeIndexRangeIter<'a, i32>), + BTreeU64(BTreeIndexRangeIter<'a, u64>), + BTreeI64(BTreeIndexRangeIter<'a, i64>), + BTreeU128(BTreeIndexRangeIter<'a, Packed>), + BTreeI128(BTreeIndexRangeIter<'a, Packed>), + BTreeU256(BTreeIndexRangeIter<'a, u256>), + BTreeI256(BTreeIndexRangeIter<'a, i256>), + BTreeF32(BTreeIndexRangeIter<'a, F32>), + BTreeF64(BTreeIndexRangeIter<'a, F64>), + BTreeString(BTreeIndexRangeIter<'a, Box>), + BTreeAV(BTreeIndexRangeIter<'a, AlgebraicValue>), // All the unique btree index iterators. - UniqueBtreeBool(BtreeUniqueIndexRangeIter<'a, bool>), - UniqueBtreeU8(BtreeUniqueIndexRangeIter<'a, u8>), - UniqueBtreeSumTag(BtreeUniqueIndexRangeIter<'a, SumTag>), - UniqueBtreeI8(BtreeUniqueIndexRangeIter<'a, i8>), - UniqueBtreeU16(BtreeUniqueIndexRangeIter<'a, u16>), - UniqueBtreeI16(BtreeUniqueIndexRangeIter<'a, i16>), - UniqueBtreeU32(BtreeUniqueIndexRangeIter<'a, u32>), - UniqueBtreeI32(BtreeUniqueIndexRangeIter<'a, i32>), - UniqueBtreeU64(BtreeUniqueIndexRangeIter<'a, u64>), - UniqueBtreeI64(BtreeUniqueIndexRangeIter<'a, i64>), - UniqueBtreeU128(BtreeUniqueIndexRangeIter<'a, Packed>), - UniqueBtreeI128(BtreeUniqueIndexRangeIter<'a, Packed>), - UniqueBtreeU256(BtreeUniqueIndexRangeIter<'a, u256>), - UniqueBtreeI256(BtreeUniqueIndexRangeIter<'a, i256>), - UniqueBtreeF32(BtreeUniqueIndexRangeIter<'a, F32>), - UniqueBtreeF64(BtreeUniqueIndexRangeIter<'a, F64>), - UniqueBtreeString(BtreeUniqueIndexRangeIter<'a, Box>), - UniqueBtreeAV(BtreeUniqueIndexRangeIter<'a, AlgebraicValue>), + UniqueBTreeBool(UniqueBTreeIndexRangeIter<'a, bool>), + UniqueBTreeU8(UniqueBTreeIndexRangeIter<'a, u8>), + UniqueBTreeSumTag(UniqueBTreeIndexRangeIter<'a, SumTag>), + UniqueBTreeI8(UniqueBTreeIndexRangeIter<'a, i8>), + UniqueBTreeU16(UniqueBTreeIndexRangeIter<'a, u16>), + UniqueBTreeI16(UniqueBTreeIndexRangeIter<'a, i16>), + UniqueBTreeU32(UniqueBTreeIndexRangeIter<'a, u32>), + UniqueBTreeI32(UniqueBTreeIndexRangeIter<'a, i32>), + UniqueBTreeU64(UniqueBTreeIndexRangeIter<'a, u64>), + UniqueBTreeI64(UniqueBTreeIndexRangeIter<'a, i64>), + UniqueBTreeU128(UniqueBTreeIndexRangeIter<'a, Packed>), + UniqueBTreeI128(UniqueBTreeIndexRangeIter<'a, Packed>), + UniqueBTreeU256(UniqueBTreeIndexRangeIter<'a, u256>), + UniqueBTreeI256(UniqueBTreeIndexRangeIter<'a, i256>), + UniqueBTreeF32(UniqueBTreeIndexRangeIter<'a, F32>), + UniqueBTreeF64(UniqueBTreeIndexRangeIter<'a, F64>), + UniqueBTreeString(UniqueBTreeIndexRangeIter<'a, Box>), + UniqueBTreeAV(UniqueBTreeIndexRangeIter<'a, AlgebraicValue>), UniqueDirect(UniqueDirectIndexRangeIter<'a>), UniqueDirectU8(UniqueDirectFixedCapIndexRangeIter<'a>), @@ -152,43 +149,43 @@ impl Iterator for TypedIndexRangeIter<'_> { match self { Self::RangeEmpty => None, - Self::BtreeBool(this) => this.next(), - Self::BtreeU8(this) => this.next(), - Self::BtreeSumTag(this) => this.next(), - Self::BtreeI8(this) => this.next(), - Self::BtreeU16(this) => this.next(), - Self::BtreeI16(this) => this.next(), - Self::BtreeU32(this) => this.next(), - Self::BtreeI32(this) => this.next(), - Self::BtreeU64(this) => this.next(), - Self::BtreeI64(this) => this.next(), - Self::BtreeU128(this) => this.next(), - Self::BtreeI128(this) => this.next(), - Self::BtreeU256(this) => this.next(), - Self::BtreeI256(this) => this.next(), - Self::BtreeF32(this) => this.next(), - Self::BtreeF64(this) => this.next(), - Self::BtreeString(this) => this.next(), - Self::BtreeAV(this) => this.next(), - - Self::UniqueBtreeBool(this) => this.next(), - Self::UniqueBtreeU8(this) => this.next(), - Self::UniqueBtreeSumTag(this) => this.next(), - Self::UniqueBtreeI8(this) => this.next(), - Self::UniqueBtreeU16(this) => this.next(), - Self::UniqueBtreeI16(this) => this.next(), - Self::UniqueBtreeU32(this) => this.next(), - Self::UniqueBtreeI32(this) => this.next(), - Self::UniqueBtreeU64(this) => this.next(), - Self::UniqueBtreeI64(this) => this.next(), - Self::UniqueBtreeU128(this) => this.next(), - Self::UniqueBtreeI128(this) => this.next(), - Self::UniqueBtreeU256(this) => this.next(), - Self::UniqueBtreeI256(this) => this.next(), - Self::UniqueBtreeF32(this) => this.next(), - Self::UniqueBtreeF64(this) => this.next(), - Self::UniqueBtreeString(this) => this.next(), - Self::UniqueBtreeAV(this) => this.next(), + Self::BTreeBool(this) => this.next(), + Self::BTreeU8(this) => this.next(), + Self::BTreeSumTag(this) => this.next(), + Self::BTreeI8(this) => this.next(), + Self::BTreeU16(this) => this.next(), + Self::BTreeI16(this) => this.next(), + Self::BTreeU32(this) => this.next(), + Self::BTreeI32(this) => this.next(), + Self::BTreeU64(this) => this.next(), + Self::BTreeI64(this) => this.next(), + Self::BTreeU128(this) => this.next(), + Self::BTreeI128(this) => this.next(), + Self::BTreeU256(this) => this.next(), + Self::BTreeI256(this) => this.next(), + Self::BTreeF32(this) => this.next(), + Self::BTreeF64(this) => this.next(), + Self::BTreeString(this) => this.next(), + Self::BTreeAV(this) => this.next(), + + Self::UniqueBTreeBool(this) => this.next(), + Self::UniqueBTreeU8(this) => this.next(), + Self::UniqueBTreeSumTag(this) => this.next(), + Self::UniqueBTreeI8(this) => this.next(), + Self::UniqueBTreeU16(this) => this.next(), + Self::UniqueBTreeI16(this) => this.next(), + Self::UniqueBTreeU32(this) => this.next(), + Self::UniqueBTreeI32(this) => this.next(), + Self::UniqueBTreeU64(this) => this.next(), + Self::UniqueBTreeI64(this) => this.next(), + Self::UniqueBTreeU128(this) => this.next(), + Self::UniqueBTreeI128(this) => this.next(), + Self::UniqueBTreeU256(this) => this.next(), + Self::UniqueBTreeI256(this) => this.next(), + Self::UniqueBTreeF32(this) => this.next(), + Self::UniqueBTreeF64(this) => this.next(), + Self::UniqueBTreeString(this) => this.next(), + Self::UniqueBTreeAV(this) => this.next(), Self::UniqueDirect(this) => this.next(), Self::UniqueDirectU8(this) => this.next(), @@ -224,24 +221,24 @@ impl fmt::Debug for TableIndexRangeIter<'_> { #[derive(Debug, PartialEq, Eq, derive_more::From)] enum TypedIndex { // All the non-unique btree index types. - BtreeBool(BtreeIndex), - BtreeU8(BtreeIndex), - BtreeSumTag(BtreeIndex), - BtreeI8(BtreeIndex), - BtreeU16(BtreeIndex), - BtreeI16(BtreeIndex), - BtreeU32(BtreeIndex), - BtreeI32(BtreeIndex), - BtreeU64(BtreeIndex), - BtreeI64(BtreeIndex), - BtreeU128(BtreeIndex>), - BtreeI128(BtreeIndex>), - BtreeU256(BtreeIndex), - BtreeI256(BtreeIndex), - BtreeF32(BtreeIndex), - BtreeF64(BtreeIndex), - BtreeString(BtreeIndex>), - BtreeAV(BtreeIndex), + BTreeBool(BTreeIndex), + BTreeU8(BTreeIndex), + BTreeSumTag(BTreeIndex), + BTreeI8(BTreeIndex), + BTreeU16(BTreeIndex), + BTreeI16(BTreeIndex), + BTreeU32(BTreeIndex), + BTreeI32(BTreeIndex), + BTreeU64(BTreeIndex), + BTreeI64(BTreeIndex), + BTreeU128(BTreeIndex>), + BTreeI128(BTreeIndex>), + BTreeU256(BTreeIndex), + BTreeI256(BTreeIndex), + BTreeF32(BTreeIndex), + BTreeF64(BTreeIndex), + BTreeString(BTreeIndex>), + BTreeAV(BTreeIndex), // All the non-unique hash index types. HashBool(HashIndex), @@ -264,24 +261,24 @@ enum TypedIndex { HashAV(HashIndex), // All the unique btree index types. - UniqueBtreeBool(BtreeUniqueIndex), - UniqueBtreeU8(BtreeUniqueIndex), - UniqueBtreeSumTag(BtreeUniqueIndex), - UniqueBtreeI8(BtreeUniqueIndex), - UniqueBtreeU16(BtreeUniqueIndex), - UniqueBtreeI16(BtreeUniqueIndex), - UniqueBtreeU32(BtreeUniqueIndex), - UniqueBtreeI32(BtreeUniqueIndex), - UniqueBtreeU64(BtreeUniqueIndex), - UniqueBtreeI64(BtreeUniqueIndex), - UniqueBtreeU128(BtreeUniqueIndex>), - UniqueBtreeI128(BtreeUniqueIndex>), - UniqueBtreeU256(BtreeUniqueIndex), - UniqueBtreeI256(BtreeUniqueIndex), - UniqueBtreeF32(BtreeUniqueIndex), - UniqueBtreeF64(BtreeUniqueIndex), - UniqueBtreeString(BtreeUniqueIndex>), - UniqueBtreeAV(BtreeUniqueIndex), + UniqueBTreeBool(UniqueBTreeIndex), + UniqueBTreeU8(UniqueBTreeIndex), + UniqueBTreeSumTag(UniqueBTreeIndex), + UniqueBTreeI8(UniqueBTreeIndex), + UniqueBTreeU16(UniqueBTreeIndex), + UniqueBTreeI16(UniqueBTreeIndex), + UniqueBTreeU32(UniqueBTreeIndex), + UniqueBTreeI32(UniqueBTreeIndex), + UniqueBTreeU64(UniqueBTreeIndex), + UniqueBTreeI64(UniqueBTreeIndex), + UniqueBTreeU128(UniqueBTreeIndex>), + UniqueBTreeI128(UniqueBTreeIndex>), + UniqueBTreeU256(UniqueBTreeIndex), + UniqueBTreeI256(UniqueBTreeIndex), + UniqueBTreeF32(UniqueBTreeIndex), + UniqueBTreeF64(UniqueBTreeIndex), + UniqueBTreeString(UniqueBTreeIndex>), + UniqueBTreeAV(UniqueBTreeIndex), // All the unique hash index types. UniqueHashBool(UniqueHashIndex), @@ -316,24 +313,24 @@ static_assert_size!(TypedIndex, 64); macro_rules! same_for_all_types { ($scrutinee:expr, $this:ident => $body:expr) => { match $scrutinee { - Self::BtreeBool($this) => $body, - Self::BtreeU8($this) => $body, - Self::BtreeSumTag($this) => $body, - Self::BtreeI8($this) => $body, - Self::BtreeU16($this) => $body, - Self::BtreeI16($this) => $body, - Self::BtreeU32($this) => $body, - Self::BtreeI32($this) => $body, - Self::BtreeU64($this) => $body, - Self::BtreeI64($this) => $body, - Self::BtreeU128($this) => $body, - Self::BtreeI128($this) => $body, - Self::BtreeU256($this) => $body, - Self::BtreeI256($this) => $body, - Self::BtreeF32($this) => $body, - Self::BtreeF64($this) => $body, - Self::BtreeString($this) => $body, - Self::BtreeAV($this) => $body, + Self::BTreeBool($this) => $body, + Self::BTreeU8($this) => $body, + Self::BTreeSumTag($this) => $body, + Self::BTreeI8($this) => $body, + Self::BTreeU16($this) => $body, + Self::BTreeI16($this) => $body, + Self::BTreeU32($this) => $body, + Self::BTreeI32($this) => $body, + Self::BTreeU64($this) => $body, + Self::BTreeI64($this) => $body, + Self::BTreeU128($this) => $body, + Self::BTreeI128($this) => $body, + Self::BTreeU256($this) => $body, + Self::BTreeI256($this) => $body, + Self::BTreeF32($this) => $body, + Self::BTreeF64($this) => $body, + Self::BTreeString($this) => $body, + Self::BTreeAV($this) => $body, Self::HashBool($this) => $body, Self::HashU8($this) => $body, @@ -354,24 +351,24 @@ macro_rules! same_for_all_types { Self::HashString($this) => $body, Self::HashAV($this) => $body, - Self::UniqueBtreeBool($this) => $body, - Self::UniqueBtreeU8($this) => $body, - Self::UniqueBtreeSumTag($this) => $body, - Self::UniqueBtreeI8($this) => $body, - Self::UniqueBtreeU16($this) => $body, - Self::UniqueBtreeI16($this) => $body, - Self::UniqueBtreeU32($this) => $body, - Self::UniqueBtreeI32($this) => $body, - Self::UniqueBtreeU64($this) => $body, - Self::UniqueBtreeI64($this) => $body, - Self::UniqueBtreeU128($this) => $body, - Self::UniqueBtreeI128($this) => $body, - Self::UniqueBtreeU256($this) => $body, - Self::UniqueBtreeI256($this) => $body, - Self::UniqueBtreeF32($this) => $body, - Self::UniqueBtreeF64($this) => $body, - Self::UniqueBtreeString($this) => $body, - Self::UniqueBtreeAV($this) => $body, + Self::UniqueBTreeBool($this) => $body, + Self::UniqueBTreeU8($this) => $body, + Self::UniqueBTreeSumTag($this) => $body, + Self::UniqueBTreeI8($this) => $body, + Self::UniqueBTreeU16($this) => $body, + Self::UniqueBTreeI16($this) => $body, + Self::UniqueBTreeU32($this) => $body, + Self::UniqueBTreeI32($this) => $body, + Self::UniqueBTreeU64($this) => $body, + Self::UniqueBTreeI64($this) => $body, + Self::UniqueBTreeU128($this) => $body, + Self::UniqueBTreeI128($this) => $body, + Self::UniqueBTreeU256($this) => $body, + Self::UniqueBTreeI256($this) => $body, + Self::UniqueBTreeF32($this) => $body, + Self::UniqueBTreeF64($this) => $body, + Self::UniqueBTreeString($this) => $body, + Self::UniqueBTreeAV($this) => $body, Self::UniqueHashBool($this) => $body, Self::UniqueHashU8($this) => $body, @@ -477,57 +474,57 @@ impl TypedIndex { // use a homogeneous map with a native key type. if is_unique { match key_type { - AlgebraicType::Bool => UniqueBtreeBool(<_>::default()), - AlgebraicType::I8 => UniqueBtreeI8(<_>::default()), - AlgebraicType::U8 => UniqueBtreeU8(<_>::default()), - AlgebraicType::I16 => UniqueBtreeI16(<_>::default()), - AlgebraicType::U16 => UniqueBtreeU16(<_>::default()), - AlgebraicType::I32 => UniqueBtreeI32(<_>::default()), - AlgebraicType::U32 => UniqueBtreeU32(<_>::default()), - AlgebraicType::I64 => UniqueBtreeI64(<_>::default()), - AlgebraicType::U64 => UniqueBtreeU64(<_>::default()), - AlgebraicType::I128 => UniqueBtreeI128(<_>::default()), - AlgebraicType::U128 => UniqueBtreeU128(<_>::default()), - AlgebraicType::I256 => UniqueBtreeI256(<_>::default()), - AlgebraicType::U256 => UniqueBtreeU256(<_>::default()), - AlgebraicType::F32 => UniqueBtreeF32(<_>::default()), - AlgebraicType::F64 => UniqueBtreeF64(<_>::default()), - AlgebraicType::String => UniqueBtreeString(<_>::default()), + AlgebraicType::Bool => UniqueBTreeBool(<_>::default()), + AlgebraicType::I8 => UniqueBTreeI8(<_>::default()), + AlgebraicType::U8 => UniqueBTreeU8(<_>::default()), + AlgebraicType::I16 => UniqueBTreeI16(<_>::default()), + AlgebraicType::U16 => UniqueBTreeU16(<_>::default()), + AlgebraicType::I32 => UniqueBTreeI32(<_>::default()), + AlgebraicType::U32 => UniqueBTreeU32(<_>::default()), + AlgebraicType::I64 => UniqueBTreeI64(<_>::default()), + AlgebraicType::U64 => UniqueBTreeU64(<_>::default()), + AlgebraicType::I128 => UniqueBTreeI128(<_>::default()), + AlgebraicType::U128 => UniqueBTreeU128(<_>::default()), + AlgebraicType::I256 => UniqueBTreeI256(<_>::default()), + AlgebraicType::U256 => UniqueBTreeU256(<_>::default()), + AlgebraicType::F32 => UniqueBTreeF32(<_>::default()), + AlgebraicType::F64 => UniqueBTreeF64(<_>::default()), + AlgebraicType::String => UniqueBTreeString(<_>::default()), // For a plain enum, use `u8` as the native type. // We use a direct index here - AlgebraicType::Sum(sum) if sum.is_simple_enum() => UniqueBtreeSumTag(<_>::default()), + AlgebraicType::Sum(sum) if sum.is_simple_enum() => UniqueBTreeSumTag(<_>::default()), // The index is either multi-column, // or we don't care to specialize on the key type, // so use a map keyed on `AlgebraicValue`. - _ => UniqueBtreeAV(<_>::default()), + _ => UniqueBTreeAV(<_>::default()), } } else { match key_type { - AlgebraicType::Bool => BtreeBool(<_>::default()), - AlgebraicType::I8 => BtreeI8(<_>::default()), - AlgebraicType::U8 => BtreeU8(<_>::default()), - AlgebraicType::I16 => BtreeI16(<_>::default()), - AlgebraicType::U16 => BtreeU16(<_>::default()), - AlgebraicType::I32 => BtreeI32(<_>::default()), - AlgebraicType::U32 => BtreeU32(<_>::default()), - AlgebraicType::I64 => BtreeI64(<_>::default()), - AlgebraicType::U64 => BtreeU64(<_>::default()), - AlgebraicType::I128 => BtreeI128(<_>::default()), - AlgebraicType::U128 => BtreeU128(<_>::default()), - AlgebraicType::I256 => BtreeI256(<_>::default()), - AlgebraicType::U256 => BtreeU256(<_>::default()), - AlgebraicType::F32 => BtreeF32(<_>::default()), - AlgebraicType::F64 => BtreeF64(<_>::default()), - AlgebraicType::String => BtreeString(<_>::default()), + AlgebraicType::Bool => BTreeBool(<_>::default()), + AlgebraicType::I8 => BTreeI8(<_>::default()), + AlgebraicType::U8 => BTreeU8(<_>::default()), + AlgebraicType::I16 => BTreeI16(<_>::default()), + AlgebraicType::U16 => BTreeU16(<_>::default()), + AlgebraicType::I32 => BTreeI32(<_>::default()), + AlgebraicType::U32 => BTreeU32(<_>::default()), + AlgebraicType::I64 => BTreeI64(<_>::default()), + AlgebraicType::U64 => BTreeU64(<_>::default()), + AlgebraicType::I128 => BTreeI128(<_>::default()), + AlgebraicType::U128 => BTreeU128(<_>::default()), + AlgebraicType::I256 => BTreeI256(<_>::default()), + AlgebraicType::U256 => BTreeU256(<_>::default()), + AlgebraicType::F32 => BTreeF32(<_>::default()), + AlgebraicType::F64 => BTreeF64(<_>::default()), + AlgebraicType::String => BTreeString(<_>::default()), // For a plain enum, use `u8` as the native type. - AlgebraicType::Sum(sum) if sum.is_simple_enum() => BtreeSumTag(<_>::default()), + AlgebraicType::Sum(sum) if sum.is_simple_enum() => BTreeSumTag(<_>::default()), // The index is either multi-column, // or we don't care to specialize on the key type, // so use a map keyed on `AlgebraicValue`. - _ => BtreeAV(<_>::default()), + _ => BTreeAV(<_>::default()), } } } @@ -605,29 +602,29 @@ impl TypedIndex { fn is_unique(&self) -> bool { use TypedIndex::*; match self { - BtreeBool(_) | BtreeU8(_) | BtreeSumTag(_) | BtreeI8(_) | BtreeU16(_) | BtreeI16(_) | BtreeU32(_) - | BtreeI32(_) | BtreeU64(_) | BtreeI64(_) | BtreeU128(_) | BtreeI128(_) | BtreeU256(_) | BtreeI256(_) - | BtreeF32(_) | BtreeF64(_) | BtreeString(_) | BtreeAV(_) | HashBool(_) | HashU8(_) | HashSumTag(_) + BTreeBool(_) | BTreeU8(_) | BTreeSumTag(_) | BTreeI8(_) | BTreeU16(_) | BTreeI16(_) | BTreeU32(_) + | BTreeI32(_) | BTreeU64(_) | BTreeI64(_) | BTreeU128(_) | BTreeI128(_) | BTreeU256(_) | BTreeI256(_) + | BTreeF32(_) | BTreeF64(_) | BTreeString(_) | BTreeAV(_) | HashBool(_) | HashU8(_) | HashSumTag(_) | HashI8(_) | HashU16(_) | HashI16(_) | HashU32(_) | HashI32(_) | HashU64(_) | HashI64(_) | HashU128(_) | HashI128(_) | HashU256(_) | HashI256(_) | HashF32(_) | HashF64(_) | HashString(_) | HashAV(_) => false, - UniqueBtreeBool(_) - | UniqueBtreeU8(_) - | UniqueBtreeSumTag(_) - | UniqueBtreeI8(_) - | UniqueBtreeU16(_) - | UniqueBtreeI16(_) - | UniqueBtreeU32(_) - | UniqueBtreeI32(_) - | UniqueBtreeU64(_) - | UniqueBtreeI64(_) - | UniqueBtreeU128(_) - | UniqueBtreeI128(_) - | UniqueBtreeU256(_) - | UniqueBtreeI256(_) - | UniqueBtreeF32(_) - | UniqueBtreeF64(_) - | UniqueBtreeString(_) - | UniqueBtreeAV(_) + UniqueBTreeBool(_) + | UniqueBTreeU8(_) + | UniqueBTreeSumTag(_) + | UniqueBTreeI8(_) + | UniqueBTreeU16(_) + | UniqueBTreeI16(_) + | UniqueBTreeU32(_) + | UniqueBTreeI32(_) + | UniqueBTreeU64(_) + | UniqueBTreeI64(_) + | UniqueBTreeU128(_) + | UniqueBTreeI128(_) + | UniqueBTreeU256(_) + | UniqueBTreeI256(_) + | UniqueBTreeF32(_) + | UniqueBTreeF64(_) + | UniqueBTreeString(_) + | UniqueBTreeAV(_) | UniqueHashBool(_) | UniqueHashU8(_) | UniqueHashSumTag(_) @@ -716,7 +713,7 @@ impl TypedIndex { row_ref: RowRef<'_>, ) -> (Result<(), RowPointer>, Option) where - TypedIndex: From>, + TypedIndex: From>, { let key = project_to_singleton_key(cols, row_ref); let ptr = row_ref.pointer(); @@ -742,24 +739,24 @@ impl TypedIndex { } let (res, new) = match self { - Self::BtreeBool(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeU8(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeSumTag(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeI8(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeU16(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeI16(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeU32(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeI32(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeU64(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeI64(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeU128(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeI128(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeU256(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeI256(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeF32(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeF64(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeString(idx) => insert_at_type(idx, cols, row_ref), - Self::BtreeAV(idx) => insert_av(idx, cols, row_ref), + Self::BTreeBool(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeU8(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeSumTag(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeI8(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeU16(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeI16(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeU32(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeI32(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeU64(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeI64(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeU128(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeI128(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeU256(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeI256(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeF32(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeF64(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeString(idx) => insert_at_type(idx, cols, row_ref), + Self::BTreeAV(idx) => insert_av(idx, cols, row_ref), Self::HashBool(idx) => insert_at_type(idx, cols, row_ref), Self::HashU8(idx) => insert_at_type(idx, cols, row_ref), Self::HashSumTag(idx) => insert_at_type(idx, cols, row_ref), @@ -778,24 +775,24 @@ impl TypedIndex { Self::HashF64(idx) => insert_at_type(idx, cols, row_ref), Self::HashString(idx) => insert_at_type(idx, cols, row_ref), Self::HashAV(idx) => insert_av(idx, cols, row_ref), - Self::UniqueBtreeBool(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeU8(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeSumTag(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeI8(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeU16(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeI16(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeU32(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeI32(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeU64(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeI64(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeU128(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeI128(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeU256(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeI256(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeF32(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeF64(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeString(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBtreeAV(idx) => insert_av(idx, cols, row_ref), + Self::UniqueBTreeBool(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeU8(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeSumTag(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeI8(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeU16(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeI16(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeU32(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeI32(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeU64(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeI64(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeU128(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeI128(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeU256(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeI256(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeF32(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeF64(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeString(idx) => insert_at_type(idx, cols, row_ref), + Self::UniqueBTreeAV(idx) => insert_av(idx, cols, row_ref), Self::UniqueHashBool(idx) => insert_at_type(idx, cols, row_ref), Self::UniqueHashU8(idx) => insert_at_type(idx, cols, row_ref), Self::UniqueHashSumTag(idx) => insert_at_type(idx, cols, row_ref), @@ -865,24 +862,24 @@ impl TypedIndex { use core::convert::identity as id; match self { - Self::BtreeBool(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeU8(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeSumTag(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeI8(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeU16(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeI16(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeU32(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeI32(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeU64(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeI64(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeU128(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeI128(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeU256(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeI256(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeF32(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeF64(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeString(this) => delete_at_type(this, cols, row_ref, id), - Self::BtreeAV(this) => delete_av(this, cols, row_ref), + Self::BTreeBool(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeU8(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeSumTag(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeI8(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeU16(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeI16(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeU32(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeI32(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeU64(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeI64(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeU128(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeI128(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeU256(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeI256(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeF32(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeF64(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeString(this) => delete_at_type(this, cols, row_ref, id), + Self::BTreeAV(this) => delete_av(this, cols, row_ref), Self::HashBool(this) => delete_at_type(this, cols, row_ref, id), Self::HashU8(this) => delete_at_type(this, cols, row_ref, id), Self::HashSumTag(this) => delete_at_type(this, cols, row_ref, id), @@ -901,24 +898,24 @@ impl TypedIndex { Self::HashF64(this) => delete_at_type(this, cols, row_ref, id), Self::HashString(this) => delete_at_type(this, cols, row_ref, id), Self::HashAV(this) => delete_av(this, cols, row_ref), - Self::UniqueBtreeBool(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeU8(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeSumTag(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeI8(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeU16(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeI16(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeU32(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeI32(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeU64(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeI64(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeU128(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeI128(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeU256(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeI256(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeF32(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeF64(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeString(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBtreeAV(this) => delete_av(this, cols, row_ref), + Self::UniqueBTreeBool(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeU8(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeSumTag(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeI8(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeU16(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeI16(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeU32(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeI32(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeU64(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeI64(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeU128(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeI128(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeU256(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeI256(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeF32(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeF64(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeString(this) => delete_at_type(this, cols, row_ref, id), + Self::UniqueBTreeAV(this) => delete_av(this, cols, row_ref), Self::UniqueHashBool(this) => delete_at_type(this, cols, row_ref, id), Self::UniqueHashU8(this) => delete_at_type(this, cols, row_ref, id), Self::UniqueHashSumTag(this) => delete_at_type(this, cols, row_ref, id), @@ -957,24 +954,24 @@ impl TypedIndex { use TypedIndex::*; use TypedIndexPointIter::*; match self { - BtreeBool(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_bool)), - BtreeU8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u8)), - BtreeSumTag(this) => NonUnique(iter_at_type(this, key, as_sum_tag)), - BtreeI8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i8)), - BtreeU16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u16)), - BtreeI16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i16)), - BtreeU32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u32)), - BtreeI32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i32)), - BtreeU64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u64)), - BtreeI64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i64)), - BtreeU128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u128)), - BtreeI128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i128)), - BtreeU256(this) => NonUnique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), - BtreeI256(this) => NonUnique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), - BtreeF32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f32)), - BtreeF64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f64)), - BtreeString(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_string)), - BtreeAV(this) => NonUnique(this.seek_point(key)), + BTreeBool(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_bool)), + BTreeU8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u8)), + BTreeSumTag(this) => NonUnique(iter_at_type(this, key, as_sum_tag)), + BTreeI8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i8)), + BTreeU16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u16)), + BTreeI16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i16)), + BTreeU32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u32)), + BTreeI32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i32)), + BTreeU64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u64)), + BTreeI64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i64)), + BTreeU128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u128)), + BTreeI128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i128)), + BTreeU256(this) => NonUnique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), + BTreeI256(this) => NonUnique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), + BTreeF32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f32)), + BTreeF64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f64)), + BTreeString(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_string)), + BTreeAV(this) => NonUnique(this.seek_point(key)), HashBool(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_bool)), HashU8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u8)), HashSumTag(this) => NonUnique(iter_at_type(this, key, as_sum_tag)), @@ -993,24 +990,24 @@ impl TypedIndex { HashF64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f64)), HashString(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_string)), HashAV(this) => NonUnique(this.seek_point(key)), - UniqueBtreeBool(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_bool)), - UniqueBtreeU8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u8)), - UniqueBtreeSumTag(this) => Unique(iter_at_type(this, key, as_sum_tag)), - UniqueBtreeI8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i8)), - UniqueBtreeU16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u16)), - UniqueBtreeI16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i16)), - UniqueBtreeU32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u32)), - UniqueBtreeI32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i32)), - UniqueBtreeU64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u64)), - UniqueBtreeI64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i64)), - UniqueBtreeU128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u128)), - UniqueBtreeI128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i128)), - UniqueBtreeU256(this) => Unique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), - UniqueBtreeI256(this) => Unique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), - UniqueBtreeF32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f32)), - UniqueBtreeF64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f64)), - UniqueBtreeString(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_string)), - UniqueBtreeAV(this) => Unique(this.seek_point(key)), + UniqueBTreeBool(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_bool)), + UniqueBTreeU8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u8)), + UniqueBTreeSumTag(this) => Unique(iter_at_type(this, key, as_sum_tag)), + UniqueBTreeI8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i8)), + UniqueBTreeU16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u16)), + UniqueBTreeI16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i16)), + UniqueBTreeU32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u32)), + UniqueBTreeI32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i32)), + UniqueBTreeU64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u64)), + UniqueBTreeI64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i64)), + UniqueBTreeU128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u128)), + UniqueBTreeI128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i128)), + UniqueBTreeU256(this) => Unique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), + UniqueBTreeI256(this) => Unique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), + UniqueBTreeF32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f32)), + UniqueBTreeF64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f64)), + UniqueBTreeString(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_string)), + UniqueBTreeAV(this) => Unique(this.seek_point(key)), UniqueHashBool(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_bool)), UniqueHashU8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u8)), @@ -1107,43 +1104,43 @@ impl TypedIndex { // Ensure we don't panic inside `BTreeMap::seek_range`. _ if is_empty(range) => RangeEmpty, - Self::BtreeBool(this) => BtreeBool(iter_at_type(this, range, AlgebraicValue::as_bool)), - Self::BtreeU8(this) => BtreeU8(iter_at_type(this, range, AlgebraicValue::as_u8)), - Self::BtreeSumTag(this) => BtreeSumTag(iter_at_type(this, range, as_sum_tag)), - Self::BtreeI8(this) => BtreeI8(iter_at_type(this, range, AlgebraicValue::as_i8)), - Self::BtreeU16(this) => BtreeU16(iter_at_type(this, range, AlgebraicValue::as_u16)), - Self::BtreeI16(this) => BtreeI16(iter_at_type(this, range, AlgebraicValue::as_i16)), - Self::BtreeU32(this) => BtreeU32(iter_at_type(this, range, AlgebraicValue::as_u32)), - Self::BtreeI32(this) => BtreeI32(iter_at_type(this, range, AlgebraicValue::as_i32)), - Self::BtreeU64(this) => BtreeU64(iter_at_type(this, range, AlgebraicValue::as_u64)), - Self::BtreeI64(this) => BtreeI64(iter_at_type(this, range, AlgebraicValue::as_i64)), - Self::BtreeU128(this) => BtreeU128(iter_at_type(this, range, AlgebraicValue::as_u128)), - Self::BtreeI128(this) => BtreeI128(iter_at_type(this, range, AlgebraicValue::as_i128)), - Self::BtreeU256(this) => BtreeU256(iter_at_type(this, range, |av| av.as_u256().map(|x| &**x))), - Self::BtreeI256(this) => BtreeI256(iter_at_type(this, range, |av| av.as_i256().map(|x| &**x))), - Self::BtreeF32(this) => BtreeF32(iter_at_type(this, range, AlgebraicValue::as_f32)), - Self::BtreeF64(this) => BtreeF64(iter_at_type(this, range, AlgebraicValue::as_f64)), - Self::BtreeString(this) => BtreeString(iter_at_type(this, range, AlgebraicValue::as_string)), - Self::BtreeAV(this) => BtreeAV(this.seek_range(range)), - - Self::UniqueBtreeBool(this) => UniqueBtreeBool(iter_at_type(this, range, AlgebraicValue::as_bool)), - Self::UniqueBtreeU8(this) => UniqueBtreeU8(iter_at_type(this, range, AlgebraicValue::as_u8)), - Self::UniqueBtreeSumTag(this) => UniqueBtreeSumTag(iter_at_type(this, range, as_sum_tag)), - Self::UniqueBtreeI8(this) => UniqueBtreeI8(iter_at_type(this, range, AlgebraicValue::as_i8)), - Self::UniqueBtreeU16(this) => UniqueBtreeU16(iter_at_type(this, range, AlgebraicValue::as_u16)), - Self::UniqueBtreeI16(this) => UniqueBtreeI16(iter_at_type(this, range, AlgebraicValue::as_i16)), - Self::UniqueBtreeU32(this) => UniqueBtreeU32(iter_at_type(this, range, AlgebraicValue::as_u32)), - Self::UniqueBtreeI32(this) => UniqueBtreeI32(iter_at_type(this, range, AlgebraicValue::as_i32)), - Self::UniqueBtreeU64(this) => UniqueBtreeU64(iter_at_type(this, range, AlgebraicValue::as_u64)), - Self::UniqueBtreeI64(this) => UniqueBtreeI64(iter_at_type(this, range, AlgebraicValue::as_i64)), - Self::UniqueBtreeU128(this) => UniqueBtreeU128(iter_at_type(this, range, AlgebraicValue::as_u128)), - Self::UniqueBtreeI128(this) => UniqueBtreeI128(iter_at_type(this, range, AlgebraicValue::as_i128)), - Self::UniqueBtreeF32(this) => UniqueBtreeF32(iter_at_type(this, range, AlgebraicValue::as_f32)), - Self::UniqueBtreeF64(this) => UniqueBtreeF64(iter_at_type(this, range, AlgebraicValue::as_f64)), - Self::UniqueBtreeU256(this) => UniqueBtreeU256(iter_at_type(this, range, |av| av.as_u256().map(|x| &**x))), - Self::UniqueBtreeI256(this) => UniqueBtreeI256(iter_at_type(this, range, |av| av.as_i256().map(|x| &**x))), - Self::UniqueBtreeString(this) => UniqueBtreeString(iter_at_type(this, range, AlgebraicValue::as_string)), - Self::UniqueBtreeAV(this) => UniqueBtreeAV(this.seek_range(range)), + Self::BTreeBool(this) => BTreeBool(iter_at_type(this, range, AlgebraicValue::as_bool)), + Self::BTreeU8(this) => BTreeU8(iter_at_type(this, range, AlgebraicValue::as_u8)), + Self::BTreeSumTag(this) => BTreeSumTag(iter_at_type(this, range, as_sum_tag)), + Self::BTreeI8(this) => BTreeI8(iter_at_type(this, range, AlgebraicValue::as_i8)), + Self::BTreeU16(this) => BTreeU16(iter_at_type(this, range, AlgebraicValue::as_u16)), + Self::BTreeI16(this) => BTreeI16(iter_at_type(this, range, AlgebraicValue::as_i16)), + Self::BTreeU32(this) => BTreeU32(iter_at_type(this, range, AlgebraicValue::as_u32)), + Self::BTreeI32(this) => BTreeI32(iter_at_type(this, range, AlgebraicValue::as_i32)), + Self::BTreeU64(this) => BTreeU64(iter_at_type(this, range, AlgebraicValue::as_u64)), + Self::BTreeI64(this) => BTreeI64(iter_at_type(this, range, AlgebraicValue::as_i64)), + Self::BTreeU128(this) => BTreeU128(iter_at_type(this, range, AlgebraicValue::as_u128)), + Self::BTreeI128(this) => BTreeI128(iter_at_type(this, range, AlgebraicValue::as_i128)), + Self::BTreeU256(this) => BTreeU256(iter_at_type(this, range, |av| av.as_u256().map(|x| &**x))), + Self::BTreeI256(this) => BTreeI256(iter_at_type(this, range, |av| av.as_i256().map(|x| &**x))), + Self::BTreeF32(this) => BTreeF32(iter_at_type(this, range, AlgebraicValue::as_f32)), + Self::BTreeF64(this) => BTreeF64(iter_at_type(this, range, AlgebraicValue::as_f64)), + Self::BTreeString(this) => BTreeString(iter_at_type(this, range, AlgebraicValue::as_string)), + Self::BTreeAV(this) => BTreeAV(this.seek_range(range)), + + Self::UniqueBTreeBool(this) => UniqueBTreeBool(iter_at_type(this, range, AlgebraicValue::as_bool)), + Self::UniqueBTreeU8(this) => UniqueBTreeU8(iter_at_type(this, range, AlgebraicValue::as_u8)), + Self::UniqueBTreeSumTag(this) => UniqueBTreeSumTag(iter_at_type(this, range, as_sum_tag)), + Self::UniqueBTreeI8(this) => UniqueBTreeI8(iter_at_type(this, range, AlgebraicValue::as_i8)), + Self::UniqueBTreeU16(this) => UniqueBTreeU16(iter_at_type(this, range, AlgebraicValue::as_u16)), + Self::UniqueBTreeI16(this) => UniqueBTreeI16(iter_at_type(this, range, AlgebraicValue::as_i16)), + Self::UniqueBTreeU32(this) => UniqueBTreeU32(iter_at_type(this, range, AlgebraicValue::as_u32)), + Self::UniqueBTreeI32(this) => UniqueBTreeI32(iter_at_type(this, range, AlgebraicValue::as_i32)), + Self::UniqueBTreeU64(this) => UniqueBTreeU64(iter_at_type(this, range, AlgebraicValue::as_u64)), + Self::UniqueBTreeI64(this) => UniqueBTreeI64(iter_at_type(this, range, AlgebraicValue::as_i64)), + Self::UniqueBTreeU128(this) => UniqueBTreeU128(iter_at_type(this, range, AlgebraicValue::as_u128)), + Self::UniqueBTreeI128(this) => UniqueBTreeI128(iter_at_type(this, range, AlgebraicValue::as_i128)), + Self::UniqueBTreeF32(this) => UniqueBTreeF32(iter_at_type(this, range, AlgebraicValue::as_f32)), + Self::UniqueBTreeF64(this) => UniqueBTreeF64(iter_at_type(this, range, AlgebraicValue::as_f64)), + Self::UniqueBTreeU256(this) => UniqueBTreeU256(iter_at_type(this, range, |av| av.as_u256().map(|x| &**x))), + Self::UniqueBTreeI256(this) => UniqueBTreeI256(iter_at_type(this, range, |av| av.as_i256().map(|x| &**x))), + Self::UniqueBTreeString(this) => UniqueBTreeString(iter_at_type(this, range, AlgebraicValue::as_string)), + Self::UniqueBTreeAV(this) => UniqueBTreeAV(this.seek_range(range)), Self::UniqueDirectSumTag(this) => UniqueDirectU8(iter_at_type(this, range, as_sum_tag)), Self::UniqueDirectU8(this) => UniqueDirect(iter_at_type(this, range, AlgebraicValue::as_u8)), @@ -1341,24 +1338,24 @@ impl TableIndex { use TypedIndex::*; match (&self.idx, &other.idx) { // For non-unique indices, it's always possible to merge. - (BtreeBool(_), BtreeBool(_)) - | (BtreeU8(_), BtreeU8(_)) - | (BtreeSumTag(_), BtreeSumTag(_)) - | (BtreeI8(_), BtreeI8(_)) - | (BtreeU16(_), BtreeU16(_)) - | (BtreeI16(_), BtreeI16(_)) - | (BtreeU32(_), BtreeU32(_)) - | (BtreeI32(_), BtreeI32(_)) - | (BtreeU64(_), BtreeU64(_)) - | (BtreeI64(_), BtreeI64(_)) - | (BtreeU128(_), BtreeU128(_)) - | (BtreeI128(_), BtreeI128(_)) - | (BtreeU256(_), BtreeU256(_)) - | (BtreeI256(_), BtreeI256(_)) - | (BtreeF32(_), BtreeF32(_)) - | (BtreeF64(_), BtreeF64(_)) - | (BtreeString(_), BtreeString(_)) - | (BtreeAV(_), BtreeAV(_)) + (BTreeBool(_), BTreeBool(_)) + | (BTreeU8(_), BTreeU8(_)) + | (BTreeSumTag(_), BTreeSumTag(_)) + | (BTreeI8(_), BTreeI8(_)) + | (BTreeU16(_), BTreeU16(_)) + | (BTreeI16(_), BTreeI16(_)) + | (BTreeU32(_), BTreeU32(_)) + | (BTreeI32(_), BTreeI32(_)) + | (BTreeU64(_), BTreeU64(_)) + | (BTreeI64(_), BTreeI64(_)) + | (BTreeU128(_), BTreeU128(_)) + | (BTreeI128(_), BTreeI128(_)) + | (BTreeU256(_), BTreeU256(_)) + | (BTreeI256(_), BTreeI256(_)) + | (BTreeF32(_), BTreeF32(_)) + | (BTreeF64(_), BTreeF64(_)) + | (BTreeString(_), BTreeString(_)) + | (BTreeAV(_), BTreeAV(_)) | (HashBool(_), HashBool(_)) | (HashU8(_), HashU8(_)) | (HashSumTag(_), HashSumTag(_)) @@ -1378,24 +1375,24 @@ impl TableIndex { | (HashString(_), HashString(_)) | (HashAV(_), HashAV(_)) => Ok(()), // For unique indices, we'll need to see if everything in `other` can be added to `idx`. - (UniqueBtreeBool(idx), UniqueBtreeBool(other)) => idx.can_merge(other, ignore), - (UniqueBtreeU8(idx), UniqueBtreeU8(other)) => idx.can_merge(other, ignore), - (UniqueBtreeSumTag(idx), UniqueBtreeSumTag(other)) => idx.can_merge(other, ignore), - (UniqueBtreeI8(idx), UniqueBtreeI8(other)) => idx.can_merge(other, ignore), - (UniqueBtreeU16(idx), UniqueBtreeU16(other)) => idx.can_merge(other, ignore), - (UniqueBtreeI16(idx), UniqueBtreeI16(other)) => idx.can_merge(other, ignore), - (UniqueBtreeU32(idx), UniqueBtreeU32(other)) => idx.can_merge(other, ignore), - (UniqueBtreeI32(idx), UniqueBtreeI32(other)) => idx.can_merge(other, ignore), - (UniqueBtreeU64(idx), UniqueBtreeU64(other)) => idx.can_merge(other, ignore), - (UniqueBtreeI64(idx), UniqueBtreeI64(other)) => idx.can_merge(other, ignore), - (UniqueBtreeU128(idx), UniqueBtreeU128(other)) => idx.can_merge(other, ignore), - (UniqueBtreeI128(idx), UniqueBtreeI128(other)) => idx.can_merge(other, ignore), - (UniqueBtreeU256(idx), UniqueBtreeU256(other)) => idx.can_merge(other, ignore), - (UniqueBtreeI256(idx), UniqueBtreeI256(other)) => idx.can_merge(other, ignore), - (UniqueBtreeF32(idx), UniqueBtreeF32(other)) => idx.can_merge(other, ignore), - (UniqueBtreeF64(idx), UniqueBtreeF64(other)) => idx.can_merge(other, ignore), - (UniqueBtreeString(idx), UniqueBtreeString(other)) => idx.can_merge(other, ignore), - (UniqueBtreeAV(idx), UniqueBtreeAV(other)) => idx.can_merge(other, ignore), + (UniqueBTreeBool(idx), UniqueBTreeBool(other)) => idx.can_merge(other, ignore), + (UniqueBTreeU8(idx), UniqueBTreeU8(other)) => idx.can_merge(other, ignore), + (UniqueBTreeSumTag(idx), UniqueBTreeSumTag(other)) => idx.can_merge(other, ignore), + (UniqueBTreeI8(idx), UniqueBTreeI8(other)) => idx.can_merge(other, ignore), + (UniqueBTreeU16(idx), UniqueBTreeU16(other)) => idx.can_merge(other, ignore), + (UniqueBTreeI16(idx), UniqueBTreeI16(other)) => idx.can_merge(other, ignore), + (UniqueBTreeU32(idx), UniqueBTreeU32(other)) => idx.can_merge(other, ignore), + (UniqueBTreeI32(idx), UniqueBTreeI32(other)) => idx.can_merge(other, ignore), + (UniqueBTreeU64(idx), UniqueBTreeU64(other)) => idx.can_merge(other, ignore), + (UniqueBTreeI64(idx), UniqueBTreeI64(other)) => idx.can_merge(other, ignore), + (UniqueBTreeU128(idx), UniqueBTreeU128(other)) => idx.can_merge(other, ignore), + (UniqueBTreeI128(idx), UniqueBTreeI128(other)) => idx.can_merge(other, ignore), + (UniqueBTreeU256(idx), UniqueBTreeU256(other)) => idx.can_merge(other, ignore), + (UniqueBTreeI256(idx), UniqueBTreeI256(other)) => idx.can_merge(other, ignore), + (UniqueBTreeF32(idx), UniqueBTreeF32(other)) => idx.can_merge(other, ignore), + (UniqueBTreeF64(idx), UniqueBTreeF64(other)) => idx.can_merge(other, ignore), + (UniqueBTreeString(idx), UniqueBTreeString(other)) => idx.can_merge(other, ignore), + (UniqueBTreeAV(idx), UniqueBTreeAV(other)) => idx.can_merge(other, ignore), (UniqueHashBool(idx), UniqueHashBool(other)) => idx.can_merge(other, ignore), (UniqueHashU8(idx), UniqueHashU8(other)) => idx.can_merge(other, ignore), (UniqueHashSumTag(idx), UniqueHashSumTag(other)) => idx.can_merge(other, ignore), diff --git a/crates/table/src/table_index/uniquemap.rs b/crates/table/src/table_index/unique_btree_index.rs similarity index 84% rename from crates/table/src/table_index/uniquemap.rs rename to crates/table/src/table_index/unique_btree_index.rs index 7701dc64fdf..ba758201478 100644 --- a/crates/table/src/table_index/uniquemap.rs +++ b/crates/table/src/table_index/unique_btree_index.rs @@ -8,14 +8,14 @@ use std::collections::btree_map::{BTreeMap, Entry, Range}; /// /// (This is just a `BTreeMap`) with a slightly modified interface. #[derive(Debug, PartialEq, Eq, Clone)] -pub struct UniqueMap { +pub struct UniqueBTreeIndex { /// The map is backed by a `BTreeMap` for relating a key to a value. map: BTreeMap, /// Storage for [`Index::num_key_bytes`]. num_key_bytes: K::MemoStorage, } -impl Default for UniqueMap { +impl Default for UniqueBTreeIndex { fn default() -> Self { Self { map: <_>::default(), @@ -24,14 +24,14 @@ impl Default for UniqueMap { } } -impl MemoryUsage for UniqueMap { +impl MemoryUsage for UniqueBTreeIndex { fn heap_usage(&self) -> usize { let Self { map, num_key_bytes } = self; map.heap_usage() + num_key_bytes.heap_usage() } } -impl Index for UniqueMap { +impl Index for UniqueBTreeIndex { type Key = K; fn clone_structure(&self) -> Self { @@ -94,7 +94,7 @@ impl Index for UniqueMap { } } -/// An iterator over the potential value in a [`UniqueMap`] for a given key. +/// An iterator over the potential value in a unique index for a given key. pub struct UniquePointIter { /// The iterator seeking for matching keys in the range. pub(super) iter: IntoIter, @@ -116,27 +116,27 @@ impl Iterator for UniquePointIter { } } -impl RangedIndex for UniqueMap { +impl RangedIndex for UniqueBTreeIndex { type RangeIter<'a> - = UniqueMapRangeIter<'a, K> + = UniqueBTreeIndexRangeIter<'a, K> where Self: 'a; fn seek_range(&self, range: &impl RangeBounds) -> Self::RangeIter<'_> { - UniqueMapRangeIter { + UniqueBTreeIndexRangeIter { iter: self.map.range((range.start_bound(), range.end_bound())), } } } -/// An iterator over values in a [`UniqueMap`] where the keys are in a certain range. +/// An iterator over values in a [`UniqueBTreeIndex`] where the keys are in a certain range. #[derive(Clone)] -pub struct UniqueMapRangeIter<'a, K> { +pub struct UniqueBTreeIndexRangeIter<'a, K> { /// The iterator seeking for matching keys in the range. iter: Range<'a, K, RowPointer>, } -impl<'a, K> Iterator for UniqueMapRangeIter<'a, K> { +impl<'a, K> Iterator for UniqueBTreeIndexRangeIter<'a, K> { type Item = RowPointer; fn next(&mut self) -> Option { diff --git a/crates/table/src/table_index/unique_direct_fixed_cap_index.rs b/crates/table/src/table_index/unique_direct_fixed_cap_index.rs index c13c12f7105..ef3dbdc97f9 100644 --- a/crates/table/src/table_index/unique_direct_fixed_cap_index.rs +++ b/crates/table/src/table_index/unique_direct_fixed_cap_index.rs @@ -1,5 +1,5 @@ use super::index::{Index, RangedIndex}; -use super::uniquemap::UniquePointIter; +use super::unique_btree_index::UniquePointIter; use super::unique_direct_index::{expose, injest, ToFromUsize, NONE_PTR}; use crate::indexes::RowPointer; use crate::table_index::KeySize; diff --git a/crates/table/src/table_index/unique_direct_index.rs b/crates/table/src/table_index/unique_direct_index.rs index cd034e36356..abdd688e80f 100644 --- a/crates/table/src/table_index/unique_direct_index.rs +++ b/crates/table/src/table_index/unique_direct_index.rs @@ -1,6 +1,6 @@ use super::index::{Despecialize, Index, RangedIndex}; -use super::uniquemap::UniquePointIter; -use super::{BtreeUniqueIndex, KeySize}; +use super::key_size::KeySize; +use super::unique_btree_index::{UniqueBTreeIndex as UniqueBtreeIndex, UniquePointIter}; use crate::indexes::{PageIndex, PageOffset, RowPointer, SquashedOffset}; use core::marker::PhantomData; use core::mem; @@ -361,8 +361,8 @@ impl Iterator for UniqueDirectIndexRangeIter<'_> { impl UniqueDirectIndex { /// Convert this Direct index into a B-Tree index. - pub fn into_btree(&self) -> BtreeUniqueIndex { - let mut new_index: BtreeUniqueIndex = <_>::default(); + pub fn into_btree(&self) -> UniqueBtreeIndex { + let mut new_index: UniqueBtreeIndex = <_>::default(); for (key_outer, inner) in self.outer.iter().enumerate() { let Some(inner) = inner else { diff --git a/crates/table/src/table_index/unique_hash_index.rs b/crates/table/src/table_index/unique_hash_index.rs index 2415896fa0e..71a9f127a73 100644 --- a/crates/table/src/table_index/unique_hash_index.rs +++ b/crates/table/src/table_index/unique_hash_index.rs @@ -1,5 +1,5 @@ +use super::unique_btree_index::UniquePointIter; use super::{Index, KeySize}; -use crate::table_index::uniquemap::UniquePointIter; use crate::{indexes::RowPointer, table_index::key_size::KeyBytesStorage}; use core::hash::Hash; use spacetimedb_data_structures::map::hash_map::Entry; From 06e914511f5ea922482cb3d792f0ef86cee4c38b Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 12 Feb 2026 18:17:58 +0000 Subject: [PATCH 4/8] adjust datastore with `IndexKey` type to avoid `AlgebraicValue` as intermediary --- crates/core/src/config.rs | 16 +- crates/core/src/db/relational_db.rs | 7 +- .../locking_tx_datastore/committed_state.rs | 5 +- .../src/locking_tx_datastore/mut_tx.rs | 45 +- .../datastore/src/locking_tx_datastore/tx.rs | 2 +- .../src/locking_tx_datastore/tx_state.rs | 2 +- crates/standalone/src/lib.rs | 2 +- crates/table/benches/page_manager.rs | 2 +- .../proptest-regressions/table_index/mod.txt | 3 + crates/table/src/table.rs | 48 +- crates/table/src/table_index/btree_index.rs | 72 +- crates/table/src/table_index/hash_index.rs | 60 +- crates/table/src/table_index/key_size.rs | 28 +- crates/table/src/table_index/mod.rs | 1197 ++++++++++------- .../src/table_index/unique_btree_index.rs | 49 +- .../unique_direct_fixed_cap_index.rs | 7 +- .../src/table_index/unique_direct_index.rs | 3 +- .../src/table_index/unique_hash_index.rs | 36 +- 18 files changed, 1014 insertions(+), 570 deletions(-) diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index d1ecc2a16ce..d2502c0e1ae 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -61,11 +61,12 @@ impl MetadataFile { /// `self` is the metadata file read from a database, and current is /// the default metadata file that the active database version would /// right to a new database. - pub fn check_compatibility_and_update(mut self, current: Self) -> anyhow::Result { + pub fn check_compatibility_and_update(mut self, current: Self, path: &Path) -> anyhow::Result { anyhow::ensure!( self.edition == current.edition, - "metadata.toml indicates that this database is from a different \ + "metadata.toml at {} indicates that this database is from a different \ edition of SpacetimeDB (running {:?}, but this database is {:?})", + path.display(), current.edition, self.edition, ); @@ -78,9 +79,10 @@ impl MetadataFile { }; anyhow::ensure!( cmp.matches(¤t.version), - "metadata.toml indicates that this database is from a newer, \ + "metadata.toml at {} indicates that this database is from a newer, \ incompatible version of SpacetimeDB (running {:?}, but this \ database is from {:?})", + path.display(), current.version, self.version, ); @@ -165,24 +167,24 @@ mod tests { fn check_metadata_compatibility_checking() { assert_eq!( mkmeta(1, 0, 0) - .check_compatibility_and_update(mkmeta(1, 0, 1)) + .check_compatibility_and_update(mkmeta(1, 0, 1), Path::new("metadata.toml")) .unwrap() .version, mkver(1, 0, 1) ); assert_eq!( mkmeta(1, 0, 1) - .check_compatibility_and_update(mkmeta(1, 0, 0)) + .check_compatibility_and_update(mkmeta(1, 0, 0), Path::new("metadata.toml")) .unwrap() .version, mkver(1, 0, 1) ); mkmeta(1, 1, 0) - .check_compatibility_and_update(mkmeta(1, 0, 5)) + .check_compatibility_and_update(mkmeta(1, 0, 5), Path::new("metadata.toml")) .unwrap_err(); mkmeta(2, 0, 0) - .check_compatibility_and_update(mkmeta(1, 3, 5)) + .check_compatibility_and_update(mkmeta(1, 3, 5), Path::new("metadata.toml")) .unwrap_err(); } } diff --git a/crates/core/src/db/relational_db.rs b/crates/core/src/db/relational_db.rs index 75fb75cbce7..70a02abc2e3 100644 --- a/crates/core/src/db/relational_db.rs +++ b/crates/core/src/db/relational_db.rs @@ -57,6 +57,7 @@ use spacetimedb_snapshot::{ReconstructedSnapshot, SnapshotError, SnapshotReposit use spacetimedb_table::indexes::RowPointer; use spacetimedb_table::page_pool::PagePool; use spacetimedb_table::table::{RowRef, TableScanIter}; +use spacetimedb_table::table_index::IndexKey; use spacetimedb_vm::errors::{ErrorType, ErrorVm}; use spacetimedb_vm::ops::parse; use std::borrow::Cow; @@ -1411,12 +1412,12 @@ impl RelationalDB { Ok(tx.index_scan_range(index_id, prefix, prefix_elems, rstart, rend)?) } - pub fn index_scan_point<'a>( + pub fn index_scan_point<'a, 'p>( &'a self, tx: &'a MutTx, index_id: IndexId, - point: &[u8], - ) -> Result<(TableId, AlgebraicValue, impl Iterator>), DBError> { + point: &'p [u8], + ) -> Result<(TableId, IndexKey<'p>, impl Iterator>), DBError> { Ok(tx.index_scan_point(index_id, point)?) } diff --git a/crates/datastore/src/locking_tx_datastore/committed_state.rs b/crates/datastore/src/locking_tx_datastore/committed_state.rs index 089b47e68f9..6da3c2c9ca8 100644 --- a/crates/datastore/src/locking_tx_datastore/committed_state.rs +++ b/crates/datastore/src/locking_tx_datastore/committed_state.rs @@ -987,7 +987,10 @@ impl CommittedState { self.tables .get(&table_id)? .get_index_by_cols_with_table(&self.blob_store, cols) - .map(|i| i.seek_point(value)) + .map(|i| { + let key = i.index().key_from_algebraic_value(value); + i.seek_point(&key) + }) } /// Returns the table associated with the given `index_id`, if any. diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index 101a5feb753..2eeea517f73 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -71,7 +71,7 @@ use spacetimedb_table::{ BlobNumBytes, DuplicateError, IndexScanPointIter, IndexScanRangeIter, InsertError, RowRef, Table, TableAndIndex, UniqueConstraintViolation, }, - table_index::{IndexCannotSeekRange, IndexSeekRangeResult, TableIndex}, + table_index::{IndexCannotSeekRange, IndexKey, IndexSeekRangeResult, TableIndex}, }; use std::{ marker::PhantomData, @@ -316,9 +316,10 @@ impl MutTxId { op: &FuncCallType, table_id: TableId, index_id: IndexId, - val: AlgebraicValue, + val: IndexKey<'_>, ) { if let FuncCallType::View(view) = op { + let val = val.into_algebraic_value(); self.record_index_scan_point_inner(view, table_id, index_id, val); }; } @@ -462,7 +463,8 @@ impl Datastore for MutTxId { .get_table_and_index(index_id) .ok_or_else(|| IndexError::NotFound(index_id))?; - Ok(self.index_scan_point_inner(table_id, tx_index, commit_index, point)) + let point = commit_index.index().key_from_algebraic_value(point); + Ok(self.index_scan_point_inner(table_id, tx_index, commit_index, &point)) } } @@ -1321,31 +1323,21 @@ impl MutTxId { /// Returns an iterator yielding rows by performing a point index scan /// on the index identified by `index_id`. - pub fn index_scan_point<'a>( + pub fn index_scan_point<'a, 'p>( &'a self, index_id: IndexId, - mut point: &[u8], - ) -> Result<(TableId, AlgebraicValue, IndexScanPoint<'a>)> { + point: &'p [u8], + ) -> Result<(TableId, IndexKey<'p>, IndexScanPoint<'a>)> { // Extract the table id, and commit/tx indices. let (table_id, commit_index, tx_index) = self .get_table_and_index(index_id) .ok_or_else(|| IndexError::NotFound(index_id))?; - // Extract the index type. - let index_ty = &commit_index.index().key_type; - - // We have the index key type, so we can decode the key. - let index_ty = WithTypespace::empty(index_ty); - let point = index_ty - .deserialize(Deserializer::new(&mut point)) - .map_err(IndexError::Decode)?; - // Get an index seek iterator for the tx and committed state. - let tx_iter = tx_index.map(|i| i.seek_point(&point)); - let commit_iter = commit_index.seek_point(&point); - - let dt = self.tx_state.get_delete_table(table_id); - let iter = ScanMutTx::combine(dt, tx_iter, commit_iter); + // Decode the key. + let point = commit_index.index().key_from_bsatn(point).map_err(IndexError::Decode)?; + // Get index seek iterators for the tx and committed state. + let iter = self.index_scan_point_inner(table_id, tx_index, commit_index, &point); Ok((table_id, point, iter)) } @@ -1355,7 +1347,7 @@ impl MutTxId { table_id: TableId, tx_index: Option>, commit_index: TableAndIndex<'a>, - point: &AlgebraicValue, + point: &IndexKey<'_>, ) -> IndexScanPoint<'a> { // Get an index seek iterator for the tx and committed state. let tx_iter = tx_index.map(|i| i.seek_point(point)); @@ -2893,10 +2885,12 @@ impl MutTxId { throw!(IndexError::NotUnique(index_id)); } - // Project the row to the index's type. + // Derive the key of `tx_row_ref` for `commit_index`. // SAFETY: `tx_row_ref`'s table is derived from `commit_index`'s table, - // so all `index.indexed_columns` will be in-bounds of the row layout. - let index_key = unsafe { tx_row_ref.project_unchecked(&commit_index.indexed_columns) }; + // so the row layouts match and thus, + // `commit_index`'s key type is the same as the type of `row_ref` + // projected to `commit_index.indexed_columns`. + let index_key = unsafe { commit_index.key_from_row(tx_row_ref) }; // Try to find the old row first in the committed state using the `index_key`. let mut old_commit_del_ptr = None; @@ -3006,6 +3000,9 @@ impl MutTxId { tx_row_ptr } else { + let index_key = tx_row_ref + .project(&commit_index.indexed_columns) + .expect("`tx_row_ref` should be compatible with `commit_index`"); throw!(IndexError::KeyNotFound(index_id, index_key)); }; diff --git a/crates/datastore/src/locking_tx_datastore/tx.rs b/crates/datastore/src/locking_tx_datastore/tx.rs index 5e06ca24b80..fae4116d07d 100644 --- a/crates/datastore/src/locking_tx_datastore/tx.rs +++ b/crates/datastore/src/locking_tx_datastore/tx.rs @@ -77,7 +77,7 @@ impl Datastore for TxId { index_id: IndexId, point: &AlgebraicValue, ) -> anyhow::Result> { - self.with_index(table_id, index_id, |i| i.seek_point(point)) + self.with_index(table_id, index_id, |i| i.seek_point_via_algebraic_value(point)) } } diff --git a/crates/datastore/src/locking_tx_datastore/tx_state.rs b/crates/datastore/src/locking_tx_datastore/tx_state.rs index 945fbdd4612..bdba9ff1f7f 100644 --- a/crates/datastore/src/locking_tx_datastore/tx_state.rs +++ b/crates/datastore/src/locking_tx_datastore/tx_state.rs @@ -206,7 +206,7 @@ impl TxState { self.insert_tables .get(&table_id)? .get_index_by_cols_with_table(&self.blob_store, cols) - .map(|i| i.seek_point(point)) + .map(|i| i.seek_point_via_algebraic_value(point)) } /// Returns the table for `table_id` combined with the index for `index_id`, if both exist. diff --git a/crates/standalone/src/lib.rs b/crates/standalone/src/lib.rs index 6c4e61dc9c8..3988f7ef51a 100644 --- a/crates/standalone/src/lib.rs +++ b/crates/standalone/src/lib.rs @@ -66,7 +66,7 @@ impl StandaloneEnv { let meta_path = data_dir.metadata_toml(); let mut meta = MetadataFile::new("standalone"); if let Some(existing_meta) = MetadataFile::read(&meta_path).context("failed reading metadata.toml")? { - meta = existing_meta.check_compatibility_and_update(meta)?; + meta = existing_meta.check_compatibility_and_update(meta, meta_path.as_ref())?; } meta.write(&meta_path).context("failed writing metadata.toml")?; diff --git a/crates/table/benches/page_manager.rs b/crates/table/benches/page_manager.rs index b88257793d7..c1bbb41710f 100644 --- a/crates/table/benches/page_manager.rs +++ b/crates/table/benches/page_manager.rs @@ -801,7 +801,7 @@ fn clear_all_same(tbl: &mut Table, index_id: IndexId, val_same: u let ptrs = tbl .get_index_by_id(index_id) .unwrap() - .seek_point(&R::column_value_from_u64(val_same)) + .seek_point_via_algebraic_value(&R::column_value_from_u64(val_same)) .collect::>(); for ptr in ptrs { tbl.delete(&mut NullBlobStore, ptr, |_| ()).unwrap(); diff --git a/crates/table/proptest-regressions/table_index/mod.txt b/crates/table/proptest-regressions/table_index/mod.txt index 8f6d53474b3..b0272b84cf4 100644 --- a/crates/table/proptest-regressions/table_index/mod.txt +++ b/crates/table/proptest-regressions/table_index/mod.txt @@ -6,3 +6,6 @@ # everyone who runs the test benefits from these saved cases. cc 3276d3db4a1a70d78db9a6a01eaa3bba810a2317e9c67e4d5d8d93cbba472c99 # shrinks to ((ty, cols, pv), is_unique) = ((ProductType {None: Bool}, [ColId(0)], ProductValue { elements: [Bool(false)] }), false) cc bc80b80ac2390452c0a152d2c6e2abc29ce146642f3cd0fe136ffe6173cf4c8c # shrinks to (ty, cols, pv) = (ProductType {None: I64}, [ColId(0)], ProductValue { elements: [I64(0)] }), kind = Direct +cc 4cb325be8b24c9efa5b1f20b9504d044d9dd110eb9e99355de4ca42f9cfc20b4 # shrinks to (ty, cols, pv) = (ProductType {None: Sum(SumType {"variant_0": Product(ProductType {})})}, [ColId(0)], ProductValue { elements: [Sum(SumValue { tag: 0, value: Product(ProductValue { elements: [] }) })] }), is_unique = false +cc a166a3c619c7cae3938f4e0cfb4e7a96cddfbb7943efd0b74e8cbb99d7a1e6a8 # shrinks to (ty, cols, pv) = (ProductType {None: U8}, [ColId(0)], ProductValue { elements: [U8(0)] }), kind = Direct +cc 05390c104810e7086fa5d3f3cac7f491a377ae6ba64431661fd94662e28d1fca # shrinks to (ty, cols, pv) = (ProductType {None: Sum(SumType {"variant_0": Product(ProductType {})})}, [ColId(0)], ProductValue { elements: [Sum(SumValue { tag: 0, value: Product(ProductValue { elements: [] }) })] }), kind = Direct diff --git a/crates/table/src/table.rs b/crates/table/src/table.rs index 7a29f32d530..351482b64b4 100644 --- a/crates/table/src/table.rs +++ b/crates/table/src/table.rs @@ -1,6 +1,6 @@ use crate::{ blob_store::NullBlobStore, - table_index::{IndexCannotSeekRange, IndexKind}, + table_index::{IndexCannotSeekRange, IndexKey, IndexKind}, }; use super::{ @@ -560,12 +560,13 @@ impl Table { mut is_deleted: impl FnMut(RowPointer) -> bool, ) -> Result<(), UniqueConstraintViolation> { for (&index_id, index) in adapt(self.indexes.iter()).filter(|(_, index)| index.is_unique()) { - // SAFETY: Caller promised that `row´ has the same layout as `self`. - // Thus, as `index.indexed_columns` is in-bounds of `self`'s layout, - // it's also in-bounds of `row`'s layout. - let value = unsafe { row.project_unchecked(&index.indexed_columns) }; - if index.seek_point(&value).next().is_some_and(|ptr| !is_deleted(ptr)) { - return Err(self.build_error_unique(index, index_id, value)); + // SAFETY: Caller promised that `row` has the same layout as `self`. + // The projection of `row`'s type onto the index's columns + // is therefore the same as the key type. + let key = unsafe { index.key_from_row(row) }; + + if index.seek_point(&key).next().is_some_and(|ptr| !is_deleted(ptr)) { + return Err(self.build_error_unique(index, index_id, row)); } } Ok(()) @@ -915,8 +916,7 @@ impl Table { } let index = self.indexes.get(&index_id).unwrap(); - let value = new.project(&index.indexed_columns).unwrap(); - let error = self.build_error_unique(index, index_id, value).into(); + let error = self.build_error_unique(index, index_id, new).into(); (index_id, error) }) .map_err(|(index_id, error)| { @@ -959,10 +959,13 @@ impl Table { .expect("there should be at least one unique index"); // Project the needle row to the columns of the index, and then seek. // As this is a unique index, there are 0-1 rows for this key. + // SAFETY: `needle_table.is_row_present(needle_ptr)` holds. let needle_row = unsafe { needle_table.get_row_ref_unchecked(needle_bs, needle_ptr) }; - let key = needle_row - .project(&target_index.indexed_columns) - .expect("needle row should be valid"); + // SAFETY: Caller promised that the row layout of both tables are the same. + // As `target_index` comes from `target_table`, + // it follows that `needle_row`'s type projected to `target_index`'s columns + // is the same as the index's key type. + let key = unsafe { target_index.key_from_row(needle_row) }; target_index.seek_point(&key).next().filter(|&target_ptr| { // SAFETY: // - Caller promised that the row layouts were the same. @@ -1252,7 +1255,8 @@ impl Table { let row_ref = unsafe { self.inner.get_row_ref_unchecked(blob_store, self.squashed_offset, ptr) }; for (_, index) in self.indexes.range_mut(..index_id) { - index.delete(row_ref).unwrap(); + // SAFETY: any index in this table was constructed with the same row type as this table. + unsafe { index.delete(row_ref) }; } } @@ -1264,7 +1268,8 @@ impl Table { let row_ref = unsafe { self.inner.get_row_ref_unchecked(blob_store, self.squashed_offset, ptr) }; for index in self.indexes.values_mut() { - index.delete(row_ref).unwrap(); + // SAFETY: any index in this table was constructed with the same row type as this table. + unsafe { index.delete(row_ref) }; } } @@ -2098,7 +2103,7 @@ impl<'a> TableAndIndex<'a> { /// Returns an iterator yielding all rows in this index for `key`. /// /// Matching is defined by `Eq for AlgebraicValue`. - pub fn seek_point(&self, key: &AlgebraicValue) -> IndexScanPointIter<'a> { + pub fn seek_point(&self, key: &IndexKey<'_>) -> IndexScanPointIter<'a> { IndexScanPointIter { table: self.table, blob_store: self.blob_store, @@ -2106,6 +2111,14 @@ impl<'a> TableAndIndex<'a> { } } + /// Returns an iterator yielding all rows in this index for `key`. + /// + /// Matching is defined by `Eq for AlgebraicValue`. + pub fn seek_point_via_algebraic_value(&self, key: &AlgebraicValue) -> IndexScanPointIter<'a> { + let key = self.index.key_from_algebraic_value(key); + self.seek_point(&key) + } + /// Returns an iterator yielding all rows in this index that fall within `range`, /// if the index is compatible with range seeks. /// @@ -2232,14 +2245,15 @@ impl UniqueConstraintViolation { // Private API: impl Table { /// Returns a unique constraint violation error for the given `index` - /// and the `value` that would have been duplicated. + /// and the `row` that caused the violation. #[cold] pub fn build_error_unique( &self, index: &TableIndex, index_id: IndexId, - value: AlgebraicValue, + row: RowRef<'_>, ) -> UniqueConstraintViolation { + let value = row.project(&index.indexed_columns).unwrap(); let schema = self.get_schema(); UniqueConstraintViolation::build(schema, index, index_id, value) } diff --git a/crates/table/src/table_index/btree_index.rs b/crates/table/src/table_index/btree_index.rs index a96ec88b5cc..aefbbbb9e37 100644 --- a/crates/table/src/table_index/btree_index.rs +++ b/crates/table/src/table_index/btree_index.rs @@ -1,6 +1,7 @@ use super::same_key_entry::{same_key_iter, SameKeyEntry, SameKeyEntryIter}; use super::{key_size::KeyBytesStorage, Index, KeySize, RangedIndex}; use crate::indexes::RowPointer; +use core::borrow::Borrow; use core::ops::RangeBounds; use spacetimedb_sats::memory_usage::MemoryUsage; use std::collections::btree_map::{BTreeMap, Range}; @@ -56,7 +57,7 @@ impl Index for BTreeIndex { /// and multimaps do not bind one `key` to the same `ptr`. fn insert(&mut self, key: Self::Key, ptr: RowPointer) -> Result<(), RowPointer> { self.num_rows += 1; - self.num_key_bytes.add_to_key_bytes::(&key); + self.num_key_bytes.add_to_key_bytes(&key); self.map.entry(key).or_default().push(ptr); Ok(()) } @@ -65,22 +66,7 @@ impl Index for BTreeIndex { /// /// Returns whether `key -> ptr` was present. fn delete(&mut self, key: &K, ptr: RowPointer) -> bool { - let Some(vset) = self.map.get_mut(key) else { - return false; - }; - - let (deleted, is_empty) = vset.delete(ptr); - - if is_empty { - self.map.remove(key); - } - - if deleted { - self.num_rows -= 1; - self.num_key_bytes.sub_from_key_bytes::(key); - } - - deleted + self.delete(key, ptr) } type PointIter<'a> @@ -88,8 +74,8 @@ impl Index for BTreeIndex { where Self: 'a; - fn seek_point(&self, key: &Self::Key) -> Self::PointIter<'_> { - same_key_iter(self.map.get(key)) + fn seek_point(&self, point: &Self::Key) -> Self::PointIter<'_> { + self.seek_point(point) } fn num_keys(&self) -> usize { @@ -118,6 +104,43 @@ impl Index for BTreeIndex { } } +impl BTreeIndex { + /// See [`Index::delete`]. + /// This version has relaxed bounds. + pub fn delete(&mut self, key: &Q, ptr: RowPointer) -> bool + where + Q: ?Sized + KeySize + Ord, + ::Key: Borrow, + { + let Some(vset) = self.map.get_mut(key) else { + return false; + }; + + let (deleted, is_empty) = vset.delete(ptr); + + if is_empty { + self.map.remove(key); + } + + if deleted { + self.num_rows -= 1; + self.num_key_bytes.sub_from_key_bytes(key); + } + + deleted + } + + /// See [`Index::seek_point`]. + /// This version has relaxed bounds. + pub fn seek_point(&self, point: &Q) -> ::PointIter<'_> + where + Q: ?Sized + Ord, + ::Key: Borrow, + { + same_key_iter(self.map.get(point)) + } +} + impl RangedIndex for BTreeIndex { type RangeIter<'a> = BTreeIndexRangeIter<'a, K> @@ -127,6 +150,17 @@ impl RangedIndex for BTreeIndex { /// Returns an iterator over the multimap that yields all the `V`s /// of the `K`s that fall within the specified `range`. fn seek_range(&self, range: &impl RangeBounds) -> Self::RangeIter<'_> { + self.seek_range(range) + } +} + +impl BTreeIndex { + /// See [`RangedIndex::seek_range`]. + /// This version has relaxed bounds. + pub fn seek_range(&self, range: &impl RangeBounds) -> ::RangeIter<'_> + where + ::Key: Borrow, + { BTreeIndexRangeIter { outer: self.map.range((range.start_bound(), range.end_bound())), inner: SameKeyEntry::empty_iter(), diff --git a/crates/table/src/table_index/hash_index.rs b/crates/table/src/table_index/hash_index.rs index c1fd89cfd97..0d05fbdd402 100644 --- a/crates/table/src/table_index/hash_index.rs +++ b/crates/table/src/table_index/hash_index.rs @@ -4,6 +4,7 @@ use super::{ Index, KeySize, }; use crate::indexes::RowPointer; +use core::borrow::Borrow; use core::hash::Hash; use spacetimedb_data_structures::map::hash_map::EntryRef; use spacetimedb_sats::memory_usage::MemoryUsage; @@ -63,7 +64,7 @@ impl Index for HashIndex { /// and multimaps do not bind one `key` to the same `ptr`. fn insert(&mut self, key: Self::Key, ptr: RowPointer) -> Result<(), RowPointer> { self.num_rows += 1; - self.num_key_bytes.add_to_key_bytes::(&key); + self.num_key_bytes.add_to_key_bytes(&key); self.map.entry(key).or_default().push(ptr); Ok(()) } @@ -72,22 +73,7 @@ impl Index for HashIndex { /// /// Returns whether `key -> ptr` was present. fn delete(&mut self, key: &K, ptr: RowPointer) -> bool { - let EntryRef::Occupied(mut entry) = self.map.entry_ref(key) else { - return false; - }; - - let (deleted, is_empty) = entry.get_mut().delete(ptr); - - if deleted { - self.num_rows -= 1; - self.num_key_bytes.sub_from_key_bytes::(key); - } - - if is_empty { - entry.remove(); - } - - deleted + self.delete(key, ptr) } type PointIter<'a> @@ -95,8 +81,8 @@ impl Index for HashIndex { where Self: 'a; - fn seek_point(&self, key: &Self::Key) -> Self::PointIter<'_> { - same_key_iter(self.map.get(key)) + fn seek_point(&self, point: &Self::Key) -> Self::PointIter<'_> { + self.seek_point(point) } fn num_keys(&self) -> usize { @@ -120,3 +106,39 @@ impl Index for HashIndex { Ok(()) } } + +impl HashIndex { + /// See [`Index::delete`]. + /// This version has relaxed bounds. + pub fn delete(&mut self, key: &Q, ptr: RowPointer) -> bool + where + Q: ?Sized + KeySize + Hash + Eq, + ::Key: Borrow, + { + let EntryRef::Occupied(mut entry) = self.map.entry_ref(key) else { + return false; + }; + + let (deleted, is_empty) = entry.get_mut().delete(ptr); + + if deleted { + self.num_rows -= 1; + self.num_key_bytes.sub_from_key_bytes(entry.key()); + } + + if is_empty { + entry.remove(); + } + + deleted + } + + /// See [`Index::seek_point`]. + /// This version has relaxed bounds. + pub fn seek_point(&self, point: &Q) -> ::PointIter<'_> + where + ::Key: Borrow, + { + same_key_iter(self.map.get(point)) + } +} diff --git a/crates/table/src/table_index/key_size.rs b/crates/table/src/table_index/key_size.rs index 70eb2535004..a51e42eea3d 100644 --- a/crates/table/src/table_index/key_size.rs +++ b/crates/table/src/table_index/key_size.rs @@ -9,10 +9,10 @@ use spacetimedb_sats::{ /// Storage for memoizing `KeySize` statistics. pub trait KeyBytesStorage: Default + MemoryUsage { /// Add `key.key_size_in_bytes()` to the statistics. - fn add_to_key_bytes(&mut self, key: &I::Key); + fn add_to_key_bytes(&mut self, key: &(impl KeySize + ?Sized)); /// Subtract `key.key_size_in_bytes()` from the statistics. - fn sub_from_key_bytes(&mut self, key: &I::Key); + fn sub_from_key_bytes(&mut self, key: &(impl KeySize + ?Sized)); /// Resets the statistics to zero. fn reset_to_zero(&mut self); @@ -22,8 +22,8 @@ pub trait KeyBytesStorage: Default + MemoryUsage { } impl KeyBytesStorage for () { - fn add_to_key_bytes(&mut self, _: &I::Key) {} - fn sub_from_key_bytes(&mut self, _: &I::Key) {} + fn add_to_key_bytes(&mut self, _: &(impl KeySize + ?Sized)) {} + fn sub_from_key_bytes(&mut self, _: &(impl KeySize + ?Sized)) {} fn reset_to_zero(&mut self) {} fn get(&self, index: &I) -> u64 { index.num_keys() as u64 * mem::size_of::() as u64 @@ -31,10 +31,10 @@ impl KeyBytesStorage for () { } impl KeyBytesStorage for u64 { - fn add_to_key_bytes(&mut self, key: &I::Key) { + fn add_to_key_bytes(&mut self, key: &(impl KeySize + ?Sized)) { *self += key.key_size_in_bytes() as u64; } - fn sub_from_key_bytes(&mut self, key: &I::Key) { + fn sub_from_key_bytes(&mut self, key: &(impl KeySize + ?Sized)) { *self -= key.key_size_in_bytes() as u64; } fn reset_to_zero(&mut self) { @@ -79,6 +79,20 @@ pub trait KeySize { } } +impl KeySize for &T { + type MemoStorage = T::MemoStorage; + fn key_size_in_bytes(&self) -> usize { + (**self).key_size_in_bytes() + } +} + +impl KeySize for Box { + type MemoStorage = T::MemoStorage; + fn key_size_in_bytes(&self) -> usize { + (**self).key_size_in_bytes() + } +} + macro_rules! impl_key_size_primitive { ($prim:ty) => { impl KeySize for $prim { @@ -112,7 +126,7 @@ impl_key_size_primitive!( F64, ); -impl KeySize for Box { +impl KeySize for str { type MemoStorage = u64; fn key_size_in_bytes(&self) -> usize { self.len() diff --git a/crates/table/src/table_index/mod.rs b/crates/table/src/table_index/mod.rs index 7b082b7ea27..7533012e337 100644 --- a/crates/table/src/table_index/mod.rs +++ b/crates/table/src/table_index/mod.rs @@ -12,20 +12,20 @@ //! are instead enums with similar-looking variants for each specialized key type, //! and methods that interact with those enums have matches with similar-looking arms. //! Some day we may devise a better solution, but this is good enough for now. -// -// I (pgoldman 2024-02-05) suspect, but have not measured, that there's no real reason -// to have a `ProductType` variant, which would apply to multi-column indexes. -// I believe `ProductValue::cmp` to not be meaningfully faster than `AlgebraicValue::cmp`. -// Eventually, we will likely want to compile comparison functions and representations -// for `ProductValue`-keyed indexes which take advantage of type information, -// since we know when creating the index the number and type of all the indexed columns. -// This may involve a bytecode compiler, a tree of closures, or a native JIT. -/// -/// We also represent unique indices more compactly than non-unique ones, avoiding the multi-map. -/// Additionally, beyond our btree indices, -/// we support direct unique indices, where key are indices into `Vec`s. -use self::hash_index::HashIndex; +//! +//! I (pgoldman 2024-02-05) suspect, but have not measured, that there's no real reason +//! to have a `ProductType` variant, which would apply to multi-column indexes. +//! I believe `ProductValue::cmp` to not be meaningfully faster than `AlgebraicValue::cmp`. +//! Eventually, we will likely want to compile comparison functions and representations +//! for `ProductValue`-keyed indexes which take advantage of type information, +//! since we know when creating the index the number and type of all the indexed columns. +//! This may involve a bytecode compiler, a tree of closures, or a native JIT. +//! +//! We also represent unique indices more compactly than non-unique ones, avoiding the multi-map. +//! Additionally, beyond our btree indices, +//! we support direct unique indices, where key are indices into `Vec`s. use self::btree_index::{BTreeIndex, BTreeIndexRangeIter}; +use self::hash_index::HashIndex; use self::same_key_entry::SameKeyEntryIter; use self::unique_btree_index::{UniqueBTreeIndex, UniqueBTreeIndexRangeIter, UniquePointIter}; use self::unique_direct_fixed_cap_index::{UniqueDirectFixedCapIndex, UniqueDirectFixedCapIndexRangeIter}; @@ -40,16 +40,20 @@ use core::fmt; use core::ops::RangeBounds; use spacetimedb_primitives::ColList; use spacetimedb_sats::memory_usage::MemoryUsage; +use spacetimedb_sats::SumValue; use spacetimedb_sats::{ - algebraic_value::Packed, i256, product_value::InvalidFieldError, sum_value::SumTag, u256, AlgebraicType, - AlgebraicValue, ProductType, F32, F64, + bsatn::{from_slice, DecodeError}, + i256, + product_value::InvalidFieldError, + sum_value::SumTag, + u256, AlgebraicType, AlgebraicValue, ProductType, F32, F64, }; use spacetimedb_schema::def::IndexAlgorithm; +mod btree_index; mod hash_index; mod index; mod key_size; -mod btree_index; mod same_key_entry; pub mod unique_btree_index; pub mod unique_direct_fixed_cap_index; @@ -110,8 +114,8 @@ enum TypedIndexRangeIter<'a> { BTreeI32(BTreeIndexRangeIter<'a, i32>), BTreeU64(BTreeIndexRangeIter<'a, u64>), BTreeI64(BTreeIndexRangeIter<'a, i64>), - BTreeU128(BTreeIndexRangeIter<'a, Packed>), - BTreeI128(BTreeIndexRangeIter<'a, Packed>), + BTreeU128(BTreeIndexRangeIter<'a, u128>), + BTreeI128(BTreeIndexRangeIter<'a, i128>), BTreeU256(BTreeIndexRangeIter<'a, u256>), BTreeI256(BTreeIndexRangeIter<'a, i256>), BTreeF32(BTreeIndexRangeIter<'a, F32>), @@ -130,8 +134,8 @@ enum TypedIndexRangeIter<'a> { UniqueBTreeI32(UniqueBTreeIndexRangeIter<'a, i32>), UniqueBTreeU64(UniqueBTreeIndexRangeIter<'a, u64>), UniqueBTreeI64(UniqueBTreeIndexRangeIter<'a, i64>), - UniqueBTreeU128(UniqueBTreeIndexRangeIter<'a, Packed>), - UniqueBTreeI128(UniqueBTreeIndexRangeIter<'a, Packed>), + UniqueBTreeU128(UniqueBTreeIndexRangeIter<'a, u128>), + UniqueBTreeI128(UniqueBTreeIndexRangeIter<'a, i128>), UniqueBTreeU256(UniqueBTreeIndexRangeIter<'a, u256>), UniqueBTreeI256(UniqueBTreeIndexRangeIter<'a, i256>), UniqueBTreeF32(UniqueBTreeIndexRangeIter<'a, F32>), @@ -215,6 +219,308 @@ impl fmt::Debug for TableIndexRangeIter<'_> { } } +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, derive_more::From)] +enum BowStr<'a> { + Borrowed(&'a str), + Owned(Box), +} + +impl<'a> BowStr<'a> { + fn borrow(&'a self) -> &'a str { + match self { + Self::Borrowed(x) => x, + Self::Owned(x) => x, + } + } + + fn into_owned(self) -> Box { + match self { + Self::Borrowed(x) => x.into(), + Self::Owned(x) => x, + } + } +} + +impl<'a> From<&'a Box> for BowStr<'a> { + fn from(value: &'a Box) -> Self { + Self::Borrowed(value) + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, derive_more::From)] +enum CowAV<'a> { + Borrowed(&'a AlgebraicValue), + Owned(AlgebraicValue), +} + +impl<'a> CowAV<'a> { + fn borrow(&'a self) -> &'a AlgebraicValue { + match self { + Self::Borrowed(x) => x, + Self::Owned(x) => x, + } + } + + fn into_owned(self) -> AlgebraicValue { + match self { + Self::Borrowed(x) => x.clone(), + Self::Owned(x) => x, + } + } +} + +/// A key into a [`TypedIndex`], the borrowed version. +#[derive(enum_as_inner::EnumAsInner, PartialEq, Eq, PartialOrd, Ord, Debug)] +enum TypedIndexKey<'a> { + Bool(bool), + U8(u8), + SumTag(SumTag), + I8(i8), + U16(u16), + I16(i16), + U32(u32), + I32(i32), + U64(u64), + I64(i64), + U128(u128), + I128(i128), + U256(u256), + I256(i256), + F32(F32), + F64(F64), + String(BowStr<'a>), + AV(CowAV<'a>), +} + +impl<'a> TypedIndexKey<'a> { + /// Derives a [`TypedIndexKey`] from an [`AlgebraicValue`] + /// driven by the kind of [`TypedIndex`] provided in `index`. + #[inline] + fn from_algebraic_value(index: &TypedIndex, value: &'a AlgebraicValue) -> Self { + use AlgebraicValue::*; + use TypedIndex::*; + match (value, index) { + (Bool(v), BTreeBool(_) | HashBool(_) | UniqueBTreeBool(_) | UniqueHashBool(_)) => Self::Bool(*v), + + (U8(v), BTreeU8(_) | HashU8(_) | UniqueBTreeU8(_) | UniqueHashU8(_) | UniqueDirectU8(_)) => Self::U8(*v), + ( + U8(v) | Sum(SumValue { tag: v, .. }), + BTreeSumTag(_) | HashSumTag(_) | UniqueBTreeSumTag(_) | UniqueHashSumTag(_) | UniqueDirectSumTag(_), + ) => Self::SumTag(SumTag(*v)), + + (U16(v), BTreeU16(_) | HashU16(_) | UniqueBTreeU16(_) | UniqueHashU16(_) | UniqueDirectU16(_)) => { + Self::U16(*v) + } + (U32(v), BTreeU32(_) | HashU32(_) | UniqueBTreeU32(_) | UniqueHashU32(_) | UniqueDirectU32(_)) => { + Self::U32(*v) + } + (U64(v), BTreeU64(_) | HashU64(_) | UniqueBTreeU64(_) | UniqueHashU64(_) | UniqueDirectU64(_)) => { + Self::U64(*v) + } + (U128(v), BTreeU128(_) | HashU128(_) | UniqueBTreeU128(_) | UniqueHashU128(_)) => Self::U128(v.0), + (U256(v), BTreeU256(_) | HashU256(_) | UniqueBTreeU256(_) | UniqueHashU256(_)) => Self::U256(**v), + + (I8(v), BTreeI8(_) | HashI8(_) | UniqueBTreeI8(_) | UniqueHashI8(_)) => Self::I8(*v), + (I16(v), BTreeI16(_) | HashI16(_) | UniqueBTreeI16(_) | UniqueHashI16(_)) => Self::I16(*v), + (I32(v), BTreeI32(_) | HashI32(_) | UniqueBTreeI32(_) | UniqueHashI32(_)) => Self::I32(*v), + (I64(v), BTreeI64(_) | HashI64(_) | UniqueBTreeI64(_) | UniqueHashI64(_)) => Self::I64(*v), + (I128(v), BTreeI128(_) | HashI128(_) | UniqueBTreeI128(_) | UniqueHashI128(_)) => Self::I128(v.0), + (I256(v), BTreeI256(_) | HashI256(_) | UniqueBTreeI256(_) | UniqueHashI256(_)) => Self::I256(**v), + + (F32(v), BTreeF32(_) | HashF32(_) | UniqueBTreeF32(_) | UniqueHashF32(_)) => Self::F32(*v), + (F64(v), BTreeF64(_) | HashF64(_) | UniqueBTreeF64(_) | UniqueHashF64(_)) => Self::F64(*v), + + (String(v), BTreeString(_) | HashString(_) | UniqueBTreeString(_) | UniqueHashString(_)) => { + Self::String(v.into()) + } + + (av, BTreeAV(_) | HashAV(_) | UniqueBTreeAV(_) | UniqueHashAV(_)) => Self::AV(CowAV::Borrowed(av)), + _ => panic!("value {value:?} is not compatible with index {index:?}"), + } + } + + /// Derives a [`TypedIndexKey`] from BSATN-encoded `bytes`, + /// driven by the kind of [`TypedIndex`] provided in `index`. + #[inline] + fn from_bsatn(index: &TypedIndex, ty: &AlgebraicType, bytes: &'a [u8]) -> Result { + use TypedIndex::*; + match index { + BTreeBool(_) | HashBool(_) | UniqueBTreeBool(_) | UniqueHashBool(_) => from_slice(bytes).map(Self::Bool), + + BTreeU8(_) | HashU8(_) | UniqueBTreeU8(_) | UniqueHashU8(_) | UniqueDirectU8(_) => { + from_slice(bytes).map(Self::U8) + } + BTreeSumTag(_) | HashSumTag(_) | UniqueBTreeSumTag(_) | UniqueHashSumTag(_) | UniqueDirectSumTag(_) => { + from_slice(bytes).map(Self::SumTag) + } + BTreeU16(_) | HashU16(_) | UniqueBTreeU16(_) | UniqueHashU16(_) | UniqueDirectU16(_) => { + from_slice(bytes).map(Self::U16) + } + BTreeU32(_) | HashU32(_) | UniqueBTreeU32(_) | UniqueHashU32(_) | UniqueDirectU32(_) => { + from_slice(bytes).map(Self::U32) + } + BTreeU64(_) | HashU64(_) | UniqueBTreeU64(_) | UniqueHashU64(_) | UniqueDirectU64(_) => { + from_slice(bytes).map(Self::U64) + } + BTreeU128(_) | HashU128(_) | UniqueBTreeU128(_) | UniqueHashU128(_) => from_slice(bytes).map(Self::U128), + BTreeU256(_) | HashU256(_) | UniqueBTreeU256(_) | UniqueHashU256(_) => from_slice(bytes).map(Self::U256), + + BTreeI8(_) | HashI8(_) | UniqueBTreeI8(_) | UniqueHashI8(_) => from_slice(bytes).map(Self::I8), + BTreeI16(_) | HashI16(_) | UniqueBTreeI16(_) | UniqueHashI16(_) => from_slice(bytes).map(Self::I16), + BTreeI32(_) | HashI32(_) | UniqueBTreeI32(_) | UniqueHashI32(_) => from_slice(bytes).map(Self::I32), + BTreeI64(_) | HashI64(_) | UniqueBTreeI64(_) | UniqueHashI64(_) => from_slice(bytes).map(Self::I64), + BTreeI128(_) | HashI128(_) | UniqueBTreeI128(_) | UniqueHashI128(_) => from_slice(bytes).map(Self::I128), + BTreeI256(_) | HashI256(_) | UniqueBTreeI256(_) | UniqueHashI256(_) => from_slice(bytes).map(Self::I256), + + BTreeF32(_) | HashF32(_) | UniqueBTreeF32(_) | UniqueHashF32(_) => from_slice(bytes).map(Self::F32), + BTreeF64(_) | HashF64(_) | UniqueBTreeF64(_) | UniqueHashF64(_) => from_slice(bytes).map(Self::F64), + + BTreeString(_) | HashString(_) | UniqueBTreeString(_) | UniqueHashString(_) => { + from_slice(bytes).map(BowStr::Borrowed).map(Self::String) + } + + BTreeAV(_) | HashAV(_) | UniqueBTreeAV(_) | UniqueHashAV(_) => AlgebraicValue::decode(ty, &mut { bytes }) + .map(CowAV::Owned) + .map(Self::AV), + } + } + + /// Derives a [`TypedIndexKey`] from a [`RowRef`] + /// and a [`ColList`] that describes what columns are indexed by `index`. + /// + /// Assumes that `row_ref` projected to `cols` + /// has the same type as the keys of `index`. + /// + /// # Safety + /// + /// 1. Caller promises that `cols` matches what was given at construction (`TableIndex::new`). + /// 2. Caller promises that the projection of `row_ref`'s type's equals the index's key type. + #[inline] + unsafe fn from_row_ref(index: &TypedIndex, cols: &ColList, row_ref: RowRef<'_>) -> Self { + fn proj(cols: &ColList, row_ref: RowRef<'_>) -> T { + // Extract the column. + let col_pos = cols.as_singleton(); + // SAFETY: Caller promised that `cols` matches what was given at construction (`Self::new`). + // In the case of `.clone_structure()`, the structure is preserved, + // so the promise is also preserved. + // This entails, that because we reached here, that `cols` is singleton. + let col_pos = unsafe { col_pos.unwrap_unchecked() }.idx(); + + // Extract the layout of the column. + let col_layouts = &row_ref.row_layout().product().elements; + // SAFETY: + // - Caller promised that projecting the `row_ref`'s type/layout to `self.indexed_columns` + // gives us the index's key type. + // This entails that each `ColId` in `self.indexed_columns` + // must be in-bounds of `row_ref`'s layout. + let col_layout = unsafe { col_layouts.get_unchecked(col_pos) }; + + // Read the column in `row_ref`. + // SAFETY: + // - `col_layout` was just derived from the row layout. + // - Caller promised that the type-projection of the row type/layout + // equals the index's key type. + // We've reached here, so the index's key type is compatible with `T`. + // - `self` is a valid row so offsetting to `col_layout` is valid. + unsafe { T::unchecked_read_column(row_ref, col_layout) } + } + + use TypedIndex::*; + match index { + BTreeBool(_) | HashBool(_) | UniqueBTreeBool(_) | UniqueHashBool(_) => Self::Bool(proj(cols, row_ref)), + + BTreeU8(_) | HashU8(_) | UniqueBTreeU8(_) | UniqueHashU8(_) | UniqueDirectU8(_) => { + Self::U8(proj(cols, row_ref)) + } + BTreeSumTag(_) | HashSumTag(_) | UniqueBTreeSumTag(_) | UniqueHashSumTag(_) | UniqueDirectSumTag(_) => { + Self::SumTag(proj(cols, row_ref)) + } + BTreeU16(_) | HashU16(_) | UniqueBTreeU16(_) | UniqueHashU16(_) | UniqueDirectU16(_) => { + Self::U16(proj(cols, row_ref)) + } + BTreeU32(_) | HashU32(_) | UniqueBTreeU32(_) | UniqueHashU32(_) | UniqueDirectU32(_) => { + Self::U32(proj(cols, row_ref)) + } + BTreeU64(_) | HashU64(_) | UniqueBTreeU64(_) | UniqueHashU64(_) | UniqueDirectU64(_) => { + Self::U64(proj(cols, row_ref)) + } + BTreeU128(_) | HashU128(_) | UniqueBTreeU128(_) | UniqueHashU128(_) => Self::U128(proj(cols, row_ref)), + BTreeU256(_) | HashU256(_) | UniqueBTreeU256(_) | UniqueHashU256(_) => Self::U256(proj(cols, row_ref)), + + BTreeI8(_) | HashI8(_) | UniqueBTreeI8(_) | UniqueHashI8(_) => Self::I8(proj(cols, row_ref)), + BTreeI16(_) | HashI16(_) | UniqueBTreeI16(_) | UniqueHashI16(_) => Self::I16(proj(cols, row_ref)), + BTreeI32(_) | HashI32(_) | UniqueBTreeI32(_) | UniqueHashI32(_) => Self::I32(proj(cols, row_ref)), + BTreeI64(_) | HashI64(_) | UniqueBTreeI64(_) | UniqueHashI64(_) => Self::I64(proj(cols, row_ref)), + BTreeI128(_) | HashI128(_) | UniqueBTreeI128(_) | UniqueHashI128(_) => Self::I128(proj(cols, row_ref)), + BTreeI256(_) | HashI256(_) | UniqueBTreeI256(_) | UniqueHashI256(_) => Self::I256(proj(cols, row_ref)), + + BTreeF32(_) | HashF32(_) | UniqueBTreeF32(_) | UniqueHashF32(_) => Self::F32(proj(cols, row_ref)), + BTreeF64(_) | HashF64(_) | UniqueBTreeF64(_) | UniqueHashF64(_) => Self::F64(proj(cols, row_ref)), + + BTreeString(_) | HashString(_) | UniqueBTreeString(_) | UniqueHashString(_) => { + Self::String(BowStr::Owned(proj(cols, row_ref))) + } + + BTreeAV(_) | HashAV(_) | UniqueBTreeAV(_) | UniqueHashAV(_) => { + // SAFETY: Caller promised that any `col` in `cols` is in-bounds of `row_ref`'s layout. + let val = unsafe { row_ref.project_unchecked(cols) }; + Self::AV(CowAV::Owned(val)) + } + } + } + + /// Returns a borrowed version of the key. + #[inline] + fn borrowed(&self) -> TypedIndexKey<'_> { + match self { + Self::Bool(x) => TypedIndexKey::Bool(*x), + Self::U8(x) => TypedIndexKey::U8(*x), + Self::SumTag(x) => TypedIndexKey::SumTag(*x), + Self::I8(x) => TypedIndexKey::I8(*x), + Self::U16(x) => TypedIndexKey::U16(*x), + Self::I16(x) => TypedIndexKey::I16(*x), + Self::U32(x) => TypedIndexKey::U32(*x), + Self::I32(x) => TypedIndexKey::I32(*x), + Self::U64(x) => TypedIndexKey::U64(*x), + Self::I64(x) => TypedIndexKey::I64(*x), + Self::U128(x) => TypedIndexKey::U128(*x), + Self::I128(x) => TypedIndexKey::I128(*x), + Self::U256(x) => TypedIndexKey::U256(*x), + Self::I256(x) => TypedIndexKey::I256(*x), + Self::F32(x) => TypedIndexKey::F32(*x), + Self::F64(x) => TypedIndexKey::F64(*x), + Self::String(x) => TypedIndexKey::String(x.borrow().into()), + Self::AV(x) => TypedIndexKey::AV(x.borrow().into()), + } + } + + /// Converts the key into an [`AlgebraicValue`]. + fn into_algebraic_value(self) -> AlgebraicValue { + match self { + Self::Bool(x) => x.into(), + Self::U8(x) => x.into(), + Self::SumTag(x) => x.into(), + Self::I8(x) => x.into(), + Self::U16(x) => x.into(), + Self::I16(x) => x.into(), + Self::U32(x) => x.into(), + Self::I32(x) => x.into(), + Self::U64(x) => x.into(), + Self::I64(x) => x.into(), + Self::U128(x) => x.into(), + Self::I128(x) => x.into(), + Self::U256(x) => x.into(), + Self::I256(x) => x.into(), + Self::F32(x) => x.into(), + Self::F64(x) => x.into(), + Self::String(x) => x.into_owned().into(), + Self::AV(x) => x.into_owned(), + } + } +} + +const WRONG_TYPE: &str = "key does not conform to key type of index"; + /// An index from a key type determined at runtime to `RowPointer`(s). /// /// See module docs for info about specialization. @@ -231,12 +537,13 @@ enum TypedIndex { BTreeI32(BTreeIndex), BTreeU64(BTreeIndex), BTreeI64(BTreeIndex), - BTreeU128(BTreeIndex>), - BTreeI128(BTreeIndex>), + BTreeU128(BTreeIndex), + BTreeI128(BTreeIndex), BTreeU256(BTreeIndex), BTreeI256(BTreeIndex), BTreeF32(BTreeIndex), BTreeF64(BTreeIndex), + // TODO(perf, centril): consider `UmbraString` or some "German string". BTreeString(BTreeIndex>), BTreeAV(BTreeIndex), @@ -251,12 +558,13 @@ enum TypedIndex { HashI32(HashIndex), HashU64(HashIndex), HashI64(HashIndex), - HashU128(HashIndex>), - HashI128(HashIndex>), + HashU128(HashIndex), + HashI128(HashIndex), HashU256(HashIndex), HashI256(HashIndex), HashF32(HashIndex), HashF64(HashIndex), + // TODO(perf, centril): consider `UmbraString` or some "German string". HashString(HashIndex>), HashAV(HashIndex), @@ -271,12 +579,13 @@ enum TypedIndex { UniqueBTreeI32(UniqueBTreeIndex), UniqueBTreeU64(UniqueBTreeIndex), UniqueBTreeI64(UniqueBTreeIndex), - UniqueBTreeU128(UniqueBTreeIndex>), - UniqueBTreeI128(UniqueBTreeIndex>), + UniqueBTreeU128(UniqueBTreeIndex), + UniqueBTreeI128(UniqueBTreeIndex), UniqueBTreeU256(UniqueBTreeIndex), UniqueBTreeI256(UniqueBTreeIndex), UniqueBTreeF32(UniqueBTreeIndex), UniqueBTreeF64(UniqueBTreeIndex), + // TODO(perf, centril): consider `UmbraString` or some "German string". UniqueBTreeString(UniqueBTreeIndex>), UniqueBTreeAV(UniqueBTreeIndex), @@ -291,12 +600,13 @@ enum TypedIndex { UniqueHashI32(UniqueHashIndex), UniqueHashU64(UniqueHashIndex), UniqueHashI64(UniqueHashIndex), - UniqueHashU128(UniqueHashIndex>), - UniqueHashI128(UniqueHashIndex>), + UniqueHashU128(UniqueHashIndex), + UniqueHashI128(UniqueHashIndex), UniqueHashU256(UniqueHashIndex), UniqueHashI256(UniqueHashIndex), UniqueHashF32(UniqueHashIndex), UniqueHashF64(UniqueHashIndex), + // TODO(perf, centril): consider `UmbraString` or some "German string". UniqueHashString(UniqueHashIndex>), UniqueHashAV(UniqueHashIndex), @@ -404,14 +714,6 @@ impl MemoryUsage for TypedIndex { } } -fn as_tag(av: &AlgebraicValue) -> Option<&u8> { - av.as_sum().map(|s| &s.tag) -} - -fn as_sum_tag(av: &AlgebraicValue) -> Option<&SumTag> { - as_tag(av).map(|s| s.into()) -} - #[derive(Debug, PartialEq)] #[cfg_attr(test, derive(proptest_derive::Arbitrary))] pub enum IndexKind { @@ -651,55 +953,12 @@ impl TypedIndex { } } - /// Add the row referred to by `row_ref` to the index `self`, - /// which must be keyed at `cols`. + /// Add the relation `key -> ptr` to the index. /// /// Returns `Errs(existing_row)` if this index was a unique index that was violated. /// The index is not inserted to in that case. - /// - /// # Safety - /// - /// 1. Caller promises that `cols` matches what was given at construction (`Self::new`). - /// 2. Caller promises that the projection of `row_ref`'s type's equals the index's key type. - unsafe fn insert(&mut self, cols: &ColList, row_ref: RowRef<'_>) -> Result<(), RowPointer> { - fn project_to_singleton_key(cols: &ColList, row_ref: RowRef<'_>) -> T { - // Extract the column. - let col_pos = cols.as_singleton(); - // SAFETY: Caller promised that `cols` matches what was given at construction (`Self::new`). - // In the case of `.clone_structure()`, the structure is preserved, - // so the promise is also preserved. - // This entails, that because we reached here, that `cols` is singleton. - let col_pos = unsafe { col_pos.unwrap_unchecked() }.idx(); - - // Extract the layout of the column. - let col_layouts = &row_ref.row_layout().product().elements; - // SAFETY: - // - Caller promised that projecting the `row_ref`'s type/layout to `self.indexed_columns` - // gives us the index's key type. - // This entails that each `ColId` in `self.indexed_columns` - // must be in-bounds of `row_ref`'s layout. - let col_layout = unsafe { col_layouts.get_unchecked(col_pos) }; - - // Read the column in `row_ref`. - // SAFETY: - // - `col_layout` was just derived from the row layout. - // - Caller promised that the type-projection of the row type/layout - // equals the index's key type. - // We've reached here, so the index's key type is compatible with `T`. - // - `self` is a valid row so offsetting to `col_layout` is valid. - unsafe { T::unchecked_read_column(row_ref, col_layout) } - } - - fn insert_at_type( - this: &mut impl Index, - cols: &ColList, - row_ref: RowRef<'_>, - ) -> (Result<(), RowPointer>, Option) { - let key = project_to_singleton_key(cols, row_ref); - let res = this.insert(key, row_ref.pointer()); - (res, None) - } - + #[inline] + fn insert(&mut self, key: TypedIndexKey<'_>, ptr: RowPointer) -> Result<(), RowPointer> { /// Avoid inlining the closure into the common path. #[cold] #[inline(never)] @@ -707,116 +966,107 @@ impl TypedIndex { work() } - fn direct_insert_at_type( - this: &mut UniqueDirectIndex, - cols: &ColList, - row_ref: RowRef<'_>, + fn direct( + index: &mut UniqueDirectIndex, + key: K, + ptr: RowPointer, ) -> (Result<(), RowPointer>, Option) where TypedIndex: From>, { - let key = project_to_singleton_key(cols, row_ref); - let ptr = row_ref.pointer(); - match this.insert_maybe_despecialize(key, ptr) { + match index.insert_maybe_despecialize(key, ptr) { Ok(res) => (res, None), Err(Despecialize) => outlined_call(|| { - let mut index = this.into_btree(); + let mut index = index.into_btree(); let res = index.insert(key, ptr); (res, Some(index.into())) }), } } - fn insert_av( - this: &mut impl Index, - cols: &ColList, - row_ref: RowRef<'_>, - ) -> (Result<(), RowPointer>, Option) { - // SAFETY: Caller promised that any `col` in `cols` is in-bounds of `row_ref`'s layout. - let key = unsafe { row_ref.project_unchecked(cols) }; - let res = this.insert(key, row_ref.pointer()); - (res, None) - } - - let (res, new) = match self { - Self::BTreeBool(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeU8(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeSumTag(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeI8(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeU16(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeI16(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeU32(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeI32(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeU64(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeI64(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeU128(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeI128(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeU256(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeI256(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeF32(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeF64(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeString(idx) => insert_at_type(idx, cols, row_ref), - Self::BTreeAV(idx) => insert_av(idx, cols, row_ref), - Self::HashBool(idx) => insert_at_type(idx, cols, row_ref), - Self::HashU8(idx) => insert_at_type(idx, cols, row_ref), - Self::HashSumTag(idx) => insert_at_type(idx, cols, row_ref), - Self::HashI8(idx) => insert_at_type(idx, cols, row_ref), - Self::HashU16(idx) => insert_at_type(idx, cols, row_ref), - Self::HashI16(idx) => insert_at_type(idx, cols, row_ref), - Self::HashU32(idx) => insert_at_type(idx, cols, row_ref), - Self::HashI32(idx) => insert_at_type(idx, cols, row_ref), - Self::HashU64(idx) => insert_at_type(idx, cols, row_ref), - Self::HashI64(idx) => insert_at_type(idx, cols, row_ref), - Self::HashU128(idx) => insert_at_type(idx, cols, row_ref), - Self::HashI128(idx) => insert_at_type(idx, cols, row_ref), - Self::HashU256(idx) => insert_at_type(idx, cols, row_ref), - Self::HashI256(idx) => insert_at_type(idx, cols, row_ref), - Self::HashF32(idx) => insert_at_type(idx, cols, row_ref), - Self::HashF64(idx) => insert_at_type(idx, cols, row_ref), - Self::HashString(idx) => insert_at_type(idx, cols, row_ref), - Self::HashAV(idx) => insert_av(idx, cols, row_ref), - Self::UniqueBTreeBool(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeU8(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeSumTag(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeI8(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeU16(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeI16(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeU32(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeI32(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeU64(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeI64(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeU128(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeI128(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeU256(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeI256(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeF32(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeF64(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeString(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueBTreeAV(idx) => insert_av(idx, cols, row_ref), - Self::UniqueHashBool(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashU8(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashSumTag(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashI8(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashU16(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashI16(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashU32(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashI32(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashU64(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashI64(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashU128(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashI128(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashU256(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashI256(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashF32(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashF64(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashString(idx) => insert_at_type(idx, cols, row_ref), - Self::UniqueHashAV(this) => insert_av(this, cols, row_ref), - Self::UniqueDirectSumTag(idx) => insert_at_type(idx, cols, row_ref), - - Self::UniqueDirectU8(idx) => direct_insert_at_type(idx, cols, row_ref), - Self::UniqueDirectU16(idx) => direct_insert_at_type(idx, cols, row_ref), - Self::UniqueDirectU32(idx) => direct_insert_at_type(idx, cols, row_ref), - Self::UniqueDirectU64(idx) => direct_insert_at_type(idx, cols, row_ref), + use TypedIndex::*; + use TypedIndexKey::*; + let (res, new) = match (&mut *self, key) { + (BTreeBool(i), Bool(k)) => (i.insert(k, ptr), None), + (BTreeU8(i), U8(k)) => (i.insert(k, ptr), None), + (BTreeSumTag(i), SumTag(k)) => (i.insert(k, ptr), None), + (BTreeI8(i), I8(k)) => (i.insert(k, ptr), None), + (BTreeU16(i), U16(k)) => (i.insert(k, ptr), None), + (BTreeI16(i), I16(k)) => (i.insert(k, ptr), None), + (BTreeU32(i), U32(k)) => (i.insert(k, ptr), None), + (BTreeI32(i), I32(k)) => (i.insert(k, ptr), None), + (BTreeU64(i), U64(k)) => (i.insert(k, ptr), None), + (BTreeI64(i), I64(k)) => (i.insert(k, ptr), None), + (BTreeU128(i), U128(k)) => (i.insert(k, ptr), None), + (BTreeI128(i), I128(k)) => (i.insert(k, ptr), None), + (BTreeU256(i), U256(k)) => (i.insert(k, ptr), None), + (BTreeI256(i), I256(k)) => (i.insert(k, ptr), None), + (BTreeF32(i), F32(k)) => (i.insert(k, ptr), None), + (BTreeF64(i), F64(k)) => (i.insert(k, ptr), None), + (BTreeString(i), String(k)) => (i.insert(k.into_owned(), ptr), None), + (BTreeAV(i), AV(k)) => (i.insert(k.into_owned(), ptr), None), + (HashBool(i), Bool(k)) => (i.insert(k, ptr), None), + (HashU8(i), U8(k)) => (i.insert(k, ptr), None), + (HashSumTag(i), SumTag(k)) => (i.insert(k, ptr), None), + (HashI8(i), I8(k)) => (i.insert(k, ptr), None), + (HashU16(i), U16(k)) => (i.insert(k, ptr), None), + (HashI16(i), I16(k)) => (i.insert(k, ptr), None), + (HashU32(i), U32(k)) => (i.insert(k, ptr), None), + (HashI32(i), I32(k)) => (i.insert(k, ptr), None), + (HashU64(i), U64(k)) => (i.insert(k, ptr), None), + (HashI64(i), I64(k)) => (i.insert(k, ptr), None), + (HashU128(i), U128(k)) => (i.insert(k, ptr), None), + (HashI128(i), I128(k)) => (i.insert(k, ptr), None), + (HashU256(i), U256(k)) => (i.insert(k, ptr), None), + (HashI256(i), I256(k)) => (i.insert(k, ptr), None), + (HashF32(i), F32(k)) => (i.insert(k, ptr), None), + (HashF64(i), F64(k)) => (i.insert(k, ptr), None), + (HashString(i), String(k)) => (i.insert(k.into_owned(), ptr), None), + (HashAV(i), AV(k)) => (i.insert(k.into_owned(), ptr), None), + (UniqueBTreeBool(i), Bool(k)) => (i.insert(k, ptr), None), + (UniqueBTreeU8(i), U8(k)) => (i.insert(k, ptr), None), + (UniqueBTreeSumTag(i), SumTag(k)) => (i.insert(k, ptr), None), + (UniqueBTreeI8(i), I8(k)) => (i.insert(k, ptr), None), + (UniqueBTreeU16(i), U16(k)) => (i.insert(k, ptr), None), + (UniqueBTreeI16(i), I16(k)) => (i.insert(k, ptr), None), + (UniqueBTreeU32(i), U32(k)) => (i.insert(k, ptr), None), + (UniqueBTreeI32(i), I32(k)) => (i.insert(k, ptr), None), + (UniqueBTreeU64(i), U64(k)) => (i.insert(k, ptr), None), + (UniqueBTreeI64(i), I64(k)) => (i.insert(k, ptr), None), + (UniqueBTreeU128(i), U128(k)) => (i.insert(k, ptr), None), + (UniqueBTreeI128(i), I128(k)) => (i.insert(k, ptr), None), + (UniqueBTreeU256(i), U256(k)) => (i.insert(k, ptr), None), + (UniqueBTreeI256(i), I256(k)) => (i.insert(k, ptr), None), + (UniqueBTreeF32(i), F32(k)) => (i.insert(k, ptr), None), + (UniqueBTreeF64(i), F64(k)) => (i.insert(k, ptr), None), + (UniqueBTreeString(i), String(k)) => (i.insert(k.into_owned(), ptr), None), + (UniqueBTreeAV(i), AV(k)) => (i.insert(k.into_owned(), ptr), None), + (UniqueHashBool(i), Bool(k)) => (i.insert(k, ptr), None), + (UniqueHashU8(i), U8(k)) => (i.insert(k, ptr), None), + (UniqueHashSumTag(i), SumTag(k)) => (i.insert(k, ptr), None), + (UniqueHashI8(i), I8(k)) => (i.insert(k, ptr), None), + (UniqueHashU16(i), U16(k)) => (i.insert(k, ptr), None), + (UniqueHashI16(i), I16(k)) => (i.insert(k, ptr), None), + (UniqueHashU32(i), U32(k)) => (i.insert(k, ptr), None), + (UniqueHashI32(i), I32(k)) => (i.insert(k, ptr), None), + (UniqueHashU64(i), U64(k)) => (i.insert(k, ptr), None), + (UniqueHashI64(i), I64(k)) => (i.insert(k, ptr), None), + (UniqueHashU128(i), U128(k)) => (i.insert(k, ptr), None), + (UniqueHashI128(i), I128(k)) => (i.insert(k, ptr), None), + (UniqueHashU256(i), U256(k)) => (i.insert(k, ptr), None), + (UniqueHashI256(i), I256(k)) => (i.insert(k, ptr), None), + (UniqueHashF32(i), F32(k)) => (i.insert(k, ptr), None), + (UniqueHashF64(i), F64(k)) => (i.insert(k, ptr), None), + (UniqueHashString(i), String(k)) => (i.insert(k.into_owned(), ptr), None), + (UniqueHashAV(i), AV(k)) => (i.insert(k.into_owned(), ptr), None), + (UniqueDirectSumTag(i), SumTag(k)) => (i.insert(k, ptr), None), + + (UniqueDirectU8(i), U8(k)) => direct(i, k, ptr), + (UniqueDirectU16(i), U16(k)) => direct(i, k, ptr), + (UniqueDirectU32(i), U32(k)) => direct(i, k, ptr), + (UniqueDirectU64(i), U64(k)) => direct(i, k, ptr), + + _ => panic!("{}", WRONG_TYPE), }; if let Some(new) = new { @@ -826,217 +1076,189 @@ impl TypedIndex { res } - /// Remove the row referred to by `row_ref` from the index `self`, - /// which must be keyed at `cols`. + /// Remove the relation `key -> ptr` from the index. /// - /// If `cols` is inconsistent with `self`, - /// or the `row_ref` has a row type other than that used for `self`, - /// this will behave oddly; it may return an error, do nothing, - /// or remove the wrong value from the index. - /// Note, however, that it will not invoke undefined behavior. + /// Returns whether the row was present and has now been deleted. /// - /// If the row was present and has been deleted, returns `Ok(true)`. - // TODO(centril): make this unsafe and use unchecked conversions. - fn delete(&mut self, cols: &ColList, row_ref: RowRef<'_>) -> Result { - fn delete_at_type( - this: &mut I, - cols: &ColList, - row_ref: RowRef<'_>, - convert: impl FnOnce(T) -> I::Key, - ) -> Result { - let col_pos = cols.as_singleton().unwrap(); - let key = row_ref.read_col(col_pos).map_err(|_| col_pos)?; - let key = convert(key); - Ok(this.delete(&key, row_ref.pointer())) - } - - fn delete_av( - this: &mut impl Index, - cols: &ColList, - row_ref: RowRef<'_>, - ) -> Result { - let key = row_ref.project(cols)?; - Ok(this.delete(&key, row_ref.pointer())) - } - - use core::convert::identity as id; - - match self { - Self::BTreeBool(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeU8(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeSumTag(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeI8(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeU16(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeI16(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeU32(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeI32(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeU64(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeI64(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeU128(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeI128(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeU256(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeI256(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeF32(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeF64(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeString(this) => delete_at_type(this, cols, row_ref, id), - Self::BTreeAV(this) => delete_av(this, cols, row_ref), - Self::HashBool(this) => delete_at_type(this, cols, row_ref, id), - Self::HashU8(this) => delete_at_type(this, cols, row_ref, id), - Self::HashSumTag(this) => delete_at_type(this, cols, row_ref, id), - Self::HashI8(this) => delete_at_type(this, cols, row_ref, id), - Self::HashU16(this) => delete_at_type(this, cols, row_ref, id), - Self::HashI16(this) => delete_at_type(this, cols, row_ref, id), - Self::HashU32(this) => delete_at_type(this, cols, row_ref, id), - Self::HashI32(this) => delete_at_type(this, cols, row_ref, id), - Self::HashU64(this) => delete_at_type(this, cols, row_ref, id), - Self::HashI64(this) => delete_at_type(this, cols, row_ref, id), - Self::HashU128(this) => delete_at_type(this, cols, row_ref, id), - Self::HashI128(this) => delete_at_type(this, cols, row_ref, id), - Self::HashU256(this) => delete_at_type(this, cols, row_ref, id), - Self::HashI256(this) => delete_at_type(this, cols, row_ref, id), - Self::HashF32(this) => delete_at_type(this, cols, row_ref, id), - Self::HashF64(this) => delete_at_type(this, cols, row_ref, id), - Self::HashString(this) => delete_at_type(this, cols, row_ref, id), - Self::HashAV(this) => delete_av(this, cols, row_ref), - Self::UniqueBTreeBool(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeU8(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeSumTag(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeI8(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeU16(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeI16(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeU32(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeI32(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeU64(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeI64(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeU128(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeI128(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeU256(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeI256(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeF32(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeF64(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeString(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueBTreeAV(this) => delete_av(this, cols, row_ref), - Self::UniqueHashBool(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashU8(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashSumTag(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashI8(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashU16(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashI16(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashU32(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashI32(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashU64(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashI64(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashU128(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashI128(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashU256(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashI256(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashF32(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashF64(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashString(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueHashAV(this) => delete_av(this, cols, row_ref), - Self::UniqueDirectSumTag(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueDirectU8(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueDirectU16(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueDirectU32(this) => delete_at_type(this, cols, row_ref, id), - Self::UniqueDirectU64(this) => delete_at_type(this, cols, row_ref, id), + /// Panics if `key` is inconsistent with `self`. + #[inline] + fn delete(&mut self, key: &TypedIndexKey<'_>, ptr: RowPointer) -> bool { + use TypedIndex::*; + use TypedIndexKey::*; + match (self, key) { + (BTreeBool(i), Bool(k)) => i.delete(k, ptr), + (BTreeU8(i), U8(k)) => i.delete(k, ptr), + (BTreeSumTag(i), SumTag(k)) => i.delete(k, ptr), + (BTreeI8(i), I8(k)) => i.delete(k, ptr), + (BTreeU16(i), U16(k)) => i.delete(k, ptr), + (BTreeI16(i), I16(k)) => i.delete(k, ptr), + (BTreeU32(i), U32(k)) => i.delete(k, ptr), + (BTreeI32(i), I32(k)) => i.delete(k, ptr), + (BTreeU64(i), U64(k)) => i.delete(k, ptr), + (BTreeI64(i), I64(k)) => i.delete(k, ptr), + (BTreeU128(i), U128(k)) => i.delete(k, ptr), + (BTreeI128(i), I128(k)) => i.delete(k, ptr), + (BTreeU256(i), U256(k)) => i.delete(k, ptr), + (BTreeI256(i), I256(k)) => i.delete(k, ptr), + (BTreeF32(i), F32(k)) => i.delete(k, ptr), + (BTreeF64(i), F64(k)) => i.delete(k, ptr), + (BTreeString(i), String(k)) => i.delete(k.borrow(), ptr), + (BTreeAV(i), AV(k)) => i.delete(k.borrow(), ptr), + (HashBool(i), Bool(k)) => i.delete(k, ptr), + (HashU8(i), U8(k)) => i.delete(k, ptr), + (HashSumTag(i), SumTag(k)) => i.delete(k, ptr), + (HashI8(i), I8(k)) => i.delete(k, ptr), + (HashU16(i), U16(k)) => i.delete(k, ptr), + (HashI16(i), I16(k)) => i.delete(k, ptr), + (HashU32(i), U32(k)) => i.delete(k, ptr), + (HashI32(i), I32(k)) => i.delete(k, ptr), + (HashU64(i), U64(k)) => i.delete(k, ptr), + (HashI64(i), I64(k)) => i.delete(k, ptr), + (HashU128(i), U128(k)) => i.delete(k, ptr), + (HashI128(i), I128(k)) => i.delete(k, ptr), + (HashU256(i), U256(k)) => i.delete(k, ptr), + (HashI256(i), I256(k)) => i.delete(k, ptr), + (HashF32(i), F32(k)) => i.delete(k, ptr), + (HashF64(i), F64(k)) => i.delete(k, ptr), + (HashString(i), String(k)) => i.delete(k.borrow(), ptr), + (HashAV(i), AV(k)) => i.delete(k.borrow(), ptr), + (UniqueBTreeBool(i), Bool(k)) => i.delete(k, ptr), + (UniqueBTreeU8(i), U8(k)) => i.delete(k, ptr), + (UniqueBTreeSumTag(i), SumTag(k)) => i.delete(k, ptr), + (UniqueBTreeI8(i), I8(k)) => i.delete(k, ptr), + (UniqueBTreeU16(i), U16(k)) => i.delete(k, ptr), + (UniqueBTreeI16(i), I16(k)) => i.delete(k, ptr), + (UniqueBTreeU32(i), U32(k)) => i.delete(k, ptr), + (UniqueBTreeI32(i), I32(k)) => i.delete(k, ptr), + (UniqueBTreeU64(i), U64(k)) => i.delete(k, ptr), + (UniqueBTreeI64(i), I64(k)) => i.delete(k, ptr), + (UniqueBTreeU128(i), U128(k)) => i.delete(k, ptr), + (UniqueBTreeI128(i), I128(k)) => i.delete(k, ptr), + (UniqueBTreeU256(i), U256(k)) => i.delete(k, ptr), + (UniqueBTreeI256(i), I256(k)) => i.delete(k, ptr), + (UniqueBTreeF32(i), F32(k)) => i.delete(k, ptr), + (UniqueBTreeF64(i), F64(k)) => i.delete(k, ptr), + (UniqueBTreeString(i), String(k)) => i.delete(k.borrow(), ptr), + (UniqueBTreeAV(i), AV(k)) => i.delete(k.borrow(), ptr), + (UniqueHashBool(i), Bool(k)) => i.delete(k, ptr), + (UniqueHashU8(i), U8(k)) => i.delete(k, ptr), + (UniqueHashSumTag(i), SumTag(k)) => i.delete(k, ptr), + (UniqueHashI8(i), I8(k)) => i.delete(k, ptr), + (UniqueHashU16(i), U16(k)) => i.delete(k, ptr), + (UniqueHashI16(i), I16(k)) => i.delete(k, ptr), + (UniqueHashU32(i), U32(k)) => i.delete(k, ptr), + (UniqueHashI32(i), I32(k)) => i.delete(k, ptr), + (UniqueHashU64(i), U64(k)) => i.delete(k, ptr), + (UniqueHashI64(i), I64(k)) => i.delete(k, ptr), + (UniqueHashU128(i), U128(k)) => i.delete(k, ptr), + (UniqueHashI128(i), I128(k)) => i.delete(k, ptr), + (UniqueHashU256(i), U256(k)) => i.delete(k, ptr), + (UniqueHashI256(i), I256(k)) => i.delete(k, ptr), + (UniqueHashF32(i), F32(k)) => i.delete(k, ptr), + (UniqueHashF64(i), F64(k)) => i.delete(k, ptr), + (UniqueHashString(i), String(k)) => i.delete(k.borrow(), ptr), + (UniqueHashAV(i), AV(k)) => i.delete(k.borrow(), ptr), + (UniqueDirectSumTag(i), SumTag(k)) => i.delete(k, ptr), + (UniqueDirectU8(i), U8(k)) => i.delete(k, ptr), + (UniqueDirectU16(i), U16(k)) => i.delete(k, ptr), + (UniqueDirectU32(i), U32(k)) => i.delete(k, ptr), + (UniqueDirectU64(i), U64(k)) => i.delete(k, ptr), + _ => panic!("{}", WRONG_TYPE), } } - fn seek_point(&self, key: &AlgebraicValue) -> TypedIndexPointIter<'_> { - fn iter_at_type<'a, I: Index>( - this: &'a I, - key: &AlgebraicValue, - av_as_t: impl Fn(&AlgebraicValue) -> Option<&I::Key>, - ) -> I::PointIter<'a> { - this.seek_point(av_as_t(key).expect("key does not conform to key type of index")) - } - + #[inline] + fn seek_point(&self, key: &TypedIndexKey<'_>) -> TypedIndexPointIter<'_> { use TypedIndex::*; + use TypedIndexKey::*; use TypedIndexPointIter::*; - match self { - BTreeBool(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_bool)), - BTreeU8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u8)), - BTreeSumTag(this) => NonUnique(iter_at_type(this, key, as_sum_tag)), - BTreeI8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i8)), - BTreeU16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u16)), - BTreeI16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i16)), - BTreeU32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u32)), - BTreeI32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i32)), - BTreeU64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u64)), - BTreeI64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i64)), - BTreeU128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u128)), - BTreeI128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i128)), - BTreeU256(this) => NonUnique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), - BTreeI256(this) => NonUnique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), - BTreeF32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f32)), - BTreeF64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f64)), - BTreeString(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_string)), - BTreeAV(this) => NonUnique(this.seek_point(key)), - HashBool(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_bool)), - HashU8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u8)), - HashSumTag(this) => NonUnique(iter_at_type(this, key, as_sum_tag)), - HashI8(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i8)), - HashU16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u16)), - HashI16(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i16)), - HashU32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u32)), - HashI32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i32)), - HashU64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u64)), - HashI64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i64)), - HashU128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_u128)), - HashI128(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_i128)), - HashU256(this) => NonUnique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), - HashI256(this) => NonUnique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), - HashF32(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f32)), - HashF64(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_f64)), - HashString(this) => NonUnique(iter_at_type(this, key, AlgebraicValue::as_string)), - HashAV(this) => NonUnique(this.seek_point(key)), - UniqueBTreeBool(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_bool)), - UniqueBTreeU8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u8)), - UniqueBTreeSumTag(this) => Unique(iter_at_type(this, key, as_sum_tag)), - UniqueBTreeI8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i8)), - UniqueBTreeU16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u16)), - UniqueBTreeI16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i16)), - UniqueBTreeU32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u32)), - UniqueBTreeI32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i32)), - UniqueBTreeU64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u64)), - UniqueBTreeI64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i64)), - UniqueBTreeU128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u128)), - UniqueBTreeI128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i128)), - UniqueBTreeU256(this) => Unique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), - UniqueBTreeI256(this) => Unique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), - UniqueBTreeF32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f32)), - UniqueBTreeF64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f64)), - UniqueBTreeString(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_string)), - UniqueBTreeAV(this) => Unique(this.seek_point(key)), - - UniqueHashBool(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_bool)), - UniqueHashU8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u8)), - UniqueHashSumTag(this) => Unique(iter_at_type(this, key, as_sum_tag)), - UniqueHashI8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i8)), - UniqueHashU16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u16)), - UniqueHashI16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i16)), - UniqueHashU32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u32)), - UniqueHashI32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i32)), - UniqueHashU64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u64)), - UniqueHashI64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i64)), - UniqueHashU128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u128)), - UniqueHashI128(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_i128)), - UniqueHashU256(this) => Unique(iter_at_type(this, key, |av| av.as_u256().map(|x| &**x))), - UniqueHashI256(this) => Unique(iter_at_type(this, key, |av| av.as_i256().map(|x| &**x))), - UniqueHashF32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f32)), - UniqueHashF64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_f64)), - UniqueHashString(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_string)), - UniqueHashAV(this) => Unique(this.seek_point(key)), - - UniqueDirectSumTag(this) => Unique(iter_at_type(this, key, as_sum_tag)), - UniqueDirectU8(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u8)), - UniqueDirectU16(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u16)), - UniqueDirectU32(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u32)), - UniqueDirectU64(this) => Unique(iter_at_type(this, key, AlgebraicValue::as_u64)), + match (self, key) { + (BTreeBool(this), Bool(key)) => NonUnique(this.seek_point(key)), + (BTreeU8(this), U8(key)) => NonUnique(this.seek_point(key)), + (BTreeSumTag(this), SumTag(key)) => NonUnique(this.seek_point(key)), + (BTreeI8(this), I8(key)) => NonUnique(this.seek_point(key)), + (BTreeU16(this), U16(key)) => NonUnique(this.seek_point(key)), + (BTreeI16(this), I16(key)) => NonUnique(this.seek_point(key)), + (BTreeU32(this), U32(key)) => NonUnique(this.seek_point(key)), + (BTreeI32(this), I32(key)) => NonUnique(this.seek_point(key)), + (BTreeU64(this), U64(key)) => NonUnique(this.seek_point(key)), + (BTreeI64(this), I64(key)) => NonUnique(this.seek_point(key)), + (BTreeU128(this), U128(key)) => NonUnique(this.seek_point(key)), + (BTreeI128(this), I128(key)) => NonUnique(this.seek_point(key)), + (BTreeU256(this), U256(key)) => NonUnique(this.seek_point(key)), + (BTreeI256(this), I256(key)) => NonUnique(this.seek_point(key)), + (BTreeF32(this), F32(key)) => NonUnique(this.seek_point(key)), + (BTreeF64(this), F64(key)) => NonUnique(this.seek_point(key)), + (BTreeString(this), String(key)) => NonUnique(this.seek_point(key.borrow())), + (BTreeAV(this), AV(key)) => NonUnique(this.seek_point(key.borrow())), + (HashBool(this), Bool(key)) => NonUnique(this.seek_point(key)), + (HashU8(this), U8(key)) => NonUnique(this.seek_point(key)), + (HashSumTag(this), SumTag(key)) => NonUnique(this.seek_point(key)), + (HashI8(this), I8(key)) => NonUnique(this.seek_point(key)), + (HashU16(this), U16(key)) => NonUnique(this.seek_point(key)), + (HashI16(this), I16(key)) => NonUnique(this.seek_point(key)), + (HashU32(this), U32(key)) => NonUnique(this.seek_point(key)), + (HashI32(this), I32(key)) => NonUnique(this.seek_point(key)), + (HashU64(this), U64(key)) => NonUnique(this.seek_point(key)), + (HashI64(this), I64(key)) => NonUnique(this.seek_point(key)), + (HashU128(this), U128(key)) => NonUnique(this.seek_point(key)), + (HashI128(this), I128(key)) => NonUnique(this.seek_point(key)), + (HashU256(this), U256(key)) => NonUnique(this.seek_point(key)), + (HashI256(this), I256(key)) => NonUnique(this.seek_point(key)), + (HashF32(this), F32(key)) => NonUnique(this.seek_point(key)), + (HashF64(this), F64(key)) => NonUnique(this.seek_point(key)), + (HashString(this), String(key)) => NonUnique(this.seek_point(key.borrow())), + (HashAV(this), AV(key)) => NonUnique(this.seek_point(key.borrow())), + (UniqueBTreeBool(this), Bool(key)) => Unique(this.seek_point(key)), + (UniqueBTreeU8(this), U8(key)) => Unique(this.seek_point(key)), + (UniqueBTreeSumTag(this), SumTag(key)) => Unique(this.seek_point(key)), + (UniqueBTreeI8(this), I8(key)) => Unique(this.seek_point(key)), + (UniqueBTreeU16(this), U16(key)) => Unique(this.seek_point(key)), + (UniqueBTreeI16(this), I16(key)) => Unique(this.seek_point(key)), + (UniqueBTreeU32(this), U32(key)) => Unique(this.seek_point(key)), + (UniqueBTreeI32(this), I32(key)) => Unique(this.seek_point(key)), + (UniqueBTreeU64(this), U64(key)) => Unique(this.seek_point(key)), + (UniqueBTreeI64(this), I64(key)) => Unique(this.seek_point(key)), + (UniqueBTreeU128(this), U128(key)) => Unique(this.seek_point(key)), + (UniqueBTreeI128(this), I128(key)) => Unique(this.seek_point(key)), + (UniqueBTreeU256(this), U256(key)) => Unique(this.seek_point(key)), + (UniqueBTreeI256(this), I256(key)) => Unique(this.seek_point(key)), + (UniqueBTreeF32(this), F32(key)) => Unique(this.seek_point(key)), + (UniqueBTreeF64(this), F64(key)) => Unique(this.seek_point(key)), + (UniqueBTreeString(this), String(key)) => Unique(this.seek_point(key.borrow())), + (UniqueBTreeAV(this), AV(key)) => Unique(this.seek_point(key.borrow())), + (UniqueHashBool(this), Bool(key)) => Unique(this.seek_point(key)), + (UniqueHashU8(this), U8(key)) => Unique(this.seek_point(key)), + (UniqueHashSumTag(this), SumTag(key)) => Unique(this.seek_point(key)), + (UniqueHashI8(this), I8(key)) => Unique(this.seek_point(key)), + (UniqueHashU16(this), U16(key)) => Unique(this.seek_point(key)), + (UniqueHashI16(this), I16(key)) => Unique(this.seek_point(key)), + (UniqueHashU32(this), U32(key)) => Unique(this.seek_point(key)), + (UniqueHashI32(this), I32(key)) => Unique(this.seek_point(key)), + (UniqueHashU64(this), U64(key)) => Unique(this.seek_point(key)), + (UniqueHashI64(this), I64(key)) => Unique(this.seek_point(key)), + (UniqueHashU128(this), U128(key)) => Unique(this.seek_point(key)), + (UniqueHashI128(this), I128(key)) => Unique(this.seek_point(key)), + (UniqueHashU256(this), U256(key)) => Unique(this.seek_point(key)), + (UniqueHashI256(this), I256(key)) => Unique(this.seek_point(key)), + (UniqueHashF32(this), F32(key)) => Unique(this.seek_point(key)), + (UniqueHashF64(this), F64(key)) => Unique(this.seek_point(key)), + (UniqueHashString(this), String(key)) => Unique(this.seek_point(key.borrow())), + (UniqueHashAV(this), AV(key)) => Unique(this.seek_point(key.borrow())), + (UniqueDirectSumTag(this), SumTag(key)) => Unique(this.seek_point(key)), + (UniqueDirectU8(this), U8(key)) => Unique(this.seek_point(key)), + (UniqueDirectU16(this), U16(key)) => Unique(this.seek_point(key)), + (UniqueDirectU32(this), U32(key)) => Unique(this.seek_point(key)), + (UniqueDirectU64(this), U64(key)) => Unique(this.seek_point(key)), + _ => panic!("{}", WRONG_TYPE), } } - fn seek_range(&self, range: &impl RangeBounds) -> IndexSeekRangeResult> { + #[inline] + fn seek_range<'a>( + &self, + range: &impl RangeBounds>, + ) -> IndexSeekRangeResult> { // Copied from `RangeBounds::is_empty` as it's unstable. // TODO(centril): replace once stable. fn is_empty(bounds: &impl RangeBounds) -> bool { @@ -1050,15 +1272,14 @@ impl TypedIndex { } } - fn iter_at_type<'a, I: RangedIndex>( - this: &'a I, - range: &impl RangeBounds, - av_as_t: impl Fn(&AlgebraicValue) -> Option<&I::Key>, - ) -> I::RangeIter<'a> { - let av_as_t = |v| av_as_t(v).expect("bound does not conform to key type of index"); - let start = range.start_bound().map(av_as_t); - let end = range.end_bound().map(av_as_t); - this.seek_range(&(start, end)) + fn map<'a, 'b: 'a, T: 'a + ?Sized>( + range: &'a impl RangeBounds>, + map: impl Copy + FnOnce(&'a TypedIndexKey<'b>) -> Option<&'a T>, + ) -> impl RangeBounds + 'a { + let as_key = |v| map(v).expect(WRONG_TYPE); + let start = range.start_bound().map(as_key); + let end = range.end_bound().map(as_key); + (start, end) } use TypedIndexRangeIter::*; @@ -1104,49 +1325,55 @@ impl TypedIndex { // Ensure we don't panic inside `BTreeMap::seek_range`. _ if is_empty(range) => RangeEmpty, - Self::BTreeBool(this) => BTreeBool(iter_at_type(this, range, AlgebraicValue::as_bool)), - Self::BTreeU8(this) => BTreeU8(iter_at_type(this, range, AlgebraicValue::as_u8)), - Self::BTreeSumTag(this) => BTreeSumTag(iter_at_type(this, range, as_sum_tag)), - Self::BTreeI8(this) => BTreeI8(iter_at_type(this, range, AlgebraicValue::as_i8)), - Self::BTreeU16(this) => BTreeU16(iter_at_type(this, range, AlgebraicValue::as_u16)), - Self::BTreeI16(this) => BTreeI16(iter_at_type(this, range, AlgebraicValue::as_i16)), - Self::BTreeU32(this) => BTreeU32(iter_at_type(this, range, AlgebraicValue::as_u32)), - Self::BTreeI32(this) => BTreeI32(iter_at_type(this, range, AlgebraicValue::as_i32)), - Self::BTreeU64(this) => BTreeU64(iter_at_type(this, range, AlgebraicValue::as_u64)), - Self::BTreeI64(this) => BTreeI64(iter_at_type(this, range, AlgebraicValue::as_i64)), - Self::BTreeU128(this) => BTreeU128(iter_at_type(this, range, AlgebraicValue::as_u128)), - Self::BTreeI128(this) => BTreeI128(iter_at_type(this, range, AlgebraicValue::as_i128)), - Self::BTreeU256(this) => BTreeU256(iter_at_type(this, range, |av| av.as_u256().map(|x| &**x))), - Self::BTreeI256(this) => BTreeI256(iter_at_type(this, range, |av| av.as_i256().map(|x| &**x))), - Self::BTreeF32(this) => BTreeF32(iter_at_type(this, range, AlgebraicValue::as_f32)), - Self::BTreeF64(this) => BTreeF64(iter_at_type(this, range, AlgebraicValue::as_f64)), - Self::BTreeString(this) => BTreeString(iter_at_type(this, range, AlgebraicValue::as_string)), - Self::BTreeAV(this) => BTreeAV(this.seek_range(range)), - - Self::UniqueBTreeBool(this) => UniqueBTreeBool(iter_at_type(this, range, AlgebraicValue::as_bool)), - Self::UniqueBTreeU8(this) => UniqueBTreeU8(iter_at_type(this, range, AlgebraicValue::as_u8)), - Self::UniqueBTreeSumTag(this) => UniqueBTreeSumTag(iter_at_type(this, range, as_sum_tag)), - Self::UniqueBTreeI8(this) => UniqueBTreeI8(iter_at_type(this, range, AlgebraicValue::as_i8)), - Self::UniqueBTreeU16(this) => UniqueBTreeU16(iter_at_type(this, range, AlgebraicValue::as_u16)), - Self::UniqueBTreeI16(this) => UniqueBTreeI16(iter_at_type(this, range, AlgebraicValue::as_i16)), - Self::UniqueBTreeU32(this) => UniqueBTreeU32(iter_at_type(this, range, AlgebraicValue::as_u32)), - Self::UniqueBTreeI32(this) => UniqueBTreeI32(iter_at_type(this, range, AlgebraicValue::as_i32)), - Self::UniqueBTreeU64(this) => UniqueBTreeU64(iter_at_type(this, range, AlgebraicValue::as_u64)), - Self::UniqueBTreeI64(this) => UniqueBTreeI64(iter_at_type(this, range, AlgebraicValue::as_i64)), - Self::UniqueBTreeU128(this) => UniqueBTreeU128(iter_at_type(this, range, AlgebraicValue::as_u128)), - Self::UniqueBTreeI128(this) => UniqueBTreeI128(iter_at_type(this, range, AlgebraicValue::as_i128)), - Self::UniqueBTreeF32(this) => UniqueBTreeF32(iter_at_type(this, range, AlgebraicValue::as_f32)), - Self::UniqueBTreeF64(this) => UniqueBTreeF64(iter_at_type(this, range, AlgebraicValue::as_f64)), - Self::UniqueBTreeU256(this) => UniqueBTreeU256(iter_at_type(this, range, |av| av.as_u256().map(|x| &**x))), - Self::UniqueBTreeI256(this) => UniqueBTreeI256(iter_at_type(this, range, |av| av.as_i256().map(|x| &**x))), - Self::UniqueBTreeString(this) => UniqueBTreeString(iter_at_type(this, range, AlgebraicValue::as_string)), - Self::UniqueBTreeAV(this) => UniqueBTreeAV(this.seek_range(range)), - - Self::UniqueDirectSumTag(this) => UniqueDirectU8(iter_at_type(this, range, as_sum_tag)), - Self::UniqueDirectU8(this) => UniqueDirect(iter_at_type(this, range, AlgebraicValue::as_u8)), - Self::UniqueDirectU16(this) => UniqueDirect(iter_at_type(this, range, AlgebraicValue::as_u16)), - Self::UniqueDirectU32(this) => UniqueDirect(iter_at_type(this, range, AlgebraicValue::as_u32)), - Self::UniqueDirectU64(this) => UniqueDirect(iter_at_type(this, range, AlgebraicValue::as_u64)), + Self::BTreeBool(this) => BTreeBool(this.seek_range(&map(range, TypedIndexKey::as_bool))), + Self::BTreeU8(this) => BTreeU8(this.seek_range(&map(range, TypedIndexKey::as_u8))), + Self::BTreeSumTag(this) => BTreeSumTag(this.seek_range(&map(range, TypedIndexKey::as_sum_tag))), + Self::BTreeI8(this) => BTreeI8(this.seek_range(&map(range, TypedIndexKey::as_i8))), + Self::BTreeU16(this) => BTreeU16(this.seek_range(&map(range, TypedIndexKey::as_u16))), + Self::BTreeI16(this) => BTreeI16(this.seek_range(&map(range, TypedIndexKey::as_i16))), + Self::BTreeU32(this) => BTreeU32(this.seek_range(&map(range, TypedIndexKey::as_u32))), + Self::BTreeI32(this) => BTreeI32(this.seek_range(&map(range, TypedIndexKey::as_i32))), + Self::BTreeU64(this) => BTreeU64(this.seek_range(&map(range, TypedIndexKey::as_u64))), + Self::BTreeI64(this) => BTreeI64(this.seek_range(&map(range, TypedIndexKey::as_i64))), + Self::BTreeU128(this) => BTreeU128(this.seek_range(&map(range, TypedIndexKey::as_u128))), + Self::BTreeI128(this) => BTreeI128(this.seek_range(&map(range, TypedIndexKey::as_i128))), + Self::BTreeU256(this) => BTreeU256(this.seek_range(&map(range, TypedIndexKey::as_u256))), + Self::BTreeI256(this) => BTreeI256(this.seek_range(&map(range, TypedIndexKey::as_i256))), + Self::BTreeF32(this) => BTreeF32(this.seek_range(&map(range, TypedIndexKey::as_f32))), + Self::BTreeF64(this) => BTreeF64(this.seek_range(&map(range, TypedIndexKey::as_f64))), + Self::BTreeString(this) => { + let range = map(range, |k| k.as_string().map(|s| s.borrow())); + BTreeString(this.seek_range(&range)) + } + Self::BTreeAV(this) => BTreeAV(this.seek_range(&map(range, |k| k.as_av().map(|s| s.borrow())))), + + Self::UniqueBTreeBool(this) => UniqueBTreeBool(this.seek_range(&map(range, TypedIndexKey::as_bool))), + Self::UniqueBTreeU8(this) => UniqueBTreeU8(this.seek_range(&map(range, TypedIndexKey::as_u8))), + Self::UniqueBTreeSumTag(this) => UniqueBTreeSumTag(this.seek_range(&map(range, TypedIndexKey::as_sum_tag))), + Self::UniqueBTreeI8(this) => UniqueBTreeI8(this.seek_range(&map(range, TypedIndexKey::as_i8))), + Self::UniqueBTreeU16(this) => UniqueBTreeU16(this.seek_range(&map(range, TypedIndexKey::as_u16))), + Self::UniqueBTreeI16(this) => UniqueBTreeI16(this.seek_range(&map(range, TypedIndexKey::as_i16))), + Self::UniqueBTreeU32(this) => UniqueBTreeU32(this.seek_range(&map(range, TypedIndexKey::as_u32))), + Self::UniqueBTreeI32(this) => UniqueBTreeI32(this.seek_range(&map(range, TypedIndexKey::as_i32))), + Self::UniqueBTreeU64(this) => UniqueBTreeU64(this.seek_range(&map(range, TypedIndexKey::as_u64))), + Self::UniqueBTreeI64(this) => UniqueBTreeI64(this.seek_range(&map(range, TypedIndexKey::as_i64))), + Self::UniqueBTreeU128(this) => UniqueBTreeU128(this.seek_range(&map(range, TypedIndexKey::as_u128))), + Self::UniqueBTreeI128(this) => UniqueBTreeI128(this.seek_range(&map(range, TypedIndexKey::as_i128))), + Self::UniqueBTreeU256(this) => UniqueBTreeU256(this.seek_range(&map(range, TypedIndexKey::as_u256))), + Self::UniqueBTreeI256(this) => UniqueBTreeI256(this.seek_range(&map(range, TypedIndexKey::as_i256))), + Self::UniqueBTreeF32(this) => UniqueBTreeF32(this.seek_range(&map(range, TypedIndexKey::as_f32))), + Self::UniqueBTreeF64(this) => UniqueBTreeF64(this.seek_range(&map(range, TypedIndexKey::as_f64))), + Self::UniqueBTreeString(this) => { + let range = map(range, |k| k.as_string().map(|s| s.borrow())); + UniqueBTreeString(this.seek_range(&range)) + } + Self::UniqueBTreeAV(this) => UniqueBTreeAV(this.seek_range(&map(range, |k| k.as_av().map(|s| s.borrow())))), + + Self::UniqueDirectSumTag(this) => UniqueDirectU8(this.seek_range(&map(range, TypedIndexKey::as_sum_tag))), + Self::UniqueDirectU8(this) => UniqueDirect(this.seek_range(&map(range, TypedIndexKey::as_u8))), + Self::UniqueDirectU16(this) => UniqueDirect(this.seek_range(&map(range, TypedIndexKey::as_u16))), + Self::UniqueDirectU32(this) => UniqueDirect(this.seek_range(&map(range, TypedIndexKey::as_u32))), + Self::UniqueDirectU64(this) => UniqueDirect(this.seek_range(&map(range, TypedIndexKey::as_u64))), }) } @@ -1185,6 +1412,17 @@ impl TypedIndex { } } +pub struct IndexKey<'a> { + key: TypedIndexKey<'a>, +} + +impl IndexKey<'_> { + /// Converts the key into an [`AlgebraicValue`]. + pub fn into_algebraic_value(self) -> AlgebraicValue { + self.key.into_algebraic_value() + } +} + /// An index on a set of [`ColId`]s of a table. #[derive(Debug, PartialEq, Eq)] pub struct TableIndex { @@ -1249,6 +1487,39 @@ impl TableIndex { self.idx.is_unique() } + /// Derives a key for this index from `value`. + /// + /// Panics if `value` is not consistent with this index's key type. + #[inline] + pub fn key_from_algebraic_value<'a>(&self, value: &'a AlgebraicValue) -> IndexKey<'a> { + let key = TypedIndexKey::from_algebraic_value(&self.idx, value); + IndexKey { key } + } + + /// Derives a key for this index from BSATN-encoded `bytes`. + /// + /// Returns an error if `bytes` is not properly encoded for this index's key type. + #[inline] + pub fn key_from_bsatn<'de>(&self, bytes: &'de [u8]) -> Result, DecodeError> { + let key = TypedIndexKey::from_bsatn(&self.idx, &self.key_type, bytes)?; + Ok(IndexKey { key }) + } + + /// Derives a key for this index from `row_ref`. + /// + /// # Safety + /// + /// Caller promises that the projection of `row_ref`'s type's + /// to the indexed column equals the index's key type. + #[inline] + pub unsafe fn key_from_row<'a>(&self, row_ref: RowRef<'a>) -> IndexKey<'a> { + // SAFETY: + // 1. We're passing the same `ColList` that was provided during construction. + // 2. Forward caller requirements. + let key = unsafe { TypedIndexKey::from_row_ref(&self.idx, &self.indexed_columns, row_ref) }; + IndexKey { key } + } + /// Inserts `ptr` with the value `row` to this index. /// This index will extract the necessary values from `row` based on `self.indexed_columns`. /// @@ -1262,39 +1533,56 @@ impl TableIndex { /// It also follows from `row_ref`'s type/layout /// being the same as passed in on `self`'s construction. pub unsafe fn check_and_insert(&mut self, row_ref: RowRef<'_>) -> Result<(), RowPointer> { - // SAFETY: - // 1. We're passing the same `ColList` that was provided during construction. - // 2. Forward the caller's proof obligation. - unsafe { self.idx.insert(&self.indexed_columns, row_ref) } + // SAFETY: Forward the caller's proof obligation. + let key = unsafe { self.key_from_row(row_ref).key }; + self.idx.insert(key, row_ref.pointer()) } /// Deletes `row_ref` with its indexed value `row_ref.project(&self.indexed_columns)` from this index. /// /// Returns whether `ptr` was present. - pub fn delete(&mut self, row_ref: RowRef<'_>) -> Result { - self.idx.delete(&self.indexed_columns, row_ref) + /// + /// # Safety + /// + /// Caller promises that projecting the `row_ref`'s type + /// to the index's columns equals the index's key type. + /// This is entailed by an index belonging to the table's schema. + /// It also follows from `row_ref`'s type/layout + /// being the same as passed in on `self`'s construction. + pub unsafe fn delete(&mut self, row_ref: RowRef<'_>) -> bool { + // SAFETY: Forward the caller's proof obligation. + let key = unsafe { self.key_from_row(row_ref).key }; + self.idx.delete(&key.borrowed(), row_ref.pointer()) } /// Returns whether `value` is in this index. pub fn contains_any(&self, value: &AlgebraicValue) -> bool { - self.seek_point(value).next().is_some() + let key = self.key_from_algebraic_value(value); + self.seek_point(&key).next().is_some() } /// Returns the number of rows associated with this `value`. /// Returns `None` if 0. /// Returns `Some(1)` if the index is unique. pub fn count(&self, value: &AlgebraicValue) -> Option { - match self.seek_point(value).count() { + let key = self.key_from_algebraic_value(value); + match self.seek_point(&key).count() { 0 => None, n => Some(n), } } /// Returns an iterator that yields all the `RowPointer`s for the given `key`. - pub fn seek_point(&self, key: &AlgebraicValue) -> TableIndexPointIter<'_> { - TableIndexPointIter { - iter: self.idx.seek_point(key), - } + pub fn seek_point_via_algebraic_value(&self, value: &AlgebraicValue) -> TableIndexPointIter<'_> { + let key = self.key_from_algebraic_value(value); + self.seek_point(&key) + } + + /// Returns an iterator that yields all the `RowPointer`s for the given `key`. + #[inline] + pub fn seek_point(&self, key: &IndexKey<'_>) -> TableIndexPointIter<'_> { + let iter = self.idx.seek_point(&key.key); + TableIndexPointIter { iter } } /// Returns an iterator over the [TableIndex], @@ -1305,9 +1593,11 @@ impl TableIndex { &self, range: &impl RangeBounds, ) -> IndexSeekRangeResult> { - Ok(TableIndexRangeIter { - iter: self.idx.seek_range(range)?, - }) + let start = range.start_bound().map(|v| self.key_from_algebraic_value(v).key); + let end = range.end_bound().map(|v| self.key_from_algebraic_value(v).key); + let range = (start, end); + let iter = self.idx.seek_range(&range)?; + Ok(TableIndexRangeIter { iter }) } /// Extends [`TableIndex`] with `rows`. @@ -1472,6 +1762,7 @@ mod test { use spacetimedb_data_structures::map::HashMap; use spacetimedb_lib::ProductTypeElement; use spacetimedb_primitives::ColId; + use spacetimedb_sats::algebraic_value::Packed; use spacetimedb_sats::proptest::{generate_algebraic_value, generate_primitive_algebraic_type}; use spacetimedb_sats::{ product, @@ -1522,7 +1813,9 @@ mod test { index: &'a TableIndex, row: &'a AlgebraicValue, ) -> Option> { - index.is_unique().then(|| index.seek_point(row)) + index + .is_unique() + .then(|| index.seek_point(&index.key_from_algebraic_value(row))) } fn successor_of_primitive(av: &AlgebraicValue) -> Option { @@ -1571,7 +1864,7 @@ mod test { let pool = PagePool::new_for_test(); let mut blob_store = HashMapBlobStore::default(); let row_ref = table.insert(&pool, &mut blob_store, &pv).unwrap().1; - prop_assert_eq!(index.delete(row_ref).unwrap(), false); + prop_assert_eq!(unsafe { index.delete(row_ref) }, false); prop_assert!(index.idx.is_empty()); prop_assert_eq!(index.num_keys(), 0); prop_assert_eq!(index.num_key_bytes(), 0); @@ -1596,7 +1889,7 @@ mod test { prop_assert_eq!(index.num_rows(), 1); prop_assert_eq!(index.contains_any(&value), true); - prop_assert_eq!(index.delete(row_ref).unwrap(), true); + prop_assert_eq!(unsafe { index.delete(row_ref) }, true); prop_assert_eq!(index.num_keys(), 0); prop_assert_eq!(index.num_rows(), 0); prop_assert_eq!(index.contains_any(&value), false); @@ -1717,7 +2010,7 @@ mod test { // Test point ranges. for x in range.clone() { test_seek(&index, &val_to_ptr, V(x), [x])?; - check_seek(index.seek_point(&V(x)).collect(), &val_to_ptr, [x])?; + check_seek(index.seek_point_via_algebraic_value(&V(x)).collect(), &val_to_ptr, [x])?; } // Test `..` (`RangeFull`). diff --git a/crates/table/src/table_index/unique_btree_index.rs b/crates/table/src/table_index/unique_btree_index.rs index ba758201478..c2681d623ce 100644 --- a/crates/table/src/table_index/unique_btree_index.rs +++ b/crates/table/src/table_index/unique_btree_index.rs @@ -1,6 +1,6 @@ use super::{Index, KeySize, RangedIndex}; use crate::{indexes::RowPointer, table_index::key_size::KeyBytesStorage}; -use core::{ops::RangeBounds, option::IntoIter}; +use core::{borrow::Borrow, ops::RangeBounds, option::IntoIter}; use spacetimedb_sats::memory_usage::MemoryUsage; use std::collections::btree_map::{BTreeMap, Entry, Range}; @@ -41,7 +41,7 @@ impl Index for UniqueBTreeIndex { fn insert(&mut self, key: K, val: RowPointer) -> Result<(), RowPointer> { match self.map.entry(key) { Entry::Vacant(e) => { - self.num_key_bytes.add_to_key_bytes::(e.key()); + self.num_key_bytes.add_to_key_bytes(e.key()); e.insert(val); Ok(()) } @@ -49,12 +49,8 @@ impl Index for UniqueBTreeIndex { } } - fn delete(&mut self, key: &K, _: RowPointer) -> bool { - let ret = self.map.remove(key).is_some(); - if ret { - self.num_key_bytes.sub_from_key_bytes::(key); - } - ret + fn delete(&mut self, key: &K, ptr: RowPointer) -> bool { + self.delete(key, ptr) } fn num_keys(&self) -> usize { @@ -94,6 +90,32 @@ impl Index for UniqueBTreeIndex { } } +impl UniqueBTreeIndex { + /// See [`Index::delete`]. + /// This version has relaxed bounds. + pub fn delete(&mut self, key: &Q, _: RowPointer) -> bool + where + Q: ?Sized + KeySize + Ord, + ::Key: Borrow, + { + let ret = self.map.remove(key).is_some(); + if ret { + self.num_key_bytes.sub_from_key_bytes(key); + } + ret + } + + /// See [`Index::seek_point`]. + /// This version has relaxed bounds. + pub fn seek_point(&self, point: &Q) -> ::PointIter<'_> + where + Q: ?Sized + Ord, + ::Key: Borrow, + { + UniquePointIter::new(self.map.get(point).copied()) + } +} + /// An iterator over the potential value in a unique index for a given key. pub struct UniquePointIter { /// The iterator seeking for matching keys in the range. @@ -123,6 +145,17 @@ impl RangedIndex for UniqueBTreeIndex { Self: 'a; fn seek_range(&self, range: &impl RangeBounds) -> Self::RangeIter<'_> { + self.seek_range(range) + } +} + +impl UniqueBTreeIndex { + /// See [`RangedIndex::seek_range`]. + /// This version has relaxed bounds. + pub fn seek_range(&self, range: &impl RangeBounds) -> ::RangeIter<'_> + where + ::Key: Borrow, + { UniqueBTreeIndexRangeIter { iter: self.map.range((range.start_bound(), range.end_bound())), } diff --git a/crates/table/src/table_index/unique_direct_fixed_cap_index.rs b/crates/table/src/table_index/unique_direct_fixed_cap_index.rs index ef3dbdc97f9..d154e2ef908 100644 --- a/crates/table/src/table_index/unique_direct_fixed_cap_index.rs +++ b/crates/table/src/table_index/unique_direct_fixed_cap_index.rs @@ -85,7 +85,12 @@ impl Index for UniqueDirectFixedCapIndex { Self: 'a; fn seek_point(&self, &key: &Self::Key) -> Self::PointIter<'_> { - let point = self.array.get(key.to_usize()).copied().filter(|slot| *slot != NONE_PTR); + let point = self + .array + .get(key.to_usize()) + .copied() + .filter(|slot| *slot != NONE_PTR) + .map(expose); UniquePointIter::new(point) } diff --git a/crates/table/src/table_index/unique_direct_index.rs b/crates/table/src/table_index/unique_direct_index.rs index abdd688e80f..b1939e11adc 100644 --- a/crates/table/src/table_index/unique_direct_index.rs +++ b/crates/table/src/table_index/unique_direct_index.rs @@ -238,7 +238,8 @@ impl Index for UniqueDirectIndex { .get(outer_key) .and_then(|x| x.as_ref()) .map(|inner| inner.get(inner_key)) - .filter(|slot| *slot != NONE_PTR); + .filter(|slot| *slot != NONE_PTR) + .map(expose); UniquePointIter::new(point) } diff --git a/crates/table/src/table_index/unique_hash_index.rs b/crates/table/src/table_index/unique_hash_index.rs index 71a9f127a73..beb3bd5bcd1 100644 --- a/crates/table/src/table_index/unique_hash_index.rs +++ b/crates/table/src/table_index/unique_hash_index.rs @@ -1,6 +1,7 @@ use super::unique_btree_index::UniquePointIter; use super::{Index, KeySize}; use crate::{indexes::RowPointer, table_index::key_size::KeyBytesStorage}; +use core::borrow::Borrow; use core::hash::Hash; use spacetimedb_data_structures::map::hash_map::Entry; use spacetimedb_sats::memory_usage::MemoryUsage; @@ -46,7 +47,7 @@ impl Index for UniqueHashIndex { fn insert(&mut self, key: Self::Key, ptr: RowPointer) -> Result<(), RowPointer> { match self.map.entry(key) { Entry::Vacant(e) => { - self.num_key_bytes.add_to_key_bytes::(e.key()); + self.num_key_bytes.add_to_key_bytes(e.key()); e.insert(ptr); Ok(()) } @@ -54,12 +55,8 @@ impl Index for UniqueHashIndex { } } - fn delete(&mut self, key: &Self::Key, _: RowPointer) -> bool { - let ret = self.map.remove(key).is_some(); - if ret { - self.num_key_bytes.sub_from_key_bytes::(key); - } - ret + fn delete(&mut self, key: &Self::Key, ptr: RowPointer) -> bool { + self.delete(key, ptr) } fn clear(&mut self) { @@ -92,6 +89,31 @@ impl Index for UniqueHashIndex { Self: 'a; fn seek_point(&self, point: &Self::Key) -> Self::PointIter<'_> { + self.seek_point(point) + } +} + +impl UniqueHashIndex { + /// See [`Index::delete`]. + /// This version has relaxed bounds. + pub fn delete(&mut self, key: &Q, _: RowPointer) -> bool + where + Q: ?Sized + KeySize + Hash + Eq, + ::Key: Borrow, + { + let ret = self.map.remove(key).is_some(); + if ret { + self.num_key_bytes.sub_from_key_bytes(key); + } + ret + } + + /// See [`Index::seek_point`]. + /// This version has relaxed bounds. + pub fn seek_point(&self, point: &Q) -> ::PointIter<'_> + where + ::Key: Borrow, + { UniquePointIter::new(self.map.get(point).copied()) } } From 7e69947232752a142d373839bb259cccb281e292 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 17 Feb 2026 10:03:41 +0000 Subject: [PATCH 5/8] indexes: push AlgebraicValue use up to higher layers --- .../locking_tx_datastore/committed_state.rs | 66 +++----------- .../src/locking_tx_datastore/mut_tx.rs | 88 ++++++++++++------- .../src/locking_tx_datastore/state_view.rs | 14 +++ .../datastore/src/locking_tx_datastore/tx.rs | 2 +- .../src/locking_tx_datastore/tx_state.rs | 45 ++-------- crates/table/benches/page_manager.rs | 11 ++- crates/table/src/table.rs | 23 ++++- crates/table/src/table_index/mod.rs | 42 +++++---- 8 files changed, 137 insertions(+), 154 deletions(-) diff --git a/crates/datastore/src/locking_tx_datastore/committed_state.rs b/crates/datastore/src/locking_tx_datastore/committed_state.rs index 6da3c2c9ca8..41bd06a70fc 100644 --- a/crates/datastore/src/locking_tx_datastore/committed_state.rs +++ b/crates/datastore/src/locking_tx_datastore/committed_state.rs @@ -12,7 +12,7 @@ use crate::{ execution_context::ExecutionContext, locking_tx_datastore::{ mut_tx::ViewReadSets, - state_view::{iter_st_column_for_table, ApplyFilter, EqOnColumn, RangeOnColumn, ScanOrIndex}, + state_view::{iter_st_column_for_table, ScanOrIndex}, IterByColRangeTx, }, system_tables::{ @@ -50,8 +50,7 @@ use spacetimedb_table::{ blob_store::{BlobStore, HashMapBlobStore}, indexes::{RowPointer, SquashedOffset}, page_pool::PagePool, - table::{IndexScanPointIter, IndexScanRangeIter, InsertError, RowRef, Table, TableAndIndex, TableScanIter}, - table_index::IndexSeekRangeResult, + table::{InsertError, RowRef, Table, TableAndIndex, TableScanIter}, }; use std::collections::BTreeMap; use std::sync::Arc; @@ -219,12 +218,12 @@ impl StateView for CommittedState { cols: ColList, range: R, ) -> Result> { - match self.index_seek_range(table_id, &cols, &range) { + let iter = self + .get_index_by_cols(table_id, &cols) + .map(|i| i.seek_range_via_algebraic_value(&range)); + match iter { Some(Ok(iter)) => Ok(ScanOrIndex::Index(iter)), - None | Some(Err(_)) => Ok(ScanOrIndex::Scan(ApplyFilter::new( - RangeOnColumn { cols, range }, - self.iter(table_id)?, - ))), + None | Some(Err(_)) => Ok(ScanOrIndex::scan_range(cols, range, self.iter(table_id)?)), } } @@ -235,12 +234,12 @@ impl StateView for CommittedState { val: &'r AlgebraicValue, ) -> Result> { let cols = cols.into(); - match self.index_seek_point(table_id, &cols, val) { + let iter = self + .get_index_by_cols(table_id, &cols) + .map(|i| i.seek_point_via_algebraic_value(val)); + match iter { Some(iter) => Ok(ScanOrIndex::Index(iter)), - None => Ok(ScanOrIndex::Scan(ApplyFilter::new( - EqOnColumn { cols, val }, - self.iter(table_id)?, - ))), + None => Ok(ScanOrIndex::scan_eq(cols, val, self.iter(table_id)?)), } } @@ -949,48 +948,11 @@ impl CommittedState { Some(self.get_table(table_id)?.scan_rows(&self.blob_store)) } - /// When there's an index on `cols`, - /// returns an iterator over the [TableIndex] that yields all the [`RowRef`]s - /// that match the specified `range` in the indexed column. - /// - /// Matching is defined by `Ord for AlgebraicValue`. - /// - /// For a unique index this will always yield at most one `RowRef` - /// when `range` is a point. - /// When there is no index this returns `None`. - pub(super) fn index_seek_range<'a>( - &'a self, - table_id: TableId, - cols: &ColList, - range: &impl RangeBounds, - ) -> Option>> { - self.tables - .get(&table_id)? - .get_index_by_cols_with_table(&self.blob_store, cols) - .map(|i| i.seek_range(range)) - } - - /// When there's an index on `cols`, - /// returns an iterator over the [TableIndex] that yields all the [`RowRef`]s - /// that equal `value` in the indexed column. - /// - /// Matching is defined by `Eq for AlgebraicValue`. - /// - /// For a unique index this will always yield at most one `RowRef`. - /// When there is no index this returns `None`. - pub(super) fn index_seek_point<'a>( - &'a self, - table_id: TableId, - cols: &ColList, - value: &AlgebraicValue, - ) -> Option> { + /// Returns an index for `table_id` on `cols`, if any. + pub(super) fn get_index_by_cols(&self, table_id: TableId, cols: &ColList) -> Option> { self.tables .get(&table_id)? .get_index_by_cols_with_table(&self.blob_store, cols) - .map(|i| { - let key = i.index().key_from_algebraic_value(value); - i.seek_point(&key) - }) } /// Returns the table associated with the given `index_id`, if any. diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index 2eeea517f73..f50fdca8a84 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -10,7 +10,6 @@ use super::{ }; use crate::{ error::ViewError, - locking_tx_datastore::state_view::EqOnColumn, system_tables::{ system_tables, ConnectionIdViaU128, IdentityViaU256, StConnectionCredentialsFields, StConnectionCredentialsRow, StViewColumnFields, StViewFields, StViewParamFields, StViewParamRow, StViewSubFields, StViewSubRow, @@ -30,7 +29,7 @@ use crate::{ use crate::{execution_context::ExecutionContext, system_tables::StViewColumnRow}; use crate::{execution_context::Workload, system_tables::StViewRow}; use crate::{ - locking_tx_datastore::state_view::{ApplyFilter, RangeOnColumn, ScanOrIndex}, + locking_tx_datastore::state_view::ScanOrIndex, traits::{InsertFlags, RowTypeForTable, TxData, UpdateFlags}, }; use core::ops::RangeBounds; @@ -448,7 +447,7 @@ impl Datastore for MutTxId { .get_table_and_index(index_id) .ok_or_else(|| IndexError::NotFound(index_id))?; - self.index_scan_range_inner(table_id, tx_index, commit_index, range) + Self::index_scan_range_via_algebraic_value(&self.tx_state, table_id, tx_index, commit_index, range) .map_err(|IndexCannotSeekRange| IndexError::IndexCannotSeekRange(index_id).into()) } @@ -464,7 +463,13 @@ impl Datastore for MutTxId { .ok_or_else(|| IndexError::NotFound(index_id))?; let point = commit_index.index().key_from_algebraic_value(point); - Ok(self.index_scan_point_inner(table_id, tx_index, commit_index, &point)) + Ok(Self::index_scan_point_inner( + &self.tx_state, + table_id, + tx_index, + commit_index, + &point, + )) } } @@ -1337,13 +1342,13 @@ impl MutTxId { let point = commit_index.index().key_from_bsatn(point).map_err(IndexError::Decode)?; // Get index seek iterators for the tx and committed state. - let iter = self.index_scan_point_inner(table_id, tx_index, commit_index, &point); + let iter = Self::index_scan_point_inner(&self.tx_state, table_id, tx_index, commit_index, &point); Ok((table_id, point, iter)) } /// See [`MutTxId::index_scan_point`]. fn index_scan_point_inner<'a>( - &'a self, + tx_state: &'a TxState, table_id: TableId, tx_index: Option>, commit_index: TableAndIndex<'a>, @@ -1354,7 +1359,7 @@ impl MutTxId { let commit_iter = commit_index.seek_point(point); // Combine it all. - let dt = self.tx_state.get_delete_table(table_id); + let dt = tx_state.get_delete_table(table_id); ScanMutTx::combine(dt, tx_iter, commit_iter) } @@ -1381,6 +1386,7 @@ impl MutTxId { let (table_id, commit_index, tx_index) = self .get_table_and_index(index_id) .ok_or_else(|| IndexError::NotFound(index_id))?; + // Extract the index type. let index_ty = &commit_index.index().key_type; @@ -1388,9 +1394,9 @@ impl MutTxId { let bounds = Self::range_scan_decode_bounds(index_ty, prefix, prefix_elems, rstart, rend).map_err(IndexError::Decode)?; - let iter = self - .index_scan_range_inner(table_id, tx_index, commit_index, &bounds) - .map_err(|IndexCannotSeekRange| IndexError::IndexCannotSeekRange(index_id))?; + let iter = + Self::index_scan_range_via_algebraic_value(&self.tx_state, table_id, tx_index, commit_index, &bounds) + .map_err(|IndexCannotSeekRange| IndexError::IndexCannotSeekRange(index_id))?; let (lower, upper) = bounds; Ok((table_id, lower, upper, iter)) @@ -1398,12 +1404,28 @@ impl MutTxId { /// See [`MutTxId::index_scan_range`]. #[inline(always)] - fn index_scan_range_inner<'a>( - &'a self, + fn index_scan_range_via_algebraic_value<'a>( + tx_state: &'a TxState, table_id: TableId, tx_index: Option>, commit_index: TableAndIndex<'a>, bounds: &impl RangeBounds, + ) -> IndexSeekRangeResult> { + let index = commit_index.index(); + let start = bounds.start_bound().map(|v| index.key_from_algebraic_value(v)); + let end = bounds.end_bound().map(|v| index.key_from_algebraic_value(v)); + let bounds = &(start, end); + Self::index_scan_range_inner(tx_state, table_id, tx_index, commit_index, bounds) + } + + /// See [`MutTxId::index_scan_range`]. + #[inline(always)] + fn index_scan_range_inner<'a, 'b>( + tx_state: &'a TxState, + table_id: TableId, + tx_index: Option>, + commit_index: TableAndIndex<'a>, + bounds: &impl RangeBounds>, ) -> IndexSeekRangeResult> { // Get an index seek iterator for the tx and committed state. let tx_iter = tx_index.map(|i| i.seek_range(bounds)).transpose(); @@ -1416,7 +1438,7 @@ impl MutTxId { }; // Combine it all. - let dt = self.tx_state.get_delete_table(table_id); + let dt = tx_state.get_delete_table(table_id); Ok(ScanMutTx::combine(dt, tx_iter, commit_iter)) } @@ -3202,23 +3224,23 @@ fn iter_by_col_range<'a, R: RangeBounds>( cols: ColList, range: R, ) -> Result> { - // If there's an index, use that. + // If there's an index that is compatible with a range scan, use that. // It's sufficient to check that the committed state has an index // as index schema changes are applied immediately. - if let Some(Ok(commit_iter)) = committed_state.index_seek_range(table_id, &cols, &range) { - let tx_iter = tx_state - .index_seek_range_by_cols(table_id, &cols, &range) - .map(|r| r.expect("got a commit index so we should have a compatible tx index")); - let delete_table = tx_state.get_delete_table(table_id); - let iter = ScanMutTx::combine(delete_table, tx_iter, commit_iter); - Ok(ScanOrIndex::Index(iter)) - } else { - unindexed_iter_by_col_range_warn(tx_state, committed_state, table_id, &cols); - let iter = iter(tx_state, committed_state, table_id)?; - let filter = RangeOnColumn { cols, range }; - let iter = ApplyFilter::new(filter, iter); - Ok(ScanOrIndex::Scan(iter)) + if let Some(commit_index) = committed_state.get_index_by_cols(table_id, &cols) { + let tx_index = tx_state.get_index_by_cols(table_id, &cols); + if let Ok(iter) = + MutTxId::index_scan_range_via_algebraic_value(tx_state, table_id, tx_index, commit_index, &range) + { + return Ok(ScanOrIndex::Index(iter)); + } } + + // No index found or it wasn't compatible with a range scan, + // so do a full scan and filter. + unindexed_iter_by_col_range_warn(tx_state, committed_state, table_id, &cols); + let iter = iter(tx_state, committed_state, table_id)?; + Ok(ScanOrIndex::scan_range(cols, range, iter)) } #[cfg(not(feature = "unindexed_iter_by_col_range_warn"))] @@ -3251,17 +3273,15 @@ fn iter_by_col_eq<'a, 'r>( // It's sufficient to check that the committed state has an index // as index schema changes are applied immediately. let cols = cols.into(); - if let Some(commit_iter) = committed_state.index_seek_point(table_id, &cols, val) { - let tx_iter = tx_state.index_seek_point_by_cols(table_id, &cols, val); - let delete_table = tx_state.get_delete_table(table_id); - let iter = ScanMutTx::combine(delete_table, tx_iter, commit_iter); + if let Some(commit_index) = committed_state.get_index_by_cols(table_id, &cols) { + let tx_index = tx_state.get_index_by_cols(table_id, &cols); + let key = commit_index.index().key_from_algebraic_value(val); + let iter = MutTxId::index_scan_point_inner(tx_state, table_id, tx_index, commit_index, &key); Ok(ScanOrIndex::Index(iter)) } else { unindexed_iter_by_col_eq_warn(tx_state, committed_state, table_id, &cols); let iter = iter(tx_state, committed_state, table_id)?; - let filter = EqOnColumn { cols, val }; - let iter = ApplyFilter::new(filter, iter); - Ok(ScanOrIndex::Scan(iter)) + Ok(ScanOrIndex::scan_eq(cols, val, iter)) } } diff --git a/crates/datastore/src/locking_tx_datastore/state_view.rs b/crates/datastore/src/locking_tx_datastore/state_view.rs index 92137474423..e5a51efd855 100644 --- a/crates/datastore/src/locking_tx_datastore/state_view.rs +++ b/crates/datastore/src/locking_tx_datastore/state_view.rs @@ -413,6 +413,20 @@ pub enum ScanOrIndex { Index(I), } +impl ScanOrIndex, I>, Idx> { + /// Returns a scan that applies a `RangeOnColumn` filter to `iter`. + pub(super) fn scan_range(cols: ColList, range: R, iter: I) -> Self { + Self::Scan(ApplyFilter::new(RangeOnColumn { cols, range }, iter)) + } +} + +impl<'r, I, Idx> ScanOrIndex, I>, Idx> { + /// Returns a scan that applies a `EqOnColumn` filter to `iter`. + pub(super) fn scan_eq(cols: ColList, val: &'r AlgebraicValue, iter: I) -> Self { + Self::Scan(ApplyFilter::new(EqOnColumn { cols, val }, iter)) + } +} + impl<'a, S, I> Iterator for ScanOrIndex where S: Iterator>, diff --git a/crates/datastore/src/locking_tx_datastore/tx.rs b/crates/datastore/src/locking_tx_datastore/tx.rs index fae4116d07d..4af060ed826 100644 --- a/crates/datastore/src/locking_tx_datastore/tx.rs +++ b/crates/datastore/src/locking_tx_datastore/tx.rs @@ -67,7 +67,7 @@ impl Datastore for TxId { index_id: IndexId, range: &impl RangeBounds, ) -> anyhow::Result> { - self.with_index(table_id, index_id, |i| i.seek_range(range))? + self.with_index(table_id, index_id, |i| i.seek_range_via_algebraic_value(range))? .map_err(|IndexCannotSeekRange| IndexError::IndexCannotSeekRange(index_id).into()) } diff --git a/crates/datastore/src/locking_tx_datastore/tx_state.rs b/crates/datastore/src/locking_tx_datastore/tx_state.rs index bdba9ff1f7f..a1ca8ba3166 100644 --- a/crates/datastore/src/locking_tx_datastore/tx_state.rs +++ b/crates/datastore/src/locking_tx_datastore/tx_state.rs @@ -1,17 +1,16 @@ use super::{delete_table::DeleteTable, sequence::Sequence}; -use core::ops::RangeBounds; use spacetimedb_data_structures::map::IntMap; use spacetimedb_lib::db::auth::StAccess; use spacetimedb_primitives::{ColList, ConstraintId, IndexId, SequenceId, TableId}; -use spacetimedb_sats::{memory_usage::MemoryUsage, AlgebraicValue}; +use spacetimedb_sats::memory_usage::MemoryUsage; use spacetimedb_schema::schema::{ColumnSchema, ConstraintSchema, IndexSchema, SequenceSchema}; use spacetimedb_table::{ blob_store::{BlobStore, HashMapBlobStore}, indexes::{RowPointer, SquashedOffset}, pointer_map::PointerMap, static_assert_size, - table::{IndexScanPointIter, IndexScanRangeIter, RowRef, Table, TableAndIndex}, - table_index::{IndexSeekRangeResult, TableIndex}, + table::{RowRef, Table, TableAndIndex}, + table_index::TableIndex, }; use std::collections::{btree_map, BTreeMap}; use thin_vec::ThinVec; @@ -168,45 +167,11 @@ impl TxState { (ins_count, del_count) } - /// When there's an index on `cols`, - /// returns an iterator over the `TableIndex` that yields all the [`RowRef`]s - /// that match the specified `range` in the indexed column. - /// - /// Matching is defined by `Ord for AlgebraicValue`. - /// - /// For a unique index this will always yield at most one `RowRef` - /// when `range` is a point. - /// When there is no index this returns `None`. - pub(super) fn index_seek_range_by_cols<'a>( - &'a self, - table_id: TableId, - cols: &ColList, - range: &impl RangeBounds, - ) -> Option>> { - self.insert_tables - .get(&table_id)? - .get_index_by_cols_with_table(&self.blob_store, cols) - .map(|i| i.seek_range(range)) - } - - /// When there's an index on `cols`, - /// returns an iterator over the `TableIndex` that yields all the [`RowRef`]s - /// that match the specified `range` in the indexed column. - /// - /// Matching is defined by `Eq for AlgebraicValue`. - /// - /// For a unique index this will always yield at most one `RowRef`. - /// When there is no index this returns `None`. - pub(super) fn index_seek_point_by_cols<'a>( - &'a self, - table_id: TableId, - cols: &ColList, - point: &AlgebraicValue, - ) -> Option> { + /// Returns an index for `table_id` on `cols`, if any. + pub(super) fn get_index_by_cols(&self, table_id: TableId, cols: &ColList) -> Option> { self.insert_tables .get(&table_id)? .get_index_by_cols_with_table(&self.blob_store, cols) - .map(|i| i.seek_point_via_algebraic_value(point)) } /// Returns the table for `table_id` combined with the index for `index_id`, if both exist. diff --git a/crates/table/benches/page_manager.rs b/crates/table/benches/page_manager.rs index c1bbb41710f..70f5f5ddb7e 100644 --- a/crates/table/benches/page_manager.rs +++ b/crates/table/benches/page_manager.rs @@ -798,11 +798,10 @@ fn insert_num_same( } fn clear_all_same(tbl: &mut Table, index_id: IndexId, val_same: u64) { - let ptrs = tbl - .get_index_by_id(index_id) - .unwrap() - .seek_point_via_algebraic_value(&R::column_value_from_u64(val_same)) - .collect::>(); + let index = tbl.get_index_by_id(index_id).unwrap(); + let key = R::column_value_from_u64(val_same); + let key = index.key_from_algebraic_value(&key); + let ptrs = index.seek_point(&key).collect::>(); for ptr in ptrs { tbl.delete(&mut NullBlobStore, ptr, |_| ()).unwrap(); } @@ -919,7 +918,7 @@ fn index_seek(c: &mut Criterion) { let mut elapsed = WallTime.zero(); for _ in 0..num_iters { let (row, none) = time(&mut elapsed, || { - let mut iter = index.seek_range(&col_to_seek).unwrap(); + let mut iter = index.seek_point_via_algebraic_value(&col_to_seek); (iter.next(), iter.next()) }); assert!( diff --git a/crates/table/src/table.rs b/crates/table/src/table.rs index 351482b64b4..b7ab3338afe 100644 --- a/crates/table/src/table.rs +++ b/crates/table/src/table.rs @@ -2111,6 +2111,21 @@ impl<'a> TableAndIndex<'a> { } } + /// Returns an iterator yielding all rows in this index that fall within `range`, + /// if the index is compatible with range seeks. + /// + /// Matching is defined by `Ord for AlgebraicValue`. + pub fn seek_range<'b>( + &self, + range: &impl RangeBounds>, + ) -> Result, IndexCannotSeekRange> { + Ok(IndexScanRangeIter { + table: self.table, + blob_store: self.blob_store, + btree_index_iter: self.index.seek_range(range)?, + }) + } + /// Returns an iterator yielding all rows in this index for `key`. /// /// Matching is defined by `Eq for AlgebraicValue`. @@ -2123,14 +2138,18 @@ impl<'a> TableAndIndex<'a> { /// if the index is compatible with range seeks. /// /// Matching is defined by `Ord for AlgebraicValue`. - pub fn seek_range( + pub fn seek_range_via_algebraic_value( &self, range: &impl RangeBounds, ) -> Result, IndexCannotSeekRange> { + let start = range.start_bound().map(|v| self.index.key_from_algebraic_value(v)); + let end = range.end_bound().map(|v| self.index.key_from_algebraic_value(v)); + let btree_index_iter = self.index.seek_range(&(start, end))?; + Ok(IndexScanRangeIter { table: self.table, blob_store: self.blob_store, - btree_index_iter: self.index.seek_range(range)?, + btree_index_iter, }) } } diff --git a/crates/table/src/table_index/mod.rs b/crates/table/src/table_index/mod.rs index 7533012e337..aa54932b6dd 100644 --- a/crates/table/src/table_index/mod.rs +++ b/crates/table/src/table_index/mod.rs @@ -269,7 +269,7 @@ impl<'a> CowAV<'a> { } } -/// A key into a [`TypedIndex`], the borrowed version. +/// A key into a [`TypedIndex`]. #[derive(enum_as_inner::EnumAsInner, PartialEq, Eq, PartialOrd, Ord, Debug)] enum TypedIndexKey<'a> { Bool(bool), @@ -1412,6 +1412,7 @@ impl TypedIndex { } } +/// A key into a [`TableIndex`]. pub struct IndexKey<'a> { key: TypedIndexKey<'a>, } @@ -1572,12 +1573,6 @@ impl TableIndex { } } - /// Returns an iterator that yields all the `RowPointer`s for the given `key`. - pub fn seek_point_via_algebraic_value(&self, value: &AlgebraicValue) -> TableIndexPointIter<'_> { - let key = self.key_from_algebraic_value(value); - self.seek_point(&key) - } - /// Returns an iterator that yields all the `RowPointer`s for the given `key`. #[inline] pub fn seek_point(&self, key: &IndexKey<'_>) -> TableIndexPointIter<'_> { @@ -1589,12 +1584,12 @@ impl TableIndex { /// that yields all the `RowPointer`s, /// that fall within the specified `range`, /// if the index is [`RangedIndex`]. - pub fn seek_range( + pub fn seek_range<'a>( &self, - range: &impl RangeBounds, + range: &impl RangeBounds>, ) -> IndexSeekRangeResult> { - let start = range.start_bound().map(|v| self.key_from_algebraic_value(v).key); - let end = range.end_bound().map(|v| self.key_from_algebraic_value(v).key); + let start = range.start_bound().map(|v| &v.key); + let end = range.end_bound().map(|v| &v.key); let range = (start, end); let iter = self.idx.seek_range(&range)?; Ok(TableIndexRangeIter { iter }) @@ -1846,6 +1841,15 @@ mod test { generate_primitive_algebraic_type().prop_flat_map(|ty| (Just(ty.clone()), generate_algebraic_value(ty))) } + fn seek_range<'a>( + index: &'a TableIndex, + range: &impl RangeBounds, + ) -> IndexSeekRangeResult> { + let start = range.start_bound().map(|v| index.key_from_algebraic_value(v)); + let end = range.end_bound().map(|v| index.key_from_algebraic_value(v)); + index.seek_range(&(start, end)) + } + proptest! { #![proptest_config(ProptestConfig { max_shrink_iters: 0x10000000, ..Default::default() })] @@ -1854,7 +1858,7 @@ mod test { let index = TableIndex::new(&ty, cols.clone(), IndexKind::Hash, is_unique).unwrap(); let key = pv.project(&cols).unwrap(); - assert_eq!(index.seek_range(&(key.clone()..=key)).unwrap_err(), IndexCannotSeekRange); + assert_eq!(seek_range(&index, &(key.clone()..=key)).unwrap_err(), IndexCannotSeekRange); } #[test] @@ -1993,7 +1997,7 @@ mod test { assert_eq!(index.num_key_bytes() as usize, 3 * size_of::()); fn test_seek(index: &TableIndex, val_to_ptr: &HashMap, range: impl RangeBounds, expect: impl IntoIterator) -> TestCaseResult { - check_seek(index.seek_range(&range).unwrap().collect(), val_to_ptr, expect) + check_seek(seek_range(index, &range).unwrap().collect(), val_to_ptr, expect) } fn check_seek(mut ptrs_in_index: Vec, val_to_ptr: &HashMap, expect: impl IntoIterator) -> TestCaseResult { @@ -2010,7 +2014,7 @@ mod test { // Test point ranges. for x in range.clone() { test_seek(&index, &val_to_ptr, V(x), [x])?; - check_seek(index.seek_point_via_algebraic_value(&V(x)).collect(), &val_to_ptr, [x])?; + check_seek(index.seek_point(&index.key_from_algebraic_value(&V(x))).collect(), &val_to_ptr, [x])?; } // Test `..` (`RangeFull`). @@ -2085,15 +2089,15 @@ mod test { assert_eq!(index.num_rows(), 1); // Seek the empty ranges. - let rows = index.seek_range(&(&succ..&val)).unwrap().collect::>(); + let rows = seek_range(&index, &(&succ..&val)).unwrap().collect::>(); assert_eq!(rows, []); - let rows = index.seek_range(&(&succ..=&val)).unwrap().collect::>(); + let rows = seek_range(&index, &(&succ..=&val)).unwrap().collect::>(); assert_eq!(rows, []); - let rows = index.seek_range(&(Excluded(&succ), Included(&val))).unwrap().collect::>(); + let rows = seek_range(&index, &(Excluded(&succ), Included(&val))).unwrap().collect::>(); assert_eq!(rows, []); - let rows = index.seek_range(&(Excluded(&succ), Excluded(&val))).unwrap().collect::>(); + let rows = seek_range(&index, &(Excluded(&succ), Excluded(&val))).unwrap().collect::>(); assert_eq!(rows, []); - let rows = index.seek_range(&(Excluded(&val), Excluded(&val))).unwrap().collect::>(); + let rows = seek_range(&index, &(Excluded(&val), Excluded(&val))).unwrap().collect::>(); assert_eq!(rows, []); } } From 167dfd8ded2d265b9e9cc4385938e23b65b4164c Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 24 Feb 2026 07:26:24 +0000 Subject: [PATCH 6/8] wip --- crates/core/src/db/relational_db.rs | 22 +- crates/core/src/host/instance_env.rs | 18 +- .../datastore/src/locking_tx_datastore/mod.rs | 2 +- .../src/locking_tx_datastore/mut_tx.rs | 204 +++--------- crates/sats/src/de.rs | 18 ++ crates/sats/src/de/impls.rs | 4 +- crates/table/src/bflatn_from.rs | 96 +++++- crates/table/src/table.rs | 31 +- crates/table/src/table_index/bytes_key.rs | 97 ++++++ crates/table/src/table_index/key_size.rs | 6 + crates/table/src/table_index/mod.rs | 303 +++++++++++++++--- 11 files changed, 558 insertions(+), 243 deletions(-) create mode 100644 crates/table/src/table_index/bytes_key.rs diff --git a/crates/core/src/db/relational_db.rs b/crates/core/src/db/relational_db.rs index 70a02abc2e3..0490a6f6c31 100644 --- a/crates/core/src/db/relational_db.rs +++ b/crates/core/src/db/relational_db.rs @@ -18,7 +18,7 @@ use spacetimedb_datastore::locking_tx_datastore::datastore::TxMetrics; use spacetimedb_datastore::locking_tx_datastore::state_view::{ IterByColEqMutTx, IterByColRangeMutTx, IterMutTx, StateView, }; -use spacetimedb_datastore::locking_tx_datastore::{MutTxId, TxId}; +use spacetimedb_datastore::locking_tx_datastore::{IndexScanPointOrRange, MutTxId, TxId}; use spacetimedb_datastore::system_tables::{ system_tables, StModuleRow, ST_CLIENT_ID, ST_CONNECTION_CREDENTIALS_ID, ST_VIEW_SUB_ID, }; @@ -62,7 +62,7 @@ use spacetimedb_vm::errors::{ErrorType, ErrorVm}; use spacetimedb_vm::ops::parse; use std::borrow::Cow; use std::io; -use std::ops::{Bound, RangeBounds}; +use std::ops::RangeBounds; use std::sync::Arc; use tokio::sync::watch; @@ -1392,23 +1392,15 @@ impl RelationalDB { Ok(self.inner.iter_by_col_range_tx(tx, table_id.into(), cols, range)?) } - pub fn index_scan_range<'a>( + pub fn index_scan_range<'de, 'a>( &'a self, tx: &'a MutTx, index_id: IndexId, - prefix: &[u8], + prefix: &'de [u8], prefix_elems: ColId, - rstart: &[u8], - rend: &[u8], - ) -> Result< - ( - TableId, - Bound, - Bound, - impl Iterator>, - ), - DBError, - > { + rstart: &'de [u8], + rend: &'de [u8], + ) -> Result<(TableId, IndexScanPointOrRange<'de, 'a>), DBError> { Ok(tx.index_scan_range(index_id, prefix, prefix_elems, rstart, rend)?) } diff --git a/crates/core/src/host/instance_env.rs b/crates/core/src/host/instance_env.rs index a3f5bb9299c..05d6130419c 100644 --- a/crates/core/src/host/instance_env.rs +++ b/crates/core/src/host/instance_env.rs @@ -17,7 +17,7 @@ use spacetimedb_client_api_messages::energy::EnergyQuanta; use spacetimedb_datastore::db_metrics::DB_METRICS; use spacetimedb_datastore::execution_context::Workload; use spacetimedb_datastore::locking_tx_datastore::state_view::StateView; -use spacetimedb_datastore::locking_tx_datastore::{FuncCallType, MutTxId}; +use spacetimedb_datastore::locking_tx_datastore::{FuncCallType, IndexScanPointOrRange, MutTxId}; use spacetimedb_datastore::traits::IsolationLevel; use spacetimedb_lib::{http as st_http, ConnectionId, Identity, Timestamp}; use spacetimedb_primitives::{ColId, ColList, IndexId, TableId}; @@ -484,9 +484,12 @@ impl InstanceEnv { let tx = &mut *self.get_tx()?; // Find all rows in the table to delete. - let (table_id, _, _, iter) = stdb.index_scan_range(tx, index_id, prefix, prefix_elems, rstart, rend)?; + let (table_id, iter) = stdb.index_scan_range(tx, index_id, prefix, prefix_elems, rstart, rend)?; // Re. `SmallVec`, `delete_by_field` only cares about 1 element, so optimize for that. - let rows_to_delete = iter.map(|row_ref| row_ref.pointer()).collect::>(); + let rows_to_delete = match iter { + IndexScanPointOrRange::Point(_, iter) => iter.map(|row_ref| row_ref.pointer()).collect(), + IndexScanPointOrRange::Range(iter) => iter.map(|row_ref| row_ref.pointer()).collect(), + }; Ok(Self::datastore_delete_by_index_scan(stdb, tx, table_id, rows_to_delete)) } @@ -648,19 +651,22 @@ impl InstanceEnv { let tx = &mut *self.get_tx()?; // Open index iterator - let (table_id, lower, upper, iter) = + let (table_id, iter) = self.relational_db() .index_scan_range(tx, index_id, prefix, prefix_elems, rstart, rend)?; // Scan the index and serialize rows to BSATN. - let (chunks, rows_scanned, bytes_scanned) = ChunkedWriter::collect_iter(pool, iter); + let (point, (chunks, rows_scanned, bytes_scanned)) = match iter { + IndexScanPointOrRange::Point(point, iter) => (Some(point), ChunkedWriter::collect_iter(pool, iter)), + IndexScanPointOrRange::Range(iter) => (None, ChunkedWriter::collect_iter(pool, iter)), + }; // Record the number of rows and the number of bytes scanned by the iterator. tx.metrics.index_seeks += 1; tx.metrics.bytes_scanned += bytes_scanned; tx.metrics.rows_scanned += rows_scanned; - tx.record_index_scan_range(&self.func_type, table_id, index_id, lower, upper); + tx.record_index_scan_range(&self.func_type, table_id, index_id, point); Ok(chunks) } diff --git a/crates/datastore/src/locking_tx_datastore/mod.rs b/crates/datastore/src/locking_tx_datastore/mod.rs index ea6a033ebec..8eb2ea93bc1 100644 --- a/crates/datastore/src/locking_tx_datastore/mod.rs +++ b/crates/datastore/src/locking_tx_datastore/mod.rs @@ -3,7 +3,7 @@ pub mod committed_state; pub mod datastore; mod mut_tx; -pub use mut_tx::{FuncCallType, MutTxId, ViewCallInfo}; +pub use mut_tx::{FuncCallType, IndexScanPointOrRange, MutTxId, ViewCallInfo}; mod sequence; pub mod state_view; pub use state_view::{IterByColEqTx, IterByColRangeTx}; diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index f50fdca8a84..a47819f5b0b 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -32,9 +32,7 @@ use crate::{ locking_tx_datastore::state_view::ScanOrIndex, traits::{InsertFlags, RowTypeForTable, TxData, UpdateFlags}, }; -use core::ops::RangeBounds; -use core::{cell::RefCell, mem}; -use core::{iter, ops::Bound}; +use core::{cell::RefCell, iter, mem, ops::RangeBounds}; use itertools::Either; use smallvec::SmallVec; use spacetimedb_data_structures::map::{HashMap, HashSet, IntMap}; @@ -49,12 +47,8 @@ use spacetimedb_primitives::{ col_list, ArgId, ColId, ColList, ColSet, ConstraintId, IndexId, ScheduleId, SequenceId, TableId, ViewFnPtr, ViewId, }; use spacetimedb_sats::{ - bsatn::{self, to_writer, DecodeError, Deserializer}, - de::{DeserializeSeed, WithBound}, - memory_usage::MemoryUsage, - raw_identifier::RawIdentifier, - ser::Serialize, - AlgebraicType, AlgebraicValue, ProductType, ProductValue, WithTypespace, + bsatn::to_writer, memory_usage::MemoryUsage, raw_identifier::RawIdentifier, ser::Serialize, AlgebraicValue, + ProductType, ProductValue, }; use spacetimedb_schema::{ def::{ModuleDef, ViewColumnDef, ViewDef, ViewParamDef}, @@ -70,7 +64,7 @@ use spacetimedb_table::{ BlobNumBytes, DuplicateError, IndexScanPointIter, IndexScanRangeIter, InsertError, RowRef, Table, TableAndIndex, UniqueConstraintViolation, }, - table_index::{IndexCannotSeekRange, IndexKey, IndexSeekRangeResult, TableIndex}, + table_index::{IndexCannotSeekRange, IndexKey, IndexSeekRangeResult, PointOrRange, TableIndex}, }; use std::{ marker::PhantomData, @@ -78,8 +72,6 @@ use std::{ time::{Duration, Instant}, }; -type DecodeResult = core::result::Result; - #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ViewCallInfo { pub view_id: ViewId, @@ -276,11 +268,10 @@ impl MutTxId { op: &FuncCallType, table_id: TableId, index_id: IndexId, - lower: Bound, - upper: Bound, + point: Option>, ) { if let FuncCallType::View(view) = op { - self.record_index_scan_range_inner(view, table_id, index_id, lower, upper); + self.record_index_scan_range_inner(view, table_id, index_id, point); }; } @@ -293,19 +284,15 @@ impl MutTxId { view: &ViewCallInfo, table_id: TableId, index_id: IndexId, - lower: Bound, - upper: Bound, + point: Option>, ) { - // Check for precise index seek. - if let (Bound::Included(low_val), Bound::Included(up_val)) = (&lower, &upper) { - if low_val == up_val { - self.record_index_scan_point_inner(view, table_id, index_id, low_val.clone()); - return; - } + if let Some(point) = point { + // We got a precise index seek. + self.record_index_scan_point_inner(view, table_id, index_id, point); + } else { + // Everything else is treated as a table scan. + self.read_sets.insert_full_table_scan(table_id, view.clone()); } - - // Everything else is treated as a table scan. - self.read_sets.insert_full_table_scan(table_id, view.clone()); } /// Record that a view performs a point index scan in this transaction's read set. @@ -315,11 +302,10 @@ impl MutTxId { op: &FuncCallType, table_id: TableId, index_id: IndexId, - val: IndexKey<'_>, + point: IndexKey<'_>, ) { if let FuncCallType::View(view) = op { - let val = val.into_algebraic_value(); - self.record_index_scan_point_inner(view, table_id, index_id, val); + self.record_index_scan_point_inner(view, table_id, index_id, point); }; } @@ -331,7 +317,7 @@ impl MutTxId { view: &ViewCallInfo, table_id: TableId, index_id: IndexId, - val: AlgebraicValue, + point: IndexKey<'_>, ) { // Fetch index metadata let Some((_, idx, _)) = self.get_table_and_index(index_id) else { @@ -339,6 +325,7 @@ impl MutTxId { }; let cols = idx.index().indexed_columns.clone(); + let val = point.into_algebraic_value(); self.read_sets.insert_index_scan(table_id, cols, val, view.clone()); } @@ -1369,37 +1356,40 @@ impl MutTxId { /// The `prefix` is equated to the first `prefix_elems` values of the index key /// and then `prefix_elem`th value is bounded to the left bys `rstart` /// and to the right by `rend`. - pub fn index_scan_range<'a>( + pub fn index_scan_range<'de, 'a>( &'a self, index_id: IndexId, - prefix: &[u8], + prefix: &'de [u8], prefix_elems: ColId, - rstart: &[u8], - rend: &[u8], - ) -> Result<( - TableId, - Bound, - Bound, - IndexScanRanged<'a>, - )> { + rstart: &'de [u8], + rend: &'de [u8], + ) -> Result<(TableId, IndexScanPointOrRange<'de, 'a>)> { // Extract the table id, and commit/tx indices. let (table_id, commit_index, tx_index) = self .get_table_and_index(index_id) .ok_or_else(|| IndexError::NotFound(index_id))?; - // Extract the index type. - let index_ty = &commit_index.index().key_type; - - // We have the index key type, so we can decode everything. - let bounds = - Self::range_scan_decode_bounds(index_ty, prefix, prefix_elems, rstart, rend).map_err(IndexError::Decode)?; - - let iter = - Self::index_scan_range_via_algebraic_value(&self.tx_state, table_id, tx_index, commit_index, &bounds) - .map_err(|IndexCannotSeekRange| IndexError::IndexCannotSeekRange(index_id))?; - - let (lower, upper) = bounds; - Ok((table_id, lower, upper, iter)) + // Decode the bounds. + let bounds = commit_index + .index() + .bounds_from_bsatn(prefix, prefix_elems, rstart, rend) + .map_err(IndexError::Decode)?; + + // Depending on whether this is a point or range bound, + // we'll either do an index point or range scan. + let iter = match bounds { + PointOrRange::Point(point) => { + let iter = Self::index_scan_point_inner(&self.tx_state, table_id, tx_index, commit_index, &point); + IndexScanPointOrRange::Point(point, iter) + } + PointOrRange::Range(start, end) => { + let bounds = (start.as_ref(), end.as_ref()); + let iter = Self::index_scan_range_inner(&self.tx_state, table_id, tx_index, commit_index, &bounds) + .map_err(|IndexCannotSeekRange| IndexError::IndexCannotSeekRange(index_id))?; + IndexScanPointOrRange::Range(iter) + } + }; + Ok((table_id, iter)) } /// See [`MutTxId::index_scan_range`]. @@ -1462,104 +1452,6 @@ impl MutTxId { Some((table_id, commit_index, tx_index)) } - /// Decode the bounds for a ranged index scan for an index typed at `key_type`. - fn range_scan_decode_bounds( - key_type: &AlgebraicType, - mut prefix: &[u8], - prefix_elems: ColId, - rstart: &[u8], - rend: &[u8], - ) -> DecodeResult<(Bound, Bound)> { - match key_type { - // Multi-column index case. - AlgebraicType::Product(key_types) => { - let key_types = &key_types.elements; - // Split into types for the prefix and for the rest. - let (prefix_types, rest_types) = key_types - .split_at_checked(prefix_elems.idx()) - .ok_or_else(|| DecodeError::Other("index key type has too few fields compared to prefix".into()))?; - - // The `rstart` and `rend`s must be typed at `Bound`. - // Extract that type and determine the length of the suffix. - let Some((range_type, suffix_types)) = rest_types.split_first() else { - return Err(DecodeError::Other( - "prefix length leaves no room for a range in ranged index scan".into(), - )); - }; - let suffix_len = suffix_types.len(); - - // We now have the types, - // so proceed to decoding the prefix, and the start/end bounds. - // Finally combine all of these to a single bound pair. - let prefix = bsatn::decode(prefix_types, &mut prefix)?; - let (start, end) = Self::range_scan_decode_start_end(&range_type.algebraic_type, rstart, rend)?; - Ok(Self::range_scan_combine_prefix_and_bounds( - prefix, start, end, suffix_len, - )) - } - // Single-column index case. We implicitly have a PT of len 1. - _ if !prefix.is_empty() && prefix_elems.idx() != 0 => Err(DecodeError::Other( - "a single-column index cannot be prefix scanned".into(), - )), - ty => Self::range_scan_decode_start_end(ty, rstart, rend), - } - } - - /// Decode `rstart` and `rend` as `Bound`. - fn range_scan_decode_start_end( - ty: &AlgebraicType, - mut rstart: &[u8], - mut rend: &[u8], - ) -> DecodeResult<(Bound, Bound)> { - let range_type = WithBound(WithTypespace::empty(ty)); - let range_start = range_type.deserialize(Deserializer::new(&mut rstart))?; - let range_end = range_type.deserialize(Deserializer::new(&mut rend))?; - Ok((range_start, range_end)) - } - - /// Combines `prefix` equality constraints with `start` and `end` bounds - /// filling with `suffix_len` to ensure that the number of fields matches - /// that of the index type. - fn range_scan_combine_prefix_and_bounds( - prefix: ProductValue, - start: Bound, - end: Bound, - suffix_len: usize, - ) -> (Bound, Bound) { - let prefix_is_empty = prefix.elements.is_empty(); - // Concatenate prefix, value, and the most permissive value for the suffix. - let concat = |prefix: ProductValue, val, fill| { - let mut vals: Vec<_> = prefix.elements.into(); - vals.reserve(1 + suffix_len); - vals.push(val); - vals.extend(iter::repeat_n(fill, suffix_len)); - AlgebraicValue::product(vals) - }; - // The start endpoint needs `Min` as the suffix-filling element, - // as it imposes the least and acts like `Unbounded`. - let concat_start = |val| concat(prefix.clone(), val, AlgebraicValue::Min); - let range_start = match start { - Bound::Included(r) => Bound::Included(concat_start(r)), - Bound::Excluded(r) => Bound::Excluded(concat_start(r)), - // Prefix is empty, and suffix will be `Min`, - // so simplify `(Min, Min, ...)` to `Unbounded`. - Bound::Unbounded if prefix_is_empty => Bound::Unbounded, - Bound::Unbounded => Bound::Included(concat_start(AlgebraicValue::Min)), - }; - // The end endpoint needs `Max` as the suffix-filling element, - // as it imposes the least and acts like `Unbounded`. - let concat_end = |val| concat(prefix, val, AlgebraicValue::Max); - let range_end = match end { - Bound::Included(r) => Bound::Included(concat_end(r)), - Bound::Excluded(r) => Bound::Excluded(concat_end(r)), - // Prefix is empty, and suffix will be `Max`, - // so simplify `(Max, Max, ...)` to `Unbounded`. - Bound::Unbounded if prefix_is_empty => Bound::Unbounded, - Bound::Unbounded => Bound::Included(concat_end(AlgebraicValue::Max)), - }; - (range_start, range_end) - } - pub fn get_next_sequence_value(&mut self, seq_id: SequenceId) -> Result { get_next_sequence_value( &mut self.tx_state, @@ -1570,6 +1462,16 @@ impl MutTxId { } } +/// Either a point or range index scan iterator. +/// Produced by [`MutTxId::index_scan_range`]. +pub enum IndexScanPointOrRange<'de, 'a> { + /// A point scan iterator, + /// with the key included as it's needed by views (read sets). + Point(IndexKey<'de>, IndexScanPoint<'a>), + /// A range scan iterator. + Range(IndexScanRanged<'a>), +} + fn get_sequence_mut(seq_state: &mut SequencesState, seq_id: SequenceId) -> Result<&mut Sequence> { seq_state .get_sequence_mut(seq_id) diff --git a/crates/sats/src/de.rs b/crates/sats/src/de.rs index 5b5b0b118c5..2c93cc10544 100644 --- a/crates/sats/src/de.rs +++ b/crates/sats/src/de.rs @@ -524,6 +524,24 @@ pub trait DeserializeSeed<'de> { fn deserialize>(self, deserializer: D) -> Result; } +/// Allows access to `DeserializeSeed` implementations +/// without actually having a seed value. +pub struct NoSeed(PhantomData); + +impl Default for NoSeed { + fn default() -> Self { + Self(PhantomData) + } +} + +impl<'de, T: Deserialize<'de>> DeserializeSeed<'de> for NoSeed { + type Output = T; + + fn deserialize>(self, deserializer: D) -> Result { + T::deserialize(deserializer) + } +} + use crate::de::impls::BorrowedSliceVisitor; pub use spacetimedb_bindings_macro::Deserialize; diff --git a/crates/sats/src/de/impls.rs b/crates/sats/src/de/impls.rs index e0b77001224..c3a46d4da5d 100644 --- a/crates/sats/src/de/impls.rs +++ b/crates/sats/src/de/impls.rs @@ -3,7 +3,7 @@ use super::{ ProductKind, ProductVisitor, SeqProductAccess, SliceVisitor, SumAccess, SumVisitor, VariantAccess, VariantVisitor, }; use crate::{ - de::{array_visit, ArrayAccess, ArrayVisitor, GrowingVec}, + de::{array_visit, ArrayAccess, ArrayVisitor, GrowingVec, NoSeed}, AlgebraicType, AlgebraicValue, ArrayType, ArrayValue, ProductType, ProductTypeElement, ProductValue, SumType, SumValue, WithTypespace, F32, F64, }; @@ -366,6 +366,8 @@ impl<'de, T: Deserialize<'de>, U: Deserialize<'de>> VariantVisitor<'de> for Resu } } +impl_deserialize!([T: Deserialize<'de>] Bound, de => NoSeed::default().deserialize(de)); + /// The visitor deserializes a `Bound`. #[derive(Clone, Copy)] pub struct WithBound(pub S); diff --git a/crates/table/src/bflatn_from.rs b/crates/table/src/bflatn_from.rs index 598d737d50b..9d0a6b6f30b 100644 --- a/crates/table/src/bflatn_from.rs +++ b/crates/table/src/bflatn_from.rs @@ -11,10 +11,12 @@ use super::{ }; use core::cell::Cell; use core::str; +use spacetimedb_primitives::ColList; use spacetimedb_sats::{ i256, impl_serialize, layout::{ - align_to, AlgebraicTypeLayout, HasLayout as _, ProductTypeLayoutView, RowTypeLayout, SumTypeLayout, VarLenType, + align_to, AlgebraicTypeLayout, HasLayout as _, ProductTypeElementLayout, ProductTypeLayoutView, RowTypeLayout, + SumTypeLayout, VarLenType, }, ser::{SerializeNamedProduct, Serializer}, u256, ArrayType, @@ -44,6 +46,46 @@ pub unsafe fn serialize_row_from_page( unsafe { serialize_product(ser, fixed_bytes, page, blob_store, &Cell::new(0), ty.product()) } } +/// Serializes the columns `cols` of the row in `page` +/// where the fixed part of `row` starts at `fixed_offset` +/// and lasts `ty.size()` bytes. This region is typed at `ty`. +/// +/// # Safety +/// +/// 1. the `fixed_offset` must point at a row in `page` lasting `ty.size()` byte. +/// 2. the row must be a valid `ty`. +/// 3. for any `vlr: VarLenRef` stored in the row, +/// `vlr.first_offset` must either be `NULL` or point to a valid granule in `page`. +/// 4. any `col` in `cols` must be in-bounds of `ty`'s layout. +pub unsafe fn serialize_columns_from_page( + ser: S, + page: &Page, + blob_store: &dyn BlobStore, + fixed_offset: PageOffset, + ty: &RowTypeLayout, + cols: &ColList, +) -> Result { + let bytes = page.get_row_data(fixed_offset, ty.size()); + + let elems = &*ty.elements; + let mut ser = ser.serialize_named_product(elems.len())?; + + for col in cols.iter() { + let col_idx = col.idx(); + // SAFETY: per 4. caller promised that any `col` is in-bounds of `ty`'s layout. + let elem_ty = unsafe { elems.get_unchecked(col_idx) }; + let offset = elem_ty.offset as usize; + // SAFETY: + // 1. `value` was valid at `ty` so we know + // `sub_val = &bytes[range_move(0..elem_ty.ty.size(), offset)]` + // is valid at `elem_ty.ty`, as `elem_ty`. + // 2. forward caller requirement. + unsafe { serialize_product_field(&mut ser, bytes, page, blob_store, offset, elem_ty) }?; + } + + ser.end() +} + /// This has to be a `Cell<_>` here as we only get `&Value` in `Serialize`. type CurrOffset<'a> = &'a Cell; @@ -70,30 +112,52 @@ unsafe fn serialize_product( curr_offset: CurrOffset<'_>, ty: ProductTypeLayoutView<'_>, ) -> Result { - let elems = &ty.elements; + let elems = ty.elements; let mut ser = ser.serialize_named_product(elems.len())?; let my_offset = curr_offset.get(); - for elem_ty in elems.iter() { - curr_offset.set(my_offset + elem_ty.offset as usize); - // SAFETY: By 1., `value` is valid at `ty`, - // so it follows that valid and properly aligned sub-`value`s - // are valid `elem_ty.ty`s. - // By 2., and the above, it follows that sub-`value`s won't have dangling `VarLenRef`s. - let value = Value { - bytes, - page, - blob_store, - curr_offset, - ty: &elem_ty.ty, - }; - ser.serialize_element(elem_ty.name.as_deref(), &value)?; + let offset = my_offset + elem_ty.offset as usize; + // SAFETY: + // 1. `value` was valid at `ty` so we know + // `sub_val = &bytes[range_move(0..elem_ty.ty.size(), offset)]` + // is valid at `elem_ty.ty`, as `elem_ty`. + // 2. forward caller requirement. + unsafe { serialize_product_field(&mut ser, bytes, page, blob_store, offset, elem_ty) }?; } ser.end() } +/// Serializes a product field in `value = &bytes[range_move(0..ty.size(), offset)]`, +/// where the field is typed at `elem_ty`, into `ser`. +/// +/// SAFETY: +/// 1. the `value` must be valid at type `ty` and properly aligned for `ty`. +/// 2. for any `vlr: VarLenRef` stored in `value`, +/// `vlr.first_offset` must either be `NULL` or point to a valid granule in `page`. +unsafe fn serialize_product_field( + ser: &mut S, + bytes: &Bytes, + page: &Page, + blob_store: &dyn BlobStore, + offset: usize, + elem_ty: &ProductTypeElementLayout, +) -> Result<(), S::Error> { + // SAFETY: By 1., `value` is valid at `ty`, + // so it follows that valid and properly aligned sub-`value`s + // are valid `elem_ty.ty`s. + // By 2., and the above, it follows that sub-`value`s won't have dangling `VarLenRef`s. + let value = Value { + bytes, + page, + blob_store, + curr_offset: &Cell::new(offset), + ty: &elem_ty.ty, + }; + ser.serialize_element(elem_ty.name.as_deref(), &value) +} + /// Serializes the sum value in `value = &bytes[range_move(0..ty.size(), *curr_offset)]`, /// which is typed at `ty`, into `ser`. /// diff --git a/crates/table/src/table.rs b/crates/table/src/table.rs index b7ab3338afe..24e566a1aa9 100644 --- a/crates/table/src/table.rs +++ b/crates/table/src/table.rs @@ -1,12 +1,7 @@ -use crate::{ - blob_store::NullBlobStore, - table_index::{IndexCannotSeekRange, IndexKey, IndexKind}, -}; - use super::{ - bflatn_from::serialize_row_from_page, + bflatn_from::{serialize_columns_from_page, serialize_row_from_page}, bflatn_to::{write_row_to_pages, write_row_to_pages_bsatn, Error}, - blob_store::BlobStore, + blob_store::{BlobStore, NullBlobStore}, eq::eq_row_in_page, eq_to_pv::eq_row_in_page_to_pv, indexes::{Bytes, PageIndex, PageOffset, RowHash, RowPointer, SquashedOffset, PAGE_DATA_SIZE}, @@ -20,7 +15,7 @@ use super::{ static_assert_size, static_bsatn_validator::{static_bsatn_validator, validate_bsatn, StaticBsatnValidator}, static_layout::StaticLayout, - table_index::{TableIndex, TableIndexPointIter, TableIndexRangeIter}, + table_index::{IndexCannotSeekRange, IndexKey, IndexKind, TableIndex, TableIndexPointIter, TableIndexRangeIter}, var_len::VarLenMembers, }; use core::{fmt, ptr}; @@ -1762,6 +1757,21 @@ impl<'a> RowRef<'a> { T::read_column(self, col.into().idx()) } + /// Serializes the `cols` of `self` using `ser`. + /// + /// # Safety + /// + /// Any `col` in `cols` is in-bounds of `self`'s layout. + pub unsafe fn serialize_columns_unchecked(self, cols: &ColList, ser: S) -> Result { + let table = self.table; + let (page, offset) = table.page_and_offset(self.pointer); + // SAFETY: + // - We have a `RowRef`, so `ptr` points to a valid row in this table + // so safety requirements 1-3 flow from that. + // - Caller promised that any `col` in `cols` is in-bounds of `self`'s layout. + unsafe { serialize_columns_from_page(ser, page, self.blob_store, offset, &table.row_layout, cols) } + } + /// Construct a projection of the row at `self` by extracting the `cols`. /// /// If `cols` contains zero or more than one column, the values of the projected columns are wrapped in a [`ProductValue`]. @@ -1771,7 +1781,7 @@ impl<'a> RowRef<'a> { /// /// - `cols` must not specify any column which is out-of-bounds for the row `self´. pub unsafe fn project_unchecked(self, cols: &ColList) -> AlgebraicValue { - let col_layouts = &self.row_layout().product().elements; + let col_layouts = self.row_layout().product().elements; if let Some(head) = cols.as_singleton() { let head = head.idx(); @@ -1920,7 +1930,8 @@ impl Serialize for RowRef<'_> { fn serialize(&self, ser: S) -> Result { let table = self.table; let (page, offset) = table.page_and_offset(self.pointer); - // SAFETY: `ptr` points to a valid row in this table per above check. + // SAFETY: We have a `RowRef`, so `ptr` points to a valid row in this table + // so safety requirements 1-3 flow from that. unsafe { serialize_row_from_page(ser, page, self.blob_store, offset, &table.row_layout) } } } diff --git a/crates/table/src/table_index/bytes_key.rs b/crates/table/src/table_index/bytes_key.rs new file mode 100644 index 00000000000..018dd73adf6 --- /dev/null +++ b/crates/table/src/table_index/bytes_key.rs @@ -0,0 +1,97 @@ +use super::RowRef; +use crate::indexes::RowPointer; +use core::mem; +use spacetimedb_memory_usage::MemoryUsage; +use spacetimedb_primitives::ColList; +use spacetimedb_sats::bsatn::{DecodeError, Serializer}; +use spacetimedb_sats::de::Error as _; + +/// A key for an all-primitive multi-column index +/// serialized to a byte array. +/// +/// The key can store up to `N` bytes +/// where `N` is determined by the summed size of each column in the index +/// when serialized in BSATN format, +/// which is the same as little-endian encoding of the keys for primitive types. +/// +/// As we cannot have too many different `N`s, +/// we have a few `N`s, where each is a power of 2. +/// A key is then padded to the nearest `N`. +/// For example, a key `(x: u8, y: u16, z: u32)` for a 3-column index +/// would have `N = 1 + 2 + 4 = 7` but would be padded to `N = 8`. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub(super) struct BytesKey([u8; N]); + +impl MemoryUsage for BytesKey<8> {} + +/// A difference between btree indices and hash indices +/// is that the former btree indices store keys and values separately, +/// i.e., as `([K], [RowPointer])` +/// whereas hash indices store them together, +/// i.e., as `([K, RowPointer])`. +/// +/// For hash indices, it's therefore profitable to ensure +/// that the key and the value together fit into an `N` that is a power of 2. +/// An `N` that is a power of 2 is well aligned around cache line sizes. +pub(super) const fn size_sub_row_pointer(n: usize) -> usize { + n - mem::size_of::() +} + +impl BytesKey { + /// Ensure bytes of length `got` fit in `N` or return an error. + fn ensure_key_fits(got: usize) -> Result<(), DecodeError> { + if got > N { + return Err(DecodeError::custom(format_args!( + "key provided is too long, expected at most {N}, but got {got}" + ))); + } + Ok(()) + } + + /// Decodes `prefix` and `endpoint` in BSATN to a `BytesKey` + /// by copying over both if they fit into the key. + /// + /// This is technically more liberal than decoding to `AlgebraicValue` + /// first and then serializing to `BytesKey`, + /// e.g., in cases like `bool` or simply getting too few bytes. + /// However, in the interest of performance, we don't check that. + /// This makes the optimization slightly leaky, + /// but we can live with that. + pub(super) fn from_bsatn_prefix_and_endpoint(prefix: &[u8], endpoint: &[u8]) -> Result { + let prefix_len = prefix.len(); + let endpoint_len = endpoint.len(); + Self::ensure_key_fits(prefix_len + endpoint_len)?; + // Copy the `prefix` and the `endpoint` over. + let mut arr = [0; N]; + arr[..prefix_len].copy_from_slice(prefix); + arr[prefix_len..prefix_len + endpoint_len].copy_from_slice(endpoint); + Ok(Self(arr)) + } + + /// Decodes `bytes` in BSATN to a `BytesKey` + /// by copying over the bytes if they fit into the key. + /// + /// This is technically more liberal than decoding to `AlgebraicValue` + /// first and then serializing to `BytesKey`, + /// e.g., in cases like `bool` or simply getting too few bytes. + /// However, in the interest of performance, we don't check that. + /// This makes the optimization slightly leaky, + /// but we can live with that. + pub(super) fn from_bsatn(bytes: &[u8]) -> Result { + let got = bytes.len(); + Self::ensure_key_fits(got)?; + // Copy the bytes over. + let mut arr = [0; N]; + arr[..got].copy_from_slice(bytes); + Ok(Self(arr)) + } + + pub(super) fn from_row_ref(cols: &ColList, row_ref: RowRef<'_>) -> Self { + let mut arr = [0; N]; + let mut sink = arr.as_mut_slice(); + let ser = Serializer::new(&mut sink); + unsafe { row_ref.serialize_columns_unchecked(cols, ser) } + .expect("should've serialized a `row_ref` to BSATN successfully"); + Self(arr) + } +} diff --git a/crates/table/src/table_index/key_size.rs b/crates/table/src/table_index/key_size.rs index a51e42eea3d..be087adff70 100644 --- a/crates/table/src/table_index/key_size.rs +++ b/crates/table/src/table_index/key_size.rs @@ -1,3 +1,5 @@ +use crate::table_index::BytesKey; + use super::Index; use core::mem; use spacetimedb_memory_usage::MemoryUsage; @@ -216,3 +218,7 @@ impl KeySize for ArrayValue { } } } + +impl KeySize for BytesKey { + type MemoStorage = (); +} diff --git a/crates/table/src/table_index/mod.rs b/crates/table/src/table_index/mod.rs index aa54932b6dd..26464c695ba 100644 --- a/crates/table/src/table_index/mod.rs +++ b/crates/table/src/table_index/mod.rs @@ -25,6 +25,7 @@ //! Additionally, beyond our btree indices, //! we support direct unique indices, where key are indices into `Vec`s. use self::btree_index::{BTreeIndex, BTreeIndexRangeIter}; +use self::bytes_key::{size_sub_row_pointer, BytesKey}; use self::hash_index::HashIndex; use self::same_key_entry::SameKeyEntryIter; use self::unique_btree_index::{UniqueBTreeIndex, UniqueBTreeIndexRangeIter, UniquePointIter}; @@ -36,21 +37,18 @@ use super::table::RowRef; use crate::table_index::index::Despecialize; use crate::table_index::unique_direct_index::ToFromUsize; use crate::{read_column::ReadColumn, static_assert_size}; -use core::fmt; -use core::ops::RangeBounds; -use spacetimedb_primitives::ColList; +use core::ops::{Bound, RangeBounds}; +use core::{fmt, iter}; +use spacetimedb_primitives::{ColId, ColList}; +use spacetimedb_sats::bsatn::{decode, from_reader, from_slice, DecodeError}; use spacetimedb_sats::memory_usage::MemoryUsage; -use spacetimedb_sats::SumValue; -use spacetimedb_sats::{ - bsatn::{from_slice, DecodeError}, - i256, - product_value::InvalidFieldError, - sum_value::SumTag, - u256, AlgebraicType, AlgebraicValue, ProductType, F32, F64, -}; +use spacetimedb_sats::product_value::InvalidFieldError; +use spacetimedb_sats::sum_value::SumTag; +use spacetimedb_sats::{i256, u256, AlgebraicType, AlgebraicValue, ProductType, ProductValue, SumValue, F32, F64}; use spacetimedb_schema::def::IndexAlgorithm; mod btree_index; +mod bytes_key; mod hash_index; mod index; mod key_size; @@ -63,6 +61,8 @@ mod unique_hash_index; pub use self::index::{Index, IndexCannotSeekRange, IndexSeekRangeResult, RangedIndex}; pub use self::key_size::KeySize; +type DecodeResult = Result; + /// A point iterator over a [`TypedIndex`], with a specialized key type. /// /// See module docs for info about specialization. @@ -122,6 +122,7 @@ enum TypedIndexRangeIter<'a> { BTreeF64(BTreeIndexRangeIter<'a, F64>), BTreeString(BTreeIndexRangeIter<'a, Box>), BTreeAV(BTreeIndexRangeIter<'a, AlgebraicValue>), + BTreeBytesKey8(BTreeIndexRangeIter<'a, BytesKey<8>>), // All the unique btree index iterators. UniqueBTreeBool(UniqueBTreeIndexRangeIter<'a, bool>), @@ -171,6 +172,7 @@ impl Iterator for TypedIndexRangeIter<'_> { Self::BTreeF64(this) => this.next(), Self::BTreeString(this) => this.next(), Self::BTreeAV(this) => this.next(), + Self::BTreeBytesKey8(this) => this.next(), Self::UniqueBTreeBool(this) => this.next(), Self::UniqueBTreeU8(this) => this.next(), @@ -290,6 +292,27 @@ enum TypedIndexKey<'a> { F64(F64), String(BowStr<'a>), AV(CowAV<'a>), + + BytesKey8(BytesKey<8>), + BytesKey16(BytesKey<16>), + BytesKey32H(BytesKey<{ size_sub_row_pointer(32) }>), + BytesKey32(BytesKey<32>), + BytesKey64H(BytesKey<{ size_sub_row_pointer(64) }>), + BytesKey64(BytesKey<64>), + BytesKey128H(BytesKey<{ size_sub_row_pointer(128) }>), + BytesKey128(BytesKey<128>), +} + +static_assert_size!(TypedIndexKey<'_>, 144); + +/// Transposes a `Bound>` to a `Result, E>`. +fn transpose_bound(bound: Bound>) -> Result, E> { + match bound { + Bound::Unbounded => Ok(Bound::Unbounded), + Bound::Included(Ok(v)) => Ok(Bound::Included(v)), + Bound::Excluded(Ok(v)) => Ok(Bound::Excluded(v)), + Bound::Included(Err(e)) | Bound::Excluded(Err(e)) => Err(e), + } } impl<'a> TypedIndexKey<'a> { @@ -342,46 +365,50 @@ impl<'a> TypedIndexKey<'a> { /// Derives a [`TypedIndexKey`] from BSATN-encoded `bytes`, /// driven by the kind of [`TypedIndex`] provided in `index`. #[inline] - fn from_bsatn(index: &TypedIndex, ty: &AlgebraicType, bytes: &'a [u8]) -> Result { + fn from_bsatn(index: &TypedIndex, ty: &AlgebraicType, mut bytes: &'a [u8]) -> DecodeResult { + let reader = &mut bytes; + use TypedIndex::*; match index { - BTreeBool(_) | HashBool(_) | UniqueBTreeBool(_) | UniqueHashBool(_) => from_slice(bytes).map(Self::Bool), + BTreeBool(_) | HashBool(_) | UniqueBTreeBool(_) | UniqueHashBool(_) => from_reader(reader).map(Self::Bool), BTreeU8(_) | HashU8(_) | UniqueBTreeU8(_) | UniqueHashU8(_) | UniqueDirectU8(_) => { - from_slice(bytes).map(Self::U8) + from_reader(reader).map(Self::U8) } BTreeSumTag(_) | HashSumTag(_) | UniqueBTreeSumTag(_) | UniqueHashSumTag(_) | UniqueDirectSumTag(_) => { - from_slice(bytes).map(Self::SumTag) + from_reader(reader).map(Self::SumTag) } BTreeU16(_) | HashU16(_) | UniqueBTreeU16(_) | UniqueHashU16(_) | UniqueDirectU16(_) => { - from_slice(bytes).map(Self::U16) + from_reader(reader).map(Self::U16) } BTreeU32(_) | HashU32(_) | UniqueBTreeU32(_) | UniqueHashU32(_) | UniqueDirectU32(_) => { - from_slice(bytes).map(Self::U32) + from_reader(reader).map(Self::U32) } BTreeU64(_) | HashU64(_) | UniqueBTreeU64(_) | UniqueHashU64(_) | UniqueDirectU64(_) => { - from_slice(bytes).map(Self::U64) + from_reader(reader).map(Self::U64) } - BTreeU128(_) | HashU128(_) | UniqueBTreeU128(_) | UniqueHashU128(_) => from_slice(bytes).map(Self::U128), - BTreeU256(_) | HashU256(_) | UniqueBTreeU256(_) | UniqueHashU256(_) => from_slice(bytes).map(Self::U256), + BTreeU128(_) | HashU128(_) | UniqueBTreeU128(_) | UniqueHashU128(_) => from_reader(reader).map(Self::U128), + BTreeU256(_) | HashU256(_) | UniqueBTreeU256(_) | UniqueHashU256(_) => from_reader(reader).map(Self::U256), - BTreeI8(_) | HashI8(_) | UniqueBTreeI8(_) | UniqueHashI8(_) => from_slice(bytes).map(Self::I8), - BTreeI16(_) | HashI16(_) | UniqueBTreeI16(_) | UniqueHashI16(_) => from_slice(bytes).map(Self::I16), - BTreeI32(_) | HashI32(_) | UniqueBTreeI32(_) | UniqueHashI32(_) => from_slice(bytes).map(Self::I32), - BTreeI64(_) | HashI64(_) | UniqueBTreeI64(_) | UniqueHashI64(_) => from_slice(bytes).map(Self::I64), - BTreeI128(_) | HashI128(_) | UniqueBTreeI128(_) | UniqueHashI128(_) => from_slice(bytes).map(Self::I128), - BTreeI256(_) | HashI256(_) | UniqueBTreeI256(_) | UniqueHashI256(_) => from_slice(bytes).map(Self::I256), + BTreeI8(_) | HashI8(_) | UniqueBTreeI8(_) | UniqueHashI8(_) => from_reader(reader).map(Self::I8), + BTreeI16(_) | HashI16(_) | UniqueBTreeI16(_) | UniqueHashI16(_) => from_reader(reader).map(Self::I16), + BTreeI32(_) | HashI32(_) | UniqueBTreeI32(_) | UniqueHashI32(_) => from_reader(reader).map(Self::I32), + BTreeI64(_) | HashI64(_) | UniqueBTreeI64(_) | UniqueHashI64(_) => from_reader(reader).map(Self::I64), + BTreeI128(_) | HashI128(_) | UniqueBTreeI128(_) | UniqueHashI128(_) => from_reader(reader).map(Self::I128), + BTreeI256(_) | HashI256(_) | UniqueBTreeI256(_) | UniqueHashI256(_) => from_reader(reader).map(Self::I256), - BTreeF32(_) | HashF32(_) | UniqueBTreeF32(_) | UniqueHashF32(_) => from_slice(bytes).map(Self::F32), - BTreeF64(_) | HashF64(_) | UniqueBTreeF64(_) | UniqueHashF64(_) => from_slice(bytes).map(Self::F64), + BTreeF32(_) | HashF32(_) | UniqueBTreeF32(_) | UniqueHashF32(_) => from_reader(reader).map(Self::F32), + BTreeF64(_) | HashF64(_) | UniqueBTreeF64(_) | UniqueHashF64(_) => from_reader(reader).map(Self::F64), BTreeString(_) | HashString(_) | UniqueBTreeString(_) | UniqueHashString(_) => { - from_slice(bytes).map(BowStr::Borrowed).map(Self::String) + from_reader(reader).map(BowStr::Borrowed).map(Self::String) + } + + BTreeAV(_) | HashAV(_) | UniqueBTreeAV(_) | UniqueHashAV(_) => { + AlgebraicValue::decode(ty, reader).map(CowAV::Owned).map(Self::AV) } - BTreeAV(_) | HashAV(_) | UniqueBTreeAV(_) | UniqueHashAV(_) => AlgebraicValue::decode(ty, &mut { bytes }) - .map(CowAV::Owned) - .map(Self::AV), + BTreeBytesKey8(_) => BytesKey::from_bsatn(bytes).map(Self::BytesKey8), } } @@ -466,6 +493,8 @@ impl<'a> TypedIndexKey<'a> { let val = unsafe { row_ref.project_unchecked(cols) }; Self::AV(CowAV::Owned(val)) } + + BTreeBytesKey8(_) => Self::BytesKey8(BytesKey::from_row_ref(cols, row_ref)), } } @@ -491,6 +520,14 @@ impl<'a> TypedIndexKey<'a> { Self::F64(x) => TypedIndexKey::F64(*x), Self::String(x) => TypedIndexKey::String(x.borrow().into()), Self::AV(x) => TypedIndexKey::AV(x.borrow().into()), + Self::BytesKey8(x) => TypedIndexKey::BytesKey8(*x), + Self::BytesKey16(x) => TypedIndexKey::BytesKey16(*x), + Self::BytesKey32H(x) => TypedIndexKey::BytesKey32H(*x), + Self::BytesKey32(x) => TypedIndexKey::BytesKey32(*x), + Self::BytesKey64H(x) => TypedIndexKey::BytesKey64H(*x), + Self::BytesKey64(x) => TypedIndexKey::BytesKey64(*x), + Self::BytesKey128H(x) => TypedIndexKey::BytesKey128H(*x), + Self::BytesKey128(x) => TypedIndexKey::BytesKey128(*x), } } @@ -515,6 +552,14 @@ impl<'a> TypedIndexKey<'a> { Self::F64(x) => x.into(), Self::String(x) => x.into_owned().into(), Self::AV(x) => x.into_owned(), + Self::BytesKey8(x) => todo!(), + Self::BytesKey16(x) => todo!(), + Self::BytesKey32H(x) => todo!(), + Self::BytesKey32(x) => todo!(), + Self::BytesKey64H(x) => todo!(), + Self::BytesKey64(x) => todo!(), + Self::BytesKey128H(x) => todo!(), + Self::BytesKey128(x) => todo!(), } } } @@ -546,6 +591,7 @@ enum TypedIndex { // TODO(perf, centril): consider `UmbraString` or some "German string". BTreeString(BTreeIndex>), BTreeAV(BTreeIndex), + BTreeBytesKey8(BTreeIndex>), // All the non-unique hash index types. HashBool(HashIndex), @@ -641,6 +687,7 @@ macro_rules! same_for_all_types { Self::BTreeF64($this) => $body, Self::BTreeString($this) => $body, Self::BTreeAV($this) => $body, + Self::BTreeBytesKey8($this) => $body, Self::HashBool($this) => $body, Self::HashU8($this) => $body, @@ -714,7 +761,7 @@ impl MemoryUsage for TypedIndex { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, derive_more::From)] #[cfg_attr(test, derive(proptest_derive::Arbitrary))] pub enum IndexKind { BTree, @@ -906,9 +953,10 @@ impl TypedIndex { match self { BTreeBool(_) | BTreeU8(_) | BTreeSumTag(_) | BTreeI8(_) | BTreeU16(_) | BTreeI16(_) | BTreeU32(_) | BTreeI32(_) | BTreeU64(_) | BTreeI64(_) | BTreeU128(_) | BTreeI128(_) | BTreeU256(_) | BTreeI256(_) - | BTreeF32(_) | BTreeF64(_) | BTreeString(_) | BTreeAV(_) | HashBool(_) | HashU8(_) | HashSumTag(_) - | HashI8(_) | HashU16(_) | HashI16(_) | HashU32(_) | HashI32(_) | HashU64(_) | HashI64(_) | HashU128(_) - | HashI128(_) | HashU256(_) | HashI256(_) | HashF32(_) | HashF64(_) | HashString(_) | HashAV(_) => false, + | BTreeF32(_) | BTreeF64(_) | BTreeString(_) | BTreeAV(_) | BTreeBytesKey8(_) | HashBool(_) | HashU8(_) + | HashSumTag(_) | HashI8(_) | HashU16(_) | HashI16(_) | HashU32(_) | HashI32(_) | HashU64(_) + | HashI64(_) | HashU128(_) | HashI128(_) | HashU256(_) | HashI256(_) | HashF32(_) | HashF64(_) + | HashString(_) | HashAV(_) => false, UniqueBTreeBool(_) | UniqueBTreeU8(_) | UniqueBTreeSumTag(_) @@ -1346,6 +1394,7 @@ impl TypedIndex { BTreeString(this.seek_range(&range)) } Self::BTreeAV(this) => BTreeAV(this.seek_range(&map(range, |k| k.as_av().map(|s| s.borrow())))), + Self::BTreeBytesKey8(this) => BTreeBytesKey8(this.seek_range(&map(range, TypedIndexKey::as_bytes_key8))), Self::UniqueBTreeBool(this) => UniqueBTreeBool(this.seek_range(&map(range, TypedIndexKey::as_bool))), Self::UniqueBTreeU8(this) => UniqueBTreeU8(this.seek_range(&map(range, TypedIndexKey::as_u8))), @@ -1413,6 +1462,7 @@ impl TypedIndex { } /// A key into a [`TableIndex`]. +#[derive(derive_more::From)] pub struct IndexKey<'a> { key: TypedIndexKey<'a>, } @@ -1424,6 +1474,14 @@ impl IndexKey<'_> { } } +/// A decoded range scan bound, which may be a point or a range. +pub enum PointOrRange<'a> { + /// A point scan. + Point(IndexKey<'a>), + /// A range scan, with the lower and upper bound. + Range(Bound>, Bound>), +} + /// An index on a set of [`ColId`]s of a table. #[derive(Debug, PartialEq, Eq)] pub struct TableIndex { @@ -1493,17 +1551,177 @@ impl TableIndex { /// Panics if `value` is not consistent with this index's key type. #[inline] pub fn key_from_algebraic_value<'a>(&self, value: &'a AlgebraicValue) -> IndexKey<'a> { - let key = TypedIndexKey::from_algebraic_value(&self.idx, value); - IndexKey { key } + TypedIndexKey::from_algebraic_value(&self.idx, value).into() } /// Derives a key for this index from BSATN-encoded `bytes`. /// /// Returns an error if `bytes` is not properly encoded for this index's key type. #[inline] - pub fn key_from_bsatn<'de>(&self, bytes: &'de [u8]) -> Result, DecodeError> { - let key = TypedIndexKey::from_bsatn(&self.idx, &self.key_type, bytes)?; - Ok(IndexKey { key }) + pub fn key_from_bsatn<'de>(&self, bytes: &'de [u8]) -> DecodeResult> { + Ok(TypedIndexKey::from_bsatn(&self.idx, &self.key_type, bytes)?.into()) + } + + pub fn bounds_from_bsatn<'de>( + &self, + mut prefix: &'de [u8], + prefix_elems: ColId, + rstart: &'de [u8], + rend: &'de [u8], + ) -> DecodeResult> { + use TypedIndex::*; + + // Decode just whether it's inclusive or other bound forms. + let start = from_slice(rstart)?; + let end = from_slice(rend)?; + + match &self.key_type { + // Multi-column index case or single-column index on a product field. + // We can treat the latter as the former + // and allow e.g., prefix scans within the product field. + AlgebraicType::Product(key_types) => { + // Split into types for the prefix and for the rest. + let key_types = &*key_types.elements; + let (prefix_types, rest_types) = key_types + .split_at_checked(prefix_elems.idx()) + .ok_or_else(|| DecodeError::Other("index key type has too few fields compared to prefix".into()))?; + // The `rstart` and `rend`s must be typed at `Bound`. + // Extract that type and determine the length of the suffix. + let Some((range_type, suffix_types)) = rest_types.split_first() else { + return Err(DecodeError::Other( + "prefix length leaves no room for a range in ranged index scan".into(), + )); + }; + let suffix_len = suffix_types.len(); + + match &self.idx { + BTreeBytesKey8(_) => { + Self::bounds_from_bsatn_bytes_key(prefix, start, end, suffix_len, TypedIndexKey::BytesKey8) + } + BTreeAV(_) | HashAV(_) | UniqueBTreeAV(_) | UniqueHashAV(_) => { + // The index is not specialized. + // We now have the types, + // so proceed to decoding the prefix, and the start/end bounds. + // Finally combine all of these to a single bound pair. + let prefix = decode(prefix_types, &mut prefix)?; + let from_av = |v: AlgebraicValue| TypedIndexKey::AV(v.into()).into(); + let decode = |mut b| AlgebraicValue::decode(&range_type.algebraic_type, &mut b); + + // Is this really a point scan? + if let Some(point) = Self::as_point_scan(&start, &end, suffix_len) { + let point = decode(point)?; + let point = Self::combine_prefix_and_point(prefix, point); + return Ok(PointOrRange::Point(from_av(point))); + } + + // It's not a point scan. + let decode_bound = |b: Bound<_>| transpose_bound(b.map(decode)); + let start = decode_bound(start)?; + let end = decode_bound(end)?; + let (start, end) = Self::combine_prefix_and_bounds(prefix, start, end, suffix_len); + Ok(PointOrRange::Range(start.map(from_av), end.map(from_av))) + } + idx => unreachable!("index should be BytesKey* or AV, but was {idx:?}"), + } + } + // Single-column index case. We implicitly have a PT of len 1. + ty if prefix.is_empty() && prefix_elems.idx() == 0 => { + // Is this really a point scan? + if let Some(point) = Self::as_point_scan(&start, &end, 0) { + return Ok(PointOrRange::Point(self.key_from_bsatn(point)?)); + } + + // It's not a point scan. + let decode = |b: Bound<_>| transpose_bound(b.map(|b| self.key_from_bsatn(b))); + Ok(PointOrRange::Range(decode(start)?, decode(end)?)) + } + _ => Err(DecodeError::Other( + "a single-column index cannot be prefix scanned".into(), + )), + } + } + + /// Decodes `prefix` ++ `start` and `prefix` ++ `end` + /// as BSATN-encoded bounds for a bytes key index. + /// The `suffix_len` is used to determine whether this is a point scan or a range scan. + fn bounds_from_bsatn_bytes_key<'de, const N: usize>( + prefix: &'de [u8], + start: Bound<&'de [u8]>, + end: Bound<&'de [u8]>, + suffix_len: usize, + ctor: impl Copy + FnOnce(BytesKey) -> TypedIndexKey<'de>, + ) -> DecodeResult> { + // Is this really a point scan? + let from = |k| ctor(k).into(); + let decode = |bytes| BytesKey::from_bsatn_prefix_and_endpoint(prefix, bytes).map(from); + Ok(if let Some(point) = Self::as_point_scan(&start, &end, suffix_len) { + PointOrRange::Point(decode(point)?) + } else { + // It's not a point scan. + let decode_bound = |b: Bound<_>| transpose_bound(b.map(decode)); + PointOrRange::Range(decode_bound(start)?, decode_bound(end)?) + }) + } + + /// Returns `start` if there is no suffix and `(start, end)` represents a point bound. + /// Otherwise, `None` is returned. + fn as_point_scan<'de>(start: &Bound<&'de [u8]>, end: &Bound<&'de [u8]>, suffix_len: usize) -> Option<&'de [u8]> { + if let (0, Bound::Included(s), Bound::Included(e)) = (suffix_len, start, end) { + if s == e { + return Some(s); + } + } + None + } + + /// Combines `prefix` and `point` into a ingle `AlgebraicValue` point. + fn combine_prefix_and_point(prefix: ProductValue, point: AlgebraicValue) -> AlgebraicValue { + let mut elems: Vec<_> = prefix.elements.into(); + elems.push(point); + AlgebraicValue::product(elems) + } + + /// Combines `prefix` equality constraints with `start` and `end` bounds + /// filling with `suffix_len` to ensure that the number of fields matches + /// that of the index type. + fn combine_prefix_and_bounds( + prefix: ProductValue, + start: Bound, + end: Bound, + suffix_len: usize, + ) -> (Bound, Bound) { + let prefix_is_empty = prefix.elements.is_empty(); + // Concatenate prefix, value, and the most permissive value for the suffix. + let concat = |prefix: ProductValue, val, fill| { + let mut vals: Vec<_> = prefix.elements.into(); + vals.reserve(1 + suffix_len); + vals.push(val); + vals.extend(iter::repeat_n(fill, suffix_len)); + AlgebraicValue::product(vals) + }; + // The start endpoint needs `Min` as the suffix-filling element, + // as it imposes the least and acts like `Unbounded`. + let concat_start = |val| concat(prefix.clone(), val, AlgebraicValue::Min); + let range_start = match start { + Bound::Included(r) => Bound::Included(concat_start(r)), + Bound::Excluded(r) => Bound::Excluded(concat_start(r)), + // Prefix is empty, and suffix will be `Min`, + // so simplify `(Min, Min, ...)` to `Unbounded`. + Bound::Unbounded if prefix_is_empty => Bound::Unbounded, + Bound::Unbounded => Bound::Included(concat_start(AlgebraicValue::Min)), + }; + // The end endpoint needs `Max` as the suffix-filling element, + // as it imposes the least and acts like `Unbounded`. + let concat_end = |val| concat(prefix, val, AlgebraicValue::Max); + let range_end = match end { + Bound::Included(r) => Bound::Included(concat_end(r)), + Bound::Excluded(r) => Bound::Excluded(concat_end(r)), + // Prefix is empty, and suffix will be `Max`, + // so simplify `(Max, Max, ...)` to `Unbounded`. + Bound::Unbounded if prefix_is_empty => Bound::Unbounded, + Bound::Unbounded => Bound::Included(concat_end(AlgebraicValue::Max)), + }; + (range_start, range_end) } /// Derives a key for this index from `row_ref`. @@ -1517,8 +1735,7 @@ impl TableIndex { // SAFETY: // 1. We're passing the same `ColList` that was provided during construction. // 2. Forward caller requirements. - let key = unsafe { TypedIndexKey::from_row_ref(&self.idx, &self.indexed_columns, row_ref) }; - IndexKey { key } + unsafe { TypedIndexKey::from_row_ref(&self.idx, &self.indexed_columns, row_ref) }.into() } /// Inserts `ptr` with the value `row` to this index. From a7c313e484e7ac07ca0cbe24669f9fa4b7acdff5 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 26 Feb 2026 13:32:34 +0000 Subject: [PATCH 7/8] add Deserialize::validate for non-allocating validation --- crates/bindings-macro/src/sats.rs | 76 +++++- crates/core/src/host/v8/de.rs | 76 ++++-- crates/sats/src/algebraic_value/de.rs | 74 ++++++ crates/sats/src/bsatn/de.rs | 32 +++ crates/sats/src/buffer.rs | 5 +- crates/sats/src/de.rs | 189 +++++++++++++- crates/sats/src/de/impls.rs | 348 ++++++++++++++++++++++---- crates/sats/src/layout.rs | 21 ++ 8 files changed, 744 insertions(+), 77 deletions(-) diff --git a/crates/bindings-macro/src/sats.rs b/crates/bindings-macro/src/sats.rs index 8bad39da837..1902592fcbd 100644 --- a/crates/bindings-macro/src/sats.rs +++ b/crates/bindings-macro/src/sats.rs @@ -347,7 +347,8 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { de_generics.params.insert(0, de_lt_param.into()); let (de_impl_generics, _, de_where_clause) = de_generics.split_for_impl(); - let (iter_n, iter_n2, iter_n3, iter_n4) = (0usize.., 0usize.., 0usize.., 0usize..); + let (iter_n, iter_n2, iter_n3, iter_n4, iter_n5, iter_n6, iter_n7) = + (0usize.., 0usize.., 0usize.., 0usize.., 0usize.., 0usize.., 0usize..); match &ty.data { SatsTypeData::Product(fields) => { @@ -382,8 +383,10 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { let field_names = fields.iter().map(|f| f.ident.unwrap()).collect::>(); let field_strings = fields.iter().map(|f| f.name.as_deref().unwrap()).collect::>(); - let field_types = fields.iter().map(|f| &f.ty); + let field_types = fields.iter().map(|f| f.ty); let field_types2 = field_types.clone(); + let field_types3 = field_types.clone(); + let field_types4 = field_types.clone(); quote! { #[allow(non_camel_case_types)] #[allow(clippy::all)] @@ -396,6 +399,12 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { _marker: std::marker::PhantomData:: #name #ty_generics>, }) } + + fn validate>(deserializer: D) -> Result<(), D::Error> { + deserializer.validate_product(__ProductVisitor { + _marker: std::marker::PhantomData:: #name #ty_generics>, + }) + } } struct __ProductVisitor #impl_generics #where_clause { @@ -419,6 +428,13 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { .ok_or_else(|| #spacetimedb_lib::de::Error::invalid_product_length(#iter_n, &self))?,)* }) } + fn validate_seq_product>(self, mut tup: A) -> Result<(), A::Error> { + #( + tup.validate_next_element::<#field_types2>()? + .ok_or_else(|| #spacetimedb_lib::de::Error::invalid_product_length(#iter_n2, &self))?; + )* + Ok(()) + } fn visit_named_product>(self, mut __prod: A) -> Result { #(let mut #field_names = None;)* while let Some(__field) = #spacetimedb_lib::de::NamedProductAccess::get_field_ident(&mut __prod, Self { @@ -427,17 +443,39 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { match __field { #(__ProductFieldIdent::#field_names => { if #field_names.is_some() { - return Err(#spacetimedb_lib::de::Error::duplicate_field(#iter_n2, Some(#field_strings), &self)) + return Err(#spacetimedb_lib::de::Error::duplicate_field(#iter_n3, Some(#field_strings), &self)) } - #field_names = Some(#spacetimedb_lib::de::NamedProductAccess::get_field_value::<#field_types2>(&mut __prod)?) + #field_names = Some(#spacetimedb_lib::de::NamedProductAccess::get_field_value::<#field_types3>(&mut __prod)?) })* } } Ok(#name { #(#field_names: - #field_names.ok_or_else(|| #spacetimedb_lib::de::Error::missing_field(#iter_n3, Some(#field_strings), &self))?,)* + #field_names.ok_or_else(|| #spacetimedb_lib::de::Error::missing_field(#iter_n4, Some(#field_strings), &self))?,)* }) } + fn validate_named_product>(self, mut __prod: A) -> Result<(), A::Error> { + #(let mut #field_names = false;)* + while let Some(__field) = #spacetimedb_lib::de::NamedProductAccess::get_field_ident(&mut __prod, Self { + _marker: std::marker::PhantomData, + })? { + match __field { + #(__ProductFieldIdent::#field_names => { + if #field_names { + return Err(#spacetimedb_lib::de::Error::duplicate_field(#iter_n5, Some(#field_strings), &self)) + } + #spacetimedb_lib::de::NamedProductAccess::validate_field_value::<#field_types4>(&mut __prod)?; + #field_names = true; + })* + } + } + #( + if !#field_names { + return Err(#spacetimedb_lib::de::Error::missing_field(#iter_n6, Some(#field_strings), &self)); + } + )* + Ok(()) + } } impl #de_impl_generics #spacetimedb_lib::de::FieldNameVisitor<'de> for __ProductVisitor #ty_generics #de_where_clause { @@ -456,7 +494,7 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { fn visit_seq(self, index: usize) -> Self::Output { match index { - #(#iter_n4 => __ProductFieldIdent::#field_names,)* + #(#iter_n7 => __ProductFieldIdent::#field_names,)* _ => core::unreachable!(), } } @@ -488,6 +526,18 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { } } }); + let arms_validate = variants.iter().map(|var| { + let ident = var.ident; + if let Some(ty) = var.ty { + quote! { + __Variant::#ident => #spacetimedb_lib::de::VariantAccess::validate::<#ty>(__access)?, + } + } else { + quote! { + __Variant::#ident => #spacetimedb_lib::de::VariantAccess::validate::<()>(__access)?, + } + } + }); quote! { #[allow(clippy::all)] const _: () = { @@ -497,6 +547,12 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { _marker: std::marker::PhantomData:: #name #ty_generics>, }) } + + fn validate>(deserializer: D) -> Result<(), D::Error> { + deserializer.validate_sum(__SumVisitor { + _marker: std::marker::PhantomData:: #name #ty_generics>, + }) + } } struct __SumVisitor #impl_generics #where_clause { @@ -516,6 +572,14 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { #(#arms)* } } + + fn validate_sum>(self, __data: A) -> Result<(), A::Error> { + let (__variant, __access) = __data.variant(self)?; + match __variant { + #(#arms_validate)* + } + Ok(()) + } } #[allow(non_camel_case_types)] diff --git a/crates/core/src/host/v8/de.rs b/crates/core/src/host/v8/de.rs index a24987402a8..0445cdc9faf 100644 --- a/crates/core/src/host/v8/de.rs +++ b/crates/core/src/host/v8/de.rs @@ -152,6 +152,28 @@ impl<'de, 'this, 'scope: 'de> de::Deserializer<'de> for Deserializer<'this, 'sco }) } + fn validate_product>(self, visitor: V) -> Result<(), Self::Error> { + // In `ProductType.serializeValue()` in the TS SDK, null/undefined is accepted for the unit type. + if visitor.product_len() == 0 && self.input.is_null_or_undefined() { + return visitor.validate_seq_product(de::UnitAccess::new()); + } + + let object = cast!( + self.common.scope, + self.input, + Object, + "object for product type `{}`", + visitor.product_name().unwrap_or("") + )?; + + visitor.validate_named_product(ProductAccess { + common: self.common, + object, + next_value: None, + index: 0, + }) + } + fn deserialize_sum>(self, visitor: V) -> Result { let scope = &*self.common.scope; @@ -302,6 +324,17 @@ impl<'de, 'scope: 'de> de::NamedProductAccess<'de> for ProductAccess<'_, 'scope, // Deserialize the field's value. seed.deserialize(Deserializer { common, input }) } + + fn validate_field_value_seed>(&mut self, seed: T) -> Result<(), Self::Error> { + let common = self.common.reborrow(); + // Extract the field's value. + let input = self + .next_value + .take() + .expect("Call next_key_seed before next_value_seed"); + // Deserialize the field's value. + seed.validate(Deserializer { common, input }) + } } /// Used in `Deserializer::deserialize_sum` to translate a `tag` property of a JS object @@ -367,6 +400,23 @@ where index: 0, } } + + fn next_elem<'a>(&'a mut self) -> Option), Error<'scope>>> { + self.seeds.next().map(move |seed| { + // Extract the array element. + let input = self + .arr + .get_index(self.common.scope, self.index) + .ok_or_else(exception_already_thrown)?; + + // Make the deserializer. + let common = self.common.reborrow(); + let de = Deserializer { common, input }; + + self.index += 1; + Ok((seed, de)) + }) + } } impl<'de, 'scope: 'de, T: DeserializeSeed<'de> + Clone> de::ArrayAccess<'de> for ArrayAccess<'_, 'scope, '_, T> { @@ -374,24 +424,14 @@ impl<'de, 'scope: 'de, T: DeserializeSeed<'de> + Clone> de::ArrayAccess<'de> for type Error = Error<'scope>; fn next_element(&mut self) -> Result, Self::Error> { - self.seeds - .next() - .map(|seed| { - // Extract the array element. - let val = self - .arr - .get_index(self.common.scope, self.index) - .ok_or_else(exception_already_thrown)?; - - // Deserialize the element. - let val = seed.deserialize(Deserializer { - common: self.common.reborrow(), - input: val, - })?; - - self.index += 1; - Ok(val) - }) + self.next_elem() + .map(|res| res.and_then(|(seed, de)| seed.deserialize(de))) + .transpose() + } + + fn validate_next_element(&mut self) -> Result, Self::Error> { + self.next_elem() + .map(|res| res.and_then(|(seed, de)| seed.validate(de))) .transpose() } diff --git a/crates/sats/src/algebraic_value/de.rs b/crates/sats/src/algebraic_value/de.rs index cd2de61c5ad..dcfeed14b51 100644 --- a/crates/sats/src/algebraic_value/de.rs +++ b/crates/sats/src/algebraic_value/de.rs @@ -58,11 +58,21 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer { visitor.visit_seq_product(ProductAccess { vals }) } + fn validate_product>(self, visitor: V) -> Result<(), Self::Error> { + let vals = map_err(self.val.into_product())?.into_iter(); + visitor.validate_seq_product(ProductAccess { vals }) + } + fn deserialize_sum>(self, visitor: V) -> Result { let sum = map_err(self.val.into_sum())?; visitor.visit_sum(SumAccess { sum }) } + fn validate_sum>(self, visitor: V) -> Result<(), Self::Error> { + let sum = map_err(self.val.into_sum())?; + visitor.validate_sum(SumAccess { sum }) + } + fn deserialize_bool(self) -> Result { map_err(self.val.into_bool()) } @@ -139,6 +149,15 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer { let iter = map_err(self.val.into_array())?.into_iter(); visitor.visit(ArrayAccess { iter, seed }) } + + fn validate_array_seed, T: de::DeserializeSeed<'de> + Clone>( + self, + visitor: V, + seed: T, + ) -> Result<(), Self::Error> { + let iter = map_err(self.val.into_array())?.into_iter(); + visitor.validate(ArrayAccess { iter, seed }) + } } /// Defines deserialization for [`ValueDeserializer`] where product elements are in the input. @@ -156,6 +175,13 @@ impl<'de> de::SeqProductAccess<'de> for ProductAccess { .map(|val| seed.deserialize(ValueDeserializer { val })) .transpose() } + + fn validate_next_element_seed>(&mut self, seed: T) -> Result, Self::Error> { + self.vals + .next() + .map(|val| seed.validate(ValueDeserializer { val })) + .transpose() + } } /// Defines deserialization for [`ValueDeserializer`] where a sum value is in the input. @@ -191,6 +217,10 @@ impl<'de> de::VariantAccess<'de> for ValueDeserializer { fn deserialize_seed>(self, seed: T) -> Result { seed.deserialize(self) } + + fn validate_seed>(self, seed: T) -> Result<(), Self::Error> { + seed.validate(self) + } } /// Defines deserialization for [`ValueDeserializer`] where an array value is in the input. @@ -212,6 +242,13 @@ impl<'de, T: de::DeserializeSeed<'de> + Clone> de::ArrayAccess<'de> for ArrayAcc .map(|val| self.seed.clone().deserialize(ValueDeserializer { val })) .transpose() } + + fn validate_next_element(&mut self) -> Result, Self::Error> { + self.iter + .next() + .map(|val| self.seed.clone().validate(ValueDeserializer { val })) + .transpose() + } } impl<'de> de::Deserializer<'de> for &'de ValueDeserializer { @@ -222,11 +259,21 @@ impl<'de> de::Deserializer<'de> for &'de ValueDeserializer { visitor.visit_seq_product(RefProductAccess { vals }) } + fn validate_product>(self, visitor: V) -> Result<(), Self::Error> { + let vals = ok_or(self.val.as_product())?.elements.iter(); + visitor.validate_seq_product(RefProductAccess { vals }) + } + fn deserialize_sum>(self, visitor: V) -> Result { let sum = ok_or(self.val.as_sum())?; visitor.visit_sum(SumAccess::from_ref(sum)) } + fn validate_sum>(self, visitor: V) -> Result<(), Self::Error> { + let sum = ok_or(self.val.as_sum())?; + visitor.validate_sum(SumAccess::from_ref(sum)) + } + fn deserialize_bool(self) -> Result { ok_or(self.val.as_bool().copied()) } @@ -289,6 +336,15 @@ impl<'de> de::Deserializer<'de> for &'de ValueDeserializer { let iter = ok_or(self.val.as_array())?.iter_cloned(); visitor.visit(RefArrayAccess { iter, seed }) } + + fn validate_array_seed, T: de::DeserializeSeed<'de> + Clone>( + self, + visitor: V, + seed: T, + ) -> Result<(), Self::Error> { + let iter = ok_or(self.val.as_array())?.iter_cloned(); + visitor.validate(RefArrayAccess { iter, seed }) + } } /// Defines deserialization for [`&'de ValueDeserializer`] where product elements are in the input. @@ -306,6 +362,13 @@ impl<'de> de::SeqProductAccess<'de> for RefProductAccess<'de> { .map(|val| seed.deserialize(ValueDeserializer::from_ref(val))) .transpose() } + + fn validate_next_element_seed>(&mut self, seed: T) -> Result, Self::Error> { + self.vals + .next() + .map(|val| seed.validate(ValueDeserializer::from_ref(val))) + .transpose() + } } impl<'de> de::SumAccess<'de> for &'de SumAccess { @@ -325,6 +388,10 @@ impl<'de> de::VariantAccess<'de> for &'de ValueDeserializer { fn deserialize_seed>(self, seed: T) -> Result { seed.deserialize(self) } + + fn validate_seed>(self, seed: T) -> Result<(), Self::Error> { + seed.validate(self) + } } /// Defines deserialization for [`&'de ValueDeserializer`] where an array value is in the input. @@ -347,4 +414,11 @@ impl<'de, T: de::DeserializeSeed<'de> + Clone> de::ArrayAccess<'de> for RefArray .map(|val| self.seed.clone().deserialize(ValueDeserializer { val })) .transpose() } + + fn validate_next_element(&mut self) -> Result, Self::Error> { + self.iter + .next() + .map(|val| self.seed.clone().validate(ValueDeserializer { val })) + .transpose() + } } diff --git a/crates/sats/src/bsatn/de.rs b/crates/sats/src/bsatn/de.rs index 4fdfad9950c..777e1041b39 100644 --- a/crates/sats/src/bsatn/de.rs +++ b/crates/sats/src/bsatn/de.rs @@ -57,10 +57,18 @@ impl<'de, R: BufReader<'de>> de::Deserializer<'de> for Deserializer<'_, R> { visitor.visit_seq_product(self) } + fn validate_product>(self, visitor: V) -> Result<(), Self::Error> { + visitor.validate_seq_product(self) + } + fn deserialize_sum>(self, visitor: V) -> Result { visitor.visit_sum(self) } + fn validate_sum>(self, visitor: V) -> Result<(), Self::Error> { + visitor.validate_sum(self) + } + fn deserialize_bool(self) -> Result { let byte = self.reader.get_u8()?; match byte { @@ -132,6 +140,16 @@ impl<'de, R: BufReader<'de>> de::Deserializer<'de> for Deserializer<'_, R> { let seeds = itertools::repeat_n(seed, len); visitor.visit(ArrayAccess { de: self, seeds }) } + + fn validate_array_seed, T: de::DeserializeSeed<'de> + Clone>( + mut self, + visitor: V, + seed: T, + ) -> Result<(), Self::Error> { + let len = self.reborrow().deserialize_len()?; + let seeds = itertools::repeat_n(seed, len); + visitor.validate(ArrayAccess { de: self, seeds }) + } } impl<'de, R: BufReader<'de>> SeqProductAccess<'de> for Deserializer<'_, R> { @@ -140,6 +158,10 @@ impl<'de, R: BufReader<'de>> SeqProductAccess<'de> for Deserializer<'_, R> { fn next_element_seed>(&mut self, seed: T) -> Result, DecodeError> { seed.deserialize(self.reborrow()).map(Some) } + + fn validate_next_element_seed>(&mut self, seed: T) -> Result, Self::Error> { + seed.validate(self.reborrow()).map(Some) + } } impl<'de, R: BufReader<'de>> SumAccess<'de> for Deserializer<'_, R> { @@ -157,6 +179,9 @@ impl<'de, R: BufReader<'de>> VariantAccess<'de> for Deserializer<'_, R> { fn deserialize_seed>(self, seed: T) -> Result { seed.deserialize(self) } + fn validate_seed>(self, seed: T) -> Result<(), Self::Error> { + seed.validate(self) + } } /// Deserializer for array elements. @@ -176,6 +201,13 @@ impl<'de, R: BufReader<'de>, T: de::DeserializeSeed<'de> + Clone> de::ArrayAcces .transpose() } + fn validate_next_element(&mut self) -> Result, Self::Error> { + self.seeds + .next() + .map(|seed| seed.validate(self.de.reborrow())) + .transpose() + } + fn size_hint(&self) -> Option { Some(self.seeds.len()) } diff --git a/crates/sats/src/buffer.rs b/crates/sats/src/buffer.rs index 17b58ec488c..2a60f4e1b76 100644 --- a/crates/sats/src/buffer.rs +++ b/crates/sats/src/buffer.rs @@ -2,9 +2,8 @@ //! without relying on types in third party libraries like `bytes::Bytes`, etc. //! Meant to be kept slim and trim for use across both native and WASM. -use bytes::{BufMut, BytesMut}; - use crate::{i256, u256}; +use bytes::{BufMut, BytesMut}; use core::cell::Cell; use core::fmt; use core::str::Utf8Error; @@ -30,6 +29,8 @@ pub enum DecodeError { Other(String), } +pub type DecodeResult = Result; + impl fmt::Display for DecodeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/crates/sats/src/de.rs b/crates/sats/src/de.rs index 2c93cc10544..342251e624f 100644 --- a/crates/sats/src/de.rs +++ b/crates/sats/src/de.rs @@ -36,6 +36,11 @@ pub trait Deserializer<'de>: Sized { /// Deserializes a product value from the input. fn deserialize_product>(self, visitor: V) -> Result; + /// Validates a product value from the input. + fn validate_product>(self, visitor: V) -> Result<(), Self::Error> { + self.deserialize_product(visitor).map(|_| ()) + } + /// Deserializes a sum value from the input. /// /// The entire process of deserializing a sum, starting from `deserialize(args...)`, is roughly: @@ -69,6 +74,40 @@ pub trait Deserializer<'de>: Sized { /// that can deserialize the contents of the variant. fn deserialize_sum>(self, visitor: V) -> Result; + /// Validates a sum value from the input. + /// + /// The entire process of validating a sum, starting from `validate(args...)`, is roughly: + /// + /// - [`validate`][Deserialize::validate] calls this method, + /// [`validate_sum(sum_visitor)`](Deserializer::validate_sum), + /// providing us with a [`sum_visitor`](SumVisitor). + /// + /// - This method calls [`sum_visitor.validate_sum(sum_access)`](SumVisitor::validate_sum), + /// where [`sum_access`](SumAccess) deals with extracting the tag and the variant data, + /// with the latter provided as [`VariantAccess`]). + /// The `SumVisitor` will then assemble these into the representation of a sum value + /// that the [`Deserialize`] implementation wants. + /// + /// - [`validate_sum`](SumVisitor::validate_sum) then calls + /// [`sum_access.variant(variant_visitor)`](SumAccess::variant), + /// and uses the provided `variant_visitor` to translate extracted variant names / tags + /// into something that is meaningful for `validate_sum`, e.g., an index. + /// + /// The call to `variant` will also return [`variant_access`](VariantAccess) + /// that can validate the contents of the variant. + /// + /// - Finally, after `variant` returns, + /// `validate_sum` validates the variant data using + /// [`variant_access.validate_seed(seed)`](VariantAccess::validate_seed) + /// or [`variant_access.validate()`](VariantAccess::validate). + /// This part may require some conditional logic depending on the identified variant. + /// + /// The data format will also return an object ([`VariantAccess`]) + /// that can validate the contents of the variant. + fn validate_sum>(self, visitor: V) -> Result<(), Self::Error> { + self.deserialize_sum(visitor).map(|_| ()) + } + /// Deserializes a `bool` value from the input. fn deserialize_bool(self) -> Result; @@ -144,6 +183,17 @@ pub trait Deserializer<'de>: Sized { visitor: V, seed: T, ) -> Result; + + /// Validates an array value. + /// + /// The validation is provided with a `seed` value. + fn validate_array_seed, T: DeserializeSeed<'de> + Clone>( + self, + visitor: V, + seed: T, + ) -> Result<(), Self::Error> { + self.deserialize_array_seed(visitor, seed).map(|_| ()) + } } /// The `Error` trait allows [`Deserialize`] implementations to create descriptive error messages @@ -268,7 +318,7 @@ fn fmt_invalid_len<'de>( } /// A visitor walking through a [`Deserializer`] for products. -pub trait ProductVisitor<'de> { +pub trait ProductVisitor<'de>: Sized { /// The resulting product. type Output; @@ -288,6 +338,16 @@ pub trait ProductVisitor<'de> { /// The input contains a named product. fn visit_named_product>(self, prod: A) -> Result; + + /// The input contains an unnamed product. + fn validate_seq_product>(self, prod: A) -> Result<(), A::Error> { + self.visit_seq_product(prod).map(|_| ()) + } + + /// The input contains a named product. + fn validate_named_product>(self, prod: A) -> Result<(), A::Error> { + self.visit_named_product(prod).map(|_| ()) + } } /// What kind of product is this? @@ -317,6 +377,14 @@ pub trait SeqProductAccess<'de> { self.next_element_seed(PhantomData) } + /// Statefully validates `T::Output` from the input provided a `seed` value. + /// + /// Returns `Ok(Some(()))` for the next element in the unnamed product, + /// or `Ok(None)` if there are no more remaining items. + fn validate_next_element>(&mut self) -> Result, Self::Error> { + self.validate_next_element_seed(PhantomData::) + } + /// Statefully deserializes `T::Output` from the input provided a `seed` value. /// /// Returns `Ok(Some(value))` for the next element in the unnamed product, @@ -325,6 +393,14 @@ pub trait SeqProductAccess<'de> { /// [`Deserialize`] implementations should typically use /// [`next_element`](SeqProductAccess::next_element) instead. fn next_element_seed>(&mut self, seed: T) -> Result, Self::Error>; + + /// Statefully validates `T::Output` from the input provided a `seed` value. + /// + /// Returns `Ok(Some(()))` for the next element in the unnamed product, + /// or `Ok(None)` if there are no more remaining items. + fn validate_next_element_seed>(&mut self, seed: T) -> Result, Self::Error> { + self.next_element_seed(seed).map(|opt| opt.map(|_| ())) + } } /// Provides a [`ProductVisitor`] with access to each element of the named product in the input. @@ -346,11 +422,24 @@ pub trait NamedProductAccess<'de> { self.get_field_value_seed(PhantomData) } + /// Deserializes field value of type `T` from the input. + /// + /// This method exists as a convenience for [`Deserialize`] implementations. + /// [`NamedProductAccess`] implementations should not override the default behavior. + fn validate_field_value>(&mut self) -> Result<(), Self::Error> { + self.validate_field_value_seed(PhantomData::) + } + /// Statefully deserializes the field value `T::Output` from the input provided a `seed` value. /// /// [`Deserialize`] implementations should typically use /// [`next_element`](NamedProductAccess::get_field_value) instead. fn get_field_value_seed>(&mut self, seed: T) -> Result; + + /// Statefully validates the field value `T::Output` from the input provided a `seed` value. + fn validate_field_value_seed>(&mut self, seed: T) -> Result<(), Self::Error> { + self.get_field_value_seed(seed).map(|_| ()) + } } /// Visitor used to deserialize the name of a field. @@ -406,6 +495,17 @@ pub trait SumVisitor<'de> { /// The data format will also return an object ([`VariantAccess`]) /// that can deserialize the contents of the variant. fn visit_sum>(self, data: A) -> Result; + + /// Drives the validation of a sum value. + /// + /// This method will ask the data format ([`A: SumAccess`][SumAccess]) + /// which variant of the sum to select in terms of a variant name / tag. + /// `A` will use a [`VariantVisitor`], that `SumVisitor` has provided, + /// to translate into something that is meaningful for `visit_sum`, e.g., an index. + /// + /// The data format will also return an object ([`VariantAccess`]) + /// that can validate the contents of the variant. + fn validate_sum>(self, data: A) -> Result<(), A::Error>; } /// Provides a [`SumVisitor`] access to the data of a sum in the input. @@ -460,6 +560,18 @@ pub trait VariantAccess<'de>: Sized { /// Called when deserializing the contents of a sum variant, and provided with a `seed` value. fn deserialize_seed>(self, seed: T) -> Result; + + /// Called when validating the contents of a sum variant. + /// + /// This method exists as a convenience for [`Deserialize`] implementations. + fn validate>(self) -> Result<(), Self::Error> { + self.validate_seed(PhantomData::) + } + + /// Called when validating the contents of a sum variant, and provided with a `seed` value. + fn validate_seed>(self, seed: T) -> Result<(), Self::Error> { + self.deserialize_seed(seed).map(|_| ()) + } } /// A `SliceVisitor` is provided a slice `T` of some elements by a [`Deserializer`] @@ -486,12 +598,18 @@ pub trait SliceVisitor<'de, T: ToOwned + ?Sized>: Sized { } /// A visitor walking through a [`Deserializer`] for arrays. -pub trait ArrayVisitor<'de, T> { +pub trait ArrayVisitor<'de, T>: Sized { /// The output produced by this visitor. type Output; - /// The input contains an array. + /// The input contains an array, deserialize it. fn visit>(self, vec: A) -> Result; + + /// The input contains an array, but just validate it, don't deserialize. + fn validate>(self, vec: A) -> Result<(), A::Error> { + let _ = self.visit(vec)?; + Ok(()) + } } /// Provides an [`ArrayVisitor`] with access to each element of the array in the input. @@ -508,6 +626,13 @@ pub trait ArrayAccess<'de> { /// or `Ok(None)` if there are no more remaining elements. fn next_element(&mut self) -> Result, Self::Error>; + /// This returns `Ok(Some(()))` for the next element in the array, + /// or `Ok(None)` if there are no more remaining elements. + fn validate_next_element(&mut self) -> Result, Self::Error> { + let opt = self.next_element()?; + Ok(opt.map(|_| ())) + } + /// Returns the number of elements remaining in the array, if known. fn size_hint(&self) -> Option { None @@ -515,13 +640,23 @@ pub trait ArrayAccess<'de> { } /// `DeserializeSeed` is the stateful form of the [`Deserialize`] trait. -pub trait DeserializeSeed<'de> { +pub trait DeserializeSeed<'de>: Sized { /// The type produced by using this seed. type Output; /// Equivalent to the more common [`Deserialize::deserialize`] associated function, /// except with some initial piece of data (the seed `self`) passed in. fn deserialize>(self, deserializer: D) -> Result; + + /// Validate that the input is of the correct form for this seed. + /// + /// The default implementation simply deserializes the input and discards the result, + /// but implementations can override this to perform more efficient validation + /// without fully deserializing the input. + fn validate>(self, deserializer: D) -> Result<(), D::Error> { + let _ = self.deserialize(deserializer)?; + Ok(()) + } } /// Allows access to `DeserializeSeed` implementations @@ -583,6 +718,18 @@ pub trait Deserialize<'de>: Sized { fn __deserialize_array, const N: usize>(deserializer: D) -> Result<[Self; N], D::Error> { deserializer.deserialize_array(BasicArrayVisitor) } + + #[doc(hidden)] + #[inline(always)] + /// Validate that the input is of the correct form for this type. + /// + /// The default implementation simply deserializes the input and discards the result, + /// but implementations can override this to perform more efficient validation + /// without fully deserializing the input. + fn validate>(deserializer: D) -> Result<(), D::Error> { + let _ = Self::deserialize(deserializer)?; + Ok(()) + } } /// A data structure that can be deserialized in SATS @@ -634,6 +781,12 @@ pub fn array_visit<'de, A: ArrayAccess<'de>, V: GrowingVec>(mut acce Ok(v) } +/// A basic implementation of `ArrayVisitor::validate`. +pub fn array_validate<'de, A: ArrayAccess<'de>>(mut access: A) -> Result<(), A::Error> { + while access.next_element()?.is_some() {} + Ok(()) +} + /// An implementation of [`ArrayVisitor<'de, T>`] where the output is a `Vec`. pub struct BasicVecVisitor; @@ -643,6 +796,10 @@ impl<'de, T> ArrayVisitor<'de, T> for BasicVecVisitor { fn visit>(self, vec: A) -> Result { array_visit(vec) } + + fn validate>(self, vec: A) -> Result<(), A::Error> { + array_validate(vec) + } } /// An implementation of [`ArrayVisitor<'de, T>`] where the output is a `SmallVec<[T; N]>`. @@ -654,6 +811,10 @@ impl<'de, T, const N: usize> ArrayVisitor<'de, T> for BasicSmallVecVisitor { fn visit>(self, vec: A) -> Result { array_visit(vec) } + + fn validate>(self, vec: A) -> Result<(), A::Error> { + array_validate(vec) + } } /// An implementation of [`ArrayVisitor<'de, T>`] where the output is a `[T; N]`. @@ -670,6 +831,23 @@ impl<'de, T, const N: usize> ArrayVisitor<'de, T> for BasicArrayVisitor { } v.into_inner().map_err(|_| Error::custom("too few elements for array")) } + + fn validate>(self, mut vec: A) -> Result<(), A::Error> { + // Validate each element and count. + let mut count = 0; + while vec.next_element()?.is_some() { + count += 1; + } + // Don't do this in the loop, + // as we bias towards there not being any errors. + if count > N { + return Err(Error::custom("too many elements for array")); + } + if count < N { + return Err(Error::custom("too few elements for array")); + } + Ok(()) + } } /// Provided a list of names, @@ -765,6 +943,9 @@ impl<'de, D: Deserializer<'de>> VariantAccess<'de> for SomeAccess { fn deserialize_seed>(self, seed: T) -> Result { seed.deserialize(self.0) } + fn validate_seed>(self, seed: T) -> Result<(), Self::Error> { + seed.validate(self.0) + } } /// A `Deserializer` that represents a unit value. diff --git a/crates/sats/src/de/impls.rs b/crates/sats/src/de/impls.rs index c3a46d4da5d..0e528ae12c5 100644 --- a/crates/sats/src/de/impls.rs +++ b/crates/sats/src/de/impls.rs @@ -3,7 +3,7 @@ use super::{ ProductKind, ProductVisitor, SeqProductAccess, SliceVisitor, SumAccess, SumVisitor, VariantAccess, VariantVisitor, }; use crate::{ - de::{array_visit, ArrayAccess, ArrayVisitor, GrowingVec, NoSeed}, + de::{array_validate, array_visit, ArrayAccess, ArrayVisitor, BasicArrayVisitor, GrowingVec, NoSeed}, AlgebraicType, AlgebraicValue, ArrayType, ArrayValue, ProductType, ProductTypeElement, ProductValue, SumType, SumValue, WithTypespace, F32, F64, }; @@ -30,9 +30,16 @@ use std::{borrow::Cow, rc::Rc, sync::Arc}; /// ``` #[macro_export] macro_rules! impl_deserialize { - ([$($generics:tt)*] $(where [$($wc:tt)*])? $typ:ty, $de:ident => $body:expr) => { + ( + [$($generics:tt)*] $(where [$($wc:tt)*])? $typ:ty, + $de:ident => $body:expr + $(, $validate_de:ident => $validate:expr)? + ) => { impl<'de, $($generics)*> $crate::de::Deserialize<'de> for $typ { fn deserialize>($de: D) -> Result { $body } + $( + fn validate>($validate_de: D) -> Result<(), D::Error> { $validate } + )? } }; } @@ -106,6 +113,16 @@ macro_rules! impl_deserialize_tuple { Ok(($($ty_name,)*)) } + fn validate_seq_product>(self, mut _prod: A) -> Result<(), A::Error> { + $( + #[allow(non_snake_case)] + _prod + .validate_next_element_seed(PhantomData::<$ty_name>)? + .ok_or_else(|| Error::invalid_product_length($const_val, &self))?; + )* + + Ok(()) + } fn visit_named_product>(self, mut prod: A) -> Result { $( #[allow(non_snake_case)] @@ -128,6 +145,34 @@ macro_rules! impl_deserialize_tuple { $ty_name.ok_or_else(|| A::Error::missing_field($const_val, None, &self))?, )*)) } + fn validate_named_product>(self, mut prod: A) -> Result<(), A::Error> { + $( + #[allow(non_snake_case)] + let mut $ty_name = false; + )* + + let visit = TupleNameVisitorMax(self.product_len()); + while let Some(index) = prod.get_field_ident(visit)? { + match index { + $($const_val => { + if $ty_name { + return Err(A::Error::duplicate_field($const_val, None, &self)) + } + prod.validate_field_value::<$ty_name>()?; + $ty_name = true; + })* + index => return Err(Error::invalid_product_length(index, &self)), + } + } + + $( + if !$ty_name { + return Err(A::Error::missing_field($const_val, None, &self)) + } + )* + + Ok(()) + } } impl_deserialize!([$($ty_name: Deserialize<'de>),*] ($($ty_name,)*), de => { @@ -168,17 +213,51 @@ impl<'de> Deserialize<'de> for u8 { impl_deserialize!([] F32, de => f32::deserialize(de).map(Into::into)); impl_deserialize!([] F64, de => f64::deserialize(de).map(Into::into)); -impl_deserialize!([] String, de => de.deserialize_str(OwnedSliceVisitor)); -impl_deserialize!([] LeanString, de => >::deserialize(de).map(|s| (&*s).into())); -impl_deserialize!([T: Deserialize<'de>] Vec, de => T::__deserialize_vec(de)); -impl_deserialize!([T: Deserialize<'de>, const N: usize] SmallVec<[T; N]>, de => { - de.deserialize_array(BasicSmallVecVisitor) -}); -impl_deserialize!([T: Deserialize<'de>, const N: usize] [T; N], de => T::__deserialize_array(de)); -impl_deserialize!([] Box, de => String::deserialize(de).map(|s| s.into_boxed_str())); -impl_deserialize!([T: Deserialize<'de>] Box<[T]>, de => Vec::deserialize(de).map(|s| s.into_boxed_slice())); -impl_deserialize!([T: Deserialize<'de>] Rc<[T]>, de => Vec::deserialize(de).map(|s| s.into())); -impl_deserialize!([T: Deserialize<'de>] Arc<[T]>, de => Vec::deserialize(de).map(|s| s.into())); +impl_deserialize!( + [] String, + de => de.deserialize_str(OwnedSliceVisitor), + de => <&str>::validate(de) +); +impl_deserialize!( + [] LeanString, + de => >::deserialize(de).map(|s| (&*s).into()), + de => <&str>::validate(de) +); +impl_deserialize!( + [T: Deserialize<'de>] Vec, + de => T::__deserialize_vec(de), + de => de.validate_array_seed(BasicVecVisitor, PhantomData::) +); +impl_deserialize!( + [T: Deserialize<'de>, const N: usize] SmallVec<[T; N]>, + de => de.deserialize_array(BasicSmallVecVisitor), + de => de.validate_array_seed(BasicVecVisitor, PhantomData::) +); +impl_deserialize!( + [T: Deserialize<'de>, const N: usize] [T; N], + de => T::__deserialize_array(de), + de => de.validate_array_seed(BasicArrayVisitor::, PhantomData::) +); +impl_deserialize!( + [] Box, + de => String::deserialize(de).map(|s| s.into_boxed_str()), + de => String::validate(de) +); +impl_deserialize!( + [T: Deserialize<'de>] Box<[T]>, + de => Vec::deserialize(de).map(|s| s.into_boxed_slice()), + de => Vec::::validate(de) +); +impl_deserialize!( + [T: Deserialize<'de>] Rc<[T]>, + de => Vec::deserialize(de).map(|s| s.into()), + de => Vec::::validate(de) +); +impl_deserialize!( + [T: Deserialize<'de>] Arc<[T]>, + de => Vec::deserialize(de).map(|s| s.into()), + de => Vec::::validate(de) +); /// The visitor converts the slice to its owned version. struct OwnedSliceVisitor; @@ -232,8 +311,16 @@ impl<'de, T: ToOwned + ?Sized + 'de> SliceVisitor<'de, T> for BorrowedSliceVisit } } -impl_deserialize!([] Cow<'de, str>, de => de.deserialize_str(CowSliceVisitor)); -impl_deserialize!([] Cow<'de, [u8]>, de => de.deserialize_bytes(CowSliceVisitor)); +impl_deserialize!( + [] Cow<'de, str>, + de => de.deserialize_str(CowSliceVisitor), + de => <&str>::validate(de) +); +impl_deserialize!( + [] Cow<'de, [u8]>, + de => de.deserialize_bytes(CowSliceVisitor), + de => <&[u8]>::validate(de) +); /// The visitor works with either owned or borrowed versions to produce `Cow<'de, T>`. struct CowSliceVisitor; @@ -254,7 +341,11 @@ impl<'de, T: ToOwned + ?Sized + 'de> SliceVisitor<'de, T> for CowSliceVisitor { } } -impl_deserialize!([T: Deserialize<'de>] Box, de => T::deserialize(de).map(Box::new)); +impl_deserialize!( + [T: Deserialize<'de>] Box, + de => T::deserialize(de).map(Box::new), + de => T::validate(de) +); impl_deserialize!([T: Deserialize<'de>] Option, de => de.deserialize_sum(OptionVisitor(PhantomData))); /// The visitor deserializes an `Option`. @@ -283,6 +374,18 @@ impl<'de, T: Deserialize<'de>> SumVisitor<'de> for OptionVisitor { None }) } + + fn validate_sum>(self, data: A) -> Result<(), A::Error> { + // Determine the variant. + let (some, data) = data.variant(self)?; + + // Validate contents for it. + if some { + data.validate::() + } else { + data.validate::<()>() + } + } } impl<'de, T: Deserialize<'de>> VariantVisitor<'de> for OptionVisitor { @@ -340,6 +443,14 @@ impl<'de, T: Deserialize<'de>, E: Deserialize<'de>> SumVisitor<'de> for ResultVi ResultVariant::Err => Err(data.deserialize()?), }) } + + fn validate_sum>(self, data: A) -> Result<(), A::Error> { + let (variant, data) = data.variant(self)?; + match variant { + ResultVariant::Ok => data.validate::(), + ResultVariant::Err => data.validate::(), + } + } } impl<'de, T: Deserialize<'de>, U: Deserialize<'de>> VariantVisitor<'de> for ResultVisitor { @@ -409,6 +520,18 @@ impl<'de, S: Copy + DeserializeSeed<'de>> SumVisitor<'de> for BoundVisitor { BoundVariant::Unbounded => data.deserialize::<()>().map(|_| Bound::Unbounded), } } + + fn validate_sum>(self, data: A) -> Result<(), A::Error> { + // Determine the variant. + let this = self.0; + let (variant, data) = data.variant(self)?; + + // Validate contents for it. + match variant { + BoundVariant::Included | BoundVariant::Excluded => data.validate_seed(this), + BoundVariant::Unbounded => data.validate::<()>(), + } + } } impl<'de, T: Copy + DeserializeSeed<'de>> VariantVisitor<'de> for BoundVisitor { @@ -465,6 +588,31 @@ impl<'de> DeserializeSeed<'de> for WithTypespace<'_, AlgebraicType> { AlgebraicType::String => >::deserialize(de).map(Into::into), } } + + fn validate>(self, de: D) -> Result<(), D::Error> { + match self.ty() { + AlgebraicType::Ref(r) => self.resolve(*r).validate(de), + AlgebraicType::Sum(sum) => self.with(sum).validate(de), + AlgebraicType::Product(prod) => self.with(prod).validate(de), + AlgebraicType::Array(ty) => self.with(ty).validate(de), + AlgebraicType::Bool => bool::validate(de), + AlgebraicType::I8 => i8::validate(de), + AlgebraicType::U8 => u8::validate(de), + AlgebraicType::I16 => i16::validate(de), + AlgebraicType::U16 => u16::validate(de), + AlgebraicType::I32 => i32::validate(de), + AlgebraicType::U32 => u32::validate(de), + AlgebraicType::I64 => i64::validate(de), + AlgebraicType::U64 => u64::validate(de), + AlgebraicType::I128 => i128::validate(de), + AlgebraicType::U128 => u128::validate(de), + AlgebraicType::I256 => i256::validate(de), + AlgebraicType::U256 => u256::validate(de), + AlgebraicType::F32 => f32::validate(de), + AlgebraicType::F64 => f64::validate(de), + AlgebraicType::String => >::validate(de), + } + } } impl<'de> DeserializeSeed<'de> for WithTypespace<'_, SumType> { @@ -473,6 +621,10 @@ impl<'de> DeserializeSeed<'de> for WithTypespace<'_, SumType> { fn deserialize>(self, deserializer: D) -> Result { deserializer.deserialize_sum(self) } + + fn validate>(self, deserializer: D) -> Result<(), D::Error> { + deserializer.validate_sum(self) + } } impl<'de> SumVisitor<'de> for WithTypespace<'_, SumType> { @@ -494,6 +646,14 @@ impl<'de> SumVisitor<'de> for WithTypespace<'_, SumType> { let value = Box::new(data.deserialize_seed(variant_ty)?); Ok(SumValue { tag, value }) } + + fn validate_sum>(self, data: A) -> Result<(), A::Error> { + let (tag, data) = data.variant(self)?; + // Find the variant type by `tag`. + let variant_ty = self.map(|ty| &ty.variants[tag as usize].algebraic_type); + + data.validate_seed(variant_ty) + } } impl VariantVisitor<'_> for WithTypespace<'_, SumType> { @@ -531,6 +691,10 @@ impl<'de> DeserializeSeed<'de> for WithTypespace<'_, ProductType> { fn deserialize>(self, deserializer: D) -> Result { deserializer.deserialize_product(self.map(|pt| &*pt.elements)) } + + fn validate>(self, deserializer: D) -> Result<(), D::Error> { + deserializer.validate_product(self.map(|pt| &*pt.elements)) + } } impl<'de> DeserializeSeed<'de> for WithTypespace<'_, [ProductTypeElement]> { @@ -539,6 +703,10 @@ impl<'de> DeserializeSeed<'de> for WithTypespace<'_, [ProductTypeElement]> { fn deserialize>(self, deserializer: D) -> Result { deserializer.deserialize_product(self) } + + fn validate>(self, deserializer: D) -> Result<(), D::Error> { + deserializer.validate_product(self) + } } impl<'de> ProductVisitor<'de> for WithTypespace<'_, [ProductTypeElement]> { @@ -555,9 +723,17 @@ impl<'de> ProductVisitor<'de> for WithTypespace<'_, [ProductTypeElement]> { visit_seq_product(self, &self, tup) } + fn validate_seq_product>(self, prod: A) -> Result<(), A::Error> { + validate_seq_product(self, &self, prod) + } + fn visit_named_product>(self, tup: A) -> Result { visit_named_product(self, &self, tup) } + + fn validate_named_product>(self, prod: A) -> Result<(), A::Error> { + validate_named_product(self, &self, prod) + } } impl<'de> DeserializeSeed<'de> for WithTypespace<'_, ArrayType> { @@ -616,37 +792,46 @@ impl<'de> DeserializeSeed<'de> for WithTypespace<'_, ArrayType> { }; } } -} - -// impl<'de> DeserializeSeed<'de> for &ReducerDef { -// type Output = ProductValue; - -// fn deserialize>(self, deserializer: D) -> Result { -// deserializer.deserialize_product(self) -// } -// } - -// impl<'de> ProductVisitor<'de> for &ReducerDef { -// type Output = ProductValue; -// fn product_name(&self) -> Option<&str> { -// self.name.as_deref() -// } -// fn product_len(&self) -> usize { -// self.args.len() -// } -// fn product_kind(&self) -> ProductKind { -// ProductKind::ReducerArgs -// } + fn validate>(self, deserializer: D) -> Result<(), D::Error> { + /// Validate a vector for the appropriate `ArrayValue` variant. + fn val_array<'de, D: Deserializer<'de>, T: Deserialize<'de>>(de: D) -> Result<(), D::Error> { + de.validate_array_seed(BasicVecVisitor, PhantomData::) + } -// fn visit_seq_product>(self, tup: A) -> Result { -// visit_seq_product(&self.args, &self, tup) -// } + let mut ty = &*self.ty().elem_ty; -// fn visit_named_product>(self, tup: A) -> Result { -// visit_named_product(&self.args, &self, tup) -// } -// } + // Loop, resolving `Ref`s, until we reach a non-`Ref` type. + loop { + break match ty { + AlgebraicType::Ref(r) => { + // The only arm that will loop. + ty = self.resolve(*r).ty(); + continue; + } + AlgebraicType::Sum(ty) => deserializer.validate_array_seed(BasicVecVisitor, self.with(ty)), + AlgebraicType::Product(ty) => deserializer.validate_array_seed(BasicVecVisitor, self.with(ty)), + AlgebraicType::Array(ty) => deserializer.validate_array_seed(BasicVecVisitor, self.with(ty)), + &AlgebraicType::Bool => val_array::<_, bool>(deserializer), + &AlgebraicType::I8 => val_array::<_, i8>(deserializer), + &AlgebraicType::U8 => val_array::<_, u8>(deserializer), + &AlgebraicType::I16 => val_array::<_, i16>(deserializer), + &AlgebraicType::U16 => val_array::<_, u16>(deserializer), + &AlgebraicType::I32 => val_array::<_, i32>(deserializer), + &AlgebraicType::U32 => val_array::<_, u32>(deserializer), + &AlgebraicType::I64 => val_array::<_, i64>(deserializer), + &AlgebraicType::U64 => val_array::<_, u64>(deserializer), + &AlgebraicType::I128 => val_array::<_, i128>(deserializer), + &AlgebraicType::U128 => val_array::<_, u128>(deserializer), + &AlgebraicType::I256 => val_array::<_, i256>(deserializer), + &AlgebraicType::U256 => val_array::<_, u256>(deserializer), + &AlgebraicType::F32 => val_array::<_, f32>(deserializer), + &AlgebraicType::F64 => val_array::<_, f64>(deserializer), + &AlgebraicType::String => val_array::<_, String>(deserializer), + }; + } + } +} /// Deserialize, provided the fields' types, a product value with unnamed fields. pub fn visit_seq_product<'de, A: SeqProductAccess<'de>>( @@ -662,6 +847,19 @@ pub fn visit_seq_product<'de, A: SeqProductAccess<'de>>( Ok(ProductValue { elements }) } +/// Validate, provided the fields' types, a product value with unnamed fields. +pub fn validate_seq_product<'de, A: SeqProductAccess<'de>>( + elems: WithTypespace<[ProductTypeElement]>, + visitor: &impl ProductVisitor<'de>, + mut tup: A, +) -> Result<(), A::Error> { + for (i, el) in elems.ty().iter().enumerate() { + tup.validate_next_element_seed(elems.with(&el.algebraic_type))? + .ok_or_else(|| Error::invalid_product_length(i, visitor))?; + } + Ok(()) +} + /// Deserialize, provided the fields' types, a product value with named fields. pub fn visit_named_product<'de, A: super::NamedProductAccess<'de>>( elems_tys: WithTypespace<[ProductTypeElement]>, @@ -707,6 +905,46 @@ pub fn visit_named_product<'de, A: super::NamedProductAccess<'de>>( Ok(ProductValue { elements }) } +/// Validate, provided the fields' types, a product value with named fields. +pub fn validate_named_product<'de, A: super::NamedProductAccess<'de>>( + elems_tys: WithTypespace<[ProductTypeElement]>, + visitor: &impl ProductVisitor<'de>, + mut tup: A, +) -> Result<(), A::Error> { + let elems = elems_tys.ty(); + // TODO(perf): replace with bitset. + let mut elements = vec![false; elems.len()]; + let kind = visitor.product_kind(); + + // Deserialize a product value corresponding to each product type field. + // This is worst case quadratic in complexity + // as fields can be specified out of order (value side) compared to `elems` (type side). + for _ in 0..elems.len() { + // Deserialize a field name, match against the element types. + let index = tup.get_field_ident(TupleNameVisitor { elems, kind })?.ok_or_else(|| { + // Couldn't deserialize a field name. + // Find the first field name we haven't filled an element for. + let missing = elements.iter().position(|&field| !field).unwrap(); + let field_name = elems[missing].name().map(|n| &**n); + Error::missing_field(missing, field_name, visitor) + })?; + + let element = &elems[index]; + + // By index we can select which element to deserialize a value for. + let slot = &mut elements[index]; + if *slot { + return Err(Error::duplicate_field(index, element.name().map(|n| &**n), visitor)); + } + + // Deserialize the value for this field's type. + tup.validate_field_value_seed(elems_tys.with(&element.algebraic_type))?; + *slot = true; + } + + Ok(()) +} + /// A visitor for extracting indices of field names in the elements of a [`ProductType`]. struct TupleNameVisitor<'a> { /// The elements of a product type, in order. @@ -770,19 +1008,35 @@ impl_deserialize!([] spacetimedb_primitives::ColList, de => { fn visit>(self, vec: A) -> Result { array_visit(vec) } + + fn validate>(self, vec: A) -> Result<(), A::Error> { + array_validate(vec) + } } de.deserialize_array(ColListVisitor) }); -impl_deserialize!([] spacetimedb_primitives::ColSet, de => ColList::deserialize(de).map(Into::into)); +impl_deserialize!( + [] spacetimedb_primitives::ColSet, + de => ColList::deserialize(de).map(Into::into), + de => ColList::validate(de) +); #[cfg(feature = "blake3")] impl_deserialize!([] blake3::Hash, de => <[u8; blake3::OUT_LEN]>::deserialize(de).map(blake3::Hash::from_bytes)); // TODO(perf): integrate Bytes with Deserializer to reduce copying -impl_deserialize!([] bytes::Bytes, de => >::deserialize(de).map(Into::into)); +impl_deserialize!( + [] bytes::Bytes, + de => >::deserialize(de).map(Into::into), + de => <&[u8]>::validate(de) +); #[cfg(feature = "bytestring")] -impl_deserialize!([] bytestring::ByteString, de => ::deserialize(de).map(Into::into)); +impl_deserialize!( + [] bytestring::ByteString, + de => ::deserialize(de).map(Into::into), + de => <&str>::validate(de) +); #[cfg(test)] mod test { diff --git a/crates/sats/src/layout.rs b/crates/sats/src/layout.rs index ac123de363d..b9e8f919899 100644 --- a/crates/sats/src/layout.rs +++ b/crates/sats/src/layout.rs @@ -959,9 +959,22 @@ impl<'de> ProductVisitor<'de> for ProductTypeLayoutView<'_> { Ok(elems.into()) } + fn validate_seq_product>(self, mut tup: A) -> Result<(), A::Error> { + for (i, elem_ty) in self.elements.iter().enumerate() { + if tup.validate_next_element_seed(&elem_ty.ty)?.is_none() { + return Err(A::Error::invalid_product_length(i, &self)); + } + } + Ok(()) + } + fn visit_named_product>(self, _: A) -> Result { unreachable!() } + + fn validate_named_product>(self, _: A) -> Result<(), A::Error> { + unreachable!() + } } impl<'de> DeserializeSeed<'de> for &SumTypeLayout { @@ -1000,6 +1013,14 @@ impl<'de> SumVisitor<'de> for &SumTypeLayout { let value = data.deserialize_seed(variant_ty)?; Ok(SumValue::new(tag, value)) } + + fn validate_sum>(self, data: A) -> Result<(), A::Error> { + let (tag, data) = data.variant(self)?; + // Find the variant type by `tag`. + let variant_ty = &self.variants[tag as usize].ty; + + data.validate_seed(variant_ty) + } } impl VariantVisitor<'_> for &SumTypeLayout { From bad551746272fff0bc71b2d4a84d31c5bb07d322 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 26 Feb 2026 13:32:44 +0000 Subject: [PATCH 8/8] fixup wip --- .../src/locking_tx_datastore/mut_tx.rs | 12 +- crates/table/src/table_index/bytes_key.rs | 74 +++-- crates/table/src/table_index/mod.rs | 298 +++++++++++++++--- 3 files changed, 316 insertions(+), 68 deletions(-) diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index a47819f5b0b..013b26766a7 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -38,10 +38,11 @@ use smallvec::SmallVec; use spacetimedb_data_structures::map::{HashMap, HashSet, IntMap}; use spacetimedb_durability::TxOffset; use spacetimedb_execution::{dml::MutDatastore, Datastore, DeltaStore, Row}; -use spacetimedb_lib::{db::raw_def::v9::RawSql, metrics::ExecutionMetrics, Timestamp}; use spacetimedb_lib::{ + db::raw_def::v9::RawSql, db::{auth::StAccess, raw_def::SEQUENCE_ALLOCATION_STEP}, - ConnectionId, Identity, + metrics::ExecutionMetrics, + ConnectionId, Identity, Timestamp, }; use spacetimedb_primitives::{ col_list, ArgId, ColId, ColList, ColSet, ConstraintId, IndexId, ScheduleId, SequenceId, TableId, ViewFnPtr, ViewId, @@ -324,9 +325,10 @@ impl MutTxId { return; }; - let cols = idx.index().indexed_columns.clone(); - let val = point.into_algebraic_value(); - self.read_sets.insert_index_scan(table_id, cols, val, view.clone()); + let idx = idx.index(); + let cols = idx.indexed_columns.clone(); + let point = point.into_algebraic_value(&idx.key_type); + self.read_sets.insert_index_scan(table_id, cols, point, view.clone()); } /// Returns the views whose read sets overlaps with this transaction's write set diff --git a/crates/table/src/table_index/bytes_key.rs b/crates/table/src/table_index/bytes_key.rs index 018dd73adf6..7228979abd9 100644 --- a/crates/table/src/table_index/bytes_key.rs +++ b/crates/table/src/table_index/bytes_key.rs @@ -1,10 +1,11 @@ -use super::RowRef; +use super::{DecodeResult, RowRef}; use crate::indexes::RowPointer; use core::mem; use spacetimedb_memory_usage::MemoryUsage; use spacetimedb_primitives::ColList; -use spacetimedb_sats::bsatn::{DecodeError, Serializer}; -use spacetimedb_sats::de::Error as _; +use spacetimedb_sats::bsatn::{DecodeError, Deserializer, Serializer}; +use spacetimedb_sats::de::{DeserializeSeed, Error as _}; +use spacetimedb_sats::{AlgebraicType, AlgebraicValue, ProductTypeElement, Serialize as _, WithTypespace}; /// A key for an all-primitive multi-column index /// serialized to a byte array. @@ -19,10 +20,10 @@ use spacetimedb_sats::de::Error as _; /// A key is then padded to the nearest `N`. /// For example, a key `(x: u8, y: u16, z: u32)` for a 3-column index /// would have `N = 1 + 2 + 4 = 7` but would be padded to `N = 8`. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub(super) struct BytesKey([u8; N]); -impl MemoryUsage for BytesKey<8> {} +impl MemoryUsage for BytesKey {} /// A difference between btree indices and hash indices /// is that the former btree indices store keys and values separately, @@ -38,8 +39,16 @@ pub(super) const fn size_sub_row_pointer(n: usize) -> usize { } impl BytesKey { + /// Decodes `self` as an [`AlgebraicValue`] at `key_type`. + /// + /// Panics if the wrong `key_type` is provided, but that should never happen. + pub(super) fn decode_algebraic_value(&self, key_type: &AlgebraicType) -> AlgebraicValue { + AlgebraicValue::decode(key_type, &mut self.0.as_slice()) + .expect("A `BytesKey` should by construction always deserialize to the right `key_type`") + } + /// Ensure bytes of length `got` fit in `N` or return an error. - fn ensure_key_fits(got: usize) -> Result<(), DecodeError> { + fn ensure_key_fits(got: usize) -> DecodeResult<()> { if got > N { return Err(DecodeError::custom(format_args!( "key provided is too long, expected at most {N}, but got {got}" @@ -48,16 +57,18 @@ impl BytesKey { Ok(()) } - /// Decodes `prefix` and `endpoint` in BSATN to a `BytesKey` + /// Decodes `prefix` and `endpoint` in BSATN to a [`BytesKey`] /// by copying over both if they fit into the key. - /// - /// This is technically more liberal than decoding to `AlgebraicValue` - /// first and then serializing to `BytesKey`, - /// e.g., in cases like `bool` or simply getting too few bytes. - /// However, in the interest of performance, we don't check that. - /// This makes the optimization slightly leaky, - /// but we can live with that. - pub(super) fn from_bsatn_prefix_and_endpoint(prefix: &[u8], endpoint: &[u8]) -> Result { + pub(super) fn from_bsatn_prefix_and_endpoint( + prefix: &[u8], + prefix_types: &[ProductTypeElement], + endpoint: &[u8], + range_type: &AlgebraicType, + ) -> DecodeResult { + // Validate the BSATN. + WithTypespace::empty(prefix_types).validate(Deserializer::new(&mut { prefix }))?; + WithTypespace::empty(range_type).validate(Deserializer::new(&mut { endpoint }))?; + // Check that the `prefix` and the `endpoint` together fit into the key. let prefix_len = prefix.len(); let endpoint_len = endpoint.len(); Self::ensure_key_fits(prefix_len + endpoint_len)?; @@ -68,16 +79,12 @@ impl BytesKey { Ok(Self(arr)) } - /// Decodes `bytes` in BSATN to a `BytesKey` + /// Decodes `bytes` in BSATN to a [`BytesKey`] /// by copying over the bytes if they fit into the key. - /// - /// This is technically more liberal than decoding to `AlgebraicValue` - /// first and then serializing to `BytesKey`, - /// e.g., in cases like `bool` or simply getting too few bytes. - /// However, in the interest of performance, we don't check that. - /// This makes the optimization slightly leaky, - /// but we can live with that. - pub(super) fn from_bsatn(bytes: &[u8]) -> Result { + pub(super) fn from_bsatn(ty: &AlgebraicType, bytes: &[u8]) -> DecodeResult { + // Validate the BSATN. + WithTypespace::empty(ty).validate(Deserializer::new(&mut { bytes }))?; + // Check that the `bytes` fit into the key. let got = bytes.len(); Self::ensure_key_fits(got)?; // Copy the bytes over. @@ -86,6 +93,11 @@ impl BytesKey { Ok(Self(arr)) } + /// Serializes the columns `cols` in `row_ref` to a [`BytesKey`]. + /// + /// It's assumed that `row_ref` projected to `cols` + /// will fit into `N` bytes when serialized into BSATN. + /// The method panics otherwise. pub(super) fn from_row_ref(cols: &ColList, row_ref: RowRef<'_>) -> Self { let mut arr = [0; N]; let mut sink = arr.as_mut_slice(); @@ -94,4 +106,18 @@ impl BytesKey { .expect("should've serialized a `row_ref` to BSATN successfully"); Self(arr) } + + /// Serializes `av` to a [`BytesKey`]. + /// + /// It's assumed that `av` + /// will fit into `N` bytes when serialized into BSATN. + /// The method panics otherwise. + pub(super) fn from_algebraic_value(av: &AlgebraicValue) -> Self { + let mut arr = [0; N]; + let mut sink = arr.as_mut_slice(); + let ser = Serializer::new(&mut sink); + av.serialize_into_bsatn(ser) + .expect("should've serialized an `AlgebraicValue` to BSATN successfully"); + Self(arr) + } } diff --git a/crates/table/src/table_index/mod.rs b/crates/table/src/table_index/mod.rs index 26464c695ba..fff1cfdf0bb 100644 --- a/crates/table/src/table_index/mod.rs +++ b/crates/table/src/table_index/mod.rs @@ -40,11 +40,14 @@ use crate::{read_column::ReadColumn, static_assert_size}; use core::ops::{Bound, RangeBounds}; use core::{fmt, iter}; use spacetimedb_primitives::{ColId, ColList}; -use spacetimedb_sats::bsatn::{decode, from_reader, from_slice, DecodeError}; +use spacetimedb_sats::bsatn::{decode, from_reader, from_slice}; +use spacetimedb_sats::buffer::{DecodeError, DecodeResult}; use spacetimedb_sats::memory_usage::MemoryUsage; use spacetimedb_sats::product_value::InvalidFieldError; use spacetimedb_sats::sum_value::SumTag; -use spacetimedb_sats::{i256, u256, AlgebraicType, AlgebraicValue, ProductType, ProductValue, SumValue, F32, F64}; +use spacetimedb_sats::{ + i256, u256, AlgebraicType, AlgebraicValue, ProductType, ProductTypeElement, ProductValue, SumValue, F32, F64, +}; use spacetimedb_schema::def::IndexAlgorithm; mod btree_index; @@ -61,8 +64,6 @@ mod unique_hash_index; pub use self::index::{Index, IndexCannotSeekRange, IndexSeekRangeResult, RangedIndex}; pub use self::key_size::KeySize; -type DecodeResult = Result; - /// A point iterator over a [`TypedIndex`], with a specialized key type. /// /// See module docs for info about specialization. @@ -122,7 +123,11 @@ enum TypedIndexRangeIter<'a> { BTreeF64(BTreeIndexRangeIter<'a, F64>), BTreeString(BTreeIndexRangeIter<'a, Box>), BTreeAV(BTreeIndexRangeIter<'a, AlgebraicValue>), - BTreeBytesKey8(BTreeIndexRangeIter<'a, BytesKey<8>>), + BTreeBytesKey8(BTreeIndexRangeIter<'a, BytesKey>), + BTreeBytesKey16(BTreeIndexRangeIter<'a, BytesKey>), + BTreeBytesKey32(BTreeIndexRangeIter<'a, BytesKey>), + BTreeBytesKey64(BTreeIndexRangeIter<'a, BytesKey>), + BTreeBytesKey128(BTreeIndexRangeIter<'a, BytesKey>), // All the unique btree index iterators. UniqueBTreeBool(UniqueBTreeIndexRangeIter<'a, bool>), @@ -143,6 +148,11 @@ enum TypedIndexRangeIter<'a> { UniqueBTreeF64(UniqueBTreeIndexRangeIter<'a, F64>), UniqueBTreeString(UniqueBTreeIndexRangeIter<'a, Box>), UniqueBTreeAV(UniqueBTreeIndexRangeIter<'a, AlgebraicValue>), + UniqueBTreeBytesKey8(UniqueBTreeIndexRangeIter<'a, BytesKey>), + UniqueBTreeBytesKey16(UniqueBTreeIndexRangeIter<'a, BytesKey>), + UniqueBTreeBytesKey32(UniqueBTreeIndexRangeIter<'a, BytesKey>), + UniqueBTreeBytesKey64(UniqueBTreeIndexRangeIter<'a, BytesKey>), + UniqueBTreeBytesKey128(UniqueBTreeIndexRangeIter<'a, BytesKey>), UniqueDirect(UniqueDirectIndexRangeIter<'a>), UniqueDirectU8(UniqueDirectFixedCapIndexRangeIter<'a>), @@ -173,6 +183,10 @@ impl Iterator for TypedIndexRangeIter<'_> { Self::BTreeString(this) => this.next(), Self::BTreeAV(this) => this.next(), Self::BTreeBytesKey8(this) => this.next(), + Self::BTreeBytesKey16(this) => this.next(), + Self::BTreeBytesKey32(this) => this.next(), + Self::BTreeBytesKey64(this) => this.next(), + Self::BTreeBytesKey128(this) => this.next(), Self::UniqueBTreeBool(this) => this.next(), Self::UniqueBTreeU8(this) => this.next(), @@ -192,6 +206,11 @@ impl Iterator for TypedIndexRangeIter<'_> { Self::UniqueBTreeF64(this) => this.next(), Self::UniqueBTreeString(this) => this.next(), Self::UniqueBTreeAV(this) => this.next(), + Self::UniqueBTreeBytesKey8(this) => this.next(), + Self::UniqueBTreeBytesKey16(this) => this.next(), + Self::UniqueBTreeBytesKey32(this) => this.next(), + Self::UniqueBTreeBytesKey64(this) => this.next(), + Self::UniqueBTreeBytesKey128(this) => this.next(), Self::UniqueDirect(this) => this.next(), Self::UniqueDirectU8(this) => this.next(), @@ -271,6 +290,17 @@ impl<'a> CowAV<'a> { } } +const BYTES_KEY_SIZE_8_B: usize = 8; +const BYTES_KEY_SIZE_8_H: usize = size_sub_row_pointer(16); +const _: () = assert!(BYTES_KEY_SIZE_8_B == BYTES_KEY_SIZE_8_H); +const BYTES_KEY_SIZE_16_B: usize = 16; +const BYTES_KEY_SIZE_24_H: usize = size_sub_row_pointer(32); +const BYTES_KEY_SIZE_32_B: usize = 32; +const BYTES_KEY_SIZE_56_H: usize = size_sub_row_pointer(64); +const BYTES_KEY_SIZE_64_B: usize = 64; +const BYTES_KEY_SIZE_120_H: usize = size_sub_row_pointer(128); +const BYTES_KEY_SIZE_128_B: usize = 128; + /// A key into a [`TypedIndex`]. #[derive(enum_as_inner::EnumAsInner, PartialEq, Eq, PartialOrd, Ord, Debug)] enum TypedIndexKey<'a> { @@ -293,14 +323,14 @@ enum TypedIndexKey<'a> { String(BowStr<'a>), AV(CowAV<'a>), - BytesKey8(BytesKey<8>), - BytesKey16(BytesKey<16>), - BytesKey32H(BytesKey<{ size_sub_row_pointer(32) }>), - BytesKey32(BytesKey<32>), - BytesKey64H(BytesKey<{ size_sub_row_pointer(64) }>), - BytesKey64(BytesKey<64>), - BytesKey128H(BytesKey<{ size_sub_row_pointer(128) }>), - BytesKey128(BytesKey<128>), + BytesKey8(BytesKey), + BytesKey16(BytesKey), + BytesKey24(BytesKey), + BytesKey32(BytesKey), + BytesKey56(BytesKey), + BytesKey64(BytesKey), + BytesKey120(BytesKey), + BytesKey128(BytesKey), } static_assert_size!(TypedIndexKey<'_>, 144); @@ -358,6 +388,22 @@ impl<'a> TypedIndexKey<'a> { } (av, BTreeAV(_) | HashAV(_) | UniqueBTreeAV(_) | UniqueHashAV(_)) => Self::AV(CowAV::Borrowed(av)), + + (av, BTreeBytesKey8(_) | HashBytesKey8(_) | UniqueBTreeBytesKey8(_) | UniqueHashBytesKey8(_)) => { + Self::BytesKey8(BytesKey::from_algebraic_value(av)) + } + (av, BTreeBytesKey16(_) | UniqueBTreeBytesKey16(_)) => Self::BytesKey16(BytesKey::from_algebraic_value(av)), + (av, BTreeBytesKey32(_) | UniqueBTreeBytesKey32(_)) => Self::BytesKey32(BytesKey::from_algebraic_value(av)), + (av, BTreeBytesKey64(_) | UniqueBTreeBytesKey64(_)) => Self::BytesKey64(BytesKey::from_algebraic_value(av)), + (av, BTreeBytesKey128(_) | UniqueBTreeBytesKey128(_)) => { + Self::BytesKey128(BytesKey::from_algebraic_value(av)) + } + (av, HashBytesKey24(_) | UniqueHashBytesKey24(_)) => Self::BytesKey24(BytesKey::from_algebraic_value(av)), + (av, HashBytesKey56(_) | UniqueHashBytesKey56(_)) => Self::BytesKey56(BytesKey::from_algebraic_value(av)), + (av, HashBytesKey120(_) | UniqueHashBytesKey120(_)) => { + Self::BytesKey120(BytesKey::from_algebraic_value(av)) + } + _ => panic!("value {value:?} is not compatible with index {index:?}"), } } @@ -408,7 +454,16 @@ impl<'a> TypedIndexKey<'a> { AlgebraicValue::decode(ty, reader).map(CowAV::Owned).map(Self::AV) } - BTreeBytesKey8(_) => BytesKey::from_bsatn(bytes).map(Self::BytesKey8), + BTreeBytesKey8(_) | HashBytesKey8(_) | UniqueBTreeBytesKey8(_) | UniqueHashBytesKey8(_) => { + BytesKey::from_bsatn(ty, bytes).map(Self::BytesKey8) + } + BTreeBytesKey16(_) | UniqueBTreeBytesKey16(_) => BytesKey::from_bsatn(ty, bytes).map(Self::BytesKey16), + BTreeBytesKey32(_) | UniqueBTreeBytesKey32(_) => BytesKey::from_bsatn(ty, bytes).map(Self::BytesKey32), + BTreeBytesKey64(_) | UniqueBTreeBytesKey64(_) => BytesKey::from_bsatn(ty, bytes).map(Self::BytesKey64), + BTreeBytesKey128(_) | UniqueBTreeBytesKey128(_) => BytesKey::from_bsatn(ty, bytes).map(Self::BytesKey128), + HashBytesKey24(_) | UniqueHashBytesKey24(_) => BytesKey::from_bsatn(ty, bytes).map(Self::BytesKey24), + HashBytesKey56(_) | UniqueHashBytesKey56(_) => BytesKey::from_bsatn(ty, bytes).map(Self::BytesKey56), + HashBytesKey120(_) | UniqueHashBytesKey120(_) => BytesKey::from_bsatn(ty, bytes).map(Self::BytesKey120), } } @@ -494,7 +549,16 @@ impl<'a> TypedIndexKey<'a> { Self::AV(CowAV::Owned(val)) } - BTreeBytesKey8(_) => Self::BytesKey8(BytesKey::from_row_ref(cols, row_ref)), + BTreeBytesKey8(_) | HashBytesKey8(_) | UniqueBTreeBytesKey8(_) | UniqueHashBytesKey8(_) => { + Self::BytesKey8(BytesKey::from_row_ref(cols, row_ref)) + } + BTreeBytesKey16(_) | UniqueBTreeBytesKey16(_) => Self::BytesKey16(BytesKey::from_row_ref(cols, row_ref)), + BTreeBytesKey32(_) | UniqueBTreeBytesKey32(_) => Self::BytesKey32(BytesKey::from_row_ref(cols, row_ref)), + BTreeBytesKey64(_) | UniqueBTreeBytesKey64(_) => Self::BytesKey64(BytesKey::from_row_ref(cols, row_ref)), + BTreeBytesKey128(_) | UniqueBTreeBytesKey128(_) => Self::BytesKey128(BytesKey::from_row_ref(cols, row_ref)), + HashBytesKey24(_) | UniqueHashBytesKey24(_) => Self::BytesKey24(BytesKey::from_row_ref(cols, row_ref)), + HashBytesKey56(_) | UniqueHashBytesKey56(_) => Self::BytesKey56(BytesKey::from_row_ref(cols, row_ref)), + HashBytesKey120(_) | UniqueHashBytesKey120(_) => Self::BytesKey120(BytesKey::from_row_ref(cols, row_ref)), } } @@ -522,17 +586,17 @@ impl<'a> TypedIndexKey<'a> { Self::AV(x) => TypedIndexKey::AV(x.borrow().into()), Self::BytesKey8(x) => TypedIndexKey::BytesKey8(*x), Self::BytesKey16(x) => TypedIndexKey::BytesKey16(*x), - Self::BytesKey32H(x) => TypedIndexKey::BytesKey32H(*x), + Self::BytesKey24(x) => TypedIndexKey::BytesKey24(*x), Self::BytesKey32(x) => TypedIndexKey::BytesKey32(*x), - Self::BytesKey64H(x) => TypedIndexKey::BytesKey64H(*x), + Self::BytesKey56(x) => TypedIndexKey::BytesKey56(*x), Self::BytesKey64(x) => TypedIndexKey::BytesKey64(*x), - Self::BytesKey128H(x) => TypedIndexKey::BytesKey128H(*x), + Self::BytesKey120(x) => TypedIndexKey::BytesKey120(*x), Self::BytesKey128(x) => TypedIndexKey::BytesKey128(*x), } } /// Converts the key into an [`AlgebraicValue`]. - fn into_algebraic_value(self) -> AlgebraicValue { + fn into_algebraic_value(self, key_type: &AlgebraicType) -> AlgebraicValue { match self { Self::Bool(x) => x.into(), Self::U8(x) => x.into(), @@ -552,14 +616,14 @@ impl<'a> TypedIndexKey<'a> { Self::F64(x) => x.into(), Self::String(x) => x.into_owned().into(), Self::AV(x) => x.into_owned(), - Self::BytesKey8(x) => todo!(), - Self::BytesKey16(x) => todo!(), - Self::BytesKey32H(x) => todo!(), - Self::BytesKey32(x) => todo!(), - Self::BytesKey64H(x) => todo!(), - Self::BytesKey64(x) => todo!(), - Self::BytesKey128H(x) => todo!(), - Self::BytesKey128(x) => todo!(), + Self::BytesKey8(x) => x.decode_algebraic_value(key_type), + Self::BytesKey16(x) => x.decode_algebraic_value(key_type), + Self::BytesKey24(x) => x.decode_algebraic_value(key_type), + Self::BytesKey32(x) => x.decode_algebraic_value(key_type), + Self::BytesKey56(x) => x.decode_algebraic_value(key_type), + Self::BytesKey64(x) => x.decode_algebraic_value(key_type), + Self::BytesKey120(x) => x.decode_algebraic_value(key_type), + Self::BytesKey128(x) => x.decode_algebraic_value(key_type), } } } @@ -591,7 +655,11 @@ enum TypedIndex { // TODO(perf, centril): consider `UmbraString` or some "German string". BTreeString(BTreeIndex>), BTreeAV(BTreeIndex), - BTreeBytesKey8(BTreeIndex>), + BTreeBytesKey8(BTreeIndex>), + BTreeBytesKey16(BTreeIndex>), + BTreeBytesKey32(BTreeIndex>), + BTreeBytesKey64(BTreeIndex>), + BTreeBytesKey128(BTreeIndex>), // All the non-unique hash index types. HashBool(HashIndex), @@ -613,6 +681,10 @@ enum TypedIndex { // TODO(perf, centril): consider `UmbraString` or some "German string". HashString(HashIndex>), HashAV(HashIndex), + HashBytesKey8(HashIndex>), + HashBytesKey24(HashIndex>), + HashBytesKey56(HashIndex>), + HashBytesKey120(HashIndex>), // All the unique btree index types. UniqueBTreeBool(UniqueBTreeIndex), @@ -634,6 +706,11 @@ enum TypedIndex { // TODO(perf, centril): consider `UmbraString` or some "German string". UniqueBTreeString(UniqueBTreeIndex>), UniqueBTreeAV(UniqueBTreeIndex), + UniqueBTreeBytesKey8(UniqueBTreeIndex>), + UniqueBTreeBytesKey16(UniqueBTreeIndex>), + UniqueBTreeBytesKey32(UniqueBTreeIndex>), + UniqueBTreeBytesKey64(UniqueBTreeIndex>), + UniqueBTreeBytesKey128(UniqueBTreeIndex>), // All the unique hash index types. UniqueHashBool(UniqueHashIndex), @@ -655,6 +732,10 @@ enum TypedIndex { // TODO(perf, centril): consider `UmbraString` or some "German string". UniqueHashString(UniqueHashIndex>), UniqueHashAV(UniqueHashIndex), + UniqueHashBytesKey8(UniqueHashIndex>), + UniqueHashBytesKey24(UniqueHashIndex>), + UniqueHashBytesKey56(UniqueHashIndex>), + UniqueHashBytesKey120(UniqueHashIndex>), // All the unique direct index types. UniqueDirectU8(UniqueDirectIndex), @@ -688,6 +769,10 @@ macro_rules! same_for_all_types { Self::BTreeString($this) => $body, Self::BTreeAV($this) => $body, Self::BTreeBytesKey8($this) => $body, + Self::BTreeBytesKey16($this) => $body, + Self::BTreeBytesKey32($this) => $body, + Self::BTreeBytesKey64($this) => $body, + Self::BTreeBytesKey128($this) => $body, Self::HashBool($this) => $body, Self::HashU8($this) => $body, @@ -707,6 +792,10 @@ macro_rules! same_for_all_types { Self::HashF64($this) => $body, Self::HashString($this) => $body, Self::HashAV($this) => $body, + Self::HashBytesKey8($this) => $body, + Self::HashBytesKey24($this) => $body, + Self::HashBytesKey56($this) => $body, + Self::HashBytesKey120($this) => $body, Self::UniqueBTreeBool($this) => $body, Self::UniqueBTreeU8($this) => $body, @@ -726,6 +815,11 @@ macro_rules! same_for_all_types { Self::UniqueBTreeF64($this) => $body, Self::UniqueBTreeString($this) => $body, Self::UniqueBTreeAV($this) => $body, + Self::UniqueBTreeBytesKey8($this) => $body, + Self::UniqueBTreeBytesKey16($this) => $body, + Self::UniqueBTreeBytesKey32($this) => $body, + Self::UniqueBTreeBytesKey64($this) => $body, + Self::UniqueBTreeBytesKey128($this) => $body, Self::UniqueHashBool($this) => $body, Self::UniqueHashU8($this) => $body, @@ -745,6 +839,10 @@ macro_rules! same_for_all_types { Self::UniqueHashF64($this) => $body, Self::UniqueHashString($this) => $body, Self::UniqueHashAV($this) => $body, + Self::UniqueHashBytesKey8($this) => $body, + Self::UniqueHashBytesKey24($this) => $body, + Self::UniqueHashBytesKey56($this) => $body, + Self::UniqueHashBytesKey120($this) => $body, Self::UniqueDirectSumTag($this) => $body, Self::UniqueDirectU8($this) => $body, @@ -953,10 +1051,12 @@ impl TypedIndex { match self { BTreeBool(_) | BTreeU8(_) | BTreeSumTag(_) | BTreeI8(_) | BTreeU16(_) | BTreeI16(_) | BTreeU32(_) | BTreeI32(_) | BTreeU64(_) | BTreeI64(_) | BTreeU128(_) | BTreeI128(_) | BTreeU256(_) | BTreeI256(_) - | BTreeF32(_) | BTreeF64(_) | BTreeString(_) | BTreeAV(_) | BTreeBytesKey8(_) | HashBool(_) | HashU8(_) + | BTreeF32(_) | BTreeF64(_) | BTreeString(_) | BTreeAV(_) | BTreeBytesKey8(_) | BTreeBytesKey16(_) + | BTreeBytesKey32(_) | BTreeBytesKey64(_) | BTreeBytesKey128(_) | HashBool(_) | HashU8(_) | HashSumTag(_) | HashI8(_) | HashU16(_) | HashI16(_) | HashU32(_) | HashI32(_) | HashU64(_) | HashI64(_) | HashU128(_) | HashI128(_) | HashU256(_) | HashI256(_) | HashF32(_) | HashF64(_) - | HashString(_) | HashAV(_) => false, + | HashString(_) | HashAV(_) | HashBytesKey8(_) | HashBytesKey24(_) | HashBytesKey56(_) + | HashBytesKey120(_) => false, UniqueBTreeBool(_) | UniqueBTreeU8(_) | UniqueBTreeSumTag(_) @@ -975,6 +1075,11 @@ impl TypedIndex { | UniqueBTreeF64(_) | UniqueBTreeString(_) | UniqueBTreeAV(_) + | UniqueBTreeBytesKey8(_) + | UniqueBTreeBytesKey16(_) + | UniqueBTreeBytesKey32(_) + | UniqueBTreeBytesKey64(_) + | UniqueBTreeBytesKey128(_) | UniqueHashBool(_) | UniqueHashU8(_) | UniqueHashSumTag(_) @@ -993,6 +1098,10 @@ impl TypedIndex { | UniqueHashF64(_) | UniqueHashString(_) | UniqueHashAV(_) + | UniqueHashBytesKey8(_) + | UniqueHashBytesKey24(_) + | UniqueHashBytesKey56(_) + | UniqueHashBytesKey120(_) | UniqueDirectU8(_) | UniqueDirectSumTag(_) | UniqueDirectU16(_) @@ -1053,6 +1162,11 @@ impl TypedIndex { (BTreeF64(i), F64(k)) => (i.insert(k, ptr), None), (BTreeString(i), String(k)) => (i.insert(k.into_owned(), ptr), None), (BTreeAV(i), AV(k)) => (i.insert(k.into_owned(), ptr), None), + (BTreeBytesKey8(i), BytesKey8(k)) => (i.insert(k, ptr), None), + (BTreeBytesKey16(i), BytesKey16(k)) => (i.insert(k, ptr), None), + (BTreeBytesKey32(i), BytesKey32(k)) => (i.insert(k, ptr), None), + (BTreeBytesKey64(i), BytesKey64(k)) => (i.insert(k, ptr), None), + (BTreeBytesKey128(i), BytesKey128(k)) => (i.insert(k, ptr), None), (HashBool(i), Bool(k)) => (i.insert(k, ptr), None), (HashU8(i), U8(k)) => (i.insert(k, ptr), None), (HashSumTag(i), SumTag(k)) => (i.insert(k, ptr), None), @@ -1071,6 +1185,10 @@ impl TypedIndex { (HashF64(i), F64(k)) => (i.insert(k, ptr), None), (HashString(i), String(k)) => (i.insert(k.into_owned(), ptr), None), (HashAV(i), AV(k)) => (i.insert(k.into_owned(), ptr), None), + (HashBytesKey8(i), BytesKey8(k)) => (i.insert(k, ptr), None), + (HashBytesKey24(i), BytesKey24(k)) => (i.insert(k, ptr), None), + (HashBytesKey56(i), BytesKey56(k)) => (i.insert(k, ptr), None), + (HashBytesKey120(i), BytesKey120(k)) => (i.insert(k, ptr), None), (UniqueBTreeBool(i), Bool(k)) => (i.insert(k, ptr), None), (UniqueBTreeU8(i), U8(k)) => (i.insert(k, ptr), None), (UniqueBTreeSumTag(i), SumTag(k)) => (i.insert(k, ptr), None), @@ -1089,6 +1207,11 @@ impl TypedIndex { (UniqueBTreeF64(i), F64(k)) => (i.insert(k, ptr), None), (UniqueBTreeString(i), String(k)) => (i.insert(k.into_owned(), ptr), None), (UniqueBTreeAV(i), AV(k)) => (i.insert(k.into_owned(), ptr), None), + (UniqueBTreeBytesKey8(i), BytesKey8(k)) => (i.insert(k, ptr), None), + (UniqueBTreeBytesKey16(i), BytesKey16(k)) => (i.insert(k, ptr), None), + (UniqueBTreeBytesKey32(i), BytesKey32(k)) => (i.insert(k, ptr), None), + (UniqueBTreeBytesKey64(i), BytesKey64(k)) => (i.insert(k, ptr), None), + (UniqueBTreeBytesKey128(i), BytesKey128(k)) => (i.insert(k, ptr), None), (UniqueHashBool(i), Bool(k)) => (i.insert(k, ptr), None), (UniqueHashU8(i), U8(k)) => (i.insert(k, ptr), None), (UniqueHashSumTag(i), SumTag(k)) => (i.insert(k, ptr), None), @@ -1107,6 +1230,10 @@ impl TypedIndex { (UniqueHashF64(i), F64(k)) => (i.insert(k, ptr), None), (UniqueHashString(i), String(k)) => (i.insert(k.into_owned(), ptr), None), (UniqueHashAV(i), AV(k)) => (i.insert(k.into_owned(), ptr), None), + (UniqueHashBytesKey8(i), BytesKey8(k)) => (i.insert(k, ptr), None), + (UniqueHashBytesKey24(i), BytesKey24(k)) => (i.insert(k, ptr), None), + (UniqueHashBytesKey56(i), BytesKey56(k)) => (i.insert(k, ptr), None), + (UniqueHashBytesKey120(i), BytesKey120(k)) => (i.insert(k, ptr), None), (UniqueDirectSumTag(i), SumTag(k)) => (i.insert(k, ptr), None), (UniqueDirectU8(i), U8(k)) => direct(i, k, ptr), @@ -1152,6 +1279,11 @@ impl TypedIndex { (BTreeF64(i), F64(k)) => i.delete(k, ptr), (BTreeString(i), String(k)) => i.delete(k.borrow(), ptr), (BTreeAV(i), AV(k)) => i.delete(k.borrow(), ptr), + (BTreeBytesKey8(i), BytesKey8(k)) => i.delete(k, ptr), + (BTreeBytesKey16(i), BytesKey16(k)) => i.delete(k, ptr), + (BTreeBytesKey32(i), BytesKey32(k)) => i.delete(k, ptr), + (BTreeBytesKey64(i), BytesKey64(k)) => i.delete(k, ptr), + (BTreeBytesKey128(i), BytesKey128(k)) => i.delete(k, ptr), (HashBool(i), Bool(k)) => i.delete(k, ptr), (HashU8(i), U8(k)) => i.delete(k, ptr), (HashSumTag(i), SumTag(k)) => i.delete(k, ptr), @@ -1170,6 +1302,10 @@ impl TypedIndex { (HashF64(i), F64(k)) => i.delete(k, ptr), (HashString(i), String(k)) => i.delete(k.borrow(), ptr), (HashAV(i), AV(k)) => i.delete(k.borrow(), ptr), + (HashBytesKey8(i), BytesKey8(k)) => i.delete(k, ptr), + (HashBytesKey24(i), BytesKey24(k)) => i.delete(k, ptr), + (HashBytesKey56(i), BytesKey56(k)) => i.delete(k, ptr), + (HashBytesKey120(i), BytesKey120(k)) => i.delete(k, ptr), (UniqueBTreeBool(i), Bool(k)) => i.delete(k, ptr), (UniqueBTreeU8(i), U8(k)) => i.delete(k, ptr), (UniqueBTreeSumTag(i), SumTag(k)) => i.delete(k, ptr), @@ -1188,6 +1324,11 @@ impl TypedIndex { (UniqueBTreeF64(i), F64(k)) => i.delete(k, ptr), (UniqueBTreeString(i), String(k)) => i.delete(k.borrow(), ptr), (UniqueBTreeAV(i), AV(k)) => i.delete(k.borrow(), ptr), + (UniqueBTreeBytesKey8(i), BytesKey8(k)) => i.delete(k, ptr), + (UniqueBTreeBytesKey16(i), BytesKey16(k)) => i.delete(k, ptr), + (UniqueBTreeBytesKey32(i), BytesKey32(k)) => i.delete(k, ptr), + (UniqueBTreeBytesKey64(i), BytesKey64(k)) => i.delete(k, ptr), + (UniqueBTreeBytesKey128(i), BytesKey128(k)) => i.delete(k, ptr), (UniqueHashBool(i), Bool(k)) => i.delete(k, ptr), (UniqueHashU8(i), U8(k)) => i.delete(k, ptr), (UniqueHashSumTag(i), SumTag(k)) => i.delete(k, ptr), @@ -1206,6 +1347,10 @@ impl TypedIndex { (UniqueHashF64(i), F64(k)) => i.delete(k, ptr), (UniqueHashString(i), String(k)) => i.delete(k.borrow(), ptr), (UniqueHashAV(i), AV(k)) => i.delete(k.borrow(), ptr), + (UniqueHashBytesKey8(i), BytesKey8(k)) => i.delete(k, ptr), + (UniqueHashBytesKey24(i), BytesKey24(k)) => i.delete(k, ptr), + (UniqueHashBytesKey56(i), BytesKey56(k)) => i.delete(k, ptr), + (UniqueHashBytesKey120(i), BytesKey120(k)) => i.delete(k, ptr), (UniqueDirectSumTag(i), SumTag(k)) => i.delete(k, ptr), (UniqueDirectU8(i), U8(k)) => i.delete(k, ptr), (UniqueDirectU16(i), U16(k)) => i.delete(k, ptr), @@ -1239,6 +1384,11 @@ impl TypedIndex { (BTreeF64(this), F64(key)) => NonUnique(this.seek_point(key)), (BTreeString(this), String(key)) => NonUnique(this.seek_point(key.borrow())), (BTreeAV(this), AV(key)) => NonUnique(this.seek_point(key.borrow())), + (BTreeBytesKey8(this), BytesKey8(key)) => NonUnique(this.seek_point(key)), + (BTreeBytesKey16(this), BytesKey16(key)) => NonUnique(this.seek_point(key)), + (BTreeBytesKey32(this), BytesKey32(key)) => NonUnique(this.seek_point(key)), + (BTreeBytesKey64(this), BytesKey64(key)) => NonUnique(this.seek_point(key)), + (BTreeBytesKey128(this), BytesKey128(key)) => NonUnique(this.seek_point(key)), (HashBool(this), Bool(key)) => NonUnique(this.seek_point(key)), (HashU8(this), U8(key)) => NonUnique(this.seek_point(key)), (HashSumTag(this), SumTag(key)) => NonUnique(this.seek_point(key)), @@ -1257,6 +1407,10 @@ impl TypedIndex { (HashF64(this), F64(key)) => NonUnique(this.seek_point(key)), (HashString(this), String(key)) => NonUnique(this.seek_point(key.borrow())), (HashAV(this), AV(key)) => NonUnique(this.seek_point(key.borrow())), + (HashBytesKey8(this), BytesKey8(key)) => NonUnique(this.seek_point(key)), + (HashBytesKey24(this), BytesKey24(key)) => NonUnique(this.seek_point(key)), + (HashBytesKey56(this), BytesKey56(key)) => NonUnique(this.seek_point(key)), + (HashBytesKey120(this), BytesKey120(key)) => NonUnique(this.seek_point(key)), (UniqueBTreeBool(this), Bool(key)) => Unique(this.seek_point(key)), (UniqueBTreeU8(this), U8(key)) => Unique(this.seek_point(key)), (UniqueBTreeSumTag(this), SumTag(key)) => Unique(this.seek_point(key)), @@ -1275,6 +1429,11 @@ impl TypedIndex { (UniqueBTreeF64(this), F64(key)) => Unique(this.seek_point(key)), (UniqueBTreeString(this), String(key)) => Unique(this.seek_point(key.borrow())), (UniqueBTreeAV(this), AV(key)) => Unique(this.seek_point(key.borrow())), + (UniqueBTreeBytesKey8(this), BytesKey8(key)) => Unique(this.seek_point(key)), + (UniqueBTreeBytesKey16(this), BytesKey16(key)) => Unique(this.seek_point(key)), + (UniqueBTreeBytesKey32(this), BytesKey32(key)) => Unique(this.seek_point(key)), + (UniqueBTreeBytesKey64(this), BytesKey64(key)) => Unique(this.seek_point(key)), + (UniqueBTreeBytesKey128(this), BytesKey128(key)) => Unique(this.seek_point(key)), (UniqueHashBool(this), Bool(key)) => Unique(this.seek_point(key)), (UniqueHashU8(this), U8(key)) => Unique(this.seek_point(key)), (UniqueHashSumTag(this), SumTag(key)) => Unique(this.seek_point(key)), @@ -1293,6 +1452,10 @@ impl TypedIndex { (UniqueHashF64(this), F64(key)) => Unique(this.seek_point(key)), (UniqueHashString(this), String(key)) => Unique(this.seek_point(key.borrow())), (UniqueHashAV(this), AV(key)) => Unique(this.seek_point(key.borrow())), + (UniqueHashBytesKey8(this), BytesKey8(key)) => Unique(this.seek_point(key)), + (UniqueHashBytesKey24(this), BytesKey24(key)) => Unique(this.seek_point(key)), + (UniqueHashBytesKey56(this), BytesKey56(key)) => Unique(this.seek_point(key)), + (UniqueHashBytesKey120(this), BytesKey120(key)) => Unique(this.seek_point(key)), (UniqueDirectSumTag(this), SumTag(key)) => Unique(this.seek_point(key)), (UniqueDirectU8(this), U8(key)) => Unique(this.seek_point(key)), (UniqueDirectU16(this), U16(key)) => Unique(this.seek_point(key)), @@ -1351,6 +1514,10 @@ impl TypedIndex { | Self::HashI256(_) | Self::HashString(_) | Self::HashAV(_) + | Self::HashBytesKey8(_) + | Self::HashBytesKey24(_) + | Self::HashBytesKey56(_) + | Self::HashBytesKey120(_) | Self::UniqueHashBool(_) | Self::UniqueHashU8(_) | Self::UniqueHashSumTag(_) @@ -1368,7 +1535,11 @@ impl TypedIndex { | Self::UniqueHashU256(_) | Self::UniqueHashI256(_) | Self::UniqueHashString(_) - | Self::UniqueHashAV(_) => return Err(IndexCannotSeekRange), + | Self::UniqueHashAV(_) + | Self::UniqueHashBytesKey8(_) + | Self::UniqueHashBytesKey24(_) + | Self::UniqueHashBytesKey56(_) + | Self::UniqueHashBytesKey120(_) => return Err(IndexCannotSeekRange), // Ensure we don't panic inside `BTreeMap::seek_range`. _ if is_empty(range) => RangeEmpty, @@ -1395,6 +1566,12 @@ impl TypedIndex { } Self::BTreeAV(this) => BTreeAV(this.seek_range(&map(range, |k| k.as_av().map(|s| s.borrow())))), Self::BTreeBytesKey8(this) => BTreeBytesKey8(this.seek_range(&map(range, TypedIndexKey::as_bytes_key8))), + Self::BTreeBytesKey16(this) => BTreeBytesKey16(this.seek_range(&map(range, TypedIndexKey::as_bytes_key16))), + Self::BTreeBytesKey32(this) => BTreeBytesKey32(this.seek_range(&map(range, TypedIndexKey::as_bytes_key32))), + Self::BTreeBytesKey64(this) => BTreeBytesKey64(this.seek_range(&map(range, TypedIndexKey::as_bytes_key64))), + Self::BTreeBytesKey128(this) => { + BTreeBytesKey128(this.seek_range(&map(range, TypedIndexKey::as_bytes_key128))) + } Self::UniqueBTreeBool(this) => UniqueBTreeBool(this.seek_range(&map(range, TypedIndexKey::as_bool))), Self::UniqueBTreeU8(this) => UniqueBTreeU8(this.seek_range(&map(range, TypedIndexKey::as_u8))), @@ -1417,6 +1594,21 @@ impl TypedIndex { UniqueBTreeString(this.seek_range(&range)) } Self::UniqueBTreeAV(this) => UniqueBTreeAV(this.seek_range(&map(range, |k| k.as_av().map(|s| s.borrow())))), + Self::UniqueBTreeBytesKey8(this) => { + UniqueBTreeBytesKey8(this.seek_range(&map(range, TypedIndexKey::as_bytes_key8))) + } + Self::UniqueBTreeBytesKey16(this) => { + UniqueBTreeBytesKey16(this.seek_range(&map(range, TypedIndexKey::as_bytes_key16))) + } + Self::UniqueBTreeBytesKey32(this) => { + UniqueBTreeBytesKey32(this.seek_range(&map(range, TypedIndexKey::as_bytes_key32))) + } + Self::UniqueBTreeBytesKey64(this) => { + UniqueBTreeBytesKey64(this.seek_range(&map(range, TypedIndexKey::as_bytes_key64))) + } + Self::UniqueBTreeBytesKey128(this) => { + UniqueBTreeBytesKey128(this.seek_range(&map(range, TypedIndexKey::as_bytes_key128))) + } Self::UniqueDirectSumTag(this) => UniqueDirectU8(this.seek_range(&map(range, TypedIndexKey::as_sum_tag))), Self::UniqueDirectU8(this) => UniqueDirect(this.seek_range(&map(range, TypedIndexKey::as_u8))), @@ -1469,8 +1661,8 @@ pub struct IndexKey<'a> { impl IndexKey<'_> { /// Converts the key into an [`AlgebraicValue`]. - pub fn into_algebraic_value(self) -> AlgebraicValue { - self.key.into_algebraic_value() + pub fn into_algebraic_value(self, key_type: &AlgebraicType) -> AlgebraicValue { + self.key.into_algebraic_value(key_type) } } @@ -1592,12 +1784,19 @@ impl TableIndex { "prefix length leaves no room for a range in ranged index scan".into(), )); }; + let range_type = &range_type.algebraic_type; let suffix_len = suffix_types.len(); match &self.idx { - BTreeBytesKey8(_) => { - Self::bounds_from_bsatn_bytes_key(prefix, start, end, suffix_len, TypedIndexKey::BytesKey8) - } + BTreeBytesKey8(_) => Self::bounds_from_bsatn_bytes_key( + prefix, + prefix_types, + start, + end, + range_type, + suffix_len, + TypedIndexKey::BytesKey8, + ), BTreeAV(_) | HashAV(_) | UniqueBTreeAV(_) | UniqueHashAV(_) => { // The index is not specialized. // We now have the types, @@ -1605,7 +1804,7 @@ impl TableIndex { // Finally combine all of these to a single bound pair. let prefix = decode(prefix_types, &mut prefix)?; let from_av = |v: AlgebraicValue| TypedIndexKey::AV(v.into()).into(); - let decode = |mut b| AlgebraicValue::decode(&range_type.algebraic_type, &mut b); + let decode = |mut b| AlgebraicValue::decode(range_type, &mut b); // Is this really a point scan? if let Some(point) = Self::as_point_scan(&start, &end, suffix_len) { @@ -1646,14 +1845,17 @@ impl TableIndex { /// The `suffix_len` is used to determine whether this is a point scan or a range scan. fn bounds_from_bsatn_bytes_key<'de, const N: usize>( prefix: &'de [u8], + prefix_types: &[ProductTypeElement], start: Bound<&'de [u8]>, end: Bound<&'de [u8]>, + range_type: &AlgebraicType, suffix_len: usize, ctor: impl Copy + FnOnce(BytesKey) -> TypedIndexKey<'de>, ) -> DecodeResult> { // Is this really a point scan? let from = |k| ctor(k).into(); - let decode = |bytes| BytesKey::from_bsatn_prefix_and_endpoint(prefix, bytes).map(from); + let decode = + |bytes| BytesKey::from_bsatn_prefix_and_endpoint(prefix, prefix_types, bytes, range_type).map(from); Ok(if let Some(point) = Self::as_point_scan(&start, &end, suffix_len) { PointOrRange::Point(decode(point)?) } else { @@ -1858,6 +2060,11 @@ impl TableIndex { | (BTreeF64(_), BTreeF64(_)) | (BTreeString(_), BTreeString(_)) | (BTreeAV(_), BTreeAV(_)) + | (BTreeBytesKey8(_), BTreeBytesKey8(_)) + | (BTreeBytesKey16(_), BTreeBytesKey16(_)) + | (BTreeBytesKey32(_), BTreeBytesKey32(_)) + | (BTreeBytesKey64(_), BTreeBytesKey64(_)) + | (BTreeBytesKey128(_), BTreeBytesKey128(_)) | (HashBool(_), HashBool(_)) | (HashU8(_), HashU8(_)) | (HashSumTag(_), HashSumTag(_)) @@ -1875,7 +2082,11 @@ impl TableIndex { | (HashF32(_), HashF32(_)) | (HashF64(_), HashF64(_)) | (HashString(_), HashString(_)) - | (HashAV(_), HashAV(_)) => Ok(()), + | (HashAV(_), HashAV(_)) + | (HashBytesKey8(_), HashBytesKey8(_)) + | (HashBytesKey24(_), HashBytesKey24(_)) + | (HashBytesKey56(_), HashBytesKey56(_)) + | (HashBytesKey120(_), HashBytesKey120(_)) => Ok(()), // For unique indices, we'll need to see if everything in `other` can be added to `idx`. (UniqueBTreeBool(idx), UniqueBTreeBool(other)) => idx.can_merge(other, ignore), (UniqueBTreeU8(idx), UniqueBTreeU8(other)) => idx.can_merge(other, ignore), @@ -1895,6 +2106,11 @@ impl TableIndex { (UniqueBTreeF64(idx), UniqueBTreeF64(other)) => idx.can_merge(other, ignore), (UniqueBTreeString(idx), UniqueBTreeString(other)) => idx.can_merge(other, ignore), (UniqueBTreeAV(idx), UniqueBTreeAV(other)) => idx.can_merge(other, ignore), + (UniqueBTreeBytesKey8(idx), UniqueBTreeBytesKey8(other)) => idx.can_merge(other, ignore), + (UniqueBTreeBytesKey16(idx), UniqueBTreeBytesKey16(other)) => idx.can_merge(other, ignore), + (UniqueBTreeBytesKey32(idx), UniqueBTreeBytesKey32(other)) => idx.can_merge(other, ignore), + (UniqueBTreeBytesKey64(idx), UniqueBTreeBytesKey64(other)) => idx.can_merge(other, ignore), + (UniqueBTreeBytesKey128(idx), UniqueBTreeBytesKey128(other)) => idx.can_merge(other, ignore), (UniqueHashBool(idx), UniqueHashBool(other)) => idx.can_merge(other, ignore), (UniqueHashU8(idx), UniqueHashU8(other)) => idx.can_merge(other, ignore), (UniqueHashSumTag(idx), UniqueHashSumTag(other)) => idx.can_merge(other, ignore), @@ -1913,6 +2129,10 @@ impl TableIndex { (UniqueHashF64(idx), UniqueHashF64(other)) => idx.can_merge(other, ignore), (UniqueHashString(idx), UniqueHashString(other)) => idx.can_merge(other, ignore), (UniqueHashAV(idx), UniqueHashAV(other)) => idx.can_merge(other, ignore), + (UniqueHashBytesKey8(idx), UniqueHashBytesKey8(other)) => idx.can_merge(other, ignore), + (UniqueHashBytesKey24(idx), UniqueHashBytesKey24(other)) => idx.can_merge(other, ignore), + (UniqueHashBytesKey56(idx), UniqueHashBytesKey56(other)) => idx.can_merge(other, ignore), + (UniqueHashBytesKey120(idx), UniqueHashBytesKey120(other)) => idx.can_merge(other, ignore), (UniqueDirectU8(idx), UniqueDirectU8(other)) => idx.can_merge(other, ignore), (UniqueDirectSumTag(idx), UniqueDirectSumTag(other)) => idx.can_merge(other, ignore), (UniqueDirectU16(idx), UniqueDirectU16(other)) => idx.can_merge(other, ignore),