Skip to content

Commit 047db8f

Browse files
committed
improve multi column index test coverage and necessary changes to get there
1 parent 62d9622 commit 047db8f

13 files changed

Lines changed: 536 additions & 180 deletions

File tree

crates/datastore/src/locking_tx_datastore/mut_tx.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,6 +1482,7 @@ impl MutTxId {
14821482
.map_err(|IndexCannotSeekRange| IndexError::IndexCannotSeekRange(index_id))?;
14831483
IndexScanPointOrRange::Range(iter)
14841484
}
1485+
PointOrRange::Unsupported => return Err(IndexError::IndexCannotSeekRange(index_id).into()),
14851486
};
14861487
Ok((table_id, iter))
14871488
}

crates/sats/src/algebraic_value/de.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::array_value::{ArrayValueIntoIter, ArrayValueIterCloned};
2-
use crate::{de, AlgebraicValue, SumValue};
2+
use crate::{de, AlgebraicValue, ProductValue, SumValue};
33
use crate::{i256, u256};
44
use derive_more::From;
55

@@ -23,6 +23,11 @@ impl ValueDeserializer {
2323
// SAFETY: The conversion is OK due to `repr(transparent)`.
2424
unsafe { &*(val as *const AlgebraicValue as *const ValueDeserializer) }
2525
}
26+
27+
pub fn from_product_ref(prod: &ProductValue) -> RefProductAccess<'_> {
28+
let vals = prod.elements.iter();
29+
RefProductAccess { vals }
30+
}
2631
}
2732

2833
/// Errors that can occur when deserializing the `AlgebraicValue`.
@@ -348,7 +353,7 @@ impl<'de> de::Deserializer<'de> for &'de ValueDeserializer {
348353
}
349354

350355
/// Defines deserialization for [`&'de ValueDeserializer`] where product elements are in the input.
351-
struct RefProductAccess<'a> {
356+
pub struct RefProductAccess<'a> {
352357
/// The element values of the product as an iterator of borrowed values.
353358
vals: std::slice::Iter<'a, AlgebraicValue>,
354359
}

crates/sats/src/de/impls.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,17 @@ impl_deserialize!(
259259
de => Vec::<T>::validate(de)
260260
);
261261

262+
/// The visitor merely valiates the slice.
263+
struct ValidatingSliceVisitor;
264+
265+
impl<T: ToOwned + ?Sized> SliceVisitor<'_, T> for ValidatingSliceVisitor {
266+
type Output = ();
267+
268+
fn visit<E: Error>(self, _: &T) -> Result<Self::Output, E> {
269+
Ok(())
270+
}
271+
}
272+
262273
/// The visitor converts the slice to its owned version.
263274
struct OwnedSliceVisitor;
264275

@@ -293,8 +304,16 @@ impl<const N: usize> SliceVisitor<'_, [u8]> for ByteArrayVisitor<N> {
293304
}
294305
}
295306

296-
impl_deserialize!([] &'de str, de => de.deserialize_str(BorrowedSliceVisitor));
297-
impl_deserialize!([] &'de [u8], de => de.deserialize_bytes(BorrowedSliceVisitor));
307+
impl_deserialize!(
308+
[] &'de str,
309+
de => de.deserialize_str_slice(),
310+
de => de.deserialize_str(ValidatingSliceVisitor)
311+
);
312+
impl_deserialize!(
313+
[] &'de [u8],
314+
de => de.deserialize_bytes(BorrowedSliceVisitor),
315+
de => de.deserialize_bytes(ValidatingSliceVisitor)
316+
);
298317

299318
/// The visitor returns the slice as-is and borrowed.
300319
pub(crate) struct BorrowedSliceVisitor;
@@ -610,7 +629,7 @@ impl<'de> DeserializeSeed<'de> for WithTypespace<'_, AlgebraicType> {
610629
AlgebraicType::U256 => u256::validate(de),
611630
AlgebraicType::F32 => f32::validate(de),
612631
AlgebraicType::F64 => f64::validate(de),
613-
AlgebraicType::String => <Box<str>>::validate(de),
632+
AlgebraicType::String => <&str>::validate(de),
614633
}
615634
}
616635
}

crates/table/proptest-regressions/table_index/mod.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ cc c1e4c959a32f6ab8ef9c4e29d39a24ec47cb03524584606a7f1fa4563f0f8cca # shrinks to
1010
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
1111
cc a166a3c619c7cae3938f4e0cfb4e7a96cddfbb7943efd0b74e8cbb99d7a1e6a8 # shrinks to (ty, cols, pv) = (ProductType {None: U8}, [ColId(0)], ProductValue { elements: [U8(0)] }), kind = Direct
1212
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
13+
cc 3b8115315ff39f3268c02ccd659b82444e6d0b12aae7e6c0feba956875dab5ab # shrinks to is_unique = false, (prefix_ty, prefix_val) = (Array(ArrayType { elem_ty: Product(ProductType {"field_0": String}) }), Array([ProductValue { elements: [String("")] }])), (middle_ty, [start, middle, end]) = (Bool, [Bool(false), Bool(false), Bool(false)]), (suffix_ty, suffix_val) = (Bool, Bool(false))
14+
cc aa8f01aec687cbe6ad36acc77c8c484ccd323a53dca132c6eb490104563d17af # shrinks to is_unique = false, index_kind = BTree, (prefix_ty, prefix_val) = (Bool, Bool(false)), (middle_ty, [included, excluded]) = (Product(ProductType {}), [Product(ProductValue { elements: [] }), Product(ProductValue { elements: [] })]), (suffix_ty, suffix_val) = (Bool, Bool(false))

