Skip to content

Fix _id where-clauses matching documents from other models - #411

Open
stickerdaniel wants to merge 1 commit into
get-convex:mainfrom
stickerdaniel:fix/id-where-model-scope
Open

Fix _id where-clauses matching documents from other models#411
stickerdaniel wants to merge 1 commit into
get-convex:mainfrom
stickerdaniel:fix/id-where-model-scope

Conversation

@stickerdaniel

@stickerdaniel stickerdaniel commented Jul 11, 2026

Copy link
Copy Markdown

Fixes #410.

  • findOne/findMany with an _id where-clause resolved the id with a bare ctx.db.get(value), which returns whatever document the id points to regardless of the requested model, so any valid id of another table matched.
  • Resolve _id values through ctx.db.normalizeId(args.model, value) first: foreign-table ids and invalid id strings both become "no match". This also covers the UUID values passed by better-auth's adapter tests that motivated the @convex-dev/explicit-table-ids exemption in use convex linter #347 (convex-test's normalizeId returns null for them), so the exemption is removed. With it gone, every ctx.db.get in the package is table-scoped.
  • Same treatment on the in branch, which since use convex linter #347 throws InvalidTable on foreign ids instead of filtering them out.
  • Adds a regression test; on main it fails on both assertions (findOne returns the foreign document, findMany with in throws).

Update/delete flows resolve their target through the same paginate path, so they are covered transitively. Side note: the two-arg db.get(table, id) overload needs convex 1.31+, which the package already de facto requires since #347 (the create and in-branch paths use it), while the convex peerDependency still says ^1.25.0; might be worth bumping separately.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

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

A member of the Team first needs to authorize it.

findOne/findMany resolved _id where-clauses with a bare ctx.db.get,
which returns whatever document the id points to regardless of the
requested model. Resolve through ctx.db.normalizeId first so foreign
ids and invalid id strings are treated as no match.
@stickerdaniel
stickerdaniel force-pushed the fix/id-where-model-scope branch from 3b715a3 to 18caf0f Compare July 12, 2026 04:12
@stickerdaniel
stickerdaniel marked this pull request as ready for review July 12, 2026 04:12
Copilot AI review requested due to automatic review settings July 12, 2026 04:12

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The adapter now normalizes string _id values before Convex lookups in both unique and in query paths. Invalid or foreign-model IDs are treated as no matches. Tests verify that a user ID does not match session records through findOne or findMany.

Possibly related issues

Possibly related PRs

Suggested reviewers: erquhart

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: preventing _id where-clauses from matching documents in other models.
Description check ✅ Passed The description is directly about the same _id normalization and cross-model matching fix described in the changeset.
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-utils.ts (1)

525-535: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct fix; consider extracting the duplicated normalize-and-get pattern.

The model-scoped normalizeIdget pattern correctly treats foreign-table/invalid _id values as no-match rather than throwing or mismatching, per Convex's documented normalizeId contract: "This does not guarantee that the ID exists (i.e. db.get(id) may return null)." and the underlying implementation confirms it returns null for foreign-table/invalid ids rather than throwing.

The unique-eq branch (Lines 529-535) and the in-clause branch (Lines 579-583) now implement identical logic (typeof value === "string" ? ctx.db.normalizeId(model, value) : null, then conditionally ctx.db.get). Extracting this into a small shared helper would keep both call sites in sync if this logic needs to change again later (e.g. supporting additional legacy ID formats).

♻️ Suggested helper extraction
+const getByNormalizedId = async <T extends TableNamesInDataModel<GenericDataModel>>(
+  ctx: GenericQueryCtx<GenericDataModel>,
+  model: T,
+  value: unknown
+) => {
+  const id = typeof value === "string" ? ctx.db.normalizeId(model, value) : null;
+  return id && (await ctx.db.get(model, id));
+};
+
 ...
-    const uniqueWhereId =
-      uniqueWhere.field === "_id" && typeof uniqueWhere.value === "string"
-        ? ctx.db.normalizeId(args.model as T, uniqueWhere.value)
-        : null;
     const doc =
       uniqueWhere.field === "_id"
-        ? uniqueWhereId && (await ctx.db.get(args.model, uniqueWhereId))
+        ? await getByNormalizedId(ctx, args.model as T, uniqueWhere.value)
         : await ctx.db
             .query(args.model as any)
             ...
       const docs = await asyncMap(inWhere.value as any[], async (value) => {
-        const id =
-          typeof value === "string"
-            ? ctx.db.normalizeId(args.model as T, value)
-            : null;
-        return id && (await ctx.db.get(args.model, id));
+        return getByNormalizedId(ctx, args.model as T, value);
       });

Also applies to: 577-583

🤖 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-utils.ts` around lines 525 - 535, Extract the duplicated
model-scoped normalize-and-get logic from the unique “_id” branch and the “in”
clause branch into a shared helper near the relevant adapter utilities. Have the
helper accept the model and value, normalize only string IDs, and conditionally
call ctx.db.get so invalid or foreign-table IDs return no match; update both
call sites to reuse it.
🤖 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-utils.ts`:
- Around line 525-535: Extract the duplicated model-scoped normalize-and-get
logic from the unique “_id” branch and the “in” clause branch into a shared
helper near the relevant adapter utilities. Have the helper accept the model and
value, normalize only string IDs, and conditionally call ctx.db.get so invalid
or foreign-table IDs return no match; update both call sites to reuse it.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro

Run ID: 59350134-97bf-4ed8-b9c1-c3788b6b2fb9

📥 Commits

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

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

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.

findOne/findMany with an _id where-clause can return a document from a different model

2 participants