Skip to content

server: a Get that selects nothing reads the row, and nothing else - #9

Merged
lesomnus merged 1 commit into
mainfrom
get-keys-from-the-row
Oct 17, 2026
Merged

lesomnus merged 1 commit into
mainfrom
get-keys-from-the-row

Conversation

@lesomnus

Copy link
Copy Markdown
Member

A Get with no select loaded every edge with a query of its own that read the neighbour's key and nothing else, and so did the snapshot a recorder takes its images from. For an edge that is not a list, Proto() already answers from the column the row keeps the key in when nothing was loaded, and that key is all such a Get answers for an edge. So each of those queries was spent on something the row already held.

The answer was also worse for it. The neighbour was read with its key alone, so every other field came back at its zero value, timestamps in the year 1, as though that were the row. image() has been trimming that back to the key; the reference built from the column is already just the key.

List edges are still loaded, because their keys are on the other table.

What it cost

Counted in Rove (lesomnus/rove#103). Its gate checks each reference with a Get through the layer below, so every write paid this once per edge of every row it referred to:

before after
statements per imported row 121.1 67.7
queries for a Get of an asset (four edges) 5 1

Rove's whole suite passes with this generator on SQLite, and server/... passes on PostgreSQL. Its regenerated diff only removes the q.With… lines and the imports they used.

Tests

  • TestGet/empty select answers an edge with its key and nothing else: before this change the reference came back as a tenant created in the year 1.
  • TestAGetThatSelectsNothingIsOneQuery: a Get of a seat, which has three edges, takes one query; before this change it took four.

Both fail on main without the change. CI's steps run clean locally: buf generate twice, go generate in internal/apptest, then the root, runtime and internal/apptest suites, the last on PostgreSQL as well.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk

A Get with no select loaded every edge with a query of its own, reading
the neighbour's key and nothing else, and so did the snapshot a recorder
takes its images from. `Proto()` already answers an edge that is not a
list from the column the row keeps it in, when nothing was loaded --
which is the whole of what such a Get answers for one. So each of those
queries was spent on an answer the row already held, and the answer was
worse for it: the neighbour read with its key alone, every other field
of it at its zero value and its timestamps in the year 1, as though that
were the row. `image()` has been trimming that back to the key ever
since; the reference the column gives is that already.

A list edge is still loaded: its keys are on the other table.

What it cost, counted in Rove (lesomnus/rove#103), whose gate checks a
reference by a Get through the layer below: an imported row sent 121
statements and sends 68, and a Get of a thing with four edges is one
query where it was five. Its tests pass on SQLite and PostgreSQL with
this generator.

- An empty select answers an edge with its key and nothing else; it
  answered a tenant created in the year 1.
- A Get of a seat, three edges, is one query; it was four.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
@lesomnus
lesomnus merged commit 84992e1 into main Oct 17, 2026
2 checks passed
@lesomnus
lesomnus deleted the get-keys-from-the-row branch October 17, 2026 12:25
lesomnus added a commit to lesomnus/roster that referenced this pull request Oct 17, 2026
#130)

protobuf-orm/protoc-gen-orm-ent#9: a Get with no select loaded every
edge with a query of its own, to answer a key the row already holds,
and answered a neighbour read with its key alone -- every other field
at its zero value, timestamps in the year 1. So does the snapshot a
recorder takes its images from. Both read the row and nothing else now:
only `server/bare/` moves, the `q.With…` lines and the imports they
used.

Every reference a write names is checked with such a Get by the gate,
so a write here pays one query less for each edge of each row it names.
In rove, whose import this was found in, a Get of an asset went from
five queries to one.


Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
lesomnus added a commit that referenced this pull request Oct 17, 2026
The pin was #9's head while it was open; it is master now, which has #9
and #8 beside it. Nothing it generates moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant