diff --git a/Cargo.lock b/Cargo.lock index dd4fc560b2e..9cf376bce70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1906,12 +1906,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" -[[package]] -name = "ecow" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e4f79b296fbaab6ce2e22d52cb4c7f010fe0ebe7a32e34fa25885fd797bd02" - [[package]] name = "educe" version = "0.4.23" @@ -3603,6 +3597,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lean_string" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962df00ba70ac8d5ca5c064e17e5c3d090c087fd8d21aa45096c716b169da514" +dependencies = [ + "castaway", + "itoa", + "ryu", +] + [[package]] name = "leb128" version = "0.2.5" @@ -8141,6 +8146,7 @@ dependencies = [ "ethnum", "hex", "itertools 0.12.1", + "lean_string", "proptest", "proptest-derive", "rand 0.9.2", @@ -8163,13 +8169,13 @@ version = "1.12.0" dependencies = [ "anyhow", "derive_more 0.99.20", - "ecow", "enum-as-inner", "enum-map", "indexmap 2.12.0", "insta", "itertools 0.12.1", "lazy_static", + "lean_string", "petgraph 0.6.5", "proptest", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index d1b57371642..db6351e7100 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,7 +179,6 @@ derive_more = "0.99" dialoguer = { version = "0.11", default-features = false } dirs = "5.0.1" duct = "0.13.5" -ecow = "0.2.6" either = "1.9" email_address = "0.2.4" enum-as-inner = "0.6" @@ -220,6 +219,7 @@ jsonwebtoken = { package = "spacetimedb-jsonwebtoken", version = "9.3.0" } junction = "1" jwks = { package = "spacetimedb-jwks", version = "0.1.3" } lazy_static = "1.4.0" +lean_string = "0.5.1" log = "0.4.17" memchr = "2" mimalloc = "0.1.39" diff --git a/crates/bench/benches/special.rs b/crates/bench/benches/special.rs index f9ec7c842a9..540dbdc8c66 100644 --- a/crates/bench/benches/special.rs +++ b/crates/bench/benches/special.rs @@ -137,7 +137,7 @@ fn serialize_benchmarks< }); let mut table_schema = TableSchema::from_product_type(T::product_type()); - table_schema.table_name = TableName::new_from_str(name); + table_schema.table_name = TableName::for_test(name); let mut table = spacetimedb_table::table::Table::new( Arc::new(table_schema), spacetimedb_table::indexes::SquashedOffset::COMMITTED_STATE, diff --git a/crates/bench/benches/subscription.rs b/crates/bench/benches/subscription.rs index 48bb452a02c..b4560126fc2 100644 --- a/crates/bench/benches/subscription.rs +++ b/crates/bench/benches/subscription.rs @@ -55,7 +55,7 @@ fn create_table_footprint(db: &RelationalDB) -> Result { fn insert_op(table_id: TableId, table_name: &str, row: ProductValue) -> DatabaseTableUpdate { DatabaseTableUpdate { table_id, - table_name: TableName::new_from_str(table_name), + table_name: TableName::for_test(table_name), inserts: [row].into(), deletes: [].into(), } diff --git a/crates/bench/src/schemas.rs b/crates/bench/src/schemas.rs index 04b4fcb049a..01d9ddd3553 100644 --- a/crates/bench/src/schemas.rs +++ b/crates/bench/src/schemas.rs @@ -164,7 +164,7 @@ pub fn table_name(style: IndexStrategy) -> TableName { let prefix = style.name(); let name = T::name(); - TableName::new_from_str(&format!("{prefix}_{name}")) + TableName::for_test(&format!("{prefix}_{name}")) } // ---------- data synthesis ---------- diff --git a/crates/bench/src/spacetime_raw.rs b/crates/bench/src/spacetime_raw.rs index a606f7c17b8..67624d33975 100644 --- a/crates/bench/src/spacetime_raw.rs +++ b/crates/bench/src/spacetime_raw.rs @@ -44,7 +44,7 @@ impl BenchDatabase for SpacetimeRaw { let mut table_schema = TableSchema::from_product_type(T::product_type()); table_schema.table_name = name.clone(); let table_id = self.db.create_table(tx, table_schema)?; - self.db.rename_table(tx, table_id, &name)?; + self.db.rename_table(tx, table_id, name)?; match index_strategy { IndexStrategy::Unique0 => { self.db.create_index( diff --git a/crates/bench/src/sqlite.rs b/crates/bench/src/sqlite.rs index 8bb288d4dbe..115e74f4ad5 100644 --- a/crates/bench/src/sqlite.rs +++ b/crates/bench/src/sqlite.rs @@ -173,7 +173,7 @@ impl BenchDatabase for SQLite { value: AlgebraicValue, ) -> ResultBench<()> { let statement = memo_query(BenchName::Filter, table_id, || { - let column: Box = T::product_type().elements[col_id.into().idx()].name.take().unwrap(); + let column = T::product_type().elements[col_id.into().idx()].name.clone().unwrap(); format!("SELECT * FROM {table_id} WHERE {column} = ?") }); diff --git a/crates/bindings/src/rt.rs b/crates/bindings/src/rt.rs index 09f2e5e40eb..7a8294d50a1 100644 --- a/crates/bindings/src/rt.rs +++ b/crates/bindings/src/rt.rs @@ -710,7 +710,7 @@ pub fn register_table() { for &col in T::UNIQUE_COLUMNS { table = table.with_unique_constraint(col); } - for &index in T::INDEXES { + for index in T::INDEXES { table = table.with_index(index.algo.into(), index.accessor_name); } if let Some(primary_key) = T::PRIMARY_KEY { diff --git a/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap b/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap index a4099798df2..9354026f0ba 100644 --- a/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap +++ b/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap @@ -2,7 +2,7 @@ source: crates/bindings/tests/deps.rs expression: "cargo tree -p spacetimedb -e no-dev --color never --target wasm32-unknown-unknown -f {lib}" --- -total crates: 70 +total crates: 73 spacetimedb ├── anyhow ├── bytemuck @@ -106,6 +106,11 @@ spacetimedb │ │ │ └── serde_core │ │ ├── hex │ │ ├── itertools (*) +│ │ ├── lean_string +│ │ │ ├── castaway +│ │ │ │ └── rustversion +│ │ │ ├── itoa +│ │ │ └── ryu │ │ ├── second_stack │ │ ├── sha3 │ │ │ ├── digest diff --git a/crates/cli/src/subcommands/call.rs b/crates/cli/src/subcommands/call.rs index 062001db6f2..d77d9b6f27e 100644 --- a/crates/cli/src/subcommands/call.rs +++ b/crates/cli/src/subcommands/call.rs @@ -6,6 +6,7 @@ use crate::util::UNSTABLE_WARNING; use anyhow::{bail, Context, Error}; use clap::{Arg, ArgMatches}; use convert_case::{Case, Casing}; +use core::ops::Deref; use itertools::Itertools; use spacetimedb_lib::sats::{self, AlgebraicType, Typespace}; use spacetimedb_lib::{Identity, ProductTypeElement}; @@ -198,7 +199,7 @@ impl std::fmt::Display for CallSignature<'_> { } comma = true; if let Some(name) = arg.name() { - write!(f, "{}: ", name.to_case(Case::Snake))?; + write!(f, "{}: ", name.deref().to_case(Case::Snake))?; } write_type::write_type(typespace, f, &arg.algebraic_type)?; } diff --git a/crates/cli/src/subcommands/sql.rs b/crates/cli/src/subcommands/sql.rs index ff8d551931f..2455feab181 100644 --- a/crates/cli/src/subcommands/sql.rs +++ b/crates/cli/src/subcommands/sql.rs @@ -201,13 +201,12 @@ fn build_table( rows: impl Iterator>, ) -> Result { let mut builder = tabled::builder::Builder::default(); - builder.set_header( - schema - .elements - .iter() - .enumerate() - .map(|(i, e)| e.name.clone().unwrap_or_else(|| format!("column {i}").into())), - ); + builder.set_header(schema.elements.iter().enumerate().map(|(i, e)| { + e.name + .as_ref() + .map(|n| n.to_string()) + .unwrap_or_else(|| format!("column {i}")) + })); let ty = Typespace::EMPTY.with_type(schema); for row in rows { diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 924a04a2d25..3ec0b44e251 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -152,6 +152,7 @@ spacetimedb-lib = { path = "../lib", features = ["proptest", "test"] } spacetimedb-sats = { path = "../sats", features = ["proptest"] } spacetimedb-commitlog = { path = "../commitlog", features = ["test"] } spacetimedb-datastore = { path = "../datastore/", features = ["test"] } +spacetimedb-vm = { workspace = true, features = ["test"]} criterion.workspace = true # Also as dev-dependencies for use in _this_ crate's tests. diff --git a/crates/core/src/client/message_handlers.rs b/crates/core/src/client/message_handlers.rs index 416b629fca5..e7df9f21908 100644 --- a/crates/core/src/client/message_handlers.rs +++ b/crates/core/src/client/message_handlers.rs @@ -11,6 +11,8 @@ use spacetimedb_datastore::execution_context::WorkloadType; use spacetimedb_lib::de::serde::DeserializeWrapper; use spacetimedb_lib::identity::RequestId; use spacetimedb_lib::{bsatn, ConnectionId, Timestamp}; +use spacetimedb_sats::raw_identifier::RawIdentifier; +use spacetimedb_schema::identifier::Identifier; use spacetimedb_schema::reducer_name::ReducerName; use std::borrow::Cow; use std::sync::Arc; @@ -174,12 +176,21 @@ pub struct MessageExecutionError { impl MessageExecutionError { fn into_event(self) -> ModuleEvent { + let reducer = self + .reducer + .as_deref() + .map(RawIdentifier::new) + .map(Identifier::new) + .transpose() + .unwrap() + .map(ReducerName::new); + ModuleEvent { timestamp: Timestamp::now(), caller_identity: self.caller_identity, caller_connection_id: self.caller_connection_id, function_call: ModuleFunctionCall { - reducer: ReducerName::new_from_str(&self.reducer.unwrap_or_else(|| "".into())), + reducer, reducer_id: self.reducer_id.unwrap_or(u32::MAX.into()), args: Default::default(), }, diff --git a/crates/core/src/client/messages.rs b/crates/core/src/client/messages.rs index 59994bf2b06..ce01c404345 100644 --- a/crates/core/src/client/messages.rs +++ b/crates/core/src/client/messages.rs @@ -7,7 +7,6 @@ use crate::subscription::row_list_builder_pool::BsatnRowListBuilderPool; use crate::subscription::websocket_building::{brotli_compress, decide_compression, gzip_compress}; use bytes::{BufMut, Bytes, BytesMut}; use bytestring::ByteString; -use core::ops::Deref; use derive_more::From; use spacetimedb_client_api_messages::websocket::{ BsatnFormat, Compression, FormatSwitch, JsonFormat, OneOffTable, RowListLen, WebsocketFormat, @@ -283,7 +282,12 @@ impl ToProtocol for TransactionUpdateMessage { status, caller_identity: event.caller_identity, reducer_call: ws::ReducerCallInfo { - reducer_name: event.function_call.reducer.deref().into(), + reducer_name: event + .function_call + .reducer + .as_ref() + .map(|r| (&**r).into()) + .unwrap_or_default(), reducer_id: event.function_call.reducer_id.into(), args, request_id, diff --git a/crates/core/src/db/durability.rs b/crates/core/src/db/durability.rs index a3e6c1de989..a5ae5744ad0 100644 --- a/crates/core/src/db/durability.rs +++ b/crates/core/src/db/durability.rs @@ -198,7 +198,7 @@ impl DurabilityWorkerActor { pub fn do_durability(durability: &Durability, reducer_context: Option, tx_data: &TxData) { if tx_data.tx_offset().is_none() { - let name = reducer_context.as_ref().map(|rcx| &*rcx.name); + let name = reducer_context.as_ref().map(|rcx| &rcx.name); debug_assert!( !tx_data.has_rows_or_connect_disconnect(name), "tx_data has no rows but has connect/disconnect: `{name:?}`" @@ -318,7 +318,7 @@ mod tests { let mut txdata = TxData::default(); txdata.set_tx_offset(i); // Ensure the transaction is non-empty. - txdata.set_inserts_for_table(4000.into(), &TableName::new_from_str("foo"), [product![42u8]].into()); + txdata.set_inserts_for_table(4000.into(), &TableName::for_test("foo"), [product![42u8]].into()); worker.request_durability(None, &Arc::new(txdata)); } diff --git a/crates/core/src/db/mod.rs b/crates/core/src/db/mod.rs index a86ab0bfbf5..07fb6cf140f 100644 --- a/crates/core/src/db/mod.rs +++ b/crates/core/src/db/mod.rs @@ -38,7 +38,7 @@ pub struct Config { /// We use a separate task to record metrics to avoid blocking transactions. pub struct MetricsMessage { /// The reducer the produced these metrics. - reducer: ReducerName, + reducer: Option, /// Metrics from a mutable transaction. metrics_for_writer: Option, /// Metrics from a read-only transaction. @@ -61,7 +61,7 @@ pub struct MetricsRecorderQueue { impl MetricsRecorderQueue { pub fn send_metrics( &self, - reducer: ReducerName, + reducer: Option, metrics_for_writer: Option, metrics_for_reader: Option, tx_data: Option>, @@ -97,7 +97,7 @@ pub fn spawn_tx_metrics_recorder() -> (MetricsRecorderQueue, tokio::task::AbortH // If row updates are present, // they will always belong to the writer transaction. tx_data.as_deref(), - &reducer, + reducer.as_ref(), |wl| &counters[wl], ); } @@ -107,7 +107,7 @@ pub fn spawn_tx_metrics_recorder() -> (MetricsRecorderQueue, tokio::task::AbortH // they will never belong to the reader transaction. // Passing row updates here will most likely panic. None, - &reducer, + reducer.as_ref(), |wl| &counters[wl], ); } diff --git a/crates/core/src/db/relational_db.rs b/crates/core/src/db/relational_db.rs index 22e51db5293..744ad322338 100644 --- a/crates/core/src/db/relational_db.rs +++ b/crates/core/src/db/relational_db.rs @@ -45,12 +45,14 @@ use spacetimedb_paths::server::{ReplicaDir, SnapshotsPath}; use spacetimedb_primitives::*; use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type; use spacetimedb_sats::memory_usage::MemoryUsage; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::{AlgebraicType, AlgebraicValue, ProductType, ProductValue}; use spacetimedb_schema::def::{ModuleDef, TableDef, ViewDef}; use spacetimedb_schema::reducer_name::ReducerName; use spacetimedb_schema::schema::{ ColumnSchema, IndexSchema, RowLevelSecuritySchema, Schema, SequenceSchema, TableSchema, }; +use spacetimedb_schema::table_name::TableName; use spacetimedb_snapshot::{ReconstructedSnapshot, SnapshotError, SnapshotRepository}; use spacetimedb_table::indexes::RowPointer; use spacetimedb_table::page_pool::PagePool; @@ -770,7 +772,7 @@ impl RelationalDB { } #[tracing::instrument(level = "trace", skip_all)] - pub fn rollback_mut_tx(&self, tx: MutTx) -> (TxOffset, TxMetrics, ReducerName) { + pub fn rollback_mut_tx(&self, tx: MutTx) -> (TxOffset, TxMetrics, Option) { log::trace!("ROLLBACK MUT TX"); self.inner.rollback_mut_tx(tx) } @@ -782,14 +784,17 @@ impl RelationalDB { } #[tracing::instrument(level = "trace", skip_all)] - pub fn release_tx(&self, tx: Tx) -> (TxOffset, TxMetrics, ReducerName) { + pub fn release_tx(&self, tx: Tx) -> (TxOffset, TxMetrics, Option) { log::trace!("RELEASE TX"); self.inner.release_tx(tx) } #[tracing::instrument(level = "trace", skip_all)] #[allow(clippy::type_complexity)] - pub fn commit_tx(&self, tx: MutTx) -> Result, TxMetrics, ReducerName)>, DBError> { + pub fn commit_tx( + &self, + tx: MutTx, + ) -> Result, TxMetrics, Option)>, DBError> { log::trace!("COMMIT MUT TX"); let reducer_context = tx.ctx.reducer_context().cloned(); @@ -1003,7 +1008,7 @@ impl RelationalDB { /// Should only be called after the tx lock has been fully released. pub(crate) fn report_tx_metrics( &self, - reducer: ReducerName, + reducer: Option, tx_data: Option>, metrics_for_writer: Option, metrics_for_reader: Option, @@ -1111,7 +1116,7 @@ impl RelationalDB { pub fn create_table_for_test_with_the_works( &self, name: &str, - schema: &[(&str, AlgebraicType)], + schema: &[(&'static str, AlgebraicType)], indexes: &[ColList], unique_constraints: &[ColList], access: StAccess, @@ -1119,7 +1124,11 @@ impl RelationalDB { let mut module_def_builder = RawModuleDefV9Builder::new(); let mut table_builder = module_def_builder - .build_table_with_new_type_for_tests(name, ProductType::from_iter(schema.iter().cloned()), true) + .build_table_with_new_type_for_tests( + RawIdentifier::new(name), + ProductType::from_iter(schema.iter().cloned()), + true, + ) .with_access(access.into()); for columns in indexes { @@ -1143,7 +1152,7 @@ impl RelationalDB { pub fn create_table_for_test_with_access( &self, name: &str, - schema: &[(&str, AlgebraicType)], + schema: &[(&'static str, AlgebraicType)], indexes: &[ColId], access: StAccess, ) -> Result { @@ -1154,7 +1163,7 @@ impl RelationalDB { pub fn create_table_for_test( &self, name: &str, - schema: &[(&str, AlgebraicType)], + schema: &[(&'static str, AlgebraicType)], indexes: &[ColId], ) -> Result { self.create_table_for_test_with_access(name, schema, indexes, StAccess::Public) @@ -1163,7 +1172,7 @@ impl RelationalDB { pub fn create_table_for_test_multi_column( &self, name: &str, - schema: &[(&str, AlgebraicType)], + schema: &[(&'static str, AlgebraicType)], idx_cols: ColList, ) -> Result { self.create_table_for_test_with_the_works(name, schema, &[idx_cols], &[], StAccess::Public) @@ -1172,7 +1181,7 @@ impl RelationalDB { pub fn create_table_for_test_mix_indexes( &self, name: &str, - schema: &[(&str, AlgebraicType)], + schema: &[(&'static str, AlgebraicType)], idx_cols_single: &[ColId], idx_cols_multi: ColList, ) -> Result { @@ -1191,7 +1200,7 @@ impl RelationalDB { /// relatively cheap operation which only modifies the system tables. /// /// If the table is not found or is a system table, an error is returned. - pub fn rename_table(&self, tx: &mut MutTx, table_id: TableId, new_name: &str) -> Result<(), DBError> { + pub fn rename_table(&self, tx: &mut MutTx, table_id: TableId, new_name: TableName) -> Result<(), DBError> { Ok(self.inner.rename_table_mut_tx(tx, table_id, new_name)?) } @@ -1470,12 +1479,17 @@ impl RelationalDB { } /// Reports the metrics for `reducer`, using counters provided by `db`. - pub fn report_mut_tx_metrics(&self, reducer: ReducerName, metrics: TxMetrics, tx_data: Option>) { + pub fn report_mut_tx_metrics( + &self, + reducer: Option, + metrics: TxMetrics, + tx_data: Option>, + ) { self.report_tx_metrics(reducer, tx_data, Some(metrics), None); } /// Reports subscription metrics for `reducer`, using counters provided by `db`. - pub fn report_read_tx_metrics(&self, reducer: ReducerName, metrics: TxMetrics) { + pub fn report_read_tx_metrics(&self, reducer: Option, metrics: TxMetrics) { self.report_tx_metrics(reducer, None, None, Some(metrics)); } @@ -2215,21 +2229,22 @@ pub mod tests_utils { pub fn create_view_for_test( db: &RelationalDB, name: &str, - schema: &[(&str, AlgebraicType)], + schema: &[(&'static str, AlgebraicType)], is_anonymous: bool, ) -> Result<(ViewId, TableId), DBError> { let mut builder = RawModuleDefV9Builder::new(); // Add the view's product type to the typespace + let name = RawIdentifier::new(name); let type_ref = builder.add_algebraic_type( [], - name, + name.clone(), AlgebraicType::Product(ProductType::from_iter(schema.iter().cloned())), true, ); builder.add_view( - name, + name.clone(), 0, true, is_anonymous, @@ -2238,7 +2253,7 @@ pub mod tests_utils { ); let module_def: ModuleDef = builder.finish().try_into()?; - let view_def: &ViewDef = module_def.view(name).expect("view not found"); + let view_def: &ViewDef = module_def.view(&*name).expect("view not found"); // Allocate a backing table and return its table id db.with_auto_commit(Workload::Internal, |tx| db.create_view(tx, &module_def, view_def)) @@ -2379,7 +2394,7 @@ mod tests { f: impl FnOnce(RawTableDefBuilder<'_>) -> RawTableDefBuilder, ) -> TableSchema { let mut builder = RawModuleDefV9Builder::new(); - f(builder.build_table_with_new_type(name, columns, true)); + f(builder.build_table_with_new_type(RawIdentifier::new(name), columns, true)); let raw = builder.finish(); let def: ModuleDef = raw.try_into().expect("table validation failed"); let table = def.table(name).expect("table not found"); @@ -3160,7 +3175,7 @@ mod tests { let mut tx = begin_mut_tx(&stdb); let table_id = stdb.create_table(&mut tx, table_indexed(true))?; - stdb.rename_table(&mut tx, table_id, "YourTable")?; + stdb.rename_table(&mut tx, table_id, TableName::for_test("YourTable"))?; let table_name = stdb.table_name_from_id_mut(&tx, table_id)?; assert_eq!(Some("YourTable"), table_name.as_ref().map(Cow::as_ref)); @@ -3278,7 +3293,7 @@ mod tests { let timestamp = Timestamp::now(); let workload = |name: &str| { Workload::Reducer(ReducerContext { - name: ReducerName::new_from_str(name), + name: ReducerName::for_test(name), caller_identity: Identity::__dummy(), caller_connection_id: ConnectionId::ZERO, timestamp, diff --git a/crates/core/src/db/update.rs b/crates/core/src/db/update.rs index ba2471c9787..896a1325e83 100644 --- a/crates/core/src/db/update.rs +++ b/crates/core/src/db/update.rs @@ -257,7 +257,7 @@ fn auto_migrate_database( stdb.drop_sequence(tx, sequence_schema.sequence_id)?; } spacetimedb_schema::auto_migrate::AutoMigrateStep::ChangeColumns(table_name) => { - let table_def = plan.new.stored_in_table_def(table_name).unwrap(); + let table_def = plan.new.stored_in_table_def(&table_name.clone().into()).unwrap(); let table_id = stdb.table_id_from_name_mut(tx, table_name).unwrap().unwrap(); let column_schemas = column_schemas_from_defs(plan.new, &table_def.columns, table_id); @@ -266,7 +266,7 @@ fn auto_migrate_database( stdb.alter_table_row_type(tx, table_id, column_schemas)?; } spacetimedb_schema::auto_migrate::AutoMigrateStep::ChangeAccess(table_name) => { - let table_def = plan.new.stored_in_table_def(table_name).unwrap(); + let table_def = plan.new.stored_in_table_def(&table_name.clone().into()).unwrap(); stdb.alter_table_access(tx, table_name, table_def.table_access.into())?; } spacetimedb_schema::auto_migrate::AutoMigrateStep::AddSchedule(_) => { @@ -287,7 +287,10 @@ fn auto_migrate_database( stdb.drop_row_level_security(tx, sql_rls.clone())?; } spacetimedb_schema::auto_migrate::AutoMigrateStep::AddColumns(table_name) => { - let table_def = plan.new.stored_in_table_def(table_name).expect("table must exist"); + let table_def = plan + .new + .stored_in_table_def(&table_name.clone().into()) + .expect("table must exist"); let table_id = stdb.table_id_from_name_mut(tx, table_name).unwrap().unwrap(); let column_schemas = column_schemas_from_defs(plan.new, &table_def.columns, table_id); diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 7b3ed973d2e..7ead93ac2c8 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -295,10 +295,8 @@ pub enum NodesError { NotInAnonTransaction, #[error("ABI call not allowed while holding open a transaction: {0}")] WouldBlockTransaction(AbiCall), - #[error("table with name {0:?} already exists")] - AlreadyExists(String), #[error("table with name `{0}` start with 'st_' and that is reserved for internal system tables.")] - SystemName(Box), + SystemName(TableName), #[error("internal db error: {0}")] Internal(#[source] Box), #[error(transparent)] @@ -314,8 +312,6 @@ pub enum NodesError { impl From for NodesError { fn from(e: DBError) -> Self { match e { - DBError::Datastore(DatastoreError::Table(TableError::Exist(name))) => Self::AlreadyExists(name), - DBError::Datastore(DatastoreError::Table(TableError::System(name))) => Self::SystemName(name), DBError::Datastore( DatastoreError::Table(TableError::IdNotFound(_, _)) | DatastoreError::Table(TableError::NotFound(_)), ) => Self::TableNotFound, diff --git a/crates/core/src/host/instance_env.rs b/crates/core/src/host/instance_env.rs index e846026bd9a..7c014c4d937 100644 --- a/crates/core/src/host/instance_env.rs +++ b/crates/core/src/host/instance_env.rs @@ -26,6 +26,7 @@ use spacetimedb_sats::{ buffer::{CountWriter, TeeWriter}, AlgebraicValue, ProductValue, }; +use spacetimedb_schema::identifier::Identifier; use spacetimedb_table::indexes::RowPointer; use spacetimedb_table::table::RowRef; use std::fmt::Display; @@ -46,7 +47,7 @@ pub struct InstanceEnv { /// The type of the last, including current, function to be executed by this environment. pub func_type: FuncCallType, /// The name of the last, including current, function to be executed by this environment. - pub func_name: String, + pub func_name: Option, /// Are we in an anonymous tx context? in_anon_tx: bool, /// A procedure's last known transaction offset. @@ -232,7 +233,7 @@ impl InstanceEnv { // arbitrary - change if we need to recognize that an `InstanceEnv` has never // run a function func_type: FuncCallType::Reducer, - func_name: String::from(""), + func_name: None, in_anon_tx: false, procedure_last_tx_offset: None, } @@ -244,11 +245,17 @@ impl InstanceEnv { } /// Signal to this `InstanceEnv` that a function call is beginning. - pub fn start_funcall(&mut self, name: &str, ts: Timestamp, func_type: FuncCallType) { + pub fn start_funcall(&mut self, name: Identifier, ts: Timestamp, func_type: FuncCallType) { self.start_time = ts; self.start_instant = Instant::now(); self.func_type = func_type; - name.clone_into(&mut self.func_name); + self.func_name = Some(name); + } + + /// Returns the name of the most recent reducer to be run in this environment, + /// or `None` if no reducer is actively being invoked. + pub fn log_record_function(&self) -> Option<&str> { + self.func_name.as_deref() } fn get_tx(&self) -> Result + '_, GetTxError> { @@ -775,7 +782,7 @@ impl InstanceEnv { Ok(()) => { let message = format!( "aborting dangling anonymous transaction in procedure {}", - self.func_name + self.func_name.as_deref().unwrap_or("") ); self.console_log_simple_message(LogLevel::Error, None, &message); } diff --git a/crates/core/src/host/mod.rs b/crates/core/src/host/mod.rs index 2c2f6b2a6fd..0daa9c359bc 100644 --- a/crates/core/src/host/mod.rs +++ b/crates/core/src/host/mod.rs @@ -53,7 +53,7 @@ impl FunctionArgs { fn into_tuple(self, seed: ArgsSeed<'_, Def>) -> Result { self._into_tuple(seed).map_err(|err| InvalidFunctionArguments { err, - function_name: seed.name().into(), + function_name: seed.name().clone(), }) } fn _into_tuple(self, seed: ArgsSeed<'_, Def>) -> anyhow::Result { @@ -113,6 +113,7 @@ impl Default for ArgsTuple { // TODO(noa): replace imports from this module with imports straight from primitives. pub use spacetimedb_primitives::ReducerId; +use spacetimedb_schema::identifier::Identifier; /// Inner error type for [`InvalidReducerArguments`] and [`InvalidProcedureArguments`]. #[derive(thiserror::Error, Debug)] @@ -120,7 +121,7 @@ pub use spacetimedb_primitives::ReducerId; pub struct InvalidFunctionArguments { #[source] err: anyhow::Error, - function_name: Box, + function_name: Identifier, } /// Newtype over [`InvalidFunctionArguments`] which renders with the word "reducer". diff --git a/crates/core/src/host/module_host.rs b/crates/core/src/host/module_host.rs index bc2b5b8ee9a..882ed75b1f6 100644 --- a/crates/core/src/host/module_host.rs +++ b/crates/core/src/host/module_host.rs @@ -57,9 +57,11 @@ use spacetimedb_lib::metrics::ExecutionMetrics; use spacetimedb_lib::{ConnectionId, Timestamp}; use spacetimedb_primitives::{ArgId, ProcedureId, TableId, ViewFnPtr, ViewId}; use spacetimedb_query::compile_subscription; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, ProductValue}; use spacetimedb_schema::auto_migrate::{AutoMigrateError, MigrationPolicy}; use spacetimedb_schema::def::{ModuleDef, ProcedureDef, ReducerDef, TableDef, ViewDef}; +use spacetimedb_schema::identifier::Identifier; use spacetimedb_schema::reducer_name::ReducerName; use spacetimedb_schema::schema::{Schema, TableSchema}; use spacetimedb_schema::table_name::TableName; @@ -181,7 +183,7 @@ impl EventStatus { #[derive(Debug, Clone, Default)] pub struct ModuleFunctionCall { - pub reducer: ReducerName, + pub reducer: Option, pub reducer_id: ReducerId, pub args: ArgsTuple, } @@ -189,7 +191,7 @@ pub struct ModuleFunctionCall { impl ModuleFunctionCall { pub fn update() -> Self { Self { - reducer: ReducerName::new_from_str("update"), + reducer: None, reducer_id: u32::MAX.into(), args: ArgsTuple::nullary(), } @@ -530,7 +532,7 @@ pub fn call_identity_connected( let reducer_lookup = module.module_def.lifecycle_reducer(Lifecycle::OnConnect); let stdb = module.relational_db(); let workload = Workload::reducer_no_args( - "call_identity_connected", + ReducerName::new(Identifier::new_assume_valid("call_identity_connected".into())), caller_auth.claims.identity, caller_connection_id, ); @@ -678,11 +680,11 @@ pub enum ViewCommandResult { Sql { result: Result, - head: Vec<(Box, AlgebraicType)>, + head: Vec<(RawIdentifier, AlgebraicType)>, }, } pub struct CallViewParams { - pub view_name: Box, + pub view_name: Identifier, pub view_id: ViewId, pub table_id: TableId, pub fn_ptr: ViewFnPtr, @@ -1286,12 +1288,12 @@ impl ModuleHost { let reducer_lookup = info.module_def.lifecycle_reducer(Lifecycle::OnDisconnect); let reducer_name = reducer_lookup .as_ref() - .map(|(_, def)| &*def.name) - .unwrap_or("__identity_disconnected__"); + .map(|(_, def)| def.name.clone()) + .unwrap_or_else(|| ReducerName::new(Identifier::new_assume_valid("__identity_disconnected__".into()))); let is_client_exist = |mut_tx: &MutTxId| mut_tx.st_client_row(caller_identity, caller_connection_id).is_some(); - let workload = || Workload::reducer_no_args(reducer_name, caller_identity, caller_connection_id); + let workload = || Workload::reducer_no_args(reducer_name.clone(), caller_identity, caller_connection_id); // Decrement the number of subscribers for each view this caller is subscribed to let dec_view_subscribers = |tx: &mut MutTxId| { @@ -1327,7 +1329,7 @@ impl ModuleHost { ); InvalidReducerArguments(InvalidFunctionArguments { err: err.into(), - function_name: reducer_name.into(), + function_name: reducer_name.clone().into(), }) .into() }) @@ -1651,7 +1653,7 @@ impl ModuleHost { sql_text: String, auth: AuthCtx, subs: Option, - head: &mut Vec<(Box, AlgebraicType)>, + head: &mut Vec<(RawIdentifier, AlgebraicType)>, ) -> Result { let cmd = ViewCommand::Sql { db, @@ -1800,7 +1802,7 @@ impl ModuleHost { view_collector.collect_views(&mut view_ids); for view_id in view_ids { let st_view_row = tx.lookup_st_view(view_id)?; - let view_name = st_view_row.view_name; + let view_name = st_view_row.view_name.into(); let view_id = st_view_row.view_id; let table_id = st_view_row.table_id.ok_or(ViewCallError::TableDoesNotExist(view_id))?; let is_anonymous = st_view_row.is_anonymous; @@ -1875,7 +1877,7 @@ impl ModuleHost { fn call_view( instance: &mut RefInstance<'_, I>, tx: MutTxId, - view_name: &str, + view_name: &Identifier, view_id: ViewId, table_id: TableId, args: FunctionArgs, @@ -1910,7 +1912,7 @@ impl ModuleHost { fn call_view_inner( instance: &mut RefInstance<'_, I>, tx: MutTxId, - name: &str, + name: &Identifier, view_id: ViewId, table_id: TableId, fn_ptr: ViewFnPtr, @@ -1919,7 +1921,7 @@ impl ModuleHost { args: ArgsTuple, row_type: AlgebraicTypeRef, ) -> Result<(ViewCallResult, bool), ViewCallError> { - let view_name = name.to_owned().into_boxed_str(); + let view_name = name.clone(); let params = CallViewParams { timestamp: Timestamp::now(), view_name, diff --git a/crates/core/src/host/v8/budget.rs b/crates/core/src/host/v8/budget.rs index 7181787025f..46db6e24708 100644 --- a/crates/core/src/host/v8/budget.rs +++ b/crates/core/src/host/v8/budget.rs @@ -56,7 +56,7 @@ pub(super) extern "C" fn cb_log_long_running(mut isolate: v8::UnsafeRawIsolatePt return; }; let database = env.instance_env.replica_ctx.database_identity; - let reducer = env.funcall_name(); + let reducer = env.instance_env.log_record_function().unwrap_or_default(); let dur = env.reducer_start().elapsed(); tracing::warn!(reducer, ?database, "JavaScript has been running for {dur:?}"); } diff --git a/crates/core/src/host/v8/mod.rs b/crates/core/src/host/v8/mod.rs index 2b76c391bc7..2d15a71ec02 100644 --- a/crates/core/src/host/v8/mod.rs +++ b/crates/core/src/host/v8/mod.rs @@ -44,6 +44,7 @@ use spacetimedb_datastore::locking_tx_datastore::FuncCallType; use spacetimedb_datastore::traits::Program; use spacetimedb_lib::{ConnectionId, Identity, RawModuleDef, Timestamp}; use spacetimedb_schema::auto_migrate::MigrationPolicy; +use spacetimedb_schema::identifier::Identifier; use spacetimedb_table::static_assert_size; use std::panic::AssertUnwindSafe; use std::sync::{Arc, LazyLock}; @@ -236,20 +237,14 @@ impl JsInstanceEnv { /// /// Returns the handle used by reducers to read from `args` /// as well as the handle used to write the error message, if any. - fn start_funcall(&mut self, name: &str, ts: Timestamp, func_type: FuncCallType) { + fn start_funcall(&mut self, name: Identifier, ts: Timestamp, func_type: FuncCallType) { self.instance_env.start_funcall(name, ts, func_type); } - /// Returns the name of the most recent reducer to be run in this environment. - fn funcall_name(&self) -> &str { - &self.instance_env.func_name - } - /// Returns the name of the most recent reducer to be run in this environment, /// or `None` if no reducer is actively being invoked. fn log_record_function(&self) -> Option<&str> { - let function = self.funcall_name(); - (!function.is_empty()).then_some(function) + self.instance_env.log_record_function() } /// Returns the name of the most recent reducer to be run in this environment. @@ -882,7 +877,7 @@ where // Start the timer. // We'd like this tightly around `call`. - env.start_funcall(op.name(), op.timestamp(), op.call_type()); + env.start_funcall(op.name().clone(), op.timestamp(), op.call_type()); let call_result = catch_exception(scope, |scope| call(scope, op)).map_err(|(e, can_continue)| { // Convert `can_continue` to whether the isolate has "trapped". @@ -969,7 +964,7 @@ mod test { let hooks = get_hooks(scope).unwrap(); let op = ReducerOp { id: ReducerId(42), - name: &ReducerName::new_from_str("foobar"), + name: &ReducerName::for_test("foobar"), caller_identity: &Identity::ONE, caller_connection_id: &ConnectionId::ZERO, timestamp: Timestamp::from_micros_since_unix_epoch(24), diff --git a/crates/core/src/host/wasm_common.rs b/crates/core/src/host/wasm_common.rs index dd77ca0427d..9e58b8e67e1 100644 --- a/crates/core/src/host/wasm_common.rs +++ b/crates/core/src/host/wasm_common.rs @@ -398,7 +398,6 @@ pub fn err_to_errno(err: NodesError) -> Result<(NonZeroU16, Option), Nod NodesError::IndexRowNotFound => errno::NO_SUCH_ROW, NodesError::IndexCannotSeekRange => errno::WRONG_INDEX_ALGO, NodesError::ScheduleError(ScheduleError::DelayTooLong(_)) => errno::SCHEDULE_AT_DELAY_TOO_LONG, - NodesError::AlreadyExists(_) => errno::UNIQUE_ALREADY_EXISTS, NodesError::HttpError(message) => return Ok((errno::HTTP_ERROR, Some(message))), NodesError::Internal(ref internal) => match **internal { DBError::Datastore(DatastoreError::Index(IndexError::UniqueConstraintViolation( diff --git a/crates/core/src/host/wasm_common/module_host_actor.rs b/crates/core/src/host/wasm_common/module_host_actor.rs index ab459dd9c47..4076a7448c6 100644 --- a/crates/core/src/host/wasm_common/module_host_actor.rs +++ b/crates/core/src/host/wasm_common/module_host_actor.rs @@ -51,6 +51,7 @@ use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, Deserialize, ProductValu use spacetimedb_schema::auto_migrate::{MigratePlan, MigrationPolicy, MigrationPolicyError}; use spacetimedb_schema::def::deserialize::FunctionDef; use spacetimedb_schema::def::{ModuleDef, ViewDef}; +use spacetimedb_schema::identifier::Identifier; use spacetimedb_schema::reducer_name::ReducerName; use spacetimedb_subscription::SubscriptionPlan; use std::sync::Arc; @@ -649,7 +650,7 @@ impl InstanceCommon { for sub in tx.lookup_st_view_subs(view_id)? { view_calls.push(CallViewParams { - view_name: view_name.to_owned().into(), + view_name: view_name.clone(), view_id, table_id, fn_ptr: *fn_ptr, @@ -682,7 +683,7 @@ impl InstanceCommon { // We've already validated by this point that the procedure exists, // so it's fine to use the panicking `procedure_by_id`. let procedure_def = self.info.module_def.procedure_by_id(procedure_id); - let procedure_name: &str = &procedure_def.name; + let procedure_name = &procedure_def.name; // TODO(observability): Add tracing spans, energy, metrics? // These will require further thinking once we implement procedure suspend/resume, @@ -690,7 +691,7 @@ impl InstanceCommon { let op = ProcedureOp { id: procedure_id, - name: procedure_name.into(), + name: procedure_name.clone(), caller_identity, caller_connection_id, timestamp, @@ -907,7 +908,7 @@ impl InstanceCommon { caller_identity, caller_connection_id: caller_connection_id_opt, function_call: ModuleFunctionCall { - reducer: reducer_name.clone(), + reducer: Some(reducer_name.clone()), reducer_id, args, }, @@ -1282,7 +1283,7 @@ impl InstanceCommon { .unwrap_or_else(|| panic!("view with fn_ptr `{}` not found", info.fn_ptr)); CallViewParams { - view_name: view_def.name.clone().into(), + view_name: view_def.name.clone(), view_id: info.view_id, table_id: info.table_id, fn_ptr: view_def.fn_ptr, @@ -1552,7 +1553,7 @@ fn lifecyle_modifications_to_tx( */ pub trait InstanceOp { - fn name(&self) -> &str; + fn name(&self) -> &Identifier; fn timestamp(&self) -> Timestamp; fn call_type(&self) -> FuncCallType; } @@ -1560,7 +1561,7 @@ pub trait InstanceOp { /// Describes a view call in a cheaply shareable way. #[derive(Clone, Debug)] pub struct ViewOp<'a> { - pub name: &'a str, + pub name: &'a Identifier, pub view_id: ViewId, pub table_id: TableId, pub fn_ptr: ViewFnPtr, @@ -1570,7 +1571,7 @@ pub struct ViewOp<'a> { } impl InstanceOp for ViewOp<'_> { - fn name(&self) -> &str { + fn name(&self) -> &Identifier { self.name } @@ -1591,7 +1592,7 @@ impl InstanceOp for ViewOp<'_> { /// Describes an anonymous view call in a cheaply shareable way. #[derive(Clone, Debug)] pub struct AnonymousViewOp<'a> { - pub name: &'a str, + pub name: &'a Identifier, pub view_id: ViewId, pub table_id: TableId, pub fn_ptr: ViewFnPtr, @@ -1600,7 +1601,7 @@ pub struct AnonymousViewOp<'a> { } impl InstanceOp for AnonymousViewOp<'_> { - fn name(&self) -> &str { + fn name(&self) -> &Identifier { self.name } @@ -1631,8 +1632,8 @@ pub struct ReducerOp<'a> { } impl InstanceOp for ReducerOp<'_> { - fn name(&self) -> &str { - self.name + fn name(&self) -> &Identifier { + self.name.as_identifier() } fn timestamp(&self) -> Timestamp { self.timestamp @@ -1667,7 +1668,7 @@ impl From> for execution_context::ReducerContext { #[derive(Clone, Debug)] pub struct ProcedureOp { pub id: ProcedureId, - pub name: Box, + pub name: Identifier, pub caller_identity: Identity, pub caller_connection_id: ConnectionId, pub timestamp: Timestamp, @@ -1675,7 +1676,7 @@ pub struct ProcedureOp { } impl InstanceOp for ProcedureOp { - fn name(&self) -> &str { + fn name(&self) -> &Identifier { &self.name } fn timestamp(&self) -> Timestamp { diff --git a/crates/core/src/host/wasmtime/wasm_instance_env.rs b/crates/core/src/host/wasmtime/wasm_instance_env.rs index bcd28adb1a5..4401156be9a 100644 --- a/crates/core/src/host/wasmtime/wasm_instance_env.rs +++ b/crates/core/src/host/wasmtime/wasm_instance_env.rs @@ -14,6 +14,7 @@ use spacetimedb_data_structures::map::IntMap; use spacetimedb_datastore::locking_tx_datastore::FuncCallType; use spacetimedb_lib::{bsatn, ConnectionId, Timestamp}; use spacetimedb_primitives::{errno, ColId}; +use spacetimedb_schema::identifier::Identifier; use std::future::Future; use std::num::NonZeroU32; use std::time::Instant; @@ -220,7 +221,7 @@ impl WasmInstanceEnv { /// as well as the handle used to write the reducer error message or procedure return value. pub fn start_funcall( &mut self, - name: &str, + name: Identifier, args: bytes::Bytes, ts: Timestamp, func_type: FuncCallType, @@ -237,16 +238,10 @@ impl WasmInstanceEnv { (args, errors) } - /// Returns the name of the most recent reducer or procedure to be run in this environment. - pub fn funcall_name(&self) -> &str { - &self.instance_env.func_name - } - /// Returns the name of the most recent reducer or procedure to be run in this environment, /// or `None` if no reducer or procedure is actively being invoked. - fn log_record_function(&self) -> Option<&str> { - let function = self.funcall_name(); - (!function.is_empty()).then_some(function) + pub fn log_record_function(&self) -> Option<&str> { + self.instance_env.log_record_function() } /// Returns the start time of the most recent reducer or procedure to be run in this environment. diff --git a/crates/core/src/host/wasmtime/wasmtime_module.rs b/crates/core/src/host/wasmtime/wasmtime_module.rs index f7bbe15db90..71aab2c1ffb 100644 --- a/crates/core/src/host/wasmtime/wasmtime_module.rs +++ b/crates/core/src/host/wasmtime/wasmtime_module.rs @@ -164,7 +164,7 @@ impl module_host_actor::WasmInstancePre for WasmtimeModule { store.epoch_deadline_callback(|store| { let env = store.data(); let database = env.instance_env().replica_ctx.database_identity; - let funcall = env.funcall_name(); + let funcall = env.log_record_function().unwrap_or_default(); let dur = env.funcall_start().elapsed(); // TODO(procedure-timing): This measurement is not super meaningful for procedures, // which may (will) suspend execution and therefore may not have been continuously running since `env.funcall_start`. @@ -405,10 +405,11 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { // Prepare arguments to the reducer + the error sink & start timings. let args_bytes = op.args.get_bsatn().clone(); + let reducer_name = op.name.clone().into(); let (args_source, errors_sink) = store .data_mut() - .start_funcall(op.name, args_bytes, op.timestamp, op.call_type()); + .start_funcall(reducer_name, args_bytes, op.timestamp, op.call_type()); let call_result = call_sync_typed_func( &self.call_reducer, @@ -448,7 +449,7 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { let (args_source, errors_sink) = store .data_mut() - .start_funcall(op.name, args_bytes, op.timestamp, op.call_type()); + .start_funcall(op.name.clone(), args_bytes, op.timestamp, op.call_type()); let Some(call_view) = self.call_view.as_ref() else { return module_host_actor::ViewExecuteResult { @@ -498,7 +499,7 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { let (args_source, errors_sink) = store .data_mut() - .start_funcall(op.name, args_bytes, op.timestamp, op.call_type()); + .start_funcall(op.name.clone(), args_bytes, op.timestamp, op.call_type()); let Some(call_view_anon) = self.call_view_anon.as_ref() else { return module_host_actor::ViewExecuteResult { @@ -543,7 +544,7 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { let (args_source, result_sink) = store .data_mut() - .start_funcall(&op.name, op.arg_bytes, op.timestamp, FuncCallType::Procedure); + .start_funcall(op.name.clone(), op.arg_bytes, op.timestamp, FuncCallType::Procedure); let Some(call_procedure) = self.call_procedure.as_ref() else { let res = module_host_actor::ProcedureExecuteResult { diff --git a/crates/core/src/sql/execute.rs b/crates/core/src/sql/execute.rs index 11e63d79a85..8816f74d51b 100644 --- a/crates/core/src/sql/execute.rs +++ b/crates/core/src/sql/execute.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use std::time::Duration; use super::ast::SchemaViewer; -use crate::db::relational_db::{RelationalDB, Tx}; +use crate::db::relational_db::RelationalDB; use crate::energy::EnergyQuanta; use crate::error::DBError; use crate::estimation::estimate_rows_scanned; @@ -19,7 +19,6 @@ use crate::vm::{check_row_limit, DbProgram, TxMode}; use anyhow::anyhow; use smallvec::SmallVec; use spacetimedb_datastore::execution_context::Workload; -use spacetimedb_datastore::locking_tx_datastore::state_view::StateView; use spacetimedb_datastore::traits::IsolationLevel; use spacetimedb_expr::statement::Statement; use spacetimedb_lib::identity::AuthCtx; @@ -27,7 +26,7 @@ use spacetimedb_lib::metrics::ExecutionMetrics; use spacetimedb_lib::Timestamp; use spacetimedb_lib::{AlgebraicType, ProductType, ProductValue}; use spacetimedb_query::{compile_sql_stmt, execute_dml_stmt, execute_select_stmt}; -use spacetimedb_schema::relation::FieldName; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_vm::eval::run_ast; use spacetimedb_vm::expr::{CodeResult, CrudExpr, Expr}; use spacetimedb_vm::relation::MemTable; @@ -199,7 +198,7 @@ pub async fn run( auth: AuthCtx, subs: Option, module: Option, - head: &mut Vec<(Box, AlgebraicType)>, + head: &mut Vec<(RawIdentifier, AlgebraicType)>, ) -> Result { match module { Some(module) => module.call_view_sql(db, sql_text, auth, subs, head).await, @@ -216,7 +215,7 @@ pub(crate) fn run_with_instance( sql_text: String, auth: AuthCtx, subs: Option, - head: &mut Vec<(Box, AlgebraicType)>, + head: &mut Vec<(RawIdentifier, AlgebraicType)>, ) -> Result<(SqlResult, bool), DBError> { run_inner::(Some(instance), db, sql_text, auth, subs, head) } @@ -227,7 +226,7 @@ fn run_inner( sql_text: String, auth: AuthCtx, subs: Option, - head: &mut Vec<(Box, AlgebraicType)>, + head: &mut Vec<(RawIdentifier, AlgebraicType)>, ) -> Result<(SqlResult, bool), DBError> { // We parse the sql statement in a mutable transaction. // If it turns out to be a query, we downgrade the tx. @@ -258,7 +257,7 @@ fn run_inner( // Compute the header for the result set stmt.for_each_return_field(|col_name, col_type| { - head.push((col_name.into(), col_type.clone())); + head.push((col_name.clone(), col_type.clone())); }); // Evaluate the query @@ -365,16 +364,6 @@ fn run_inner( } } -/// Translates a `FieldName` to the field's name. -pub fn translate_col(tx: &Tx, field: FieldName) -> Option> { - Some( - tx.get_schema(field.table)? - .get_column(field.col.idx())? - .col_name - .clone(), - ) -} - #[cfg(test)] pub(crate) mod tests { use std::sync::Arc; diff --git a/crates/core/src/subscription/module_subscription_manager.rs b/crates/core/src/subscription/module_subscription_manager.rs index ad22764e4e0..b88dc41182c 100644 --- a/crates/core/src/subscription/module_subscription_manager.rs +++ b/crates/core/src/subscription/module_subscription_manager.rs @@ -2180,7 +2180,7 @@ mod tests { // select * from t let table_update = DatabaseTableUpdate { table_id, - table_name: TableName::new_from_str("t"), + table_name: TableName::for_test("t"), inserts: [product![2u8]].into(), deletes: [product![3u8]].into(), }; @@ -2198,7 +2198,7 @@ mod tests { // Only: select * from t let table_update = DatabaseTableUpdate { table_id, - table_name: TableName::new_from_str("t"), + table_name: TableName::for_test("t"), inserts: [product![8u8]].into(), deletes: [product![9u8]].into(), }; @@ -2239,7 +2239,7 @@ mod tests { // Therefore we must evaluate it for any update on `t`. let table_update = DatabaseTableUpdate { table_id: t_id, - table_name: TableName::new_from_str("t"), + table_name: TableName::for_test("t"), inserts: [product![0u8, 0u8]].into(), deletes: [].into(), }; @@ -2255,7 +2255,7 @@ mod tests { // Yes, because `s.a = 1`. let table_update = DatabaseTableUpdate { table_id: s_id, - table_name: TableName::new_from_str("s"), + table_name: TableName::for_test("s"), inserts: [product![0u8, 1u8]].into(), deletes: [].into(), }; @@ -2271,7 +2271,7 @@ mod tests { // No, because `s.a != 1`. let table_update = DatabaseTableUpdate { table_id: s_id, - table_name: TableName::new_from_str("s"), + table_name: TableName::for_test("s"), inserts: [product![0u8, 2u8]].into(), deletes: [].into(), }; @@ -2526,7 +2526,7 @@ mod tests { caller_identity: id0, caller_connection_id: Some(client0.id.connection_id), function_call: ModuleFunctionCall { - reducer: ReducerName::new_from_str("DummyReducer"), + reducer: Some(ReducerName::for_test("DummyReducer")), reducer_id: u32::MAX.into(), args: ArgsTuple::nullary(), }, diff --git a/crates/core/src/subscription/query.rs b/crates/core/src/subscription/query.rs index 5abc1b552e6..979127b720d 100644 --- a/crates/core/src/subscription/query.rs +++ b/crates/core/src/subscription/query.rs @@ -205,7 +205,7 @@ mod tests { fn insert_op(table_id: TableId, table_name: &str, row: ProductValue) -> DatabaseTableUpdate { DatabaseTableUpdate { table_id, - table_name: TableName::new_from_str(table_name), + table_name: TableName::for_test(table_name), deletes: [].into(), inserts: [row].into(), } @@ -214,7 +214,7 @@ mod tests { fn delete_op(table_id: TableId, table_name: &str, row: ProductValue) -> DatabaseTableUpdate { DatabaseTableUpdate { table_id, - table_name: TableName::new_from_str(table_name), + table_name: TableName::for_test(table_name), deletes: [row].into(), inserts: [].into(), } @@ -242,7 +242,7 @@ mod tests { let data = DatabaseTableUpdate { table_id: schema.table_id, - table_name: TableName::new_from_str(table_name), + table_name: TableName::for_test(table_name), deletes: [].into(), inserts: [row.clone()].into(), }; @@ -449,7 +449,7 @@ mod tests { let update = DatabaseUpdate { tables: [DatabaseTableUpdate { table_id, - table_name: TableName::new_from_str("test"), + table_name: TableName::for_test("test"), deletes: deletes.into(), inserts: [].into(), }] @@ -532,7 +532,7 @@ mod tests { let data = DatabaseTableUpdate { table_id: schema.table_id, - table_name: TableName::new_from_str("inventory"), + table_name: TableName::for_test("inventory"), deletes: [].into(), inserts: [row.clone()].into(), }; @@ -646,14 +646,14 @@ mod tests { let data1 = DatabaseTableUpdate { table_id: schema_1.table_id, - table_name: TableName::new_from_str("inventory"), + table_name: TableName::for_test("inventory"), deletes: [row_1].into(), inserts: [].into(), }; let data2 = DatabaseTableUpdate { table_id: schema_2.table_id, - table_name: TableName::new_from_str("player"), + table_name: TableName::for_test("player"), deletes: [].into(), inserts: [row_2].into(), }; @@ -1016,7 +1016,7 @@ mod tests { result.tables[0], DatabaseTableUpdate { table_id: lhs_id, - table_name: TableName::new_from_str("lhs"), + table_name: TableName::for_test("lhs"), deletes: [lhs_old].into(), inserts: [lhs_new].into(), }, @@ -1437,7 +1437,7 @@ mod tests { result.tables[0], DatabaseTableUpdate { table_id: lhs_id, - table_name: TableName::new_from_str("lhs"), + table_name: TableName::for_test("lhs"), deletes: [lhs_old].into(), inserts: [lhs_new].into(), }, diff --git a/crates/core/src/vm.rs b/crates/core/src/vm.rs index 3c07f901bf8..d8bb27a1caa 100644 --- a/crates/core/src/vm.rs +++ b/crates/core/src/vm.rs @@ -652,8 +652,10 @@ pub(crate) mod tests { }; use spacetimedb_lib::db::auth::{StAccess, StTableType}; use spacetimedb_lib::error::ResultTest; + use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::{product, AlgebraicType, ProductType, ProductValue}; use spacetimedb_schema::def::{BTreeAlgorithm, IndexAlgorithm}; + use spacetimedb_schema::identifier::Identifier; use spacetimedb_schema::relation::{FieldName, Header}; use spacetimedb_schema::schema::{ColumnSchema, IndexSchema, TableSchema}; use spacetimedb_schema::table_name::TableName; @@ -673,11 +675,12 @@ pub(crate) mod tests { let columns = schema .elements .iter() + .cloned() .enumerate() .map(|(i, element)| ColumnSchema { table_id: TableId::SENTINEL, - col_name: element.name.as_ref().unwrap().clone(), - col_type: element.algebraic_type.clone(), + col_name: Identifier::new(element.name.unwrap()).unwrap(), + col_type: element.algebraic_type, col_pos: ColId(i as _), }) .collect(); @@ -686,7 +689,7 @@ pub(crate) mod tests { tx, TableSchema::new( TableId::SENTINEL, - TableName::new_from_str(table_name), + TableName::for_test(table_name), None, columns, vec![], @@ -799,7 +802,7 @@ pub(crate) mod tests { .unwrap(); let st_table_row = StTableRow { table_id: ST_TABLE_ID, - table_name: TableName::new_from_str(ST_TABLE_NAME), + table_name: TableName::for_test(ST_TABLE_NAME), table_type: StTableType::System, table_access: StAccess::Public, table_primary_key: Some(StTableFields::TableId.into()), @@ -847,13 +850,13 @@ pub(crate) mod tests { let (schema, _) = with_auto_commit(&db, |tx| create_inv_table(&db, tx))?; let table_id = schema.table_id; let columns = ColList::from(ColId(0)); - let index_name = "idx_1"; + let index_name: RawIdentifier = "idx_1".into(); let is_unique = false; let index = IndexSchema { table_id, index_id: IndexId::SENTINEL, - index_name: index_name.into(), + index_name: index_name.clone(), index_algorithm: IndexAlgorithm::BTree(BTreeAlgorithm { columns: columns.clone(), }), @@ -865,13 +868,13 @@ pub(crate) mod tests { .with_select_cmp( OpCmp::Eq, FieldName::new(ST_INDEX_ID, StIndexFields::IndexName.into()), - scalar(index_name), + scalar(&*index_name), ) .unwrap(); let st_index_row = StIndexRow { index_id, - index_name: index_name.into(), + index_name: index_name.clone(), table_id, index_algorithm: StIndexAlgorithm::BTree { columns }, } diff --git a/crates/datastore/src/error.rs b/crates/datastore/src/error.rs index 56330afee0c..f0d52f2bc7b 100644 --- a/crates/datastore/src/error.rs +++ b/crates/datastore/src/error.rs @@ -3,8 +3,9 @@ use enum_as_inner::EnumAsInner; use spacetimedb_lib::db::raw_def::{v9::RawSql, RawIndexDefV8}; use spacetimedb_primitives::{ColId, ColList, IndexId, SequenceId, TableId, ViewId}; use spacetimedb_sats::buffer::DecodeError; -use spacetimedb_sats::{product_value::InvalidFieldError, satn::Satn}; -use spacetimedb_sats::{AlgebraicType, AlgebraicValue, ProductValue}; +use spacetimedb_sats::product_value::InvalidFieldError; +use spacetimedb_sats::raw_identifier::RawIdentifier; +use spacetimedb_sats::{AlgebraicType, AlgebraicValue}; use spacetimedb_schema::def::error::LibError; use spacetimedb_snapshot::SnapshotError; use spacetimedb_table::{ @@ -40,7 +41,7 @@ pub enum DatastoreError { #[derive(Error, Debug)] pub enum ViewError { #[error("view '{0}' not found")] - NotFound(Box), + NotFound(RawIdentifier), #[error("Table backing View '{0}' not found")] TableNotFound(ViewId), #[error("failed to deserialize view arguments from row")] @@ -63,10 +64,6 @@ pub enum ViewError { #[derive(Error, Debug, EnumAsInner)] pub enum TableError { - #[error("Table with name `{0}` start with 'st_' and that is reserved for internal system tables.")] - System(Box), - #[error("Table with name `{0}` already exists.")] - Exist(String), #[error("Table with name `{0}` not found.")] NotFound(String), #[error("Table with ID `{1}` not found in `{0}`.")] @@ -75,31 +72,8 @@ pub enum TableError { RawSqlNotFound(SystemTable, RawSql), #[error("Table with ID `{0}` not found in `TxState`.")] IdNotFoundState(TableId), - #[error("Column `{0}.{1}` is missing a name")] - ColumnWithoutName(String, ColId), - #[error("schema_for_table: Table has invalid schema: {0} Err: {1}")] - InvalidSchema(TableId, LibError), - #[error("Row has invalid row type for table: {0} Err: {1}", table_id, row.to_satn())] - RowInvalidType { table_id: TableId, row: ProductValue }, - #[error("failed to decode row in table")] - RowDecodeError(DecodeError), - #[error("Column with name `{0}` already exists")] - DuplicateColumnName(String), #[error("Column `{0}` not found")] ColumnNotFound(ColId), - #[error( - "DecodeError for field `{0}.{1}`, expect `{2}` but found `{3}`", - table, - field, - expect, - found - )] - DecodeField { - table: String, - field: Box, - expect: String, - found: String, - }, #[error(transparent)] Bflatn(#[from] bflatn_to::Error), #[error(transparent)] diff --git a/crates/datastore/src/execution_context.rs b/crates/datastore/src/execution_context.rs index 9c46d4e01ee..89be8c17759 100644 --- a/crates/datastore/src/execution_context.rs +++ b/crates/datastore/src/execution_context.rs @@ -4,8 +4,8 @@ use bytes::Bytes; use derive_more::Display; use spacetimedb_commitlog::{payload::txdata, Varchar}; use spacetimedb_lib::{ConnectionId, Identity, Timestamp}; -use spacetimedb_sats::bsatn; -use spacetimedb_schema::reducer_name::ReducerName; +use spacetimedb_sats::{bsatn, raw_identifier::RawIdentifier}; +use spacetimedb_schema::{identifier::Identifier, reducer_name::ReducerName}; /// Represents the context under which a database runtime method is executed. /// In particular it provides details about the currently executing txn to runtime operations. @@ -80,8 +80,11 @@ impl TryFrom<&txdata::Inputs> for ReducerContext { let caller_connection_id = bsatn::from_reader(args)?; let timestamp = bsatn::from_reader(args)?; + let name = RawIdentifier::new(&**inputs.reducer_name); + let name = ReducerName::new(Identifier::new_assume_valid(name)); + Ok(Self { - name: ReducerName::new_from_str(&inputs.reducer_name), + name, caller_identity, caller_connection_id, timestamp, @@ -108,9 +111,9 @@ pub enum Workload { impl Workload { /// Returns a reducer workload with no arguments to the reducer /// and the current timestamp. - pub fn reducer_no_args(name: &str, id: Identity, conn_id: ConnectionId) -> Self { + pub fn reducer_no_args(name: ReducerName, id: Identity, conn_id: ConnectionId) -> Self { Self::Reducer(ReducerContext { - name: ReducerName::new_from_str(name), + name, caller_identity: id, caller_connection_id: conn_id, timestamp: Timestamp::now(), @@ -187,8 +190,8 @@ impl ExecutionContext { /// If this is a reducer context, returns the name of the reducer. #[inline] - pub fn into_reducer_name(self) -> ReducerName { - self.reducer.map(|ctx| ctx.name).unwrap_or_default() + pub fn into_reducer_name(self) -> Option { + self.reducer.map(|ctx| ctx.name) } /// If this is a reducer context, returns the full reducer metadata. diff --git a/crates/datastore/src/locking_tx_datastore/committed_state.rs b/crates/datastore/src/locking_tx_datastore/committed_state.rs index bc23f9ae0ac..b597f742336 100644 --- a/crates/datastore/src/locking_tx_datastore/committed_state.rs +++ b/crates/datastore/src/locking_tx_datastore/committed_state.rs @@ -1037,7 +1037,7 @@ impl CommittedState { // Note that this may change in the future: some analytics and/or // timetravel queries may benefit from seeing all inputs, even if // the database state did not change. - tx_data.has_rows_or_connect_disconnect(ctx.reducer_context().map(|rcx| &*rcx.name)) + tx_data.has_rows_or_connect_disconnect(ctx.reducer_context().map(|rcx| &rcx.name)) } pub(super) fn drop_view_from_read_sets(&mut self, view_id: ViewId, sender: Option) { diff --git a/crates/datastore/src/locking_tx_datastore/datastore.rs b/crates/datastore/src/locking_tx_datastore/datastore.rs index 04b8d3de3b9..d562bff6f99 100644 --- a/crates/datastore/src/locking_tx_datastore/datastore.rs +++ b/crates/datastore/src/locking_tx_datastore/datastore.rs @@ -392,8 +392,8 @@ impl Tx for Locking { /// Returns: /// - [`TxOffset`], the smallest transaction offset visible to this transaction. /// - [`TxMetrics`], various measurements of the work performed by this transaction. - /// - `String`, the name of the reducer which ran within this transaction. - fn release_tx(&self, tx: Self::Tx) -> (TxOffset, TxMetrics, ReducerName) { + /// - `ReducerName`, the name of the reducer which ran within this transaction. + fn release_tx(&self, tx: Self::Tx) -> (TxOffset, TxMetrics, Option) { tx.release() } } @@ -526,7 +526,7 @@ impl MutTxDatastore for Locking { tx.drop_table(table_id) } - fn rename_table_mut_tx(&self, tx: &mut Self::MutTx, table_id: TableId, new_name: &str) -> Result<()> { + fn rename_table_mut_tx(&self, tx: &mut Self::MutTx, table_id: TableId, new_name: TableName) -> Result<()> { tx.rename_table(table_id, new_name) } @@ -782,7 +782,7 @@ impl TxMetrics { pub fn report<'a, R: MetricsRecorder + 'a>( &self, tx_data: Option<&TxData>, - reducer: &str, + reducer: Option<&ReducerName>, get_exec_counter: impl FnOnce(WorkloadType) -> &'a R, ) { let workload = &self.workload; @@ -793,6 +793,8 @@ impl TxMetrics { let elapsed_time = self.elapsed_time.as_secs_f64(); let cpu_time = cpu_time.as_secs_f64(); + let reducer = reducer.map(|r| &**r).unwrap_or_default(); + // Increment tx counter DB_METRICS .rdb_num_txns @@ -811,7 +813,6 @@ impl TxMetrics { get_exec_counter(self.workload).record(&self.exec_metrics); - // TODO(centril): simplify this by exposing `tx_data.for_table(table_id)`. if let Some(tx_data) = tx_data { for (table_id, table_entry) in tx_data.iter_table_entries() { let table_name = &table_entry.table_name; @@ -938,13 +939,13 @@ impl MutTx for Locking { } } - fn rollback_mut_tx(&self, tx: Self::MutTx) -> (TxOffset, TxMetrics, ReducerName) { + fn rollback_mut_tx(&self, tx: Self::MutTx) -> (TxOffset, TxMetrics, Option) { tx.rollback() } /// This method only updates the in-memory `committed_state`. /// For durability, see `RelationalDB::commit_tx`. - fn commit_mut_tx(&self, tx: Self::MutTx) -> Result> { + fn commit_mut_tx(&self, tx: Self::MutTx) -> Result)>> { Ok(Some(tx.commit())) } } @@ -1302,8 +1303,10 @@ mod tests { use spacetimedb_sats::algebraic_value::ser::value_serialize; use spacetimedb_sats::bsatn::ToBsatn; use spacetimedb_sats::layout::RowTypeLayout; + use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::{product, AlgebraicType, GroundSpacetimeType, SumTypeVariant, SumValue}; use spacetimedb_schema::def::BTreeAlgorithm; + use spacetimedb_schema::identifier::Identifier; use spacetimedb_schema::schema::{ columns_to_row_type, ColumnSchema, ConstraintSchema, IndexSchema, RowLevelSecuritySchema, ScheduleSchema, SequenceSchema, @@ -1427,7 +1430,7 @@ mod tests { Self { index_id: value.id.into(), table_id: value.table.into(), - index_name: value.name.into(), + index_name: RawIdentifier::new(value.name), index_algorithm: StIndexAlgorithm::BTree { columns: value.col }, } } @@ -1450,7 +1453,7 @@ mod tests { fn from(value: TableRow<'_>) -> Self { Self { table_id: value.id.into(), - table_name: TableName::new_from_str(value.name), + table_name: TableName::for_test(value.name), table_type: value.ty, table_access: value.access, table_primary_key: value.primary_key.map(ColList::new), @@ -1469,7 +1472,7 @@ mod tests { Self { table_id: value.table.into(), col_pos: value.pos.into(), - col_name: value.name.into(), + col_name: Identifier::for_test(value.name), col_type: value.ty.into(), } } @@ -1479,7 +1482,7 @@ mod tests { Self { table_id: value.table.into(), col_pos: value.pos.into(), - col_name: value.name.into(), + col_name: Identifier::for_test(value.name), col_type: value.ty, } } @@ -1496,7 +1499,7 @@ mod tests { fn from(value: SequenceRow<'_>) -> Self { Self { sequence_id: value.id.into(), - sequence_name: value.name.into(), + sequence_name: RawIdentifier::new(value.name), table_id: value.table.into(), col_pos: value.col_pos.into(), increment: 1, @@ -1512,7 +1515,7 @@ mod tests { fn from(value: SequenceRow<'_>) -> Self { Self { sequence_id: value.id.into(), - sequence_name: value.name.into(), + sequence_name: RawIdentifier::new(value.name), table_id: value.table.into(), col_pos: value.col_pos.into(), increment: 1, @@ -1533,7 +1536,7 @@ mod tests { fn from(value: ConstraintRow<'_>) -> Self { Self { constraint_id: value.constraint_id.into(), - constraint_name: value.constraint_name.into(), + constraint_name: RawIdentifier::new(value.constraint_name), table_id: value.table_id.into(), constraint_data: StConstraintData::Unique { columns: value.unique_columns.into(), @@ -1601,7 +1604,7 @@ mod tests { ) -> TableSchema { TableSchema::new( TableId::SENTINEL, - TableName::new_from_str("Foo"), + TableName::for_test("Foo"), None, cols.into(), indices.into(), @@ -3412,8 +3415,8 @@ mod tests { let schedule = ScheduleSchema { table_id: TableId::SENTINEL, schedule_id: ScheduleId::SENTINEL, - schedule_name: "schedule".into(), - function_name: "reducer".into(), + schedule_name: Identifier::for_test("schedule"), + function_name: Identifier::for_test("reducer"), at_column: 1.into(), }; let sum_ty = AlgebraicType::sum([("foo", AlgebraicType::Bool), ("bar", AlgebraicType::U16)]); diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index b1b4a699ae6..71bf3bda615 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -53,6 +53,7 @@ use spacetimedb_sats::{ bsatn::{self, to_writer, DecodeError, Deserializer}, de::{DeserializeSeed, WithBound}, memory_usage::MemoryUsage, + raw_identifier::RawIdentifier, ser::Serialize, AlgebraicType, AlgebraicValue, ProductType, ProductValue, WithTypespace, }; @@ -570,7 +571,7 @@ impl MutTxId { .. } = view_def; - let view_name: Box = name.clone().into(); + let view_name: RawIdentifier = name.clone().into(); // `create_table` inserts into `st_view` and updates the table schema. let view_id = self @@ -789,7 +790,7 @@ impl MutTxId { &StViewColumnRow { view_id, col_pos: def.col_id, - col_name: def.name.clone().into(), + col_name: def.name.clone(), col_type: def.ty.clone().into(), }, )?; @@ -912,9 +913,9 @@ impl MutTxId { } // TODO(centril): remove this. It doesn't seem to be used by anything. - pub fn rename_table(&mut self, table_id: TableId, new_name: &str) -> Result<()> { + pub fn rename_table(&mut self, table_id: TableId, new_name: TableName) -> Result<()> { // Update the table's name in st_tables. - self.update_st_table_row(table_id, |st| st.table_name = TableName::new_from_str(new_name)) + self.update_st_table_row(table_id, |st| st.table_name = new_name) } fn update_st_table_row(&mut self, table_id: TableId, updater: impl FnOnce(&mut StTableRow) -> R) -> Result { @@ -1104,7 +1105,7 @@ impl MutTxId { // Store sequence values to restore them later with new table. // Using a map from name to value as the new sequence ids will be different. // and I am not sure if we should rely on the order of sequences in the table schema. - let seq_values: HashMap, i128> = original_table_schema + let seq_values: HashMap<_, i128> = original_table_schema .sequences .iter() .map(|s| { @@ -1149,7 +1150,7 @@ impl MutTxId { fn create_table_and_update_seq( &mut self, table_schema: TableSchema, - seq_values: HashMap, i128>, + seq_values: HashMap, ) -> Result { let table_id = self.create_table(table_schema)?; let table_schema = self.schema_for_table(table_id)?; @@ -1935,7 +1936,7 @@ impl MutTxId { /// - [`TxData`], the set of inserts and deletes performed by this transaction. /// - [`TxMetrics`], various measurements of the work performed by this transaction. /// - `String`, the name of the reducer which ran during this transaction. - pub(super) fn commit(mut self) -> (TxOffset, TxData, TxMetrics, ReducerName) { + pub(super) fn commit(mut self) -> (TxOffset, TxData, TxMetrics, Option) { let tx_offset = self.committed_state_write_lock.next_tx_offset; let tx_data = self .committed_state_write_lock @@ -2020,8 +2021,8 @@ impl MutTxId { /// /// Returns: /// - [`TxMetrics`], various measurements of the work performed by this transaction. - /// - `String`, the name of the reducer which ran during this transaction. - pub fn rollback(mut self) -> (TxOffset, TxMetrics, ReducerName) { + /// - `ReducerName`, the name of the reducer which ran during this transaction. + pub fn rollback(mut self) -> (TxOffset, TxMetrics, Option) { let offset = self .committed_state_write_lock .rollback(&mut self.sequence_state_lock, self.tx_state); diff --git a/crates/datastore/src/locking_tx_datastore/sequence.rs b/crates/datastore/src/locking_tx_datastore/sequence.rs index 80bd93f20ae..607eacb95fe 100644 --- a/crates/datastore/src/locking_tx_datastore/sequence.rs +++ b/crates/datastore/src/locking_tx_datastore/sequence.rs @@ -245,7 +245,7 @@ mod tests { start: params.start, col_pos: ColId(1), table_id: TableId(1), - sequence_name: "test_sequence".to_owned().into_boxed_str(), + sequence_name: "test_sequence".into(), }; Sequence::new(schema, params.previous_allocation) } diff --git a/crates/datastore/src/locking_tx_datastore/tx.rs b/crates/datastore/src/locking_tx_datastore/tx.rs index 0edba3cc0c7..5e06ca24b80 100644 --- a/crates/datastore/src/locking_tx_datastore/tx.rs +++ b/crates/datastore/src/locking_tx_datastore/tx.rs @@ -148,8 +148,8 @@ impl TxId { /// Returns: /// - [`TxOffset`], the smallest transaction offset visible to this transaction. /// - [`TxMetrics`], various measurements of the work performed by this transaction. - /// - `String`, the name of the reducer which ran within this transaction. - pub(super) fn release(self) -> (TxOffset, TxMetrics, ReducerName) { + /// - `ReducerName`, the name of the reducer which ran within this transaction. + pub(super) fn release(self) -> (TxOffset, TxMetrics, Option) { // A read tx doesn't consume `next_tx_offset`, so subtract one to obtain // the offset that was visible to the transaction. // diff --git a/crates/datastore/src/system_tables.rs b/crates/datastore/src/system_tables.rs index 342e2bd1d3b..947e8bcb0d0 100644 --- a/crates/datastore/src/system_tables.rs +++ b/crates/datastore/src/system_tables.rs @@ -24,10 +24,12 @@ use spacetimedb_sats::algebraic_value::de::ValueDeserializer; use spacetimedb_sats::algebraic_value::ser::value_serialize; use spacetimedb_sats::hash::Hash; use spacetimedb_sats::product_value::InvalidFieldError; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::{impl_deserialize, impl_serialize, impl_st, u256, AlgebraicType, AlgebraicValue, ArrayValue}; use spacetimedb_schema::def::{ BTreeAlgorithm, ConstraintData, DirectAlgorithm, HashAlgorithm, IndexAlgorithm, ModuleDef, UniqueConstraintData, }; +use spacetimedb_schema::identifier::Identifier; use spacetimedb_schema::schema::{ ColumnSchema, ConstraintSchema, IndexSchema, RowLevelSecuritySchema, ScheduleSchema, Schema, SequenceSchema, TableSchema, @@ -221,10 +223,10 @@ pub trait StFields: Copy + Sized { /// Returns the column name of the system table field a static string slice. fn name(self) -> &'static str; - /// Returns the column name of the system table field as a boxed slice. + /// Returns the column name of the system table field as a [`RawIdentifier`]. #[inline] - fn col_name(self) -> Box { - self.name().into() + fn col_name(self) -> Identifier { + Identifier::new_assume_valid(self.name().into()) } /// Return all fields of this type, in order. @@ -923,7 +925,7 @@ impl From for AlgebraicTypeViaBytes { pub struct StColumnRow { pub table_id: TableId, pub col_pos: ColId, - pub col_name: Box, + pub col_name: Identifier, pub col_type: AlgebraicTypeViaBytes, } @@ -973,7 +975,7 @@ pub struct StViewColumnRow { /// A foreign key referencing [`ST_VIEW_NAME`]. pub view_id: ViewId, pub col_pos: ColId, - pub col_name: Box, + pub col_name: Identifier, pub col_type: AlgebraicTypeViaBytes, } @@ -988,7 +990,7 @@ pub struct StViewParamRow { /// A foreign key referencing [`ST_VIEW_NAME`]. pub view_id: ViewId, pub param_pos: ColId, - pub param_name: Box, + pub param_name: RawIdentifier, pub param_type: AlgebraicTypeViaBytes, } @@ -1038,7 +1040,7 @@ pub struct StViewArgRow { pub struct StIndexRow { pub index_id: IndexId, pub table_id: TableId, - pub index_name: Box, + pub index_name: RawIdentifier, pub index_algorithm: StIndexAlgorithm, } @@ -1138,7 +1140,7 @@ impl From for StIndexRow { #[sats(crate = spacetimedb_lib)] pub struct StSequenceRow { pub sequence_id: SequenceId, - pub sequence_name: Box, + pub sequence_name: RawIdentifier, pub table_id: TableId, pub col_pos: ColId, pub increment: i128, @@ -1189,7 +1191,7 @@ impl From for SequenceSchema { #[sats(crate = spacetimedb_lib)] pub struct StConstraintRow { pub(crate) constraint_id: ConstraintId, - pub(crate) constraint_name: Box, + pub(crate) constraint_name: RawIdentifier, pub table_id: TableId, pub(crate) constraint_data: StConstraintData, } @@ -1604,8 +1606,8 @@ pub struct StScheduledRow { /// Note that, despite the column name, this may refer to either a reducer or a procedure. /// We cannot change the schema of existing system tables, /// so we are unable to rename this column. - pub(crate) reducer_name: Box, - pub(crate) schedule_name: Box, + pub(crate) reducer_name: Identifier, + pub(crate) schedule_name: Identifier, pub(crate) at_column: ColId, } diff --git a/crates/datastore/src/traits.rs b/crates/datastore/src/traits.rs index a2e6f5880ea..9b98c11addf 100644 --- a/crates/datastore/src/traits.rs +++ b/crates/datastore/src/traits.rs @@ -353,7 +353,7 @@ impl TxData { /// the method returns `false`. /// /// This is used to determine if a transaction should be written to disk. - pub fn has_rows_or_connect_disconnect(&self, reducer_name: Option<&str>) -> bool { + pub fn has_rows_or_connect_disconnect(&self, reducer_name: Option<&ReducerName>) -> bool { // Persist if the table is non-emphemeral and had any inserts or deletes. self.entries.values().any(|e| !e.ephemeral @@ -452,7 +452,7 @@ pub trait Tx { /// /// - [`TxMetrics`], various measurements of the work performed by this transaction. /// - `ReducerName`, the name of the reducer which ran within this transaction. - fn release_tx(&self, tx: Self::Tx) -> (TxOffset, TxMetrics, ReducerName); + fn release_tx(&self, tx: Self::Tx) -> (TxOffset, TxMetrics, Option); } pub trait MutTx { @@ -477,14 +477,15 @@ pub trait MutTx { /// - [`TxData`], the set of inserts and deletes performed by this transaction. /// - [`TxMetrics`], various measurements of the work performed by this transaction. /// - `ReducerName`, the name of the reducer which ran during this transaction. - fn commit_mut_tx(&self, tx: Self::MutTx) -> Result>; + #[allow(clippy::type_complexity)] + fn commit_mut_tx(&self, tx: Self::MutTx) -> Result)>>; /// Rolls back this transaction, discarding its changes. /// /// Returns: /// - [`TxMetrics`], various measurements of the work performed by this transaction. /// - `ReducerName`, the name of the reducer which ran within this transaction. - fn rollback_mut_tx(&self, tx: Self::MutTx) -> (TxOffset, TxMetrics, ReducerName); + fn rollback_mut_tx(&self, tx: Self::MutTx) -> (TxOffset, TxMetrics, Option); } /// Standard metadata associated with a database. @@ -604,7 +605,7 @@ pub trait MutTxDatastore: TxDatastore + MutTx { fn row_type_for_table_mut_tx<'tx>(&self, tx: &'tx Self::MutTx, table_id: TableId) -> Result>; fn schema_for_table_mut_tx(&self, tx: &Self::MutTx, table_id: TableId) -> Result>; fn drop_table_mut_tx(&self, tx: &mut Self::MutTx, table_id: TableId) -> Result<()>; - fn rename_table_mut_tx(&self, tx: &mut Self::MutTx, table_id: TableId, new_name: &str) -> Result<()>; + fn rename_table_mut_tx(&self, tx: &mut Self::MutTx, table_id: TableId, new_name: TableName) -> Result<()>; fn view_id_from_name_mut_tx(&self, tx: &Self::MutTx, view_name: &str) -> Result>; fn table_id_from_name_mut_tx(&self, tx: &Self::MutTx, table_name: &str) -> Result>; fn table_id_exists_mut_tx(&self, tx: &Self::MutTx, table_id: &TableId) -> bool; diff --git a/crates/expr/src/check.rs b/crates/expr/src/check.rs index 7bd7d5bf98c..58093e97df2 100644 --- a/crates/expr/src/check.rs +++ b/crates/expr/src/check.rs @@ -4,6 +4,7 @@ use spacetimedb_data_structures::map::HashMap; use spacetimedb_lib::identity::AuthCtx; use spacetimedb_lib::AlgebraicType; use spacetimedb_primitives::TableId; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_schema::schema::TableOrViewSchema; use spacetimedb_sql_parser::ast::BinOp; use spacetimedb_sql_parser::{ @@ -34,10 +35,10 @@ pub trait SchemaView { } #[derive(Default)] -pub struct Relvars(HashMap, Arc>); +pub struct Relvars(HashMap>); impl Deref for Relvars { - type Target = HashMap, Arc>; + type Target = HashMap>; fn deref(&self) -> &Self::Target { &self.0 } @@ -85,7 +86,7 @@ pub trait TypeChecker { { // Check for duplicate aliases if vars.contains_key(&alias) { - return Err(DuplicateName(alias.into_string()).into()); + return Err(DuplicateName(alias.clone()).into()); } let lhs = Box::new(join); @@ -177,6 +178,7 @@ fn expect_table_type(expr: ProjectList) -> TypingResult { pub mod test_utils { use spacetimedb_lib::{db::raw_def::v9::RawModuleDefV9Builder, ProductType}; use spacetimedb_primitives::TableId; + use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_schema::{ def::ModuleDef, schema::{Schema, TableOrViewSchema, TableSchema}, @@ -188,7 +190,7 @@ pub mod test_utils { pub fn build_module_def(types: Vec<(&str, ProductType)>) -> ModuleDef { let mut builder = RawModuleDefV9Builder::new(); for (name, ty) in types { - builder.build_table_with_new_type(name, ty, true); + builder.build_table_with_new_type(RawIdentifier::new(name), ty, true); } builder.finish().try_into().expect("failed to generate module def") } diff --git a/crates/expr/src/errors.rs b/crates/expr/src/errors.rs index 50858178f94..489882ce8c8 100644 --- a/crates/expr/src/errors.rs +++ b/crates/expr/src/errors.rs @@ -1,6 +1,8 @@ use super::statement::InvalidVar; use spacetimedb_lib::AlgebraicType; use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type; +use spacetimedb_sats::raw_identifier::RawIdentifier; +use spacetimedb_schema::table_name::TableName; use spacetimedb_sql_parser::ast::BinOp; use spacetimedb_sql_parser::parser::errors::SqlParseError; use thiserror::Error; @@ -12,7 +14,7 @@ pub enum Unresolved { #[error("no such table: `{0}`. If the table exists, it may be marked private.")] Table(String), #[error("`{0}` does not have a field `{1}`")] - Field(String, String), + Field(TableName, String), #[error("Cannot resolve type for literal expression")] Literal, } @@ -29,8 +31,8 @@ impl Unresolved { } /// Cannot resolve field name within table - pub fn field(table: &str, field: &str) -> Self { - Self::Field(table.to_owned(), field.to_owned()) + pub fn field(table: TableName, field: &str) -> Self { + Self::Field(table, field.to_owned()) } } @@ -52,7 +54,7 @@ pub enum Unsupported { #[derive(Error, Debug)] #[error("Inserting a row with {values} values into `{table}` which has {fields} fields")] pub struct InsertValuesError { - pub table: String, + pub table: TableName, pub values: usize, pub fields: usize, } @@ -61,7 +63,7 @@ pub struct InsertValuesError { #[derive(Error, Debug)] #[error("The number of fields ({nfields}) in the INSERT does not match the number of columns ({ncols}) of the table `{table}`")] pub struct InsertFieldsError { - pub table: String, + pub table: TableName, pub ncols: usize, pub nfields: usize, } @@ -116,7 +118,7 @@ impl UnexpectedType { #[derive(Debug, Error)] #[error("Duplicate name `{0}`")] -pub struct DuplicateName(pub String); +pub struct DuplicateName(pub RawIdentifier); #[derive(Debug, Error)] #[error("`filter!` does not support column projections; Must return table rows")] @@ -125,7 +127,7 @@ pub struct FilterReturnType; #[derive(Debug, Error)] #[error("`{view_name}` is a view; DML on views is not supported")] pub struct DmlOnView { - pub view_name: Box, + pub view_name: TableName, } #[derive(Error, Debug)] diff --git a/crates/expr/src/expr.rs b/crates/expr/src/expr.rs index 8360a658dee..cf5688d537c 100644 --- a/crates/expr/src/expr.rs +++ b/crates/expr/src/expr.rs @@ -1,7 +1,8 @@ use spacetimedb_data_structures::map::HashSet; use spacetimedb_lib::{query::Delta, AlgebraicType, AlgebraicValue}; use spacetimedb_primitives::{TableId, ViewId}; -use spacetimedb_schema::schema::TableOrViewSchema; +use spacetimedb_sats::raw_identifier::RawIdentifier; +use spacetimedb_schema::{identifier::Identifier, schema::TableOrViewSchema}; use spacetimedb_sql_parser::ast::{BinOp, LogOp}; use std::sync::Arc; @@ -40,7 +41,7 @@ impl CollectViews for Vec { #[derive(Debug, PartialEq, Eq)] pub enum ProjectName { None(RelExpr), - Some(RelExpr, Box), + Some(RelExpr, RawIdentifier), } impl CollectViews for ProjectName { @@ -61,10 +62,10 @@ impl ProjectName { /// What is the name of the return table? /// This is either the table name itself or its alias. - pub fn return_name(&self) -> Option<&str> { + pub fn return_name(&self) -> Option<&RawIdentifier> { match self { Self::None(input) => input.return_name(), - Self::Some(_, name) => Some(name.as_ref()), + Self::Some(_, name) => Some(name), } } @@ -89,7 +90,7 @@ impl ProjectName { } /// Iterate over the returned column names and types - pub fn for_each_return_field(&self, mut f: impl FnMut(&str, &AlgebraicType)) { + pub fn for_each_return_field(&self, mut f: impl FnMut(&Identifier, &AlgebraicType)) { if let Some(schema) = self.return_table() { for schema in schema.public_columns() { f(&schema.col_name, &schema.col_type); @@ -162,9 +163,9 @@ impl ProjectName { #[derive(Debug)] pub enum ProjectList { Name(Vec), - List(Vec, Vec<(Box, FieldProject)>), + List(Vec, Vec<(RawIdentifier, FieldProject)>), Limit(Box, u64), - Agg(Vec, AggType, Box, AlgebraicType), + Agg(Vec, AggType, RawIdentifier, AlgebraicType), } #[derive(Debug)] @@ -216,10 +217,12 @@ impl ProjectList { } /// Iterate over the projected column names and types - pub fn for_each_return_field(&self, mut f: impl FnMut(&str, &AlgebraicType)) { + pub fn for_each_return_field(&self, mut f: impl FnMut(&RawIdentifier, &AlgebraicType)) { match self { Self::Name(input) => { - input.first().inspect(|expr| expr.for_each_return_field(f)); + input + .first() + .inspect(|expr| expr.for_each_return_field(|n, at| f(n.as_raw(), at))); } Self::Limit(input, _) => { input.for_each_return_field(f); @@ -253,7 +256,7 @@ pub struct Relvar { /// The table schema of this relvar pub schema: Arc, /// The name of this relvar - pub alias: Box, + pub alias: RawIdentifier, /// Does this relvar represent a delta table? pub delta: Option, } @@ -353,9 +356,9 @@ impl RelExpr { /// Does this expression return a single relvar? /// If so, return its name or equivalently its alias. - pub fn return_name(&self) -> Option<&str> { + pub fn return_name(&self) -> Option<&RawIdentifier> { match self { - Self::RelVar(Relvar { alias, .. }) => Some(alias.as_ref()), + Self::RelVar(Relvar { alias, .. }) => Some(alias), Self::Select(input, _) => input.return_name(), _ => None, } @@ -431,7 +434,7 @@ impl Expr { /// A typed qualified field projection #[derive(Debug, Clone, PartialEq, Eq)] pub struct FieldProject { - pub table: Box, + pub table: RawIdentifier, pub field: usize, pub ty: AlgebraicType, } diff --git a/crates/expr/src/lib.rs b/crates/expr/src/lib.rs index ef4475309bb..3a461957df6 100644 --- a/crates/expr/src/lib.rs +++ b/crates/expr/src/lib.rs @@ -68,7 +68,7 @@ pub(crate) fn type_proj(input: RelExpr, proj: ast::Project, vars: &Relvars) -> T for ProjectElem(expr, SqlIdent(alias)) in elems { if !names.insert(alias.clone()) { - return Err(DuplicateName(alias.into_string()).into()); + return Err(DuplicateName(alias.clone()).into()); } if let Expr::Field(p) = type_expr(vars, expr.into(), None)? { @@ -84,7 +84,7 @@ pub(crate) fn type_proj(input: RelExpr, proj: ast::Project, vars: &Relvars) -> T // These types determine the size of each stack frame during type checking. // Changing their sizes will require updating the recursion limit to avoid stack overflows. const _: () = assert!(size_of::>() == 64); -const _: () = assert!(size_of::() == 40); +const _: () = assert!(size_of::() == 32); fn _type_expr(vars: &Relvars, expr: SqlExpr, expected: Option<&AlgebraicType>, depth: usize) -> TypingResult { recursion::guard(depth, recursion::MAX_RECURSION_TYP_EXPR, "expr::type_expr")?; @@ -99,26 +99,19 @@ fn _type_expr(vars: &Relvars, expr: SqlExpr, expected: Option<&AlgebraicType>, d parse(&v, ty).map_err(|_| InvalidLiteral::new(v.into_string(), ty))?, ty.clone(), )), - (SqlExpr::Field(SqlIdent(table), SqlIdent(field)), None) => { - let table_type = vars.deref().get(&table).ok_or_else(|| Unresolved::var(&table))?; - let ColumnSchema { col_pos, col_type, .. } = table_type - .get_column_by_name(&field) - .ok_or_else(|| Unresolved::var(&field))?; - Ok(Expr::Field(FieldProject { - table, - field: col_pos.idx(), - ty: col_type.clone(), - })) - } - (SqlExpr::Field(SqlIdent(table), SqlIdent(field)), Some(ty)) => { - let table_type = vars.deref().get(&table).ok_or_else(|| Unresolved::var(&table))?; + (SqlExpr::Field(SqlIdent(table), SqlIdent(field)), expected) => { + let table_type = vars.deref().get(&*table).ok_or_else(|| Unresolved::var(&table))?; let ColumnSchema { col_pos, col_type, .. } = table_type .as_ref() .get_column_by_name(&field) .ok_or_else(|| Unresolved::var(&field))?; - if col_type != ty { - return Err(UnexpectedType::new(col_type, ty).into()); + + if let Some(ty) = expected { + if col_type != ty { + return Err(UnexpectedType::new(col_type, ty).into()); + } } + Ok(Expr::Field(FieldProject { table, field: col_pos.idx(), diff --git a/crates/expr/src/rls.rs b/crates/expr/src/rls.rs index 75f096ae50c..7382bd5e630 100644 --- a/crates/expr/src/rls.rs +++ b/crates/expr/src/rls.rs @@ -2,6 +2,7 @@ use std::rc::Rc; use spacetimedb_lib::identity::AuthCtx; use spacetimedb_primitives::TableId; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sql_parser::ast::BinOp; use crate::{ @@ -22,7 +23,7 @@ pub fn resolve_views_for_sub( return Ok(vec![expr]); } - let Some(return_name) = expr.return_name().map(|name| name.to_owned().into_boxed_str()) else { + let Some(return_name) = expr.return_name().cloned() else { anyhow::bail!("Could not determine return type during RLS resolution") }; @@ -287,7 +288,11 @@ fn resolve_views_for_expr( /// After we collect all the necessary view definitions and run alpha conversion, /// this function handles the actual replacement of the view with its definition. - fn expand_views(expr: RelExpr, view_def_fragments: &[(TableId, Box, Vec)], out: &mut Vec) { + fn expand_views( + expr: RelExpr, + view_def_fragments: &[(TableId, RawIdentifier, Vec)], + out: &mut Vec, + ) { match view_def_fragments { [] => out.push(expr), [(table_id, alias, fragments), view_def_fragments @ ..] => { @@ -332,19 +337,19 @@ fn resolve_views_for_expr( /// JOIN t AS t ON t.id = v.id WHERE v.x = 0 /// ``` fn alpha_rename_fragments( - return_name: &str, - outer_alias: &str, + return_name: &RawIdentifier, + outer_alias: &RawIdentifier, inputs: Vec, output: &mut Vec, suffix: &mut usize, ) { for mut fragment in inputs { *suffix += 1; - alpha_rename(&mut fragment, &mut |name: &str| { + alpha_rename(&mut fragment, &mut |name: &RawIdentifier| { if name == return_name { - return outer_alias.to_owned().into_boxed_str(); + return outer_alias.clone(); } - (name.to_owned() + "_" + &suffix.to_string()).into_boxed_str() + RawIdentifier::new(name.to_string() + "_" + &suffix.to_string()) }); output.push(fragment); } @@ -352,13 +357,13 @@ fn alpha_rename_fragments( /// When expanding a view, we must do an alpha conversion on the view definition. /// This involves renaming the table aliases before replacing the view reference. -fn alpha_rename(expr: &mut RelExpr, f: &mut impl FnMut(&str) -> Box) { +fn alpha_rename(expr: &mut RelExpr, f: &mut impl FnMut(&RawIdentifier) -> RawIdentifier) { /// Helper for renaming a relvar - fn rename(relvar: &mut Relvar, f: &mut impl FnMut(&str) -> Box) { + fn rename(relvar: &mut Relvar, f: &mut impl FnMut(&RawIdentifier) -> RawIdentifier) { relvar.alias = f(&relvar.alias); } /// Helper for renaming a field reference - fn rename_field(field: &mut FieldProject, f: &mut impl FnMut(&str) -> Box) { + fn rename_field(field: &mut FieldProject, f: &mut impl FnMut(&RawIdentifier) -> RawIdentifier) { field.table = f(&field.table); } expr.visit_mut(&mut |expr| match expr { diff --git a/crates/expr/src/statement.rs b/crates/expr/src/statement.rs index c31e5160075..68c59f0adca 100644 --- a/crates/expr/src/statement.rs +++ b/crates/expr/src/statement.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use spacetimedb_lib::{identity::AuthCtx, st_var::StVarValue, AlgebraicType, AlgebraicValue, ProductValue}; use spacetimedb_primitives::{ColId, TableId}; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_schema::schema::{ColumnSchema, TableOrViewSchema}; use spacetimedb_schema::table_name::TableName; use spacetimedb_sql_parser::{ @@ -96,16 +97,19 @@ pub fn type_insert(insert: SqlInsert, tx: &impl SchemaView) -> TypingResult TypingResult TypingResult TypingResult TypingResult { values.push((*col_id, AlgebraicValue::Bool(v))); @@ -226,7 +236,7 @@ pub fn type_update(update: SqlUpdate, tx: &impl SchemaView) -> TypingResult TypingResult TypingResult { let SqlSet(SqlIdent(var_name), lit) = set; if !is_var_valid(&var_name) { - return Err(InvalidVar { - name: var_name.into_string(), - } - .into()); + return Err(InvalidVar { name: var_name }.into()); } match lit { @@ -284,7 +291,7 @@ pub fn type_and_rewrite_set(set: SqlSet, tx: &impl SchemaView) -> TypingResult Err(UnexpectedType::new(&AlgebraicType::U64, &AlgebraicType::bytes()).into()), SqlLiteral::Num(n) => { let table = tx.schema(ST_VAR_NAME).ok_or_else(|| Unresolved::table(ST_VAR_NAME))?; - let var_name = AlgebraicValue::String(var_name); + let var_name = AlgebraicValue::String(var_name.as_ref().into()); let sum_value = StVarValue::try_from_primitive( parse(&n, &AlgebraicType::U64) .map_err(|_| InvalidLiteral::new(n.clone().into_string(), &AlgebraicType::U64))?, @@ -315,26 +322,24 @@ pub fn type_and_rewrite_set(set: SqlSet, tx: &impl SchemaView) -> TypingResult TypingResult { let SqlShow(SqlIdent(var_name)) = show; if !is_var_valid(&var_name) { - return Err(InvalidVar { - name: var_name.into_string(), - } - .into()); + return Err(InvalidVar { name: var_name }.into()); } let table_schema = tx.schema(ST_VAR_NAME).ok_or_else(|| Unresolved::table(ST_VAR_NAME))?; + let table_name = &table_schema.table_name; let value_col_ty = table_schema .as_ref() .get_column_by_name(VALUE_COLUMN) .map(|ColumnSchema { col_type, .. }| col_type) - .ok_or_else(|| Unresolved::field(ST_VAR_NAME, VALUE_COLUMN))?; + .ok_or_else(|| Unresolved::field(table_name.clone(), VALUE_COLUMN))?; // ------------------------------------------- // SELECT value FROM st_var WHERE name = 'var' // ^^^^ // ------------------------------------------- let var_name_field = Expr::Field(FieldProject { - table: ST_VAR_NAME.into(), + table: table_name.clone().into(), // TODO: Avoid hard coding the field position. // See `StVarFields` for the schema of `st_var`. field: 0, @@ -345,7 +350,7 @@ pub fn type_and_rewrite_show(show: SqlShow, tx: &impl SchemaView) -> TypingResul // SELECT value FROM st_var WHERE name = 'var' // ^^^ // ------------------------------------------- - let var_name_value = Expr::Value(AlgebraicValue::String(var_name), AlgebraicType::String); + let var_name_value = Expr::Value(AlgebraicValue::String(var_name.as_ref().into()), AlgebraicType::String); // ------------------------------------------- // SELECT value FROM st_var WHERE name = 'var' @@ -354,7 +359,7 @@ pub fn type_and_rewrite_show(show: SqlShow, tx: &impl SchemaView) -> TypingResul let column_list = vec![( VALUE_COLUMN.into(), FieldProject { - table: ST_VAR_NAME.into(), + table: table_name.clone().into(), // TODO: Avoid hard coding the field position. // See `StVarFields` for the schema of `st_var`. field: 1, @@ -367,8 +372,8 @@ pub fn type_and_rewrite_show(show: SqlShow, tx: &impl SchemaView) -> TypingResul // ^^^^^^ // ------------------------------------------- let relvar = RelExpr::RelVar(Relvar { - schema: table_schema, - alias: ST_VAR_NAME.into(), + schema: table_schema.clone(), + alias: table_name.clone().into(), delta: None, }); @@ -475,6 +480,7 @@ mod tests { use spacetimedb::TableId; use spacetimedb_lib::db::raw_def::v9::RawModuleDefV9Builder; use spacetimedb_lib::{identity::AuthCtx, AlgebraicType, ProductType}; + use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_schema::def::ModuleDef; use spacetimedb_schema::schema::{TableOrViewSchema, TableSchema}; use spacetimedb_sql_parser::ast::{SqlExpr, SqlLiteral}; @@ -603,8 +609,9 @@ mod tests { columns: impl Into, is_anonymous: bool, ) { + let name = RawIdentifier::new(name); let product_type = AlgebraicType::from(columns.into()); - let type_ref = builder.add_algebraic_type([], name, product_type, true); + let type_ref = builder.add_algebraic_type([], name.clone(), product_type, true); let return_type = AlgebraicType::array(AlgebraicType::Ref(type_ref)); builder.add_view(name, 0, true, is_anonymous, ProductType::unit(), return_type); } diff --git a/crates/lib/src/db/raw_def/v10.rs b/crates/lib/src/db/raw_def/v10.rs index e36461f8990..35ef5aa38c0 100644 --- a/crates/lib/src/db/raw_def/v10.rs +++ b/crates/lib/src/db/raw_def/v10.rs @@ -5,16 +5,14 @@ //! into dedicated sections for cleaner organization. //! It allows easier future extensibility to add new kinds of definitions. +use crate::db::raw_def::v9::{Lifecycle, RawIndexAlgorithm, TableAccess, TableType}; use core::fmt; -use std::any::TypeId; -use std::collections::{btree_map, BTreeMap}; - -use itertools::Itertools as _; use spacetimedb_primitives::{ColId, ColList}; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::typespace::TypespaceBuilder; use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, AlgebraicValue, ProductType, SpacetimeType, Typespace}; - -use crate::db::raw_def::v9::{Lifecycle, RawIdentifier, RawIndexAlgorithm, TableAccess, TableType}; +use std::any::TypeId; +use std::collections::{btree_map, BTreeMap}; /// A possibly-invalid raw module definition. /// @@ -199,7 +197,7 @@ pub struct RawScheduleDefV10 { /// In the future, the user may FOR SOME REASON want to override this. /// Even though there is ABSOLUTELY NO REASON TO. /// If `None`, a nicely-formatted unique default will be chosen. - pub source_name: Option>, + pub source_name: Option, /// The name of the table containing the schedule. pub table_name: RawIdentifier, @@ -253,7 +251,7 @@ pub struct RawSequenceDefV10 { /// In the future, the user may FOR SOME REASON want to override this. /// Even though there is ABSOLUTELY NO REASON TO. /// If `None`, a nicely-formatted unique default will be chosen. - pub source_name: Option>, + pub source_name: Option, /// The position of the column associated with this sequence. /// This refers to a column in the same `RawTableDef` that contains this `RawSequenceDef`. @@ -285,7 +283,7 @@ pub struct RawSequenceDefV10 { pub struct RawIndexDefV10 { /// In the future, the user may FOR SOME REASON want to override this. /// Even though there is ABSOLUTELY NO REASON TO. - pub source_name: Option>, + pub source_name: Option, /// Accessor name for the index used in client codegen. /// @@ -310,7 +308,7 @@ pub struct RawIndexDefV10 { pub struct RawConstraintDefV10 { /// In the future, the user may FOR SOME REASON want to override this. /// Even though there is ABSOLUTELY NO REASON TO. - pub source_name: Option>, + pub source_name: Option, /// The data for the constraint. pub data: RawConstraintDataV10, @@ -747,7 +745,7 @@ impl RawModuleDefV10Builder { // Make the type into a ref. let name = *name_gen; let add_ty = core::mem::replace(ty, AlgebraicType::U8); - *ty = AlgebraicType::Ref(self.add_algebraic_type([], format!("gen_{name}"), add_ty, true)); + *ty = AlgebraicType::Ref(self.add_algebraic_type([], RawIdentifier::new(format!("gen_{name}")), add_ty, true)); *name_gen += 1; } @@ -1007,7 +1005,11 @@ pub fn reducer_default_err_return_type() -> AlgebraicType { /// pub fn sats_name_to_scoped_name_v10(sats_name: &str) -> RawScopedTypeNameV10 { // We can't use `&[char]: Pattern` for `split` here because "::" is not a char :/ - let mut scope: Vec = sats_name.split("::").flat_map(|s| s.split('.')).map_into().collect(); + let mut scope: Vec = sats_name + .split("::") + .flat_map(|s| s.split('.')) + .map(RawIdentifier::new) + .collect(); // Unwrapping to "" will result in a validation error down the line, which is exactly what we want. let source_name = scope.pop().unwrap_or_default(); RawScopedTypeNameV10 { @@ -1154,7 +1156,7 @@ impl RawTableDefBuilderV10<'_> { .as_product()? .elements .iter() - .position(|e| e.name().is_some_and(|n| n == column)) + .position(|x| x.has_name(column.as_ref())) .map(|i| ColId(i as u16)) } } diff --git a/crates/lib/src/db/raw_def/v8.rs b/crates/lib/src/db/raw_def/v8.rs index 5bea0effe32..d0266ba5ea7 100644 --- a/crates/lib/src/db/raw_def/v8.rs +++ b/crates/lib/src/db/raw_def/v8.rs @@ -6,6 +6,7 @@ use crate::db::auth::{StAccess, StTableType}; use crate::{AlgebraicType, ProductType, SpacetimeType}; use derive_more::Display; use spacetimedb_primitives::*; +use spacetimedb_sats::raw_identifier::RawIdentifier; // TODO(1.0): move these definitions into this file, // along with the other structs contained in it, @@ -24,7 +25,7 @@ pub const SEQUENCE_ALLOCATION_STEP: i128 = 4096; #[sats(crate = crate)] pub struct RawSequenceDefV8 { /// The name of the sequence. - pub sequence_name: Box, + pub sequence_name: RawIdentifier, /// The position of the column associated with this sequence. pub col_pos: ColId, /// The increment value for the sequence. @@ -62,7 +63,7 @@ impl RawSequenceDefV8 { let seq_name = column_or_name.trim_start_matches(&format!("ct_{table}_")); RawSequenceDefV8 { - sequence_name: format!("seq_{table}_{seq_name}").into(), + sequence_name: RawIdentifier::new(format!("seq_{table}_{seq_name}")), col_pos, increment: 1, start: None, @@ -110,7 +111,7 @@ impl TryFrom for IndexType { pub struct RawIndexDefV8 { /// The name of the index. /// This should not be assumed to follow any particular format. - pub index_name: Box, + pub index_name: RawIdentifier, /// Whether the index is unique. pub is_unique: bool, /// The type of the index. @@ -127,7 +128,7 @@ impl RawIndexDefV8 { /// * `index_name`: The name of the index. /// * `columns`: List of column positions that compose the index. /// * `is_unique`: Indicates whether the index enforces uniqueness. - pub fn btree(index_name: Box, columns: impl Into, is_unique: bool) -> Self { + pub fn btree(index_name: RawIdentifier, columns: impl Into, is_unique: bool) -> Self { Self { columns: columns.into(), index_name, @@ -162,7 +163,7 @@ impl RawIndexDefV8 { } else { format!("idx_{table}_{name}_{unique}") }; - Self::btree(name.into(), columns, is_unique) + Self::btree(RawIdentifier::new(name), columns, is_unique) } } @@ -171,7 +172,7 @@ impl RawIndexDefV8 { #[sats(crate = crate)] pub struct RawColumnDefV8 { /// The name of the column. - pub col_name: Box, + pub col_name: RawIdentifier, /// The type of the column. /// /// Must satisfy [AlgebraicType::is_valid_for_client_type_use]. @@ -188,7 +189,7 @@ impl RawColumnDefV8 { let col_name = if let Some(name) = col.name { name } else { - format!("col_{pos}").into() + RawIdentifier::new(format!("col_{pos}")) }; RawColumnDefV8 { @@ -213,7 +214,7 @@ impl RawColumnDefV8 { /// If `type_` is not `AlgebraicType::Builtin` or `AlgebraicType::Ref`, an error will result at validation time. pub fn sys(field_name: &str, col_type: AlgebraicType) -> Self { Self { - col_name: field_name.into(), + col_name: RawIdentifier::new(field_name), col_type, } } @@ -225,7 +226,7 @@ impl RawColumnDefV8 { #[sats(crate = crate)] pub struct RawConstraintDefV8 { /// The name of the constraint. - pub constraint_name: Box, + pub constraint_name: RawIdentifier, /// The constraints applied to the columns. pub constraints: Constraints, /// List of column positions associated with the constraint. @@ -240,7 +241,7 @@ impl RawConstraintDefV8 { /// * `constraint_name`: The name of the constraint. /// * `constraints`: The constraints. /// * `columns`: List of column positions associated with the constraint. - pub fn new(constraint_name: Box, constraints: Constraints, columns: impl Into) -> Self { + pub fn new(constraint_name: RawIdentifier, constraints: Constraints, columns: impl Into) -> Self { Self { constraint_name, constraints, @@ -279,11 +280,12 @@ impl RawConstraintDefV8 { let kind_name = format!("{:?}", constraints.kind()).to_lowercase(); // No duplicate the `kind_name` that was added by an index - if name.ends_with(&kind_name) { - Self::new(format!("ct_{table}_{name}").into(), constraints, columns) + let name = if name.ends_with(&kind_name) { + format!("ct_{table}_{name}") } else { - Self::new(format!("ct_{table}_{name}_{kind_name}").into(), constraints, columns) - } + format!("ct_{table}_{name}_{kind_name}") + }; + Self::new(RawIdentifier::new(name), constraints, columns) } } @@ -306,7 +308,7 @@ pub fn generate_cols_name<'a>(columns: &ColList, col_name: impl Fn(ColId) -> Opt #[sats(crate = crate)] pub struct RawTableDefV8 { /// The name of the table. - pub table_name: Box, + pub table_name: RawIdentifier, /// The columns of the table. /// The ordering of the columns is significant. Columns are frequently identified by `ColId`, that is, position in this list. pub columns: Vec, @@ -321,7 +323,7 @@ pub struct RawTableDefV8 { /// The visibility of the table. pub table_access: StAccess, /// If this is a schedule table, the reducer it is scheduled for. - pub scheduled: Option>, + pub scheduled: Option, } impl RawTableDefV8 { @@ -332,7 +334,7 @@ impl RawTableDefV8 { /// - `table_name`: The name of the table. /// - `columns`: A `vec` of `ColumnDef` instances representing the columns of the table. /// - pub fn new(table_name: Box, columns: Vec) -> Self { + pub fn new(table_name: RawIdentifier, columns: Vec) -> Self { Self { table_name, columns, @@ -346,7 +348,7 @@ impl RawTableDefV8 { } #[cfg(feature = "test")] - pub fn new_for_tests(table_name: impl Into>, columns: ProductType) -> Self { + pub fn new_for_tests(table_name: impl Into, columns: ProductType) -> Self { Self::new(table_name.into(), RawColumnDefV8::from_product_type(columns)) } @@ -431,7 +433,7 @@ impl RawTableDefV8 { } /// Set the reducer name for scheduled tables and return updated `TableDef`. - pub fn with_scheduled(mut self, scheduled: Option>) -> Self { + pub fn with_scheduled(mut self, scheduled: Option) -> Self { self.scheduled = scheduled; self } @@ -439,14 +441,14 @@ impl RawTableDefV8 { /// Create a `TableDef` from a product type and table name. /// /// NOTE: If the [ProductType.name] is `None` then it auto-generate a name like `col_{col_pos}` - pub fn from_product(table_name: &str, row: ProductType) -> Self { + pub fn from_product(table_name: RawIdentifier, row: ProductType) -> Self { Self::new( - table_name.into(), + table_name, Vec::from(row.elements) .into_iter() .enumerate() .map(|(col_pos, e)| RawColumnDefV8 { - col_name: e.name.unwrap_or_else(|| format!("col_{col_pos}").into()), + col_name: e.name.unwrap_or_else(|| RawIdentifier::new(format!("col_{col_pos}"))), col_type: e.algebraic_type, }) .collect::>(), diff --git a/crates/lib/src/db/raw_def/v9.rs b/crates/lib/src/db/raw_def/v9.rs index 7c7efcb49a5..20b32250d89 100644 --- a/crates/lib/src/db/raw_def/v9.rs +++ b/crates/lib/src/db/raw_def/v9.rs @@ -9,8 +9,8 @@ use std::collections::btree_map; use std::collections::BTreeMap; use std::fmt; -use itertools::Itertools; use spacetimedb_primitives::*; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::typespace::TypespaceBuilder; use spacetimedb_sats::AlgebraicType; use spacetimedb_sats::AlgebraicTypeRef; @@ -28,9 +28,6 @@ use crate::db::raw_def::v10::RawScopedTypeNameV10; use crate::db::raw_def::v10::RawSequenceDefV10; use crate::db::raw_def::v10::RawTypeDefV10; -/// A not-yet-validated identifier. -pub type RawIdentifier = Box; - /// A not-yet-validated `sql`. pub type RawSql = Box; @@ -262,7 +259,7 @@ pub struct RawSequenceDefV9 { /// In the future, the user may FOR SOME REASON want to override this. /// Even though there is ABSOLUTELY NO REASON TO. /// If `None`, a nicely-formatted unique default will be chosen. - pub name: Option>, + pub name: Option, /// The position of the column associated with this sequence. /// This refers to a column in the same `RawTableDef` that contains this `RawSequenceDef`. @@ -294,7 +291,7 @@ pub struct RawSequenceDefV9 { pub struct RawIndexDefV9 { /// In the future, the user may FOR SOME REASON want to override this. /// Even though there is ABSOLUTELY NO REASON TO. - pub name: Option>, + pub name: Option, /// Accessor name for the index used in client codegen. /// @@ -366,7 +363,7 @@ pub fn direct(col: impl Into) -> RawIndexAlgorithm { pub struct RawScheduleDefV9 { /// In the future, the user may FOR SOME REASON want to override this. /// Even though there is ABSOLUTELY NO REASON TO. - pub name: Option>, + pub name: Option, /// The name of the reducer or procedure to call. /// @@ -384,7 +381,7 @@ pub struct RawScheduleDefV9 { pub struct RawConstraintDefV9 { /// In the future, the user may FOR SOME REASON want to override this. /// Even though there is ABSOLUTELY NO REASON TO. - pub name: Option>, + pub name: Option, /// The data for the constraint. pub data: RawConstraintDataV9, @@ -708,7 +705,7 @@ impl RawModuleDefV9Builder { // Make the type into a ref. let name = *name_gen; let add_ty = core::mem::replace(ty, AlgebraicType::U8); - *ty = AlgebraicType::Ref(self.add_algebraic_type([], format!("gen_{name}"), add_ty, true)); + *ty = AlgebraicType::Ref(self.add_algebraic_type([], RawIdentifier::new(format!("gen_{name}")), add_ty, true)); *name_gen += 1; } @@ -847,7 +844,11 @@ impl RawModuleDefV9Builder { /// TODO(1.0): build namespacing directly into the bindings macros so that we don't need to do this. pub fn sats_name_to_scoped_name(sats_name: &str) -> RawScopedTypeNameV9 { // We can't use `&[char]: Pattern` for `split` here because "::" is not a char :/ - let mut scope: Vec = sats_name.split("::").flat_map(|s| s.split('.')).map_into().collect(); + let mut scope: Vec = sats_name + .split("::") + .flat_map(|s| s.split('.')) + .map(RawIdentifier::new) + .collect(); // Unwrapping to "" will result in a validation error down the line, which is exactly what we want. let name = scope.pop().unwrap_or_default(); RawScopedTypeNameV9 { @@ -1029,7 +1030,7 @@ impl RawTableDefBuilder<'_> { let column = column.as_ref(); self.columns()? .iter() - .position(|x| x.name().is_some_and(|s| s == column)) + .position(|x| x.has_name(column.as_ref())) .map(|x| x.into()) } diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 99b8624da92..9bd0a26bf15 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -2,6 +2,7 @@ use crate::db::raw_def::v9::RawModuleDefV9Builder; use crate::db::raw_def::RawTableDefV8; use anyhow::Context; use sats::typespace::TypespaceBuilder; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::WithTypespace; use std::any::TypeId; use std::collections::{btree_map, BTreeMap}; @@ -124,7 +125,7 @@ impl TableDesc { #[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))] #[sats(crate = crate)] pub struct ReducerDef { - pub name: Box, + pub name: RawIdentifier, pub args: Vec, } @@ -187,7 +188,7 @@ impl ModuleDefBuilder { pub fn add_type_for_tests(&mut self, name: &str, ty: AlgebraicType) -> spacetimedb_sats::AlgebraicTypeRef { let slot_ref = self.module.typespace.add(ty); self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias { - name: name.to_owned(), + name: RawIdentifier::new(name), ty: slot_ref, })); slot_ref @@ -209,7 +210,7 @@ impl ModuleDefBuilder { .collect(); let data = self.module.typespace.add(ty.into()); self.add_type_alias(TypeAlias { - name: schema.table_name.clone().into(), + name: schema.table_name.clone(), ty: data, }); self.add_table(TableDesc { schema, data }); @@ -225,9 +226,9 @@ impl ModuleDefBuilder { } #[cfg(feature = "test")] - pub fn add_reducer_for_tests(&mut self, name: impl Into>, args: ProductType) { + pub fn add_reducer_for_tests(&mut self, name: impl AsRef, args: ProductType) { self.add_reducer(ReducerDef { - name: name.into(), + name: RawIdentifier::new(name.as_ref()), args: args.elements.to_vec(), }); } @@ -267,7 +268,7 @@ impl TypespaceBuilder for ModuleDefBuilder { // Alias provided? Relate `name -> slot_ref`. if let Some(name) = name { self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias { - name: name.to_owned(), + name: name.into(), ty: slot_ref, })); } @@ -294,7 +295,7 @@ pub enum MiscModuleExport { #[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))] #[sats(crate = crate)] pub struct TypeAlias { - pub name: String, + pub name: RawIdentifier, pub ty: sats::AlgebraicTypeRef, } diff --git a/crates/lib/tests/serde.rs b/crates/lib/tests/serde.rs index 661cc94116d..a9bbdeeb36c 100644 --- a/crates/lib/tests/serde.rs +++ b/crates/lib/tests/serde.rs @@ -104,7 +104,7 @@ fn test_json_mappings() { de_json_snapshot!(schema, data); } -fn tuple<'a>(elems: impl IntoIterator) -> ProductType { +fn tuple(elems: impl IntoIterator) -> ProductType { ProductType { elements: elems .into_iter() @@ -112,7 +112,7 @@ fn tuple<'a>(elems: impl IntoIterator) -> Produ .collect(), } } -fn enumm<'a>(elems: impl IntoIterator) -> SumType { +fn enumm(elems: impl IntoIterator) -> SumType { SumType { variants: elems .into_iter() diff --git a/crates/pg/src/encoder.rs b/crates/pg/src/encoder.rs index b2d9146f33a..e2fd8601bf6 100644 --- a/crates/pg/src/encoder.rs +++ b/crates/pg/src/encoder.rs @@ -17,7 +17,11 @@ pub(crate) fn row_desc(schema: &ProductType, format: &Format) -> Arc Arc Type { - let format = PsqlPrintFmt::use_fmt(schema, ty, ty.name()); + let format = PsqlPrintFmt::use_fmt(schema, ty, ty.name().map(|n| &**n)); match &ty.algebraic_type { AlgebraicType::String => Type::VARCHAR, AlgebraicType::Bool => Type::BOOL, @@ -266,10 +270,7 @@ mod tests { assert_eq!(row, "\0\0\0\u{b}{\"Gray\": 1}\0\0\0\u{15}{\"some\": {\"Gray\": 2}}"); // Now nested product - let product = AlgebraicType::product([ - ProductTypeElement::new(AlgebraicType::Product(schema), Some("x".into())), - ProductTypeElement::new(AlgebraicType::String, Some("y".into())), - ]); + let product = AlgebraicType::product([("x", AlgebraicType::Product(schema)), ("y", AlgebraicType::String)]); let schema = ProductType::from([product.clone()]); let value = product![AlgebraicValue::product(vec![ value.into(), diff --git a/crates/physical-plan/src/dml.rs b/crates/physical-plan/src/dml.rs index 680ec8e4efe..f6a0aa1aa73 100644 --- a/crates/physical-plan/src/dml.rs +++ b/crates/physical-plan/src/dml.rs @@ -1,4 +1,3 @@ -use core::ops::Deref; use std::sync::Arc; use anyhow::Result; @@ -62,7 +61,7 @@ impl DeletePlan { pub(crate) fn compile(delete: TableDelete) -> Self { let TableDelete { table, filter } = delete; let schema = table.clone(); - let alias = table.table_name.deref().into(); + let alias = table.table_name.clone().into(); let relvar = RelExpr::RelVar(Relvar { schema, alias, @@ -96,7 +95,7 @@ impl UpdatePlan { pub(crate) fn compile(update: TableUpdate) -> Self { let TableUpdate { table, columns, filter } = update; let schema = table.clone(); - let alias = table.table_name.deref().into(); + let alias = table.table_name.clone().into(); let relvar = RelExpr::RelVar(Relvar { schema, alias, diff --git a/crates/physical-plan/src/plan.rs b/crates/physical-plan/src/plan.rs index cfaae9c9f06..b1572040f72 100644 --- a/crates/physical-plan/src/plan.rs +++ b/crates/physical-plan/src/plan.rs @@ -1425,6 +1425,7 @@ mod tests { use spacetimedb_primitives::{ColId, ColList, ColSet, TableId}; use spacetimedb_schema::{ def::{BTreeAlgorithm, ConstraintData, IndexAlgorithm, UniqueConstraintData}, + identifier::Identifier, schema::{ColumnSchema, ConstraintSchema, IndexSchema, TableOrViewSchema, TableSchema}, table_name::TableName, }; @@ -1468,14 +1469,14 @@ mod tests { ) -> TableOrViewSchema { TableOrViewSchema::from(Arc::new(TableSchema::new( table_id, - TableName::new_from_str(table_name), + TableName::for_test(table_name), None, columns .iter() .enumerate() .map(|(i, (name, ty))| ColumnSchema { table_id, - col_name: (*name).to_owned().into_boxed_str(), + col_name: Identifier::for_test(*name), col_pos: i.into(), col_type: ty.clone(), }) @@ -1486,7 +1487,7 @@ mod tests { .map(|(i, cols)| IndexSchema { table_id, index_id: i.into(), - index_name: "".to_owned().into_boxed_str(), + index_name: "".into(), index_algorithm: IndexAlgorithm::BTree(BTreeAlgorithm { columns: ColList::from_iter(cols.iter().copied()), }), @@ -1498,7 +1499,7 @@ mod tests { .map(|(i, cols)| ConstraintSchema { table_id, constraint_id: i.into(), - constraint_name: "".to_owned().into_boxed_str(), + constraint_name: "".into(), data: ConstraintData::Unique(UniqueConstraintData { columns: ColSet::from_iter(cols.iter().copied()), }), diff --git a/crates/sats/Cargo.toml b/crates/sats/Cargo.toml index 396fd6f327d..27696f34ac6 100644 --- a/crates/sats/Cargo.toml +++ b/crates/sats/Cargo.toml @@ -50,6 +50,7 @@ enum-as-inner.workspace = true ethnum.workspace = true hex.workspace = true itertools.workspace = true +lean_string.workspace = true # For the 'proptest' feature. proptest = { workspace = true, optional = true } proptest-derive = { workspace = true, optional = true } diff --git a/crates/sats/src/algebraic_type.rs b/crates/sats/src/algebraic_type.rs index 837c1b94787..6c2472f0bb1 100644 --- a/crates/sats/src/algebraic_type.rs +++ b/crates/sats/src/algebraic_type.rs @@ -359,7 +359,7 @@ impl AlgebraicType { } /// Returns a sum type of unit variants with names taken from `var_names`. - pub fn simple_enum<'a>(var_names: impl Iterator) -> Self { + pub fn simple_enum(var_names: impl Iterator) -> Self { Self::sum(var_names.into_iter().map(SumTypeVariant::unit).collect::>()) } diff --git a/crates/sats/src/de/impls.rs b/crates/sats/src/de/impls.rs index d0c9a92c23d..e0b77001224 100644 --- a/crates/sats/src/de/impls.rs +++ b/crates/sats/src/de/impls.rs @@ -9,6 +9,7 @@ use crate::{ }; use crate::{i256, u256}; use core::{iter, marker::PhantomData, ops::Bound}; +use lean_string::LeanString; use smallvec::SmallVec; use spacetimedb_primitives::{ColId, ColList}; use std::{borrow::Cow, rc::Rc, sync::Arc}; @@ -168,6 +169,7 @@ impl<'de> Deserialize<'de> for u8 { impl_deserialize!([] F32, de => f32::deserialize(de).map(Into::into)); impl_deserialize!([] F64, de => f64::deserialize(de).map(Into::into)); impl_deserialize!([] String, de => de.deserialize_str(OwnedSliceVisitor)); +impl_deserialize!([] LeanString, de => >::deserialize(de).map(|s| (&*s).into())); impl_deserialize!([T: Deserialize<'de>] Vec, de => T::__deserialize_vec(de)); impl_deserialize!([T: Deserialize<'de>, const N: usize] SmallVec<[T; N]>, de => { de.deserialize_array(BasicSmallVecVisitor) @@ -497,7 +499,7 @@ impl VariantVisitor<'_> for WithTypespace<'_, SumType> { fn variant_names(&self) -> impl '_ + Iterator { // Provide the names known from the `SumType`. - self.ty().variants.iter().filter_map(|v| v.name()) + self.ty().variants.iter().filter_map(|v| v.name().map(|n| &**n)) } fn visit_tag(self, tag: u8) -> Result { @@ -677,7 +679,7 @@ pub fn visit_named_product<'de, A: super::NamedProductAccess<'de>>( // Couldn't deserialize a field name. // Find the first field name we haven't filled an element for. let missing = elements.iter().position(|field| field.is_none()).unwrap(); - let field_name = elems[missing].name(); + let field_name = elems[missing].name().map(|n| &**n); Error::missing_field(missing, field_name, visitor) })?; @@ -686,7 +688,7 @@ pub fn visit_named_product<'de, A: super::NamedProductAccess<'de>>( // By index we can select which element to deserialize a value for. let slot = &mut elements[index]; if slot.is_some() { - return Err(Error::duplicate_field(index, element.name(), visitor)); + return Err(Error::duplicate_field(index, element.name().map(|n| &**n), visitor)); } // Deserialize the value for this field's type. @@ -716,7 +718,7 @@ impl FieldNameVisitor<'_> for TupleNameVisitor<'_> { type Output = usize; fn field_names(&self) -> impl '_ + Iterator> { - self.elems.iter().map(|f| f.name()) + self.elems.iter().map(|f| f.name().map(|n| &**n)) } fn kind(&self) -> ProductKind { diff --git a/crates/sats/src/layout.rs b/crates/sats/src/layout.rs index 855d322fd92..ac123de363d 100644 --- a/crates/sats/src/layout.rs +++ b/crates/sats/src/layout.rs @@ -12,6 +12,7 @@ use crate::{ SumAccess, SumVisitor, VariantAccess as _, VariantVisitor, }, i256, impl_deserialize, impl_serialize, + raw_identifier::RawIdentifier, sum_type::{OPTION_NONE_TAG, OPTION_SOME_TAG}, u256, AlgebraicType, AlgebraicValue, ArrayType, ProductType, ProductTypeElement, ProductValue, SumType, SumTypeVariant, SumValue, WithTypespace, @@ -449,7 +450,7 @@ pub struct ProductTypeElementLayout { /// /// This allows us to convert back to `ProductTypeElement`, /// which we do when reporting type errors. - pub name: Option>, + pub name: Option, } #[cfg(feature = "memory-usage")] @@ -500,7 +501,7 @@ pub struct SumTypeVariantLayout { /// /// This allows us to convert back to `SumTypeVariant`, /// which we do when reporting type errors. - pub name: Option>, + pub name: Option, } #[cfg(feature = "memory-usage")] diff --git a/crates/sats/src/lib.rs b/crates/sats/src/lib.rs index c4b6d9b9add..c8409841877 100644 --- a/crates/sats/src/lib.rs +++ b/crates/sats/src/lib.rs @@ -20,6 +20,7 @@ pub mod primitives; pub mod product_type; pub mod product_type_element; pub mod product_value; +pub mod raw_identifier; mod resolve_refs; pub mod satn; pub mod ser; @@ -74,6 +75,7 @@ pub mod serde { /// which will then emit `$krate::sats`. #[doc(hidden)] pub use crate as sats; +use crate::raw_identifier::RawIdentifier; pub use algebraic_type::AlgebraicType; pub use algebraic_type_ref::AlgebraicTypeRef; @@ -300,6 +302,6 @@ macro_rules! __make_register_reftype { } /// A helper for prettier Debug implementation, without extra indirection around Some("name"). -fn dbg_aggregate_name(opt: &Option>) -> &dyn std::fmt::Debug { +fn dbg_aggregate_name(opt: &Option) -> &dyn std::fmt::Debug { opt.as_ref().map_or(opt, |s| s) } diff --git a/crates/sats/src/memory_usage_impls.rs b/crates/sats/src/memory_usage_impls.rs index 1eca2e8ae59..c621c9e01a7 100644 --- a/crates/sats/src/memory_usage_impls.rs +++ b/crates/sats/src/memory_usage_impls.rs @@ -1,7 +1,8 @@ use crate::{ - algebraic_value::Packed, AlgebraicType, AlgebraicValue, ArrayType, ArrayValue, ProductType, ProductTypeElement, - ProductValue, SumType, SumTypeVariant, SumValue, + algebraic_value::Packed, raw_identifier::RawIdentifier, AlgebraicType, AlgebraicValue, ArrayType, ArrayValue, + ProductType, ProductTypeElement, ProductValue, SumType, SumTypeVariant, SumValue, }; +use core::mem; use spacetimedb_memory_usage::MemoryUsage; impl MemoryUsage for AlgebraicValue { @@ -107,3 +108,13 @@ impl MemoryUsage for Packed { { self.0 }.heap_usage() } } + +impl MemoryUsage for RawIdentifier { + fn heap_usage(&self) -> usize { + if self.0.len() <= 15 { + 0 + } else { + self.0.len() + 2 * mem::size_of::() + } + } +} diff --git a/crates/sats/src/product_type.rs b/crates/sats/src/product_type.rs index 4f920ea94f3..0131c9365b7 100644 --- a/crates/sats/src/product_type.rs +++ b/crates/sats/src/product_type.rs @@ -3,6 +3,7 @@ use crate::algebraic_value::ser::value_serialize; use crate::de::Deserialize; use crate::meta_type::MetaType; use crate::product_value::InvalidFieldError; +use crate::raw_identifier::RawIdentifier; use crate::{ AlgebraicType, AlgebraicValue, ProductTypeElement, ProductValue, SpacetimeType, Typespace, ValueWithType, WithTypespace, @@ -247,16 +248,16 @@ impl> FromIterator for ProductType { Self::new(iter.into_iter().map(Into::into).collect()) } } -impl<'a, I: Into> FromIterator<(&'a str, I)> for ProductType { - fn from_iter>(iter: T) -> Self { +impl, I: Into> FromIterator<(Id, I)> for ProductType { + fn from_iter>(iter: T) -> Self { iter.into_iter() .map(|(name, ty)| ProductTypeElement::new_named(ty.into(), name)) .collect() } } -impl<'a, I: Into> FromIterator<(Option<&'a str>, I)> for ProductType { - fn from_iter, I)>>(iter: T) -> Self { +impl> FromIterator<(Option<&'static str>, I)> for ProductType { + fn from_iter, I)>>(iter: T) -> Self { iter.into_iter() .map(|(name, ty)| ProductTypeElement::new(ty.into(), name.map(Into::into))) .collect() @@ -273,13 +274,13 @@ impl From<[ProductTypeElement; N]> for ProductType { ProductType::new(fields.into()) } } -impl From<[(Option<&str>, AlgebraicType); N]> for ProductType { - fn from(fields: [(Option<&str>, AlgebraicType); N]) -> Self { +impl From<[(Option<&'static str>, AlgebraicType); N]> for ProductType { + fn from(fields: [(Option<&'static str>, AlgebraicType); N]) -> Self { fields.into_iter().collect() } } -impl From<[(&str, AlgebraicType); N]> for ProductType { - fn from(fields: [(&str, AlgebraicType); N]) -> Self { +impl, const N: usize> From<[(Id, AlgebraicType); N]> for ProductType { + fn from(fields: [(Id, AlgebraicType); N]) -> Self { fields.into_iter().collect() } } diff --git a/crates/sats/src/product_type_element.rs b/crates/sats/src/product_type_element.rs index 1a581e5e571..51c6d9ca90a 100644 --- a/crates/sats/src/product_type_element.rs +++ b/crates/sats/src/product_type_element.rs @@ -1,4 +1,5 @@ use crate::meta_type::MetaType; +use crate::raw_identifier::RawIdentifier; use crate::{AlgebraicType, SpacetimeType, WithTypespace}; /// A factor / element of a product type. @@ -15,7 +16,7 @@ pub struct ProductTypeElement { /// As our type system is structural, /// a type like `{ foo: U8 }`, where `foo: U8` is the `ProductTypeElement`, /// is inequal to `{ bar: U8 }`, although their `algebraic_type`s (`U8`) match. - pub name: Option>, + pub name: Option, /// The type of the element. /// /// Only values of this type can be stored in the element. @@ -24,23 +25,23 @@ pub struct ProductTypeElement { impl ProductTypeElement { /// Returns an element with the given `name` and `algebraic_type`. - pub const fn new(algebraic_type: AlgebraicType, name: Option>) -> Self { + pub const fn new(algebraic_type: AlgebraicType, name: Option) -> Self { Self { algebraic_type, name } } /// Returns a named element with `name` and `algebraic_type`. - pub fn new_named(algebraic_type: AlgebraicType, name: impl Into>) -> Self { + pub fn new_named(algebraic_type: AlgebraicType, name: impl Into) -> Self { Self::new(algebraic_type, Some(name.into())) } /// Returns the name of the field. - pub fn name(&self) -> Option<&str> { - self.name.as_deref() + pub fn name(&self) -> Option<&RawIdentifier> { + self.name.as_ref() } /// Returns whether the field has the given name. pub fn has_name(&self, name: &str) -> bool { - self.name() == Some(name) + self.name().is_some_and(|n| &**n == name) } } diff --git a/crates/sats/src/proptest.rs b/crates/sats/src/proptest.rs index 5cf498a6d42..bb296e14da7 100644 --- a/crates/sats/src/proptest.rs +++ b/crates/sats/src/proptest.rs @@ -2,6 +2,7 @@ //! //! This notably excludes `Ref` types. +use crate::raw_identifier::RawIdentifier; use crate::{i256, u256, ProductTypeElement, SumTypeVariant}; use crate::{ AlgebraicType, AlgebraicTypeRef, AlgebraicValue, ArrayValue, ProductType, ProductValue, SumType, SumValue, @@ -71,7 +72,7 @@ fn generate_algebraic_type_from_leaves( .enumerate() .map(|(i, ty)| ProductTypeElement { // Generate names because the validation code in the `schema` crate requires them. - name: Some(format!("field_{i}").into()), + name: Some(RawIdentifier::new(format!("field_{i}"))), algebraic_type: ty }) .collect()) @@ -83,7 +84,7 @@ fn generate_algebraic_type_from_leaves( .into_iter() .enumerate() .map(|(i, ty)| SumTypeVariant { - name: Some(format!("variant_{i}").into()), + name: Some(RawIdentifier::new(format!("variant_{i}"))), algebraic_type: ty }) .collect::>()) diff --git a/crates/sats/src/raw_identifier.rs b/crates/sats/src/raw_identifier.rs new file mode 100644 index 00000000000..65267b622b3 --- /dev/null +++ b/crates/sats/src/raw_identifier.rs @@ -0,0 +1,62 @@ +use crate::algebraic_type::AlgebraicType; +use crate::{impl_deserialize, impl_serialize, impl_st}; +use core::borrow::Borrow; +use core::fmt; +use core::ops::Deref; +use lean_string::LeanString; + +/// A not-yet-validated identifier. +#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +pub struct RawIdentifier(pub(crate) LeanString); + +impl_st!([] RawIdentifier, _ts => AlgebraicType::String); +impl_serialize!([] RawIdentifier, (self, ser) => ser.serialize_str(&self.0)); +impl_deserialize!([] RawIdentifier, de => LeanString::deserialize(de).map(Self)); +impl RawIdentifier { + /// Creates a new `RawIdentifier` from a string. + pub fn new(name: impl Into) -> Self { + Self(name.into()) + } + + pub fn into_inner(self) -> LeanString { + self.0 + } +} + +impl Deref for RawIdentifier { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef for RawIdentifier { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Borrow for RawIdentifier { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl fmt::Debug for RawIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +impl fmt::Display for RawIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl From<&'static str> for RawIdentifier { + fn from(s: &'static str) -> Self { + RawIdentifier(LeanString::from_static_str(s)) + } +} diff --git a/crates/sats/src/satn.rs b/crates/sats/src/satn.rs index 575edf230e5..afaaf421264 100644 --- a/crates/sats/src/satn.rs +++ b/crates/sats/src/satn.rs @@ -810,6 +810,7 @@ impl<'a, 'f, F: TypedWriter> ser::Serializer for TypedSerializer<'a, 'f, F> { record.push(( field .name() + .map(|n| &**n) .map(Cow::from) .unwrap_or_else(|| Cow::from(format!("col_{idx}"))), ty, @@ -830,8 +831,12 @@ impl<'a, 'f, F: TypedWriter> ser::Serializer for TypedSerializer<'a, 'f, F> { field: &product.elements[0], idx: 0, }; - self.f - .write_variant(tag, ty, var_ty.name(), sum.with(&var_ty.algebraic_type, val)) + self.f.write_variant( + tag, + ty, + var_ty.name().map(|n| &**n), + sum.with(&var_ty.algebraic_type, val), + ) } fn serialize_variant( diff --git a/crates/sats/src/ser.rs b/crates/sats/src/ser.rs index 8236fbe9c3a..185fd4eb6ac 100644 --- a/crates/sats/src/ser.rs +++ b/crates/sats/src/ser.rs @@ -126,7 +126,7 @@ pub trait Serializer: Sized { assert_eq!(val.len(), value.ty().elements.len()); let mut prod = self.serialize_named_product(val.len())?; for (val, el_ty) in val.iter().zip(&*value.ty().elements) { - prod.serialize_element(el_ty.name(), &value.with(&el_ty.algebraic_type, val))? + prod.serialize_element(el_ty.name().map(|n| &**n), &value.with(&el_ty.algebraic_type, val))? } prod.end() } @@ -139,7 +139,7 @@ pub trait Serializer: Sized { let sv = sum.value(); let (tag, val) = (sv.tag, &*sv.value); let var_ty = &sum.ty().variants[tag as usize]; // Extract the variant type by tag. - self.serialize_variant(tag, var_ty.name(), &sum.with(&var_ty.algebraic_type, val)) + self.serialize_variant(tag, var_ty.name().map(|n| &**n), &sum.with(&var_ty.algebraic_type, val)) } /// Serialize a sum value provided the chosen `tag`, `name`, and `value`. diff --git a/crates/sats/src/ser/impls.rs b/crates/sats/src/ser/impls.rs index 9baac393dff..a4b008bad48 100644 --- a/crates/sats/src/ser/impls.rs +++ b/crates/sats/src/ser/impls.rs @@ -2,6 +2,7 @@ use super::{Serialize, SerializeArray, SerializeSeqProduct, Serializer}; use crate::{i256, u256}; use crate::{AlgebraicType, AlgebraicValue, ArrayValue, ProductValue, SumValue, ValueWithType, F32, F64}; use core::ops::Bound; +use lean_string::LeanString; use smallvec::SmallVec; use spacetimedb_primitives::{ColList, ColSet}; use std::rc::Rc; @@ -111,6 +112,7 @@ impl_serialize!([T: Serialize + ?Sized] Rc, (self, ser) => (**self).serialize impl_serialize!([T: Serialize + ?Sized] Arc, (self, ser) => (**self).serialize(ser)); impl_serialize!([T: Serialize + ?Sized] &T, (self, ser) => (**self).serialize(ser)); impl_serialize!([] String, (self, ser) => ser.serialize_str(self)); +impl_serialize!([] LeanString, (self, ser) => ser.serialize_str(self)); impl_serialize!([T: Serialize] Option, (self, ser) => match self { Some(v) => ser.serialize_variant(0, Some("some"), v), None => ser.serialize_variant(1, Some("none"), &()), diff --git a/crates/sats/src/sum_type.rs b/crates/sats/src/sum_type.rs index 4d9afca349c..93e257bf6e8 100644 --- a/crates/sats/src/sum_type.rs +++ b/crates/sats/src/sum_type.rs @@ -2,6 +2,7 @@ use crate::algebraic_value::de::{ValueDeserializeError, ValueDeserializer}; use crate::algebraic_value::ser::value_serialize; use crate::de::Deserialize; use crate::meta_type::MetaType; +use crate::raw_identifier::RawIdentifier; use crate::{AlgebraicType, AlgebraicValue, SpacetimeType, SumTypeVariant, SumValue, Typespace}; /// The tag used for the `Interval` variant of the special `ScheduleAt` sum type. @@ -243,13 +244,13 @@ impl From<[SumTypeVariant; N]> for SumType { SumType::new(fields.into()) } } -impl From<[(Option<&str>, AlgebraicType); N]> for SumType { - fn from(fields: [(Option<&str>, AlgebraicType); N]) -> Self { - fields.map(|(s, t)| SumTypeVariant::new(t, s.map(<_>::into))).into() +impl From<[(Option<&'static str>, AlgebraicType); N]> for SumType { + fn from(fields: [(Option<&'static str>, AlgebraicType); N]) -> Self { + fields.map(|(s, t)| SumTypeVariant::new(t, s.map(Into::into))).into() } } -impl From<[(&str, AlgebraicType); N]> for SumType { - fn from(fields: [(&str, AlgebraicType); N]) -> Self { +impl, const N: usize> From<[(Id, AlgebraicType); N]> for SumType { + fn from(fields: [(Id, AlgebraicType); N]) -> Self { fields.map(|(s, t)| SumTypeVariant::new_named(t, s)).into() } } diff --git a/crates/sats/src/sum_type_variant.rs b/crates/sats/src/sum_type_variant.rs index b11044e7cd9..f4f91edffb7 100644 --- a/crates/sats/src/sum_type_variant.rs +++ b/crates/sats/src/sum_type_variant.rs @@ -1,5 +1,6 @@ use crate::algebraic_type::AlgebraicType; use crate::meta_type::MetaType; +use crate::raw_identifier::RawIdentifier; use crate::SpacetimeType; /// A variant of a sum type. @@ -10,7 +11,7 @@ use crate::SpacetimeType; #[sats(crate = crate)] pub struct SumTypeVariant { /// The name of the variant, if any. - pub name: Option>, + pub name: Option, /// The type of the variant. /// /// Unlike a language like Rust, @@ -23,31 +24,31 @@ pub struct SumTypeVariant { impl SumTypeVariant { /// Returns a sum type variant with an optional `name` and `algebraic_type`. - pub const fn new(algebraic_type: AlgebraicType, name: Option>) -> Self { + pub const fn new(algebraic_type: AlgebraicType, name: Option) -> Self { Self { algebraic_type, name } } /// Returns a sum type variant with `name` and `algebraic_type`. - pub fn new_named(algebraic_type: AlgebraicType, name: impl AsRef) -> Self { + pub fn new_named(algebraic_type: AlgebraicType, name: impl Into) -> Self { Self { algebraic_type, - name: Some(name.as_ref().into()), + name: Some(name.into()), } } /// Returns a unit variant with `name`. - pub fn unit(name: &str) -> Self { + pub fn unit(name: impl Into) -> Self { Self::new_named(AlgebraicType::unit(), name) } /// Returns the name of the variant. - pub fn name(&self) -> Option<&str> { - self.name.as_deref() + pub fn name(&self) -> Option<&RawIdentifier> { + self.name.as_ref() } /// Returns whether the variant has the given name. pub fn has_name(&self, name: &str) -> bool { - self.name() == Some(name) + self.name().is_some_and(|n| &**n == name) } /// Returns whether this is a unit variant. diff --git a/crates/schema/Cargo.toml b/crates/schema/Cargo.toml index a0e14bb2f74..f76a12ad7e9 100644 --- a/crates/schema/Cargo.toml +++ b/crates/schema/Cargo.toml @@ -19,10 +19,10 @@ spacetimedb-sql-parser.workspace = true anyhow.workspace = true derive_more.workspace = true -ecow.workspace = true indexmap.workspace = true itertools.workspace = true lazy_static.workspace = true +lean_string.workspace = true thiserror.workspace = true unicode-ident.workspace = true unicode-normalization.workspace = true diff --git a/crates/schema/src/auto_migrate.rs b/crates/schema/src/auto_migrate.rs index da8f9c41871..446c91638b7 100644 --- a/crates/schema/src/auto_migrate.rs +++ b/crates/schema/src/auto_migrate.rs @@ -8,11 +8,12 @@ use spacetimedb_data_structures::{ }; use spacetimedb_lib::{ db::raw_def::v9::{RawRowLevelSecurityDefV9, TableType}, - hash_bytes, AlgebraicType, Identity, + hash_bytes, Identity, }; use spacetimedb_sats::{ layout::{HasLayout, SumTypeLayout}, - WithTypespace, + raw_identifier::RawIdentifier, + AlgebraicType, WithTypespace, }; use termcolor_formatter::{ColorScheme, TermColorFormatter}; use thiserror::Error; @@ -400,10 +401,10 @@ pub enum AutoMigrateError { ChangeWithinColumnTypeRenamedField(ChangeColumnTypeParts), #[error("Adding a unique constraint {constraint} requires a manual migration")] - AddUniqueConstraint { constraint: Box }, + AddUniqueConstraint { constraint: RawIdentifier }, #[error("Changing a unique constraint {constraint} requires a manual migration")] - ChangeUniqueConstraint { constraint: Box }, + ChangeUniqueConstraint { constraint: RawIdentifier }, #[error("Removing the table {table} requires a manual migration")] RemoveTable { table: Identifier }, @@ -419,7 +420,7 @@ pub enum AutoMigrateError { "Changing the accessor name on index {index} from {old_accessor:?} to {new_accessor:?} requires a manual migration" )] ChangeIndexAccessor { - index: Box, + index: RawIdentifier, old_accessor: Option, new_accessor: Option, }, @@ -1275,20 +1276,20 @@ mod tests { let oranges = expect_identifier("Oranges"); let my_view = expect_identifier("my_view"); - let bananas_sequence = "Bananas_id_seq"; - let apples_unique_constraint = "Apples_id_key"; - let apples_sequence = "Apples_id_seq"; - let apples_id_name_index = "Apples_id_name_idx_btree"; - let apples_id_count_index = "Apples_id_count_idx_btree"; - let deliveries_schedule = "Deliveries_sched"; - let inspections_schedule = "Inspections_sched"; + let bananas_sequence: RawIdentifier = "Bananas_id_seq".into(); + let apples_unique_constraint: RawIdentifier = "Apples_id_key".into(); + let apples_sequence: RawIdentifier = "Apples_id_seq".into(); + let apples_id_name_index: RawIdentifier = "Apples_id_name_idx_btree".into(); + let apples_id_count_index: RawIdentifier = "Apples_id_count_idx_btree".into(); + let deliveries_schedule = expect_identifier("Deliveries_sched"); + let inspections_schedule = expect_identifier("Inspections_sched"); assert!(plan.prechecks.is_sorted()); assert_eq!(plan.prechecks.len(), 1); assert_eq!( plan.prechecks[0], - AutoMigratePrecheck::CheckAddSequenceRangeValid(bananas_sequence) + AutoMigratePrecheck::CheckAddSequenceRangeValid(&bananas_sequence) ); let sql_old = RawRowLevelSecurityDefV9 { sql: "SELECT * FROM Apples".into(), @@ -1303,36 +1304,36 @@ mod tests { assert!(steps.is_sorted()); assert!( - steps.contains(&AutoMigrateStep::RemoveSequence(apples_sequence)), + steps.contains(&AutoMigrateStep::RemoveSequence(&apples_sequence)), "{steps:?}" ); assert!( - steps.contains(&AutoMigrateStep::RemoveConstraint(apples_unique_constraint)), + steps.contains(&AutoMigrateStep::RemoveConstraint(&apples_unique_constraint)), "{steps:?}" ); assert!( - steps.contains(&AutoMigrateStep::RemoveIndex(apples_id_name_index)), + steps.contains(&AutoMigrateStep::RemoveIndex(&apples_id_name_index)), "{steps:?}" ); assert!( - steps.contains(&AutoMigrateStep::AddIndex(apples_id_count_index)), + steps.contains(&AutoMigrateStep::AddIndex(&apples_id_count_index)), "{steps:?}" ); assert!(steps.contains(&AutoMigrateStep::ChangeAccess(&bananas)), "{steps:?}"); assert!( - steps.contains(&AutoMigrateStep::AddSequence(bananas_sequence)), + steps.contains(&AutoMigrateStep::AddSequence(&bananas_sequence)), "{steps:?}" ); assert!(steps.contains(&AutoMigrateStep::AddTable(&oranges)), "{steps:?}"); assert!( - steps.contains(&AutoMigrateStep::RemoveSchedule(deliveries_schedule)), + steps.contains(&AutoMigrateStep::RemoveSchedule(&deliveries_schedule)), "{steps:?}" ); assert!( - steps.contains(&AutoMigrateStep::AddSchedule(inspections_schedule)), + steps.contains(&AutoMigrateStep::AddSchedule(&inspections_schedule)), "{steps:?}" ); diff --git a/crates/schema/src/auto_migrate/formatter.rs b/crates/schema/src/auto_migrate/formatter.rs index 3cd79c13051..a3edad35b97 100644 --- a/crates/schema/src/auto_migrate/formatter.rs +++ b/crates/schema/src/auto_migrate/formatter.rs @@ -9,11 +9,9 @@ use crate::{ identifier::Identifier, }; use itertools::Itertools; -use spacetimedb_lib::{ - db::raw_def::v9::{RawRowLevelSecurityDefV9, TableAccess, TableType}, - AlgebraicType, AlgebraicValue, -}; -use spacetimedb_sats::WithTypespace; +use spacetimedb_lib::db::raw_def::v9::{RawRowLevelSecurityDefV9, TableAccess, TableType}; +use spacetimedb_sats::raw_identifier::RawIdentifier; +use spacetimedb_sats::{AlgebraicType, AlgebraicValue, WithTypespace}; use thiserror::Error; pub fn format_plan(f: &mut F, plan: &AutoMigratePlan) -> Result<(), FormattingErrors> { @@ -156,7 +154,7 @@ pub trait MigrationFormatter { #[derive(Debug, Clone, PartialEq)] pub struct TableInfo { - pub name: String, + pub name: RawIdentifier, pub is_system: bool, pub access: TableAccess, pub columns: Vec, @@ -168,7 +166,7 @@ pub struct TableInfo { #[derive(Debug, Clone, PartialEq)] pub struct ViewInfo { - pub name: String, + pub name: RawIdentifier, pub params: Vec, pub columns: Vec, pub is_anonymous: bool, @@ -195,21 +193,21 @@ pub struct ColumnInfo { #[derive(Debug, Clone, PartialEq)] pub struct ConstraintInfo { - pub name: String, + pub name: RawIdentifier, pub columns: Vec, pub table_name: Identifier, } #[derive(Debug, Clone, PartialEq)] pub struct IndexInfo { - pub name: String, + pub name: RawIdentifier, pub columns: Vec, - pub table_name: Identifier, + pub table_name: RawIdentifier, } #[derive(Debug, Clone, PartialEq)] pub struct SequenceInfo { - pub name: String, + pub name: RawIdentifier, pub column_name: Identifier, pub table_name: Identifier, } @@ -222,7 +220,7 @@ pub struct AccessChangeInfo { #[derive(Debug, Clone, PartialEq)] pub struct ScheduleInfo { - pub table_name: String, + pub table_name: Identifier, pub function_name: Identifier, pub function_kind: FunctionKind, } @@ -288,7 +286,7 @@ fn extract_table_info( .map(|constraint| { let ConstraintData::Unique(unique) = &constraint.data; Ok(ConstraintInfo { - name: constraint.name.to_string(), + name: constraint.name.clone(), columns: unique .columns .iter() @@ -318,9 +316,9 @@ fn extract_table_info( .collect::, FormattingErrors>>()?; Ok(IndexInfo { - name: index.name.to_string(), + name: index.name.clone(), columns, - table_name: table_def.name.clone(), + table_name: table_def.name.clone().into(), }) }) .collect::, FormattingErrors>>()?; @@ -334,7 +332,7 @@ fn extract_table_info( .get_column(sequence.column) .ok_or(FormattingErrors::ColumnNotFound)?; Ok(SequenceInfo { - name: sequence.name.to_string(), + name: sequence.name.clone(), column_name: column.name.clone(), table_name: table_def.name.clone(), }) @@ -342,13 +340,13 @@ fn extract_table_info( .collect::, FormattingErrors>>()?; let schedule = table_def.schedule.as_ref().map(|schedule| ScheduleInfo { - table_name: table_def.name.to_string().clone(), + table_name: table_def.name.clone(), function_name: schedule.function_name.clone(), function_kind: schedule.function_kind, }); Ok(TableInfo { - name: table_def.name.to_string(), + name: table_def.name.clone().into(), is_system: table_def.table_type == TableType::System, access: table_def.table_access, columns, @@ -367,7 +365,7 @@ fn extract_view_info( view: view.to_string().into(), })?; - let name = view_def.name.to_string(); + let name = view_def.name.clone().into(); let is_anonymous = view_def.is_anonymous; let params = view_def @@ -426,9 +424,9 @@ fn extract_index_info( .collect::, FormattingErrors>>()?; Ok(IndexInfo { - name: index_def.name.to_string(), + name: index_def.name.clone(), columns, - table_name: table_def.name.clone(), + table_name: table_def.name.clone().into(), }) } @@ -455,7 +453,7 @@ fn extract_constraint_info( .collect::, FormattingErrors>>()?; Ok(ConstraintInfo { - name: constraint_def.name.to_string(), + name: constraint_def.name.clone(), columns, table_name: table_def.name.clone(), }) @@ -478,7 +476,7 @@ fn extract_sequence_info( .ok_or(FormattingErrors::ColumnNotFound)?; Ok(SequenceInfo { - name: sequence_def.name.to_string(), + name: sequence_def.name.clone(), column_name: column.name.clone(), table_name: table_def.name.clone(), }) @@ -507,7 +505,7 @@ fn extract_schedule_info( .ok_or(FormattingErrors::ScheduleNotFound)?; Ok(ScheduleInfo { - table_name: schedule_def.name.to_string().clone(), + table_name: schedule_def.name.clone(), function_name: schedule_def.function_name.clone(), function_kind: schedule_def.function_kind, }) diff --git a/crates/schema/src/def.rs b/crates/schema/src/def.rs index 8c8f0582fcd..031a923c6d2 100644 --- a/crates/schema/src/def.rs +++ b/crates/schema/src/def.rs @@ -32,15 +32,15 @@ use spacetimedb_data_structures::error_stream::{CollectAllErrors, CombineErrors, use spacetimedb_data_structures::map::{Equivalent, HashMap}; use spacetimedb_lib::db::raw_def; use spacetimedb_lib::db::raw_def::v9::{ - Lifecycle, RawColumnDefaultValueV9, RawConstraintDataV9, RawConstraintDefV9, RawIdentifier, RawIndexAlgorithm, - RawIndexDefV9, RawMiscModuleExportV9, RawModuleDefV9, RawProcedureDefV9, RawReducerDefV9, RawRowLevelSecurityDefV9, + Lifecycle, RawColumnDefaultValueV9, RawConstraintDataV9, RawConstraintDefV9, RawIndexAlgorithm, RawIndexDefV9, + RawMiscModuleExportV9, RawModuleDefV9, RawProcedureDefV9, RawReducerDefV9, RawRowLevelSecurityDefV9, RawScheduleDefV9, RawScopedTypeNameV9, RawSequenceDefV9, RawSql, RawTableDefV9, RawTypeDefV9, RawUniqueConstraintDataV9, RawViewDefV9, TableAccess, TableType, }; use spacetimedb_lib::{ProductType, RawModuleDef}; use spacetimedb_primitives::{ColId, ColList, ColOrCols, ColSet, ProcedureId, ReducerId, TableId, ViewFnPtr}; -use spacetimedb_sats::{AlgebraicType, AlgebraicValue}; -use spacetimedb_sats::{AlgebraicTypeRef, Typespace}; +use spacetimedb_sats::raw_identifier::RawIdentifier; +use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, AlgebraicValue, Typespace}; pub mod deserialize; pub mod error; @@ -49,8 +49,8 @@ pub mod validate; /// A map from `Identifier`s to values of type `T`. pub type IdentifierMap = HashMap; -/// A map from `Box`s to values of type `T`. -pub type StrMap = HashMap, T>; +/// A map from `RawIdentifier`s to values of type `T`. +pub type StrMap = HashMap; // We may eventually want to reorganize this module to look more // like the system tables, with numeric IDs used for lookups @@ -65,6 +65,7 @@ pub type StrMap = HashMap, T>; /// /// ```rust /// use spacetimedb_lib::RawModuleDef; +/// use spacetimedb_sats::raw_identifier::RawIdentifier; /// use spacetimedb_schema::def::{ModuleDef, TableDef, IndexDef, TypeDef, ModuleDefLookup, ScopedTypeName}; /// use spacetimedb_schema::identifier::Identifier; /// @@ -77,11 +78,11 @@ pub type StrMap = HashMap, T>; /// let module_def = ModuleDef::try_from(raw_module_def).expect("valid module def"); /// /// let table_name = Identifier::new("my_table".into()).expect("valid table name"); -/// let index_name = "my_table_my_column_idx_btree"; +/// let index_name: RawIdentifier = "my_table_my_column_idx_btree".into(); /// let scoped_type_name = ScopedTypeName::try_new([], "MyType").expect("valid scoped type name"); /// /// let table: Option<&TableDef> = module_def.lookup(&table_name); -/// let index: Option<&IndexDef> = module_def.lookup(index_name); +/// let index: Option<&IndexDef> = module_def.lookup(&index_name); /// let type_def: Option<&TypeDef> = module_def.lookup(&scoped_type_name); /// // etc. /// ``` @@ -235,7 +236,7 @@ impl ModuleDef { /// The `TableDef` an entity in the global namespace is stored in, if any. /// /// Generally, you will want to use the `lookup` method on the entity type instead. - pub fn stored_in_table_def(&self, name: &str) -> Option<&TableDef> { + pub fn stored_in_table_def(&self, name: &RawIdentifier) -> Option<&TableDef> { self.stored_in_table_def .get(name) .and_then(|table_name| self.tables.get(table_name)) @@ -595,7 +596,7 @@ impl From for TableDef { #[derive(Debug, Clone, Eq, PartialEq)] pub struct SequenceDef { /// The name of the sequence. Must be unique within the containing `ModuleDef`. - pub name: Box, + pub name: RawIdentifier, /// The position of the column associated with this sequence. /// This refers to a column in the same `RawTableDef` that contains this `RawSequenceDef`. @@ -641,7 +642,7 @@ impl From for RawSequenceDefV9 { #[non_exhaustive] pub struct IndexDef { /// The name of the index. Must be unique within the containing `ModuleDef`. - pub name: Box, + pub name: RawIdentifier, /// Accessor name for the index used in client codegen. /// @@ -925,7 +926,7 @@ pub struct ViewParamDef { #[derive(Debug, Clone, Eq, PartialEq)] pub struct ConstraintDef { /// The name of the constraint. Unique within the containing `ModuleDef`. - pub name: Box, + pub name: RawIdentifier, /// The data for the constraint. pub data: ConstraintData, @@ -1043,7 +1044,7 @@ impl fmt::Display for FunctionKind { #[non_exhaustive] pub struct ScheduleDef { /// The name of the schedule. Must be unique within the containing `ModuleDef`. - pub name: Box, + pub name: Identifier, /// The name of the column that stores the desired invocation time. /// @@ -1065,7 +1066,7 @@ pub struct ScheduleDef { impl From for RawScheduleDefV9 { fn from(val: ScheduleDef) -> Self { RawScheduleDefV9 { - name: Some(val.name), + name: Some(val.name.into()), reducer_name: val.function_name.into(), scheduled_at_column: val.at_column, } @@ -1187,7 +1188,7 @@ impl TryFrom for ScopedTypeName { impl From for RawScopedTypeNameV9 { fn from(val: ScopedTypeName) -> Self { RawScopedTypeNameV9 { - scope: val.scope.into_vec().into_iter().map_into().collect(), + scope: val.scope.into_vec().into_iter().map(|id| id.into()).collect(), name: val.name.into(), } } @@ -1350,7 +1351,7 @@ pub struct ReducerDef { impl From for RawReducerDefV9 { fn from(val: ReducerDef) -> Self { RawReducerDefV9 { - name: val.name.to_string().into(), + name: val.name.into(), params: val.params, lifecycle: val.lifecycle, } @@ -1420,7 +1421,7 @@ impl ModuleDefLookup for TableDef { } impl ModuleDefLookup for SequenceDef { - type Key<'a> = &'a str; + type Key<'a> = &'a RawIdentifier; fn key(&self) -> Self::Key<'_> { &self.name @@ -1432,7 +1433,7 @@ impl ModuleDefLookup for SequenceDef { } impl ModuleDefLookup for IndexDef { - type Key<'a> = &'a str; + type Key<'a> = &'a RawIdentifier; fn key(&self) -> Self::Key<'_> { &self.name @@ -1496,7 +1497,7 @@ impl ModuleDefLookup for ViewParamDef { } impl ModuleDefLookup for ConstraintDef { - type Key<'a> = &'a str; + type Key<'a> = &'a RawIdentifier; fn key(&self) -> Self::Key<'_> { &self.name @@ -1520,15 +1521,15 @@ impl ModuleDefLookup for RawRowLevelSecurityDefV9 { } impl ModuleDefLookup for ScheduleDef { - type Key<'a> = &'a str; + type Key<'a> = &'a Identifier; fn key(&self) -> Self::Key<'_> { &self.name } fn lookup<'a>(module_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> { - let schedule = module_def.stored_in_table_def(key)?.schedule.as_ref()?; - if &schedule.name[..] == key { + let schedule = module_def.stored_in_table_def(key.as_raw())?.schedule.as_ref()?; + if &schedule.name == key { Some(schedule) } else { None diff --git a/crates/schema/src/def/deserialize.rs b/crates/schema/src/def/deserialize.rs index 993bf064ac4..336ff2a22a3 100644 --- a/crates/schema/src/def/deserialize.rs +++ b/crates/schema/src/def/deserialize.rs @@ -1,6 +1,9 @@ //! Helpers to allow deserializing data using a ReducerDef. -use crate::def::{ProcedureDef, ReducerDef, ViewDef}; +use crate::{ + def::{ProcedureDef, ReducerDef, ViewDef}, + identifier::Identifier, +}; use spacetimedb_lib::{ sats::{self, de, impl_serialize, ser, ProductValue}, ProductType, @@ -23,15 +26,15 @@ impl Copy for ArgsSeed<'_, Def> {} pub trait FunctionDef { fn params(&self) -> &ProductType; - fn name(&self) -> &str; + fn name(&self) -> &Identifier; } impl FunctionDef for ReducerDef { fn params(&self) -> &ProductType { &self.params } - fn name(&self) -> &str { - &self.name + fn name(&self) -> &Identifier { + self.name.as_identifier() } } @@ -39,7 +42,7 @@ impl FunctionDef for ProcedureDef { fn params(&self) -> &ProductType { &self.params } - fn name(&self) -> &str { + fn name(&self) -> &Identifier { &self.name } } @@ -48,13 +51,13 @@ impl FunctionDef for ViewDef { fn params(&self) -> &ProductType { &self.params } - fn name(&self) -> &str { + fn name(&self) -> &Identifier { &self.name } } impl ArgsSeed<'_, Def> { - pub fn name(&self) -> &str { + pub fn name(&self) -> &Identifier { self.0.ty().name() } diff --git a/crates/schema/src/def/error.rs b/crates/schema/src/def/error.rs index d1008bbd7a7..da3ec2e1ac9 100644 --- a/crates/schema/src/def/error.rs +++ b/crates/schema/src/def/error.rs @@ -1,9 +1,11 @@ +use crate::identifier::Identifier; use crate::relation::{FieldName, Header}; use crate::table_name::TableName; use derive_more::Display; use spacetimedb_lib::db::raw_def::IndexType; -use spacetimedb_primitives::{ColId, ColList, TableId}; +use spacetimedb_primitives::{ColId, ColList}; use spacetimedb_sats::product_value::InvalidFieldError; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::satn::Satn as _; use spacetimedb_sats::{buffer, AlgebraicType, AlgebraicValue}; use std::fmt; @@ -110,11 +112,9 @@ pub enum DefType { pub enum SchemaError { #[error("Multiple primary columns defined for table: {table} columns: {pks:?}")] MultiplePrimaryKeys { table: Box, pks: Vec }, - #[error("table id `{table_id}` should have name")] - EmptyTableName { table_id: TableId }, #[error("{ty} {name} columns `{columns:?}` not found in table `{table}`")] ColumnsNotFound { - name: Box, + name: RawIdentifier, table: TableName, columns: Vec, ty: DefType, @@ -123,16 +123,16 @@ pub enum SchemaError { EmptyName { table: TableName, ty: DefType, id: u32 }, #[error("table `{table}` have `Constraints::unset()` for columns: {columns:?}")] ConstraintUnset { - table: Box, - name: Box, + table: TableName, + name: RawIdentifier, columns: ColList, }, #[error("Attempt to define a column with more than 1 auto_inc sequence: Table: `{table}`, Field: `{field}`")] - OneAutoInc { table: TableName, field: Box }, + OneAutoInc { table: TableName, field: Identifier }, #[error("Only Btree Indexes are supported: Table: `{table}`, Index: `{index}` is a `{index_type}`")] OnlyBtree { - table: Box, - index: Box, + table: TableName, + index: RawIdentifier, index_type: IndexType, }, } diff --git a/crates/schema/src/def/validate.rs b/crates/schema/src/def/validate.rs index 1c9461244da..44829a0e8b8 100644 --- a/crates/schema/src/def/validate.rs +++ b/crates/schema/src/def/validate.rs @@ -11,9 +11,8 @@ pub type Result = std::result::Result; /// Helpers used in tests for validation modules. #[cfg(test)] pub mod tests { - use itertools::Itertools; use spacetimedb_lib::{db::raw_def::v9::RawScopedTypeNameV9, AlgebraicType}; - use spacetimedb_sats::{Typespace, WithTypespace}; + use spacetimedb_sats::{raw_identifier::RawIdentifier, Typespace, WithTypespace}; use crate::{ def::{ModuleDef, ScopedTypeName, TableDef}, @@ -21,8 +20,9 @@ pub mod tests { }; /// Create an identifier, panicking if invalid. - pub fn expect_identifier(data: impl Into>) -> Identifier { - Identifier::new(data.into()).expect("invalid identifier") + pub fn expect_identifier(data: impl AsRef) -> Identifier { + let raw = RawIdentifier::new(data.as_ref()); + Identifier::new(raw).expect("invalid identifier") } /// Expect a name in the form "(scope::)*name". @@ -31,7 +31,8 @@ pub mod tests { let mut scope = scoped_name .split("::") .map(|module| { - Identifier::new(module.into()).expect("all components of a scoped name must be valid identifiers.") + let raw = RawIdentifier::new(module); + Identifier::new(raw).expect("all components of a scoped name must be valid identifiers.") }) .collect::>(); let name = scope.pop().expect("scoped names must contain at least one identifier"); @@ -43,7 +44,7 @@ pub mod tests { /// Expect a name in the form "(scope::)*name". /// Panics if the input is invalid. pub fn expect_raw_type_name(scoped_name: &str) -> RawScopedTypeNameV9 { - let mut scope = scoped_name.split("::").map_into().collect::>(); + let mut scope = scoped_name.split("::").map(RawIdentifier::new).collect::>(); let name = scope.pop().expect("scoped names must contain at least one identifier"); let scope = scope.into(); @@ -68,7 +69,7 @@ pub mod tests { .unwrap(); for (element, column) in product_type.elements.iter().zip(table_def.columns.iter()) { - assert_eq!(Some(&*column.name), element.name()); + assert_eq!(Some(&column.name.clone().into()), element.name()); assert_eq!(column.ty, element.algebraic_type); } } @@ -78,7 +79,7 @@ pub mod tests { assert_eq!( expect_raw_type_name("foo::bar::baz"), RawScopedTypeNameV9 { - scope: Box::new(["foo".into(), "bar".into()]), + scope: Box::new(["foo", "bar"].map(Into::into)), name: "baz".into(), } ); diff --git a/crates/schema/src/def/validate/v10.rs b/crates/schema/src/def/validate/v10.rs index b0d8d132d13..6808a1edd3a 100644 --- a/crates/schema/src/def/validate/v10.rs +++ b/crates/schema/src/def/validate/v10.rs @@ -1,5 +1,3 @@ -use std::borrow::Cow; - use spacetimedb_lib::bsatn::Deserializer; use spacetimedb_lib::db::raw_def::v10::*; use spacetimedb_lib::de::DeserializeSeed as _; @@ -126,7 +124,7 @@ pub fn validate(def: RawModuleDefV10) -> Result { .into_iter() .flatten() .map(|lifecycle_def| { - let function_name = ReducerName::new_from_str(&identifier(lifecycle_def.function_name.clone())?); + let function_name = ReducerName::new(identifier(lifecycle_def.function_name.clone())?); let (pos, _) = reducers_vec .iter() @@ -377,7 +375,7 @@ impl<'a> ModuleValidatorV10<'a> { let params_for_generate = self.core .params_for_generate(¶ms, |position, arg_name| TypeLocation::ReducerArg { - reducer_name: (&*source_name).into(), + reducer_name: source_name.clone(), position, arg_name, }); @@ -400,7 +398,7 @@ impl<'a> ModuleValidatorV10<'a> { let (ok_return_type, err_return_type) = return_res; Ok(ReducerDef { - name: ReducerName::new_from_str(&name_result), + name: ReducerName::new(name_result.clone()), params: params.clone(), params_for_generate: ProductTypeDef { elements: params_for_generate, @@ -418,7 +416,7 @@ impl<'a> ModuleValidatorV10<'a> { &mut self, schedule: RawScheduleDefV10, tables: &HashMap, - ) -> Result<(ScheduleDef, Box)> { + ) -> Result<(ScheduleDef, RawIdentifier)> { let RawScheduleDefV10 { source_name, table_name, @@ -481,14 +479,14 @@ impl<'a> ModuleValidatorV10<'a> { let params_for_generate = self.core .params_for_generate(¶ms, |position, arg_name| TypeLocation::ProcedureArg { - procedure_name: Cow::Borrowed(&source_name), + procedure_name: source_name.clone(), position, arg_name, }); let return_type_for_generate = self.core.validate_for_type_use( - &TypeLocation::ProcedureReturn { - procedure_name: Cow::Borrowed(&source_name), + || TypeLocation::ProcedureReturn { + procedure_name: source_name.clone(), }, &return_type, ); @@ -556,14 +554,14 @@ impl<'a> ModuleValidatorV10<'a> { let params_for_generate = self.core .params_for_generate(¶ms, |position, arg_name| TypeLocation::ViewArg { - view_name: Cow::Borrowed(&name), + view_name: name.clone(), position, arg_name, })?; let return_type_for_generate = self.core.validate_for_type_use( - &TypeLocation::ViewReturn { - view_name: Cow::Borrowed(&name), + || TypeLocation::ViewReturn { + view_name: name.clone(), }, &return_type, ); @@ -635,7 +633,7 @@ fn attach_lifecycles_to_reducers( fn attach_schedules_to_tables( tables: &mut HashMap, - schedules: Vec<(ScheduleDef, Box)>, + schedules: Vec<(ScheduleDef, RawIdentifier)>, ) -> Result<()> { for schedule in schedules { let (schedule, table_name) = schedule; @@ -1424,9 +1422,9 @@ mod tests { tables[0].sequences[0].source_name = Some("wacky.sequence()".into()); let def: ModuleDef = raw_def.try_into().unwrap(); - assert!(def.lookup::("wacky.constraint()").is_some()); - assert!(def.lookup::("wacky.index()").is_some()); - assert!(def.lookup::("wacky.sequence()").is_some()); + assert!(def.lookup::(&"wacky.constraint()".into()).is_some()); + assert!(def.lookup::(&"wacky.index()".into()).is_some()); + assert!(def.lookup::(&"wacky.sequence()".into()).is_some()); } #[test] diff --git a/crates/schema/src/def/validate/v8.rs b/crates/schema/src/def/validate/v8.rs index 382f0c43280..4089940f098 100644 --- a/crates/schema/src/def/validate/v8.rs +++ b/crates/schema/src/def/validate/v8.rs @@ -15,6 +15,7 @@ use spacetimedb_lib::{ TypeAlias as RawTypeAliasV8, }; use spacetimedb_primitives::{ColId, ColList, ConstraintKind, Constraints}; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::{AlgebraicTypeRef, Typespace, WithTypespace}; const INIT_NAME: &str = "__init__"; diff --git a/crates/schema/src/def/validate/v9.rs b/crates/schema/src/def/validate/v9.rs index cb5bce6ef7a..459f0cc80d2 100644 --- a/crates/schema/src/def/validate/v9.rs +++ b/crates/schema/src/def/validate/v9.rs @@ -10,7 +10,6 @@ use spacetimedb_lib::db::raw_def::v9::RawViewDefV9; use spacetimedb_lib::ProductType; use spacetimedb_primitives::col_list; use spacetimedb_sats::{bsatn::de::Deserializer, de::DeserializeSeed, WithTypespace}; -use std::borrow::Cow; /// Validate a `RawModuleDefV9` and convert it into a `ModuleDef`, /// or return a stream of errors if the definition is invalid. @@ -336,14 +335,14 @@ impl ModuleValidatorV9<'_> { let params_for_generate: Result<_> = self.core .params_for_generate(¶ms, |position, arg_name| TypeLocation::ReducerArg { - reducer_name: (&*name).into(), + reducer_name: name.clone(), position, arg_name, }); // Reducers share the "function namespace" with procedures. // Uniqueness is validated in a later pass, in `check_function_names_are_unique`. - let name = identifier(name.clone()); + let name = identifier(name); let lifecycle = lifecycle .map(|lifecycle| match &mut self.core.lifecycle_reducers[lifecycle] { @@ -355,7 +354,7 @@ impl ModuleValidatorV9<'_> { }) .transpose(); let (reducer_name, params_for_generate, lifecycle) = (name, params_for_generate, lifecycle).combine_errors()?; - let name = ReducerName::new_from_str(&reducer_name); + let name = ReducerName::new(reducer_name.clone()); let def = ReducerDef { name, params: params.clone(), @@ -381,14 +380,14 @@ impl ModuleValidatorV9<'_> { let params_for_generate = self.core .params_for_generate(¶ms, |position, arg_name| TypeLocation::ProcedureArg { - procedure_name: Cow::Borrowed(&name), + procedure_name: name.clone(), position, arg_name, }); let return_type_for_generate = self.core.validate_for_type_use( - &TypeLocation::ProcedureReturn { - procedure_name: Cow::Borrowed(&name), + || TypeLocation::ProcedureReturn { + procedure_name: name.clone(), }, &return_type, ); @@ -462,14 +461,14 @@ impl ModuleValidatorV9<'_> { let params_for_generate = self.core .params_for_generate(¶ms, |position, arg_name| TypeLocation::ViewArg { - view_name: Cow::Borrowed(&name), + view_name: name.clone(), position, arg_name, })?; let return_type_for_generate = self.core.validate_for_type_use( - &TypeLocation::ViewReturn { - view_name: Cow::Borrowed(&name), + || TypeLocation::ViewReturn { + view_name: name.clone(), }, &return_type, ); @@ -587,41 +586,40 @@ pub(crate) struct CoreValidator<'a> { } impl CoreValidator<'_> { - pub(crate) fn params_for_generate<'a>( + pub(crate) fn params_for_generate( &mut self, - params: &'a ProductType, - make_type_location: impl Fn(usize, Option>) -> TypeLocation<'a>, + params: &ProductType, + make_type_location: impl Fn(usize, Option) -> TypeLocation, ) -> Result> { params .elements .iter() .enumerate() .map(|(position, param)| { - // Note: this does not allocate, since `TypeLocation` is defined using `Cow`. - // We only allocate if an error is returned. - let location = make_type_location(position, param.name().map(Into::into)); + let location = || make_type_location(position, param.name().cloned()); let param_name = param .name() + .cloned() .ok_or_else(|| { ValidationError::ClientCodegenError { - location: location.clone().make_static(), + location: location(), error: ClientCodegenError::NamelessReducerParam, } .into() }) - .and_then(|s| identifier(s.into())); - let ty_use = self.validate_for_type_use(&location, ¶m.algebraic_type); + .and_then(identifier); + let ty_use = self.validate_for_type_use(location, ¶m.algebraic_type); (param_name, ty_use).combine_errors() }) .collect_all_errors() } - /// Add a name to the global namespace. + /// Add a `name` to the global namespace. /// /// If it has already been added, return an error. /// /// This is not used for all `Def` types. - pub(crate) fn add_to_global_namespace(&mut self, name: Box, ident: Identifier) -> Result> { + pub(crate) fn add_to_global_namespace(&mut self, name: RawIdentifier, ident: Identifier) -> Result { // This may report the table_name as invalid multiple times, but this will be removed // when we sort and deduplicate the error stream. if self.stored_in_table_def.contains_key(&name) { @@ -713,12 +711,12 @@ impl CoreValidator<'_> { /// Validates that a type can be used to generate a client type use. pub(crate) fn validate_for_type_use( &mut self, - location: &TypeLocation, + mut location: impl FnMut() -> TypeLocation, ty: &AlgebraicType, ) -> Result { self.typespace_for_generate.parse_use(ty).map_err(|err| { ErrorStream::expect_nonempty(err.into_iter().map(|error| ValidationError::ClientCodegenError { - location: location.clone().make_static(), + location: location(), error, })) }) @@ -746,9 +744,9 @@ impl CoreValidator<'_> { pub(crate) fn validate_schedule_def( &mut self, - table_name: Box, + table_name: RawIdentifier, name: Identifier, - function_name: Box, + function_name: RawIdentifier, product_type: &ProductType, schedule_at_col: ColId, primary_key: Option, @@ -775,10 +773,10 @@ impl CoreValidator<'_> { .into() }); let table_name = identifier(table_name)?; - let name = self.add_to_global_namespace(name.into(), table_name); + let name_res = self.add_to_global_namespace(name.clone().into(), table_name); let function_name = identifier(function_name); - let (name, (at_column, id_column), function_name) = (name, at_id, function_name).combine_errors()?; + let (_, (at_column, id_column), function_name) = (name_res, at_id, function_name).combine_errors()?; Ok(ScheduleDef { name, @@ -807,7 +805,7 @@ pub(crate) struct ViewValidator<'a, 'b> { impl<'a, 'b> ViewValidator<'a, 'b> { pub(crate) fn new( - raw_name: Box, + raw_name: RawIdentifier, product_type_ref: AlgebraicTypeRef, product_type: &'a ProductType, params: &'a ProductType, @@ -842,8 +840,8 @@ impl<'a, 'b> ViewValidator<'a, 'b> { let name: Result = identifier( column .name() - .map(|name| name.into()) - .unwrap_or_else(|| format!("param_{}", col_id).into_boxed_str()), + .cloned() + .unwrap_or_else(|| RawIdentifier::new(format!("param_{}", col_id))), ); // This error will be created multiple times if the view name is invalid, @@ -869,7 +867,7 @@ impl<'a, 'b> ViewValidator<'a, 'b> { self.inner.validate_column_def(col_id).map(ViewColumnDef::from) } - pub(crate) fn add_to_global_namespace(&mut self, name: Box) -> Result> { + pub(crate) fn add_to_global_namespace(&mut self, name: RawIdentifier) -> Result { self.inner.add_to_global_namespace(name) } } @@ -877,7 +875,7 @@ impl<'a, 'b> ViewValidator<'a, 'b> { /// A partially validated table. pub(crate) struct TableValidator<'a, 'b> { module_validator: &'a mut CoreValidator<'b>, - raw_name: Box, + raw_name: RawIdentifier, product_type_ref: AlgebraicTypeRef, product_type: &'a ProductType, has_sequence: HashSet, @@ -885,7 +883,7 @@ pub(crate) struct TableValidator<'a, 'b> { impl<'a, 'b> TableValidator<'a, 'b> { pub(crate) fn new( - raw_name: Box, + raw_name: RawIdentifier, product_type_ref: AlgebraicTypeRef, product_type: &'a ProductType, module_validator: &'a mut CoreValidator<'b>, @@ -911,16 +909,17 @@ impl<'a, 'b> TableValidator<'a, 'b> { let name: Result = column .name() + .cloned() .ok_or_else(|| { ValidationError::UnnamedColumn { column: self.raw_column_name(col_id), } .into() }) - .and_then(|name| identifier(name.into())); + .and_then(identifier); let ty_for_generate = self.module_validator.validate_for_type_use( - &TypeLocation::InTypespace { + || TypeLocation::InTypespace { ref_: self.product_type_ref, }, &column.algebraic_type, @@ -1078,7 +1077,10 @@ impl<'a, 'b> TableValidator<'a, 'b> { if is_bad_type { return Err(ValidationError::DirectIndexOnBadType { index: name.clone(), - column: field.name.clone().unwrap_or_else(|| column.idx().to_string().into()), + column: field + .name + .clone() + .unwrap_or_else(|| RawIdentifier::new(format!("{}", column.idx()))), ty: ty.clone().into(), } .into()); @@ -1151,7 +1153,7 @@ impl<'a, 'b> TableValidator<'a, 'b> { /// If it has already been added, return an error. /// /// This is not used for all `Def` types. - pub(crate) fn add_to_global_namespace(&mut self, name: Box) -> Result> { + pub(crate) fn add_to_global_namespace(&mut self, name: RawIdentifier) -> Result { let table_name = identifier(self.raw_name.clone())?; // This may report the table_name as invalid multiple times, but this will be removed // when we sort and deduplicate the error stream. @@ -1160,14 +1162,14 @@ impl<'a, 'b> TableValidator<'a, 'b> { /// Validate a `ColId` for this table, returning it unmodified if valid. /// `def_name` is the name of the definition being validated and is used in errors. - pub(crate) fn validate_col_id(&self, def_name: &str, col_id: ColId) -> Result { + pub(crate) fn validate_col_id(&self, def_name: &RawIdentifier, col_id: ColId) -> Result { if self.product_type.elements.get(col_id.idx()).is_some() { Ok(col_id) } else { Err(ValidationError::ColumnNotFound { column: col_id, table: self.raw_name.clone(), - def: def_name.into(), + def: def_name.clone(), } .into()) } @@ -1175,7 +1177,7 @@ impl<'a, 'b> TableValidator<'a, 'b> { /// Validate a `ColList` for this table, returning it unmodified if valid. /// `def_name` is the name of the definition being validated and is used in errors. - pub(crate) fn validate_col_ids(&self, def_name: &str, ids: ColList) -> Result { + pub(crate) fn validate_col_ids(&self, def_name: &RawIdentifier, ids: ColList) -> Result { let mut collected: Vec = ids .iter() .map(|column| self.validate_col_id(def_name, column)) @@ -1187,7 +1189,7 @@ impl<'a, 'b> TableValidator<'a, 'b> { if collected.len() != ids.len() as usize { Err(ValidationError::DuplicateColumns { columns: ids, - def: def_name.into(), + def: def_name.clone(), } .into()) } else { @@ -1201,13 +1203,13 @@ impl<'a, 'b> TableValidator<'a, 'b> { /// (It's generally preferable to avoid integer names, since types using the default /// ordering are implicitly shuffled!) pub(crate) fn raw_column_name(&self, col_id: ColId) -> RawColumnName { - let column: Box = self + let column: RawIdentifier = self .product_type .elements .get(col_id.idx()) .and_then(|col| col.name()) - .map(|name| name.into()) - .unwrap_or_else(|| format!("{col_id}").into()); + .cloned() + .unwrap_or_else(|| RawIdentifier::new(format!("{col_id}"))); RawColumnName { table: self.raw_name.clone(), @@ -1244,18 +1246,18 @@ pub fn generate_index_name(table_name: &str, table_type: &ProductType, algorithm _ => unimplemented!("Unknown index algorithm {:?}", algorithm), }; let column_names = concat_column_names(table_type, columns); - format!("{table_name}_{column_names}_idx_{label}").into() + RawIdentifier::new(format!("{table_name}_{column_names}_idx_{label}")) } /// All sequences have this name format. pub fn generate_sequence_name(table_name: &str, table_type: &ProductType, column: ColId) -> RawIdentifier { let column_name = column_name(table_type, column); - format!("{table_name}_{column_name}_seq").into() + RawIdentifier::new(format!("{table_name}_{column_name}_seq")) } /// All schedules have this name format. pub fn generate_schedule_name(table_name: &str) -> RawIdentifier { - format!("{table_name}_sched").into() + RawIdentifier::new(format!("{table_name}_sched")) } /// All unique constraints have this name format. @@ -1265,12 +1267,12 @@ pub fn generate_unique_constraint_name( columns: &ColList, ) -> RawIdentifier { let column_names = concat_column_names(product_type, columns); - format!("{table_name}_{column_names}_key").into() + RawIdentifier::new(format!("{table_name}_{column_names}_key")) } -/// Helper to create an `Identifier` from a `str` with the appropriate error type. +/// Helper to create an `Identifier` from a `RawIdentifier` with the appropriate error type. /// TODO: memoize this. -pub(crate) fn identifier(name: Box) -> Result { +pub(crate) fn identifier(name: RawIdentifier) -> Result { Identifier::new(name).map_err(|error| ValidationError::IdentifierError { error }.into()) } @@ -1283,7 +1285,7 @@ pub(crate) fn check_scheduled_functions_exist( procedures: &IndexMap, ) -> Result<()> { let validate_params = - |params_from_function: &ProductType, table_row_type_ref: AlgebraicTypeRef, function_name: &str| { + |params_from_function: &ProductType, table_row_type_ref: AlgebraicTypeRef, function_name: Identifier| { if params_from_function.elements.len() == 1 && params_from_function.elements[0].algebraic_type == table_row_type_ref.into() { @@ -1304,10 +1306,12 @@ pub(crate) fn check_scheduled_functions_exist( if let Some(schedule) = &mut table.schedule { if let Some(reducer) = reducers.get(&schedule.function_name) { schedule.function_kind = FunctionKind::Reducer; - validate_params(&reducer.params, table.product_type_ref, &reducer.name).map_err(Into::into) + validate_params(&reducer.params, table.product_type_ref, reducer.name.clone().into()) + .map_err(Into::into) } else if let Some(procedure) = procedures.get(&schedule.function_name) { schedule.function_kind = FunctionKind::Procedure; - validate_params(&procedure.params, table.product_type_ref, &procedure.name).map_err(Into::into) + validate_params(&procedure.params, table.product_type_ref, procedure.name.clone()) + .map_err(Into::into) } else { Err(ValidationError::MissingScheduledFunction { schedule: schedule.name.clone(), @@ -2178,9 +2182,9 @@ mod tests { raw_def.tables[0].sequences[0].name = Some("wacky.sequence()".into()); let def: ModuleDef = raw_def.try_into().unwrap(); - assert!(def.lookup::("wacky.constraint()").is_some()); - assert!(def.lookup::("wacky.index()").is_some()); - assert!(def.lookup::("wacky.sequence()").is_some()); + assert!(def.lookup::(&"wacky.constraint()".into()).is_some()); + assert!(def.lookup::(&"wacky.index()".into()).is_some()); + assert!(def.lookup::(&"wacky.sequence()".into()).is_some()); } #[test] diff --git a/crates/schema/src/error.rs b/crates/schema/src/error.rs index cc57f0e4d3b..06f284998b5 100644 --- a/crates/schema/src/error.rs +++ b/crates/schema/src/error.rs @@ -1,10 +1,9 @@ use spacetimedb_data_structures::error_stream::ErrorStream; -use spacetimedb_lib::db::raw_def::v9::{Lifecycle, RawIdentifier, RawScopedTypeNameV9}; +use spacetimedb_lib::db::raw_def::v9::{Lifecycle, RawScopedTypeNameV9}; use spacetimedb_lib::{ProductType, SumType}; use spacetimedb_primitives::{ColId, ColList, ColSet}; use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type; -use spacetimedb_sats::{bsatn::DecodeError, AlgebraicType, AlgebraicTypeRef}; -use std::borrow::Cow; +use spacetimedb_sats::{bsatn::DecodeError, raw_identifier::RawIdentifier, AlgebraicType, AlgebraicTypeRef}; use std::fmt; use crate::def::{FunctionKind, ScopedTypeName}; @@ -22,7 +21,7 @@ pub type ValidationErrors = ErrorStream; #[non_exhaustive] pub enum ValidationError { #[error("name `{name}` is used for multiple entities")] - DuplicateName { name: Box }, + DuplicateName { name: RawIdentifier }, #[error("name `{name}` is used for multiple types")] DuplicateTypeName { name: ScopedTypeName }, #[error("Multiple reducers defined for lifecycle event {lifecycle:?}")] @@ -58,7 +57,7 @@ pub enum ValidationError { #[error("Attempt to define {column} with more than 1 auto_inc sequence")] OneAutoInc { column: RawColumnName }, #[error("No index found to support unique constraint `{constraint}` for columns `{columns:?}`")] - UniqueConstraintWithoutIndex { constraint: Box, columns: ColSet }, + UniqueConstraintWithoutIndex { constraint: RawIdentifier, columns: ColSet }, #[error("Direct index does not support type `{ty}` in column `{column}` in index `{index}`")] DirectIndexOnBadType { index: RawIdentifier, @@ -99,7 +98,7 @@ pub enum ValidationError { ScheduledIncorrectColumns { table: RawIdentifier, columns: ProductType }, #[error("error at {location}: {error}")] ClientCodegenError { - location: TypeLocation<'static>, + location: TypeLocation, error: ClientCodegenError, }, #[error("Missing type definition for ref: {ref_}, holds type: {ty}")] @@ -112,7 +111,7 @@ pub enum ValidationError { #[error("Table {table} should have a type definition for its product_type_element, but does not")] TableTypeNameMismatch { table: Identifier }, #[error("Schedule {schedule} refers to a scheduled reducer or procedure {function} that does not exist")] - MissingScheduledFunction { schedule: Box, function: Identifier }, + MissingScheduledFunction { schedule: Identifier, function: Identifier }, #[error("Scheduled {function_kind} {function_name} expected to have type {expected}, but has type {actual}")] IncorrectScheduledFunctionParams { function_name: RawIdentifier, @@ -144,8 +143,8 @@ pub enum ValidationError { DuplicateSchedule { table: Identifier }, #[error("table {} corresponding to schedule {} not found", table_name, schedule_name)] MissingScheduleTable { - table_name: Box, - schedule_name: Box, + table_name: RawIdentifier, + schedule_name: Identifier, }, #[error("reducer {reducer_name} has invalid return type: found Result<{ok_type}, {err_type}>")] InvalidReducerReturnType { @@ -189,80 +188,37 @@ impl From for PrettyAlgebraicType { /// A place a type can be located in a module. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum TypeLocation<'a> { +pub enum TypeLocation { /// A reducer argument. ReducerArg { - reducer_name: Cow<'a, str>, + reducer_name: RawIdentifier, position: usize, - arg_name: Option>, + arg_name: Option, }, /// A procedure argument. ProcedureArg { - procedure_name: Cow<'a, str>, + procedure_name: RawIdentifier, position: usize, - arg_name: Option>, + arg_name: Option, }, /// A view argument. ViewArg { - view_name: Cow<'a, str>, + view_name: RawIdentifier, position: usize, - arg_name: Option>, + arg_name: Option, }, /// A procedure return type. - ProcedureReturn { procedure_name: Cow<'a, str> }, + ProcedureReturn { procedure_name: RawIdentifier }, /// A view return type. - ViewReturn { view_name: Cow<'a, str> }, + ViewReturn { view_name: RawIdentifier }, /// A type in the typespace. InTypespace { /// The reference to the type within the typespace. ref_: AlgebraicTypeRef, }, } -impl TypeLocation<'_> { - /// Make the lifetime of the location `'static`. - /// This allocates. - pub fn make_static(self) -> TypeLocation<'static> { - match self { - TypeLocation::ReducerArg { - reducer_name, - position, - arg_name, - } => TypeLocation::ReducerArg { - reducer_name: reducer_name.to_string().into(), - position, - arg_name: arg_name.map(|s| s.to_string().into()), - }, - TypeLocation::ProcedureArg { - procedure_name, - position, - arg_name, - } => TypeLocation::ProcedureArg { - procedure_name: procedure_name.to_string().into(), - position, - arg_name: arg_name.map(|s| s.to_string().into()), - }, - TypeLocation::ViewArg { - view_name, - position, - arg_name, - } => TypeLocation::ViewArg { - view_name: view_name.to_string().into(), - position, - arg_name: arg_name.map(|s| s.to_string().into()), - }, - Self::ProcedureReturn { procedure_name } => TypeLocation::ProcedureReturn { - procedure_name: procedure_name.to_string().into(), - }, - Self::ViewReturn { view_name } => TypeLocation::ViewReturn { - view_name: view_name.to_string().into(), - }, - // needed to convince rustc this is allowed. - TypeLocation::InTypespace { ref_ } => TypeLocation::InTypespace { ref_ }, - } - } -} -impl fmt::Display for TypeLocation<'_> { +impl fmt::Display for TypeLocation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TypeLocation::ReducerArg { diff --git a/crates/schema/src/identifier.rs b/crates/schema/src/identifier.rs index 546992e74e3..64ea8a46fec 100644 --- a/crates/schema/src/identifier.rs +++ b/crates/schema/src/identifier.rs @@ -1,6 +1,7 @@ use crate::error::IdentifierError; use spacetimedb_data_structures::map::{Equivalent, HashSet}; -use spacetimedb_sats::{de, ser}; +use spacetimedb_sats::raw_identifier::RawIdentifier; +use spacetimedb_sats::{impl_deserialize, impl_serialize, impl_st}; use std::fmt::{self, Debug, Display}; use std::ops::Deref; use unicode_ident::{is_xid_continue, is_xid_start}; @@ -23,18 +24,28 @@ lazy_static::lazy_static! { /// [`String::to_uppercase`](https://doc.rust-lang.org/std/string/struct.String.html#method.to_uppercase) will be rejected. /// /// The list of reserved words can be found in the file `SpacetimeDB/crates/sats/db/reserved_identifiers.txt`. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, de::Deserialize, ser::Serialize)] -#[sats(crate = spacetimedb_sats)] +/// +/// Internally, this is just a raw identifier with some validation on construction. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Identifier { - id: Box, + id: RawIdentifier, } +impl_st!([] Identifier, ts => RawIdentifier::make_type(ts)); +impl_serialize!([] Identifier, (self, ser) => ser.serialize_str(&self.id)); +impl_deserialize!([] Identifier, de => RawIdentifier::deserialize(de).map(Self::new_assume_valid)); + impl Identifier { + /// Returns a new identifier without validating the input. + pub fn new_assume_valid(name: RawIdentifier) -> Self { + Self { id: name } + } + /// Validates that the input string is a valid identifier. /// /// Currently, this rejects non-canonicalized identifiers. /// Eventually, it will be changed to canonicalize the input string. - pub fn new(name: Box) -> Result { + pub fn new(name: RawIdentifier) -> Result { if name.is_empty() { return Err(IdentifierError::Empty {}); } @@ -70,6 +81,16 @@ impl Identifier { Ok(Identifier { id: name }) } + #[cfg(any(test, feature = "test"))] + pub fn for_test(name: impl AsRef) -> Self { + Identifier::new(RawIdentifier::new(name.as_ref())).unwrap() + } + + /// Returns the raw identifier of this identifier. + pub fn as_raw(&self) -> &RawIdentifier { + &self.id + } + /// Check if a string is a reserved identifier. pub fn is_reserved(name: &str) -> bool { RESERVED_IDENTIFIERS.contains(&*name.to_uppercase()) @@ -96,47 +117,51 @@ impl Deref for Identifier { } } -impl From for Box { - fn from(value: Identifier) -> Self { - value.id - } -} - impl Equivalent for str { fn equivalent(&self, other: &Identifier) -> bool { self == &other.id[..] } } +impl From for RawIdentifier { + fn from(id: Identifier) -> Self { + id.id + } +} + #[cfg(test)] mod tests { use super::*; use proptest::prelude::*; + fn new(s: &str) -> Result { + Identifier::new(RawIdentifier::new(s)) + } + #[test] fn test_a_bunch_of_identifiers() { - assert!(Identifier::new("friends".into()).is_ok()); - assert!(Identifier::new("Oysters".into()).is_ok()); - assert!(Identifier::new("_hello".into()).is_ok()); - assert!(Identifier::new("bananas_there_".into()).is_ok()); - assert!(Identifier::new("Москва".into()).is_ok()); - assert!(Identifier::new("東京".into()).is_ok()); - assert!(Identifier::new("bees123".into()).is_ok()); - - assert!(Identifier::new("".into()).is_err()); - assert!(Identifier::new("123bees".into()).is_err()); - assert!(Identifier::new("\u{200B}hello".into()).is_err()); // zero-width space - assert!(Identifier::new(" hello".into()).is_err()); - assert!(Identifier::new("hello ".into()).is_err()); - assert!(Identifier::new("🍌".into()).is_err()); // ;-; the unicode committee is no fun - assert!(Identifier::new("".into()).is_err()); + assert!(new("friends").is_ok()); + assert!(new("Oysters").is_ok()); + assert!(new("_hello").is_ok()); + assert!(new("bananas_there_").is_ok()); + assert!(new("Москва").is_ok()); + assert!(new("東京").is_ok()); + assert!(new("bees123").is_ok()); + + assert!(new("").is_err()); + assert!(new("123bees").is_err()); + assert!(new("\u{200B}hello").is_err()); // zero-width space + assert!(new(" hello").is_err()); + assert!(new("hello ").is_err()); + assert!(new("🍌").is_err()); // ;-; the unicode committee is no fun + assert!(new("").is_err()); } #[test] fn test_canonicalization() { - assert!(Identifier::new("_\u{0041}\u{030A}".into()).is_err()); + assert!(new("_\u{0041}\u{030A}").is_err()); // canonicalized version of the above. - assert!(Identifier::new("_\u{00C5}".into()).is_ok()); + assert!(new("_\u{00C5}").is_ok()); } proptest! { @@ -145,7 +170,7 @@ mod tests { // Ha! Proptest will reliably find these. prop_assume!(!Identifier::is_reserved(&s)); - prop_assert!(Identifier::new(s.into()).is_ok()); + prop_assert!(new(&s).is_ok()); } } } diff --git a/crates/schema/src/reducer_name.rs b/crates/schema/src/reducer_name.rs index 189170cc910..9e05db1061c 100644 --- a/crates/schema/src/reducer_name.rs +++ b/crates/schema/src/reducer_name.rs @@ -1,25 +1,24 @@ +use crate::identifier::Identifier; +use core::fmt; use core::ops::Deref; -use core::{borrow::Borrow, fmt}; -use ecow::EcoString; -use spacetimedb_sats::{impl_deserialize, impl_serialize, impl_st, AlgebraicType}; +use spacetimedb_sats::raw_identifier::RawIdentifier; /// The name of a reducer. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] -pub struct ReducerName( - // TODO(perf, centril): Use this sort of optimization - // in RawIdentifier and `Identifier` and more places. - // TODO(perf): Consider `lean_string` instead for `&'static str` optimization. - // This could be useful in e.g., `SumType` and friends. - pub EcoString, -); - -impl_st!([] ReducerName, _ts => AlgebraicType::String); -impl_serialize!([] ReducerName, (self, ser) => ser.serialize_str(&self.0)); -impl_deserialize!([] ReducerName, de => >::deserialize(de).map(|s| Self(EcoString::from(s.as_ref())))); +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct ReducerName(pub Identifier); impl ReducerName { - pub fn new_from_str(name: &str) -> Self { - Self(EcoString::from(name)) + pub fn new(id: Identifier) -> Self { + Self(id) + } + + #[cfg(feature = "test")] + pub fn for_test(name: &str) -> Self { + Self(Identifier::for_test(name)) + } + + pub fn as_identifier(&self) -> &Identifier { + &self.0 } } @@ -37,14 +36,26 @@ impl AsRef for ReducerName { } } -impl Borrow for ReducerName { - fn borrow(&self) -> &str { - &self.0 +impl From for Identifier { + fn from(id: ReducerName) -> Self { + id.0 + } +} + +impl From for RawIdentifier { + fn from(id: ReducerName) -> Self { + Identifier::from(id).into() + } +} + +impl fmt::Debug for ReducerName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) } } impl fmt::Display for ReducerName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", &self.0) + fmt::Display::fmt(&self.0, f) } } diff --git a/crates/schema/src/relation.rs b/crates/schema/src/relation.rs index e8e32ccff4e..36b33ed2a14 100644 --- a/crates/schema/src/relation.rs +++ b/crates/schema/src/relation.rs @@ -311,7 +311,7 @@ mod tests { let id = id.into(); let fields = [fields.0, fields.1].map(|col| Column::new(FieldName::new(id, col), AlgebraicType::I8)); - Header::new(id, TableName::new_from_str(name), fields.into(), ct) + Header::new(id, TableName::for_test(name), fields.into(), ct) } #[test] diff --git a/crates/schema/src/schema.rs b/crates/schema/src/schema.rs index 0a8219e5e7d..4fa2085844f 100644 --- a/crates/schema/src/schema.rs +++ b/crates/schema/src/schema.rs @@ -3,20 +3,20 @@ //! They are mirrored in the system tables -- see `spacetimedb_core::db::datastore::system_tables`. //! Types in this file are not public ABI or API and may be changed at any time; it's the system tables that cannot. -// TODO(1.0): change all the `Box`s in this file to `Identifier`. +// TODO(1.0): change all the `RawIdentifier`s in this file to `Identifier`. // This doesn't affect the ABI so can wait until 1.0. use crate::def::error::{DefType, SchemaError}; use crate::relation::{combine_constraints, Column, DbTable, FieldName, Header}; use crate::table_name::TableName; use core::mem; -use core::ops::Deref; use itertools::Itertools; use spacetimedb_lib::db::auth::{StAccess, StTableType}; use spacetimedb_lib::db::raw_def::v9::RawSql; use spacetimedb_lib::db::raw_def::{generate_cols_name, RawConstraintDefV8}; use spacetimedb_primitives::*; use spacetimedb_sats::product_value::InvalidFieldError; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, WithTypespace}; use std::collections::BTreeMap; use std::sync::Arc; @@ -240,14 +240,18 @@ impl TableSchema { .map(|(col_pos, element)| ColumnSchema { table_id: TableId::SENTINEL, col_pos: ColId(col_pos as _), - col_name: element.name.clone().unwrap_or_else(|| format!("col{col_pos}").into()), + col_name: element + .name + .clone() + .map(Identifier::new_assume_valid) + .unwrap_or_else(|| Identifier::for_test(format!("col{col_pos}"))), col_type: element.algebraic_type.clone(), }) .collect(); TableSchema::new( TableId::SENTINEL, - TableName::new_from_str("TestTable"), + TableName::for_test("TestTable"), None, columns, vec![], @@ -528,12 +532,6 @@ impl TableSchema { pub fn validated(self) -> Result> { let mut errors = Vec::new(); - if self.table_name.is_empty() { - errors.push(SchemaError::EmptyTableName { - table_id: self.table_id, - }); - } - let columns_not_found = self .sequences .iter() @@ -651,11 +649,9 @@ impl TableSchema { /// This method works around this problem by copying the column types from the module def into the table schema. /// It can be removed once v8 is removed, since v9 will reject modules with an inconsistency like this. pub fn janky_fix_column_defs(&mut self, module_def: &ModuleDef) { - let table_name = Identifier::new(self.table_name.deref().into()).unwrap(); + let table_name = self.table_name.clone().into(); for col in &mut self.columns { - let def: &ColumnDef = module_def - .lookup((&table_name, &Identifier::new(col.col_name.clone()).unwrap())) - .unwrap(); + let def: &ColumnDef = module_def.lookup((&table_name, &col.col_name)).unwrap(); col.col_type = def.ty.clone(); } let table_def: &TableDef = module_def.expect_lookup(&table_name); @@ -747,7 +743,7 @@ impl TableSchema { TableSchema::new( TableId::SENTINEL, - TableName::new_from_str(name), + TableName::new(name.clone()), Some(view_info), columns, vec![], @@ -808,16 +804,16 @@ impl TableSchema { let n = return_columns.len() + 2; let mut columns = Vec::with_capacity(n); let mut meta_cols = 0; - let mut index_name = format!("{name}"); + let mut index_name = name.as_raw().clone().into_inner(); - let mut push_column = |name, col_type| { + let mut push_column = |name: &'static str, col_type| { meta_cols += 1; index_name += "_"; index_name += name; columns.push(ColumnSchema { table_id: TableId::SENTINEL, col_pos: columns.len().into(), - col_name: name.into(), + col_name: Identifier::new_assume_valid(name.into()), col_type, }); }; @@ -844,7 +840,7 @@ impl TableSchema { IndexSchema { index_id: IndexId::SENTINEL, table_id: TableId::SENTINEL, - index_name: index_name.into_boxed_str(), + index_name: RawIdentifier::new(index_name), index_algorithm: IndexAlgorithm::BTree(col_list.into()), } }; @@ -869,7 +865,7 @@ impl TableSchema { TableSchema::new( TableId::SENTINEL, - TableName::new_from_str(name), + TableName::new(name.clone()), Some(view_info), columns, indexes, @@ -935,7 +931,7 @@ impl Schema for TableSchema { TableSchema::new( table_id, - TableName::new_from_str(name), + TableName::new(name.clone()), None, columns, indexes, @@ -968,7 +964,7 @@ impl Schema for TableSchema { for index in &self.indexes { let index_def = def .indexes - .get(&index.index_name[..]) + .get(&index.index_name) .ok_or_else(|| anyhow::anyhow!("Index {} not found in definition", index.index_id.0))?; index.check_compatible(module_def, index_def)?; } @@ -977,7 +973,7 @@ impl Schema for TableSchema { for constraint in &self.constraints { let constraint_def = def .constraints - .get(&constraint.constraint_name[..]) + .get(&constraint.constraint_name) .ok_or_else(|| anyhow::anyhow!("Constraint {} not found in definition", constraint.constraint_id.0))?; constraint.check_compatible(module_def, constraint_def)?; } @@ -990,7 +986,7 @@ impl Schema for TableSchema { for sequence in &self.sequences { let sequence_def = def .sequences - .get(&sequence.sequence_name[..]) + .get(&sequence.sequence_name) .ok_or_else(|| anyhow::anyhow!("Sequence {} not found in definition", sequence.sequence_id.0))?; sequence.check_compatible(module_def, sequence_def)?; } @@ -1061,7 +1057,7 @@ pub struct ColumnSchema { /// The position of the column within the table. pub col_pos: ColId, /// The name of the column. Unique within the table. - pub col_name: Box, + pub col_name: Identifier, /// The type of the column. This will never contain any `AlgebraicTypeRef`s, /// that is, it will be resolved. pub col_type: AlgebraicType, @@ -1080,11 +1076,12 @@ impl spacetimedb_memory_usage::MemoryUsage for ColumnSchema { } impl ColumnSchema { - pub fn for_test(pos: impl Into, name: impl Into>, ty: AlgebraicType) -> Self { + #[cfg(any(test, feature = "test"))] + pub fn for_test(pos: impl Into, name: impl AsRef, ty: AlgebraicType) -> Self { Self { table_id: TableId::SENTINEL, col_pos: pos.into(), - col_name: name.into(), + col_name: Identifier::for_test(name), col_type: ty, } } @@ -1096,7 +1093,7 @@ impl ColumnSchema { ColumnSchema { table_id: TableId::SENTINEL, col_pos: def.col_id, - col_name: (*def.name).into(), + col_name: def.name.clone(), col_type, } } @@ -1121,7 +1118,7 @@ impl Schema for ColumnSchema { ColumnSchema { table_id, col_pos, - col_name: (*def.name).into(), + col_name: def.name.clone(), col_type, } } @@ -1138,7 +1135,7 @@ impl Schema for ColumnSchema { impl From<&ColumnSchema> for ProductTypeElement { fn from(value: &ColumnSchema) -> Self { Self { - name: Some(value.col_name.clone()), + name: Some(value.col_name.clone().into()), algebraic_type: value.col_type.clone(), } } @@ -1162,12 +1159,15 @@ pub struct ColumnSchemaRef<'a> { /// The column we are referring to. pub column: &'a ColumnSchema, /// The name of the table the column is attached to. - pub table_name: &'a str, + pub table_name: &'a RawIdentifier, } impl From> for ProductTypeElement { fn from(value: ColumnSchemaRef) -> Self { - ProductTypeElement::new(value.column.col_type.clone(), Some(value.column.col_name.clone())) + ProductTypeElement::new( + value.column.col_type.clone(), + Some(value.column.col_name.clone().into()), + ) } } @@ -1178,7 +1178,7 @@ pub struct SequenceSchema { pub sequence_id: SequenceId, /// The name of the sequence. /// Deprecated. In the future, sequences will be identified by col_pos. - pub sequence_name: Box, + pub sequence_name: RawIdentifier, /// The ID of the table associated with the sequence. pub table_id: TableId, /// The position of the column associated with this sequence. @@ -1226,7 +1226,7 @@ impl Schema for SequenceSchema { SequenceSchema { sequence_id: id, - sequence_name: (*def.name).into(), + sequence_name: def.name.clone(), table_id: parent_id, col_pos: def.column, increment: def.increment, @@ -1264,22 +1264,23 @@ pub struct ScheduleSchema { pub schedule_id: ScheduleId, /// The name of the schedule. - pub schedule_name: Box, + pub schedule_name: Identifier, /// The name of the reducer or procedure to call. - pub function_name: Box, + pub function_name: Identifier, /// The column containing the `ScheduleAt` enum. pub at_column: ColId, } impl ScheduleSchema { - pub fn for_test(name: impl Into>, function: impl Into>, at: impl Into) -> Self { + #[cfg(any(test, feature = "test"))] + pub fn for_test(name: impl AsRef, function: impl AsRef, at: impl Into) -> Self { Self { table_id: TableId::SENTINEL, schedule_id: ScheduleId::SENTINEL, - schedule_name: name.into(), - function_name: function.into(), + schedule_name: Identifier::for_test(name.as_ref()), + function_name: Identifier::for_test(function.as_ref()), at_column: at.into(), } } @@ -1298,8 +1299,8 @@ impl Schema for ScheduleSchema { ScheduleSchema { table_id: parent_id, schedule_id: id, - schedule_name: (*def.name).into(), - function_name: (*def.function_name).into(), + schedule_name: def.name.clone(), + function_name: def.function_name.clone(), at_column: def.at_column, // Ignore def.at_column and id_column. Those are recovered at runtime. } @@ -1325,7 +1326,7 @@ pub struct IndexSchema { pub table_id: TableId, /// The name of the index. This should not be assumed to follow any particular format. /// Unique within the database. - pub index_name: Box, + pub index_name: RawIdentifier, /// The data for the schema. pub index_algorithm: IndexAlgorithm, } @@ -1343,11 +1344,11 @@ impl spacetimedb_memory_usage::MemoryUsage for IndexSchema { } impl IndexSchema { - pub fn for_test(name: impl Into>, algo: impl Into) -> Self { + pub fn for_test(name: impl AsRef, algo: impl Into) -> Self { Self { index_id: IndexId::SENTINEL, table_id: TableId::SENTINEL, - index_name: name.into(), + index_name: RawIdentifier::new(name.as_ref()), index_algorithm: algo.into(), } } @@ -1365,7 +1366,7 @@ impl Schema for IndexSchema { IndexSchema { index_id: id, table_id: parent_id, - index_name: (*def.name).into(), + index_name: def.name.clone(), index_algorithm, } } @@ -1388,7 +1389,7 @@ pub struct ConstraintSchema { /// The unique ID of the constraint within the database. pub constraint_id: ConstraintId, /// The name of the constraint. - pub constraint_name: Box, + pub constraint_name: RawIdentifier, /// The data for the constraint. pub data: ConstraintData, // this reuses the type from Def, which is fine, neither of `schema` nor `def` are ABI modules. } @@ -1406,11 +1407,11 @@ impl spacetimedb_memory_usage::MemoryUsage for ConstraintSchema { } impl ConstraintSchema { - pub fn unique_for_test(name: impl Into>, cols: impl Into) -> Self { + pub fn unique_for_test(name: impl AsRef, cols: impl Into) -> Self { Self { table_id: TableId::SENTINEL, constraint_id: ConstraintId::SENTINEL, - constraint_name: name.into(), + constraint_name: RawIdentifier::new(name.as_ref()), data: ConstraintData::Unique(UniqueConstraintData { columns: cols.into() }), } } @@ -1426,7 +1427,7 @@ impl ConstraintSchema { if constraint.constraints.has_unique() { Some(ConstraintSchema { constraint_id: ConstraintId::SENTINEL, // Set to 0 as it may be assigned later. - constraint_name: constraint.constraint_name.trim().into(), + constraint_name: RawIdentifier::new(constraint.constraint_name.trim()), table_id, data: ConstraintData::Unique(UniqueConstraintData { columns: constraint.columns.into(), @@ -1448,7 +1449,7 @@ impl Schema for ConstraintSchema { ConstraintSchema { constraint_id: id, - constraint_name: (*def.name).into(), + constraint_name: def.name.clone(), table_id: parent_id, data: def.data.clone(), } diff --git a/crates/schema/src/table_name.rs b/crates/schema/src/table_name.rs index 6778f742bc2..9cfc9c3ad32 100644 --- a/crates/schema/src/table_name.rs +++ b/crates/schema/src/table_name.rs @@ -1,25 +1,24 @@ +use crate::identifier::Identifier; use core::fmt; use core::ops::Deref; -use ecow::EcoString; -use spacetimedb_sats::{impl_deserialize, impl_serialize, impl_st, AlgebraicType}; +use spacetimedb_sats::{impl_deserialize, impl_serialize, impl_st, raw_identifier::RawIdentifier}; /// The name of a table. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct TableName( - // TODO(perf, centril): Use this sort of optimization - // in RawIdentifier and `Identifier` and more places. - // TODO(perf): Consider `lean_string` instead for `&'static str` optimization. - // This could be useful in e.g., `SumType` and friends. - EcoString, -); - -impl_st!([] TableName, _ts => AlgebraicType::String); -impl_serialize!([] TableName, (self, ser) => ser.serialize_str(&self.0)); -impl_deserialize!([] TableName, de => >::deserialize(de).map(|s| Self(EcoString::from(s.as_ref())))); +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct TableName(Identifier); + +impl_st!([] TableName, ts => Identifier::make_type(ts)); +impl_serialize!([] TableName, (self, ser) => self.0.serialize(ser)); +impl_deserialize!([] TableName, de => Identifier::deserialize(de).map(Self)); impl TableName { - pub fn new_from_str(name: &str) -> Self { - Self(EcoString::from(name)) + pub fn new(id: Identifier) -> Self { + Self(id) + } + + #[cfg(feature = "test")] + pub fn for_test(name: &str) -> Self { + Self(Identifier::for_test(name)) } pub fn to_boxed_str(&self) -> Box { @@ -41,8 +40,26 @@ impl AsRef for TableName { } } +impl From for Identifier { + fn from(id: TableName) -> Self { + id.0 + } +} + +impl From for RawIdentifier { + fn from(id: TableName) -> Self { + Identifier::from(id).into() + } +} + +impl fmt::Debug for TableName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + impl fmt::Display for TableName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", &self.0) + fmt::Display::fmt(&self.0, f) } } diff --git a/crates/schema/src/type_for_generate.rs b/crates/schema/src/type_for_generate.rs index a4ba94fc33f..94666642e8b 100644 --- a/crates/schema/src/type_for_generate.rs +++ b/crates/schema/src/type_for_generate.rs @@ -16,7 +16,8 @@ use spacetimedb_data_structures::{ }; use spacetimedb_lib::{AlgebraicType, ProductTypeElement}; use spacetimedb_sats::{ - layout::PrimitiveType, typespace::TypeRefError, AlgebraicTypeRef, ArrayType, SumTypeVariant, Typespace, + layout::PrimitiveType, raw_identifier::RawIdentifier, typespace::TypeRefError, AlgebraicTypeRef, ArrayType, + SumTypeVariant, Typespace, }; use std::{cell::RefCell, ops::Index, sync::Arc}; @@ -576,7 +577,7 @@ impl TypespaceForGenerateBuilder<'_> { fn process_element( &mut self, def: &AlgebraicType, - element_name: &Option>, + element_name: &Option, element_type: &AlgebraicType, ) -> Result<(Identifier, AlgebraicTypeUse)> { let element_name = element_name diff --git a/crates/snapshot/tests/remote.rs b/crates/snapshot/tests/remote.rs index b1fe13c1116..41097b33abd 100644 --- a/crates/snapshot/tests/remote.rs +++ b/crates/snapshot/tests/remote.rs @@ -23,7 +23,7 @@ use spacetimedb_lib::{ }; use spacetimedb_paths::{server::SnapshotsPath, FromPathUnchecked}; use spacetimedb_primitives::TableId; -use spacetimedb_sats::product; +use spacetimedb_sats::{product, raw_identifier::RawIdentifier}; use spacetimedb_schema::{ def::ModuleDef, schema::{Schema as _, TableSchema}, @@ -277,7 +277,7 @@ fn table( f: impl FnOnce(RawTableDefBuilder<'_>) -> RawTableDefBuilder, ) -> TableSchema { let mut builder = RawModuleDefV9Builder::new(); - f(builder.build_table_with_new_type(name, columns, true)); + f(builder.build_table_with_new_type(RawIdentifier::new(name), columns, true)); let raw = builder.finish(); let def: ModuleDef = raw.try_into().expect("table validation failed"); let table = def.table(name).expect("table not found"); diff --git a/crates/sql-parser/src/ast/mod.rs b/crates/sql-parser/src/ast/mod.rs index 776d4fc5006..c6ce8969801 100644 --- a/crates/sql-parser/src/ast/mod.rs +++ b/crates/sql-parser/src/ast/mod.rs @@ -1,7 +1,7 @@ -use std::fmt::{Display, Formatter}; - +use spacetimedb_lib::sats::raw_identifier::RawIdentifier; use spacetimedb_lib::Identity; use sqlparser::ast::Ident; +use std::fmt::{Display, Formatter}; pub mod sql; pub mod sub; @@ -187,12 +187,12 @@ pub enum Parameter { /// A SQL identifier or named reference. /// Currently case sensitive. #[derive(Debug, Clone)] -pub struct SqlIdent(pub Box); +pub struct SqlIdent(pub RawIdentifier); /// Case insensitivity should be implemented here if at all impl From for SqlIdent { fn from(Ident { value, .. }: Ident) -> Self { - SqlIdent(value.into_boxed_str()) + SqlIdent(RawIdentifier::new(value)) } } diff --git a/crates/table/benches/page_manager.rs b/crates/table/benches/page_manager.rs index 7224980654c..b88257793d7 100644 --- a/crates/table/benches/page_manager.rs +++ b/crates/table/benches/page_manager.rs @@ -11,6 +11,7 @@ use spacetimedb_lib::db::raw_def::v9::RawIndexAlgorithm; use spacetimedb_lib::db::raw_def::v9::RawModuleDefV9Builder; use spacetimedb_primitives::{ColList, IndexId, TableId}; use spacetimedb_sats::layout::{row_size_for_bytes, row_size_for_type, Size}; +use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::{AlgebraicType, AlgebraicValue, ProductType, ProductValue}; use spacetimedb_schema::def::BTreeAlgorithm; use spacetimedb_schema::def::ModuleDef; @@ -68,7 +69,7 @@ unsafe trait Row { // so that its accepted when used in a `ModuleDef` as a row type. for (idx, elem) in ty.elements.iter_mut().enumerate() { if elem.name.is_none() { - elem.name = Some(format!("col_{idx}").into()); + elem.name = Some(RawIdentifier::new(format!("col_{idx}"))); } } ty @@ -491,7 +492,7 @@ criterion_group!( fn schema_from_ty(ty: ProductType, name: &str) -> TableSchema { let mut result = TableSchema::from_product_type(ty); - result.table_name = TableName::new_from_str(name); + result.table_name = TableName::for_test(name); result } @@ -705,7 +706,7 @@ trait IndexedRow: Row + Sized { } /// Don't call this in a loop, it runs validation code. fn make_schema() -> TableSchema { - let name = Self::table_name(); + let name = RawIdentifier::new(Self::table_name()); let mut builder = RawModuleDefV9Builder::new(); builder .build_table_with_new_type(name.clone(), Self::row_type_for_schema(), true) @@ -716,7 +717,7 @@ trait IndexedRow: Row + Sized { "accessor_name_doesnt_matter", ); let def: ModuleDef = builder.finish().try_into().expect("failed to build table schema"); - def.table_schema(&name[..], TableId::SENTINEL).unwrap() + def.table_schema(&*name, TableId::SENTINEL).unwrap() } fn throughput() -> Throughput { Throughput::Bytes(mem::size_of::() as u64) diff --git a/crates/table/src/table.rs b/crates/table/src/table.rs index 1609b578182..7a29f32d530 100644 --- a/crates/table/src/table.rs +++ b/crates/table/src/table.rs @@ -33,7 +33,6 @@ use derive_more::{Add, AddAssign, From, Sub, SubAssign}; use enum_as_inner::EnumAsInner; use smallvec::SmallVec; use spacetimedb_primitives::{ColId, ColList, IndexId, SequenceId, TableId}; -use spacetimedb_sats::memory_usage::MemoryUsage; use spacetimedb_sats::{ algebraic_value::ser::ValueSerializer, bsatn::{self, ser::BsatnError, BufReservedFill, DecodeError, ToBsatn}, @@ -49,8 +48,10 @@ use spacetimedb_sats::{ layout::{AlgebraicTypeLayout, IncompatibleTypeLayoutError, PrimitiveType, RowTypeLayout, Size}, Typespace, }; +use spacetimedb_sats::{memory_usage::MemoryUsage, raw_identifier::RawIdentifier}; use spacetimedb_schema::{ def::{BTreeAlgorithm, IndexAlgorithm}, + identifier::Identifier, schema::{columns_to_row_type, ColumnSchema, IndexSchema, TableSchema}, table_name::TableName, }; @@ -2179,9 +2180,9 @@ impl<'a> Iterator for IndexScanRangeIter<'a> { #[derive(Error, Debug, PartialEq, Eq)] #[error("Unique constraint violation '{}' in table '{}': column(s): '{:?}' value: {}", constraint_name, table_name, cols, value.to_satn())] pub struct UniqueConstraintViolation { - pub constraint_name: Box, + pub constraint_name: RawIdentifier, pub table_name: TableName, - pub cols: Vec>, + pub cols: Vec, pub value: AlgebraicValue, } diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index cb527c0d8f5..e0851c967f9 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -6,6 +6,9 @@ license-file = "LICENSE" description = "A VM for SpacetimeDB" rust-version.workspace = true +[features] +test = ["spacetimedb-schema/test"] + [dependencies] spacetimedb-data-structures.workspace = true spacetimedb-execution.workspace = true @@ -25,6 +28,7 @@ thiserror.workspace = true tracing.workspace = true [dev-dependencies] +spacetimedb-schema = { path = "../schema", features = ["test"] } tempfile.workspace = true typed-arena.workspace = true diff --git a/crates/vm/src/eval.rs b/crates/vm/src/eval.rs index 729a3f57b6d..d30c15358a4 100644 --- a/crates/vm/src/eval.rs +++ b/crates/vm/src/eval.rs @@ -92,6 +92,7 @@ pub fn run_ast( /// Used internally for testing SQL JOINS. #[doc(hidden)] +#[cfg(any(test, feature = "test"))] pub mod test_helpers { use crate::relation::MemTable; use core::hash::BuildHasher as _; @@ -110,7 +111,7 @@ pub mod test_helpers { pub fn header_for_mem_table(table_id: TableId, fields: ProductType) -> Header { let hash = DefaultHashBuilder::default().hash_one(&fields); - let table_name = TableName::new_from_str(&format!("mem#{hash:x}")); + let table_name = TableName::for_test(&format!("mem_{hash:x}")); let cols = Vec::from(fields.elements) .into_iter() @@ -193,7 +194,6 @@ pub mod tests { use spacetimedb_sats::{product, AlgebraicType, ProductType}; use spacetimedb_schema::def::error::RelationError; use spacetimedb_schema::relation::{FieldName, Header}; - use spacetimedb_schema::table_name::TableName; /// From an original source of `result`s, applies `queries` and returns a final set of results. fn build_query<'a, const N: usize>( @@ -308,6 +308,7 @@ pub mod tests { fn test_join_inner() { let table_id = 0.into(); let table = mem_table_one_u64(table_id); + let table_name = table.head.table_name.clone(); let col: ColId = 0.into(); let field = table.head.fields[col.idx()].clone(); @@ -319,12 +320,7 @@ pub mod tests { let result = run_query(q.into(), sources); // The expected result. - let head = Header::new( - table_id, - TableName::new_from_str(""), - [field.clone(), field].into(), - Vec::new(), - ); + let head = Header::new(table_id, table_name, [field.clone(), field].into(), Vec::new()); let input = MemTable::from_iter(head.into(), [product!(1u64, 1u64)]); println!("{}", &result.head); diff --git a/crates/vm/src/expr.rs b/crates/vm/src/expr.rs index 99c7366b8c6..886982ae02e 100644 --- a/crates/vm/src/expr.rs +++ b/crates/vm/src/expr.rs @@ -2129,7 +2129,7 @@ mod tests { source_id: SourceId(0), header: Arc::new(Header { table_id: 42.into(), - table_name: TableName::new_from_str("foo"), + table_name: TableName::for_test("foo"), fields: vec![], constraints: Default::default(), }), @@ -2139,7 +2139,7 @@ mod tests { SourceExpr::DbTable(DbTable { head: Arc::new(Header { table_id: 42.into(), - table_name: TableName::new_from_str("foo"), + table_name: TableName::for_test("foo"), fields: vec![], constraints: [(ColId(42).into(), Constraints::indexed())].into_iter().collect(), }), @@ -2215,7 +2215,7 @@ mod tests { let table_access = StAccess::Public; let head = Header::new( id, - TableName::new_from_str(name), + TableName::for_test(name), fields .iter() .map(|(col, ty, _)| Column::new(FieldName::new(id, (*col).into()), ty.clone())) @@ -2290,7 +2290,7 @@ mod tests { let head1 = Header::new( table_id, - TableName::new_from_str("t1"), + TableName::for_test("t1"), columns.to_vec(), vec![ // Index a