diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 2c69e0ef06e..89c9fa7c21c 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -184,7 +184,8 @@ jobs: runs-on: spacetimedb-linux timeout-minutes: 20 # on a successful run, runs in 8 minutes container: - image: rust:1.93.0 + # !rust-toolchain-sync + image: rust:1.98.1 options: --privileged # disable until we fix the benchmarks if: false diff --git a/Cargo.lock b/Cargo.lock index a7215a04f4a..fefdb34fa47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8397,6 +8397,7 @@ dependencies = [ "enum-map", "hex", "insta", + "paste", "proptest", "proptest-derive", "ron", diff --git a/Cargo.toml b/Cargo.toml index 1aa13fa971b..52915a70cab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,7 +143,7 @@ debug = true version = "2.10.1" edition = "2024" # update rust-toolchain.toml too! -rust-version = "1.93.0" +rust-version = "1.98.1" [workspace.dependencies] spacetimedb = { path = "crates/bindings", version = "=2.10.1" } diff --git a/crates/bench/Dockerfile b/crates/bench/Dockerfile index 0f64f819c82..64919db3aae 100644 --- a/crates/bench/Dockerfile +++ b/crates/bench/Dockerfile @@ -2,8 +2,8 @@ # Set up to run from linux / WSL (running from a windows file system will be extremely slow). # See the README for commands to run. -# sync with: ../../rust-toolchain.toml -FROM rust:1.93.0 +# !rust-toolchain-sync +FROM rust:1.98.1 RUN apt-get update && \ apt-get install -y valgrind bash && \ diff --git a/crates/cli/src/subcommands/dev.rs b/crates/cli/src/subcommands/dev.rs index 95cc579182f..d24344b6490 100644 --- a/crates/cli/src/subcommands/dev.rs +++ b/crates/cli/src/subcommands/dev.rs @@ -1270,7 +1270,7 @@ async fn select_database(config: &Config, server: &str, token: &str) -> Result = stream::iter(result.identities.into_iter()) + let databases: Vec = stream::iter(result.identities) .map(|identity_str| { let config = config.clone(); async move { diff --git a/crates/cli/src/subcommands/repl.rs b/crates/cli/src/subcommands/repl.rs index 6561475ca92..9e4d7e5b50d 100644 --- a/crates/cli/src/subcommands/repl.rs +++ b/crates/cli/src/subcommands/repl.rs @@ -61,7 +61,7 @@ pub(crate) async fn exec(con: Connection, format: Format) -> Result<(), anyhow:: let api = ClientApi::new(con); loop { - let readline = rl.readline(&format!("🪐{}>", &database).green()); + let readline = rl.readline(&format!("🪐{}>", database).green()); match readline { Ok(line) => match line.as_str() { ".exit" => break, diff --git a/crates/codegen/src/unrealcpp.rs b/crates/codegen/src/unrealcpp.rs index a521e739180..f5d03e3f3de 100644 --- a/crates/codegen/src/unrealcpp.rs +++ b/crates/codegen/src/unrealcpp.rs @@ -416,7 +416,7 @@ impl Lang for UnrealCpp<'_> { let name = type_ref_name(self.module_prefix, module, typ.ty); let filename = format!( "Source/{}/Public/ModuleBindings/Types/{}Type.g.h", - self.module_name, &name + self.module_name, name ); let code: String = match &module.typespace_for_generate()[typ.ty] { AlgebraicTypeDef::PlainEnum(plain_enum) => autogen_cpp_enum(&name, plain_enum), diff --git a/crates/commitlog/src/segment.rs b/crates/commitlog/src/segment.rs index c810e2399ae..c67246f8cf3 100644 --- a/crates/commitlog/src/segment.rs +++ b/crates/commitlog/src/segment.rs @@ -16,7 +16,7 @@ use crate::{ Options, }; -pub const MAGIC: [u8; 6] = [b'(', b'd', b's', b')', b'^', b'2']; +pub const MAGIC: [u8; 6] = *b"(ds)^2"; pub const DEFAULT_LOG_FORMAT_VERSION: u8 = 1; pub const DEFAULT_CHECKSUM_ALGORITHM: u8 = CHECKSUM_ALGORITHM_CRC32C; diff --git a/crates/core/src/host/instance_env.rs b/crates/core/src/host/instance_env.rs index d24876e6883..355da050691 100644 --- a/crates/core/src/host/instance_env.rs +++ b/crates/core/src/host/instance_env.rs @@ -325,7 +325,7 @@ impl InstanceEnv { /// End a console timer by logging the span at INFO level. pub(crate) fn console_timer_end(&self, span: &TimingSpan, function: Option<&str>) { let elapsed = span.start.elapsed(); - let message = format!("Timing span {:?}: {:?}", &span.name, elapsed); + let message = format!("Timing span {:?}: {:?}", span.name, elapsed); self.console_log_simple_message(LogLevel::Info, function, &message); } diff --git a/crates/core/src/host/v8/error.rs b/crates/core/src/host/v8/error.rs index 54095f657cf..aa462506bef 100644 --- a/crates/core/src/host/v8/error.rs +++ b/crates/core/src/host/v8/error.rs @@ -520,7 +520,7 @@ impl fmt::Display for JsStackTraceFrame { // TODO(v8): make it more like chrome in the future. f.write_fmt(format_args!( "at {} ({}:{}:{})", - fn_name, script_name, &self.line, &self.column + fn_name, script_name, self.line, self.column ))?; if self.is_ctor { diff --git a/crates/datastore/src/locking_tx_datastore/datastore.rs b/crates/datastore/src/locking_tx_datastore/datastore.rs index 7293a81fba0..3230ec8f7d6 100644 --- a/crates/datastore/src/locking_tx_datastore/datastore.rs +++ b/crates/datastore/src/locking_tx_datastore/datastore.rs @@ -229,9 +229,8 @@ impl Locking { /// error. pub fn take_snapshot(&self, repo: &DynSnapshotRepo) -> Result> { Self::take_snapshot_internal(&self.committed_state, repo)? - .map(|(_offset, snap)| snap.sync_all()) + .map(|(_offset, snap)| snap.sync_all().map_err(Into::into)) .transpose() - .map_err(Into::into) } pub fn assert_system_tables_match(&self) -> Result<()> { @@ -3014,7 +3013,7 @@ pub(crate) mod tests { fn assert_rows(datastore: &Locking, table_id: TableId, rows: Vec) -> ResultTest<()> { let tx = begin_tx(datastore); - for (actual, expected) in datastore.iter_tx(&tx, table_id)?.zip_eq(rows.into_iter()) { + for (actual, expected) in datastore.iter_tx(&tx, table_id)?.zip_eq(rows) { assert_eq!(actual.to_bsatn_vec()?, expected.to_bsatn_vec()?); } Ok(()) diff --git a/crates/dst/src/schema.rs b/crates/dst/src/schema.rs index 641281db3c3..80c98dabe4a 100644 --- a/crates/dst/src/schema.rs +++ b/crates/dst/src/schema.rs @@ -456,12 +456,12 @@ mod tests { }], }; - let raw = to_raw_def(&schema); + let raw = to_raw_def(&schema).into_sections(); // Should have Typespace, Types, and Tables sections. - assert!(raw.typespace().is_some()); - assert!(raw.types().is_some()); - let tables = raw.tables().unwrap(); + assert!(raw.typespace.is_some()); + assert!(raw.types.is_some()); + let tables = raw.tables.unwrap(); assert_eq!(tables.len(), 1); let t = &tables[0]; diff --git a/crates/engine/src/relational_db.rs b/crates/engine/src/relational_db.rs index 19cda5b6fbe..800148f9251 100644 --- a/crates/engine/src/relational_db.rs +++ b/crates/engine/src/relational_db.rs @@ -627,13 +627,10 @@ impl RelationalDB { // Try to restore from any snapshot that was taken within the // range `(min_commitlog_offset + 1)..=durable_tx_offset`. let mut upper_bound = durable_tx_offset; - loop { - let Some(snapshot_offset) = snapshot_repo - .latest_snapshot_older_than(upper_bound) - .map_err(Box::new)? - else { - break; - }; + while let Some(snapshot_offset) = snapshot_repo + .latest_snapshot_older_than(upper_bound) + .map_err(Box::new)? + { if min_commitlog_offset > 0 && min_commitlog_offset > snapshot_offset + 1 { log::debug!("snapshot_offset={snapshot_offset} min_commitlog_offset={min_commitlog_offset}"); break; diff --git a/crates/engine/src/update.rs b/crates/engine/src/update.rs index 5b9e6e0fde2..ab864688d1c 100644 --- a/crates/engine/src/update.rs +++ b/crates/engine/src/update.rs @@ -705,7 +705,7 @@ pub fn create_table_from_view_def( view_def: &ViewDef, ) -> anyhow::Result<()> { stdb.create_view(tx, module_def, view_def) - .with_context(|| format!("failed to create table for view {}", &view_def.name))?; + .with_context(|| format!("failed to create table for view {}", view_def.name))?; Ok(()) } @@ -719,7 +719,7 @@ pub fn create_table_from_view_def_with_prefix( name_prefix: &NamespacePath, ) -> anyhow::Result<()> { stdb.create_view_with_prefix(tx, owning_def, view_def, name_prefix) - .with_context(|| format!("failed to create table for view {}{}", name_prefix, &view_def.name))?; + .with_context(|| format!("failed to create table for view {}{}", name_prefix, view_def.name))?; Ok(()) } diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index b07774b3ee6..237b64a528b 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -43,6 +43,7 @@ hex.workspace = true serde = { workspace = true, optional = true } blake3.workspace = true enum-map = { workspace = true, optional = true } +paste.workspace = true # For the 'proptest' feature. proptest = { workspace = true, optional = true } diff --git a/crates/lib/src/db/raw_def/v10.rs b/crates/lib/src/db/raw_def/v10.rs index b21d8470b84..1447ba1b7aa 100644 --- a/crates/lib/src/db/raw_def/v10.rs +++ b/crates/lib/src/db/raw_def/v10.rs @@ -8,6 +8,7 @@ use crate::db::raw_def::v9::{Lifecycle, RawIndexAlgorithm, TableAccess, TableType}; use core::fmt; use spacetimedb_primitives::{ColId, ColList}; +use spacetimedb_sats::hash::Hash; use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::typespace::TypespaceBuilder; use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, AlgebraicValue, ProductType, SpacetimeType, Typespace}; @@ -41,68 +42,147 @@ pub struct RawModuleDefV10 { pub sections: Vec, } -/// A section of a V10 module definition. -/// -/// New variants MUST be added to the END of this enum, to maintain ABI compatibility. -#[derive(Debug, Clone, SpacetimeType)] -#[sats(crate = crate)] -#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))] -#[non_exhaustive] -pub enum RawModuleDefV10Section { - /// The `Typespace` used by the module. - /// - /// `AlgebraicTypeRef`s in other sections refer to this typespace. - /// See [`crate::db::raw_def::v9::RawModuleDefV9::typespace`] for validation requirements. - Typespace(Typespace), +macro_rules! with_v10_sections { + ($mac:ident) => { + // New variants MUST be added to the END of this enum, to maintain ABI compatibility. + $mac! { + /// The `Typespace` used by the module. + /// + /// `AlgebraicTypeRef`s in other sections refer to this typespace. + /// See [`crate::db::raw_def::v9::RawModuleDefV9::typespace`] for validation requirements. + Typespace(Typespace), - /// Type definitions exported by the module. - Types(Vec), + /// Type definitions exported by the module. + Types(Vec), - /// Table definitions. - Tables(Vec), + /// Table definitions. + Tables(Vec), - /// Reducer definitions. - Reducers(Vec), + /// Reducer definitions. + Reducers(Vec), - /// Procedure definitions. - Procedures(Vec), + /// Procedure definitions. + Procedures(Vec), - /// View definitions. - Views(Vec), + /// View definitions. + Views(Vec), - /// Schedule definitions. - /// - /// Unlike V9 where schedules were embedded in table definitions, - /// V10 stores them in a dedicated section. - Schedules(Vec), + /// Schedule definitions. + /// + /// Unlike V9 where schedules were embedded in table definitions, + /// V10 stores them in a dedicated section. + Schedules(Vec), - /// Lifecycle reducer assignments. - /// - /// Unlike V9 where lifecycle was a field on reducers, - /// V10 stores lifecycle-to-reducer mappings separately. - LifeCycleReducers(Vec), + /// Lifecycle reducer assignments. + /// + /// Unlike V9 where lifecycle was a field on reducers, + /// V10 stores lifecycle-to-reducer mappings separately. + LifeCycleReducers(Vec), - RowLevelSecurity(Vec), //TODO: Add section for Event tables, and Case conversion before exposing this from module + RowLevelSecurity(Vec), //TODO: Add section for Event tables, and Case conversion before exposing this from module - /// Case conversion policy for identifiers in this module. - CaseConversionPolicy(CaseConversionPolicy), + /// Case conversion policy for identifiers in this module. + CaseConversionPolicy(CaseConversionPolicy), + + /// Names provided explicitly by the user that do not follow from the case conversion policy. + ExplicitNames(ExplicitNames), + + /// HTTP handler function definitions. + HttpHandlers(Vec), + + /// HTTP route definitions. + HttpRoutes(Vec), + + /// Primary key metadata for views. + ViewPrimaryKeys(Vec), + + /// Submodules, keyed by the namespace they are registered under. + Submodules(Vec), + + /// Migrations from old schema versions, keyed by that schema version's hash. + Migrations(Vec), + } + }; +} - /// Names provided explicitly by the user that do not follow from the case conversion policy. - ExplicitNames(ExplicitNames), +trait SectionPayload { + fn skip_serializing(&self) -> bool { + false + } +} - /// HTTP handler function definitions. - HttpHandlers(Vec), +impl SectionPayload for Vec { + fn skip_serializing(&self) -> bool { + self.is_empty() + } +} - /// HTTP route definitions. - HttpRoutes(Vec), +impl SectionPayload for Typespace { + fn skip_serializing(&self) -> bool { + self.types.is_empty() + } +} - /// Primary key metadata for views. - ViewPrimaryKeys(Vec), +impl SectionPayload for CaseConversionPolicy {} - /// Submodules, keyed by the namespace they are registered under. - Submodules(Vec), +impl SectionPayload for ExplicitNames { + fn skip_serializing(&self) -> bool { + self.entries.is_empty() + } } +macro_rules! define_section_types { + ($($(#[$attr:meta])* $name:ident($payload:ty),)*) => { + /// A section of a V10 module definition. + #[derive(Debug, Clone, SpacetimeType)] + #[sats(crate = crate)] + #[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))] + #[non_exhaustive] + pub enum RawModuleDefV10Section { + $( $(#[$attr])* $name($payload), )* + } + + paste::paste! { + #[derive(Debug, Default)] + pub struct RawModuleDefV10Sections { + $($(#[$attr])* pub [< $name:snake >]: Option<$payload>,)* + } + + impl FromIterator for RawModuleDefV10Sections { + fn from_iter>(iter: I) -> Self { + let mut sections = Self::default(); + for section in iter { + match section { + // TODO(noa): should we error when coming across duplicate sections? merge them? + $(RawModuleDefV10Section::$name(payload) => { sections.[< $name:snake >].get_or_insert(payload); })* + } + } + sections + } + } + + impl RawModuleDefV10Sections { + #[allow(path_statements)] + const NUM_SECTIONS: usize = [$({ RawModuleDefV10Section::$name; }),*].len(); + } + + impl IntoIterator for RawModuleDefV10Sections { + type Item = RawModuleDefV10Section; + type IntoIter = std::iter::Flatten, { Self::NUM_SECTIONS }>>; + fn into_iter(self) -> Self::IntoIter { + [ + $(self.[< $name:snake >].filter(|x| !SectionPayload::skip_serializing(x)).map(RawModuleDefV10Section::$name),)* + ] + .into_iter() + .flatten() + } + } + } + }; +} + +with_v10_sections!(define_section_types); + #[derive(Debug, Clone, SpacetimeType)] #[sats(crate = crate)] #[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))] @@ -569,133 +649,16 @@ pub struct RawViewPrimaryKeyDefV10 { } impl RawModuleDefV10 { - /// Get the submodules for this module definition. - pub fn submodules(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::Submodules(submodules) => Some(submodules), - _ => None, - }) - } - - /// Get the types section, if present. - pub fn types(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::Types(types) => Some(types), - _ => None, - }) - } - - /// Get the tables section, if present. - pub fn tables(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::Tables(tables) => Some(tables), - _ => None, - }) - } - - /// Get the typespace section, if present. - pub fn typespace(&self) -> Option<&Typespace> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::Typespace(ts) => Some(ts), - _ => None, - }) - } - - /// Get the reducers section, if present. - pub fn reducers(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::Reducers(reducers) => Some(reducers), - _ => None, - }) - } - - /// Get the procedures section, if present. - pub fn procedures(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::Procedures(procedures) => Some(procedures), - _ => None, - }) - } - - /// Get the views section, if present. - pub fn views(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::Views(views) => Some(views), - _ => None, - }) - } - - /// Get the view primary keys section, if present. - pub fn view_primary_keys(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::ViewPrimaryKeys(primary_keys) => Some(primary_keys), - _ => None, - }) - } - - /// Get the schedules section, if present. - pub fn schedules(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::Schedules(schedules) => Some(schedules), - _ => None, - }) - } - - /// Get the lifecycle reducers section, if present. - pub fn lifecycle_reducers(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::LifeCycleReducers(lcrs) => Some(lcrs), - _ => None, - }) - } - - pub fn tables_mut_for_tests(&mut self) -> &mut Vec { - self.sections - .iter_mut() - .find_map(|s| match s { - RawModuleDefV10Section::Tables(tables) => Some(tables), - _ => None, - }) - .expect("Tables section must exist for tests") - } - - // Get the row-level security section, if present. - pub fn row_level_security(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::RowLevelSecurity(rls) => Some(rls), - _ => None, - }) - } - - pub fn case_conversion_policy(&self) -> CaseConversionPolicy { - self.sections - .iter() - .find_map(|s| match s { - RawModuleDefV10Section::CaseConversionPolicy(policy) => Some(*policy), - _ => None, - }) - .unwrap_or_default() - } - - pub fn explicit_names(&self) -> Option<&ExplicitNames> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::ExplicitNames(names) => Some(names), - _ => None, - }) - } - - pub fn http_handlers(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::HttpHandlers(handlers) => Some(handlers), - _ => None, - }) + /// Convert the vec of sections into a struct for easier processing. + pub fn into_sections(self) -> RawModuleDefV10Sections { + self.sections.into_iter().collect() } +} - pub fn http_routes(&self) -> Option<&Vec> { - self.sections.iter().find_map(|s| match s { - RawModuleDefV10Section::HttpRoutes(routes) => Some(routes), - _ => None, - }) +impl From for RawModuleDefV10 { + fn from(sections: RawModuleDefV10Sections) -> Self { + let sections = sections.into_iter().collect(); + Self { sections } } } @@ -703,7 +666,7 @@ impl RawModuleDefV10 { #[derive(Default)] pub struct RawModuleDefV10Builder { /// The module definition being built. - module: RawModuleDefV10, + module: RawModuleDefV10Sections, /// The type map from `T: 'static` Rust types to sats types. type_map: BTreeMap, @@ -717,154 +680,47 @@ impl RawModuleDefV10Builder { /// Get mutable access to the typespace section, creating it if missing. fn typespace_mut(&mut self) -> &mut Typespace { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::Typespace(_))) - .unwrap_or_else(|| { - self.module - .sections - .push(RawModuleDefV10Section::Typespace(Typespace::EMPTY.clone())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::Typespace(ts) => ts, - _ => unreachable!("Just ensured Typespace section exists"), - } + self.module.typespace.get_or_insert_default() + } + + /// Get mutable access to the tables section, creating it if missing. + fn tables_mut(&mut self) -> &mut Vec { + self.module.tables.get_or_insert_default() } /// Get mutable access to the reducers section, creating it if missing. fn reducers_mut(&mut self) -> &mut Vec { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::Reducers(_))) - .unwrap_or_else(|| { - self.module.sections.push(RawModuleDefV10Section::Reducers(Vec::new())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::Reducers(reducers) => reducers, - _ => unreachable!("Just ensured Reducers section exists"), - } + self.module.reducers.get_or_insert_default() } /// Get mutable access to the procedures section, creating it if missing. fn procedures_mut(&mut self) -> &mut Vec { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::Procedures(_))) - .unwrap_or_else(|| { - self.module - .sections - .push(RawModuleDefV10Section::Procedures(Vec::new())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::Procedures(procedures) => procedures, - _ => unreachable!("Just ensured Procedures section exists"), - } + self.module.procedures.get_or_insert_default() } /// Get mutable access to the views section, creating it if missing. fn views_mut(&mut self) -> &mut Vec { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::Views(_))) - .unwrap_or_else(|| { - self.module.sections.push(RawModuleDefV10Section::Views(Vec::new())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::Views(views) => views, - _ => unreachable!("Just ensured Views section exists"), - } + self.module.views.get_or_insert_default() } /// Get mutable access to the view primary keys section, creating it if missing. fn view_primary_keys_mut(&mut self) -> &mut Vec { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::ViewPrimaryKeys(_))) - .unwrap_or_else(|| { - self.module - .sections - .push(RawModuleDefV10Section::ViewPrimaryKeys(Vec::new())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::ViewPrimaryKeys(primary_keys) => primary_keys, - _ => unreachable!("Just ensured ViewPrimaryKeys section exists"), - } + self.module.view_primary_keys.get_or_insert_default() } /// Get mutable access to the schedules section, creating it if missing. fn schedules_mut(&mut self) -> &mut Vec { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::Schedules(_))) - .unwrap_or_else(|| { - self.module.sections.push(RawModuleDefV10Section::Schedules(Vec::new())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::Schedules(schedules) => schedules, - _ => unreachable!("Just ensured Schedules section exists"), - } + self.module.schedules.get_or_insert_default() } /// Get mutable access to the lifecycle reducers section, creating it if missing. fn lifecycle_reducers_mut(&mut self) -> &mut Vec { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::LifeCycleReducers(_))) - .unwrap_or_else(|| { - self.module - .sections - .push(RawModuleDefV10Section::LifeCycleReducers(Vec::new())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::LifeCycleReducers(lcrs) => lcrs, - _ => unreachable!("Just ensured LifeCycleReducers section exists"), - } + self.module.life_cycle_reducers.get_or_insert_default() } /// Get mutable access to the types section, creating it if missing. fn types_mut(&mut self) -> &mut Vec { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::Types(_))) - .unwrap_or_else(|| { - self.module.sections.push(RawModuleDefV10Section::Types(Vec::new())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::Types(types) => types, - _ => unreachable!("Just ensured Types section exists"), - } + self.module.types.get_or_insert_default() } /// Add a type to the in-progress module. @@ -876,82 +732,22 @@ impl RawModuleDefV10Builder { /// Get mutable access to the row-level security section, creating it if missing. fn row_level_security_mut(&mut self) -> &mut Vec { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::RowLevelSecurity(_))) - .unwrap_or_else(|| { - self.module - .sections - .push(RawModuleDefV10Section::RowLevelSecurity(Vec::new())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::RowLevelSecurity(rls) => rls, - _ => unreachable!("Just ensured RowLevelSecurity section exists"), - } + self.module.row_level_security.get_or_insert_default() } /// Get mutable access to the case conversion policy, creating it if missing. fn explicit_names_mut(&mut self) -> &mut ExplicitNames { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::ExplicitNames(_))) - .unwrap_or_else(|| { - self.module - .sections - .push(RawModuleDefV10Section::ExplicitNames(ExplicitNames::default())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::ExplicitNames(names) => names, - _ => unreachable!("Just ensured ExplicitNames section exists"), - } + self.module.explicit_names.get_or_insert_default() } /// Get mutable access to the HTTP handlers section, creating it if missing. fn http_handlers_mut(&mut self) -> &mut Vec { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::HttpHandlers(_))) - .unwrap_or_else(|| { - self.module - .sections - .push(RawModuleDefV10Section::HttpHandlers(Vec::new())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::HttpHandlers(handlers) => handlers, - _ => unreachable!("Just ensured HttpHandlers section exists"), - } + self.module.http_handlers.get_or_insert_default() } /// Get mutable access to the HTTP routes section, creating it if missing. fn http_routes_mut(&mut self) -> &mut Vec { - let idx = self - .module - .sections - .iter() - .position(|s| matches!(s, RawModuleDefV10Section::HttpRoutes(_))) - .unwrap_or_else(|| { - self.module - .sections - .push(RawModuleDefV10Section::HttpRoutes(Vec::new())); - self.module.sections.len() - 1 - }); - - match &mut self.module.sections[idx] { - RawModuleDefV10Section::HttpRoutes(routes) => routes, - _ => unreachable!("Just ensured HttpRoutes section exists"), - } + self.module.http_routes.get_or_insert_default() } /// Create a table builder. @@ -964,7 +760,7 @@ impl RawModuleDefV10Builder { ) -> RawTableDefBuilderV10<'_> { let source_name = source_name.into(); RawTableDefBuilderV10 { - module: &mut self.module, + module: self, table: RawTableDefV10 { source_name, product_type_ref, @@ -1244,18 +1040,13 @@ impl RawModuleDefV10Builder { /// was stored under the original naming convention). pub fn set_case_conversion_policy(&mut self, policy: CaseConversionPolicy) { // Remove any existing policy section. - self.module - .sections - .retain(|s| !matches!(s, RawModuleDefV10Section::CaseConversionPolicy(_))); - self.module - .sections - .push(RawModuleDefV10Section::CaseConversionPolicy(policy)); + self.module.case_conversion_policy = Some(policy); } /// Finish building, consuming the builder and returning the module. /// The module should be validated before use. pub fn finish(self) -> RawModuleDefV10 { - self.module + self.module.into() } } @@ -1330,7 +1121,7 @@ pub fn sats_name_to_scoped_name_v10(sats_name: &str) -> RawScopedTypeNameV10 { /// Builder for a `RawTableDefV10`. pub struct RawTableDefBuilderV10<'a> { - module: &'a mut RawModuleDefV10, + module: &'a mut RawModuleDefV10Builder, table: RawTableDefV10, } @@ -1442,23 +1233,8 @@ impl RawTableDefBuilderV10<'_> { pub fn finish(self) -> AlgebraicTypeRef { let product_type_ref = self.table.product_type_ref; - let tables = match self - .module - .sections - .iter_mut() - .find(|s| matches!(s, RawModuleDefV10Section::Tables(_))) - { - Some(RawModuleDefV10Section::Tables(t)) => t, - _ => { - self.module.sections.push(RawModuleDefV10Section::Tables(Vec::new())); - match self.module.sections.last_mut().expect("Just pushed Tables section") { - RawModuleDefV10Section::Tables(t) => t, - _ => unreachable!(), - } - } - }; + self.module.tables_mut().push(self.table); - tables.push(self.table); product_type_ref } @@ -1466,13 +1242,7 @@ impl RawTableDefBuilderV10<'_> { pub fn find_col_pos_by_name(&self, column: impl AsRef) -> Option { let column = column.as_ref(); - let typespace = self.module.sections.iter().find_map(|s| { - if let RawModuleDefV10Section::Typespace(ts) = s { - Some(ts) - } else { - None - } - })?; + let typespace = self.module.module.typespace.as_ref()?; typespace .get(self.table.product_type_ref)? @@ -1483,3 +1253,11 @@ impl RawTableDefBuilderV10<'_> { .map(|i| ColId(i as u16)) } } + +#[derive(Debug, Clone, SpacetimeType)] +#[sats(crate = crate)] +#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))] +pub struct RawMigrationDefV10 { + pub schema_hash: Hash, + pub dropped: RawModuleDefV10, +} diff --git a/crates/pg/src/pg_server.rs b/crates/pg/src/pg_server.rs index f1de39f4efd..361da919418 100644 --- a/crates/pg/src/pg_server.rs +++ b/crates/pg/src/pg_server.rs @@ -157,6 +157,7 @@ where let params = self.cached.lock().await.clone().unwrap(); let name_or_identity = database::NameOrIdentity::Name(DatabaseName(params.database.clone())); let database_identity = response(name_or_identity.resolve(&self.ctx).await, ¶ms.database).await?; + #[expect(clippy::result_large_err)] let database = response( self.ctx .get_database_by_identity(&database_identity) diff --git a/crates/schema/src/def.rs b/crates/schema/src/def.rs index 521bcb35aa9..084428da868 100644 --- a/crates/schema/src/def.rs +++ b/crates/schema/src/def.rs @@ -33,9 +33,10 @@ use spacetimedb_data_structures::map::{Equivalent, HashMap}; use spacetimedb_lib::db::raw_def; use spacetimedb_lib::db::raw_def::v10::{ ExplicitNames, MethodOrAny, RawColumnDefaultValueV10, RawConstraintDefV10, RawHttpHandlerDefV10, - RawHttpRouteDefV10, RawIndexDefV10, RawLifeCycleReducerDefV10, RawModuleDefV10, RawModuleDefV10Section, - RawProcedureDefV10, RawReducerDefV10, RawRowLevelSecurityDefV10, RawScheduleDefV10, RawScopedTypeNameV10, - RawSequenceDefV10, RawSubmoduleV10, RawTableDefV10, RawTypeDefV10, RawViewDefV10, RawViewPrimaryKeyDefV10, + RawHttpRouteDefV10, RawIndexDefV10, RawLifeCycleReducerDefV10, RawMigrationDefV10, RawModuleDefV10, + RawModuleDefV10Sections, RawProcedureDefV10, RawReducerDefV10, RawRowLevelSecurityDefV10, RawScheduleDefV10, + RawScopedTypeNameV10, RawSequenceDefV10, RawSubmoduleV10, RawTableDefV10, RawTypeDefV10, RawViewDefV10, + RawViewPrimaryKeyDefV10, }; use spacetimedb_lib::db::raw_def::v9::{ Lifecycle, RawColumnDefaultValueV9, RawConstraintDataV9, RawConstraintDefV9, RawIndexAlgorithm, RawIndexDefV9, @@ -179,6 +180,9 @@ pub struct ModuleDef { /// Submodules, keyed by the namespace they are registered under. submodules: IndexMap, + + /// Migrations, keyed by the schema hash of the old module. + migrations: IndexMap, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -986,6 +990,7 @@ impl From for RawModuleDefV9 { http_routes: _, raw_module_def_version: _, submodules: _, + migrations: _, } = val; // Extract column defaults from tables before consuming tables @@ -1046,13 +1051,11 @@ impl From for RawModuleDefV10 { http_routes, raw_module_def_version: _, submodules, + migrations, } = val; - let mut sections = Vec::new(); let mut explicit_names = ExplicitNames::default(); - sections.push(RawModuleDefV10Section::Typespace(typespace)); - // Extract lifecycle reducer names before consuming reducers. let raw_lifecycle: Vec = lifecycle_reducers .into_iter() @@ -1067,9 +1070,6 @@ impl From for RawModuleDefV10 { .collect(); let raw_types: Vec = types.into_values().map(Into::into).collect(); - if !raw_types.is_empty() { - sections.push(RawModuleDefV10Section::Types(raw_types)); - } // Collect schedules from tables (V10 stores them in a separate section). // Also collect ExplicitNames for tables: accessor_name → source_name, name → canonical_name. @@ -1093,9 +1093,6 @@ impl From for RawModuleDefV10 { td.into() }) .collect(); - if !raw_tables.is_empty() { - sections.push(RawModuleDefV10Section::Tables(raw_tables)); - } // Collect ExplicitNames for reducers: accessor_name → source_name, name → canonical_name. // local name so re-validating this raw def doesn't reject dotted identifiers. @@ -1109,9 +1106,6 @@ impl From for RawModuleDefV10 { rd.into() }) .collect(); - if !raw_reducers.is_empty() { - sections.push(RawModuleDefV10Section::Reducers(raw_reducers)); - } // Collect ExplicitNames for procedures: accessor_name → source_name, name → canonical_name. let raw_procedures: Vec = procedures @@ -1124,9 +1118,6 @@ impl From for RawModuleDefV10 { pd.into() }) .collect(); - if !raw_procedures.is_empty() { - sections.push(RawModuleDefV10Section::Procedures(raw_procedures)); - } let raw_http_handlers: Vec = http_handlers .into_values() @@ -1134,21 +1125,15 @@ impl From for RawModuleDefV10 { source_name: hd.accessor_name.into(), }) .collect(); - if !raw_http_handlers.is_empty() { - sections.push(RawModuleDefV10Section::HttpHandlers(raw_http_handlers)); - } - if !http_routes.is_empty() { - let raw_http_routes: Vec = http_routes - .into_iter() - .map(|route| RawHttpRouteDefV10 { - handler_function: route.handler_name.into(), - method: route.method, - path: RawIdentifier::new(route.path.as_ref()), - }) - .collect(); - sections.push(RawModuleDefV10Section::HttpRoutes(raw_http_routes)); - } + let raw_http_routes: Vec = http_routes + .into_iter() + .map(|route| RawHttpRouteDefV10 { + handler_function: route.handler_name.into(), + method: route.method, + path: RawIdentifier::new(route.path.as_ref()), + }) + .collect(); // Collect ExplicitNames for views: accessor_name → source_name, name → canonical_name. let mut raw_view_primary_keys = Vec::new(); @@ -1182,28 +1167,8 @@ impl From for RawModuleDefV10 { vd.into() }) .collect(); - if !raw_views.is_empty() { - sections.push(RawModuleDefV10Section::Views(raw_views)); - } - if !raw_view_primary_keys.is_empty() { - sections.push(RawModuleDefV10Section::ViewPrimaryKeys(raw_view_primary_keys)); - } - - if !schedules.is_empty() { - sections.push(RawModuleDefV10Section::Schedules(schedules)); - } - - if !raw_lifecycle.is_empty() { - sections.push(RawModuleDefV10Section::LifeCycleReducers(raw_lifecycle)); - } let raw_rls: Vec = row_level_security_raw.into_values().collect(); - if !raw_rls.is_empty() { - sections.push(RawModuleDefV10Section::RowLevelSecurity(raw_rls)); - } - - // Always emit ExplicitNames so canonical names survive the round-trip. - sections.push(RawModuleDefV10Section::ExplicitNames(explicit_names)); let submodules: Vec<_> = submodules .into_iter() @@ -1212,11 +1177,35 @@ impl From for RawModuleDefV10 { module: module.into(), }) .collect(); - if !submodules.is_empty() { - sections.push(RawModuleDefV10Section::Submodules(submodules)); - } - RawModuleDefV10 { sections } + let migrations: Vec = migrations + .into_iter() + .map(|(schema_hash, dropped)| RawMigrationDefV10 { + schema_hash, + dropped: dropped.into(), + }) + .collect(); + + RawModuleDefV10Sections { + typespace: Some(typespace), + types: Some(raw_types), + tables: Some(raw_tables), + reducers: Some(raw_reducers), + procedures: Some(raw_procedures), + views: Some(raw_views), + schedules: Some(schedules), + life_cycle_reducers: Some(raw_lifecycle), + row_level_security: Some(raw_rls), + case_conversion_policy: None, + // Always emit ExplicitNames so canonical names survive the round-trip. + explicit_names: Some(explicit_names), + http_handlers: Some(raw_http_handlers), + http_routes: Some(raw_http_routes), + view_primary_keys: Some(raw_view_primary_keys), + submodules: Some(submodules), + migrations: Some(migrations), + } + .into() } } @@ -1326,6 +1315,7 @@ impl TableDef { impl From for RawTableDefV9 { fn from(val: TableDef) -> Self { + #[expect(clippy::unneeded_wildcard_pattern)] let TableDef { name, product_type_ref, @@ -2708,8 +2698,8 @@ mod tests { .finish(); let module_def: ModuleDef = builder.finish().try_into().expect("valid module"); - let raw = RawModuleDefV10::from(module_def); - let tables = raw.tables().expect("tables section"); + let raw = RawModuleDefV10::from(module_def).into_sections(); + let tables = raw.tables.expect("tables section"); let defaults = &tables[0].default_values; assert_eq!(defaults.len(), 1); assert_eq!(defaults[0].col_id, ColId(1)); diff --git a/crates/schema/src/def/validate/v10.rs b/crates/schema/src/def/validate/v10.rs index d6303f3a81c..aadacf23cf1 100644 --- a/crates/schema/src/def/validate/v10.rs +++ b/crates/schema/src/def/validate/v10.rs @@ -78,16 +78,31 @@ impl From for ValidationCase { /// Validate a `RawModuleDefV10` and convert it into a `ModuleDef`, /// or return a stream of errors if the definition is invalid. pub fn validate(def: RawModuleDefV10) -> Result { - let mut typespace = def.typespace().cloned().unwrap_or_else(|| Typespace::EMPTY.clone()); - let known_type_definitions = def.types().into_iter().flatten().map(|def| def.ty); - let case_policy = def.case_conversion_policy().into(); - let explicit_names = def - .explicit_names() - .cloned() - .map(ExplicitNamesLookup::new) - .unwrap_or_default(); - let view_primary_keys = def.view_primary_keys().cloned().unwrap_or_default(); - let submodules = validate_submodules(def.submodules().into_iter().flat_map(|s| s.iter().cloned()).collect()); + let RawModuleDefV10Sections { + typespace, + types, + tables, + reducers, + procedures, + views, + schedules, + life_cycle_reducers, + row_level_security, + case_conversion_policy, + explicit_names, + http_handlers, + http_routes, + view_primary_keys, + submodules, + migrations, + } = def.into_sections(); + + let mut typespace = typespace.unwrap_or_default(); + let known_type_definitions = types.iter().flatten().map(|def| def.ty); + let case_policy = case_conversion_policy.unwrap_or_default().into(); + let explicit_names = explicit_names.map_or_default(ExplicitNamesLookup::new); + let view_primary_keys = view_primary_keys.unwrap_or_default(); + let submodules = validate_submodules(submodules.into_iter().flatten().collect()); // Original `typespace` needs to be preserved to be assign `accesor_name`s to columns. let typespace_with_accessor_names = typespace.clone(); @@ -119,9 +134,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { // `combine_errors` or `collect_all_errors` on all the things we need to validate. // Sometimes it is unavoidable to use `?` early and this should be commented on. - let reducers = def - .reducers() - .cloned() + let reducers = reducers .into_iter() .flatten() .map(|reducer| validator.validate_reducer_def(reducer)) @@ -129,9 +142,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { // Later on, in `check_function_names_are_unique`, we'll transform this into an `IndexMap`. .collect_all_errors::>(); - let procedures = def - .procedures() - .cloned() + let procedures = procedures .into_iter() .flatten() .map(|procedure| { @@ -143,9 +154,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { // Later on, in `check_function_names_are_unique`, we'll transform this into an `IndexMap`. .collect_all_errors::>(); - let http_handlers = def - .http_handlers() - .cloned() + let http_handlers = http_handlers .into_iter() .flatten() .map(|handler| { @@ -155,9 +164,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { }) .collect_all_errors::>(); - let views = def - .views() - .cloned() + let views = views .into_iter() .flatten() .map(|view| { @@ -167,9 +174,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { }) .collect_all_errors(); - let tables = def - .tables() - .cloned() + let tables = tables .into_iter() .flatten() .map(|table| { @@ -180,9 +185,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { .collect_all_errors(); let mut refmap = HashMap::default(); - let types = def - .types() - .cloned() + let types = types .into_iter() .flatten() .map(|ty| { @@ -198,8 +201,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { .as_ref() .ok() .map(|tables_map| { - def.schedules() - .cloned() + schedules .into_iter() .flatten() .map(|schedule| validator.validate_schedule_def(schedule, tables_map)) @@ -212,8 +214,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { .as_ref() .ok() .map(|reducers_vec| { - def.lifecycle_reducers() - .cloned() + life_cycle_reducers .into_iter() .flatten() .map(|lifecycle_def| { @@ -243,9 +244,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { let http_handlers_and_routes = http_handlers.and_then(|handlers| { let handlers = check_http_handler_names_are_unique(handlers)?; - let routes = def - .http_routes() - .cloned() + let routes = http_routes .into_iter() .flatten() .map(|route| validator.validate_http_route_def(route, &handlers)) @@ -290,8 +289,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { .. } = validator.core; - let row_level_security_raw = def - .row_level_security() + let row_level_security_raw = row_level_security .into_iter() .flatten() .map(|rls| (rls.sql.clone(), rls.to_owned())) @@ -304,6 +302,20 @@ pub fn validate(def: RawModuleDefV10) -> Result { let typespace_for_generate = typespace_for_generate.finish(); + let migrations = migrations + .into_iter() + .flatten() + .map(|RawMigrationDefV10 { schema_hash, dropped }| { + let submodule_validation = dropped + .sections + .iter() + .all(|section| section.is_tables() || section.is_types() || section.is_typespace()) + .ok_or_else(|| ValidationError::InvalidMigrationSubmodule { schema_hash }.into()); + let ((), submodule) = (submodule_validation, validate(dropped)).combine_errors()?; + Ok((schema_hash, submodule)) + }) + .collect_all_errors()?; + let mut module_def = ModuleDef { // Set by `apply_namespace` below. path: NamespacePath::root(), @@ -322,6 +334,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { http_routes, raw_module_def_version: RawModuleDefVersion::V10, submodules, + migrations, }; // Submodules were validated in isolation, so their defs carry root-relative names. @@ -343,25 +356,15 @@ fn validate_submodules(submodules: Vec) -> Result 63 { - errors.push(ValidationError::NamespaceTooLong { - namespace: submodule.namespace.clone().into(), - len: submodule.namespace.len(), - }); - } - - let namespace = match Identifier::new(RawIdentifier::from(submodule.namespace.clone())) { - Ok(namespace) => namespace, - Err(error) => { - errors.push(ValidationError::IdentifierError { error }); + let namespace = match validate_namespace(submodule.namespace.into()) { + Ok(ns) => ns, + Err(e) => { + errors.extend(e); continue; } }; - if map.contains_key(&namespace) { - errors.push(ValidationError::DuplicateName { - name: submodule.namespace.into(), - }); + errors.push(ValidationError::DuplicateName { name: namespace.into() }); } else { match validate(submodule.module) { Ok(def) => { @@ -369,13 +372,13 @@ fn validate_submodules(submodules: Vec) -> Result errors.extend(e.into_iter()), + Err(e) => errors.extend(e), } } } @@ -383,6 +386,25 @@ fn validate_submodules(submodules: Vec) -> Result Result { + let len_error = (namespace.len() > 63).then(|| ValidationError::NamespaceTooLong { + namespace: namespace.clone(), + len: namespace.len(), + }); + + let namespace = Identifier::new(namespace) + .and_then(|ns| { + if &ns == "dropped" { + Err(IdentifierError::Reserved { name: ns.into() }) + } else { + Ok(ns) + } + }) + .map_err(|error| ValidationError::IdentifierError { error }.into()); + + ValidationErrors::add_extra_errors(namespace, len_error) +} + /// Change the visibility of scheduled functions and lifecycle reducers to Internal. /// fn change_scheduled_functions_and_lifetimes_visibility( diff --git a/crates/schema/src/def/validate/v9.rs b/crates/schema/src/def/validate/v9.rs index 8de4927afa2..149565d1760 100644 --- a/crates/schema/src/def/validate/v9.rs +++ b/crates/schema/src/def/validate/v9.rs @@ -171,6 +171,7 @@ pub fn validate(def: RawModuleDefV9) -> Result { http_routes: Vec::new(), raw_module_def_version: RawModuleDefVersion::V9OrEarlier, submodules: IndexMap::new(), + migrations: Default::default(), }; // Records each def's namespace. V9 has no submodules, so this just resolves everything at diff --git a/crates/schema/src/error.rs b/crates/schema/src/error.rs index e9408a35482..50e8a72a5f4 100644 --- a/crates/schema/src/error.rs +++ b/crates/schema/src/error.rs @@ -2,7 +2,7 @@ use spacetimedb_data_structures::error_stream::ErrorStream; use spacetimedb_lib::db::raw_def::v10::MethodOrAny; use spacetimedb_lib::db::raw_def::v9::{Lifecycle, RawScopedTypeNameV9}; use spacetimedb_lib::http::ACCEPTABLE_ROUTE_PATH_CHARS_HUMAN_DESCRIPTION; -use spacetimedb_lib::{ProductType, SumType}; +use spacetimedb_lib::{Hash, ProductType, SumType}; use spacetimedb_primitives::{ColId, ColList, ColSet}; use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type; use spacetimedb_sats::{bsatn::DecodeError, raw_identifier::RawIdentifier, AlgebraicType, AlgebraicTypeRef}; @@ -183,6 +183,8 @@ pub enum ValidationError { LifecycleInSubmodule { lifecycle: Lifecycle, namespace: String }, #[error("submodule namespace `{namespace}` is {len} bytes, which exceeds the 63-byte limit")] NamespaceTooLong { namespace: RawIdentifier, len: usize }, + #[error("submodule def for migration from schema with hash {schema_hash} contains disallowed sections")] + InvalidMigrationSubmodule { schema_hash: Hash }, } /// A wrapper around an `AlgebraicType` that implements `fmt::Display`. diff --git a/crates/schema/src/identifier.rs b/crates/schema/src/identifier.rs index b5950d46c76..c059678ef5e 100644 --- a/crates/schema/src/identifier.rs +++ b/crates/schema/src/identifier.rs @@ -3,6 +3,7 @@ use lean_string::LeanString; use spacetimedb_data_structures::map::{Equivalent, HashSet}; use spacetimedb_sats::raw_identifier::{RawIdentifier, RawNamespacedIdentifier}; use spacetimedb_sats::{impl_deserialize, impl_serialize, impl_st}; +use std::borrow::Borrow; use std::fmt::{self, Debug, Display}; use std::ops::Deref; use std::sync::Arc; @@ -132,9 +133,15 @@ impl Deref for Identifier { } } -impl Equivalent for str { +impl Borrow for Identifier { + fn borrow(&self) -> &str { + self + } +} + +impl Equivalent for String { fn equivalent(&self, other: &Identifier) -> bool { - self == &other.id[..] + self.as_str().equivalent(other) } } @@ -144,6 +151,12 @@ impl PartialEq for Identifier { } } +impl PartialEq for str { + fn eq(&self, other: &Identifier) -> bool { + other == self + } +} + impl From for RawIdentifier { fn from(id: Identifier) -> Self { id.id diff --git a/crates/sqltest/src/main.rs b/crates/sqltest/src/main.rs index 7856008afd3..304042fc21c 100644 --- a/crates/sqltest/src/main.rs +++ b/crates/sqltest/src/main.rs @@ -449,15 +449,11 @@ async fn update_test_file { - if sql.contains("NOT_REWRITE") { - continue; - } + Record::Statement { sql, .. } if sql.contains("NOT_REWRITE") => { + continue; } - Record::Query { sql, .. } => { - if sql.contains("NOT_REWRITE") { - continue; - } + Record::Query { sql, .. } if sql.contains("NOT_REWRITE") => { + continue; } _ => (), } diff --git a/crates/standalone/Dockerfile b/crates/standalone/Dockerfile index fa74fa1f232..1a9cfa1aaba 100644 --- a/crates/standalone/Dockerfile +++ b/crates/standalone/Dockerfile @@ -1,7 +1,8 @@ ARG CARGO_PROFILE=release -FROM rust:1.93.0 AS chef +# !rust-toolchain-sync +FROM rust:1.98.1 AS chef RUN rust_target=$(rustc -vV | awk '/^host:/{ print $2 }') && \ curl https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-$rust_target.tgz -fL | tar xz -C $CARGO_HOME/bin RUN cargo binstall -y cargo-chef@0.1.70 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 28f0403f3d4..7676abe47ec 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,8 +1,7 @@ [toolchain] -# change crates/{standalone,bench}/Dockerfile, .github/Dockerfile, and the docker image tag in -# .github/workflows/benchmarks.yml:jobs/callgrind_benchmark/container/image -# maybe also the rust-version in Cargo.toml -channel = "1.93.0" +# run ./tools/rust-toolchain-sync.sh when you change the channel! +# also consider changing the rust-version in Cargo.toml +channel = "1.98.1" profile = "default" targets = ["wasm32-unknown-unknown"] components = ["rust-src"] diff --git a/tools/ci/commands/update-flow/src/main.rs b/tools/ci/commands/update-flow/src/main.rs index 273e5f26efc..be9d99b8460 100644 --- a/tools/ci/commands/update-flow/src/main.rs +++ b/tools/ci/commands/update-flow/src/main.rs @@ -56,7 +56,7 @@ fn main() -> Result<()> { ["run", "-p", "spacetimedb-update"] .into_iter() .chain(common_args.clone()) - .chain(["--", "self-install", &root_arg, "--yes"].into_iter()), + .chain(["--", "self-install", &root_arg, "--yes"]), ) .run()?; diff --git a/tools/release/src/crates_resolver.rs b/tools/release/src/crates_resolver.rs index 3412ecf93da..158cfcc441c 100644 --- a/tools/release/src/crates_resolver.rs +++ b/tools/release/src/crates_resolver.rs @@ -72,9 +72,9 @@ pub fn get_crate_deps(crate_name: &String, manifest_map: &HashMap&2 "$0: couldn't extract version from rust-toolchain.toml" + exit 1 +} + +rg -.F "$MAGIC_COMMENT" -l "$@" | xargs gawk -i inplace -v toolchain_ver="$toolchain" " +/$MAGIC_COMMENT/ { version = 1; print; next } +{ if (version) { gsub(/$VERSION_REGEX/, toolchain_ver); version = 0; } print } +" diff --git a/tools/xtask-llm-benchmark/src/bin/llm_benchmark.rs b/tools/xtask-llm-benchmark/src/bin/llm_benchmark.rs index be76423286d..2b6caaa1ada 100644 --- a/tools/xtask-llm-benchmark/src/bin/llm_benchmark.rs +++ b/tools/xtask-llm-benchmark/src/bin/llm_benchmark.rs @@ -564,7 +564,7 @@ fn model_filter_from_groups(groups: Option>) -> Option> = HashMap::new(); for g in groups { - out.entry(g.vendor).or_default().extend(g.models.into_iter()); + out.entry(g.vendor).or_default().extend(g.models); } Some(out) } diff --git a/tools/xtask-llm-benchmark/src/context/combine.rs b/tools/xtask-llm-benchmark/src/context/combine.rs index ac8eb278c1b..9c3b1a2f59f 100644 --- a/tools/xtask-llm-benchmark/src/context/combine.rs +++ b/tools/xtask-llm-benchmark/src/context/combine.rs @@ -163,7 +163,7 @@ fn build_context_from_rustdoc_json() -> Result { }); } - rows.sort_by(|a, b| (order_key(&a.kind), a.path.to_lowercase()).cmp(&(order_key(&b.kind), b.path.to_lowercase()))); + rows.sort_by_key(|a| (order_key(&a.kind), a.path.to_lowercase())); let mut out = String::with_capacity(1024 * 1024); out.push_str(&format!(