Commit Graph

14 Commits

Author SHA1 Message Date
Ryan 4f0a21f948 C# client typed query builder (#3982)
# 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
2026-01-28 02:12:59 +00:00
Ryan 6f91cfd524 Enable RefOption returns from Views to support Views returning a single class (#3964)
# Description of Changes

This PR adds a regression test covering nullable reference-type view
returns in C# modules (e.g. Account? where Account is a class), as
reported in
[#3962](https://github.com/clockworklabs/SpacetimeDB/issues/3962).

```csharp
public static Account? MyAccount(ViewContext ctx)
```
* Updated the C# regression-test server module to include a
reference-type table and views that exercise the RefOption path:
* A new public reference-type table row:
```csharp
[SpacetimeDB.Table(Name = "account", Public = true)]
public partial class Account { ... }
```
* A public at-most-one view that returns a nullable reference type
(Account?) via Find(...):
```csharp
[SpacetimeDB.View(Name = "my_account", Public = true)]
public static Account? MyAccount(ViewContext ctx)
{
    return ctx.Db.account.Identity.Find(ctx.Sender) as Account;
}
```
* A second public view that returns null to ensure the “empty result”
case is exercised:
```csharp
[SpacetimeDB.View(Name = "my_account_missing", Public = true)]
public static Account? MyAccountMissing(ViewContext ctx) => null;
```
* Updated ClientConnected to ensure an Account row exists for the
connecting identity so the “one row” case is deterministic.
* Updated the C# regression-test client to:
  * Subscribe to the new views:
    * `SELECT * FROM my_account`
    * `SELECT * FROM my_account_missing`
  * Assert correct semantics for nullable reference-type view returns:
    * MyAccount.Count == 1
    * MyAccountMissing.Count == 0
* Updated the regression-test server project to use local C#
runtime/codegen project references so the regression module exercises
the in-repo generator/runtime behavior (instead of the published
SpacetimeDB.Runtime package).

# API and ABI breaking changes

None.
* No changes to public module schema/wire format semantics beyond adding
regression-test-only tables/views.
* No behavior changes outside the C# regression test module + harness.

# Expected complexity level and risk

2 - Low

* Changes are isolated to regression tests and project wiring.
* The scenario specifically guards the nullable reference-type
“Option-like view return” path against regressions.

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [X] Ran C# regression tests with no failures in new View tests

Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
2026-01-15 00:21:08 +00:00
Ryan 0b91afadef Fix C# null string serialization error + add regression coverage for Issue #3960 (#3967)
# Description of Changes
This PR fixes a C# SDK/BSATN serialization issue where inserting a
`null` string into a non-nullable `string` column could throw an
`ArgumentNullException` with an unhelpful stack trace, as reported in
[#3960](https://github.com/clockworklabs/SpacetimeDB/issues/3960).

* Updated the C# BSATN runtime to reject `null` for non-nullable
`string` serialization with a clear, actionable error message (guides
users to model nullable strings as `string?` so BSATN encodes an
option).
* Added BSATN runtime unit tests to cover:
  * Non-nullable string rejects `null` with an actionable message
* Nullable string (`RefOption<string, BSATN.String>`) round-trips `null`
* Added end-to-end C# regression-test module + client assertions for
#3960:
  * Inserting `""` into non-nullable `string` succeeds
* Inserting `null` into non-nullable `string` fails with the expected
message
* Inserting `null` into nullable `string?` succeeds and round-trips as
`null`
# API and ABI breaking changes
None.
* No schema or wire-format changes.
* Behavior change is limited to improving the error surfaced when
attempting to serialize null as a non-nullable BSATN `String`, plus
stricter input validation for `ConnectionId` / `Identity` parsing.
# Expected complexity level and risk
2 - Low
* Changes are isolated to regression test coverage.
* No impact on valid serialization paths; only affects invalid/malformed
inputs and improves diagnostics.
# Testing
- [X] Ran C# regression-tests client and verified new tests pass.

---------

Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
2026-01-09 21:46:52 +00:00
Ryan 952402d6ab Fix reducer errors to return message not stack trace (#3965)
# Description of Changes
This PR fixes a C# reducer error-reporting issue where exceptions thrown
in reducers were returned to clients with full stack traces, as reported
in [#3959](https://github.com/clockworklabs/SpacetimeDB/issues/3959).

- Updated the C# reducer host-call error serialization to return only
`Exception.Message` (instead of `Exception.ToString()`), preventing
server-side stack traces from being leaked to clients.
- Added a C# regression-test client assertion to validate reducer
failures do not contain stack traces:
  - `exception.Message` matches the thrown message exactly
- the message does not contain newlines or `" at "` stack-trace markers

# API and ABI breaking changes
None.
- No schema or wire-format changes.
- Behavior change is limited to the *contents* of reducer error strings
returned to clients (stack traces removed).

# Expected complexity level and risk
2 - Low
- Change is isolated to C# reducer error formatting plus a regression
assertion.
- Removes unintended information exposure without affecting reducer
success paths.

# Testing
- [X] Ran C# regression-tests client; verified reducer error message is
preserved and stack trace is not present.
2026-01-09 18:15:42 +00:00
Mario Montoya 82d5a4f6c0 Implement SpacetimeType for Result<T, E> (#3790)
# Description of Changes

Closes #3673 

*NOTE*: C++ part will be in another PR

# Expected complexity level and risk

2

Adding a new type touch everywhere

# Testing

- [x] Adding smoke and unit test

---------

Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
Co-authored-by: rekhoff <r.ekhoff@clockworklabs.io>
Co-authored-by: Phoebe Goldman <phoebe@goldman-tribe.org>
Co-authored-by: Jason Larabie <jason@clockworklabs.io>
2026-01-08 15:50:18 +00:00
Ryan 8dd18f078f C# bindings: add procedure_http_request support + fix HTTP BSATN wire format to match spacetimedb_lib::http (#3944)
# Description of Changes

Rust added procedure-scoped HTTP via the `procedure_http_request` ABI
(see Rust PR #3684) to C# host bindings.

What changed:
1) Fix for BSATN wire format
* Updated C# BSATN wire types for HTTP
(`BSATN.Runtime/HttpWireTypes.cs`) to match exactly the Rust stable
types:
* `spacetimedb_lib::http::Request` fields and order: `method`,
`headers`, `timeout`, `uri`, `version`
* `spacetimedb_lib::http::Response` fields and order: `headers`,
`version`, `code`
* Header pairs are (`name: string, value: bytes`); no additional
metadata is encoded.
* Added explicit “do not reorder/extend” note in the C# wire type file
since the host BSATN-decodes these bytes directly and may trap on
mismatch.
2) C# runtime HTTP client implementation
* Implemented `ProcedureContext.Http` support (`Runtime/Http.cs`) that:
* BSATN-serializes `HttpRequestWire` + sends body bytes via
`procedure_http_request`
* On success, BSATN-decodes `HttpResponseWire` and returns `(response,
body)` as a typed `HttpResponse`
  * On `HTTP_ERROR`, decodes the error payload as a BSATN `string`
* On `WOULD_BLOCK_TRANSACTION`, returns a stable error message (host
rejects blocking HTTP while a mut tx is open)
* Clamps timeouts to 500ms max (matching host behavior documented on the
Rust side)
3) ABI wiring / import plumbing
* Added the `procedure_http_request` import to the C# wasm shim
(`Runtime/bindings.c`) under the `spacetime_10.3` import module.
* Verified the generated C# P/Invoke signature matches the host ABI
(request ptr/len, body ptr/len, out `[BytesSource; 2]`).
4) Procedure entrypoint contract (trap vs errno)
* Updated/clarified the module procedure trampoline behavior
(`Runtime/Internal/Module.call_procedure`): it must either return
`Errno.OK` or trap (log + rethrow). This mirrors the host’s expectations
and avoids “unexpected errno values from guest entrypoints” behavior.
Behavioral notes vs Rust
* Rust bindings may `expect(...)`/panic on “should never happen”
serialization failures; C# runtime explicitly avoids throwing across the
procedure boundary for the HTTP client path and instead returns
`Result.Err` for unexpected failures. This prevents whole-module traps
from user-space HTTP usage while still surfacing rich errors.
* The host remains authoritative on timeout enforcement; C# now mirrors
the effective host clamp (500ms) to keep behavior aligned.

# API and ABI breaking changes

No new host ABI introduced, and does not change the syscall signature.
* This is not an ABI “break” in the host, but it is a guest-side wire
compatibility correction: older C# guests built with the incorrect wire
types could cause the host to trap during BSATN decode. After this PR,
C# guests are compatible with the host’s expected layout.

**Adds API**: `ProcedureContextBase.Http` is available for procedures
No existing public types/method signatures are removed
* A notable difference from Rust's panic behavior is that Rust code
sometimes `expect(...)`s and may panic/trap on internal “should not
happen” cases. On the other hand, C#'s `HttpClient.Send` explicitly
converts unexpected failures into `Result.Err` to avoid trapping the
module. This is a behavioral difference, but not an API break.

# Expected complexity level and risk

2 - Mostly just creating equivalents to the Rust version. 

# Testing

C# regression tests updated to cover:
- [X] Successful request path (`ReadMySchemaViaHttp`)
- [X] Invalid request path (`InvalidHttpRequest`) returning error
without trapping

Testing performed:
- [X] Full regression suite run against local node (see chat log); no
wasm traps, all suites reported `Success`.

---------

Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
2026-01-06 22:51:24 +00:00
Ryan f906be6bd9 Fixed Nullable value-type fields in Views in C# module causing excessive logging (#3949)
# Description of Changes

This PR fixes a C# codegen performance/behavior issue triggered by views
that include nullable value-type fields (e.g. `DbVector2?`), as reported
in #3914.

* Updated the C# BSATN code generator to emit non-boxing equality for
`Nullable<T>` members by using `System.Nullable.Equals(a, b)` rather
than `a.Equals(b)` (which can box and cause excessive host logging/work
in view diff/equality paths).

This causes code generation to change from something like:
``` csharp
// From:
var ___eqNullableIntField = this.NullableIntField.Equals(that.NullableIntField);
// To:
var ___eqNullableIntField = System.Nullable.Equals(
    this.NullableIntField,
    that.NullableIntField
);
```
* Added a regression scenario to the C# regression-test module that
exercises the problematic pattern:
  * A table containing a nullable struct field (`DbVector2? Pos`).
  * A public view that returns rows containing that nullable field.
* A reducer to mutate the nullable field from `some` → `none` → `some`
to force view re-evaluation and diffing.
* Updated the regression-test client to:
  * Subscribe to the new view.
* Validate that the view evaluates successfully and contains the
expected rows.
  * Call the reducer and validate view state after updates.
  * Fail the test if view evaluation/diffing produces errors.

# API and ABI breaking changes

None.
* No changes to public SpacetimeDB schema or wire format semantics.
* Changes are limited to generated equality code and regression tests.

# Expected complexity level and risk

2 - Low
* The codegen change is small and localized (special-casing
`System.Nullable<T>` equality generation).
* Risk is primarily around subtle behavior differences in equality for
nullable value types; however, `System.Nullable.Equals` matches the
expected semantics and avoids boxing.
* Regression tests specifically cover the nullable-struct-in-view
scenario to guard against regressions.

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [X] Ran the C# regression test suite via `run-regression-tests.sh` and
confirmed no errors.
2026-01-06 17:56:05 +00:00
Mario Montoya 8fb0bcf922 Add UUID built-in convenience type to SpacetimeDB (#3538)
# Description of Changes

Closes
[#3290](https://github.com/clockworklabs/SpacetimeDB/issues/3290).

Adds a new "special" type to SATS, `UUID`, which is represented as the
product `{ __uuid__: u128 }`. Adds versions of this type to all of our
various languages' module bindings libraries and client SDKs, and
updates codegen to recognize it and output references to those named
library types. Adds methods for creating new UUIDs according to the V4
(all random) and V7 (timestamp, monotonic counter and random)
specifications.

# API and ABI breaking changes

We add a new type 

# Expected complexity level and risk

2

it impacts all over the code

# Testing

- [x] Extends the Rust and Unreal SDK tests, and the associated
`module-test` modules in Rust, C# and TypeScript, with uses of UUIDs.
- [x] Extends the C# SDK regression tests with uses of UUIDs.
- [x] Extends the TypeScript test suite with tests with uses of UUIDs.

---------

Signed-off-by: Mario Montoya <mamcx@elmalabarista.com>
Co-authored-by: Phoebe Goldman <phoebe@clockworklabs.io>
Co-authored-by: Jason Larabie <jason@clockworklabs.io>
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
2026-01-02 17:17:24 +00:00
Ryan f309ea93f9 Removes C# regression test of TimestampCapabilities (#3908)
# Description of Changes

Removes the `TimestampCapabilities` test from the C# SDK's regression
test suite, due to creating inconsistent failures in automated CI
testing.

# API and ABI breaking changes

Not API/ABI breaking

# Expected complexity level and risk

1

# Testing

- [X] Ran locally, no failures in building or running C# regression
tests
2025-12-19 22:15:13 +00:00
rekhoff 39f01289e5 C# implementation of Transactions for Procedures (#3809)
# Description of Changes

Implements the C# equivalent of #3638

This implement uses inheritance, where abstract base classes (like
`ProcedureContextBase` in `ProcedureContext.cs`) store the core of the
implementation, and then generated wrappers (like `ProcedureContext` in
the generated FFI.cs file) inherit from them.

For error handling, we work like Rust's implementation of `Result<T,E>`
but we require `where E : Exception` because of how exceptions work in
C#. Transaction-level failures come back as a `TxOutcome` and user
errors should follow the `Result<T,E>` pattern. In this implementation,
we have `UnwrapOrThrow()` throws exceptions directly because of C#'s
error handling pattern.

Unlike the Rust implementation's direct `Result` propagation, we are
using an `AbortGuard` pattern (in `ProcedureContext.cs`) for exception
handling, which uses `IDisposable` for automatic cleanup.

Most changes should have fairly similar Rust-equivalents beyond that.
For module authors, the changes here allow for the transation logic to
work like:
```csharp
ctx.TryWithTx<ResultType, Exception>(tx => {
    // transaction logic
    return Result<ResultType, Exception>.Ok(result);
});
```
This change includes a number of tests added to the
`sdks/csharp/examples~/regression-tests/`'s `server` and `client` to
validate the behavior of the changes. `server` changes provide further
usage examples for module authors.

# API and ABI breaking changes

Should not be a breaking change

# Expected complexity level and risk

2

# Testing

- [x] Created Regression Tests that show transitions in procedures
working in various ways, all of which pass.
2025-12-18 18:41:47 +00:00
rekhoff cb35104c0f Export __call_view__ in C# (#3691)
# Description of Changes

1. Updates the Replication Tests in
`sdks/csharp/examples~/regression-tests` to include better coverage of
Views
2. Added missing linkage for __call_view__ and __call_view_anon__
3. Updated *ViewDispatcher Invoke to transform BSATN.ValueOption<> into
BSATN.List<>
4. Fixed issues with the indexing of views to match correctly during
__call_view__ and __call_view_anon__

# API and ABI breaking changes

No

# Expected complexity level and risk

2

# Testing

- [x] Running `run-regression-tests.sh` passes.

---------

Signed-off-by: rekhoff <r.ekhoff@clockworklabs.io>
Signed-off-by: Jason Larabie <jason@clockworklabs.io>
Co-authored-by: Jason Larabie <jason@clockworklabs.io>
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
2025-11-20 17:17:37 +00:00
rekhoff b5eac84caf Add a RemoteQuery regression test to C# SDK (#3163)
# Description of Changes

Add a RemoteQuery regression test to C# SDK to address issue:
https://github.com/clockworklabs/SpacetimeDB/issues/3064
This adds on to existing regression tests, creating a rather small
change.

# API and ABI breaking changes

Not breaking change

# Expected complexity level and risk

1

# Testing

- [X] Ran `dotnet test`, all tests pass
2025-08-22 17:43:00 +00:00
james gilles b6f8362b75 Implement OnUnhandledReducerError (#295)
## Description of Changes
SDK side of https://github.com/clockworklabs/SpacetimeDB/pull/2636
Addresses
https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk/issues/281

## API

 - [ ] This is an API breaking change to the SDK

## Requires SpacetimeDB PRs
https://github.com/clockworklabs/SpacetimeDB/pull/2636

## Testsuite
*If you would like to run the your SDK changes in this PR against a
specific SpacetimeDB branch, specify that here. This can be a branch
name or a link to a PR.*

SpacetimeDB branch name: jgilles/on-unhandled-reducer-error

## Testing

- [ ] Add a test that this works to the regression-tests example module
2025-04-23 13:50:53 -07:00
james gilles 3543132c7d Fix for unsubscribe bug + regression tests (#264)
## Description of Changes

This PR fixes one issue and adds regression tests for it, plus another
issue. These regression tests are run on CI. You can also run them
locally -- run a local spacetime instance and then
`tools~/run-regression-tests.sh ../SpacetimeDB`).

### Issue 1: Unsubscribe is non-functional

See
https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk/issues/279.
This was a coding mistake made during the rush to 1.0. It was easily
fixed.

### Issue 2: Indexes not tracking deletes

This was recently discovered by the BitCraft team.

The problem was introduced in
https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk/commit/cefc727b7693fe97e3dd848fef2bb17bf20c8be6
which introduced `RemoteTableHandle<EventContext,
Row>.IndexBase<Column>`. This class stores a `Dictionary<Column,
List<Row>>` to allow fast filtering. It relies on `List<Row>.Remove` to
remove elements from a list when a row delete is received.
`List<Row>.Remove` relies on `Row.Equals` to determine when elements are
equal.

`Row.Equals` formerly was not implemented for `[SpacetimeDB.Type]`s or
for generated Row types. So, this didn't work.

Now it does: it was fixed by
https://github.com/clockworklabs/SpacetimeDB/pull/239 . So all this PR
does to address this issue is add a regression test.

## API

 - [x] This is an API breaking change to the SDK

The interface IDbConnection was changed; however, this changed part of
the interface is `internal`, so this should be entirely invisible to
users.

## Requires SpacetimeDB PRs
https://github.com/clockworklabs/SpacetimeDB/pull/239 (merged)

## Testsuite
*If you would like to run the your SDK changes in this PR against a
specific SpacetimeDB branch, specify that here. This can be a branch
name or a link to a PR.*

SpacetimeDB branch name: master

## Testing
*Write instructions for a test that you performed for this PR*

- [x] Added a repro for both issues
- [x] Added the repro to CI
- [x] Fixed both issues
2025-03-28 20:15:51 +00:00