Skip to main content

spacetimedb_schema/
def.rs

1//! Canonicalized module definitions.
2//!
3//! This module contains a set of types that represent the canonical form of SpacetimeDB module definitions.
4//! These types are immutable to prevent accidental introduction of errors.
5//! The internal data structures of this module are not considered public API or ABI and may change
6//! at any time.
7//!
8//! Different module ABI versions correspond to different submodules of `spacetimedb_lib::db::raw_def`.
9//! All of these ABI versions can be converted to the standard form in this module via `TryFrom`.
10//! We provide streams of errors in case the conversion fails, to provide as much information
11//! to the user as possible about why their module is invalid.
12//!
13//! The `ModuleDef` type is the main type in this module. It contains all the information about a module, including its tables, reducers, typespace, and type metadata.
14//!
15//! After validation, a `ModuleDef` can be converted to the `*Schema` types in `crate::schema` for use in the database.
16//! (Eventually, we may unify these types...)
17
18use std::collections::BTreeMap;
19use std::fmt::{self, Debug, Write};
20use std::hash::Hash;
21
22use crate::error::{IdentifierError, ValidationErrors};
23use crate::identifier::Identifier;
24use crate::reducer_name::ReducerName;
25use crate::schema::{Schema, TableSchema};
26use crate::type_for_generate::{AlgebraicTypeUse, ProductTypeDef, TypespaceForGenerate};
27use deserialize::ArgsSeed;
28use enum_map::EnumMap;
29use indexmap::IndexMap;
30use itertools::Itertools;
31use spacetimedb_data_structures::error_stream::{CollectAllErrors, CombineErrors, ErrorStream};
32use spacetimedb_data_structures::map::{Equivalent, HashMap};
33use spacetimedb_lib::db::raw_def;
34use spacetimedb_lib::db::raw_def::v10::{
35    ExplicitNames, RawConstraintDefV10, RawIndexDefV10, RawLifeCycleReducerDefV10, RawModuleDefV10,
36    RawModuleDefV10Section, RawProcedureDefV10, RawReducerDefV10, RawRowLevelSecurityDefV10, RawScheduleDefV10,
37    RawScopedTypeNameV10, RawSequenceDefV10, RawTableDefV10, RawTypeDefV10, RawViewDefV10,
38};
39use spacetimedb_lib::db::raw_def::v9::{
40    Lifecycle, RawColumnDefaultValueV9, RawConstraintDataV9, RawConstraintDefV9, RawIndexAlgorithm, RawIndexDefV9,
41    RawMiscModuleExportV9, RawModuleDefV9, RawProcedureDefV9, RawReducerDefV9, RawRowLevelSecurityDefV9,
42    RawScheduleDefV9, RawScopedTypeNameV9, RawSequenceDefV9, RawSql, RawTableDefV9, RawTypeDefV9,
43    RawUniqueConstraintDataV9, RawViewDefV9, TableAccess, TableType,
44};
45use spacetimedb_lib::{ProductType, RawModuleDef};
46use spacetimedb_primitives::{ColId, ColList, ColOrCols, ColSet, ProcedureId, ReducerId, TableId, ViewFnPtr};
47use spacetimedb_sats::raw_identifier::RawIdentifier;
48use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, AlgebraicValue, Typespace};
49
50pub mod deserialize;
51pub mod error;
52pub mod validate;
53
54/// A map from `Identifier`s to values of type `T`.
55pub type IdentifierMap<T> = HashMap<Identifier, T>;
56
57/// A map from `RawIdentifier`s to values of type `T`.
58pub type StrMap<T> = HashMap<RawIdentifier, T>;
59
60// We may eventually want to reorganize this module to look more
61// like the system tables, with numeric IDs used for lookups
62// in addition to `Identifier`s.
63//
64// If that path is taken, it might be possible to have this type
65// entirely subsume the various `Schema` types, which would be cool.
66
67/// A validated, canonicalized, immutable module definition.
68///
69/// Cannot be created directly. Instead, create/deserialize a [spacetimedb_lib::RawModuleDef] and call [ModuleDef::try_from].
70///
71/// ```rust
72/// use spacetimedb_lib::RawModuleDef;
73/// use spacetimedb_sats::raw_identifier::RawIdentifier;
74/// use spacetimedb_schema::def::{ModuleDef, TableDef, IndexDef, TypeDef, ModuleDefLookup, ScopedTypeName};
75/// use spacetimedb_schema::identifier::Identifier;
76///
77/// fn read_raw_module_def_from_file() -> RawModuleDef {
78///     // ...
79/// #   RawModuleDef::V9(Default::default())
80/// }
81///
82/// let raw_module_def = read_raw_module_def_from_file();
83/// let module_def = ModuleDef::try_from(raw_module_def).expect("valid module def");
84///
85/// let table_name = Identifier::new("my_table".into()).expect("valid table name");
86/// let index_name: RawIdentifier = "my_table_my_column_idx_btree".into();
87/// let scoped_type_name = ScopedTypeName::try_new([], "MyType").expect("valid scoped type name");
88///
89/// let table: Option<&TableDef> = module_def.lookup(&table_name);
90/// let index: Option<&IndexDef> = module_def.lookup(&index_name);
91/// let type_def: Option<&TypeDef> = module_def.lookup(&scoped_type_name);
92/// // etc.
93/// ```
94///
95/// Author's apology:
96/// If you find yourself asking:
97/// "Why are we using strings to look up everything here, rather than integer indexes?"
98/// The answer is "I tried to get rid of the strings, but people thought it would be too confusing to have multiple
99/// kinds of integer index." Because the system tables and stuff would be using a different sort of integer index.
100/// shrug emoji.
101#[derive(Debug, Clone)]
102#[non_exhaustive]
103pub struct ModuleDef {
104    /// The tables of the module definition.
105    tables: IdentifierMap<TableDef>,
106
107    /// The reducers of the module definition.
108    /// Note: this is using IndexMap because reducer order is important
109    /// and must be preserved for future calls to `__call_reducer__`.
110    reducers: IndexMap<Identifier, ReducerDef>,
111
112    /// The procedures of the module definition.
113    ///
114    /// Like `reducers`, this uses [`IndexMap`] to preserve order
115    /// so that `__call_procedure__` receives stable integer IDs.
116    procedures: IndexMap<Identifier, ProcedureDef>,
117
118    /// The views of the module definition.
119    ///
120    /// Like `reducers`, this uses [`IndexMap`] to preserve order
121    /// so that `__call_view__` receives stable integer IDs.
122    views: IndexMap<Identifier, ViewDef>,
123
124    /// A map from lifecycle reducer kind to reducer id.
125    lifecycle_reducers: EnumMap<Lifecycle, Option<ReducerId>>,
126
127    /// The type definitions of the module definition.
128    types: HashMap<ScopedTypeName, TypeDef>,
129
130    /// The typespace of the module definition.
131    typespace: Typespace,
132
133    /// The typespace, restructured to be useful for client codegen.
134    typespace_for_generate: TypespaceForGenerate,
135
136    /// The global namespace of the module:
137    /// tables, indexes, constraints, schedules, and sequences live in the global namespace.
138    /// Concretely, though, they're stored in the `TableDef` data structures.
139    /// This map allows looking up which `TableDef` stores the `Def` you're looking for.
140    stored_in_table_def: StrMap<Identifier>,
141
142    /// A map from type defs to their names.
143    refmap: HashMap<AlgebraicTypeRef, ScopedTypeName>,
144
145    /// The row-level security policies.
146    ///
147    /// **Note**: Are only validated syntax-wise.
148    row_level_security_raw: HashMap<RawSql, RawRowLevelSecurityDefV9>,
149
150    /// Indicates which raw module definition semantics this module
151    /// was authored under.
152    #[allow(unused)]
153    raw_module_def_version: RawModuleDefVersion,
154}
155
156#[derive(Debug, Clone, Copy, Eq, PartialEq)]
157pub enum RawModuleDefVersion {
158    /// Represents [`RawModuleDefV9`] and earlier.
159    V9OrEarlier,
160    /// Represents [`RawModuleDefV10`].
161    V10,
162}
163
164impl ModuleDef {
165    /// The raw module definition version this module was authored under.
166    pub fn raw_module_def_version(&self) -> RawModuleDefVersion {
167        self.raw_module_def_version
168    }
169
170    /// The tables of the module definition.
171    pub fn tables(&self) -> impl Iterator<Item = &TableDef> {
172        self.tables.values()
173    }
174
175    /// The indexes of the module definition.
176    pub fn indexes(&self) -> impl Iterator<Item = &IndexDef> {
177        self.tables().flat_map(|table| table.indexes.values())
178    }
179
180    /// The constraints of the module definition.
181    pub fn constraints(&self) -> impl Iterator<Item = &ConstraintDef> {
182        self.tables().flat_map(|table| table.constraints.values())
183    }
184
185    /// The sequences of the module definition.
186    pub fn sequences(&self) -> impl Iterator<Item = &SequenceDef> {
187        self.tables().flat_map(|table| table.sequences.values())
188    }
189
190    /// The schedules of the module definition.
191    pub fn schedules(&self) -> impl Iterator<Item = &ScheduleDef> {
192        self.tables().filter_map(|table| table.schedule.as_ref())
193    }
194
195    /// The reducers of the module definition.
196    pub fn reducers(&self) -> impl Iterator<Item = &ReducerDef> {
197        self.reducers.values()
198    }
199
200    /// Returns an iterator over all reducer ids and definitions.
201    pub fn reducer_ids_and_defs(&self) -> impl ExactSizeIterator<Item = (ReducerId, &ReducerDef)> {
202        self.reducers.values().enumerate().map(|(idx, def)| (idx.into(), def))
203    }
204
205    /// The procedures of the module definition.
206    pub fn procedures(&self) -> impl Iterator<Item = &ProcedureDef> {
207        self.procedures.values()
208    }
209
210    /// The views of the module definition.
211    pub fn views(&self) -> impl Iterator<Item = &ViewDef> {
212        self.views.values()
213    }
214
215    /// The type definitions of the module definition.
216    pub fn types(&self) -> impl Iterator<Item = &TypeDef> {
217        self.types.values()
218    }
219
220    /// The row-level security policies of the module definition.
221    pub fn row_level_security(&self) -> impl Iterator<Item = &RawRowLevelSecurityDefV9> {
222        self.row_level_security_raw.values()
223    }
224
225    /// The `Typespace` used by the module.
226    ///
227    /// `AlgebraicTypeRef`s in the table, reducer, and type alias declarations refer to this typespace.
228    ///
229    /// The typespace must satisfy `Typespace::is_valid_for_client_code_generation`. That is, all types stored in the typespace must either:
230    /// 1. satisfy `AlgebraicType::is_valid_for_client_type_definition`
231    /// 2. and/or `AlgebraicType::is_valid_for_client_type_use`.
232    ///
233    /// Types satisfying condition 1 correspond to generated classes in client code.
234    /// (Types satisfying condition 2 are an artifact of the module bindings, and do not affect the semantics of the module definition.)
235    ///
236    /// Types satisfying condition 1 are required to have corresponding `RawTypeDefV9` declarations in the module.
237    pub fn typespace(&self) -> &Typespace {
238        &self.typespace
239    }
240
241    /// The typespace of the module from a different perspective, one useful for client code generation.
242    pub fn typespace_for_generate(&self) -> &TypespaceForGenerate {
243        &self.typespace_for_generate
244    }
245
246    /// The `TableDef` an entity in the global namespace is stored in, if any.
247    ///
248    /// Generally, you will want to use the `lookup` method on the entity type instead.
249    pub fn stored_in_table_def(&self, name: &RawIdentifier) -> Option<&TableDef> {
250        self.stored_in_table_def
251            .get(name)
252            .and_then(|table_name| self.tables.get(table_name))
253    }
254
255    /// Lookup a definition by its key in `self`.
256    pub fn lookup<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> Option<&T> {
257        T::lookup(self, key)
258    }
259
260    /// Lookup a definition by its key in `self`, panicking if not found.
261    /// Only use this method if you are sure the key exists in the module definition.
262    pub fn lookup_expect<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> &T {
263        T::lookup(self, key).expect("expected ModuleDef to contain key, but it does not")
264    }
265
266    /// Convenience method to look up a table, possibly by a string.
267    pub fn table<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&TableDef> {
268        // If the string IS a valid identifier, we can just look it up.
269        self.tables.get(name)
270    }
271
272    /// Convenience method to look up a view, possibly by a string.
273    pub fn view<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&ViewDef> {
274        // If the string IS a valid identifier, we can just look it up.
275        self.views.get(name)
276    }
277
278    /// Convenience method to look up a view, possibly by a string, returning its id as well.
279    pub fn view_full<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<(ViewFnPtr, &ViewDef)> {
280        // If the string IS a valid identifier, we can just look it up.
281        self.views.get(name).map(|def| (def.fn_ptr, def))
282    }
283
284    /// Convenience method to look up a reducer, possibly by a string.
285    pub fn reducer<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&ReducerDef> {
286        // If the string IS a valid identifier, we can just look it up.
287        self.reducers.get(name)
288    }
289
290    /// Convenience method to look up a reducer, possibly by a string, returning its id as well.
291    pub fn reducer_full<K: ?Sized + Hash + Equivalent<Identifier>>(
292        &self,
293        name: &K,
294    ) -> Option<(ReducerId, &ReducerDef)> {
295        // If the string IS a valid identifier, we can just look it up.
296        self.reducers.get_full(name).map(|(idx, _, def)| (idx.into(), def))
297    }
298
299    /// Look up a reducer by its id.
300    pub fn reducer_by_id(&self, id: ReducerId) -> &ReducerDef {
301        &self.reducers[id.idx()]
302    }
303
304    /// Look up a reducer by its id.
305    pub fn get_reducer_by_id(&self, id: ReducerId) -> Option<&ReducerDef> {
306        self.reducers.get_index(id.idx()).map(|(_, def)| def)
307    }
308
309    /// Look up a view by its id, and whether it is anonymous.
310    pub fn get_view_by_id(&self, id: ViewFnPtr, is_anonymous: bool) -> Option<&ViewDef> {
311        self.views
312            .iter()
313            .find(|(_, def)| def.fn_ptr == id && def.is_anonymous == is_anonymous)
314            .map(|(_, def)| def)
315    }
316
317    /// Convenience method to look up a procedure, possibly by a string.
318    pub fn procedure<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&ProcedureDef> {
319        // If the string IS a valid identifier, we can just look it up.
320        self.procedures.get(name)
321    }
322
323    /// Convenience method to look up a procedure, possibly by a string, returning its id as well.
324    pub fn procedure_full<K: ?Sized + Hash + Equivalent<Identifier>>(
325        &self,
326        name: &K,
327    ) -> Option<(ProcedureId, &ProcedureDef)> {
328        // If the string IS a valid identifier, we can just look it up.
329        self.procedures.get_full(name).map(|(idx, _, def)| (idx.into(), def))
330    }
331
332    /// Look up a procuedure by its id, panicking if it doesn't exist.
333    pub fn procedure_by_id(&self, id: ProcedureId) -> &ProcedureDef {
334        &self.procedures[id.idx()]
335    }
336
337    /// Look up a procuedure by its id, returning `None` if it doesn't exist.
338    pub fn get_procedure_by_id(&self, id: ProcedureId) -> Option<&ProcedureDef> {
339        self.procedures.get_index(id.idx()).map(|(_, def)| def)
340    }
341
342    /// Looks up a lifecycle reducer defined in the module.
343    pub fn lifecycle_reducer(&self, lifecycle: Lifecycle) -> Option<(ReducerId, &ReducerDef)> {
344        self.lifecycle_reducers[lifecycle].map(|i| (i, &self.reducers[i.idx()]))
345    }
346
347    /// Returns a `DeserializeSeed` that can pull data from a `Deserializer` for `def`.
348    pub fn arg_seed_for<'a, T>(&'a self, def: &'a T) -> ArgsSeed<'a, T> {
349        ArgsSeed(self.typespace.with_type(def))
350    }
351
352    /// Look up the name corresponding to an `AlgebraicTypeRef`.
353    pub fn type_def_from_ref(&self, r: AlgebraicTypeRef) -> Option<(&ScopedTypeName, &TypeDef)> {
354        let name = self.refmap.get(&r)?;
355        let def = self
356            .types
357            .get(name)
358            .expect("if it was in refmap, it should be in types");
359
360        Some((name, def))
361    }
362
363    /// Convenience method to look up a table and convert it to a `TableSchema`.
364    /// All indexes, constraints, etc inside the table will have ID 0!
365    pub fn table_schema<K: ?Sized + Hash + Equivalent<Identifier>>(
366        &self,
367        name: &K,
368        table_id: TableId,
369    ) -> Option<TableSchema> {
370        // If the string IS a valid identifier, we can just look it up.
371        let table_def = self.tables.get(name)?;
372        Some(TableSchema::from_module_def(self, table_def, (), table_id))
373    }
374
375    /// Lookup a definition by its key in `self`, panicking if it is not found.
376    pub fn expect_lookup<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> &T {
377        if let Some(result) = T::lookup(self, key) {
378            result
379        } else {
380            panic!("expected ModuleDef to contain {key:?}, but it does not");
381        }
382    }
383
384    /// Expect that this module definition contains a definition.
385    pub fn expect_contains<Def: ModuleDefLookup>(&self, def: &Def) {
386        if let Some(my_def) = self.lookup(def.key()) {
387            assert_eq!(
388                def as *const Def, my_def as *const Def,
389                "expected ModuleDef to contain {def:?}, but it contained {my_def:?}"
390            );
391        } else {
392            panic!("expected ModuleDef to contain {:?}, but it does not", def.key());
393        }
394    }
395}
396
397impl TryFrom<RawModuleDef> for ModuleDef {
398    type Error = ValidationErrors;
399
400    fn try_from(raw: RawModuleDef) -> Result<Self, Self::Error> {
401        match raw {
402            RawModuleDef::V8BackCompat(v8_mod) => Self::try_from(v8_mod),
403            RawModuleDef::V9(v9_mod) => Self::try_from(v9_mod),
404            RawModuleDef::V10(v10_mod) => Self::try_from(v10_mod),
405            _ => unimplemented!(),
406        }
407    }
408}
409impl TryFrom<raw_def::v8::RawModuleDefV8> for ModuleDef {
410    type Error = ValidationErrors;
411
412    fn try_from(v8_mod: raw_def::v8::RawModuleDefV8) -> Result<Self, Self::Error> {
413        // it is not necessary to generate indexes for a v8 mod, since the validation
414        // handles index generation.
415        validate::v8::validate(v8_mod)
416    }
417}
418impl TryFrom<raw_def::v9::RawModuleDefV9> for ModuleDef {
419    type Error = ValidationErrors;
420
421    fn try_from(v9_mod: raw_def::v9::RawModuleDefV9) -> Result<Self, Self::Error> {
422        validate::v9::validate(v9_mod)
423    }
424}
425impl From<ModuleDef> for RawModuleDefV9 {
426    fn from(val: ModuleDef) -> Self {
427        let ModuleDef {
428            tables,
429            views,
430            reducers,
431            lifecycle_reducers: _,
432            types,
433            typespace,
434            stored_in_table_def: _,
435            typespace_for_generate: _,
436            refmap: _,
437            row_level_security_raw,
438            procedures,
439            raw_module_def_version: _,
440        } = val;
441
442        // Extract column defaults from tables before consuming tables
443        let column_defaults: Vec<_> = tables
444            .iter()
445            .flat_map(|(table_name, table_def)| {
446                table_def.columns.iter().enumerate().filter_map(|(col_id, col)| {
447                    col.default_value.as_ref().map(|default_val| {
448                        RawMiscModuleExportV9::ColumnDefaultValue(RawColumnDefaultValueV9 {
449                            table: table_name.clone().into(),
450                            col_id: ColId(col_id as u16),
451                            value: spacetimedb_sats::bsatn::to_vec(default_val).unwrap().into(),
452                        })
453                    })
454                })
455            })
456            .collect();
457
458        RawModuleDefV9 {
459            tables: to_raw(tables),
460            reducers: reducers.into_iter().map(|(_, def)| def.into()).collect(),
461            types: to_raw(types),
462            misc_exports: column_defaults
463                .into_iter()
464                .chain(procedures.into_iter().map(|(_, def)| def.into()))
465                .chain(views.into_iter().map(|(_, def)| def.into()))
466                .collect(),
467            typespace,
468            row_level_security: row_level_security_raw.into_iter().map(|(_, def)| def).collect(),
469        }
470    }
471}
472
473impl TryFrom<raw_def::v10::RawModuleDefV10> for ModuleDef {
474    type Error = ValidationErrors;
475
476    fn try_from(v10_mod: raw_def::v10::RawModuleDefV10) -> Result<Self, Self::Error> {
477        validate::v10::validate(v10_mod)
478    }
479}
480
481impl From<ModuleDef> for RawModuleDefV10 {
482    fn from(val: ModuleDef) -> Self {
483        let ModuleDef {
484            tables,
485            views,
486            reducers,
487            lifecycle_reducers,
488            types,
489            typespace,
490            stored_in_table_def: _,
491            typespace_for_generate: _,
492            refmap: _,
493            row_level_security_raw,
494            procedures,
495            raw_module_def_version: _,
496        } = val;
497
498        let mut sections = Vec::new();
499        let mut explicit_names = ExplicitNames::default();
500
501        sections.push(RawModuleDefV10Section::Typespace(typespace));
502
503        // Extract lifecycle reducer names before consuming reducers.
504        let raw_lifecycle: Vec<RawLifeCycleReducerDefV10> = lifecycle_reducers
505            .into_iter()
506            .filter_map(|(lifecycle, reducer_id)| {
507                let id = reducer_id?;
508                let (name, _) = reducers.get_index(id.idx())?;
509                Some(RawLifeCycleReducerDefV10 {
510                    lifecycle_spec: lifecycle,
511                    function_name: name.clone().into(),
512                })
513            })
514            .collect();
515
516        let raw_types: Vec<RawTypeDefV10> = types.into_values().map(Into::into).collect();
517        if !raw_types.is_empty() {
518            sections.push(RawModuleDefV10Section::Types(raw_types));
519        }
520
521        // Collect schedules from tables (V10 stores them in a separate section).
522        // Also collect ExplicitNames for tables: accessor_name → source_name, name → canonical_name.
523        let mut schedules = Vec::new();
524        let raw_tables: Vec<RawTableDefV10> = tables
525            .into_values()
526            .map(|td| {
527                // Always emit name as ExplicitNames canonical_name.
528                explicit_names.insert_table(
529                    RawIdentifier::from(td.accessor_name.clone()),
530                    RawIdentifier::from(td.name.clone()),
531                );
532                if let Some(sched) = td.schedule.clone() {
533                    schedules.push(RawScheduleDefV10 {
534                        source_name: Some(sched.name.into()),
535                        table_name: td.name.clone().into(),
536                        schedule_at_col: sched.at_column,
537                        function_name: sched.function_name.into(),
538                    });
539                }
540                td.into()
541            })
542            .collect();
543        if !raw_tables.is_empty() {
544            sections.push(RawModuleDefV10Section::Tables(raw_tables));
545        }
546
547        // Collect ExplicitNames for reducers: accessor_name → source_name, name → canonical_name.
548        let raw_reducers: Vec<RawReducerDefV10> = reducers
549            .into_values()
550            .map(|rd| {
551                explicit_names.insert_function(
552                    RawIdentifier::from(rd.accessor_name.clone()),
553                    RawIdentifier::from(rd.name.clone()),
554                );
555                rd.into()
556            })
557            .collect();
558        if !raw_reducers.is_empty() {
559            sections.push(RawModuleDefV10Section::Reducers(raw_reducers));
560        }
561
562        // Collect ExplicitNames for procedures: accessor_name → source_name, name → canonical_name.
563        let raw_procedures: Vec<RawProcedureDefV10> = procedures
564            .into_values()
565            .map(|pd| {
566                explicit_names.insert_function(
567                    RawIdentifier::from(pd.accessor_name.clone()),
568                    RawIdentifier::from(pd.name.clone()),
569                );
570                pd.into()
571            })
572            .collect();
573        if !raw_procedures.is_empty() {
574            sections.push(RawModuleDefV10Section::Procedures(raw_procedures));
575        }
576
577        // Collect ExplicitNames for views: accessor_name → source_name, name → canonical_name.
578        let raw_views: Vec<RawViewDefV10> = views
579            .into_values()
580            .map(|vd| {
581                explicit_names.insert_function(
582                    RawIdentifier::from(vd.accessor_name.clone()),
583                    RawIdentifier::from(vd.name.clone()),
584                );
585                vd.into()
586            })
587            .collect();
588        if !raw_views.is_empty() {
589            sections.push(RawModuleDefV10Section::Views(raw_views));
590        }
591
592        if !schedules.is_empty() {
593            sections.push(RawModuleDefV10Section::Schedules(schedules));
594        }
595
596        if !raw_lifecycle.is_empty() {
597            sections.push(RawModuleDefV10Section::LifeCycleReducers(raw_lifecycle));
598        }
599
600        let raw_rls: Vec<RawRowLevelSecurityDefV10> = row_level_security_raw.into_values().collect();
601        if !raw_rls.is_empty() {
602            sections.push(RawModuleDefV10Section::RowLevelSecurity(raw_rls));
603        }
604
605        // Always emit ExplicitNames so canonical names survive the round-trip.
606        sections.push(RawModuleDefV10Section::ExplicitNames(explicit_names));
607
608        RawModuleDefV10 { sections }
609    }
610}
611
612/// Implemented by definitions stored in a `ModuleDef`.
613/// Allows looking definitions up in a `ModuleDef`, and across
614/// `ModuleDef`s during migrations.
615pub trait ModuleDefLookup: Sized + Debug + 'static {
616    /// A reference to a definition of this type within a module def. This reference should be portable across migrations.
617    type Key<'a>: Debug + Copy;
618
619    /// Get a reference to this definition.
620    fn key(&self) -> Self::Key<'_>;
621
622    /// Look up this entity in the module def.
623    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self>;
624}
625/// A data structure representing the validated definition of a database table.
626///
627/// Cannot be created directly. Construct a [`ModuleDef`] by validating a [`RawModuleDef`] instead,
628/// and then access the tables from there.
629///
630/// This struct holds information about the table, including its name, columns, indexes,
631/// constraints, sequences, type, and access rights.
632///
633/// Validation rules:
634/// - The table name must be a valid identifier.
635/// - The table's columns must be sorted according to [crate::db::ordering::canonical_ordering].
636/// - The table's indexes, constraints, and sequences must be sorted by their keys.
637/// - The table's column types may refer only to types in the containing DatabaseDef's typespace.
638/// - The table's column names must be unique.
639#[derive(Debug, Clone, Eq, PartialEq)]
640#[non_exhaustive]
641pub struct TableDef {
642    /// The name of the table.
643    /// Unique within a module, acts as the table's identifier.
644    /// Must be a valid [crate::db::identifier::Identifier].
645    pub name: Identifier,
646    /// The table identifier as defined in the module source.
647    ///
648    /// All the codegens should use `accessor_name` to derive the table identifier for clients.
649    /// However, the `name` field should still be used when communicating with the
650    /// database over the network, since the database does not necessarily know
651    /// about accessor names.
652    pub accessor_name: Identifier,
653    /// A reference to a `ProductType` containing the columns of this table.
654    /// This is the single source of truth for the table's columns.
655    /// All elements of the `ProductType` must have names.
656    ///
657    /// Like all types in the module, this must have the [default element ordering](crate::db::default_element_ordering), UNLESS a custom ordering is declared via `ModuleDef.misc_exports` for this type.
658    pub product_type_ref: AlgebraicTypeRef,
659
660    /// The primary key of the table, if present. Must refer to a valid column.
661    ///
662    /// Currently, there must be a unique constraint and an index corresponding to the primary key.
663    /// Eventually, we may remove the requirement for an index.
664    ///
665    /// The database engine does not actually care about this, but client code generation does.
666    pub primary_key: Option<ColId>,
667
668    /// The columns of this table. This stores the information in
669    /// `product_type_ref` in a more convenient-to-access format.
670    pub columns: Vec<ColumnDef>,
671
672    /// The indices on the table, indexed by name.
673    pub indexes: StrMap<IndexDef>,
674
675    /// The unique constraints on the table, indexed by name.
676    pub constraints: StrMap<ConstraintDef>,
677
678    /// The sequences for the table, indexed by name.
679    pub sequences: StrMap<SequenceDef>,
680
681    /// The schedule for the table, if present.
682    pub schedule: Option<ScheduleDef>,
683
684    /// Whether this is a system- or user-created table.
685    pub table_type: TableType,
686
687    /// Whether this table is public or private.
688    pub table_access: TableAccess,
689
690    /// Whether this is an event table.
691    ///
692    /// Event tables persist to the commitlog but are not merged into committed state.
693    /// Their rows are only visible to V2 subscribers in the transaction that inserted them.
694    pub is_event: bool,
695}
696
697impl TableDef {
698    /// Get a column of the `TableDef`.
699    pub fn get_column(&self, id: ColId) -> Option<&ColumnDef> {
700        self.columns.get(id.idx())
701    }
702    /// Get a column by the column's name.
703    pub fn get_column_by_name(&self, name: &Identifier) -> Option<&ColumnDef> {
704        self.columns.iter().find(|c| &c.name == name)
705    }
706}
707
708impl From<TableDef> for RawTableDefV9 {
709    fn from(val: TableDef) -> Self {
710        let TableDef {
711            name,
712            product_type_ref,
713            primary_key,
714            columns: _, // will be reconstructed from the product type.
715            indexes,
716            constraints,
717            sequences,
718            schedule,
719            table_type,
720            table_access,
721            is_event: _, // V9 does not support event tables; ignore when converting back
722            ..
723        } = val;
724
725        RawTableDefV9 {
726            name: name.into(),
727            product_type_ref,
728            primary_key: ColList::from_iter(primary_key),
729            indexes: to_raw(indexes),
730            constraints: to_raw(constraints),
731            sequences: to_raw(sequences),
732            schedule: schedule.map(Into::into),
733            table_type,
734            table_access,
735        }
736    }
737}
738
739impl From<TableDef> for RawTableDefV10 {
740    fn from(val: TableDef) -> Self {
741        let TableDef {
742            name: _,
743            product_type_ref,
744            primary_key,
745            columns: _, // will be reconstructed from the product type.
746            indexes,
747            constraints,
748            sequences,
749            schedule: _, // V10 stores schedules in a separate section; handled in From<ModuleDef>.
750            table_type,
751            table_access,
752            is_event,
753            accessor_name,
754        } = val;
755
756        RawTableDefV10 {
757            source_name: accessor_name.into(),
758            product_type_ref,
759            primary_key: ColList::from_iter(primary_key),
760            indexes: indexes.into_values().map(Into::into).collect(),
761            constraints: constraints.into_values().map(Into::into).collect(),
762            sequences: sequences.into_values().map(Into::into).collect(),
763            table_type,
764            table_access,
765            default_values: Vec::new(),
766            is_event,
767        }
768    }
769}
770
771impl From<ViewDef> for TableDef {
772    fn from(def: ViewDef) -> Self {
773        use TableAccess::*;
774        let ViewDef {
775            name,
776            is_public,
777            product_type_ref,
778            primary_key,
779            return_columns,
780            accessor_name,
781            ..
782        } = def;
783        Self {
784            name,
785            product_type_ref,
786            primary_key,
787            columns: return_columns.into_iter().map(ColumnDef::from).collect(),
788            indexes: <_>::default(),
789            constraints: <_>::default(),
790            sequences: <_>::default(),
791            schedule: None,
792            table_type: TableType::User,
793            table_access: if is_public { Public } else { Private },
794            is_event: false,
795            accessor_name,
796        }
797    }
798}
799
800/// A sequence definition for a database table column.
801#[derive(Debug, Clone, Eq, PartialEq)]
802pub struct SequenceDef {
803    /// The name of the sequence. Must be unique within the containing `ModuleDef`.
804    pub name: RawIdentifier,
805
806    /// The position of the column associated with this sequence.
807    /// This refers to a column in the same `RawTableDef` that contains this `RawSequenceDef`.
808    /// The column must have integral type.
809    /// This must be the unique `RawSequenceDef` for this column.
810    pub column: ColId,
811
812    /// The value to start assigning to this column.
813    /// Will be incremented by 1 for each new row.
814    /// If not present, an arbitrary start point may be selected.
815    pub start: Option<i128>,
816
817    /// The minimum allowed value in this column.
818    /// If not present, no minimum.
819    pub min_value: Option<i128>,
820
821    /// The maximum allowed value in this column.
822    /// If not present, no maximum.
823    pub max_value: Option<i128>,
824
825    /// The increment to use when updating the sequence.
826    pub increment: i128,
827}
828
829impl From<SequenceDef> for RawSequenceDefV9 {
830    fn from(val: SequenceDef) -> Self {
831        RawSequenceDefV9 {
832            name: Some(val.name),
833            column: val.column,
834            start: val.start,
835            min_value: val.min_value,
836            max_value: val.max_value,
837            increment: val.increment,
838        }
839    }
840}
841
842impl From<SequenceDef> for RawSequenceDefV10 {
843    fn from(val: SequenceDef) -> Self {
844        RawSequenceDefV10 {
845            source_name: Some(val.name),
846            column: val.column,
847            start: val.start,
848            min_value: val.min_value,
849            max_value: val.max_value,
850            increment: val.increment,
851        }
852    }
853}
854
855/// A struct representing the validated definition of a database index.
856///
857/// Cannot be created directly. Construct a [`ModuleDef`] by validating a [`RawModuleDef`] instead,
858/// and then access the index from there.
859#[derive(Debug, Clone, Eq, PartialEq)]
860#[non_exhaustive]
861pub struct IndexDef {
862    /// The name of the index. Must be unique within the containing `ModuleDef`.
863    ///
864    /// In V9 and earlier, this was system generated.
865    /// in V10, this can be optionally passed in by the user, but if not passed in, it will be
866    /// generated by the system using the same algorithm as V9 and earlier.
867    pub name: RawIdentifier,
868
869    /// This will be the same as `name` for [`RawModuleDefV9`].
870    /// For [`RawModuleDefV10`], this is an auto-generated globally unique name
871    /// derived from the module source.
872    ///
873    /// This exists as a hacky workaround to make the `index_name_from_id`
874    /// syscall work without requiring the module to know about the canonical
875    /// index name field. It serves as an alias for `name` in Database.
876    pub source_name: RawIdentifier,
877
878    /// The name of the index to use for client code generation.
879    ///
880    /// In V9 and earlier, this could be supplied by the user via the `accessor`
881    /// macro in bindings. In those versions, this may be `None` if the index
882    /// was auto-generated and no accessor name was provided by the user. In
883    /// that case, no client code will be generated for this index.
884    pub accessor_name: Option<Identifier>,
885    /// The algorithm parameters for the index.
886    pub algorithm: IndexAlgorithm,
887}
888
889impl IndexDef {
890    /// Whether this index was generated by the system.
891    pub fn generated(&self) -> bool {
892        self.accessor_name.is_none()
893    }
894}
895
896impl From<IndexDef> for RawIndexDefV9 {
897    fn from(val: IndexDef) -> Self {
898        RawIndexDefV9 {
899            name: Some(val.name),
900            algorithm: match val.algorithm {
901                IndexAlgorithm::BTree(BTreeAlgorithm { columns }) => RawIndexAlgorithm::BTree { columns },
902                IndexAlgorithm::Hash(HashAlgorithm { columns }) => RawIndexAlgorithm::Hash { columns },
903                IndexAlgorithm::Direct(DirectAlgorithm { column }) => RawIndexAlgorithm::Direct { column },
904            },
905            accessor_name: val.accessor_name.map(Into::into),
906        }
907    }
908}
909
910impl From<IndexDef> for RawIndexDefV10 {
911    fn from(val: IndexDef) -> Self {
912        RawIndexDefV10 {
913            source_name: Some(val.source_name),
914            accessor_name: val.accessor_name.map(Into::into),
915            algorithm: val.algorithm.into(),
916        }
917    }
918}
919
920/// Data specifying a supported index algorithm.
921#[non_exhaustive]
922#[derive(Debug, Clone, Eq, PartialEq)]
923pub enum IndexAlgorithm {
924    /// Implemented using a rust `std::collections::BTreeMap`.
925    BTree(BTreeAlgorithm),
926    /// Implemented using a rust `HashMap`.
927    Hash(HashAlgorithm),
928    /// Implemented using `DirectUniqueIndex`.
929    Direct(DirectAlgorithm),
930}
931
932impl spacetimedb_memory_usage::MemoryUsage for IndexAlgorithm {
933    fn heap_usage(&self) -> usize {
934        match self {
935            Self::BTree(a) => a.heap_usage(),
936            Self::Direct(a) => a.heap_usage(),
937            Self::Hash(a) => a.heap_usage(),
938        }
939    }
940}
941
942impl IndexAlgorithm {
943    /// Get the columns of the index.
944    pub fn columns(&self) -> ColOrCols<'_> {
945        match self {
946            Self::BTree(btree) => ColOrCols::ColList(&btree.columns),
947            Self::Hash(hash) => ColOrCols::ColList(&hash.columns),
948            Self::Direct(direct) => ColOrCols::Col(direct.column),
949        }
950    }
951    /// Find the column index for a given field.
952    ///
953    /// *NOTE*: This take in account the possibility of permutations.
954    pub fn find_col_index(&self, pos: usize) -> Option<ColId> {
955        self.columns().iter().find(|col_id| col_id.idx() == pos)
956    }
957}
958
959impl From<IndexAlgorithm> for RawIndexAlgorithm {
960    fn from(val: IndexAlgorithm) -> Self {
961        match val {
962            IndexAlgorithm::BTree(BTreeAlgorithm { columns }) => Self::BTree { columns },
963            IndexAlgorithm::Hash(HashAlgorithm { columns }) => Self::Hash { columns },
964            IndexAlgorithm::Direct(DirectAlgorithm { column }) => Self::Direct { column },
965        }
966    }
967}
968
969/// Data specifying a BTree index.
970#[derive(Debug, Clone, Eq, PartialEq)]
971pub struct BTreeAlgorithm {
972    /// The columns to index.
973    pub columns: ColList,
974}
975
976impl spacetimedb_memory_usage::MemoryUsage for BTreeAlgorithm {
977    fn heap_usage(&self) -> usize {
978        self.columns.heap_usage()
979    }
980}
981
982impl<CL: Into<ColList>> From<CL> for BTreeAlgorithm {
983    fn from(columns: CL) -> Self {
984        let columns = columns.into();
985        Self { columns }
986    }
987}
988
989impl From<BTreeAlgorithm> for IndexAlgorithm {
990    fn from(val: BTreeAlgorithm) -> Self {
991        IndexAlgorithm::BTree(val)
992    }
993}
994
995/// Data specifying a Hash index.
996#[derive(Debug, Clone, Eq, PartialEq)]
997pub struct HashAlgorithm {
998    /// The columns to index.
999    pub columns: ColList,
1000}
1001
1002impl spacetimedb_memory_usage::MemoryUsage for HashAlgorithm {
1003    fn heap_usage(&self) -> usize {
1004        self.columns.heap_usage()
1005    }
1006}
1007
1008impl<CL: Into<ColList>> From<CL> for HashAlgorithm {
1009    fn from(columns: CL) -> Self {
1010        let columns = columns.into();
1011        Self { columns }
1012    }
1013}
1014
1015impl From<HashAlgorithm> for IndexAlgorithm {
1016    fn from(val: HashAlgorithm) -> Self {
1017        IndexAlgorithm::Hash(val)
1018    }
1019}
1020
1021/// Data specifying a Direct index.
1022#[derive(Debug, Clone, Eq, PartialEq)]
1023pub struct DirectAlgorithm {
1024    /// The column to index.
1025    pub column: ColId,
1026}
1027
1028impl spacetimedb_memory_usage::MemoryUsage for DirectAlgorithm {
1029    fn heap_usage(&self) -> usize {
1030        self.column.heap_usage()
1031    }
1032}
1033
1034impl<C: Into<ColId>> From<C> for DirectAlgorithm {
1035    fn from(column: C) -> Self {
1036        let column = column.into();
1037        Self { column }
1038    }
1039}
1040
1041impl From<DirectAlgorithm> for IndexAlgorithm {
1042    fn from(val: DirectAlgorithm) -> Self {
1043        IndexAlgorithm::Direct(val)
1044    }
1045}
1046
1047/// A struct representing the validated definition of a database column.
1048///
1049/// Cannot be created directly. Construct a [`ModuleDef`] by validating a [`RawModuleDef`] instead,
1050/// and then access the column from there.
1051#[derive(Debug, Clone, Eq, PartialEq)]
1052#[non_exhaustive]
1053pub struct ColumnDef {
1054    /// The name of the column.
1055    /// Unique within the containing `TableDef`, but
1056    /// NOT within the containing `ModuleDef`.
1057    pub name: Identifier,
1058
1059    /// The name of the column as define in the module source code.
1060    ///
1061    /// This should be used in client codegens and Not `name` field.
1062    pub accessor_name: Identifier,
1063
1064    /// The ID of this column.
1065    pub col_id: ColId,
1066
1067    /// The type of this column. May refer to the containing `ModuleDef`'s `Typespace`.
1068    /// Must satisfy `AlgebraicType::is_valid_for_client_type_use`.
1069    ///
1070    /// Will always correspond to the corresponding element of this table's
1071    /// `product_type_ref`, that is, the element at index `col_id.idx()`
1072    /// with name `Some(name.as_str())`.
1073    pub ty: AlgebraicType,
1074
1075    /// The type of the column, formatted for client code generation.
1076    pub ty_for_generate: AlgebraicTypeUse,
1077
1078    /// The table this `ColumnDef` is stored in.
1079    pub table_name: Identifier,
1080
1081    /// The default value of this column, if present.
1082    pub default_value: Option<AlgebraicValue>,
1083}
1084
1085impl From<ViewColumnDef> for ColumnDef {
1086    fn from(def: ViewColumnDef) -> Self {
1087        let ViewColumnDef {
1088            name,
1089            col_id,
1090            ty,
1091            ty_for_generate,
1092            view_name: table_name,
1093            accessor_name,
1094        } = def;
1095        Self {
1096            name,
1097            col_id,
1098            ty,
1099            ty_for_generate,
1100            table_name,
1101            default_value: None,
1102            accessor_name,
1103        }
1104    }
1105}
1106
1107/// A struct representing a validated view column
1108#[derive(Debug, Clone, Eq, PartialEq)]
1109#[non_exhaustive]
1110pub struct ViewColumnDef {
1111    /// The name of the column.
1112    pub name: Identifier,
1113
1114    pub accessor_name: Identifier,
1115
1116    /// The position of this column in the view's return type.
1117    pub col_id: ColId,
1118
1119    /// The type of this column.
1120    pub ty: AlgebraicType,
1121
1122    /// The type of the column, formatted for client code generation.
1123    pub ty_for_generate: AlgebraicTypeUse,
1124
1125    /// The view this def is stored in.
1126    pub view_name: Identifier,
1127}
1128
1129impl From<ColumnDef> for ViewColumnDef {
1130    fn from(
1131        ColumnDef {
1132            name,
1133            col_id,
1134            ty,
1135            ty_for_generate,
1136            table_name: view_name,
1137            accessor_name,
1138            ..
1139        }: ColumnDef,
1140    ) -> Self {
1141        Self {
1142            name,
1143            col_id,
1144            ty,
1145            ty_for_generate,
1146            view_name,
1147            accessor_name,
1148        }
1149    }
1150}
1151
1152/// A struct representing a validated view parameter
1153#[derive(Debug, Clone, Eq, PartialEq)]
1154#[non_exhaustive]
1155pub struct ViewParamDef {
1156    /// The name of the parameter.
1157    pub name: Identifier,
1158
1159    /// The position of this parameter in the view's parameter list.
1160    pub col_id: ColId,
1161
1162    /// The type of this parameter.
1163    pub ty: AlgebraicType,
1164
1165    /// The type of the parameter, formatted for client code generation.
1166    pub ty_for_generate: AlgebraicTypeUse,
1167
1168    /// The view this def is stored in.
1169    pub view_name: Identifier,
1170}
1171
1172/// A constraint definition attached to a table.
1173#[derive(Debug, Clone, Eq, PartialEq)]
1174pub struct ConstraintDef {
1175    /// The name of the constraint. Unique within the containing `ModuleDef`.
1176    pub name: RawIdentifier,
1177
1178    /// The data for the constraint.
1179    pub data: ConstraintData,
1180}
1181
1182impl From<ConstraintDef> for RawConstraintDefV9 {
1183    fn from(val: ConstraintDef) -> Self {
1184        RawConstraintDefV9 {
1185            name: Some(val.name),
1186            data: val.data.into(),
1187        }
1188    }
1189}
1190
1191impl From<ConstraintDef> for RawConstraintDefV10 {
1192    fn from(val: ConstraintDef) -> Self {
1193        RawConstraintDefV10 {
1194            source_name: Some(val.name),
1195            data: val.data.into(),
1196        }
1197    }
1198}
1199
1200/// Data for a constraint attached to a table.
1201#[derive(Debug, Clone, Eq, PartialEq)]
1202#[non_exhaustive]
1203pub enum ConstraintData {
1204    Unique(UniqueConstraintData),
1205}
1206
1207impl spacetimedb_memory_usage::MemoryUsage for ConstraintData {
1208    fn heap_usage(&self) -> usize {
1209        match self {
1210            ConstraintData::Unique(d) => d.heap_usage(),
1211        }
1212    }
1213}
1214
1215impl ConstraintData {
1216    /// If this is a unique constraint, returns the columns that must be unique.
1217    /// Otherwise, returns `None`.
1218    pub fn unique_columns(&self) -> Option<&ColSet> {
1219        match &self {
1220            ConstraintData::Unique(UniqueConstraintData { columns }) => Some(columns),
1221        }
1222    }
1223}
1224
1225impl From<ConstraintData> for RawConstraintDataV9 {
1226    fn from(val: ConstraintData) -> Self {
1227        match val {
1228            ConstraintData::Unique(unique) => RawConstraintDataV9::Unique(unique.into()),
1229        }
1230    }
1231}
1232
1233/// Requires that the projection of the table onto these columns is an bijection.
1234///
1235/// That is, there must be a one-to-one relationship between a row and the `columns` of that row.
1236#[derive(Debug, Clone, Eq, PartialEq)]
1237pub struct UniqueConstraintData {
1238    /// The columns on the containing `TableDef`
1239    pub columns: ColSet,
1240}
1241
1242impl spacetimedb_memory_usage::MemoryUsage for UniqueConstraintData {
1243    fn heap_usage(&self) -> usize {
1244        self.columns.heap_usage()
1245    }
1246}
1247
1248impl From<UniqueConstraintData> for RawUniqueConstraintDataV9 {
1249    fn from(val: UniqueConstraintData) -> Self {
1250        RawUniqueConstraintDataV9 {
1251            columns: val.columns.into(),
1252        }
1253    }
1254}
1255
1256impl From<UniqueConstraintData> for ConstraintData {
1257    fn from(val: UniqueConstraintData) -> Self {
1258        ConstraintData::Unique(val)
1259    }
1260}
1261
1262/// Data for the `RLS` policy on a table.
1263#[derive(Debug, Clone, Eq, PartialEq)]
1264pub struct RowLevelSecurityDef {
1265    /// The `sql` expression to use for row-level security.
1266    pub sql: RawSql,
1267}
1268
1269impl From<RowLevelSecurityDef> for RawRowLevelSecurityDefV9 {
1270    fn from(val: RowLevelSecurityDef) -> Self {
1271        RawRowLevelSecurityDefV9 { sql: val.sql }
1272    }
1273}
1274
1275#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
1276pub enum FunctionKind {
1277    /// Functions which have not yet been determined to be reducers or procedures.
1278    ///
1279    /// Used as a placeholder during module validation,
1280    /// when pre-processing [`ScheduleDef`]s prior to validating their scheduled functions.
1281    /// Will never appear in a fully-validated [`ModuleDef`],
1282    /// and should not be placed in errors either.
1283    Unknown,
1284    Reducer,
1285    Procedure,
1286}
1287
1288impl fmt::Display for FunctionKind {
1289    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1290        f.write_str(match self {
1291            FunctionKind::Unknown => "exported function",
1292            FunctionKind::Reducer => "reducer",
1293            FunctionKind::Procedure => "procedure",
1294        })
1295    }
1296}
1297
1298/// Marks a table as a timer table for a scheduled reducer or procedure.
1299#[derive(Debug, Clone, Eq, PartialEq)]
1300#[non_exhaustive]
1301pub struct ScheduleDef {
1302    /// The name of the schedule. Must be unique within the containing `ModuleDef`.
1303    pub name: Identifier,
1304
1305    /// The name of the column that stores the desired invocation time.
1306    ///
1307    /// Must be named `scheduled_at` and be of type `ScheduleAt`.
1308    pub at_column: ColId,
1309
1310    /// The name of the column that stores the invocation ID.
1311    ///
1312    /// Must be named `scheduled_id` and be of type `u64`.
1313    pub id_column: ColId,
1314
1315    /// The name of the reducer or procedure to call.
1316    pub function_name: Identifier,
1317
1318    /// Whether the `function_name` refers to a reducer or a procedure.
1319    pub function_kind: FunctionKind,
1320}
1321
1322impl From<ScheduleDef> for RawScheduleDefV9 {
1323    fn from(val: ScheduleDef) -> Self {
1324        RawScheduleDefV9 {
1325            name: Some(val.name.into()),
1326            reducer_name: val.function_name.into(),
1327            scheduled_at_column: val.at_column,
1328        }
1329    }
1330}
1331
1332/// A type exported by the module.
1333#[derive(Debug, Clone, Eq, PartialEq)]
1334#[non_exhaustive]
1335pub struct TypeDef {
1336    /// The (scoped) name of the type.
1337    pub accessor_name: ScopedTypeName,
1338
1339    /// The type to which the alias refers.
1340    /// Look in `ModuleDef.typespace` for the actual type,
1341    /// or in `ModuleDef.typespace_for_generate` for the client codegen version.
1342    pub ty: AlgebraicTypeRef,
1343
1344    /// Whether this type has a custom ordering.
1345    pub custom_ordering: bool,
1346}
1347impl From<TypeDef> for RawTypeDefV9 {
1348    fn from(val: TypeDef) -> Self {
1349        RawTypeDefV9 {
1350            name: val.accessor_name.into(),
1351            ty: val.ty,
1352            custom_ordering: val.custom_ordering,
1353        }
1354    }
1355}
1356
1357impl From<TypeDef> for RawTypeDefV10 {
1358    fn from(val: TypeDef) -> Self {
1359        RawTypeDefV10 {
1360            source_name: val.accessor_name.into(),
1361            ty: val.ty,
1362            custom_ordering: val.custom_ordering,
1363        }
1364    }
1365}
1366
1367/// A scoped type name, in the form `scope0::scope1::...::scopeN::name`.
1368///
1369/// These are the names that will be used *in client code generation*, NOT the names used for types
1370/// in the module source code.
1371#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1372pub struct ScopedTypeName {
1373    /// The scope for this type.
1374    ///
1375    /// Empty unless a sats `name` attribute is used, e.g.
1376    /// `#[sats(name = "namespace.name")]` in Rust.
1377    scope: Box<[Identifier]>,
1378
1379    /// The name of the type.
1380    ///
1381    /// Eventually, we may add more information to this, such as generic arguments.
1382    name: Identifier,
1383}
1384impl ScopedTypeName {
1385    /// Create a new `ScopedTypeName` from a scope and a name.
1386    pub fn new(scope: Box<[Identifier]>, name: Identifier) -> Self {
1387        ScopedTypeName { scope, name }
1388    }
1389
1390    /// Try to create a new `ScopedTypeName` from a scope and a name.
1391    /// Errors if the scope or name are invalid.
1392    pub fn try_new(
1393        scope: impl IntoIterator<Item = RawIdentifier>,
1394        name: impl Into<RawIdentifier>,
1395    ) -> Result<Self, ErrorStream<IdentifierError>> {
1396        let scope = scope
1397            .into_iter()
1398            .map(|chunk| Identifier::new(chunk).map_err(ErrorStream::from))
1399            .collect_all_errors();
1400        let name = Identifier::new(name.into()).map_err(ErrorStream::from);
1401        let (scope, name) = (scope, name).combine_errors()?;
1402        Ok(ScopedTypeName { scope, name })
1403    }
1404
1405    /// Create a new `ScopedTypeName` with an empty scope.
1406    pub fn from_name(name: Identifier) -> Self {
1407        ScopedTypeName {
1408            scope: Box::new([]),
1409            name,
1410        }
1411    }
1412
1413    /// Retrieve the name of this type.
1414    pub fn name(&self) -> &Identifier {
1415        &self.name
1416    }
1417
1418    /// Retrieve the name of this type, if the scope is empty.
1419    pub fn as_identifier(&self) -> Option<&Identifier> {
1420        self.scope.is_empty().then_some(&self.name)
1421    }
1422
1423    /// Iterate over the segments of this name.
1424    pub fn name_segments(&self) -> impl Iterator<Item = &Identifier> {
1425        self.scope.iter().chain(std::iter::once(&self.name))
1426    }
1427}
1428impl fmt::Debug for ScopedTypeName {
1429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1430        // we can wrap this in a pair of double quotes, since we know
1431        // none of its elements contain quotes.
1432        f.write_char('"')?;
1433        for scope in &*self.scope {
1434            write!(f, "{scope}::")?;
1435        }
1436        write!(f, "{}\"", self.name)
1437    }
1438}
1439impl fmt::Display for ScopedTypeName {
1440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1441        for scope in &*self.scope {
1442            write!(f, "{scope}::")?;
1443        }
1444        fmt::Display::fmt(&self.name, f)
1445    }
1446}
1447impl TryFrom<RawScopedTypeNameV9> for ScopedTypeName {
1448    type Error = ErrorStream<IdentifierError>;
1449
1450    fn try_from(value: RawScopedTypeNameV9) -> Result<Self, Self::Error> {
1451        Self::try_new(value.scope.into_vec(), value.name)
1452    }
1453}
1454impl From<ScopedTypeName> for RawScopedTypeNameV9 {
1455    fn from(val: ScopedTypeName) -> Self {
1456        RawScopedTypeNameV9 {
1457            scope: val.scope.into_vec().into_iter().map(|id| id.into()).collect(),
1458            name: val.name.into(),
1459        }
1460    }
1461}
1462
1463impl From<ScopedTypeName> for RawScopedTypeNameV10 {
1464    fn from(val: ScopedTypeName) -> Self {
1465        RawScopedTypeNameV10 {
1466            scope: val.scope.into_vec().into_iter().map(|id| id.into()).collect(),
1467            source_name: val.name.into(),
1468        }
1469    }
1470}
1471
1472/// A view exported by the module.
1473#[derive(Debug, Clone, Eq, PartialEq)]
1474#[non_exhaustive]
1475pub struct ViewDef {
1476    /// The name of the view. This must be unique within the module.
1477    pub name: Identifier,
1478
1479    /// The view identifier as defined in the module source.
1480    ///
1481    /// Similar to `[TableDef.accessor_name]`.
1482    pub accessor_name: Identifier,
1483
1484    /// Is this a public or a private view?
1485    /// Currently only public views are supported.
1486    /// Private views may be supported in the future.
1487    pub is_public: bool,
1488
1489    /// Is this view anonymous?
1490    /// An anonymous view does not know who called it.
1491    /// Specifically, it is a view that has an `AnonymousViewContext` as its first argument.
1492    /// This type does not have access to the `Identity` of the caller.
1493    pub is_anonymous: bool,
1494
1495    /// It represents the unique index of this view within the module.
1496    /// Module contains separate list for anonymous and non-anonymous views,
1497    /// so `is_anonymous` is needed to fully identify the view along with this index.
1498    pub fn_ptr: ViewFnPtr,
1499
1500    /// The parameters of the view.
1501    ///
1502    /// This `ProductType` need not be registered in the module's `Typespace`.
1503    pub params: ProductType,
1504
1505    /// The parameters of the view, formatted for client codegen.
1506    ///
1507    /// This `ProductType` need not be registered in the module's `TypespaceForGenerate`.
1508    pub params_for_generate: ProductTypeDef,
1509
1510    /// The return type of the view.
1511    /// Either `Option<T>`, `Vec<T>`, or `Query<T>` where:
1512    ///
1513    /// 1. `T` is a [`ProductType`] containing the columns of the view
1514    /// 2. `T` is registered in the module's typespace
1515    /// 3. `Option<T>` refers to [`AlgebraicType::option()`]
1516    /// 4. `Vec<T>` refers to [`AlgebraicType::array()`]
1517    /// 5. `Query<T>` is a special [`ProductType`] `{ __query__: T }`
1518    pub return_type: AlgebraicType,
1519
1520    /// The return type of the view, formatted for client codegen.
1521    pub return_type_for_generate: AlgebraicTypeUse,
1522
1523    /// The single source of truth for the view's columns.
1524    ///
1525    /// If a view can return only `Option<T>`, `Vec<T>`, or `Query<T>`,
1526    /// this is a reference to the inner product type `T`.
1527    /// All elements of `T` must have names.
1528    pub product_type_ref: AlgebraicTypeRef,
1529
1530    /// The primary key of the view.
1531    ///
1532    /// This is set for query-builder views when the underlying table has a primary key.
1533    /// The database engine does not actually care about this, but client code generation does.
1534    pub primary_key: Option<ColId>,
1535
1536    /// The return columns of this view.
1537    /// The same information is stored in `product_type_ref`.
1538    /// This is just a more convenient-to-access format.
1539    pub return_columns: Vec<ViewColumnDef>,
1540
1541    /// The columns that track the arguments of this view.
1542    /// The same information is stored in `params`.
1543    /// This is just a more convenient-to-access format.
1544    pub param_columns: Vec<ViewParamDef>,
1545}
1546
1547impl ViewDef {
1548    /// Get a column by the column's name.
1549    pub fn get_column_by_name(&self, name: &Identifier) -> Option<&ViewColumnDef> {
1550        self.return_columns.iter().find(|c| &c.name == name)
1551    }
1552
1553    /// Get a parameter by the parameter's name.
1554    pub fn get_param_by_name(&self, name: &Identifier) -> Option<&ViewParamDef> {
1555        self.param_columns.iter().find(|c| &c.name == name)
1556    }
1557}
1558
1559impl From<ViewDef> for RawViewDefV9 {
1560    fn from(val: ViewDef) -> Self {
1561        let ViewDef {
1562            name,
1563            is_anonymous,
1564            is_public,
1565            params,
1566            return_type,
1567            fn_ptr: index,
1568            ..
1569        } = val;
1570        RawViewDefV9 {
1571            name: name.into(),
1572            index: index.into(),
1573            is_anonymous,
1574            is_public,
1575            params,
1576            return_type,
1577        }
1578    }
1579}
1580
1581impl From<ViewDef> for RawViewDefV10 {
1582    fn from(val: ViewDef) -> Self {
1583        let ViewDef {
1584            accessor_name,
1585            is_anonymous,
1586            is_public,
1587            params,
1588            return_type,
1589            fn_ptr,
1590            ..
1591        } = val;
1592        RawViewDefV10 {
1593            source_name: accessor_name.into(),
1594            index: fn_ptr.into(),
1595            is_public,
1596            is_anonymous,
1597            params,
1598            return_type,
1599        }
1600    }
1601}
1602
1603impl From<ViewDef> for RawMiscModuleExportV9 {
1604    fn from(def: ViewDef) -> Self {
1605        Self::View(def.into())
1606    }
1607}
1608
1609/// The visibility of a function (reducer or procedure).
1610#[derive(Debug, Clone, Eq, PartialEq)]
1611pub enum FunctionVisibility {
1612    /// Not callable by arbitrary clients.
1613    ///
1614    /// Still callable by the module owner, collaborators,
1615    /// and internal module code.
1616    Private,
1617
1618    /// Callable from client code.
1619    ClientCallable,
1620}
1621
1622impl FunctionVisibility {
1623    pub fn is_private(&self) -> bool {
1624        matches!(self, FunctionVisibility::Private)
1625    }
1626}
1627
1628use spacetimedb_lib::db::raw_def::v10::FunctionVisibility as RawFunctionVisibility;
1629impl From<RawFunctionVisibility> for FunctionVisibility {
1630    fn from(val: RawFunctionVisibility) -> Self {
1631        match val {
1632            RawFunctionVisibility::Private => FunctionVisibility::Private,
1633            RawFunctionVisibility::ClientCallable => FunctionVisibility::ClientCallable,
1634        }
1635    }
1636}
1637
1638impl From<FunctionVisibility> for RawFunctionVisibility {
1639    fn from(val: FunctionVisibility) -> Self {
1640        match val {
1641            FunctionVisibility::Private => RawFunctionVisibility::Private,
1642            FunctionVisibility::ClientCallable => RawFunctionVisibility::ClientCallable,
1643        }
1644    }
1645}
1646
1647/// A reducer exported by the module.
1648#[derive(Debug, Clone, Eq, PartialEq)]
1649#[non_exhaustive]
1650pub struct ReducerDef {
1651    /// The name of the reducer. This must be unique within the module's set of reducers and procedures.
1652    pub name: ReducerName,
1653
1654    /// The reducer name as defined in the module source.
1655    ///
1656    /// All the codegens should use `accessor_name` to generate reducers.
1657    /// However, the `name` field should still be used when communicating with the
1658    /// database over the network, since the database does not necessarily know
1659    /// about accessor names.
1660    pub accessor_name: ReducerName,
1661
1662    /// The parameters of the reducer.
1663    ///
1664    /// This `ProductType` need not be registered in the module's `Typespace`.
1665    pub params: ProductType,
1666
1667    /// The parameters of the reducer, formatted for client codegen.
1668    ///
1669    /// This `ProductType` need not be registered in the module's `TypespaceForGenerate`.
1670    pub params_for_generate: ProductTypeDef,
1671
1672    /// The special role of this reducer in the module lifecycle, if any.
1673    pub lifecycle: Option<Lifecycle>,
1674
1675    /// The visibility of this reducer.
1676    pub visibility: FunctionVisibility,
1677
1678    /// The return type of the reducer on success.
1679    pub ok_return_type: AlgebraicType,
1680
1681    /// The return type of the reducer on error.
1682    pub err_return_type: AlgebraicType,
1683}
1684
1685impl From<ReducerDef> for RawReducerDefV9 {
1686    fn from(val: ReducerDef) -> Self {
1687        RawReducerDefV9 {
1688            name: val.name.into(),
1689            params: val.params,
1690            lifecycle: val.lifecycle,
1691        }
1692    }
1693}
1694
1695impl From<ReducerDef> for RawReducerDefV10 {
1696    fn from(val: ReducerDef) -> Self {
1697        RawReducerDefV10 {
1698            source_name: val.accessor_name.into(),
1699            params: val.params,
1700            visibility: val.visibility.into(),
1701            ok_return_type: val.ok_return_type,
1702            err_return_type: val.err_return_type,
1703        }
1704    }
1705}
1706
1707#[derive(Debug, Clone, Eq, PartialEq)]
1708#[non_exhaustive]
1709pub struct ProcedureDef {
1710    /// The name of the procedure.
1711    ///
1712    /// This must be unique within the module's set of reducers and procedures.
1713    pub name: Identifier,
1714
1715    /// The procedure name as defined in the module source.
1716    ///
1717    /// Similar to `[ReducerDef.accessor_name]`.
1718    pub accessor_name: Identifier,
1719    /// The parameters of the procedure.
1720    ///
1721    /// This `ProductType` need not be registered in the module's `Typespace`.
1722    pub params: ProductType,
1723
1724    /// The parameters of the procedure, formatted for client codegen.
1725    ///
1726    /// This `ProductType` need not be registered in the module's `TypespaceForGenerate`.
1727    pub params_for_generate: ProductTypeDef,
1728
1729    /// The return type of the procedure.
1730    ///
1731    /// If this is a non-special compound type, it should be registered in the module's `Typespace`
1732    /// and indirected through an [`AlgebraicType::Ref`].
1733    pub return_type: AlgebraicType,
1734
1735    /// The return type of the procedure.
1736    ///
1737    /// If this is a non-special compound type, it should be registered in the module's `TypespaceForGenerate`
1738    /// and indirected through an [`AlgebraicTypeUse::Ref`].
1739    pub return_type_for_generate: AlgebraicTypeUse,
1740
1741    /// The visibility of this procedure.
1742    pub visibility: FunctionVisibility,
1743}
1744
1745impl From<ProcedureDef> for RawProcedureDefV9 {
1746    fn from(val: ProcedureDef) -> Self {
1747        RawProcedureDefV9 {
1748            name: val.name.into(),
1749            params: val.params,
1750            return_type: val.return_type,
1751        }
1752    }
1753}
1754
1755impl From<ProcedureDef> for RawProcedureDefV10 {
1756    fn from(val: ProcedureDef) -> Self {
1757        RawProcedureDefV10 {
1758            source_name: val.accessor_name.into(),
1759            params: val.params,
1760            return_type: val.return_type,
1761            visibility: val.visibility.into(),
1762        }
1763    }
1764}
1765
1766impl From<ProcedureDef> for RawMiscModuleExportV9 {
1767    fn from(def: ProcedureDef) -> Self {
1768        Self::Procedure(def.into())
1769    }
1770}
1771
1772impl ModuleDefLookup for TableDef {
1773    type Key<'a> = &'a Identifier;
1774
1775    fn key(&self) -> Self::Key<'_> {
1776        &self.name
1777    }
1778
1779    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1780        module_def.tables.get(key)
1781    }
1782}
1783
1784impl ModuleDefLookup for SequenceDef {
1785    type Key<'a> = &'a RawIdentifier;
1786
1787    fn key(&self) -> Self::Key<'_> {
1788        &self.name
1789    }
1790
1791    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1792        module_def.stored_in_table_def(key)?.sequences.get(key)
1793    }
1794}
1795
1796impl ModuleDefLookup for IndexDef {
1797    type Key<'a> = &'a RawIdentifier;
1798
1799    fn key(&self) -> Self::Key<'_> {
1800        &self.name
1801    }
1802
1803    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1804        module_def.stored_in_table_def(key)?.indexes.get(key)
1805    }
1806}
1807
1808impl ModuleDefLookup for ColumnDef {
1809    // (table_name, column_name).
1810    // We don't use `ColId` here because we want this to be portable
1811    // across migrations.
1812    type Key<'a> = (&'a Identifier, &'a Identifier);
1813
1814    fn key(&self) -> Self::Key<'_> {
1815        (&self.table_name, &self.name)
1816    }
1817
1818    fn lookup<'a>(module_def: &'a ModuleDef, (table_name, name): Self::Key<'_>) -> Option<&'a Self> {
1819        module_def
1820            .tables
1821            .get(table_name)
1822            .and_then(|table| table.get_column_by_name(name))
1823    }
1824}
1825
1826impl ModuleDefLookup for ViewColumnDef {
1827    // We don't use `ColId` here because we want this to be portable
1828    // across migrations.
1829    type Key<'a> = (&'a Identifier, &'a Identifier);
1830
1831    fn key(&self) -> Self::Key<'_> {
1832        (&self.view_name, &self.name)
1833    }
1834
1835    fn lookup<'a>(module_def: &'a ModuleDef, (view_name, name): Self::Key<'_>) -> Option<&'a Self> {
1836        module_def
1837            .views
1838            .get(view_name)
1839            .and_then(|view| view.get_column_by_name(name))
1840    }
1841}
1842
1843impl ModuleDefLookup for ViewParamDef {
1844    // We don't use `ColId` here because we want this to be portable
1845    // across migrations.
1846    type Key<'a> = (&'a Identifier, &'a Identifier);
1847
1848    fn key(&self) -> Self::Key<'_> {
1849        (&self.view_name, &self.name)
1850    }
1851
1852    fn lookup<'a>(module_def: &'a ModuleDef, (view_name, name): Self::Key<'_>) -> Option<&'a Self> {
1853        module_def
1854            .views
1855            .get(view_name)
1856            .and_then(|view| view.get_param_by_name(name))
1857    }
1858}
1859
1860impl ModuleDefLookup for ConstraintDef {
1861    type Key<'a> = &'a RawIdentifier;
1862
1863    fn key(&self) -> Self::Key<'_> {
1864        &self.name
1865    }
1866
1867    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1868        module_def.stored_in_table_def(key)?.constraints.get(key)
1869    }
1870}
1871
1872impl ModuleDefLookup for RawRowLevelSecurityDefV9 {
1873    type Key<'a> = &'a RawSql;
1874
1875    fn key(&self) -> Self::Key<'_> {
1876        &self.sql
1877    }
1878
1879    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1880        module_def.row_level_security_raw.get(key)
1881    }
1882}
1883
1884impl ModuleDefLookup for ScheduleDef {
1885    type Key<'a> = &'a Identifier;
1886
1887    fn key(&self) -> Self::Key<'_> {
1888        &self.name
1889    }
1890
1891    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1892        let schedule = module_def.stored_in_table_def(key.as_raw())?.schedule.as_ref()?;
1893        if &schedule.name == key {
1894            Some(schedule)
1895        } else {
1896            None
1897        }
1898    }
1899}
1900
1901impl ModuleDefLookup for TypeDef {
1902    type Key<'a> = &'a ScopedTypeName;
1903
1904    fn key(&self) -> Self::Key<'_> {
1905        &self.accessor_name
1906    }
1907
1908    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1909        module_def.types.get(key)
1910    }
1911}
1912
1913impl ModuleDefLookup for ReducerDef {
1914    type Key<'a> = &'a ReducerName;
1915
1916    fn key(&self) -> Self::Key<'_> {
1917        &self.name
1918    }
1919
1920    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1921        let key = &**key;
1922        module_def.reducers.get(key)
1923    }
1924}
1925
1926impl ModuleDefLookup for ProcedureDef {
1927    type Key<'a> = &'a Identifier;
1928
1929    fn key(&self) -> Self::Key<'_> {
1930        &self.name
1931    }
1932
1933    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1934        let key = &**key;
1935        module_def.procedures.get(key)
1936    }
1937}
1938
1939impl ModuleDefLookup for ViewDef {
1940    type Key<'a> = &'a Identifier;
1941
1942    fn key(&self) -> Self::Key<'_> {
1943        &self.name
1944    }
1945
1946    fn lookup<'a>(view_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1947        view_def.views.get(key)
1948    }
1949}
1950
1951fn to_raw<Def, RawDef, Name>(data: HashMap<Name, Def>) -> Vec<RawDef>
1952where
1953    Def: ModuleDefLookup + Into<RawDef>,
1954    Name: Eq + Ord + 'static,
1955{
1956    let sorted: BTreeMap<Name, Def> = data.into_iter().collect();
1957    sorted.into_values().map_into().collect()
1958}
1959
1960#[cfg(test)]
1961mod tests {
1962    use crate::{def::validate::tests::expect_identifier, error::ValidationError};
1963
1964    use super::*;
1965    use proptest::prelude::*;
1966    use spacetimedb_data_structures::{expect_error_matching, map::HashCollectionExt as _};
1967    use spacetimedb_lib::db::raw_def::v9::RawModuleDefV9Builder;
1968
1969    proptest! {
1970        #[test]
1971        fn to_raw_deterministic(vec in prop::collection::vec(any::<u32>(), 0..5)) {
1972            let mut map = HashMap::new();
1973            let name = ScopedTypeName::try_new([], "fake_name").unwrap();
1974            for k in vec {
1975                let def = TypeDef { accessor_name: name.clone(), ty: AlgebraicTypeRef(k), custom_ordering: false };
1976                map.insert(k, def);
1977            }
1978            let raw: Vec<RawTypeDefV9> = to_raw(map.clone());
1979            let raw2: Vec<RawTypeDefV9> = to_raw(map);
1980            prop_assert_eq!(raw, raw2);
1981        }
1982    }
1983
1984    #[test]
1985    fn validate_new_column_with_multiple_values() {
1986        let mut old_builder = RawModuleDefV9Builder::new();
1987        old_builder
1988            .build_table_with_new_type(
1989                "Apples",
1990                ProductType::from([("id", AlgebraicType::U64), ("count", AlgebraicType::U16)]),
1991                true,
1992            )
1993            .with_default_column_value(1, AlgebraicValue::U16(12))
1994            .with_default_column_value(1, AlgebraicValue::U16(10))
1995            .finish();
1996
1997        let result: Result<ModuleDef, ValidationErrors> = old_builder.finish().try_into();
1998        let apples = expect_identifier("Apples");
1999
2000        expect_error_matching!(
2001            result,
2002            ValidationError::MultipleColumnDefaultValues {
2003                table,
2004                ..
2005            } => *table == apples.clone().into()
2006        );
2007    }
2008
2009    #[test]
2010    fn validate_new_column_with_malformed_value() {
2011        let mut old_builder = RawModuleDefV9Builder::new();
2012        old_builder
2013            .build_table_with_new_type(
2014                "Apples",
2015                ProductType::from([("id", AlgebraicType::U64), ("count", AlgebraicType::U16)]),
2016                true,
2017            )
2018            .with_default_column_value(1, AlgebraicValue::Bool(false))
2019            .with_default_column_value(1, AlgebraicValue::unit())
2020            .finish();
2021
2022        let result: Result<ModuleDef, ValidationErrors> = old_builder.finish().try_into();
2023        let apples = expect_identifier("Apples");
2024
2025        expect_error_matching!(
2026            result,
2027            ValidationError::ColumnDefaultValueMalformed { table, col_id, .. } => *table == apples.clone().into() && *col_id == ColId(1)
2028        );
2029        assert!(result.is_err_and(|e| e
2030            .into_iter()
2031            .filter(|e| matches!(e, ValidationError::ColumnDefaultValueMalformed { .. }))
2032            .count()
2033            == 2))
2034    }
2035}