1use crate::def::error::{DefType, SchemaError};
10use crate::relation::{combine_constraints, Column, DbTable, FieldName, Header};
11use crate::table_name::TableName;
12use core::mem;
13use itertools::Itertools;
14use spacetimedb_lib::db::auth::{StAccess, StTableType};
15use spacetimedb_lib::db::raw_def::v9::RawSql;
16use spacetimedb_lib::db::raw_def::{generate_cols_name, RawConstraintDefV8};
17use spacetimedb_primitives::*;
18use spacetimedb_sats::product_value::InvalidFieldError;
19use spacetimedb_sats::raw_identifier::RawIdentifier;
20use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, WithTypespace};
21use std::collections::BTreeMap;
22use std::sync::Arc;
23
24use crate::def::{
25 ColumnDef, ConstraintData, ConstraintDef, IndexAlgorithm, IndexDef, ModuleDef, ModuleDefLookup,
26 RawModuleDefVersion, ScheduleDef, SequenceDef, TableDef, UniqueConstraintData, ViewColumnDef, ViewDef,
27};
28use crate::identifier::Identifier;
29
30pub trait Schema: Sized {
32 type Def: ModuleDefLookup;
34 type Id;
36 type ParentId;
39
40 fn from_module_def(module_def: &ModuleDef, def: &Self::Def, parent_id: Self::ParentId, id: Self::Id) -> Self;
50
51 fn check_compatible(&self, module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error>;
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct ViewDefInfo {
57 pub view_id: ViewId,
58 pub has_args: bool,
59 pub is_anonymous: bool,
60}
61
62impl ViewDefInfo {
63 pub fn num_private_cols(&self) -> usize {
64 (if self.is_anonymous { 0 } else { 1 }) + (if self.has_args { 1 } else { 0 })
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct TableOrViewSchema {
71 pub table_id: TableId,
72 pub view_info: Option<ViewDefInfo>,
73 pub table_name: TableName,
74 pub table_access: StAccess,
75 inner: Arc<TableSchema>,
76}
77
78impl From<Arc<TableSchema>> for TableOrViewSchema {
79 fn from(inner: Arc<TableSchema>) -> Self {
80 Self {
81 table_id: inner.table_id,
82 view_info: inner.view_info,
83 table_name: inner.table_name.clone(),
84 table_access: inner.table_access,
85 inner,
86 }
87 }
88}
89
90impl TableOrViewSchema {
91 pub fn is_view(&self) -> bool {
93 self.view_info.is_some()
94 }
95
96 pub fn is_anonymous_view(&self) -> bool {
98 self.view_info.as_ref().is_some_and(|view_info| view_info.is_anonymous)
99 }
100
101 pub fn inner(&self) -> Arc<TableSchema> {
104 self.inner.clone()
105 }
106
107 pub fn public_columns(&self) -> &[ColumnSchema] {
115 match self.view_info {
116 Some(ViewDefInfo {
117 has_args: true,
118 is_anonymous: false,
119 ..
120 }) => &self.inner.columns[2..],
121 Some(ViewDefInfo {
122 has_args: true,
123 is_anonymous: true,
124 ..
125 }) => &self.inner.columns[1..],
126 Some(ViewDefInfo {
127 has_args: false,
128 is_anonymous: false,
129 ..
130 }) => &self.inner.columns[1..],
131 Some(ViewDefInfo {
132 has_args: false,
133 is_anonymous: true,
134 ..
135 })
136 | None => &self.inner.columns,
137 }
138 }
139
140 pub fn get_column_by_name(&self, col_name: &str) -> Option<&ColumnSchema> {
142 self.public_columns().iter().find(|x| &*x.col_name == col_name)
143 }
144
145 pub fn get_column_by_name_or_alias(&self, col_name: &str) -> Option<&ColumnSchema> {
147 self.public_columns()
148 .iter()
149 .find(|col| col.alias.as_deref().is_some_and(|alias| alias == col_name))
150 .or_else(|| self.get_column_by_name(col_name))
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct TableSchema {
160 pub table_id: TableId,
162
163 pub table_name: TableName,
165
166 pub alias: Option<Identifier>,
167
168 pub view_info: Option<ViewDefInfo>,
170
171 pub columns: Vec<ColumnSchema>,
174
175 pub primary_key: Option<ColId>,
182
183 pub indexes: Vec<IndexSchema>,
185
186 pub constraints: Vec<ConstraintSchema>,
188
189 pub sequences: Vec<SequenceSchema>,
191
192 pub table_type: StTableType,
194
195 pub table_access: StAccess,
197
198 pub schedule: Option<ScheduleSchema>,
200
201 pub is_event: bool,
203
204 pub row_type: ProductType,
206}
207
208pub fn columns_to_row_type(columns: &[ColumnSchema]) -> ProductType {
210 ProductType::new(columns.iter().map(ProductTypeElement::from).collect())
211}
212
213impl TableSchema {
214 #[allow(clippy::too_many_arguments)]
216 pub fn new(
217 table_id: TableId,
218 table_name: TableName,
219 view_info: Option<ViewDefInfo>,
220 columns: Vec<ColumnSchema>,
221 indexes: Vec<IndexSchema>,
222 constraints: Vec<ConstraintSchema>,
223 sequences: Vec<SequenceSchema>,
224 table_type: StTableType,
225 table_access: StAccess,
226 schedule: Option<ScheduleSchema>,
227 primary_key: Option<ColId>,
228 is_event: bool,
229 alias: Option<Identifier>,
230 ) -> Self {
231 Self {
232 row_type: columns_to_row_type(&columns),
233 table_id,
234 table_name,
235 view_info,
236 columns,
237 indexes,
238 constraints,
239 sequences,
240 table_type,
241 table_access,
242 schedule,
243 primary_key,
244 is_event,
245 alias,
246 }
247 }
248
249 #[cfg(any(test, feature = "test"))]
252 pub fn from_product_type(ty: ProductType) -> TableSchema {
253 let columns = ty
254 .elements
255 .iter()
256 .enumerate()
257 .map(|(col_pos, element)| ColumnSchema {
258 table_id: TableId::SENTINEL,
259 col_pos: ColId(col_pos as _),
260 col_name: element
261 .name
262 .clone()
263 .map(Identifier::new_assume_valid)
264 .unwrap_or_else(|| Identifier::for_test(format!("col{col_pos}"))),
265 col_type: element.algebraic_type.clone(),
266 alias: None,
267 })
268 .collect();
269
270 TableSchema::new(
271 TableId::SENTINEL,
272 TableName::for_test("TestTable"),
273 None,
274 columns,
275 vec![],
276 vec![],
277 vec![],
278 StTableType::User,
279 StAccess::Public,
280 None,
281 None,
282 false,
283 None,
284 )
285 }
286
287 pub fn is_view(&self) -> bool {
289 self.view_info.is_some()
290 }
291
292 pub fn is_anonymous_view(&self) -> bool {
294 self.view_info.as_ref().is_some_and(|view_info| view_info.is_anonymous)
295 }
296
297 pub fn num_private_cols(&self) -> usize {
300 self.view_info
301 .as_ref()
302 .map(|view_info| view_info.num_private_cols())
303 .unwrap_or_default()
304 }
305
306 pub fn update_table_id(&mut self, id: TableId) {
309 self.table_id = id;
310 self.columns.iter_mut().for_each(|c| c.table_id = id);
311 self.indexes.iter_mut().for_each(|i| i.table_id = id);
312 self.constraints.iter_mut().for_each(|c| c.table_id = id);
313 self.sequences.iter_mut().for_each(|s| s.table_id = id);
314 if let Some(s) = self.schedule.as_mut() {
315 s.table_id = id;
316 }
317 }
318
319 pub fn reset(&mut self) {
322 self.update_table_id(TableId::SENTINEL);
323 self.indexes.iter_mut().for_each(|i| i.index_id = IndexId::SENTINEL);
324 self.sequences
325 .iter_mut()
326 .for_each(|i| i.sequence_id = SequenceId::SENTINEL);
327 self.constraints
328 .iter_mut()
329 .for_each(|i| i.constraint_id = ConstraintId::SENTINEL);
330 self.row_type = columns_to_row_type(&self.columns);
331 }
332
333 pub fn into_columns(self) -> Vec<ColumnSchema> {
335 self.columns
336 }
337
338 pub fn columns(&self) -> &[ColumnSchema] {
341 &self.columns
342 }
343
344 pub fn num_cols(&self) -> usize {
346 self.columns.len()
347 }
348
349 pub fn take_adjacent_schemas(&mut self) -> (Vec<IndexSchema>, Vec<SequenceSchema>, Vec<ConstraintSchema>) {
351 (
352 mem::take(&mut self.indexes),
353 mem::take(&mut self.sequences),
354 mem::take(&mut self.constraints),
355 )
356 }
357
358 pub fn update_sequence(&mut self, of: SequenceSchema) {
362 if let Some(x) = self.sequences.iter_mut().find(|x| x.sequence_id == of.sequence_id) {
363 *x = of;
364 } else {
365 self.sequences.push(of);
366 }
367 }
368
369 pub fn remove_sequence(&mut self, sequence_id: SequenceId) -> Option<SequenceSchema> {
371 find_remove(&mut self.sequences, |x| x.sequence_id == sequence_id)
372 }
373
374 pub fn update_index(&mut self, of: IndexSchema) {
376 if let Some(x) = self.indexes.iter_mut().find(|x| x.index_id == of.index_id) {
377 *x = of;
378 } else {
379 self.indexes.push(of);
380 }
381 }
382
383 pub fn remove_index(&mut self, index_id: IndexId) -> Option<IndexSchema> {
385 find_remove(&mut self.indexes, |x| x.index_id == index_id)
386 }
387
388 pub fn update_constraint(&mut self, of: ConstraintSchema) {
390 if let Some(x) = self
391 .constraints
392 .iter_mut()
393 .find(|x| x.constraint_id == of.constraint_id)
394 {
395 *x = of;
396 } else {
397 self.constraints.push(of);
398 }
399 }
400
401 pub fn remove_constraint(&mut self, constraint_id: ConstraintId) -> Option<ConstraintSchema> {
403 find_remove(&mut self.constraints, |x| x.constraint_id == constraint_id)
404 }
405
406 pub fn generate_cols_name(&self, columns: &ColList) -> String {
412 generate_cols_name(columns, |p| self.get_column(p.idx()).map(|c| &*c.col_name))
413 }
414
415 pub fn get_column_by_field(&self, field: FieldName) -> Option<&ColumnSchema> {
421 self.get_column(field.col.idx())
422 }
423
424 pub fn get_columns<'a>(
427 &'a self,
428 columns: &'a ColList,
429 ) -> impl 'a + Iterator<Item = (ColId, Option<&'a ColumnSchema>)> {
430 columns.iter().map(|col| (col, self.columns.get(col.idx())))
431 }
432
433 pub fn get_column(&self, pos: usize) -> Option<&ColumnSchema> {
435 self.columns.get(pos)
436 }
437
438 pub fn get_column_by_name(&self, col_name: &str) -> Option<&ColumnSchema> {
440 self.columns.iter().find(|x| &*x.col_name == col_name)
441 }
442
443 pub fn get_column_by_name_or_alias(&self, col_name: &str) -> Option<&ColumnSchema> {
445 self.columns
446 .iter()
447 .find(|col| col.alias.as_deref().is_some_and(|alias| alias == col_name))
448 .or_else(|| self.get_column_by_name(col_name))
449 }
450
451 pub fn get_column_id_by_name(&self, col_name: &str) -> Option<ColId> {
455 self.columns
456 .iter()
457 .position(|x| &*x.col_name == col_name)
458 .map(|x| x.into())
459 }
460
461 pub fn get_column_id_by_name_or_alias(&self, col_name: &str) -> Option<ColId> {
465 self.columns
466 .iter()
467 .position(|col| col.alias.as_deref().is_some_and(|alias| alias == col_name))
468 .or_else(|| self.get_column_id_by_name(col_name).map(|id| id.idx()))
469 .map(Into::into)
470 }
471
472 pub fn matches_name_or_alias(&self, name: &str) -> bool {
474 self.alias.as_deref().is_some_and(|alias| alias == name) || self.table_name.as_ref() == name
475 }
476
477 pub fn col_list_for_index_id(&self, index_id: IndexId) -> ColList {
479 self.indexes
480 .iter()
481 .find(|schema| schema.index_id == index_id)
482 .map(|schema| schema.index_algorithm.columns())
483 .map(|cols| ColList::from_iter(cols.iter()))
484 .unwrap_or_else(ColList::empty)
485 }
486
487 pub fn is_unique(&self, cols: &impl PartialEq<ColList>) -> bool {
489 self.constraints
490 .iter()
491 .filter_map(|cs| cs.data.unique_columns())
492 .any(|unique_cols| *cols == **unique_cols)
493 }
494
495 pub fn project(&self, indexes: impl Iterator<Item = ColId>) -> Result<Vec<&ColumnSchema>, InvalidFieldError> {
497 indexes
498 .map(|index| self.get_column(index.0 as usize).ok_or_else(|| index.into()))
499 .collect()
500 }
501
502 pub fn project_not_empty(&self, indexes: ColList) -> Result<Vec<&ColumnSchema>, InvalidFieldError> {
505 self.project(indexes.iter())
506 }
507
508 pub fn get_row_type(&self) -> &ProductType {
510 &self.row_type
511 }
512
513 pub fn into_row_type(self) -> ProductType {
515 self.row_type
516 }
517
518 fn backcompat_constraints_iter(&self) -> impl Iterator<Item = (ColList, Constraints)> + '_ {
520 self.constraints
521 .iter()
522 .map(|x| -> (ColList, Constraints) {
523 match &x.data {
524 ConstraintData::Unique(unique) => (unique.columns.clone().into(), Constraints::unique()),
525 }
526 })
527 .chain(self.indexes.iter().map(|x| match &x.index_algorithm {
528 IndexAlgorithm::BTree(btree) => (btree.columns.clone(), Constraints::indexed()),
529 IndexAlgorithm::Hash(hash) => (hash.columns.clone(), Constraints::indexed()),
530 IndexAlgorithm::Direct(direct) => (direct.column.into(), Constraints::indexed()),
531 }))
532 .chain(
533 self.sequences
534 .iter()
535 .map(|x| (col_list![x.col_pos], Constraints::auto_inc())),
536 )
537 .chain(
538 self.primary_key
539 .iter()
540 .map(|x| (col_list![*x], Constraints::primary_key())),
541 )
542 }
543
544 pub fn backcompat_constraints(&self) -> BTreeMap<ColList, Constraints> {
548 combine_constraints(self.backcompat_constraints_iter())
549 }
550
551 pub fn backcompat_column_constraints(&self) -> BTreeMap<ColList, Constraints> {
556 let mut result = self.backcompat_constraints();
557 for col in &self.columns {
558 result.entry(col_list![col.col_pos]).or_insert(Constraints::unset());
559 }
560 result
561 }
562
563 pub fn pk(&self) -> Option<&ColumnSchema> {
565 self.primary_key.and_then(|pk| self.get_column(pk.0 as usize))
566 }
567
568 pub fn validated(self) -> Result<Self, Vec<SchemaError>> {
577 let mut errors = Vec::new();
578
579 let columns_not_found = self
580 .sequences
581 .iter()
582 .map(|x| (DefType::Sequence, x.sequence_name.clone(), ColList::new(x.col_pos)))
583 .chain(self.indexes.iter().map(|x| {
584 let cols = x.index_algorithm.columns().to_owned();
585 (DefType::Index, x.index_name.clone(), cols)
586 }))
587 .chain(self.constraints.iter().map(|x| {
588 (
589 DefType::Constraint,
590 x.constraint_name.clone(),
591 match &x.data {
592 ConstraintData::Unique(unique) => unique.columns.clone().into(),
593 },
594 )
595 }))
596 .filter_map(|(ty, name, cols)| {
597 let mut not_found_iter = self
598 .get_columns(&cols)
599 .filter(|(_, x)| x.is_none())
600 .map(|(col, _)| col)
601 .peekable();
602
603 if not_found_iter.peek().is_none() {
604 None
605 } else {
606 Some(SchemaError::ColumnsNotFound {
607 name,
608 table: self.table_name.clone(),
609 columns: not_found_iter.collect(),
610 ty,
611 })
612 }
613 });
614
615 errors.extend(columns_not_found);
616
617 errors.extend(self.columns.iter().filter_map(|x| {
618 if x.col_name.is_empty() {
619 Some(SchemaError::EmptyName {
620 table: self.table_name.clone(),
621 ty: DefType::Column,
622 id: x.col_pos.0 as _,
623 })
624 } else {
625 None
626 }
627 }));
628
629 errors.extend(self.indexes.iter().filter_map(|x| {
630 if x.index_name.is_empty() {
631 Some(SchemaError::EmptyName {
632 table: self.table_name.clone(),
633 ty: DefType::Index,
634 id: x.index_id.0,
635 })
636 } else {
637 None
638 }
639 }));
640 errors.extend(self.constraints.iter().filter_map(|x| {
641 if x.constraint_name.is_empty() {
642 Some(SchemaError::EmptyName {
643 table: self.table_name.clone(),
644 ty: DefType::Constraint,
645 id: x.constraint_id.0,
646 })
647 } else {
648 None
649 }
650 }));
651
652 errors.extend(self.sequences.iter().filter_map(|x| {
653 if x.sequence_name.is_empty() {
654 Some(SchemaError::EmptyName {
655 table: self.table_name.clone(),
656 ty: DefType::Sequence,
657 id: x.sequence_id.0,
658 })
659 } else {
660 None
661 }
662 }));
663
664 if let Some(err) = self
666 .sequences
667 .iter()
668 .group_by(|&seq| seq.col_pos)
669 .into_iter()
670 .find_map(|(key, group)| {
671 let count = group.count();
672 if count > 1 {
673 Some(SchemaError::OneAutoInc {
674 table: self.table_name.clone(),
675 field: self.columns[key.idx()].col_name.clone(),
676 })
677 } else {
678 None
679 }
680 })
681 {
682 errors.push(err);
683 }
684
685 if errors.is_empty() {
686 Ok(self)
687 } else {
688 Err(errors)
689 }
690 }
691
692 pub fn janky_fix_column_defs(&mut self, module_def: &ModuleDef) {
696 let table_name = self.table_name.clone().into();
697 for col in &mut self.columns {
698 let def: &ColumnDef = module_def.lookup((&table_name, &col.col_name)).unwrap();
699 col.col_type = def.ty.clone();
700 }
701 let table_def: &TableDef = module_def.expect_lookup(&table_name);
702 self.row_type = module_def.typespace()[table_def.product_type_ref]
703 .as_product()
704 .unwrap()
705 .clone();
706 }
707
708 pub fn normalize(&mut self) {
712 self.indexes.sort_by(|a, b| a.index_name.cmp(&b.index_name));
713 self.constraints
714 .sort_by(|a, b| a.constraint_name.cmp(&b.constraint_name));
715 self.sequences.sort_by(|a, b| a.sequence_name.cmp(&b.sequence_name));
716 }
717}
718
719fn find_remove<T>(vec: &mut Vec<T>, predicate: impl Fn(&T) -> bool) -> Option<T> {
721 let pos = vec.iter().position(predicate)?;
722 Some(vec.remove(pos))
723}
724
725macro_rules! ensure_eq {
727 ($a:expr, $b:expr, $msg:expr) => {
728 if $a != $b {
729 anyhow::bail!(
730 "{0}: expected {1} == {2}:\n {1}: {3:?}\n {2}: {4:?}",
731 $msg,
732 stringify!($a),
733 stringify!($b),
734 $a,
735 $b
736 );
737 }
738 };
739}
740
741pub fn column_schemas_from_defs(module_def: &ModuleDef, columns: &[ColumnDef], table_id: TableId) -> Vec<ColumnSchema> {
743 columns
744 .iter()
745 .enumerate()
746 .map(|(col_pos, def)| ColumnSchema::from_module_def(module_def, def, (), (table_id, col_pos.into())))
747 .collect()
748}
749
750impl TableSchema {
751 pub fn from_view_def_for_codegen(module_def: &ModuleDef, view_def: &ViewDef) -> Self {
757 module_def.expect_contains(view_def);
758
759 let ViewDef {
760 name,
761 is_public,
762 is_anonymous,
763 primary_key,
764 param_columns,
765 return_columns,
766 ..
767 } = view_def;
768
769 let columns = return_columns
770 .iter()
771 .map(|def| ColumnSchema::from_view_column_def(module_def, def))
772 .enumerate()
773 .map(|(i, schema)| (ColId::from(i), schema))
774 .map(|(col_pos, schema)| ColumnSchema { col_pos, ..schema })
775 .collect();
776 let view_primary_key = (module_def.raw_module_def_version() == RawModuleDefVersion::V10)
777 .then_some(*primary_key)
778 .flatten();
779
780 let table_access = if *is_public {
781 StAccess::Public
782 } else {
783 StAccess::Private
784 };
785
786 let view_info = ViewDefInfo {
787 view_id: ViewId::SENTINEL,
788 has_args: !param_columns.is_empty(),
789 is_anonymous: *is_anonymous,
790 };
791
792 TableSchema::new(
793 TableId::SENTINEL,
794 TableName::new(name.clone()),
795 Some(view_info),
796 columns,
797 vec![],
798 vec![],
799 vec![],
800 StTableType::User,
801 table_access,
802 None,
803 view_primary_key,
804 false,
805 None,
806 )
807 }
808
809 pub fn from_view_def_for_datastore(module_def: &ModuleDef, view_def: &ViewDef) -> Self {
843 module_def.expect_contains(view_def);
844
845 let ViewDef {
846 name,
847 is_public,
848 is_anonymous,
849 primary_key,
850 param_columns,
851 return_columns,
852 accessor_name,
853 ..
854 } = view_def;
855
856 let n = return_columns.len() + 2;
857 let mut columns = Vec::with_capacity(n);
858 let mut meta_cols = 0;
859
860 let mut push_column = |name: &'static str, col_type| {
861 meta_cols += 1;
862 columns.push(ColumnSchema {
863 table_id: TableId::SENTINEL,
864 col_pos: columns.len().into(),
865 col_name: Identifier::new_assume_valid(name.into()),
866 col_type,
867 alias: None,
868 });
869 };
870
871 if !is_anonymous {
872 push_column("sender", AlgebraicType::identity());
873 }
874
875 if !param_columns.is_empty() {
876 push_column("arg_id", AlgebraicType::U64);
877 }
878
879 columns.extend(
880 return_columns
881 .iter()
882 .map(|def| ColumnSchema::from_view_column_def(module_def, def))
883 .enumerate()
884 .map(|(i, schema)| (ColId::from(meta_cols + i), schema))
885 .map(|(col_pos, schema)| ColumnSchema { col_pos, ..schema }),
886 );
887
888 let make_index_name = |col_list: &ColList| {
889 let cols_name = generate_cols_name(col_list, |col| columns.get(col.idx()).map(|col| &*col.col_name));
890 RawIdentifier::new(format!("{name}_{cols_name}_idx_btree"))
891 };
892
893 let make_constraint_name = |col_list: &ColList| {
894 let cols_name = generate_cols_name(col_list, |col| columns.get(col.idx()).map(|col| &*col.col_name));
895 RawIdentifier::new(format!("{name}_{cols_name}_key"))
896 };
897
898 let mut indexes = match meta_cols {
899 1 => vec![IndexSchema {
900 index_id: IndexId::SENTINEL,
901 table_id: TableId::SENTINEL,
902 index_name: make_index_name(&col_list![0]),
903 index_algorithm: IndexAlgorithm::BTree(col_list![0].into()),
904 alias: None,
905 }],
906 2 => vec![IndexSchema {
907 index_id: IndexId::SENTINEL,
908 table_id: TableId::SENTINEL,
909 index_name: make_index_name(&col_list![0, 1]),
910 index_algorithm: IndexAlgorithm::BTree(col_list![0, 1].into()),
911 alias: None,
912 }],
913 _ => vec![],
914 };
915
916 let mut constraints = vec![];
917 let view_primary_key = (module_def.raw_module_def_version() == RawModuleDefVersion::V10)
918 .then_some(primary_key.map(|pk| ColId::from(meta_cols + pk.idx())))
919 .flatten();
920
921 if *is_anonymous {
922 if let Some(pk_col) = view_primary_key {
923 let cols = col_list![pk_col];
924 constraints.push(ConstraintSchema {
925 table_id: TableId::SENTINEL,
926 constraint_id: ConstraintId::SENTINEL,
927 constraint_name: make_constraint_name(&cols),
928 data: ConstraintData::Unique(UniqueConstraintData {
929 columns: ColSet::from(cols.clone()),
930 }),
931 });
932 indexes.push(IndexSchema {
933 index_id: IndexId::SENTINEL,
934 table_id: TableId::SENTINEL,
935 index_name: make_index_name(&cols),
936 index_algorithm: IndexAlgorithm::BTree(cols.into()),
937 alias: None,
938 });
939 }
940 } else if let Some(pk_col) = view_primary_key {
941 let cols = col_list![ColId(0), pk_col];
942 indexes.push(IndexSchema {
943 index_id: IndexId::SENTINEL,
944 table_id: TableId::SENTINEL,
945 index_name: make_index_name(&cols),
946 index_algorithm: IndexAlgorithm::BTree(cols.into()),
947 alias: None,
948 });
949 }
950
951 let table_access = if *is_public {
952 StAccess::Public
953 } else {
954 StAccess::Private
955 };
956
957 let view_info = ViewDefInfo {
958 view_id: ViewId::SENTINEL,
959 has_args: !param_columns.is_empty(),
960 is_anonymous: *is_anonymous,
961 };
962
963 TableSchema::new(
964 TableId::SENTINEL,
965 TableName::new(name.clone()),
966 Some(view_info),
967 columns,
968 indexes,
969 constraints,
970 vec![],
971 StTableType::User,
972 table_access,
973 None,
974 if *is_anonymous { view_primary_key } else { None },
975 false,
976 Some(accessor_name.clone()),
977 )
978 }
979}
980
981impl Schema for TableSchema {
982 type Def = TableDef;
983 type Id = TableId;
984 type ParentId = ();
985
986 fn from_module_def(
988 module_def: &ModuleDef,
989 def: &Self::Def,
990 _parent_id: Self::ParentId,
991 table_id: Self::Id,
992 ) -> Self {
993 module_def.expect_contains(def);
994
995 let TableDef {
996 name,
997 product_type_ref: _,
998 primary_key,
999 columns,
1000 indexes,
1001 constraints,
1002 sequences,
1003 schedule,
1004 table_type,
1005 table_access,
1006 is_event,
1007 accessor_name,
1008 ..
1009 } = def;
1010
1011 let columns = column_schemas_from_defs(module_def, columns, table_id);
1012
1013 let indexes = indexes
1016 .values()
1017 .map(|def| IndexSchema::from_module_def(module_def, def, table_id, IndexId::SENTINEL))
1018 .collect();
1019
1020 let sequences = sequences
1021 .values()
1022 .map(|def| SequenceSchema::from_module_def(module_def, def, table_id, SequenceId::SENTINEL))
1023 .collect();
1024
1025 let constraints = constraints
1026 .values()
1027 .map(|def| ConstraintSchema::from_module_def(module_def, def, table_id, ConstraintId::SENTINEL))
1028 .collect();
1029
1030 let schedule = schedule
1031 .as_ref()
1032 .map(|schedule| ScheduleSchema::from_module_def(module_def, schedule, table_id, ScheduleId::SENTINEL));
1033
1034 TableSchema::new(
1035 table_id,
1036 TableName::new(name.clone()),
1037 None,
1038 columns,
1039 indexes,
1040 constraints,
1041 sequences,
1042 (*table_type).into(),
1043 (*table_access).into(),
1044 schedule,
1045 *primary_key,
1046 *is_event,
1047 Some(accessor_name.clone()),
1048 )
1049 }
1050
1051 fn check_compatible(&self, module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
1052 ensure_eq!(&self.table_name[..], &def.name[..], "Table name mismatch");
1053 ensure_eq!(self.primary_key, def.primary_key, "Primary key mismatch");
1054 let def_table_access: StAccess = (def.table_access).into();
1055 ensure_eq!(self.table_access, def_table_access, "Table access mismatch");
1056 let def_table_type: StTableType = (def.table_type).into();
1057 ensure_eq!(self.table_type, def_table_type, "Table type mismatch");
1058
1059 for col in &self.columns {
1060 let col_def = def
1061 .columns
1062 .get(col.col_pos.0 as usize)
1063 .ok_or_else(|| anyhow::anyhow!("Column {} not found in definition", col.col_pos.0))?;
1064 col.check_compatible(module_def, col_def)?;
1065 }
1066 ensure_eq!(self.columns.len(), def.columns.len(), "Column count mismatch");
1067
1068 for index in &self.indexes {
1069 let index_def = def
1070 .indexes
1071 .get(&index.index_name)
1072 .ok_or_else(|| anyhow::anyhow!("Index {} not found in definition", index.index_id.0))?;
1073 index.check_compatible(module_def, index_def)?;
1074 }
1075 ensure_eq!(self.indexes.len(), def.indexes.len(), "Index count mismatch");
1076
1077 for constraint in &self.constraints {
1078 let constraint_def = def
1079 .constraints
1080 .get(&constraint.constraint_name)
1081 .ok_or_else(|| anyhow::anyhow!("Constraint {} not found in definition", constraint.constraint_id.0))?;
1082 constraint.check_compatible(module_def, constraint_def)?;
1083 }
1084 ensure_eq!(
1085 self.constraints.len(),
1086 def.constraints.len(),
1087 "Constraint count mismatch"
1088 );
1089
1090 for sequence in &self.sequences {
1091 let sequence_def = def
1092 .sequences
1093 .get(&sequence.sequence_name)
1094 .ok_or_else(|| anyhow::anyhow!("Sequence {} not found in definition", sequence.sequence_id.0))?;
1095 sequence.check_compatible(module_def, sequence_def)?;
1096 }
1097 ensure_eq!(self.sequences.len(), def.sequences.len(), "Sequence count mismatch");
1098
1099 if let Some(schedule) = &self.schedule {
1100 let schedule_def = def
1101 .schedule
1102 .as_ref()
1103 .ok_or_else(|| anyhow::anyhow!("Schedule not found in definition"))?;
1104 schedule.check_compatible(module_def, schedule_def)?;
1105 }
1106 ensure_eq!(
1107 self.schedule.is_some(),
1108 def.schedule.is_some(),
1109 "Schedule presence mismatch"
1110 );
1111 Ok(())
1112 }
1113}
1114
1115impl From<&TableSchema> for ProductType {
1116 fn from(value: &TableSchema) -> Self {
1117 value.row_type.clone()
1118 }
1119}
1120
1121impl From<&TableSchema> for DbTable {
1122 fn from(value: &TableSchema) -> Self {
1123 DbTable::new(
1124 Arc::new(value.into()),
1125 value.table_id,
1126 value.table_type,
1127 value.table_access,
1128 )
1129 }
1130}
1131
1132impl From<&TableSchema> for Header {
1133 fn from(value: &TableSchema) -> Self {
1134 let fields = value
1135 .columns
1136 .iter()
1137 .map(|x| Column::new(FieldName::new(value.table_id, x.col_pos), x.col_type.clone()))
1138 .collect();
1139
1140 Header::new(
1141 value.table_id,
1142 value.table_name.clone(),
1143 fields,
1144 value.backcompat_constraints(),
1145 )
1146 }
1147}
1148
1149impl From<TableSchema> for Header {
1150 fn from(schema: TableSchema) -> Self {
1151 Header::from(&schema)
1153 }
1154}
1155
1156#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
1158pub struct ColumnSchema {
1159 pub table_id: TableId,
1161 pub col_pos: ColId,
1163 pub col_name: Identifier,
1165
1166 pub alias: Option<Identifier>,
1167 pub col_type: AlgebraicType,
1170}
1171
1172impl spacetimedb_memory_usage::MemoryUsage for ColumnSchema {
1173 fn heap_usage(&self) -> usize {
1174 let Self {
1175 table_id,
1176 col_pos,
1177 col_name,
1178 col_type,
1179 ..
1180 } = self;
1181 table_id.heap_usage() + col_pos.heap_usage() + col_name.heap_usage() + col_type.heap_usage()
1182 }
1183}
1184
1185impl ColumnSchema {
1186 #[cfg(any(test, feature = "test"))]
1187 pub fn for_test(pos: impl Into<ColId>, name: impl AsRef<str>, ty: AlgebraicType) -> Self {
1188 Self {
1189 table_id: TableId::SENTINEL,
1190 col_pos: pos.into(),
1191 col_name: Identifier::for_test(name),
1192 col_type: ty,
1193 alias: None,
1194 }
1195 }
1196
1197 fn from_view_column_def(module_def: &ModuleDef, def: &ViewColumnDef) -> Self {
1198 let col_type = WithTypespace::new(module_def.typespace(), &def.ty)
1199 .resolve_refs()
1200 .expect("validated module should have all types resolve");
1201 ColumnSchema {
1202 table_id: TableId::SENTINEL,
1203 col_pos: def.col_id,
1204 col_name: def.name.clone(),
1205 col_type,
1206 alias: Some(def.accessor_name.clone()),
1207 }
1208 }
1209}
1210
1211impl Schema for ColumnSchema {
1212 type Def = ColumnDef;
1213 type ParentId = ();
1214 type Id = (TableId, ColId);
1217
1218 fn from_module_def(
1219 module_def: &ModuleDef,
1220 def: &ColumnDef,
1221 _parent_id: (),
1222 (table_id, col_pos): (TableId, ColId),
1223 ) -> Self {
1224 let col_type = WithTypespace::new(module_def.typespace(), &def.ty)
1225 .resolve_refs()
1226 .expect("validated module should have all types resolve");
1227 ColumnSchema {
1228 table_id,
1229 col_pos,
1230 col_name: def.name.clone(),
1231 col_type,
1232 alias: Some(def.accessor_name.clone()),
1233 }
1234 }
1235
1236 fn check_compatible(&self, module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
1237 ensure_eq!(&self.col_name[..], &def.name[..], "Column name mismatch");
1238 let resolved_def_ty = WithTypespace::new(module_def.typespace(), &def.ty).resolve_refs()?;
1239 ensure_eq!(self.col_type, resolved_def_ty, "Column type mismatch");
1240 ensure_eq!(self.col_pos, def.col_id, "Column ID mismatch");
1241 Ok(())
1242 }
1243}
1244
1245impl From<&ColumnSchema> for ProductTypeElement {
1246 fn from(value: &ColumnSchema) -> Self {
1247 Self {
1248 name: Some(value.col_name.clone().into()),
1249 algebraic_type: value.col_type.clone(),
1250 }
1251 }
1252}
1253
1254impl From<ColumnSchema> for Column {
1255 fn from(schema: ColumnSchema) -> Self {
1256 Column {
1257 field: FieldName {
1258 table: schema.table_id,
1259 col: schema.col_pos,
1260 },
1261 algebraic_type: schema.col_type,
1262 }
1263 }
1264}
1265
1266#[derive(Debug, Clone)]
1268pub struct ColumnSchemaRef<'a> {
1269 pub column: &'a ColumnSchema,
1271 pub table_name: &'a RawIdentifier,
1273}
1274
1275impl From<ColumnSchemaRef<'_>> for ProductTypeElement {
1276 fn from(value: ColumnSchemaRef) -> Self {
1277 ProductTypeElement::new(
1278 value.column.col_type.clone(),
1279 Some(value.column.col_name.clone().into()),
1280 )
1281 }
1282}
1283
1284#[derive(Debug, Clone, PartialEq, Eq)]
1286pub struct SequenceSchema {
1287 pub sequence_id: SequenceId,
1289 pub sequence_name: RawIdentifier,
1292 pub table_id: TableId,
1294 pub col_pos: ColId,
1296 pub increment: i128,
1298 pub start: i128,
1300 pub min_value: i128,
1302 pub max_value: i128,
1304}
1305
1306impl spacetimedb_memory_usage::MemoryUsage for SequenceSchema {
1307 fn heap_usage(&self) -> usize {
1308 let Self {
1309 sequence_id,
1310 sequence_name,
1311 table_id,
1312 col_pos,
1313 increment,
1314 start,
1315 min_value,
1316 max_value,
1317 } = self;
1318 sequence_id.heap_usage()
1319 + sequence_name.heap_usage()
1320 + table_id.heap_usage()
1321 + col_pos.heap_usage()
1322 + increment.heap_usage()
1323 + start.heap_usage()
1324 + min_value.heap_usage()
1325 + max_value.heap_usage()
1326 }
1327}
1328
1329impl Schema for SequenceSchema {
1330 type Def = SequenceDef;
1331 type Id = SequenceId;
1332 type ParentId = TableId;
1333
1334 fn from_module_def(module_def: &ModuleDef, def: &Self::Def, parent_id: Self::ParentId, id: Self::Id) -> Self {
1335 module_def.expect_contains(def);
1336
1337 SequenceSchema {
1338 sequence_id: id,
1339 sequence_name: def.name.clone(),
1340 table_id: parent_id,
1341 col_pos: def.column,
1342 increment: def.increment,
1343 start: def.start.unwrap_or(1),
1344 min_value: def.min_value.unwrap_or(1),
1345 max_value: def.max_value.unwrap_or(i128::MAX),
1346 }
1348 }
1349
1350 fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
1351 ensure_eq!(&self.sequence_name[..], &def.name[..], "Sequence name mismatch");
1352 ensure_eq!(self.col_pos, def.column, "Sequence column mismatch");
1353 ensure_eq!(self.increment, def.increment, "Sequence increment mismatch");
1354 if let Some(start) = &def.start {
1355 ensure_eq!(self.start, *start, "Sequence start mismatch");
1356 }
1357 if let Some(min_value) = &def.min_value {
1358 ensure_eq!(self.min_value, *min_value, "Sequence min_value mismatch");
1359 }
1360 if let Some(max_value) = &def.max_value {
1361 ensure_eq!(self.max_value, *max_value, "Sequence max_value mismatch");
1362 }
1363 Ok(())
1364 }
1365}
1366
1367#[derive(Debug, Clone, PartialEq, Eq)]
1369pub struct ScheduleSchema {
1370 pub table_id: TableId,
1372
1373 pub schedule_id: ScheduleId,
1375
1376 pub schedule_name: Identifier,
1378
1379 pub function_name: Identifier,
1381
1382 pub at_column: ColId,
1384}
1385
1386impl ScheduleSchema {
1387 #[cfg(any(test, feature = "test"))]
1388 pub fn for_test(name: impl AsRef<str>, function: impl AsRef<str>, at: impl Into<ColId>) -> Self {
1389 Self {
1390 table_id: TableId::SENTINEL,
1391 schedule_id: ScheduleId::SENTINEL,
1392 schedule_name: Identifier::for_test(name.as_ref()),
1393 function_name: Identifier::for_test(function.as_ref()),
1394 at_column: at.into(),
1395 }
1396 }
1397}
1398
1399impl Schema for ScheduleSchema {
1400 type Def = ScheduleDef;
1401
1402 type Id = ScheduleId;
1403
1404 type ParentId = TableId;
1405
1406 fn from_module_def(module_def: &ModuleDef, def: &Self::Def, parent_id: Self::ParentId, id: Self::Id) -> Self {
1407 module_def.expect_contains(def);
1408
1409 ScheduleSchema {
1410 table_id: parent_id,
1411 schedule_id: id,
1412 schedule_name: def.name.clone(),
1413 function_name: def.function_name.clone(),
1414 at_column: def.at_column,
1415 }
1417 }
1418
1419 fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
1420 ensure_eq!(&self.schedule_name[..], &def.name[..], "Schedule name mismatch");
1421 ensure_eq!(
1422 &self.function_name[..],
1423 &def.function_name[..],
1424 "Schedule function name mismatch"
1425 );
1426 Ok(())
1427 }
1428}
1429
1430#[derive(Debug, Clone, PartialEq, Eq)]
1432pub struct IndexSchema {
1433 pub index_id: IndexId,
1435 pub table_id: TableId,
1437 pub index_name: RawIdentifier,
1440
1441 pub alias: Option<RawIdentifier>,
1442 pub index_algorithm: IndexAlgorithm,
1444}
1445
1446impl spacetimedb_memory_usage::MemoryUsage for IndexSchema {
1447 fn heap_usage(&self) -> usize {
1448 let Self {
1449 index_id,
1450 table_id,
1451 index_name,
1452 index_algorithm,
1453 alias: _,
1454 } = self;
1455 index_id.heap_usage() + table_id.heap_usage() + index_name.heap_usage() + index_algorithm.heap_usage()
1456 }
1457}
1458
1459impl IndexSchema {
1460 pub fn for_test(name: impl AsRef<str>, algo: impl Into<IndexAlgorithm>) -> Self {
1461 Self {
1462 index_id: IndexId::SENTINEL,
1463 table_id: TableId::SENTINEL,
1464 index_name: RawIdentifier::new(name.as_ref()),
1465 index_algorithm: algo.into(),
1466 alias: None,
1467 }
1468 }
1469}
1470
1471impl Schema for IndexSchema {
1472 type Def = IndexDef;
1473 type Id = IndexId;
1474 type ParentId = TableId;
1475
1476 fn from_module_def(module_def: &ModuleDef, def: &Self::Def, parent_id: Self::ParentId, id: Self::Id) -> Self {
1477 module_def.expect_contains(def);
1478
1479 let index_algorithm = def.algorithm.clone();
1480 IndexSchema {
1481 index_id: id,
1482 table_id: parent_id,
1483 index_name: def.name.clone(),
1484 index_algorithm,
1485 alias: Some(def.source_name.clone()),
1486 }
1487 }
1488
1489 fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
1490 ensure_eq!(&self.index_name[..], &def.name[..], "Index name mismatch");
1491 ensure_eq!(&self.index_algorithm, &def.algorithm, "Index algorithm mismatch");
1492 Ok(())
1493 }
1494}
1495
1496#[derive(Debug, Clone, PartialEq, Eq)]
1501pub struct ConstraintSchema {
1502 pub table_id: TableId,
1504 pub constraint_id: ConstraintId,
1506 pub constraint_name: RawIdentifier,
1508 pub data: ConstraintData, }
1511
1512impl spacetimedb_memory_usage::MemoryUsage for ConstraintSchema {
1513 fn heap_usage(&self) -> usize {
1514 let Self {
1515 table_id,
1516 constraint_id,
1517 constraint_name,
1518 data,
1519 } = self;
1520 table_id.heap_usage() + constraint_id.heap_usage() + constraint_name.heap_usage() + data.heap_usage()
1521 }
1522}
1523
1524impl ConstraintSchema {
1525 pub fn unique_for_test(name: impl AsRef<str>, cols: impl Into<ColSet>) -> Self {
1526 Self {
1527 table_id: TableId::SENTINEL,
1528 constraint_id: ConstraintId::SENTINEL,
1529 constraint_name: RawIdentifier::new(name.as_ref()),
1530 data: ConstraintData::Unique(UniqueConstraintData { columns: cols.into() }),
1531 }
1532 }
1533
1534 #[deprecated(note = "Use TableSchema::from_module_def instead")]
1541 pub fn from_def(table_id: TableId, constraint: RawConstraintDefV8) -> Option<Self> {
1542 if constraint.constraints.has_unique() {
1543 Some(ConstraintSchema {
1544 constraint_id: ConstraintId::SENTINEL, constraint_name: RawIdentifier::new(constraint.constraint_name.trim()),
1546 table_id,
1547 data: ConstraintData::Unique(UniqueConstraintData {
1548 columns: constraint.columns.into(),
1549 }),
1550 })
1551 } else {
1552 None
1553 }
1554 }
1555}
1556
1557impl Schema for ConstraintSchema {
1558 type Def = ConstraintDef;
1559 type Id = ConstraintId;
1560 type ParentId = TableId;
1561
1562 fn from_module_def(module_def: &ModuleDef, def: &Self::Def, parent_id: Self::ParentId, id: Self::Id) -> Self {
1563 module_def.expect_contains(def);
1564
1565 ConstraintSchema {
1566 constraint_id: id,
1567 constraint_name: def.name.clone(),
1568 table_id: parent_id,
1569 data: def.data.clone(),
1570 }
1571 }
1572
1573 fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
1574 ensure_eq!(&self.constraint_name[..], &def.name[..], "Constraint name mismatch");
1575 ensure_eq!(&self.data, &def.data, "Constraint data mismatch");
1576 Ok(())
1577 }
1578}
1579
1580#[derive(Debug, Clone, PartialEq, Eq)]
1582pub struct RowLevelSecuritySchema {
1583 pub table_id: TableId,
1584 pub sql: RawSql,
1585}