Skip to content

feat!: a blank filter is the empty filter, not an error - #8

Merged
iamralch merged 1 commit into
mainfrom
feat/blank-filter
Sep 24, 2026
Merged

iamralch merged 1 commit into
mainfrom
feat/blank-filter

Conversation

@iamralch

Copy link
Copy Markdown
Contributor

FilterClause::parse read a blank string as FilterClauseError::Blank, so every caller special-cased it before parsing. OrderByClause::parse, by contrast, reads a blank ordering as the empty clause and needs no such care. AIP-160 reads a blank filter as no filter as well.

Now both read the same way:

let filter = FilterClause::parse(request.filter)          // "" -> the empty filter
    .and_then(|filter| filter.resolve(&COLUMNS))
    .map_err(...)?;
let order_by = OrderByClause::parse(request.order_by)     // "" -> the empty clause
    .and_then(|order_by| order_by.resolve(&COLUMNS))
    .map_err(...)?;
  • sqlx-query-cel: a blank filter parses to the empty FilterClause (now Default). It resolves against any mapping and renders as an empty WhereClause. FilterClauseError::Blank is removed.
  • sqlx-query: WhereClause gains Default and is_empty(), and and/or return the other side when one is empty. Before this they rendered () AND (...). The composer leaves the slot empty for an empty clause, and a query with no where slot accepts one.

Breaking: FilterClauseError::Blank is gone, and FilterClause::parse returns Ok for a blank string.

Tests: and/or with an empty side; an empty filter dropping the slot, needing no slot, and leaving a cursor's comparison alone; a blank filter parsing, resolving and rendering empty; and a live SQLite query where a blank filter returns every row. The README's Filtering section is updated.

`FilterClause::parse` read a blank string as `FilterClauseError::Blank`,
so every caller special-cased it before parsing -- while
`OrderByClause::parse` reads a blank ordering as the empty clause and
needs no such care. AIP-160 reads a blank `filter` as no filter too.

A blank filter now parses to the empty `FilterClause`, which resolves
against any mapping and renders as an empty `WhereClause`. The empty
clause is a valid value throughout: `WhereClause` gains `Default` and
`is_empty()`, `and`/`or` return the other side when one is empty -- they
rendered `() AND (...)` before -- and the composer leaves the slot empty,
as for an empty ordering.

BREAKING CHANGE: `FilterClauseError::Blank` is gone; `FilterClause::parse`
returns `Ok` for a blank string.
@iamralch
iamralch merged commit 7dcfa8e into main Sep 24, 2026
4 checks passed
@iamralch
iamralch deleted the feat/blank-filter branch September 24, 2026 09:15
@github-actions github-actions Bot mentioned this pull request Sep 24, 2026
iamralch added a commit that referenced this pull request Sep 24, 2026
A first page had no cursor and a last page returned `None`, so every
caller special-cased the empty `page_token` on the way in and mapped
`None` to it on the way out -- the same branch the blank filter needed
before #8. AIP-158 reads the empty token as the first page in a request
and as "no more pages" in a response.

`Cursor` gains a default, the empty cursor -- no keys, no position -- and
`is_empty()`. `Cursor::parse("")` returns it and `encode()` of it returns
"", so a token and a cursor are each other's image, empty one included.
`with_cursor` takes the empty cursor as no cursor, which also keeps it
clear of the order_by mismatch check a first page would otherwise fail.
`Page::cursor` is a plain `Cursor`, empty on the last page.

BREAKING CHANGE: `Page::cursor` is `Cursor`, not `Option<Cursor>`: test
`is_empty()` for the last page. `Cursor::parse("")` returns `Ok` with the
empty cursor rather than a token error.

Co-authored-by: Svetlin Ralchev <iamralch@users.noreply.github.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