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