Commit 60fd960
committed
feat(stack)!: replace like/ilike with contains on the v3 supabase surface
EQL v3 free-text search is token containment over a bloom filter, not SQL
wildcard matching: `%` is tokenized like any other character, so a `like`
pattern is a category error. The v3 Drizzle integration already omits
`like`/`ilike` in favour of `contains`; the supabase adapter now matches.
This is a surface rename, not a re-encoding. The bundle declares
CREATE OPERATOR @> (FUNCTION = eql_v3.contains,
LEFTARG = public.text_search, RIGHTARG = jsonb)
so PostgREST's `cs` already resolved through `@>` into `eql_v3.contains` →
`match_term(a) @> match_term(b)` → smallint[] bloom containment. It was never
the built-in `jsonb @> jsonb`. The emitted operator and the full-envelope
operand are unchanged; no ciphertext moves. The live PostgREST suite proves the
resolution: a 3-character needle matches while a 7-character one does not, and
neither shares the stored `c`.
`contains` is narrowed at compile time to freeTextSearch-capable domains
(text_match, text_search). The shared builder interface is now parameterised by
its own return type, so `like` is absent at every chain depth rather than only
on the first call; the runtime still throws for untyped JS callers, and passes
`like` through on a genuine plaintext column.
Three silent-failure bugs fixed alongside.
order() on ANY encrypted v3 column now throws, including the ORE-capable ones —
the non-obvious half. The `*_ord` domains are `DOMAIN … AS jsonb` and the bundle
declares no btree operator class on any domain (it lints against one), so
`ORDER BY col` resolved through jsonb's default `jsonb_cmp` and sorted by the
envelope's byte structure, first key `bf`. Stable, plausible-looking, and
meaningless, with no error. Confirmed against the live database: integer_ord has
zero operator classes and a jsonb base type. Correct ordering is
`ORDER BY eql_v3.ord_term(col)`, which PostgREST's `order=` cannot express. The
old guard rejected exactly the columns where the wrongness was obvious and
admitted the ones where it was silent. gte/lte remain correct: the comparison
operators ARE declared on the ord domains; only sorting resolves through the
missing operator class.
or() now understands PostgREST's `column.not.<op>.<value>` negation. The parser
split on the first two dots, read `not` as the operator, and so
`or('nickname.not.in.(ada,grace)')` on an encrypted column encrypted the literal
string `in.(ada,grace)` as one plaintext — a filter that silently matched
nothing. A malformed `col.not.<value>` is passed through for PostgREST to
reject rather than dropped, which would quietly widen the result set.
Raw filter() derives its query type from the operator on v3 instead of always
encrypting an equality term, so `.filter('bio','cs',…)` on a text_match column
(equality: false, freeTextSearch: true) answers the query it was given rather
than being rejected. Unknown operators throw. The v2 base still returns
'equality' verbatim: there `queryType` selects the encryptQuery narrowing, so
correcting it moves v2 ciphertext. Tracked separately.
Every new test was mutation-checked against the implementation it protects.
The include_original substring defect is untouched and still pinned by a test
asserting the broken behaviour: the operand is a storage envelope whose bloom
carries the whole needle as an extra token, so a substring only matches when it
equals the stored value or is exactly the tokenizer's 3-character window. Shared
with v3 Drizzle's contains and tracked upstream in EQL.
BREAKING CHANGE: `like`/`ilike` are removed from the EQL v3 supabase builder —
use `contains`. `order()` on an encrypted v3 column now throws; order by a
plaintext column, expose `eql_v3.ord_term()` as a generated column or view, or
use the EQL v3 Drizzle integration. v2 (`encryptedSupabase`) is unchanged.1 parent cbf2da4 commit 60fd960
12 files changed
Lines changed: 913 additions & 169 deletions
File tree
- .changeset
- packages/stack
- __tests__
- helpers
- src/supabase
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
| 23 | + | |
27 | 24 | | |
28 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
155 | 155 | | |
156 | 156 | | |
157 | 157 | | |
| 158 | + | |
158 | 159 | | |
159 | 160 | | |
160 | 161 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| 65 | + | |
65 | 66 | | |
66 | 67 | | |
67 | 68 | | |
| |||
82 | 83 | | |
83 | 84 | | |
84 | 85 | | |
85 | | - | |
86 | | - | |
| 86 | + | |
| 87 | + | |
87 | 88 | | |
88 | 89 | | |
89 | 90 | | |
| |||
110 | 111 | | |
111 | 112 | | |
112 | 113 | | |
113 | | - | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
114 | 117 | | |
115 | 118 | | |
116 | 119 | | |
| |||
119 | 122 | | |
120 | 123 | | |
121 | 124 | | |
122 | | - | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
123 | 128 | | |
124 | 129 | | |
125 | 130 | | |
| |||
138 | 143 | | |
139 | 144 | | |
140 | 145 | | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
0 commit comments