Skip to content

fix(drizzle): treat undefined query properties as absent - #1648

Merged
hayes merged 1 commit into
mainfrom
fix/drizzle-omit-undefined-query-keys
Aug 18, 2026
Merged

fix(drizzle): treat undefined query properties as absent#1648
hayes merged 1 commit into
mainfrom
fix/drizzle-omit-undefined-query-keys

Conversation

@hayes

@hayes hayes commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Problem

A Drizzle query callback that returns a conditional filter silently breaks field selection:

t.relation('posts', {
  query: (args) => ({ where: args.filter ? buildFilter(args.filter) : undefined }),
})

The root cause is not Drizzle — Drizzle truthy-checks where at runtime (sqlite-core/dialect.js:495) and types it as where?: ... | undefined. The break is on the Pothos side: deepEqual (src/utils/deep-equal.ts:37) compares key counts, so { where: undefined } does not match an equivalent selection that omits the key. selectionCompatible then rejects the nested relation and the field's selection is dropped.

Fix

Normalize query objects by removing undefined-valued properties before they are stored on selection state, compared, or passed to Drizzle (omitUndefinedKeys in src/utils/selections.ts), applied in mergeSelection, selectionCompatible, selectionToQuery, connection-helpers, drizzle-field-builder, and cursors.

The helper is key-agnostic rather than where-specific by design. The failing mechanism is the key-count comparison, not anything about where, so orderBy, limit, and offset are reachable through the identical path — verified against a where-only version of this fix:

✗ orderBy: undefined leaks into selectionToQuery output
✗ orderBy: undefined → selectionCompatible(state, {}) === false
✗ limit:   undefined → selectionCompatible(state, {}) === false

columns / with / extras are destructured out before the rest-spread, so they are unaffected.

Tests

98 tests pass, typecheck clean. Every new assertion is a genuine regression test — reverting only src/ fails 8 integration assertions across 5 files plus 7 unit assertions in selections.test.ts. Coverage spans the merge level (it.each over where/orderBy/limit/offset), the compatibility level (selectionCompatible / stateCompatible), and end-to-end via vi.spyOn on db.query.* asserting the property never reaches Drizzle. All existing inline SQL snapshots are unchanged.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pothos Ready Ready Preview Aug 18, 2026 8:06pm

@changeset-bot

changeset-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 265c4c8

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

@pothos/core

npm i https://pkg.pr.new/@pothos/core@1648

@pothos/plugin-add-graphql

npm i https://pkg.pr.new/@pothos/plugin-add-graphql@1648

@pothos/plugin-complexity

npm i https://pkg.pr.new/@pothos/plugin-complexity@1648

@pothos/plugin-dataloader

npm i https://pkg.pr.new/@pothos/plugin-dataloader@1648

@pothos/plugin-directives

npm i https://pkg.pr.new/@pothos/plugin-directives@1648

@pothos/plugin-drizzle

npm i https://pkg.pr.new/@pothos/plugin-drizzle@1648

@pothos/plugin-errors

npm i https://pkg.pr.new/@pothos/plugin-errors@1648

@pothos/plugin-example

npm i https://pkg.pr.new/@pothos/plugin-example@1648

@pothos/plugin-federation

npm i https://pkg.pr.new/@pothos/plugin-federation@1648

@pothos/plugin-grafast

npm i https://pkg.pr.new/@pothos/plugin-grafast@1648

@pothos/plugin-mocks

npm i https://pkg.pr.new/@pothos/plugin-mocks@1648

@pothos/plugin-prisma

npm i https://pkg.pr.new/@pothos/plugin-prisma@1648

@pothos/plugin-prisma-utils

npm i https://pkg.pr.new/@pothos/plugin-prisma-utils@1648

@pothos/plugin-relay

npm i https://pkg.pr.new/@pothos/plugin-relay@1648

@pothos/plugin-scope-auth

npm i https://pkg.pr.new/@pothos/plugin-scope-auth@1648

@pothos/plugin-simple-objects

npm i https://pkg.pr.new/@pothos/plugin-simple-objects@1648

@pothos/plugin-smart-subscriptions

