mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-13 03:08:40 -04:00
4f0a21f948
# Description of Changes This PR implements the C# client-side typed query builder, as assigned in https://github.com/clockworklabs/SpacetimeDB/issues/3759. Key pieces: * Added a small C# runtime query-builder surface in the client SDK (`sdks/csharp/src/QueryBuilder.cs`): * `Query` (wraps the generated SQL string) * `Table<TRow, TCols, TIxCols>` (entry point for `All()` / `Where(...)`) * `Col<TRow, TValue>` and `IxCol<TRow, TValue>` (typed column references) * `BoolExpr` (typed boolean expression composition) * SQL identifier quoting + literal formatting helpers (`SqlFormat`) * `Join(...)` with `WhereLeft(...)` / `WhereRight(...)` * `LeftSemijoin(...)` / `RightSemijoin(...)` with `Where(...)` chaining * Extended C# client bindings codegen (`crates/codegen/src/csharp.rs`) to generate: * Per-table/view `*Cols` and `*IxCols` helper classes used by the typed query builder. * A generated per-module `QueryBuilder` with a `From` accessor for each table/view, producing `Table<...>` values. * A generated `TypedSubscriptionBuilder` which collects `Query<TRow>.Sql` values and calls the existing subscription API. * An `AddQuery(Func<QueryBuilder, Query> build)` entry point off `SubscriptionBuilder`, mirroring the proposal’s Rust API. * Fixed a codegen naming collision found during regression testing: * `*Cols`/`*IxCols` helpers are now named after the table/view accessor name (PascalCase) instead of the row type, since multiple tables/views can share the same row type (e.g. alias tables / views returning an existing product type). * Kept `Cols`/`IxCols` off the public surface: * `Table.Cols` and `Table.IxCols` are internal, so consumers only access columns via the `Where(...)`/join predicate lambdas. C# usage examples (mirroring the proposal’s Rust examples) 1) Typed subscription flow (no raw SQL) ```csharp void Subscribe(SpacetimeDB.Types.DbConnection conn) { conn.SubscriptionBuilder() .OnApplied(ctx => { /* ... */ }) .OnError((ctx, err) => { /* ... */ }) .AddQuery(qb => qb.From.Users().Build()) .AddQuery(qb => qb.From.Players().Build()) .Subscribe(); } ``` 2) Typed `WHERE` filters and boolean composition ```csharp conn.SubscriptionBuilder() .OnApplied(ctx => { /* ... */ }) .OnError((ctx, err) => { /* ... */ }) .AddQuery(qb => qb.From.Players().Where(p => p.Name.Eq("alice").And(p.IsOnline.Eq(true))).Build()) .Subscribe(); ``` 3) “Admin can see all, otherwise only self” (proposal’s “player” view logic, but client-side) ```csharp Identity self = /* ... */; conn.SubscriptionBuilder() .AddQuery(qb => qb.From.Players().Where(p => p.Identity.Eq(self) ) ) .Subscribe(); ``` 4) Index-column access for query construction (IxCols) ```csharp conn.SubscriptionBuilder() .AddQuery(qb => qb.From.Players().Where( qb.From.Players().IxCols.Identity.Eq(self) ) ) .Subscribe(); ``` # API and ABI breaking changes None. * Additive client SDK runtime types. * Additive client bindings codegen output. * No wire-format changes. # Expected complexity level and risk 2 - Low to moderate * Mostly additive code + codegen. * The main risk is correctness/compat of generated SQL strings and name/casing conventions across languages; this is mitigated by targeted unit tests + full C# regression test runs. # Testing - [X] Ran run-regression-tests.sh successfully after regenerating C# bindings. - [X] Ran C# unit tests using `dotnet test sdks/csharp/tests~/tests.csproj -c Release` - [X] Added a new unit test suite (`sdks/csharp/tests~/QueryBuilderTests.cs`) validating: * Identifier quoting / escaping * Literal formatting (including `Identity`/`ConnectionId`/`Uuid` hex literals; `U128` integer literal) * null + enum unsupported behavior throws * Boolean expression parenthesization (`And`/`Or`/`Not`) * `Where(...)` overloads including `IxCols`-based predicates * left/right semijoin SQL formatting and predicate chaining
Running tests
You can use dotnet test (either in this directory or in the project root directory) to run the tests.
Using a different SpacetimeDB version
To run tests using a local version of the SpacetimeDB repo, you can add a nuget.config file in the root of this repository.
The tools/write-nuget-config.sh script can generate the nuget.config. It takes one parameter: the path to the root SpacetimeDB repository (relative or absolute).
Then, you need to dotnet pack the BSATN.Runtime package in the SpacetimeDB repo.
Lastly, before running dotnet test, you should dotnet nuget locals all --clear to clear out any cached packages. This ensures you're actually testing with the new package you just built.
Example:
$ export SPACETIMEDB_REPO_PATH="../SpacetimeDB"
$ tools/write-nuget-config.sh "${SPACETIMEDB_REPO_PATH}"
$ ( cd "${SPACETIMEDB_REPO_PATH}"/crates/bindings-csharp/BSATN.Runtime && dotnet pack )
$ dotnet nuget locals all --clear
$ dotnet test