Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9e82466
C++ query builder
JasonAtClockwork Mar 17, 2026
f8e89b5
Updates for documentation
JasonAtClockwork Mar 18, 2026
f71a7c4
Update crates/bindings-cpp/include/spacetimedb/query_builder/expr.h
JasonAtClockwork Mar 20, 2026
ae3e538
Update docs/docs/00200-core-concepts/00100-databases/00500-cheat-shee…
JasonAtClockwork Mar 20, 2026
8cef62e
Merge branch 'master' into jlarabie/cpp-query-builder
JasonAtClockwork Mar 20, 2026
2f5761b
Sync up with changes from the Unreal Query Builder work
JasonAtClockwork Apr 13, 2026
6769b11
Merge branch 'master' into jlarabie/cpp-query-builder
JasonAtClockwork Apr 14, 2026
0f225dc
Clean up of invalid join expressions and matching where consistency f…
JasonAtClockwork Apr 20, 2026
05bc5a2
Update crates/bindings-cpp/include/spacetimedb/query_builder/expr.h
JasonAtClockwork Apr 22, 2026
cea807b
Merge branch 'master' into jlarabie/cpp-query-builder
JasonAtClockwork Apr 23, 2026
4119eb4
Working first pass before refactoring
JasonAtClockwork Mar 23, 2026
55577ae
Refactor to switch the query builder to pass cols + indexes with the …
JasonAtClockwork Mar 23, 2026
b8af3f0
Adding Unreal Blueprint support for Query Builder
JasonAtClockwork Apr 10, 2026
50cfd21
Updated core query builder to match with the C++ core and added docum…
JasonAtClockwork Apr 15, 2026
5607701
Mirrored changes from the C++ query builder core
JasonAtClockwork Apr 20, 2026
dd87408
Carry over changes from C++ query builder
JasonAtClockwork Apr 23, 2026
00fb728
Expand negative testing and fixed issues around brittle checks
JasonAtClockwork Apr 30, 2026
d5c07c1
Move the internals for where_col/where_ix out of the public api
JasonAtClockwork May 11, 2026
66c80c2
Merge branch 'master' into jlarabie/cpp-query-builder
JasonAtClockwork May 15, 2026
28799b3
Fix from missed pieces in merge
JasonAtClockwork May 15, 2026
3ce446e
Merge branch 'master' into jlarabie/cpp-query-builder
bfops May 16, 2026
d555ed4
Narrowed the API surface for where()
JasonAtClockwork Jun 10, 2026
3eaac46
Merge branch 'jlarabie/cpp-query-builder' into jlarabie/unreal-query-…
JasonAtClockwork Jun 10, 2026
5c7be0a
Update to match C++ query builder and fix codegen for more than 255 r…
JasonAtClockwork Jun 10, 2026
40f316a
Merge branch 'master' into jlarabie/cpp-query-builder
JasonAtClockwork Jun 10, 2026
30eb64e
Fix query builder test script
JasonAtClockwork Jun 10, 2026
6608280
Merge branch 'jlarabie/cpp-query-builder' into jlarabie/unreal-query-…
JasonAtClockwork Jun 10, 2026
23d683b
Linting fix
JasonAtClockwork Jun 10, 2026
8ae04f9
Merge branch 'master' into jlarabie/unreal-query-builder
JasonAtClockwork Jun 12, 2026
5dd8057
Merge branch 'master' into jlarabie/unreal-query-builder
JasonAtClockwork Jun 15, 2026
12f5dcd
Merge branch 'master' into jlarabie/unreal-query-builder
JasonAtClockwork Jun 16, 2026
f28fbff
Merge branch 'master' into jlarabie/unreal-query-builder
JasonAtClockwork Jun 18, 2026
d8a2be9
Merge branch 'master' into jlarabie/unreal-query-builder
JasonAtClockwork Jun 19, 2026
e8c2ec8
Refactor Unreal tests for latest Rust test harness changes
JasonAtClockwork Jun 19, 2026
f0f4b79
Merge branch 'master' into jlarabie/unreal-query-builder
JasonAtClockwork Jun 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
**/module_bindings/** linguist-generated=true eol=lf
**/ModuleBindings/** linguist-generated=true eol=lf
/docs/llms/** linguist-generated=true
/docs/llms/*-details.json linguist-generated=false
994 changes: 955 additions & 39 deletions crates/codegen/src/unrealcpp.rs

Large diffs are not rendered by default.

166 changes: 157 additions & 9 deletions docs/docs/00200-core-concepts/00400-subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,69 @@ conn.db().user().on_update(|ctx, old_user, new_user| {
});
```

</TabItem>
<TabItem value="unrealcpp" label="Unreal C++">

```cpp
// Connect to the database
FOnConnectDelegate ConnectDelegate;
BIND_DELEGATE_SAFE(ConnectDelegate, this, AMyActor, OnConnected);

Conn = UDbConnection::Builder()
->WithUri(TEXT("wss://maincloud.spacetimedb.com"))
->WithDatabaseName(TEXT("my_module"))
->OnConnect(ConnectDelegate)
->Build();

// React to new rows being inserted
Conn->Db->User->OnInsert.AddDynamic(this, &AMyActor::OnUserInsert);

// React to rows being deleted
Conn->Db->User->OnDelete.AddDynamic(this, &AMyActor::OnUserDelete);

// React to rows being updated
Conn->Db->User->OnUpdate.AddDynamic(this, &AMyActor::OnUserUpdate);

void AMyActor::OnConnected(UDbConnection* Connection, FSpacetimeDBIdentity Identity, const FString& Token)
{
FOnSubscriptionApplied SubscriptionAppliedDelegate;
BIND_DELEGATE_SAFE(SubscriptionAppliedDelegate, this, AMyActor, OnSubscriptionApplied);

Connection->SubscriptionBuilder()
->OnApplied(SubscriptionAppliedDelegate)
->AddQuery([](const FQueryBuilder& Q)
{
return Q.From.User();
})
->AddQuery([](const FQueryBuilder& Q)
{
return Q.From.Message();
})
->Subscribe();
}

void AMyActor::OnSubscriptionApplied(const FSubscriptionEventContext& Context)
{
UE_LOG(LogTemp, Log, TEXT("Subscription ready!"));

// Initial data is now in the client cache
for (const FUserType& User : Context.Db->User->Iter())
{
UE_LOG(LogTemp, Log, TEXT("User: %s"), *User.Name);
}
}
```

</TabItem>
<TabItem value="unrealblueprint" label="Unreal Blueprint">

![Unreal Blueprint quick start subscription graph](/images/unreal/subscriptions/ue-blueprint-quick-start.png)

</TabItem>
</Tabs>

:::tip Typed Query Builders
Type-safe query builders are available in TypeScript, C#, and Rust and are the recommended default. They provide auto-completion and compile-time type checking. For complete API details, see [TypeScript](./00600-clients/00700-typescript-reference.md#query-builder-api), [C#](./00600-clients/00600-csharp-reference.md#query-builder-api), and [Rust](./00600-clients/00500-rust-reference.md#query-builder-api) references.
Type-safe query builders are available in TypeScript, C#, Rust, and Unreal and are the recommended default. They provide auto-completion and compile-time type checking. For complete API details, see [TypeScript](./00600-clients/00700-typescript-reference.md#query-builder-api), [C#](./00600-clients/00600-csharp-reference.md#query-builder-api), [Rust](./00600-clients/00500-rust-reference.md#query-builder-api), and [Unreal](./00600-clients/00800-unreal-reference.md#query-builder-api) references.
:::

## How Subscriptions Work
Expand All @@ -169,16 +227,20 @@ All SDKs expose a builder API for creating subscriptions:
- Register an error callback: runs if subscription registration fails or a subscription later terminates with an error.
- Subscribe with one or more queries.

In Unreal, inspect the initial subscribed data set in `OnApplied`. Use table callbacks like `OnInsert`, `OnDelete`, and `OnUpdate` for later live changes.

### Query Forms

All SDKs support subscriptions. TypeScript, C#, and Rust support query builders (recommended), while Unreal uses query strings:
All SDKs support subscriptions. Query builders are the recommended default across SDKs, with raw SQL available where needed:

| SDK | Typed Query Builder Support | Entry Point |
| --- | --- | --- |
| TypeScript | Yes | `tables.<table>.where(...)` passed to `subscribe(...)` |
| C# | Yes | `SubscriptionBuilder.AddQuery(...).Subscribe()` |
| Rust | Yes | `subscription_builder().add_query(...).subscribe()` |
| Unreal | No | Query strings passed to `Subscribe(...)` |
| Unreal | Yes | `SubscriptionBuilder.AddQuery(...).Subscribe()` |

Unreal also supports raw SQL subscriptions through `Subscribe(const TArray<FString>& SQL)` when you need direct SQL control.

### Subscription Handles

Expand All @@ -198,6 +260,7 @@ Subscribing returns a handle that manages an individual subscription lifecycle.
- [Rust subscription API](./00600-clients/00500-rust-reference.md#subscribe-to-queries)
- [Rust query builder API](./00600-clients/00500-rust-reference.md#query-builder-api)
- [Unreal subscription API](./00600-clients/00800-unreal-reference.md#subscriptions)
- [Unreal query builder API](./00600-clients/00800-unreal-reference.md#query-builder-api)

## Best Practices for Optimizing Server Compute and Reducing Serialization Overhead

Expand Down Expand Up @@ -265,7 +328,7 @@ var globalSubscriptions = conn
// May unsubscribe to shop_items as player advances
var shopSubscription = conn
.SubscriptionBuilder()
.AddQuery(q => q.From.ShopItems().Where(r => r.RequiredLevel.Lte(5U)))
.AddQuery(q => q.From.ShopItems().Where(r => r.RequiredLevel.Lte(5)))
.Subscribe();
```

Expand All @@ -285,10 +348,45 @@ let global_subscriptions = conn
// May unsubscribe to shop_items as player advances
let shop_subscription = conn
.subscription_builder()
.add_query(|q| q.from.shop_items().r#where(|r| r.required_level.lte(5u32)))
.add_query(|q| q.from.shop_items().r#where(|r| r.required_level.lte(5)))
.subscribe();
```

</TabItem>
<TabItem value="unrealcpp" label="Unreal C++">

```cpp
UDbConnection* Conn = ConnectToDB();

// Never need to unsubscribe from global subscriptions
USubscriptionHandle* GlobalSubscriptions = Conn->SubscriptionBuilder()
->AddQuery([](const FQueryBuilder& Q)
{
return Q.From.Announcements();
})
->AddQuery([](const FQueryBuilder& Q)
{
return Q.From.Badges();
})
->Subscribe();

// May unsubscribe from ShopItems as player advances
USubscriptionHandle* ShopSubscription = Conn->SubscriptionBuilder()
->AddQuery([](const FQueryBuilder& Q)
{
return Q.From.ShopItems().Where([](const FShopItemsCols& Row)
{
return Row.RequiredLevel.Lte(5);
});
})
->Subscribe();
```

</TabItem>
<TabItem value="unrealblueprint" label="Unreal Blueprint">

![Unreal Blueprint grouped subscriptions graph](/images/unreal/subscriptions/ue-blueprint-group-subscriptions.png)

</TabItem>
</Tabs>

Expand Down Expand Up @@ -349,14 +447,14 @@ var conn = ConnectToDB();
var shopSubscription = conn
.SubscriptionBuilder()
.AddQuery(q => q.From.ExchangeRates())
.AddQuery(q => q.From.ShopItems().Where(r => r.RequiredLevel.Lte(5U)))
.AddQuery(q => q.From.ShopItems().Where(r => r.RequiredLevel.Lte(5)))
.Subscribe();

// New subscription: player now at level 6, which overlaps with the previous query.
var newShopSubscription = conn
.SubscriptionBuilder()
.AddQuery(q => q.From.ExchangeRates())
.AddQuery(q => q.From.ShopItems().Where(r => r.RequiredLevel.Lte(6U)))
.AddQuery(q => q.From.ShopItems().Where(r => r.RequiredLevel.Lte(6)))
.Subscribe();

// Unsubscribe from the old subscription once the new one is in place.
Expand All @@ -376,14 +474,14 @@ let conn: DbConnection = connect_to_db();
let shop_subscription = conn
.subscription_builder()
.add_query(|q| q.from.exchange_rates())
.add_query(|q| q.from.shop_items().r#where(|r| r.required_level.lte(5u32)))
.add_query(|q| q.from.shop_items().r#where(|r| r.required_level.lte(5)))
.subscribe();

// New subscription: player now at level 6, which overlaps with the previous query.
let new_shop_subscription = conn
.subscription_builder()
.add_query(|q| q.from.exchange_rates())
.add_query(|q| q.from.shop_items().r#where(|r| r.required_level.lte(6u32)))
.add_query(|q| q.from.shop_items().r#where(|r| r.required_level.lte(6)))
.subscribe();

// Unsubscribe from the old subscription once the new one is active.
Expand All @@ -392,6 +490,56 @@ if shop_subscription.is_active() {
}
```

</TabItem>
<TabItem value="unrealcpp" label="Unreal C++">

```cpp
UDbConnection* Conn = ConnectToDB();

// Initial subscription: player at level 5.
USubscriptionHandle* ShopSubscription = Conn->SubscriptionBuilder()
->AddQuery([](const FQueryBuilder& Q)
{
return Q.From.ExchangeRates();
})
->AddQuery([](const FQueryBuilder& Q)
{
return Q.From.ShopItems().Where([](const FShopItemsCols& Row)
{
return Row.RequiredLevel.Lte(5);
});
})
->Subscribe();

// New subscription: player now at level 6, which overlaps with the previous query.
USubscriptionHandle* NewShopSubscription = Conn->SubscriptionBuilder()
->AddQuery([](const FQueryBuilder& Q)
{
return Q.From.ExchangeRates();
})
->AddQuery([](const FQueryBuilder& Q)
{
return Q.From.ShopItems().Where([](const FShopItemsCols& Row)
{
return Row.RequiredLevel.Lte(6);
});
})
->Subscribe();

// Unsubscribe from the old subscription once the new one is in place.
if (ShopSubscription->IsActive())
{
ShopSubscription->Unsubscribe();
}
```

</TabItem>
<TabItem value="unrealblueprint" label="Unreal Blueprint">

![Unreal Blueprint new overlapping subscription graph](/images/unreal/subscriptions/ue-blueprint-unsubscription-1.png)

![Unreal Blueprint unsubscribe old subscription graph](/images/unreal/subscriptions/ue-blueprint-unsubscription-2.png)

</TabItem>
</Tabs>

Expand Down
Loading
Loading