perf: flip eql_v2_encrypted infix operator implementations to inlinable SQL (#193)#196
perf: flip eql_v2_encrypted infix operator implementations to inlinable SQL (#193)#196
Conversation
|
Important Review skippedDraft detected. 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 |
c80c760 to
1ab0b00
Compare
|
Pushed The 11 operator overloads this PR makes inlinable — 3×
Verified the 11 functions retain I haven't touched the existing test failures ( Filed #198 to track the broader story of why downstream Supabase users will still see these flagged in their Security Advisor until EQL can ship as a real |
…le SQL (#193) Resolves cipherstash/stack#420 (encryptedSupabase silent seq-scan) by making the `=`, `<>`, `~~`, `~~*`, `@>`, and `<@` operator implementations on `eql_v2_encrypted` eligible for planner inlining. The Postgres planner inlines a custom operator's implementation function during index matching, provided the function is `LANGUAGE sql IMMUTABLE` with a single-statement body and no `SET` clause. Previously every operator wrapper was either `LANGUAGE plpgsql` (which can never be inlined) or had `SET search_path = pg_catalog, extensions, public` (which blocks inlining even on sql functions). As a result, bare queries like `WHERE col = val` from PostgREST and ORMs that don't wrap columns themselves silently fell back to seq scan on every install where the catch-all `eql_v2.encrypted_operator_class` btree wasn't available — most prominently Supabase. This change rewrites the wrappers as inlinable SQL, so the planner reduces them during planning and matches functional indexes built on the underlying extractors: WHERE col = val inlines to eql_v2.hmac_256(col) = eql_v2.hmac_256(val) └── matches functional hash idx WHERE col ~~ val inlines to eql_v2.bloom_filter(col) @> eql_v2.bloom_filter(val) └── matches functional GIN idx WHERE col @> val inlines to eql_v2.ste_vec_contains(col, val) (preserves) Verified empirically: bare `WHERE enc = ...` produces a Bitmap Index Scan on the hmac functional index, where it previously seq-scanned. The lint introduced in the parent commit (#194) goes from 12+ inlinability errors on these operators to zero. Comparison operators (<, <=, >, >=), JSONB extractors (->, ->>), and ORE block operators remain flagged — those are out of scope for Phase 1 and tracked separately in the predicate/extractor RFC. Behaviour change: `=` and `<>` previously dispatched through `eql_v2.compare`, which fell back to ORE / Blake3 / literal comparison when the column's HMAC variant wasn't present. The new implementations require the column to have `equality` configured (i.e. carry an `hm` field). Calling `=` on an ORE-only encrypted column now returns NULL instead of a Boolean — surfacing the config error rather than hiding it. This is intentional per the RFC (`docs/plans/uniform-predicate-extractor-pairs-rfc.md`).
1ab0b00 to
a5229a6
Compare
Summary
Resolves #193. Stacked on #195 (the inlinability lint).
Makes the
=,<>,~~,~~*,@>, and<@operator implementations oneql_v2_encryptedeligible for planner inlining. Once inlined, bare queries likeWHERE col = valfrom PostgREST and ORMs that don't wrap columns themselves engage the documented functional indexes (bench_text_hmac_idx,bench_text_bloom_idx,bench_jsonb_stevec_idx) instead of falling back to seq scan.This fixes cipherstash/stack#420 — encryptedSupabase silent seq-scan — at the EQL layer. No changes are needed in
encryptedSupabaseitself.What changed
src/operators/=.sql,<>.sql,~~.sql: wrapper functions rewritten fromLANGUAGE plpgsql(withSET search_path) toLANGUAGE sql IMMUTABLE STRICT PARALLEL SAFEwith single-statement bodies.src/operators/@>.sql,<@.sql: existingLANGUAGE SQLwrappers gain explicitIMMUTABLE STRICT PARALLEL SAFE(previously default-VOLATILE which blocks inlining).tests/sqlx/tests/lint_tests.rs: tightens the lint test added in #195 with a Phase 1 regression assertion that the targeted operators report zero violations.Mechanism
For each operator, the inlining produces:
Functional indexes built on the matching expression engage automatically.
Verification
Empirical confirmation against a fresh install of this branch:
Where the same query previously produced
Seq Scan. The planner inlined=through to the wrapped form, matched the functional hash index, picked Bitmap Index Scan.Lint output (validates the fix)
After this PR:
The 12+ violations on the operators rewritten by this PR (
=,<>,~~,~~*,@>,<@) drop to zero. Out-of-scope operators continue to flag, which is correct — they're tracked by Phase 2 of the predicate/extractor RFC and by cipherstash/stack#423.Behavioural change
Per the RFC and the issue,
=and<>previously dispatched througheql_v2.compare, which fell back to ORE / Blake3 / literal comparison when the column's HMAC variant wasn't present. The new implementations require the column to haveequalityconfigured (i.e. carry anhmfield). Calling=on an ORE-only encrypted column now returns NULL — surfacing the config error rather than hiding it.For customers configured correctly (which is the common case), this change is purely a perf improvement. For misconfigured columns, queries that previously returned silently-slow results now return NULL; encrypt-query-language CLAUDE.md should be updated to document the new contract.
What's NOT in this PR
<,<=,>,>=): need acmp_semextractor design from Phase 2 of the RFC. Tracked in the predicate/extractor RFC.->,->>): tracked in Drizzle jsonbPathQueryFirst / jsonbGet typed as predicates but return eql_v2_encrypted stack#423; needs the predicate vs extractor namespace split.eql_v2.ore_block_u64_8_256_*): internal to ORE machinery, not customer-facing; addressable in a follow-up.Downstream effect
@cipherstash/drizzle.like/ilikevolatility flip, subsumed.Test plan
mise run buildsucceeds.mise run test:sqlxpasses;lint_phase_1_operators_are_cleantest asserts zero violations on the targeted operators.EXPLAIN SELECT … WHERE col = …against a hmac-indexed encrypted column shows Bitmap Index Scan (not Seq Scan).(col eql_v2.encrypted_operator_class)btree indexes.