diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs index a4bdb86fac4..5d6c6321776 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs @@ -904,6 +904,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView.DoCount(); @@ -997,6 +1003,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< TestAutoIncNotInteger, @@ -1087,6 +1099,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< TestDefaultFieldValues, @@ -1145,6 +1163,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< TestDuplicateTableName, @@ -1232,6 +1256,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< TestIndexIssues, @@ -1425,6 +1455,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< TestScheduleWithMissingScheduleAtField, @@ -1487,6 +1523,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar global::TestScheduleIssues >.MakeSchedule("DummyScheduledReducer", 3); + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< TestScheduleWithoutPrimaryKey, @@ -1558,6 +1600,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< TestScheduleWithoutScheduleAt, @@ -1655,6 +1703,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar global::TestScheduleIssues >.MakeSchedule("DummyScheduledReducer", 3); + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< TestScheduleWithWrongPrimaryKeyType, @@ -1752,6 +1806,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar global::TestScheduleIssues >.MakeSchedule("DummyScheduledReducer", 2); + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< TestScheduleWithWrongScheduleAtType, @@ -1854,6 +1914,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< TestUniqueNotEquatable, @@ -2441,6 +2507,12 @@ public sealed class PlayerReadOnly internal PlayerReadOnly() : base("Player") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class IdentityIndex @@ -2466,6 +2538,12 @@ public sealed class TestAutoIncNotIntegerReadOnly internal TestAutoIncNotIntegerReadOnly() : base("TestAutoIncNotInteger") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class IdentityFieldIndex @@ -2491,6 +2569,12 @@ public sealed class TestDefaultFieldValuesReadOnly internal TestDefaultFieldValuesReadOnly() : base("TestDefaultFieldValues") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); } @@ -2500,6 +2584,12 @@ public sealed class TestDuplicateTableNameReadOnly internal TestDuplicateTableNameReadOnly() : base("TestDuplicateTableName") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); } @@ -2509,6 +2599,12 @@ public sealed class TestIndexIssuesReadOnly internal TestIndexIssuesReadOnly() : base("TestIndexIssues") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class TestIndexWithoutColumnsIndex @@ -2620,6 +2716,12 @@ public sealed class TestScheduleWithMissingScheduleAtFieldReadOnly internal TestScheduleWithMissingScheduleAtFieldReadOnly() : base("TestScheduleWithMissingScheduleAtField") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); } @@ -2629,6 +2731,12 @@ public sealed class TestScheduleWithoutPrimaryKeyReadOnly internal TestScheduleWithoutPrimaryKeyReadOnly() : base("TestScheduleWithoutPrimaryKey") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); } @@ -2638,6 +2746,12 @@ public sealed class TestScheduleWithoutScheduleAtReadOnly internal TestScheduleWithoutScheduleAtReadOnly() : base("TestScheduleWithoutScheduleAt") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class IdCorrectTypeIndex @@ -2663,6 +2777,12 @@ public sealed class TestScheduleWithWrongPrimaryKeyTypeReadOnly internal TestScheduleWithWrongPrimaryKeyTypeReadOnly() : base("TestScheduleWithWrongPrimaryKeyType") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class IdWrongTypeIndex @@ -2688,6 +2808,12 @@ public sealed class TestScheduleWithWrongScheduleAtTypeReadOnly internal TestScheduleWithWrongScheduleAtTypeReadOnly() : base("TestScheduleWithWrongScheduleAtType") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class IdCorrectTypeIndex @@ -2713,6 +2839,12 @@ public sealed class TestUniqueNotEquatableReadOnly internal TestUniqueNotEquatableReadOnly() : base("TestUniqueNotEquatable") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class PrimaryKeyFieldIndex diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/explicitnames/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/explicitnames/snapshots/Module#FFI.verified.cs index 82127603515..dd315283c7b 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/explicitnames/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/explicitnames/snapshots/Module#FFI.verified.cs @@ -294,6 +294,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView.DoCount(); @@ -391,6 +397,12 @@ public sealed class DemoTableReadOnly internal DemoTableReadOnly() : base("DemoTable") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class IdIndex diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs index 260b42c3098..fdaef0c96be 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs @@ -734,6 +734,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< BTreeMultiColumn, @@ -926,6 +932,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView.DoCount(); @@ -1114,6 +1126,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView.DoCount(); @@ -1236,6 +1254,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView.DoCount(); @@ -1297,6 +1321,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView.DoCount(); @@ -1367,6 +1397,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView.DoCount(); @@ -1453,6 +1489,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< RegressionMultipleUniqueIndexesHadSameName, @@ -1577,6 +1619,12 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar global::Timers.SendMessageTimer >.MakeSchedule("SendScheduledMessage", 1); + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => global::SpacetimeDB.Internal.ITableView< SendMessageTimer, @@ -1769,6 +1817,12 @@ internal sealed class BTreeMultiColumnReadOnly internal BTreeMultiColumnReadOnly() : base("BTreeMultiColumn") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class LocationIndex @@ -1851,6 +1905,12 @@ internal sealed class BTreeViewsReadOnly internal BTreeViewsReadOnly() : base("BTreeViews") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class IdIndex @@ -1948,6 +2008,12 @@ public sealed class MultiTable1ReadOnly internal MultiTable1ReadOnly() : base("MultiTable1") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class FooIndex @@ -2000,6 +2066,12 @@ public sealed class MultiTable2ReadOnly internal MultiTable2ReadOnly() : base("MultiTable2") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class BarIndex @@ -2025,6 +2097,12 @@ public sealed class PrivateTableReadOnly internal PrivateTableReadOnly() : base("PrivateTable") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); } @@ -2034,6 +2112,12 @@ public sealed class PublicTableReadOnly internal PublicTableReadOnly() : base("PublicTable") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class IdIndex @@ -2059,6 +2143,12 @@ internal sealed class RegressionMultipleUniqueIndexesHadSameNameReadOnly internal RegressionMultipleUniqueIndexesHadSameNameReadOnly() : base("RegressionMultipleUniqueIndexesHadSameName") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class Unique1Index @@ -2102,6 +2192,12 @@ public sealed class SendMessageTimerReadOnly internal SendMessageTimerReadOnly() : base("SendMessageTimer") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); public sealed class ScheduledIdIndex diff --git a/crates/bindings-csharp/Codegen/Module.cs b/crates/bindings-csharp/Codegen/Module.cs index 111503f0472..504a0789046 100644 --- a/crates/bindings-csharp/Codegen/Module.cs +++ b/crates/bindings-csharp/Codegen/Module.cs @@ -831,6 +831,12 @@ v.Scheduled is { } scheduled : "null" )}}}; + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => {{{iTable}}}.DoCount(); public IEnumerable<{{{globalName}}}> Iter() => {{{iTable}}}.DoIter(); public {{{globalName}}} Insert({{{globalName}}} row) => {{{iTable}}}.DoInsert(row); @@ -873,6 +879,12 @@ public IEnumerable GenerateReadOnlyAccessors() { internal {{{accessor.Name}}}ReadOnly() : base("{{{accessor.Name}}}") { } + /// + /// Returns the number of rows in this table. + /// + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. + /// public ulong Count => DoCount(); {{{readOnlyIndexDecls}}} diff --git a/crates/bindings-macro/src/table.rs b/crates/bindings-macro/src/table.rs index ca6d08c4d56..1cbba4c5ca1 100644 --- a/crates/bindings-macro/src/table.rs +++ b/crates/bindings-macro/src/table.rs @@ -1302,6 +1302,11 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R } impl #viewhandle_ident { + #[inline] + pub fn count(&self) -> u64 { + spacetimedb::table::count::<#tablehandle_ident>() + } + #(#index_accessors_ro)* } diff --git a/crates/bindings-typescript/src/lib/table.ts b/crates/bindings-typescript/src/lib/table.ts index a07611e9500..4db184c8098 100644 --- a/crates/bindings-typescript/src/lib/table.ts +++ b/crates/bindings-typescript/src/lib/table.ts @@ -239,7 +239,12 @@ export type ReadonlyTable = Prettify< >; export interface ReadonlyTableMethods { - /** Returns the number of rows in the TX state. */ + /** + * Returns the number of rows in this table. + * + * This reads datastore metadata, so it runs in constant time. + * It also takes into account modifications by the current transaction. + */ count(): bigint; /** Iterate over all rows in the TX state. Rust Iterator → TS IterableIterator. */ diff --git a/crates/bindings/src/lib.rs b/crates/bindings/src/lib.rs index a74534663ee..f1eec19e79a 100644 --- a/crates/bindings/src/lib.rs +++ b/crates/bindings/src/lib.rs @@ -822,6 +822,11 @@ pub use spacetimedb_bindings_macro::procedure; /// id: u64, /// } /// +/// #[derive(SpacetimeType)] +/// struct PlayerCount { +/// count: u64, +/// } +/// /// #[table(accessor = location, index(accessor = coordinates, btree(columns = [x, y])))] /// struct Location { /// #[unique] @@ -850,6 +855,14 @@ pub use spacetimedb_bindings_macro::procedure; /// ctx.db.player().identity().find(ctx.sender()).map(|Player { id, .. }| PlayerId { id }) /// } /// +/// // A view that counts the number of rows in a table +/// #[view(accessor = player_count, public)] +/// fn player_count(ctx: &AnonymousViewContext) -> Option { +/// Some(PlayerCount { +/// count: ctx.db.player().count(), +/// }) +/// } +/// /// // An example that is analogous to a semijoin in sql /// #[view(accessor = players_at_coordinates, public)] /// fn players_at_coordinates(ctx: &AnonymousViewContext) -> Vec { diff --git a/crates/bindings/src/table.rs b/crates/bindings/src/table.rs index 5a043b083e1..a4c25c039e2 100644 --- a/crates/bindings/src/table.rs +++ b/crates/bindings/src/table.rs @@ -21,13 +21,12 @@ pub trait Table: TableInternal + ExplicitNames { /// The type of rows stored in this table. type Row: SpacetimeType + Serialize + DeserializeOwned + Sized + 'static; - /// Returns the number of rows of this table. + /// Returns the number of rows in this table. /// - /// This takes into account modifications by the current transaction, - /// even though those modifications have not yet been committed or broadcast to clients. - /// This applies generally to insertions, deletions, updates, and iteration as well. + /// This reads datastore metadata, so it runs in constant time. + /// It also takes into account modifications by the current transaction. fn count(&self) -> u64 { - sys::datastore_table_row_count(Self::table_id()).expect("datastore_table_row_count() call failed") + count::() } /// Iterate over all rows of the table. @@ -119,6 +118,12 @@ pub trait Table: TableInternal + ExplicitNames { fn integrate_generated_columns(row: &mut Self::Row, generated_cols: &[u8]); } +#[doc(hidden)] +#[inline] +pub fn count() -> u64 { + sys::datastore_table_row_count(Tbl::table_id()).expect("datastore_table_row_count() call failed") +} + #[doc(hidden)] pub trait TableInternal: Sized { const TABLE_NAME: &'static str; diff --git a/crates/bindings/tests/ui/views.rs b/crates/bindings/tests/ui/views.rs index 2e2b9320696..06ba581d84d 100644 --- a/crates/bindings/tests/ui/views.rs +++ b/crates/bindings/tests/ui/views.rs @@ -15,13 +15,6 @@ fn view_handle_no_iter(ctx: &ReducerContext) { for _ in read_only.db.test().iter() {} } -#[reducer] -fn view_handle_no_count(ctx: &ReducerContext) { - let read_only = ctx.as_read_only(); - // Should not compile: ViewHandle does not expose `count()` - let _ = read_only.db.test().count(); -} - #[reducer] fn view_handle_no_insert(ctx: &ReducerContext) { let read_only = ctx.as_read_only(); diff --git a/crates/bindings/tests/ui/views.stderr b/crates/bindings/tests/ui/views.stderr index 928c6889e1a..1baf379ceea 100644 --- a/crates/bindings/tests/ui/views.stderr +++ b/crates/bindings/tests/ui/views.stderr @@ -1,105 +1,105 @@ error: views must be `public`, e.g. `#[view(public)]` - --> tests/ui/views.rs:76:1 + --> tests/ui/views.rs:69:1 | -76 | #[view(accessor = view_def_no_public)] +69 | #[view(accessor = view_def_no_public)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error: `public` already specified - --> tests/ui/views.rs:82:48 + --> tests/ui/views.rs:75:48 | -82 | #[view(accessor = view_def_dup_public, public, public)] +75 | #[view(accessor = view_def_dup_public, public, public)] | ^^^^^^ error: expected string literal - --> tests/ui/views.rs:88:45 + --> tests/ui/views.rs:81:45 | -88 | #[view(accessor = view_def_dup_name, name = view_def_dup_name, public)] +81 | #[view(accessor = view_def_dup_name, name = view_def_dup_name, public)] | ^^^^^^^^^^^^^^^^^ error: expected one of: `name`, `public`, `accessor` - --> tests/ui/views.rs:94:53 + --> tests/ui/views.rs:87:53 | -94 | #[view(accessor = view_def_unsupported_arg, public, anonymous)] +87 | #[view(accessor = view_def_unsupported_arg, public, anonymous)] | ^^^^^^^^^ error: Views must always have a context parameter: `&ViewContext` or `&AnonymousViewContext` - --> tests/ui/views.rs:101:1 - | -101 | fn view_def_no_context() -> Vec { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui/views.rs:94:1 + | +94 | fn view_def_no_context() -> Vec { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: The first parameter of a view must be a context parameter: `&ViewContext` or `&AnonymousViewContext`; passed by reference - --> tests/ui/views.rs:113:38 + --> tests/ui/views.rs:106:38 | -113 | fn view_def_pass_context_by_value(_: ViewContext) -> Vec { +106 | fn view_def_pass_context_by_value(_: ViewContext) -> Vec { | ^^^^^^^^^^^ error: Views do not take parameters other than `&ViewContext` or `&AnonymousViewContext` - --> tests/ui/views.rs:119:1 + --> tests/ui/views.rs:112:1 | -119 | fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec { +112 | fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: views must return `Vec` or `Option` where `T` is a `SpacetimeType` - --> tests/ui/views.rs:125:1 + --> tests/ui/views.rs:118:1 | -125 | fn view_def_no_return(_: &ViewContext) {} +118 | fn view_def_no_return(_: &ViewContext) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Views do not take parameters other than `&ViewContext` or `&AnonymousViewContext` - --> tests/ui/views.rs:143:1 + --> tests/ui/views.rs:136:1 | -143 | fn sched_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { +136 | fn sched_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0425]: cannot find type `ScheduledTable` in this scope - --> tests/ui/views.rs:143:45 + --> tests/ui/views.rs:136:45 | -143 | fn sched_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { +136 | fn sched_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { | ^^^^^^^^^^^^^^ not found in this scope error[E0425]: cannot find type `T` in this scope - --> tests/ui/views.rs:201:60 + --> tests/ui/views.rs:194:60 | -201 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { +194 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { | ^ not found in this scope | help: you might be missing a type parameter | -201 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { +194 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { | +++ error[E0425]: cannot find type `T` in this scope - --> tests/ui/views.rs:201:60 + --> tests/ui/views.rs:194:60 | -201 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { +194 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { | ^ not found in this scope error[E0277]: the trait bound `spacetimedb::rt::ViewKind: ViewKindTrait` is not satisfied - --> tests/ui/views.rs:106:1 - | -106 | #[view(accessor = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ViewKindTrait` is not implemented for `spacetimedb::rt::ViewKind` - | + --> tests/ui/views.rs:99:1 + | +99 | #[view(accessor = view_def_wrong_context, public)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ViewKindTrait` is not implemented for `spacetimedb::rt::ViewKind` + | help: the following other types implement trait `ViewKindTrait` - --> src/rt.rs - | - | impl ViewKindTrait for ViewKind { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `spacetimedb::rt::ViewKind` + --> src/rt.rs + | + | impl ViewKindTrait for ViewKind { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `spacetimedb::rt::ViewKind` ... - | impl ViewKindTrait for ViewKind { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `spacetimedb::rt::ViewKind` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) + | impl ViewKindTrait for ViewKind { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `spacetimedb::rt::ViewKind` + = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0276]: impl has stricter requirements than trait - --> tests/ui/views.rs:106:1 - | -106 | #[view(accessor = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl has extra requirement `spacetimedb::rt::ViewKind: ViewKindTrait` - | - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/ui/views.rs:99:1 + | +99 | #[view(accessor = view_def_wrong_context, public)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl has extra requirement `spacetimedb::rt::ViewKind: ViewKindTrait` + | + = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no method named `iter` found for reference `&test__ViewHandle` in the current scope --> tests/ui/views.rs:15:34 @@ -112,34 +112,10 @@ error[E0599]: no method named `iter` found for reference `&test__ViewHandle` in candidate #1: `bitflags::traits::Flags` candidate #2: `spacetimedb::Table` -error[E0599]: `&test__ViewHandle` is not an iterator - --> tests/ui/views.rs:22:33 - | - 3 | #[table(accessor = test)] - | ----------------------- doesn't satisfy `test__ViewHandle: Iterator` -... -22 | let _ = read_only.db.test().count(); - | ^^^^^ `&test__ViewHandle` is not an iterator - | - = note: the following trait bounds were not satisfied: - `&test__ViewHandle: Iterator` - which is required by `&mut &test__ViewHandle: Iterator` - `test__ViewHandle: Iterator` - which is required by `&mut test__ViewHandle: Iterator` -note: the trait `Iterator` must be implemented - --> $RUST/core/src/iter/traits/iterator.rs - | - | pub trait Iterator { - | ^^^^^^^^^^^^^^^^^^ - = help: items from traits can only be used if the trait is implemented and in scope - = note: the following traits define an item `count`, perhaps you need to implement one of them: - candidate #1: `spacetimedb::Table` - = note: the trait `Iterator` defines an item `count`, but is explicitly unimplemented - error[E0599]: no method named `insert` found for reference `&test__ViewHandle` in the current scope - --> tests/ui/views.rs:29:25 + --> tests/ui/views.rs:22:25 | -29 | read_only.db.test().insert(Test { id: 0, x: 0 }); +22 | read_only.db.test().insert(Test { id: 0, x: 0 }); | ^^^^^^ method not found in `&test__ViewHandle` | = help: items from traits can only be used if the trait is implemented and in scope @@ -151,9 +127,9 @@ error[E0599]: no method named `insert` found for reference `&test__ViewHandle` i candidate #5: `spacetimedb::Table` error[E0599]: no method named `try_insert` found for reference `&test__ViewHandle` in the current scope - --> tests/ui/views.rs:36:25 + --> tests/ui/views.rs:29:25 | -36 | read_only.db.test().try_insert(Test { id: 0, x: 0 }); +29 | read_only.db.test().try_insert(Test { id: 0, x: 0 }); | ^^^^^^^^^^ | = help: items from traits can only be used if the trait is implemented and in scope @@ -167,9 +143,9 @@ help: there is a method `try_into` with a similar name, but with different argum | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0599]: no method named `delete` found for reference `&test__ViewHandle` in the current scope - --> tests/ui/views.rs:43:25 + --> tests/ui/views.rs:36:25 | -43 | read_only.db.test().delete(Test { id: 0, x: 0 }); +36 | read_only.db.test().delete(Test { id: 0, x: 0 }); | ^^^^^^ method not found in `&test__ViewHandle` | = help: items from traits can only be used if the trait is implemented and in scope @@ -178,38 +154,38 @@ error[E0599]: no method named `delete` found for reference `&test__ViewHandle` i candidate #2: `spacetimedb::Table` error[E0599]: no method named `delete` found for struct `UniqueColumnReadOnly` in the current scope - --> tests/ui/views.rs:50:30 + --> tests/ui/views.rs:43:30 | -50 | read_only.db.test().id().delete(&0); +43 | read_only.db.test().id().delete(&0); | ^^^^^^ method not found in `UniqueColumnReadOnly` error[E0599]: no method named `update` found for struct `UniqueColumnReadOnly` in the current scope - --> tests/ui/views.rs:57:30 + --> tests/ui/views.rs:50:30 | -57 | read_only.db.test().id().update(Test { id: 0, x: 0 }); +50 | read_only.db.test().id().update(Test { id: 0, x: 0 }); | ^^^^^^ method not found in `UniqueColumnReadOnly` error[E0599]: no method named `delete` found for struct `RangedIndexReadOnly` in the current scope - --> tests/ui/views.rs:64:29 + --> tests/ui/views.rs:57:29 | -64 | read_only.db.test().x().delete(0u32..); +57 | read_only.db.test().x().delete(0u32..); | ^^^^^^ method not found in `RangedIndexReadOnly` error[E0599]: no function or associated item named `register` found for struct `ViewRegistrar` in the current scope - --> tests/ui/views.rs:106:1 - | -106 | #[view(accessor = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `ViewRegistrar` - | - = note: the function or associated item was found for - - `ViewRegistrar` - - `ViewRegistrar` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/ui/views.rs:99:1 + | +99 | #[view(accessor = view_def_wrong_context, public)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `ViewRegistrar` + | + = note: the function or associated item was found for + - `ViewRegistrar` + - `ViewRegistrar` + = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: The first parameter of a `#[view]` must be `&ViewContext` or `&AnonymousViewContext` - --> tests/ui/views.rs:107:31 + --> tests/ui/views.rs:100:31 | -107 | fn view_def_wrong_context(_: &ReducerContext) -> Vec { +100 | fn view_def_wrong_context(_: &ReducerContext) -> Vec { | ^^^^^^^^^^^^^^ the trait `ViewContextArg` is not implemented for `ReducerContext` | help: the following other types implement trait `ViewContextArg` @@ -221,20 +197,20 @@ help: the following other types implement trait `ViewContextArg` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `AnonymousViewContext` error[E0599]: no function or associated item named `invoke` found for struct `ViewDispatcher` in the current scope - --> tests/ui/views.rs:106:1 - | -106 | #[view(accessor = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `ViewDispatcher` - | - = note: the function or associated item was found for - - `ViewDispatcher` - - `ViewDispatcher` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/ui/views.rs:99:1 + | +99 | #[view(accessor = view_def_wrong_context, public)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `ViewDispatcher` + | + = note: the function or associated item was found for + - `ViewDispatcher` + - `ViewDispatcher` + = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: invalid view signature - --> tests/ui/views.rs:128:1 + --> tests/ui/views.rs:121:1 | -128 | #[view(accessor = view_def_wrong_return, public)] +121 | #[view(accessor = view_def_wrong_return, public)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this view signature is not valid | = help: the trait `spacetimedb::rt::View<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a ViewContext) -> Player {view_def_wrong_return}` @@ -254,15 +230,15 @@ note: required by a bound in `ViewRegistrar::::register` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: Views must return `Vec` or `Option` where `T` is a `SpacetimeType` - --> tests/ui/views.rs:129:46 + --> tests/ui/views.rs:122:46 | -129 | fn view_def_wrong_return(_: &ViewContext) -> Player { +122 | fn view_def_wrong_return(_: &ViewContext) -> Player { | ^^^^^^ unsatisfied trait bound | help: the trait `ViewReturn` is not implemented for `Player` - --> tests/ui/views.rs:68:1 + --> tests/ui/views.rs:61:1 | - 68 | struct Player { + 61 | struct Player { | ^^^^^^^^^^^^^ = help: the following other types implement trait `ViewReturn`: FromWhere @@ -274,9 +250,9 @@ help: the trait `ViewReturn` is not implemented for `Player` spacetimedb::spacetimedb_query_builder::Table error[E0277]: invalid view signature - --> tests/ui/views.rs:128:1 + --> tests/ui/views.rs:121:1 | -128 | #[view(accessor = view_def_wrong_return, public)] +121 | #[view(accessor = view_def_wrong_return, public)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this view signature is not valid | = help: the trait `spacetimedb::rt::View<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a ViewContext) -> Player {view_def_wrong_return}` @@ -296,9 +272,9 @@ note: required by a bound in `ViewDispatcher::::invoke` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: invalid anonymous view signature - --> tests/ui/views.rs:136:1 + --> tests/ui/views.rs:129:1 | -136 | #[view(accessor = view_def_returns_not_a_spacetime_type, public)] +129 | #[view(accessor = view_def_returns_not_a_spacetime_type, public)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this view signature is not valid | = help: the trait `AnonymousView<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a AnonymousViewContext) -> Option {view_def_returns_not_a_spacetime_type}` @@ -318,15 +294,15 @@ note: required by a bound in `ViewRegistrar::::register` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied - --> tests/ui/views.rs:137:71 + --> tests/ui/views.rs:130:71 | -137 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { +130 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { | ^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound | help: the trait `SpacetimeType` is not implemented for `NotSpacetimeType` - --> tests/ui/views.rs:73:1 + --> tests/ui/views.rs:66:1 | - 73 | struct NotSpacetimeType {} + 66 | struct NotSpacetimeType {} | ^^^^^^^^^^^^^^^^^^^^^^^ = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition = help: the following other types implement trait `SpacetimeType`: @@ -342,15 +318,15 @@ help: the trait `SpacetimeType` is not implemented for `NotSpacetimeType` = note: required for `Option` to implement `ViewReturn` error[E0277]: the trait bound `NotSpacetimeType: Serialize` is not satisfied - --> tests/ui/views.rs:137:71 + --> tests/ui/views.rs:130:71 | -137 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { +130 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { | ^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound | help: the trait `Serialize` is not implemented for `NotSpacetimeType` - --> tests/ui/views.rs:73:1 + --> tests/ui/views.rs:66:1 | - 73 | struct NotSpacetimeType {} + 66 | struct NotSpacetimeType {} | ^^^^^^^^^^^^^^^^^^^^^^^ = help: the following other types implement trait `Serialize`: &T @@ -365,9 +341,9 @@ help: the trait `Serialize` is not implemented for `NotSpacetimeType` = note: required for `Option` to implement `ViewReturn` error[E0277]: invalid anonymous view signature - --> tests/ui/views.rs:136:1 + --> tests/ui/views.rs:129:1 | -136 | #[view(accessor = view_def_returns_not_a_spacetime_type, public)] +129 | #[view(accessor = view_def_returns_not_a_spacetime_type, public)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this view signature is not valid | = help: the trait `AnonymousView<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a AnonymousViewContext) -> Option {view_def_returns_not_a_spacetime_type}` @@ -387,15 +363,15 @@ note: required by a bound in `ViewDispatcher::::invoke` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied - --> tests/ui/views.rs:137:71 + --> tests/ui/views.rs:130:71 | -137 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { +130 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { | ^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound | help: the trait `SpacetimeType` is not implemented for `NotSpacetimeType` - --> tests/ui/views.rs:73:1 + --> tests/ui/views.rs:66:1 | - 73 | struct NotSpacetimeType {} + 66 | struct NotSpacetimeType {} | ^^^^^^^^^^^^^^^^^^^^^^^ = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition = help: the following other types implement trait `SpacetimeType`: @@ -411,9 +387,9 @@ help: the trait `SpacetimeType` is not implemented for `NotSpacetimeType` = note: required for `Option` to implement `SpacetimeType` error[E0277]: the trait bound `{integer}: RHS` is not satisfied - --> tests/ui/views.rs:159:49 + --> tests/ui/views.rs:152:49 | -159 | ctx.from.player().r#where(|a| a.identity.eq(42)).build() +152 | ctx.from.player().r#where(|a| a.identity.eq(42)).build() | -- ^^ the trait `RHS` is not implemented for `{integer}` | | | required by a bound introduced by this call @@ -435,9 +411,9 @@ note: required by a bound in `Col::::eq` | ^^^^^^^^^ required by this bound in `Col::::eq` error[E0277]: the trait bound `u32: RHS` is not satisfied - --> tests/ui/views.rs:165:49 + --> tests/ui/views.rs:158:49 | -165 | ctx.from.player_info().r#where(|a| a.age.eq(4200u32)).build() +158 | ctx.from.player_info().r#where(|a| a.age.eq(4200u32)).build() | -- ^^^^^^^ the trait `RHS` is not implemented for `u32` | | | required by a bound introduced by this call @@ -457,9 +433,9 @@ note: required by a bound in `Col::::eq` = note: this error originates in the macro `impl_rhs` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0308]: mismatched types - --> tests/ui/views.rs:174:62 + --> tests/ui/views.rs:167:62 | -174 | .left_semijoin(ctx.from.player(), |a, b| a.weight.eq(b.identity)) +167 | .left_semijoin(ctx.from.player(), |a, b| a.weight.eq(b.identity)) | -- ^^^^^^^^^^ expected `IxCol`, found `IxCol` | | | arguments to this method are incorrect @@ -473,15 +449,15 @@ note: method defined here | ^^ error[E0609]: no field `age` on type `&PlayerInfoIxCols` - --> tests/ui/views.rs:184:72 + --> tests/ui/views.rs:177:72 | -184 | .right_semijoin(ctx.from.player_info(), |a, b| a.identity.eq(b.age)) +177 | .right_semijoin(ctx.from.player_info(), |a, b| a.identity.eq(b.age)) | ^^^ unknown field | = note: available fields are: `identity`, `weight` error[E0599]: no method named `xyz` found for struct `QueryBuilder` in the current scope - --> tests/ui/views.rs:202:14 + --> tests/ui/views.rs:195:14 | -202 | ctx.from.xyz().build() +195 | ctx.from.xyz().build() | ^^^ method not found in `QueryBuilder` diff --git a/crates/smoketests/modules/Cargo.lock b/crates/smoketests/modules/Cargo.lock index ef895ea256b..6498ec5a691 100644 --- a/crates/smoketests/modules/Cargo.lock +++ b/crates/smoketests/modules/Cargo.lock @@ -895,6 +895,13 @@ dependencies = [ "spacetimedb", ] +[[package]] +name = "smoketest-module-views-count" +version = "0.1.0" +dependencies = [ + "spacetimedb", +] + [[package]] name = "smoketest-module-views-drop-view" version = "0.1.0" diff --git a/crates/smoketests/modules/Cargo.toml b/crates/smoketests/modules/Cargo.toml index 450a4ea962e..8fd333116db 100644 --- a/crates/smoketests/modules/Cargo.toml +++ b/crates/smoketests/modules/Cargo.toml @@ -25,6 +25,7 @@ members = [ "views-subscribe", "views-query", "views-callable", + "views-count", # Security and permissions "rls", diff --git a/crates/smoketests/modules/views-count/Cargo.toml b/crates/smoketests/modules/views-count/Cargo.toml new file mode 100644 index 00000000000..159731791eb --- /dev/null +++ b/crates/smoketests/modules/views-count/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "smoketest-module-views-count" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true diff --git a/crates/smoketests/modules/views-count/src/lib.rs b/crates/smoketests/modules/views-count/src/lib.rs new file mode 100644 index 00000000000..c1b4f4e82e7 --- /dev/null +++ b/crates/smoketests/modules/views-count/src/lib.rs @@ -0,0 +1,44 @@ +use spacetimedb::{reducer, table, view, AnonymousViewContext}; +use spacetimedb::{ReducerContext, SpacetimeType, Table, ViewContext}; + +#[table(accessor = item)] +pub struct Item { + #[primary_key] + id: u32, + value: u32, +} + +#[derive(SpacetimeType)] +pub struct ItemCount { + count: u64, +} + +#[view(accessor = sender_table_count, public)] +pub fn sender_table_count(ctx: &ViewContext) -> Option { + Some(ItemCount { + count: ctx.db.item().count(), + }) +} + +#[view(accessor = anon_table_count, public)] +pub fn anon_table_count(ctx: &AnonymousViewContext) -> Option { + Some(ItemCount { + count: ctx.db.item().count(), + }) +} + +#[reducer] +pub fn insert_item(ctx: &ReducerContext, id: u32, value: u32) { + ctx.db.item().insert(Item { id, value }); +} + +#[reducer] +pub fn replace_item(ctx: &ReducerContext, id: u32, value: u32) { + ctx.db.item().id().delete(&id); + ctx.db.item().insert(Item { id, value }); +} + +#[reducer] +pub fn delete_item(ctx: &ReducerContext, id: u32) { + ctx.db.item().id().delete(&id); +} diff --git a/crates/smoketests/tests/smoketests/views.rs b/crates/smoketests/tests/smoketests/views.rs index bfaadc1b219..a98635418b6 100644 --- a/crates/smoketests/tests/smoketests/views.rs +++ b/crates/smoketests/tests/smoketests/views.rs @@ -77,6 +77,109 @@ public static partial class Module } "#; +const CS_COUNT_VIEW_MODULE: &str = r#"using SpacetimeDB; + +[SpacetimeDB.Type] +public partial struct ItemCount +{ + public ulong count; +} + +public static partial class Module +{ + [Table(Accessor = "item", Public = true)] + public partial struct Item + { + [PrimaryKey] + public uint id; + public uint value; + } + + [View(Accessor = "sender_table_count", Public = true)] + public static ItemCount? sender_table_count(ViewContext ctx) + { + return new ItemCount { count = ctx.Db.item.Count }; + } + + [View(Accessor = "anon_table_count", Public = true)] + public static ItemCount? anon_table_count(AnonymousViewContext ctx) + { + return new ItemCount { count = ctx.Db.item.Count }; + } + + [Reducer] + public static void insert_item(ReducerContext ctx, uint id, uint value) + { + ctx.Db.item.Insert(new Item { id = id, value = value }); + } + + [Reducer] + public static void replace_item(ReducerContext ctx, uint id, uint value) + { + ctx.Db.item.id.Delete(id); + ctx.Db.item.Insert(new Item { id = id, value = value }); + } + + [Reducer] + public static void delete_item(ReducerContext ctx, uint id) + { + ctx.Db.item.id.Delete(id); + } +} +"#; + +const TS_COUNT_VIEW_MODULE: &str = r#"import { schema, t, table } from "spacetimedb/server"; + +const item = table( + { name: "item" }, + { + id: t.u32().primaryKey(), + value: t.u32(), + } +); + +const itemCount = t.object("ItemCountRow", { + count: t.u64(), +}); + +const spacetimedb = schema({ item }); +export default spacetimedb; + +export const sender_table_count = spacetimedb.view( + { public: true }, + t.option(itemCount), + ctx => ({ count: ctx.db.item.count() }) +); + +export const anon_table_count = spacetimedb.anonymousView( + { public: true }, + t.option(itemCount), + ctx => ({ count: ctx.db.item.count() }) +); + +export const insert_item = spacetimedb.reducer( + { id: t.u32(), value: t.u32() }, + (ctx, { id, value }) => { + ctx.db.item.insert({ id, value }); + } +); + +export const replace_item = spacetimedb.reducer( + { id: t.u32(), value: t.u32() }, + (ctx, { id, value }) => { + ctx.db.item.id.delete(id); + ctx.db.item.insert({ id, value }); + } +); + +export const delete_item = spacetimedb.reducer( + { id: t.u32() }, + (ctx, { id }) => { + ctx.db.item.id.delete(id); + } +); +"#; + fn project_fields(events: Vec, view_name: &str, projected_fields: &[&str]) -> Vec { let project_row = |row: &Value| { if projected_fields.is_empty() { @@ -115,6 +218,30 @@ fn project_fields(events: Vec, view_name: &str, projected_fields: &[&str] .collect() } +fn assert_count_view_refresh_behavior(test: &Smoketest, view_name: &str, id: &str, value: &str, updated_value: &str) { + let query = format!("select * from {view_name}"); + let sub = test.subscribe_background(&[&query], 2).unwrap(); + + test.call("insert_item", &[id, value]).unwrap(); + test.call("replace_item", &[id, updated_value]).unwrap(); + test.call("delete_item", &[id]).unwrap(); + + let events = sub.collect().unwrap(); + let projection = project_fields(events, view_name, &["count"]); + assert_eq!( + serde_json::json!(projection), + serde_json::json!([ + {view_name: {"deletes": [{"count": 0}], "inserts": [{"count": 1}]}}, + {view_name: {"deletes": [{"count": 1}], "inserts": [{"count": 0}]}} + ]) + ); +} + +fn assert_all_count_view_refreshes(test: &Smoketest) { + assert_count_view_refresh_behavior(test, "sender_table_count", "1", "10", "11"); + assert_count_view_refresh_behavior(test, "anon_table_count", "2", "20", "21"); +} + /// Tests that views populate the st_view_* system tables #[test] fn test_st_view_tables() { @@ -553,6 +680,34 @@ fn test_typescript_procedure_triggers_subscription_updates() { ); } +#[test] +fn test_rust_count_view_subscription_refreshes() { + let test = Smoketest::builder().precompiled_module("views-count").build(); + assert_all_count_view_refreshes(&test); +} + +#[test] +fn test_csharp_count_view_subscription_refreshes() { + require_dotnet!(); + + let mut test = Smoketest::builder().autopublish(false).build(); + test.publish_csharp_module_source("views-count-csharp", "views-count-csharp", CS_COUNT_VIEW_MODULE) + .unwrap(); + + assert_all_count_view_refreshes(&test); +} + +#[test] +fn test_typescript_count_view_subscription_refreshes() { + require_pnpm!(); + + let mut test = Smoketest::builder().autopublish(false).build(); + test.publish_typescript_module_source("views-count-typescript", "views-count-typescript", TS_COUNT_VIEW_MODULE) + .unwrap(); + + assert_all_count_view_refreshes(&test); +} + #[test] fn test_disconnect_does_not_break_sender_view() { let test = Smoketest::builder().precompiled_module("views-sql").build(); diff --git a/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md b/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md index ec05ce1fe13..f0dcf677ae1 100644 --- a/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md +++ b/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md @@ -614,6 +614,13 @@ export const top_players = spacetimedb.view({ name: 'top_players' }, {}, t.array export const bottom_players = spacetimedb.view({ name: 'bottom_players' }, {}, t.array(player.rowType), ctx => { return ctx.from.player.where(p => p.score.lt(1000)) }); + +// Count rows in a table. +export const player_count = spacetimedb.anonymousView({ name: 'player_count' }, {}, t.array(t.row('PlayerCount', { + count: t.u64(), +})), ctx => { + return [{ count: ctx.db.player.count() }]; +}); ``` @@ -643,13 +650,26 @@ public static IQuery BottomPlayers(ViewContext ctx) { return ctx.From.Player().Where(p => p.Score.Lt(1000)); } + +// Count rows in a table. +[SpacetimeDB.Type] +public partial struct PlayerCount +{ + public ulong Count; +} + +[SpacetimeDB.View(Accessor = "PlayerCount", Public = true)] +public static List PlayerCountView(AnonymousViewContext ctx) +{ + return new List { new PlayerCount { Count = ctx.Db.Player.Count } }; +} ``` ```rust -use spacetimedb::{view, Query, ViewContext}; +use spacetimedb::{view, AnonymousViewContext, Query, SpacetimeType, ViewContext}; // Return single row #[view(accessor = my_player, public)] @@ -669,6 +689,19 @@ fn top_players(ctx: &ViewContext) -> Vec { fn bottom_players(ctx: &ViewContext) -> impl Query { ctx.from.player().r#where(|p| p.score.lt(1000)) } + +#[derive(SpacetimeType)] +struct PlayerCount { + count: u64, +} + +// Count rows in a table. +#[view(accessor = player_count, public)] +fn player_count(ctx: &AnonymousViewContext) -> Vec { + vec![PlayerCount { + count: ctx.db.player().count(), + }] +} ``` @@ -686,6 +719,16 @@ SPACETIMEDB_VIEW(std::optional, my_player, Public, ViewContext ctx) { SPACETIMEDB_VIEW(std::vector, top_players, Public, ViewContext ctx) { return ctx.db[player_score].filter(range_from(int32_t(1000))).collect(); } + +struct PlayerCount { + uint64_t count; +}; +SPACETIMEDB_STRUCT(PlayerCount, count) + +// Count rows in a table. +SPACETIMEDB_VIEW(std::optional, player_count, Public, AnonymousViewContext ctx) { + return std::optional(PlayerCount{ctx.db[player].count()}); +} ``` diff --git a/docs/docs/00200-core-concepts/00200-functions/00500-views.md b/docs/docs/00200-core-concepts/00200-functions/00500-views.md index f957f8f217e..4d6e9143165 100644 --- a/docs/docs/00200-core-concepts/00200-functions/00500-views.md +++ b/docs/docs/00200-core-concepts/00200-functions/00500-views.md @@ -723,6 +723,85 @@ The `entities_in_origin_chunk` view is shared - if 100 players are all looking a For games with many players in the same area, the shared approach scales much better. Clients can subscribe to the specific chunk views they need based on their position, and players in the same chunk automatically share the same materialized data. +### Example: Counts + +Procedural views can call `count()` or `Count` on a table accessor to read the current number of rows in a table. +This works with both `ViewContext` and `AnonymousViewContext`. + + + + +```typescript +const playerCountRow = t.row('PlayerCountRow', { + count: t.u64(), +}); + +export const player_count = spacetimedb.anonymousView( + { name: 'player_count', public: true }, + t.array(playerCountRow), + (ctx) => [{ count: ctx.db.players.count() }] +); +``` + + + + +```csharp +[SpacetimeDB.Type] +public partial struct PlayerCountRow +{ + public ulong Count; +} + +[SpacetimeDB.View(Accessor = "PlayerCount", Public = true)] +public static List PlayerCount(AnonymousViewContext ctx) +{ + return new List + { + new PlayerCountRow { Count = ctx.Db.Player.Count } + }; +} +``` + + + + +```rust +use spacetimedb::{view, AnonymousViewContext, SpacetimeType}; + +#[derive(SpacetimeType)] +pub struct PlayerCountRow { + count: u64, +} + +#[view(accessor = player_count, public)] +fn player_count(ctx: &AnonymousViewContext) -> Vec { + vec![PlayerCountRow { + count: ctx.db.player().count(), + }] +} +``` + + + + +```cpp +struct PlayerCountRow { + uint64_t count; +}; +SPACETIMEDB_STRUCT(PlayerCountRow, count) + +SPACETIMEDB_VIEW(std::optional, player_count, Public, AnonymousViewContext ctx) { + return std::optional(PlayerCountRow{ctx.db[player].count()}); +} +``` + + + + +`count()` is a constant-time metadata lookup, so it does not iterate the table. +`count()` requires re-evaluation whenever any row of the underlying table is updated, however that re-evaluation is still cheap because `count()` is cheap. + ## Querying Views Views can be queried and subscribed to just like normal tables using SQL: @@ -736,7 +815,7 @@ When subscribed to, views automatically update when their underlying tables chan ## Why Views Cannot Use `.iter()` -You may notice that views can only access table data through indexed lookups (`.find()` and `.filter()` on indexed columns), not through `.iter()` which scans all rows. This is a deliberate design choice for performance. +You may notice that views can only access table data through indexed lookups (`.find()` and `.filter()` on indexed columns) and table-level metadata lookups like `count()`, not through `.iter()` which scans all rows. This is a deliberate design choice for performance. **Views are black boxes.** View functions are Turing-complete code that SpacetimeDB cannot analyze or optimize. When a view reads from a table, SpacetimeDB tracks the "read set" - which rows the view accessed. If any row in that read set changes, the view's output might have changed, so SpacetimeDB must re-execute the entire view function.