diff --git a/crates/bindings-sys/src/lib.rs b/crates/bindings-sys/src/lib.rs index a815967ea1..07f7dadaa2 100644 --- a/crates/bindings-sys/src/lib.rs +++ b/crates/bindings-sys/src/lib.rs @@ -214,6 +214,54 @@ pub mod raw { out: *mut u32, ) -> u16; + /// Deletes all rows found in the index identified by `index_id`, + /// according to the: + /// - `prefix = prefix_ptr[..prefix_len]`, + /// - `rstart = rstart_ptr[..rstart_len]`, + /// - `rend = rend_ptr[..rend_len]`, + /// in WASM memory. + /// + /// This syscall will delete all the rows found by + /// [`datastore_btree_scan_bsatn`] with the same arguments passed, + /// including `prefix_elems`. + /// See `datastore_btree_scan_bsatn` for details. + /// + /// The number of rows deleted is written to the WASM pointer `out`. + /// + /// # Traps + /// + /// Traps if: + /// - `prefix_elems > 0` + /// and (`prefix_ptr` is NULL or `prefix` is not in bounds of WASM memory). + /// - `rstart` is NULL or `rstart` is not in bounds of WASM memory. + /// - `rend` is NULL or `rend` is not in bounds of WASM memory. + /// - `out` is NULL or `out[..size_of::()]` is not in bounds of WASM memory. + /// + /// # Errors + /// + /// Returns an error: + /// + /// - `NOT_IN_TRANSACTION`, when called outside of a transaction. + /// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index. + /// - `WRONG_INDEX_ALGO` if the index is not a btree index. + /// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to + /// a `prefix_elems` number of `AlgebraicValue` + /// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type. + /// Or when `rstart` or `rend` cannot be decoded to an `Bound` + /// where the inner `AlgebraicValue`s are + /// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type. + pub fn _datastore_delete_by_btree_scan_bsatn( + index_id: IndexId, + prefix_ptr: *const u8, + prefix_len: usize, + prefix_elems: ColId, + rstart_ptr: *const u8, // Bound + rstart_len: usize, + rend_ptr: *const u8, // Bound + rend_len: usize, + out: *mut u32, + ) -> u16; + /// Deletes those rows, in the table identified by `table_id`, /// that match any row in the byte string `rel = rel_ptr[..rel_len]` in WASM memory. /// @@ -898,6 +946,53 @@ pub fn datastore_btree_scan_bsatn( Ok(RowIter { raw }) } +/// Deletes all rows found in the index identified by `index_id`, +/// according to the `prefix`, `rstart`, and `rend`. +/// +/// This syscall will delete all the rows found by +/// [`datastore_btree_scan_bsatn`] with the same arguments passed, +/// including `prefix_elems`. +/// See `datastore_btree_scan_bsatn` for details. +/// +/// The number of rows deleted is returned on success. +/// +/// # Errors +/// +/// Returns an error: +/// +/// - `NOT_IN_TRANSACTION`, when called outside of a transaction. +/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index. +/// - `WRONG_INDEX_ALGO` if the index is not a btree index. +/// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to +/// a `prefix_elems` number of `AlgebraicValue` +/// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type. +/// Or when `rstart` or `rend` cannot be decoded to an `Bound` +/// where the inner `AlgebraicValue`s are +/// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type. +pub fn datastore_delete_by_btree_scan_bsatn( + index_id: IndexId, + prefix: &[u8], + prefix_elems: ColId, + rstart: &[u8], + rend: &[u8], +) -> Result { + unsafe { + call(|out| { + raw::_datastore_delete_by_btree_scan_bsatn( + index_id, + prefix.as_ptr(), + prefix.len(), + prefix_elems, + rstart.as_ptr(), + rstart.len(), + rend.as_ptr(), + rend.len(), + out, + ) + }) + } +} + /// Iterate through a table, filtering by an encoded `spacetimedb_lib::filter::Expr`. /// /// # Errors diff --git a/crates/core/src/db/datastore/locking_tx_datastore/mut_tx.rs b/crates/core/src/db/datastore/locking_tx_datastore/mut_tx.rs index c55483c5c8..03d02c7824 100644 --- a/crates/core/src/db/datastore/locking_tx_datastore/mut_tx.rs +++ b/crates/core/src/db/datastore/locking_tx_datastore/mut_tx.rs @@ -514,7 +514,7 @@ impl MutTxId { prefix_elems: ColId, rstart: &[u8], rend: &[u8], - ) -> Result>> { + ) -> Result<(TableId, impl Iterator>)> { // Extract the table and index type for the tx state. let (table_id, col_list, tx_idx_key_type) = self .get_table_and_index_type(index_id) @@ -548,7 +548,7 @@ impl MutTxId { } } } - Ok(match commit_iter { + let iter = match commit_iter { None => Choice::A(tx_iter), Some(commit_iter) => match self.tx_state.delete_tables.get(&table_id) { None => Choice::B(tx_iter.chain(commit_iter)), @@ -556,7 +556,8 @@ impl MutTxId { Choice::C(tx_iter.chain(commit_iter.filter(move |row| !tx_dels.contains(&row.pointer())))) } }, - }) + }; + Ok((table_id, iter)) } /// Translate `index_id` to the table id, the column list and index key type. diff --git a/crates/core/src/db/relational_db.rs b/crates/core/src/db/relational_db.rs index 80dba4ef85..f5f692e2fa 100644 --- a/crates/core/src/db/relational_db.rs +++ b/crates/core/src/db/relational_db.rs @@ -1073,7 +1073,7 @@ impl RelationalDB { prefix_elems: ColId, rstart: &[u8], rend: &[u8], - ) -> Result>, DBError> { + ) -> Result<(TableId, impl Iterator>), DBError> { tx.btree_scan(index_id, prefix, prefix_elems, rstart, rend) } diff --git a/crates/core/src/host/instance_env.rs b/crates/core/src/host/instance_env.rs index 7329954dcd..406e136b9a 100644 --- a/crates/core/src/host/instance_env.rs +++ b/crates/core/src/host/instance_env.rs @@ -175,6 +175,27 @@ impl InstanceEnv { Ok(stdb.delete(tx, table_id, rows_to_delete)) } + #[tracing::instrument(skip_all)] + pub fn datastore_delete_by_btree_scan_bsatn( + &self, + index_id: IndexId, + prefix: &[u8], + prefix_elems: ColId, + rstart: &[u8], + rend: &[u8], + ) -> Result { + let stdb = &*self.dbic.relational_db; + let tx = &mut *self.tx.get()?; + + // Find all rows in the table to delete. + let (table_id, iter) = stdb.btree_scan(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::>(); + + // Delete them and count how many we deleted. + Ok(stdb.delete(tx, table_id, rows_to_delete)) + } + /// Deletes all rows in the table identified by `table_id` /// where the rows match one in `relation` /// which is a bsatn encoding of `Vec`. @@ -289,7 +310,7 @@ impl InstanceEnv { let stdb = &*self.dbic.relational_db; let tx = &mut *self.tx.get()?; - let iter = stdb.btree_scan(tx, index_id, prefix, prefix_elems, rstart, rend)?; + let (_, iter) = stdb.btree_scan(tx, index_id, prefix, prefix_elems, rstart, rend)?; let chunks = ChunkedWriter::collect_iter(iter); Ok(chunks) } diff --git a/crates/core/src/host/mod.rs b/crates/core/src/host/mod.rs index af773fe4b3..b4ae1836c0 100644 --- a/crates/core/src/host/mod.rs +++ b/crates/core/src/host/mod.rs @@ -149,6 +149,7 @@ pub enum AbiCall { RowIterBsatnAdvance, RowIterBsatnClose, DatastoreInsertBsatn, + DatastoreDeleteByBtreeScanBsatn, DatastoreDeleteAllByEqBsatn, BytesSourceRead, BytesSinkWrite, diff --git a/crates/core/src/host/wasmtime/wasm_instance_env.rs b/crates/core/src/host/wasmtime/wasm_instance_env.rs index 4fc2732da3..98f77c6905 100644 --- a/crates/core/src/host/wasmtime/wasm_instance_env.rs +++ b/crates/core/src/host/wasmtime/wasm_instance_env.rs @@ -824,6 +824,78 @@ impl WasmInstanceEnv { }) } + /// Deletes all rows found in the index identified by `index_id`, + /// according to the: + /// - `prefix = prefix_ptr[..prefix_len]`, + /// - `rstart = rstart_ptr[..rstart_len]`, + /// - `rend = rend_ptr[..rend_len]`, + /// in WASM memory. + /// + /// This syscall will delete all the rows found by + /// [`datastore_btree_scan_bsatn`] with the same arguments passed, + /// including `prefix_elems`. + /// See `datastore_btree_scan_bsatn` for details. + /// + /// The number of rows deleted is written to the WASM pointer `out`. + /// + /// # Traps + /// + /// Traps if: + /// - `prefix_elems > 0` + /// and (`prefix_ptr` is NULL or `prefix` is not in bounds of WASM memory). + /// - `rstart` is NULL or `rstart` is not in bounds of WASM memory. + /// - `rend` is NULL or `rend` is not in bounds of WASM memory. + /// - `out` is NULL or `out[..size_of::()]` is not in bounds of WASM memory. + /// + /// # Errors + /// + /// Returns an error: + /// + /// - `NOT_IN_TRANSACTION`, when called outside of a transaction. + /// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index. + /// - `WRONG_INDEX_ALGO` if the index is not a btree index. + /// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to + /// a `prefix_elems` number of `AlgebraicValue` + /// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type. + /// Or when `rstart` or `rend` cannot be decoded to an `Bound` + /// where the inner `AlgebraicValue`s are + /// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type. + pub fn datastore_delete_by_btree_scan_bsatn( + caller: Caller<'_, Self>, + index_id: u32, + prefix_ptr: WasmPtr, + prefix_len: u32, + prefix_elems: u32, + rstart_ptr: WasmPtr, // Bound + rstart_len: u32, + rend_ptr: WasmPtr, // Bound + rend_len: u32, + out: WasmPtr, + ) -> RtResult { + Self::cvt_ret(caller, AbiCall::DatastoreDeleteByBtreeScanBsatn, out, |caller| { + let prefix_elems = Self::convert_u32_to_col_id(prefix_elems)?; + + let (mem, env) = Self::mem_env(caller); + // Read the prefix and range start & end from WASM memory. + let prefix = if prefix_elems.idx() == 0 { + &[] + } else { + mem.deref_slice(prefix_ptr, prefix_len)? + }; + let rstart = mem.deref_slice(rstart_ptr, rstart_len)?; + let rend = mem.deref_slice(rend_ptr, rend_len)?; + + // Delete the relevant rows. + Ok(env.instance_env.datastore_delete_by_btree_scan_bsatn( + index_id.into(), + prefix, + prefix_elems, + rstart, + rend, + )?) + }) + } + /// Deletes those rows, in the table identified by `table_id`, /// that match any row in the byte string `rel = rel_ptr[..rel_len]` in WASM memory. ///