|
| 1 | +--- |
| 2 | +"@objectstack/driver-sql": minor |
| 3 | +"@objectstack/driver-sqlite-wasm": minor |
| 4 | +"@objectstack/driver-turso": minor |
| 5 | +"@objectstack/driver-memory": minor |
| 6 | +"@objectstack/driver-mongodb": minor |
| 7 | +"@objectstack/objectql": minor |
| 8 | +--- |
| 9 | + |
| 10 | +feat(drivers,objectql): `$regex` / `$options` are refused everywhere, and `$icontains` is implemented on the SQL family (#5702) |
| 11 | + |
| 12 | +The driver half of the #4706 ruling. #5701 landed the contract (the vocabulary, |
| 13 | +the `RETIRED_FILTER_OPERATORS` prescriptions, the shared text case-set) and |
| 14 | +#5710 flipped the last live producer — `plugin-auth`'s ObjectQL adapter, which |
| 15 | +emitted `$regex` on the authentication path — so the refusal can now land |
| 16 | +without breaking sign-in. |
| 17 | + |
| 18 | +**BREAKING for anyone writing `$regex` or `$options` in a filter.** Both are |
| 19 | +refused on every backend with `INVALID_FILTER` / 400 and a message that names |
| 20 | +the replacement. `$regex` was never a declared operator: `driver-sql` compiled |
| 21 | +it to a LIKE-escaped substring (so `a.b` matched only the literal `a.b`), |
| 22 | +`driver-memory` ran it as a real `RegExp` (so the same filter also matched |
| 23 | +`axb`, and an *invalid* pattern was caught and answered `false` — zero rows, in |
| 24 | +silence), and `objectql`'s `having` did the same. Write `$icontains` for the |
| 25 | +case-insensitive substring search this was almost always used for, `$contains` |
| 26 | +for a case-sensitive one; a pattern that genuinely needs a regex has no |
| 27 | +filter-level replacement. |
| 28 | + |
| 29 | +**`$icontains` now runs on the SQL family** — `driver-sql`, `driver-sqlite-wasm`, |
| 30 | +and both of `driver-turso`'s transports (the remote one does not go through |
| 31 | +knex, so it needed its own). It compiles to `LOWER(col) LIKE LOWER(?) ESCAPE ?` |
| 32 | +through the same `applyLike` / `pushLike` that carries the `%` / `_` / `\` |
| 33 | +escaping, as a `fold` parameter rather than a second emitter — a copied emitter |
| 34 | +is where the escape class would have been dropped, and an unescaped `%` matches |
| 35 | +every row. An empty or non-string comparand is refused on the validating walk |
| 36 | +(an empty one matches every row, which widens rather than narrows). On SQLite |
| 37 | +`lower()` folds ASCII only, which IS the contract (#4706 Q1 = A): `$icontains: |
| 38 | +'café'` does not match `CAFÉ`. |
| 39 | + |
| 40 | +<!-- adr-0087: registered filter-regex-options-retired --> |
| 41 | + |
| 42 | +`driver-mongodb`'s unknown-operator arm was throwing a bare `Error` with no |
| 43 | +`code` and no `status`, three lines from the helper in its own file that sets |
| 44 | +`INVALID_FILTER` / 400 — a 500-shaped body for a 400-class client mistake. It |
| 45 | +now speaks the same envelope as its three siblings. |
| 46 | + |
| 47 | +Two parts of the ruling are deliberately NOT in this change and stay tracked in |
| 48 | +`scripts/check-driver-conformance.mjs`'s ledger: the `$contains` family's |
| 49 | +case-sensitivity (#4706 Q2 = A) needs SQLite's `LIKE` replaced by a case-exact |
| 50 | +construct in the driver, the RLS lowering and the analytics lowering together, |
| 51 | +or one permission rule compiles to two row sets (#6518); and `$icontains` on the |
| 52 | +JS evaluation faces needs the spec vocabulary to take the operator, which cannot |
| 53 | +happen before `driver-memory` has an arm for it (#6520). |
0 commit comments