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::schema::{Schema, TableSchema};
25use crate::type_for_generate::{AlgebraicTypeUse, ProductTypeDef, TypespaceForGenerate};
26use deserialize::ReducerArgsDeserializeSeed;
27use enum_map::EnumMap;
28use hashbrown::Equivalent;
29use indexmap::IndexMap;
30use itertools::Itertools;
31use spacetimedb_data_structures::error_stream::{CollectAllErrors, CombineErrors, ErrorStream};
32use spacetimedb_data_structures::map::HashMap;
33use spacetimedb_lib::db::raw_def;
34use spacetimedb_lib::db::raw_def::v9::{
35    Lifecycle, RawConstraintDataV9, RawConstraintDefV9, RawIdentifier, RawIndexAlgorithm, RawIndexDefV9,
36    RawModuleDefV9, RawReducerDefV9, RawRowLevelSecurityDefV9, RawScheduleDefV9, RawScopedTypeNameV9, RawSequenceDefV9,
37    RawSql, RawTableDefV9, RawTypeDefV9, RawUniqueConstraintDataV9, TableAccess, TableType,
38};
39use spacetimedb_lib::{ProductType, RawModuleDef};
40use spacetimedb_primitives::{ColId, ColList, ColOrCols, ColSet, ReducerId, TableId};
41use spacetimedb_sats::AlgebraicType;
42use spacetimedb_sats::{AlgebraicTypeRef, Typespace};
43
44pub mod deserialize;
45pub mod error;
46pub mod validate;
47
48/// A map from `Identifier`s to values of type `T`.
49pub type IdentifierMap<T> = HashMap<Identifier, T>;
50
51/// A map from `Box<str>`s to values of type `T`.
52pub type StrMap<T> = HashMap<Box<str>, T>;
53
54// We may eventually want to reorganize this module to look more
55// like the system tables, with numeric IDs used for lookups
56// in addition to `Identifier`s.
57//
58// If that path is taken, it might be possible to have this type
59// entirely subsume the various `Schema` types, which would be cool.
60
61/// A validated, canonicalized, immutable module definition.
62///
63/// Cannot be created directly. Instead, create/deserialize a [spacetimedb_lib::RawModuleDef] and call [ModuleDef::try_from].
64///
65/// ```rust
66/// use spacetimedb_lib::RawModuleDef;
67/// use spacetimedb_schema::def::{ModuleDef, TableDef, IndexDef, TypeDef, ModuleDefLookup, ScopedTypeName};
68/// use spacetimedb_schema::identifier::Identifier;
69///
70/// fn read_raw_module_def_from_file() -> RawModuleDef {
71///     // ...
72/// #   RawModuleDef::V9(Default::default())
73/// }
74///
75/// let raw_module_def = read_raw_module_def_from_file();
76/// let module_def = ModuleDef::try_from(raw_module_def).expect("valid module def");
77///
78/// let table_name = Identifier::new("my_table".into()).expect("valid table name");
79/// let index_name = "my_table_my_column_idx_btree";
80/// let scoped_type_name = ScopedTypeName::try_new([], "MyType").expect("valid scoped type name");
81///
82/// let table: Option<&TableDef> = module_def.lookup(&table_name);
83/// let index: Option<&IndexDef> = module_def.lookup(index_name);
84/// let type_def: Option<&TypeDef> = module_def.lookup(&scoped_type_name);
85/// // etc.
86/// ```
87///
88/// Author's apology:
89/// If you find yourself asking:
90/// "Why are we using strings to look up everything here, rather than integer indexes?"
91/// The answer is "I tried to get rid of the strings, but people thought it would be too confusing to have multiple
92/// kinds of integer index." Because the system tables and stuff would be using a different sort of integer index.
93/// shrug emoji.
94#[derive(Debug, Clone)]
95#[non_exhaustive]
96pub struct ModuleDef {
97    /// The tables of the module definition.
98    tables: IdentifierMap<TableDef>,
99
100    /// The reducers of the module definition.
101    /// Note: this is using IndexMap because reducer order is important
102    /// and must be preserved for future calls to `__call_reducer__`.
103    reducers: IndexMap<Identifier, ReducerDef>,
104
105    /// A map from lifecycle reducer kind to reducer id.
106    lifecycle_reducers: EnumMap<Lifecycle, Option<ReducerId>>,
107
108    /// The type definitions of the module definition.
109    types: HashMap<ScopedTypeName, TypeDef>,
110
111    /// The typespace of the module definition.
112    typespace: Typespace,
113
114    /// The typespace, restructured to be useful for client codegen.
115    typespace_for_generate: TypespaceForGenerate,
116
117    /// The global namespace of the module:
118    /// tables, indexes, constraints, schedules, and sequences live in the global namespace.
119    /// Concretely, though, they're stored in the `TableDef` data structures.
120    /// This map allows looking up which `TableDef` stores the `Def` you're looking for.
121    stored_in_table_def: StrMap<Identifier>,
122
123    /// A map from type defs to their names.
124    refmap: HashMap<AlgebraicTypeRef, ScopedTypeName>,
125
126    /// The row-level security policies.
127    ///
128    /// **Note**: Are only validated syntax-wise.
129    row_level_security_raw: HashMap<RawSql, RawRowLevelSecurityDefV9>,
130}
131
132impl ModuleDef {
133    /// The tables of the module definition.
134    pub fn tables(&self) -> impl Iterator<Item = &TableDef> {
135        self.tables.values()
136    }
137
138    /// The indexes of the module definition.
139    pub fn indexes(&self) -> impl Iterator<Item = &IndexDef> {
140        self.tables().flat_map(|table| table.indexes.values())
141    }
142
143    /// The constraints of the module definition.
144    pub fn constraints(&self) -> impl Iterator<Item = &ConstraintDef> {
145        self.tables().flat_map(|table| table.constraints.values())
146    }
147
148    /// The sequences of the module definition.
149    pub fn sequences(&self) -> impl Iterator<Item = &SequenceDef> {
150        self.tables().flat_map(|table| table.sequences.values())
151    }
152
153    /// The schedules of the module definition.
154    pub fn schedules(&self) -> impl Iterator<Item = &ScheduleDef> {
155        self.tables().filter_map(|table| table.schedule.as_ref())
156    }
157
158    /// The reducers of the module definition.
159    pub fn reducers(&self) -> impl Iterator<Item = &ReducerDef> {
160        self.reducers.values()
161    }
162
163    /// The type definitions of the module definition.
164    pub fn types(&self) -> impl Iterator<Item = &TypeDef> {
165        self.types.values()
166    }
167
168    /// The row-level security policies of the module definition.
169    pub fn row_level_security(&self) -> impl Iterator<Item = &RawRowLevelSecurityDefV9> {
170        self.row_level_security_raw.values()
171    }
172
173    /// The `Typespace` used by the module.
174    ///
175    /// `AlgebraicTypeRef`s in the table, reducer, and type alias declarations refer to this typespace.
176    ///
177    /// The typespace must satisfy `Typespace::is_valid_for_client_code_generation`. That is, all types stored in the typespace must either:
178    /// 1. satisfy `AlgebraicType::is_valid_for_client_type_definition`
179    /// 2. and/or `AlgebraicType::is_valid_for_client_type_use`.
180    ///
181    /// Types satisfying condition 1 correspond to generated classes in client code.
182    /// (Types satisfying condition 2 are an artifact of the module bindings, and do not affect the semantics of the module definition.)
183    ///
184    /// Types satisfying condition 1 are required to have corresponding `RawTypeDefV9` declarations in the module.
185    pub fn typespace(&self) -> &Typespace {
186        &self.typespace
187    }
188
189    /// The typespace of the module from a different perspective, one useful for client code generation.
190    pub fn typespace_for_generate(&self) -> &TypespaceForGenerate {
191        &self.typespace_for_generate
192    }
193
194    /// The `TableDef` an entity in the global namespace is stored in, if any.
195    ///
196    /// Generally, you will want to use the `lookup` method on the entity type instead.
197    pub fn stored_in_table_def(&self, name: &str) -> Option<&TableDef> {
198        self.stored_in_table_def
199            .get(name)
200            .and_then(|table_name| self.tables.get(table_name))
201    }
202
203    /// Lookup a definition by its key in `self`.
204    pub fn lookup<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> Option<&T> {
205        T::lookup(self, key)
206    }
207
208    /// Lookup a definition by its key in `self`, panicking if not found.
209    /// Only use this method if you are sure the key exists in the module definition.
210    pub fn lookup_expect<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> &T {
211        T::lookup(self, key).expect("expected ModuleDef to contain key, but it does not")
212    }
213
214    /// Convenience method to look up a table, possibly by a string.
215    pub fn table<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&TableDef> {
216        // If the string IS a valid identifier, we can just look it up.
217        self.tables.get(name)
218    }
219
220    /// Convenience method to look up a reducer, possibly by a string.
221    pub fn reducer<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&ReducerDef> {
222        // If the string IS a valid identifier, we can just look it up.
223        self.reducers.get(name)
224    }
225
226    /// Convenience method to look up a reducer, possibly by a string, returning its id as well.
227    pub fn reducer_full<K: ?Sized + Hash + Equivalent<Identifier>>(
228        &self,
229        name: &K,
230    ) -> Option<(ReducerId, &ReducerDef)> {
231        // If the string IS a valid identifier, we can just look it up.
232        self.reducers.get_full(name).map(|(idx, _, def)| (idx.into(), def))
233    }
234
235    /// Look up a reducer by its id.
236    pub fn reducer_by_id(&self, id: ReducerId) -> &ReducerDef {
237        &self.reducers[id.idx()]
238    }
239
240    /// Look up a reducer by its id.
241    pub fn get_reducer_by_id(&self, id: ReducerId) -> Option<&ReducerDef> {
242        self.reducers.get_index(id.idx()).map(|(_, def)| def)
243    }
244
245    /// Looks up a lifecycle reducer defined in the module.
246    pub fn lifecycle_reducer(&self, lifecycle: Lifecycle) -> Option<(ReducerId, &ReducerDef)> {
247        self.lifecycle_reducers[lifecycle].map(|i| (i, &self.reducers[i.idx()]))
248    }
249
250    /// Get a `DeserializeSeed` that can pull data from a `Deserializer` and format it into a `ProductType`
251    /// at the parameter type of the reducer named `name`.
252    pub fn reducer_arg_deserialize_seed<K: ?Sized + Hash + Equivalent<Identifier>>(
253        &self,
254        name: &K,
255    ) -> Option<(ReducerId, ReducerArgsDeserializeSeed)> {
256        let (id, reducer) = self.reducer_full(name)?;
257        Some((id, ReducerArgsDeserializeSeed(self.typespace.with_type(reducer))))
258    }
259
260    /// Look up the name corresponding to an `AlgebraicTypeRef`.
261    pub fn type_def_from_ref(&self, r: AlgebraicTypeRef) -> Option<(&ScopedTypeName, &TypeDef)> {
262        let name = self.refmap.get(&r)?;
263        let def = self
264            .types
265            .get(name)
266            .expect("if it was in refmap, it should be in types");
267
268        Some((name, def))
269    }
270
271    /// Convenience method to look up a table and convert it to a `TableSchema`.
272    /// All indexes, constraints, etc inside the table will have ID 0!
273    pub fn table_schema<K: ?Sized + Hash + Equivalent<Identifier>>(
274        &self,
275        name: &K,
276        table_id: TableId,
277    ) -> Option<TableSchema> {
278        // If the string IS a valid identifier, we can just look it up.
279        let table_def = self.tables.get(name)?;
280        Some(TableSchema::from_module_def(self, table_def, (), table_id))
281    }
282
283    /// Lookup a definition by its key in `self`, panicking if it is not found.
284    pub fn expect_lookup<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> &T {
285        if let Some(result) = T::lookup(self, key) {
286            result
287        } else {
288            panic!("expected ModuleDef to contain {key:?}, but it does not");
289        }
290    }
291
292    /// Expect that this module definition contains a definition.
293    pub fn expect_contains<Def: ModuleDefLookup>(&self, def: &Def) {
294        if let Some(my_def) = self.lookup(def.key()) {
295            assert_eq!(
296                def as *const Def, my_def as *const Def,
297                "expected ModuleDef to contain {def:?}, but it contained {my_def:?}"
298            );
299        } else {
300            panic!("expected ModuleDef to contain {:?}, but it does not", def.key());
301        }
302    }
303}
304
305impl TryFrom<RawModuleDef> for ModuleDef {
306    type Error = ValidationErrors;
307
308    fn try_from(raw: RawModuleDef) -> Result<Self, Self::Error> {
309        match raw {
310            RawModuleDef::V8BackCompat(v8_mod) => Self::try_from(v8_mod),
311            RawModuleDef::V9(v9_mod) => Self::try_from(v9_mod),
312            _ => unimplemented!(),
313        }
314    }
315}
316impl TryFrom<raw_def::v8::RawModuleDefV8> for ModuleDef {
317    type Error = ValidationErrors;
318
319    fn try_from(v8_mod: raw_def::v8::RawModuleDefV8) -> Result<Self, Self::Error> {
320        // it is not necessary to generate indexes for a v8 mod, since the validation
321        // handles index generation.
322        validate::v8::validate(v8_mod)
323    }
324}
325impl TryFrom<raw_def::v9::RawModuleDefV9> for ModuleDef {
326    type Error = ValidationErrors;
327
328    fn try_from(v9_mod: raw_def::v9::RawModuleDefV9) -> Result<Self, Self::Error> {
329        validate::v9::validate(v9_mod)
330    }
331}
332impl From<ModuleDef> for RawModuleDefV9 {
333    fn from(val: ModuleDef) -> Self {
334        let ModuleDef {
335            tables,
336            reducers,
337            lifecycle_reducers: _,
338            types,
339            typespace,
340            stored_in_table_def: _,
341            typespace_for_generate: _,
342            refmap: _,
343            row_level_security_raw,
344        } = val;
345
346        RawModuleDefV9 {
347            tables: to_raw(tables),
348            reducers: reducers.into_iter().map(|(_, def)| def.into()).collect(),
349            types: to_raw(types),
350            misc_exports: vec![],
351            typespace,
352            row_level_security: row_level_security_raw.into_iter().map(|(_, def)| def).collect(),
353        }
354    }
355}
356
357/// Implemented by definitions stored in a `ModuleDef`.
358/// Allows looking definitions up in a `ModuleDef`, and across
359/// `ModuleDef`s during migrations.
360pub trait ModuleDefLookup: Sized + Debug + 'static {
361    /// A reference to a definition of this type within a module def. This reference should be portable across migrations.
362    type Key<'a>: Debug + Copy;
363
364    /// Get a reference to this definition.
365    fn key(&self) -> Self::Key<'_>;
366
367    /// Look up this entity in the module def.
368    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self>;
369}
370
371/// A data structure representing the validated definition of a database table.
372///
373/// Cannot be created directly. Construct a [`ModuleDef`] by validating a [`RawModuleDef`] instead,
374/// and then access the tables from there.
375///
376/// This struct holds information about the table, including its name, columns, indexes,
377/// constraints, sequences, type, and access rights.
378///
379/// Validation rules:
380/// - The table name must be a valid identifier.
381/// - The table's columns must be sorted according to [crate::db::ordering::canonical_ordering].
382/// - The table's indexes, constraints, and sequences must be sorted by their keys.
383/// - The table's column types may refer only to types in the containing DatabaseDef's typespace.
384/// - The table's column names must be unique.
385#[derive(Debug, Clone, Eq, PartialEq)]
386#[non_exhaustive]
387pub struct TableDef {
388    /// The name of the table.
389    /// Unique within a module, acts as the table's identifier.
390    /// Must be a valid [crate::db::identifier::Identifier].
391    pub name: Identifier,
392
393    /// A reference to a `ProductType` containing the columns of this table.
394    /// This is the single source of truth for the table's columns.
395    /// All elements of the `ProductType` must have names.
396    ///
397    /// 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.
398    pub product_type_ref: AlgebraicTypeRef,
399
400    /// The primary key of the table, if present. Must refer to a valid column.
401    ///
402    /// Currently, there must be a unique constraint and an index corresponding to the primary key.
403    /// Eventually, we may remove the requirement for an index.
404    ///
405    /// The database engine does not actually care about this, but client code generation does.
406    pub primary_key: Option<ColId>,
407
408    /// The columns of this table. This stores the information in
409    /// `product_type_ref` in a more convenient-to-access format.
410    pub columns: Vec<ColumnDef>,
411
412    /// The indices on the table, indexed by name.
413    pub indexes: StrMap<IndexDef>,
414
415    /// The unique constraints on the table, indexed by name.
416    pub constraints: StrMap<ConstraintDef>,
417
418    /// The sequences for the table, indexed by name.
419    pub sequences: StrMap<SequenceDef>,
420
421    /// The schedule for the table, if present.
422    pub schedule: Option<ScheduleDef>,
423
424    /// Whether this is a system- or user-created table.
425    pub table_type: TableType,
426
427    /// Whether this table is public or private.
428    pub table_access: TableAccess,
429}
430
431impl TableDef {
432    /// Get a column of the `TableDef`.
433    pub fn get_column(&self, id: ColId) -> Option<&ColumnDef> {
434        self.columns.get(id.idx())
435    }
436    /// Get a column by the column's name.
437    pub fn get_column_by_name(&self, name: &Identifier) -> Option<&ColumnDef> {
438        self.columns.iter().find(|c| &c.name == name)
439    }
440}
441
442impl From<TableDef> for RawTableDefV9 {
443    fn from(val: TableDef) -> Self {
444        let TableDef {
445            name,
446            product_type_ref,
447            primary_key,
448            columns: _, // will be reconstructed from the product type.
449            indexes,
450            constraints,
451            sequences,
452            schedule,
453            table_type,
454            table_access,
455        } = val;
456
457        RawTableDefV9 {
458            name: name.into(),
459            product_type_ref,
460            primary_key: ColList::from_iter(primary_key),
461            indexes: to_raw(indexes),
462            constraints: to_raw(constraints),
463            sequences: to_raw(sequences),
464            schedule: schedule.map(Into::into),
465            table_type,
466            table_access,
467        }
468    }
469}
470
471/// A sequence definition for a database table column.
472#[derive(Debug, Clone, Eq, PartialEq)]
473pub struct SequenceDef {
474    /// The name of the sequence. Must be unique within the containing `ModuleDef`.
475    pub name: Box<str>,
476
477    /// The position of the column associated with this sequence.
478    /// This refers to a column in the same `RawTableDef` that contains this `RawSequenceDef`.
479    /// The column must have integral type.
480    /// This must be the unique `RawSequenceDef` for this column.
481    pub column: ColId,
482
483    /// The value to start assigning to this column.
484    /// Will be incremented by 1 for each new row.
485    /// If not present, an arbitrary start point may be selected.
486    pub start: Option<i128>,
487
488    /// The minimum allowed value in this column.
489    /// If not present, no minimum.
490    pub min_value: Option<i128>,
491
492    /// The maximum allowed value in this column.
493    /// If not present, no maximum.
494    pub max_value: Option<i128>,
495
496    /// The increment to use when updating the sequence.
497    pub increment: i128,
498}
499
500impl From<SequenceDef> for RawSequenceDefV9 {
501    fn from(val: SequenceDef) -> Self {
502        RawSequenceDefV9 {
503            name: Some(val.name),
504            column: val.column,
505            start: val.start,
506            min_value: val.min_value,
507            max_value: val.max_value,
508            increment: val.increment,
509        }
510    }
511}
512
513/// A struct representing the validated definition of a database index.
514///
515/// Cannot be created directly. Construct a [`ModuleDef`] by validating a [`RawModuleDef`] instead,
516/// and then access the index from there.
517#[derive(Debug, Clone, Eq, PartialEq)]
518#[non_exhaustive]
519pub struct IndexDef {
520    /// The name of the index. Must be unique within the containing `ModuleDef`.
521    pub name: Box<str>,
522
523    /// Accessor name for the index used in client codegen.
524    ///
525    /// This is set the user and should not be assumed to follow
526    /// any particular format.
527    ///
528    /// May be set to `None` if this is an auto-generated index for which the user
529    /// has not supplied a name. In this case, no client code generation for this index
530    /// will be performed.
531    ///
532    /// This name is not visible in the system tables, it is only used for client codegen.
533    pub accessor_name: Option<Identifier>,
534
535    /// The algorithm parameters for the index.
536    pub algorithm: IndexAlgorithm,
537}
538
539impl IndexDef {
540    /// Whether this index was generated by the system.
541    pub fn generated(&self) -> bool {
542        self.accessor_name.is_none()
543    }
544}
545
546impl From<IndexDef> for RawIndexDefV9 {
547    fn from(val: IndexDef) -> Self {
548        RawIndexDefV9 {
549            name: Some(val.name),
550            algorithm: match val.algorithm {
551                IndexAlgorithm::BTree(BTreeAlgorithm { columns }) => RawIndexAlgorithm::BTree { columns },
552                IndexAlgorithm::Direct(DirectAlgorithm { column }) => RawIndexAlgorithm::Direct { column },
553            },
554            accessor_name: val.accessor_name.map(Into::into),
555        }
556    }
557}
558
559/// Data specifying a supported index algorithm.
560#[non_exhaustive]
561#[derive(Debug, Clone, Eq, PartialEq)]
562pub enum IndexAlgorithm {
563    /// Implemented using a rust `std::collections::BTreeMap`.
564    BTree(BTreeAlgorithm),
565    /// Implemented using `DirectUniqueIndex`.
566    Direct(DirectAlgorithm),
567}
568
569impl IndexAlgorithm {
570    /// Get the columns of the index.
571    pub fn columns(&self) -> ColOrCols<'_> {
572        match self {
573            Self::BTree(btree) => ColOrCols::ColList(&btree.columns),
574            Self::Direct(direct) => ColOrCols::Col(direct.column),
575        }
576    }
577    /// Find the column index for a given field.
578    ///
579    /// *NOTE*: This take in account the possibility of permutations.
580    pub fn find_col_index(&self, pos: usize) -> Option<ColId> {
581        self.columns().iter().find(|col_id| col_id.idx() == pos)
582    }
583}
584
585impl From<IndexAlgorithm> for RawIndexAlgorithm {
586    fn from(val: IndexAlgorithm) -> Self {
587        match val {
588            IndexAlgorithm::BTree(BTreeAlgorithm { columns }) => Self::BTree { columns },
589            IndexAlgorithm::Direct(DirectAlgorithm { column }) => Self::Direct { column },
590        }
591    }
592}
593
594/// Data specifying a BTree index.
595#[derive(Debug, Clone, Eq, PartialEq)]
596pub struct BTreeAlgorithm {
597    /// The columns to index.
598    pub columns: ColList,
599}
600
601impl<CL: Into<ColList>> From<CL> for BTreeAlgorithm {
602    fn from(columns: CL) -> Self {
603        let columns = columns.into();
604        Self { columns }
605    }
606}
607
608impl From<BTreeAlgorithm> for IndexAlgorithm {
609    fn from(val: BTreeAlgorithm) -> Self {
610        IndexAlgorithm::BTree(val)
611    }
612}
613
614/// Data specifying a Direct index.
615#[derive(Debug, Clone, Eq, PartialEq)]
616pub struct DirectAlgorithm {
617    /// The column to index.
618    pub column: ColId,
619}
620
621impl<C: Into<ColId>> From<C> for DirectAlgorithm {
622    fn from(column: C) -> Self {
623        let column = column.into();
624        Self { column }
625    }
626}
627
628impl From<DirectAlgorithm> for IndexAlgorithm {
629    fn from(val: DirectAlgorithm) -> Self {
630        IndexAlgorithm::Direct(val)
631    }
632}
633
634/// A struct representing the validated definition of a database column.
635///
636/// Cannot be created directly. Construct a [`ModuleDef`] by validating a [`RawModuleDef`] instead,
637/// and then access the column from there.
638#[derive(Debug, Clone, Eq, PartialEq)]
639#[non_exhaustive]
640pub struct ColumnDef {
641    /// The name of the column.
642    /// Unique within the containing `TableDef`, but
643    /// NOT within the containing `ModuleDef`.
644    pub name: Identifier,
645
646    /// The ID of this column.
647    pub col_id: ColId,
648
649    /// The type of this column. May refer to the containing `ModuleDef`'s `Typespace`.
650    /// Must satisfy `AlgebraicType::is_valid_for_client_type_use`.
651    ///
652    /// Will always correspond to the corresponding element of this table's
653    /// `product_type_ref`, that is, the element at index `col_id.idx()`
654    /// with name `Some(name.as_str())`.
655    pub ty: AlgebraicType,
656
657    /// The type of the column, formatted for client code generation.
658    pub ty_for_generate: AlgebraicTypeUse,
659
660    /// The table this `ColumnDef` is stored in.
661    pub table_name: Identifier,
662}
663
664/// A constraint definition attached to a table.
665#[derive(Debug, Clone, Eq, PartialEq)]
666pub struct ConstraintDef {
667    /// The name of the constraint. Unique within the containing `ModuleDef`.
668    pub name: Box<str>,
669
670    /// The data for the constraint.
671    pub data: ConstraintData,
672}
673
674impl From<ConstraintDef> for RawConstraintDefV9 {
675    fn from(val: ConstraintDef) -> Self {
676        RawConstraintDefV9 {
677            name: Some(val.name),
678            data: val.data.into(),
679        }
680    }
681}
682
683/// Data for a constraint attached to a table.
684#[derive(Debug, Clone, Eq, PartialEq)]
685#[non_exhaustive]
686pub enum ConstraintData {
687    Unique(UniqueConstraintData),
688}
689
690impl ConstraintData {
691    /// If this is a unique constraint, returns the columns that must be unique.
692    /// Otherwise, returns `None`.
693    pub fn unique_columns(&self) -> Option<&ColSet> {
694        match &self {
695            ConstraintData::Unique(UniqueConstraintData { columns }) => Some(columns),
696        }
697    }
698}
699
700impl From<ConstraintData> for RawConstraintDataV9 {
701    fn from(val: ConstraintData) -> Self {
702        match val {
703            ConstraintData::Unique(unique) => RawConstraintDataV9::Unique(unique.into()),
704        }
705    }
706}
707
708/// Requires that the projection of the table onto these columns is an bijection.
709///
710/// That is, there must be a one-to-one relationship between a row and the `columns` of that row.
711#[derive(Debug, Clone, Eq, PartialEq)]
712pub struct UniqueConstraintData {
713    /// The columns on the containing `TableDef`
714    pub columns: ColSet,
715}
716
717impl From<UniqueConstraintData> for RawUniqueConstraintDataV9 {
718    fn from(val: UniqueConstraintData) -> Self {
719        RawUniqueConstraintDataV9 {
720            columns: val.columns.into(),
721        }
722    }
723}
724
725impl From<UniqueConstraintData> for ConstraintData {
726    fn from(val: UniqueConstraintData) -> Self {
727        ConstraintData::Unique(val)
728    }
729}
730
731/// Data for the `RLS` policy on a table.
732#[derive(Debug, Clone, Eq, PartialEq)]
733pub struct RowLevelSecurityDef {
734    /// The `sql` expression to use for row-level security.
735    pub sql: RawSql,
736}
737
738impl From<RowLevelSecurityDef> for RawRowLevelSecurityDefV9 {
739    fn from(val: RowLevelSecurityDef) -> Self {
740        RawRowLevelSecurityDefV9 { sql: val.sql }
741    }
742}
743
744/// Marks a table as a timer table for a scheduled reducer.
745#[derive(Debug, Clone, Eq, PartialEq)]
746#[non_exhaustive]
747pub struct ScheduleDef {
748    /// The name of the schedule. Must be unique within the containing `ModuleDef`.
749    pub name: Box<str>,
750
751    /// The name of the column that stores the desired invocation time.
752    ///
753    /// Must be named `scheduled_at` and be of type `ScheduleAt`.
754    pub at_column: ColId,
755
756    /// The name of the column that stores the invocation ID.
757    ///
758    /// Must be named `scheduled_id` and be of type `u64`.
759    pub id_column: ColId,
760
761    /// The name of the reducer to call. Not yet an `Identifier` because
762    /// reducer names are not currently validated.
763    pub reducer_name: Identifier,
764}
765
766impl From<ScheduleDef> for RawScheduleDefV9 {
767    fn from(val: ScheduleDef) -> Self {
768        RawScheduleDefV9 {
769            name: Some(val.name),
770            reducer_name: val.reducer_name.into(),
771            scheduled_at_column: val.at_column,
772        }
773    }
774}
775
776/// A type exported by the module.
777#[derive(Debug, Clone, Eq, PartialEq)]
778#[non_exhaustive]
779pub struct TypeDef {
780    /// The (scoped) name of the type.
781    pub name: ScopedTypeName,
782
783    /// The type to which the alias refers.
784    /// Look in `ModuleDef.typespace` for the actual type,
785    /// or in `ModuleDef.typespace_for_generate` for the client codegen version.
786    pub ty: AlgebraicTypeRef,
787
788    /// Whether this type has a custom ordering.
789    pub custom_ordering: bool,
790}
791impl From<TypeDef> for RawTypeDefV9 {
792    fn from(val: TypeDef) -> Self {
793        RawTypeDefV9 {
794            name: val.name.into(),
795            ty: val.ty,
796            custom_ordering: val.custom_ordering,
797        }
798    }
799}
800
801/// A scoped type name, in the form `scope0::scope1::...::scopeN::name`.
802///
803/// These are the names that will be used *in client code generation*, NOT the names used for types
804/// in the module source code.
805#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
806pub struct ScopedTypeName {
807    /// The scope for this type.
808    ///
809    /// Empty unless a sats `name` attribute is used, e.g.
810    /// `#[sats(name = "namespace.name")]` in Rust.
811    scope: Box<[Identifier]>,
812
813    /// The name of the type.
814    ///
815    /// Eventually, we may add more information to this, such as generic arguments.
816    name: Identifier,
817}
818impl ScopedTypeName {
819    /// Create a new `ScopedTypeName` from a scope and a name.
820    pub fn new(scope: Box<[Identifier]>, name: Identifier) -> Self {
821        ScopedTypeName { scope, name }
822    }
823
824    /// Try to create a new `ScopedTypeName` from a scope and a name.
825    /// Errors if the scope or name are invalid.
826    pub fn try_new(
827        scope: impl IntoIterator<Item = RawIdentifier>,
828        name: impl Into<RawIdentifier>,
829    ) -> Result<Self, ErrorStream<IdentifierError>> {
830        let scope = scope
831            .into_iter()
832            .map(|chunk| Identifier::new(chunk).map_err(ErrorStream::from))
833            .collect_all_errors();
834        let name = Identifier::new(name.into()).map_err(ErrorStream::from);
835        let (scope, name) = (scope, name).combine_errors()?;
836        Ok(ScopedTypeName { scope, name })
837    }
838
839    /// Create a new `ScopedTypeName` with an empty scope.
840    pub fn from_name(name: Identifier) -> Self {
841        ScopedTypeName {
842            scope: Box::new([]),
843            name,
844        }
845    }
846
847    /// Retrieve the name of this type.
848    pub fn name(&self) -> &Identifier {
849        &self.name
850    }
851
852    /// Retrieve the name of this type, if the scope is empty.
853    pub fn as_identifier(&self) -> Option<&Identifier> {
854        self.scope.is_empty().then_some(&self.name)
855    }
856
857    /// Iterate over the segments of this name.
858    pub fn name_segments(&self) -> impl Iterator<Item = &Identifier> {
859        self.scope.iter().chain(std::iter::once(&self.name))
860    }
861}
862impl fmt::Debug for ScopedTypeName {
863    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
864        // we can wrap this in a pair of double quotes, since we know
865        // none of its elements contain quotes.
866        f.write_char('"')?;
867        for scope in &*self.scope {
868            write!(f, "{scope}::")?;
869        }
870        write!(f, "{}\"", self.name)
871    }
872}
873impl fmt::Display for ScopedTypeName {
874    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
875        for scope in &*self.scope {
876            write!(f, "{scope}::")?;
877        }
878        fmt::Display::fmt(&self.name, f)
879    }
880}
881impl TryFrom<RawScopedTypeNameV9> for ScopedTypeName {
882    type Error = ErrorStream<IdentifierError>;
883
884    fn try_from(value: RawScopedTypeNameV9) -> Result<Self, Self::Error> {
885        Self::try_new(value.scope.into_vec(), value.name)
886    }
887}
888impl From<ScopedTypeName> for RawScopedTypeNameV9 {
889    fn from(val: ScopedTypeName) -> Self {
890        RawScopedTypeNameV9 {
891            scope: val.scope.into_vec().into_iter().map_into().collect(),
892            name: val.name.into(),
893        }
894    }
895}
896
897/// A reducer exported by the module.
898#[derive(Debug, Clone, Eq, PartialEq)]
899#[non_exhaustive]
900pub struct ReducerDef {
901    /// The name of the reducer. This must be unique within the module.
902    pub name: Identifier,
903
904    /// The parameters of the reducer.
905    ///
906    /// This `ProductType` need not be registered in the module's `Typespace`.
907    pub params: ProductType,
908
909    /// The parameters of the reducer, formatted for client codegen.
910    ///
911    /// This `ProductType` need not be registered in the module's `TypespaceForGenerate`.
912    pub params_for_generate: ProductTypeDef,
913
914    /// The special role of this reducer in the module lifecycle, if any.
915    pub lifecycle: Option<Lifecycle>,
916}
917
918impl From<ReducerDef> for RawReducerDefV9 {
919    fn from(val: ReducerDef) -> Self {
920        RawReducerDefV9 {
921            name: val.name.into(),
922            params: val.params,
923            lifecycle: val.lifecycle,
924        }
925    }
926}
927
928impl ModuleDefLookup for TableDef {
929    type Key<'a> = &'a Identifier;
930
931    fn key(&self) -> Self::Key<'_> {
932        &self.name
933    }
934
935    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
936        module_def.tables.get(key)
937    }
938}
939
940impl ModuleDefLookup for SequenceDef {
941    type Key<'a> = &'a str;
942
943    fn key(&self) -> Self::Key<'_> {
944        &self.name
945    }
946
947    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
948        module_def.stored_in_table_def(key)?.sequences.get(key)
949    }
950}
951
952impl ModuleDefLookup for IndexDef {
953    type Key<'a> = &'a str;
954
955    fn key(&self) -> Self::Key<'_> {
956        &self.name
957    }
958
959    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
960        module_def.stored_in_table_def(key)?.indexes.get(key)
961    }
962}
963
964impl ModuleDefLookup for ColumnDef {
965    // (table_name, column_name).
966    // We don't use `ColId` here because we want this to be portable
967    // across migrations.
968    type Key<'a> = (&'a Identifier, &'a Identifier);
969
970    fn key(&self) -> Self::Key<'_> {
971        (&self.table_name, &self.name)
972    }
973
974    fn lookup<'a>(module_def: &'a ModuleDef, (table_name, name): Self::Key<'_>) -> Option<&'a Self> {
975        module_def
976            .tables
977            .get(table_name)
978            .and_then(|table| table.get_column_by_name(name))
979    }
980}
981
982impl ModuleDefLookup for ConstraintDef {
983    type Key<'a> = &'a str;
984
985    fn key(&self) -> Self::Key<'_> {
986        &self.name
987    }
988
989    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
990        module_def.stored_in_table_def(key)?.constraints.get(key)
991    }
992}
993
994impl ModuleDefLookup for RawRowLevelSecurityDefV9 {
995    type Key<'a> = &'a RawSql;
996
997    fn key(&self) -> Self::Key<'_> {
998        &self.sql
999    }
1000
1001    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1002        module_def.row_level_security_raw.get(key)
1003    }
1004}
1005
1006impl ModuleDefLookup for ScheduleDef {
1007    type Key<'a> = &'a str;
1008
1009    fn key(&self) -> Self::Key<'_> {
1010        &self.name
1011    }
1012
1013    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1014        let schedule = module_def.stored_in_table_def(key)?.schedule.as_ref()?;
1015        if &schedule.name[..] == key {
1016            Some(schedule)
1017        } else {
1018            None
1019        }
1020    }
1021}
1022
1023impl ModuleDefLookup for TypeDef {
1024    type Key<'a> = &'a ScopedTypeName;
1025
1026    fn key(&self) -> Self::Key<'_> {
1027        &self.name
1028    }
1029
1030    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1031        module_def.types.get(key)
1032    }
1033}
1034
1035impl ModuleDefLookup for ReducerDef {
1036    type Key<'a> = &'a Identifier;
1037
1038    fn key(&self) -> Self::Key<'_> {
1039        &self.name
1040    }
1041
1042    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1043        module_def.reducers.get(key)
1044    }
1045}
1046
1047fn to_raw<Def, RawDef, Name, A>(data: HashMap<Name, Def, A>) -> Vec<RawDef>
1048where
1049    Def: ModuleDefLookup + Into<RawDef>,
1050    Name: Eq + Ord + 'static,
1051{
1052    let sorted: BTreeMap<Name, Def> = data.into_iter().collect();
1053    sorted.into_values().map_into().collect()
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058    use super::*;
1059    use proptest::prelude::*;
1060
1061    proptest! {
1062        #[test]
1063        fn to_raw_deterministic(vec in prop::collection::vec(any::<u32>(), 0..5)) {
1064            let mut map = HashMap::new();
1065            let name = ScopedTypeName::try_new([], "fake_name").unwrap();
1066            for k in vec {
1067                let def = TypeDef { name: name.clone(), ty: AlgebraicTypeRef(k), custom_ordering: false };
1068                map.insert(k, def);
1069            }
1070            let raw: Vec<RawTypeDefV9> = to_raw(map.clone());
1071            let raw2: Vec<RawTypeDefV9> = to_raw(map);
1072            prop_assert_eq!(raw, raw2);
1073        }
1074    }
1075}