diff --git a/Cargo.lock b/Cargo.lock index f0a3820aa13..b02e250b07f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4145,6 +4145,7 @@ dependencies = [ "bytes", "derive_more", "log", + "nonempty", "once_cell", "rand", "scoped-tls", @@ -4193,6 +4194,7 @@ dependencies = [ "humantime", "proc-macro2", "quote", + "spacetimedb-primitives", "syn 2.0.38", ] @@ -4406,6 +4408,7 @@ dependencies = [ name = "spacetimedb-primitives" version = "0.7.3" dependencies = [ + "bitflags 2.4.1", "nonempty", ] @@ -4421,6 +4424,7 @@ name = "spacetimedb-sats" version = "0.7.3" dependencies = [ "arrayvec", + "bitflags 2.4.1", "bytes", "decorum", "derive_more", @@ -4429,8 +4433,10 @@ dependencies = [ "itertools 0.11.0", "nonempty", "proptest", + "proptest-derive", "rand", "serde", + "sha3", "smallvec", "spacetimedb-bindings-macro", "spacetimedb-primitives", diff --git a/crates/bench/src/spacetime_raw.rs b/crates/bench/src/spacetime_raw.rs index 998d732eecb..5865efac019 100644 --- a/crates/bench/src/spacetime_raw.rs +++ b/crates/bench/src/spacetime_raw.rs @@ -4,10 +4,8 @@ use crate::{ ResultBench, }; use spacetimedb::db::relational_db::{open_db, RelationalDB}; -use spacetimedb::{ - db::datastore::traits::{IndexDef, TableDef}, - execution_context::ExecutionContext, -}; +use spacetimedb::execution_context::ExecutionContext; +use spacetimedb_lib::sats::db::def::{IndexDef, TableDef}; use spacetimedb_lib::sats::AlgebraicValue; use spacetimedb_primitives::{ColId, TableId}; use std::hint::black_box; diff --git a/crates/bindings-macro/Cargo.toml b/crates/bindings-macro/Cargo.toml index 75254e2f80b..fe41e473711 100644 --- a/crates/bindings-macro/Cargo.toml +++ b/crates/bindings-macro/Cargo.toml @@ -11,6 +11,8 @@ proc-macro = true bench = false [dependencies] +spacetimedb-primitives = { path = "../primitives", version = "0.7.3" } + bitflags.workspace = true humantime.workspace = true proc-macro2.workspace = true diff --git a/crates/bindings-macro/src/lib.rs b/crates/bindings-macro/src/lib.rs index ebf5a9ff853..c01be68639b 100644 --- a/crates/bindings-macro/src/lib.rs +++ b/crates/bindings-macro/src/lib.rs @@ -14,10 +14,11 @@ extern crate proc_macro; use std::collections::HashMap; use std::time::Duration; -use bitflags::{bitflags, Flags}; +use bitflags::Flags; use module::{derive_deserialize, derive_satstype, derive_serialize}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, TokenStreamExt}; +use spacetimedb_primitives::ColumnIndexAttribute; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::{ @@ -302,7 +303,7 @@ fn gen_reducer(original_function: ItemFn, reducer_name: &str, extra: ReducerExtr // // TODO: better (non-string-based) validation for these // if !matches!( // &*arg1.to_token_stream().to_string(), - // "spacetimedb::spacetimedb_lib::hash::Hash" | "Hash" + // "spacetimedb::spacetimedb_sats::hash::Hash" | "Hash" // ) { // return Err(syn::Error::new_spanned( // &arg1, @@ -435,26 +436,6 @@ struct Column<'a> { attr: ColumnIndexAttribute, } -// TODO: any way to avoid duplication with same structure in bindings crate? Extra crate? -bitflags! { - #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] - struct ColumnIndexAttribute: u8 { - const UNSET = Self::empty().bits(); - /// Index no unique - const INDEXED = 0b0001; - /// Generate the next [Sequence] - const AUTO_INC = 0b0010; - /// Index unique - const UNIQUE = Self::INDEXED.bits() | 0b0100; - /// Unique + AutoInc - const IDENTITY = Self::UNIQUE.bits() | Self::AUTO_INC.bits(); - /// Primary key column (implies Unique) - const PRIMARY_KEY = Self::UNIQUE.bits() | 0b1000; - /// PrimaryKey + AutoInc - const PRIMARY_KEY_AUTO = Self::PRIMARY_KEY.bits() | Self::AUTO_INC.bits(); - } -} - fn spacetimedb_table(item: TokenStream) -> syn::Result { Ok(quote! { #[derive(spacetimedb::TableType)] @@ -613,7 +594,7 @@ fn spacetimedb_tabletype_impl(item: syn::DeriveInput) -> syn::Result syn::Result] = &[#(#indexes),*]; type InsertResult = #insert_result; diff --git a/crates/bindings/Cargo.toml b/crates/bindings/Cargo.toml index df54b09eaba..8a7b828b880 100644 --- a/crates/bindings/Cargo.toml +++ b/crates/bindings/Cargo.toml @@ -23,6 +23,7 @@ spacetimedb-primitives = { path = "../primitives", version = "0.7.3" } derive_more.workspace = true log.workspace = true +nonempty.workspace = true once_cell.workspace = true scoped-tls.workspace = true diff --git a/crates/bindings/src/impls.rs b/crates/bindings/src/impls.rs index 88ce0de63f4..b515ba37695 100644 --- a/crates/bindings/src/impls.rs +++ b/crates/bindings/src/impls.rs @@ -1,6 +1,8 @@ -use spacetimedb_lib::{Address, DataKey, Hash, Identity}; +use spacetimedb_lib::{Address, Identity}; use super::PrimaryKey; +use crate::sats::data_key::DataKey; +use crate::sats::hash::Hash; use crate::{FilterableValue, UniqueValue}; macro_rules! impl_primitives { diff --git a/crates/bindings/src/lib.rs b/crates/bindings/src/lib.rs index 2fbcd10afc2..e97c6266934 100644 --- a/crates/bindings/src/lib.rs +++ b/crates/bindings/src/lib.rs @@ -10,32 +10,31 @@ pub mod rt; pub mod time_span; mod timestamp; +use crate::sats::db::def::IndexType; use spacetimedb_lib::buffer::{BufReader, BufWriter, Cursor, DecodeError}; -pub use spacetimedb_lib::de::{Deserialize, DeserializeOwned}; use spacetimedb_lib::sats::{impl_deserialize, impl_serialize, impl_st}; -pub use spacetimedb_lib::ser::Serialize; -use spacetimedb_lib::{bsatn, ColumnIndexAttribute, IndexType, PrimaryKey, ProductType, ProductValue}; +use spacetimedb_lib::{bsatn, PrimaryKey, ProductType, ProductValue}; +use spacetimedb_primitives::ColumnIndexAttribute; use std::cell::RefCell; use std::marker::PhantomData; use std::slice::from_ref; use std::{fmt, panic}; +use sys::{Buffer, BufferIter}; -pub use spacetimedb_bindings_macro::{duration, query, spacetimedb, TableType}; - +pub use log; pub use sats::SpacetimeType; +pub use spacetimedb_bindings_macro::{duration, query, spacetimedb, TableType}; +pub use spacetimedb_bindings_sys as sys; pub use spacetimedb_lib; +pub use spacetimedb_lib::de::{Deserialize, DeserializeOwned}; pub use spacetimedb_lib::sats; +pub use spacetimedb_lib::ser::Serialize; pub use spacetimedb_lib::Address; pub use spacetimedb_lib::AlgebraicValue; pub use spacetimedb_lib::Identity; pub use spacetimedb_primitives::TableId; -pub use timestamp::Timestamp; - -pub use spacetimedb_bindings_sys as sys; pub use sys::Errno; -use sys::{Buffer, BufferIter}; - -pub use log; +pub use timestamp::Timestamp; pub type Result = core::result::Result; diff --git a/crates/bindings/src/rt.rs b/crates/bindings/src/rt.rs index a4586f6aa2f..a3cf9b37798 100644 --- a/crates/bindings/src/rt.rs +++ b/crates/bindings/src/rt.rs @@ -7,9 +7,10 @@ use std::marker::PhantomData; use std::sync::Mutex; use std::time::Duration; +use crate::sats::db::auth::{StAccess, StTableType}; use crate::timestamp::with_timestamp_set; use crate::{sys, ReducerContext, ScheduleToken, SpacetimeType, TableType, Timestamp}; -use spacetimedb_lib::auth::{StAccess, StTableType}; +pub use once_cell::sync::{Lazy, OnceCell}; use spacetimedb_lib::de::{self, Deserialize, SeqProductAccess}; use spacetimedb_lib::sats::typespace::TypespaceBuilder; use spacetimedb_lib::sats::{impl_deserialize, impl_serialize, AlgebraicType, AlgebraicTypeRef, ProductTypeElement}; @@ -18,8 +19,6 @@ use spacetimedb_lib::{bsatn, Address, Identity, MiscModuleExport, ModuleDef, Red use spacetimedb_primitives::TableId; use sys::Buffer; -pub use once_cell::sync::{Lazy, OnceCell}; - /// The `sender` invokes `reducer` at `timestamp` and provides it with the given `args`. /// /// The `epilogue` is executed after `reducer` has finished. diff --git a/crates/cli/src/subcommands/generate/csharp.rs b/crates/cli/src/subcommands/generate/csharp.rs index 57967a9dcc4..45495bb186c 100644 --- a/crates/cli/src/subcommands/generate/csharp.rs +++ b/crates/cli/src/subcommands/generate/csharp.rs @@ -3,10 +3,11 @@ use super::util::fmt_fn; use std::fmt::{self, Write}; use convert_case::{Case, Casing}; +use spacetimedb_lib::sats::db::attr::ColumnIndexAttribute; use spacetimedb_lib::sats::{ AlgebraicType, AlgebraicType::Builtin, AlgebraicTypeRef, ArrayType, BuiltinType, MapType, ProductType, SumType, }; -use spacetimedb_lib::{ColumnIndexAttribute, ProductTypeElement, ReducerDef, TableDef}; +use spacetimedb_lib::{ProductTypeElement, ReducerDef, TableDef}; use super::code_indenter::CodeIndenter; use super::{GenCtx, GenItem, INDENT}; diff --git a/crates/cli/src/subcommands/generate/python.rs b/crates/cli/src/subcommands/generate/python.rs index ecef7944c5c..627280ded24 100644 --- a/crates/cli/src/subcommands/generate/python.rs +++ b/crates/cli/src/subcommands/generate/python.rs @@ -1,9 +1,10 @@ use super::util::fmt_fn; use convert_case::{Case, Casing}; +use spacetimedb_lib::sats::db::attr::ColumnIndexAttribute; use spacetimedb_lib::{ sats::{AlgebraicType::Builtin, AlgebraicTypeRef, ArrayType, BuiltinType, MapType}, - AlgebraicType, ColumnIndexAttribute, ProductType, ProductTypeElement, ReducerDef, SumType, TableDef, + AlgebraicType, ProductType, ProductTypeElement, ReducerDef, SumType, TableDef, }; use std::fmt::{self, Write}; diff --git a/crates/cli/src/subcommands/generate/rust.rs b/crates/cli/src/subcommands/generate/rust.rs index c29e06204a8..339a587a621 100644 --- a/crates/cli/src/subcommands/generate/rust.rs +++ b/crates/cli/src/subcommands/generate/rust.rs @@ -1,11 +1,12 @@ use super::code_indenter::CodeIndenter; use super::{GenCtx, GenItem}; use convert_case::{Case, Casing}; +use spacetimedb_lib::sats::db::attr::ColumnIndexAttribute; use spacetimedb_lib::sats::{ AlgebraicType, AlgebraicTypeRef, ArrayType, BuiltinType, MapType, ProductType, ProductTypeElement, SumType, SumTypeVariant, }; -use spacetimedb_lib::{ColumnIndexAttribute, ReducerDef, TableDef}; +use spacetimedb_lib::{ReducerDef, TableDef}; use std::collections::HashSet; use std::fmt::Write; diff --git a/crates/cli/src/subcommands/generate/typescript.rs b/crates/cli/src/subcommands/generate/typescript.rs index cc76176dedc..b8eba1d4447 100644 --- a/crates/cli/src/subcommands/generate/typescript.rs +++ b/crates/cli/src/subcommands/generate/typescript.rs @@ -3,11 +3,12 @@ use super::util::fmt_fn; use std::fmt::{self, Write}; use convert_case::{Case, Casing}; +use spacetimedb_lib::sats::db::attr::ColumnIndexAttribute; use spacetimedb_lib::sats::{ AlgebraicType, AlgebraicType::Builtin, AlgebraicTypeRef, ArrayType, BuiltinType, MapType, ProductType, ProductTypeElement, SumType, SumTypeVariant, }; -use spacetimedb_lib::{ColumnIndexAttribute, ReducerDef, TableDef}; +use spacetimedb_lib::{ReducerDef, TableDef}; use super::code_indenter::CodeIndenter; use super::{GenCtx, GenItem, INDENT}; diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 3216e24dc9e..c502b0a9f60 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -101,9 +101,11 @@ default = ["odb_sled"] [dev-dependencies] spacetimedb-lib = { path = "../lib", features = ["proptest"] } +spacetimedb-sats = { path = "../sats", features = ["proptest"] } rusqlite.workspace = true criterion.workspace = true +# Also as dev-dependencies for use in _this_ crate's tests. proptest.workspace = true proptest-derive.workspace = true rand.workspace = true diff --git a/crates/core/src/client/messages.rs b/crates/core/src/client/messages.rs index cb1ae93d8eb..26c9df27986 100644 --- a/crates/core/src/client/messages.rs +++ b/crates/core/src/client/messages.rs @@ -1,7 +1,5 @@ use base64::Engine; use prost::Message as _; -use spacetimedb_client_api_messages::client_api::{OneOffQueryResponse, OneOffTable}; -use spacetimedb_lib::{relation::MemTable, Address}; use crate::host::module_host::{DatabaseUpdate, EventStatus, ModuleEvent}; use crate::identity::Identity; @@ -10,6 +8,9 @@ use crate::json::client_api::{ TransactionUpdateJson, }; use crate::protobuf::client_api::{event, message, Event, FunctionCall, IdentityToken, Message, TransactionUpdate}; +use spacetimedb_client_api_messages::client_api::{OneOffQueryResponse, OneOffTable}; +use spacetimedb_lib::Address; +use spacetimedb_sats::relation::MemTable; use super::{DataMessage, Protocol}; diff --git a/crates/core/src/db/commit_log.rs b/crates/core/src/db/commit_log.rs index 53e8ddeda62..8bb1349ea97 100644 --- a/crates/core/src/db/commit_log.rs +++ b/crates/core/src/db/commit_log.rs @@ -18,11 +18,9 @@ use crate::{ }; use anyhow::Context; -use spacetimedb_lib::{ - hash::{hash_bytes, Hash}, - DataKey, -}; +use spacetimedb_sats::hash::{hash_bytes, Hash}; +use spacetimedb_sats::DataKey; use std::io; use std::sync::Arc; use std::sync::{Mutex, MutexGuard}; @@ -357,11 +355,10 @@ impl From for Iter { #[cfg(test)] mod tests { use super::*; - - use spacetimedb_lib::data_key::InlineData; use tempfile::TempDir; use crate::db::ostorage::memory_object_db::MemoryObjectDB; + use spacetimedb_sats::data_key::InlineData; #[test] fn test_iter_commits() { diff --git a/crates/core/src/db/cursor.rs b/crates/core/src/db/cursor.rs index f5c26ba04b2..c682b577b97 100644 --- a/crates/core/src/db/cursor.rs +++ b/crates/core/src/db/cursor.rs @@ -1,7 +1,7 @@ use std::ops::RangeBounds; use crate::error::DBError; -use spacetimedb_lib::relation::{DbTable, RowCount}; +use spacetimedb_sats::relation::{DbTable, RowCount}; use spacetimedb_sats::{AlgebraicValue, ProductValue}; use super::datastore::locking_tx_datastore::{Iter, IterByColRange}; diff --git a/crates/core/src/db/datastore/locking_tx_datastore/btree_index.rs b/crates/core/src/db/datastore/locking_tx_datastore/btree_index.rs index 11fabc71f1e..f449ca6e7e8 100644 --- a/crates/core/src/db/datastore/locking_tx_datastore/btree_index.rs +++ b/crates/core/src/db/datastore/locking_tx_datastore/btree_index.rs @@ -1,13 +1,10 @@ use super::RowId; -use crate::{ - db::datastore::locking_tx_datastore::Table, - db::datastore::traits::IndexSchema, - error::{DBError, IndexError}, -}; +use crate::db::datastore::locking_tx_datastore::table::Table; +use crate::error::{DBError, IndexError}; use nonempty::NonEmpty; -use spacetimedb_lib::{data_key::ToDataKey, DataKey, IndexType}; use spacetimedb_primitives::{ColId, IndexId, TableId}; -use spacetimedb_sats::{AlgebraicValue, ProductValue}; +use spacetimedb_sats::db::def::{IndexSchema, IndexType}; +use spacetimedb_sats::{data_key::ToDataKey, AlgebraicValue, DataKey, ProductValue}; use std::{ collections::{btree_set, BTreeSet}, ops::{Bound, RangeBounds}, diff --git a/crates/core/src/db/datastore/locking_tx_datastore/mod.rs b/crates/core/src/db/datastore/locking_tx_datastore/mod.rs index 5cc726a3a0c..8aef6b05d0f 100644 --- a/crates/core/src/db/datastore/locking_tx_datastore/mod.rs +++ b/crates/core/src/db/datastore/locking_tx_datastore/mod.rs @@ -13,10 +13,7 @@ use super::{ SEQUENCE_ID_SEQUENCE_ID, ST_COLUMNS_ID, ST_COLUMNS_ROW_TYPE, ST_INDEXES_ID, ST_INDEX_ROW_TYPE, ST_MODULE_ID, ST_SEQUENCES_ID, ST_SEQUENCE_ROW_TYPE, ST_TABLES_ID, ST_TABLE_ROW_TYPE, TABLE_ID_SEQUENCE_ID, WASM_MODULE, }, - traits::{ - self, DataRow, IndexDef, IndexSchema, MutTx, MutTxDatastore, SequenceDef, TableDef, TableSchema, TxData, - TxDatastore, - }, + traits::{self, DataRow, MutTx, MutTxDatastore, TxData, TxDatastore}, }; use crate::db::datastore::system_tables::{ st_constraints_schema, st_module_schema, table_name_is_system, StColumnFields, StConstraintRow, StIndexFields, @@ -27,10 +24,7 @@ use crate::db::db_metrics::DB_METRICS; use crate::{ db::datastore::traits::{TxOp, TxRecord}, db::{ - datastore::{ - system_tables::{st_columns_schema, st_indexes_schema, st_sequences_schema, st_table_schema}, - traits::ColumnSchema, - }, + datastore::system_tables::{st_columns_schema, st_indexes_schema, st_sequences_schema, st_table_schema}, messages::{transaction::Transaction, write::Operation}, ostorage::ObjectDB, }, @@ -40,13 +34,16 @@ use crate::{ use anyhow::anyhow; use nonempty::NonEmpty; use parking_lot::{lock_api::ArcMutexGuard, Mutex, RawMutex}; -use spacetimedb_lib::{ - auth::{StAccess, StTableType}, +use spacetimedb_lib::Address; +use spacetimedb_primitives::{ColId, IndexId, SequenceId, TableId}; +use spacetimedb_sats::db::def::{ColumnSchema, IndexDef, IndexSchema, IndexType, SequenceDef, TableDef, TableSchema}; +use spacetimedb_sats::hash::Hash; +use spacetimedb_sats::{ data_key::ToDataKey, + db::auth::{StAccess, StTableType}, relation::RelValue, - Address, DataKey, Hash, IndexType, + DataKey, }; -use spacetimedb_primitives::{ColId, IndexId, SequenceId, TableId}; use spacetimedb_sats::{AlgebraicType, AlgebraicValue, ProductType, ProductValue}; use std::{ borrow::Cow, @@ -486,7 +483,7 @@ impl Inner { .inc(); //TODO: This is a bug fixed in PR#267 - let index_id = constraint.constraint_id; + let index_id = IndexId(constraint.constraint_id.0); //Check if add an index: match constraint.kind { @@ -606,7 +603,7 @@ impl Inner { let rows = self.iter_by_col_eq(&ctx, &table_id, col_id, value)?; let ids_to_delete = rows.map(|row| RowId(*row.id())).collect::>(); if ids_to_delete.is_empty() { - return Err(TableError::IdNotFound(table_id).into()); + return Err(TableError::IdNotFound(table_id, table_id.0).into()); } self.delete(&table_id, ids_to_delete); @@ -866,7 +863,9 @@ impl Inner { .collect::>(); assert!(rows.len() <= 1, "Expected at most one row in st_tables for table_id"); - let row = rows.first().ok_or_else(|| TableError::IdNotFound(table_id))?; + let row = rows + .first() + .ok_or_else(|| TableError::IdNotFound(table_id, table_id.0))?; let el = StTableRow::try_from(row.view())?; let table_name = el.table_name.to_owned(); let table_id = el.table_id; @@ -951,7 +950,9 @@ impl Inner { let ctx = ExecutionContext::internal(self.database_address); let mut row_iter = self.iter_by_col_eq(&ctx, &ST_TABLES_ID, StTableFields::TableId, table_id.into())?; - let row = row_iter.next().ok_or_else(|| TableError::IdNotFound(table_id))?; + let row = row_iter + .next() + .ok_or_else(|| TableError::IdNotFound(ST_TABLES_ID, table_id.0))?; let row_id = RowId(*row.id); let mut el = StTableRow::try_from(row.view())?; el.table_name = new_name; @@ -1002,6 +1003,10 @@ impl Inner { index.table_id, index.cols ); + // Create the index in memory + if !self.table_exists(&index.table_id) { + return Err(TableError::IdNotFound(ST_INDEXES_ID, index.table_id.0).into()); + } // Insert the index row into st_indexes // NOTE: Because st_indexes has a unique index on index_name, this will // fail if the index already exists. @@ -1015,10 +1020,6 @@ impl Inner { }; let index_id = StIndexRow::try_from(&self.insert(ST_INDEXES_ID, row.into())?)?.index_id; - // Create the index in memory - if !self.table_exists(&index.table_id) { - return Err(TableError::IdNotFound(index.table_id).into()); - } self.create_index_internal(index_id, index)?; log::trace!("INDEX CREATED: id = {}", index_id); @@ -1254,7 +1255,7 @@ impl Inner { table } else { let Some(committed_table) = self.committed_state.tables.get(&table_id) else { - return Err(TableError::IdNotFound(table_id).into()); + return Err(TableError::IdNotFoundState(table_id).into()); }; let table = Table { row_type: committed_table.row_type.clone(), @@ -1353,7 +1354,7 @@ impl Inner { fn get<'a>(&'a self, table_id: &TableId, row_id: &'a RowId) -> super::Result>> { if !self.table_exists(table_id) { - return Err(TableError::IdNotFound(*table_id).into()); + return Err(TableError::IdNotFound(ST_TABLES_ID, table_id.0).into()); } match self.tx_state.as_ref().unwrap().get_row_op(table_id, row_id) { RowState::Committed(_) => unreachable!("a row cannot be committed in a tx state"), @@ -1446,7 +1447,7 @@ impl Inner { if self.table_exists(table_id) { return Ok(Iter::new(ctx, *table_id, self)); } - Err(TableError::IdNotFound(*table_id).into()) + Err(TableError::IdNotFound(ST_TABLES_ID, table_id.0).into()) } /// Returns an iterator, @@ -2364,28 +2365,16 @@ impl traits::MutProgrammable for Locking { #[cfg(test)] mod tests { - use super::{ColId, Inner, Locking, MutTxId, StTableRow}; - use crate::db::datastore::system_tables::{ - StColumnRow, StConstraintRow, StIndexRow, StSequenceRow, ST_COLUMNS_ID, ST_CONSTRAINTS_ID, ST_INDEXES_ID, - ST_SEQUENCES_ID, ST_TABLES_ID, - }; + use super::*; + + use crate::db::datastore::system_tables::{StConstraintRow, ST_CONSTRAINTS_ID}; use crate::db::datastore::Result; - use crate::execution_context::ExecutionContext; - use crate::{ - db::datastore::traits::{ - ColumnDef, ColumnSchema, IndexDef, IndexSchema, MutTx, MutTxDatastore, TableDef, TableSchema, - }, - error::{DBError, IndexError}, - }; + use crate::error::IndexError; use itertools::Itertools; use nonempty::NonEmpty; - use spacetimedb_lib::Address; - use spacetimedb_lib::{ - auth::{StAccess, StTableType}, - error::ResultTest, - ColumnIndexAttribute, IndexType, - }; - use spacetimedb_primitives::TableId; + use spacetimedb_lib::error::ResultTest; + use spacetimedb_sats::db::auth::{StAccess, StTableType}; + use spacetimedb_sats::db::def::{ColumnDef, Constraints}; use spacetimedb_sats::{product, AlgebraicType, AlgebraicValue, ProductValue}; /// Utility to query the system tables and return their concrete table row @@ -2747,7 +2736,7 @@ mod tests { assert_eq!(query.scan_st_constraints()?, vec![StConstraintRow { constraint_id: 5.into(), constraint_name: "ct_columns_table_id".to_string(), - kind: ColumnIndexAttribute::INDEXED, + kind: Constraints::indexed(), table_id: 1.into(), columns: col(0), }]); diff --git a/crates/core/src/db/datastore/locking_tx_datastore/sequence.rs b/crates/core/src/db/datastore/locking_tx_datastore/sequence.rs index f048e261509..6bd3ee176b0 100644 --- a/crates/core/src/db/datastore/locking_tx_datastore/sequence.rs +++ b/crates/core/src/db/datastore/locking_tx_datastore/sequence.rs @@ -1,4 +1,4 @@ -use crate::db::datastore::traits::SequenceSchema; +use spacetimedb_sats::db::def::SequenceSchema; pub struct Sequence { schema: SequenceSchema, diff --git a/crates/core/src/db/datastore/locking_tx_datastore/table.rs b/crates/core/src/db/datastore/locking_tx_datastore/table.rs index 11a5fcc3073..a87d07f4860 100644 --- a/crates/core/src/db/datastore/locking_tx_datastore/table.rs +++ b/crates/core/src/db/datastore/locking_tx_datastore/table.rs @@ -2,10 +2,10 @@ use super::{ btree_index::{BTreeIndex, BTreeIndexRangeIter}, RowId, }; -use crate::db::datastore::traits::TableSchema; use indexmap::IndexMap; use nonempty::NonEmpty; use spacetimedb_primitives::ColId; +use spacetimedb_sats::db::def::TableSchema; use spacetimedb_sats::{AlgebraicValue, ProductType, ProductValue}; use std::{collections::HashMap, ops::RangeBounds}; diff --git a/crates/core/src/db/datastore/system_tables.rs b/crates/core/src/db/datastore/system_tables.rs index 00a4ef997bd..1cad80e76d0 100644 --- a/crates/core/src/db/datastore/system_tables.rs +++ b/crates/core/src/db/datastore/system_tables.rs @@ -1,12 +1,13 @@ -use super::traits::{ColumnSchema, IndexSchema, SequenceSchema, TableSchema}; -use crate::db::datastore::traits::ConstraintSchema; use crate::error::{DBError, TableError}; use core::fmt; use nonempty::NonEmpty; use once_cell::sync::Lazy; -use spacetimedb_lib::auth::{StAccess, StTableType}; -use spacetimedb_lib::{ColumnIndexAttribute, Hash, IndexType}; -use spacetimedb_primitives::{ColId, IndexId, SequenceId, TableId}; +use spacetimedb_primitives::{ColId, ConstraintId, IndexId, SequenceId, TableId}; +use spacetimedb_sats::db::auth::{StAccess, StTableType}; +use spacetimedb_sats::db::def::{ + ColumnSchema, ConstraintSchema, Constraints, IndexSchema, IndexType, SequenceSchema, TableSchema, +}; +use spacetimedb_sats::hash::Hash; use spacetimedb_sats::{ impl_deserialize, impl_serialize, product, product_value::InvalidFieldError, AlgebraicType, AlgebraicValue, ArrayValue, ProductType, ProductValue, @@ -70,7 +71,7 @@ impl SystemTables { pub(crate) fn total_constraints_indexes() -> usize { Self::tables() .iter() - .flat_map(|x| x.constraints.iter().filter(|x| x.kind != ColumnIndexAttribute::UNSET)) + .flat_map(|x| x.constraints.iter().filter(|x| x.kind != Constraints::unset())) .count() } @@ -95,14 +96,22 @@ macro_rules! st_fields_enum { } impl $ty_name { + #[inline] pub fn col_id(self) -> ColId { ColId(self as u32) } + #[inline] + pub fn col_idx(self) -> usize { + self.col_id().idx() + } + + #[inline] pub fn col_name(self) -> String { self.name().into() } + #[inline] pub fn name(self) -> &'static str { match self { $(Self::$var => $name,)* @@ -288,10 +297,10 @@ pub fn st_columns_schema() -> TableSchema { constraints: vec![ConstraintSchema { constraint_id: ST_CONSTRAINT_ID_INDEX_HACK.0.into(), constraint_name: "ct_columns_table_id".to_string(), - kind: ColumnIndexAttribute::INDEXED, + kind: Constraints::indexed(), table_id: ST_COLUMNS_ID, //TODO: Change to multi-columns when PR for it land: StColumnFields::ColId as u32 - columns: StColumnFields::TableId.into(), + columns: NonEmpty::new(StColumnFields::TableId.col_id()), }], table_type: StTableType::System, table_access: StAccess::Public, @@ -593,10 +602,10 @@ impl<'a> TryFrom<&'a ProductValue> for StTableRow<&'a str> { type Error = DBError; // TODO(cloutiertyler): Noa, can we just decorate `StTableRow` with Deserialize or something instead? fn try_from(row: &'a ProductValue) -> Result, DBError> { - let table_id = row.field_as_u32(StTableFields::TableId as usize, None)?.into(); - let table_name = row.field_as_str(StTableFields::TableName as usize, None)?; + let table_id = row.field_as_u32(StTableFields::TableId.col_idx(), None)?.into(); + let table_name = row.field_as_str(StTableFields::TableName.col_idx(), None)?; let table_type = row - .field_as_str(StTableFields::TableType as usize, None)? + .field_as_str(StTableFields::TableType.col_idx(), None)? .try_into() .map_err(|x: &str| TableError::DecodeField { table: ST_TABLES_NAME.into(), @@ -606,7 +615,7 @@ impl<'a> TryFrom<&'a ProductValue> for StTableRow<&'a str> { })?; let table_access = row - .field_as_str(StTableFields::TablesAccess as usize, None)? + .field_as_str(StTableFields::TablesAccess.col_idx(), None)? .try_into() .map_err(|x: &str| TableError::DecodeField { table: ST_TABLES_NAME.into(), @@ -670,13 +679,13 @@ impl StColumnRow<&str> { impl<'a> TryFrom<&'a ProductValue> for StColumnRow<&'a str> { type Error = DBError; fn try_from(row: &'a ProductValue) -> Result, DBError> { - let table_id: TableId = row.field_as_u32(StColumnFields::TableId as usize, None)?.into(); - let col_id = row.field_as_u32(StColumnFields::ColId as usize, None)?.into(); - let col_name = row.field_as_str(StColumnFields::ColName as usize, None)?; - let bytes = row.field_as_bytes(StColumnFields::ColType as usize, None)?; + let table_id: TableId = row.field_as_u32(StColumnFields::TableId.col_idx(), None)?.into(); + let col_id = row.field_as_u32(StColumnFields::ColId.col_idx(), None)?.into(); + let col_name = row.field_as_str(StColumnFields::ColName.col_idx(), None)?; + let bytes = row.field_as_bytes(StColumnFields::ColType.col_idx(), None)?; let col_type = AlgebraicType::decode(&mut &bytes[..]).map_err(|e| TableError::InvalidSchema(table_id, e.into()))?; - let is_autoinc = row.field_as_bool(StColumnFields::IsAutoInc as usize, None)?; + let is_autoinc = row.field_as_bool(StColumnFields::IsAutoInc.col_idx(), None)?; Ok(StColumnRow { table_id, @@ -726,7 +735,7 @@ impl StIndexRow<&str> { } fn to_cols(row: &ProductValue, col_pos: ColId, col_name: &'static str) -> Result, DBError> { - let index = col_pos.0 as usize; + let index = col_pos.idx(); let cols = row.field_as_array(index, Some(col_name))?; if let ArrayValue::U32(x) = &cols { let x: Vec<_> = x.iter().map(|x| ColId::from(*x)).collect(); @@ -734,7 +743,7 @@ fn to_cols(row: &ProductValue, col_pos: ColId, col_name: &'static str) -> Result } else { Err(InvalidFieldError { name: Some(col_name), - col_pos, + index, } .into()) } @@ -743,16 +752,16 @@ fn to_cols(row: &ProductValue, col_pos: ColId, col_name: &'static str) -> Result impl<'a> TryFrom<&'a ProductValue> for StIndexRow<&'a str> { type Error = DBError; fn try_from(row: &'a ProductValue) -> Result, DBError> { - let index_id = row.field_as_u32(StIndexFields::IndexId as usize, None)?.into(); - let table_id = row.field_as_u32(StIndexFields::TableId as usize, None)?.into(); - let index_name = row.field_as_str(StIndexFields::IndexName as usize, None)?; - let index_type = row.field_as_u8(StIndexFields::IndexType as usize, None)?; + let index_id = row.field_as_u32(StIndexFields::IndexId.col_idx(), None)?.into(); + let table_id = row.field_as_u32(StIndexFields::TableId.col_idx(), None)?.into(); + let index_name = row.field_as_str(StIndexFields::IndexName.col_idx(), None)?; + let index_type = row.field_as_u8(StIndexFields::IndexType.col_idx(), None)?; let index_type = IndexType::try_from(index_type).map_err(|_| InvalidFieldError { - col_pos: StIndexFields::IndexType.col_id(), + index: StIndexFields::IndexType.col_idx(), name: Some(StIndexFields::IndexType.name()), })?; let columns = to_cols(row, StIndexFields::Columns.col_id(), StIndexFields::Columns.name())?; - let is_unique = row.field_as_bool(StIndexFields::IsUnique as usize, None)?; + let is_unique = row.field_as_bool(StIndexFields::IsUnique.col_idx(), None)?; Ok(StIndexRow { index_id, table_id, @@ -810,15 +819,15 @@ impl> StSequenceRow { impl<'a> TryFrom<&'a ProductValue> for StSequenceRow<&'a str> { type Error = DBError; fn try_from(row: &'a ProductValue) -> Result, DBError> { - let sequence_id = row.field_as_u32(StSequenceFields::SequenceId as usize, None)?.into(); - let sequence_name = row.field_as_str(StSequenceFields::SequenceName as usize, None)?; - let table_id = row.field_as_u32(StSequenceFields::TableId as usize, None)?.into(); - let col_id = row.field_as_u32(StSequenceFields::ColId as usize, None)?.into(); - let increment = row.field_as_i128(StSequenceFields::Increment as usize, None)?; - let start = row.field_as_i128(StSequenceFields::Start as usize, None)?; - let min_value = row.field_as_i128(StSequenceFields::MinValue as usize, None)?; - let max_value = row.field_as_i128(StSequenceFields::MaxValue as usize, None)?; - let allocated = row.field_as_i128(StSequenceFields::Allocated as usize, None)?; + let sequence_id = row.field_as_u32(StSequenceFields::SequenceId.col_idx(), None)?.into(); + let sequence_name = row.field_as_str(StSequenceFields::SequenceName.col_idx(), None)?; + let table_id = row.field_as_u32(StSequenceFields::TableId.col_idx(), None)?.into(); + let col_id = row.field_as_u32(StSequenceFields::ColId.col_idx(), None)?.into(); + let increment = row.field_as_i128(StSequenceFields::Increment.col_idx(), None)?; + let start = row.field_as_i128(StSequenceFields::Start.col_idx(), None)?; + let min_value = row.field_as_i128(StSequenceFields::MinValue.col_idx(), None)?; + let max_value = row.field_as_i128(StSequenceFields::MaxValue.col_idx(), None)?; + let allocated = row.field_as_i128(StSequenceFields::Allocated.col_idx(), None)?; Ok(StSequenceRow { sequence_id, sequence_name, @@ -867,9 +876,9 @@ impl From> for SequenceSchema { #[derive(Debug, PartialEq, Eq)] pub struct StConstraintRow> { - pub(crate) constraint_id: IndexId, + pub(crate) constraint_id: ConstraintId, pub(crate) constraint_name: Name, - pub(crate) kind: ColumnIndexAttribute, + pub(crate) kind: Constraints, pub(crate) table_id: TableId, pub(crate) columns: NonEmpty, } @@ -890,12 +899,12 @@ impl<'a> TryFrom<&'a ProductValue> for StConstraintRow<&'a str> { type Error = DBError; fn try_from(row: &'a ProductValue) -> Result, DBError> { let constraint_id = row - .field_as_u32(StConstraintFields::ConstraintId as usize, None)? + .field_as_u32(StConstraintFields::ConstraintId.col_idx(), None)? .into(); - let constraint_name = row.field_as_str(StConstraintFields::ConstraintName as usize, None)?; - let kind = row.field_as_u8(StConstraintFields::Kind as usize, None)?; - let kind = ColumnIndexAttribute::try_from(kind).expect("Fail to decode ColumnIndexAttribute"); - let table_id = row.field_as_u32(StConstraintFields::TableId as usize, None)?.into(); + let constraint_name = row.field_as_str(StConstraintFields::ConstraintName.col_idx(), None)?; + let kind = row.field_as_u8(StConstraintFields::Kind.col_idx(), None)?; + let kind = Constraints::try_from(kind).expect("Fail to decode Constraints"); + let table_id = row.field_as_u32(StConstraintFields::TableId.col_idx(), None)?.into(); let columns = to_cols( row, StConstraintFields::Columns.col_id(), @@ -974,15 +983,15 @@ impl TryFrom<&ProductValue> for StModuleRow { fn try_from(row: &ProductValue) -> Result { let program_hash = row .field_as_bytes( - StModuleFields::ProgramHash as usize, + StModuleFields::ProgramHash.col_idx(), Some(StModuleFields::ProgramHash.name()), ) .map(Hash::from_slice)?; let kind = row - .field_as_u8(StModuleFields::Kind as usize, Some(StModuleFields::Kind.name())) + .field_as_u8(StModuleFields::Kind.col_idx(), Some(StModuleFields::Kind.name())) .map(ModuleKind)?; let epoch = row - .field_as_u128(StModuleFields::Epoch as usize, Some(StModuleFields::Epoch.name())) + .field_as_u128(StModuleFields::Epoch.col_idx(), Some(StModuleFields::Epoch.name())) .map(Epoch)?; Ok(Self { diff --git a/crates/core/src/db/datastore/traits.rs b/crates/core/src/db/datastore/traits.rs index 6982042e0c6..2c4605436cb 100644 --- a/crates/core/src/db/datastore/traits.rs +++ b/crates/core/src/db/datastore/traits.rs @@ -1,457 +1,15 @@ -use crate::db::relational_db::ST_TABLES_ID; -use crate::execution_context::ExecutionContext; -use anyhow::Context; use nonempty::NonEmpty; -use spacetimedb_lib::auth::{StAccess, StTableType}; -use spacetimedb_lib::relation::{Column, DbTable, FieldName, FieldOnly, Header, TableField}; -use spacetimedb_lib::{ColumnIndexAttribute, DataKey, Hash, IndexType}; -use spacetimedb_primitives::{ColId, IndexId, SequenceId, TableId}; -use spacetimedb_sats::product_value::InvalidFieldError; -use spacetimedb_sats::{AlgebraicType, AlgebraicValue, ProductType, ProductTypeElement, ProductValue, WithTypespace}; -use spacetimedb_vm::expr::SourceExpr; -use std::iter; -use std::{borrow::Cow, ops::RangeBounds, sync::Arc}; +use std::borrow::Cow; +use std::{ops::RangeBounds, sync::Arc}; use super::{system_tables::StTableRow, Result}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SequenceSchema { - pub(crate) sequence_id: SequenceId, - pub(crate) sequence_name: String, - pub(crate) table_id: TableId, - pub(crate) col_id: ColId, - pub(crate) increment: i128, - pub(crate) start: i128, - pub(crate) min_value: i128, - pub(crate) max_value: i128, - pub(crate) allocated: i128, -} - -/// This type is just the [SequenceSchema] without the autoinc fields -/// It's also adjusted to be convenient for specifying a new sequence -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SequenceDef { - pub(crate) sequence_name: String, - pub(crate) table_id: TableId, - pub(crate) col_id: ColId, - pub(crate) increment: i128, - pub(crate) start: Option, - pub(crate) min_value: Option, - pub(crate) max_value: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct IndexSchema { - pub(crate) index_id: IndexId, - pub(crate) table_id: TableId, - pub(crate) index_name: String, - pub(crate) is_unique: bool, - pub(crate) cols: NonEmpty, - pub(crate) index_type: IndexType, -} - -/// This type is just the [IndexSchema] without the autoinc fields -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct IndexDef { - pub(crate) table_id: TableId, - pub(crate) cols: NonEmpty, - pub(crate) name: String, - pub(crate) is_unique: bool, - pub(crate) index_type: IndexType, -} - -impl IndexDef { - pub fn new(name: String, table_id: TableId, col_id: ColId, is_unique: bool) -> Self { - Self { - cols: NonEmpty::new(col_id), - name, - is_unique, - table_id, - index_type: IndexType::BTree, - } - } -} - -impl From for IndexDef { - fn from(value: IndexSchema) -> Self { - Self { - table_id: value.table_id, - cols: value.cols, - name: value.index_name, - is_unique: value.is_unique, - index_type: value.index_type, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ColumnSchema { - pub table_id: TableId, - pub col_id: ColId, - pub col_name: String, - pub col_type: AlgebraicType, - pub is_autoinc: bool, -} - -impl From<&ColumnSchema> for spacetimedb_lib::table::ColumnDef { - fn from(value: &ColumnSchema) -> Self { - Self { - column: ProductTypeElement::from(value), - // TODO(cloutiertyler): !!! This is not correct !!! We do not have the information regarding constraints here. - // We should remove this field from the ColumnDef struct. - attr: if value.is_autoinc { - spacetimedb_lib::ColumnIndexAttribute::AUTO_INC - } else { - spacetimedb_lib::ColumnIndexAttribute::UNSET - }, - // if value.is_autoinc && value.is_unique { - // spacetimedb_lib::ColumnIndexAttribute::Identity - // } else if value.is_autoinc { - // spacetimedb_lib::ColumnIndexAttribute::AutoInc - // } else if value.is_unique { - // spacetimedb_lib::ColumnIndexAttribute::Unique - // } else { - // spacetimedb_lib::ColumnIndexAttribute::UnSet - // }, - pos: value.col_id.idx(), - } - } -} - -impl From<&ColumnSchema> for ProductTypeElement { - fn from(value: &ColumnSchema) -> Self { - Self { - name: Some(value.col_name.clone()), - algebraic_type: value.col_type.clone(), - } - } -} - -/// This type is just the [ColumnSchema] without the autoinc fields -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ColumnDef { - pub(crate) col_name: String, - pub(crate) col_type: AlgebraicType, - pub(crate) is_autoinc: bool, -} - -impl From for ColumnDef { - fn from(value: ColumnSchema) -> Self { - Self { - col_name: value.col_name, - col_type: value.col_type, - is_autoinc: value.is_autoinc, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ConstraintSchema { - pub(crate) constraint_id: IndexId, - pub(crate) constraint_name: String, - pub(crate) kind: ColumnIndexAttribute, - pub(crate) table_id: TableId, - pub(crate) columns: NonEmpty, -} - -/// This type is just the [ConstraintSchema] without the autoinc fields -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ConstraintDef { - pub(crate) constraint_name: String, - pub(crate) kind: ColumnIndexAttribute, - pub(crate) table_id: TableId, - pub(crate) columns: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct TableSchema { - pub table_id: TableId, - pub table_name: String, - pub columns: Vec, - pub indexes: Vec, - pub constraints: Vec, - pub table_type: StTableType, - pub table_access: StAccess, -} - -impl TableSchema { - /// Check if the `name` of the [FieldName] exist on this [TableSchema] - /// - /// Warning: It ignores the `table_name` - pub fn get_column_by_field(&self, field: &FieldName) -> Option<&ColumnSchema> { - match field.field() { - FieldOnly::Name(x) => self.get_column_by_name(x), - FieldOnly::Pos(x) => self.get_column(x), - } - } - - /// Check if there is an index for this [FieldName] - /// - /// Warning: It ignores the `table_name` - pub fn get_index_by_field(&self, field: &FieldName) -> Option<&IndexSchema> { - let ColumnSchema { col_id, .. } = self.get_column_by_field(field)?; - self.indexes.iter().find( - |IndexSchema { - cols: NonEmpty { head: index_col, tail }, - .. - }| tail.is_empty() && index_col == col_id, - ) - } - - pub fn get_column(&self, pos: usize) -> Option<&ColumnSchema> { - self.columns.get(pos) - } - - /// Check if the `col_name` exist on this [TableSchema] - /// - /// Warning: It ignores the `table_name` - pub fn get_column_by_name(&self, col_name: &str) -> Option<&ColumnSchema> { - self.columns.iter().find(|x| x.col_name == col_name) - } - - /// Turn a [TableField] that could be an unqualified field `id` into `table.id` - pub fn normalize_field(&self, or_use: &TableField) -> FieldName { - FieldName::named(or_use.table.unwrap_or(&self.table_name), or_use.field) - } - - /// Project the fields from the supplied `columns`. - pub fn project(&self, columns: impl Iterator) -> Result> { - columns - .map(|pos| { - self.get_column(pos).ok_or( - InvalidFieldError { - col_pos: pos.into(), - name: None, - } - .into(), - ) - }) - .collect() - } - - /// Utility for project the fields from the supplied `columns` that is a [NonEmpty], - /// used for when the list of field columns have at least one value. - pub fn project_not_empty(&self, columns: &NonEmpty) -> Result> { - self.project(columns.iter().map(|&x| x.idx())) - } -} - -impl From<&TableSchema> for ProductType { - fn from(value: &TableSchema) -> Self { - ProductType::new( - value - .columns - .iter() - .map(|c| ProductTypeElement { - name: Some(c.col_name.clone()), - algebraic_type: c.col_type.clone(), - }) - .collect(), - ) - } -} - -impl From<&TableSchema> for SourceExpr { - fn from(value: &TableSchema) -> Self { - SourceExpr::DbTable(DbTable::new( - value.into(), - value.table_id, - value.table_type, - value.table_access, - )) - } -} - -impl From<&TableSchema> for DbTable { - fn from(value: &TableSchema) -> Self { - DbTable::new(value.into(), value.table_id, value.table_type, value.table_access) - } -} - -impl From<&TableSchema> for Header { - fn from(value: &TableSchema) -> Self { - Header::new( - value.table_name.clone(), - value - .columns - .iter() - .map(|x| { - let field = FieldName::named(&value.table_name, &x.col_name); - let is_indexed = value.get_index_by_field(&field).is_some(); - - Column::new(field, x.col_type.clone(), x.col_id, is_indexed) - }) - .collect(), - ) - } -} - -impl TableDef { - pub fn get_row_type(&self) -> ProductType { - ProductType::new( - self.columns - .iter() - .map(|c| ProductTypeElement { - name: None, - algebraic_type: c.col_type.clone(), - }) - .collect(), - ) - } -} - -/// The magic table id zero, for use in [`IndexDef`]s. -/// -/// The actual table id is usually not yet known when constructing an -/// [`IndexDef`]. [`AUTO_TABLE_ID`] can be used instead, which the storage -/// engine will replace with the actual table id upon creation of the table -/// respectively index. -pub const AUTO_TABLE_ID: TableId = TableId(0); - -/// This type is just the [TableSchema] without the autoinc fields -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct TableDef { - pub(crate) table_name: String, - pub(crate) columns: Vec, - pub(crate) indexes: Vec, - pub(crate) table_type: StTableType, - pub(crate) table_access: StAccess, -} - -impl TableDef { - pub fn from_lib_tabledef(table: WithTypespace<'_, spacetimedb_lib::TableDef>) -> anyhow::Result { - let schema = table - .map(|t| &t.data) - .resolve_refs() - .context("recursive types not yet supported")?; - let schema = schema.into_product().ok().context("table not a product type?")?; - let table = table.ty(); - anyhow::ensure!( - table.column_attrs.len() == schema.elements.len(), - "mismatched number of columns" - ); - - // Build single-column index definitions, determining `is_unique` from - // their respective column attributes. - let mut columns = Vec::with_capacity(schema.elements.len()); - let mut indexes = Vec::new(); - for (col_id, (ty, col_attr)) in std::iter::zip(&schema.elements, &table.column_attrs).enumerate() { - let col = ColumnDef { - col_name: ty.name.clone().context("column without name")?, - col_type: ty.algebraic_type.clone(), - is_autoinc: col_attr.has_autoinc(), - }; - - let index_for_column = table.indexes.iter().find(|index| { - // Ignore multi-column indexes - matches!(*index.cols, [index_col_id] if index_col_id as usize == col_id) - }); - - // If there's an index defined for this column already, use it, - // making sure that it is unique if the column has a unique constraint - let index_info = if let Some(index) = index_for_column { - Some((index.name.clone(), index.index_type)) - } else if col_attr.has_unique() { - // If you didn't find an index, but the column is unique then create a unique btree index - // anyway. - Some(( - format!("{}_{}_unique", table.name, col.col_name), - spacetimedb_lib::IndexType::BTree, - )) - } else { - None - }; - if let Some((name, ty)) = index_info { - match ty { - spacetimedb_lib::IndexType::BTree => {} - // TODO - spacetimedb_lib::IndexType::Hash => anyhow::bail!("hash indexes not yet supported"), - } - indexes.push(IndexDef::new( - name, - AUTO_TABLE_ID, - ColId(col_id as u32), - col_attr.has_unique(), - )) - } - columns.push(col); - } - - // Multi-column indexes cannot be unique (yet), so just add them. - let multi_col_indexes = table.indexes.iter().filter_map(|index| { - if let [a, b, rest @ ..] = &*index.cols { - Some(IndexDef { - table_id: AUTO_TABLE_ID, - cols: NonEmpty { - head: ColId::from(*a), - tail: iter::once(ColId::from(*b)) - .chain(rest.iter().copied().map(Into::into)) - .collect(), - }, - name: index.name.clone(), - is_unique: false, - index_type: IndexType::BTree, - }) - } else { - None - } - }); - indexes.extend(multi_col_indexes); - - Ok(TableDef { - table_name: table.name.clone(), - columns, - indexes, - table_type: table.table_type, - table_access: table.table_access, - }) - } -} - -impl From for TableDef { - fn from(value: ProductType) -> Self { - Self { - table_name: "".to_string(), - columns: value - .elements - .iter() - .enumerate() - .map(|(i, e)| ColumnDef { - col_name: e.name.to_owned().unwrap_or_else(|| i.to_string()), - col_type: e.algebraic_type.clone(), - is_autoinc: false, - }) - .collect(), - indexes: vec![], - table_type: StTableType::User, - table_access: StAccess::Public, - } - } -} - -impl From<&TableSchema> for TableDef { - fn from(value: &TableSchema) -> Self { - Self { - table_name: value.table_name.clone(), - columns: value.columns.iter().cloned().map(Into::into).collect(), - indexes: value.indexes.iter().cloned().map(Into::into).collect(), - table_type: value.table_type, - table_access: value.table_access, - } - } -} - -impl From for TableDef { - fn from(value: TableSchema) -> Self { - Self { - table_name: value.table_name, - columns: value.columns.into_iter().map(Into::into).collect(), - indexes: value.indexes.into_iter().map(Into::into).collect(), - table_type: value.table_type, - table_access: value.table_access, - } - } -} +use crate::db::datastore::system_tables::ST_TABLES_ID; +use crate::execution_context::ExecutionContext; +use spacetimedb_primitives::*; +use spacetimedb_sats::db::def::*; +use spacetimedb_sats::hash::Hash; +use spacetimedb_sats::DataKey; +use spacetimedb_sats::{AlgebraicValue, ProductType, ProductValue}; /// Operations in a transaction are either Inserts or Deletes. /// Inserts report the byte objects they inserted, to be persisted @@ -695,14 +253,13 @@ pub trait MutProgrammable: MutTxDatastore { #[cfg(test)] mod tests { use nonempty::NonEmpty; - use spacetimedb_lib::{ - auth::{StAccess, StTableType}, - ColumnIndexAttribute, IndexType, - }; use spacetimedb_primitives::ColId; + use spacetimedb_sats::db::attr::ColumnIndexAttribute; + use spacetimedb_sats::db::auth::{StAccess, StTableType}; + use spacetimedb_sats::db::def::{IndexType, AUTO_TABLE_ID}; use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, ProductType, ProductTypeElement, Typespace}; - use super::{ColumnDef, IndexDef, TableDef, AUTO_TABLE_ID}; + use super::{ColumnDef, IndexDef, TableDef}; #[test] fn test_tabledef_from_lib_tabledef() -> anyhow::Result<()> { @@ -713,12 +270,12 @@ mod tests { indexes: vec![ spacetimedb_lib::IndexDef { name: "id_and_name".into(), - index_type: spacetimedb_lib::IndexType::BTree, + index_type: IndexType::BTree, cols: [0, 1].into(), }, spacetimedb_lib::IndexDef { name: "just_name".into(), - index_type: spacetimedb_lib::IndexType::BTree, + index_type: IndexType::BTree, cols: [1].into(), }, ], @@ -737,7 +294,7 @@ mod tests { ]); let mut datastore_schema = - TableDef::from_lib_tabledef(Typespace::new(vec![row_type.into()]).with_type(&lib_table_def))?; + spacetimedb_lib::TableDef::into_table_def(Typespace::new(vec![row_type.into()]).with_type(&lib_table_def))?; let mut expected_schema = TableDef { table_name: "Person".into(), columns: vec![ diff --git a/crates/core/src/db/messages/write.rs b/crates/core/src/db/messages/write.rs index 01447e45cb1..9890a22d045 100644 --- a/crates/core/src/db/messages/write.rs +++ b/crates/core/src/db/messages/write.rs @@ -1,8 +1,8 @@ +use anyhow::Context as _; use std::fmt; -use anyhow::Context as _; -pub use spacetimedb_lib::DataKey; use spacetimedb_sats::buffer::{BufReader, BufWriter, DecodeError}; +pub use spacetimedb_sats::DataKey; #[cfg(test)] use proptest_derive::Arbitrary; diff --git a/crates/core/src/db/ostorage/memory_object_db.rs b/crates/core/src/db/ostorage/memory_object_db.rs index 162aa81575f..44d229f9ba0 100644 --- a/crates/core/src/db/ostorage/memory_object_db.rs +++ b/crates/core/src/db/ostorage/memory_object_db.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use bytes::Bytes; -use spacetimedb_lib::{hash::hash_bytes, Hash}; +use spacetimedb_sats::hash::{hash_bytes, Hash}; use crate::db::ostorage::ObjectDB; diff --git a/crates/core/src/db/relational_db.rs b/crates/core/src/db/relational_db.rs index dc686694dbc..74b1127493e 100644 --- a/crates/core/src/db/relational_db.rs +++ b/crates/core/src/db/relational_db.rs @@ -1,8 +1,6 @@ use super::commit_log::{CommitLog, CommitLogView}; use super::datastore::locking_tx_datastore::{DataRef, Iter, IterByColEq, IterByColRange, Locking, MutTxId, RowId}; -use super::datastore::traits::{ - DataRow, IndexDef, MutProgrammable, MutTx, MutTxDatastore, Programmable, SequenceDef, TableDef, TableSchema, TxData, -}; +use super::datastore::traits::{DataRow, MutProgrammable, MutTx, MutTxDatastore, Programmable, TxData}; use super::message_log::MessageLog; use super::ostorage::memory_object_db::MemoryObjectDB; use super::relational_operators::Relation; @@ -14,12 +12,13 @@ use crate::db::ostorage::hashmap_object_db::HashMapObjectDB; use crate::db::ostorage::ObjectDB; use crate::error::{DBError, DatabaseError, IndexError, TableError}; use crate::execution_context::ExecutionContext; -use crate::hash::Hash; use fs2::FileExt; use nonempty::NonEmpty; -use spacetimedb_lib::ColumnIndexAttribute; -use spacetimedb_lib::{data_key::ToDataKey, PrimaryKey}; -use spacetimedb_primitives::{ColId, IndexId, SequenceId, TableId}; +use spacetimedb_lib::PrimaryKey; +use spacetimedb_primitives::{ColId, ColumnIndexAttribute, IndexId, SequenceId, TableId}; +use spacetimedb_sats::data_key::ToDataKey; +use spacetimedb_sats::db::def::{IndexDef, SequenceDef, TableDef, TableSchema}; +use spacetimedb_sats::hash::Hash; use spacetimedb_sats::{AlgebraicType, AlgebraicValue, ProductType, ProductValue}; use std::borrow::Cow; use std::fs::{create_dir_all, File}; @@ -670,33 +669,17 @@ pub(crate) mod tests_utils { mod tests { #![allow(clippy::disallowed_macros)] - use nonempty::NonEmpty; - use spacetimedb_lib::IndexType; - use spacetimedb_primitives::ColId; + use super::*; + + use spacetimedb_lib::error::ResultTest; + use spacetimedb_sats::db::auth::{StAccess, StTableType}; + use spacetimedb_sats::db::def::{ColumnDef, IndexDef, IndexType, TableDef}; use std::sync::{Arc, Mutex}; - use crate::address::Address; - use crate::db::datastore::locking_tx_datastore::IterByColEq; - use crate::db::datastore::system_tables::StIndexRow; - use crate::db::datastore::system_tables::StSequenceRow; - use crate::db::datastore::system_tables::StTableRow; - use crate::db::datastore::system_tables::ST_INDEXES_ID; - use crate::db::datastore::system_tables::ST_SEQUENCES_ID; - use crate::db::datastore::traits::ColumnDef; - use crate::db::datastore::traits::IndexDef; - use crate::db::datastore::traits::TableDef; + use crate::db::datastore::system_tables::*; use crate::db::message_log::MessageLog; - use crate::db::relational_db::{open_db, ST_TABLES_ID}; - use crate::execution_context::ExecutionContext; - - use super::RelationalDB; - use crate::db::relational_db::make_default_ostorage; use crate::db::relational_db::tests_utils::make_test_db; - use crate::error::{DBError, DatabaseError, IndexError}; - use spacetimedb_lib::auth::StAccess; - use spacetimedb_lib::auth::StTableType; - use spacetimedb_lib::error::ResultTest; - use spacetimedb_lib::{AlgebraicType, AlgebraicValue, ProductType}; + use crate::db::relational_db::{open_db, ST_TABLES_ID}; use spacetimedb_sats::product; fn column(name: &str, ty: AlgebraicType) -> ColumnDef { diff --git a/crates/core/src/db/update.rs b/crates/core/src/db/update.rs index 756174287e4..32ad06a6e22 100644 --- a/crates/core/src/db/update.rs +++ b/crates/core/src/db/update.rs @@ -3,16 +3,16 @@ use std::borrow::Cow; use std::collections::{BTreeMap, HashMap}; use anyhow::Context; -use spacetimedb_lib::Hash; use crate::database_logger::SystemLogger; use crate::error::DBError; use crate::execution_context::ExecutionContext; use super::datastore::locking_tx_datastore::MutTxId; -use super::datastore::traits::{IndexDef, TableDef, TableSchema}; use super::relational_db::RelationalDB; use spacetimedb_primitives::IndexId; +use spacetimedb_sats::db::def::{IndexDef, TableDef, TableSchema}; +use spacetimedb_sats::hash::Hash; #[derive(thiserror::Error, Debug)] pub enum UpdateDatabaseError { @@ -245,18 +245,15 @@ fn equiv(a: &TableDef, b: &TableDef) -> bool { #[cfg(test)] mod tests { + use super::*; + use anyhow::bail; use nonempty::NonEmpty; - use spacetimedb_lib::{ - auth::{StAccess, StTableType}, - IndexType, - }; use spacetimedb_primitives::{ColId, TableId}; + use spacetimedb_sats::db::auth::{StAccess, StTableType}; use spacetimedb_sats::AlgebraicType; - use crate::db::datastore::traits::{ColumnDef, ColumnSchema, IndexSchema, AUTO_TABLE_ID}; - - use super::*; + use spacetimedb_sats::db::def::{ColumnDef, ColumnSchema, IndexSchema, IndexType, AUTO_TABLE_ID}; #[test] fn test_updates_new_table() -> anyhow::Result<()> { diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 2b3ffcfb14f..b04bf116cb1 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -1,20 +1,22 @@ -use crate::client::ClientActorId; -use crate::db::datastore::traits::IndexDef; +use std::num::ParseIntError; +use std::path::PathBuf; +use std::sync::{MutexGuard, PoisonError}; + use hex::FromHexError; +use thiserror::Error; + +use crate::client::ClientActorId; use spacetimedb_lib::buffer::DecodeError; -use spacetimedb_lib::error::{LibError, RelationError}; -use spacetimedb_lib::relation::FieldName; use spacetimedb_lib::{PrimaryKey, ProductValue}; use spacetimedb_primitives::{ColId, IndexId, TableId}; +use spacetimedb_sats::db::def::IndexDef; +use spacetimedb_sats::db::error::{LibError, RelationError, SchemaError}; use spacetimedb_sats::product_value::InvalidFieldError; +use spacetimedb_sats::relation::FieldName; use spacetimedb_sats::satn::Satn; use spacetimedb_sats::AlgebraicValue; use spacetimedb_vm::errors::{ErrorKind, ErrorLang, ErrorVm}; use spacetimedb_vm::expr::Crud; -use std::num::ParseIntError; -use std::path::PathBuf; -use std::sync::{MutexGuard, PoisonError}; -use thiserror::Error; #[derive(Error, Debug)] pub enum TableError { @@ -24,8 +26,10 @@ pub enum TableError { Exist(String), #[error("Table with name `{0}` not found.")] NotFound(String), - #[error("Table with ID `{0}` not found.")] - IdNotFound(TableId), + #[error("ID `{1}` not found in table ID `{0}`.")] + IdNotFound(TableId, u32), + #[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}")] @@ -135,6 +139,8 @@ pub enum DBError { Sequence2(#[from] crate::db::datastore::locking_tx_datastore::SequenceError), #[error("IndexError: {0}")] Index(#[from] IndexError), + #[error("SchemaError: {0}")] + Schema(#[from] SchemaError), #[error("IOError: {0}.")] IoError(#[from] std::io::Error), #[error("ParseIntError: {0}.")] @@ -245,7 +251,7 @@ impl From for NodesError { match e { DBError::Table(TableError::Exist(name)) => Self::AlreadyExists(name), DBError::Table(TableError::System(name)) => Self::SystemName(name), - DBError::Table(TableError::IdNotFound(_) | TableError::NotFound(_)) => Self::TableNotFound, + DBError::Table(TableError::IdNotFound(_, _) | TableError::NotFound(_)) => Self::TableNotFound, DBError::Table(TableError::ColumnNotFound(_)) => Self::BadColumn, _ => Self::Internal(Box::new(e)), } diff --git a/crates/core/src/host/instance_env.rs b/crates/core/src/host/instance_env.rs index 4e75b6d50b7..130a7823e99 100644 --- a/crates/core/src/host/instance_env.rs +++ b/crates/core/src/host/instance_env.rs @@ -1,13 +1,12 @@ use nonempty::NonEmpty; use parking_lot::{Mutex, MutexGuard}; -use spacetimedb_lib::{bsatn, IndexType, ProductValue}; +use spacetimedb_lib::{bsatn, ProductValue}; use std::ops::DerefMut; use std::sync::Arc; use crate::database_instance_context::DatabaseInstanceContext; use crate::database_logger::{BacktraceProvider, LogLevel, Record}; use crate::db::datastore::locking_tx_datastore::{MutTxId, RowId}; -use crate::db::datastore::traits::IndexDef; use crate::error::{IndexError, NodesError}; use crate::execution_context::ExecutionContext; use crate::util::ResultInspectExt; @@ -18,9 +17,10 @@ use crate::vm::DbProgram; use spacetimedb_lib::filter::CmpArgs; use spacetimedb_lib::identity::AuthCtx; use spacetimedb_lib::operator::OpQuery; -use spacetimedb_lib::relation::{FieldExpr, FieldName}; use spacetimedb_primitives::{ColId, TableId}; use spacetimedb_sats::buffer::BufWriter; +use spacetimedb_sats::db::def::{IndexDef, IndexType}; +use spacetimedb_sats::relation::{FieldExpr, FieldName}; use spacetimedb_sats::{ProductType, Typespace}; use spacetimedb_vm::expr::{Code, ColumnOp}; diff --git a/crates/core/src/host/mod.rs b/crates/core/src/host/mod.rs index 2e4d6ad7fde..bcf15e02d4c 100644 --- a/crates/core/src/host/mod.rs +++ b/crates/core/src/host/mod.rs @@ -1,31 +1,28 @@ -use std::time::Duration; - -use anyhow::Context; -use bytes::Bytes; -use bytestring::ByteString; -use derive_more::Display; -use enum_map::Enum; -use spacetimedb_lib::de::serde::SeedWrapper; -use spacetimedb_lib::de::DeserializeSeed; -use spacetimedb_lib::{bsatn, Hash, Identity}; -use spacetimedb_lib::{ProductValue, ReducerDef}; -use spacetimedb_sats::WithTypespace; - mod host_controller; pub(crate) mod module_host; -pub use module_host::{UpdateDatabaseResult, UpdateDatabaseSuccess}; pub mod scheduler; mod wasmer; - // Visible for integration testing. pub mod instance_env; mod timestamp; mod wasm_common; +use anyhow::Context; +use bytes::Bytes; +use bytestring::ByteString; +use derive_more::Display; +use enum_map::Enum; pub use host_controller::{ DescribedEntityType, EnergyDiff, EnergyQuanta, HostController, ReducerCallResult, ReducerOutcome, UpdateOutcome, }; pub use module_host::{ModuleHost, NoSuchModule}; +pub use module_host::{UpdateDatabaseResult, UpdateDatabaseSuccess}; +use spacetimedb_lib::de::serde::SeedWrapper; +use spacetimedb_lib::de::DeserializeSeed; +use spacetimedb_lib::{bsatn, Identity}; +use spacetimedb_lib::{ProductValue, ReducerDef}; +use spacetimedb_sats::WithTypespace; +use std::time::Duration; pub use timestamp::Timestamp; #[derive(Debug)] @@ -123,6 +120,7 @@ pub struct InvalidReducerArguments { } pub use module_host::{EntityDef, ReducerCallError}; +use spacetimedb_sats::hash::Hash; fn from_json_seed<'de, T: serde::de::DeserializeSeed<'de>>(s: &'de str, seed: T) -> anyhow::Result { let mut de = serde_json::Deserializer::from_str(s); diff --git a/crates/core/src/host/module_host.rs b/crates/core/src/host/module_host.rs index 9e04cee25eb..180fabbe24d 100644 --- a/crates/core/src/host/module_host.rs +++ b/crates/core/src/host/module_host.rs @@ -19,9 +19,9 @@ use crate::worker_metrics::WORKER_METRICS; use base64::{engine::general_purpose::STANDARD as BASE_64_STD, Engine as _}; use futures::{Future, FutureExt}; use indexmap::IndexMap; -use spacetimedb_lib::relation::MemTable; use spacetimedb_lib::{Address, ReducerDef, TableDef}; use spacetimedb_primitives::TableId; +use spacetimedb_sats::relation::MemTable; use spacetimedb_sats::{ProductValue, Typespace, WithTypespace}; use std::collections::HashMap; use std::fmt; @@ -248,7 +248,7 @@ pub trait Module: Send + Sync + 'static { &self, caller_identity: Identity, query: String, - ) -> Result, DBError>; + ) -> Result, DBError>; fn clear_table(&self, table_name: String) -> Result<(), anyhow::Error>; #[cfg(feature = "tracelogging")] @@ -343,7 +343,7 @@ trait DynModuleHost: Send + Sync + 'static { &self, caller_identity: Identity, query: String, - ) -> Result, DBError>; + ) -> Result, DBError>; fn clear_table(&self, table_name: String) -> Result<(), anyhow::Error>; fn start(&self); fn exit(&self) -> Closed<'_>; @@ -433,7 +433,7 @@ impl DynModuleHost for HostControllerActor { &self, caller_identity: Identity, query: String, - ) -> Result, DBError> { + ) -> Result, DBError> { self.module.one_off_query(caller_identity, query) } 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 532d8e8e588..631277acf00 100644 --- a/crates/core/src/host/wasm_common/module_host_actor.rs +++ b/crates/core/src/host/wasm_common/module_host_actor.rs @@ -1,20 +1,18 @@ +use anyhow::{anyhow, Context}; +use bytes::Bytes; use std::sync::Arc; use std::time::Duration; -use crate::db::datastore::locking_tx_datastore::MutTxId; -use crate::db::datastore::traits::TableDef; -use crate::execution_context::ExecutionContext; -use crate::sql; -use crate::util::{const_unwrap, ResultInspectExt}; -use anyhow::{anyhow, Context}; -use bytes::Bytes; use spacetimedb_lib::buffer::DecodeError; use spacetimedb_lib::identity::AuthCtx; use spacetimedb_lib::{bsatn, Address, ModuleDef}; +use spacetimedb_sats::db::def::TableDef; use spacetimedb_vm::expr::CrudExpr; use crate::database_instance_context::DatabaseInstanceContext; use crate::database_logger::{LogLevel, Record, SystemLogger}; +use crate::db::datastore::locking_tx_datastore::MutTxId; +use crate::execution_context::ExecutionContext; use crate::hash::Hash; use crate::host::instance_env::InstanceEnv; use crate::host::module_host::{ @@ -27,7 +25,9 @@ use crate::host::{ ReducerId, ReducerOutcome, Timestamp, }; use crate::identity::Identity; +use crate::sql; use crate::subscription::module_subscription_actor::{ModuleSubscriptionManager, SubscriptionEventSender}; +use crate::util::{const_unwrap, ResultInspectExt}; use crate::worker_metrics::WORKER_METRICS; use super::instrumentation::CallTimes; @@ -248,7 +248,7 @@ impl Module for WasmModuleHostActor { &self, caller_identity: Identity, query: String, - ) -> Result, DBError> { + ) -> Result, DBError> { let db = &self.database_instance_context.relational_db; let auth = AuthCtx::new(self.database_instance_context.identity, caller_identity); // TODO(jgilles): make this a read-only TX when those get added @@ -313,7 +313,7 @@ fn get_tabledefs(info: &ModuleInfo) -> impl Iterator ModuleInstance for WasmModuleInstance { diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index d00420c1cfc..46f94e63a24 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -25,8 +25,8 @@ pub mod db; pub mod messages; pub use spacetimedb_lib::Identity; pub mod error; -pub use spacetimedb_lib::hash; pub use spacetimedb_lib::identity; +pub use spacetimedb_sats::hash; pub mod protobuf { pub use spacetimedb_client_api_messages::*; } diff --git a/crates/core/src/messages/control_db.rs b/crates/core/src/messages/control_db.rs index 592a40ec448..5e4634c1301 100644 --- a/crates/core/src/messages/control_db.rs +++ b/crates/core/src/messages/control_db.rs @@ -1,5 +1,6 @@ -use spacetimedb_lib::{Hash, Identity}; +use spacetimedb_lib::Identity; use spacetimedb_sats::de::Deserialize; +use spacetimedb_sats::hash::Hash; use spacetimedb_sats::ser::Serialize; use crate::address::Address; diff --git a/crates/core/src/sql/ast.rs b/crates/core/src/sql/ast.rs index ce1fc2c10ae..dbc1d338cec 100644 --- a/crates/core/src/sql/ast.rs +++ b/crates/core/src/sql/ast.rs @@ -1,8 +1,17 @@ -use spacetimedb_lib::auth::{StAccess, StTableType}; -use spacetimedb_lib::error::RelationError; -use spacetimedb_lib::table::{ColumnDef, ProductTypeMeta}; -use spacetimedb_lib::ColumnIndexAttribute; +use crate::db::datastore::locking_tx_datastore::MutTxId; +use crate::db::datastore::traits::MutTxDatastore; +use crate::db::relational_db::RelationalDB; +use crate::error::{DBError, PlanError}; +use spacetimedb_primitives::ColumnIndexAttribute; +use spacetimedb_sats::db::auth::{StAccess, StTableType}; +use spacetimedb_sats::db::def::{ColumnDefMeta, ProductTypeMeta, TableSchema}; +use spacetimedb_sats::db::error::RelationError; +use spacetimedb_sats::relation::{extract_table_field, FieldExpr, FieldName}; use spacetimedb_sats::{AlgebraicType, AlgebraicValue, ProductTypeElement}; +use spacetimedb_vm::errors::ErrorVm; +use spacetimedb_vm::expr::{ColumnOp, DbType, Expr}; +use spacetimedb_vm::operator::{OpCmp, OpLogic, OpQuery}; +use spacetimedb_vm::ops::parse::parse; use sqlparser::ast::{ Assignment, BinaryOperator, ColumnDef as SqlColumnDef, ColumnOption, DataType, ExactNumberInfo, Expr as SqlExpr, GeneratedAs, HiveDistributionStyle, Ident, JoinConstraint, JoinOperator, ObjectName, ObjectType, Query, Select, @@ -13,16 +22,6 @@ use sqlparser::parser::Parser; use std::borrow::Cow; use std::collections::HashMap; -use crate::db::datastore::locking_tx_datastore::MutTxId; -use crate::db::datastore::traits::{MutTxDatastore, TableSchema}; -use crate::db::relational_db::RelationalDB; -use crate::error::{DBError, PlanError}; -use spacetimedb_lib::relation::{extract_table_field, FieldExpr, FieldName}; -use spacetimedb_vm::errors::ErrorVm; -use spacetimedb_vm::expr::{ColumnOp, DbType, Expr}; -use spacetimedb_vm::operator::{OpCmp, OpLogic, OpQuery}; -use spacetimedb_vm::ops::parse::parse; - /// Simplify to detect features of the syntax we don't support yet /// Because we use [PostgreSqlDialect] in the compiler step it already protect against features /// that are not in the standard SQL-92 but still need to check for completeness @@ -126,7 +125,7 @@ pub enum Join { #[derive(Clone)] pub struct FromField { pub field: FieldName, - pub column: ColumnDef, + pub column: ColumnDefMeta, } /// The list of tables in `... FROM table1 [JOIN table2] ...` diff --git a/crates/core/src/sql/compiler.rs b/crates/core/src/sql/compiler.rs index 36f62d5d3f0..6d54f72ba73 100644 --- a/crates/core/src/sql/compiler.rs +++ b/crates/core/src/sql/compiler.rs @@ -1,18 +1,18 @@ +use std::collections::HashMap; +use tracing::info; + use crate::db::datastore::locking_tx_datastore::MutTxId; -use crate::db::datastore::traits::TableSchema; use crate::db::relational_db::RelationalDB; use crate::error::{DBError, PlanError}; use crate::sql::ast::{compile_to_ast, Column, From, Join, Selection, SqlAst}; -use spacetimedb_lib::auth::{StAccess, StTableType}; -use spacetimedb_lib::relation::{self, DbTable, FieldExpr, FieldName, Header}; -use spacetimedb_lib::table::ProductTypeMeta; use spacetimedb_primitives::ColId; +use spacetimedb_sats::db::auth::{StAccess, StTableType}; +use spacetimedb_sats::db::def::{ProductTypeMeta, TableSchema}; +use spacetimedb_sats::relation::{self, DbTable, FieldExpr, FieldName, Header}; use spacetimedb_sats::AlgebraicValue; use spacetimedb_vm::dsl::{db_table, db_table_raw, query}; use spacetimedb_vm::expr::{ColumnOp, CrudExpr, DbType, Expr, QueryExpr, SourceExpr}; use spacetimedb_vm::operator::OpCmp; -use std::collections::HashMap; -use tracing::info; /// Compile the `SQL` expression into a `ast` #[tracing::instrument(skip_all)] @@ -292,15 +292,15 @@ fn compile_statement(statement: SqlAst) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::db::datastore::traits::{ColumnDef, IndexDef, TableDef}; + use std::ops::Bound; + use crate::db::relational_db::tests_utils::make_test_db; use spacetimedb_lib::error::ResultTest; use spacetimedb_lib::operator::OpQuery; - use spacetimedb_lib::relation::DbTable; use spacetimedb_primitives::TableId; + use spacetimedb_sats::db::def::{ColumnDef, IndexDef, TableDef}; use spacetimedb_sats::AlgebraicType; use spacetimedb_vm::expr::{IndexJoin, IndexScan, JoinExpr, Query}; - use std::ops::Bound; fn create_table( db: &RelationalDB, diff --git a/crates/core/src/sql/execute.rs b/crates/core/src/sql/execute.rs index 7af28bb8b6c..6ef9572708a 100644 --- a/crates/core/src/sql/execute.rs +++ b/crates/core/src/sql/execute.rs @@ -1,5 +1,5 @@ use spacetimedb_lib::identity::AuthCtx; -use spacetimedb_lib::relation::MemTable; +use spacetimedb_sats::relation::MemTable; use spacetimedb_sats::{ProductType, ProductValue}; use spacetimedb_vm::eval::run_ast; use spacetimedb_vm::expr::{CodeResult, CrudExpr, Expr}; @@ -105,9 +105,9 @@ pub(crate) mod tests { use crate::db::relational_db::{ST_TABLES_ID, ST_TABLES_NAME}; use crate::vm::tests::create_table_with_rows; use itertools::Itertools; - use spacetimedb_lib::auth::{StAccess, StTableType}; use spacetimedb_lib::error::ResultTest; - use spacetimedb_lib::relation::{Header, RelValue}; + use spacetimedb_sats::db::auth::{StAccess, StTableType}; + use spacetimedb_sats::relation::{Header, RelValue}; use spacetimedb_sats::{product, AlgebraicType, ProductType}; use spacetimedb_vm::dsl::{mem_table, scalar}; use spacetimedb_vm::eval::create_game_data; diff --git a/crates/core/src/subscription/query.rs b/crates/core/src/subscription/query.rs index 4147a3439c8..8f06af24bb1 100644 --- a/crates/core/src/subscription/query.rs +++ b/crates/core/src/subscription/query.rs @@ -6,9 +6,8 @@ use crate::sql::compiler::compile_sql; use crate::sql::execute::execute_single_sql; use crate::subscription::subscription::QuerySet; use spacetimedb_lib::identity::AuthCtx; -use spacetimedb_lib::relation::{Column, FieldName, MemTable, RelValue}; -use spacetimedb_lib::DataKey; -use spacetimedb_sats::AlgebraicType; +use spacetimedb_sats::relation::{Column, FieldName, MemTable, RelValue}; +use spacetimedb_sats::{AlgebraicType, DataKey}; use spacetimedb_vm::expr::{self, Crud, CrudExpr, DbType, QueryExpr, SourceExpr}; pub const SUBSCRIBE_TO_ALL_QUERY: &str = "SELECT * FROM *"; @@ -160,19 +159,17 @@ pub fn classify(expr: &QueryExpr) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::db::datastore::traits::{ColumnDef, IndexDef, TableDef, TableSchema}; use crate::db::relational_db::tests_utils::make_test_db; - use crate::host::module_host::{DatabaseTableUpdate, DatabaseUpdate, TableOp}; + use crate::host::module_host::{DatabaseUpdate, TableOp}; use crate::sql::execute::run; - use crate::subscription::subscription::QuerySet; use crate::vm::tests::create_table_with_rows; use itertools::Itertools; - use spacetimedb_lib::auth::{StAccess, StTableType}; - use spacetimedb_lib::data_key::ToDataKey; use spacetimedb_lib::error::ResultTest; - use spacetimedb_lib::relation::FieldName; use spacetimedb_lib::Identity; - use spacetimedb_primitives::{ColId, TableId}; + use spacetimedb_primitives::*; + use spacetimedb_sats::data_key::ToDataKey; + use spacetimedb_sats::db::auth::{StAccess, StTableType}; + use spacetimedb_sats::db::def::*; use spacetimedb_sats::{product, ProductType, ProductValue}; use spacetimedb_vm::dsl::{db_table, mem_table, scalar}; use spacetimedb_vm::operator::OpCmp; diff --git a/crates/core/src/subscription/subscription.rs b/crates/core/src/subscription/subscription.rs index dcd17818458..1062b509cfc 100644 --- a/crates/core/src/subscription/subscription.rs +++ b/crates/core/src/subscription/subscription.rs @@ -25,11 +25,11 @@ use anyhow::Context; use derive_more::{Deref, DerefMut, From, IntoIterator}; -use spacetimedb_lib::auth::{StAccess, StTableType}; use spacetimedb_lib::identity::AuthCtx; -use spacetimedb_lib::relation::{DbTable, MemTable, RelValue}; -use spacetimedb_lib::{DataKey, PrimaryKey}; -use spacetimedb_sats::{AlgebraicValue, ProductValue}; +use spacetimedb_lib::PrimaryKey; +use spacetimedb_sats::db::auth::{StAccess, StTableType}; +use spacetimedb_sats::relation::{DbTable, MemTable, RelValue}; +use spacetimedb_sats::{AlgebraicValue, DataKey, ProductValue}; use spacetimedb_vm::expr::{self, IndexJoin, QueryExpr, SourceExpr}; use std::collections::{btree_set, BTreeSet, HashMap, HashSet}; use std::ops::Deref; diff --git a/crates/core/src/util/typed_prometheus.rs b/crates/core/src/util/typed_prometheus.rs index ef3e1859481..e97deb58b96 100644 --- a/crates/core/src/util/typed_prometheus.rs +++ b/crates/core/src/util/typed_prometheus.rs @@ -1,5 +1,6 @@ +use crate::hash::Hash; use prometheus::core::{Metric, MetricVec, MetricVecBuilder}; -use spacetimedb_lib::{Address, Hash, Identity}; +use spacetimedb_lib::{Address, Identity}; #[macro_export] macro_rules! metrics_group { diff --git a/crates/core/src/vm.rs b/crates/core/src/vm.rs index 400fc1c17ea..d51b819479d 100644 --- a/crates/core/src/vm.rs +++ b/crates/core/src/vm.rs @@ -1,16 +1,15 @@ //! The [DbProgram] that execute arbitrary queries & code against the database. use crate::db::cursor::{CatalogCursor, IndexCursor, TableCursor}; use crate::db::datastore::locking_tx_datastore::{IterByColEq, MutTxId}; -use crate::db::datastore::traits::{ColumnDef, IndexDef, TableDef}; use crate::db::relational_db::RelationalDB; use crate::execution_context::ExecutionContext; use itertools::Itertools; -use spacetimedb_lib::auth::{StAccess, StTableType}; use spacetimedb_lib::identity::AuthCtx; -use spacetimedb_lib::relation::{DbTable, FieldExpr, FieldName, Relation}; -use spacetimedb_lib::relation::{Header, MemTable, RelIter, RelValue, RowCount, Table}; -use spacetimedb_lib::table::ProductTypeMeta; use spacetimedb_primitives::{ColId, TableId}; +use spacetimedb_sats::db::auth::{StAccess, StTableType}; +use spacetimedb_sats::db::def::{ColumnDef, IndexDef, ProductTypeMeta, TableDef}; +use spacetimedb_sats::relation::{DbTable, FieldExpr, FieldName, Relation}; +use spacetimedb_sats::relation::{Header, MemTable, RelIter, RelValue, RowCount, Table}; use spacetimedb_sats::{AlgebraicValue, ProductValue}; use spacetimedb_vm::env::EnvDb; use spacetimedb_vm::errors::ErrorVm; @@ -538,9 +537,9 @@ pub(crate) mod tests { use crate::execution_context::ExecutionContext; use nonempty::NonEmpty; use spacetimedb_lib::error::ResultTest; - use spacetimedb_lib::relation::{DbTable, FieldName}; - use spacetimedb_lib::IndexType; use spacetimedb_primitives::TableId; + use spacetimedb_sats::db::def::IndexType; + use spacetimedb_sats::relation::{DbTable, FieldName}; use spacetimedb_sats::{product, AlgebraicType, ProductType, ProductValue}; use spacetimedb_vm::dsl::*; use spacetimedb_vm::eval::run_ast; diff --git a/crates/core/src/worker_metrics/mod.rs b/crates/core/src/worker_metrics/mod.rs index f09c73286fe..644d6633bef 100644 --- a/crates/core/src/worker_metrics/mod.rs +++ b/crates/core/src/worker_metrics/mod.rs @@ -1,7 +1,8 @@ +use crate::hash::Hash; use crate::util::typed_prometheus::metrics_group; use once_cell::sync::Lazy; use prometheus::{Gauge, GaugeVec, HistogramVec, IntCounterVec, IntGauge, IntGaugeVec}; -use spacetimedb_lib::{Address, Hash, Identity}; +use spacetimedb_lib::{Address, Identity}; metrics_group!( pub struct WorkerMetrics { diff --git a/crates/lib/src/address.rs b/crates/lib/src/address.rs index e9e682855f7..ebb74343329 100644 --- a/crates/lib/src/address.rs +++ b/crates/lib/src/address.rs @@ -4,8 +4,8 @@ use sats::{impl_deserialize, impl_serialize, impl_st, AlgebraicType}; use spacetimedb_bindings_macro::{Deserialize, Serialize}; use std::{fmt, net::Ipv6Addr}; -use crate::hex::HexString; use crate::sats; +use spacetimedb_sats::hex::HexString; /// This is the address for a SpacetimeDB database or client connection. /// @@ -65,7 +65,7 @@ impl Address { } pub fn to_hex(self) -> HexString<16> { - crate::hex::encode(self.as_slice()) + spacetimedb_sats::hex::encode(self.as_slice()) } pub fn abbreviate(&self) -> [u8; 8] { @@ -73,7 +73,7 @@ impl Address { } pub fn to_abbreviated_hex(self) -> HexString<8> { - crate::hex::encode(&self.abbreviate()) + spacetimedb_sats::hex::encode(&self.abbreviate()) } pub fn from_slice(slice: impl AsRef<[u8]>) -> Self { diff --git a/crates/lib/src/error.rs b/crates/lib/src/error.rs index 8bae167b8c8..7fc7e3bdfb5 100644 --- a/crates/lib/src/error.rs +++ b/crates/lib/src/error.rs @@ -1,87 +1,4 @@ -use crate::relation::{FieldName, Header}; -use crate::{buffer, AlgebraicType}; -use spacetimedb_sats::product_value::InvalidFieldError; -use spacetimedb_sats::AlgebraicValue; use std::fmt; -use std::string::FromUtf8Error; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum TypeError { - #[error("Arrays must be homogeneous. It expects to be `{{expect.to_satns()}}` but `{{value.to_satns()}}` is of type `{{found.to_satns()}}`")] - Array { - expect: AlgebraicType, - found: AlgebraicType, - value: AlgebraicValue, - }, - #[error("Arrays must define a type for the elements")] - ArrayEmpty, - #[error("Maps must be homogeneous. It expects to be `{{key_expect.to_satns()}}:{{value_expect.to_satns()}}` but `{{key.to_satns()}}::{{value.to_satns()}}` is of type `{{key_found.to_satns()}}:{{value_found.to_satns()}}`")] - Map { - key_expect: AlgebraicType, - value_expect: AlgebraicType, - key_found: AlgebraicType, - value_found: AlgebraicType, - key: AlgebraicValue, - value: AlgebraicValue, - }, - #[error("Maps must define a type for both key & value")] - MapEmpty, -} - -#[derive(Error, Debug, Clone)] -pub enum DecodeError { - #[error("Decode UTF8: {0}")] - Utf8(#[from] FromUtf8Error), - #[error("AlgebraicType::decode: Unknown: {0}")] - AlgebraicTypeUnknown(u8), - #[error("AlgebraicType::decode: Byte array has invalid length: {0:?}")] - AlgebraicType(usize), - #[error("SumType::decode: Byte array has invalid length: {0:?}")] - SumType(usize), - #[error("ProductType::decode: Byte array has invalid length: {0:?}")] - ProductType(usize), - #[error("ProductTypeElement::decode: Byte array has invalid length: {0:?}")] - ProductTypeElement(usize), - #[error("AlgebraicValue::decode: byte array length not long enough to decode {0:?}")] - AlgebraicValue(AlgebraicType), - #[error("AlgebraicValue::decode: byte array length not long enough to get length of {0:?}")] - AlgebraicValueGetLength(AlgebraicType), - #[error( - "AlgebraicValue::decode: buffer has no room to decode any more elements from this {kind:?}. (len: {len} <= read:{read})" - )] - AlgebraicValueRoom { - kind: AlgebraicType, - len: usize, - read: usize, - }, - #[error("AlgebraicValue::decode: Cannot decode {kind:?}, buffer not long enough. (len: {len}, read:{read})")] - TypeBufferSmall { - kind: AlgebraicType, - len: usize, - read: usize, - }, - #[error( - "AlgebraicValue::decode: byte array length not long enough to decode {kind:?}. (expect: {expect}, read:{read})" - )] - TypeTooSmall { - kind: AlgebraicType, - expect: usize, - read: usize, - }, - #[error("EnumValue::decode: Byte array length is invalid.")] - EnumValue, -} - -#[derive(Error, Debug, Clone)] -pub enum LibError { - #[error("DecodeError: {0}")] - Decode(#[from] DecodeError), - #[error("BufferError: {0}")] - Buffer(#[from] buffer::DecodeError), - #[error(transparent)] - TupleFieldInvalid(#[from] InvalidFieldError), -} /// A wrapper for using on test so the error display nicely pub struct TestError { @@ -103,27 +20,3 @@ impl From for TestError { /// A wrapper for using [Result] in tests, so it display nicely pub type ResultTest = Result; - -#[derive(thiserror::Error, Debug)] -pub enum AuthError { - #[error("Table `{named}` is private")] - TablePrivate { named: String }, - #[error("Index `{named}` is private")] - IndexPrivate { named: String }, - #[error("Sequence `{named}` is private")] - SequencePrivate { named: String }, - #[error("Only the database owner can perform the requested operation")] - OwnerRequired, -} - -#[derive(thiserror::Error, Debug)] -pub enum RelationError { - #[error("Field `{1}` not found. Must be one of {0}")] - FieldNotFound(Header, FieldName), - #[error("Field `{1}` not found at position {0}")] - FieldNotFoundAtPos(usize, FieldName), - #[error("Field `{0}` fail to infer the type: {1}")] - TypeInference(FieldName, TypeError), - #[error("Field declaration only support `table.field` or `field`. It gets instead `{0}`")] - FieldPathInvalid(String), -} diff --git a/crates/lib/src/identity.rs b/crates/lib/src/identity.rs index 9334327fda8..ff7786afb19 100644 --- a/crates/lib/src/identity.rs +++ b/crates/lib/src/identity.rs @@ -1,10 +1,9 @@ use spacetimedb_bindings_macro::{Deserialize, Serialize}; -use spacetimedb_sats::{impl_st, AlgebraicType}; +use spacetimedb_sats::hex::HexString; +use spacetimedb_sats::{hash, impl_st, AlgebraicType}; use std::fmt; use std::str::FromStr; -use crate::hex::HexString; - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct AuthCtx { pub owner: Identity, @@ -63,7 +62,7 @@ impl Identity { } pub fn to_hex(&self) -> HexString<32> { - crate::hex::encode(&self.__identity_bytes) + spacetimedb_sats::hex::encode(&self.__identity_bytes) } pub fn abbreviate(&self) -> &[u8; 8] { @@ -71,7 +70,7 @@ impl Identity { } pub fn to_abbreviated_hex(&self) -> HexString<8> { - crate::hex::encode(self.abbreviate()) + spacetimedb_sats::hex::encode(self.abbreviate()) } pub fn from_hex(hex: impl AsRef<[u8]>) -> Result { @@ -79,7 +78,7 @@ impl Identity { } pub fn from_hashing_bytes(bytes: impl AsRef<[u8]>) -> Self { - Identity::from_byte_array(crate::hash::hash_bytes(bytes).data) + Identity::from_byte_array(hash::hash_bytes(bytes).data) } } diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 6a9a24f0bea..6637e9757ab 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -1,46 +1,37 @@ -use auth::StAccess; -use auth::StTableType; -use sats::impl_serialize; -pub use spacetimedb_sats::buffer; +use anyhow::Context; +use spacetimedb_primitives::{ColId, ColumnIndexAttribute}; +use spacetimedb_sats::db::auth::{StAccess, StTableType}; +use spacetimedb_sats::db::def::{ColumnDef, IndexType, AUTO_TABLE_ID}; +use spacetimedb_sats::{impl_serialize, WithTypespace}; +use std::iter; + pub mod address; -pub mod data_key; pub mod filter; -pub mod hex; pub mod identity; -pub use spacetimedb_sats::de; -pub mod error; -pub mod hash; pub mod name; pub mod operator; pub mod primary_key; -pub use spacetimedb_sats::ser; pub mod type_def { pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType}; } pub mod type_value { pub use spacetimedb_sats::{AlgebraicValue, ProductValue}; } -pub mod auth; + +pub mod error; #[cfg(feature = "serde")] pub mod recovery; -pub mod relation; -pub mod table; #[cfg(feature = "cli")] pub mod util; pub mod version; -pub use spacetimedb_sats::bsatn; - pub use address::Address; -pub use data_key::DataKey; -pub use hash::Hash; pub use identity::Identity; pub use primary_key::PrimaryKey; +pub use spacetimedb_sats::{self as sats, bsatn, buffer, de, ser}; pub use type_def::*; pub use type_value::{AlgebraicValue, ProductValue}; -pub use spacetimedb_sats as sats; - pub const MODULE_ABI_MAJOR_VERSION: u16 = 7; // if it ends up we need more fields in the future, we can split one of them in two @@ -91,8 +82,16 @@ impl std::fmt::Display for VersionTuple { extern crate self as spacetimedb_lib; +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, de::Deserialize, ser::Serialize)] +pub struct IndexDef { + pub name: String, + pub index_type: IndexType, + pub cols: Vec, +} + //WARNING: Change this structure(or any of their members) is an ABI change. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, de::Deserialize, ser::Serialize)] +#[sats(crate = crate)] pub struct TableDef { pub name: String, /// data should always point to a ProductType in the typespace @@ -103,6 +102,94 @@ pub struct TableDef { pub table_access: StAccess, } +impl TableDef { + pub fn into_table_def(table: WithTypespace<'_, TableDef>) -> anyhow::Result { + let schema = table + .map(|t| &t.data) + .resolve_refs() + .context("recursive types not yet supported")?; + let schema = schema.into_product().ok().context("table not a product type?")?; + let table = table.ty(); + anyhow::ensure!( + table.column_attrs.len() == schema.elements.len(), + "mismatched number of columns" + ); + + // Build single-column index definitions, determining `is_unique` from + // their respective column attributes. + let mut columns = Vec::with_capacity(schema.elements.len()); + let mut indexes = Vec::new(); + for (col_id, (ty, col_attr)) in std::iter::zip(&schema.elements, &table.column_attrs).enumerate() { + let col = ColumnDef { + col_name: ty.name.clone().context("column without name")?, + col_type: ty.algebraic_type.clone(), + is_autoinc: col_attr.has_autoinc(), + }; + + let index_for_column = table.indexes.iter().find(|index| { + // Ignore multi-column indexes + matches!(*index.cols, [index_col_id] if index_col_id as usize == col_id) + }); + + // If there's an index defined for this column already, use it, + // making sure that it is unique if the column has a unique constraint + let index_info = if let Some(index) = index_for_column { + Some((index.name.clone(), index.index_type)) + } else if col_attr.has_unique() { + // If you didn't find an index, but the column is unique then create a unique btree index + // anyway. + Some((format!("{}_{}_unique", table.name, col.col_name), IndexType::BTree)) + } else { + None + }; + if let Some((name, ty)) = index_info { + match ty { + IndexType::BTree => {} + // TODO + IndexType::Hash => anyhow::bail!("hash indexes not yet supported"), + } + indexes.push(spacetimedb_sats::db::def::IndexDef::new( + name, + AUTO_TABLE_ID, + ColId(col_id as u32), + col_attr.has_unique(), + )) + } + columns.push(col); + } + + // Multi-column indexes cannot be unique (yet), so just add them. + let multi_col_indexes = table.indexes.iter().filter_map(|index| { + if let [a, b, rest @ ..] = &*index.cols { + let idx = spacetimedb_sats::db::def::IndexDef::new_cols( + index.name.clone(), + AUTO_TABLE_ID, + false, + ( + ColId::from(*a), + iter::once(ColId::from(*b)) + .chain(rest.iter().copied().map(Into::into)) + .collect(), + ), + ); + + Some(idx) + } else { + None + } + }); + indexes.extend(multi_col_indexes); + + Ok(spacetimedb_sats::db::def::TableDef { + table_name: table.name.clone(), + columns, + indexes, + table_type: table.table_type, + table_access: table.table_access, + }) + } +} + #[derive(Debug, Clone, de::Deserialize, ser::Serialize)] pub struct ReducerDef { pub name: String, @@ -191,90 +278,3 @@ pub struct TypeAlias { pub name: String, pub ty: sats::AlgebraicTypeRef, } - -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, de::Deserialize, ser::Serialize)] -pub struct IndexDef { - pub name: String, - pub index_type: IndexType, - pub cols: Vec, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, de::Deserialize, ser::Serialize)] -#[repr(u8)] -pub enum IndexType { - BTree = 0, - Hash = 1, -} - -impl From for u8 { - fn from(value: IndexType) -> Self { - value as u8 - } -} - -impl TryFrom for IndexType { - type Error = (); - fn try_from(v: u8) -> Result { - match v { - 0 => Ok(IndexType::BTree), - 1 => Ok(IndexType::Hash), - _ => Err(()), - } - } -} - -// NOTE: Duplicated in `crates/bindings-macro/src/lib.rs` -bitflags::bitflags! { - #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] - pub struct ColumnIndexAttribute: u8 { - const UNSET = Self::empty().bits(); - /// Index no unique - const INDEXED = 0b0001; - /// Generate the next [Sequence] - const AUTO_INC = 0b0010; - /// Index unique - const UNIQUE = Self::INDEXED.bits() | 0b0100; - /// Unique + AutoInc - const IDENTITY = Self::UNIQUE.bits() | Self::AUTO_INC.bits(); - /// Primary key column (implies Unique) - const PRIMARY_KEY = Self::UNIQUE.bits() | 0b1000; - /// PrimaryKey + AutoInc - const PRIMARY_KEY_AUTO = Self::PRIMARY_KEY.bits() | Self::AUTO_INC.bits(); - } -} - -impl ColumnIndexAttribute { - pub const fn has_unique(self) -> bool { - self.contains(Self::UNIQUE) - } - pub const fn has_indexed(self) -> bool { - self.contains(Self::INDEXED) - } - pub const fn has_autoinc(self) -> bool { - self.contains(Self::AUTO_INC) - } - pub const fn has_primary(self) -> bool { - self.contains(Self::PRIMARY_KEY) - } -} - -impl TryFrom for ColumnIndexAttribute { - type Error = (); - - fn try_from(v: u8) -> Result { - Self::from_bits(v).ok_or(()) - } -} - -impl<'de> de::Deserialize<'de> for ColumnIndexAttribute { - fn deserialize>(deserializer: D) -> Result { - Self::from_bits(deserializer.deserialize_u8()?) - .ok_or_else(|| de::Error::custom("invalid bitflags for ColumnIndexAttribute")) - } -} - -impl ser::Serialize for ColumnIndexAttribute { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_u8(self.bits()) - } -} diff --git a/crates/lib/src/primary_key.rs b/crates/lib/src/primary_key.rs index 37c9a42d62e..cc34ccc0d34 100644 --- a/crates/lib/src/primary_key.rs +++ b/crates/lib/src/primary_key.rs @@ -1,7 +1,7 @@ +use spacetimedb_sats::data_key::DataKey; use std::fmt; use crate::buffer::{BufReader, BufWriter, DecodeError}; -use crate::DataKey; // TODO(280): Remove PrimaryKey. // PrimaryKey is a wrapper for DataKey which identifies each row in the database. diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 55c26c0d94f..756d2276318 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -7,3 +7,4 @@ description = "Primitives such as TableId and ColumnIndexAttribute" [dependencies] nonempty.workspace = true +bitflags.workspace = true diff --git a/crates/primitives/src/attr.rs b/crates/primitives/src/attr.rs new file mode 100644 index 00000000000..8f1e0d88008 --- /dev/null +++ b/crates/primitives/src/attr.rs @@ -0,0 +1,90 @@ +use bitflags::bitflags; + +#[allow(non_camel_case_types, clippy::upper_case_acronyms)] +#[derive(Eq, PartialEq)] +pub enum AttributeKind { + UNSET, + /// Index no unique + INDEXED, + /// Auto Increment + AUTO_INC, + /// Index unique + UNIQUE, + /// Unique + AutoInc + IDENTITY, + /// Primary key column (implies Unique) + PRIMARY_KEY, + /// PrimaryKey + AutoInc + PRIMARY_KEY_AUTO, + /// PrimaryKey + Identity + PRIMARY_KEY_IDENTITY, +} + +// This indeed is only used for defining columns + constraints AND/OR auto_inc, +// and is distinct to `Constraints` in `sats/db/def.rs` +bitflags! { + #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] + pub struct ColumnIndexAttribute: u8 { + const UNSET = Self::empty().bits(); + /// Index no unique + const INDEXED = 0b0001; + /// Generate the next [Sequence] + const AUTO_INC = 0b0010; + /// Index unique + const UNIQUE = Self::INDEXED.bits() | 0b0100; + /// Unique + AutoInc + const IDENTITY = Self::UNIQUE.bits() | Self::AUTO_INC.bits(); + /// Primary key column (implies Unique) + const PRIMARY_KEY = Self::UNIQUE.bits() | 0b1000; + /// PrimaryKey + AutoInc + const PRIMARY_KEY_AUTO = Self::PRIMARY_KEY.bits() | Self::AUTO_INC.bits(); + /// PrimaryKey + Identity + const PRIMARY_KEY_IDENTITY = Self::PRIMARY_KEY.bits() | Self::IDENTITY.bits() ; + } +} + +impl TryFrom for ColumnIndexAttribute { + type Error = (); + + fn try_from(v: u8) -> Result { + Self::from_bits(v).ok_or(()) + } +} + +impl ColumnIndexAttribute { + /// Checks if either 'IDENTITY' or 'PRIMARY_KEY_AUTO' constraints are set because the imply the use of + /// auto increment sequence. + pub const fn has_autoinc(&self) -> bool { + self.contains(ColumnIndexAttribute::IDENTITY) + || self.contains(ColumnIndexAttribute::PRIMARY_KEY_AUTO) + || self.contains(ColumnIndexAttribute::AUTO_INC) + } + + pub const fn has_unique(&self) -> bool { + self.contains(ColumnIndexAttribute::UNIQUE) + } + + pub const fn has_primary(&self) -> bool { + self.contains(ColumnIndexAttribute::IDENTITY) + || self.contains(ColumnIndexAttribute::PRIMARY_KEY) + || self.contains(ColumnIndexAttribute::PRIMARY_KEY_AUTO) + } + + /// Returns the [ColumnIndexAttribute] of constraints as an enum variant. + /// + /// NOTE: This represent the higher possible representation of a constraints, so for example + /// `IDENTITY` imply that is `INDEXED, UNIQUE` + pub fn kind(&self) -> AttributeKind { + match *self { + x if x == Self::UNSET => AttributeKind::UNSET, + x if x == Self::INDEXED => AttributeKind::INDEXED, + x if x == Self::UNIQUE => AttributeKind::UNIQUE, + x if x == Self::AUTO_INC => AttributeKind::AUTO_INC, + x if x == Self::IDENTITY => AttributeKind::IDENTITY, + x if x == Self::PRIMARY_KEY => AttributeKind::PRIMARY_KEY, + x if x == Self::PRIMARY_KEY_AUTO => AttributeKind::PRIMARY_KEY_AUTO, + x if x == Self::PRIMARY_KEY_IDENTITY => AttributeKind::PRIMARY_KEY_IDENTITY, + x => unreachable!("Unexpected value {x:?}"), + } + } +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index ba3687921d3..d1210f3e00f 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] +mod attr; mod ids; +pub use attr::{AttributeKind, ColumnIndexAttribute}; pub use ids::{ColId, ConstraintId, IndexId, SequenceId, TableId}; diff --git a/crates/sats/Cargo.toml b/crates/sats/Cargo.toml index 823d75233e4..7a73c9e6649 100644 --- a/crates/sats/Cargo.toml +++ b/crates/sats/Cargo.toml @@ -8,19 +8,26 @@ description = "Spacetime Algebraic Type Notation" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -serde = ["dep:serde", "hex"] +serde = ["dep:serde"] +# Allows using `Arbitrary` impls defined in this crate. +proptest = ["dep:proptest", "dep:proptest-derive"] [dependencies] spacetimedb-bindings-macro = { path = "../bindings-macro", version = "0.7.3" } spacetimedb-primitives = { path = "../primitives", version = "0.7.3" } arrayvec.workspace = true +bitflags.workspace = true decorum.workspace = true derive_more.workspace = true enum-as-inner.workspace = true -hex = { workspace = true, optional = true } +hex.workspace = true itertools.workspace = true nonempty.workspace = true +# For the 'proptest' feature. +proptest = { workspace = true, optional = true } +proptest-derive = { workspace = true, optional = true } +sha3.workspace = true serde = { workspace = true, optional = true } smallvec.workspace = true thiserror.workspace = true @@ -28,5 +35,7 @@ tracing.workspace = true [dev-dependencies] bytes.workspace = true -proptest.workspace = true rand.workspace = true +# Also as dev-dependencies for use in _this_ crate's tests. +proptest.workspace = true +proptest-derive.workspace = true \ No newline at end of file diff --git a/crates/lib/src/data_key.rs b/crates/sats/src/data_key.rs similarity index 97% rename from crates/lib/src/data_key.rs rename to crates/sats/src/data_key.rs index 2d33a84f0e5..24f3ee32c72 100644 --- a/crates/lib/src/data_key.rs +++ b/crates/sats/src/data_key.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Write}; use std::ops::Deref; use crate::buffer::{BufReader, BufWriter, DecodeError}; -use crate::hash::hash_bytes; +use crate::hash::{hash_bytes, Hash}; #[cfg(any(test, feature = "proptest"))] use proptest::prelude::*; @@ -13,7 +13,7 @@ use proptest_derive::Arbitrary; #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] pub enum DataKey { Data(InlineData), - Hash(super::Hash), + Hash(Hash), } impl DataKey { @@ -24,7 +24,7 @@ impl DataKey { /// The maximum possible value for a DataKey, used for sorting DataKeys pub fn max_datakey() -> Self { - DataKey::Hash(super::Hash::from_slice(&[255; 32])) + DataKey::Hash(Hash::from_slice(&[255; 32])) } } @@ -124,7 +124,7 @@ impl DataKey { if header != IS_HASH_BIT { return Err(DecodeError::InvalidTag); } - let hash = super::hash::Hash { + let hash = Hash { data: bytes.get_array()?, }; Ok(Self::Hash(hash)) diff --git a/crates/sats/src/db/attr.rs b/crates/sats/src/db/attr.rs new file mode 100644 index 00000000000..57c3604010d --- /dev/null +++ b/crates/sats/src/db/attr.rs @@ -0,0 +1,9 @@ +use crate::{de, impl_deserialize, impl_serialize}; +pub use spacetimedb_primitives::ColumnIndexAttribute; + +impl_deserialize!([] ColumnIndexAttribute, de => + Self::from_bits(de.deserialize_u8()?) + .ok_or_else(|| de::Error::custom("invalid bitflags for `ColumnIndexAttribute`")) +); + +impl_serialize!([] ColumnIndexAttribute, (self, ser) => ser.serialize_u8(self.bits())); diff --git a/crates/lib/src/auth.rs b/crates/sats/src/db/auth.rs similarity index 97% rename from crates/lib/src/auth.rs rename to crates/sats/src/db/auth.rs index ba927336e92..04e4a2c2e3b 100644 --- a/crates/lib/src/auth.rs +++ b/crates/sats/src/db/auth.rs @@ -1,4 +1,4 @@ -use spacetimedb_sats::{impl_deserialize, impl_serialize}; +use crate::{impl_deserialize, impl_serialize}; use crate::de::Error; diff --git a/crates/sats/src/db/def.rs b/crates/sats/src/db/def.rs new file mode 100644 index 00000000000..039ee4130a3 --- /dev/null +++ b/crates/sats/src/db/def.rs @@ -0,0 +1,728 @@ +use crate::db::auth::{StAccess, StTableType}; +use crate::de::BasicVecVisitor; +use crate::product_value::InvalidFieldError; +use crate::relation::{Column, DbTable, FieldName, FieldOnly, Header, TableField}; +use crate::ser::SerializeArray; +use crate::{de, impl_deserialize, impl_serialize, ser, AlgebraicValue, ProductValue}; +use crate::{AlgebraicType, ProductType, ProductTypeElement}; +use derive_more::Display; +use nonempty::NonEmpty; +use spacetimedb_primitives::{AttributeKind, ColId, ColumnIndexAttribute, ConstraintId, IndexId, SequenceId, TableId}; + +/// The default preallocation amount for sequences. +pub const SEQUENCE_PREALLOCATION_AMOUNT: i128 = 4_096; + +bitflags::bitflags! { + #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] + pub struct ConstraintFlags: u8 { + const UNSET = Self::empty().bits(); + /// Index no unique + const INDEXED = 0b0001; + /// Index unique + const UNIQUE = Self::INDEXED.bits() | 0b0010; + /// Unique + AutoInc + const IDENTITY = Self::UNIQUE.bits() | 0b0100; + /// Primary key column (implies Unique) + const PRIMARY_KEY = Self::UNIQUE.bits() | 0b1000; + /// PrimaryKey + AutoInc + const PRIMARY_KEY_AUTO = Self::PRIMARY_KEY.bits() | 0b10000; + /// PrimaryKey + Identity + const PRIMARY_KEY_IDENTITY = Self::PRIMARY_KEY.bits() | Self::IDENTITY.bits(); + } +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] +pub enum ConstraintKind { + UNSET, + /// Index no unique + INDEXED, + /// Index unique + UNIQUE, + /// Unique + AutoInc + IDENTITY, + /// Primary key column (implies Unique) + PRIMARY_KEY, + /// PrimaryKey + AutoInc + PRIMARY_KEY_AUTO, + /// PrimaryKey + Identity + PRIMARY_KEY_IDENTITY, +} + +/// Represents `constraints` for a database `table`. +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] +pub struct Constraints { + pub attr: ConstraintFlags, +} + +impl Constraints { + /// Creates a new `Constraints` instance with the given `attr` flags. + #[inline(always)] + const fn new(attr: ConstraintFlags) -> Self { + Self { attr } + } + + /// Creates a new `Constraints` instance with no constraints set. + pub const fn unset() -> Self { + Self::new(ConstraintFlags::UNSET) + } + + /// Creates a new `Constraints` instance with [ConstraintFlags::INDEXED] set. + pub const fn indexed() -> Self { + Self::new(ConstraintFlags::INDEXED) + } + + /// Creates a new `Constraints` instance with [ConstraintAttribute::UNIQUE' constraint set. + pub const fn unique() -> Self { + Self::new(ConstraintFlags::UNIQUE) + } + + /// Creates a new `Constraints` instance with [ConstraintFlags::IDENTITY] set. + pub const fn identity() -> Self { + Self::new(ConstraintFlags::IDENTITY) + } + + /// Creates a new `Constraints` instance with [ConstraintFlags::PRIMARY_KEY] set. + pub const fn primary_key() -> Self { + Self::new(ConstraintFlags::PRIMARY_KEY) + } + + /// Creates a new `Constraints` instance with [ConstraintFlags::PRIMARY_KEY_AUTO] set. + pub const fn primary_key_auto() -> Self { + Self::new(ConstraintFlags::PRIMARY_KEY_AUTO) + } + + /// Creates a new `Constraints` instance with [ConstraintFlags::PRIMARY_KEY_IDENTITY] set. + pub const fn primary_key_identity() -> Self { + Self::new(ConstraintFlags::PRIMARY_KEY_IDENTITY) + } + + /// Adds a constraint to the existing constraints. + /// + /// # Example + /// + /// ``` + /// use spacetimedb_sats::db::def::*; + /// + /// let constraints = Constraints::unset().push(ConstraintFlags::INDEXED); + /// assert!(constraints.has_indexed()); + /// ``` + pub fn push(self, attr: ConstraintFlags) -> Self { + Self::new(self.attr | attr) + } + + /// Returns the bits representing the constraints. + pub const fn bits(&self) -> u8 { + self.attr.bits() + } + + /// Returns the [ConstraintKind] of constraints as an enum variant. + /// + /// NOTE: This represent the higher possible representation of a constraints, so for example + /// `IDENTITY` imply that is `INDEXED, UNIQUE` + pub fn kind(&self) -> ConstraintKind { + match self { + x if x.attr == ConstraintFlags::UNSET => ConstraintKind::UNSET, + x if x.attr == ConstraintFlags::INDEXED => ConstraintKind::INDEXED, + x if x.attr == ConstraintFlags::UNIQUE => ConstraintKind::UNIQUE, + x if x.attr == ConstraintFlags::IDENTITY => ConstraintKind::IDENTITY, + x if x.attr == ConstraintFlags::PRIMARY_KEY => ConstraintKind::PRIMARY_KEY, + x if x.attr == ConstraintFlags::PRIMARY_KEY_AUTO => ConstraintKind::PRIMARY_KEY_AUTO, + x if x.attr == ConstraintFlags::PRIMARY_KEY_IDENTITY => ConstraintKind::PRIMARY_KEY_IDENTITY, + x => unreachable!("Unexpected value {x:?}"), + } + } + + /// Checks if the 'UNIQUE' constraint is set. + pub const fn has_unique(&self) -> bool { + self.attr.contains(ConstraintFlags::UNIQUE) + } + + /// Checks if the 'INDEXED' constraint is set. + pub const fn has_indexed(&self) -> bool { + self.attr.contains(ConstraintFlags::INDEXED) + } + + /// Checks if either 'IDENTITY' or 'PRIMARY_KEY_AUTO' constraints are set because the imply the use of + /// auto increment sequence. + pub const fn has_autoinc(&self) -> bool { + self.attr.contains(ConstraintFlags::IDENTITY) || self.attr.contains(ConstraintFlags::PRIMARY_KEY_AUTO) + } + + /// Checks if the 'PRIMARY_KEY' constraint is set. + pub const fn has_pk(&self) -> bool { + self.attr.contains(ConstraintFlags::PRIMARY_KEY) + } +} + +impl From for Constraints { + fn from(attr: ConstraintFlags) -> Self { + Self::new(attr) + } +} + +impl TryFrom for Constraints { + type Error = (); + fn try_from(v: u8) -> Result { + ConstraintFlags::from_bits(v).ok_or(()).map(Self::new) + } +} + +impl TryFrom for Constraints { + type Error = (); + + fn try_from(value: ColumnIndexAttribute) -> Result { + Ok(match value.kind() { + AttributeKind::UNSET => Self::unset(), + AttributeKind::INDEXED => Self::indexed(), + AttributeKind::UNIQUE => Self::unique(), + AttributeKind::IDENTITY => Self::identity(), + AttributeKind::PRIMARY_KEY => Self::primary_key(), + AttributeKind::PRIMARY_KEY_AUTO => Self::primary_key_auto(), + AttributeKind::PRIMARY_KEY_IDENTITY => Self::primary_key_identity(), + AttributeKind::AUTO_INC => return Err(()), + }) + } +} + +impl_deserialize!([] Constraints, de => Self::try_from(de.deserialize_u8()?) + .map_err(|_| de::Error::custom("invalid bitflags for `Constraints`")) +); +impl_serialize!([] Constraints, (self, ser) => ser.serialize_u8(self.bits())); + +impl_deserialize!([T: de::Deserialize<'de> + Clone] NonEmpty, de => { + let arr: Vec = de.deserialize_array(BasicVecVisitor)?; + Self::from_slice(&arr).ok_or_else(|| { + de::Error::custom(format!( + "invalid NonEmpty<{}>. Len is {}", + std::any::type_name::(), + arr.len() + )) + }) +}); +impl_serialize!([T: ser::Serialize] NonEmpty, (self, ser) => { + let mut arr = ser.serialize_array(self.len())?; + self.into_iter().try_for_each(|x| arr.serialize_element(x))?; + arr.end() +}); + +#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Display, de::Deserialize, ser::Serialize)] +#[sats(crate = crate)] +pub enum IndexType { + BTree = 0, + Hash = 1, +} + +impl From for u8 { + fn from(value: IndexType) -> Self { + value as u8 + } +} + +impl TryFrom for IndexType { + type Error = (); + fn try_from(v: u8) -> Result { + match v { + 0 => Ok(IndexType::BTree), + 1 => Ok(IndexType::Hash), + _ => Err(()), + } + } +} + +impl TryFrom<&str> for IndexType { + type Error = (); + fn try_from(v: &str) -> Result { + match v { + "BTree" => Ok(IndexType::BTree), + "Hash" => Ok(IndexType::Hash), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SequenceSchema { + pub sequence_id: SequenceId, + pub sequence_name: String, + pub table_id: TableId, + pub col_id: ColId, + pub increment: i128, + pub start: i128, + pub min_value: i128, + pub max_value: i128, + pub allocated: i128, +} + +/// This type is just the [SequenceSchema] without the autoinc fields +/// It's also adjusted to be convenient for specifying a new sequence +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SequenceDef { + pub sequence_name: String, + pub table_id: TableId, + pub col_id: ColId, + pub increment: i128, + pub start: Option, + pub min_value: Option, + pub max_value: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IndexSchema { + pub index_id: IndexId, + pub table_id: TableId, + pub index_name: String, + pub is_unique: bool, + pub cols: NonEmpty, + pub index_type: IndexType, +} + +/// This type is just the [IndexSchema] without the autoinc fields +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, de::Deserialize, ser::Serialize)] +#[sats(crate = crate)] +pub struct IndexDef { + pub table_id: TableId, + pub cols: NonEmpty, + pub name: String, + pub is_unique: bool, + pub index_type: IndexType, +} + +impl IndexDef { + pub fn new(name: String, table_id: TableId, col_id: ColId, is_unique: bool) -> Self { + Self { + cols: NonEmpty::new(col_id), + name, + is_unique, + table_id, + index_type: IndexType::BTree, + } + } + + pub fn new_cols>>(name: String, table_id: TableId, is_unique: bool, cols: Col) -> Self { + Self { + cols: cols.into(), + name, + is_unique, + table_id, + index_type: IndexType::BTree, + } + } +} + +impl From for IndexDef { + fn from(value: IndexSchema) -> Self { + Self { + table_id: value.table_id, + cols: value.cols, + name: value.index_name, + is_unique: value.is_unique, + index_type: value.index_type, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ColumnSchema { + pub table_id: TableId, + pub col_id: ColId, + pub col_name: String, + pub col_type: AlgebraicType, + pub is_autoinc: bool, +} + +impl From<&ColumnSchema> for ProductTypeElement { + fn from(value: &ColumnSchema) -> Self { + Self { + name: Some(value.col_name.clone()), + algebraic_type: value.col_type.clone(), + } + } +} + +#[derive(Clone)] +pub struct ColumnDefMeta { + pub column: ProductTypeElement, + pub attr: ColumnIndexAttribute, + pub pos: usize, +} + +impl From<&ColumnSchema> for ColumnDefMeta { + fn from(value: &ColumnSchema) -> Self { + Self { + column: ProductTypeElement::from(value), + // TODO(cloutiertyler): !!! This is not correct !!! We do not have the information regarding constraints here. + // We should remove this field from the ColumnDef struct. + attr: if value.is_autoinc { + ColumnIndexAttribute::AUTO_INC + } else { + ColumnIndexAttribute::UNSET + }, + pos: value.col_id.idx(), + } + } +} + +/// This type is just the [ColumnSchema] without the autoinc fields +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ColumnDef { + pub col_name: String, + pub col_type: AlgebraicType, + pub is_autoinc: bool, +} + +impl From for ColumnDef { + fn from(value: ColumnSchema) -> Self { + Self { + col_name: value.col_name, + col_type: value.col_type, + is_autoinc: value.is_autoinc, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConstraintSchema { + pub constraint_id: ConstraintId, + pub constraint_name: String, + pub kind: Constraints, + pub table_id: TableId, + pub columns: NonEmpty, +} + +/// This type is just the [ConstraintSchema] without the autoinc fields +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConstraintDef { + pub(crate) constraint_name: String, + pub(crate) kind: Constraints, + pub(crate) table_id: TableId, + pub(crate) columns: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TableSchema { + pub table_id: TableId, + pub table_name: String, + pub columns: Vec, + pub indexes: Vec, + pub constraints: Vec, + pub table_type: StTableType, + pub table_access: StAccess, +} + +impl TableSchema { + /// Check if the `name` of the [FieldName] exist on this [TableSchema] + /// + /// Warning: It ignores the `table_name` + pub fn get_column_by_field(&self, field: &FieldName) -> Option<&ColumnSchema> { + match field.field() { + FieldOnly::Name(x) => self.get_column_by_name(x), + FieldOnly::Pos(x) => self.get_column(x), + } + } + + /// Check if there is an index for this [FieldName] + /// + /// Warning: It ignores the `table_name` + pub fn get_index_by_field(&self, field: &FieldName) -> Option<&IndexSchema> { + let ColumnSchema { col_id, .. } = self.get_column_by_field(field)?; + self.indexes.iter().find( + |IndexSchema { + cols: NonEmpty { head: index_col, tail }, + .. + }| tail.is_empty() && index_col == col_id, + ) + } + + pub fn get_column(&self, pos: usize) -> Option<&ColumnSchema> { + self.columns.get(pos) + } + + /// Check if the `col_name` exist on this [TableSchema] + /// + /// Warning: It ignores the `table_name` + pub fn get_column_by_name(&self, col_name: &str) -> Option<&ColumnSchema> { + self.columns.iter().find(|x| x.col_name == col_name) + } + + /// Turn a [TableField] that could be an unqualified field `id` into `table.id` + pub fn normalize_field(&self, or_use: &TableField) -> FieldName { + FieldName::named(or_use.table.unwrap_or(&self.table_name), or_use.field) + } + + /// Project the fields from the supplied `columns`. + pub fn project(&self, columns: impl Iterator) -> Result, InvalidFieldError> { + columns + .map(|pos| { + self.get_column(usize::from(pos)).ok_or(InvalidFieldError { + index: pos.idx(), + name: None, + }) + }) + .collect() + } + + /// Utility for project the fields from the supplied `columns` that is a [NonEmpty], + /// used for when the list of field columns have at least one value. + pub fn project_not_empty(&self, columns: &NonEmpty) -> Result, InvalidFieldError> { + self.project(columns.iter().copied()) + } +} + +impl From<&TableSchema> for ProductType { + fn from(value: &TableSchema) -> Self { + ProductType::new( + value + .columns + .iter() + .map(|c| ProductTypeElement { + name: Some(c.col_name.clone()), + algebraic_type: c.col_type.clone(), + }) + .collect(), + ) + } +} + +impl From<&TableSchema> for DbTable { + fn from(value: &TableSchema) -> Self { + DbTable::new(value.into(), value.table_id, value.table_type, value.table_access) + } +} + +impl From for DbTable { + fn from(value: TableSchema) -> Self { + (&value).into() + } +} + +impl From<&TableSchema> for Header { + fn from(value: &TableSchema) -> Self { + Header::new( + value.table_name.clone(), + value + .columns + .iter() + .map(|x| { + let field = FieldName::named(&value.table_name, &x.col_name); + let is_indexed = value.get_index_by_field(&field).is_some(); + + Column::new(field, x.col_type.clone(), x.col_id, is_indexed) + }) + .collect(), + ) + } +} + +/// The magic table id zero, for use in [`IndexDef`]s. +/// +/// The actual table id is usually not yet known when constructing an +/// [`IndexDef`]. [`AUTO_TABLE_ID`] can be used instead, which the storage +/// engine will replace with the actual table id upon creation of the table +/// respectively index. +pub const AUTO_TABLE_ID: TableId = TableId(0); + +/// This type is just the [TableSchema] without the autoinc fields +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TableDef { + pub table_name: String, + pub columns: Vec, + pub indexes: Vec, + pub table_type: StTableType, + pub table_access: StAccess, +} + +impl TableDef { + pub fn get_row_type(&self) -> ProductType { + ProductType::new( + self.columns + .iter() + .map(|c| ProductTypeElement { + name: None, + algebraic_type: c.col_type.clone(), + }) + .collect(), + ) + } +} + +impl From for TableDef { + fn from(value: ProductType) -> Self { + Self { + table_name: "".to_string(), + columns: value + .elements + .iter() + .enumerate() + .map(|(i, e)| ColumnDef { + col_name: e.name.to_owned().unwrap_or_else(|| i.to_string()), + col_type: e.algebraic_type.clone(), + is_autoinc: false, + }) + .collect(), + indexes: vec![], + table_type: StTableType::User, + table_access: StAccess::Public, + } + } +} + +impl From<&TableSchema> for TableDef { + fn from(value: &TableSchema) -> Self { + Self { + table_name: value.table_name.clone(), + columns: value.columns.iter().cloned().map(Into::into).collect(), + indexes: value.indexes.iter().cloned().map(Into::into).collect(), + table_type: value.table_type, + table_access: value.table_access, + } + } +} + +impl From for TableDef { + fn from(value: TableSchema) -> Self { + Self { + table_name: value.table_name, + columns: value.columns.into_iter().map(Into::into).collect(), + indexes: value.indexes.into_iter().map(Into::into).collect(), + table_type: value.table_type, + table_access: value.table_access, + } + } +} + +/// For get the original `table_name` for where a [ColumnSchema] belongs. +#[derive(Debug, Clone)] +pub struct FieldDef { + pub column: ColumnSchema, + pub table_name: String, +} + +impl From for FieldName { + fn from(value: FieldDef) -> Self { + FieldName::named(&value.table_name, &value.column.col_name) + } +} + +impl From<&FieldDef> for FieldName { + fn from(value: &FieldDef) -> Self { + FieldName::named(&value.table_name, &value.column.col_name) + } +} + +impl From for ProductTypeElement { + fn from(value: FieldDef) -> Self { + let f: FieldName = (&value).into(); + ProductTypeElement::new(value.column.col_type, Some(f.to_string())) + } +} + +/// Describe the columns + meta attributes +/// TODO(cloutiertyler): This type should be deprecated and replaced with +/// ColumnDef or ColumnSchema where appropriate +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)] +pub struct ProductTypeMeta { + pub columns: ProductType, + pub attr: Vec, +} + +impl ProductTypeMeta { + pub fn new(columns: ProductType) -> Self { + Self { + attr: vec![ColumnIndexAttribute::UNSET; columns.elements.len()], + columns, + } + } + + pub fn with_capacity(capacity: usize) -> Self { + Self { + attr: Vec::with_capacity(capacity), + columns: ProductType::new(Vec::with_capacity(capacity)), + } + } + + pub fn clear(&mut self) { + self.columns.elements.clear(); + self.attr.clear(); + } + + pub fn push(&mut self, name: &str, ty: AlgebraicType, attr: ColumnIndexAttribute) { + self.columns + .elements + .push(ProductTypeElement::new(ty, Some(name.to_string()))); + self.attr.push(attr); + } + + /// Removes the data at position `index` and returns it. + /// + /// # Panics + /// + /// If `index` is out of bounds. + pub fn remove(&mut self, index: usize) -> (ProductTypeElement, ColumnIndexAttribute) { + (self.columns.elements.remove(index), self.attr.remove(index)) + } + + /// Return mutable references to the data at position `index`, or `None` if + /// the index is out of bounds. + pub fn get_mut(&mut self, index: usize) -> Option<(&mut ProductTypeElement, &mut ColumnIndexAttribute)> { + self.columns + .elements + .get_mut(index) + .and_then(|pte| self.attr.get_mut(index).map(|attr| (pte, attr))) + } + + pub fn with_attributes(iter: impl Iterator) -> Self { + let mut columns = Vec::new(); + let mut attrs = Vec::new(); + for (col, attr) in iter { + columns.push(col); + attrs.push(attr); + } + Self { + attr: attrs, + columns: ProductType::new(columns), + } + } + + pub fn len(&self) -> usize { + self.columns.elements.len() + } + + pub fn is_empty(&self) -> bool { + self.columns.elements.is_empty() + } + + pub fn iter(&self) -> impl Iterator + '_ { + self.columns + .elements + .iter() + .zip(self.attr.iter()) + .enumerate() + .map(|(pos, (column, attr))| ColumnDefMeta { + column: column.clone(), + attr: *attr, + pos, + }) + } + + pub fn with_defaults<'a>( + &'a self, + row: &'a mut ProductValue, + ) -> impl Iterator + 'a { + self.iter() + .zip(row.elements.iter_mut()) + .filter(|(col, _)| col.attr.has_autoinc()) + } +} + +impl From for ProductTypeMeta { + fn from(value: ProductType) -> Self { + ProductTypeMeta::new(value) + } +} + +impl From for ProductType { + fn from(value: ProductTypeMeta) -> Self { + value.columns + } +} diff --git a/crates/sats/src/db/error.rs b/crates/sats/src/db/error.rs new file mode 100644 index 00000000000..9a2f1fe2f16 --- /dev/null +++ b/crates/sats/src/db/error.rs @@ -0,0 +1,135 @@ +use crate::db::def::IndexType; +use crate::product_value::InvalidFieldError; +use crate::relation::{FieldName, Header}; +use crate::{buffer, AlgebraicType, AlgebraicValue}; +use derive_more::Display; +use std::string::FromUtf8Error; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TypeError { + #[error("Arrays must be homogeneous. It expects to be `{{expect.to_satns()}}` but `{{value.to_satns()}}` is of type `{{found.to_satns()}}`")] + Array { + expect: AlgebraicType, + found: AlgebraicType, + value: AlgebraicValue, + }, + #[error("Arrays must define a type for the elements")] + ArrayEmpty, + #[error("Maps must be homogeneous. It expects to be `{{key_expect.to_satns()}}:{{value_expect.to_satns()}}` but `{{key.to_satns()}}::{{value.to_satns()}}` is of type `{{key_found.to_satns()}}:{{value_found.to_satns()}}`")] + Map { + key_expect: AlgebraicType, + value_expect: AlgebraicType, + key_found: AlgebraicType, + value_found: AlgebraicType, + key: AlgebraicValue, + value: AlgebraicValue, + }, + #[error("Maps must define a type for both key & value")] + MapEmpty, +} + +#[derive(Error, Debug, Clone)] +pub enum DecodeError { + #[error("Decode UTF8: {0}")] + Utf8(#[from] FromUtf8Error), + #[error("AlgebraicType::decode: Unknown: {0}")] + AlgebraicTypeUnknown(u8), + #[error("AlgebraicType::decode: Byte array has invalid length: {0:?}")] + AlgebraicType(usize), + #[error("SumType::decode: Byte array has invalid length: {0:?}")] + SumType(usize), + #[error("ProductType::decode: Byte array has invalid length: {0:?}")] + ProductType(usize), + #[error("ProductTypeElement::decode: Byte array has invalid length: {0:?}")] + ProductTypeElement(usize), + #[error("AlgebraicValue::decode: byte array length not long enough to decode {0:?}")] + AlgebraicValue(AlgebraicType), + #[error("AlgebraicValue::decode: byte array length not long enough to get length of {0:?}")] + AlgebraicValueGetLength(AlgebraicType), + #[error( + "AlgebraicValue::decode: buffer has no room to decode any more elements from this {kind:?}. (len: {len} <= read:{read})" + )] + AlgebraicValueRoom { + kind: AlgebraicType, + len: usize, + read: usize, + }, + #[error("AlgebraicValue::decode: Cannot decode {kind:?}, buffer not long enough. (len: {len}, read:{read})")] + TypeBufferSmall { + kind: AlgebraicType, + len: usize, + read: usize, + }, + #[error( + "AlgebraicValue::decode: byte array length not long enough to decode {kind:?}. (expect: {expect}, read:{read})" + )] + TypeTooSmall { + kind: AlgebraicType, + expect: usize, + read: usize, + }, + #[error("EnumValue::decode: Byte array length is invalid.")] + EnumValue, +} + +#[derive(Error, Debug, Clone)] +pub enum LibError { + #[error("DecodeError: {0}")] + Decode(#[from] DecodeError), + #[error("BufferError: {0}")] + Buffer(#[from] buffer::DecodeError), + #[error(transparent)] + TupleFieldInvalid(#[from] InvalidFieldError), +} + +#[derive(thiserror::Error, Debug)] +pub enum AuthError { + #[error("Table `{named}` is private")] + TablePrivate { named: String }, + #[error("Index `{named}` is private")] + IndexPrivate { named: String }, + #[error("Sequence `{named}` is private")] + SequencePrivate { named: String }, + #[error("Only the database owner can perform the requested operation")] + OwnerRequired, +} + +#[derive(thiserror::Error, Debug)] +pub enum RelationError { + #[error("Field `{1}` not found. Must be one of {0}")] + FieldNotFound(Header, FieldName), + #[error("Field `{0}` fail to infer the type: {1}")] + TypeInference(FieldName, TypeError), + #[error("Field declaration only support `table.field` or `field`. It gets instead `{0}`")] + FieldPathInvalid(String), + #[error("Field `{1}` not found at position {0}")] + FieldNotFoundAtPos(usize, FieldName), +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Display)] +pub enum DefType { + Table, + Column, + Index, + Sequence, + Constraint, +} + +#[derive(thiserror::Error, Debug)] +pub enum SchemaError { + #[error("Multiple primary columns defined for table: {0}")] + MultiplePrimaryKeys(String), + #[error("table id `{table_id}` should have name")] + EmptyTableName { table_id: u32 }, + #[error("table `{table}` {ty} should have name. {ty} id: {id}")] + EmptyName { table: String, ty: DefType, id: u32 }, + #[error("Attempt to define a column with more than 1 auto_inc sequence: Table: `{table}`, Field: `{field}`")] + OneAutoInc { table: String, field: String }, + #[error("Only Btree Indexes are supported: Table: `{table}`, Index: `{index}` is a `{index_type}`")] + OnlyBtree { + table: String, + index: String, + index_type: IndexType, + }, +} diff --git a/crates/sats/src/db/mod.rs b/crates/sats/src/db/mod.rs new file mode 100644 index 00000000000..bee72d5d586 --- /dev/null +++ b/crates/sats/src/db/mod.rs @@ -0,0 +1,6 @@ +//! Defines all the typed database objects & schemas. + +pub mod attr; +pub mod auth; +pub mod def; +pub mod error; diff --git a/crates/lib/src/hash.rs b/crates/sats/src/hash.rs similarity index 91% rename from crates/lib/src/hash.rs rename to crates/sats/src/hash.rs index 1597e47a232..c3d1ec4f79f 100644 --- a/crates/lib/src/hash.rs +++ b/crates/sats/src/hash.rs @@ -1,8 +1,7 @@ +use crate::hex::HexString; +use crate::{impl_deserialize, impl_serialize, impl_st, AlgebraicType}; use core::fmt; use sha3::{Digest, Keccak256}; -use spacetimedb_sats::{impl_deserialize, impl_serialize, impl_st, AlgebraicType}; - -use crate::hex::HexString; pub const HASH_SIZE: usize = 32; @@ -85,12 +84,12 @@ impl hex::FromHex for Hash { #[cfg(feature = "serde")] impl serde::Serialize for Hash { fn serialize(&self, serializer: S) -> Result { - spacetimedb_sats::ser::serde::serialize_to(self, serializer) + crate::ser::serde::serialize_to(self, serializer) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for Hash { fn deserialize>(deserializer: D) -> Result { - spacetimedb_sats::de::serde::deserialize_from(deserializer) + crate::de::serde::deserialize_from(deserializer) } } diff --git a/crates/lib/src/hex.rs b/crates/sats/src/hex.rs similarity index 100% rename from crates/lib/src/hex.rs rename to crates/sats/src/hex.rs diff --git a/crates/sats/src/lib.rs b/crates/sats/src/lib.rs index 242d663fa90..5a45700326c 100644 --- a/crates/sats/src/lib.rs +++ b/crates/sats/src/lib.rs @@ -7,13 +7,18 @@ pub mod bsatn; pub mod buffer; pub mod builtin_type; pub mod convert; +pub mod data_key; +pub mod db; pub mod de; +pub mod hash; +pub mod hex; pub mod map_type; pub mod map_value; pub mod meta_type; pub mod product_type; pub mod product_type_element; pub mod product_value; +pub mod relation; mod resolve_refs; pub mod satn; pub mod ser; @@ -28,6 +33,7 @@ pub use algebraic_value::{AlgebraicValue, F32, F64}; pub use array_type::ArrayType; pub use array_value::ArrayValue; pub use builtin_type::BuiltinType; +pub use data_key::DataKey; pub use map_type::MapType; pub use map_value::MapValue; pub use product_type::ProductType; diff --git a/crates/sats/src/product_value.rs b/crates/sats/src/product_value.rs index cec0a807fac..a131484a215 100644 --- a/crates/sats/src/product_value.rs +++ b/crates/sats/src/product_value.rs @@ -64,10 +64,10 @@ impl crate::Value for ProductValue { /// An error that occurs when a field, of a product value, is accessed that doesn't exist. #[derive(thiserror::Error, Debug, Copy, Clone)] -#[error("Field at position {col_pos} named: {name:?} not found or has an invalid type")] +#[error("Field at position {index} named: {name:?} not found or has an invalid type")] pub struct InvalidFieldError { - /// The claimed col_pos of the field within the product value. - pub col_pos: ColId, + /// The claimed index of the field within the product value. + pub index: usize, /// The name of the field, if any. pub name: Option<&'static str>, } @@ -77,10 +77,7 @@ impl ProductValue { /// /// The `name` is non-functional and is only used for error-messages. pub fn get_field(&self, index: usize, name: Option<&'static str>) -> Result<&AlgebraicValue, InvalidFieldError> { - self.elements.get(index).ok_or(InvalidFieldError { - col_pos: index.into(), - name, - }) + self.elements.get(index).ok_or(InvalidFieldError { index, name }) } /// This function is used to project fields based on the provided `indexes`. @@ -133,10 +130,7 @@ impl ProductValue { name: Option<&'static str>, f: impl 'a + Fn(&'a AlgebraicValue) -> Option, ) -> Result { - f(self.get_field(index, name)?).ok_or(InvalidFieldError { - col_pos: index.into(), - name, - }) + f(self.get_field(index, name)?).ok_or(InvalidFieldError { index, name }) } /// Interprets the value at field of `self` identified by `index` as a `bool`. @@ -184,7 +178,7 @@ impl ProductValue { self.extract_field(index, named, |f| f.as_bytes()) } - /// Interprets the value at field of `self` identified by `index` as an `ArrayValue`. + /// Interprets the value at field of `self` identified by `index` as a array. pub fn field_as_array(&self, index: usize, named: Option<&'static str>) -> Result<&ArrayValue, InvalidFieldError> { self.extract_field(index, named, |f| f.as_array()) } diff --git a/crates/lib/src/relation.rs b/crates/sats/src/relation.rs similarity index 96% rename from crates/lib/src/relation.rs rename to crates/sats/src/relation.rs index da1f35e3f47..a733a54b8b1 100644 --- a/crates/lib/src/relation.rs +++ b/crates/sats/src/relation.rs @@ -1,28 +1,17 @@ use derive_more::From; +use spacetimedb_primitives::{ColId, TableId}; use std::cmp::Ordering; use std::collections::hash_map::DefaultHasher; use std::fmt; use std::hash::{Hash, Hasher}; -use crate::auth::{StAccess, StTableType}; -use crate::error::RelationError; -use crate::table::ColumnDef; -use crate::DataKey; -use spacetimedb_primitives::{ColId, TableId}; -use spacetimedb_sats::algebraic_value::AlgebraicValue; -use spacetimedb_sats::product_value::ProductValue; -use spacetimedb_sats::satn::Satn; -use spacetimedb_sats::{algebraic_type, AlgebraicType, ProductType, ProductTypeElement, Typespace, WithTypespace}; - -impl ColumnDef { - pub fn name(&self) -> FieldOnly { - if let Some(name) = &self.column.name { - FieldOnly::Name(name) - } else { - FieldOnly::Pos(self.pos) - } - } -} +use crate::algebraic_value::AlgebraicValue; +use crate::data_key::DataKey; +use crate::db::auth::{StAccess, StTableType}; +use crate::db::error::RelationError; +use crate::product_value::ProductValue; +use crate::satn::Satn; +use crate::{algebraic_type, AlgebraicType, ProductType, ProductTypeElement, Typespace, WithTypespace}; pub fn calculate_hash(t: &T) -> u64 { let mut s = DefaultHasher::new(); diff --git a/crates/sqltest/src/space.rs b/crates/sqltest/src/space.rs index fe773c9d727..13e66f65cf3 100644 --- a/crates/sqltest/src/space.rs +++ b/crates/sqltest/src/space.rs @@ -6,8 +6,8 @@ use spacetimedb::execution_context::ExecutionContext; use spacetimedb::sql::compiler::compile_sql; use spacetimedb::sql::execute::execute_sql; use spacetimedb_lib::identity::AuthCtx; -use spacetimedb_lib::relation::MemTable; use spacetimedb_sats::meta_type::MetaType; +use spacetimedb_sats::relation::MemTable; use spacetimedb_sats::satn::Satn; use spacetimedb_sats::{AlgebraicType, AlgebraicValue, BuiltinType}; use sqllogictest::{AsyncDB, ColumnType, DBOutput}; diff --git a/crates/vm/src/dsl.rs b/crates/vm/src/dsl.rs index dc2cdc27bf6..8192d6d48bd 100644 --- a/crates/vm/src/dsl.rs +++ b/crates/vm/src/dsl.rs @@ -1,12 +1,13 @@ //! Utilities for build valid constructs for the vm. +use std::collections::HashMap; + use crate::expr::{Expr, QueryExpr, SourceExpr}; use crate::operator::*; -use spacetimedb_lib::auth::{StAccess, StTableType}; -use spacetimedb_lib::relation::{DbTable, Header, MemTable}; use spacetimedb_primitives::TableId; use spacetimedb_sats::algebraic_value::AlgebraicValue; +use spacetimedb_sats::db::auth::{StAccess, StTableType}; use spacetimedb_sats::product_value::ProductValue; -use std::collections::HashMap; +use spacetimedb_sats::relation::{DbTable, Header, MemTable}; pub fn scalar>(of: T) -> AlgebraicValue { of.into() diff --git a/crates/vm/src/errors.rs b/crates/vm/src/errors.rs index 7ec6cfddf9b..a94fcd40628 100644 --- a/crates/vm/src/errors.rs +++ b/crates/vm/src/errors.rs @@ -1,6 +1,6 @@ use crate::operator::{Op, OpLogic}; use crate::types::Ty; -use spacetimedb_lib::error::{AuthError, RelationError}; +use spacetimedb_sats::db::error::{AuthError, RelationError}; use spacetimedb_sats::AlgebraicValue; use std::fmt; use thiserror::Error; diff --git a/crates/vm/src/eval.rs b/crates/vm/src/eval.rs index c65a08512aa..9231b5ebafd 100644 --- a/crates/vm/src/eval.rs +++ b/crates/vm/src/eval.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; -use spacetimedb_lib::relation::{FieldExpr, MemTable, RelIter, Relation, Table}; use spacetimedb_sats::algebraic_type::AlgebraicType; use spacetimedb_sats::algebraic_value::AlgebraicValue; +use spacetimedb_sats::relation::{FieldExpr, MemTable, RelIter, Relation, Table}; use spacetimedb_sats::{product, ProductType, ProductValue}; use crate::dsl::{bin_op, call_fn, if_, mem_table, scalar, var}; @@ -561,10 +561,10 @@ mod tests { use super::*; use crate::dsl::{prefix_op, query, value}; use crate::program::Program; - use spacetimedb_lib::auth::StAccess; - use spacetimedb_lib::error::RelationError; use spacetimedb_lib::identity::AuthCtx; - use spacetimedb_lib::relation::{FieldName, MemTable, RelValue}; + use spacetimedb_sats::db::auth::StAccess; + use spacetimedb_sats::db::error::RelationError; + use spacetimedb_sats::relation::{FieldName, MemTable, RelValue}; fn fib(n: u64) -> u64 { if n < 2 { diff --git a/crates/vm/src/expr.rs b/crates/vm/src/expr.rs index 9e99e5da4fa..3f2ec8e24b2 100644 --- a/crates/vm/src/expr.rs +++ b/crates/vm/src/expr.rs @@ -1,14 +1,15 @@ use derive_more::From; -use spacetimedb_lib::auth::{StAccess, StTableType}; -use spacetimedb_lib::error::AuthError; -use spacetimedb_lib::relation::{ - Column, DbTable, FieldExpr, FieldName, Header, MemTable, RelValueRef, Relation, RowCount, Table, -}; -use spacetimedb_lib::table::ProductTypeMeta; + use spacetimedb_lib::Identity; use spacetimedb_primitives::{ColId, TableId}; use spacetimedb_sats::algebraic_type::AlgebraicType; use spacetimedb_sats::algebraic_value::AlgebraicValue; +use spacetimedb_sats::db::auth::{StAccess, StTableType}; +use spacetimedb_sats::db::def::{ProductTypeMeta, TableSchema}; +use spacetimedb_sats::db::error::AuthError; +use spacetimedb_sats::relation::{ + Column, DbTable, FieldExpr, FieldName, Header, MemTable, RelValueRef, Relation, RowCount, Table, +}; use spacetimedb_sats::satn::Satn; use spacetimedb_sats::{ProductValue, Typespace, WithTypespace}; use std::cmp::Ordering; @@ -285,33 +286,6 @@ pub enum SourceExpr { DbTable(DbTable), } -impl From for SourceExpr { - fn from(value: Table) -> Self { - match value { - Table::MemTable(t) => SourceExpr::MemTable(t), - Table::DbTable(t) => SourceExpr::DbTable(t), - } - } -} - -impl From for Table { - fn from(value: SourceExpr) -> Self { - match value { - SourceExpr::MemTable(t) => Table::MemTable(t), - SourceExpr::DbTable(t) => Table::DbTable(t), - } - } -} - -impl From<&SourceExpr> for DbTable { - fn from(value: &SourceExpr) -> Self { - match value { - SourceExpr::MemTable(_) => unreachable!(), - SourceExpr::DbTable(t) => t.clone(), - } - } -} - impl SourceExpr { pub fn get_db_table(&self) -> Option<&DbTable> { match self { @@ -369,6 +343,44 @@ impl Relation for SourceExpr { } } +impl From
for SourceExpr { + fn from(value: Table) -> Self { + match value { + Table::MemTable(t) => SourceExpr::MemTable(t), + Table::DbTable(t) => SourceExpr::DbTable(t), + } + } +} + +impl From for Table { + fn from(value: SourceExpr) -> Self { + match value { + SourceExpr::MemTable(t) => Table::MemTable(t), + SourceExpr::DbTable(t) => Table::DbTable(t), + } + } +} + +impl From<&TableSchema> for SourceExpr { + fn from(value: &TableSchema) -> Self { + SourceExpr::DbTable(DbTable::new( + value.into(), + value.table_id, + value.table_type, + value.table_access, + )) + } +} + +impl From<&SourceExpr> for DbTable { + fn from(value: &SourceExpr) -> Self { + match value { + SourceExpr::MemTable(_) => unreachable!(), + SourceExpr::DbTable(t) => t.clone(), + } + } +} + // A descriptor for an index join operation. // The semantics are that of a semi-join with rows from the index side being returned. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)] diff --git a/crates/vm/src/iterators.rs b/crates/vm/src/iterators.rs index 1269edcbddf..c96843ea2a5 100644 --- a/crates/vm/src/iterators.rs +++ b/crates/vm/src/iterators.rs @@ -1,7 +1,7 @@ use crate::errors::ErrorVm; use crate::rel_ops::RelOps; -use spacetimedb_lib::relation::{Header, MemTable, RelIter, RelValue, RowCount}; use spacetimedb_sats::product_value::ProductValue; +use spacetimedb_sats::relation::{Header, MemTable, RelIter, RelValue, RowCount}; impl RelOps for RelIter { fn head(&self) -> &Header { diff --git a/crates/vm/src/program.rs b/crates/vm/src/program.rs index 20ffba5bb00..c9c378a5eab 100644 --- a/crates/vm/src/program.rs +++ b/crates/vm/src/program.rs @@ -2,7 +2,7 @@ //! //! It carries an [EnvDb] with the functions, idents, types. use spacetimedb_lib::identity::AuthCtx; -use spacetimedb_lib::relation::{MemTable, RelIter, Relation, Table}; +use spacetimedb_sats::relation::{MemTable, RelIter, Relation, Table}; use std::collections::HashMap; use crate::env::EnvDb; diff --git a/crates/vm/src/rel_ops.rs b/crates/vm/src/rel_ops.rs index f9d6bebceaa..8bdb7a4d22e 100644 --- a/crates/vm/src/rel_ops.rs +++ b/crates/vm/src/rel_ops.rs @@ -1,6 +1,6 @@ use crate::errors::ErrorVm; -use spacetimedb_lib::relation::{FieldExpr, Header, RelValue, RelValueRef, RowCount}; use spacetimedb_sats::product_value::ProductValue; +use spacetimedb_sats::relation::{FieldExpr, Header, RelValue, RelValueRef, RowCount}; use std::collections::HashMap; pub(crate) trait ResultExt {