use std::collections::BTreeMap;
use std::fmt::{self, Debug, Write};
use std::hash::Hash;
use crate::error::{IdentifierError, ValidationErrors};
use crate::identifier::Identifier;
use crate::reducer_name::ReducerName;
use crate::schema::{Schema, TableSchema};
use crate::type_for_generate::{AlgebraicTypeUse, ProductTypeDef, TypespaceForGenerate};
use deserialize::ArgsSeed;
use enum_map::EnumMap;
use indexmap::IndexMap;
use itertools::Itertools;
use spacetimedb_data_structures::error_stream::{CollectAllErrors, CombineErrors, ErrorStream};
use spacetimedb_data_structures::map::{Equivalent, HashMap};
use spacetimedb_lib::db::raw_def;
use spacetimedb_lib::db::raw_def::v10::{
ExplicitNames, RawConstraintDefV10, RawIndexDefV10, RawLifeCycleReducerDefV10, RawModuleDefV10,
RawModuleDefV10Section, RawProcedureDefV10, RawReducerDefV10, RawRowLevelSecurityDefV10, RawScheduleDefV10,
RawScopedTypeNameV10, RawSequenceDefV10, RawTableDefV10, RawTypeDefV10, RawViewDefV10,
};
use spacetimedb_lib::db::raw_def::v9::{
Lifecycle, RawColumnDefaultValueV9, RawConstraintDataV9, RawConstraintDefV9, RawIndexAlgorithm, RawIndexDefV9,
RawMiscModuleExportV9, RawModuleDefV9, RawProcedureDefV9, RawReducerDefV9, RawRowLevelSecurityDefV9,
RawScheduleDefV9, RawScopedTypeNameV9, RawSequenceDefV9, RawSql, RawTableDefV9, RawTypeDefV9,
RawUniqueConstraintDataV9, RawViewDefV9, TableAccess, TableType,
};
use spacetimedb_lib::{ProductType, RawModuleDef};
use spacetimedb_primitives::{ColId, ColList, ColOrCols, ColSet, ProcedureId, ReducerId, TableId, ViewFnPtr};
use spacetimedb_sats::raw_identifier::RawIdentifier;
use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, AlgebraicValue, Typespace};
pub mod deserialize;
pub mod error;
pub mod validate;
pub type IdentifierMap<T> = HashMap<Identifier, T>;
pub type StrMap<T> = HashMap<RawIdentifier, T>;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ModuleDef {
tables: IdentifierMap<TableDef>,
reducers: IndexMap<Identifier, ReducerDef>,
procedures: IndexMap<Identifier, ProcedureDef>,
views: IndexMap<Identifier, ViewDef>,
lifecycle_reducers: EnumMap<Lifecycle, Option<ReducerId>>,
types: HashMap<ScopedTypeName, TypeDef>,
typespace: Typespace,
typespace_for_generate: TypespaceForGenerate,
stored_in_table_def: StrMap<Identifier>,
refmap: HashMap<AlgebraicTypeRef, ScopedTypeName>,
row_level_security_raw: HashMap<RawSql, RawRowLevelSecurityDefV9>,
#[allow(unused)]
raw_module_def_version: RawModuleDefVersion,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum RawModuleDefVersion {
V9OrEarlier,
V10,
}
impl ModuleDef {
pub fn raw_module_def_version(&self) -> RawModuleDefVersion {
self.raw_module_def_version
}
pub fn tables(&self) -> impl Iterator<Item = &TableDef> {
self.tables.values()
}
pub fn indexes(&self) -> impl Iterator<Item = &IndexDef> {
self.tables().flat_map(|table| table.indexes.values())
}
pub fn constraints(&self) -> impl Iterator<Item = &ConstraintDef> {
self.tables().flat_map(|table| table.constraints.values())
}
pub fn sequences(&self) -> impl Iterator<Item = &SequenceDef> {
self.tables().flat_map(|table| table.sequences.values())
}
pub fn schedules(&self) -> impl Iterator<Item = &ScheduleDef> {
self.tables().filter_map(|table| table.schedule.as_ref())
}
pub fn reducers(&self) -> impl Iterator<Item = &ReducerDef> {
self.reducers.values()
}
pub fn reducer_ids_and_defs(&self) -> impl ExactSizeIterator<Item = (ReducerId, &ReducerDef)> {
self.reducers.values().enumerate().map(|(idx, def)| (idx.into(), def))
}
pub fn procedures(&self) -> impl Iterator<Item = &ProcedureDef> {
self.procedures.values()
}
pub fn views(&self) -> impl Iterator<Item = &ViewDef> {
self.views.values()
}
pub fn types(&self) -> impl Iterator<Item = &TypeDef> {
self.types.values()
}
pub fn row_level_security(&self) -> impl Iterator<Item = &RawRowLevelSecurityDefV9> {
self.row_level_security_raw.values()
}
pub fn typespace(&self) -> &Typespace {
&self.typespace
}
pub fn typespace_for_generate(&self) -> &TypespaceForGenerate {
&self.typespace_for_generate
}
pub fn stored_in_table_def(&self, name: &RawIdentifier) -> Option<&TableDef> {
self.stored_in_table_def
.get(name)
.and_then(|table_name| self.tables.get(table_name))
}
pub fn lookup<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> Option<&T> {
T::lookup(self, key)
}
pub fn lookup_expect<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> &T {
T::lookup(self, key).expect("expected ModuleDef to contain key, but it does not")
}
pub fn table<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&TableDef> {
self.tables.get(name)
}
pub fn view<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&ViewDef> {
self.views.get(name)
}
pub fn view_full<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<(ViewFnPtr, &ViewDef)> {
self.views.get(name).map(|def| (def.fn_ptr, def))
}
pub fn reducer<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&ReducerDef> {
self.reducers.get(name)
}
pub fn reducer_full<K: ?Sized + Hash + Equivalent<Identifier>>(
&self,
name: &K,
) -> Option<(ReducerId, &ReducerDef)> {
self.reducers.get_full(name).map(|(idx, _, def)| (idx.into(), def))
}
pub fn reducer_by_id(&self, id: ReducerId) -> &ReducerDef {
&self.reducers[id.idx()]
}
pub fn get_reducer_by_id(&self, id: ReducerId) -> Option<&ReducerDef> {
self.reducers.get_index(id.idx()).map(|(_, def)| def)
}
pub fn get_view_by_id(&self, id: ViewFnPtr, is_anonymous: bool) -> Option<&ViewDef> {
self.views
.iter()
.find(|(_, def)| def.fn_ptr == id && def.is_anonymous == is_anonymous)
.map(|(_, def)| def)
}
pub fn procedure<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&ProcedureDef> {
self.procedures.get(name)
}
pub fn procedure_full<K: ?Sized + Hash + Equivalent<Identifier>>(
&self,
name: &K,
) -> Option<(ProcedureId, &ProcedureDef)> {
self.procedures.get_full(name).map(|(idx, _, def)| (idx.into(), def))
}
pub fn procedure_by_id(&self, id: ProcedureId) -> &ProcedureDef {
&self.procedures[id.idx()]
}
pub fn get_procedure_by_id(&self, id: ProcedureId) -> Option<&ProcedureDef> {
self.procedures.get_index(id.idx()).map(|(_, def)| def)
}
pub fn lifecycle_reducer(&self, lifecycle: Lifecycle) -> Option<(ReducerId, &ReducerDef)> {
self.lifecycle_reducers[lifecycle].map(|i| (i, &self.reducers[i.idx()]))
}
pub fn arg_seed_for<'a, T>(&'a self, def: &'a T) -> ArgsSeed<'a, T> {
ArgsSeed(self.typespace.with_type(def))
}
pub fn type_def_from_ref(&self, r: AlgebraicTypeRef) -> Option<(&ScopedTypeName, &TypeDef)> {
let name = self.refmap.get(&r)?;
let def = self
.types
.get(name)
.expect("if it was in refmap, it should be in types");
Some((name, def))
}
pub fn table_schema<K: ?Sized + Hash + Equivalent<Identifier>>(
&self,
name: &K,
table_id: TableId,
) -> Option<TableSchema> {
let table_def = self.tables.get(name)?;
Some(TableSchema::from_module_def(self, table_def, (), table_id))
}
pub fn expect_lookup<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> &T {
if let Some(result) = T::lookup(self, key) {
result
} else {
panic!("expected ModuleDef to contain {key:?}, but it does not");
}
}
pub fn expect_contains<Def: ModuleDefLookup>(&self, def: &Def) {
if let Some(my_def) = self.lookup(def.key()) {
assert_eq!(
def as *const Def, my_def as *const Def,
"expected ModuleDef to contain {def:?}, but it contained {my_def:?}"
);
} else {
panic!("expected ModuleDef to contain {:?}, but it does not", def.key());
}
}
}
impl TryFrom<RawModuleDef> for ModuleDef {
type Error = ValidationErrors;
fn try_from(raw: RawModuleDef) -> Result<Self, Self::Error> {
match raw {
RawModuleDef::V8BackCompat(v8_mod) => Self::try_from(v8_mod),
RawModuleDef::V9(v9_mod) => Self::try_from(v9_mod),
RawModuleDef::V10(v10_mod) => Self::try_from(v10_mod),
_ => unimplemented!(),
}
}
}
impl TryFrom<raw_def::v8::RawModuleDefV8> for ModuleDef {
type Error = ValidationErrors;
fn try_from(v8_mod: raw_def::v8::RawModuleDefV8) -> Result<Self, Self::Error> {
validate::v8::validate(v8_mod)
}
}
impl TryFrom<raw_def::v9::RawModuleDefV9> for ModuleDef {
type Error = ValidationErrors;
fn try_from(v9_mod: raw_def::v9::RawModuleDefV9) -> Result<Self, Self::Error> {
validate::v9::validate(v9_mod)
}
}
impl From<ModuleDef> for RawModuleDefV9 {
fn from(val: ModuleDef) -> Self {
let ModuleDef {
tables,
views,
reducers,
lifecycle_reducers: _,
types,
typespace,
stored_in_table_def: _,
typespace_for_generate: _,
refmap: _,
row_level_security_raw,
procedures,
raw_module_def_version: _,
} = val;
let column_defaults: Vec<_> = tables
.iter()
.flat_map(|(table_name, table_def)| {
table_def.columns.iter().enumerate().filter_map(|(col_id, col)| {
col.default_value.as_ref().map(|default_val| {
RawMiscModuleExportV9::ColumnDefaultValue(RawColumnDefaultValueV9 {
table: table_name.clone().into(),
col_id: ColId(col_id as u16),
value: spacetimedb_sats::bsatn::to_vec(default_val).unwrap().into(),
})
})
})
})
.collect();
RawModuleDefV9 {
tables: to_raw(tables),
reducers: reducers.into_iter().map(|(_, def)| def.into()).collect(),
types: to_raw(types),
misc_exports: column_defaults
.into_iter()
.chain(procedures.into_iter().map(|(_, def)| def.into()))
.chain(views.into_iter().map(|(_, def)| def.into()))
.collect(),
typespace,
row_level_security: row_level_security_raw.into_iter().map(|(_, def)| def).collect(),
}
}
}
impl TryFrom<raw_def::v10::RawModuleDefV10> for ModuleDef {
type Error = ValidationErrors;
fn try_from(v10_mod: raw_def::v10::RawModuleDefV10) -> Result<Self, Self::Error> {
validate::v10::validate(v10_mod)
}
}
impl From<ModuleDef> for RawModuleDefV10 {
fn from(val: ModuleDef) -> Self {
let ModuleDef {
tables,
views,
reducers,
lifecycle_reducers,
types,
typespace,
stored_in_table_def: _,
typespace_for_generate: _,
refmap: _,
row_level_security_raw,
procedures,
raw_module_def_version: _,
} = val;
let mut sections = Vec::new();
let mut explicit_names = ExplicitNames::default();
sections.push(RawModuleDefV10Section::Typespace(typespace));
let raw_lifecycle: Vec<RawLifeCycleReducerDefV10> = lifecycle_reducers
.into_iter()
.filter_map(|(lifecycle, reducer_id)| {
let id = reducer_id?;
let (name, _) = reducers.get_index(id.idx())?;
Some(RawLifeCycleReducerDefV10 {
lifecycle_spec: lifecycle,
function_name: name.clone().into(),
})
})
.collect();
let raw_types: Vec<RawTypeDefV10> = types.into_values().map(Into::into).collect();
if !raw_types.is_empty() {
sections.push(RawModuleDefV10Section::Types(raw_types));
}
let mut schedules = Vec::new();
let raw_tables: Vec<RawTableDefV10> = tables
.into_values()
.map(|td| {
explicit_names.insert_table(
RawIdentifier::from(td.accessor_name.clone()),
RawIdentifier::from(td.name.clone()),
);
if let Some(sched) = td.schedule.clone() {
schedules.push(RawScheduleDefV10 {
source_name: Some(sched.name.into()),
table_name: td.name.clone().into(),
schedule_at_col: sched.at_column,
function_name: sched.function_name.into(),
});
}
td.into()
})
.collect();
if !raw_tables.is_empty() {
sections.push(RawModuleDefV10Section::Tables(raw_tables));
}
let raw_reducers: Vec<RawReducerDefV10> = reducers
.into_values()
.map(|rd| {
explicit_names.insert_function(
RawIdentifier::from(rd.accessor_name.clone()),
RawIdentifier::from(rd.name.clone()),
);
rd.into()
})
.collect();
if !raw_reducers.is_empty() {
sections.push(RawModuleDefV10Section::Reducers(raw_reducers));
}
let raw_procedures: Vec<RawProcedureDefV10> = procedures
.into_values()
.map(|pd| {
explicit_names.insert_function(
RawIdentifier::from(pd.accessor_name.clone()),
RawIdentifier::from(pd.name.clone()),
);
pd.into()
})
.collect();
if !raw_procedures.is_empty() {
sections.push(RawModuleDefV10Section::Procedures(raw_procedures));
}
let raw_views: Vec<RawViewDefV10> = views
.into_values()
.map(|vd| {
explicit_names.insert_function(
RawIdentifier::from(vd.accessor_name.clone()),
RawIdentifier::from(vd.name.clone()),
);
vd.into()
})
.collect();
if !raw_views.is_empty() {
sections.push(RawModuleDefV10Section::Views(raw_views));
}
if !schedules.is_empty() {
sections.push(RawModuleDefV10Section::Schedules(schedules));
}
if !raw_lifecycle.is_empty() {
sections.push(RawModuleDefV10Section::LifeCycleReducers(raw_lifecycle));
}
let raw_rls: Vec<RawRowLevelSecurityDefV10> = row_level_security_raw.into_values().collect();
if !raw_rls.is_empty() {
sections.push(RawModuleDefV10Section::RowLevelSecurity(raw_rls));
}
sections.push(RawModuleDefV10Section::ExplicitNames(explicit_names));
RawModuleDefV10 { sections }
}
}
pub trait ModuleDefLookup: Sized + Debug + 'static {
type Key<'a>: Debug + Copy;
fn key(&self) -> Self::Key<'_>;
fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self>;
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct TableDef {
pub name: Identifier,
pub accessor_name: Identifier,
pub product_type_ref: AlgebraicTypeRef,
pub primary_key: Option<ColId>,
pub columns: Vec<ColumnDef>,
pub indexes: StrMap<IndexDef>,
pub constraints: StrMap<ConstraintDef>,
pub sequences: StrMap<SequenceDef>,
pub schedule: Option<ScheduleDef>,
pub table_type: TableType,
pub table_access: TableAccess,
pub is_event: bool,
}
impl TableDef {
pub fn get_column(&self, id: ColId) -> Option<&ColumnDef> {
self.columns.get(id.idx())
}
pub fn get_column_by_name(&self, name: &Identifier) -> Option<&ColumnDef> {
self.columns.iter().find(|c| &c.name == name)
}
}
impl From<TableDef> for RawTableDefV9 {
fn from(val: TableDef) -> Self {
let TableDef {
name,
product_type_ref,
primary_key,
columns: _, indexes,
constraints,
sequences,
schedule,
table_type,
table_access,
is_event: _, ..
} = val;
RawTableDefV9 {
name: name.into(),
product_type_ref,
primary_key: ColList::from_iter(primary_key),
indexes: to_raw(indexes),
constraints: to_raw(constraints),
sequences: to_raw(sequences),
schedule: schedule.map(Into::into),
table_type,
table_access,
}
}
}
impl From<TableDef> for RawTableDefV10 {
fn from(val: TableDef) -> Self {
let TableDef {
name: _,
product_type_ref,
primary_key,
columns: _, indexes,
constraints,
sequences,
schedule: _, table_type,
table_access,
is_event,
accessor_name,
} = val;
RawTableDefV10 {
source_name: accessor_name.into(),
product_type_ref,
primary_key: ColList::from_iter(primary_key),
indexes: indexes.into_values().map(Into::into).collect(),
constraints: constraints.into_values().map(Into::into).collect(),
sequences: sequences.into_values().map(Into::into).collect(),
table_type,
table_access,
default_values: Vec::new(),
is_event,
}
}
}
impl From<ViewDef> for TableDef {
fn from(def: ViewDef) -> Self {
use TableAccess::*;
let ViewDef {
name,
is_public,
product_type_ref,
primary_key,
return_columns,
accessor_name,
..
} = def;
Self {
name,
product_type_ref,
primary_key,
columns: return_columns.into_iter().map(ColumnDef::from).collect(),
indexes: <_>::default(),
constraints: <_>::default(),
sequences: <_>::default(),
schedule: None,
table_type: TableType::User,
table_access: if is_public { Public } else { Private },
is_event: false,
accessor_name,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SequenceDef {
pub name: RawIdentifier,
pub column: ColId,
pub start: Option<i128>,
pub min_value: Option<i128>,
pub max_value: Option<i128>,
pub increment: i128,
}
impl From<SequenceDef> for RawSequenceDefV9 {
fn from(val: SequenceDef) -> Self {
RawSequenceDefV9 {
name: Some(val.name),
column: val.column,
start: val.start,
min_value: val.min_value,
max_value: val.max_value,
increment: val.increment,
}
}
}
impl From<SequenceDef> for RawSequenceDefV10 {
fn from(val: SequenceDef) -> Self {
RawSequenceDefV10 {
source_name: Some(val.name),
column: val.column,
start: val.start,
min_value: val.min_value,
max_value: val.max_value,
increment: val.increment,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct IndexDef {
pub name: RawIdentifier,
pub source_name: RawIdentifier,
pub accessor_name: Option<Identifier>,
pub algorithm: IndexAlgorithm,
}
impl IndexDef {
pub fn generated(&self) -> bool {
self.accessor_name.is_none()
}
}
impl From<IndexDef> for RawIndexDefV9 {
fn from(val: IndexDef) -> Self {
RawIndexDefV9 {
name: Some(val.name),
algorithm: match val.algorithm {
IndexAlgorithm::BTree(BTreeAlgorithm { columns }) => RawIndexAlgorithm::BTree { columns },
IndexAlgorithm::Hash(HashAlgorithm { columns }) => RawIndexAlgorithm::Hash { columns },
IndexAlgorithm::Direct(DirectAlgorithm { column }) => RawIndexAlgorithm::Direct { column },
},
accessor_name: val.accessor_name.map(Into::into),
}
}
}
impl From<IndexDef> for RawIndexDefV10 {
fn from(val: IndexDef) -> Self {
RawIndexDefV10 {
source_name: Some(val.source_name),
accessor_name: val.accessor_name.map(Into::into),
algorithm: val.algorithm.into(),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum IndexAlgorithm {
BTree(BTreeAlgorithm),
Hash(HashAlgorithm),
Direct(DirectAlgorithm),
}
impl spacetimedb_memory_usage::MemoryUsage for IndexAlgorithm {
fn heap_usage(&self) -> usize {
match self {
Self::BTree(a) => a.heap_usage(),
Self::Direct(a) => a.heap_usage(),
Self::Hash(a) => a.heap_usage(),
}
}
}
impl IndexAlgorithm {
pub fn columns(&self) -> ColOrCols<'_> {
match self {
Self::BTree(btree) => ColOrCols::ColList(&btree.columns),
Self::Hash(hash) => ColOrCols::ColList(&hash.columns),
Self::Direct(direct) => ColOrCols::Col(direct.column),
}
}
pub fn find_col_index(&self, pos: usize) -> Option<ColId> {
self.columns().iter().find(|col_id| col_id.idx() == pos)
}
}
impl From<IndexAlgorithm> for RawIndexAlgorithm {
fn from(val: IndexAlgorithm) -> Self {
match val {
IndexAlgorithm::BTree(BTreeAlgorithm { columns }) => Self::BTree { columns },
IndexAlgorithm::Hash(HashAlgorithm { columns }) => Self::Hash { columns },
IndexAlgorithm::Direct(DirectAlgorithm { column }) => Self::Direct { column },
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct BTreeAlgorithm {
pub columns: ColList,
}
impl spacetimedb_memory_usage::MemoryUsage for BTreeAlgorithm {
fn heap_usage(&self) -> usize {
self.columns.heap_usage()
}
}
impl<CL: Into<ColList>> From<CL> for BTreeAlgorithm {
fn from(columns: CL) -> Self {
let columns = columns.into();
Self { columns }
}
}
impl From<BTreeAlgorithm> for IndexAlgorithm {
fn from(val: BTreeAlgorithm) -> Self {
IndexAlgorithm::BTree(val)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct HashAlgorithm {
pub columns: ColList,
}
impl spacetimedb_memory_usage::MemoryUsage for HashAlgorithm {
fn heap_usage(&self) -> usize {
self.columns.heap_usage()
}
}
impl<CL: Into<ColList>> From<CL> for HashAlgorithm {
fn from(columns: CL) -> Self {
let columns = columns.into();
Self { columns }
}
}
impl From<HashAlgorithm> for IndexAlgorithm {
fn from(val: HashAlgorithm) -> Self {
IndexAlgorithm::Hash(val)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct DirectAlgorithm {
pub column: ColId,
}
impl spacetimedb_memory_usage::MemoryUsage for DirectAlgorithm {
fn heap_usage(&self) -> usize {
self.column.heap_usage()
}
}
impl<C: Into<ColId>> From<C> for DirectAlgorithm {
fn from(column: C) -> Self {
let column = column.into();
Self { column }
}
}
impl From<DirectAlgorithm> for IndexAlgorithm {
fn from(val: DirectAlgorithm) -> Self {
IndexAlgorithm::Direct(val)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct ColumnDef {
pub name: Identifier,
pub accessor_name: Identifier,
pub col_id: ColId,
pub ty: AlgebraicType,
pub ty_for_generate: AlgebraicTypeUse,
pub table_name: Identifier,
pub default_value: Option<AlgebraicValue>,
}
impl From<ViewColumnDef> for ColumnDef {
fn from(def: ViewColumnDef) -> Self {
let ViewColumnDef {
name,
col_id,
ty,
ty_for_generate,
view_name: table_name,
accessor_name,
} = def;
Self {
name,
col_id,
ty,
ty_for_generate,
table_name,
default_value: None,
accessor_name,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct ViewColumnDef {
pub name: Identifier,
pub accessor_name: Identifier,
pub col_id: ColId,
pub ty: AlgebraicType,
pub ty_for_generate: AlgebraicTypeUse,
pub view_name: Identifier,
}
impl From<ColumnDef> for ViewColumnDef {
fn from(
ColumnDef {
name,
col_id,
ty,
ty_for_generate,
table_name: view_name,
accessor_name,
..
}: ColumnDef,
) -> Self {
Self {
name,
col_id,
ty,
ty_for_generate,
view_name,
accessor_name,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct ViewParamDef {
pub name: Identifier,
pub col_id: ColId,
pub ty: AlgebraicType,
pub ty_for_generate: AlgebraicTypeUse,
pub view_name: Identifier,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ConstraintDef {
pub name: RawIdentifier,
pub data: ConstraintData,
}
impl From<ConstraintDef> for RawConstraintDefV9 {
fn from(val: ConstraintDef) -> Self {
RawConstraintDefV9 {
name: Some(val.name),
data: val.data.into(),
}
}
}
impl From<ConstraintDef> for RawConstraintDefV10 {
fn from(val: ConstraintDef) -> Self {
RawConstraintDefV10 {
source_name: Some(val.name),
data: val.data.into(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum ConstraintData {
Unique(UniqueConstraintData),
}
impl spacetimedb_memory_usage::MemoryUsage for ConstraintData {
fn heap_usage(&self) -> usize {
match self {
ConstraintData::Unique(d) => d.heap_usage(),
}
}
}
impl ConstraintData {
pub fn unique_columns(&self) -> Option<&ColSet> {
match &self {
ConstraintData::Unique(UniqueConstraintData { columns }) => Some(columns),
}
}
}
impl From<ConstraintData> for RawConstraintDataV9 {
fn from(val: ConstraintData) -> Self {
match val {
ConstraintData::Unique(unique) => RawConstraintDataV9::Unique(unique.into()),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct UniqueConstraintData {
pub columns: ColSet,
}
impl spacetimedb_memory_usage::MemoryUsage for UniqueConstraintData {
fn heap_usage(&self) -> usize {
self.columns.heap_usage()
}
}
impl From<UniqueConstraintData> for RawUniqueConstraintDataV9 {
fn from(val: UniqueConstraintData) -> Self {
RawUniqueConstraintDataV9 {
columns: val.columns.into(),
}
}
}
impl From<UniqueConstraintData> for ConstraintData {
fn from(val: UniqueConstraintData) -> Self {
ConstraintData::Unique(val)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RowLevelSecurityDef {
pub sql: RawSql,
}
impl From<RowLevelSecurityDef> for RawRowLevelSecurityDefV9 {
fn from(val: RowLevelSecurityDef) -> Self {
RawRowLevelSecurityDefV9 { sql: val.sql }
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
pub enum FunctionKind {
Unknown,
Reducer,
Procedure,
}
impl fmt::Display for FunctionKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
FunctionKind::Unknown => "exported function",
FunctionKind::Reducer => "reducer",
FunctionKind::Procedure => "procedure",
})
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct ScheduleDef {
pub name: Identifier,
pub at_column: ColId,
pub id_column: ColId,
pub function_name: Identifier,
pub function_kind: FunctionKind,
}
impl From<ScheduleDef> for RawScheduleDefV9 {
fn from(val: ScheduleDef) -> Self {
RawScheduleDefV9 {
name: Some(val.name.into()),
reducer_name: val.function_name.into(),
scheduled_at_column: val.at_column,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct TypeDef {
pub accessor_name: ScopedTypeName,
pub ty: AlgebraicTypeRef,
pub custom_ordering: bool,
}
impl From<TypeDef> for RawTypeDefV9 {
fn from(val: TypeDef) -> Self {
RawTypeDefV9 {
name: val.accessor_name.into(),
ty: val.ty,
custom_ordering: val.custom_ordering,
}
}
}
impl From<TypeDef> for RawTypeDefV10 {
fn from(val: TypeDef) -> Self {
RawTypeDefV10 {
source_name: val.accessor_name.into(),
ty: val.ty,
custom_ordering: val.custom_ordering,
}
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ScopedTypeName {
scope: Box<[Identifier]>,
name: Identifier,
}
impl ScopedTypeName {
pub fn new(scope: Box<[Identifier]>, name: Identifier) -> Self {
ScopedTypeName { scope, name }
}
pub fn try_new(
scope: impl IntoIterator<Item = RawIdentifier>,
name: impl Into<RawIdentifier>,
) -> Result<Self, ErrorStream<IdentifierError>> {
let scope = scope
.into_iter()
.map(|chunk| Identifier::new(chunk).map_err(ErrorStream::from))
.collect_all_errors();
let name = Identifier::new(name.into()).map_err(ErrorStream::from);
let (scope, name) = (scope, name).combine_errors()?;
Ok(ScopedTypeName { scope, name })
}
pub fn from_name(name: Identifier) -> Self {
ScopedTypeName {
scope: Box::new([]),
name,
}
}
pub fn name(&self) -> &Identifier {
&self.name
}
pub fn as_identifier(&self) -> Option<&Identifier> {
self.scope.is_empty().then_some(&self.name)
}
pub fn name_segments(&self) -> impl Iterator<Item = &Identifier> {
self.scope.iter().chain(std::iter::once(&self.name))
}
}
impl fmt::Debug for ScopedTypeName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_char('"')?;
for scope in &*self.scope {
write!(f, "{scope}::")?;
}
write!(f, "{}\"", self.name)
}
}
impl fmt::Display for ScopedTypeName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for scope in &*self.scope {
write!(f, "{scope}::")?;
}
fmt::Display::fmt(&self.name, f)
}
}
impl TryFrom<RawScopedTypeNameV9> for ScopedTypeName {
type Error = ErrorStream<IdentifierError>;
fn try_from(value: RawScopedTypeNameV9) -> Result<Self, Self::Error> {
Self::try_new(value.scope.into_vec(), value.name)
}
}
impl From<ScopedTypeName> for RawScopedTypeNameV9 {
fn from(val: ScopedTypeName) -> Self {
RawScopedTypeNameV9 {
scope: val.scope.into_vec().into_iter().map(|id| id.into()).collect(),
name: val.name.into(),
}
}
}
impl From<ScopedTypeName> for RawScopedTypeNameV10 {
fn from(val: ScopedTypeName) -> Self {
RawScopedTypeNameV10 {
scope: val.scope.into_vec().into_iter().map(|id| id.into()).collect(),
source_name: val.name.into(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct ViewDef {
pub name: Identifier,
pub accessor_name: Identifier,
pub is_public: bool,
pub is_anonymous: bool,
pub fn_ptr: ViewFnPtr,
pub params: ProductType,
pub params_for_generate: ProductTypeDef,
pub return_type: AlgebraicType,
pub return_type_for_generate: AlgebraicTypeUse,
pub product_type_ref: AlgebraicTypeRef,
pub primary_key: Option<ColId>,
pub return_columns: Vec<ViewColumnDef>,
pub param_columns: Vec<ViewParamDef>,
}
impl ViewDef {
pub fn get_column_by_name(&self, name: &Identifier) -> Option<&ViewColumnDef> {
self.return_columns.iter().find(|c| &c.name == name)
}
pub fn get_param_by_name(&self, name: &Identifier) -> Option<&ViewParamDef> {
self.param_columns.iter().find(|c| &c.name == name)
}
}
impl From<ViewDef> for RawViewDefV9 {
fn from(val: ViewDef) -> Self {
let ViewDef {
name,
is_anonymous,
is_public,
params,
return_type,
fn_ptr: index,
..
} = val;
RawViewDefV9 {
name: name.into(),
index: index.into(),
is_anonymous,
is_public,
params,
return_type,
}
}
}
impl From<ViewDef> for RawViewDefV10 {
fn from(val: ViewDef) -> Self {
let ViewDef {
accessor_name,
is_anonymous,
is_public,
params,
return_type,
fn_ptr,
..
} = val;
RawViewDefV10 {
source_name: accessor_name.into(),
index: fn_ptr.into(),
is_public,
is_anonymous,
params,
return_type,
}
}
}
impl From<ViewDef> for RawMiscModuleExportV9 {
fn from(def: ViewDef) -> Self {
Self::View(def.into())
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum FunctionVisibility {
Private,
ClientCallable,
}
impl FunctionVisibility {
pub fn is_private(&self) -> bool {
matches!(self, FunctionVisibility::Private)
}
}
use spacetimedb_lib::db::raw_def::v10::FunctionVisibility as RawFunctionVisibility;
impl From<RawFunctionVisibility> for FunctionVisibility {
fn from(val: RawFunctionVisibility) -> Self {
match val {
RawFunctionVisibility::Private => FunctionVisibility::Private,
RawFunctionVisibility::ClientCallable => FunctionVisibility::ClientCallable,
}
}
}
impl From<FunctionVisibility> for RawFunctionVisibility {
fn from(val: FunctionVisibility) -> Self {
match val {
FunctionVisibility::Private => RawFunctionVisibility::Private,
FunctionVisibility::ClientCallable => RawFunctionVisibility::ClientCallable,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct ReducerDef {
pub name: ReducerName,
pub accessor_name: ReducerName,
pub params: ProductType,
pub params_for_generate: ProductTypeDef,
pub lifecycle: Option<Lifecycle>,
pub visibility: FunctionVisibility,
pub ok_return_type: AlgebraicType,
pub err_return_type: AlgebraicType,
}
impl From<ReducerDef> for RawReducerDefV9 {
fn from(val: ReducerDef) -> Self {
RawReducerDefV9 {
name: val.name.into(),
params: val.params,
lifecycle: val.lifecycle,
}
}
}
impl From<ReducerDef> for RawReducerDefV10 {
fn from(val: ReducerDef) -> Self {
RawReducerDefV10 {
source_name: val.accessor_name.into(),
params: val.params,
visibility: val.visibility.into(),
ok_return_type: val.ok_return_type,
err_return_type: val.err_return_type,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct ProcedureDef {
pub name: Identifier,
pub accessor_name: Identifier,
pub params: ProductType,
pub params_for_generate: ProductTypeDef,
pub return_type: AlgebraicType,
pub return_type_for_generate: AlgebraicTypeUse,
pub visibility: FunctionVisibility,
}
impl From<ProcedureDef> for RawProcedureDefV9 {
fn from(val: ProcedureDef) -> Self {
RawProcedureDefV9 {
name: val.name.into(),
params: val.params,
return_type: val.return_type,
}
}
}
impl From<ProcedureDef> for RawProcedureDefV10 {
fn from(val: ProcedureDef) -> Self {
RawProcedureDefV10 {
source_name: val.accessor_name.into(),
params: val.params,
return_type: val.return_type,
visibility: val.visibility.into(),
}
}
}
impl From<ProcedureDef> for RawMiscModuleExportV9 {
fn from(def: ProcedureDef) -> Self {
Self::Procedure(def.into())
}
}
impl ModuleDefLookup for TableDef {
type Key<'a> = &'a Identifier;
fn key(&self) -> Self::Key<'_> {
&self.name
}
fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
module_def.tables.get(key)
}
}
impl ModuleDefLookup for SequenceDef {
type Key<'a> = &'a RawIdentifier;
fn key(&self) -> Self::Key<'_> {
&self.name
}
fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
module_def.stored_in_table_def(key)?.sequences.get(key)
}
}
impl ModuleDefLookup for IndexDef {
type Key<'a> = &'a RawIdentifier;
fn key(&self) -> Self::Key<'_> {
&self.name
}
fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
module_def.stored_in_table_def(key)?.indexes.get(key)
}
}
impl ModuleDefLookup for ColumnDef {
type Key<'a> = (&'a Identifier, &'a Identifier);
fn key(&self) -> Self::Key<'_> {
(&self.table_name, &self.name)
}
fn lookup<'a>(module_def: &'a ModuleDef, (table_name, name): Self::Key<'_>) -> Option<&'a Self> {
module_def
.tables
.get(table_name)
.and_then(|table| table.get_column_by_name(name))
}
}
impl ModuleDefLookup for ViewColumnDef {
type Key<'a> = (&'a Identifier, &'a Identifier);
fn key(&self) -> Self::Key<'_> {
(&self.view_name, &self.name)
}
fn lookup<'a>(module_def: &'a ModuleDef, (view_name, name): Self::Key<'_>) -> Option<&'a Self> {
module_def
.views
.get(view_name)
.and_then(|view| view.get_column_by_name(name))
}
}
impl ModuleDefLookup for ViewParamDef {
type Key<'a> = (&'a Identifier, &'a Identifier);
fn key(&self) -> Self::Key<'_> {
(&self.view_name, &self.name)
}
fn lookup<'a>(module_def: &'a ModuleDef, (view_name, name): Self::Key<'_>) -> Option<&'a Self> {
module_def
.views
.get(view_name)
.and_then(|view| view.get_param_by_name(name))
}
}
impl ModuleDefLookup for ConstraintDef {
type Key<'a> = &'a RawIdentifier;
fn key(&self) -> Self::Key<'_> {
&self.name
}
fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
module_def.stored_in_table_def(key)?.constraints.get(key)
}
}
impl ModuleDefLookup for RawRowLevelSecurityDefV9 {
type Key<'a> = &'a RawSql;
fn key(&self) -> Self::Key<'_> {
&self.sql
}
fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
module_def.row_level_security_raw.get(key)
}
}
impl ModuleDefLookup for ScheduleDef {
type Key<'a> = &'a Identifier;
fn key(&self) -> Self::Key<'_> {
&self.name
}
fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
let schedule = module_def.stored_in_table_def(key.as_raw())?.schedule.as_ref()?;
if &schedule.name == key {
Some(schedule)
} else {
None
}
}
}
impl ModuleDefLookup for TypeDef {
type Key<'a> = &'a ScopedTypeName;
fn key(&self) -> Self::Key<'_> {
&self.accessor_name
}
fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
module_def.types.get(key)
}
}
impl ModuleDefLookup for ReducerDef {
type Key<'a> = &'a ReducerName;
fn key(&self) -> Self::Key<'_> {
&self.name
}
fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
let key = &**key;
module_def.reducers.get(key)
}
}
impl ModuleDefLookup for ProcedureDef {
type Key<'a> = &'a Identifier;
fn key(&self) -> Self::Key<'_> {
&self.name
}
fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
let key = &**key;
module_def.procedures.get(key)
}
}
impl ModuleDefLookup for ViewDef {
type Key<'a> = &'a Identifier;
fn key(&self) -> Self::Key<'_> {
&self.name
}
fn lookup<'a>(view_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> {
view_def.views.get(key)
}
}
fn to_raw<Def, RawDef, Name>(data: HashMap<Name, Def>) -> Vec<RawDef>
where
Def: ModuleDefLookup + Into<RawDef>,
Name: Eq + Ord + 'static,
{
let sorted: BTreeMap<Name, Def> = data.into_iter().collect();
sorted.into_values().map_into().collect()
}
#[cfg(test)]
mod tests {
use crate::{def::validate::tests::expect_identifier, error::ValidationError};
use super::*;
use proptest::prelude::*;
use spacetimedb_data_structures::{expect_error_matching, map::HashCollectionExt as _};
use spacetimedb_lib::db::raw_def::v9::RawModuleDefV9Builder;
proptest! {
#[test]
fn to_raw_deterministic(vec in prop::collection::vec(any::<u32>(), 0..5)) {
let mut map = HashMap::new();
let name = ScopedTypeName::try_new([], "fake_name").unwrap();
for k in vec {
let def = TypeDef { accessor_name: name.clone(), ty: AlgebraicTypeRef(k), custom_ordering: false };
map.insert(k, def);
}
let raw: Vec<RawTypeDefV9> = to_raw(map.clone());
let raw2: Vec<RawTypeDefV9> = to_raw(map);
prop_assert_eq!(raw, raw2);
}
}
#[test]
fn validate_new_column_with_multiple_values() {
let mut old_builder = RawModuleDefV9Builder::new();
old_builder
.build_table_with_new_type(
"Apples",
ProductType::from([("id", AlgebraicType::U64), ("count", AlgebraicType::U16)]),
true,
)
.with_default_column_value(1, AlgebraicValue::U16(12))
.with_default_column_value(1, AlgebraicValue::U16(10))
.finish();
let result: Result<ModuleDef, ValidationErrors> = old_builder.finish().try_into();
let apples = expect_identifier("Apples");
expect_error_matching!(
result,
ValidationError::MultipleColumnDefaultValues {
table,
..
} => *table == apples.clone().into()
);
}
#[test]
fn validate_new_column_with_malformed_value() {
let mut old_builder = RawModuleDefV9Builder::new();
old_builder
.build_table_with_new_type(
"Apples",
ProductType::from([("id", AlgebraicType::U64), ("count", AlgebraicType::U16)]),
true,
)
.with_default_column_value(1, AlgebraicValue::Bool(false))
.with_default_column_value(1, AlgebraicValue::unit())
.finish();
let result: Result<ModuleDef, ValidationErrors> = old_builder.finish().try_into();
let apples = expect_identifier("Apples");
expect_error_matching!(
result,
ValidationError::ColumnDefaultValueMalformed { table, col_id, .. } => *table == apples.clone().into() && *col_id == ColId(1)
);
assert!(result.is_err_and(|e| e
.into_iter()
.filter(|e| matches!(e, ValidationError::ColumnDefaultValueMalformed { .. }))
.count()
== 2))
}
}