From 7e417058713c4af783ba2a1b3fdacd75f344c5d8 Mon Sep 17 00:00:00 2001 From: Shubham Mishra Date: Fri, 29 May 2026 16:01:16 +0530 Subject: [PATCH] index alias fix on restart --- .../src/locking_tx_datastore/state_view.rs | 27 ++++- crates/smoketests/tests/smoketests/mod.rs | 1 + .../typescript_index_source_name.rs | 105 ++++++++++++++++++ 3 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 crates/smoketests/tests/smoketests/typescript_index_source_name.rs diff --git a/crates/datastore/src/locking_tx_datastore/state_view.rs b/crates/datastore/src/locking_tx_datastore/state_view.rs index 8c4c978aaa0..6595a0b72c9 100644 --- a/crates/datastore/src/locking_tx_datastore/state_view.rs +++ b/crates/datastore/src/locking_tx_datastore/state_view.rs @@ -16,7 +16,7 @@ use core::ops::RangeBounds; use spacetimedb_lib::ConnectionId; use spacetimedb_primitives::{ColList, TableId}; use spacetimedb_sats::AlgebraicValue; -use spacetimedb_schema::schema::{ColumnSchema, TableSchema, ViewDefInfo}; +use spacetimedb_schema::schema::{ColumnSchema, IndexSchema, TableSchema, ViewDefInfo}; use spacetimedb_table::table::IndexScanPointIter; use spacetimedb_table::{ blob_store::HashMapBlobStore, @@ -120,6 +120,22 @@ pub trait StateView { .transpose() } + /// Look up an `st_index_accessor` row by its canonical index name. + fn find_st_index_accessor_row_by_index_name(&self, index_name: &str) -> Result> { + match self.iter_by_col_eq( + ST_INDEX_ACCESSOR_ID, + StIndexAccessorFields::IndexName, + &index_name.into(), + ) { + Ok(mut iter) => iter.next().map(StIndexAccessorRow::try_from).transpose(), + // `schema_for_table_raw` is called while restoring snapshots, + // before `migrate_system_tables` creates newer system tables. + // Treat a missing `st_index_accessor` as "no aliases yet" here. + Err(DatastoreError::Table(TableError::IdNotFound(..))) => Ok(None), + Err(e) => Err(e), + } + } + /// Look up an `st_column_accessor` row by its canonical table and column names fn find_st_column_accessor_row(&self, table_name: &str, col_name: &str) -> Result> { match self.iter_by_col_eq( @@ -187,7 +203,14 @@ pub trait StateView { // Look up the indexes for the table in question. let indexes = self .iter_by_col_eq(ST_INDEX_ID, StIndexFields::TableId, value_eq)? - .map(|row| StIndexRow::try_from(row).map(Into::into)) + .map(|row| { + let row = StIndexRow::try_from(row)?; + let mut index_schema = IndexSchema::from(row); + index_schema.alias = self + .find_st_index_accessor_row_by_index_name(index_schema.index_name.as_ref())? + .map(|row| row.accessor_name); + Ok(index_schema) + }) .collect::>>()?; let schedule = self diff --git a/crates/smoketests/tests/smoketests/mod.rs b/crates/smoketests/tests/smoketests/mod.rs index ce26d29d09f..0bd57e8aef3 100644 --- a/crates/smoketests/tests/smoketests/mod.rs +++ b/crates/smoketests/tests/smoketests/mod.rs @@ -38,4 +38,5 @@ mod sql; mod sql_connect_hook; mod templates; mod timestamp_route; +mod typescript_index_source_name; mod views; diff --git a/crates/smoketests/tests/smoketests/typescript_index_source_name.rs b/crates/smoketests/tests/smoketests/typescript_index_source_name.rs new file mode 100644 index 00000000000..ecf23982ab9 --- /dev/null +++ b/crates/smoketests/tests/smoketests/typescript_index_source_name.rs @@ -0,0 +1,105 @@ +use spacetimedb_smoketests::{random_string, require_local_server, require_pnpm, Smoketest}; + +const TYPESCRIPT_MODULE_WITHOUT_NEW_COLUMNS: &str = r#"import { schema, table, t } from "spacetimedb/server"; + +const users = table( + { name: "users", public: false }, + { + id: t.u64().primaryKey().autoInc(), + name: t.string(), + emailAddress: t.string().index("btree"), + }, +); + +const spacetimedb = schema({ + users, +}); +export default spacetimedb; + +export const insert_user = spacetimedb.reducer( + { + name: t.string(), + emailAddress: t.string(), + }, + (ctx, { name, emailAddress }) => { + ctx.db.users.insert({ + id: 0n, + name, + emailAddress, + }); + }, +); +"#; + +const TYPESCRIPT_MODULE_WITH_NEW_COLUMNS: &str = r#"import { schema, table, t } from "spacetimedb/server"; + +const users = table( + { name: "users", public: false }, + { + id: t.u64().primaryKey().autoInc(), + name: t.string(), + emailAddress: t.string().index("btree"), + age: t.number().optional().default(undefined), + isActive: t.bool().default(false).index(), + }, +); + +const spacetimedb = schema({ + users, +}); +export default spacetimedb; + +export const find_user_by_email = spacetimedb.reducer( + { emailAddress: t.string() }, + (ctx, { emailAddress }) => { + let count = 0; + for (const _row of ctx.db.users.emailAddress.filter(emailAddress)) { + count += 1; + } + console.info(`matched ${count}`); + }, +); + +export const find_users_by_active_status = spacetimedb.reducer( + { isActive: t.bool() }, + (ctx, { isActive }) => { + let count = 0; + for (const _row of ctx.db.users.isActive.filter(isActive)) { + count += 1; + } + console.info(`matched active users ${count}`); + }, +); +"#; + +#[test] +fn test_typescript_add_optional_columns() { + require_pnpm!(); + require_local_server!(); + + let mut test = Smoketest::builder().autopublish(false).build(); + let module_name = format!("typescript-add-optional-columns-{}", random_string()); + + let database_identity = test + .publish_typescript_module_source( + "typescript-add-optional-columns-v1", + &module_name, + TYPESCRIPT_MODULE_WITHOUT_NEW_COLUMNS, + ) + .unwrap(); + + test.call("insert_user", &["Alice", "alice@example.com"]).unwrap(); + + test.restart_server(); + + test.publish_typescript_module_source_clear( + "typescript-add-optional-columns-v2", + &database_identity, + TYPESCRIPT_MODULE_WITH_NEW_COLUMNS, + false, + ) + .unwrap(); + + test.call("find_user_by_email", &["alice@example.com"]).unwrap(); + test.call("find_users_by_active_status", &["false"]).unwrap(); +}