diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs index 01d952e83c5..47d5cf291f5 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.ComponentModel; using SpacetimeDB; @@ -554,20 +555,36 @@ public static List ViewDefWrongContext(ReducerContext ctx) } // TODO: Investigate why void return breaks the FFI generation - // // Invalid: Void return type is not Vec or Option + // // Invalid: Void return type is not List or T? // [SpacetimeDB.View(Accessor = "view_def_no_return", Public = true)] // public static void ViewDefNoReturn(ViewContext ctx) // { // return; // } - // Invalid: Wrong return type is not Vec or Option + // Invalid: Wrong return type is not List or T? [SpacetimeDB.View(Accessor = "view_def_wrong_return", Public = true)] public static Player ViewDefWrongReturn(ViewContext ctx) { return new Player { Identity = new() }; } + // Invalid: IEnumerable return type (from Iter()) is not List or T? + [SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_iter", Public = true)] + public static IEnumerable ViewDefIEnumerableReturnFromIter(ViewContext ctx) + { + return ctx.Db.Player.Iter(); + } + + // Invalid: IEnumerable return type (from Filter()) is not List or T? + [SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_filter", Public = true)] + public static IEnumerable ViewDefIEnumerableReturnFromFilter( + ViewContext ctx + ) + { + return ctx.Db.TestIndexIssues.TestUnexpectedColumns.Filter(0); + } + // Invalid: Returns type that is not a SpacetimeType [SpacetimeDB.View(Accessor = "view_def_returns_not_a_spacetime_type", Public = true)] public static NotSpacetimeType? ViewDefReturnsNotASpacetimeType(AnonymousViewContext ctx) diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt index eab68c570e4..8e960c9f38a 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt @@ -114,6 +114,29 @@ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_FOU ] } }, + {/* + { + return ctx.Db.Player.Iter(); + ^^^^ + } +*/ + Message: 'PlayerReadOnly' does not contain a definition for 'Iter' and no accessible extension method 'Iter' accepting a first argument of type 'PlayerReadOnly' could be found (are you missing a using directive or an assembly reference?), + Severity: Error, + Descriptor: { + Id: CS1061, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS1061), + MessageFormat: '{0}' does not contain a definition for '{1}' and no accessible extension method '{1}' accepting a first argument of type '{0}' could be found (are you missing a using directive or an assembly reference?), + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, {/* { ctx.Db.Player.Iter(); @@ -275,6 +298,29 @@ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_THI ] } }, + {/* + { + return ctx.Db.TestIndexIssues.TestUnexpectedColumns.Filter(0); + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + } +*/ + Message: Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'System.Collections.Generic.IEnumerable'. An explicit conversion exists (are you missing a cast?), + Severity: Error, + Descriptor: { + Id: CS0266, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0266), + MessageFormat: Cannot implicitly convert type '{0}' to '{1}'. An explicit conversion exists (are you missing a cast?), + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, {/* var returnValue = Module.ViewDefNoContext((SpacetimeDB.ViewContext)ctx); 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 85ee14b0d50..e69962b169c 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 @@ -1807,6 +1807,98 @@ internal PrimaryKeyFieldUniqueIndex() } } +sealed class view_def_ienumerable_return_from_filterViewDispatcher + : global::SpacetimeDB.Internal.IView +{ + public SpacetimeDB.Internal.RawViewDefV10 MakeViewDef( + SpacetimeDB.BSATN.ITypeRegistrar registrar + ) => + new global::SpacetimeDB.Internal.RawViewDefV10( + SourceName: "view_def_ienumerable_return_from_filter", + Index: 0, + IsPublic: true, + IsAnonymous: false, + Params: [], + ReturnType: new SpacetimeDB.BSATN.Unsupported>().GetAlgebraicType( + registrar + ) + ); + + public byte[] Invoke( + System.IO.BinaryReader reader, + global::SpacetimeDB.Internal.IViewContext ctx + ) + { + try + { + var returnValue = Module.ViewDefIEnumerableReturnFromFilter( + (SpacetimeDB.ViewContext)ctx + ); + SpacetimeDB.BSATN.Unsupported> returnRW = + new(); + var header = new global::SpacetimeDB.Internal.ViewResultHeader.RowData(default); + var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN(); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + headerRW.Write(writer, header); + returnRW.Write(writer, returnValue); + return output.ToArray(); + } + catch (System.Exception e) + { + global::SpacetimeDB.Log.Error( + "Error in view 'view_def_ienumerable_return_from_filter': " + e + ); + throw; + } + } +} + +sealed class view_def_ienumerable_return_from_iterViewDispatcher + : global::SpacetimeDB.Internal.IView +{ + public SpacetimeDB.Internal.RawViewDefV10 MakeViewDef( + SpacetimeDB.BSATN.ITypeRegistrar registrar + ) => + new global::SpacetimeDB.Internal.RawViewDefV10( + SourceName: "view_def_ienumerable_return_from_iter", + Index: 1, + IsPublic: true, + IsAnonymous: false, + Params: [], + ReturnType: new SpacetimeDB.BSATN.Unsupported>().GetAlgebraicType( + registrar + ) + ); + + public byte[] Invoke( + System.IO.BinaryReader reader, + global::SpacetimeDB.Internal.IViewContext ctx + ) + { + try + { + var returnValue = Module.ViewDefIEnumerableReturnFromIter((SpacetimeDB.ViewContext)ctx); + SpacetimeDB.BSATN.Unsupported> returnRW = + new(); + var header = new global::SpacetimeDB.Internal.ViewResultHeader.RowData(default); + var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN(); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + headerRW.Write(writer, header); + returnRW.Write(writer, returnValue); + return output.ToArray(); + } + catch (System.Exception e) + { + global::SpacetimeDB.Log.Error( + "Error in view 'view_def_ienumerable_return_from_iter': " + e + ); + throw; + } + } +} + sealed class view_def_no_contextViewDispatcher : global::SpacetimeDB.Internal.IView { public SpacetimeDB.Internal.RawViewDefV10 MakeViewDef( @@ -1814,7 +1906,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_no_context", - Index: 0, + Index: 2, IsPublic: true, IsAnonymous: false, Params: [], @@ -1855,7 +1947,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_no_public", - Index: 1, + Index: 3, IsPublic: false, IsAnonymous: false, Params: [], @@ -1896,7 +1988,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_wrong_context", - Index: 2, + Index: 4, IsPublic: true, IsAnonymous: false, Params: [], @@ -1937,7 +2029,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_wrong_return", - Index: 3, + Index: 5, IsPublic: true, IsAnonymous: false, Params: [], @@ -1976,7 +2068,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_no_delete", - Index: 4, + Index: 6, IsPublic: true, IsAnonymous: false, Params: [], @@ -2021,7 +2113,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_no_insert", - Index: 5, + Index: 7, IsPublic: true, IsAnonymous: false, Params: [], @@ -2750,6 +2842,8 @@ public static void Main() // IMPORTANT: The order in which we register views matters. // It must correspond to the order in which we call `GenerateDispatcherClass`. // See the comment on `GenerateDispatcherClass` for more explanation. + SpacetimeDB.Internal.Module.RegisterView(); + SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module.verified.txt b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module.verified.txt index d4052d2d5ce..f1a3835b97c 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module.verified.txt +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module.verified.txt @@ -330,7 +330,7 @@ public partial struct TestScheduleIssues } }, {/* - // Invalid: Wrong return type is not Vec or Option + // Invalid: Wrong return type is not List or T? [SpacetimeDB.View(Accessor = "view_def_wrong_return", Public = true)] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ public static Player ViewDefWrongReturn(ViewContext ctx) @@ -355,6 +355,94 @@ public partial struct TestScheduleIssues } }, {/* + [SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_iter", Public = true)] + public static IEnumerable ViewDefIEnumerableReturnFromIter(ViewContext ctx) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + { +*/ + Message: BSATN implementation for System.Collections.Generic.IEnumerable is not found: Unsupported system type System.Collections.Generic.IEnumerable, + Severity: Error, + Descriptor: { + Id: BSATN0001, + Title: Unsupported type, + MessageFormat: BSATN implementation for {0} is not found: {1}, + Category: SpacetimeDB.BSATN, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + }, + {/* + // Invalid: IEnumerable return type (from Iter()) is not List or T? + [SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_iter", Public = true)] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + public static IEnumerable ViewDefIEnumerableReturnFromIter(ViewContext ctx) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + { +^^^^^ + return ctx.Db.Player.Iter(); +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + } +^^^^^ + +*/ + Message: View 'ViewDefIEnumerableReturnFromIter' must return Vec or Option., + Severity: Error, + Descriptor: { + Id: STDB0024, + Title: Views must return Vec or Option, + MessageFormat: View '{0}' must return Vec or Option., + Category: SpacetimeDB, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + }, + {/* + [SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_filter", Public = true)] + public static IEnumerable ViewDefIEnumerableReturnFromFilter( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ViewContext ctx +*/ + Message: BSATN implementation for System.Collections.Generic.IEnumerable is not found: Unsupported system type System.Collections.Generic.IEnumerable, + Severity: Error, + Descriptor: { + Id: BSATN0001, + Title: Unsupported type, + MessageFormat: BSATN implementation for {0} is not found: {1}, + Category: SpacetimeDB.BSATN, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + }, + {/* + // Invalid: IEnumerable return type (from Filter()) is not List or T? + [SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_filter", Public = true)] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + public static IEnumerable ViewDefIEnumerableReturnFromFilter( +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ViewContext ctx +^^^^^^^^^^^^^^^^^^^^^^^ + ) +^^^^^ + { +^^^^^ + return ctx.Db.TestIndexIssues.TestUnexpectedColumns.Filter(0); +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + } +^^^^^ + +*/ + Message: View 'ViewDefIEnumerableReturnFromFilter' must return Vec or Option., + Severity: Error, + Descriptor: { + Id: STDB0024, + Title: Views must return Vec or Option, + MessageFormat: View '{0}' must return Vec or Option., + Category: SpacetimeDB, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + }, + {/* [SpacetimeDB.Reducer] public static int TestReducerReturnType(ReducerContext ctx) => 0; ^^^ 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 bf312b46c97..6f6eeb35b6b 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 @@ -354,10 +354,10 @@ export const onDisconnect = spacetimedb.clientDisconnected(ctx => { /* ... */ }) public static void Init(ReducerContext ctx) { /* ... */ } [SpacetimeDB.Reducer(ReducerKind.ClientConnected)] -public static void OnConnect(ReducerContext ctx) { /* ... */ } +public static void ClientConnect(ReducerContext ctx) { /* ... */ } [SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)] -public static void OnDisconnect(ReducerContext ctx) { /* ... */ } +public static void ClientDisconnect(ReducerContext ctx) { /* ... */ } ``` @@ -619,25 +619,25 @@ export const bottom_players = spacetimedb.view({ name: 'bottom_players' }, {}, t using SpacetimeDB; // Return single row -[SpacetimeDB.View(Public = true)] +[SpacetimeDB.View(Accessor = "MyPlayer", Public = true)] public static Player? MyPlayer(ViewContext ctx) { return ctx.Db.Player.Identity.Find(ctx.Sender); } // Return potentially multiple rows -[SpacetimeDB.View(Public = true)] -public static IEnumerable TopPlayers(ViewContext ctx) +[SpacetimeDB.View(Accessor = "TopPlayers", Public = true)] +public static List TopPlayers(ViewContext ctx) { - return ctx.Db.Player.Score.Filter(1000); + return ctx.Db.Player.Score.Filter(1000).ToList(); } // Perform a generic filter using the query builder. // Equivalent to `SELECT * FROM player WHERE score < 1000`. -[SpacetimeDB.View(Public = true)] +[SpacetimeDB.View(Accessor = "BottomPlayers", Public = true)] public static IQuery BottomPlayers(ViewContext ctx) { - return ctx.From.Player.Where(p => p.Score.Lt(1000)); + return ctx.From.Player().Where(p => p.Score.Lt(1000)); } ``` 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 479a38c9ff8..869d63911bd 100644 --- a/docs/docs/00200-core-concepts/00200-functions/00500-views.md +++ b/docs/docs/00200-core-concepts/00200-functions/00500-views.md @@ -132,7 +132,7 @@ public static partial class Module [SpacetimeDB.View(Accessor = "MyPlayer", Public = true)] public static Player? MyPlayer(ViewContext ctx) { - return ctx.Db.Player.Identity.Find(ctx.Sender) as Player; + return ctx.Db.Player.Identity.Find(ctx.Sender) as Player?; } // Multiple rows: return a list @@ -976,36 +976,36 @@ export const all_player_levels = spacetimedb.anonymousView( ```csharp using SpacetimeDB; -[SpacetimeDB.Table(Accessor = "Player", Public = true)] -public partial struct Player +public partial class Module { - [SpacetimeDB.PrimaryKey] - [SpacetimeDB.AutoInc] - public ulong Id; - public string Name; - [SpacetimeDB.Index.BTree] - public ulong Score; -} + [SpacetimeDB.Table(Accessor = "Player", Public = true)] + public partial struct Player + { + [SpacetimeDB.PrimaryKey] [SpacetimeDB.AutoInc] + public ulong Id; -[SpacetimeDB.Table(Accessor = "PlayerLevel", Public = true)] -public partial struct PlayerLevel -{ - [SpacetimeDB.Unique] - public ulong PlayerId; - [SpacetimeDB.Index.BTree] - public ulong Level; -} + public string Name; + [SpacetimeDB.Index.BTree] public ulong Score; + } -[SpacetimeDB.View(Accessor = "AllPlayers", Public = true)] -public static IQuery AllPlayers(AnonymousViewContext ctx) -{ - return ctx.From.Player(); -} + [SpacetimeDB.Table(Accessor = "PlayerLevel", Public = true)] + public partial struct PlayerLevel + { + [SpacetimeDB.Unique] public ulong PlayerId; + [SpacetimeDB.Index.BTree] public ulong Level; + } -[SpacetimeDB.View(Accessor = "AllPlayerLevels", Public = true)] -public static IQuery AllPlayerLevels(AnonymousViewContext ctx) -{ - return ctx.From.PlayerLevel(); + [SpacetimeDB.View(Accessor = "AllPlayers", Public = true)] + public static IQuery AllPlayers(AnonymousViewContext ctx) + { + return ctx.From.Player(); + } + + [SpacetimeDB.View(Accessor = "AllPlayerLevels", Public = true)] + public static IQuery AllPlayerLevels(AnonymousViewContext ctx) + { + return ctx.From.PlayerLevel(); + } } ``` diff --git a/docs/static/llms.md b/docs/static/llms.md index c8618e43443..c605e2b6bcd 100644 --- a/docs/static/llms.md +++ b/docs/static/llms.md @@ -964,13 +964,13 @@ Both contexts provide read-only access to tables and indexes through `ctx.db`. Views can return: - `Option` - For at-most-one row (e.g., looking up a specific player) - `Vec` - For multiple rows (e.g., listing all players at a level) -- `impl Query` - A typed SQL query that behaves like the deprecated RLS (Row-Level Security) feature +- `impl IQuery` - A typed SQL query that behaves like the deprecated RLS (Row-Level Security) feature Where `T` can be a table type or any custom type derived with `SpacetimeType`. -**impl Query Return Type** +**impl IQuery Return Type** -When a view returns `impl Query`, SpacetimeDB computes results incrementally as the underlying data changes. This enables efficient table scanning because query results are maintained incrementally rather than recomputed from scratch. Without `impl Query`, you must use indexed column lookups to access tables inside view functions. +When a view returns `impl IQuery`, SpacetimeDB computes results incrementally as the underlying data changes. This enables efficient table scanning because query results are maintained incrementally rather than recomputed from scratch. Without `impl IQuery`, you must use indexed column lookups to access tables inside view functions. The query builder provides a fluent API for constructing type-safe SQL queries: @@ -978,7 +978,7 @@ The query builder provides a fluent API for constructing type-safe SQL queries: use spacetimedb::{view, ViewContext, Query}; // This view can scan the whole table efficiently because -// impl Query results are computed incrementally +// impl IQuery results are computed incrementally #[view(accessor = my_messages, public)] fn my_messages(ctx: &ViewContext) -> impl Query { // Return a typed query builder directly