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