Skip to content

C# client typed query builder#3982

Merged
rekhoff merged 29 commits into
masterfrom
rekhoff/csharp-client-query-builder
Jan 28, 2026
Merged

C# client typed query builder#3982
rekhoff merged 29 commits into
masterfrom
rekhoff/csharp-client-query-builder

Conversation

@rekhoff
Copy link
Copy Markdown
Contributor

@rekhoff rekhoff commented Jan 9, 2026

Description of Changes

This PR implements the C# client-side typed query builder, as assigned in #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)
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();
}
  1. Typed WHERE filters and boolean composition
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();
  1. “Admin can see all, otherwise only self” (proposal’s “player” view logic, but client-side)
Identity self = /* ... */;

conn.SubscriptionBuilder()
    .AddQuery(qb =>
        qb.From.Players().Where(p =>
            p.Identity.Eq(self)
        )
    )
    .Subscribe();
  1. Index-column access for query construction (IxCols)
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

  • Ran run-regression-tests.sh successfully after regenerating C# bindings.
  • Ran C# unit tests using dotnet test sdks/csharp/tests~/tests.csproj -c Release
  • 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

@rekhoff rekhoff self-assigned this Jan 9, 2026
@rekhoff rekhoff marked this pull request as ready for review January 9, 2026 20:54
@bfops bfops added release-1.12 backward-compatible enhancement New feature or request release-any To be landed in any release window and removed backward-compatible release-1.12 labels Jan 12, 2026
Copy link
Copy Markdown
Contributor

@joshua-spacetime joshua-spacetime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some end-to-end tests?

Comment thread sdks/csharp/src/QueryBuilder.cs Outdated
Comment thread sdks/csharp/src/QueryBuilder.cs Outdated
Comment thread sdks/csharp/src/QueryBuilder.cs Outdated
Comment thread sdks/csharp/src/QueryBuilder.cs Outdated
Comment thread sdks/csharp/src/QueryBuilder.cs Outdated
Comment thread sdks/csharp/tests~/QueryBuilderTests.cs Outdated
Comment thread sdks/csharp/tests~/QueryBuilderTests.cs Outdated
Comment thread sdks/csharp/tests~/QueryBuilderTests.cs Outdated
@rekhoff
Copy link
Copy Markdown
Contributor Author

rekhoff commented Jan 15, 2026

Can we add some end-to-end tests?

Added end-to-end coverage in sdks/csharp/examples~/regression-tests by converging existing SQL queries used, into the typed query builder subscription path (AddQuery). Also added a targeted WHERE case as that hadn't been covered before.

@joshua-spacetime joshua-spacetime linked an issue Jan 16, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

@joshua-spacetime joshua-spacetime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. Only a few small remaining requests/questions.

Comment thread sdks/csharp/examples~/regression-tests/client/Program.cs Outdated
Comment thread sdks/csharp/tests~/QueryBuilderTests.cs Outdated
Comment thread sdks/csharp/tests~/QueryBuilderTests.cs Outdated
@rekhoff rekhoff changed the base branch from master to rekhoff/remove-1.5.0-dlls January 23, 2026 00:30
Base automatically changed from rekhoff/remove-1.5.0-dlls to master January 23, 2026 22:03
Comment thread sdks/csharp/src/Table.cs Outdated
Comment thread sdks/csharp/tests~/QueryBuilderTests.cs Outdated
Comment thread sdks/csharp/examples~/regression-tests/client/Program.cs
@rekhoff rekhoff force-pushed the rekhoff/csharp-client-query-builder branch from 726269f to f415652 Compare January 27, 2026 17:38
@rekhoff rekhoff added this pull request to the merge queue Jan 28, 2026
Merged via the queue into master with commit 4f0a21f Jan 28, 2026
27 of 28 checks passed
@bfops bfops deleted the rekhoff/csharp-client-query-builder branch May 7, 2026 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request release-any To be landed in any release window

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[C#] Client bindings for query builder

3 participants