Skip to content

Commit

Permalink
Don't autogen schedule fields (#1894)
Browse files Browse the repository at this point in the history
Co-authored-by: Phoebe Goldman <[email protected]>
  • Loading branch information
coolreader18 and gefjon authored Nov 22, 2024
1 parent f04d281 commit 94c66c9
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 102 deletions.
5 changes: 3 additions & 2 deletions crates/bindings-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ mod sym {
symbol!(public);
symbol!(sats);
symbol!(scheduled);
symbol!(scheduled_at);
symbol!(unique);
symbol!(update);

Expand Down Expand Up @@ -154,7 +155,7 @@ mod sym {
pub fn reducer(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
cvt_attr::<ItemFn>(args, item, quote!(), |args, original_function| {
let args = reducer::ReducerArgs::parse(args)?;
reducer::reducer_impl(args, &original_function)
reducer::reducer_impl(args, original_function)
})
}

Expand Down Expand Up @@ -241,7 +242,7 @@ pub fn table(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {

/// Provides helper attributes for `#[spacetimedb::table]`, so that we don't get unknown attribute errors.
#[doc(hidden)]
#[proc_macro_derive(__TableHelper, attributes(sats, unique, auto_inc, primary_key, index))]
#[proc_macro_derive(__TableHelper, attributes(sats, unique, auto_inc, primary_key, index, scheduled_at))]
pub fn table_helper(_input: StdTokenStream) -> StdTokenStream {
Default::default()
}
Expand Down
113 changes: 67 additions & 46 deletions crates/bindings-macro/src/table.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::sats::{self, derive_deserialize, derive_satstype, derive_serialize};
use crate::sym;
use crate::util::{check_duplicate, check_duplicate_msg, ident_to_litstr, match_meta, MutItem};
use crate::util::{check_duplicate, check_duplicate_msg, ident_to_litstr, match_meta};
use heck::ToSnakeCase;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
Expand Down Expand Up @@ -36,21 +36,6 @@ impl TableAccess {
}
}

// add scheduled_id and scheduled_at fields to the struct
fn add_scheduled_fields(item: &mut syn::DeriveInput) {
if let syn::Data::Struct(struct_data) = &mut item.data {
if let syn::Fields::Named(fields) = &mut struct_data.fields {
let extra_fields: syn::FieldsNamed = parse_quote!({
#[primary_key]
#[auto_inc]
pub scheduled_id: u64,
pub scheduled_at: spacetimedb::spacetimedb_lib::ScheduleAt,
});
fields.named.extend(extra_fields.named);
}
}
}

struct IndexArg {
name: Ident,
kind: IndexType,
Expand Down Expand Up @@ -373,6 +358,7 @@ enum ColumnAttr {
AutoInc(Span),
PrimaryKey(Span),
Index(IndexArg),
ScheduledAt(Span),
}

impl ColumnAttr {
Expand All @@ -392,23 +378,18 @@ impl ColumnAttr {
} else if ident == sym::primary_key {
attr.meta.require_path_only()?;
Some(ColumnAttr::PrimaryKey(ident.span()))
} else if ident == sym::scheduled_at {
attr.meta.require_path_only()?;
Some(ColumnAttr::ScheduledAt(ident.span()))
} else {
None
})
}
}

pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput>) -> syn::Result<TokenStream> {
let scheduled_reducer_type_check = args.scheduled.as_ref().map(|reducer| {
add_scheduled_fields(&mut item);
let struct_name = &item.ident;
quote! {
const _: () = spacetimedb::rt::scheduled_reducer_typecheck::<#struct_name>(#reducer);
}
});

pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::Result<TokenStream> {
let vis = &item.vis;
let sats_ty = sats::sats_type_from_derive(&item, quote!(spacetimedb::spacetimedb_lib))?;
let sats_ty = sats::sats_type_from_derive(item, quote!(spacetimedb::spacetimedb_lib))?;

let original_struct_ident = sats_ty.ident;
let table_ident = &args.name;
Expand Down Expand Up @@ -437,7 +418,7 @@ pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput

if fields.len() > u16::MAX.into() {
return Err(syn::Error::new_spanned(
&*item,
item,
"too many columns; the most a table can have is 2^16",
));
}
Expand All @@ -446,6 +427,7 @@ pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput
let mut unique_columns = vec![];
let mut sequenced_columns = vec![];
let mut primary_key_column = None;
let mut scheduled_at_column = None;

for (i, field) in fields.iter().enumerate() {
let col_num = i as u16;
Expand All @@ -454,6 +436,7 @@ pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput
let mut unique = None;
let mut auto_inc = None;
let mut primary_key = None;
let mut scheduled_at = None;
for attr in field.original_attrs {
let Some(attr) = ColumnAttr::parse(attr, field_ident)? else {
continue;
Expand All @@ -472,6 +455,10 @@ pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput
primary_key = Some(span);
}
ColumnAttr::Index(index_arg) => args.indices.push(index_arg),
ColumnAttr::ScheduledAt(span) => {
check_duplicate(&scheduled_at, span)?;
scheduled_at = Some(span);
}
}
}

Expand All @@ -497,10 +484,27 @@ pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput
check_duplicate_msg(&primary_key_column, span, "can only have one primary key per table")?;
primary_key_column = Some(column);
}
if let Some(span) = scheduled_at {
check_duplicate_msg(
&scheduled_at_column,
span,
"can only have one scheduled_at column per table",
)?;
scheduled_at_column = Some(column);
}

columns.push(column);
}

let scheduled_at_typecheck = scheduled_at_column.map(|col| {
let ty = col.ty;
quote!(let _ = |x: #ty| { let _: spacetimedb::ScheduleAt = x; };)
});
let scheduled_id_typecheck = primary_key_column.filter(|_| args.scheduled.is_some()).map(|col| {
let ty = col.ty;
quote!(spacetimedb::rt::assert_scheduled_table_primary_key::<#ty>();)
});

let row_type = quote!(#original_struct_ident);

let mut indices = args
Expand Down Expand Up @@ -548,17 +552,46 @@ pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput
let primary_col_id = primary_key_column.iter().map(|col| col.index);
let sequence_col_ids = sequenced_columns.iter().map(|col| col.index);

let scheduled_reducer_type_check = args.scheduled.as_ref().map(|reducer| {
quote! {
spacetimedb::rt::scheduled_reducer_typecheck::<#original_struct_ident>(#reducer);
}
});
let schedule = args
.scheduled
.as_ref()
.map(|reducer| {
// scheduled_at was inserted as the last field
let scheduled_at_id = (fields.len() - 1) as u16;
quote!(spacetimedb::table::ScheduleDesc {
// better error message when both are missing
if scheduled_at_column.is_none() && primary_key_column.is_none() {
return Err(syn::Error::new(
original_struct_ident.span(),
"scheduled table missing required columns; add these to your struct:\n\
#[primary_key]\n\
#[auto_inc]\n\
scheduled_id: u64,\n\
#[scheduled_at]\n\
scheduled_at: spacetimedb::ScheduleAt,",
));
}
let scheduled_at_column = scheduled_at_column.ok_or_else(|| {
syn::Error::new(
original_struct_ident.span(),
"scheduled tables must have a `#[scheduled_at] scheduled_at: spacetimedb::ScheduleAt` column.",
)
})?;
let scheduled_at_id = scheduled_at_column.index;
if primary_key_column.is_none() {
return Err(syn::Error::new(
original_struct_ident.span(),
"scheduled tables should have a `#[primary_key] #[auto_inc] scheduled_id: u64` column",
));
}
Ok(quote!(spacetimedb::table::ScheduleDesc {
reducer_name: <#reducer as spacetimedb::rt::ReducerInfo>::NAME,
scheduled_at_column: #scheduled_at_id,
})
}))
})
.transpose()?
.into_iter();

let unique_err = if !unique_columns.is_empty() {
Expand All @@ -572,7 +605,6 @@ pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput
quote!(::core::convert::Infallible)
};

let field_names = fields.iter().map(|f| f.ident.unwrap()).collect::<Vec<_>>();
let field_types = fields.iter().map(|f| f.ty).collect::<Vec<_>>();

let tabletype_impl = quote! {
Expand Down Expand Up @@ -607,16 +639,6 @@ pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput
}
};

let col_num = 0u16..;
let field_access_impls = quote! {
#(impl spacetimedb::table::FieldAccess<#col_num> for #original_struct_ident {
type Field = #field_types;
fn get_field(&self) -> &Self::Field {
&self.#field_names
}
})*
};

let row_type_to_table = quote!(<#row_type as spacetimedb::table::__MapRowTypeToTable>::Table);

// Output all macro data
Expand All @@ -643,6 +665,9 @@ pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput
let emission = quote! {
const _: () = {
#(let _ = <#field_types as spacetimedb::rt::TableColumn>::_ITEM;)*
#scheduled_reducer_type_check
#scheduled_at_typecheck
#scheduled_id_typecheck
};

#trait_def
Expand Down Expand Up @@ -677,10 +702,6 @@ pub(crate) fn table_impl(mut args: TableArgs, mut item: MutItem<syn::DeriveInput
#schema_impl
#deserialize_impl
#serialize_impl

#field_access_impls

#scheduled_reducer_type_check
};

if std::env::var("PROC_MACRO_DEBUG").is_ok() {
Expand Down
33 changes: 3 additions & 30 deletions crates/bindings-macro/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,21 @@ pub(crate) fn cvt_attr<Item: Parse + quote::ToTokens>(
args: StdTokenStream,
item: StdTokenStream,
extra_attr: TokenStream,
f: impl FnOnce(TokenStream, MutItem<'_, Item>) -> syn::Result<TokenStream>,
f: impl FnOnce(TokenStream, &Item) -> syn::Result<TokenStream>,
) -> StdTokenStream {
let item: TokenStream = item.into();
let mut parsed_item = match syn::parse2::<Item>(item.clone()) {
let parsed_item = match syn::parse2::<Item>(item.clone()) {
Ok(i) => i,
Err(e) => return TokenStream::from_iter([item, e.into_compile_error()]).into(),
};
let mut modified = false;
let mut_item = MutItem {
val: &mut parsed_item,
modified: &mut modified,
};
let generated = f(args.into(), mut_item).unwrap_or_else(syn::Error::into_compile_error);
let item = if modified {
parsed_item.into_token_stream()
} else {
item
};
let generated = f(args.into(), &parsed_item).unwrap_or_else(syn::Error::into_compile_error);
TokenStream::from_iter([extra_attr, item, generated]).into()
}

pub(crate) fn ident_to_litstr(ident: &Ident) -> syn::LitStr {
syn::LitStr::new(&ident.to_string(), ident.span())
}

pub(crate) struct MutItem<'a, T> {
val: &'a mut T,
modified: &'a mut bool,
}
impl<T> std::ops::Deref for MutItem<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.val
}
}
impl<T> std::ops::DerefMut for MutItem<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
*self.modified = true;
self.val
}
}

pub(crate) trait ErrorSource {
fn error(self, msg: impl std::fmt::Display) -> syn::Error;
}
Expand Down
13 changes: 0 additions & 13 deletions crates/bindings/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,19 +261,6 @@ impl MaybeError for AutoIncOverflow {
}
}

