spacetimedb_lib/db/raw_def/
v9.rs

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