From 9e9d69c4109786ae19d03185202bc3e98ed45707 Mon Sep 17 00:00:00 2001 From: joshua-spacetime Date: Fri, 7 Mar 2025 19:10:04 -0800 Subject: [PATCH] test: intra-query bag semantics for joins --- crates/sdk/tests/test-client/src/main.rs | 52 +++++++++ .../src/module_bindings/b_tree_u_32_type.rs | 16 +++ .../src/module_bindings/btree_u_32_table.rs | 95 ++++++++++++++++ .../delete_from_btree_u_32_reducer.rs | 104 ++++++++++++++++++ .../insert_into_btree_u_32_reducer.rs | 104 ++++++++++++++++++ .../test-client/src/module_bindings/mod.rs | 34 ++++++ crates/sdk/tests/test.rs | 5 + modules/sdk-test-cs/Lib.cs | 26 +++++ modules/sdk-test/src/lib.rs | 23 ++++ 9 files changed, 459 insertions(+) create mode 100644 crates/sdk/tests/test-client/src/module_bindings/b_tree_u_32_type.rs create mode 100644 crates/sdk/tests/test-client/src/module_bindings/btree_u_32_table.rs create mode 100644 crates/sdk/tests/test-client/src/module_bindings/delete_from_btree_u_32_reducer.rs create mode 100644 crates/sdk/tests/test-client/src/module_bindings/insert_into_btree_u_32_reducer.rs diff --git a/crates/sdk/tests/test-client/src/main.rs b/crates/sdk/tests/test-client/src/main.rs index 592bdabdfc8..5122af7bdfa 100644 --- a/crates/sdk/tests/test-client/src/main.rs +++ b/crates/sdk/tests/test-client/src/main.rs @@ -121,6 +121,7 @@ fn main() { "row-deduplication" => exec_row_deduplication(), "row-deduplication-join-r-and-s" => exec_row_deduplication_join_r_and_s(), "row-deduplication-r-join-s-and-r-joint" => exec_row_deduplication_r_join_s_and_r_join_t(), + "test-intra-query-bag-semantics-for-join" => test_intra_query_bag_semantics_for_join(), _ => panic!("Unknown test: {}", test), } } @@ -2084,3 +2085,54 @@ fn exec_row_deduplication_r_join_s_and_r_join_t() { assert_eq!(count_unique_u32_on_insert.load(Ordering::SeqCst), 1); } + +/// Test that when subscribing to a single join query, +/// the server returns a bag of rows to the client - not a set. +/// +/// This is a regression test for [2397](https://github.com/clockworklabs/SpacetimeDB/issues/2397), +/// where the server was incorrectly deduplicating incremental subscription updates. +fn test_intra_query_bag_semantics_for_join() { + let test_counter = TestCounter::new(); + let sub_applied_nothing_result = test_counter.add_test("on_subscription_applied_nothing"); + + connect_then(&test_counter, { + move |ctx| { + subscribe_these_then( + ctx, + &["SELECT pk_u32.* FROM pk_u32 JOIN btree_u32 ON pk_u32.n = btree_u32.n"], + move |ctx| { + // Insert the row (n: 0, data: 1) into pk_u32. + // At this point btree_u32 is empty, + // so no subscription update will be sent, + // and no callbacks invoked. + PkU32::insert(ctx, 0, 1); + + // Now we insert two tuples into btree_u32. + // Both of them join with the row in pk_u32. + // Hence an update will be sent from the server, + // and on_insert invoked for (n: 0, data: 1). + // Note, the multiplicity of this row is 2. + ctx.reducers + .insert_into_btree_u_32(vec![BTreeU32 { n: 0, data: 0 }, BTreeU32 { n: 0, data: 1 }]) + .unwrap(); + + // Now we delete one of the rows in btree_u32. + // If we have implemented bag semantics correctly, + // we will not invoke pk_u32::on_delete, + // because (n: 0, data: 1) still has a multiplicity of 1. + ctx.reducers + .delete_from_btree_u_32(vec![BTreeU32 { n: 0, data: 0 }]) + .unwrap(); + + sub_applied_nothing_result(assert_all_tables_empty(ctx)); + }, + ); + + PkU32::on_delete(ctx, |_, _| { + panic!("Bag semantics not implemented correctly; we never delete a `PkU32`") + }); + } + }); + + test_counter.wait_for_all(); +} diff --git a/crates/sdk/tests/test-client/src/module_bindings/b_tree_u_32_type.rs b/crates/sdk/tests/test-client/src/module_bindings/b_tree_u_32_type.rs new file mode 100644 index 00000000000..31b702493be --- /dev/null +++ b/crates/sdk/tests/test-client/src/module_bindings/b_tree_u_32_type.rs @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct BTreeU32 { + pub n: u32, + pub data: i32, +} + +impl __sdk::InModule for BTreeU32 { + type Module = super::RemoteModule; +} diff --git a/crates/sdk/tests/test-client/src/module_bindings/btree_u_32_table.rs b/crates/sdk/tests/test-client/src/module_bindings/btree_u_32_table.rs new file mode 100644 index 00000000000..7e13e2cc9e3 --- /dev/null +++ b/crates/sdk/tests/test-client/src/module_bindings/btree_u_32_table.rs @@ -0,0 +1,95 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::b_tree_u_32_type::BTreeU32; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `btree_u32`. +/// +/// Obtain a handle from the [`BtreeU32TableAccess::btree_u_32`] method on [`super::RemoteTables`], +/// like `ctx.db.btree_u_32()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.btree_u_32().on_insert(...)`. +pub struct BtreeU32TableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `btree_u32`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait BtreeU32TableAccess { + #[allow(non_snake_case)] + /// Obtain a [`BtreeU32TableHandle`], which mediates access to the table `btree_u32`. + fn btree_u_32(&self) -> BtreeU32TableHandle<'_>; +} + +impl BtreeU32TableAccess for super::RemoteTables { + fn btree_u_32(&self) -> BtreeU32TableHandle<'_> { + BtreeU32TableHandle { + imp: self.imp.get_table::("btree_u32"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct BtreeU32InsertCallbackId(__sdk::CallbackId); +pub struct BtreeU32DeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for BtreeU32TableHandle<'ctx> { + type Row = BTreeU32; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = BtreeU32InsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> BtreeU32InsertCallbackId { + BtreeU32InsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: BtreeU32InsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = BtreeU32DeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> BtreeU32DeleteCallbackId { + BtreeU32DeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: BtreeU32DeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("btree_u32"); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} diff --git a/crates/sdk/tests/test-client/src/module_bindings/delete_from_btree_u_32_reducer.rs b/crates/sdk/tests/test-client/src/module_bindings/delete_from_btree_u_32_reducer.rs new file mode 100644 index 00000000000..253f3237c5c --- /dev/null +++ b/crates/sdk/tests/test-client/src/module_bindings/delete_from_btree_u_32_reducer.rs @@ -0,0 +1,104 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::b_tree_u_32_type::BTreeU32; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct DeleteFromBtreeU32Args { + pub rows: Vec, +} + +impl From for super::Reducer { + fn from(args: DeleteFromBtreeU32Args) -> Self { + Self::DeleteFromBtreeU32 { rows: args.rows } + } +} + +impl __sdk::InModule for DeleteFromBtreeU32Args { + type Module = super::RemoteModule; +} + +pub struct DeleteFromBtreeU32CallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `delete_from_btree_u32`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait delete_from_btree_u_32 { + /// Request that the remote module invoke the reducer `delete_from_btree_u32` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_delete_from_btree_u_32`] callbacks. + fn delete_from_btree_u_32(&self, rows: Vec) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `delete_from_btree_u32`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`DeleteFromBtreeU32CallbackId`] can be passed to [`Self::remove_on_delete_from_btree_u_32`] + /// to cancel the callback. + fn on_delete_from_btree_u_32( + &self, + callback: impl FnMut(&super::ReducerEventContext, &Vec) + Send + 'static, + ) -> DeleteFromBtreeU32CallbackId; + /// Cancel a callback previously registered by [`Self::on_delete_from_btree_u_32`], + /// causing it not to run in the future. + fn remove_on_delete_from_btree_u_32(&self, callback: DeleteFromBtreeU32CallbackId); +} + +impl delete_from_btree_u_32 for super::RemoteReducers { + fn delete_from_btree_u_32(&self, rows: Vec) -> __sdk::Result<()> { + self.imp + .call_reducer("delete_from_btree_u32", DeleteFromBtreeU32Args { rows }) + } + fn on_delete_from_btree_u_32( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &Vec) + Send + 'static, + ) -> DeleteFromBtreeU32CallbackId { + DeleteFromBtreeU32CallbackId(self.imp.on_reducer( + "delete_from_btree_u32", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::DeleteFromBtreeU32 { rows }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, rows) + }), + )) + } + fn remove_on_delete_from_btree_u_32(&self, callback: DeleteFromBtreeU32CallbackId) { + self.imp.remove_on_reducer("delete_from_btree_u32", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `delete_from_btree_u32`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_delete_from_btree_u_32 { + /// Set the call-reducer flags for the reducer `delete_from_btree_u32` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn delete_from_btree_u_32(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_delete_from_btree_u_32 for super::SetReducerFlags { + fn delete_from_btree_u_32(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("delete_from_btree_u32", flags); + } +} diff --git a/crates/sdk/tests/test-client/src/module_bindings/insert_into_btree_u_32_reducer.rs b/crates/sdk/tests/test-client/src/module_bindings/insert_into_btree_u_32_reducer.rs new file mode 100644 index 00000000000..51433ea79a4 --- /dev/null +++ b/crates/sdk/tests/test-client/src/module_bindings/insert_into_btree_u_32_reducer.rs @@ -0,0 +1,104 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::b_tree_u_32_type::BTreeU32; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct InsertIntoBtreeU32Args { + pub rows: Vec, +} + +impl From for super::Reducer { + fn from(args: InsertIntoBtreeU32Args) -> Self { + Self::InsertIntoBtreeU32 { rows: args.rows } + } +} + +impl __sdk::InModule for InsertIntoBtreeU32Args { + type Module = super::RemoteModule; +} + +pub struct InsertIntoBtreeU32CallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `insert_into_btree_u32`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait insert_into_btree_u_32 { + /// Request that the remote module invoke the reducer `insert_into_btree_u32` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_insert_into_btree_u_32`] callbacks. + fn insert_into_btree_u_32(&self, rows: Vec) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `insert_into_btree_u32`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`InsertIntoBtreeU32CallbackId`] can be passed to [`Self::remove_on_insert_into_btree_u_32`] + /// to cancel the callback. + fn on_insert_into_btree_u_32( + &self, + callback: impl FnMut(&super::ReducerEventContext, &Vec) + Send + 'static, + ) -> InsertIntoBtreeU32CallbackId; + /// Cancel a callback previously registered by [`Self::on_insert_into_btree_u_32`], + /// causing it not to run in the future. + fn remove_on_insert_into_btree_u_32(&self, callback: InsertIntoBtreeU32CallbackId); +} + +impl insert_into_btree_u_32 for super::RemoteReducers { + fn insert_into_btree_u_32(&self, rows: Vec) -> __sdk::Result<()> { + self.imp + .call_reducer("insert_into_btree_u32", InsertIntoBtreeU32Args { rows }) + } + fn on_insert_into_btree_u_32( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &Vec) + Send + 'static, + ) -> InsertIntoBtreeU32CallbackId { + InsertIntoBtreeU32CallbackId(self.imp.on_reducer( + "insert_into_btree_u32", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::InsertIntoBtreeU32 { rows }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, rows) + }), + )) + } + fn remove_on_insert_into_btree_u_32(&self, callback: InsertIntoBtreeU32CallbackId) { + self.imp.remove_on_reducer("insert_into_btree_u32", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `insert_into_btree_u32`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_insert_into_btree_u_32 { + /// Set the call-reducer flags for the reducer `insert_into_btree_u32` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn insert_into_btree_u_32(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_insert_into_btree_u_32 for super::SetReducerFlags { + fn insert_into_btree_u_32(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("insert_into_btree_u32", flags); + } +} diff --git a/crates/sdk/tests/test-client/src/module_bindings/mod.rs b/crates/sdk/tests/test-client/src/module_bindings/mod.rs index 269b3bc4d39..f3a061375c7 100644 --- a/crates/sdk/tests/test-client/src/module_bindings/mod.rs +++ b/crates/sdk/tests/test-client/src/module_bindings/mod.rs @@ -4,7 +4,10 @@ #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; +pub mod b_tree_u_32_type; +pub mod btree_u_32_table; pub mod byte_struct_type; +pub mod delete_from_btree_u_32_reducer; pub mod delete_large_table_reducer; pub mod delete_pk_bool_reducer; pub mod delete_pk_connection_id_reducer; @@ -56,6 +59,7 @@ pub mod insert_caller_unique_connection_id_reducer; pub mod insert_caller_unique_identity_reducer; pub mod insert_caller_vec_connection_id_reducer; pub mod insert_caller_vec_identity_reducer; +pub mod insert_into_btree_u_32_reducer; pub mod insert_large_table_reducer; pub mod insert_one_bool_reducer; pub mod insert_one_byte_struct_reducer; @@ -371,7 +375,12 @@ pub mod vec_u_8_type; pub mod vec_unit_struct_table; pub mod vec_unit_struct_type; +pub use b_tree_u_32_type::BTreeU32; +pub use btree_u_32_table::*; pub use byte_struct_type::ByteStruct; +pub use delete_from_btree_u_32_reducer::{ + delete_from_btree_u_32, set_flags_for_delete_from_btree_u_32, DeleteFromBtreeU32CallbackId, +}; pub use delete_large_table_reducer::{ delete_large_table, set_flags_for_delete_large_table, DeleteLargeTableCallbackId, }; @@ -469,6 +478,9 @@ pub use insert_caller_vec_connection_id_reducer::{ pub use insert_caller_vec_identity_reducer::{ insert_caller_vec_identity, set_flags_for_insert_caller_vec_identity, InsertCallerVecIdentityCallbackId, }; +pub use insert_into_btree_u_32_reducer::{ + insert_into_btree_u_32, set_flags_for_insert_into_btree_u_32, InsertIntoBtreeU32CallbackId, +}; pub use insert_large_table_reducer::{ insert_large_table, set_flags_for_insert_large_table, InsertLargeTableCallbackId, }; @@ -892,6 +904,9 @@ pub use vec_unit_struct_type::VecUnitStruct; /// to indicate which reducer caused the event. pub enum Reducer { + DeleteFromBtreeU32 { + rows: Vec, + }, DeleteLargeTable { a: u8, b: u16, @@ -1036,6 +1051,9 @@ pub enum Reducer { }, InsertCallerVecConnectionId, InsertCallerVecIdentity, + InsertIntoBtreeU32 { + rows: Vec, + }, InsertLargeTable { a: u8, b: u16, @@ -1517,6 +1535,7 @@ impl __sdk::InModule for Reducer { impl __sdk::Reducer for Reducer { fn reducer_name(&self) -> &'static str { match self { + Reducer::DeleteFromBtreeU32 { .. } => "delete_from_btree_u32", Reducer::DeleteLargeTable { .. } => "delete_large_table", Reducer::DeletePkBool { .. } => "delete_pk_bool", Reducer::DeletePkConnectionId { .. } => "delete_pk_connection_id", @@ -1561,6 +1580,7 @@ impl __sdk::Reducer for Reducer { Reducer::InsertCallerUniqueIdentity { .. } => "insert_caller_unique_identity", Reducer::InsertCallerVecConnectionId => "insert_caller_vec_connection_id", Reducer::InsertCallerVecIdentity => "insert_caller_vec_identity", + Reducer::InsertIntoBtreeU32 { .. } => "insert_into_btree_u32", Reducer::InsertLargeTable { .. } => "insert_large_table", Reducer::InsertOneBool { .. } => "insert_one_bool", Reducer::InsertOneByteStruct { .. } => "insert_one_byte_struct", @@ -1696,6 +1716,10 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { type Error = __sdk::Error; fn try_from(value: __ws::ReducerCallInfo<__ws::BsatnFormat>) -> __sdk::Result { match &value.reducer_name[..] { + "delete_from_btree_u32" => Ok(__sdk::parse_reducer_args::< + delete_from_btree_u_32_reducer::DeleteFromBtreeU32Args, + >("delete_from_btree_u32", &value.args)? + .into()), "delete_large_table" => Ok( __sdk::parse_reducer_args::( "delete_large_table", @@ -1938,6 +1962,10 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { insert_caller_vec_identity_reducer::InsertCallerVecIdentityArgs, >("insert_caller_vec_identity", &value.args)? .into()), + "insert_into_btree_u32" => Ok(__sdk::parse_reducer_args::< + insert_into_btree_u_32_reducer::InsertIntoBtreeU32Args, + >("insert_into_btree_u32", &value.args)? + .into()), "insert_large_table" => Ok( __sdk::parse_reducer_args::( "insert_large_table", @@ -2648,6 +2676,7 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { #[allow(non_snake_case)] #[doc(hidden)] pub struct DbUpdate { + btree_u_32: __sdk::TableUpdate, indexed_table: __sdk::TableUpdate, indexed_table_2: __sdk::TableUpdate, large_table: __sdk::TableUpdate, @@ -2750,6 +2779,7 @@ impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { let mut db_update = DbUpdate::default(); for table_update in raw.tables { match &table_update.table_name[..] { + "btree_u32" => db_update.btree_u_32 = btree_u_32_table::parse_table_update(table_update)?, "indexed_table" => db_update.indexed_table = indexed_table_table::parse_table_update(table_update)?, "indexed_table_2" => { db_update.indexed_table_2 = indexed_table_2_table::parse_table_update(table_update)? @@ -2913,6 +2943,7 @@ impl __sdk::DbUpdate for DbUpdate { fn apply_to_client_cache(&self, cache: &mut __sdk::ClientCache) -> AppliedDiff<'_> { let mut diff = AppliedDiff::default(); + diff.btree_u_32 = cache.apply_diff_to_table::("btree_u32", &self.btree_u_32); diff.indexed_table = cache.apply_diff_to_table::("indexed_table", &self.indexed_table); diff.indexed_table_2 = cache.apply_diff_to_table::("indexed_table_2", &self.indexed_table_2); diff.large_table = cache.apply_diff_to_table::("large_table", &self.large_table); @@ -3071,6 +3102,7 @@ impl __sdk::DbUpdate for DbUpdate { #[allow(non_snake_case)] #[doc(hidden)] pub struct AppliedDiff<'r> { + btree_u_32: __sdk::TableAppliedDiff<'r, BTreeU32>, indexed_table: __sdk::TableAppliedDiff<'r, IndexedTable>, indexed_table_2: __sdk::TableAppliedDiff<'r, IndexedTable2>, large_table: __sdk::TableAppliedDiff<'r, LargeTable>, @@ -3173,6 +3205,7 @@ impl __sdk::InModule for AppliedDiff<'_> { impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { fn invoke_row_callbacks(&self, event: &EventContext, callbacks: &mut __sdk::DbCallbacks) { + callbacks.invoke_table_row_callbacks::("btree_u32", &self.btree_u_32, event); callbacks.invoke_table_row_callbacks::("indexed_table", &self.indexed_table, event); callbacks.invoke_table_row_callbacks::("indexed_table_2", &self.indexed_table_2, event); callbacks.invoke_table_row_callbacks::("large_table", &self.large_table, event); @@ -3878,6 +3911,7 @@ impl __sdk::SpacetimeModule for RemoteModule { type SubscriptionHandle = SubscriptionHandle; fn register_tables(client_cache: &mut __sdk::ClientCache) { + btree_u_32_table::register_table(client_cache); indexed_table_table::register_table(client_cache); indexed_table_2_table::register_table(client_cache); large_table_table::register_table(client_cache); diff --git a/crates/sdk/tests/test.rs b/crates/sdk/tests/test.rs index 211f5eb0058..91927beff4f 100644 --- a/crates/sdk/tests/test.rs +++ b/crates/sdk/tests/test.rs @@ -215,6 +215,11 @@ macro_rules! declare_tests_with_suffix { fn row_deduplication_r_join_s_and_r_join_t8() { make_test("row-deduplication-r-join-s-and-r-joint").run(); } + + #[test] + fn test_intra_query_bag_semantics_for_join() { + make_test("test-intra-query-bag-semantics-for-join").run() + } } }; } diff --git a/modules/sdk-test-cs/Lib.cs b/modules/sdk-test-cs/Lib.cs index ec5eebea551..a1210e460e1 100644 --- a/modules/sdk-test-cs/Lib.cs +++ b/modules/sdk-test-cs/Lib.cs @@ -1739,6 +1739,24 @@ public static void insert_caller_pk_connection_id(ReducerContext ctx, int data) ctx.Db.pk_connection_id.Insert(new PkConnectionId { a = (ConnectionId)ctx.ConnectionId!, data = data }); } + [SpacetimeDB.Reducer] + public static void insert_into_btree_u32(ReducerContext ctx, List rows) + { + foreach (var row in rows) + { + ctx.Db.btree_u32.Insert(row); + } + } + + [SpacetimeDB.Reducer] + public static void delete_from_btree_u32(ReducerContext ctx, List rows) + { + foreach (var row in rows) + { + ctx.Db.btree_u32.Delete(row); + } + } + [SpacetimeDB.Table(Name = "large_table", Public = true)] public partial struct LargeTable { @@ -1954,4 +1972,12 @@ public partial struct IndexedTable2 uint player_id; float player_snazz; } + + [SpacetimeDB.Table(Name = "btree_u32", Public = true)] + public partial struct BTreeU32 + { + [SpacetimeDB.Index.BTree] + uint n; + int data; + } } diff --git a/modules/sdk-test/src/lib.rs b/modules/sdk-test/src/lib.rs index 46f74998408..a85d33a0f20 100644 --- a/modules/sdk-test/src/lib.rs +++ b/modules/sdk-test/src/lib.rs @@ -544,6 +544,22 @@ define_tables! { } #[primary_key] a ConnectionId, data i32; } +#[spacetimedb::reducer] +fn insert_into_btree_u32(ctx: &ReducerContext, rows: Vec) -> anyhow::Result<()> { + for row in rows { + ctx.db.btree_u32().insert(row); + } + Ok(()) +} + +#[spacetimedb::reducer] +fn delete_from_btree_u32(ctx: &ReducerContext, rows: Vec) -> anyhow::Result<()> { + for row in rows { + ctx.db.btree_u32().delete(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. @@ -731,3 +747,10 @@ struct IndexedTable2 { player_id: u32, player_snazz: f32, } + +#[spacetimedb::table(name = btree_u32, public)] +struct BTreeU32 { + #[index(btree)] + n: u32, + data: i32, +}