fix(v3): convert ore_block_256 opclass-path helpers to plpgsql (+43% ordered-scan regression) - #357
Merged
coderdan merged 2 commits intoJul 4, 2026
Conversation
jsonb_array_to_bytea_array and jsonb_array_to_ore_block_256 were LANGUAGE sql for inlineability, but their only caller chain — ore_block_256(val) (plpgsql) feeding the btree operator class — can never inline SQL functions. Every compared value instead paid the per-call SQL-function executor: 3.5x the per-call cost of the logic-identical plpgsql form, +43% end-to-end on ORE ordered index scans vs EQL 2.3 (0.513 -> 0.736 ms at 1M rows) and +36% on the composite bloom+ORE-order shape (16.6 -> 22.7 ms). Language-only change; semantics preserved and probed on an installed bundle: NULL / JSON null / non-array inputs still return NULL, and the empty-`ob` COALESCE (#262) still yields an empty (not NULL) terms array, sorting before every non-empty term. The eql-inline-critical markers are kept so pin_search_path leaves both functions unpinned — SET search_path on plpgsql forces per-call configuration switching in the same hot path (verified unpinned post-install). Validated A->B->A on a live 1M-row bench database before this patch was authored: with the plpgsql form, ORE/range_lt_ordered_10 returns to 0.553 ms (within 8% of v2) and COMBO/bloom_ore_order_limit to 13.97 ms (16% faster than v2), with non-ordering controls unmoved. Attribution data: cipherstash/benches#23, v3-regressions-report.md. plpgsql note for reviewers: `SELECT <composite> INTO <composite-var>` assigns field-wise — the row constructor is returned directly. Closes #353 Claude-Session: https://claude.ai/code/session_01StQnoycoFXMDdSpQ6zKDav
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The two failing CI tests encoded the old assumption that jsonb_array_to_bytea_array / jsonb_array_to_ore_block_256 must stay inlinable LANGUAGE sql — the premise the parent commit corrects (their only caller chain is plpgsql + opclass contexts, where SQL functions never inline; issue #353). - Remove both helpers from the inlinable-SQL sets of eql_v3_sem_inline_critical_functions_are_unpinned and eql_v3_sem_inline_critical_helpers_carry_marker. - Add a dedicated guard, eql_v3_ore_block_256_opclass_helpers_are_plpgsql_and_unpinned, asserting the corrected invariant: plpgsql (a revert to LANGUAGE sql reintroduces the +43% regression), IMMUTABLE, marker present, and unpinned (SET search_path on plpgsql adds per-call overhead in the same hot path). All three guard predicates verified by hand against a freshly built and installed bundle (0 offenders each). Claude-Session: https://claude.ai/code/session_01StQnoycoFXMDdSpQ6zKDav
coderdan
added a commit
that referenced
this pull request
Jul 4, 2026
The jsonb_entry / jsonb_query domain doc blocks are public API docs (Doxygen renders src/ with INTERNAL_DOCS at its default NO), and the issue-#354 performance rationale added there is an internal concern — wrap it in @internal/@endinternal so the published docs keep only the user-facing contract. The #357-style notes on the internal validator functions were already excluded via their blocks' @internal tags. Claude-Session: https://claude.ai/code/session_01StQnoycoFXMDdSpQ6zKDav
coderdan
deleted the
dan/cip-3380-v3-opclass-path-helper-functions-are-language-sql-cannot
branch
July 4, 2026 04:21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #353 (Linear: CIP-3380).
What
Language-only conversion of
eql_v3_internal.jsonb_array_to_bytea_arrayandeql_v3_internal.jsonb_array_to_ore_block_256fromLANGUAGE sqlto plpgsql, with the doc comments andeql-inline-criticalmarkers updated to the corrected rationale. No logic changes.Why
The
LANGUAGE sqlchoice was made for inlineability — but these helpers' only caller chain isore_block_256(val)(plpgsql) feeding the btree operator class, and neither plpgsql callers nor opclass support contexts can ever inline a SQL function. Every compared value paid the per-call SQL-function executor instead: 3.5× the per-call cost of the logic-identical plpgsql form (track_functionsattribution), and end-to-end in the v3 release benchmarks:The "with fix" numbers are from applying this exact change to a live 1M-row bench database (A→B→A, restored after measurement); non-ordering control scenarios moved ±2.6%. Notably the plpgsql comparator is NOT the problem — its per-query cost is identical to v2's and the 1M index build times match (42 s v2 / 44 s v3). Full attribution data: cipherstash/benches#23,
v3-regressions-report.md.Semantics preserved (probed on an installed bundle)
obarray → composite with an empty (not NULL)termsarray, sorting before every non-empty term — the Empty-string ("") handling for ordered encrypted text is undefined / inconsistent between eql_v2 and eql_v3 #262 COALESCE is retainedSET search_pathclause on plpgsql would force per-call configuration switching in the same hot path)SELECT <composite> INTO <composite-var>assigns field-wise, so the row constructor is returned directlyFollow-ups (separate issues)
#354 (
jsonb_entryCHECK function — same non-inlinable-context pattern, third instance), and the structural option of a C-level decode+compare; per-query attribution splits ~0.19 ms comparator / ~0.26 ms term re-decoding, so caching the decoded representation is the next win beyond this fix.Verification suggestion
After merge, cipherstash/benches#23's suite re-runs against a rebuilt bundle (
mise run setup-db-v3after uninstall) —report:v3-compareshould show ORE/range_lt_ordered_10 and COMBO/bloom_ore_order_limit drop out of the regression list.https://claude.ai/code/session_01StQnoycoFXMDdSpQ6zKDav