perf(stack/drizzle): wrap like/ilike in eql_v2.bloom_filter(...) @> ...#430
perf(stack/drizzle): wrap like/ilike in eql_v2.bloom_filter(...) @> ...#430coderdan wants to merge 1 commit intodan/fix-bare-equality-supabasefrom
Conversation
`eql_v2.like` and `eql_v2.ilike` are SQL functions whose bodies are already a single inlinable `SELECT eql_v2.bloom_filter(a) @> eql_v2.bloom_filter(b)` — but they're marked `VOLATILE`, so the planner won't inline them into an index match, and the documented `bench_text_bloom_idx` GIN functional index never engages. Customers on Supabase silently seq-scan every encrypted free-text search. Emit the inlined containment form directly so the GIN index engages on every install. Same shape as the hmac_256 wrap for eq/ne/inArray (#421). Verified via the bench: like and ilike both report `Bitmap Heap Scan` (over `bench_text_bloom_idx`) post-fix instead of `Seq Scan`. Note that `eql_v2.like` and `eql_v2.ilike` resolve to the same post-encryption SQL — case sensitivity is determined by the column's `freeTextSearch` token filters (e.g. `downcase`), not by which operator the user picked. The wrapped form preserves that behavior. The proper EQL-side fix (mark these functions IMMUTABLE so the planner can inline them and bypass the Drizzle workaround entirely) should be filed against cipherstash/encrypt-query-language as a follow-up.
🦋 Changeset detectedLatest commit: 3a17789 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
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 |
Summary
Stacked on #425.
Addresses the
like/ilikeslice of #422.eql_v2.likeandeql_v2.ilikeare SQL functions whose bodies are already a single inlinableSELECT eql_v2.bloom_filter(a) @> eql_v2.bloom_filter(b)— but they're markedVOLATILE, which blocks the planner from inlining them into an index match. The documentedbench_text_bloom_idxGIN functional index never engages on Supabase, so every encrypted free-text search silently seq-scans.Drizzle now emits the inlined containment form directly, bypassing the function indirection. Same shape as #425's
hmac_256wrap for eq/ne/inArray.What changed
packages/stack/src/drizzle/operators.ts(around line 854): emitted SQL changes fromto
likeandilikeresolve to the same post-encryption SQL — case sensitivity is determined by the column'sfreeTextSearchtoken filters (e.g.downcase), not by which operator the user picked. The wrapped form preserves that.notIlikebecomesNOT (bloom_filter(col) @> bloom_filter(value))(sameNOTwrap as before).Plan-shape verification
Same 10k-row fixture as #425. Note: the search pattern needs to be selective — the bench was using
%value-00000%which is shared by every seeded row, so the planner correctly picked seq scan even with the index available. Updated to%value-0000042%(matches one row).likebench_text_bloom_idx)ilikebench_text_bloom_idx)Proper fix (EQL-side, follow-up)
The durable cleanup is to mark
eql_v2.likeandeql_v2.ilikeasIMMUTABLEin EQL itself — they're already single-statementLANGUAGE sqlfunctions, so they'd inline cleanly and the planner would match the index without any Drizzle change. That helps every consumer of EQL (raw SQL, other ORMs, etc.), not just Drizzle. Will file an issue againstcipherstash/encrypt-query-languagedescribing the volatility fix.This Drizzle-side wrap is the same kind of workaround #425 introduced for hmac_256 — it gets users the speedup immediately while the EQL fix lands.
What's NOT in this PR
Issue #422 also covers
gt/gte/lt/lte/between/order_by/jsonb_path_*. None of those have a stack-side wrap available today:gt/gte/lt/lte/between/order_by: no Supabase functional index path exists in EQL today. Blocked on the OPE work in flight incipherstash/encrypt-query-language.jsonb_path_query_first/jsonb_path_exists/jsonbGet: function bodies don't reduce to a containment shape oneql_v2.ste_vec(col). Needs an EQL-side investigation.jsonbPathQueryFirst/jsonbGettyping: tracked separately in Drizzle jsonbPathQueryFirst / jsonbGet typed as predicates but return eql_v2_encrypted #423.The bench's
#422: remaining call-shaped operatorssuite continues to record their plan shapes (still seq scan) without asserting; tests won't block CI.Test plan
packages/bench:pnpm db:setup && pnpm test:local. Expect 10/10 green on__tests__/drizzle/operators.explain.test.ts.packages/bench/results/explain-shape.json—likeandilikeshould reportBitmap Heap Scan.@cipherstash/stackunit tests still pass:pnpm -F @cipherstash/stack test.