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 validate;
46
47/// A map from `Identifier`s to values of type `T`.
48pub type IdentifierMap<T> = HashMap<Identifier, T>;
49
50/// A map from `Box<str>`s to values of type `T`.
51pub type StrMap<T> = HashMap<Box<str>, T>;
52
53// We may eventually want to reorganize this module to look more
54// like the system tables, with numeric IDs used for lookups
55// in addition to `Identifier`s.
56//
57// If that path is taken, it might be possible to have this type
58// entirely subsume the various `Schema` types, which would be cool.
59
60/// A validated, canonicalized, immutable module definition.
61///
62/// Cannot be created directly. Instead, create/deserialize a [spacetimedb_lib::RawModuleDef] and call [ModuleDef::try_from].
63///
64/// ```rust
65/// use spacetimedb_lib::RawModuleDef;
66/// use spacetimedb_schema::def::{ModuleDef, TableDef, IndexDef, TypeDef, ModuleDefLookup, ScopedTypeName};
67/// use spacetimedb_schema::identifier::Identifier;
68///
69/// fn read_raw_module_def_from_file() -> RawModuleDef {
70///     // ...
71/// #   RawModuleDef::V9(Default::default())
72/// }
73///
74/// let raw_module_def = read_raw_module_def_from_file();
75/// let module_def = ModuleDef::try_from(raw_module_def).expect("valid module def");
76///
77/// let table_name = Identifier::new("my_table".into()).expect("valid table name");
78/// let index_name = "my_table_my_column_idx_btree";
79/// let scoped_type_name = ScopedTypeName::try_new([], "MyType").expect("valid scoped type name");
80///
81/// let table: Option<&TableDef> = module_def.lookup(&table_name);
82/// let index: Option<&IndexDef> = module_def.lookup(index_name);
83/// let type_def: Option<&TypeDef> = module_def.lookup(&scoped_type_name);
84/// // etc.
85/// ```
86///
87/// Author's apology:
88/// If you find yourself asking:
89/// "Why are we using strings to look up everything here, rather than integer indexes?"
90/// The answer is "I tried to get rid of the strings, but people thought it would be too confusing to have multiple
91/// kinds of integer index." Because the system tables and stuff would be using a different sort of integer index.
92/// shrug emoji.
93#[derive(Debug, Clone)]
94#[non_exhaustive]
95pub struct ModuleDef {
96    /// The tables of the module definition.
97    tables: IdentifierMap<TableDef>,
98
99    /// The reducers of the module definition.
100    /// Note: this is using IndexMap because reducer order is important
101    /// and must be preserved for future calls to `__call_reducer__`.
102    reducers: IndexMap<Identifier, ReducerDef>,
103
104    /// A map from lifecycle reducer kind to reducer id.
105    lifecycle_reducers: EnumMap<Lifecycle, Option<ReducerId>>,
106
107    /// The type definitions of the module definition.
108    types: HashMap<ScopedTypeName, TypeDef>,
109
110    /// The typespace of the module definition.
111    typespace: Typespace,
112
113    /// The typespace, restructured to be useful for client codegen.
114    typespace_for_generate: TypespaceForGenerate,
115
116    /// The global namespace of the module:
117    /// tables, indexes, constraints, schedules, and sequences live in the global namespace.
118    /// Concretely, though, they're stored in the `TableDef` data structures.
119    /// This map allows looking up which `TableDef` stores the `Def` you're looking for.
120    stored_in_table_def: StrMap<Identifier>,
121
122    /// A map from type defs to their names.
123    refmap: HashMap<AlgebraicTypeRef, ScopedTypeName>,
124
125    /// The row-level security policies.
126    ///
127    /// **Note**: Are only validated syntax-wise.
128    row_level_security_raw: HashMap<RawSql, RawRowLevelSecurityDefV9>,
129}
130
131impl ModuleDef {
132    /// The tables of the module definition.
133    pub fn tables(&self) -> impl Iterator<Item = &TableDef> {
134        self.tables.values()
135    }
136
137    /// The indexes of the module definition.
138    pub fn indexes(&self) -> impl Iterator<Item = &IndexDef> {
139        self.tables().flat_map(|table| table.indexes.values())
140    }
141
142    /// The constraints of the module definition.
143    pub fn constraints(&self) -> impl Iterator<Item = &ConstraintDef> {
144        self.tables().flat_map(|table| table.constraints.values())
145    }
146
147    /// The sequences of the module definition.
148    pub fn sequences(&self) -> impl Iterator<Item = &SequenceDef> {
149        self.tables().flat_map(|table| table.sequences.values())
150    }
151
152    /// The schedules of the module definition.
153    pub fn schedules(&self) -> impl Iterator<Item = &ScheduleDef> {
154        self.tables().filter_map(|table| table.schedule.as_ref())
155    }
156
157    /// The reducers of the module definition.
158    pub fn reducers(&self) -> impl Iterator<Item = &ReducerDef> {
159        self.reducers.values()
160    }
161
162    /// The type definitions of the module definition.
163    pub fn types(&self) -> impl Iterator<Item = &TypeDef> {
164        self.types.values()
165    }
166
167    /// The row-level security policies of the module definition.
168    pub fn row_level_security(&self) -> impl Iterator<Item = &RawRowLevelSecurityDefV9> {
169        self.row_level_security_raw.values()
170    }
171
172    /// The `Typespace` used by the module.
173    ///
174    /// `AlgebraicTypeRef`s in the table, reducer, and type alias declarations refer to this typespace.
175    ///
176    /// The typespace must satisfy `Typespace::is_valid_for_client_code_generation`. That is, all types stored in the typespace must either:
177    /// 1. satisfy `AlgebraicType::is_valid_for_client_type_definition`
178    /// 2. and/or `AlgebraicType::is_valid_for_client_type_use`.
179    ///
180    /// Types satisfying condition 1 correspond to generated classes in client code.
181    /// (Types satisfying condition 2 are an artifact of the module bindings, and do not affect the semantics of the module definition.)
182    ///
183    /// Types satisfying condition 1 are required to have corresponding `RawTypeDefV9` declarations in the module.
184    pub fn typespace(&self) -> &Typespace {
185        &self.typespace
186    }
187
188    /// The typespace of the module from a different perspective, one useful for client code generation.
189    pub fn typespace_for_generate(&self) -> &TypespaceForGenerate {
190        &self.typespace_for_generate
191    }
192
193    /// The `TableDef` an entity in the global namespace is stored in, if any.
194    ///
195    /// Generally, you will want to use the `lookup` method on the entity type instead.
196    pub fn stored_in_table_def(&self, name: &str) -> Option<&TableDef> {
197        self.stored_in_table_def
198            .get(name)
199            .and_then(|table_name| self.tables.get(table_name))
200    }
201
202    /// Lookup a definition by its key in `self`.
203    pub fn lookup<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> Option<&T> {
204        T::lookup(self, key)
205    }
206
207    /// Lookup a definition by its key in `self`, panicking if not found.
208    /// Only use this method if you are sure the key exists in the module definition.
209    pub fn lookup_expect<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> &T {
210        T::lookup(self, key).expect("expected ModuleDef to contain key, but it does not")
211    }
212
213    /// Convenience method to look up a table, possibly by a string.
214    pub fn table<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&TableDef> {
215        // If the string IS a valid identifier, we can just look it up.
216        self.tables.get(name)
217    }
218
219    /// Convenience method to look up a reducer, possibly by a string.
220    pub fn reducer<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&ReducerDef> {
221        // If the string IS a valid identifier, we can just look it up.
222        self.reducers.get(name)
223    }
224
225    /// Convenience method to look up a reducer, possibly by a string, returning its id as well.
226    pub fn reducer_full<K: ?Sized + Hash + Equivalent<Identifier>>(
227        &self,
228        name: &K,
229    ) -> Option<(ReducerId, &ReducerDef)> {
230        // If the string IS a valid identifier, we can just look it up.
231        self.reducers.get_full(name).map(|(idx, _, def)| (idx.into(), def))
232    }
233
234    /// Look up a reducer by its id.
235    pub fn reducer_by_id(&self, id: ReducerId) -> &ReducerDef {
236        &self.reducers[id.idx()]
237    }
238
239    /// Look up a reducer by its id.
240    pub fn get_reducer_by_id(&self, id: ReducerId) -> Option<&ReducerDef> {
241        self.reducers.get_index(id.idx()).map(|(_, def)| def)
242    }
243
244    /// Looks up a lifecycle reducer defined in the module.
245    pub fn lifecycle_reducer(&self, lifecycle: Lifecycle) -> Option<(ReducerId, &ReducerDef)> {
246        self.lifecycle_reducers[lifecycle].map(|i| (i, &self.reducers[i.idx()]))
247    }
248
249    /// Get a `DeserializeSeed` that can pull data from a `Deserializer` and format it into a `ProductType`
250    /// at the parameter type of the reducer named `name`.
251    pub fn reducer_arg_deserialize_seed<K: ?Sized + Hash + Equivalent<Identifier>>(
252        &self,
253        name: &K,
254    ) -> Option<(ReducerId, ReducerArgsDeserializeSeed)> {
255        let (id, reducer) = self.reducer_full(name)?;
256        Some((id, ReducerArgsDeserializeSeed(self.typespace.with_type(reducer))))
257    }
258
259    /// Look up the name corresponding to an `AlgebraicTypeRef`.
260    pub fn type_def_from_ref(&self, r: AlgebraicTypeRef) -> Option<(&ScopedTypeName, &TypeDef)> {
261        let name = self.refmap.get(&r)?;
262        let def = self
263            .types
264            .get(name)
265            .expect("if it was in refmap, it should be in types");
266
267        Some((name, def))
268    }
269
270    /// Convenience method to look up a table and convert it to a `TableSchema`.
271    /// All indexes, constraints, etc inside the table will have ID 0!
272    pub fn table_schema<K: ?Sized + Hash + Equivalent<Identifier>>(
273        &self,
274        name: &K,
275        table_id: TableId,
276    ) -> Option<TableSchema> {
277        // If the string IS a valid identifier, we can just look it up.
278        let table_def = self.tables.get(name)?;
279        Some(TableSchema::from_module_def(self, table_def, (), table_id))
280    }
281
282    /// Lookup a definition by its key in `self`, panicking if it is not found.
283    pub fn expect_lookup<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> &T {
284        if let Some(result) = T::lookup(self, key) {
285            result
286        } else {
287            panic!("expected ModuleDef to contain {:?}, but it does not", key);
288        }
289    }
290
291    /// Expect that this module definition contains a definition.
292    pub fn expect_contains<Def: ModuleDefLookup>(&self, def: &Def) {
293        if let Some(my_def) = self.lookup(def.key()) {
294            assert_eq!(
295                def as *const Def, my_def as *const Def,
296                "expected ModuleDef to contain {:?}, but it contained {:?}",
297                def, 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 From<BTreeAlgorithm> for IndexAlgorithm {
602    fn from(val: BTreeAlgorithm) -> Self {
603        IndexAlgorithm::BTree(val)
604    }
605}
606
607/// Data specifying a Direct index.
608#[derive(Debug, Clone, Eq, PartialEq)]
609pub struct DirectAlgorithm {
610    /// The column to index.
611    pub column: ColId,
612}
613
614impl From<DirectAlgorithm> for IndexAlgorithm {
615    fn from(val: DirectAlgorithm) -> Self {
616        IndexAlgorithm::Direct(val)
617    }
618}
619
620/// A struct representing the validated definition of a database column.
621///
622/// Cannot be created directly. Construct a [`ModuleDef`] by validating a [`RawModuleDef`] instead,
623/// and then access the column from there.
624#[derive(Debug, Clone, Eq, PartialEq)]
625#[non_exhaustive]
626pub struct ColumnDef {
627    /// The name of the column.
628    /// Unique within the containing `TableDef`, but
629    /// NOT within the containing `ModuleDef`.
630    pub name: Identifier,
631
632    /// The ID of this column.
633    pub col_id: ColId,
634
635    /// The type of this column. May refer to the containing `ModuleDef`'s `Typespace`.
636    /// Must satisfy `AlgebraicType::is_valid_for_client_type_use`.
637    ///
638    /// Will always correspond to the corresponding element of this table's
639    /// `product_type_ref`, that is, the element at index `col_id.idx()`
640    /// with name `Some(name.as_str())`.
641    pub ty: AlgebraicType,
642
643    /// The type of the column, formatted for client code generation.
644    pub ty_for_generate: AlgebraicTypeUse,
645
646    /// The table this `ColumnDef` is stored in.
647    pub table_name: Identifier,
648}
649
650/// A constraint definition attached to a table.
651#[derive(Debug, Clone, Eq, PartialEq)]
652pub struct ConstraintDef {
653    /// The name of the constraint. Unique within the containing `ModuleDef`.
654    pub name: Box<str>,
655
656    /// The data for the constraint.
657    pub data: ConstraintData,
658}
659
660impl From<ConstraintDef> for RawConstraintDefV9 {
661    fn from(val: ConstraintDef) -> Self {
662        RawConstraintDefV9 {
663            name: Some(val.name),
664            data: val.data.into(),
665        }
666    }
667}
668
669/// Data for a constraint attached to a table.
670#[derive(Debug, Clone, Eq, PartialEq)]
671#[non_exhaustive]
672pub enum ConstraintData {
673    Unique(UniqueConstraintData),
674}
675
676impl ConstraintData {
677    /// If this is a unique constraint, returns the columns that must be unique.
678    /// Otherwise, returns `None`.
679    pub fn unique_columns(&self) -> Option<&ColSet> {
680        match &self {
681            ConstraintData::Unique(UniqueConstraintData { columns }) => Some(columns),
682        }
683    }
684}
685
686impl From<ConstraintData> for RawConstraintDataV9 {
687    fn from(val: ConstraintData) -> Self {
688        match val {
689            ConstraintData::Unique(unique) => RawConstraintDataV9::Unique(unique.into()),
690        }
691    }
692}
693
694/// Requires that the projection of the table onto these columns is an bijection.
695///
696/// That is, there must be a one-to-one relationship between a row and the `columns` of that row.
697#[derive(Debug, Clone, Eq, PartialEq)]
698pub struct UniqueConstraintData {
699    /// The columns on the containing `TableDef`
700    pub columns: ColSet,
701}
702
703impl From<UniqueConstraintData> for RawUniqueConstraintDataV9 {
704    fn from(val: UniqueConstraintData) -> Self {
705        RawUniqueConstraintDataV9 {
706            columns: val.columns.into(),
707        }
708    }
709}
710
711impl From<UniqueConstraintData> for ConstraintData {
712    fn from(val: UniqueConstraintData) -> Self {
713        ConstraintData::Unique(val)
714    }
715}
716
717/// Data for the `RLS` policy on a table.
718#[derive(Debug, Clone, Eq, PartialEq)]
719pub struct RowLevelSecurityDef {
720    /// The `sql` expression to use for row-level security.
721    pub sql: RawSql,
722}
723
724impl From<RowLevelSecurityDef> for RawRowLevelSecurityDefV9 {
725    fn from(val: RowLevelSecurityDef) -> Self {
726        RawRowLevelSecurityDefV9 { sql: val.sql }
727    }
728}
729
730/// Marks a table as a timer table for a scheduled reducer.
731#[derive(Debug, Clone, Eq, PartialEq)]
732#[non_exhaustive]
733pub struct ScheduleDef {
734    /// The name of the schedule. Must be unique within the containing `ModuleDef`.
735    pub name: Box<str>,
736
737    /// The name of the column that stores the desired invocation time.
738    ///
739    /// Must be named `scheduled_at` and be of type `ScheduleAt`.
740    pub at_column: ColId,
741
742    /// The name of the column that stores the invocation ID.
743    ///
744    /// Must be named `scheduled_id` and be of type `u64`.
745    pub id_column: ColId,
746
747    /// The name of the reducer to call. Not yet an `Identifier` because
748    /// reducer names are not currently validated.
749    pub reducer_name: Identifier,
750}
751
752impl From<ScheduleDef> for RawScheduleDefV9 {
753    fn from(val: ScheduleDef) -> Self {
754        RawScheduleDefV9 {
755            name: Some(val.name),
756            reducer_name: val.reducer_name.into(),
757            scheduled_at_column: val.at_column,
758        }
759    }
760}
761
762/// A type exported by the module.
763#[derive(Debug, Clone, Eq, PartialEq)]
764#[non_exhaustive]
765pub struct TypeDef {
766    /// The (scoped) name of the type.
767    pub name: ScopedTypeName,
768
769    /// The type to which the alias refers.
770    /// Look in `ModuleDef.typespace` for the actual type,
771    /// or in `ModuleDef.typespace_for_generate` for the client codegen version.
772    pub ty: AlgebraicTypeRef,
773
774    /// Whether this type has a custom ordering.
775    pub custom_ordering: bool,
776}
777impl From<TypeDef> for RawTypeDefV9 {
778    fn from(val: TypeDef) -> Self {
779        RawTypeDefV9 {
780            name: val.name.into(),
781            ty: val.ty,
782            custom_ordering: val.custom_ordering,
783        }
784    }
785}
786
787/// A scoped type name, in the form `scope0::scope1::...::scopeN::name`.
788///
789/// These are the names that will be used *in client code generation*, NOT the names used for types
790/// in the module source code.
791#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
792pub struct ScopedTypeName {
793    /// The scope for this type.
794    ///
795    /// Empty unless a sats `name` attribute is used, e.g.
796    /// `#[sats(name = "namespace.name")]` in Rust.
797    scope: Box<[Identifier]>,
798
799    /// The name of the type.
800    ///
801    /// Eventually, we may add more information to this, such as generic arguments.
802    name: Identifier,
803}
804impl ScopedTypeName {
805    /// Create a new `ScopedTypeName` from a scope and a name.
806    pub fn new(scope: Box<[Identifier]>, name: Identifier) -> Self {
807        ScopedTypeName { scope, name }
808    }
809
810    /// Try to create a new `ScopedTypeName` from a scope and a name.
811    /// Errors if the scope or name are invalid.
812    pub fn try_new(
813        scope: impl IntoIterator<Item = RawIdentifier>,
814        name: impl Into<RawIdentifier>,
815    ) -> Result<Self, ErrorStream<IdentifierError>> {
816        let scope = scope
817            .into_iter()
818            .map(|chunk| Identifier::new(chunk).map_err(ErrorStream::from))
819            .collect_all_errors();
820        let name = Identifier::new(name.into()).map_err(ErrorStream::from);
821        let (scope, name) = (scope, name).combine_errors()?;
822        Ok(ScopedTypeName { scope, name })
823    }
824
825    /// Create a new `ScopedTypeName` with an empty scope.
826    pub fn from_name(name: Identifier) -> Self {
827        ScopedTypeName {
828            scope: Box::new([]),
829            name,
830        }
831    }
832
833    /// Retrieve the name of this type.
834    pub fn name(&self) -> &Identifier {
835        &self.name
836    }
837
838    /// Retrieve the name of this type, if the scope is empty.
839    pub fn as_identifier(&self) -> Option<&Identifier> {
840        self.scope.is_empty().then_some(&self.name)
841    }
842
843    /// Iterate over the segments of this name.
844    pub fn name_segments(&self) -> impl Iterator<Item = &Identifier> {
845        self.scope.iter().chain(std::iter::once(&self.name))
846    }
847}
848impl fmt::Debug for ScopedTypeName {
849    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
850        // we can wrap this in a pair of double quotes, since we know
851        // none of its elements contain quotes.
852        f.write_char('"')?;
853        for scope in &*self.scope {
854            write!(f, "{}::", scope)?;
855        }
856        write!(f, "{}\"", self.name)
857    }
858}
859impl fmt::Display for ScopedTypeName {
860    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
861        for scope in &*self.scope {
862            write!(f, "{}::", scope)?;
863        }
864        fmt::Display::fmt(&self.name, f)
865    }
866}
867impl TryFrom<RawScopedTypeNameV9> for ScopedTypeName {
868    type Error = ErrorStream<IdentifierError>;
869
870    fn try_from(value: RawScopedTypeNameV9) -> Result<Self, Self::Error> {
871        Self::try_new(value.scope.into_vec(), value.name)
872    }
873}
874impl From<ScopedTypeName> for RawScopedTypeNameV9 {
875    fn from(val: ScopedTypeName) -> Self {
876        RawScopedTypeNameV9 {
877            scope: val.scope.into_vec().into_iter().map_into().collect(),
878            name: val.name.into(),
879        }
880    }
881}
882
883/// A reducer exported by the module.
884#[derive(Debug, Clone, Eq, PartialEq)]
885#[non_exhaustive]
886pub struct ReducerDef {
887    /// The name of the reducer. This must be unique within the module.
888    pub name: Identifier,
889
890    /// The parameters of the reducer.
891    ///
892    /// This `ProductType` need not be registered in the module's `Typespace`.
893    pub params: ProductType,
894
895    /// The parameters of the reducer, formatted for client codegen.
896    ///
897    /// This `ProductType` need not be registered in the module's `TypespaceForGenerate`.
898    pub params_for_generate: ProductTypeDef,
899
900    /// The special role of this reducer in the module lifecycle, if any.
901    pub lifecycle: Option<Lifecycle>,
902}
903
904impl From<ReducerDef> for RawReducerDefV9 {
905    fn from(val: ReducerDef) -> Self {
906        RawReducerDefV9 {
907            name: val.name.into(),
908            params: val.params,
909            lifecycle: val.lifecycle,
910        }
911    }
912}
913
914impl ModuleDefLookup for TableDef {
915    type Key<'a> = &'a Identifier;
916
917    fn key(&self) -> Self::Key<'_> {
918        &self.name
919    }
920
921    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
922        module_def.tables.get(key)
923    }
924}
925
926impl ModuleDefLookup for SequenceDef {
927    type Key<'a> = &'a str;
928
929    fn key(&self) -> Self::Key<'_> {
930        &self.name
931    }
932
933    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
934        module_def.stored_in_table_def(key)?.sequences.get(key)
935    }
936}
937
938impl ModuleDefLookup for IndexDef {
939    type Key<'a> = &'a str;
940
941    fn key(&self) -> Self::Key<'_> {
942        &self.name
943    }
944
945    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
946        module_def.stored_in_table_def(key)?.indexes.get(key)
947    }
948}
949
950impl ModuleDefLookup for ColumnDef {
951    // (table_name, column_name).
952    // We don't use `ColId` here because we want this to be portable
953    // across migrations.
954    type Key<'a> = (&'a Identifier, &'a Identifier);
955
956    fn key(&self) -> Self::Key<'_> {
957        (&self.table_name, &self.name)
958    }
959
960    fn lookup<'a>(module_def: &'a ModuleDef, (table_name, name): Self::Key<'_>) -> Option<&'a Self> {
961        module_def
962            .tables
963            .get(table_name)
964            .and_then(|table| table.get_column_by_name(name))
965    }
966}
967
968impl ModuleDefLookup for ConstraintDef {
969    type Key<'a> = &'a str;
970
971    fn key(&self) -> Self::Key<'_> {
972        &self.name
973    }
974
975    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
976        module_def.stored_in_table_def(key)?.constraints.get(key)
977    }
978}
979
980impl ModuleDefLookup for RawRowLevelSecurityDefV9 {
981    type Key<'a> = &'a RawSql;
982
983    fn key(&self) -> Self::Key<'_> {
984        &self.sql
985    }
986
987    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
988        module_def.row_level_security_raw.get(key)
989    }
990}
991
992impl ModuleDefLookup for ScheduleDef {
993    type Key<'a> = &'a str;
994
995    fn key(&self) -> Self::Key<'_> {
996        &self.name
997    }
998
999    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1000        let schedule = module_def.stored_in_table_def(key)?.schedule.as_ref()?;
1001        if &schedule.name[..] == key {
1002            Some(schedule)
1003        } else {
1004            None
1005        }
1006    }
1007}
1008
1009impl ModuleDefLookup for TypeDef {
1010    type Key<'a> = &'a ScopedTypeName;
1011
1012    fn key(&self) -> Self::Key<'_> {
1013        &self.name
1014    }
1015
1016    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1017        module_def.types.get(key)
1018    }
1019}
1020
1021impl ModuleDefLookup for ReducerDef {
1022    type Key<'a> = &'a Identifier;
1023
1024    fn key(&self) -> Self::Key<'_> {
1025        &self.name
1026    }
1027
1028    fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
1029        module_def.reducers.get(key)
1030    }
1031}
1032
1033fn to_raw<Def, RawDef, Name, A>(data: HashMap<Name, Def, A>) -> Vec<RawDef>
1034where
1035    Def: ModuleDefLookup + Into<RawDef>,
1036    Name: Eq + Ord + 'static,
1037{
1038    let sorted: BTreeMap<Name, Def> = data.into_iter().collect();
1039    sorted.into_values().map_into().collect()
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044    use super::*;
1045    use proptest::prelude::*;
1046
1047    proptest! {
1048        #[test]
1049        fn to_raw_deterministic(vec in prop::collection::vec(any::<u32>(), 0..5)) {
1050            let mut map = HashMap::new();
1051            let name = ScopedTypeName::try_new([], "fake_name").unwrap();
1052            for k in vec {
1053                let def = TypeDef { name: name.clone(), ty: AlgebraicTypeRef(k), custom_ordering: false };
1054                map.insert(k, def);
1055            }
1056            let raw: Vec<RawTypeDefV9> = to_raw(map.clone());
1057            let raw2: Vec<RawTypeDefV9> = to_raw(map);
1058            prop_assert_eq!(raw, raw2);
1059        }
1060    }
1061}