Skip to main content

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