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