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