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: 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([], table_name.clone(), product_type.into(), custom_ordering);
502
503        self.build_table(table_name, product_type_ref)
504    }
505
506    /// Build a new table with a product type, for testing.
507    /// Adds the type to the module.
508    pub fn build_table_with_new_type_for_tests(
509        &mut self,
510        table_name: impl Into<RawIdentifier>,
511        mut product_type: spacetimedb_sats::ProductType,
512        custom_ordering: bool,
513    ) -> RawTableDefBuilder {
514        self.add_expand_product_type_for_tests(&mut 0, &mut product_type);
515
516        self.build_table_with_new_type(table_name, product_type, custom_ordering)
517    }
518
519    fn add_expand_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut AlgebraicType) {
520        if ty.is_valid_for_client_type_use() {
521            return;
522        }
523
524        match ty {
525            AlgebraicType::Product(prod_ty) => self.add_expand_product_type_for_tests(name_gen, prod_ty),
526            AlgebraicType::Sum(sum_type) => {
527                if let Some(wrapped) = sum_type.as_option_mut() {
528                    self.add_expand_type_for_tests(name_gen, wrapped);
529                } else {
530                    for elem in sum_type.variants.iter_mut() {
531                        self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
532                    }
533                }
534            }
535            AlgebraicType::Array(ty) => {
536                self.add_expand_type_for_tests(name_gen, &mut ty.elem_ty);
537                return;
538            }
539            _ => return,
540        }
541
542        // Make the type into a ref.
543        let name = *name_gen;
544        let add_ty = core::mem::replace(ty, AlgebraicType::U8);
545        *ty = AlgebraicType::Ref(self.add_algebraic_type([], format!("gen_{name}"), add_ty, true));
546        *name_gen += 1;
547    }
548
549    fn add_expand_product_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut ProductType) {
550        for elem in ty.elements.iter_mut() {
551            self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
552        }
553    }
554
555    /// Add a type to the typespace, along with a type alias declaring its name.
556    /// This method should only be use for `AlgebraicType`s not corresponding to a Rust
557    /// type that implements `SpacetimeType`.
558    ///
559    /// Returns a reference to the newly-added type.
560    ///
561    /// NOT idempotent, calling this twice with the same name will cause errors during
562    /// validation.
563    ///
564    /// You must set `custom_ordering` if you're not using the default element ordering.
565    pub fn add_algebraic_type(
566        &mut self,
567        scope: impl IntoIterator<Item = RawIdentifier>,
568        name: impl Into<RawIdentifier>,
569        ty: spacetimedb_sats::AlgebraicType,
570        custom_ordering: bool,
571    ) -> AlgebraicTypeRef {
572        let ty = self.module.typespace.add(ty);
573        let scope = scope.into_iter().collect();
574        let name = name.into();
575        self.module.types.push(RawTypeDefV9 {
576            name: RawScopedTypeNameV9 { name, scope },
577            ty,
578            custom_ordering,
579        });
580        // 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.
581        ty
582    }
583
584    /// Add a reducer to the in-progress module.
585    /// Accepts a `ProductType` of reducer arguments for convenience.
586    /// The `ProductType` need not be registered in the typespace.
587    ///
588    /// Importantly, if the reducer's first argument is a `ReducerContext`, that
589    /// information should not be provided to this method.
590    /// That is an implementation detail handled by the module bindings and can be ignored.
591    /// As far as the module definition is concerned, the reducer's arguments
592    /// start with the first non-`ReducerContext` argument.
593    ///
594    /// (It is impossible, with the current implementation of `ReducerContext`, to
595    /// have more than one `ReducerContext` argument, at least in Rust.
596    /// This is because `SpacetimeType` is not implemented for `ReducerContext`,
597    /// so it can never act like an ordinary argument.)
598    pub fn add_reducer(
599        &mut self,
600        name: impl Into<RawIdentifier>,
601        params: spacetimedb_sats::ProductType,
602        lifecycle: Option<Lifecycle>,
603    ) {
604        self.module.reducers.push(RawReducerDefV9 {
605            name: name.into(),
606            params,
607            lifecycle,
608        });
609    }
610
611    /// Add a row-level security policy to the module.
612    ///
613    /// The `sql` expression should be a valid SQL expression that will be used to filter rows.
614    ///
615    /// **NOTE**: The `sql` expression must be unique within the module.
616    pub fn add_row_level_security(&mut self, sql: &str) {
617        self.module
618            .row_level_security
619            .push(RawRowLevelSecurityDefV9 { sql: sql.into() });
620    }
621
622    /// Get the typespace of the module.
623    pub fn typespace(&self) -> &Typespace {
624        &self.module.typespace
625    }
626
627    /// Finish building, consuming the builder and returning the module.
628    /// The module should be validated before use.
629    pub fn finish(self) -> RawModuleDefV9 {
630        self.module
631    }
632}
633
634/// Convert a string from a sats type-name annotation like `#[sats(name = "namespace.name")]` to a `RawScopedTypeNameV9`.
635/// We split the input on the strings `"::"` and `"."` to split up module paths.
636///
637/// TODO(1.0): build namespacing directly into the bindings macros so that we don't need to do this.
638pub fn sats_name_to_scoped_name(sats_name: &str) -> RawScopedTypeNameV9 {
639    // We can't use `&[char]: Pattern` for `split` here because "::" is not a char :/
640    let mut scope: Vec<RawIdentifier> = sats_name.split("::").flat_map(|s| s.split('.')).map_into().collect();
641    // Unwrapping to "" will result in a validation error down the line, which is exactly what we want.
642    let name = scope.pop().unwrap_or_default();
643    RawScopedTypeNameV9 {
644        scope: scope.into(),
645        name,
646    }
647}
648
649impl TypespaceBuilder for RawModuleDefV9Builder {
650    fn add(
651        &mut self,
652        typeid: TypeId,
653        name: Option<&'static str>,
654        make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
655    ) -> AlgebraicType {
656        let r = match self.type_map.entry(typeid) {
657            btree_map::Entry::Occupied(o) => *o.get(),
658            btree_map::Entry::Vacant(v) => {
659                // Bind a fresh alias to the unit type.
660                let slot_ref = self.module.typespace.add(AlgebraicType::unit());
661                // Relate `typeid -> fresh alias`.
662                v.insert(slot_ref);
663
664                // Alias provided? Relate `name -> slot_ref`.
665                if let Some(sats_name) = name {
666                    let name = sats_name_to_scoped_name(sats_name);
667
668                    self.module.types.push(RawTypeDefV9 {
669                        name,
670                        ty: slot_ref,
671                        // TODO(1.0): we need to update the `TypespaceBuilder` trait to include
672                        // a `custom_ordering` parameter.
673                        // For now, we assume all types have custom orderings, since the derive
674                        // macro doesn't know about the default ordering yet.
675                        custom_ordering: true,
676                    });
677                }
678
679                // Borrow of `v` has ended here, so we can now convince the borrow checker.
680                let ty = make_ty(self);
681                self.module.typespace[slot_ref] = ty;
682                slot_ref
683            }
684        };
685        AlgebraicType::Ref(r)
686    }
687}
688
689/// Builder for a `RawTableDef`.
690pub struct RawTableDefBuilder<'a> {
691    module_def: &'a mut RawModuleDefV9,
692    table: RawTableDefV9,
693}
694
695impl RawTableDefBuilder<'_> {
696    /// Sets the type of the table and return it.
697    ///
698    /// This is not about column algebraic types, but about whether the table
699    /// was created by the system or the user.
700    pub fn with_type(mut self, table_type: TableType) -> Self {
701        self.table.table_type = table_type;
702        self
703    }
704
705    /// Sets the access rights for the table and return it.
706    pub fn with_access(mut self, table_access: TableAccess) -> Self {
707        self.table.table_access = table_access;
708        self
709    }
710
711    /// Generates a [RawConstraintDefV9] using the supplied `columns`.
712    pub fn with_unique_constraint(mut self, columns: impl Into<ColList>) -> Self {
713        let columns = columns.into();
714        self.table.constraints.push(RawConstraintDefV9 {
715            name: None,
716            data: RawConstraintDataV9::Unique(RawUniqueConstraintDataV9 { columns }),
717        });
718        self
719    }
720
721    /// Adds a primary key to the table.
722    /// You must also add a unique constraint on the primary key column.
723    pub fn with_primary_key(mut self, column: impl Into<ColId>) -> Self {
724        self.table.primary_key = ColList::new(column.into());
725        self
726    }
727
728    /// Adds a primary key to the table, with corresponding unique constraint and sequence definitions.
729    /// You will also need to call [`Self::with_index`] to create an index on `column`.
730    pub fn with_auto_inc_primary_key(self, column: impl Into<ColId>) -> Self {
731        let column = column.into();
732        self.with_primary_key(column)
733            .with_unique_constraint(column)
734            .with_column_sequence(column)
735    }
736
737    /// Generates a [RawIndexDefV9] using the supplied `columns`.
738    pub fn with_index(mut self, algorithm: RawIndexAlgorithm, accessor_name: impl Into<RawIdentifier>) -> Self {
739        let accessor_name = accessor_name.into();
740
741        self.table.indexes.push(RawIndexDefV9 {
742            name: None,
743            accessor_name: Some(accessor_name),
744            algorithm,
745        });
746        self
747    }
748
749    /// Generates a [RawIndexDefV9] using the supplied `columns` but with no `accessor_name`.
750    pub fn with_index_no_accessor_name(mut self, algorithm: RawIndexAlgorithm) -> Self {
751        self.table.indexes.push(RawIndexDefV9 {
752            name: None,
753            accessor_name: None,
754            algorithm,
755        });
756        self
757    }
758
759    /// Adds a [RawSequenceDefV9] on the supplied `column`.
760    pub fn with_column_sequence(mut self, column: impl Into<ColId>) -> Self {
761        let column = column.into();
762        self.table.sequences.push(RawSequenceDefV9 {
763            name: None,
764            column,
765            start: None,
766            min_value: None,
767            max_value: None,
768            increment: 1,
769        });
770
771        self
772    }
773
774    /// Adds a schedule definition to the table.
775    ///
776    /// The table must have the appropriate columns for a scheduled table.
777    pub fn with_schedule(
778        mut self,
779        reducer_name: impl Into<RawIdentifier>,
780        scheduled_at_column: impl Into<ColId>,
781    ) -> Self {
782        let reducer_name = reducer_name.into();
783        let scheduled_at_column = scheduled_at_column.into();
784        self.table.schedule = Some(RawScheduleDefV9 {
785            name: None,
786            reducer_name,
787            scheduled_at_column,
788        });
789        self
790    }
791
792    /// Build the table and add it to the module, returning the `product_type_ref` of the table.
793    pub fn finish(self) -> AlgebraicTypeRef {
794        self.table.product_type_ref
795        // self is now dropped.
796    }
797
798    /// Get the column ID of the column with the specified name, if any.
799    ///
800    /// Returns `None` if this `TableDef` has been constructed with an invalid `ProductTypeRef`,
801    /// or if no column exists with that name.
802    pub fn find_col_pos_by_name(&self, column: impl AsRef<str>) -> Option<ColId> {
803        let column = column.as_ref();
804        self.columns()?
805            .iter()
806            .position(|x| x.name().is_some_and(|s| s == column))
807            .map(|x| x.into())
808    }
809
810    /// Get the columns of this type.
811    ///
812    /// Returns `None` if this `TableDef` has been constructed with an invalid `ProductTypeRef`.
813    fn columns(&self) -> Option<&[ProductTypeElement]> {
814        self.module_def
815            .typespace
816            .get(self.table.product_type_ref)
817            .and_then(|ty| ty.as_product())
818            .map(|p| &p.elements[..])
819    }
820}
821
822impl Drop for RawTableDefBuilder<'_> {
823    fn drop(&mut self) {
824        self.module_def.tables.push(self.table.clone());
825    }
826}