docs: say how to decode a one-off row, and why we do not do it for you #96

Merged
yuris merged 1 commit from docs-oneoff-decode into main 2026-08-21 11:50:36 +00:00
Owner

No behaviour change. Docstring + two pinned test cases.

I set out to make v2 auto-decode one-off query rows, on the reasoning that tables[i].table_name names a def so the row could be decoded for the caller, the way cached rows already are. That would have been a bug, and the measurement is the reason it is not in this PR.

A def is POSITIONAL, and a one-off projects an arbitrary relation. Decoding a projection against the table's def reads the wrong columns, and it does not have to fail:

query, decoded against the message def result
SELECT sender, text FROM message raises — the string's length prefix is read as the u64, widths stop lining up
SELECT id, text, sender FROM message decodes clean, sender and text reversed

The second is three columns of the same widths with two swapped, so the bytes are a valid message row and nothing can catch it. That is the silent-wrong-value class cache.lua:169 and protocol.lua:207 already exist to prevent, and it would have arrived through a convenience nobody would think to distrust.

So the existing behaviour is correct and stays. What was actually missing is that nothing said so: the docstring gave no recipe for decoding a row and no warning against the obvious wrong one. Both are now there, and both cases are pinned in test_protocol_v2.lua — the silent one by asserting the swap it produces, so inverting that assertion (what someone would "fix" it to if they believed the shortcut were safe) fails with the swap named.

Also corrects a framing error of mine: rows arriving encoded is symmetric, not a v2 wart. v1 hands back encoded JSON strings for the same reason.

No behaviour change. Docstring + two pinned test cases. I set out to make v2 auto-decode one-off query rows, on the reasoning that `tables[i].table_name` names a def so the row could be decoded for the caller, the way cached rows already are. **That would have been a bug**, and the measurement is the reason it is not in this PR. A def is POSITIONAL, and a one-off projects an arbitrary relation. Decoding a projection against the table's def reads the wrong columns, and it does not have to fail: | query, decoded against the `message` def | result | | --- | --- | | `SELECT sender, text FROM message` | raises — the string's length prefix is read as the u64, widths stop lining up | | `SELECT id, text, sender FROM message` | **decodes clean, `sender` and `text` reversed** | The second is three columns of the same widths with two swapped, so the bytes *are* a valid `message` row and nothing can catch it. That is the silent-wrong-value class `cache.lua:169` and `protocol.lua:207` already exist to prevent, and it would have arrived through a convenience nobody would think to distrust. So the existing behaviour is correct and stays. What was actually missing is that nothing said so: the docstring gave no recipe for decoding a row and no warning against the obvious wrong one. Both are now there, and both cases are pinned in `test_protocol_v2.lua` — the silent one by asserting the swap it produces, so inverting that assertion (what someone would "fix" it to if they believed the shortcut were safe) fails with the swap named. Also corrects a framing error of mine: rows arriving encoded is *symmetric*, not a v2 wart. v1 hands back encoded JSON strings for the same reason.
docs: say how to decode a one-off row, and why we do not do it for you
All checks were successful
ci / ci (pull_request) Successful in 1m31s
conformance / conformance (v1) (pull_request) Successful in 2m52s
conformance / conformance (v2) (pull_request) Successful in 2m39s
11b2a3a86b
`one_off_query` hands rows over encoded on both wires -- JSON strings on v1,
BSATN bytes on v2 -- and nothing said what to do with them or what not to do.
The docstring now carries the recipe.

It also carries the warning, which is the point. The obvious convenience is that
`tables[i].table_name` names a def, so a row could be decoded with it -- and
that is unsafe for a reason worth writing down: a def is POSITIONAL and a
one-off projects an arbitrary relation, so a projection decodes against the
wrong columns. It does not have to raise. `SELECT sender, text FROM message`
against the `message` def is caught, because the string's length prefix gets
read as the u64 and the widths stop lining up. But `SELECT id, text, sender`
has the same three widths with two swapped, so it decodes clean and returns
`sender` and `text` REVERSED, silently, which is the failure class cache.lua and
protocol.lua's accessors already exist to prevent.

Both cases are now pinned in test_protocol_v2.lua, the silent one by asserting
the swap it produces. Inverting that assertion -- what someone would "fix" it to
if they thought decoding by table name were safe -- fails with the swap named.

No behaviour change.
yuris merged commit 6cc6cd2d9f into main 2026-08-21 11:50:36 +00:00
yuris deleted branch docs-oneoff-decode 2026-08-21 11:50:36 +00:00
Sign in to join this conversation.
No description provided.