mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-07-13 13:41:57 -04:00
Update docs for view primary keys (#5327)
# Description of Changes All of rust, C#, and typescript are included. # API and ABI breaking changes N/A # Expected complexity level and risk 1 # Testing N/A - docs only change
This commit is contained in:
@@ -609,6 +609,12 @@ export const top_players = spacetimedb.view({ name: 'top_players', public: true
|
||||
return ctx.db.player.score.filter(1000);
|
||||
});
|
||||
|
||||
// Procedural view with update callbacks.
|
||||
// The returned row type has exactly one `.primaryKey()` column.
|
||||
export const top_players_with_updates = spacetimedb.view({ name: 'top_players_with_updates', public: true }, t.array(player.rowType), ctx => {
|
||||
return ctx.db.player.score.filter(1000);
|
||||
});
|
||||
|
||||
// Perform a generic filter using the query builder.
|
||||
// Equivalent to `SELECT * FROM player WHERE score < 1000`.
|
||||
export const bottom_players = spacetimedb.view({ name: 'bottom_players', public: true }, t.array(player.rowType), ctx => {
|
||||
@@ -643,6 +649,13 @@ public static IEnumerable<Player> TopPlayers(ViewContext ctx)
|
||||
return ctx.Db.Player.Score.Filter(1000);
|
||||
}
|
||||
|
||||
// Procedural view with update callbacks.
|
||||
[SpacetimeDB.View(Accessor = "TopPlayersWithUpdates", Public = true, PrimaryKey = "Id")]
|
||||
public static IEnumerable<Player> TopPlayersWithUpdates(ViewContext ctx)
|
||||
{
|
||||
return ctx.Db.Player.Score.Filter(1000);
|
||||
}
|
||||
|
||||
// Perform a generic filter using the query builder.
|
||||
// Equivalent to `SELECT * FROM player WHERE score < 1000`.
|
||||
[SpacetimeDB.View(Accessor = "BottomPlayers", Public = true)]
|
||||
@@ -683,6 +696,12 @@ fn top_players(ctx: &ViewContext) -> Vec<Player> {
|
||||
ctx.db.player().score().filter(1000).collect()
|
||||
}
|
||||
|
||||
// Procedural view with update callbacks.
|
||||
#[view(accessor = top_players_with_updates, public, primary_key = id)]
|
||||
fn top_players_with_updates(ctx: &ViewContext) -> Vec<Player> {
|
||||
ctx.db.player().score().filter(1000).collect()
|
||||
}
|
||||
|
||||
// Perform a generic filter using the query builder.
|
||||
// Equivalent to `SELECT * FROM player WHERE score < 1000`.
|
||||
#[view(accessor = bottom_players, public)]
|
||||
|
||||
@@ -1429,13 +1429,73 @@ SPACETIMEDB_VIEW(Query<PlayerLevel>, levels_for_high_scorers, Public, AnonymousV
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Primary Key Inference for Query Builder Views
|
||||
### Primary Keys for Views
|
||||
|
||||
Query builder views may carry primary-key semantics from their underlying row type.
|
||||
When a view's row type maps to a table with a primary key,
|
||||
Views can have primary-key semantics when SpacetimeDB knows which column identifies each returned row.
|
||||
Generated client bindings treat these views like primary-key tables for client-cache updates.
|
||||
In particular, update callbacks (`on_update` / `OnUpdate` / `onUpdate`) are generated for these view handles.
|
||||
Views without a known primary key fall back to insert/delete-only behavior.
|
||||
|
||||
For procedural views, primary keys are declared in the view definition for Rust and C#.
|
||||
In TypeScript, you mark one of the columns in the returned row type with `.primaryKey()`.
|
||||
View primary keys refer to source/accessor names, not case-converted or canonical database column names.
|
||||
The examples below use a `Player` row with a primary-key `id` / `Id` column and an indexed `owner` / `Owner` column.
|
||||
|
||||
<Tabs groupId="server-language" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
```typescript
|
||||
import { schema, table, t } from 'spacetimedb/server';
|
||||
|
||||
const players = table(
|
||||
{ name: 'players', public: true },
|
||||
{
|
||||
id: t.u64().primaryKey().autoInc(),
|
||||
owner: t.identity().index('btree'),
|
||||
name: t.string(),
|
||||
}
|
||||
);
|
||||
|
||||
const spacetimedb = schema({ players });
|
||||
export default spacetimedb;
|
||||
|
||||
export const my_players = spacetimedb.view(
|
||||
{ name: 'my_players', public: true },
|
||||
t.array(players.rowType),
|
||||
(ctx) => Array.from(ctx.db.players.owner.filter(ctx.sender))
|
||||
);
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="csharp" label="C#">
|
||||
|
||||
```csharp
|
||||
[SpacetimeDB.View(Accessor = "MyPlayers", Public = true, PrimaryKey = "Id")]
|
||||
public static IEnumerable<Player> MyPlayers(ViewContext ctx)
|
||||
{
|
||||
return ctx.Db.Player.Owner.Filter(ctx.Sender);
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
|
||||
```rust
|
||||
#[view(accessor = my_players, public, primary_key = id)]
|
||||
fn my_players(ctx: &ViewContext) -> Vec<Player> {
|
||||
ctx.db.player().owner().filter(ctx.sender()).collect()
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Query builder views may also carry primary-key semantics from their underlying row type.
|
||||
When a query builder view's row type maps to a table with a primary key,
|
||||
generated client bindings can treat the view like a primary-key table for client-cache updates.
|
||||
In particular, update callbacks (`on_update` / `OnUpdate` / `onUpdate`) will be generated for these view handles.
|
||||
If a primary key cannot be inferred for the view row type, clients fall back to insert/delete-only behavior for that view handle.
|
||||
|
||||
A view result must not contain two rows with the same primary key.
|
||||
If a view returns duplicate primary key values during a view refresh, SpacetimeDB rejects that view result and fails the transaction that triggered the refresh.
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
@@ -346,6 +346,7 @@ For each [view](../00200-functions/00500-views.md) in your module, codegen gener
|
||||
- **Type definitions** for the view's return type
|
||||
- **Subscription interfaces** for subscribing to view results
|
||||
- **Query methods** for accessing cached view results
|
||||
- **Update callbacks** when the view has a known primary key
|
||||
|
||||
Views provide subscribable, computed queries over your data.
|
||||
|
||||
|
||||
@@ -1057,7 +1057,8 @@ The `on_delete` callback runs whenever a previously-resident row is deleted from
|
||||
spacetimedb_sdk::TableWithPrimaryKey
|
||||
```
|
||||
|
||||
Implemented for handles whose rows have a known primary key, including query builder views with inferred primary keys.
|
||||
Implemented for handles whose rows have a known primary key.
|
||||
This includes table handles for tables with primary keys, query builder view handles with inferred primary keys, and procedural view handles with declared primary keys.
|
||||
|
||||
| Name | Description |
|
||||
| ------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
@@ -1077,7 +1078,7 @@ trait spacetimedb_sdk::TableWithPrimaryKey {
|
||||
|
||||
The `on_update` callback runs whenever an already-resident row in the client cache is updated, i.e. replaced with a new row that has the same primary key. Registering an `on_update` callback returns a callback id, which can later be passed to `remove_on_update` to cancel the callback. Newly registered or canceled callbacks do not take effect until the following event.
|
||||
|
||||
This also applies to query builder views over tables with primary keys.
|
||||
This also applies to views with known primary keys, including query builder views with inferred keys and procedural views that declare a primary key.
|
||||
|
||||
### Unique constraint index access
|
||||
|
||||
|
||||
@@ -1027,9 +1027,7 @@ class RemoteTableHandle
|
||||
}
|
||||
```
|
||||
|
||||
The `OnUpdate` callback runs whenever an already-resident row in the client cache is updated, i.e. replaced with a new row that has the same primary key. The table must have a primary key for callbacks to be triggered. Newly registered or canceled callbacks do not take effect until the following event.
|
||||
|
||||
This also applies to query builder views over tables with primary keys.
|
||||
The `OnUpdate` callback runs whenever an already-resident row in the client cache is updated, i.e. replaced with a new row that has the same primary key. The handle must have a known primary key for callbacks to be triggered. This includes tables with primary keys, query builder views with inferred primary keys, and procedural views declared with `PrimaryKey`. Newly registered or canceled callbacks do not take effect until the following event.
|
||||
|
||||
See [the quickstart](../../00100-intro/00200-quickstarts/00600-c-sharp.md) for examples of registering and unregistering row callbacks.
|
||||
|
||||
|
||||
@@ -905,9 +905,9 @@ The `reducers` field of the context provides access to reducers exposed by the r
|
||||
|
||||
## Access the client cache
|
||||
|
||||
All [`DbContext`](#interface-dbcontext) implementors, including [`DbConnection`](#type-dbconnection) and [`EventContext`](#type-eventcontext), have fields `.db`, which in turn has methods for accessing tables in the client cache.
|
||||
All [`DbContext`](#interface-dbcontext) implementors, including [`DbConnection`](#type-dbconnection) and [`EventContext`](#type-eventcontext), have fields `.db`, which in turn has methods for accessing tables and views in the client cache.
|
||||
|
||||
Each table defined by a module has an accessor method, whose name is the table name converted to `camelCase`, on this `.db` field. The table accessor methods return table handles. Table handles have methods for [accessing rows](#accessing-rows) and [registering `onInsert`](#callback-oninsert) and [`onDelete` callbacks](#callback-ondelete). Handles for tables which have a declared primary key field also expose [`onUpdate` callbacks](#callback-onupdate). Table handles also offer the ability to find subscribed rows by unique index.
|
||||
Each table or view defined by a module has an accessor method, whose name is the table or view name converted to `camelCase`, on this `.db` field. The accessor methods return table handles. Table handles have methods for [accessing rows](#accessing-rows) and [registering `onInsert`](#callback-oninsert) and [`onDelete` callbacks](#callback-ondelete). Handles with a known primary key also expose [`onUpdate` callbacks](#callback-onupdate). Table handles also offer the ability to find subscribed rows by unique index.
|
||||
|
||||
| Name | Description |
|
||||
| ------------------------------------------------------ | -------------------------------------------------------------------------------- |
|
||||
@@ -990,7 +990,7 @@ class TableHandle {
|
||||
|
||||
The `onUpdate` callback runs whenever an already-resident row in the client cache is updated, i.e. replaced with a new row that has the same primary key.
|
||||
|
||||
Only handles with a declared or inferred primary key expose `onUpdate` callbacks. Handles for tables or views without a known primary key will not have `onUpdate` or `removeOnUpdate` methods. Only views over tables with primary keys will expose `onUpdate` callbacks.
|
||||
Only handles with a declared primary key expose `onUpdate` callbacks. Handles for tables or views without a primary key will not have `onUpdate` or `removeOnUpdate` methods. TypeScript view handles expose `onUpdate` callbacks when the returned row type has exactly one column marked with `.primaryKey()`.
|
||||
|
||||
The `Row` type will be an autogenerated type which matches the row type defined by the module.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user