Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dda387c
index source name change migration step
Shubham8287 Jun 2, 2026
c08b186
Add failing tests for table/column accessor name auto-migration
Shubham8287 Jun 4, 2026
f314f54
Implement auto-migration for table and column accessor name changes
Shubham8287 Jun 4, 2026
5fc9d75
Merge branch 'master' into shub/fix-table-migration-index-migration-step
Shubham8287 Jul 15, 2026
81df2d1
Merge branch 'master' into shub/fix-table-migration-index-migration-step
Shubham8287 Jul 29, 2026
5735c78
fmt
Shubham8287 Jul 29, 2026
1551612
lint
Shubham8287 Jul 29, 2026
df4ca53
Merge branch 'master' into shub/fix-table-migration-index-migration-step
Shubham8287 Jul 29, 2026
325e91c
comments
Shubham8287 Jul 29, 2026
e5a6b3f
Merge branch 'master' into shub/fix-table-migration-index-migration-step
Shubham8287 Jul 30, 2026
77e19a9
insert 'PendingSchemaChanges' early
Shubham8287 Aug 3, 2026
f8bd6df
Merge branch 'master' into shub/fix-table-migration-index-migration-step
Shubham8287 Aug 3, 2026
9f436d0
Merge branch 'master' into shub/fix-table-migration-index-migration-step
Shubham8287 Aug 4, 2026
02b17d4
lints
Shubham8287 Aug 5, 2026
310920d
Merge branch 'master' into shub/fix-table-migration-index-migration-step
Shubham8287 Aug 6, 2026
db3d0f1
Merge branch 'master' into shub/fix-table-migration-index-migration-step
Shubham8287 Aug 10, 2026
f30a54c
Merge branch 'master' into shub/fix-table-migration-index-migration-step
joshua-spacetime Aug 11, 2026
fa98a58
Merge branch 'master' into shub/fix-table-migration-index-migration-step
joshua-spacetime Aug 12, 2026
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
1 change: 1 addition & 0 deletions crates/datastore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ test = ["spacetimedb-commitlog/test", "spacetimedb-schema/test"]
spacetimedb-lib = { path = "../lib", features = ["proptest"] }
spacetimedb-sats = { path = "../sats", features = ["proptest"] }
spacetimedb-commitlog = { path = "../commitlog", features = ["test"] }
spacetimedb-schema = { workspace = true, features = ["test"] }

