Skip to main content

spacetimedb_lib/db/raw_def/
v10.rs

1//! ABI Version 10 of the raw module definitions.
2//!
3//! This is a refactored version of V9 with a section-based structure.
4//! V10 moves schedules, lifecycle reducers, and default values out of their V9 locations
5//! into dedicated sections for cleaner organization.
6//! It allows easier future extensibility to add new kinds of definitions.
7
8use crate::db::raw_def::v9::{Lifecycle, RawIndexAlgorithm, TableAccess, TableType};
9use core::fmt;
10use spacetimedb_primitives::{ColId, ColList};
11use spacetimedb_sats::raw_identifier::RawIdentifier;
12use spacetimedb_sats::typespace::TypespaceBuilder;
13use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, AlgebraicValue, ProductType, SpacetimeType, Typespace};
14use std::any::TypeId;
15use std::collections::{btree_map, BTreeMap};
16
17/// A possibly-invalid raw module definition.
18///
19/// ABI Version 10.
20///
21/// These "raw definitions" may contain invalid data, and are validated by the `validate` module
22/// into a proper `spacetimedb_schema::ModuleDef`, or a collection of errors.
23///
24/// The module definition maintains the same logical global namespace as V9, mapping `Identifier`s to:
25///
26/// - database-level objects:
27///     - logical schema objects: tables, constraints, sequence definitions
28///     - physical schema objects: indexes
29/// - module-level objects: reducers, procedures, schedule definitions
30/// - binding-level objects: type aliases
31///
32/// All of these types of objects must have unique names within the module.
33/// The exception is columns, which need unique names only within a table.
34#[derive(Default, Debug, Clone, SpacetimeType)]
35#[sats(crate = crate)]
36#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
37pub struct RawModuleDefV10 {
38    /// The sections comprising this module definition.
39    ///
40    /// Sections can appear in any order and are optional.
41    pub sections: Vec<RawModuleDefV10Section>,
42}
43
44/// A section of a V10 module definition.
45///
46/// New variants MUST be added to the END of this enum, to maintain ABI compatibility.
47#[derive(Debug, Clone, SpacetimeType)]
48#[sats(crate = crate)]
49#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
50#[non_exhaustive]
51pub enum RawModuleDefV10Section {
52    /// The `Typespace` used by the module.
53    ///
54    /// `AlgebraicTypeRef`s in other sections refer to this typespace.
55    /// See [`crate::db::raw_def::v9::RawModuleDefV9::typespace`] for validation requirements.
56    Typespace(Typespace),
57
58    /// Type definitions exported by the module.
59    Types(Vec<RawTypeDefV10>),
60
61    /// Table definitions.
62    Tables(Vec<RawTableDefV10>),
63
64    /// Reducer definitions.
65    Reducers(Vec<RawReducerDefV10>),
66
67    /// Procedure definitions.
68    Procedures(Vec<RawProcedureDefV10>),
69
70    /// View definitions.
71    Views(Vec<RawViewDefV10>),
72
73    /// Schedule definitions.
74    ///
75    /// Unlike V9 where schedules were embedded in table definitions,
76    /// V10 stores them in a dedicated section.
77    Schedules(Vec<RawScheduleDefV10>),
78
79    /// Lifecycle reducer assignments.
80    ///
81    /// Unlike V9 where lifecycle was a field on reducers,
82    /// V10 stores lifecycle-to-reducer mappings separately.
83    LifeCycleReducers(Vec<RawLifeCycleReducerDefV10>),
84
85    RowLevelSecurity(Vec<RawRowLevelSecurityDefV10>), //TODO: Add section for Event tables, and Case conversion before exposing this from module
86
87    /// Case conversion policy for identifiers in this module.
88    CaseConversionPolicy(CaseConversionPolicy),
89
90    /// Names provided explicitly by the user that do not follow from the case conversion policy.
91    ExplicitNames(ExplicitNames),
92}
93
94#[derive(Debug, Clone, Copy, Default, SpacetimeType)]
95#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
96#[sats(crate = crate)]
97#[non_exhaustive]
98pub enum CaseConversionPolicy {
99    /// No conversion - names used verbatim as canonical names
100    None,
101    /// Convert to snake_case (SpacetimeDB default)
102    #[default]
103    SnakeCase,
104}
105
106#[derive(Debug, Clone, SpacetimeType)]
107#[sats(crate = crate)]
108#[cfg_attr(feature = "test", derive(PartialEq, Eq, Ord, PartialOrd))]
109#[non_exhaustive]
110pub struct NameMapping {
111    /// The original name as defined or generated inside module.
112    ///
113    /// Generated as:
114    /// - Tables: value from `#[spacetimedb::table(accessor = ...)]`.
115    /// - Reducers/Procedures/Views: function name
116    /// - Indexes: `{table_name}_{column_names}_idx_{algorithm}`
117    ///
118    /// During validation, this may be replaced by `canonical_name`
119    /// if an explicit or policy-based name is applied.
120    pub source_name: RawIdentifier,
121
122    /// The canonical identifier used in system tables and client code generation.
123    ///
124    /// Set via:
125    /// - `#[spacetimedb::table(name = "...")]` for tables
126    /// - `#[spacetimedb::reducer(name = "...")]` for reducers
127    /// - `#[name("...")]` for other entities
128    ///
129    /// If not explicitly provided, this defaults to `source_name`
130    /// after validation. No particular format should be assumed.
131    pub canonical_name: RawIdentifier,
132}
133
134#[derive(Debug, Clone, SpacetimeType)]
135#[sats(crate = crate)]
136#[cfg_attr(feature = "test", derive(PartialEq, Eq, Ord, PartialOrd))]
137#[non_exhaustive]
138pub enum ExplicitNameEntry {
139    Table(NameMapping),
140    Function(NameMapping),
141    Index(NameMapping),
142}
143
144#[derive(Debug, Default, Clone, SpacetimeType)]
145#[sats(crate = crate)]
146#[cfg_attr(feature = "test", derive(PartialEq, Eq, Ord, PartialOrd))]
147#[non_exhaustive]
148pub struct ExplicitNames {
149    /// Explicit name mappings defined in the module.
150    ///
151    /// These override policy-based or auto-generated names
152    /// during schema validation.
153    entries: Vec<ExplicitNameEntry>,
154}
155
156impl ExplicitNames {
157    fn insert(&mut self, entry: ExplicitNameEntry) {
158        self.entries.push(entry);
159    }
160
161    pub fn insert_table(&mut self, source_name: impl Into<RawIdentifier>, canonical_name: impl Into<RawIdentifier>) {
162        self.insert(ExplicitNameEntry::Table(NameMapping {
163            source_name: source_name.into(),
164            canonical_name: canonical_name.into(),
165        }));
166    }
167
168    pub fn insert_function(&mut self, source_name: impl Into<RawIdentifier>, canonical_name: impl Into<RawIdentifier>) {
169        self.insert(ExplicitNameEntry::Function(NameMapping {
170            source_name: source_name.into(),
171            canonical_name: canonical_name.into(),
172        }));
173    }
174
175    pub fn insert_index(&mut self, source_name: impl Into<RawIdentifier>, canonical_name: impl Into<RawIdentifier>) {
176        self.insert(ExplicitNameEntry::Index(NameMapping {
177            source_name: source_name.into(),
178            canonical_name: canonical_name.into(),
179        }));
180    }
181
182    pub fn merge(&mut self, other: ExplicitNames) {
183        self.entries.extend(other.entries);
184    }
185
186    pub fn into_entries(self) -> Vec<ExplicitNameEntry> {
187        self.entries
188    }
189}
190
191pub type RawRowLevelSecurityDefV10 = crate::db::raw_def::v9::RawRowLevelSecurityDefV9;
192
193/// The definition of a database table.
194///
195/// This struct holds information about the table, including its name, columns, indexes,
196/// constraints, sequences, type, and access rights.
197///
198/// Validation rules are the same as V9, except:
199/// - Default values are stored inline rather than in `MiscModuleExport`
200/// - Schedules are stored in a separate section rather than embedded here
201#[derive(Debug, Clone, SpacetimeType)]
202#[sats(crate = crate)]
203#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
204pub struct RawTableDefV10 {
205    /// The name of the table.
206    /// Unique within a module, acts as the table's identifier.
207    /// Must be a valid `spacetimedb_schema::identifier::Identifier`.
208    pub source_name: RawIdentifier,
209
210    /// A reference to a `ProductType` containing the columns of this table.
211    /// This is the single source of truth for the table's columns.
212    /// All elements of the `ProductType` must have names.
213    ///
214    /// Like all types in the module, this must have the [default element ordering](crate::db::default_element_ordering),
215    /// UNLESS a custom ordering is declared via a `RawTypeDefV10` for this type.
216    pub product_type_ref: AlgebraicTypeRef,
217
218    /// The primary key of the table, if present. Must refer to a valid column.
219    ///
220    /// Currently, there must be a unique constraint and an index corresponding to the primary key.
221    /// Eventually, we may remove the requirement for an index.
222    ///
223    /// The database engine does not actually care about this, but client code generation does.
224    ///
225    /// A list of length 0 means no primary key. Currently, a list of length >1 is not supported.
226    pub primary_key: ColList,
227
228    /// The indices of the table.
229    pub indexes: Vec<RawIndexDefV10>,
230
231    /// Any unique constraints on the table.
232    pub constraints: Vec<RawConstraintDefV10>,
233
234    /// The sequences for the table.
235    pub sequences: Vec<RawSequenceDefV10>,
236
237    /// Whether this is a system- or user-created table.
238    pub table_type: TableType,
239
240    /// Whether this table is public or private.
241    pub table_access: TableAccess,
242
243    /// Default values for columns in this table.
244    pub default_values: Vec<RawColumnDefaultValueV10>,
245
246    /// Whether this is an event table.
247    ///
248    /// Event tables are write-only: their rows are persisted to the commitlog
249    /// but are NOT merged into committed state. They are only visible to V2
250    /// subscribers in the transaction that inserted them.
251    pub is_event: bool,
252}
253
254/// Marks a particular table column as having a particular default value.
255#[derive(Debug, Clone, SpacetimeType)]
256#[sats(crate = crate)]
257#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
258pub struct RawColumnDefaultValueV10 {
259    /// Identifies which column has the default value.
260    pub col_id: ColId,
261
262    /// A BSATN-encoded [`AlgebraicValue`] valid at the column's type.
263    /// (We cannot use `AlgebraicValue` directly as it isn't `SpacetimeType`.)
264    pub value: Box<[u8]>,
265}
266
267/// A reducer definition.
268#[derive(Debug, Clone, SpacetimeType)]
269#[sats(crate = crate)]
270#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
271pub struct RawReducerDefV10 {
272    /// The name of the reducer.
273    pub source_name: RawIdentifier,
274
275    /// The types and optional names of the parameters, in order.
276    /// This `ProductType` need not be registered in the typespace.
277    pub params: ProductType,
278
279    /// Whether this reducer is callable from clients or is internal-only.
280    pub visibility: FunctionVisibility,
281
282    /// The type of the `Ok` return value.
283    pub ok_return_type: AlgebraicType,
284
285    /// The type of the `Err` return value.
286    pub err_return_type: AlgebraicType,
287}
288
289/// The visibility of a function (reducer or procedure).
290#[derive(Debug, Copy, Clone, SpacetimeType)]
291#[sats(crate = crate)]
292#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
293pub enum FunctionVisibility {
294    /// Not callable by arbitrary clients.
295    ///
296    /// Still callable by the module owner, collaborators,
297    /// and internal module code.
298    ///
299    /// Enabled for lifecycle reducers and scheduled functions by default.
300    Private,
301
302    /// Callable from client code.
303    ClientCallable,
304}
305
306/// A schedule definition.
307#[derive(Debug, Clone, SpacetimeType)]
308#[sats(crate = crate)]
309#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
310pub struct RawScheduleDefV10 {
311    /// In the future, the user may FOR SOME REASON want to override this.
312    /// Even though there is ABSOLUTELY NO REASON TO.
313    /// If `None`, a nicely-formatted unique default will be chosen.
314    pub source_name: Option<RawIdentifier>,
315
316    /// The name of the table containing the schedule.
317    pub table_name: RawIdentifier,
318
319    /// The column of the `scheduled_at` field in the table.
320    pub schedule_at_col: ColId,
321
322    /// The name of the reducer or procedure to call.
323    pub function_name: RawIdentifier,
324}
325
326/// A lifecycle reducer assignment.
327#[derive(Debug, Clone, SpacetimeType)]
328#[sats(crate = crate)]
329#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
330pub struct RawLifeCycleReducerDefV10 {
331    /// Which lifecycle event this reducer handles.
332    pub lifecycle_spec: Lifecycle,
333
334    /// The name of the reducer to call for this lifecycle event.
335    pub function_name: RawIdentifier,
336}
337
338/// A procedure definition.
339#[derive(Debug, Clone, SpacetimeType)]
340#[sats(crate = crate)]
341#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
342pub struct RawProcedureDefV10 {
343    /// The name of the procedure.
344    pub source_name: RawIdentifier,
345
346    /// The types and optional names of the parameters, in order.
347    /// This `ProductType` need not be registered in the typespace.
348    pub params: ProductType,
349
350    /// The type of the return value.
351    ///
352    /// If this is a user-defined product or sum type,
353    /// it should be registered in the typespace and indirected through an [`AlgebraicType::Ref`].
354    pub return_type: AlgebraicType,
355
356    /// Whether this procedure is callable from clients or is internal-only.
357    pub visibility: FunctionVisibility,
358}
359
360/// A sequence definition for a database table column.
361#[derive(Debug, Clone, SpacetimeType)]
362#[sats(crate = crate)]
363#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
364pub struct RawSequenceDefV10 {
365    /// In the future, the user may FOR SOME REASON want to override this.
366    /// Even though there is ABSOLUTELY NO REASON TO.
367    /// If `None`, a nicely-formatted unique default will be chosen.
368    pub source_name: Option<RawIdentifier>,
369
370    /// The position of the column associated with this sequence.
371    /// This refers to a column in the same `RawTableDef` that contains this `RawSequenceDef`.
372    /// The column must have integral type.
373    /// This must be the unique `RawSequenceDef` for this column.
374    pub column: ColId,
375
376    /// The value to start assigning to this column.
377    /// Will be incremented by 1 for each new row.
378    /// If not present, an arbitrary start point may be selected.
379    pub start: Option<i128>,
380
381    /// The minimum allowed value in this column.
382    /// If not present, no minimum.
383    pub min_value: Option<i128>,
384
385    /// The maximum allowed value in this column.
386    /// If not present, no maximum.
387    pub max_value: Option<i128>,
388
389    /// The increment used when updating the SequenceDef.
390    pub increment: i128,
391}
392
393/// The definition of a database index.
394#[derive(Debug, Clone, SpacetimeType)]
395#[sats(crate = crate)]
396#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
397pub struct RawIndexDefV10 {
398    /// Must be supplied as `{table_name}_{column_names}_idx_{algorithm}`.
399    /// Where `{table_name}` is the name of the table containing in `RawTableDefV10`.
400    pub source_name: Option<RawIdentifier>,
401
402    /// `accessor_name` is the name of the index accessor function that is used inside the module
403    /// code.
404    pub accessor_name: Option<RawIdentifier>,
405
406    /// The algorithm parameters for the index.
407    pub algorithm: RawIndexAlgorithm,
408}
409
410/// A constraint definition attached to a table.
411#[derive(Debug, Clone, SpacetimeType)]
412#[sats(crate = crate)]
413#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
414pub struct RawConstraintDefV10 {
415    /// In the future, the user may FOR SOME REASON want to override this.
416    /// Even though there is ABSOLUTELY NO REASON TO.
417    pub source_name: Option<RawIdentifier>,
418
419    /// The data for the constraint.
420    pub data: RawConstraintDataV10,
421}
422
423type RawConstraintDataV10 = crate::db::raw_def::v9::RawConstraintDataV9;
424type RawUniqueConstraintDataV10 = crate::db::raw_def::v9::RawUniqueConstraintDataV9;
425
426/// A type declaration.
427///
428/// Exactly of these must be attached to every `Product` and `Sum` type used by a module.
429#[derive(Debug, Clone, SpacetimeType)]
430#[sats(crate = crate)]
431#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
432pub struct RawTypeDefV10 {
433    /// The name of the type declaration.
434    pub source_name: RawScopedTypeNameV10,
435
436    /// The type to which the declaration refers.
437    /// This must point to an `AlgebraicType::Product` or an `AlgebraicType::Sum` in the module's typespace.
438    pub ty: AlgebraicTypeRef,
439
440    /// Whether this type has a custom ordering.
441    pub custom_ordering: bool,
442}
443
444/// A scoped type name, in the form `scope0::scope1::...::scopeN::name`.
445///
446/// These are the names that will be used *in client code generation*, NOT the names used for types
447/// in the module source code.
448#[derive(Clone, SpacetimeType, PartialEq, Eq, PartialOrd, Ord)]
449#[sats(crate = crate)]
450pub struct RawScopedTypeNameV10 {
451    /// The scope for this type.
452    ///
453    /// Empty unless a sats `name` attribute is used, e.g.
454    /// `#[sats(name = "namespace.name")]` in Rust.
455    pub scope: Box<[RawIdentifier]>,
456
457    /// The name of the type. This must be unique within the module.
458    ///
459    /// Eventually, we may add more information to this, such as generic arguments.
460    pub source_name: RawIdentifier,
461}
462
463impl fmt::Debug for RawScopedTypeNameV10 {
464    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465        for module in self.scope.iter() {
466            fmt::Debug::fmt(module, f)?;
467            f.write_str("::")?;
468        }
469        fmt::Debug::fmt(&self.source_name, f)?;
470        Ok(())
471    }
472}
473
474/// A view definition.
475#[derive(Debug, Clone, SpacetimeType)]
476#[sats(crate = crate)]
477#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
478pub struct RawViewDefV10 {
479    /// The name of the view function as defined in the module
480    pub source_name: RawIdentifier,
481
482    /// The index of the view in the module's list of views.
483    pub index: u32,
484
485    /// Is this a public or a private view?
486    /// Currently only public views are supported.
487    /// Private views may be supported in the future.
488    pub is_public: bool,
489
490    /// Is this view anonymous?
491    /// An anonymous view does not know who called it.
492    /// Specifically, it is a view that has an `AnonymousViewContext` as its first argument.
493    /// This type does not have access to the `Identity` of the caller.
494    pub is_anonymous: bool,
495
496    /// The types and optional names of the parameters, in order.
497    /// This `ProductType` need not be registered in the typespace.
498    pub params: ProductType,
499
500    /// The return type of the view.
501    /// Either `T`, `Option<T>`, or `Vec<T>` where `T` is a `SpacetimeType`.
502    ///
503    /// More strictly `T` must be a SATS `ProductType`,
504    /// however this will be validated by the server on publish.
505    ///
506    /// This is the single source of truth for the views's columns.
507    /// All elements of the inner `ProductType` must have names.
508    /// This again will be validated by the server on publish.
509    pub return_type: AlgebraicType,
510}
511
512impl RawModuleDefV10 {
513    /// Get the types section, if present.
514    pub fn types(&self) -> Option<&Vec<RawTypeDefV10>> {
515        self.sections.iter().find_map(|s| match s {
516            RawModuleDefV10Section::Types(types) => Some(types),
517            _ => None,
518        })
519    }
520
521    /// Get the tables section, if present.
522    pub fn tables(&self) -> Option<&Vec<RawTableDefV10>> {
523        self.sections.iter().find_map(|s| match s {
524            RawModuleDefV10Section::Tables(tables) => Some(tables),
525            _ => None,
526        })
527    }
528
529    /// Get the typespace section, if present.
530    pub fn typespace(&self) -> Option<&Typespace> {
531        self.sections.iter().find_map(|s| match s {
532            RawModuleDefV10Section::Typespace(ts) => Some(ts),
533            _ => None,
534        })
535    }
536
537    /// Get the reducers section, if present.
538    pub fn reducers(&self) -> Option<&Vec<RawReducerDefV10>> {
539        self.sections.iter().find_map(|s| match s {
540            RawModuleDefV10Section::Reducers(reducers) => Some(reducers),
541            _ => None,
542        })
543    }
544
545    /// Get the procedures section, if present.
546    pub fn procedures(&self) -> Option<&Vec<RawProcedureDefV10>> {
547        self.sections.iter().find_map(|s| match s {
548            RawModuleDefV10Section::Procedures(procedures) => Some(procedures),
549            _ => None,
550        })
551    }
552
553    /// Get the views section, if present.
554    pub fn views(&self) -> Option<&Vec<RawViewDefV10>> {
555        self.sections.iter().find_map(|s| match s {
556            RawModuleDefV10Section::Views(views) => Some(views),
557            _ => None,
558        })
559    }
560
561    /// Get the schedules section, if present.
562    pub fn schedules(&self) -> Option<&Vec<RawScheduleDefV10>> {
563        self.sections.iter().find_map(|s| match s {
564            RawModuleDefV10Section::Schedules(schedules) => Some(schedules),
565            _ => None,
566        })
567    }
568
569    /// Get the lifecycle reducers section, if present.
570    pub fn lifecycle_reducers(&self) -> Option<&Vec<RawLifeCycleReducerDefV10>> {
571        self.sections.iter().find_map(|s| match s {
572            RawModuleDefV10Section::LifeCycleReducers(lcrs) => Some(lcrs),
573            _ => None,
574        })
575    }
576
577    pub fn tables_mut_for_tests(&mut self) -> &mut Vec<RawTableDefV10> {
578        self.sections
579            .iter_mut()
580            .find_map(|s| match s {
581                RawModuleDefV10Section::Tables(tables) => Some(tables),
582                _ => None,
583            })
584            .expect("Tables section must exist for tests")
585    }
586
587    // Get the row-level security section, if present.
588    pub fn row_level_security(&self) -> Option<&Vec<RawRowLevelSecurityDefV10>> {
589        self.sections.iter().find_map(|s| match s {
590            RawModuleDefV10Section::RowLevelSecurity(rls) => Some(rls),
591            _ => None,
592        })
593    }
594
595    pub fn case_conversion_policy(&self) -> CaseConversionPolicy {
596        self.sections
597            .iter()
598            .find_map(|s| match s {
599                RawModuleDefV10Section::CaseConversionPolicy(policy) => Some(*policy),
600                _ => None,
601            })
602            .unwrap_or_default()
603    }
604
605    pub fn explicit_names(&self) -> Option<&ExplicitNames> {
606        self.sections.iter().find_map(|s| match s {
607            RawModuleDefV10Section::ExplicitNames(names) => Some(names),
608            _ => None,
609        })
610    }
611}
612
613/// A builder for a [`RawModuleDefV10`].
614#[derive(Default)]
615pub struct RawModuleDefV10Builder {
616    /// The module definition being built.
617    module: RawModuleDefV10,
618
619    /// The type map from `T: 'static` Rust types to sats types.
620    type_map: BTreeMap<TypeId, AlgebraicTypeRef>,
621}
622
623impl RawModuleDefV10Builder {
624    /// Create a new, empty `RawModuleDefV10Builder`.
625    pub fn new() -> Self {
626        Default::default()
627    }
628
629    /// Get mutable access to the typespace section, creating it if missing.
630    fn typespace_mut(&mut self) -> &mut Typespace {
631        let idx = self
632            .module
633            .sections
634            .iter()
635            .position(|s| matches!(s, RawModuleDefV10Section::Typespace(_)))
636            .unwrap_or_else(|| {
637                self.module
638                    .sections
639                    .push(RawModuleDefV10Section::Typespace(Typespace::EMPTY.clone()));
640                self.module.sections.len() - 1
641            });
642
643        match &mut self.module.sections[idx] {
644            RawModuleDefV10Section::Typespace(ts) => ts,
645            _ => unreachable!("Just ensured Typespace section exists"),
646        }
647    }
648
649    /// Get mutable access to the reducers section, creating it if missing.
650    fn reducers_mut(&mut self) -> &mut Vec<RawReducerDefV10> {
651        let idx = self
652            .module
653            .sections
654            .iter()
655            .position(|s| matches!(s, RawModuleDefV10Section::Reducers(_)))
656            .unwrap_or_else(|| {
657                self.module.sections.push(RawModuleDefV10Section::Reducers(Vec::new()));
658                self.module.sections.len() - 1
659            });
660
661        match &mut self.module.sections[idx] {
662            RawModuleDefV10Section::Reducers(reducers) => reducers,
663            _ => unreachable!("Just ensured Reducers section exists"),
664        }
665    }
666
667    /// Get mutable access to the procedures section, creating it if missing.
668    fn procedures_mut(&mut self) -> &mut Vec<RawProcedureDefV10> {
669        let idx = self
670            .module
671            .sections
672            .iter()
673            .position(|s| matches!(s, RawModuleDefV10Section::Procedures(_)))
674            .unwrap_or_else(|| {
675                self.module
676                    .sections
677                    .push(RawModuleDefV10Section::Procedures(Vec::new()));
678                self.module.sections.len() - 1
679            });
680
681        match &mut self.module.sections[idx] {
682            RawModuleDefV10Section::Procedures(procedures) => procedures,
683            _ => unreachable!("Just ensured Procedures section exists"),
684        }
685    }
686
687    /// Get mutable access to the views section, creating it if missing.
688    fn views_mut(&mut self) -> &mut Vec<RawViewDefV10> {
689        let idx = self
690            .module
691            .sections
692            .iter()
693            .position(|s| matches!(s, RawModuleDefV10Section::Views(_)))
694            .unwrap_or_else(|| {
695                self.module.sections.push(RawModuleDefV10Section::Views(Vec::new()));
696                self.module.sections.len() - 1
697            });
698
699        match &mut self.module.sections[idx] {
700            RawModuleDefV10Section::Views(views) => views,
701            _ => unreachable!("Just ensured Views section exists"),
702        }
703    }
704
705    /// Get mutable access to the schedules section, creating it if missing.
706    fn schedules_mut(&mut self) -> &mut Vec<RawScheduleDefV10> {
707        let idx = self
708            .module
709            .sections
710            .iter()
711            .position(|s| matches!(s, RawModuleDefV10Section::Schedules(_)))
712            .unwrap_or_else(|| {
713                self.module.sections.push(RawModuleDefV10Section::Schedules(Vec::new()));
714                self.module.sections.len() - 1
715            });
716
717        match &mut self.module.sections[idx] {
718            RawModuleDefV10Section::Schedules(schedules) => schedules,
719            _ => unreachable!("Just ensured Schedules section exists"),
720        }
721    }
722
723    /// Get mutable access to the lifecycle reducers section, creating it if missing.
724    fn lifecycle_reducers_mut(&mut self) -> &mut Vec<RawLifeCycleReducerDefV10> {
725        let idx = self
726            .module
727            .sections
728            .iter()
729            .position(|s| matches!(s, RawModuleDefV10Section::LifeCycleReducers(_)))
730            .unwrap_or_else(|| {
731                self.module
732                    .sections
733                    .push(RawModuleDefV10Section::LifeCycleReducers(Vec::new()));
734                self.module.sections.len() - 1
735            });
736
737        match &mut self.module.sections[idx] {
738            RawModuleDefV10Section::LifeCycleReducers(lcrs) => lcrs,
739            _ => unreachable!("Just ensured LifeCycleReducers section exists"),
740        }
741    }
742
743    /// Get mutable access to the types section, creating it if missing.
744    fn types_mut(&mut self) -> &mut Vec<RawTypeDefV10> {
745        let idx = self
746            .module
747            .sections
748            .iter()
749            .position(|s| matches!(s, RawModuleDefV10Section::Types(_)))
750            .unwrap_or_else(|| {
751                self.module.sections.push(RawModuleDefV10Section::Types(Vec::new()));
752                self.module.sections.len() - 1
753            });
754
755        match &mut self.module.sections[idx] {
756            RawModuleDefV10Section::Types(types) => types,
757            _ => unreachable!("Just ensured Types section exists"),
758        }
759    }
760
761    /// Add a type to the in-progress module.
762    ///
763    /// The returned type must satisfy `AlgebraicType::is_valid_for_client_type_definition` or `AlgebraicType::is_valid_for_client_type_use`.
764    pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
765        TypespaceBuilder::add_type::<T>(self)
766    }
767
768    /// Get mutable access to the row-level security section, creating it if missing.
769    fn row_level_security_mut(&mut self) -> &mut Vec<RawRowLevelSecurityDefV10> {
770        let idx = self
771            .module
772            .sections
773            .iter()
774            .position(|s| matches!(s, RawModuleDefV10Section::RowLevelSecurity(_)))
775            .unwrap_or_else(|| {
776                self.module
777                    .sections
778                    .push(RawModuleDefV10Section::RowLevelSecurity(Vec::new()));
779                self.module.sections.len() - 1
780            });
781
782        match &mut self.module.sections[idx] {
783            RawModuleDefV10Section::RowLevelSecurity(rls) => rls,
784            _ => unreachable!("Just ensured RowLevelSecurity section exists"),
785        }
786    }
787
788    /// Get mutable access to the case conversion policy, creating it if missing.
789    fn explicit_names_mut(&mut self) -> &mut ExplicitNames {
790        let idx = self
791            .module
792            .sections
793            .iter()
794            .position(|s| matches!(s, RawModuleDefV10Section::ExplicitNames(_)))
795            .unwrap_or_else(|| {
796                self.module
797                    .sections
798                    .push(RawModuleDefV10Section::ExplicitNames(ExplicitNames::default()));
799                self.module.sections.len() - 1
800            });
801
802        match &mut self.module.sections[idx] {
803            RawModuleDefV10Section::ExplicitNames(names) => names,
804            _ => unreachable!("Just ensured ExplicitNames section exists"),
805        }
806    }
807
808    /// Create a table builder.
809    ///
810    /// Does not validate that the product_type_ref is valid; this is left to the module validation code.
811    pub fn build_table(
812        &mut self,
813        source_name: impl Into<RawIdentifier>,
814        product_type_ref: AlgebraicTypeRef,
815    ) -> RawTableDefBuilderV10<'_> {
816        let source_name = source_name.into();
817        RawTableDefBuilderV10 {
818            module: &mut self.module,
819            table: RawTableDefV10 {
820                source_name,
821                product_type_ref,
822                indexes: vec![],
823                constraints: vec![],
824                sequences: vec![],
825                primary_key: ColList::empty(),
826                table_type: TableType::User,
827                table_access: TableAccess::Public,
828                default_values: vec![],
829                is_event: false,
830            },
831        }
832    }
833
834    /// Build a new table with a product type.
835    /// Adds the type to the module.
836    pub fn build_table_with_new_type(
837        &mut self,
838        table_name: impl Into<RawIdentifier>,
839        product_type: impl Into<ProductType>,
840        custom_ordering: bool,
841    ) -> RawTableDefBuilderV10<'_> {
842        let table_name = table_name.into();
843
844        let product_type_ref = self.add_algebraic_type(
845            [],
846            table_name.clone(),
847            AlgebraicType::from(product_type.into()),
848            custom_ordering,
849        );
850
851        self.build_table(table_name, product_type_ref)
852    }
853
854    /// Build a new table with a product type, for testing.
855    /// Adds the type to the module.
856    pub fn build_table_with_new_type_for_tests(
857        &mut self,
858        table_name: impl Into<RawIdentifier>,
859        mut product_type: ProductType,
860        custom_ordering: bool,
861    ) -> RawTableDefBuilderV10<'_> {
862        self.add_expand_product_type_for_tests(&mut 0, &mut product_type);
863        self.build_table_with_new_type(table_name, product_type, custom_ordering)
864    }
865
866    fn add_expand_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut AlgebraicType) {
867        if ty.is_valid_for_client_type_use() {
868            return;
869        }
870
871        match ty {
872            AlgebraicType::Product(prod_ty) => self.add_expand_product_type_for_tests(name_gen, prod_ty),
873            AlgebraicType::Sum(sum_type) => {
874                if let Some(wrapped) = sum_type.as_option_mut() {
875                    self.add_expand_type_for_tests(name_gen, wrapped);
876                } else {
877                    for elem in sum_type.variants.iter_mut() {
878                        self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
879                    }
880                }
881            }
882            AlgebraicType::Array(ty) => {
883                self.add_expand_type_for_tests(name_gen, &mut ty.elem_ty);
884                return;
885            }
886            _ => return,
887        }
888
889        // Make the type into a ref.
890        let name = *name_gen;
891        let add_ty = core::mem::replace(ty, AlgebraicType::U8);
892        *ty = AlgebraicType::Ref(self.add_algebraic_type([], RawIdentifier::new(format!("gen_{name}")), add_ty, true));
893        *name_gen += 1;
894    }
895
896    fn add_expand_product_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut ProductType) {
897        for elem in ty.elements.iter_mut() {
898            self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
899        }
900    }
901
902    /// Add a type to the typespace, along with a type alias declaring its name.
903    /// This method should only be used for `AlgebraicType`s not corresponding to a Rust
904    /// type that implements `SpacetimeType`.
905    ///
906    /// Returns a reference to the newly-added type.
907    ///
908    /// NOT idempotent, calling this twice with the same name will cause errors during validation.
909    ///
910    /// You must set `custom_ordering` if you're not using the default element ordering.
911    pub fn add_algebraic_type(
912        &mut self,
913        scope: impl IntoIterator<Item = RawIdentifier>,
914        source_name: impl Into<RawIdentifier>,
915        ty: AlgebraicType,
916        custom_ordering: bool,
917    ) -> AlgebraicTypeRef {
918        let ty_ref = self.typespace_mut().add(ty);
919        let scope = scope.into_iter().collect();
920        let source_name = source_name.into();
921        self.types_mut().push(RawTypeDefV10 {
922            source_name: RawScopedTypeNameV10 { source_name, scope },
923            ty: ty_ref,
924            custom_ordering,
925        });
926        // We don't add a `TypeId` to `self.type_map`, because there may not be a corresponding Rust type!
927        // e.g. if we are randomly generating types in proptests.
928        ty_ref
929    }
930
931    /// Add a reducer to the in-progress module.
932    /// Accepts a `ProductType` of reducer arguments for convenience.
933    /// The `ProductType` need not be registered in the typespace.
934    ///
935    /// Importantly, if the reducer's first argument is a `ReducerContext`, that
936    /// information should not be provided to this method.
937    /// That is an implementation detail handled by the module bindings and can be ignored.
938    /// As far as the module definition is concerned, the reducer's arguments
939    /// start with the first non-`ReducerContext` argument.
940    ///
941    /// (It is impossible, with the current implementation of `ReducerContext`, to
942    /// have more than one `ReducerContext` argument, at least in Rust.
943    /// This is because `SpacetimeType` is not implemented for `ReducerContext`,
944    /// so it can never act like an ordinary argument.)
945    pub fn add_reducer(&mut self, source_name: impl Into<RawIdentifier>, params: ProductType) {
946        self.reducers_mut().push(RawReducerDefV10 {
947            source_name: source_name.into(),
948            params,
949            visibility: FunctionVisibility::ClientCallable,
950            ok_return_type: reducer_default_ok_return_type(),
951            err_return_type: reducer_default_err_return_type(),
952        });
953    }
954
955    /// Add a procedure to the in-progress module.
956    ///
957    /// Accepts a `ProductType` of arguments.
958    /// The arguments `ProductType` need not be registered in the typespace.
959    ///
960    /// Also accepts an `AlgebraicType` return type.
961    /// If this is a user-defined product or sum type,
962    /// it should be registered in the typespace and indirected through an `AlgebraicType::Ref`.
963    ///
964    /// The `&mut ProcedureContext` first argument to the procedure should not be included in the `params`.
965    pub fn add_procedure(
966        &mut self,
967        source_name: impl Into<RawIdentifier>,
968        params: ProductType,
969        return_type: AlgebraicType,
970    ) {
971        self.procedures_mut().push(RawProcedureDefV10 {
972            source_name: source_name.into(),
973            params,
974            return_type,
975            visibility: FunctionVisibility::ClientCallable,
976        })
977    }
978
979    /// Add a view to the in-progress module.
980    pub fn add_view(
981        &mut self,
982        source_name: impl Into<RawIdentifier>,
983        index: usize,
984        is_public: bool,
985        is_anonymous: bool,
986        params: ProductType,
987        return_type: AlgebraicType,
988    ) {
989        self.views_mut().push(RawViewDefV10 {
990            source_name: source_name.into(),
991            index: index as u32,
992            is_public,
993            is_anonymous,
994            params,
995            return_type,
996        });
997    }
998
999    /// Add a lifecycle reducer assignment to the module.
1000    ///
1001    /// The function must be a previously-added reducer.
1002    pub fn add_lifecycle_reducer(
1003        &mut self,
1004        lifecycle_spec: Lifecycle,
1005        function_name: impl Into<RawIdentifier>,
1006        params: ProductType,
1007    ) {
1008        let function_name = function_name.into();
1009        self.lifecycle_reducers_mut().push(RawLifeCycleReducerDefV10 {
1010            lifecycle_spec,
1011            function_name: function_name.clone(),
1012        });
1013
1014        self.reducers_mut().push(RawReducerDefV10 {
1015            source_name: function_name,
1016            params,
1017            visibility: FunctionVisibility::Private,
1018            ok_return_type: reducer_default_ok_return_type(),
1019            err_return_type: reducer_default_err_return_type(),
1020        });
1021    }
1022
1023    /// Add a schedule definition to the module.
1024    ///
1025    /// The `function_name` should name a reducer or procedure
1026    /// which accepts one argument, a row of the specified table.
1027    ///
1028    /// The table must have the appropriate columns for a scheduled table.
1029    pub fn add_schedule(
1030        &mut self,
1031        table: impl Into<RawIdentifier>,
1032        column: impl Into<ColId>,
1033        function: impl Into<RawIdentifier>,
1034    ) {
1035        self.schedules_mut().push(RawScheduleDefV10 {
1036            source_name: None,
1037            table_name: table.into(),
1038            schedule_at_col: column.into(),
1039            function_name: function.into(),
1040        });
1041    }
1042
1043    /// Add a row-level security policy to the module.
1044    ///
1045    /// The `sql` expression should be a valid SQL expression that will be used to filter rows.
1046    ///
1047    /// **NOTE**: The `sql` expression must be unique within the module.
1048    pub fn add_row_level_security(&mut self, sql: &str) {
1049        self.row_level_security_mut()
1050            .push(RawRowLevelSecurityDefV10 { sql: sql.into() });
1051    }
1052
1053    pub fn add_explicit_names(&mut self, names: ExplicitNames) {
1054        self.explicit_names_mut().merge(names);
1055    }
1056
1057    /// Set the case conversion policy for this module.
1058    ///
1059    /// By default, SpacetimeDB applies `SnakeCase` conversion to table names,
1060    /// column names, reducer names, etc. Use `CaseConversionPolicy::None` to
1061    /// disable all case conversion (useful for modules with existing data that
1062    /// was stored under the original naming convention).
1063    pub fn set_case_conversion_policy(&mut self, policy: CaseConversionPolicy) {
1064        // Remove any existing policy section.
1065        self.module
1066            .sections
1067            .retain(|s| !matches!(s, RawModuleDefV10Section::CaseConversionPolicy(_)));
1068        self.module
1069            .sections
1070            .push(RawModuleDefV10Section::CaseConversionPolicy(policy));
1071    }
1072
1073    /// Finish building, consuming the builder and returning the module.
1074    /// The module should be validated before use.
1075    pub fn finish(self) -> RawModuleDefV10 {
1076        self.module
1077    }
1078}
1079
1080/// Implement TypespaceBuilder for V10
1081impl TypespaceBuilder for RawModuleDefV10Builder {
1082    fn add(
1083        &mut self,
1084        typeid: TypeId,
1085        source_name: Option<&'static str>,
1086        make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
1087    ) -> AlgebraicType {
1088        if let btree_map::Entry::Occupied(o) = self.type_map.entry(typeid) {
1089            AlgebraicType::Ref(*o.get())
1090        } else {
1091            let slot_ref = {
1092                let ts = self.typespace_mut();
1093                // Bind a fresh alias to the unit type.
1094                let slot_ref = ts.add(AlgebraicType::unit());
1095                // Relate `typeid -> fresh alias`.
1096                self.type_map.insert(typeid, slot_ref);
1097
1098                // Alias provided? Relate `name -> slot_ref`.
1099                if let Some(sats_name) = source_name {
1100                    let source_name = sats_name_to_scoped_name_v10(sats_name);
1101
1102                    self.types_mut().push(RawTypeDefV10 {
1103                        source_name,
1104                        ty: slot_ref,
1105                        // TODO(1.0): we need to update the `TypespaceBuilder` trait to include
1106                        // a `custom_ordering` parameter.
1107                        // For now, we assume all types have custom orderings, since the derive
1108                        // macro doesn't know about the default ordering yet.
1109                        custom_ordering: true,
1110                    });
1111                }
1112                slot_ref
1113            };
1114
1115            // Borrow of `v` has ended here, so we can now convince the borrow checker.
1116            let ty = make_ty(self);
1117            self.typespace_mut()[slot_ref] = ty;
1118            AlgebraicType::Ref(slot_ref)
1119        }
1120    }
1121}
1122
1123pub fn reducer_default_ok_return_type() -> AlgebraicType {
1124    AlgebraicType::unit()
1125}
1126
1127pub fn reducer_default_err_return_type() -> AlgebraicType {
1128    AlgebraicType::String
1129}
1130
1131/// Convert a string from a sats type-name annotation like `#[sats(name = "namespace.name")]` to a `RawScopedTypeNameV9`.
1132/// We split the input on the strings `"::"` and `"."` to split up module paths.
1133///
1134pub fn sats_name_to_scoped_name_v10(sats_name: &str) -> RawScopedTypeNameV10 {
1135    // We can't use `&[char]: Pattern` for `split` here because "::" is not a char :/
1136    let mut scope: Vec<RawIdentifier> = sats_name
1137        .split("::")
1138        .flat_map(|s| s.split('.'))
1139        .map(RawIdentifier::new)
1140        .collect();
1141    // Unwrapping to "" will result in a validation error down the line, which is exactly what we want.
1142    let source_name = scope.pop().unwrap_or_default();
1143    RawScopedTypeNameV10 {
1144        scope: scope.into(),
1145        source_name,
1146    }
1147}
1148
1149/// Builder for a `RawTableDefV10`.
1150pub struct RawTableDefBuilderV10<'a> {
1151    module: &'a mut RawModuleDefV10,
1152    table: RawTableDefV10,
1153}
1154
1155impl RawTableDefBuilderV10<'_> {
1156    /// Set the table type.
1157    ///
1158    /// This is not about column algebraic types, but about whether the table
1159    /// was created by the system or the user.
1160    pub fn with_type(mut self, table_type: TableType) -> Self {
1161        self.table.table_type = table_type;
1162        self
1163    }
1164
1165    /// Sets the access rights for the table and return it.
1166    pub fn with_access(mut self, table_access: TableAccess) -> Self {
1167        self.table.table_access = table_access;
1168        self
1169    }
1170
1171    /// Sets whether this table is an event table.
1172    pub fn with_event(mut self, is_event: bool) -> Self {
1173        self.table.is_event = is_event;
1174        self
1175    }
1176
1177    /// Generates a `RawConstraintDefV10` using the supplied `columns`.
1178    pub fn with_unique_constraint(mut self, columns: impl Into<ColList>) -> Self {
1179        let columns = columns.into();
1180        self.table.constraints.push(RawConstraintDefV10 {
1181            source_name: None,
1182            data: RawConstraintDataV10::Unique(RawUniqueConstraintDataV10 { columns }),
1183        });
1184
1185        self
1186    }
1187
1188    /// Adds a primary key to the table.
1189    /// You must also add a unique constraint on the primary key column.
1190    pub fn with_primary_key(mut self, column: impl Into<ColId>) -> Self {
1191        self.table.primary_key = ColList::new(column.into());
1192        self
1193    }
1194
1195    /// Adds a primary key to the table, with corresponding unique constraint and sequence definitions.
1196    /// You will also need to call [`Self::with_index`] to create an index on `column`.
1197    pub fn with_auto_inc_primary_key(self, column: impl Into<ColId>) -> Self {
1198        let column = column.into();
1199        self.with_primary_key(column)
1200            .with_unique_constraint(column)
1201            .with_column_sequence(column)
1202    }
1203
1204    /// Generates a [RawIndexDefV10] using the supplied `columns`.
1205    pub fn with_index(
1206        mut self,
1207        algorithm: RawIndexAlgorithm,
1208        source_name: impl Into<RawIdentifier>,
1209        accessor_name: impl Into<RawIdentifier>,
1210    ) -> Self {
1211        self.table.indexes.push(RawIndexDefV10 {
1212            source_name: Some(source_name.into()),
1213            accessor_name: Some(accessor_name.into()),
1214            algorithm,
1215        });
1216        self
1217    }
1218
1219    /// Generates a [RawIndexDefV10] using the supplied `columns`.
1220    pub fn with_index_no_accessor_name(
1221        mut self,
1222        algorithm: RawIndexAlgorithm,
1223        source_name: impl Into<RawIdentifier>,
1224    ) -> Self {
1225        self.table.indexes.push(RawIndexDefV10 {
1226            source_name: Some(source_name.into()),
1227            accessor_name: None,
1228            algorithm,
1229        });
1230        self
1231    }
1232
1233    /// Adds a [RawSequenceDefV10] on the supplied `column`.
1234    pub fn with_column_sequence(mut self, column: impl Into<ColId>) -> Self {
1235        let column = column.into();
1236        self.table.sequences.push(RawSequenceDefV10 {
1237            source_name: None,
1238            column,
1239            start: None,
1240            min_value: None,
1241            max_value: None,
1242            increment: 1,
1243        });
1244
1245        self
1246    }
1247
1248    /// Adds a default value for a column.
1249    pub fn with_default_column_value(mut self, column: impl Into<ColId>, value: AlgebraicValue) -> Self {
1250        let col_id = column.into();
1251        self.table.default_values.push(RawColumnDefaultValueV10 {
1252            col_id,
1253            value: spacetimedb_sats::bsatn::to_vec(&value).unwrap().into(),
1254        });
1255
1256        self
1257    }
1258
1259    /// Build the table and add it to the module, returning the `product_type_ref` of the table.
1260    pub fn finish(self) -> AlgebraicTypeRef {
1261        let product_type_ref = self.table.product_type_ref;
1262
1263        let tables = match self
1264            .module
1265            .sections
1266            .iter_mut()
1267            .find(|s| matches!(s, RawModuleDefV10Section::Tables(_)))
1268        {
1269            Some(RawModuleDefV10Section::Tables(t)) => t,
1270            _ => {
1271                self.module.sections.push(RawModuleDefV10Section::Tables(Vec::new()));
1272                match self.module.sections.last_mut().expect("Just pushed Tables section") {
1273                    RawModuleDefV10Section::Tables(t) => t,
1274                    _ => unreachable!(),
1275                }
1276            }
1277        };
1278
1279        tables.push(self.table);
1280        product_type_ref
1281    }
1282
1283    /// Find a column position by its name in the table's product type.
1284    pub fn find_col_pos_by_name(&self, column: impl AsRef<str>) -> Option<ColId> {
1285        let column = column.as_ref();
1286
1287        let typespace = self.module.sections.iter().find_map(|s| {
1288            if let RawModuleDefV10Section::Typespace(ts) = s {
1289                Some(ts)
1290            } else {
1291                None
1292            }
1293        })?;
1294
1295        typespace
1296            .get(self.table.product_type_ref)?
1297            .as_product()?
1298            .elements
1299            .iter()
1300            .position(|x| x.has_name(column.as_ref()))
1301            .map(|i| ColId(i as u16))
1302    }
1303}