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/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..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, }; @@ -57,11 +57,12 @@ 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; use std::io; -use std::ops::{Bound, RangeBounds}; +use std::ops::RangeBounds; use std::sync::Arc; use tokio::sync::watch; @@ -1391,32 +1392,24 @@ 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)?) } - 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/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/committed_state.rs b/crates/datastore/src/locking_tx_datastore/committed_state.rs index 089b47e68f9..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,45 +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| i.seek_point(value)) } /// Returns the table associated with the given `index_id`, if any. 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 101a5feb753..a47819f5b0b 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,12 +29,10 @@ 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; -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}; @@ -50,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}, @@ -71,7 +64,7 @@ use spacetimedb_table::{ BlobNumBytes, DuplicateError, IndexScanPointIter, IndexScanRangeIter, InsertError, RowRef, Table, TableAndIndex, UniqueConstraintViolation, }, - table_index::{IndexCannotSeekRange, IndexSeekRangeResult, TableIndex}, + table_index::{IndexCannotSeekRange, IndexKey, IndexSeekRangeResult, PointOrRange, TableIndex}, }; use std::{ marker::PhantomData, @@ -79,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, @@ -277,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); }; } @@ -294,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. @@ -316,10 +302,10 @@ impl MutTxId { op: &FuncCallType, table_id: TableId, index_id: IndexId, - val: AlgebraicValue, + point: IndexKey<'_>, ) { if let FuncCallType::View(view) = op { - 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()); } @@ -447,7 +434,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()) } @@ -462,7 +449,14 @@ 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( + &self.tx_state, + table_id, + tx_index, + commit_index, + &point, + )) } } @@ -1321,48 +1315,38 @@ 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(&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>, - 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)); 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) } @@ -1372,46 +1356,66 @@ 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_inner(table_id, tx_index, commit_index, &bounds) - .map_err(|IndexCannotSeekRange| IndexError::IndexCannotSeekRange(index_id))?; + // Decode the bounds. + let bounds = commit_index + .index() + .bounds_from_bsatn(prefix, prefix_elems, rstart, rend) + .map_err(IndexError::Decode)?; - let (lower, upper) = bounds; - Ok((table_id, lower, upper, iter)) + // 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`]. #[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(); @@ -1424,7 +1428,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)) } @@ -1448,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, @@ -1556,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) @@ -2893,10 +2809,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 +2924,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)); }; @@ -3205,23 +3126,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"))] @@ -3254,17 +3175,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 5e06ca24b80..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()) } @@ -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..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(point)) } /// Returns the table for `table_id` combined with the index for `index_id`, if both exist. 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/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/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 {} 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..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(&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/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/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 7a29f32d530..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, 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}; @@ -560,12 +555,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 +911,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 +954,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 +1250,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 +1263,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) }; } } @@ -1757,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`]. @@ -1766,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(); @@ -1915,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) } } } @@ -2098,7 +2114,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, @@ -2110,9 +2126,9 @@ 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<'b>( &self, - range: &impl RangeBounds, + range: &impl RangeBounds>, ) -> Result, IndexCannotSeekRange> { Ok(IndexScanRangeIter { table: self.table, @@ -2120,6 +2136,33 @@ impl<'a> TableAndIndex<'a> { 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`. + 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. + /// + /// Matching is defined by `Ord for AlgebraicValue`. + 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, + }) + } } /// An iterator using a [`TableIndex`] to scan a `table` @@ -2232,14 +2275,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/multimap.rs b/crates/table/src/table_index/btree_index.rs similarity index 71% rename from crates/table/src/table_index/multimap.rs rename to crates/table/src/table_index/btree_index.rs index b1ab1361072..aefbbbb9e37 100644 --- a/crates/table/src/table_index/multimap.rs +++ b/crates/table/src/table_index/btree_index.rs @@ -1,13 +1,14 @@ 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}; /// 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 +22,7 @@ pub struct MultiMap { num_key_bytes: u64, } -impl Default for MultiMap { +impl Default for BTreeIndex { fn default() -> Self { Self { map: <_>::default(), @@ -31,7 +32,7 @@ impl Default for MultiMap { } } -impl MemoryUsage for MultiMap { +impl MemoryUsage for BTreeIndex { fn heap_usage(&self) -> usize { let Self { map, @@ -42,7 +43,7 @@ impl MemoryUsage for MultiMap { } } -impl Index for MultiMap { +impl Index for BTreeIndex { type Key = K; fn clone_structure(&self) -> Self { @@ -56,7 +57,7 @@ impl Index for MultiMap { /// 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 MultiMap { /// /// 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 MultiMap { 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,32 +104,80 @@ impl Index for MultiMap { } } -impl RangedIndex for MultiMap { +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> - = 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 { + 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(), } } } -/// 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/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/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..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; @@ -9,10 +11,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 +24,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 +33,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 +81,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 +128,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() @@ -202,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 a467f87efc8..26464c695ba 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`. //! @@ -12,74 +12,71 @@ //! 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. +//! +//! 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::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}; 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; 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::{ - algebraic_value::Packed, 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; -mod multimap; 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 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>; +type DecodeResult = Result; /// 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(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(), } } } @@ -107,44 +104,45 @@ 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, u128>), + BTreeI128(BTreeIndexRangeIter<'a, i128>), + BTreeU256(BTreeIndexRangeIter<'a, u256>), + BTreeI256(BTreeIndexRangeIter<'a, i256>), + BTreeF32(BTreeIndexRangeIter<'a, F32>), + BTreeF64(BTreeIndexRangeIter<'a, F64>), + BTreeString(BTreeIndexRangeIter<'a, Box>), + BTreeAV(BTreeIndexRangeIter<'a, AlgebraicValue>), + BTreeBytesKey8(BTreeIndexRangeIter<'a, BytesKey<8>>), // 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, u128>), + UniqueBTreeI128(UniqueBTreeIndexRangeIter<'a, i128>), + 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>), @@ -156,43 +154,44 @@ 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::BTreeBytesKey8(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(), @@ -222,30 +221,377 @@ 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`]. +#[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>), + + 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> { + /// 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, mut bytes: &'a [u8]) -> DecodeResult { + let reader = &mut bytes; + + use TypedIndex::*; + match index { + BTreeBool(_) | HashBool(_) | UniqueBTreeBool(_) | UniqueHashBool(_) => from_reader(reader).map(Self::Bool), + + BTreeU8(_) | HashU8(_) | UniqueBTreeU8(_) | UniqueHashU8(_) | UniqueDirectU8(_) => { + from_reader(reader).map(Self::U8) + } + BTreeSumTag(_) | HashSumTag(_) | UniqueBTreeSumTag(_) | UniqueHashSumTag(_) | UniqueDirectSumTag(_) => { + from_reader(reader).map(Self::SumTag) + } + BTreeU16(_) | HashU16(_) | UniqueBTreeU16(_) | UniqueHashU16(_) | UniqueDirectU16(_) => { + from_reader(reader).map(Self::U16) + } + BTreeU32(_) | HashU32(_) | UniqueBTreeU32(_) | UniqueHashU32(_) | UniqueDirectU32(_) => { + from_reader(reader).map(Self::U32) + } + BTreeU64(_) | HashU64(_) | UniqueBTreeU64(_) | UniqueHashU64(_) | UniqueDirectU64(_) => { + from_reader(reader).map(Self::U64) + } + 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_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_reader(reader).map(Self::F32), + BTreeF64(_) | HashF64(_) | UniqueBTreeF64(_) | UniqueHashF64(_) => from_reader(reader).map(Self::F64), + + BTreeString(_) | HashString(_) | UniqueBTreeString(_) | UniqueHashString(_) => { + from_reader(reader).map(BowStr::Borrowed).map(Self::String) + } + + BTreeAV(_) | HashAV(_) | UniqueBTreeAV(_) | UniqueHashAV(_) => { + AlgebraicValue::decode(ty, reader).map(CowAV::Owned).map(Self::AV) + } + + BTreeBytesKey8(_) => BytesKey::from_bsatn(bytes).map(Self::BytesKey8), + } + } + + /// 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)) + } + + BTreeBytesKey8(_) => Self::BytesKey8(BytesKey::from_row_ref(cols, row_ref)), + } + } + + /// 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()), + 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), + } + } + + /// 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(), + 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!(), + } + } +} + +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. #[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), + // TODO(perf, centril): consider `UmbraString` or some "German string". + BTreeString(BTreeIndex>), + BTreeAV(BTreeIndex), + BTreeBytesKey8(BTreeIndex>), // All the non-unique hash index types. HashBool(HashIndex), @@ -258,34 +604,36 @@ 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), // 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), + // TODO(perf, centril): consider `UmbraString` or some "German string". + UniqueBTreeString(UniqueBTreeIndex>), + UniqueBTreeAV(UniqueBTreeIndex), // All the unique hash index types. UniqueHashBool(UniqueHashIndex), @@ -298,12 +646,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), @@ -320,24 +669,25 @@ 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::BTreeBytesKey8($this) => $body, Self::HashBool($this) => $body, Self::HashU8($this) => $body, @@ -358,24 +708,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, @@ -411,15 +761,7 @@ 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)] +#[derive(Debug, PartialEq, derive_more::From)] #[cfg_attr(test, derive(proptest_derive::Arbitrary))] pub enum IndexKind { BTree, @@ -481,57 +823,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()), } } } @@ -609,29 +951,30 @@ 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(_) - | 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(_) + BTreeBool(_) | BTreeU8(_) | BTreeSumTag(_) | BTreeI8(_) | BTreeU16(_) | BTreeI16(_) | BTreeU32(_) + | BTreeI32(_) | BTreeU64(_) | BTreeI64(_) | BTreeU128(_) | BTreeI128(_) | BTreeU256(_) | BTreeI256(_) + | 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(_) + | UniqueBTreeI8(_) + | UniqueBTreeU16(_) + | UniqueBTreeI16(_) + | UniqueBTreeU32(_) + | UniqueBTreeI32(_) + | UniqueBTreeU64(_) + | UniqueBTreeI64(_) + | UniqueBTreeU128(_) + | UniqueBTreeI128(_) + | UniqueBTreeU256(_) + | UniqueBTreeI256(_) + | UniqueBTreeF32(_) + | UniqueBTreeF64(_) + | UniqueBTreeString(_) + | UniqueBTreeAV(_) | UniqueHashBool(_) | UniqueHashU8(_) | UniqueHashSumTag(_) @@ -658,55 +1001,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)] @@ -714,116 +1014,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>, + 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 { @@ -833,217 +1124,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) => 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)), + 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 { @@ -1057,15 +1320,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::*; @@ -1111,49 +1373,56 @@ 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::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))), + 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))), }) } @@ -1192,6 +1461,27 @@ impl TypedIndex { } } +/// A key into a [`TableIndex`]. +#[derive(derive_more::From)] +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() + } +} + +/// 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 { @@ -1256,6 +1546,198 @@ 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> { + 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]) -> 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`. + /// + /// # 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. + unsafe { TypedIndexKey::from_row_ref(&self.idx, &self.indexed_columns, row_ref) }.into() + } + /// Inserts `ptr` with the value `row` to this index. /// This index will extract the necessary values from `row` based on `self.indexed_columns`. /// @@ -1269,52 +1751,65 @@ 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), - } + #[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], /// 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> { - Ok(TableIndexRangeIter { - iter: self.idx.seek_range(range)?, - }) + 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 }) } /// Extends [`TableIndex`] with `rows`. @@ -1345,24 +1840,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(_)) @@ -1382,24 +1877,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), @@ -1479,6 +1974,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, @@ -1529,7 +2025,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 { @@ -1560,6 +2058,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() })] @@ -1568,7 +2075,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] @@ -1578,7 +2085,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); @@ -1603,7 +2110,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); @@ -1707,7 +2214,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 { @@ -1724,7 +2231,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(&index.key_from_algebraic_value(&V(x))).collect(), &val_to_ptr, [x])?; } // Test `..` (`RangeFull`). @@ -1799,15 +2306,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, []); } } diff --git a/crates/table/src/table_index/uniquemap.rs b/crates/table/src/table_index/unique_btree_index.rs similarity index 54% rename from crates/table/src/table_index/uniquemap.rs rename to crates/table/src/table_index/unique_btree_index.rs index fa77885d233..c2681d623ce 100644 --- a/crates/table/src/table_index/uniquemap.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}; @@ -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 { @@ -41,7 +41,7 @@ impl Index for UniqueMap { 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 UniqueMap { } } - 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 { @@ -66,13 +62,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. @@ -95,41 +90,86 @@ impl Index for UniqueMap { } } -/// An iterator over the potential value in a [`UniqueMap`] for a given key. -pub struct UniqueMapPointIter<'a> { +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. - 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() } } -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 { + 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())), } } } -/// 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 805d70fc83d..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 @@ -1,5 +1,6 @@ use super::index::{Index, RangedIndex}; -use super::unique_direct_index::{expose, injest, ToFromUsize, UniqueDirectIndexPointIter, NONE_PTR}; +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; use core::marker::PhantomData; @@ -79,13 +80,18 @@ 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) + let point = self + .array + .get(key.to_usize()) + .copied() + .filter(|slot| *slot != NONE_PTR) + .map(expose); + 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..b1939e11adc 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::{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; 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; @@ -238,8 +238,9 @@ impl Index for UniqueDirectIndex { .get(outer_key) .and_then(|x| x.as_ref()) .map(|inner| inner.get(inner_key)) - .filter(|slot| *slot != NONE_PTR); - UniqueDirectIndexPointIter::new(point) + .filter(|slot| *slot != NONE_PTR) + .map(expose); + UniquePointIter::new(point) } fn num_keys(&self) -> usize { @@ -315,25 +316,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> { @@ -380,8 +362,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 ba572d03085..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::table_index::uniquemap::UniqueMapPointIter; 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) { @@ -87,12 +84,36 @@ 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 } + 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()) } }