Skip to content

fix(search)!: reject a date range bound the codec cannot read, instead of dropping it - #751

Merged
ddeboer merged 1 commit into
mainfrom
worktree-issue-750-unparseable-date-bound
Aug 20, 2026
Merged

fix(search)!: reject a date range bound the codec cannot read, instead of dropping it#751
ddeboer merged 1 commit into
mainfrom
worktree-issue-750-unparseable-date-bound

Conversation

@ddeboer

@ddeboer ddeboer commented Aug 20, 2026

Copy link
Copy Markdown
Member

A date range bound isoToUnixSeconds cannot read was dropped, and a criterion left with no
usable bound compiled to VACUOUS – “states no constraint”, true for every document. So a
filtered search silently answered with everything, and under an or clause the vacuous
reading short-circuited the whole clause, deleting the valid criteria beside it. Nothing in the
response distinguished any of that from a deliberate match-all.

Rejected at the edge, not compiled

validateQuery gains an unparseable-bound issue, reported per rejected bound – the first rule
about a criterion’s value rather than its field, because no surface’s type system catches it
(String is String). assertValidQuery therefore throws for every caller: GraphQL, deployment
queryDefaults, in-process callers alike.

QueryIssue carries a new optional value naming the literal that was rejected, so a caller is
told which bound to fix and not just which field. assertValidQuery renders it:
where: “datePosted” (unparseable-bound: “yesterday”).

Not deep-time-specific: 'yesterday' behaved identically, and has since before the codec expanded
out-of-form years (#741, #747).

Why the compiler is left alone

The issue also proposed compiling an unreadable bound to UNUSABLE rather than VACUOUS. I tried
that and measured it, and it does not hold up: UNUSABLE reads as false within a clause (it
drops out of the ||, siblings stand) but the moment it empties a clause, compileFilterBy omits
that clause from the && – and an omitted conjunct is true. So status is valid AND date is garbage compiled to status:[valid], dropping the date constraint and returning more than
was asked for. It fixed the OR-sibling case and regressed the commoner AND cases.

The underlying reason is that filter_by has no term meaning “matches nothing”, so neither
reading can be right in both positions. The compiler keeps its existing behaviour and its JSDoc
now states this limit plainly, pointing at validation as the fix.

That leaves a real but separate defect: any clause left with no terms silently widens the
query, including one whose only criterion names a misspelled field. isUnsatisfiable covers only
the empty-id shape. Filed separately – it is not a date bug.

Also fixed

A null range bound (a GraphQL variable left unfilled, which the surface passes through) compiled
to datePosted:[null..…], which Typesense rejects – an unset filter failing the whole search. It
now reads as a bound not set.

Fix #750

…d of dropping it

A date bound isoToUnixSeconds could not parse was dropped, and a criterion
left with no usable bound compiled to “no constraint” – true for every
document. A filtered search silently answered with everything, and under an
or clause it took its valid siblings with it.

- validateQuery reports an unparseable-bound issue per rejected bound, so
  assertValidQuery rejects the query at the edge, for every surface and policy
- QueryIssue carries the offending literal in a new value field, naming which
  bound to fix rather than only which field
- the Typesense compiler reads a null bound as a bound not set: a GraphQL
  variable left unfilled reached the engine as datePosted:[null..…], which
  Typesense rejects outright

The compiler is deliberately left as it was otherwise. It cannot recover from
an unreadable bound either way: a criterion dropped from a clause narrows the
query, while a clause dropped from the conjunction widens it, and filter_by
has no term meaning “matches nothing”. Its JSDoc now says so.

BREAKING CHANGE: a query whose date range carries a bound the storage codec
cannot read (‘yesterday’, or a year past the ±271,821 window Date covers) now
throws from assertValidQuery instead of matching every document. QueryIssue
gains the unparseable-bound reason and an optional value.
@ddeboer
ddeboer force-pushed the worktree-issue-750-unparseable-date-bound branch from 276df47 to bf50487 Compare August 20, 2026 14:16
@ddeboer
ddeboer merged commit 83bf010 into main Aug 20, 2026
4 checks passed
@ddeboer
ddeboer deleted the worktree-issue-750-unparseable-date-bound branch August 20, 2026 14:28
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.

An unparseable date range bound compiles to no constraint, so the search returns everything

1 participant