Org-scoped tables use a global primary key rather than a composite one, so a blind save-on-conflict could match and overwrite a row belonging to another account. The proxy therefore pre-checks ownership of every conflict key in the batch, and refuses the whole request if any existing row belongs elsewhere. The same check runs one axis down for per-user rows, and one axis sideways for records a restricted sales rep isn't allowed to see.
Also called: upsert guard · conflicting row belongs to another organization · id collision
- 1The conflict columns are read from the request (defaulting to 'id') and every row must supply them.
- 2Fast path: one batched select fetches all matching ids with their org_id in a single round-trip.
- 3If any existing row's org_id differs from the caller's, the request is refused with a 403.
- 4For per-user tables, a matching row owned by a different user is refused the same way.
- 5For lead tables, existing rows are re-selected through the caller's visibility scope; an id that exists but doesn't come back is a record they may not overwrite.
- 6Composite or custom conflict targets fall back to a per-row check, since those call sites are single-row writes.
The comment states the hole precisely: 'a blind ON CONFLICT (id) DO UPDATE would match — and overwrite — a row belonging to ANOTHER org, with stampOrg then rewriting that row's org_id to the caller's.' Because the database driver 'offers no WHERE on the conflict target', the check has to happen before the statement. The batching is a performance fix with its own story: the original per-row pre-check meant 'a 3,200-lead import = ~3,264 round-trips', so the fast path does the same ownership check in one query per 50-row chunk.
- A save keyed on a global id could silently take over another account's row and relabel it.
- Hiding a record from a restricted rep didn't stop them overwriting it by id.
- Ownership checking one row at a time made bulk imports unusably slow.
See it on your own jobs
Twenty minutes, your numbers, no slide deck. We’ll build one of your real buildings in front of you and send you the estimate link at the end — yours to keep either way.
or keep browsing features →