Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ColumnIndexAttribute bitflags in the ABI #212

Merged
merged 1 commit into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
1 change: 1 addition & 0 deletions crates/bindings-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ proc-macro = true
bench = false

[dependencies]
bitflags.workspace = true
humantime.workspace = true
proc-macro2.workspace = true
quote.workspace = true
Expand Down
97 changes: 46 additions & 51 deletions crates/bindings-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<TokenStream> {
Expand Down Expand Up @@ -551,35 +554,27 @@ fn spacetimedb_tabletype_impl(item: syn::DeriveInput) -> syn::Result<TokenStream
.try_into()
.map_err(|_| syn::Error::new_spanned(field.ident, "too many columns; the most a table can have is 256"))?;

use ColumnIndexAttribute::*;
let mut col_attr = UnSet;
let mut col_attr = ColumnIndexAttribute::UNSET;
for attr in field.original_attrs {
let Some(attr) = ColumnAttr::parse(attr)? else { continue };
let duplicate = |span| syn::Error::new(span, "duplicate attribute");
match attr {
ColumnAttr::Unique(span) => 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!(
Expand Down Expand Up @@ -631,15 +626,9 @@ fn spacetimedb_tabletype_impl(item: syn::DeriveInput) -> syn::Result<TokenStream
}));
}

let (unique_columns, nonunique_columns): (Vec<_>, 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();

Expand Down Expand Up @@ -746,9 +735,15 @@ fn spacetimedb_tabletype_impl(item: syn::DeriveInput) -> syn::Result<TokenStream
let deserialize_impl = derive_deserialize(&sats_ty);
let serialize_impl = derive_serialize(&sats_ty);
let schema_impl = derive_satstype(&sats_ty, false);
let column_attrs = columns
.iter()
.map(|col| Ident::new(&format!("{:?}", col.attr), Span::call_site()));
let column_attrs = columns.iter().map(|col| {
Ident::new(
ColumnIndexAttribute::FLAGS
.iter()
.find_map(|f| (col.attr == *f.value()).then_some(f.name()))
.expect("Invalid column attribute"),
Span::call_site(),
)
});
let tabletype_impl = quote! {
impl spacetimedb::TableType for #original_struct_ident {
const TABLE_NAME: &'static str = #table_name;
Expand Down
2 changes: 1 addition & 1 deletion crates/bindings-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use alloc::boxed::Box;
/// can run a module declaring `X.Y` if and only if `X == A && Y <= B`.
/// So, the minor version is intended for backwards-compatible changes, e.g. adding a new function,
/// and the major version is for fully breaking changes.
pub const ABI_VERSION: u32 = 0x0003_0000;
pub const ABI_VERSION: u32 = 0x0004_0000;

/// Provides a raw set of sys calls which abstractions can be built atop of.
pub mod raw {
Expand Down
4 changes: 2 additions & 2 deletions crates/core/src/db/datastore/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ impl From<&ColumnSchema> 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
Expand Down
19 changes: 12 additions & 7 deletions crates/core/src/db/relational_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/host/wasmer/wasmer_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<WasmInstanceEnv>) -> Imports {
const _: () = assert!(WasmerModule::IMPLEMENTED_ABI.eq(spacetimedb_lib::MODULE_ABI_VERSION));
Expand Down
16 changes: 6 additions & 10 deletions crates/core/src/sql/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions crates/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 35 additions & 33 deletions crates/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
RReverser marked this conversation as resolved.
Show resolved Hide resolved
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)]
Expand Down Expand Up @@ -205,52 +205,54 @@ 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)
}
}

impl TryFrom<u8> for ColumnIndexAttribute {
type Error = ();

fn try_from(v: u8) -> Result<Self, Self::Error> {
match v {
RReverser marked this conversation as resolved.
Show resolved Hide resolved
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<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::from_bits(deserializer.deserialize_u8()?)
.ok_or_else(|| de::Error::custom("invalid bitflags for ColumnIndexAttribute"))
}
}

impl ser::Serialize for ColumnIndexAttribute {
fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u8(self.bits())
}
}
11 changes: 4 additions & 7 deletions crates/lib/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -100,12 +100,9 @@ impl ProductTypeMeta {
&'a self,
row: &'a mut ProductValue,
) -> impl Iterator<Item = (ColumnDef, &'a mut AlgebraicValue)> + '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())
}
}

Expand Down
Loading