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
185 changes: 107 additions & 78 deletions crates/core/src/db/update.rs

Large diffs are not rendered by default.

1,525 changes: 1,070 additions & 455 deletions crates/schema/src/auto_migrate.rs

Large diffs are not rendered by default.

235 changes: 108 additions & 127 deletions crates/schema/src/auto_migrate/formatter.rs

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions crates/schema/src/auto_migrate/termcolor_formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type;
use termcolor::{Buffer, Color, ColorChoice, ColorSpec, WriteColor};

use crate::auto_migrate::formatter::ViewInfo;
use crate::identifier::Identifier;

use super::formatter::{
AccessChangeInfo, Action, ColumnChange, ColumnChanges, ConstraintInfo, IndexInfo, MigrationFormatter, NewColumns,
Expand Down Expand Up @@ -231,7 +230,7 @@ impl MigrationFormatter for TermColorFormatter {
self.write_line("")
}

fn format_remove_table(&mut self, table_name: &Identifier) -> io::Result<()> {
fn format_remove_table(&mut self, table_name: &str) -> io::Result<()> {
self.write_action_prefix(&Action::Removed)?;
self.buffer.write_all(b" table: ")?;
self.write_colored(table_name, Some(self.colors.table_name), true)?;
Expand Down
72 changes: 72 additions & 0 deletions crates/schema/src/identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,78 @@ impl From<Identifier> for RawIdentifier {
}
}

/// An identifier that allows dot or slash separators between otherwise-normal identifier segments.
///
/// Used for fully-qualified names of mounted module items, e.g.:
/// - `"lib.library_table"` (table name)
/// - `"lib.library_table_id_idx_btree"` (index name)
/// - `"lib/library_reducer"` (reducer name)
///
/// Root-level items use their plain name with no separator (e.g., `"user"`).
/// Construction from known-valid components should use `new_assume_valid`.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NamespacedIdentifier(Box<str>);

impl NamespacedIdentifier {
/// Construct without validation. Use when building from known-valid `Identifier` segments
/// (e.g., `format!("{}{}", prefix, &*identifier)`).
pub fn new_assume_valid(s: impl Into<Box<str>>) -> Self {
Self(s.into())
}

/// Validated construction: each segment (split on `.` and `/`) must satisfy XID rules.
pub fn new(s: impl Into<Box<str>>) -> Result<Self, IdentifierError> {
let s = s.into();
for segment in s.split(['.', '/']) {
Identifier::new(RawIdentifier::new(segment))?;
}
Ok(Self(s))
}
}

impl std::fmt::Debug for NamespacedIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", &*self.0)
}
}

impl std::fmt::Display for NamespacedIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}

impl std::ops::Deref for NamespacedIdentifier {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}

impl AsRef<str> for NamespacedIdentifier {
fn as_ref(&self) -> &str {
&self.0
}
}

impl From<NamespacedIdentifier> for Box<str> {
fn from(id: NamespacedIdentifier) -> Self {
id.0
}
}

impl From<&str> for NamespacedIdentifier {
fn from(s: &str) -> Self {
Self::new_assume_valid(s)
}
}

