Skip to content

Fix AND where clauses being dropped when combined with OR clauses - #404

Open
moritz-mode wants to merge 1 commit into
convex:mainfrom
neunzig-zehn:fix/mixed-and-or-where
Open

moritz-mode wants to merge 1 commit into
convex:mainfrom
neunzig-zehn:fix/mixed-and-or-where

Conversation

@moritz-mode

Copy link
Copy Markdown

Motivation

Better Auth where semantics: clauses with connector: "OR" form a single OR-group that is AND'd with the remaining (implicitly AND) clauses — AND(...) AND OR(...). The Convex adapter currently unions every clause as an OR branch, so AND filters are silently dropped. For example, the dashboard's user search sends its search term as OR clauses combined with a createdAt range filter — the range filter is ignored and the query returns rows outside the range. This affects findOne, findMany, count, and (via collectIdsForOrWhere) updateMany/deleteMany.

Separately, findOne with an all-OR where and no matching branch fell through to a combined query that the component rejects (OR connector not supported with multiple where statements) instead of returning null.

Changes

  • where is partitioned into the OR-group and the AND clauses (splitWhereByConnector); each OR branch runs as one component query AND'd with the AND clauses (orBranchWhere), and results are unioned/deduped.
  • Applies to findOne, findMany, count, and updateMany/deleteMany via collectIdsForOrWhere.
  • findOne returns null when no OR branch matches instead of falling through.

Behavior change

updateMany/deleteMany with a mixed AND/OR where now affect only rows matching (OR-group) AND (AND clauses) — previously they affected the broader all-OR union. That is the bug being fixed, but any caller relying on the old behavior will see fewer rows affected.

Tests

New convex-custom tests: OR+AND combination for findMany/count/findOne, findOne → null when no OR branch matches, updateMany with mixed clauses.

npm run build, npm run typecheck (src), and the full vitest suite pass: 10 files, 185 passed | 31 skipped (main baseline: 182 passed | 31 skipped).

Note: this touches the same findMany region as the offset PR; whichever merges second will be rebased promptly.

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

@MauruschatM is attempting to deploy a commit to the Convex Team on Vercel.

A member of the Team first needs to authorize it.

@moritz-mode

Copy link
Copy Markdown
Author

Applied it as a patch file in my project and it's running smooth in production.

@moritz-mode
moritz-mode marked this pull request as ready for review July 8, 2026 06:04
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The adapter's where-clause handling was reworked to correctly support mixed AND/OR semantics. New helper functions split incoming where clauses into OR-branches and shared AND-clauses, then combine each OR branch with all AND clauses for querying. This replaces prior logic that only handled OR when clauses were uniformly OR or when any clause was OR. The change affects findOne, findMany, count, and collectIdsForOrWhere. New tests validate mixed OR/AND behavior across findMany, count, findOne, and updateMany.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Adapter
  participant SplitWhereByConnector
  participant ConvexQuery

  Caller->>Adapter: findOne/findMany/count(where)
  Adapter->>SplitWhereByConnector: splitWhereByConnector(where)
  SplitWhereByConnector-->>Adapter: orClauses, andClauses
  loop each orClause
    Adapter->>ConvexQuery: query(orBranchWhere(orClause, andClauses))
    ConvexQuery-->>Adapter: matching records
  end
  Adapter-->>Caller: combined/deduped results
Loading

Related PRs: None found.

Suggested labels: bug, adapter, tests

Suggested reviewers: get-convex/better-auth maintainers

🐰 A rabbit split its whiskers OR from AND,
Combined each branch, then took a stand,
Tests confirm the twisted trail,
Where boolean logic used to fail,
Now every query finds its land.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: preserving AND clauses when OR clauses are present.
Description check ✅ Passed The description directly matches the changes and explains the bug fix, scope, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/client/adapter.ts (1)

268-284: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: extract the shared OR-branch fetch/dedupe pattern.

The asyncMap(orClauses, … handlePagination(… findMany({ where: parseWhere(orBranchWhere(w, andClauses)) }))) followed by dedupeDocsById(results.flatMap((r) => r.docs)) is now duplicated in collectIdsForOrWhere (here), findMany (Lines 342-355), and count (Lines 385-395). A single helper returning the deduped docs for (orClauses, andClauses, { limit }) would keep these branches from diverging.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/adapter.ts` around lines 268 - 284, The OR-branch fetch/dedupe
flow is duplicated across collectIdsForOrWhere, findMany, and count, so extract
it into a shared helper that takes the OR/AND clauses plus optional
pagination/limit inputs and returns the deduped docs or IDs. Update the logic in
collectIdsForOrWhere, findMany, and count to call that helper instead of
inlining asyncMap(handlePagination(ctx.runQuery(api.adapter.findMany, ...))) and
dedupeDocsById(results.flatMap((r) => r.docs)), keeping
parseWhere(orBranchWhere(...)) and getDocId usage centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/client/adapter.ts`:
- Around line 268-284: The OR-branch fetch/dedupe flow is duplicated across
collectIdsForOrWhere, findMany, and count, so extract it into a shared helper
that takes the OR/AND clauses plus optional pagination/limit inputs and returns
the deduped docs or IDs. Update the logic in collectIdsForOrWhere, findMany, and
count to call that helper instead of inlining
asyncMap(handlePagination(ctx.runQuery(api.adapter.findMany, ...))) and
dedupeDocsById(results.flatMap((r) => r.docs)), keeping
parseWhere(orBranchWhere(...)) and getDocId usage centralized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: get-convex/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8e270ea4-d926-424f-92f6-4fe9041fcd22

📥 Commits

Reviewing files that changed from the base of the PR and between c628916 and deb07f2.

📒 Files selected for processing (2)
  • src/client/adapter.ts
  • src/test/adapter-factory/convex-custom.ts

helixnebulatech added a commit to helixnebulatech/convex-better-auth that referenced this pull request Oct 8, 2026
Better Auth (and its SQL/Mongo adapters) treat clauses with connector
"OR" as one OR group AND'd with all other clauses. The adapter ran every
clause as its own OR branch, so AND clauses were silently dropped from
findMany, count, updateMany and deleteMany (which then wrote/deleted
rows outside the filter). findOne with an all-OR where and no match fell
through to a query the component rejects instead of returning null.

Run one component query per OR clause, each with all AND clauses, and
union the results. Ports convex#404.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sshkeda

sshkeda commented Oct 26, 2026

Copy link
Copy Markdown

Thanks for the fix. I found a case where updateMany and deleteMany can change a record after it stops matching the filter, if the record changes between ID selection and the write.

I reproduced this with convex-test by changing a record before the bulk mutation ran. I tested changes to an AND condition and to the matching OR condition. In both cases, the mutation still changed the record and returned a count of one.

Could the mutation recheck the full filter before writing? A test could also cover a record that moves between OR alternatives but still matches the filter.

This branch has not been deployed

No deployments
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