drizzle-team / drizzle-team/drizzle-orm

[BUG]: rc.5 RQB v2 throws "Unexpected 'undefined' in filter value" on undefined where-filter fields that rc.4 skipped (types still allow undefined)

Open
#6,180 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
35.8k
Forks
1.6k
Avg merge
2d 7h
Merged PRs (30d)
4

Description

### What version of `drizzle-orm` are you using?

1.0.0-rc.5-169397b (the `rc5` dist-tag snapshot; rc.4 is fine)

### What version of `drizzle-kit` are you using?

1.0.0-rc.5-ab785fc (not involved)

### Other packages

postgres 3.4.x (postgres-js driver), RQB v2 (`defineRelations`)

### Describe the Bug

Between `1.0.0-rc.4` and the `1.0.0-rc.5-*` snapshots, RQB v2 started **throwing at runtime when a `where` filter object contains a field whose value is `undefined`**:

```
Error: Unexpected 'undefined' in filter value. Use 'EmptyFilter' if you want the filter field to be skipped.
at relationsFilterToSQL (...)
at PgDialect.buildRelationalQuery (...)
```

Minimal repro:

```ts
const db = drizzle({ client, relations });

// rc.4: `name` is skipped, filters on the remaining fields.
// rc.5-169397b: throws "Unexpected 'undefined' in filter value."
await db.query.organizations.findMany({
where: { name: undefined },
limit: 1,
});
```

Two problems with this:

1. **The types still accept it.** The filter field types are optional (`name?: ...`), so `{ name: cond ? value : undefined }` type-checks cleanly and then explodes at runtime. If skipping-on-undefined is being removed deliberately, the types should reject `undefined` (`exactOptionalPropertyTypes`-style) so the break is caught at compile time — and `EmptyFilter` doesn't appear to be an exported symbol to migrate to.

2. **It breaks the established optional-filter idiom silently.** `where: { siteId, translationGroupId: maybeFilter ?? undefined }` was the natural way to express optional filters under rc.4 (and matches how most JS query builders treat `undefined`). Every such call site now becomes a production 500 on upgrade, with no changelog entry (the rc5 snapshots have no release notes yet).

We hit this via Dependabot bumping to the `rc5` snapshot: every list endpoint with an optional filter started returning 500s in e2e, e.g.

```ts
const translationGroupId =
opts.translationGroupIsNull === true ? { isNull: true as const } :
opts.translationGroupIsNull === false ? { isNotNull: true as const } :
undefined; // ← rc.4: skipped; rc.5: throws

await db.query.pages.findMany({
where: { organizationId, siteId, translationGroupId },
...
});
```

If the new strictness is intentional, this issue is a request to (a) make the filter types reject `undefined` so it fails at compile time, (b) export/document `EmptyFilter`, and (c) call it out in the rc.5 release notes as breaking. If it's not intentional, `undefined` should keep meaning "skip this field" as in rc.4.

### Environment

Node 24 / Cloudflare Workers (workerd), PostgreSQL, postgres-js driver. Reproduces identically under plain Node.

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the minimal RQB v2 query from the issue with an undefined where value, then trace the reported relationsFilterToSQL and PgDialect.buildRelationalQuery entry points. Compare rc.4 with the rc.5 snapshot to determine the intended undefined behavior and align the filter types, EmptyFilter availability, and release documentation with that decision.

Written by the indexing model from the issue text.

Assessment

Tech stack
postgresql, typescript
Domain
database
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.