Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/bindings/tests/ui/reducers.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ help: the trait `SpacetimeType` is not implemented for `Test`
= help: the following other types implement trait `SpacetimeType`:
&T
()
(T, U)
AlgebraicType
AlgebraicTypeRef
Arc<T>
ArrayType
Box<T>
ColumnAttribute
and $N others
= note: required for `Test` to implement `ReducerArg`

Expand Down
4 changes: 2 additions & 2 deletions crates/bindings/tests/ui/tables.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ help: the trait `SpacetimeType` is not implemented for `Test`
= help: the following other types implement trait `SpacetimeType`:
&T
()
(T, U)
AlgebraicType
AlgebraicTypeRef
Alpha
Arc<T>
ArrayType
Box<T>
and $N others

error[E0277]: the trait bound `Test: Deserialize<'de>` is not satisfied
Expand Down Expand Up @@ -190,12 +190,12 @@ help: the trait `SpacetimeType` is not implemented for `Test`
= help: the following other types implement trait `SpacetimeType`:
&T
()
(T, U)
AlgebraicType
AlgebraicTypeRef
Alpha
Arc<T>
ArrayType
Box<T>
and $N others
= note: required for `Test` to implement `TableColumn`

Expand Down
4 changes: 2 additions & 2 deletions crates/bindings/tests/ui/views.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,12 @@ help: the trait `SpacetimeType` is not implemented for `NotSpacetimeType`
= help: the following other types implement trait `SpacetimeType`:
&T
()
(T, U)
AlgebraicType
AlgebraicTypeRef
Arc<T>
ArrayType
Box<T>
ColumnAttribute
and $N others
= note: required for `Option<NotSpacetimeType>` to implement `ViewReturn`

Expand Down Expand Up @@ -377,12 +377,12 @@ help: the trait `SpacetimeType` is not implemented for `NotSpacetimeType`
= help: the following other types implement trait `SpacetimeType`:
&T
()
(T, U)
AlgebraicType
AlgebraicTypeRef
Arc<T>
ArrayType
Box<T>
ColumnAttribute
and $N others
= note: required for `Option<NotSpacetimeType>` to implement `SpacetimeType`

Expand Down
11 changes: 11 additions & 0 deletions crates/lib/src/db/raw_def/v10.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ pub enum RawModuleDefV10Section {

/// Names provided explicitly by the user that do not follow from the case conversion policy.
ExplicitNames(ExplicitNames),

/// Mounted submodules, keyed by the namespace they are mounted under.
Mounts(Vec<(String, RawModuleDefV10)>),
}

#[derive(Debug, Clone, Copy, Default, SpacetimeType)]
Expand Down Expand Up @@ -510,6 +513,14 @@ pub struct RawViewDefV10 {
}

