spacetimedb_lib/db/raw_def/
v9.rs

1//! ABI Version 9 of the raw module definitions.
2//!
3//! This is the ABI that will be used for 1.0.
4//! We are keeping around the old ABI (v8) for now, to allow ourselves to convert the codebase
5//! a component-at-a-time.
6
7use std::any::TypeId;
8use std::collections::btree_map;
9use std::collections::BTreeMap;
10use std::fmt;
11
12use itertools::Itertools;
13use spacetimedb_primitives::*;
14use spacetimedb_sats::typespace::TypespaceBuilder;
15use spacetimedb_sats::AlgebraicType;
16use spacetimedb_sats::AlgebraicTypeRef;
17use spacetimedb_sats::AlgebraicValue;
18use spacetimedb_sats::ProductType;
19use spacetimedb_sats::ProductTypeElement;
20use spacetimedb_sats::SpacetimeType;
21use spacetimedb_sats::Typespace;
22
23use crate::db::auth::StAccess;
24use crate::db::auth::StTableType;
25
26/// A not-yet-validated identifier.
27pub type RawIdentifier = Box<str>;
28
29/// A not-yet-validated `sql`.
30pub type RawSql = Box<str>;
31
32/// A possibly-invalid raw module definition.
33///
34/// ABI Version 9.
35///
36/// These "raw definitions" may contain invalid data, and are validated by the `validate` module into a proper `spacetimedb_schema::ModuleDef`, or a collection of errors.
37///
38/// The module definition has a single logical global namespace, which maps `Identifier`s to:
39///
40/// - database-level objects:
41///     - logical schema objects:
42///         - tables
43///         - constraints
44///         - sequence definitions
45///     - physical schema objects:
46///         - indexes
47/// - module-level objects:
48///     - reducers
49///     - schedule definitions
50/// - binding-level objects:
51///     - type aliases
52///
53/// All of these types of objects must have unique names within the module.
54/// The exception is columns, which need unique names only within a table.
55#[derive(Debug, Clone, Default, SpacetimeType)]
56#[sats(crate = crate)]
57#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
58pub struct RawModuleDefV9 {
59    /// The `Typespace` used by the module.
60    ///
61    /// `AlgebraicTypeRef`s in the table, reducer, and type alias declarations refer to this typespace.
62    ///
63    /// The typespace must satisfy `Typespace::is_valid_for_client_code_generation`. That is, all types stored in the typespace must either:
64    /// 1. satisfy `AlgebraicType::is_valid_for_client_type_definition`
65    /// 2. and/or `AlgebraicType::is_valid_for_client_type_use`.
66    ///
67    /// Types satisfying condition 1 correspond to generated classes in client code.
68    /// (Types satisfying condition 2 are an artifact of the module bindings, and do not affect the semantics of the module definition.)
69    ///
70    /// Types satisfying condition 1 are required to have corresponding `RawTypeDefV9` declarations in the module.
71    pub typespace: Typespace,
72
73    /// The tables of the database definition used in the module.
74    ///
75    /// Each table must have a unique name.
76    pub tables: Vec<RawTableDefV9>,
77
78    /// The reducers exported by the module.
79    pub reducers: Vec<RawReducerDefV9>,
80
81    /// The types exported by the module.
82    pub types: Vec<RawTypeDefV9>,
83
84    /// Miscellaneous additional module exports.
85    ///
86    /// The enum [`RawMiscModuleExportV9`] can have new variants added
87    /// without breaking existing compiled modules.
88    /// As such, this acts as a sort of dumping ground for any exports added after we defined `RawModuleDefV9`.
89    ///
90    /// If/when we define `RawModuleDefV10`, these should be moved out of `misc_exports` and into their own fields,
91    /// and the new `misc_exports` should once again be initially empty.
92    pub misc_exports: Vec<RawMiscModuleExportV9>,
93
94    /// Row level security definitions.
95    ///
96    /// Each definition must have a unique name.
97    pub row_level_security: Vec<RawRowLevelSecurityDefV9>,
98}
99
100impl RawModuleDefV9 {
101    /// Find a [`RawTableDefV9`] by name in this raw module def
102    fn find_table_def(&self, table_name: &str) -> Option<&RawTableDefV9> {
103        self.tables
104            .iter()
105            .find(|table_def| table_def.name.as_ref() == table_name)
106    }
107
108    /// Find a [`RawViewDefV9`] by name in this raw module def
109    fn find_view_def(&self, view_name: &str) -> Option<&RawViewDefV9> {
110        self.misc_exports.iter().find_map(|misc_export| match misc_export {
111            RawMiscModuleExportV9::View(view_def) if view_def.name.as_ref() == view_name => Some(view_def),
112            _ => None,
113        })
114    }
115
116    /// Find and return the product type ref for a table in this module def
117    fn type_ref_for_table(&self, table_name: &str) -> Option<AlgebraicTypeRef> {
118        self.find_table_def(table_name)
119            .map(|table_def| table_def.product_type_ref)
120    }
121
122    /// Find and return the product type ref for a view in this module def
123    fn type_ref_for_view(&self, view_name: &str) -> Option<AlgebraicTypeRef> {
124        self.find_view_def(view_name)
125            .map(|view_def| &view_def.return_type)
126            .and_then(|return_type| {
127                return_type
128                    .as_option()
129                    .and_then(|inner| inner.clone().into_ref().ok())
130                    .or_else(|| {
131                        return_type
132                            .as_array()
133                            .and_then(|inner| inner.elem_ty.clone().into_ref().ok())
134                    })
135            })
136    }
137
138    /// Find and return the product type ref for a table or view in this module def
139    pub fn type_ref_for_table_like(&self, name: &str) -> Option<AlgebraicTypeRef> {
140        self.type_ref_for_table(name).or_else(|| self.type_ref_for_view(name))
141    }
142}
143
144/// The definition of a database table.
145///
146/// This struct holds information about the table, including its name, columns, indexes,
147/// constraints, sequences, type, and access rights.
148///
149/// Validation rules:
150/// - The table name must be a valid `spacetimedb_schema::identifier::Identifier`.
151/// - The table's indexes, constraints, and sequences need not be sorted; they will be sorted according to their respective ordering rules.
152/// - The table's column types may refer only to types in the containing `RawModuleDefV9`'s typespace.
153/// - The table's column names must be unique.
154#[derive(Debug, Clone, SpacetimeType)]
155#[sats(crate = crate)]
156#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
157pub struct RawTableDefV9 {
158    /// The name of the table.
159    /// Unique within a module, acts as the table's identifier.
160    /// Must be a valid `spacetimedb_schema::identifier::Identifier`.
161    pub name: RawIdentifier,
162
163    /// A reference to a `ProductType` containing the columns of this table.
164    /// This is the single source of truth for the table's columns.
165    /// All elements of the `ProductType` must have names.
166    ///
167    /// Like all types in the module, this must have the [default element ordering](crate::db::default_element_ordering),
168    /// UNLESS a custom ordering is declared via a `RawTypeDefv9` for this type.
169    pub product_type_ref: AlgebraicTypeRef,
170
171    /// The primary key of the table, if present. Must refer to a valid column.
172    ///
173    /// Currently, there must be a unique constraint and an index corresponding to the primary key.
174    /// Eventually, we may remove the requirement for an index.
175    ///
176    /// The database engine does not actually care about this, but client code generation does.
177    ///
178    /// A list of length 0 means no primary key. Currently, a list of length >1 is not supported.
179    pub primary_key: ColList,
180
181    /// The indices of the table.
182    pub indexes: Vec<RawIndexDefV9>,
183
184    /// Any unique constraints on the table.
185    pub constraints: Vec<RawConstraintDefV9>,
186
187    /// The sequences for the table.
188    pub sequences: Vec<RawSequenceDefV9>,
189
190    /// The schedule for the table.
191    pub schedule: Option<RawScheduleDefV9>,
192
193    /// Whether this is a system- or user-created table.
194    pub table_type: TableType,
195
196    /// Whether this table is public or private.
197    pub table_access: TableAccess,
198}
199
200/// Whether the table was created by the system or the user.
201#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, SpacetimeType)]
202#[sats(crate = crate)]
203pub enum TableType {
204    /// Created by the system.
205    System,
206    /// Created by the user.
207    User,
208}
209impl From<StTableType> for TableType {
210    fn from(t: StTableType) -> Self {
211        match t {
212            StTableType::System => TableType::System,
213            StTableType::User => TableType::User,
214        }
215    }
216}
217impl From<TableType> for StTableType {
218    fn from(t: TableType) -> Self {
219        match t {
220            TableType::System => StTableType::System,
221            TableType::User => StTableType::User,
222        }
223    }
224}
225
226/// The visibility of the table.
227#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, SpacetimeType)]
228#[sats(crate = crate)]
229pub enum TableAccess {
230    /// Visible to all
231    Public,
232    /// Visible only to the owner
233    Private,
234}
235impl From<StAccess> for TableAccess {
236    fn from(t: StAccess) -> Self {
237        match t {
238            StAccess::Public => TableAccess::Public,
239            StAccess::Private => TableAccess::Private,
240        }
241    }
242}
243impl From<TableAccess> for StAccess {
244    fn from(t: TableAccess) -> Self {
245        match t {
246            TableAccess::Public => StAccess::Public,
247            TableAccess::Private => StAccess::Private,
248        }
249    }
250}
251
252/// A sequence definition for a database table column.
253#[derive(Debug, Clone, SpacetimeType)]
254#[sats(crate = crate)]
255#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
256pub struct RawSequenceDefV9 {
257    /// In the future, the user may FOR SOME REASON want to override this.
258    /// Even though there is ABSOLUTELY NO REASON TO.
259    /// If `None`, a nicely-formatted unique default will be chosen.
260    pub name: Option<Box<str>>,
261
262    /// The position of the column associated with this sequence.
263    /// This refers to a column in the same `RawTableDef` that contains this `RawSequenceDef`.
264    /// The column must have integral type.
265    /// This must be the unique `RawSequenceDef` for this column.
266    pub column: ColId,
267
268    /// The value to start assigning to this column.
269    /// Will be incremented by 1 for each new row.
270    /// If not present, an arbitrary start point may be selected.
271    pub start: Option<i128>,
272
273    /// The minimum allowed value in this column.
274    /// If not present, no minimum.
275    pub min_value: Option<i128>,
276
277    /// The maximum allowed value in this column.
278    /// If not present, no maximum.
279    pub max_value: Option<i128>,
280
281    /// The increment used when updating the SequenceDef.
282    pub increment: i128,
283}
284
285/// The definition of a database index.
286#[derive(Debug, Clone, SpacetimeType)]
287#[sats(crate = crate)]
288#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
289pub struct RawIndexDefV9 {
290    /// In the future, the user may FOR SOME REASON want to override this.
291    /// Even though there is ABSOLUTELY NO REASON TO.
292    pub name: Option<Box<str>>,
293
294    /// Accessor name for the index used in client codegen.
295    ///
296    /// This is set the user and should not be assumed to follow
297    /// any particular format.
298    ///
299    /// May be set to `None` if this is an auto-generated index for which the user
300    /// has not supplied a name. In this case, no client code generation for this index
301    /// will be performed.
302    ///
303    /// This name is not visible in the system tables, it is only used for client codegen.
304    pub accessor_name: Option<RawIdentifier>,
305
306    /// The algorithm parameters for the index.
307    pub algorithm: RawIndexAlgorithm,
308}
309
310/// Data specifying an index algorithm.
311/// New fields MUST be added to the END of this enum, to maintain ABI compatibility.
312#[non_exhaustive]
313#[derive(Debug, Clone, SpacetimeType)]
314#[sats(crate = crate)]
315#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
316pub enum RawIndexAlgorithm {
317    /// Implemented using a B-Tree.
318    ///
319    /// Currently, this uses a rust `std::collections::BTreeMap`.
320    BTree {
321        /// The columns to index on. These are ordered.
322        columns: ColList,
323    },
324    /// Implemented using a hashmap.
325    Hash {
326        /// The columns to index on. These are ordered.
327        columns: ColList,
328    },
329    /// Implemented using direct indexing in list(s) of `RowPointer`s.
330    /// The column this is placed on must also have a unique constraint.
331    Direct {
332        /// The column to index on.
333        /// Only one is allowed, as direct indexing with more is nonsensical.
334        column: ColId,
335    },
336}
337
338/// Returns a btree index algorithm for the columns `cols`.
339pub fn btree(cols: impl Into<ColList>) -> RawIndexAlgorithm {
340    RawIndexAlgorithm::BTree { columns: cols.into() }
341}
342
343/// Returns a hash index algorithm for the columns `cols`.
344pub fn hash(cols: impl Into<ColList>) -> RawIndexAlgorithm {
345    RawIndexAlgorithm::Hash { columns: cols.into() }
346}
347
348/// Returns a direct index algorithm for the column `col`.
349pub fn direct(col: impl Into<ColId>) -> RawIndexAlgorithm {
350    RawIndexAlgorithm::Direct { column: col.into() }
351}
352
353/// Marks a table as a timer table for a scheduled reducer or procedure.
354///
355/// The table must have columns:
356/// - `scheduled_id` of type `u64`.
357/// - `scheduled_at` of type `ScheduleAt`.
358#[derive(Debug, Clone, SpacetimeType)]
359#[sats(crate = crate)]
360#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
361pub struct RawScheduleDefV9 {
362    /// In the future, the user may FOR SOME REASON want to override this.
363    /// Even though there is ABSOLUTELY NO REASON TO.
364    pub name: Option<Box<str>>,
365
366    /// The name of the reducer or procedure to call.
367    ///
368    /// Despite the field name here, this may be either a reducer or a procedure.
369    pub reducer_name: RawIdentifier,
370
371    /// The column of the `scheduled_at` field of this scheduled table.
372    pub scheduled_at_column: ColId,
373}
374
375/// A constraint definition attached to a table.
376#[derive(Debug, Clone, SpacetimeType)]
377#[sats(crate = crate)]
378#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
379pub struct RawConstraintDefV9 {
380    /// In the future, the user may FOR SOME REASON want to override this.
381    /// Even though there is ABSOLUTELY NO REASON TO.
382    pub name: Option<Box<str>>,
383
384    /// The data for the constraint.
385    pub data: RawConstraintDataV9,
386}
387
388/// Raw data attached to a constraint.
389/// New fields MUST be added to the END of this enum, to maintain ABI compatibility.
390#[derive(Debug, Clone, SpacetimeType)]
391#[sats(crate = crate)]
392#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
393#[non_exhaustive]
394pub enum RawConstraintDataV9 {
395    Unique(RawUniqueConstraintDataV9),
396}
397
398/// Requires that the projection of the table onto these `columns` is a bijection.
399///
400/// That is, there must be a one-to-one relationship between a row and the `columns` of that row.
401#[derive(Debug, Clone, SpacetimeType)]
402#[sats(crate = crate)]
403#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
404pub struct RawUniqueConstraintDataV9 {
405    /// The columns that must be unique.
406    pub columns: ColList,
407}
408
409/// Data for the `RLS` policy on a table.
410#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
411#[sats(crate = crate)]
412#[cfg_attr(feature = "test", derive(PartialOrd, Ord))]
413pub struct RawRowLevelSecurityDefV9 {
414    /// The `sql` expression to use for row-level security.
415    pub sql: RawSql,
416}
417
418/// A miscellaneous module export.
419///
420/// All of the variants here were added after the format of [`RawModuleDefV9`] was already stabilized.
421/// If/when we define `RawModuleDefV10`, these should allbe moved out of `misc_exports` and into their own fields.
422#[derive(Debug, Clone, SpacetimeType)]
423#[sats(crate = crate)]
424#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord, derive_more::From))]
425#[non_exhaustive]
426pub enum RawMiscModuleExportV9 {
427    /// A default value for a column added during a supervised automigration.
428    ColumnDefaultValue(RawColumnDefaultValueV9),
429    /// A procedure definition.
430    Procedure(RawProcedureDefV9),
431    /// A view definition.
432    View(RawViewDefV9),
433}
434
435/// Marks a particular table's column as having a particular default.
436#[derive(Debug, Clone, SpacetimeType)]
437#[sats(crate = crate)]
438#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
439pub struct RawColumnDefaultValueV9 {
440    /// Identifies which table that has the default value.
441    /// This corresponds to `name` in `RawTableDefV9`.
442    pub table: RawIdentifier,
443    /// Identifies which column of `table` that has the default value.
444    pub col_id: ColId,
445    /// A BSATN-encoded [`AlgebraicValue`] valid at the table column's type.
446    /// (We cannot use `AlgebraicValue` directly as it isn't `Spacetimetype`.)
447    pub value: Box<[u8]>,
448}
449
450/// A type declaration.
451///
452/// Exactly of these must be attached to every `Product` and `Sum` type used by a module.
453#[derive(Debug, Clone, SpacetimeType)]
454#[sats(crate = crate)]
455#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
456pub struct RawTypeDefV9 {
457    /// The name of the type declaration.
458    pub name: RawScopedTypeNameV9,
459
460    /// The type to which the declaration refers.
461    /// This must point to an `AlgebraicType::Product` or an `AlgebraicType::Sum` in the module's typespace.
462    pub ty: AlgebraicTypeRef,
463
464    /// Whether this type has a custom ordering.
465    pub custom_ordering: bool,
466}
467
468/// A scoped type name, in the form `scope0::scope1::...::scopeN::name`.
469///
470/// These are the names that will be used *in client code generation*, NOT the names used for types
471/// in the module source code.
472#[derive(Clone, SpacetimeType, PartialEq, Eq, PartialOrd, Ord)]
473#[sats(crate = crate)]
474pub struct RawScopedTypeNameV9 {
475    /// The scope for this type.
476    ///
477    /// Empty unless a sats `name` attribute is used, e.g.
478    /// `#[sats(name = "namespace.name")]` in Rust.
479    pub scope: Box<[RawIdentifier]>,
480
481    /// The name of the type. This must be unique within the module.
482    ///
483    /// Eventually, we may add more information to this, such as generic arguments.
484    pub name: RawIdentifier,
485}
486
487impl fmt::Debug for RawScopedTypeNameV9 {
488    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489        for module in self.scope.iter() {
490            fmt::Debug::fmt(module, f)?;
491            f.write_str("::")?;
492        }
493        fmt::Debug::fmt(&self.name, f)?;
494        Ok(())
495    }
496}
497
498/// A view definition.
499#[derive(Debug, Clone, SpacetimeType)]
500#[sats(crate = crate)]
501#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
502pub struct RawViewDefV9 {
503    /// The name of the view function as defined in the module
504    pub name: RawIdentifier,
505
506    /// The index of the view in the module's list of views.
507    pub index: u32,
508
509    /// Is this a public or a private view?
510    /// Currently only public views are supported.
511    /// Private views may be supported in the future.
512    pub is_public: bool,
513
514    /// Is this view anonymous?
515    /// An anonymous view does not know who called it.
516    /// Specifically, it is a view that has an `AnonymousViewContext` as its first argument.
517    /// This type does not have access to the `Identity` of the caller.
518    pub is_anonymous: bool,
519
520    /// The types and optional names of the parameters, in order.
521    /// This `ProductType` need not be registered in the typespace.
522    pub params: ProductType,
523
524    /// The return type of the view.
525    /// Either `T`, `Option<T>`, or `Vec<T>` where `T` is a `SpacetimeType`.
526    ///
527    /// More strictly `T` must be a SATS `ProductType`,
528    /// however this will be validated by the server on publish.
529    ///
530    /// This is the single source of truth for the views's columns.
531    /// All elements of the inner `ProductType` must have names.
532    /// This again will be validated by the server on publish.
533    pub return_type: AlgebraicType,
534}
535
536#[derive(Debug, Clone, SpacetimeType)]
537#[sats(crate = crate)]
538pub enum ViewResultHeader {
539    // This means the row data will follow, as a bsatn-encoded Vec<RowType>.
540    // We could make RowData contain an Vec<u8> of the bytes, but that forces us to make an extra copy when we serialize and
541    // when we deserialize.
542    RowData,
543    // This means we the view wants to return the results of the sql query.
544    RawSql(String),
545    // We can add an option for parameterized queries later,
546    // which would make it easier to cache query plans on the host side.
547}
548
549/// A reducer definition.
550#[derive(Debug, Clone, SpacetimeType)]
551#[sats(crate = crate)]
552#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
553pub struct RawReducerDefV9 {
554    /// The name of the reducer.
555    pub name: RawIdentifier,
556
557    /// The types and optional names of the parameters, in order.
558    /// This `ProductType` need not be registered in the typespace.
559    pub params: ProductType,
560
561    /// If the reducer has a special role in the module lifecycle, it should be marked here.
562    pub lifecycle: Option<Lifecycle>,
563}
564
565/// Special roles a reducer can play in the module lifecycle.
566#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SpacetimeType)]
567#[cfg_attr(feature = "enum-map", derive(enum_map::Enum))]
568#[sats(crate = crate)]
569#[non_exhaustive]
570pub enum Lifecycle {
571    /// The reducer will be invoked upon module initialization.
572    Init,
573    /// The reducer will be invoked when a client connects.
574    OnConnect,
575    /// The reducer will be invoked when a client disconnects.
576    OnDisconnect,
577}
578
579/// A procedure definition.
580///
581/// Will be wrapped in [`RawMiscModuleExportV9`] and included in the [`RawModuleDefV9`]'s `misc_exports` vec.
582#[derive(Debug, Clone, SpacetimeType)]
583#[sats(crate = crate)]
584#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
585pub struct RawProcedureDefV9 {
586    /// The name of the procedure.
587    pub name: RawIdentifier,
588
589    /// The types and optional names of the parameters, in order.
590    /// This `ProductType` need not be registered in the typespace.
591    pub params: ProductType,
592
593    /// The type of the return value.
594    ///
595    /// If this is a user-defined product or sum type,
596    /// it should be registered in the typespace and indirected through an [`AlgebraicType::Ref`].
597    pub return_type: AlgebraicType,
598}
599
600/// A builder for a [`RawModuleDefV9`].
601#[derive(Default)]
602pub struct RawModuleDefV9Builder {
603    /// The module definition.
604    module: RawModuleDefV9,
605    /// The type map from `T: 'static` Rust types to sats types.
606    type_map: BTreeMap<TypeId, AlgebraicTypeRef>,
607}
608
609impl RawModuleDefV9Builder {
610    /// Create a new, empty `RawModuleDefBuilder`.
611    pub fn new() -> Self {
612        Default::default()
613    }
614
615    /// Add a type to the in-progress module.
616    ///
617    /// The returned type must satisfy `AlgebraicType::is_valid_for_client_type_definition` or  `AlgebraicType::is_valid_for_client_type_use` .
618    pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
619        TypespaceBuilder::add_type::<T>(self)
620    }
621
622    /// Create a table builder.
623    ///
624    /// Does not validate that the product_type_ref is valid; this is left to the module validation code.
625    pub fn build_table(
626        &mut self,
627        name: impl Into<RawIdentifier>,
628        product_type_ref: AlgebraicTypeRef,
629    ) -> RawTableDefBuilder<'_> {
630        let name = name.into();
631        RawTableDefBuilder {
632            module_def: &mut self.module,
633            table: RawTableDefV9 {
634                name,
635                product_type_ref,
636                indexes: vec![],
637                constraints: vec![],
638                sequences: vec![],
639                schedule: None,
640                primary_key: ColList::empty(),
641                table_type: TableType::User,
642                table_access: TableAccess::Public,
643            },
644        }
645    }
646
647    /// Build a new table with a product type.
648    /// Adds the type to the module.
649    pub fn build_table_with_new_type(
650        &mut self,
651        table_name: impl Into<RawIdentifier>,
652        product_type: impl Into<spacetimedb_sats::ProductType>,
653        custom_ordering: bool,
654    ) -> RawTableDefBuilder<'_> {
655        let table_name = table_name.into();
656
657        let product_type_ref = self.add_algebraic_type(
658            [],
659            table_name.clone(),
660            AlgebraicType::from(product_type.into()),
661            custom_ordering,
662        );
663
664        self.build_table(table_name, product_type_ref)
665    }
666
667    /// Build a new table with a product type, for testing.
668    /// Adds the type to the module.
669    pub fn build_table_with_new_type_for_tests(
670        &mut self,
671        table_name: impl Into<RawIdentifier>,
672        mut product_type: spacetimedb_sats::ProductType,
673        custom_ordering: bool,
674    ) -> RawTableDefBuilder<'_> {
675        self.add_expand_product_type_for_tests(&mut 0, &mut product_type);
676
677        self.build_table_with_new_type(table_name, product_type, custom_ordering)
678    }
679
680    fn add_expand_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut AlgebraicType) {
681        if ty.is_valid_for_client_type_use() {
682            return;
683        }
684
685        match ty {
686            AlgebraicType::Product(prod_ty) => self.add_expand_product_type_for_tests(name_gen, prod_ty),
687            AlgebraicType::Sum(sum_type) => {
688                if let Some(wrapped) = sum_type.as_option_mut() {
689                    self.add_expand_type_for_tests(name_gen, wrapped);
690                } else {
691                    for elem in sum_type.variants.iter_mut() {
692                        self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
693                    }
694                }
695            }
696            AlgebraicType::Array(ty) => {
697                self.add_expand_type_for_tests(name_gen, &mut ty.elem_ty);
698                return;
699            }
700            _ => return,
701        }
702
703        // Make the type into a ref.
704        let name = *name_gen;
705        let add_ty = core::mem::replace(ty, AlgebraicType::U8);
706        *ty = AlgebraicType::Ref(self.add_algebraic_type([], format!("gen_{name}"), add_ty, true));
707        *name_gen += 1;
708    }
709
710    fn add_expand_product_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut ProductType) {
711        for elem in ty.elements.iter_mut() {
712            self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
713        }
714    }
715
716    /// Add a type to the typespace, along with a type alias declaring its name.
717    /// This method should only be use for `AlgebraicType`s not corresponding to a Rust
718    /// type that implements `SpacetimeType`.
719    ///
720    /// Returns a reference to the newly-added type.
721    ///
722    /// NOT idempotent, calling this twice with the same name will cause errors during
723    /// validation.
724    ///
725    /// You must set `custom_ordering` if you're not using the default element ordering.
726    pub fn add_algebraic_type(
727        &mut self,
728        scope: impl IntoIterator<Item = RawIdentifier>,
729        name: impl Into<RawIdentifier>,
730        ty: spacetimedb_sats::AlgebraicType,
731        custom_ordering: bool,
732    ) -> AlgebraicTypeRef {
733        let ty = self.module.typespace.add(ty);
734        let scope = scope.into_iter().collect();
735        let name = name.into();
736        self.module.types.push(RawTypeDefV9 {
737            name: RawScopedTypeNameV9 { name, scope },
738            ty,
739            custom_ordering,
740        });
741        // We don't add a `TypeId` to `self.type_map`, because there may not be a corresponding Rust type! e.g. if we are randomly generating types in proptests.
742        ty
743    }
744
745    /// Add a reducer to the in-progress module.
746    /// Accepts a `ProductType` of reducer arguments for convenience.
747    /// The `ProductType` need not be registered in the typespace.
748    ///
749    /// Importantly, if the reducer's first argument is a `ReducerContext`, that
750    /// information should not be provided to this method.
751    /// That is an implementation detail handled by the module bindings and can be ignored.
752    /// As far as the module definition is concerned, the reducer's arguments
753    /// start with the first non-`ReducerContext` argument.
754    ///
755    /// (It is impossible, with the current implementation of `ReducerContext`, to
756    /// have more than one `ReducerContext` argument, at least in Rust.
757    /// This is because `SpacetimeType` is not implemented for `ReducerContext`,
758    /// so it can never act like an ordinary argument.)
759    pub fn add_reducer(
760        &mut self,
761        name: impl Into<RawIdentifier>,
762        params: spacetimedb_sats::ProductType,
763        lifecycle: Option<Lifecycle>,
764    ) {
765        self.module.reducers.push(RawReducerDefV9 {
766            name: name.into(),
767            params,
768            lifecycle,
769        });
770    }
771
772    /// Add a procedure to the in-progress module.
773    ///
774    /// Accepts a `ProductType` of arguments.
775    /// The arguments `ProductType` need not be registered in the typespace.
776    ///
777    /// Also accepts an `AlgebraicType` return type.
778    /// If this is a user-defined product or sum type,
779    /// it should be registered in the typespace and indirected through an `AlgebraicType::Ref`.
780    ///
781    /// The `&mut ProcedureContext` first argument to the procedure should not be included in the `params`.
782    pub fn add_procedure(
783        &mut self,
784        name: impl Into<RawIdentifier>,
785        params: spacetimedb_sats::ProductType,
786        return_type: spacetimedb_sats::AlgebraicType,
787    ) {
788        self.module
789            .misc_exports
790            .push(RawMiscModuleExportV9::Procedure(RawProcedureDefV9 {
791                name: name.into(),
792                params,
793                return_type,
794            }))
795    }
796
797    pub fn add_view(
798        &mut self,
799        name: impl Into<RawIdentifier>,
800        index: usize,
801        is_public: bool,
802        is_anonymous: bool,
803        params: ProductType,
804        return_type: AlgebraicType,
805    ) {
806        self.module.misc_exports.push(RawMiscModuleExportV9::View(RawViewDefV9 {
807            name: name.into(),
808            index: index as u32,
809            is_public,
810            is_anonymous,
811            params,
812            return_type,
813        }));
814    }
815
816    /// Add a row-level security policy to the module.
817    ///
818    /// The `sql` expression should be a valid SQL expression that will be used to filter rows.
819    ///
820    /// **NOTE**: The `sql` expression must be unique within the module.
821    pub fn add_row_level_security(&mut self, sql: &str) {
822        self.module
823            .row_level_security
824            .push(RawRowLevelSecurityDefV9 { sql: sql.into() });
825    }
826
827    /// Get the typespace of the module.
828    pub fn typespace(&self) -> &Typespace {
829        &self.module.typespace
830    }
831
832    /// Finish building, consuming the builder and returning the module.
833    /// The module should be validated before use.
834    pub fn finish(self) -> RawModuleDefV9 {
835        self.module
836    }
837}
838
839/// Convert a string from a sats type-name annotation like `#[sats(name = "namespace.name")]` to a `RawScopedTypeNameV9`.
840/// We split the input on the strings `"::"` and `"."` to split up module paths.
841///
842/// TODO(1.0): build namespacing directly into the bindings macros so that we don't need to do this.
843pub fn sats_name_to_scoped_name(sats_name: &str) -> RawScopedTypeNameV9 {
844    // We can't use `&[char]: Pattern` for `split` here because "::" is not a char :/
845    let mut scope: Vec<RawIdentifier> = sats_name.split("::").flat_map(|s| s.split('.')).map_into().collect();
846    // Unwrapping to "" will result in a validation error down the line, which is exactly what we want.
847    let name = scope.pop().unwrap_or_default();
848    RawScopedTypeNameV9 {
849        scope: scope.into(),
850        name,
851    }
852}
853
854impl TypespaceBuilder for RawModuleDefV9Builder {
855    fn add(
856        &mut self,
857        typeid: TypeId,
858        name: Option<&'static str>,
859        make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
860    ) -> AlgebraicType {
861        let r = match self.type_map.entry(typeid) {
862            btree_map::Entry::Occupied(o) => *o.get(),
863            btree_map::Entry::Vacant(v) => {
864                // Bind a fresh alias to the unit type.
865                let slot_ref = self.module.typespace.add(AlgebraicType::unit());
866                // Relate `typeid -> fresh alias`.
867                v.insert(slot_ref);
868
869                // Alias provided? Relate `name -> slot_ref`.
870                if let Some(sats_name) = name {
871                    let name = sats_name_to_scoped_name(sats_name);
872
873                    self.module.types.push(RawTypeDefV9 {
874                        name,
875                        ty: slot_ref,
876                        // TODO(1.0): we need to update the `TypespaceBuilder` trait to include
877                        // a `custom_ordering` parameter.
878                        // For now, we assume all types have custom orderings, since the derive
879                        // macro doesn't know about the default ordering yet.
880                        custom_ordering: true,
881                    });
882                }
883
884                // Borrow of `v` has ended here, so we can now convince the borrow checker.
885                let ty = make_ty(self);
886                self.module.typespace[slot_ref] = ty;
887                slot_ref
888            }
889        };
890        AlgebraicType::Ref(r)
891    }
892}
893
894/// Builder for a `RawTableDef`.
895pub struct RawTableDefBuilder<'a> {
896    module_def: &'a mut RawModuleDefV9,
897    table: RawTableDefV9,
898}
899
900impl RawTableDefBuilder<'_> {
901    /// Sets the type of the table and return it.
902    ///
903    /// This is not about column algebraic types, but about whether the table
904    /// was created by the system or the user.
905    pub fn with_type(mut self, table_type: TableType) -> Self {
906        self.table.table_type = table_type;
907        self
908    }
909
910    /// Sets the access rights for the table and return it.
911    pub fn with_access(mut self, table_access: TableAccess) -> Self {
912        self.table.table_access = table_access;
913        self
914    }
915
916    /// Generates a [RawConstraintDefV9] using the supplied `columns`.
917    pub fn with_unique_constraint(mut self, columns: impl Into<ColList>) -> Self {
918        let columns = columns.into();
919        self.table.constraints.push(RawConstraintDefV9 {
920            name: None,
921            data: RawConstraintDataV9::Unique(RawUniqueConstraintDataV9 { columns }),
922        });
923        self
924    }
925
926    /// Adds a primary key to the table.
927    /// You must also add a unique constraint on the primary key column.
928    pub fn with_primary_key(mut self, column: impl Into<ColId>) -> Self {
929        self.table.primary_key = ColList::new(column.into());
930        self
931    }
932
933    /// Adds a primary key to the table, with corresponding unique constraint and sequence definitions.
934    /// You will also need to call [`Self::with_index`] to create an index on `column`.
935    pub fn with_auto_inc_primary_key(self, column: impl Into<ColId>) -> Self {
936        let column = column.into();
937        self.with_primary_key(column)
938            .with_unique_constraint(column)
939            .with_column_sequence(column)
940    }
941
942    /// Generates a [RawIndexDefV9] using the supplied `columns`.
943    pub fn with_index(mut self, algorithm: RawIndexAlgorithm, accessor_name: impl Into<RawIdentifier>) -> Self {
944        let accessor_name = accessor_name.into();
945
946        self.table.indexes.push(RawIndexDefV9 {
947            name: None,
948            accessor_name: Some(accessor_name),
949            algorithm,
950        });
951        self
952    }
953
954    /// Generates a [RawIndexDefV9] using the supplied `columns` but with no `accessor_name`.
955    pub fn with_index_no_accessor_name(mut self, algorithm: RawIndexAlgorithm) -> Self {
956        self.table.indexes.push(RawIndexDefV9 {
957            name: None,
958            accessor_name: None,
959            algorithm,
960        });
961        self
962    }
963
964    /// Adds a [RawSequenceDefV9] on the supplied `column`.
965    pub fn with_column_sequence(mut self, column: impl Into<ColId>) -> Self {
966        let column = column.into();
967        self.table.sequences.push(RawSequenceDefV9 {
968            name: None,
969            column,
970            start: None,
971            min_value: None,
972            max_value: None,
973            increment: 1,
974        });
975
976        self
977    }
978
979    /// Adds a schedule definition to the table.
980    ///
981    /// The `function_name` should name a reducer or procedure
982    /// which accepts one argument, a row of this table.
983    ///
984    /// The table must have the appropriate columns for a scheduled table.
985    pub fn with_schedule(
986        mut self,
987        function_name: impl Into<RawIdentifier>,
988        scheduled_at_column: impl Into<ColId>,
989    ) -> Self {
990        let reducer_name = function_name.into();
991        let scheduled_at_column = scheduled_at_column.into();
992        self.table.schedule = Some(RawScheduleDefV9 {
993            name: None,
994            reducer_name,
995            scheduled_at_column,
996        });
997        self
998    }
999
1000    /// Adds a default value for the `column`.
1001    pub fn with_default_column_value(self, column: impl Into<ColId>, value: AlgebraicValue) -> Self {
1002        // Added to `misc_exports` for backwards-compatibility reasons.
1003        self.module_def
1004            .misc_exports
1005            .push(RawMiscModuleExportV9::ColumnDefaultValue(RawColumnDefaultValueV9 {
1006                table: self.table.name.clone(),
1007                col_id: column.into(),
1008                value: spacetimedb_sats::bsatn::to_vec(&value).unwrap().into(),
1009            }));
1010        self
1011    }
1012
1013    /// Build the table and add it to the module, returning the `product_type_ref` of the table.
1014    pub fn finish(self) -> AlgebraicTypeRef {
1015        self.table.product_type_ref
1016        // self is now dropped.
1017    }
1018
1019    /// Get the column ID of the column with the specified name, if any.
1020    ///
1021    /// Returns `None` if this `TableDef` has been constructed with an invalid `ProductTypeRef`,
1022    /// or if no column exists with that name.
1023    pub fn find_col_pos_by_name(&self, column: impl AsRef<str>) -> Option<ColId> {
1024        let column = column.as_ref();
1025        self.columns()?
1026            .iter()
1027            .position(|x| x.name().is_some_and(|s| s == column))
1028            .map(|x| x.into())
1029    }
1030
1031    /// Get the columns of this type.
1032    ///
1033    /// Returns `None` if this `TableDef` has been constructed with an invalid `ProductTypeRef`.
1034    fn columns(&self) -> Option<&[ProductTypeElement]> {
1035        self.module_def
1036            .typespace
1037            .get(self.table.product_type_ref)
1038            .and_then(|ty| ty.as_product())
1039            .map(|p| &p.elements[..])
1040    }
1041}
1042
1043impl Drop for RawTableDefBuilder<'_> {
1044    fn drop(&mut self) {
1045        self.module_def.tables.push(self.table.clone());
1046    }
1047}