diff --git a/Cargo.lock b/Cargo.lock index ff6ee7d53c..1df1788067 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4075,6 +4075,7 @@ dependencies = [ name = "spacetimedb-bindings-macro" version = "0.6.1" dependencies = [ + "bitflags 2.3.3", "humantime", "proc-macro2", "quote", @@ -4242,6 +4243,7 @@ name = "spacetimedb-lib" version = "0.6.1" dependencies = [ "anyhow", + "bitflags 2.3.3", "bytes", "chrono", "clap 4.3.10", diff --git a/Cargo.toml b/Cargo.toml index 45270e5a94..bffd2cbea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ axum = "0.6" arrayvec = "0.7.2" backtrace = "0.3.66" base64 = "0.21.2" +bitflags = "2.3.3" byte-unit = "4.0.18" bytes = "1.2.1" bytestring = { version = "1.2.0", features = ["serde"] } diff --git a/crates/bindings-macro/Cargo.toml b/crates/bindings-macro/Cargo.toml index 9be1b9662a..e69003c8b5 100644 --- a/crates/bindings-macro/Cargo.toml +++ b/crates/bindings-macro/Cargo.toml @@ -11,6 +11,7 @@ proc-macro = true bench = false [dependencies] +bitflags.workspace = true humantime.workspace = true proc-macro2.workspace = true quote.workspace = true diff --git a/crates/bindings-macro/src/lib.rs b/crates/bindings-macro/src/lib.rs index 265e5a6508..de357fd8b9 100644 --- a/crates/bindings-macro/src/lib.rs +++ b/crates/bindings-macro/src/lib.rs @@ -14,6 +14,7 @@ extern crate proc_macro; use std::collections::HashMap; use std::time::Duration; +use bitflags::{bitflags, Flags}; use module::{derive_deserialize, derive_satstype, derive_serialize}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, TokenStreamExt}; @@ -442,22 +443,24 @@ struct Column<'a> { attr: ColumnIndexAttribute, } -#[derive(Debug)] -enum ColumnIndexAttribute { - UnSet = 0, - /// Unique + AutoInc - Identity = 1, - /// Index unique - Unique = 2, - /// Index no unique - #[allow(unused)] - Indexed = 3, - /// Generate the next [Sequence] - AutoInc = 4, - /// Primary key column (implies Unique) - PrimaryKey = 5, - /// PrimaryKey + AutoInc - PrimaryKeyAuto = 6, +// 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 { @@ -551,35 +554,27 @@ fn spacetimedb_tabletype_impl(item: syn::DeriveInput) -> syn::Result match col_attr { - UnSet => col_attr = Unique, - Identity | Unique | PrimaryKey | PrimaryKeyAuto => return Err(duplicate(span)), - Indexed => unreachable!(), - AutoInc => col_attr = Identity, - }, - ColumnAttr::Autoinc(span) => match col_attr { - UnSet => col_attr = AutoInc, - Identity | AutoInc | PrimaryKeyAuto => return Err(duplicate(span)), - Unique => col_attr = Identity, - Indexed => unreachable!(), - PrimaryKey => col_attr = PrimaryKeyAuto, - }, - ColumnAttr::Primarykey(span) => match col_attr { - UnSet => col_attr = PrimaryKey, - Identity | Unique | PrimaryKey | PrimaryKeyAuto => return Err(duplicate(span)), - AutoInc => col_attr = PrimaryKeyAuto, - Indexed => unreachable!(), - }, + let (extra_col_attr, span) = match attr { + ColumnAttr::Unique(span) => (ColumnIndexAttribute::UNIQUE, span), + ColumnAttr::Autoinc(span) => (ColumnIndexAttribute::AUTO_INC, span), + ColumnAttr::Primarykey(span) => (ColumnIndexAttribute::PRIMARY_KEY, span), + }; + // do those attributes intersect (not counting the INDEXED bit which is present in all attributes)? + // this will check that no two attributes both have UNIQUE, AUTOINC or PRIMARY_KEY bits set + if !(col_attr & extra_col_attr) + .difference(ColumnIndexAttribute::INDEXED) + .is_empty() + { + return Err(duplicate(span)); } + col_attr |= extra_col_attr; } - if matches!(col_attr, AutoInc | Identity | PrimaryKeyAuto) { + if col_attr.contains(ColumnIndexAttribute::AUTO_INC) { let valid_for_autoinc = if let syn::Type::Path(p) = field.ty { // TODO: this is janky as heck matches!( @@ -631,15 +626,9 @@ fn spacetimedb_tabletype_impl(item: syn::DeriveInput) -> syn::Result, Vec<_>) = columns.iter().partition(|x| { - matches!( - x.attr, - ColumnIndexAttribute::Identity - | ColumnIndexAttribute::Unique - | ColumnIndexAttribute::PrimaryKey - | ColumnIndexAttribute::PrimaryKeyAuto - ) - }); + let (unique_columns, nonunique_columns): (Vec<_>, Vec<_>) = columns + .iter() + .partition(|x| x.attr.contains(ColumnIndexAttribute::UNIQUE)); let has_unique = !unique_columns.is_empty(); @@ -746,9 +735,15 @@ fn spacetimedb_tabletype_impl(item: syn::DeriveInput) -> syn::Result for spacetimedb_lib::table::ColumnDef { // 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::AutoInc + spacetimedb_lib::ColumnIndexAttribute::AUTO_INC } else { - spacetimedb_lib::ColumnIndexAttribute::UnSet + spacetimedb_lib::ColumnIndexAttribute::UNSET }, // if value.is_autoinc && value.is_unique { // spacetimedb_lib::ColumnIndexAttribute::Identity diff --git a/crates/core/src/db/relational_db.rs b/crates/core/src/db/relational_db.rs index 80db98e74a..31c68020ae 100644 --- a/crates/core/src/db/relational_db.rs +++ b/crates/core/src/db/relational_db.rs @@ -334,13 +334,18 @@ impl RelationalDB { return Ok(None); }; let unique_index = table.indexes.iter().find(|x| x.col_id == col_id).map(|x| x.is_unique); - Ok(Some(match (column.is_autoinc, unique_index) { - (true, Some(true)) => ColumnIndexAttribute::Identity, - (true, Some(false) | None) => ColumnIndexAttribute::AutoInc, - (false, Some(true)) => ColumnIndexAttribute::Unique, - (false, Some(false)) => ColumnIndexAttribute::Indexed, - (false, None) => ColumnIndexAttribute::UnSet, - })) + let mut attr = ColumnIndexAttribute::UNSET; + if column.is_autoinc { + attr |= ColumnIndexAttribute::AUTO_INC; + } + if let Some(is_unique) = unique_index { + attr |= if is_unique { + ColumnIndexAttribute::UNIQUE + } else { + ColumnIndexAttribute::INDEXED + }; + } + Ok(Some(attr)) } #[tracing::instrument(skip_all)] diff --git a/crates/core/src/host/wasmer/wasmer_module.rs b/crates/core/src/host/wasmer/wasmer_module.rs index 6309c2a135..716f4faed6 100644 --- a/crates/core/src/host/wasmer/wasmer_module.rs +++ b/crates/core/src/host/wasmer/wasmer_module.rs @@ -45,7 +45,7 @@ impl WasmerModule { WasmerModule { module, engine } } - pub const IMPLEMENTED_ABI: abi::VersionTuple = abi::VersionTuple::new(3, 0); + pub const IMPLEMENTED_ABI: abi::VersionTuple = abi::VersionTuple::new(4, 0); fn imports(&self, store: &mut Store, env: &FunctionEnv) -> Imports { const _: () = assert!(WasmerModule::IMPLEMENTED_ABI.eq(spacetimedb_lib::MODULE_ABI_VERSION)); diff --git a/crates/core/src/sql/ast.rs b/crates/core/src/sql/ast.rs index 9169437c09..351dbb2cf5 100644 --- a/crates/core/src/sql/ast.rs +++ b/crates/core/src/sql/ast.rs @@ -766,7 +766,7 @@ fn column_def_type(named: &String, is_null: bool, data_type: &DataType) -> Resul /// Extract the column attributes into [ColumnIndexAttribute] fn compile_column_option(col: &SqlColumnDef) -> Result<(bool, ColumnIndexAttribute), PlanError> { - let mut attr = ColumnIndexAttribute::UnSet; + let mut attr = ColumnIndexAttribute::UNSET; let mut is_null = false; for x in &col.options { @@ -778,11 +778,11 @@ fn compile_column_option(col: &SqlColumnDef) -> Result<(bool, ColumnIndexAttribu is_null = false; } ColumnOption::Unique { is_primary } => { - if *is_primary { - attr = ColumnIndexAttribute::PrimaryKey + attr = if *is_primary { + ColumnIndexAttribute::PRIMARY_KEY } else { - attr = ColumnIndexAttribute::Unique - } + ColumnIndexAttribute::UNIQUE + }; } ColumnOption::Generated { generated_as, @@ -793,11 +793,7 @@ fn compile_column_option(col: &SqlColumnDef) -> Result<(bool, ColumnIndexAttribu match generated_as { GeneratedAs::ByDefault => { - if attr == ColumnIndexAttribute::PrimaryKey { - attr = ColumnIndexAttribute::PrimaryKeyAuto - } else { - attr = ColumnIndexAttribute::Identity - } + attr |= ColumnIndexAttribute::IDENTITY; } x => { return Err(PlanError::Unsupported { diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 6bb97b5bc1..fb3749c232 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -23,6 +23,7 @@ spacetimedb-bindings-macro = { path = "../bindings-macro", version = "0.6.1" } spacetimedb-sats = { path = "../sats", version = "0.6.1" } anyhow.workspace = true +bitflags.workspace = true chrono = { workspace = true, optional = true } clap = {workspace = true, optional = true } enum-as-inner.workspace = true diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index f4e5df2ec0..d9c7e4eaee 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -41,7 +41,7 @@ pub use type_value::{AlgebraicValue, ProductValue}; pub use spacetimedb_sats as sats; -pub const MODULE_ABI_VERSION: VersionTuple = VersionTuple::new(3, 0); +pub const MODULE_ABI_VERSION: VersionTuple = VersionTuple::new(4, 0); // if it ends up we need more fields in the future, we can split one of them in two #[derive(PartialEq, Eq, Copy, Clone, Debug)] @@ -205,36 +205,34 @@ pub enum IndexType { Hash, } -#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, de::Deserialize, ser::Serialize)] -pub enum ColumnIndexAttribute { - #[default] - UnSet = 0, - /// Unique + AutoInc - Identity = 1, - /// Index unique - Unique = 2, - /// Index no unique - Indexed = 3, - /// Generate the next [Sequence] - AutoInc = 4, - /// Primary key column (implies Unique) - PrimaryKey = 5, - /// PrimaryKey + AutoInc - PrimaryKeyAuto = 6, +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 is_unique(self) -> bool { - matches!( - self, - Self::Identity | Self::Unique | Self::PrimaryKey | Self::PrimaryKeyAuto - ) + self.contains(Self::UNIQUE) } pub const fn is_autoinc(self) -> bool { - matches!(self, Self::Identity | Self::AutoInc | Self::PrimaryKeyAuto) + self.contains(Self::AUTO_INC) } pub const fn is_primary(self) -> bool { - matches!(self, Self::PrimaryKey | Self::PrimaryKeyAuto) + self.contains(Self::PRIMARY_KEY) } } @@ -242,15 +240,19 @@ impl TryFrom for ColumnIndexAttribute { type Error = (); fn try_from(v: u8) -> Result { - match v { - 0 => Ok(Self::UnSet), - 1 => Ok(Self::Identity), - 2 => Ok(Self::Unique), - 3 => Ok(Self::Indexed), - 4 => Ok(Self::AutoInc), - 5 => Ok(Self::PrimaryKey), - 6 => Ok(Self::PrimaryKeyAuto), - _ => Err(()), - } + 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/table.rs b/crates/lib/src/table.rs index 887c87c6d6..49765f49c6 100644 --- a/crates/lib/src/table.rs +++ b/crates/lib/src/table.rs @@ -20,7 +20,7 @@ pub struct ProductTypeMeta { impl ProductTypeMeta { pub fn new(columns: ProductType) -> Self { Self { - attr: vec![ColumnIndexAttribute::UnSet; columns.elements.len()], + attr: vec![ColumnIndexAttribute::UNSET; columns.elements.len()], columns, } } @@ -100,12 +100,9 @@ impl ProductTypeMeta { &'a self, row: &'a mut ProductValue, ) -> impl Iterator + 'a { - self.iter().zip(row.elements.iter_mut()).filter(|(col, _)| { - matches!( - col.attr, - ColumnIndexAttribute::Identity | ColumnIndexAttribute::AutoInc | ColumnIndexAttribute::PrimaryKeyAuto - ) - }) + self.iter() + .zip(row.elements.iter_mut()) + .filter(|(col, _)| col.attr.is_autoinc()) } }