# Also as dev-dependencies for use in _this_ crate's tests.
proptest.workspace = true
Expand Down
26 changes: 26 additions & 0 deletions crates/datastore/src/locking_tx_datastore/committed_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,32 @@ impl CommittedState {
table.with_mut_schema(|s| s.remove_index(index_id));
self.index_id_map.remove(&index_id);
}
// An index alias/source-name changed. Change it back.
IndexAlterSourceName(table_id, index_id, old_alias) => {
let table = self.tables.get_mut(&table_id)?;
let mut index_schema = table
.get_schema()
.indexes
.iter()
.find(|x| x.index_id == index_id)?
.clone();
index_schema.alias = old_alias;
table.with_mut_schema(|s| s.update_index(index_schema));
}
// A table accessor name alias changed. Change it back.
TableAlterAccessorName(table_id, old_alias) => {
let table = self.tables.get_mut(&table_id)?;
table.with_mut_schema(|s| s.alias = old_alias);
}
// A column accessor name alias changed. Change it back.
ColumnAlterAccessorName(table_id, col_id, old_alias) => {
let table = self.tables.get_mut(&table_id)?;
table.with_mut_schema(|s| {
if let Some(col) = s.columns.iter_mut().find(|x| x.col_pos == col_id) {
col.alias = old_alias;
}
});
}
// A table was removed. Add it back.
TableRemoved(table_id, table) => {
let is_view_table = table.schema.is_view();
Expand Down
56 changes: 56 additions & 0 deletions crates/datastore/src/locking_tx_datastore/datastore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,34 @@ impl Locking {
tx.alter_table_primary_key(table_id, primary_key)
}

pub fn alter_index_source_name_mut_tx(
&self,
tx: &mut MutTxId,
index_id: IndexId,
source_name: spacetimedb_sats::raw_identifier::RawNamespacedIdentifier,
) -> Result<()> {
tx.alter_index_source_name(index_id, source_name)
}

pub fn alter_table_accessor_name_mut_tx(
&self,
tx: &mut MutTxId,
table_id: TableId,
new_alias: spacetimedb_schema::identifier::NamespacedIdentifier,
) -> Result<()> {
tx.alter_table_accessor_name(table_id, new_alias)
}

pub fn alter_column_accessor_name_mut_tx(
&self,
tx: &mut MutTxId,
table_id: TableId,
col_id: ColId,
new_alias: spacetimedb_schema::identifier::Identifier,
) -> Result<()> {
tx.alter_column_accessor_name(table_id, col_id, new_alias)
}

pub fn alter_table_row_type_mut_tx(
&self,
tx: &mut MutTxId,
Expand Down Expand Up @@ -551,6 +579,34 @@ impl MutTxDatastore for Locking {
tx.drop_index(index_id)
}

fn alter_index_source_name_mut_tx(
&self,
tx: &mut Self::MutTx,
index_id: IndexId,
source_name: spacetimedb_sats::raw_identifier::RawNamespacedIdentifier,
) -> Result<()> {
tx.alter_index_source_name(index_id, source_name)
}

fn alter_table_accessor_name_mut_tx(
&self,
tx: &mut Self::MutTx,
table_id: TableId,
new_alias: spacetimedb_schema::identifier::NamespacedIdentifier,
) -> Result<()> {
tx.alter_table_accessor_name(table_id, new_alias)
}

fn alter_column_accessor_name_mut_tx(
&self,
tx: &mut Self::MutTx,
table_id: TableId,
col_id: ColId,
new_alias: spacetimedb_schema::identifier::Identifier,
) -> Result<()> {
tx.alter_column_accessor_name(table_id, col_id, new_alias)
}

fn index_id_from_name_mut_tx(&self, tx: &Self::MutTx, index_name: &str) -> Result<Option<IndexId>> {
tx.index_id_from_name_or_alias(index_name)
}
Expand Down
156 changes: 140 additions & 16 deletions crates/datastore/src/locking_tx_datastore/mut_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use crate::{
locking_tx_datastore::state_view::ScanOrIndex,
traits::{InsertFlags, RowTypeForTable, TxData, UpdateFlags},
};
use core::{cell::RefCell, iter, mem, ops::RangeBounds};
use core::{cell::RefCell, iter, ops::RangeBounds};
use itertools::Either;
use smallvec::SmallVec;
use spacetimedb_data_structures::map::{HashMap, HashSet, IntMap};
Expand Down Expand Up @@ -1372,16 +1372,18 @@ impl MutTxId {
impl MutTxId {
/// Set the table access of `table_id` to `access`.
pub(crate) fn alter_table_access(&mut self, table_id: TableId, access: StAccess) -> Result<()> {
let old_access = self.find_st_table_row(table_id)?.table_access;

// Write to the table in the tx state.
let ((tx_table, ..), (commit_table, ..)) = self.get_or_create_insert_table_mut(table_id)?;
tx_table.with_mut_schema_and_clone(commit_table, |s| s.table_access = access);

// Update system tables.
let old_access = self.update_st_table_row(table_id, |st| mem::replace(&mut st.table_access, access))?;

// Remember the pending change so we can undo if necessary.
// Remember the pending change so we can undo it if a later system-table update fails.
self.push_schema_change(PendingSchemaChange::TableAlterAccess(table_id, old_access));

// Update system tables.
self.update_st_table_row(table_id, |st| st.table_access = access)?;

Ok(())
}

Expand All @@ -1390,18 +1392,142 @@ impl MutTxId {
/// Updates both the in-memory schema and the `st_table` system table.
/// See: <https://github.com/clockworklabs/SpacetimeDB/issues/3934>
pub(crate) fn alter_table_primary_key(&mut self, table_id: TableId, new_primary_key: Option<ColId>) -> Result<()> {
let old_pk = self.find_st_table_row(table_id)?.table_primary_key;

// Write to the table in the tx state.
let ((tx_table, ..), (commit_table, ..)) = self.get_or_create_insert_table_mut(table_id)?;
tx_table.with_mut_schema_and_clone(commit_table, |s| s.primary_key = new_primary_key);

// Update system tables.
// Remember the pending change so we can undo it if a later system-table update fails.
let new_pk_col_list = new_primary_key.map(|col| col.into());
let old_pk =
self.update_st_table_row(table_id, |st| mem::replace(&mut st.table_primary_key, new_pk_col_list))?;

// Remember the pending change so we can undo if necessary.
self.push_schema_change(PendingSchemaChange::TableAlterPrimaryKey(table_id, old_pk));

// Update system tables.
self.update_st_table_row(table_id, |st| st.table_primary_key = new_pk_col_list)?;

Ok(())
}

/// Change the source-name alias of the index identified by `index_id`.
pub(crate) fn alter_index_source_name(
&mut self,
index_id: IndexId,
source_name: RawNamespacedIdentifier,
) -> Result<()> {
let st_index_ref = self
.iter_by_col_eq(ST_INDEX_ID, StIndexFields::IndexId, &index_id.into())?
.next()
.ok_or_else(|| TableError::IdNotFound(SystemTable::st_index, index_id.into()))?;
let st_index_row = StIndexRow::try_from(st_index_ref)?;
let table_id = st_index_row.table_id;

let old_alias = self
.find_st_index_accessor_row_by_index_name(st_index_row.index_name.as_ref())?
.map(|row| row.accessor_name);

if old_alias.as_ref() == Some(&source_name) {
return Ok(());
}

let ((tx_table, ..), (commit_table, ..)) = self.get_or_create_insert_table_mut(table_id)?;
let mut index_schema = tx_table
.get_schema()
.indexes
.iter()
.find(|index| index.index_id == index_id)
.cloned()
.ok_or_else(|| TableError::IdNotFound(SystemTable::st_index, index_id.into()))?;
index_schema.alias = Some(source_name.clone());
tx_table.with_mut_schema_and_clone(commit_table, |s| s.update_index(index_schema));
self.push_schema_change(PendingSchemaChange::IndexAlterSourceName(table_id, index_id, old_alias));

self.drop_st_index_accessor(&st_index_row.index_name)?;
self.insert_st_index_accessor(&st_index_row.index_name, Some(&source_name))?;

Ok(())
}

/// Change the accessor name alias of the table identified by `table_id`.
pub(crate) fn alter_table_accessor_name(
&mut self,
table_id: TableId,
new_alias: NamespacedIdentifier,
) -> Result<()> {
let table_name = self.find_st_table_row(table_id)?.table_name;

let old_alias = self
.iter_by_col_eq(
ST_TABLE_ACCESSOR_ID,
StTableAccessorFields::TableName,
&table_name.as_ref().into(),
)?
.next()
.and_then(|row| StTableAccessorRow::try_from(row).ok())
.map(|row| row.accessor_name);

if old_alias.as_ref() == Some(&new_alias) {
return Ok(());
}

let ((tx_table, ..), (commit_table, ..)) = self.get_or_create_insert_table_mut(table_id)?;
tx_table.with_mut_schema_and_clone(commit_table, |s| s.alias = Some(new_alias.clone()));
self.push_schema_change(PendingSchemaChange::TableAlterAccessorName(table_id, old_alias));

self.drop_st_table_accessor(&table_name)?;
self.insert_st_table_accessor(&table_name, Some(&new_alias))?;

Ok(())
}

/// Change the accessor name alias of the column identified by `table_id` and `col_id`.
pub(crate) fn alter_column_accessor_name(
&mut self,
table_id: TableId,
col_id: ColId,
new_alias: Identifier,
) -> Result<()> {
let table_name = self.find_st_table_row(table_id)?.table_name;

// Read old column info from the current schema
let col_info: Vec<(Identifier, Option<Identifier>, ColId)> = self
.schema_for_table(table_id)?
.columns
.iter()
.map(|c| (c.col_name.clone(), c.alias.clone(), c.col_pos))
.collect();

let old_alias = col_info
.iter()
.find(|(_, _, pos)| *pos == col_id)
.and_then(|(_, alias, _)| alias.clone());

if old_alias.as_ref() == Some(&new_alias) {
return Ok(());
}

// Update system tables: drop and re-insert all column accessor rows for this table
self.drop_st_column_accessor(&table_name)?;
for (col_name, alias, pos) in &col_info {
let name = if *pos == col_id {
&new_alias
} else {
alias.as_ref().unwrap_or(col_name)
};
self.insert_st_column_accessor(&table_name, col_name, Some(name))?;
}

// Update in-memory schema
let ((tx_table, ..), (commit_table, ..)) = self.get_or_create_insert_table_mut(table_id)?;
tx_table.with_mut_schema_and_clone(commit_table, |s| {
if let Some(col) = s.columns.iter_mut().find(|c| c.col_pos == col_id) {
col.alias = Some(new_alias);
}
});

self.push_schema_change(PendingSchemaChange::ColumnAlterAccessorName(
table_id, col_id, old_alias,
));

Ok(())
}

Expand Down Expand Up @@ -1437,6 +1563,8 @@ impl MutTxId {
// SAFETY: `commit_table` should have a schema identical to that of `tx_table`
// prior to changing it just now.
unsafe { commit_table.set_layout_and_schema_to(tx_table) };
// Remember the pending change so we can undo it if a later system-table update fails.
self.push_schema_change(PendingSchemaChange::TableAlterRowType(table_id, old_column_schemas));

// Update system tables.
// We'll simply remove all rows in `st_columns` and then add the new ones.
Expand All @@ -1446,9 +1574,6 @@ impl MutTxId {
self.drop_st_column_accessor(&table_name)?;
self.insert_st_column(&table_name, &column_schemas)?;

// Remember the pending change so we can undo if necessary.
self.push_schema_change(PendingSchemaChange::TableAlterRowType(table_id, old_column_schemas));

Ok(())
}

Expand Down Expand Up @@ -1477,6 +1602,8 @@ impl MutTxId {
commit_table
.change_columns_of_empty_table_to(column_schemas.clone())
.map_err(|_| TableError::EventTableNotEmpty(table_id))?;
// Remember the pending change so we can undo it if a later system-table update fails.
self.push_schema_change(PendingSchemaChange::ReschemaEventTable(table_id, old_column_schemas));

// Update system tables.
// We'll simply remove all rows in `st_columns` and then add the new ones.
Expand All @@ -1486,9 +1613,6 @@ impl MutTxId {
self.drop_st_column_accessor(&table_name)?;
self.insert_st_column(&table_name, &column_schemas)?;

// Remember the pending change so we can undo if necessary.
self.push_schema_change(PendingSchemaChange::ReschemaEventTable(table_id, old_column_schemas));

Ok(())
}

Expand Down
24 changes: 23 additions & 1 deletion crates/datastore/src/locking_tx_datastore/tx_state.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use super::{delete_table::DeleteTable, sequence::Sequence};
use spacetimedb_data_structures::map::IntMap;
use spacetimedb_lib::db::auth::StAccess;
use spacetimedb_primitives::{ColList, ConstraintId, IndexId, SequenceId, TableId};
use spacetimedb_primitives::{ColId, ColList, ConstraintId, IndexId, SequenceId, TableId};
use spacetimedb_sats::memory_usage::MemoryUsage;
use spacetimedb_sats::raw_identifier::RawNamespacedIdentifier;
use spacetimedb_schema::identifier::{Identifier, NamespacedIdentifier};
use spacetimedb_schema::schema::{ColumnSchema, ConstraintSchema, IndexSchema, SequenceSchema};
use spacetimedb_table::{
blob_store::{BlobStore, HashMapBlobStore},
Expand Down Expand Up @@ -111,6 +113,15 @@ pub enum PendingSchemaChange {
/// If adding this index caused the pointer map to be removed,
/// it will be present here.
IndexAdded(TableId, IndexId, Option<PointerMap>),
/// The source-name alias of the index with [`IndexId`] changed.
/// The old alias is stored for rollback.
IndexAlterSourceName(TableId, IndexId, Option<RawNamespacedIdentifier>),
/// The accessor name alias of the table with [`TableId`] changed.
/// The old alias is stored for rollback.
TableAlterAccessorName(TableId, Option<NamespacedIdentifier>),
/// The accessor name alias of the column with [`ColId`] in the table with [`TableId`] changed.
/// The old alias is stored for rollback.
ColumnAlterAccessorName(TableId, ColId, Option<Identifier>),
/// The [`Table`] with [`TableId`] was removed.
TableRemoved(TableId, Table),
/// The table with [`TableId`] was added.
Expand Down Expand Up @@ -153,6 +164,9 @@ impl MemoryUsage for PendingSchemaChange {
Self::IndexAdded(table_id, index_id, pointer_map) => {
table_id.heap_usage() + index_id.heap_usage() + pointer_map.heap_usage()
}
Self::IndexAlterSourceName(table_id, index_id, alias) => {
table_id.heap_usage() + index_id.heap_usage() + alias.as_ref().map(|a| a.as_ref().len()).unwrap_or(0)
}
Self::TableRemoved(table_id, table) => table_id.heap_usage() + table.heap_usage(),
Self::TableAdded(table_id) => table_id.heap_usage(),
Self::TableAlterAccess(table_id, st_access) => table_id.heap_usage() + st_access.heap_usage(),
Expand All @@ -168,6 +182,14 @@ impl MemoryUsage for PendingSchemaChange {
table_id.heap_usage() + sequence.heap_usage() + sequence_schema.heap_usage()
}
Self::SequenceAdded(table_id, sequence_id) => table_id.heap_usage() + sequence_id.heap_usage(),
Self::TableAlterAccessorName(table_id, alias) => {
table_id.heap_usage() + alias.as_ref().map(|a| a.as_ref().len()).unwrap_or(0)
}
Self::ColumnAlterAccessorName(table_id, col_id, alias) => {
table_id.heap_usage()
+ col_id.heap_usage()
+ alias.as_ref().map(|a| a.as_raw().heap_usage()).unwrap_or(0)
}
Self::ReschemaEventTable(table_id, column_schemas) => table_id.heap_usage() + column_schemas.heap_usage(),
}
}
Expand Down
Loading
Loading