Skip to content

Commit 4fb542f

Browse files
committed
Migrations: handle mounted submodule tables, views, and indexes
1 parent e54ed46 commit 4fb542f

8 files changed

Lines changed: 1381 additions & 670 deletions

crates/core/src/db/update.rs

Lines changed: 107 additions & 78 deletions
Large diffs are not rendered by default.

crates/schema/src/auto_migrate.rs

Lines changed: 1070 additions & 455 deletions
Large diffs are not rendered by default.

crates/schema/src/auto_migrate/formatter.rs

Lines changed: 108 additions & 127 deletions
Large diffs are not rendered by default.

crates/schema/src/auto_migrate/termcolor_formatter.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type;
77
use termcolor::{Buffer, Color, ColorChoice, ColorSpec, WriteColor};
88

99
use crate::auto_migrate::formatter::ViewInfo;
10-
use crate::identifier::Identifier;
1110

1211
use super::formatter::{
1312
AccessChangeInfo, Action, ColumnChange, ColumnChanges, ConstraintInfo, IndexInfo, MigrationFormatter, NewColumns,
@@ -231,7 +230,7 @@ impl MigrationFormatter for TermColorFormatter {
231230
self.write_line("")
232231
}
233232

234-
fn format_remove_table(&mut self, table_name: &Identifier) -> io::Result<()> {
233+
fn format_remove_table(&mut self, table_name: &str) -> io::Result<()> {
235234
self.write_action_prefix(&Action::Removed)?;
236235
self.buffer.write_all(b" table: ")?;
237236
self.write_colored(table_name, Some(self.colors.table_name), true)?;

crates/schema/src/identifier.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,78 @@ impl From<Identifier> for RawIdentifier {
128128
}
129129
}
130130

131+
/// An identifier that allows dot or slash separators between otherwise-normal identifier segments.
132+
///
133+
/// Used for fully-qualified names of mounted module items, e.g.:
134+
/// - `"lib.library_table"` (table name)
135+
/// - `"lib.library_table_id_idx_btree"` (index name)
136+
/// - `"lib/library_reducer"` (reducer name)
137+
///
138+
/// Root-level items use their plain name with no separator (e.g., `"user"`).
139+
/// Construction from known-valid components should use `new_assume_valid`.
140+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
141+
pub struct NamespacedIdentifier(Box<str>);
142+
143+
impl NamespacedIdentifier {
144+
/// Construct without validation. Use when building from known-valid `Identifier` segments
145+
/// (e.g., `format!("{}{}", prefix, &*identifier)`).
146+
pub fn new_assume_valid(s: impl Into<Box<str>>) -> Self {
147+
Self(s.into())
148+
}
149+
150+
/// Validated construction: each segment (split on `.` and `/`) must satisfy XID rules.
151+
pub fn new(s: impl Into<Box<str>>) -> Result<Self, IdentifierError> {
152+
let s = s.into();
153+
for segment in s.split(['.', '/']) {
154+
Identifier::new(RawIdentifier::new(segment))?;
155+
}
156+
Ok(Self(s))
157+
}
158+
}
159+
160+
impl std::fmt::Debug for NamespacedIdentifier {
161+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162+
write!(f, "{:?}", &*self.0)
163+
}
164+
}
165+
166+
impl std::fmt::Display for NamespacedIdentifier {
167+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168+
f.write_str(&self.0)
169+
}
170+
}
171+
172+
impl std::ops::Deref for NamespacedIdentifier {
173+
type Target = str;
174+
fn deref(&self) -> &str {
175+
&self.0
176+
}
177+
}
178+
179+
impl AsRef<str> for NamespacedIdentifier {
180+
fn as_ref(&self) -> &str {
181+
&self.0
182+
}
183+
}
184+
185+
impl From<NamespacedIdentifier> for Box<str> {
186+
fn from(id: NamespacedIdentifier) -> Self {
187+
id.0
188+
}
189+
}
190+
191+
impl From<&str> for NamespacedIdentifier {
192+
fn from(s: &str) -> Self {
193+
Self::new_assume_valid(s)
194+
}
195+
}
196+
197+
impl From<String> for NamespacedIdentifier {
198+
fn from(s: String) -> Self {
199+
Self::new_assume_valid(s)
200+
}
201+
}
202+
131203
#[cfg(test)]
132204
mod tests {
133205
use super::*;

crates/schema/src/schema.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,7 +1049,10 @@ impl Schema for TableSchema {
10491049
}
10501050

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

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

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

14891500
fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> {
1490-
ensure_eq!(&self.index_name[..], &def.name[..], "Index name mismatch");
1501+
// For mounted tables, the DB stores index names with a namespace prefix
1502+
// (e.g. "lib.library_table_id_idx_btree") while def.name is the bare name.
1503+
// Strip any prefix by comparing only the suffix of length def.name.len().
1504+
let ns_len = self.index_name.len().saturating_sub(def.name.len());
1505+
ensure_eq!(&self.index_name[ns_len..], &def.name[..], "Index name mismatch");
14911506
ensure_eq!(&self.index_algorithm, &def.algorithm, "Index algorithm mismatch");
14921507
Ok(())
14931508
}

crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__updated pretty print no color.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Database Migration Plan
99
Removed index Apples_id_name_idx_btree on [id, name] of table Apples
1010
Removed unique constraint Apples_id_key on [id] of table Apples
1111
Removed auto-increment constraint Apples_id_seq on column id of table Apples
12-
Removed schedule for table Deliveries_sched calling reducer check_deliveries
12+
Removed schedule for table Deliveries calling reducer check_deliveries
1313
▸ ▸ Removed anonymous view: my_view
1414
Parameters:
1515
x: U32
@@ -38,7 +38,7 @@ Database Migration Plan
3838

3939
Created index Apples_id_count_idx_btree on [id, count] of table Apples
4040
Created auto-increment constraint Bananas_id_seq on column id of table Bananas
41-
Created schedule for table Inspections_sched calling reducer perform_inspection
41+
Created schedule for table Inspections calling reducer perform_inspection
4242
▸ ▸ Created anonymous view: my_view
4343
Parameters:
4444
x: U32

crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__updated pretty print.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ expression: "plan.pretty_print(PrettyPrintStyle::AnsiColor).expect(\"should pret
99
▸ Removed index Apples_id_name_idx_btree on [id, name] of table Apples
1010
▸ Removed unique constraint Apples_id_key on [id] of table Apples
1111
▸ Removed auto-increment constraint Apples_id_seq on column id of table Apples
12-
▸ [0m[1m[31mRemoved[0m schedule for table [0m[1m[36mDeliveries_sched[0m calling reducer check_deliveries
12+
▸ [0m[1m[31mRemoved[0m schedule for table [0m[1m[36mDeliveries[0m calling reducer check_deliveries
1313
▸ ▸ Removed anonymous view: my_view
1414
Parameters:
1515
x: U32
@@ -38,7 +38,7 @@ expression: "plan.pretty_print(PrettyPrintStyle::AnsiColor).expect(\"should pret
3838

3939
▸ Created index Apples_id_count_idx_btree on [id, count] of table Apples
4040
▸ Created auto-increment constraint Bananas_id_seq on column id of table Bananas
41-
▸ [0m[1m[32mCreated[0m schedule for table [0m[1m[36mInspections_sched[0m calling reducer perform_inspection
41+
▸ [0m[1m[32mCreated[0m schedule for table [0m[1m[36mInspections[0m calling reducer perform_inspection
4242
▸ ▸ Created anonymous view: my_view
4343
Parameters:
4444
x: U32

0 commit comments

Comments
 (0)