fix(qwp): accept empty arrays in the egress result decoder#50
Conversation
QuestDB emits a non-null empty array (cardinality 0) on QWP egress inline with a 0-length dimension, distinct from a NULL array (carried in the null bitmap). parseArrayColumn rejected dl < 1, so reading an empty array back failed with "ARRAY dim N must be >= 1". Accept dl >= 0, keeping nDims >= 1 and a per-dimension cap so a 0 in one dimension cannot slip an arbitrary value past the element-count cap. Flip the zero-dim hardening case to assert the empty array decodes while a hostile oversized dim is still rejected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review: PR #50 (level 2)Verdict: Approve with one recommended changeThe PR fixes a real, confirmed bug: the decoder rejected the server's own empty-array egress frames ( There is one substantive issue, plus minor notes. Moderate1. The per-dimension cap (
|
The egress decoder bounded each array dimension by MAX_ARRAY_ELEMENTS, the total element-count cap. That value is 128 below DIM_MAX_LEN ((1 << 28) - 1), the server's per-dimension ceiling. For an empty array the 0-length dimension drives the element-count product to 0, so a sibling dimension is bounded only by the per-dimension check. The old cap therefore rejected empty arrays carrying a dimension above MAX_ARRAY_ELEMENTS but within DIM_MAX_LEN -- shapes the server can build and emit, since it checks each dimension against DIM_MAX_LEN at construction and applies no per-dimension cap when encoding egress. Add MAX_ARRAY_DIM_LEN, mirroring the server's DIM_MAX_LEN, and bound each dimension by it. The element-count product guard is unchanged, so the payload still fits the int-addressed buffer and the product stays within long range. A boundary test pins acceptance at DIM_MAX_LEN and rejection one element past it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[PR Coverage check]😍 pass : 2 / 2 (100.00%) file detail
|
bluestreak01
left a comment
There was a problem hiding this comment.
Approve. Verified at level 3 against the server source.
The fix is correct, memory-safe, and precisely mirrors both the server egress encoder (QwpResultBatchBuffer.appendArrayBytesDirect) and reader (QwpArrayColumnCursor): per-dimension [0, DIM_MAX_LEN] plus the unchanged order-dependent MAX_ARRAY_ELEMENTS product cap. The product guard mirrors the server exactly — shapes whose leading-dimension prefix product exceeds the cap (e.g. (30000, 30000, 0)) throw at the server encoder before any bytes hit the wire, so the client cannot under-accept a real egress frame. Downstream accessors (getArrayNDims, getDoubleArrayElements) handle the newly-accepted 0-element arrays correctly (non-null, double[0], no int overflow). Both affected test classes pass (23 + 33, 0 failures).
Non-blocking minor suggestions:
- Reference the existing public
ArrayView.DIM_MAX_LENinstead of re-deriving the literal as the privateMAX_ARRAY_DIM_LEN— the Javadoc already names that constant, so referencing it makes the mirror a compile-time fact and avoids drift. - Add a client-side unit assertion of the empty-array result (
getArrayNDims,getDoubleArrayElements().length == 0, not-null) inQwpColumnBatchViewsTest— currently only no-throw is asserted client-side; the result-level proof lives only in the parent-repo e2e test. - Add the repo's Bug / client-protocol labels.
What
QuestDB emits a non-null empty array (cardinality 0) on QWP egress inline with a 0-length dimension, distinct from a NULL array (carried in the null bitmap).
QwpResultBatchDecoder.parseArrayColumnrejecteddl < 1, so reading an empty array back failed withARRAY dim N must be >= 1.Changes
dl >= 0, keepingnDims >= 1and a per-dimension cap so a0in one dimension cannot slip an arbitrary value past the element-count cap. Matches the server'sQwpArrayColumnCursorand the spec.QwpResultBatchDecoderHardeningTestzero-dim case to assert the empty array decodes, while a hostile oversized dimension is still rejected (now via the per-dim cap).Verification
QwpResultBatchDecoderHardeningTest(22) andQwpResultBatchDecoderColumnTypesTest(33) pass. Proven end-to-end via the matching questdb-core e2e test: a real server's empty-array egress frame is now decoded instead of rejected.🤖 Generated with Claude Code