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}