/// A trait for types exposing an operation to access their `N`th field.
///
/// In other words, a type implementing `FieldAccess<N>` allows
/// shared projection from `self` to its `N`th field.
#[doc(hidden)]
pub trait FieldAccess<const N: u16> {
/// The type of the field at the `N`th position.
type Field;

/// Project to the value of the field at position `N`.
fn get_field(&self) -> &Self::Field;
}

pub trait Column {
type Row;
type ColType;
Expand Down
14 changes: 14 additions & 0 deletions crates/bindings/tests/ui/reducers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,22 @@ fn missing_ctx(_a: u8) {}
#[spacetimedb::reducer]
fn ctx_by_val(_ctx: ReducerContext, _a: u8) {}

#[spacetimedb::table(name = scheduled_table_missing_rows, scheduled(scheduled_table_missing_rows_reducer))]
struct ScheduledTableMissingRows {
x: u8,
y: u8,
}

// #[spacetimedb::reducer]
// fn scheduled_table_missing_rows_reducer(_ctx: &ReducerContext, _: &ScheduledTableMissingRows) {}

#[spacetimedb::table(name = scheduled_table, scheduled(scheduled_table_reducer))]
struct ScheduledTable {
#[primary_key]
#[auto_inc]
scheduled_id: u64,
#[scheduled_at]
scheduled_at: spacetimedb::ScheduleAt,
x: u8,
y: u8,
}
Expand Down
17 changes: 14 additions & 3 deletions crates/bindings/tests/ui/reducers.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,27 @@ error: const parameters are not allowed on reducers
20 | fn const_param<const X: u8>() {}
| ^^^^^^^^^^^

error: scheduled table missing required columns; add these to your struct:
#[primary_key]
#[auto_inc]
scheduled_id: u64,
#[scheduled_at]
scheduled_at: spacetimedb::ScheduleAt,
--> tests/ui/reducers.rs:29:8
|
29 | struct ScheduledTableMissingRows {
| ^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0593]: function is expected to take 2 arguments, but it takes 3 arguments
--> tests/ui/reducers.rs:28:56
--> tests/ui/reducers.rs:37:56
|
28 | #[spacetimedb::table(name = scheduled_table, scheduled(scheduled_table_reducer))]
37 | #[spacetimedb::table(name = scheduled_table, scheduled(scheduled_table_reducer))]
| -------------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^---
| | |
| | expected function that takes 2 arguments
| required by a bound introduced by this call
...
35 | fn scheduled_table_reducer(_ctx: &ReducerContext, _x: u8, _y: u8) {}
49 | fn scheduled_table_reducer(_ctx: &ReducerContext, _x: u8, _y: u8) {}
| ----------------------------------------------------------------- takes 3 arguments
|
= note: required for `for<'a> fn(&'a ReducerContext, u8, u8) {scheduled_table_reducer}` to implement `Reducer<'_, (ScheduledTable,)>`
Expand Down
Loading

2 comments on commit 94c66c9

@github-actions
Copy link

@github-actions github-actions bot commented on 94c66c9 Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarking failed. Please check the workflow run for details.

@github-actions
Copy link

@github-actions github-actions bot commented on 94c66c9 Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callgrind benchmark results

Callgrind Benchmark Report

These benchmarks were run using callgrind,
an instruction-level profiler. They allow comparisons between sqlite (sqlite), SpacetimeDB running through a module (stdb_module), and the underlying SpacetimeDB data storage engine (stdb_raw). Callgrind emulates a CPU to collect the below estimates.

Measurement changes larger than five percent are in bold.

In-memory benchmarks

callgrind: empty transaction

db total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw 6426 6426 0.00% 6544 6544 0.00%
sqlite 5585 5585 0.00% 6113 6113 0.00%

callgrind: filter

db schema indices count preload _column data_type total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str no_index 64 128 1 u64 76579 76579 0.00% 77007 77007 0.00%
stdb_raw u32_u64_str no_index 64 128 2 string 118821 118821 0.00% 119547 119511 0.03%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 25114 25110 0.02% 25660 25624 0.14%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 24078 24078 0.00% 24518 24514 0.02%
sqlite u32_u64_str no_index 64 128 2 string 144695 144695 0.00% 146165 146169 -0.00%
sqlite u32_u64_str no_index 64 128 1 u64 124044 124044 0.00% 125276 125280 -0.00%
sqlite u32_u64_str btree_each_column 64 128 1 u64 131361 131361 0.00% 132817 132813 0.00%
sqlite u32_u64_str btree_each_column 64 128 2 string 134494 134494 0.00% 136144 136140 0.00%

callgrind: insert bulk

db schema indices count preload total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 64 128 880451 878667 0.20% 932615 898069 3.85%
stdb_raw u32_u64_str btree_each_column 64 128 1026671 1032582 -0.57% 1084305 1089996 -0.52%
sqlite u32_u64_str unique_0 64 128 398320 398320 0.00% 417704 417712 -0.00%
sqlite u32_u64_str btree_each_column 64 128 983655 983637 0.00% 1020569 1020543 0.00%

callgrind: iterate

db schema indices count total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 1024 153810 153810 0.00% 153926 153926 0.00%
stdb_raw u32_u64_str unique_0 64 16835 16835 0.00% 16935 16935 0.00%
sqlite u32_u64_str unique_0 1024 1068281 1068281 0.00% 1071651 1071651 0.00%
sqlite u32_u64_str unique_0 64 76267 76267 0.00% 77411 77411 0.00%

callgrind: serialize_product_value

count format total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
64 json 47528 47528 0.00% 50214 50214 0.00%
64 bsatn 25509 25509 0.00% 27753 27753 0.00%
16 bsatn 8200 8200 0.00% 9560 9560 0.00%
16 json 12188 12188 0.00% 14126 14126 0.00%

callgrind: update bulk

db schema indices count preload total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 1024 1024 20557141 20559222 -0.01% 21037357 21047298 -0.05%
stdb_raw u32_u64_str unique_0 64 128 1288415 1288235 0.01% 1353867 1354323 -0.03%
sqlite u32_u64_str unique_0 1024 1024 1802182 1802182 0.00% 1811352 1811352 0.00%
sqlite u32_u64_str unique_0 64 128 128528 128528 0.00% 131244 131244 0.00%
On-disk benchmarks

callgrind: empty transaction

db total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw 6431 6431 0.00% 6557 6557 0.00%
sqlite 5627 5627 0.00% 6211 6211 0.00%

callgrind: filter

db schema indices count preload _column data_type total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str no_index 64 128 1 u64 76584 76584 0.00% 76988 76992 -0.01%
stdb_raw u32_u64_str no_index 64 128 2 string 118826 118826 0.00% 119512 119492 0.02%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 25115 25131 -0.06% 25601 25649 -0.19%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 24083 24083 0.00% 24503 24503 0.00%
sqlite u32_u64_str no_index 64 128 1 u64 125965 125965 0.00% 127561 127565 -0.00%
sqlite u32_u64_str no_index 64 128 2 string 146616 146616 0.00% 148478 148482 -0.00%
sqlite u32_u64_str btree_each_column 64 128 2 string 136616 136616 0.00% 138792 138792 0.00%
sqlite u32_u64_str btree_each_column 64 128 1 u64 133457 133457 0.00% 135379 135379 0.00%

callgrind: insert bulk

db schema indices count preload total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 64 128 826987 827485 -0.06% 845675 877135 -3.59%
stdb_raw u32_u64_str btree_each_column 64 128 977801 976480 0.14% 1034389 1033682 0.07%
sqlite u32_u64_str unique_0 64 128 415857 415857 0.00% 434583 434571 0.00%
sqlite u32_u64_str btree_each_column 64 128 1021908 1021908 0.00% 1057624 1057624 0.00%

callgrind: iterate

db schema indices count total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 1024 153815 153815 0.00% 153915 153915 0.00%
stdb_raw u32_u64_str unique_0 64 16840 16840 0.00% 16940 16940 0.00%
sqlite u32_u64_str unique_0 1024 1071349 1071349 0.00% 1075093 1075093 0.00%
sqlite u32_u64_str unique_0 64 78039 78039 0.00% 79395 79395 0.00%

callgrind: serialize_product_value

count format total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
64 json 47528 47528 0.00% 50214 50214 0.00%
64 bsatn 25509 25509 0.00% 27753 27753 0.00%
16 bsatn 8200 8200 0.00% 9560 9560 0.00%
16 json 12188 12188 0.00% 14126 14126 0.00%

callgrind: update bulk

db schema indices count preload total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 1024 1024 19045907 19046716 -0.00% 19570857 19568318 0.01%
stdb_raw u32_u64_str unique_0 64 128 1240373 1240266 0.01% 1304129 1303944 0.01%
sqlite u32_u64_str unique_0 1024 1024 1809743 1809743 0.00% 1818405 1818405 0.00%
sqlite u32_u64_str unique_0 64 128 132654 132654 0.00% 135586 135586 0.00%

Please sign in to comment.