spacetimedb_schema_2/
schema.rs

1//! Schema data structures.
2//! These are used at runtime by the vm to store the schema of the database.
3//! They are mirrored in the system tables -- see `spacetimedb_core::db::datastore::system_tables`.
4//! Types in this file are not public ABI or API and may be changed at any time; it's the system tables that cannot.
5
6// TODO(1.0): change all the `Box<str>`s in this file to `Identifier`.
7// This doesn't affect the ABI so can wait until 1.0.
8
9use itertools::Itertools;
10use spacetimedb_lib::db::auth::{StAccess, StTableType};
11use spacetimedb_lib::db::error::{DefType, SchemaError};
12use spacetimedb_lib::db::raw_def::v9::RawSql;
13use spacetimedb_lib::db::raw_def::{generate_cols_name, RawConstraintDefV8};
14use spacetimedb_lib::relation::{combine_constraints, Column, DbTable, FieldName, Header};
15use spacetimedb_lib::{AlgebraicType, ProductType, ProductTypeElement};
16use spacetimedb_primitives::*;
17use spacetimedb_sats::product_value::InvalidFieldError;
18use spacetimedb_sats::WithTypespace;
19use std::collections::BTreeMap;
20use std::sync::Arc;
21
22use crate::def::{
23    ColumnDef, ConstraintData, ConstraintDef, IndexAlgorithm, IndexDef, ModuleDef, ModuleDefLookup, ScheduleDef,
24    SequenceDef, TableDef, UniqueConstraintData,
25};
26use crate::identifier::Identifier;
27
28/// Helper trait documenting allowing schema entities to be built from a validated `ModuleDef`.
29pub trait Schema: Sized {
30    /// The `Def` type corresponding to this schema type.
31    type Def: ModuleDefLookup;
32    /// The `Id` type corresponding to this schema type.
33    type Id;
34    /// The `Id` type corresponding to the parent of this schema type.
35    /// Set to `()` if there is no parent.
36    type ParentId;
37
38    /// Construct a schema entity from a validated `ModuleDef`.
39    /// Panics if `module_def` does not contain `def`.
40    ///
41    /// If this schema entity contains children (e.g. if it is a table schema), they should be constructed with
42    /// IDs set to `ChildId::SENTINEL`.
43    ///
44    /// If this schema entity contains `AlgebraicType`s, they should be fully resolved by this function (via
45    /// `WithTypespace::resolve_refs`). This means they will no longer contain references to any typespace (and be non-recursive).
46    /// This is necessary because the database does not currently attempt to handle typespaces / recursive types.
47    fn from_module_def(module_def: &ModuleDef, def: &Self::Def, parent_id: Self::ParentId, id: Self::Id) -> Self;
48
49    /// Check that a schema entity is compatible with a definition.
50    fn check_compatible(&self, module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error>;
51}
52
53/// A data structure representing the schema of a database table.
54///
55/// This struct holds information about the table, including its identifier,
56/// name, columns, indexes, constraints, sequences, type, and access rights.
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct TableSchema {
59    /// The unique identifier of the table within the database.
60    pub table_id: TableId,
61
62    /// The name of the table.
63    pub table_name: Box<str>,
64
65    /// The columns of the table.
66    /// Inaccessible to prevent mutation.
67    /// The ordering of the columns is significant. Columns are frequently identified by `ColId`, that is, position in this list.
68    columns: Vec<ColumnSchema>,
69
70    /// The primary key of the table, if present. Must refer to a valid column.
71    ///
72    /// Currently, there must be a unique constraint and an index corresponding to the primary key.
73    /// Eventually, we may remove the requirement for an index.
74    ///
75    /// The database engine does not actually care about this, but client code generation does.
76    pub primary_key: Option<ColId>,
77
78    /// The indexes on the table.
79    pub indexes: Vec<IndexSchema>,
80
81    /// The constraints on the table.
82    pub constraints: Vec<ConstraintSchema>,
83
84    /// The sequences on the table.
85    pub sequences: Vec<SequenceSchema>,
86
87    /// Whether the table was created by a user or by the system.
88    pub table_type: StTableType,
89
90    /// The visibility of the table.
91    pub table_access: StAccess,
92
93    /// The schedule for the table, if present.
94    pub schedule: Option<ScheduleSchema>,
95
96    /// Cache for `row_type_for_table` in the data store.
97    row_type: ProductType,
98}
99
100impl TableSchema {
101    /// Create a table schema.
102    #[allow(clippy::too_many_arguments)]
103    pub fn new(
104        table_id: TableId,
105        table_name: Box<str>,
106        columns: Vec<ColumnSchema>,
107        indexes: Vec<IndexSchema>,
108        constraints: Vec<ConstraintSchema>,
109        sequences: Vec<SequenceSchema>,
110        table_type: StTableType,
111        table_access: StAccess,
112        schedule: Option<ScheduleSchema>,
113        primary_key: Option<ColId>,
114    ) -> Self {
115        let row_type = ProductType::new(
116            columns
117                .iter()
118                .map(|c| ProductTypeElement {
119                    name: Some(c.col_name.clone()),
120                    algebraic_type: c.col_type.clone(),
121                })
122                .collect(),
123        );
124
125        Self {
126            table_id,
127            table_name,
128            columns,
129            indexes,
130            constraints,
131            sequences,
132            table_type,
133            table_access,
134            row_type,
135            schedule,
136            primary_key,
137        }
138    }
139
140    /// Create a `TableSchema` corresponding to a product type.
141    /// For use in tests.
142    #[cfg(feature = "test")]
143    pub fn from_product_type(ty: ProductType) -> TableSchema {
144        let columns = ty
145            .elements
146            .iter()
147            .enumerate()
148            .map(|(col_pos, element)| ColumnSchema {
149                table_id: TableId::SENTINEL,
150                col_pos: ColId(col_pos as _),
151                col_name: element.name.clone().unwrap_or_else(|| format!("col{}", col_pos).into()),
152                col_type: element.algebraic_type.clone(),
153            })
154            .collect();
155
156        TableSchema::new(
157            TableId::SENTINEL,
158            "TestTable".into(),
159            columns,
160            vec![],
161            vec![],
162            vec![],
163            StTableType::User,
164            StAccess::Public,
165            None,
166            None,
167        )
168    }
169
170    /// Update the table id of this schema.
171    /// For use by the core database engine after assigning a table id.
172    pub fn update_table_id(&mut self, id: TableId) {
173        self.table_id = id;
174        self.columns.iter_mut().for_each(|c| c.table_id = id);
175        self.indexes.iter_mut().for_each(|i| i.table_id = id);
176        self.constraints.iter_mut().for_each(|c| c.table_id = id);
177        self.sequences.iter_mut().for_each(|s| s.table_id = id);
178        if let Some(s) = self.schedule.as_mut() {
179            s.table_id = id;
180        }
181    }
182
183    /// Convert a table schema into a list of columns.
184    pub fn into_columns(self) -> Vec<ColumnSchema> {
185        self.columns
186    }
187
188    /// Get the columns of the table. Only immutable access to the columns is provided.
189    /// The ordering of the columns is significant. Columns are frequently identified by `ColId`, that is, position in this list.
190    pub fn columns(&self) -> &[ColumnSchema] {
191        &self.columns
192    }
193
194    /// Clear all the [Self::indexes], [Self::sequences] & [Self::constraints]
195    pub fn clear_adjacent_schemas(&mut self) {
196        self.indexes.clear();
197        self.sequences.clear();
198        self.constraints.clear();
199    }
200
201    // Crud operation on adjacent schemas
202
203    /// Add OR replace the [SequenceSchema]
204    pub fn update_sequence(&mut self, of: SequenceSchema) {
205        if let Some(x) = self.sequences.iter_mut().find(|x| x.sequence_id == of.sequence_id) {
206            *x = of;
207        } else {
208            self.sequences.push(of);
209        }
210    }
211
212    /// Removes the given `sequence_id`
213    pub fn remove_sequence(&mut self, sequence_id: SequenceId) {
214        self.sequences.retain(|x| x.sequence_id != sequence_id)
215    }
216
217    /// Add OR replace the [IndexSchema]
218    pub fn update_index(&mut self, of: IndexSchema) {
219        if let Some(x) = self.indexes.iter_mut().find(|x| x.index_id == of.index_id) {
220            *x = of;
221        } else {
222            self.indexes.push(of);
223        }
224    }
225
226    /// Removes the given `index_id`
227    pub fn remove_index(&mut self, index_id: IndexId) {
228        self.indexes.retain(|x| x.index_id != index_id)
229    }
230
231    /// Add OR replace the [ConstraintSchema]
232    pub fn update_constraint(&mut self, of: ConstraintSchema) {
233        if let Some(x) = self
234            .constraints
235            .iter_mut()
236            .find(|x| x.constraint_id == of.constraint_id)
237        {
238            *x = of;
239        } else {
240            self.constraints.push(of);
241        }
242    }
243
244    /// Removes the given `index_id`
245    pub fn remove_constraint(&mut self, constraint_id: ConstraintId) {
246        self.constraints.retain(|x| x.constraint_id != constraint_id)
247    }
248
249    /// Concatenate the column names from the `columns`
250    ///
251    /// WARNING: If the `ColId` not exist, is skipped.
252    /// TODO(Tyler): This should return an error and not allow this to be constructed
253    /// if there is an invalid `ColId`
254    pub fn generate_cols_name(&self, columns: &ColList) -> String {
255        generate_cols_name(columns, |p| self.get_column(p.idx()).map(|c| &*c.col_name))
256    }
257
258    /// Check if the specified `field` exists in this [TableSchema].
259    ///
260    /// # Warning
261    ///
262    /// This function ignores the `table_id` when searching for a column.
263    pub fn get_column_by_field(&self, field: FieldName) -> Option<&ColumnSchema> {
264        self.get_column(field.col.idx())
265    }
266
267    /// Look up a list of columns by their positions in the table.
268    /// Invalid column positions are permitted.
269    pub fn get_columns(&self, columns: &ColList) -> Vec<(ColId, Option<&ColumnSchema>)> {
270        columns.iter().map(|col| (col, self.columns.get(col.idx()))).collect()
271    }
272
273    /// Get a reference to a column by its position (`pos`) in the table.
274    pub fn get_column(&self, pos: usize) -> Option<&ColumnSchema> {
275        self.columns.get(pos)
276    }
277
278    /// Check if the `col_name` exist on this [TableSchema]
279    pub fn get_column_by_name(&self, col_name: &str) -> Option<&ColumnSchema> {
280        self.columns.iter().find(|x| &*x.col_name == col_name)
281    }
282
283    /// Check if the `col_name` exist on this [TableSchema]
284    ///
285    /// Warning: It ignores the `table_name`
286    pub fn get_column_id_by_name(&self, col_name: &str) -> Option<ColId> {
287        self.columns
288            .iter()
289            .position(|x| &*x.col_name == col_name)
290            .map(|x| x.into())
291    }
292
293    /// Project the fields from the supplied `indexes`.
294    pub fn project(&self, indexes: impl Iterator<Item = ColId>) -> Result<Vec<&ColumnSchema>, InvalidFieldError> {
295        indexes
296            .map(|index| self.get_column(index.0 as usize).ok_or_else(|| index.into()))
297            .collect()
298    }
299
300    /// Utility for project the fields from the supplied `indexes` that is a [ColList],
301    /// used for when the list of field indexes have at least one value.
302    pub fn project_not_empty(&self, indexes: ColList) -> Result<Vec<&ColumnSchema>, InvalidFieldError> {
303        self.project(indexes.iter())
304    }
305
306    /// IMPORTANT: Is required to have this cached to avoid a perf drop on datastore operations
307    pub fn get_row_type(&self) -> &ProductType {
308        &self.row_type
309    }
310
311    /// Utility to avoid cloning in `row_type_for_table`
312    pub fn into_row_type(self) -> ProductType {
313        self.row_type
314    }
315
316    /// Iterate over the constraints on sets of columns on this table.
317    fn backcompat_constraints_iter(&self) -> impl Iterator<Item = (ColList, Constraints)> + '_ {
318        self.constraints
319            .iter()
320            .map(|x| -> (ColList, Constraints) {
321                match &x.data {
322                    ConstraintData::Unique(unique) => (unique.columns.clone().into(), Constraints::unique()),
323                }
324            })
325            .chain(self.indexes.iter().map(|x| match &x.index_algorithm {
326                IndexAlgorithm::BTree(btree) => (btree.columns.clone(), Constraints::indexed()),
327            }))
328            .chain(
329                self.sequences
330                    .iter()
331                    .map(|x| (col_list![x.col_pos], Constraints::auto_inc())),
332            )
333            .chain(
334                self.primary_key
335                    .iter()
336                    .map(|x| (col_list![*x], Constraints::primary_key())),
337            )
338    }
339
340    /// Get backwards-compatible constraints for this table.
341    ///
342    /// This is closer to how `TableSchema` used to work.
343    pub fn backcompat_constraints(&self) -> BTreeMap<ColList, Constraints> {
344        combine_constraints(self.backcompat_constraints_iter())
345    }
346
347    /// Get backwards-compatible constraints for this table.
348    ///
349    /// Resolves the constraints per each column. If the column don't have one, auto-generate [Constraints::unset()].
350    /// This guarantee all columns can be queried for it constraints.
351    pub fn backcompat_column_constraints(&self) -> BTreeMap<ColList, Constraints> {
352        let mut result = combine_constraints(self.backcompat_constraints_iter());
353        for col in &self.columns {
354            result.entry(col_list![col.col_pos]).or_insert(Constraints::unset());
355        }
356        result
357    }
358
359    /// Get the column corresponding to the primary key, if any.
360    pub fn pk(&self) -> Option<&ColumnSchema> {
361        self.primary_key.and_then(|pk| self.get_column(pk.0 as usize))
362    }
363
364    /// Verify the definitions of this schema are valid:
365    /// - Check all names are not empty
366    /// - All columns exists
367    /// - Only 1 PK
368    /// - Only 1 sequence per column
369    /// - Only Btree Indexes
370    ///
371    /// Deprecated. This will eventually be replaced by the `schema` crate.
372    pub fn validated(self) -> Result<Self, Vec<SchemaError>> {
373        let mut errors = Vec::new();
374
375        if self.table_name.is_empty() {
376            errors.push(SchemaError::EmptyTableName {
377                table_id: self.table_id,
378            });
379        }
380
381        let columns_not_found = self
382            .sequences
383            .iter()
384            .map(|x| (DefType::Sequence, x.sequence_name.clone(), ColList::new(x.col_pos)))
385            .chain(self.indexes.iter().map(|x| match &x.index_algorithm {
386                IndexAlgorithm::BTree(btree) => (DefType::Index, x.index_name.clone(), btree.columns.clone()),
387            }))
388            .chain(self.constraints.iter().map(|x| {
389                (
390                    DefType::Constraint,
391                    x.constraint_name.clone(),
392                    match &x.data {
393                        ConstraintData::Unique(unique) => unique.columns.clone().into(),
394                    },
395                )
396            }))
397            .filter_map(|(ty, name, cols)| {
398                let empty: Vec<_> = self
399                    .get_columns(&cols)
400                    .iter()
401                    .filter_map(|(col, x)| if x.is_none() { Some(*col) } else { None })
402                    .collect();
403
404                if empty.is_empty() {
405                    None
406                } else {
407                    Some(SchemaError::ColumnsNotFound {
408                        name,
409                        table: self.table_name.clone(),
410                        columns: empty,
411                        ty,
412                    })
413                }
414            });
415
416        errors.extend(columns_not_found);
417
418        errors.extend(self.columns.iter().filter_map(|x| {
419            if x.col_name.is_empty() {
420                Some(SchemaError::EmptyName {
421                    table: self.table_name.clone(),
422                    ty: DefType::Column,
423                    id: x.col_pos.0 as _,
424                })
425            } else {
426                None
427            }
428        }));
429
430        errors.extend(self.indexes.iter().filter_map(|x| {
431            if x.index_name.is_empty() {
432                Some(SchemaError::EmptyName {
433                    table: self.table_name.clone(),
434                    ty: DefType::Index,
435                    id: x.index_id.0,
436                })
437            } else {
438                None
439            }
440        }));
441        errors.extend(self.constraints.iter().filter_map(|x| {
442            if x.constraint_name.is_empty() {
443                Some(SchemaError::EmptyName {
444                    table: self.table_name.clone(),
445                    ty: DefType::Constraint,
446                    id: x.constraint_id.0,
447                })
448            } else {
449                None
450            }
451        }));
452
453        errors.extend(self.sequences.iter().filter_map(|x| {
454            if x.sequence_name.is_empty() {
455                Some(SchemaError::EmptyName {
456                    table: self.table_name.clone(),
457                    ty: DefType::Sequence,
458                    id: x.sequence_id.0,
459                })
460            } else {
461                None
462            }
463        }));
464
465        // Verify we don't have more than 1 auto_inc for the same column
466        if let Some(err) = self
467            .sequences
468            .iter()
469            .group_by(|&seq| seq.col_pos)
470            .into_iter()
471            .find_map(|(key, group)| {
472                let count = group.count();
473                if count > 1 {
474                    Some(SchemaError::OneAutoInc {
475                        table: self.table_name.clone(),
476                        field: self.columns[key.idx()].col_name.clone(),
477                    })
478                } else {
479                    None
480                }
481            })
482        {
483            errors.push(err);
484        }
485
486        if errors.is_empty() {
487            Ok(self)
488        } else {
489            Err(errors)
490        }
491    }
492
493    /// The C# and Rust SDKs are inconsistent about whether v8 column defs store resolved or unresolved algebraic types.
494    /// This method works around this problem by copying the column types from the module def into the table schema.
495    /// It can be removed once v8 is removed, since v9 will reject modules with an inconsistency like this.
496    pub fn janky_fix_column_defs(&mut self, module_def: &ModuleDef) {
497        let table_name = Identifier::new(self.table_name.clone()).unwrap();
498        for col in &mut self.columns {
499            let def: &ColumnDef = module_def
500                .lookup((&table_name, &Identifier::new(col.col_name.clone()).unwrap()))
501                .unwrap();
502            col.col_type = def.ty.clone();
503        }
504        let table_def: &TableDef = module_def.expect_lookup(&table_name);
505        self.row_type = module_def.typespace()[table_def.product_type_ref]
506            .as_product()
507            .unwrap()
508            .clone();
509    }
510
511    /// Normalize a `TableSchema`.
512    /// The result is semantically equivalent, but may have reordered indexes, constraints, or sequences.
513    /// Columns will not be reordered.
514    pub fn normalize(&mut self) {
515        self.indexes.sort_by(|a, b| a.index_name.cmp(&b.index_name));
516        self.constraints
517            .sort_by(|a, b| a.constraint_name.cmp(&b.constraint_name));
518        self.sequences.sort_by(|a, b| a.sequence_name.cmp(&b.sequence_name));
519    }
520}
521
522/// Like `assert_eq!` for `anyhow`, but `$msg` is just a string, not a format string.
523macro_rules! ensure_eq {
524    ($a:expr, $b:expr, $msg:expr) => {
525        if $a != $b {
526            anyhow::bail!(
527                "{0}: expected {1} == {2}:\n   {1}: {3:?}\n   {2}: {4:?}",
528                $msg,
529                stringify!($a),
530                stringify!($b),
531                $a,
532                $b
533            );
534        }
535    };
536}
537
538impl Schema for TableSchema {
539    type Def = TableDef;
540    type Id = TableId;
541    type ParentId = ();
542
543    // N.B. This implementation gives all children ID 0 (the auto-inc sentinel value.)
544    fn from_module_def(
545        module_def: &ModuleDef,
546        def: &Self::Def,
547        _parent_id: Self::ParentId,
548        table_id: Self::Id,
549    ) -> Self {
550        module_def.expect_contains(def);
551
552        let TableDef {
553            name,
554            product_type_ref: _,
555            primary_key,
556            columns,
557            indexes,
558            constraints,
559            sequences,
560            schedule,
561            table_type,
562            table_access,
563        } = def;
564
565        let columns: Vec<ColumnSchema> = columns
566            .iter()
567            .enumerate()
568            .map(|(col_pos, def)| ColumnSchema::from_module_def(module_def, def, (), (table_id, col_pos.into())))
569            .collect();
570
571        // note: these Ids are fixed up somewhere else, so we can just use 0 here...
572        // but it would be nice to pass the correct values into this method.
573        let indexes = indexes
574            .values()
575            .map(|def| IndexSchema::from_module_def(module_def, def, table_id, IndexId::SENTINEL))
576            .collect();
577
578        let sequences = sequences
579            .values()
580            .map(|def| SequenceSchema::from_module_def(module_def, def, table_id, SequenceId::SENTINEL))
581            .collect();
582
583        let constraints = constraints
584            .values()
585            .map(|def| ConstraintSchema::from_module_def(module_def, def, table_id, ConstraintId::SENTINEL))
586            .collect();
587
588        let schedule = schedule
589            .as_ref()
590            .map(|schedule| ScheduleSchema::from_module_def(module_def, schedule, table_id, ScheduleId::SENTINEL));
591
592        TableSchema::new(
593            table_id,
594            (*name).clone().into(),
595            columns,
596            indexes,
597            constraints,
598            sequences,
599            (*table_type).into(),
600            (*table_access).into(),
601            schedule,
602            *primary_key,
603        )
604    }
605
606    fn check_compatible(&self, module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
607        ensure_eq!(&self.table_name[..], &def.name[..], "Table name mismatch");
608        ensure_eq!(self.primary_key, def.primary_key, "Primary key mismatch");
609        let def_table_access: StAccess = (def.table_access).into();
610        ensure_eq!(self.table_access, def_table_access, "Table access mismatch");
611        let def_table_type: StTableType = (def.table_type).into();
612        ensure_eq!(self.table_type, def_table_type, "Table type mismatch");
613
614        for col in &self.columns {
615            let col_def = def
616                .columns
617                .get(col.col_pos.0 as usize)
618                .ok_or_else(|| anyhow::anyhow!("Column {} not found in definition", col.col_pos.0))?;
619            col.check_compatible(module_def, col_def)?;
620        }
621        ensure_eq!(self.columns.len(), def.columns.len(), "Column count mismatch");
622
623        for index in &self.indexes {
624            let index_def = def
625                .indexes
626                .get(&index.index_name[..])
627                .ok_or_else(|| anyhow::anyhow!("Index {} not found in definition", index.index_id.0))?;
628            index.check_compatible(module_def, index_def)?;
629        }
630        ensure_eq!(self.indexes.len(), def.indexes.len(), "Index count mismatch");
631
632        for constraint in &self.constraints {
633            let constraint_def = def
634                .constraints
635                .get(&constraint.constraint_name[..])
636                .ok_or_else(|| anyhow::anyhow!("Constraint {} not found in definition", constraint.constraint_id.0))?;
637            constraint.check_compatible(module_def, constraint_def)?;
638        }
639        ensure_eq!(
640            self.constraints.len(),
641            def.constraints.len(),
642            "Constraint count mismatch"
643        );
644
645        for sequence in &self.sequences {
646            let sequence_def = def
647                .sequences
648                .get(&sequence.sequence_name[..])
649                .ok_or_else(|| anyhow::anyhow!("Sequence {} not found in definition", sequence.sequence_id.0))?;
650            sequence.check_compatible(module_def, sequence_def)?;
651        }
652        ensure_eq!(self.sequences.len(), def.sequences.len(), "Sequence count mismatch");
653
654        if let Some(schedule) = &self.schedule {
655            let schedule_def = def
656                .schedule
657                .as_ref()
658                .ok_or_else(|| anyhow::anyhow!("Schedule not found in definition"))?;
659            schedule.check_compatible(module_def, schedule_def)?;
660        }
661        ensure_eq!(
662            self.schedule.is_some(),
663            def.schedule.is_some(),
664            "Schedule presence mismatch"
665        );
666        Ok(())
667    }
668}
669
670impl From<&TableSchema> for ProductType {
671    fn from(value: &TableSchema) -> Self {
672        ProductType::new(
673            value
674                .columns
675                .iter()
676                .map(|c| ProductTypeElement {
677                    name: Some(c.col_name.clone()),
678                    algebraic_type: c.col_type.clone(),
679                })
680                .collect(),
681        )
682    }
683}
684
685impl From<&TableSchema> for DbTable {
686    fn from(value: &TableSchema) -> Self {
687        DbTable::new(
688            Arc::new(value.into()),
689            value.table_id,
690            value.table_type,
691            value.table_access,
692        )
693    }
694}
695
696impl From<&TableSchema> for Header {
697    fn from(value: &TableSchema) -> Self {
698        let fields = value
699            .columns
700            .iter()
701            .map(|x| Column::new(FieldName::new(value.table_id, x.col_pos), x.col_type.clone()))
702            .collect();
703
704        Header::new(
705            value.table_id,
706            value.table_name.clone(),
707            fields,
708            value.backcompat_constraints(),
709        )
710    }
711}
712
713impl From<TableSchema> for Header {
714    fn from(schema: TableSchema) -> Self {
715        // TODO: optimize.
716        Header::from(&schema)
717    }
718}
719
720/// A struct representing the schema of a database column.
721#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
722pub struct ColumnSchema {
723    /// The ID of the table this column is attached to.
724    pub table_id: TableId,
725    /// The position of the column within the table.
726    pub col_pos: ColId,
727    /// The name of the column. Unique within the table.
728    pub col_name: Box<str>,
729    /// The type of the column. This will never contain any `AlgebraicTypeRef`s,
730    /// that is, it will be resolved.
731    pub col_type: AlgebraicType,
732}
733
734impl Schema for ColumnSchema {
735    type Def = ColumnDef;
736    type ParentId = ();
737    // This is not like the other ID types: it's a tuple of the table ID and the column position.
738    // A `ColId` alone does NOT suffice to identify a column!
739    type Id = (TableId, ColId);
740
741    fn from_module_def(
742        module_def: &ModuleDef,
743        def: &ColumnDef,
744        _parent_id: (),
745        (table_id, col_pos): (TableId, ColId),
746    ) -> Self {
747        let col_type = WithTypespace::new(module_def.typespace(), &def.ty)
748            .resolve_refs()
749            .expect("validated module should have all types resolve");
750        ColumnSchema {
751            table_id,
752            col_pos,
753            col_name: (*def.name).into(),
754            col_type,
755        }
756    }
757
758    fn check_compatible(&self, module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
759        ensure_eq!(&self.col_name[..], &def.name[..], "Column name mismatch");
760        let resolved_def_ty = WithTypespace::new(module_def.typespace(), &def.ty).resolve_refs()?;
761        ensure_eq!(self.col_type, resolved_def_ty, "Column type mismatch");
762        ensure_eq!(self.col_pos, def.col_id, "Columnh ID mismatch");
763        Ok(())
764    }
765}
766
767impl From<&ColumnSchema> for ProductTypeElement {
768    fn from(value: &ColumnSchema) -> Self {
769        Self {
770            name: Some(value.col_name.clone()),
771            algebraic_type: value.col_type.clone(),
772        }
773    }
774}
775
776impl From<ColumnSchema> for Column {
777    fn from(schema: ColumnSchema) -> Self {
778        Column {
779            field: FieldName {
780                table: schema.table_id,
781                col: schema.col_pos,
782            },
783            algebraic_type: schema.col_type,
784        }
785    }
786}
787
788/// Contextualizes a reference to a [ColumnSchema] with the name of the table the column is attached to.
789#[derive(Debug, Clone)]
790pub struct ColumnSchemaRef<'a> {
791    /// The column we are referring to.
792    pub column: &'a ColumnSchema,
793    /// The name of the table the column is attached to.
794    pub table_name: &'a str,
795}
796
797impl From<ColumnSchemaRef<'_>> for ProductTypeElement {
798    fn from(value: ColumnSchemaRef) -> Self {
799        ProductTypeElement::new(value.column.col_type.clone(), Some(value.column.col_name.clone()))
800    }
801}
802
803/// Represents a schema definition for a database sequence.
804#[derive(Debug, Clone, PartialEq, Eq)]
805pub struct SequenceSchema {
806    /// The unique identifier for the sequence within a database.
807    pub sequence_id: SequenceId,
808    /// The name of the sequence.
809    /// Deprecated. In the future, sequences will be identified by col_pos.
810    pub sequence_name: Box<str>,
811    /// The ID of the table associated with the sequence.
812    pub table_id: TableId,
813    /// The position of the column associated with this sequence.
814    pub col_pos: ColId,
815    /// The increment value for the sequence.
816    pub increment: i128,
817    /// The starting value for the sequence.
818    pub start: i128,
819    /// The minimum value for the sequence.
820    pub min_value: i128,
821    /// The maximum value for the sequence.
822    pub max_value: i128,
823    /// How many values have already been allocated for the sequence.
824    pub allocated: i128,
825}
826
827impl Schema for SequenceSchema {
828    type Def = SequenceDef;
829    type Id = SequenceId;
830    type ParentId = TableId;
831
832    fn from_module_def(module_def: &ModuleDef, def: &Self::Def, parent_id: Self::ParentId, id: Self::Id) -> Self {
833        module_def.expect_contains(def);
834
835        SequenceSchema {
836            sequence_id: id,
837            sequence_name: (*def.name).into(),
838            table_id: parent_id,
839            col_pos: def.column,
840            increment: def.increment,
841            start: def.start.unwrap_or(1),
842            min_value: def.min_value.unwrap_or(1),
843            max_value: def.max_value.unwrap_or(i128::MAX),
844            allocated: 0, // TODO: information not available in the `Def`s anymore, which is correct, but this may need to be overridden later.
845        }
846    }
847
848    fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
849        ensure_eq!(&self.sequence_name[..], &def.name[..], "Sequence name mismatch");
850        ensure_eq!(self.col_pos, def.column, "Sequence column mismatch");
851        ensure_eq!(self.increment, def.increment, "Sequence increment mismatch");
852        if let Some(start) = &def.start {
853            ensure_eq!(self.start, *start, "Sequence start mismatch");
854        }
855        if let Some(min_value) = &def.min_value {
856            ensure_eq!(self.min_value, *min_value, "Sequence min_value mismatch");
857        }
858        if let Some(max_value) = &def.max_value {
859            ensure_eq!(self.max_value, *max_value, "Sequence max_value mismatch");
860        }
861        Ok(())
862    }
863}
864
865/// Marks a table as a timer table for a scheduled reducer.
866#[derive(Debug, Clone, PartialEq, Eq)]
867pub struct ScheduleSchema {
868    /// The identifier of the table.
869    pub table_id: TableId,
870
871    /// The identifier of the schedule.
872    pub schedule_id: ScheduleId,
873
874    /// The name of the schedule.
875    pub schedule_name: Box<str>,
876
877    /// The name of the reducer to call.
878    pub reducer_name: Box<str>,
879
880    /// The column containing the `ScheduleAt` enum.
881    pub at_column: ColId,
882}
883
884impl Schema for ScheduleSchema {
885    type Def = ScheduleDef;
886
887    type Id = ScheduleId;
888
889    type ParentId = TableId;
890
891    fn from_module_def(module_def: &ModuleDef, def: &Self::Def, parent_id: Self::ParentId, id: Self::Id) -> Self {
892        module_def.expect_contains(def);
893
894        ScheduleSchema {
895            table_id: parent_id,
896            schedule_id: id,
897            schedule_name: (*def.name).into(),
898            reducer_name: (*def.reducer_name).into(),
899            at_column: def.at_column,
900            // Ignore def.at_column and id_column. Those are recovered at runtime.
901        }
902    }
903
904    fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
905        ensure_eq!(&self.schedule_name[..], &def.name[..], "Schedule name mismatch");
906        ensure_eq!(
907            &self.reducer_name[..],
908            &def.reducer_name[..],
909            "Schedule reducer name mismatch"
910        );
911        Ok(())
912    }
913}
914
915/// A struct representing the schema of a database index.
916#[derive(Debug, Clone, PartialEq, Eq)]
917pub struct IndexSchema {
918    /// The unique ID of the index within the schema.
919    pub index_id: IndexId,
920    /// The ID of the table associated with the index.
921    pub table_id: TableId,
922    /// The name of the index. This should not be assumed to follow any particular format.
923    /// Unique within the database.
924    pub index_name: Box<str>,
925    /// The data for the schema.
926    pub index_algorithm: IndexAlgorithm,
927}
928
929impl IndexSchema {}
930
931impl Schema for IndexSchema {
932    type Def = IndexDef;
933    type Id = IndexId;
934    type ParentId = TableId;
935
936    fn from_module_def(module_def: &ModuleDef, def: &Self::Def, parent_id: Self::ParentId, id: Self::Id) -> Self {
937        module_def.expect_contains(def);
938
939        let index_algorithm = def.algorithm.clone();
940        IndexSchema {
941            index_id: id,
942            table_id: parent_id,
943            index_name: (*def.name).into(),
944            index_algorithm,
945        }
946    }
947
948    fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
949        ensure_eq!(&self.index_name[..], &def.name[..], "Index name mismatch");
950        ensure_eq!(&self.index_algorithm, &def.algorithm, "Index algorithm mismatch");
951        Ok(())
952    }
953}
954
955/// A struct representing the schema of a database constraint.
956///
957/// This struct holds information about a database constraint, including its unique identifier,
958/// name, the table it belongs to, and the columns it is associated with.
959#[derive(Debug, Clone, PartialEq, Eq)]
960pub struct ConstraintSchema {
961    /// The ID of the table the constraint applies to.
962    pub table_id: TableId,
963    /// The unique ID of the constraint within the database.
964    pub constraint_id: ConstraintId,
965    /// The name of the constraint.
966    pub constraint_name: Box<str>,
967    /// The data for the constraint.
968    pub data: ConstraintData, // this reuses the type from Def, which is fine, neither of `schema` nor `def` are ABI modules.
969}
970
971impl ConstraintSchema {
972    /// Constructs a `ConstraintSchema` from a given `ConstraintDef` and table identifier.
973    ///
974    /// # Parameters
975    ///
976    /// * `table_id`: Identifier of the table to which the constraint belongs.
977    /// * `constraint`: The `ConstraintDef` containing constraint information.
978    #[deprecated(note = "Use TableSchema::from_module_def instead")]
979    pub fn from_def(table_id: TableId, constraint: RawConstraintDefV8) -> Option<Self> {
980        if constraint.constraints.has_unique() {
981            Some(ConstraintSchema {
982                constraint_id: ConstraintId::SENTINEL, // Set to 0 as it may be assigned later.
983                constraint_name: constraint.constraint_name.trim().into(),
984                table_id,
985                data: ConstraintData::Unique(UniqueConstraintData {
986                    columns: constraint.columns.into(),
987                }),
988            })
989        } else {
990            None
991        }
992    }
993}
994
995impl Schema for ConstraintSchema {
996    type Def = ConstraintDef;
997    type Id = ConstraintId;
998    type ParentId = TableId;
999
1000    fn from_module_def(module_def: &ModuleDef, def: &Self::Def, parent_id: Self::ParentId, id: Self::Id) -> Self {
1001        module_def.expect_contains(def);
1002
1003        ConstraintSchema {
1004            constraint_id: id,
1005            constraint_name: (*def.name).into(),
1006            table_id: parent_id,
1007            data: def.data.clone(),
1008        }
1009    }
1010
1011    fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
1012        ensure_eq!(&self.constraint_name[..], &def.name[..], "Constraint name mismatch");
1013        ensure_eq!(&self.data, &def.data, "Constraint data mismatch");
1014        Ok(())
1015    }
1016}
1017
1018/// A struct representing the schema of a row-level security policy.
1019#[derive(Debug, Clone, PartialEq, Eq)]
1020pub struct RowLevelSecuritySchema {
1021    pub table_id: TableId,
1022    pub sql: RawSql,
1023}