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