Skip to content

Ergonomic Partial Updates via AsChangeset Macro #5577

Description

@website-features

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>,
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions