Skip to main content

spacetimedb/
table.rs

1use crate::{bsatn, rt::ExplicitNames, sys, DeserializeOwned, IterBuf, Serialize, SpacetimeType, TableId};
2use core::borrow::Borrow;
3use core::convert::Infallible;
4use core::fmt;
5use core::marker::PhantomData;
6pub use spacetimedb_lib::db::raw_def::v9::TableAccess;
7use spacetimedb_lib::{
8    buffer::{BufReader, Cursor, DecodeError},
9    AlgebraicValue,
10};
11use spacetimedb_lib::{FilterableValue, IndexScanRangeBoundsTerminator};
12pub use spacetimedb_primitives::{ColId, IndexId};
13
14/// Implemented for every `TableHandle` struct generated by the [`table`](macro@crate::table) macro.
15/// Contains methods that are present for every table, regardless of what unique constraints
16/// and indexes are present.
17///
18/// To get a `TableHandle`
19// TODO: should we rename this `TableHandle`? Documenting this, I think that's much clearer.
20pub trait Table: TableInternal + ExplicitNames {
21    /// The type of rows stored in this table.
22    type Row: SpacetimeType + Serialize + DeserializeOwned + Sized + 'static;
23
24    /// Returns the number of rows in this table.
25    ///
26    /// This reads datastore metadata, so it runs in constant time.
27    /// It also takes into account modifications by the current transaction.
28    fn count(&self) -> u64 {
29        count::<Self>()
30    }
31
32    /// Iterate over all rows of the table.
33    ///
34    /// For large tables, this can be a slow operation!
35    /// Prefer [filtering](RangedIndex::filter) a [`RangedIndex`] or [finding](UniqueColumn::find) a [`UniqueColumn`] if
36    /// possible.
37    ///
38    /// (This keeps track of changes made to the table since the start of this reducer invocation. For example, if rows have been deleted since the start of this reducer invocation, those rows will not be returned by `iter`. Similarly, inserted rows WILL be returned.)
39    #[inline]
40    fn iter(&self) -> impl Iterator<Item = Self::Row> {
41        let table_id = Self::table_id();
42        let iter = sys::datastore_table_scan_bsatn(table_id).expect("datastore_table_scan_bsatn() call failed");
43        TableIter::new(iter)
44    }
45
46    /// Inserts `row` into the table.
47    ///
48    /// The return value is the inserted row, with any auto-incrementing columns replaced with computed values.
49    /// The `insert` method always returns the inserted row,
50    /// even when the table contains no auto-incrementing columns.
51    ///
52    /// (The returned row is a copy of the row in the database.
53    /// Modifying this copy does not directly modify the database.
54    /// See [`UniqueColumn::update`] if you want to update the row.)
55    ///
56    /// May panic if inserting the row violates any constraints.
57    /// Callers which intend to handle constraint violation errors should instead use [`Self::try_insert`].
58    ///
59    /// Inserting an exact duplicate of a row already present in the table is a no-op,
60    /// as SpacetimeDB is a set-semantic database.
61    /// This is true even for tables with unique constraints;
62    /// inserting an exact duplicate of an already-present row will not panic.
63    #[track_caller]
64    fn insert(&self, row: Self::Row) -> Self::Row {
65        self.try_insert(row).unwrap_or_else(|e| panic!("{e}"))
66    }
67
68    /// The error type for this table for unique constraint violations. Will either be
69    /// [`UniqueConstraintViolation`] if the table has any unique constraints, or [`Infallible`]
70    /// otherwise.
71    type UniqueConstraintViolation: MaybeError<UniqueConstraintViolation>;
72
73    /// The error type for this table for auto-increment overflows. Will either be
74    /// [`AutoIncOverflow`] if the table has any auto-incrementing columns, or [`Infallible`]
75    /// otherwise.
76    type AutoIncOverflow: MaybeError<AutoIncOverflow>;
77
78    /// Counterpart to [`Self::insert`] which allows handling failed insertions.
79    ///
80    /// For tables with constraints, this method returns an `Err` when the insertion fails rather than panicking.
81    /// For tables without any constraints, [`Self::UniqueConstraintViolation`] and [`Self::AutoIncOverflow`]
82    /// will be [`std::convert::Infallible`], and this will be a more-verbose [`Self::insert`].
83    ///
84    /// Inserting an exact duplicate of a row already present in the table is a no-op and returns `Ok`,
85    /// as SpacetimeDB is a set-semantic database.
86    /// This is true even for tables with unique constraints;
87    /// inserting an exact duplicate of an already-present row will return `Ok`.
88    #[track_caller]
89    fn try_insert(&self, row: Self::Row) -> Result<Self::Row, TryInsertError<Self>> {
90        insert::<Self>(row, IterBuf::take())
91    }
92
93    /// Deletes a row equal to `row` from the table.
94    ///
95    /// Returns `true` if the row was present and has been deleted,
96    /// or `false` if the row was not present and therefore the tables have not changed.
97    ///
98    /// Unlike [`Self::insert`], there is no need to return the deleted row,
99    /// as it must necessarily have been exactly equal to the `row` argument.
100    /// No analogue to auto-increment placeholders exists for deletions.
101    ///
102    /// May panic if deleting the row violates any constraints.
103    fn delete(&self, row: Self::Row) -> bool {
104        // Note that as of writing deletion is infallible, but future work may define new constraints,
105        // e.g. foreign keys, which cause deletion to fail in some cases.
106        // If and when these new constraints are added,
107        // we should define `Self::ForeignKeyViolation`,
108        // analogous to [`Self::UniqueConstraintViolation`].
109
110        let relation = std::slice::from_ref(&row);
111        let buf = IterBuf::serialize(relation).unwrap();
112        let count = sys::datastore_delete_all_by_eq_bsatn(Self::table_id(), &buf).unwrap();
113        count > 0
114    }
115
116    /// Clears the table of all rows.
117    ///
118    /// Returns the number of rows that were deleted,
119    /// i.e., the value of [`self.count()`](Table::count) before this call.
120    fn clear(&self) -> u64 {
121        sys::datastore_clear(Self::table_id()).expect("datastore_clear() call failed")
122    }
123
124    // Re-integrates the BSATN of the `generated_cols` into `row`.
125    #[doc(hidden)]
126    fn integrate_generated_columns(row: &mut Self::Row, generated_cols: &[u8]);
127}
128
129#[doc(hidden)]
130#[inline]
131pub fn count<Tbl: Table>() -> u64 {
132    sys::datastore_table_row_count(Tbl::table_id()).expect("datastore_table_row_count() call failed")
133}
134
135#[doc(hidden)]
136pub trait TableInternal: Sized {
137    const TABLE_NAME: &'static str;
138    const TABLE_ACCESS: TableAccess = TableAccess::Private;
139    const UNIQUE_COLUMNS: &'static [u16];
140    const INDEXES: &'static [IndexDesc<'static>];
141    const PRIMARY_KEY: Option<u16> = None;
142    const SEQUENCES: &'static [u16];
143    const SCHEDULE: Option<ScheduleDesc<'static>> = None;
144    const IS_EVENT: bool = false;
145
146    /// Returns the ID of this table.
147    fn table_id() -> TableId;
148
149    fn get_default_col_values() -> Vec<ColumnDefault>;
150}
151
152/// Describe a named index with an index type over a set of columns identified by their IDs.
153#[derive(Clone, Copy)]
154pub struct IndexDesc<'a> {
155    pub source_name: &'a str,
156    pub accessor_name: &'a str,
157    pub algo: IndexAlgo<'a>,
158}
159
160#[derive(Clone, Copy)]
161pub enum IndexAlgo<'a> {
162    BTree { columns: &'a [u16] },
163    Hash { columns: &'a [u16] },
164    Direct { column: u16 },
165}
166
167pub struct ScheduleDesc<'a> {
168    pub reducer_or_procedure_name: &'a str,
169    pub scheduled_at_column: u16,
170}
171
172#[derive(Debug, Clone)]
173pub struct ColumnDefault {
174    pub col_id: u16,
175    pub value: AlgebraicValue,
176}
177
178/// A row operation was attempted that would violate a unique constraint.
179// TODO: add column name for better error message
180#[derive(Debug)]
181#[non_exhaustive]
182pub struct UniqueConstraintViolation;
183
184impl fmt::Display for UniqueConstraintViolation {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        write!(f, "duplicate unique column")
187    }
188}
189
190impl std::error::Error for UniqueConstraintViolation {}
191
192/// An auto-inc column overflowed its data type.
193#[derive(Debug)]
194#[non_exhaustive]
195// TODO: add column name for better error message
196pub struct AutoIncOverflow;
197
198impl fmt::Display for AutoIncOverflow {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        write!(f, "auto-inc sequence overflowed its column type")
201    }
202}
203
204impl std::error::Error for AutoIncOverflow {}
205
206/// The error type returned from [`Table::try_insert()`], signalling a constraint violation.
207pub enum TryInsertError<Tbl: Table> {
208    /// A [`UniqueConstraintViolation`].
209    ///
210    /// Returned from [`Table::try_insert`] if an attempted insertion
211    /// has the same value in a unique column as an already-present row.
212    ///
213    /// This variant is only possible if the table has at least one unique column,
214    /// and is otherwise [`std::convert::Infallible`].
215    UniqueConstraintViolation(Tbl::UniqueConstraintViolation),
216
217    /// An [`AutoIncOverflow`].
218    ///
219    /// Returned from [`Table::try_insert`] if an attempted insertion
220    /// advances an auto-inc sequence past the bounds of the column type.
221    ///
222    /// This variant is only possible if the table has at least one auto-inc column,
223    /// and is otherwise [`std::convert::Infallible`].
224    AutoIncOverflow(Tbl::AutoIncOverflow),
225}
226
227impl<Tbl: Table> fmt::Debug for TryInsertError<Tbl> {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        write!(f, "TryInsertError::<{}>::", Tbl::TABLE_NAME)?;
230        match self {
231            Self::UniqueConstraintViolation(e) => fmt::Debug::fmt(e, f),
232            Self::AutoIncOverflow(e) => fmt::Debug::fmt(e, f),
233        }
234    }
235}
236
237impl<Tbl: Table> fmt::Display for TryInsertError<Tbl> {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        write!(f, "insertion error on table `{}`:", Tbl::TABLE_NAME)?;
240        match self {
241            Self::UniqueConstraintViolation(e) => fmt::Display::fmt(e, f),
242            Self::AutoIncOverflow(e) => fmt::Display::fmt(e, f),
243        }
244    }
245}
246
247impl<Tbl: Table> std::error::Error for TryInsertError<Tbl> {
248    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
249        Some(match self {
250            Self::UniqueConstraintViolation(e) => e,
251            Self::AutoIncOverflow(e) => e,
252        })
253    }
254}
255
256impl<Tbl: Table> From<TryInsertError<Tbl>> for String {
257    fn from(err: TryInsertError<Tbl>) -> Self {
258        err.to_string()
259    }
260}
261
262#[doc(hidden)]
263pub trait MaybeError<E = Self>: std::error::Error + Send + Sync + Sized + 'static {
264    fn get() -> Option<Self>;
265}
266
267impl<E> MaybeError<E> for Infallible {
268    fn get() -> Option<Self> {
269        None
270    }
271}
272
273impl MaybeError for UniqueConstraintViolation {
274    fn get() -> Option<Self> {
275        Some(UniqueConstraintViolation)
276    }
277}
278
279impl MaybeError for AutoIncOverflow {
280    fn get() -> Option<AutoIncOverflow> {
281        Some(AutoIncOverflow)
282    }
283}
284
285pub trait Column {
286    type Table: Table;
287    type ColType: SpacetimeType + Serialize + DeserializeOwned;
288    const COLUMN_NAME: &'static str;
289    fn get_field(row: &<Self::Table as Table>::Row) -> &Self::ColType;
290}
291
292/// A marker trait for columns that are the primary key of their table.
293///
294/// This is used to restrict [`UniqueColumn::update`] to only work on primary key columns.
295pub trait PrimaryKey {}
296
297/// A handle to a unique index on a column.
298/// Available for `#[unique]` and `#[primary_key]` columns.
299///
300/// For a table *table* with a column *column*, use `ctx.db.{table}().{column}()`
301/// to get a `UniqueColumn` from a [`ReducerContext`](crate::ReducerContext).
302///
303/// Example:
304///
305/// ```no_run
306/// # #[cfg(target_arch = "wasm32")] mod demo {
307/// use spacetimedb::{table, UniqueColumn, ReducerContext, DbContext};
308///
309/// #[table(accessor = user)]
310/// struct User {
311///     #[primary_key]
312///     id: u32,
313///     #[unique]
314///     username: String,
315///     dog_count: u64
316/// }
317///
318/// fn demo(ctx: &ReducerContext) {
319///     let user = ctx.db().user();
320///
321///     let by_id: UniqueColumn<_, u32, _> = user.id();
322///
323///     let mut example_user: User = by_id.find(357).unwrap();
324///     example_user.dog_count += 5;
325///     by_id.update(example_user);
326///
327///     let by_username: UniqueColumn<_, String, _> = user.username();
328///     by_username.delete(&"Evil Bob".to_string());
329/// }
330/// # }
331/// ```
332///
333/// <!-- TODO: do we need integer type suffixes on literal arguments, like for RangedIndex? -->
334pub struct UniqueColumn<Tbl, ColType, Col> {
335    _marker: PhantomData<(Tbl, ColType, Col)>,
336}
337
338impl<Tbl: Table, Col: Index + Column<Table = Tbl>> UniqueColumn<Tbl, Col::ColType, Col> {
339    #[doc(hidden)]
340    pub const __NEW: Self = Self { _marker: PhantomData };
341
342    /// Finds and returns the row where the value in the unique column matches the supplied `col_val`,
343    /// or `None` if no such row is present in the database state.
344    //
345    // TODO: consider whether we should accept the sought value by ref or by value.
346    // Should be consistent with the implementors of `IndexScanRangeBounds` (see below).
347    // By-value makes passing `Copy` fields more convenient,
348    // whereas by-ref makes passing `!Copy` fields more performant.
349    // Can we do something smart with `std::borrow::Borrow`?
350    #[inline]
351    pub fn find(&self, col_val: impl Borrow<Col::ColType>) -> Option<Tbl::Row>
352    where
353        for<'a> &'a Col::ColType: FilterableValue,
354    {
355        find::<Tbl, Col>(col_val.borrow())
356    }
357
358    /// Deletes the row where the value in the unique column matches the supplied `col_val`,
359    /// if any such row is present in the database state.
360    ///
361    /// Returns `true` if a row with the specified `col_val` was previously present and has been deleted,
362    /// or `false` if no such row was present.
363    #[inline]
364    pub fn delete(&self, col_val: impl Borrow<Col::ColType>) -> bool {
365        self._delete(col_val.borrow()).0
366    }
367
368    fn _delete(&self, col_val: &Col::ColType) -> (bool, IterBuf) {
369        let index_id = Col::index_id();
370        let point = IterBuf::serialize(col_val).unwrap();
371        let n_del = sys::datastore_delete_by_index_scan_point_bsatn(index_id, &point).unwrap_or_else(|e| {
372            panic!("unique: unexpected error from datastore_delete_by_index_scan_point_bsatn: {e}")
373        });
374
375        (n_del > 0, point)
376    }
377
378    /// Deletes the row where the value in the unique column matches that in the corresponding field of `new_row`, and
379    /// then inserts the `new_row`.
380    ///
381    /// Returns the new row as actually inserted, with computed values substituted for any auto-inc placeholders.
382    ///
383    /// This method can only be called on primary key columns, not any unique column.
384    /// This prevents confusion regarding what constitutes a row update vs. a delete+insert.
385    /// To perform this operation for a non-primary unique column, call
386    /// `.delete(key)` followed by `.insert(row)`.
387    ///
388    /// # Panics
389    /// Panics if no row was previously present with the matching value in the unique column,
390    /// or if either the delete or the insertion would violate a constraint.
391    #[track_caller]
392    pub fn update(&self, new_row: Tbl::Row) -> Tbl::Row
393    where
394        Col: PrimaryKey,
395    {
396        let buf = IterBuf::take();
397        update::<Tbl>(Col::index_id(), new_row, buf)
398    }
399
400    /// Inserts `new_row` into the table, first checking for an existing
401    /// row with a matching value in the unique column and deleting it if present.
402    ///
403    /// Be careful: in case of a constraint violation, this method will return Err,
404    /// but the previous row will be deleted. If you propagate the error, SpacetimeDB will
405    /// rollback the transaction and the old row will be restored. If you ignore the error,
406    /// the old row will be lost.
407    #[track_caller]
408    #[doc(alias = "try_upsert")]
409    #[cfg(feature = "unstable")]
410    pub fn try_insert_or_update(&self, new_row: Tbl::Row) -> Result<Tbl::Row, TryInsertError<Tbl>> {
411        let col_val = Col::get_field(&new_row);
412        // If the row doesn't exist, delete will return false, which we ignore.
413        let _ = self.delete(col_val);
414
415        // Then, insert the new row.
416        let buf = IterBuf::take();
417        insert::<Tbl>(new_row, buf)
418    }
419
420    /// Inserts `new_row` into the table, first checking for an existing
421    /// row with a matching value in the unique column and deleting it if present.
422    ///
423    /// # Panics
424    /// Panics if either the delete or the insertion would violate a constraint.
425    #[track_caller]
426    #[doc(alias = "upsert")]
427    #[cfg(feature = "unstable")]
428    pub fn insert_or_update(&self, new_row: Tbl::Row) -> Tbl::Row {
429        self.try_insert_or_update(new_row).unwrap_or_else(|e| panic!("{e}"))
430    }
431}
432
433#[inline]
434fn find<Tbl: Table, Col: Index + Column<Table = Tbl>>(col_val: &Col::ColType) -> Option<Tbl::Row> {
435    // Find the row with a match.
436    let index_id = Col::index_id();
437    let point = IterBuf::serialize(col_val).unwrap();
438
439    let iter = datastore_index_scan_point_bsatn(index_id, &point);
440    let mut iter = TableIter::new_with_buf(iter, point);
441
442    // We will always find either 0 or 1 rows here due to the unique constraint.
443    let row = iter.next();
444    assert!(
445        iter.is_exhausted(),
446        "`datastore_index_scan_point_bsatn` on unique field cannot return >1 rows"
447    );
448    row
449}
450
451/// See `sys::datastore_index_scan_point_bsatn`.
452/// Panics when the aforementioned errors.
453fn datastore_index_scan_point_bsatn(index_id: IndexId, point: &[u8]) -> sys::RowIter {
454    sys::datastore_index_scan_point_bsatn(index_id, point)
455        .unwrap_or_else(|e| panic!("unexpected error from `datastore_index_scan_point_bsatn`: {e}"))
456}
457
458/// A read-only handle to a unique (single-column) index.
459///
460/// This is the read-only version of [`UniqueColumn`].
461/// It mirrors [`UniqueColumn`] but only exposes read APIs.
462/// It cannot insert or delete rows.
463/// It is used by `{table}__ViewHandle` to keep view code read-only at compile time.
464///
465/// Note, the `Tbl` generic is the read-write table handle `{table}__TableHandle`.
466/// This is because read-only indexes still need [`Table`] metadata.
467/// The view handle itself deliberately does not implement `Table`.
468pub struct UniqueColumnReadOnly<Tbl, ColType, Col> {
469    _marker: PhantomData<(Tbl, ColType, Col)>,
470}
471
472impl<Tbl: Table, Col: Index + Column<Table = Tbl>> UniqueColumnReadOnly<Tbl, Col::ColType, Col> {
473    #[doc(hidden)]
474    pub const __NEW: Self = Self { _marker: PhantomData };
475
476    #[inline]
477    pub fn find(&self, col_val: impl Borrow<Col::ColType>) -> Option<Tbl::Row>
478    where
479        for<'a> &'a Col::ColType: FilterableValue,
480    {
481        find::<Tbl, Col>(col_val.borrow())
482    }
483}
484
485/// Information about the `index_id` of an index
486/// and the number of columns the index indexes.
487pub trait Index {
488    /// The number of columns the index indexes.
489    ///
490    /// Used to determine whether a scan for e.g., `(a, b)`,
491    /// is actually a point scan or whether there's a suffix, e.g., `(c, d)`.
492    const NUM_COLS_INDEXED: usize;
493
494    /// Determine the `IndexId` of this index.
495    ///
496    /// For generated implementations,
497    /// this results in a *memoized* syscall to determine the index,
498    /// based on the hard coded name of the index.
499    fn index_id() -> IndexId;
500}
501
502/// Marks an index as only having point query capabilities.
503///
504/// This applies to Hash indices but not BTree and Direct indices.
505pub trait IndexIsPointed: Index {}
506
507/// A handle to a Hash index on a table.
508///
509/// To get one of these from a `ReducerContext`, use:
510/// ```text
511/// ctx.db.{table}().{index}()
512/// ```
513/// for a table *table* and an index *index*.
514///
515/// Example:
516///
517/// ```no_run
518/// # #[cfg(target_arch = "wasm32")] mod demo {
519/// use spacetimedb::{table, DbContext, PointIndex, ReducerContext};
520///
521/// #[table(accessor = user,
522///     index(accessor = dogs_and_name, hash(columns = [dogs, name])))]
523/// struct User {
524///     id: u32,
525///     name: String,
526///     /// Number of dogs owned by the user.
527///     dogs: u64
528/// }
529///
530/// fn demo(ctx: &ReducerContext) {
531///     let by_dogs_and_name: PointIndex<_, (u64, String), _> = ctx.db.user().dogs_and_name();
532/// }
533/// # }
534/// ```
535///
536/// For single-column indexes, use the name of the column:
537///
538/// ```no_run
539/// # #[cfg(target_arch = "wasm32")] mod demo {
540/// use spacetimedb::{table, DbContext, RangedIndex, ReducerContext};
541///
542/// #[table(accessor = user)]
543/// struct User {
544///     id: u32,
545///     username: String,
546///     #[index(btree)]
547///     dogs: u64
548/// }
549///
550/// fn demo(ctx: &ReducerContext) {
551///     let by_dogs: RangedIndex<_, (u64,), _> = ctx.db().user().dogs();
552/// }
553/// # }
554/// ```
555///
556pub struct PointIndex<Tbl: Table, IndexType, Idx: Index> {
557    _marker: PhantomData<(Tbl, IndexType, Idx)>,
558}
559
560impl<Tbl: Table, IndexType, Idx: IndexIsPointed> PointIndex<Tbl, IndexType, Idx> {
561    #[doc(hidden)]
562    pub const __NEW: Self = Self { _marker: PhantomData };
563
564    /// Returns an iterator over all rows in the database state
565    /// where the indexed column(s) equal `point`.
566    ///
567    /// Unlike for ranged indices,
568    /// this method only accepts a `point` and not any prefix or range.
569    ///
570    /// For example:
571    ///
572    /// ```no_run
573    /// # #[cfg(target_arch = "wasm32")] mod demo {
574    /// use spacetimedb::{table, ReducerContext, PointIndex};
575    ///
576    /// #[table(accessor = user,
577    ///     index(accessor = dogs_and_name, hash(columns = [dogs, name])))]
578    /// struct User {
579    ///     id: u32,
580    ///     name: String,
581    ///     dogs: u64
582    /// }
583    ///
584    /// fn demo(ctx: &ReducerContext) {
585    ///     let by_dogs_and_name: PointIndex<_, (u64, String), _> = ctx.db.user().dogs_and_name();
586    ///
587    ///     // Find user with exactly 25 dogs and exactly the name "Joseph".
588    ///     for user in by_dogs_and_name.filter((25u64, "Joseph")) {
589    ///         /* ... */
590    ///     }
591    ///
592    ///     // You can also pass arguments by reference if desired.
593    ///     for user in by_dogs_and_name.filter((&25u64, &"Joseph".to_string())) {
594    ///         /* ... */
595    ///     }
596    /// }
597    /// # }
598    /// ```
599    pub fn filter<P, K>(&self, point: P) -> impl Iterator<Item = Tbl::Row> + use<P, K, Tbl, IndexType, Idx>
600    where
601        P: WithPointArg<K>,
602    {
603        filter_point::<Tbl, Idx, K>(point)
604    }
605
606    /// Deletes all rows in the database state
607    /// where the indexed column(s) equal `point`.
608    ///
609    /// Unlike for ranged indices,
610    /// this method only accepts a `point` and not any prefix or range.
611    ///
612    /// For example:
613    ///
614    /// ```no_run
615    /// # #[cfg(target_arch = "wasm32")] mod demo {
616    /// use spacetimedb::{table, ReducerContext, PointIndex};
617    ///
618    /// #[table(accessor = user,
619    ///     index(accessor = dogs_and_name, hash(columns = [dogs, name])))]
620    /// struct User {
621    ///     id: u32,
622    ///     name: String,
623    ///     dogs: u64
624    /// }
625    ///
626    /// fn demo(ctx: &ReducerContext) {
627    ///     let by_dogs_and_name: PointIndex<_, (u64, String), _> = ctx.db.user().dogs_and_name();
628    ///
629    ///     // Delete users with exactly 25 dogs, and exactly the name "Joseph".
630    ///     by_dogs_and_name.delete((25u64, "Joseph"));
631    ///
632    ///     // You can also pass arguments by reference if desired.
633    ///     by_dogs_and_name.delete((&25u64, &"Joseph".to_string()));
634    /// }
635    /// # }
636    /// ```
637    ///
638    /// May panic if deleting any one of the rows would violate a constraint,
639    /// though at present no such constraints exist.
640    pub fn delete<P, K>(&self, point: P) -> u64
641    where
642        P: WithPointArg<K>,
643    {
644        let index_id = Idx::index_id();
645        point.with_point_arg(|point| {
646            sys::datastore_delete_by_index_scan_point_bsatn(index_id, point)
647                .unwrap_or_else(|e| panic!("unexpected error from `datastore_delete_by_index_scan_point_bsatn`: {e}"))
648                .into()
649        })
650    }
651}
652
653/// Scans `Tbl` for `point` using the index `Idx`.
654///
655/// The type parameter `K` is either `()` or [`SingleBound`]
656/// and is used to workaround the orphan rule.
657fn filter_point<Tbl, Idx, K>(point: impl WithPointArg<K>) -> impl Iterator<Item = Tbl::Row>
658where
659    Tbl: Table,
660    Idx: IndexIsPointed,
661{
662    let index_id = Idx::index_id();
663    let iter = point.with_point_arg(|point| datastore_index_scan_point_bsatn(index_id, point));
664    TableIter::new(iter)
665}
666
667/// A read-only handle to a Hash index.
668///
669/// This is the read-only version of [`PointIndex`].
670/// It mirrors [`PointIndex`] but exposes only `.filter(..)`, not `.delete(..)`.
671/// It is used by `{table}__ViewHandle` to keep view code read-only at compile time.
672///
673/// Note, the `Tbl` generic is the read-write table handle `{table}__TableHandle`.
674/// This is because read-only indexes still need [`Table`] metadata.
675/// The view handle itself deliberately does not implement `Table`.
676pub struct PointIndexReadOnly<Tbl: Table, IndexType, Idx: Index> {
677    _marker: PhantomData<(Tbl, IndexType, Idx)>,
678}
679
680impl<Tbl: Table, IndexType, Idx: IndexIsPointed> PointIndexReadOnly<Tbl, IndexType, Idx> {
681    #[doc(hidden)]
682    pub const __NEW: Self = Self { _marker: PhantomData };
683
684    pub fn filter<P, K>(&self, point: P) -> impl Iterator<Item = Tbl::Row> + use<P, K, Tbl, IndexType, Idx>
685    where
686        P: WithPointArg<K>,
687    {
688        filter_point::<Tbl, Idx, K>(point)
689    }
690}
691
692/// Trait used for running point index scans.
693///
694/// The type parameter `K` is either `()` or [`SingleBound`]
695/// and is used to workaround the orphan rule.
696pub trait WithPointArg<K = ()> {
697    /// Runs `run` with the BSATN-serialized point to pass to the index scan.
698    // TODO(perf, centril): once we have stable specialization,
699    // just use `to_le_bytes` internally instead.
700    #[doc(hidden)]
701    fn with_point_arg<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R;
702}
703
704impl<Arg: FilterableValue> WithPointArg<SingleBound> for Arg {
705    fn with_point_arg<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R {
706        run(&IterBuf::serialize(self).unwrap())
707    }
708}
709
710macro_rules! impl_with_point_arg {
711    ($($arg:ident),+) => {
712        impl<$($arg: FilterableValue),+> WithPointArg for ($($arg,)+) {
713            fn with_point_arg<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R {
714                // We can assume here that we have a point bound.
715                let mut data = IterBuf::take();
716
717                // Destructure the argument tuple into variables with the same names as their types.
718                #[allow(non_snake_case)]
719                let ($($arg,)+) = self;
720
721                // For each part in the tuple queried, serialize it into the `data` buffer.
722                Ok(())
723                    $(.and_then(|()| data.serialize_into($arg)))+
724                    .unwrap();
725
726                run(&*data)
727            }
728        }
729    };
730}
731
732impl_with_point_arg!(A);
733impl_with_point_arg!(A, B);
734impl_with_point_arg!(A, B, C);
735impl_with_point_arg!(A, B, C, D);
736impl_with_point_arg!(A, B, C, D, E);
737impl_with_point_arg!(A, B, C, D, E, F);
738
739/// Marks an index as having range query capabilities.
740///
741/// This applies to BTree and Direct indices but not Hash indices.
742pub trait IndexIsRanged: Index {}
743
744/// A handle to a B-Tree or Direct index on a table.
745///
746/// To get one of these from a `ReducerContext`, use:
747/// ```text
748/// ctx.db.{table}().{index}()
749/// ```
750/// for a table *table* and an index *index*.
751///
752/// Example:
753///
754/// ```no_run
755/// # #[cfg(target_arch = "wasm32")] mod demo {
756/// use spacetimedb::{table, RangedIndex, ReducerContext, DbContext};
757///
758/// #[table(accessor = user,
759///     index(accessor = dogs_and_name, btree(columns = [dogs, name])))]
760/// struct User {
761///     id: u32,
762///     name: String,
763///     /// Number of dogs owned by the user.
764///     dogs: u64
765/// }
766///
767/// fn demo(ctx: &ReducerContext) {
768///     let by_dogs_and_name: RangedIndex<_, (u64, String), _> = ctx.db.user().dogs_and_name();
769/// }
770/// # }
771/// ```
772///
773/// For single-column indexes, use the name of the column:
774///
775/// ```no_run
776/// # #[cfg(target_arch = "wasm32")] mod demo {
777/// use spacetimedb::{table, RangedIndex, ReducerContext, DbContext};
778///
779/// #[table(accessor = user)]
780/// struct User {
781///     id: u32,
782///     username: String,
783///     #[index(btree)]
784///     dogs: u64
785/// }
786///
787/// fn demo(ctx: &ReducerContext) {
788///     let by_dogs: RangedIndex<_, (u64,), _> = ctx.db().user().dogs();
789/// }
790/// # }
791/// ```
792///
793pub struct RangedIndex<Tbl: Table, IndexType, Idx: IndexIsRanged> {
794    _marker: PhantomData<(Tbl, IndexType, Idx)>,
795}
796
797impl<Tbl: Table, IndexType, Idx: IndexIsRanged> RangedIndex<Tbl, IndexType, Idx> {
798    #[doc(hidden)]
799    pub const __NEW: Self = Self { _marker: PhantomData };
800
801    /// Returns an iterator over all rows in the database state where the indexed column(s) match the bounds `b`.
802    ///
803    /// This method accepts a variable numbers of arguments using the [`IndexScanRangeBounds`] trait.
804    /// This depends on the type of the B-Tree index. `b` may be:
805    /// - A value for the first indexed column.
806    /// - A range of values for the first indexed column.
807    /// - A tuple of values for any prefix of the indexed columns, optionally terminated by a range for the next.
808    ///
809    /// For example:
810    ///
811    /// ```no_run
812    /// # #[cfg(target_arch = "wasm32")] mod demo {
813    /// use spacetimedb::{table, ReducerContext, RangedIndex};
814    ///
815    /// #[table(accessor = user,
816    ///     index(accessor = dogs_and_name, btree(columns = [dogs, name])))]
817    /// struct User {
818    ///     id: u32,
819    ///     name: String,
820    ///     dogs: u64
821    /// }
822    ///
823    /// fn demo(ctx: &ReducerContext) {
824    ///     let by_dogs_and_name: RangedIndex<_, (u64, String), _> = ctx.db.user().dogs_and_name();
825    ///
826    ///     // Find user with exactly 25 dogs.
827    ///     for user in by_dogs_and_name.filter(25u64) { // The `u64` is required, see below.
828    ///         /* ... */
829    ///     }
830    ///
831    ///     // Find user with at least 25 dogs.
832    ///     for user in by_dogs_and_name.filter(25u64..) {
833    ///         /* ... */
834    ///     }
835    ///
836    ///     // Find user with exactly 25 dogs, and a name beginning with "J".
837    ///     for user in by_dogs_and_name.filter((25u64, "J".."K")) {
838    ///         /* ... */
839    ///     }
840    ///
841    ///     // Find user with exactly 25 dogs, and exactly the name "Joseph".
842    ///     for user in by_dogs_and_name.filter((25u64, "Joseph")) {
843    ///         /* ... */
844    ///     }
845    ///
846    ///     // You can also pass arguments by reference if desired.
847    ///     for user in by_dogs_and_name.filter((&25u64, &"Joseph".to_string())) {
848    ///         /* ... */
849    ///     }
850    /// }
851    /// # }
852    /// ```
853    ///
854    /// **NOTE:** An unfortunate interaction between Rust's trait solver and integer literal defaulting rules means that you must specify the types of integer literals passed to `filter` and `find` methods via the suffix syntax, like `21u32`.
855    ///
856    /// If you don't, you'll see a compiler error like:
857    /// > ```text
858    /// > error[E0271]: type mismatch resolving `<i32 as FilterableValue>::Column == u32`
859    /// >    --> modules/rust-wasm-test/src/lib.rs:356:48
860    /// >     |
861    /// > 356 |     for person in ctx.db.person().age().filter(21) {
862    /// >     |                                         ------ ^^ expected `u32`, found `i32`
863    /// >     |                                         |
864    /// >     |                                         required by a bound introduced by this call
865    /// >     |
866    /// >     = note: required for `i32` to implement `IndexScanRangeBounds<(u32,), SingleBound>`
867    /// > note: required by a bound in `RangedIndex::<Tbl, IndexType, Idx>::filter`
868    /// >     |
869    /// > 410 |     pub fn filter<B, K>(&self, b: B) -> impl Iterator<Item = Tbl::Row>
870    /// >     |            ------ required by a bound in this associated function
871    /// > 411 |     where
872    /// > 412 |         B: IndexScanRangeBounds<IndexType, K>,
873    /// >     |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RangedIndex::<Tbl, IndexType, Idx>::filter`
874    /// > ```
875    /// <!-- TODO: check if that error is up to date! -->
876    pub fn filter<B, K>(&self, b: B) -> impl Iterator<Item = Tbl::Row> + use<B, K, Tbl, IndexType, Idx>
877    where
878        B: IndexScanRangeBounds<IndexType, K>,
879    {
880        filter::<Tbl, Idx, IndexType, B, K>(b)
881    }
882
883    /// Deletes all rows in the database state where the indexed column(s) match the bounds `b`.
884    ///
885    /// This method accepts a variable numbers of arguments using the [`IndexScanRangeBounds`] trait.
886    /// This depends on the type of the B-Tree index. `b` may be:
887    /// - A value for the first indexed column.
888    /// - A range of values for the first indexed column.
889    /// - A tuple of values for any prefix of the indexed columns, optionally terminated by a range for the next.
890    ///
891    /// For example:
892    ///
893    /// ```no_run
894    /// # #[cfg(target_arch = "wasm32")] mod demo {
895    /// use spacetimedb::{table, ReducerContext, RangedIndex};
896    ///
897    /// #[table(accessor = user,
898    ///     index(accessor = dogs_and_name, btree(columns = [dogs, name])))]
899    /// struct User {
900    ///     id: u32,
901    ///     name: String,
902    ///     dogs: u64
903    /// }
904    ///
905    /// fn demo(ctx: &ReducerContext) {
906    ///     let by_dogs_and_name: RangedIndex<_, (u64, String), _> = ctx.db.user().dogs_and_name();
907    ///
908    ///     // Delete users with exactly 25 dogs.
909    ///     by_dogs_and_name.delete(25u64); // The `u64` is required, see below.
910    ///
911    ///     // Delete users with at least 25 dogs.
912    ///     by_dogs_and_name.delete(25u64..);
913    ///
914    ///     // Delete users with exactly 25 dogs, and a name beginning with "J".
915    ///     by_dogs_and_name.delete((25u64, "J".."K"));
916    ///
917    ///     // Delete users with exactly 25 dogs, and exactly the name "Joseph".
918    ///     by_dogs_and_name.delete((25u64, "Joseph"));
919    ///
920    ///     // You can also pass arguments by reference if desired.
921    ///     by_dogs_and_name.delete((&25u64, &"Joseph".to_string()));
922    /// }
923    /// # }
924    /// ```
925    ///
926    /// **NOTE:** An unfortunate interaction between Rust's trait solver and integer literal defaulting rules means that you must specify the types of integer literals passed to `filter` and `find` methods via the suffix syntax, like `21u32`.
927    ///
928    /// If you don't, you'll see a compiler error like:
929    /// > ```text
930    /// > error[E0271]: type mismatch resolving `<i32 as FilterableValue>::Column == u32`
931    /// >    --> modules/rust-wasm-test/src/lib.rs:356:48
932    /// >     |
933    /// > 356 |     for person in ctx.db.person().age().filter(21) {
934    /// >     |                                         ------ ^^ expected `u32`, found `i32`
935    /// >     |                                         |
936    /// >     |                                         required by a bound introduced by this call
937    /// >     |
938    /// >     = note: required for `i32` to implement `IndexScanRangeBounds<(u32,), SingleBound>`
939    /// > note: required by a bound in `RangedIndex::<Tbl, IndexType, Idx>::filter`
940    /// >     |
941    /// > 410 |     pub fn filter<B, K>(&self, b: B) -> impl Iterator<Item = Tbl::Row>
942    /// >     |            ------ required by a bound in this associated function
943    /// > 411 |     where
944    /// > 412 |         B: IndexScanRangeBounds<IndexType, K>,
945    /// >     |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RangedIndex::<Tbl, IndexType, Idx>::filter`
946    /// > ```
947    ///
948    /// May panic if deleting any one of the rows would violate a constraint,
949    /// though at present no such constraints exist.
950    pub fn delete<B, K>(&self, b: B) -> u64
951    where
952        B: IndexScanRangeBounds<IndexType, K>,
953    {
954        let index_id = Idx::index_id();
955        if const { is_point_scan::<Idx, B, _, _>() } {
956            b.with_point_arg(|point| {
957                sys::datastore_delete_by_index_scan_point_bsatn(index_id, point)
958                    .unwrap_or_else(|e| {
959                        panic!("unexpected error from `datastore_delete_by_index_scan_point_bsatn`: {e}")
960                    })
961                    .into()
962            })
963        } else {
964            let args = b.get_range_args();
965            let (prefix, prefix_elems, rstart, rend) = args.args_for_syscall();
966            sys::datastore_delete_by_index_scan_range_bsatn(index_id, prefix, prefix_elems, rstart, rend)
967                .unwrap_or_else(|e| panic!("unexpected error from `datastore_delete_by_index_scan_range_bsatn`: {e}"))
968                .into()
969        }
970    }
971}
972
973/// Performs a ranged scan using the range arguments `B` in `Tbl` using `Idx`.
974///
975/// The type parameter `K` is either `()` or [`SingleBound`]
976/// and is used to workaround the orphan rule.
977fn filter<Tbl, Idx, IndexType, B, K>(b: B) -> impl Iterator<Item = Tbl::Row>
978where
979    Tbl: Table,
980    Idx: Index,
981    B: IndexScanRangeBounds<IndexType, K>,
982{
983    let index_id = Idx::index_id();
984
985    let iter = if const { is_point_scan::<Idx, B, _, _>() } {
986        b.with_point_arg(|point| datastore_index_scan_point_bsatn(index_id, point))
987    } else {
988        let args = b.get_range_args();
989        let (prefix, prefix_elems, rstart, rend) = args.args_for_syscall();
990        sys::datastore_index_scan_range_bsatn(index_id, prefix, prefix_elems, rstart, rend)
991            .unwrap_or_else(|e| panic!("unexpected error from `datastore_index_scan_range_bsatn`: {e}"))
992    };
993
994    TableIter::new(iter)
995}
996
997/// A read-only handle to a B-tree or Direct index.
998///
999/// This is the read-only version of [`RangedIndex`].
1000/// It mirrors [`RangedIndex`] but exposes only `.filter(..)`, not `.delete(..)`.
1001/// It is used by `{table}__ViewHandle` to keep view code read-only at compile time.
1002///
1003/// Note, the `Tbl` generic is the read-write table handle `{table}__TableHandle`.
1004/// This is because read-only indexes still need [`Table`] metadata.
1005/// The view handle itself deliberately does not implement `Table`.
1006pub struct RangedIndexReadOnly<Tbl: Table, IndexType, Idx: Index> {
1007    _marker: PhantomData<(Tbl, IndexType, Idx)>,
1008}
1009
1010impl<Tbl: Table, IndexType, Idx: Index> RangedIndexReadOnly<Tbl, IndexType, Idx> {
1011    #[doc(hidden)]
1012    pub const __NEW: Self = Self { _marker: PhantomData };
1013
1014    pub fn filter<B, K>(&self, b: B) -> impl Iterator<Item = Tbl::Row> + use<B, K, Tbl, IndexType, Idx>
1015    where
1016        B: IndexScanRangeBounds<IndexType, K>,
1017    {
1018        filter::<Tbl, Idx, IndexType, B, K>(b)
1019    }
1020}
1021
1022/// Returns whether `B` is a point scan on `I`.
1023///
1024/// The type parameter `K` is either `()` or [`SingleBound`]
1025/// and is used to workaround the orphan rule.
1026const fn is_point_scan<I: Index, B: IndexScanRangeBounds<T, K>, T, K>() -> bool {
1027    B::POINT && B::COLS_PROVIDED == I::NUM_COLS_INDEXED
1028}
1029
1030/// Trait used for overloading methods on [`RangedIndex`].
1031/// See [`RangedIndex`] for more information.
1032///
1033/// The type parameter `K` is either `()` or [`SingleBound`]
1034/// and is used to workaround the orphan rule.
1035pub trait IndexScanRangeBounds<T, K = ()> {
1036    /// True if no range occurs in this range bounds.
1037    #[doc(hidden)]
1038    const POINT: bool;
1039
1040    /// The number of columns mentioned in this range bounds.
1041    /// For `(42, 12..24)` it's `2`.
1042    #[doc(hidden)]
1043    const COLS_PROVIDED: usize;
1044
1045    // TODO(perf, centril): once we have stable specialization,
1046    // just use `to_le_bytes` internally instead.
1047    #[doc(hidden)]
1048    fn with_point_arg<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R;
1049
1050    #[doc(hidden)]
1051    fn get_range_args(&self) -> IndexScanRangeArgs;
1052}
1053
1054#[doc(hidden)]
1055/// Arguments to one of the ranged-index-scan-related host-/sys-calls.
1056///
1057/// All pointers passed into the syscall are packed into a single buffer, `data`,
1058/// with slices taken at the appropriate offsets, to save allocatons in WASM.
1059pub struct IndexScanRangeArgs {
1060    data: IterBuf,
1061    prefix_elems: usize,
1062    rstart_idx: usize,
1063    // None if rstart and rend are the same
1064    rend_idx: Option<usize>,
1065}
1066
1067impl IndexScanRangeArgs {
1068    /// Get slices into `self.data` for the prefix, range start and range end.
1069    pub(crate) fn args_for_syscall(&self) -> (&[u8], ColId, &[u8], &[u8]) {
1070        let prefix = &self.data[..self.rstart_idx];
1071        let (rstart, rend) = if let Some(rend_idx) = self.rend_idx {
1072            (&self.data[self.rstart_idx..rend_idx], &self.data[rend_idx..])
1073        } else {
1074            let elem = &self.data[self.rstart_idx..];
1075            (elem, elem)
1076        };
1077        (prefix, ColId::from(self.prefix_elems), rstart, rend)
1078    }
1079}
1080
1081// Implement `IndexScanRangeBounds` for all the different index column types
1082// and filter argument types we support.
1083macro_rules! impl_index_scan_range_bounds {
1084    // In the first pattern, we accept two Prolog-style lists of type variables,
1085    // the first of which we use for the column types in the index,
1086    // and the second for the arguments supplied to the filter function.
1087    // We do our "outer recursion" to visit the sublists of these two lists,
1088    // at each step implementing the trait for indexes of that many columns.
1089    //
1090    // There's also an "inner recursion" later on, which, given a fixed number of columns,
1091    // implements the trait with the arguments being all the prefixes of that list.
1092    (($ColTerminator:ident $(, $ColPrefix:ident)*), ($ArgTerminator:ident $(, $ArgPrefix:ident)*)) => {
1093        // Implement the trait for all arguments N-column indexes.
1094        // The "inner recursion" described above happens in here.
1095        impl_index_scan_range_bounds!(@inner_recursion (), ($ColTerminator $(, $ColPrefix)*), ($ArgTerminator $(, $ArgPrefix)*));
1096
1097        // Recurse on the suffix of the two lists, to implement the trait for all arguments to (N - 1)-column indexes.
1098        impl_index_scan_range_bounds!(($($ColPrefix),*), ($($ArgPrefix),*));
1099    };
1100    // Base case for the previous "outer recursion."
1101    ((), ()) => {};
1102
1103    // The recursive case for the inner loop.
1104    //
1105    // When we start this recursion, `$ColUnused` will be empty,
1106    // so we'll implement N-element queries on N-column indexes.
1107    // The next call will move one type name from `($ColTerminator, $ColPrefix)` into `$ColUnused`,
1108    // so we'll implement (N - 1)-element queries on N-column indexes.
1109    // And so on.
1110    (@inner_recursion ($($ColUnused:ident),*), ($ColTerminator:ident $(, $ColPrefix:ident)+), ($ArgTerminator:ident $(, $ArgPrefix:ident)+)) => {
1111        // Emit the actual `impl IndexScanRangeBounds` form for M-element queries on N-column indexes.
1112        impl_index_scan_range_bounds!(@emit_impl ($($ColUnused),*), ($ColTerminator $(,$ColPrefix)*), ($ArgTerminator $(, $ArgPrefix)*));
1113        // Recurse, to implement for (M - 1)-element queries on N-column indexes.
1114        impl_index_scan_range_bounds!(@inner_recursion ($($ColUnused,)* $ColTerminator), ($($ColPrefix),*), ($($ArgPrefix),*));
1115    };
1116    // Base case for the inner recursive loop, when there is only one column remaining.
1117    // Implement the trait for both single-element tuples of arguments,
1118    // and for an argument passed outside of a tuple.
1119    //
1120    // As in the following `@emit_impl` case:
1121    // - `$ColUnused` are the types of the ignored suffix of the indexed columns.
1122    // - `$ColTerminator` is the type of the queried indexed column,
1123    //   which may have a range supplied as its argument.
1124    // - `$ArgTerminator` is the type of the argument provided for the queried column.
1125    //   More precisely it is the "inner" type, like `i32` or `&str`,
1126    //   which may be wrapped in a range like `std::ops::Range<$ArgTerminator>`.
1127    // - `Term` (not a meta-variable) is the type of the range wrapped around the `$ArgTerminator`.
1128    (@inner_recursion ($($ColUnused:ident),*), ($ColTerminator:ident), ($ArgTerminator:ident)) => {
1129        // Implementation for one-element tuples: defer to the implementation for bare values.
1130        impl<
1131            $($ColUnused,)*
1132            $ColTerminator,
1133            Term: IndexScanRangeBoundsTerminator<Arg = $ArgTerminator>,
1134            $ArgTerminator: FilterableValue<Column = $ColTerminator>,
1135        > IndexScanRangeBounds<($ColTerminator, $($ColUnused,)*)> for (Term,) {
1136            const POINT: bool = Term::POINT;
1137            const COLS_PROVIDED: usize = 1;
1138
1139            fn with_point_arg<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R {
1140                IndexScanRangeBounds::<($ColTerminator, $($ColUnused,)*), SingleBound>::with_point_arg(&self.0, run)
1141            }
1142
1143            fn get_range_args(&self) -> IndexScanRangeArgs {
1144                IndexScanRangeBounds::<($ColTerminator, $($ColUnused,)*), SingleBound>::get_range_args(&self.0)
1145            }
1146        }
1147        // Implementation for bare values: serialize the value as the terminating bounds.
1148        impl<
1149            $($ColUnused,)*
1150            $ColTerminator,
1151            Term: IndexScanRangeBoundsTerminator<Arg = $ArgTerminator>,
1152            $ArgTerminator: FilterableValue<Column = $ColTerminator>,
1153        > IndexScanRangeBounds<($ColTerminator, $($ColUnused,)*), SingleBound> for Term {
1154            const POINT: bool = Term::POINT;
1155            const COLS_PROVIDED: usize = 1;
1156
1157            fn with_point_arg<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R {
1158                // We can assume here that we have a point bound.
1159                run(&IterBuf::serialize(self.point()).unwrap())
1160            }
1161
1162            fn get_range_args(&self) -> IndexScanRangeArgs {
1163                let mut data = IterBuf::take();
1164                let rend_idx = self.bounds().serialize_into(&mut data);
1165                IndexScanRangeArgs { data, prefix_elems: 0, rstart_idx: 0, rend_idx }
1166            }
1167        }
1168    };
1169
1170    // - `$ColUnused` are the types of the ignored suffix of the indexed columns.
1171    // - `$ColTerminator` is the type of the last queried indexed column,
1172    //   which may have a range supplied as its argument.
1173    // - `$ColPrefix` are the types of the queried prefix of the indexed columns,
1174    //   which must have single values supplied as their arguments.
1175    // - `$ArgTerminator` is the type of the argument provided for the last queried column.
1176    //   More precisely it is the "inner" type, like `i32` or `&str`,
1177    //   which may be wrapped in a range like `std::ops::Range<$ArgTerminator>`.
1178    // - `Term` (not a meta-variable) is the type of the range wrapped around the `$ArgTerminator`.
1179    // - `$ArgPrefix` are the types of the arguments provided for the queried prefix columns.
1180    (@emit_impl ($($ColUnused:ident),*), ($ColTerminator:ident $(, $ColPrefix:ident)+), ($ArgTerminator:ident $(, $ArgPrefix:ident)+)) => {
1181        impl<
1182            $($ColUnused,)*
1183            $ColTerminator,
1184            $($ColPrefix,)*
1185            Term: IndexScanRangeBoundsTerminator<Arg = $ArgTerminator>,
1186            $ArgTerminator: FilterableValue<Column = $ColTerminator>,
1187            $($ArgPrefix: FilterableValue<Column = $ColPrefix>,)+
1188        > IndexScanRangeBounds<
1189            ($($ColPrefix,)+
1190             $ColTerminator,
1191             $($ColUnused,)*)
1192          > for ($($ArgPrefix,)+ Term,) {
1193            const POINT: bool = Term::POINT;
1194            const COLS_PROVIDED: usize = 1 + impl_index_scan_range_bounds!(@count $($ColPrefix)+);
1195
1196            fn with_point_arg<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R {
1197                // We can assume here that we have a point bound.
1198                let mut data = IterBuf::take();
1199
1200                // Destructure the argument tuple into variables with the same names as their types.
1201                #[allow(non_snake_case)]
1202                let ($($ArgPrefix,)+ term,) = self;
1203
1204                // For each part in the tuple queried, serialize it into the `data` buffer.
1205                Ok(())
1206                    $(.and_then(|()| data.serialize_into($ArgPrefix)))+
1207                    .and_then(|()| data.serialize_into(term.point()))
1208                    .unwrap();
1209
1210                run(&*data)
1211            }
1212
1213            fn get_range_args(&self) -> IndexScanRangeArgs {
1214                let mut data = IterBuf::take();
1215
1216                // Get the number of prefix elements.
1217                let prefix_elems = impl_index_scan_range_bounds!(@count $($ColPrefix)+);
1218
1219                // Destructure the argument tuple into variables with the same names as their types.
1220                #[allow(non_snake_case)]
1221                let ($($ArgPrefix,)+ term,) = self;
1222
1223                // For each prefix queried, serialize it into the `data` buffer.
1224                Ok(())
1225                    $(.and_then(|()| data.serialize_into($ArgPrefix)))+
1226                    .unwrap();
1227
1228                // Remember the separator between the prefix and the terminator,
1229                // so that we can slice them separately and pass them to the appropriate filter host call.
1230                let rstart_idx = data.len();
1231
1232                // Serialize the terminating range,
1233                // and get the info required to separately slice the lower and upper bounds of that range
1234                // since the host call takes those as separate slices.
1235                let rend_idx = term.bounds().serialize_into(&mut data);
1236                IndexScanRangeArgs { data, prefix_elems, rstart_idx, rend_idx }
1237            }
1238        }
1239    };
1240
1241    // Counts the number of elements in the tuple.
1242    (@count $($T:ident)*) => {
1243        0 $(+ impl_index_scan_range_bounds!(@drop $T 1))*
1244    };
1245    (@drop $a:tt $b:tt) => { $b };
1246}
1247
1248pub struct SingleBound;
1249
1250impl_index_scan_range_bounds!(
1251    (ColA, ColB, ColC, ColD, ColE, ColF),
1252    (ArgA, ArgB, ArgC, ArgD, ArgE, ArgF)
1253);
1254
1255// Single-column indexes
1256// impl<T> IndexScanRangeBounds<(T,)> for Range<T> {}
1257// impl<T> IndexScanRangeBounds<(T,)> for T {}
1258
1259// // Two-column indexes
1260// impl<T, U> IndexScanRangeBounds<(T, U)> for Range<T> {}
1261// impl<T, U> IndexScanRangeBounds<(T, U)> for T {}
1262// impl<T, U> IndexScanRangeBounds<(T, U)> for (T, Range<U>) {}
1263// impl<T, U> IndexScanRangeBounds<(T, U)> for (T, U) {}
1264
1265// // Three-column indexes
1266// impl<T, U, V> IndexScanRangeBounds<(T, U, V)> for Range<T> {}
1267// impl<T, U, V> IndexScanRangeBounds<(T, U, V)> for T {}
1268// impl<T, U, V> IndexScanRangeBounds<(T, U, V)> for (T, Range<U>) {}
1269// impl<T, U, V> IndexScanRangeBounds<(T, U, V)> for (T, U) {}
1270// impl<T, U, V> IndexScanRangeBounds<(T, U, V)> for (T, U, Range<V>) {}
1271// impl<T, U, V> IndexScanRangeBounds<(T, U, V)> for (T, U, V) {}
1272
1273/// A trait for types that can have a sequence based on them.
1274/// This is used for auto-inc columns to determine if an insertion of a row
1275/// will require the column to be updated in the row.
1276pub trait SequenceTrigger: Sized {
1277    /// Is this value one that will trigger a sequence, if any,
1278    /// when used as a column value.
1279    /// For numeric types, this is `0`.
1280    fn is_sequence_trigger(&self) -> bool;
1281    /// Should invoke `BufReader::get_{Self}`, for example `BufReader::get_u32`.
1282    fn decode(reader: &mut &[u8]) -> Result<Self, DecodeError>;
1283    /// Read a generated column from the slice, if this row was a sequence trigger.
1284    #[inline(always)]
1285    fn maybe_decode_into(&mut self, gen_cols: &mut &[u8]) {
1286        if self.is_sequence_trigger() {
1287            *self = Self::decode(gen_cols).unwrap_or_else(|_| sequence_decode_error())
1288        }
1289    }
1290}
1291
1292#[cold]
1293#[inline(never)]
1294fn sequence_decode_error() -> ! {
1295    unreachable!("a row was a sequence trigger but there was no generated column for it.")
1296}
1297
1298macro_rules! impl_seq_trigger {
1299    ($($get:ident($t:ty),)*) => {
1300        $(
1301            impl SequenceTrigger for $t {
1302                #[inline(always)]
1303                fn is_sequence_trigger(&self) -> bool { *self == 0 }
1304                #[inline(always)]
1305                fn decode(reader: &mut &[u8]) -> Result<Self, DecodeError> {
1306                    reader.$get()
1307                }
1308            }
1309        )*
1310    };
1311}
1312
1313impl_seq_trigger!(
1314    get_u8(u8),
1315    get_i8(i8),
1316    get_u16(u16),
1317    get_i16(i16),
1318    get_u32(u32),
1319    get_i32(i32),
1320    get_u64(u64),
1321    get_i64(i64),
1322    get_u128(u128),
1323    get_i128(i128),
1324);
1325
1326impl SequenceTrigger for crate::sats::i256 {
1327    #[inline(always)]
1328    fn is_sequence_trigger(&self) -> bool {
1329        *self == Self::ZERO
1330    }
1331    #[inline(always)]
1332    fn decode(reader: &mut &[u8]) -> Result<Self, DecodeError> {
1333        reader.get_i256()
1334    }
1335}
1336
1337impl SequenceTrigger for crate::sats::u256 {
1338    #[inline(always)]
1339    fn is_sequence_trigger(&self) -> bool {
1340        *self == Self::ZERO
1341    }
1342    #[inline(always)]
1343    fn decode(reader: &mut &[u8]) -> Result<Self, DecodeError> {
1344        reader.get_u256()
1345    }
1346}
1347
1348/// Insert a row of type `T` into the table identified by `table_id`.
1349#[track_caller]
1350fn insert<T: Table>(mut row: T::Row, mut buf: IterBuf) -> Result<T::Row, TryInsertError<T>> {
1351    let table_id = T::table_id();
1352    // Encode the row as bsatn into the buffer `buf`.
1353    buf.clear();
1354    buf.serialize_into(&row).unwrap();
1355
1356    // Insert row into table.
1357    // When table has an auto-incrementing column, we must re-decode the changed `buf`.
1358    let res = sys::datastore_insert_bsatn(table_id, &mut buf).map(|gen_cols| {
1359        // Let the caller handle any generated columns written back by `sys::datastore_insert_bsatn` to `buf`.
1360        T::integrate_generated_columns(&mut row, gen_cols);
1361        row
1362    });
1363    res.map_err(|e| {
1364        let err = match e {
1365            sys::Errno::UNIQUE_ALREADY_EXISTS => {
1366                T::UniqueConstraintViolation::get().map(TryInsertError::UniqueConstraintViolation)
1367            }
1368            sys::Errno::AUTO_INC_OVERFLOW => T::AutoIncOverflow::get().map(TryInsertError::AutoIncOverflow),
1369            _ => None,
1370        };
1371        err.unwrap_or_else(|| panic!("unexpected insertion error: {e}"))
1372    })
1373}
1374
1375/// Update a row of type `T` to `row` using the index identified by `index_id`.
1376#[track_caller]
1377fn update<T: Table>(index_id: IndexId, mut row: T::Row, mut buf: IterBuf) -> T::Row {
1378    let table_id = T::table_id();
1379    // Encode the row as bsatn into the buffer `buf`.
1380    buf.clear();
1381    buf.serialize_into(&row).unwrap();
1382
1383    // Insert row into table.
1384    // When table has an auto-incrementing column, we must re-decode the changed `buf`.
1385    let res = sys::datastore_update_bsatn(table_id, index_id, &mut buf).map(|gen_cols| {
1386        // Let the caller handle any generated columns written back by `sys::datastore_update_bsatn` to `buf`.
1387        T::integrate_generated_columns(&mut row, gen_cols);
1388        row
1389    });
1390
1391    // TODO(centril): introduce a `TryUpdateError`.
1392    res.unwrap_or_else(|e| panic!("unexpected update error: {e}"))
1393}
1394
1395/// A table iterator which yields values of the `TableType` corresponding to the table.
1396struct TableIter<T: DeserializeOwned> {
1397    /// The underlying source of our `Buffer`s.
1398    inner: sys::RowIter,
1399
1400    /// The current position in the buffer, from which `deserializer` can read.
1401    reader: Cursor<IterBuf>,
1402
1403    _marker: PhantomData<T>,
1404}
1405
1406impl<T: DeserializeOwned> TableIter<T> {
1407    #[inline]
1408    fn new(iter: sys::RowIter) -> Self {
1409        TableIter::new_with_buf(iter, IterBuf::take())
1410    }
1411
1412    #[inline]
1413    fn new_with_buf(iter: sys::RowIter, mut buf: IterBuf) -> Self {
1414        buf.clear();
1415        TableIter {
1416            inner: iter,
1417            reader: Cursor::new(buf),
1418            _marker: PhantomData,
1419        }
1420    }
1421
1422    fn is_exhausted(&self) -> bool {
1423        (&self.reader).remaining() == 0 && self.inner.is_exhausted()
1424    }
1425}
1426
1427impl<T: DeserializeOwned> Iterator for TableIter<T> {
1428    type Item = T;
1429
1430    fn next(&mut self) -> Option<Self::Item> {
1431        loop {
1432            // If we currently have some bytes in the buffer to still decode, do that.
1433            if (&self.reader).remaining() > 0 {
1434                let row = bsatn::from_reader(&mut &self.reader).expect("Failed to decode row!");
1435                return Some(row);
1436            }
1437
1438            // Don't fetch the next chunk if there is none.
1439            if self.inner.is_exhausted() {
1440                return None;
1441            }
1442
1443            // Otherwise, try to fetch the next chunk while reusing the buffer.
1444            self.reader.buf.clear();
1445            self.reader.pos.set(0);
1446            self.inner.read(&mut self.reader.buf);
1447        }
1448    }
1449}