impl RawModuleDefV10 {
/// Get the mounted submodules for this module definition.
pub fn mounts(&self) -> Option<&Vec<(String, RawModuleDefV10)>> {
self.sections.iter().find_map(|s| match s {
RawModuleDefV10Section::Mounts(mounts) => Some(mounts),
_ => None,
})
}

/// Get the types section, if present.
pub fn types(&self) -> Option<&Vec<RawTypeDefV10>> {
self.sections.iter().find_map(|s| match s {
Expand Down
12 changes: 12 additions & 0 deletions crates/sats/src/typespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,18 @@ impl_st!([T] Vec<T>, ts => <[T]>::make_type(ts));
impl_st!([T, const N: usize] SmallVec<[T; N]>, ts => <[T]>::make_type(ts));
impl_st!([T] Option<T>, ts => AlgebraicType::option(T::make_type(ts)));

// SATS derives need tuples to have a structural product representation so
// tuple payloads like `(String, RawModuleDefV10)` can appear in wire types.
impl<T, U> SpacetimeType for (T, U)
where
T: SpacetimeType,
U: SpacetimeType,
{
fn make_type<S: TypespaceBuilder>(ts: &mut S) -> AlgebraicType {
AlgebraicType::product([T::make_type(ts), U::make_type(ts)])
}
}

impl_st!([] spacetimedb_primitives::ArgId, AlgebraicType::U64);
impl_st!([] spacetimedb_primitives::ColId, AlgebraicType::U16);
impl_st!([] spacetimedb_primitives::TableId, AlgebraicType::U32);
Expand Down
18 changes: 18 additions & 0 deletions crates/schema/src/def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ pub struct ModuleDef {
/// was authored under.
#[allow(unused)]
raw_module_def_version: RawModuleDefVersion,

/// Mounted submodules, keyed by the namespace they are mounted under.
mounts: Vec<(String, ModuleDef)>,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
Expand All @@ -167,6 +170,11 @@ impl ModuleDef {
self.raw_module_def_version
}

/// The mounted submodules of the module definition.
pub fn mounts(&self) -> &[(String, ModuleDef)] {
&self.mounts
}

/// The tables of the module definition.
pub fn tables(&self) -> impl Iterator<Item = &TableDef> {
self.tables.values()
Expand Down Expand Up @@ -437,6 +445,7 @@ impl From<ModuleDef> for RawModuleDefV9 {
row_level_security_raw,
procedures,
raw_module_def_version: _,
mounts: _,
} = val;

// Extract column defaults from tables before consuming tables
Expand Down Expand Up @@ -493,6 +502,7 @@ impl From<ModuleDef> for RawModuleDefV10 {
row_level_security_raw,
procedures,
raw_module_def_version: _,
mounts,
} = val;

let mut sections = Vec::new();
Expand Down Expand Up @@ -605,6 +615,14 @@ impl From<ModuleDef> for RawModuleDefV10 {
// Always emit ExplicitNames so canonical names survive the round-trip.
sections.push(RawModuleDefV10Section::ExplicitNames(explicit_names));

let mounts: Vec<_> = mounts
.into_iter()
.map(|(namespace, module)| (namespace, module.into()))
.collect();
if !mounts.is_empty() {
sections.push(RawModuleDefV10Section::Mounts(mounts));
}

RawModuleDefV10 { sections }
}
}
Expand Down
64 changes: 61 additions & 3 deletions crates/schema/src/def/validate/v10.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ pub fn validate(def: RawModuleDefV10) -> Result<ModuleDef> {
.cloned()
.map(ExplicitNamesLookup::new)
.unwrap_or_default();
let mounts = def
.mounts()
.into_iter()
.flat_map(|mounts| mounts.iter().cloned())
.map(validate_mount)
.collect_all_errors::<Vec<_>>();

// Original `typespace` needs to be preserved to be assign `accesor_name`s to columns.
let typespace_with_accessor_names = typespace.clone();
Expand Down Expand Up @@ -263,8 +269,12 @@ pub fn validate(def: RawModuleDefV10) -> Result<ModuleDef> {
.map(|rls| (rls.sql.clone(), rls.to_owned()))
.collect();

let (tables, types, reducers, procedures, views) =
(tables_types_reducers_procedures_views).map_err(|errors| errors.sort_deduplicate())?;
let (tables, types, reducers, procedures, views, mounts) = (tables_types_reducers_procedures_views, mounts)
.combine_errors()
.map(|((tables, types, reducers, procedures, views), mounts)| {
(tables, types, reducers, procedures, views, mounts)
})
.map_err(|errors: ValidationErrors| errors.sort_deduplicate())?;

let typespace_for_generate = typespace_for_generate.finish();

Expand All @@ -281,9 +291,17 @@ pub fn validate(def: RawModuleDefV10) -> Result<ModuleDef> {
lifecycle_reducers,
procedures,
raw_module_def_version: RawModuleDefVersion::V10,
mounts,
})
}

fn validate_mount((namespace, module): (String, RawModuleDefV10)) -> Result<(String, ModuleDef)> {
Identifier::new(namespace.clone().into())
.map_err(|error| ValidationErrors::from(ValidationError::IdentifierError { error }))?;

Ok((namespace, validate(module)?))
}

/// Change the visibility of scheduled functions and lifecycle reducers to Internal.
///
fn change_scheduled_functions_and_lifetimes_visibility(
Expand Down Expand Up @@ -877,7 +895,9 @@ mod tests {

use itertools::Itertools;
use spacetimedb_data_structures::expect_error_matching;
use spacetimedb_lib::db::raw_def::v10::{CaseConversionPolicy, RawModuleDefV10Builder};
use spacetimedb_lib::db::raw_def::v10::{
CaseConversionPolicy, RawModuleDefV10, RawModuleDefV10Builder, RawModuleDefV10Section,
};
use spacetimedb_lib::db::raw_def::v9::{btree, direct, hash};
use spacetimedb_lib::db::raw_def::*;
use spacetimedb_lib::ScheduleAt;
Expand Down Expand Up @@ -1256,6 +1276,44 @@ mod tests {
});
}

#[test]
fn validates_mounted_submodules_recursively() {
let mut mounted_builder = RawModuleDefV10Builder::new();
mounted_builder
.build_table_with_new_type("Sessions", ProductType::from([("id", AlgebraicType::U64)]), true)
.finish();

let raw = RawModuleDefV10 {
sections: vec![RawModuleDefV10Section::Mounts(vec![(
"authlib".to_string(),
mounted_builder.finish(),
)])],
};

let def: ModuleDef = raw.try_into().expect("mounted module should validate");
let mounts = def.mounts();

assert_eq!(mounts.len(), 1);
assert_eq!(mounts[0].0, "authlib");
assert!(mounts[0].1.table(&expect_identifier("sessions")).is_some());
}

#[test]
fn invalid_mount_namespace() {
let raw = RawModuleDefV10 {
sections: vec![RawModuleDefV10Section::Mounts(vec![(
"".to_string(),
RawModuleDefV10::default(),
)])],
};

let result: Result<ModuleDef> = raw.try_into();

expect_error_matching!(result, ValidationError::IdentifierError { error } => {
error == &IdentifierError::Empty {}
});
}

#[test]
fn invalid_unique_constraint_column_ref() {
let mut builder = RawModuleDefV10Builder::new();
Expand Down
1 change: 1 addition & 0 deletions crates/schema/src/def/validate/v9.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ pub fn validate(def: RawModuleDefV9) -> Result<ModuleDef> {
lifecycle_reducers,
procedures,
raw_module_def_version: RawModuleDefVersion::V9OrEarlier,
mounts: Vec::new(),
})
}

Expand Down
Loading