Skip to content

Feat/q decode cols - #23

Merged
vbanas merged 2 commits into
RayforceDB:masterfrom
vbanas:feat/q-decode-cols
Oct 20, 2026
Merged

vbanas merged 2 commits into
RayforceDB:masterfrom
vbanas:feat/q-decode-cols

Conversation

@vbanas

@vbanas vbanas commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator

Closes #22

Adds q_decode_cols(resp, resp_len, compressed, cols, ncols, err, errlen): as q_decode, but a table keeps
only the named columns, in the order named (as q's cols#t). The other columns are skipped by their
serialized length, with no allocation per string cell and no symbol interning.

  • Columns come back in requested order, not the message order sketched in the issue, so q_decode_cols(full, names) equals q_decode(names#t) whatever order the names are in.
  • Skipping reads only lengths, so a timespan column (which q_decode rejects) can be dropped.
  • Keyed table: the key comes back whole and the names pick value columns. A missing name returns a
    RAY_ERROR naming it; a NULL or repeated name returns NULL with err set.

Synthetic 100 000-row, 142-column window with 23 columns kept: ~43 ms, against ~182 ms for q_decode of the
whole message and ~37 ms for a message holding only those 23 columns.

A separate commit fixes q_build_table, which released a table that ray_table_add_col had already
released on failure.

Tests: q-free codec checks (requested order, keyed table, timespan skip, skipped names never interned, every
truncation, a symbol column ending at each offset in an 8-byte word) and a --cols-selftest leg against
real q (every type plus a timespan with p#/s#, a keyed table, compressed -18! output). make recheck
passes against core v2.11.5 (5d5f47c2) with q.

A caller that keeps a few columns of a wide table paid q_decode for all
of them: one STR allocation per string cell and one intern per symbol
cell, then freed what it did not want. q_decode_cols takes the names to
keep and returns those columns in that order, as q's cols#t does. The
other columns are skipped by their serialized length: symbol
terminators are counted eight bytes at a time, string cells are stepped
over inline, and nothing is allocated or interned. Skipping reads only
lengths, so a timespan column, which q_decode rejects, can be dropped.

For a keyed table the key comes back whole and the names pick among the
value columns. A name the table lacks is a RAY_ERROR naming it; a NULL
or repeated name returns NULL with err set. With no names, or for a
message that is not a table, it is q_decode. Kept columns go through
the same q_des_obj call q_decode makes.

On a synthetic 100 000-row, 142-column window kept to 23 columns it
takes ~43 ms, against ~182 ms for q_decode of the whole message and
~37 ms for a message holding only those 23 columns.

Tested without q on encoded and hand-built tables: every truncation of
them, a symbol column ending at each offset in a word, and skipped
names that stay uninterned. Tested against q on a table of every
supported type plus a timespan, with p#/s# attributes, a keyed table,
and -18! output.

Release 2.5.0.
q_build_table released the table again when ray_table_add_col failed,
but add_col consumes the table on every path, failure included: in
core v2.11.0 and later on all of them, and in earlier cores on every
failure q_build_table can reach. The second ray_release read the freed
block's refcount, which ray_free leaves at 1, and freed it again.

A table whose column is an atom reaches this from the wire. There the
double release is masked: add_col's ray_error reuses the freed block,
and ray_release ignores errors. add_col's out-of-memory paths create
their error before releasing the table, so there the block really was
freed twice.

The new codec check decodes such a table and expects the error. On the
current allocator it passes with or without the fix, because the
allocator hides the double release; it keeps the path covered.

@morsel-ray morsel-ray left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 0f0dfd2. No further requested changes from my scoped review of the bounded skip walk, requested-order/keyed projection, cleanup and legacy-decode compatibility. I also checked the consumed-table failure contract directly in core v2.11.5 /5d5f47c2; removing the extra release is correct.

Local checks against that core: instrumented adapter/driver codec, exchange and CLI selftests plus33 server assertions and malformed/fragmented-frame checks pass. Real q is absent here and its legs explicitly skipped. The existing core archive was uninstrumented; this is not a whole-core sanitizer claim.

I additionally ran36 fresh-process synthetic raw/compressed projection cases with type/order/every-cell assertions: multiple empty/control-byte symbols around word boundaries and at input end, key/value request interleaving, key-only selection, and actual two-byte compression backreferences. Skipped control-byte symbols remain uninterned. Adapter/harness ASan/UBSan runs passed. These are synthetic protocol fixtures, not real-q interoperability or independent confirmation of the latency figures.

Optional follow-up coverage: capture a compact positive compressed fixture in the q-free codec tests, since the new in-tree compressed-projection leg currently requires q. Not a blocker for this change.

@vbanas
vbanas merged commit c7582d2 into RayforceDB:master Oct 20, 2026
1 check passed
@vbanas
vbanas deleted the feat/q-decode-cols branch October 20, 2026 13:39
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.

rayforce-q ask — decode only the named columns of a table message

2 participants