Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

When comparison operators (eq, gt, lt, etc.) were used with null/undefined values, the SQL compiler would generate placeholders ($1, $2) in the WHERE clause but skip adding the params to the dictionary because serialize() returns empty string for null/undefined.

This resulted in invalid queries being sent to Electric like:
subset__where="name" = $1
subset__params={}

The fix:

  • For eq(col, null): Transform to "col IS NULL" syntax
  • For other comparisons (gt, lt, gte, lte, like, ilike): Throw a clear error since null comparisons don't make semantic sense in SQL

Added comprehensive tests for the sql-compiler including null handling.

🎯 Changes

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

When comparison operators (eq, gt, lt, etc.) were used with null/undefined
values, the SQL compiler would generate placeholders ($1, $2) in the WHERE
clause but skip adding the params to the dictionary because serialize()
returns empty string for null/undefined.

This resulted in invalid queries being sent to Electric like:
  subset__where="name" = $1
  subset__params={}

The fix:
- For eq(col, null): Transform to "col IS NULL" syntax
- For other comparisons (gt, lt, gte, lte, like, ilike): Throw a clear
  error since null comparisons don't make semantic sense in SQL

Added comprehensive tests for the sql-compiler including null handling.
@changeset-bot
Copy link

changeset-bot bot commented Dec 2, 2025

🦋 Changeset detected

Latest commit: 9401b42

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

This PR includes changesets to release 2 packages
Name Type
@tanstack/electric-db-collection Patch
@tanstack/db-collection-e2e Patch

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
Copy link

pkg-pr-new bot commented Dec 2, 2025

More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@951

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@951

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@951

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@951

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@951

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@951

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@951

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@951

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@951

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@951

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@951

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@951

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@951

commit: 9401b42

@github-actions
Copy link
Contributor

github-actions bot commented Dec 2, 2025

Size Change: 0 B

Total Size: 87.1 kB

ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.39 kB
./packages/db/dist/esm/collection/changes.js 977 B
./packages/db/dist/esm/collection/events.js 388 B
./packages/db/dist/esm/collection/index.js 3.24 kB
./packages/db/dist/esm/collection/indexes.js 1.1 kB
./packages/db/dist/esm/collection/lifecycle.js 1.67 kB
./packages/db/dist/esm/collection/mutations.js 2.31 kB
./packages/db/dist/esm/collection/state.js 3.43 kB
./packages/db/dist/esm/collection/subscription.js 2.55 kB
./packages/db/dist/esm/collection/sync.js 2.37 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/errors.js 4.19 kB
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/index.js 2.64 kB
./packages/db/dist/esm/indexes/auto-index.js 742 B
./packages/db/dist/esm/indexes/base-index.js 766 B
./packages/db/dist/esm/indexes/btree-index.js 1.87 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.1 kB
./packages/db/dist/esm/indexes/reverse-index.js 513 B
./packages/db/dist/esm/local-only.js 837 B
./packages/db/dist/esm/local-storage.js 2.1 kB
./packages/db/dist/esm/optimistic-action.js 359 B
./packages/db/dist/esm/paced-mutations.js 496 B
./packages/db/dist/esm/proxy.js 3.75 kB
./packages/db/dist/esm/query/builder/functions.js 733 B
./packages/db/dist/esm/query/builder/index.js 3.96 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 917 B
./packages/db/dist/esm/query/compiler/evaluators.js 1.35 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 1.8 kB
./packages/db/dist/esm/query/compiler/index.js 1.96 kB
./packages/db/dist/esm/query/compiler/joins.js 2 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.25 kB
./packages/db/dist/esm/query/compiler/select.js 1.07 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 673 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-config-builder.js 5.33 kB
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/collection-subscriber.js 1.74 kB
./packages/db/dist/esm/query/live/internal.js 130 B
./packages/db/dist/esm/query/optimizer.js 2.56 kB
./packages/db/dist/esm/query/predicate-utils.js 2.91 kB
./packages/db/dist/esm/query/subset-dedupe.js 921 B
./packages/db/dist/esm/scheduler.js 1.3 kB
./packages/db/dist/esm/SortedMap.js 1.18 kB
./packages/db/dist/esm/strategies/debounceStrategy.js 247 B
./packages/db/dist/esm/strategies/queueStrategy.js 428 B
./packages/db/dist/esm/strategies/throttleStrategy.js 246 B
./packages/db/dist/esm/transactions.js 2.9 kB
./packages/db/dist/esm/utils.js 881 B
./packages/db/dist/esm/utils/browser-polyfills.js 304 B
./packages/db/dist/esm/utils/btree.js 5.61 kB
./packages/db/dist/esm/utils/comparison.js 852 B
./packages/db/dist/esm/utils/index-optimization.js 1.51 kB
./packages/db/dist/esm/utils/type-guards.js 157 B

compressed-size-action::db-package-size

@github-actions
Copy link
Contributor

github-actions bot commented Dec 2, 2025

Size Change: 0 B

Total Size: 3.35 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 225 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.17 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.12 kB
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 431 B
./packages/react-db/dist/esm/usePacedMutations.js 401 B

compressed-size-action::react-db-package-size

Address reviewer feedback:
- eq(null, null) now throws (both args null would cause missing params)
- eq(null, literal) now throws (comparing literal to null is nonsensical)
- Only allow ref and func types as non-null arg in eq(..., null)
- Update changeset to explicitly mention undefined behavior
- Add tests for edge cases and OR + null equality
…antics

When eq() is called with a literal null/undefined value, the local
JavaScript evaluator now treats it as an IS NULL check instead of
using 3-valued logic (which would always return UNKNOWN).

This matches the SQL compiler's transformation of eq(col, null) to
IS NULL, ensuring consistent behavior between local query evaluation
and remote SQL queries.

- eq(col, null) now returns true if col is null/undefined, false otherwise
- eq(null, col) is also handled symmetrically
- eq(null, null) returns true (both are null)
- 3-valued logic still applies for column-to-column comparisons

This fixes e2e test failures where eq(col, null) queries returned 0
results because all rows were being excluded by the UNKNOWN result.
Add design document explaining the middle-ground approach for handling
eq(col, null) in the context of PR #765's 3-valued logic implementation.

Key points:
- Literal null values in eq() are transformed to isNull semantics
- 3-valued logic still applies for column-to-column comparisons
- This maintains SQL/JS consistency and handles dynamic values gracefully
Per Kevin's feedback on the 3-valued logic design (PR #765), eq(col, null)
should throw an error rather than being transformed to IS NULL. This is
consistent with how other comparison operators (gt, lt, etc.) handle null.

Changes:
- Revert JS evaluator change that transformed eq(col, null) to isNull semantics
- Update SQL compiler to throw error for eq(col, null) instead of IS NULL
- Update all related unit tests to expect errors
- Remove e2e tests for eq(col, null) (now throws error)
- Update documentation to explain the correct approach

Users should:
- Use isNull(col) or isUndefined(col) to check for null values
- Handle dynamic null values explicitly in their code
- Use non-nullable columns for cursor-based pagination

The original bug (invalid SQL with missing params) is fixed by throwing
a clear error that guides users to the correct approach.
@@ -0,0 +1,105 @@
# Handling Null Values in Comparison Operators
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this file intended to be merged into the codebase or is this cursor/claude writing an md file to tell us what it did but not actually intended to be merged?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah telling us what it did — I wanted a more detailed doc for you to look at


Now:

- `eq(col, null)` and `eq(col, undefined)` transform to `"col" IS NULL` syntax
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this transformation happening? I don't see it in the changes?

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.

4 participants