impl From<String> for NamespacedIdentifier {
fn from(s: String) -> Self {
Self::new_assume_valid(s)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
23 changes: 19 additions & 4 deletions crates/schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,10 @@ impl Schema for TableSchema {
}

fn check_compatible(&self, module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
ensure_eq!(&self.table_name[..], &def.name[..], "Table name mismatch");
// Mounted tables are stored in the DB with a namespace prefix (e.g. "lib.library_table"),
// but the def's name is just the local name ("library_table"). Strip any prefix before comparing.
let self_local_name = self.table_name.rsplit('.').next().unwrap_or(&self.table_name[..]);
ensure_eq!(self_local_name, &def.name[..], "Table name mismatch");
ensure_eq!(self.primary_key, def.primary_key, "Primary key mismatch");
let def_table_access: StAccess = (def.table_access).into();
ensure_eq!(self.table_access, def_table_access, "Table access mismatch");
Expand All @@ -1065,10 +1068,15 @@ impl Schema for TableSchema {
}
ensure_eq!(self.columns.len(), def.columns.len(), "Column count mismatch");

// Index names in the DB are prefixed for mounted tables (e.g. "lib.library_table_id_idx_btree"),
// but def.indexes is keyed by the bare name. Derive the namespace prefix length from the
// difference between the full DB table name and the def's canonical (local) table name.
let ns_prefix_len = self.table_name.len() - def.name.len();
for index in &self.indexes {
let bare_name = &index.index_name[ns_prefix_len..];
let index_def = def
.indexes
.get(&index.index_name)
.get(bare_name)
.ok_or_else(|| anyhow::anyhow!("Index {} not found in definition", index.index_id.0))?;
index.check_compatible(module_def, index_def)?;
}
Expand Down Expand Up @@ -1418,8 +1426,11 @@ impl Schema for ScheduleSchema {

fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
ensure_eq!(&self.schedule_name[..], &def.name[..], "Schedule name mismatch");
// For mounted tables, schedule function names in the DB are namespace-prefixed using '/'
// (e.g. "lib/library_scheduled_procedure") while def.function_name is the bare name.
let ns_len = self.function_name.len().saturating_sub(def.function_name.len());
ensure_eq!(
&self.function_name[..],
&self.function_name[ns_len..],
&def.function_name[..],
"Schedule function name mismatch"
);
Expand Down Expand Up @@ -1487,7 +1498,11 @@ impl Schema for IndexSchema {
}

fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
ensure_eq!(&self.index_name[..], &def.name[..], "Index name mismatch");
// For mounted tables, the DB stores index names with a namespace prefix
// (e.g. "lib.library_table_id_idx_btree") while def.name is the bare name.
// Strip any prefix by comparing only the suffix of length def.name.len().
let ns_len = self.index_name.len().saturating_sub(def.name.len());
ensure_eq!(&self.index_name[ns_len..], &def.name[..], "Index name mismatch");
ensure_eq!(&self.index_algorithm, &def.algorithm, "Index algorithm mismatch");
Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Database Migration Plan
▸ Removed index Apples_id_name_idx_btree on [id, name] of table Apples
▸ Removed unique constraint Apples_id_key on [id] of table Apples
▸ Removed auto-increment constraint Apples_id_seq on column id of table Apples
▸ Removed schedule for table Deliveries_sched calling reducer check_deliveries
▸ Removed schedule for table Deliveries calling reducer check_deliveries
▸ ▸ Removed anonymous view: my_view
Parameters:
• x: U32
Expand Down Expand Up @@ -38,7 +38,7 @@ Database Migration Plan

▸ Created index Apples_id_count_idx_btree on [id, count] of table Apples
▸ Created auto-increment constraint Bananas_id_seq on column id of table Bananas
▸ Created schedule for table Inspections_sched calling reducer perform_inspection
▸ Created schedule for table Inspections calling reducer perform_inspection
▸ ▸ Created anonymous view: my_view
Parameters:
• x: U32
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ expression: "plan.pretty_print(PrettyPrintStyle::AnsiColor).expect(\"should pret
▸ Removed index Apples_id_name_idx_btree on [id, name] of table Apples
▸ Removed unique constraint Apples_id_key on [id] of table Apples
▸ Removed auto-increment constraint Apples_id_seq on column id of table Apples
▸ Removed schedule for table Deliveries_sched calling reducer check_deliveries
▸ Removed schedule for table Deliveries calling reducer check_deliveries
▸ ▸ Removed anonymous view: my_view
Parameters:
• x: U32
Expand Down Expand Up @@ -38,7 +38,7 @@ expression: "plan.pretty_print(PrettyPrintStyle::AnsiColor).expect(\"should pret

▸ Created index Apples_id_count_idx_btree on [id, count] of table Apples
▸ Created auto-increment constraint Bananas_id_seq on column id of table Bananas
▸ Created schedule for table Inspections_sched calling reducer perform_inspection
▸ Created schedule for table Inspections calling reducer perform_inspection
▸ ▸ Created anonymous view: my_view
Parameters:
• x: U32
Expand Down
Loading