Skip to content

add nested AND/OR and LIKE/ILIKE - #13

Open
lukechinworth wants to merge 1 commit into
supabase:mainfrom
lukechinworth:feat/or-like-pushdown
Open

add nested AND/OR and LIKE/ILIKE#13
lukechinworth wants to merge 1 commit into
supabase:mainfrom
lukechinworth:feat/or-like-pushdown

Conversation

@lukechinworth

Copy link
Copy Markdown

NOTE: Claude Opus made this update.

  • I needed simple text matching with ilike. I also needed or to check for a match on several columns.
  • New tests added to cover changes and all tests passing.

`parseLoadSubsetOptions` routes a live query's `where` through
`extractSimpleComparisons`, which throws on `or`, `like` and `ilike`. When it
throws the collection fetches nothing at all, so a screen using any of them
empties rather than degrading to a client-side filter.

Replace it with a pure IR walker, `toPostgrestParams`, that renders a WHERE
expression as the PostgREST query parameters it pushes down to, and use that one
walk for **both** the request and the query key. That closes a second bug:
`subsetOptionsToQueryKey` returned `null` for the whole `where` as soon as any
operator was unsupported, collapsing the key to `[tableName]` — so two different
queries shared a cache entry and returned each other's rows. Now two queries
share an entry only when they issue the identical request.

Pushability rules — the correctness argument:

- `and` — a conjunct that cannot be pushed is dropped on its own. The result is
  a superset and the client re-filters it. Safe.
- `or` — all-or-nothing. Dropping one branch would *narrow* the result and lose
  matching rows, so an unpushable branch drops the whole `or`.
- `not` — pushable only when its inner expression is fully pushable, since under
  a negation dropping a conjunct narrows too.

Only the top level drops anything; everything nested is strict.

Values are quoted where PostgREST can be confused by them. `in.(…)` and `or=(…)`
are parsed on `,` `(` `)`, so a value carrying one — a search term typed by a
user — silently corrupts the filter around it; those forms parse double quotes,
with `\` escaping. A top-level `col=eq.value` is left bare: the value runs to the
end of the parameter so nothing can delimit it early, and PostgREST does *not*
strip quotes there (`col=eq."x"` matches the literal `"x"`).

Also in `query-once.ts`, whose translator only the aggregate / groupBy / having
path reaches:

- add `like` / `ilike` to `toFilterString`, `toNotFilterString`, `applyFilter`
  and `applyNotFilter`;
- fix `case "inArray"`, which was dead — `inArray()` builds a func named `in`, so
  an `IN` inside an aggregate query was silently not pushed;
- drop `case "neq"`, also dead: `@tanstack/db` has no `neq` operator, only
  `not(eq(…))`.

Tests: the four `test.todo` stubs for `OR` and nested `AND`/`OR` are enabled.
`tests/index.test.ts` asserted AND semantics for an OR
(`active=eq.true&id=eq.1`); corrected to `or=(active.eq.true,id.eq.1)`. Added
coverage for `like`, `ilike`, an `or` of `ilike`s, nested `and(eq, or(ilike,
ilike))`, values needing quoting, `IN` lists inside an `or`, `NOT(like)`, and
both partial-pushdown cases — an `and` dropping only its unpushable conjunct and
an `or` being dropped entirely.

The `range(offset, offset + 5)` bug and the two pagination/`findOne` todos that
depend on it are left alone as out of scope.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant