This fixes an issue with the C# implementation of Query Builder requiring `Build()` to return a query, and the return type being `Query<TRow>` rather than `IQuery<TRow>`. # Description of Changes 1. Runtime/query-builder * Removed the concrete `Query<TRow>` carrier type and every `.Build()`. Query builder shapes now expose only `IQuery<TRow>` plus `ToSql()`. * Ensured all builder entry points (tables, joins, filters) continue to return `IQuery<TRow>`. 2. Source generator + bindings * Updated `ViewDeclaration` analysis to treat any return type implementing `SpacetimeDB.IQuery<TRow>` as a SQL view. * Dispatcher generation now emits `ViewResultHeader.RawSql(returnValue.ToSql())` . This eliminates a `Query<TRow>` special-case. 3. Tests, fixtures, regression module * Converted the C# query-builder unit tests, codegen fixtures, and regression-test server views to call `ToSql()`/return `IQuery<TRow>`. * Added coverage proving `RightSemiJoin` (and friends) still satisfy `IQuery<TRow>`. 4. CLI templates & generated bindings * Regenerated/edited C# template bindings so `SubscriptionBuilder.AddQuery` accepts `Func<QueryBuilder, IQuery<TRow>>` and captures SQL via `ToSql()`. # API and ABI breaking changes While technically API breaking, this actually brings the API closer to the intended design. * `Query<TRow>` has been removed from the public surface area; any previous references (including `.Build()` and `.Sql`) must be replaced with the builder instance itself plus `.ToSql()`. * View methods must now return an `IQuery<TRow>` (or any custom type implementing it) when producing SQL for the host. * Generated C# client bindings now expect typed subscription callbacks to produce `IQuery<TRow>`, aligning the client SDK with the new runtime contract. # Expected complexity level and risk 3 - Medium: Touches runtime, codegen, fixtures, and templates. Risk is mitigated by parity with Rust semantics and comprehensive test updates, but downstream modules must recompile to adopt the new interface. # Testing - [X] Built CLI and ran regression tests locally with removed `.Build()` - [X] Ran `dotnet test .\sdks\csharp\tests~\tests.csproj -c Release` with all tests passing - [X] Ran `dotnet test .\crates\bindings-csharp\Codegen.Tests\Codegen.Tests.csproj -c Release` with all tests passing
SpacetimeDB.Codegen
This project contains Roslyn incremental source generators that augment types and tables with static methods for self-describing and registration. They look for different attributes to know which types to augment:
-
[SpacetimeDB.Type]- generates aGetSatsTypeInfo()static method that registers this type with the runtime and returns aTypeInfoobject. It supports onlystructs for now to explicitly forbid infinitely recursive types and to make the implementation simpler, as it doesn't need to deal with type references - each table is registered as an entirely self-contained type together with its nested structs if any. This is unlikely to be a problem in common scenarios, but it will be optimised in the future.All the nested fields will be added to the product type. Because it's not possible to implement static extension methods on 3rd-party types (including built-ins) in C#, the codegen is responsible for manually routing different types to their
TypeInfodescriptors. See various staticTypeInfoproperties and helper methods onSpacetimeDB.BSATN.AlgebraicType(Runtime/AlgebraicType.cs) and routing logic inUtils.GetTypeInfo(Codegen/Utils.cs) for more details.Also, for the same reason - absence of static extension methods in C# - the codegen expects that your struct, as well as any of its parents, is
partialso methods can be added from extra source files generated by the codegen. -
[SpacetimeDB.Type]- also supports emulation of tagged enums in C#. For that, the struct needs to inherit a marker interfaceSpacetimeDB.TaggedEnum<Variants>whereVariantsis a named tuple of all possible variants, e.g.:[SpacetimeDB.Type] partial record Option<T> : SpacetimeDB.TaggedEnum<(T Some, Unit None)>;will generate inherited records
Option.Some(T Some_)andOption.None(Unit None_). It allows you to use tagged enums in C# in a similar way to Rust enums by leveraging C# pattern-matching on any instance ofOption<T>. -
[SpacetimeDB.Table]- generates code to register this table in theFFIupon startup so that they can be enumerated by the__describe_module__FFI API. It implies[SpacetimeDB.Type], so you must not specify both attributes on the same struct.The fields can be marked with
[SpacetimeDB.ColumnAttrs]and those will be detected by the codegen and passed on to the runtime as well. Example:[SpacetimeDB.Table] public partial struct Person { [SpacetimeDB.Column(ColumnAttrs.Identity)] public int Id; public string Name; } -
[SpacetimeDB.Reducer]- generates code to register a static function as a SpacetimeDB reducer in theFFIupon startup and creates a wrapper that will parse SATS binary blob into individual arguments and invoke the underlying function for the__call_reducer__FFI API.