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