clockworklabs / clockworklabs/SpacetimeDB

Ergonomic Partial Updates via AsChangeset Macro

Open
#5,577 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feature-request
Dominant language
Rust
Stars
25.2k
Forks
1.1k
Avg merge
2d 7h
Merged PRs (30d)
46

Description

I’d like to request a quality-of-life feature for partial row update requests, similar to Diesel’s #[derive(AsChangeset)] macro.

To be clear up front: I am not asking for column-level mutations at the storage layer. I know SpacetimeDB currently requires whole-row operations under the hood. Rather, this is a request for a DX wrapper to hide the boilerplate of requesting an update to specific fields while preserving the rest of the row.

Currently, modifying a single field requires a manual "read-modify-write" pattern in user-land, meaning we have to execute a find() query first just to preserve the unmodified data. A macro could generate a changeset struct where non-primary-key fields are wrapped in Option<T>. This would allow us to call an update_changeset method, and the host (or a generated wrapper) would handle fetching the old row, applying the Some values, and writing the full row back.

This eliminates the boilerplate of the initial read query, allows for clean ..Default::default() syntax when updating large tables, and potentially offers a minor performance perk by removing user-land boundary crossing.

// CURRENT: Requires a read to avoid overwriting `zone`
let prev = ctx.db.locality_tbl().actor_id().find(actor_id).unwrap();
ctx.db.locality_tbl().actor_id().update(Locality {
    actor_id,
    chunk_x: new_x,
    chunk_z: new_z,
    zone: prev.zone, 
});

// PROPOSED: Using a generated changeset struct
ctx.db.locality_tbl().actor_id().update_partial(LocalityChangeset {
    actor_id,
    chunk_x: Some(new_x),
    chunk_z: Some(new_z),
    ..Default::default() // `zone` defaults to None and is untouched
});

Table definition with macro

#[table(accessor = locality_tbl)]
#[derive(AsChangeset)]
pub struct Locality {
    #[primary_key]
    pub actor_id: ActorId,
    pub zone: ZoneName,
    pub chunk_x: i8,
    pub chunk_z: i8,
}

// ...which would automatically yield:
pub struct LocalityChangeset {
    pub actor_id: ActorId,
    pub zone: Option<ZoneName>,
    pub chunk_x: Option<i8>,
    pub chunk_z: Option<i8>,
}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue names no files, tests, or entry points. Start by locating the table/accessor macro and generated update path, then investigate how host-side read-modify-write behavior is handled. Done means an agreed changeset API and macro design, with coverage for partial updates and untouched fields.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design, databases
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.