Cache: synthesize update events for primary-key tables #21
Labels
No labels
area:defold
area:docs
area:infra
area:protocol
area:testing
kind:bug
kind:chore
phase:breadth
phase:core
phase:hardening
phase:slice
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
yuris/spacetimedb-defold#21
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A field change on a primary-key table is delivered as a
deletefollowed by aninsert. Every official SDK surfaces it as a singleupdatecarrying both the old and new row.Where it stands
cache.apply_database_updatealready handles the state correctly — deletes are applied before inserts precisely so a pk change lands as an upsert (stdb/cache.lua:95-108):The cache ends up right. The event stream does not: the same loop pushes
{ type = "delete" }then{ type = "insert" }intocounts.events, so a consumer readingevt.counts.eventssees the row leave and a different row arrive.Why this matters more in a game than elsewhere
The natural Defold mapping is event → sprite.
deletedespawns a game object,insertspawns one. So a player moving one tile — a single column update — destroys and recreates their game object: the sprite flickers, any tweens or animation state on it are lost, and ago.delete/factory.createpair runs every tick of movement instead of a cheapgo.set_position.Working around it in user code means diffing the event list for delete/insert pairs sharing a pk, which is exactly the bookkeeping an SDK exists to absorb — and it has to be redone in every project.
Proposed change
In the per-table loop, when the table has a
pkand one batch contains both a delete and an insert for the same key, emit one event instead of two:Notes for whoever picks this up:
updbatch, matching the server's own framing. Do not try to correlate across tables or across separateTransactionUpdatemessages.pkkeep the current behaviour. With no key there is no defensible way to pair a delete with an insert, and guessing by field equality would be wrong for genuine same-shape churn. Official SDKs draw the line in the same place —on_updateexists only onTableWithPrimaryKey.counts.inserts/counts.deletesshould keep counting the underlying row operations; changing those numbers to count synthesized updates would break the conformance assertions that use them.Acceptance
A unit test in
dev/test/test_cache.luafeeding a delete+insert of one pk in a single batch and asserting exactly oneupdateevent with correctoldandrow. Plus a case proving a pk-less table still emits the two separate events, and a case proving an unpaired delete stays adelete.The README event table gains the
updaterow, and the docs note that pk-less tables never produce one.Priority
This is the highest-value feature gap for 1.0. It is small, self-contained in one already-tested pure module, and it removes a footgun that every consumer of this SDK would otherwise hit and have to solve themselves.