npm i https://pkg.pr.new/@pothos/plugin-smart-subscriptions@1648

@pothos/plugin-sub-graph

npm i https://pkg.pr.new/@pothos/plugin-sub-graph@1648

@pothos/plugin-tracing

npm i https://pkg.pr.new/@pothos/plugin-tracing@1648

@pothos/plugin-validation

npm i https://pkg.pr.new/@pothos/plugin-validation@1648

@pothos/plugin-with-input

npm i https://pkg.pr.new/@pothos/plugin-with-input@1648

@pothos/plugin-zod

npm i https://pkg.pr.new/@pothos/plugin-zod@1648

@pothos/tracing-newrelic

npm i https://pkg.pr.new/@pothos/tracing-newrelic@1648

@pothos/tracing-opentelemetry

npm i https://pkg.pr.new/@pothos/tracing-opentelemetry@1648

@pothos/tracing-sentry

npm i https://pkg.pr.new/@pothos/tracing-sentry@1648

@pothos/tracing-xray

npm i https://pkg.pr.new/@pothos/tracing-xray@1648

commit: 265c4c8

Query callbacks that return a conditional filter (`where: cond ? filter
: undefined`) produced a selection state carrying an explicit `undefined`
key. `deepEqual` compares key counts, so `{ where: undefined }` did not
match an equivalent selection that omitted the key, `selectionCompatible`
rejected the nested relation, and the field's selection was silently
dropped.

Normalize query objects by removing undefined-valued properties before
they are stored on selection state, compared, or passed to Drizzle. This
covers `where`, `orderBy`, `limit`, and `offset` — the mechanism is
key-agnostic, so scoping the fix to `where` alone would leave the same
bug reachable through its siblings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Drizzle integration edge case where query callbacks that conditionally return undefined (e.g. where: cond ? filter : undefined) could cause nested selections to be treated as incompatible, leading to dropped field selections. It does so by normalizing query objects to remove undefined-valued properties before storing, comparing, or emitting Drizzle queries.

Changes:

  • Add a shared omitUndefinedKeys normalization helper and apply it across selection merge/compatibility and query construction paths.
  • Ensure undefined query properties are treated equivalently to absent properties in selection state comparisons and emitted Drizzle queries.
  • Add regression tests (unit + integration) to assert undefined query properties never reach Drizzle runtime calls.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/plugin-drizzle/src/utils/selections.ts Introduces omitUndefinedKeys and applies it in selectionCompatible, mergeSelection, and selectionToQuery.
packages/plugin-drizzle/src/utils/cursors.ts Normalizes cursor-connection query objects so undefined properties are omitted before mapping to Drizzle.
packages/plugin-drizzle/src/drizzle-field-builder.ts Normalizes relation query callback results so undefined keys don’t leak into relation with selections.
packages/plugin-drizzle/src/connection-helpers.ts Normalizes the final merged connection helper query (baseQuery + selection query).
packages/plugin-drizzle/tests/selections.test.ts Adds unit tests for omitUndefinedKeys behavior and compatibility/merge normalization cases.
packages/plugin-drizzle/tests/drizzle-field.test.ts Adds an integration assertion that root queries omit where when it is undefined.
packages/plugin-drizzle/tests/drizzle-connections.test.ts Adds integration assertions via spies that where: undefined is omitted in connection queries.
packages/plugin-drizzle/tests/connection-helpers.test.ts Adds integration assertions that nested with.*.where is omitted when undefined.
packages/plugin-drizzle/tests/related-connection.test.ts Adds a spy-based integration assertion that nested relation where does not reach Drizzle.
packages/plugin-drizzle/tests/query-path.test.ts Adds a spy-based integration assertion that relation query callbacks don’t pass where: undefined through.
packages/plugin-drizzle/tests/example/schema/user.ts Updates test schema to include where: undefined in callbacks to exercise the regression path.
packages/plugin-drizzle/tests/example/schema/query.ts Updates test schema to pass where: undefined into the root query builder to validate normalization.
.changeset/calm-trees-filter.md Adds a patch changeset describing the fix and impacted query keys.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@hayes
hayes merged commit ddc305c into main Aug 18, 2026
7 checks passed
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.

2 participants