Files
Shubham Mishra e4098f98d9 Rust: macro change name -> accessor (#4264)
## Description of Changes

This PR primarily affects the `bindings-macro` and `schema` crates to
review:

### Core changes

1. Replaces the `name` macro with `accessor` for **Tables, Views,
Procedures, and Reducers** in Rust modules.
2. Extends `RawModuleDefV10` with a new section for:

   * case conversion policies
   * explicit names
    New sections are not validated in this PR so not functional.
3. Updates index behavior:

* Index names are now always **system-generated** for clients. Which
will be fixed in follow-up PR when we start validating RawModuleDef with
explicit names.
   * The `accessor` name for an index is used only inside the module.


## Breaking changes (API/ABI)

1. **Rust modules**

   * The `name` macro must be replaced with `accessor`.

2. **Client bindings (all languages)**

* Index names are now system-generated instead of using explicitly
provided names.


**Complexity:** 3

A follow-up PR will reintroduce explicit names with support for case
conversion.

---------

Co-authored-by: rekhoff <r.ekhoff@clockworklabs.io>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: clockwork-labs-bot <bot@clockworklabs.com>
2026-02-16 15:23:50 +00:00

904 lines
29 KiB
Rust

// Some of our tests include reducers with large numbers of arguments.
// This is on purpose.
// Due to a clippy bug (as of 2023-08-31),
// we cannot locally disable the lint around the definitions of those reducers,
// because the definitions are macro-generated,
// and clippy misunderstands `#[allow]` attributes in macro-expansions.
#![allow(clippy::too_many_arguments)]
use anyhow::{anyhow, Context, Result};
use spacetimedb::{
sats::{i256, u256},
ConnectionId, Identity, ReducerContext, SpacetimeType, Table, TimeDuration, Timestamp, Uuid,
};
#[derive(PartialEq, Eq, Hash, SpacetimeType)]
pub enum SimpleEnum {
Zero,
One,
Two,
}
#[derive(SpacetimeType)]
pub enum EnumWithPayload {
U8(u8),
U16(u16),
U32(u32),
U64(u64),
U128(u128),
U256(u256),
I8(i8),
I16(i16),
I32(i32),
I64(i64),
I128(i128),
I256(i256),
Bool(bool),
F32(f32),
F64(f64),
Str(String),
Identity(Identity),
ConnectionId(ConnectionId),
Timestamp(Timestamp),
Uuid(Uuid),
Bytes(Vec<u8>),
Ints(Vec<i32>),
Strings(Vec<String>),
SimpleEnums(Vec<SimpleEnum>),
// SpacetimeDB doesn't yet support recursive types in modules
// Recursive(Vec<EnumWithPayload>),
}
#[derive(SpacetimeType)]
pub struct UnitStruct {}
#[derive(SpacetimeType)]
pub struct ByteStruct {
b: u8,
}
#[derive(SpacetimeType)]
pub struct EveryPrimitiveStruct {
a: u8,
b: u16,
c: u32,
d: u64,
e: u128,
f: u256,
g: i8,
h: i16,
i: i32,
j: i64,
k: i128,
l: i256,
m: bool,
n: f32,
o: f64,
p: String,
q: Identity,
r: ConnectionId,
s: Timestamp,
t: TimeDuration,
u: Uuid,
}
#[derive(SpacetimeType)]
pub struct EveryVecStruct {
a: Vec<u8>,
b: Vec<u16>,
c: Vec<u32>,
d: Vec<u64>,
e: Vec<u128>,
f: Vec<u256>,
g: Vec<i8>,
h: Vec<i16>,
i: Vec<i32>,
j: Vec<i64>,
k: Vec<i128>,
l: Vec<i256>,
m: Vec<bool>,
n: Vec<f32>,
o: Vec<f64>,
p: Vec<String>,
q: Vec<Identity>,
r: Vec<ConnectionId>,
s: Vec<Timestamp>,
t: Vec<TimeDuration>,
u: Vec<Uuid>,
}
/// Defines one or more tables, and optionally reducers alongside them.
///
/// Each table specifier is:
///
/// TableName { reducers... } fields...;
///
/// where:
///
/// - TableName is an identifier for the new table.
///
/// - reducers... is a comma-separated list of reducer specifiers, which may be:
/// - insert reducer_name
/// Defines a reducer which takes an argument for each of the table's columns, and inserts a new row.
/// Not suitable for tables with unique constraints.
/// e.g. insert insert_my_table
/// - insert_or_panic reducer_name
/// Like insert, but for tables with unique constraints. Unwraps the output of `insert`.
/// e.g. insert_or_panic insert_my_table
/// - update_by reducer_name = update_method(field_name)
/// Defines a reducer which takes an argument for each of the table's columns,
/// and calls the update method on the primary key column to update an existing row.
/// e.g. update_by update_my_table = update_by_name(name)
/// - update_non_pk_by reducer_name = update_method(field_name)
/// Like update_by, but for non-primary-key unique columns. Since `update()` is only
/// available on primary key columns, this uses `delete()` + `insert()` instead.
/// e.g. update_non_pk_by update_my_table = update_by_name(name)
/// - delete_by reducer_name = delete_method(field_name: field_type)
/// Defines a reducer which takes a single argument, and passes it to the delete_method
/// to delete a row.
/// e.g. delete_by delete_my_table = delete_by_name(name: String)
///
/// - fields is a comma-separated list of field specifiers, which are optional attributes,
/// followed by a field name identifier and a type.
/// e.g. #[unique] name String
///
/// A full table definition might be:
///
/// MyTable {
/// insert_or_panic insert_my_table,
/// update_by update_my_table = update_by_name(name),
/// delete_by delete_my_table = delete_by_name(name: String),
/// } #[primary_key] name String,
/// #[auto_inc] #[unique] id u32,
/// count i64;
//
// Internal rules are prefixed with @.
macro_rules! define_tables {
// Base case for `@impl_ops` recursion: no more ops to define.
(@impl_ops $name:ident { $(,)? } $($more:tt)*) => {};
// Define a reducer for tables without unique constraints,
// which inserts a row.
(@impl_ops $name:ident
{ insert $insert:ident
$(, $($ops:tt)* )? }
$($field_name:ident $ty:ty),* $(,)*) => {
paste::paste! {
#[spacetimedb::reducer]
pub fn $insert (ctx: &ReducerContext, $($field_name : $ty,)*) {
ctx.db.[<$name:snake>]().insert($name { $($field_name,)* });
}
}
define_tables!(@impl_ops $name { $($($ops)*)? } $($field_name $ty,)*);
};
// Define a reducer for tables without unique constraints,
// which deletes a row.
(@impl_ops $name:ident
{ delete $delete:ident
$(, $($ops:tt)* )? }
$($field_name:ident $ty:ty),* $(,)*) => {
paste::paste! {
#[spacetimedb::reducer]
pub fn $delete (ctx: &ReducerContext, $($field_name : $ty,)*) {
ctx.db.[<$name:snake>]().delete($name { $($field_name,)* });
}
}
define_tables!(@impl_ops $name { $($($ops)*)? } $($field_name $ty,)*);
};
// Define a reducer for tables with unique constraints,
// which inserts a row, or panics with `expect` if the row violates a unique constraint.
(@impl_ops $name:ident
{ insert_or_panic $insert:ident
$(, $($ops:tt)* )? }
$($field_name:ident $ty:ty),* $(,)*) => {
paste::paste! {
#[spacetimedb::reducer]
pub fn $insert (ctx: &ReducerContext, $($field_name : $ty,)*) {
ctx.db.[<$name:snake>]().insert($name { $($field_name,)* });
}
}
define_tables!(@impl_ops $name { $($($ops)*)? } $($field_name $ty,)*);
};
// Define a reducer for tables with a primary key field,
// which uses `update` to update by that primary key field.
(@impl_ops $name:ident
{ update_by $update:ident = $update_method:ident($unique_field:ident)
$(, $($ops:tt)* )? }
$($field_name:ident $ty:ty),* $(,)*) => {
paste::paste! {
#[spacetimedb::reducer]
pub fn $update (ctx: &ReducerContext, $($field_name : $ty,)*) {
ctx.db.[<$name:snake>]().$unique_field().update($name { $($field_name,)* });
}
}
define_tables!(@impl_ops $name { $($($ops)*)? } $($field_name $ty,)*);
};
// Define a reducer for tables with a non-pk unique field,
// which uses delete + insert to simulate an update by that unique field.
(@impl_ops $name:ident
{ update_non_pk_by $update:ident = $update_method:ident($unique_field:ident)
$(, $($ops:tt)* )? }
$($field_name:ident $ty:ty),* $(,)*) => {
paste::paste! {
#[spacetimedb::reducer]
pub fn $update (ctx: &ReducerContext, $($field_name : $ty,)*) {
ctx.db.[<$name:snake>]().$unique_field().delete(&$unique_field);
ctx.db.[<$name:snake>]().insert($name { $($field_name,)* });
}
}
define_tables!(@impl_ops $name { $($($ops)*)? } $($field_name $ty,)*);
};
// Define a reducer for tables with a unique field,
// which uses `$delete_method` to delete by that unique field.
(@impl_ops $name:ident
{ delete_by $delete:ident = $delete_method:ident($unique_field:ident : $unique_ty:ty)
$(, $($ops:tt)*)? }
$($other_fields:tt)* ) => {
paste::paste! {
#[spacetimedb::reducer]
pub fn $delete (ctx: &ReducerContext, $unique_field : $unique_ty) {
ctx.db.[<$name:snake>]().$unique_field().delete(&$unique_field);
}
}
define_tables!(@impl_ops $name { $($($ops)*)? } $($other_fields)*);
};
// Define a table.
(@one $name:ident { $($ops:tt)* } $($(#[$attr:meta])* $field_name:ident $ty:ty),* $(,)*) => {
paste::paste! {
#[spacetimedb::table(accessor = [<$name:snake>], public)]
pub struct $name {
$($(#[$attr])* pub $field_name : $ty,)*
}
}
// Recursively implement reducers based on the `ops`.
define_tables!(@impl_ops $name { $($ops)* } $($field_name $ty,)*);
};
// "Public" interface: Define many tables.
($($name:ident { $($ops:tt)* } $($(#[$attr:meta])* $field_name:ident $ty:ty),* $(,)*;)*) => {
// Define each table one-by-one, iteratively.
$(define_tables!(@one $name { $($ops)* } $($(#[$attr])* $field_name $ty,)*);)*
};
}
// Tables holding a single value.
define_tables! {
OneU8 { insert insert_one_u8 } n u8;
OneU16 { insert insert_one_u16 } n u16;
OneU32 { insert insert_one_u32 } n u32;
OneU64 { insert insert_one_u64 } n u64;
OneU128 { insert insert_one_u128 } n u128;
OneU256 { insert insert_one_u256 } n u256;
OneI8 { insert insert_one_i8 } n i8;
OneI16 { insert insert_one_i16 } n i16;
OneI32 { insert insert_one_i32 } n i32;
OneI64 { insert insert_one_i64 } n i64;
OneI128 { insert insert_one_i128 } n i128;
OneI256 { insert insert_one_i256 } n i256;
OneBool { insert insert_one_bool } b bool;
OneF32 { insert insert_one_f32 } f f32;
OneF64 { insert insert_one_f64 } f f64;
OneString { insert insert_one_string } s String;
OneIdentity { insert insert_one_identity } i Identity;
OneConnectionId { insert insert_one_connection_id} a ConnectionId;
OneUuid { insert insert_one_uuid } u Uuid;
OneTimestamp { insert insert_one_timestamp } t Timestamp;
OneSimpleEnum { insert insert_one_simple_enum } e SimpleEnum;
OneEnumWithPayload { insert insert_one_enum_with_payload } e EnumWithPayload;
OneUnitStruct { insert insert_one_unit_struct } s UnitStruct;
OneByteStruct { insert insert_one_byte_struct } s ByteStruct;
OneEveryPrimitiveStruct { insert insert_one_every_primitive_struct } s EveryPrimitiveStruct;
OneEveryVecStruct { insert insert_one_every_vec_struct } s EveryVecStruct;
}
// Tables holding a Vec of various types.
define_tables! {
VecU8 { insert insert_vec_u8 } n Vec<u8>;
VecU16 { insert insert_vec_u16 } n Vec<u16>;
VecU32 { insert insert_vec_u32 } n Vec<u32>;
VecU64 { insert insert_vec_u64 } n Vec<u64>;
VecU128 { insert insert_vec_u128 } n Vec<u128>;
VecU256 { insert insert_vec_u256 } n Vec<u256>;
VecI8 { insert insert_vec_i8 } n Vec<i8>;
VecI16 { insert insert_vec_i16 } n Vec<i16>;
VecI32 { insert insert_vec_i32 } n Vec<i32>;
VecI64 { insert insert_vec_i64 } n Vec<i64>;
VecI128 { insert insert_vec_i128 } n Vec<i128>;
VecI256 { insert insert_vec_i256 } n Vec<i256>;
VecBool { insert insert_vec_bool } b Vec<bool>;
VecF32 { insert insert_vec_f32 } f Vec<f32>;
VecF64 { insert insert_vec_f64 } f Vec<f64>;
VecString { insert insert_vec_string } s Vec<String>;
VecIdentity { insert insert_vec_identity } i Vec<Identity>;
VecConnectionId { insert insert_vec_connection_id} a Vec<ConnectionId>;
VecUuid { insert insert_vec_uuid } u Vec<Uuid>;
VecTimestamp { insert insert_vec_timestamp } t Vec<Timestamp>;
VecSimpleEnum { insert insert_vec_simple_enum } e Vec<SimpleEnum>;
VecEnumWithPayload { insert insert_vec_enum_with_payload } e Vec<EnumWithPayload>;
VecUnitStruct { insert insert_vec_unit_struct } s Vec<UnitStruct>;
VecByteStruct { insert insert_vec_byte_struct } s Vec<ByteStruct>;
VecEveryPrimitiveStruct { insert insert_vec_every_primitive_struct } s Vec<EveryPrimitiveStruct>;
VecEveryVecStruct { insert insert_vec_every_vec_struct } s Vec<EveryVecStruct>;
}
// Tables holding an Option of various types.
define_tables! {
OptionI32 { insert insert_option_i32 } n Option<i32>;
OptionString { insert insert_option_string } s Option<String>;
OptionIdentity { insert insert_option_identity } i Option<Identity>;
OptionUuid { insert insert_option_uuid } u Option<Uuid>;
OptionSimpleEnum { insert insert_option_simple_enum } e Option<SimpleEnum>;
OptionEveryPrimitiveStruct { insert insert_option_every_primitive_struct } s Option<EveryPrimitiveStruct>;
OptionVecOptionI32 { insert insert_option_vec_option_i32 } v Option<Vec<Option<i32>>>;
}
// Tables holding a Result of various types.
define_tables! {
ResultI32String { insert insert_result_i32_string } r Result<i32, String>;
ResultStringI32 { insert insert_result_string_i32 } r Result<String, i32>;
ResultIdentityString { insert insert_result_identity_string } r Result<Identity, String>;
ResultSimpleEnumI32 { insert insert_result_simple_enum_i32 } r Result<SimpleEnum, i32>;
ResultEveryPrimitiveStructString { insert insert_result_every_primitive_struct_string } r Result<EveryPrimitiveStruct, String>;
ResultVecI32String { insert insert_result_vec_i32_string } r Result<Vec<i32>, String>;
}
// Tables mapping a unique, but non-pk, key to a boring i32 payload.
// This allows us to test delete events, and the semantically correct absence of update events.
define_tables! {
UniqueU8 {
insert_or_panic insert_unique_u8,
update_non_pk_by update_unique_u8 = update_by_n(n),
delete_by delete_unique_u8 = delete_by_n(n: u8),
} #[unique] n u8, data i32;
UniqueU16 {
insert_or_panic insert_unique_u16,
update_non_pk_by update_unique_u16 = update_by_n(n),
delete_by delete_unique_u16 = delete_by_n(n: u16),
} #[unique] n u16, data i32;
UniqueU32 {
insert_or_panic insert_unique_u32,
update_non_pk_by update_unique_u32 = update_by_n(n),
delete_by delete_unique_u32 = delete_by_n(n: u32),
} #[unique] n u32, data i32;
UniqueU64 {
insert_or_panic insert_unique_u64,
update_non_pk_by update_unique_u64 = update_by_n(n),
delete_by delete_unique_u64 = delete_by_n(n: u64),
} #[unique] n u64, data i32;
UniqueU128 {
insert_or_panic insert_unique_u128,
update_non_pk_by update_unique_u128 = update_by_n(n),
delete_by delete_unique_u128 = delete_by_n(n: u128),
} #[unique] n u128, data i32;
UniqueU256 {
insert_or_panic insert_unique_u256,
update_non_pk_by update_unique_u256 = update_by_n(n),
delete_by delete_unique_u256 = delete_by_n(n: u256),
} #[unique] n u256, data i32;
UniqueI8 {
insert_or_panic insert_unique_i8,
update_non_pk_by update_unique_i8 = update_by_n(n),
delete_by delete_unique_i8 = delete_by_n(n: i8),
} #[unique] n i8, data i32;
UniqueI16 {
insert_or_panic insert_unique_i16,
update_non_pk_by update_unique_i16 = update_by_n(n),
delete_by delete_unique_i16 = delete_by_n(n: i16),
} #[unique] n i16, data i32;
UniqueI32 {
insert_or_panic insert_unique_i32,
update_non_pk_by update_unique_i32 = update_by_n(n),
delete_by delete_unique_i32 = delete_by_n(n: i32),
} #[unique] n i32, data i32;
UniqueI64 {
insert_or_panic insert_unique_i64,
update_non_pk_by update_unique_i64 = update_by_n(n),
delete_by delete_unique_i64 = delete_by_n(n: i64),
} #[unique] n i64, data i32;
UniqueI128 {
insert_or_panic insert_unique_i128,
update_non_pk_by update_unique_i128 = update_by_n(n),
delete_by delete_unique_i128 = delete_by_n(n: i128),
} #[unique] n i128, data i32;
UniqueI256 {
insert_or_panic insert_unique_i256,
update_non_pk_by update_unique_i256 = update_by_n(n),
delete_by delete_unique_i256 = delete_by_n(n: i256),
} #[unique] n i256, data i32;
UniqueBool {
insert_or_panic insert_unique_bool,
update_non_pk_by update_unique_bool = update_by_b(b),
delete_by delete_unique_bool = delete_by_b(b: bool),
} #[unique] b bool, data i32;
UniqueString {
insert_or_panic insert_unique_string,
update_non_pk_by update_unique_string = update_by_s(s),
delete_by delete_unique_string = delete_by_s(s: String),
} #[unique] s String, data i32;
UniqueIdentity {
insert_or_panic insert_unique_identity,
update_non_pk_by update_unique_identity = update_by_i(i),
delete_by delete_unique_identity = delete_by_i(i: Identity),
} #[unique] i Identity, data i32;
UniqueConnectionId {
insert_or_panic insert_unique_connection_id,
update_non_pk_by update_unique_connection_id = update_by_a(a),
delete_by delete_unique_connection_id = delete_by_a(a: ConnectionId),
} #[unique] a ConnectionId, data i32;
UniqueUuid {
insert_or_panic insert_unique_uuid,
update_non_pk_by update_unique_uuid = update_by_u(u),
delete_by delete_unique_uuid = delete_by_u(u: Uuid),
} #[unique] u Uuid, data i32;
}
// Tables mapping a primary key to a boring i32 payload.
// This allows us to test update and delete events.
define_tables! {
PkU8 {
insert_or_panic insert_pk_u8,
update_by update_pk_u8 = update_by_n(n),
delete_by delete_pk_u8 = delete_by_n(n: u8),
} #[primary_key] n u8, data i32;
PkU16 {
insert_or_panic insert_pk_u16,
update_by update_pk_u16 = update_by_n(n),
delete_by delete_pk_u16 = delete_by_n(n: u16),
} #[primary_key] n u16, data i32;
PkU32 {
insert_or_panic insert_pk_u32,
update_by update_pk_u32 = update_by_n(n),
delete_by delete_pk_u32 = delete_by_n(n: u32),
} #[primary_key] n u32, data i32;
PkU32Two {
insert_or_panic insert_pk_u32_two,
update_by update_pk_u32_two = update_by_n(n),
delete_by delete_pk_u32_two = delete_by_n(n: u32),
} #[primary_key] n u32, data i32;
PkU64 {
insert_or_panic insert_pk_u64,
update_by update_pk_u64 = update_by_n(n),
delete_by delete_pk_u64 = delete_by_n(n: u64),
} #[primary_key] n u64, data i32;
PkU128 {
insert_or_panic insert_pk_u128,
update_by update_pk_u128 = update_by_n(n),
delete_by delete_pk_u128 = delete_by_n(n: u128),
} #[primary_key] n u128, data i32;
PkU256 {
insert_or_panic insert_pk_u256,
update_by update_pk_u256 = update_by_n(n),
delete_by delete_pk_u256 = delete_by_n(n: u256),
} #[primary_key] n u256, data i32;
PkI8 {
insert_or_panic insert_pk_i8,
update_by update_pk_i8 = update_by_n(n),
delete_by delete_pk_i8 = delete_by_n(n: i8),
} #[primary_key] n i8, data i32;
PkI16 {
insert_or_panic insert_pk_i16,
update_by update_pk_i16 = update_by_n(n),
delete_by delete_pk_i16 = delete_by_n(n: i16),
} #[primary_key] n i16, data i32;
PkI32 {
insert_or_panic insert_pk_i32,
update_by update_pk_i32 = update_by_n(n),
delete_by delete_pk_i32 = delete_by_n(n: i32),
} #[primary_key] n i32, data i32;
PkI64 {
insert_or_panic insert_pk_i64,
update_by update_pk_i64 = update_by_n(n),
delete_by delete_pk_i64 = delete_by_n(n: i64),
} #[primary_key] n i64, data i32;
PkI128 {
insert_or_panic insert_pk_i128,
update_by update_pk_i128 = update_by_n(n),
delete_by delete_pk_i128 = delete_by_n(n: i128),
} #[primary_key] n i128, data i32;
PkI256 {
insert_or_panic insert_pk_i256,
update_by update_pk_i256 = update_by_n(n),
delete_by delete_pk_i256 = delete_by_n(n: i256),
} #[primary_key] n i256, data i32;
PkBool {
insert_or_panic insert_pk_bool,
update_by update_pk_bool = update_by_b(b),
delete_by delete_pk_bool = delete_by_b(b: bool),
} #[primary_key] b bool, data i32;
PkString {
insert_or_panic insert_pk_string,
update_by update_pk_string = update_by_s(s),
delete_by delete_pk_string = delete_by_s(s: String),
} #[primary_key] s String, data i32;
PkIdentity {
insert_or_panic insert_pk_identity,
update_by update_pk_identity = update_by_i(i),
delete_by delete_pk_identity = delete_by_i(i: Identity),
} #[primary_key] i Identity, data i32;
PkConnectionId {
insert_or_panic insert_pk_connection_id,
update_by update_pk_connection_id = update_by_a(a),
delete_by delete_pk_connection_id = delete_by_a(a: ConnectionId),
} #[primary_key] a ConnectionId, data i32;
PkUuid {
insert_or_panic insert_pk_uuid,
update_by update_pk_uuid = update_by_u(u),
delete_by delete_pk_uuid = delete_by_u(u: Uuid),
} #[primary_key] u Uuid, data i32;
PkSimpleEnum {
insert_or_panic insert_pk_simple_enum,
} #[primary_key] a SimpleEnum, data i32;
}
#[spacetimedb::reducer]
fn update_pk_simple_enum(ctx: &ReducerContext, a: SimpleEnum, data: i32) -> anyhow::Result<()> {
let Some(mut o) = ctx.db.pk_simple_enum().a().find(a) else {
return Err(anyhow!("row not found"));
};
o.data = data;
ctx.db.pk_simple_enum().a().update(o);
Ok(())
}
#[spacetimedb::reducer]
fn insert_into_btree_u32(ctx: &ReducerContext, rows: Vec<BTreeU32>) -> anyhow::Result<()> {
for row in rows {
ctx.db.btree_u32().insert(row);
}
Ok(())
}
#[spacetimedb::reducer]
fn delete_from_btree_u32(ctx: &ReducerContext, rows: Vec<BTreeU32>) -> anyhow::Result<()> {
for row in rows {
ctx.db.btree_u32().delete(row);
}
Ok(())
}
#[spacetimedb::reducer]
fn insert_into_pk_btree_u32(ctx: &ReducerContext, pk_u32: Vec<PkU32>, bt_u32: Vec<BTreeU32>) -> anyhow::Result<()> {
for row in pk_u32 {
ctx.db.pk_u32().insert(row);
}
for row in bt_u32 {
ctx.db.btree_u32().insert(row);
}
Ok(())
}
/// The purpose of this reducer is for a test which
/// left-semijoins `UniqueU32` to `PkU32`
/// for the purposes of behavior testing row-deduplication.
#[spacetimedb::reducer]
fn insert_unique_u32_update_pk_u32(ctx: &ReducerContext, n: u32, d_unique: i32, d_pk: i32) -> anyhow::Result<()> {
ctx.db.unique_u32().insert(UniqueU32 { n, data: d_unique });
ctx.db.pk_u32().n().update(PkU32 { n, data: d_pk });
Ok(())
}
/// The purpose of this reducer is for a test with two separate semijoin queries
/// - `UniqueU32` to `PkU32`
/// - `UniqueU32` to `PkU32Two`
///
/// for the purposes of behavior testing row-deduplication.
#[spacetimedb::reducer]
fn delete_pk_u32_insert_pk_u32_two(ctx: &ReducerContext, n: u32, data: i32) -> anyhow::Result<()> {
ctx.db.pk_u32_two().insert(PkU32Two { n, data });
ctx.db.pk_u32().delete(PkU32 { n, data });
Ok(())
}
#[spacetimedb::reducer]
fn insert_caller_one_identity(ctx: &ReducerContext) -> anyhow::Result<()> {
ctx.db.one_identity().insert(OneIdentity { i: ctx.sender() });
Ok(())
}
#[spacetimedb::reducer]
fn insert_caller_vec_identity(ctx: &ReducerContext) -> anyhow::Result<()> {
ctx.db.vec_identity().insert(VecIdentity { i: vec![ctx.sender()] });
Ok(())
}
#[spacetimedb::reducer]
fn insert_caller_unique_identity(ctx: &ReducerContext, data: i32) -> anyhow::Result<()> {
ctx.db
.unique_identity()
.insert(UniqueIdentity { i: ctx.sender(), data });
Ok(())
}
#[spacetimedb::reducer]
fn insert_caller_pk_identity(ctx: &ReducerContext, data: i32) -> anyhow::Result<()> {
ctx.db.pk_identity().insert(PkIdentity { i: ctx.sender(), data });
Ok(())
}
#[spacetimedb::reducer]
fn insert_caller_one_connection_id(ctx: &ReducerContext) -> anyhow::Result<()> {
ctx.db.one_connection_id().insert(OneConnectionId {
a: ctx.connection_id().context("No connection id in reducer context")?,
});
Ok(())
}
#[spacetimedb::reducer]
fn insert_caller_vec_connection_id(ctx: &ReducerContext) -> anyhow::Result<()> {
ctx.db.vec_connection_id().insert(VecConnectionId {
a: vec![ctx.connection_id().context("No connection id in reducer context")?],
});
Ok(())
}
#[spacetimedb::reducer]
fn insert_caller_unique_connection_id(ctx: &ReducerContext, data: i32) -> anyhow::Result<()> {
ctx.db.unique_connection_id().insert(UniqueConnectionId {
a: ctx.connection_id().context("No connection id in reducer context")?,
data,
});
Ok(())
}
#[spacetimedb::reducer]
fn insert_caller_pk_connection_id(ctx: &ReducerContext, data: i32) -> anyhow::Result<()> {
ctx.db.pk_connection_id().insert(PkConnectionId {
a: ctx.connection_id().context("No connection id in reducer context")?,
data,
});
Ok(())
}
#[spacetimedb::reducer]
fn insert_call_timestamp(ctx: &ReducerContext) {
ctx.db.one_timestamp().insert(OneTimestamp { t: ctx.timestamp });
}
#[spacetimedb::reducer]
fn insert_call_uuid_v4(ctx: &ReducerContext) {
ctx.db.one_uuid().insert(OneUuid {
u: ctx.new_uuid_v4().unwrap(),
});
}
#[spacetimedb::reducer]
fn insert_call_uuid_v7(ctx: &ReducerContext) {
ctx.db.one_uuid().insert(OneUuid {
u: ctx.new_uuid_v7().unwrap(),
});
}
#[spacetimedb::reducer]
fn insert_primitives_as_strings(ctx: &ReducerContext, s: EveryPrimitiveStruct) {
ctx.db.vec_string().insert(VecString {
s: vec![
s.a.to_string(),
s.b.to_string(),
s.c.to_string(),
s.d.to_string(),
s.e.to_string(),
s.f.to_string(),
s.g.to_string(),
s.h.to_string(),
s.i.to_string(),
s.j.to_string(),
s.k.to_string(),
s.l.to_string(),
s.m.to_string(),
s.n.to_string(),
s.o.to_string(),
s.p.to_string(),
s.q.to_string(),
s.r.to_string(),
s.s.to_string(),
s.t.to_string(),
s.u.to_string(),
],
});
}
// Some weird-looking tables.
define_tables! {
// A table with many fields, of many different types.
LargeTable {
insert insert_large_table,
delete delete_large_table,
}
a u8,
b u16,
c u32,
d u64,
e u128,
f u256,
g i8,
h i16,
i i32,
j i64,
k i128,
l i256,
m bool,
n f32,
o f64,
p String,
q SimpleEnum,
r EnumWithPayload,
s UnitStruct,
t ByteStruct,
u EveryPrimitiveStruct,
v EveryVecStruct,
;
// A table which holds instances of other table structs.
// This tests that we can use tables as types.
TableHoldsTable {
insert insert_table_holds_table,
}
a OneU8,
b VecU8,
;
}
#[spacetimedb::reducer]
fn no_op_succeeds(_ctx: &ReducerContext) {}
#[spacetimedb::client_visibility_filter]
const ONE_U8_VISIBLE: spacetimedb::Filter = spacetimedb::Filter::Sql("SELECT * FROM one_u8");
#[spacetimedb::table(accessor = scheduled_table, scheduled(send_scheduled_message), public)]
pub struct ScheduledTable {
#[primary_key]
#[auto_inc]
scheduled_id: u64,
scheduled_at: spacetimedb::ScheduleAt,
text: String,
}
#[spacetimedb::reducer]
fn send_scheduled_message(_ctx: &ReducerContext, arg: ScheduledTable) {
let _ = arg.text;
let _ = arg.scheduled_at;
let _ = arg.scheduled_id;
}
#[spacetimedb::table(accessor = indexed_table)]
struct IndexedTable {
#[index(btree)]
player_id: u32,
}
#[spacetimedb::table(accessor = indexed_table_2, index(accessor=player_id_snazz_index, btree(columns = [player_id, player_snazz])))]
struct IndexedTable2 {
player_id: u32,
player_snazz: f32,
}
#[spacetimedb::table(accessor = btree_u32, public)]
struct BTreeU32 {
#[index(btree)]
n: u32,
data: i32,
}
#[spacetimedb::client_visibility_filter]
const USERS_FILTER: spacetimedb::Filter = spacetimedb::Filter::Sql("SELECT * FROM users WHERE identity = :sender");
#[spacetimedb::table(accessor = users, public)]
struct Users {
#[primary_key]
identity: Identity,
name: String,
}
#[spacetimedb::reducer]
fn insert_user(ctx: &ReducerContext, name: String, identity: Identity) -> anyhow::Result<()> {
ctx.db.users().insert(Users { name, identity });
Ok(())
}
#[spacetimedb::table(accessor = indexed_simple_enum, public)]
struct IndexedSimpleEnum {
#[index(btree)]
n: SimpleEnum,
}
#[spacetimedb::reducer]
fn insert_into_indexed_simple_enum(ctx: &ReducerContext, n: SimpleEnum) -> anyhow::Result<()> {
ctx.db.indexed_simple_enum().insert(IndexedSimpleEnum { n });
Ok(())
}
#[spacetimedb::reducer]
fn update_indexed_simple_enum(ctx: &ReducerContext, a: SimpleEnum, b: SimpleEnum) -> anyhow::Result<()> {
if ctx.db.indexed_simple_enum().n().filter(&a).next().is_some() {
ctx.db.indexed_simple_enum().n().delete(&a);
ctx.db.indexed_simple_enum().insert(IndexedSimpleEnum { n: b });
}
Ok(())
}
#[spacetimedb::reducer]
fn sorted_uuids_insert(ctx: &ReducerContext) -> anyhow::Result<()> {
for _ in 0..1000 {
let uuid = ctx.new_uuid_v7()?;
ctx.db.pk_uuid().insert(PkUuid { u: uuid, data: 0 });
}
// Verify UUIDs are sorted
let mut last_uuid = None;
for row in ctx.db.pk_uuid().iter() {
if let Some(last) = last_uuid {
if last >= row.u {
return Err(anyhow!("UUIDs are not sorted correctly"));
}
}
last_uuid = Some(row.u);
}
Ok(())
}