crates/table/src/table_index/btree_index.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ impl<K: Ord + KeySize> Index for BTreeIndex<K> {
112112
// `self.insert` always returns `Ok(_)`.
113113
Ok(())
114114
}
115+
116+
const IS_RANGED: bool = true;
115117
}
116118

117119
impl<K: KeySize + Ord> BTreeIndex<K> {

crates/table/src/table_index/bytes_key.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ use core::cmp::Ordering;
55
use core::hash::{Hash, Hasher};
66
use core::mem;
77
use core::ops::Deref;
8-
use spacetimedb_lib::buffer::{CountWriter, TeeWriter};
8+
use spacetimedb_lib::de::ProductVisitor;
9+
use spacetimedb_lib::ser::{SerializeSeqProduct, Serializer as _};
910
use spacetimedb_memory_usage::MemoryUsage;
1011
use spacetimedb_primitives::ColList;
12+
use spacetimedb_sats::algebraic_value::de::{ValueDeserializeError, ValueDeserializer};
1113
use spacetimedb_sats::bsatn::{DecodeError, Deserializer, Serializer};
14+
use spacetimedb_sats::buffer::{CountWriter, TeeWriter};
1215
use spacetimedb_sats::de::{DeserializeSeed, Error as _};
13-
use spacetimedb_sats::{i256, u256, AlgebraicType, AlgebraicValue, ProductTypeElement, Serialize as _, WithTypespace};
16+
use spacetimedb_sats::{
17+
i256, u256, AlgebraicType, AlgebraicValue, ProductTypeElement, ProductValue, Serialize as _, WithTypespace,
18+
};
1419

1520
/// A key for an all-primitive multi-column index
1621
/// serialized to a byte array.
@@ -418,6 +423,66 @@ impl<const N: usize> RangeCompatBytesKey<N> {
418423
Self::from_bytes_key(key, ty)
419424
}
420425

426+
/// Decodes `prefix` in `AlgebraicValue` form to a [`RangeCompatBytesKey<N>`]
427+
/// by serializing the prefix and massaging.
428+
pub(super) fn from_algebraic_value_prefix(
429+
prefix: &ProductValue,
430+
prefix_types: &[ProductTypeElement],
431+
) -> Result<Self, ValueDeserializeError> {
432+
// Validate the prefix.
433+
WithTypespace::empty(prefix_types).validate_seq_product(ValueDeserializer::from_product_ref(prefix))?;
434+
435+
// Serialize the `prefix` and the `endpoint` over.
436+
let bytes = BytesKey::via_serializer(|ser| {
437+
prefix
438+
.serialize(ser)
439+
.expect("should've serialized to BSATN successfully");
440+
});
441+
let BytesKey { mut bytes, length } = bytes;
442+
443+
// Massage the bytes.
444+
let mut slice = bytes.as_mut_slice();
445+
for ty in prefix_types {
446+
slice = Self::process_from_bytes_key(slice, &ty.algebraic_type);
447+
}
448+
449+
Ok(Self::new(length as usize, bytes))
450+
}
451+
452+
/// Decodes `prefix` and `endpoint` in `AlgebraicValue` form to a [`RangeCompatBytesKey<N>`]
453+
/// by serializing over both and massaging if they fit into the key.
454+
pub(super) fn from_algebraic_value_prefix_and_endpoint(
455+
prefix: &ProductValue,
456+
prefix_types: &[ProductTypeElement],
457+
endpoint: &AlgebraicValue,
458+
range_type: &AlgebraicType,
459+
) -> Result<Self, ValueDeserializeError> {
460+
// Validate the values.
461+
WithTypespace::empty(prefix_types).validate_seq_product(ValueDeserializer::from_product_ref(prefix))?;
462+
WithTypespace::empty(range_type).validate(ValueDeserializer::from_ref(endpoint))?;
463+
464+
// Serialize the `prefix` and the `endpoint` over.
465+
let bytes = BytesKey::via_serializer(|ser| {
466+
(|| {
467+
let mut ser = ser.serialize_seq_product(2)?;
468+
ser.serialize_element(&prefix)?;
469+
ser.serialize_element(&endpoint)?;
470+
ser.end()
471+
})()
472+
.expect("should've serialized to BSATN successfully");
473+
});
474+
let BytesKey { mut bytes, length } = bytes;
475+
476+
// Massage the bytes.
477+
let mut slice = bytes.as_mut_slice();
478+
for ty in prefix_types {
479+
slice = Self::process_from_bytes_key(slice, &ty.algebraic_type);
480+
}
481+
Self::process_from_bytes_key(slice, range_type);
482+
483+
Ok(Self::new(length as usize, bytes))
484+
}
485+
421486
/// Serializes `av` to a [`BytesKey<N>`].
422487
///
423488
/// It's assumed that `av`

crates/table/src/table_index/hash_index.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ impl<K: KeySize + Eq + Hash> Index for HashIndex<K> {
118118
// `self.insert` always returns `Ok(_)`.
119119
Ok(())
120120
}
121+
122+
const IS_RANGED: bool = false;
121123
}
122124

123125
impl<K: KeySize + Eq + Hash> HashIndex<K> {

crates/table/src/table_index/index.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ pub trait Index {
133133
/// The trait imposes no particular order.
134134
/// Implementations may provide a non-deterministic order.
135135
fn iter(&self) -> Self::Iter<'_>;
136+
137+
/// Whether the index is ranged or not.
138+
const IS_RANGED: bool;
136139
}
137140

138141
pub trait RangedIndex: Index {

0 commit comments

Comments
 (0)