Skip to content

fix(retain): make document lock/upsert dialect-aware for Oracle (#1944) - #1952

Merged
nicoloboschi merged 1 commit into
mainfrom
fix/oracle-retain-document-lock
Jun 4, 2026
Merged

nicoloboschi merged 1 commit into
mainfrom
fix/oracle-retain-document-lock

Conversation

@nicoloboschi

Copy link
Copy Markdown
Collaborator

Summary

Fixes #1944. Retain returned 500 DPY-1003: the executed statement does not return rows on Oracle, turning test-python-client-oracle and test-typescript-client-oracle red.

Cause

#1930 changed the retain document-ownership gate to a single-statement upsert:

INSERT INTO documents (...) VALUES (...)
ON CONFLICT (id, bank_id) DO UPDATE SET content_hash = documents.content_hash
RETURNING content_hash

PostgreSQL runs this as-is. The Oracle adapter rewrites ON CONFLICT DO UPDATE to a MERGE — and Oracle MERGE cannot carry a RETURNING clause, so the rewritten statement returns no rows and the fetchval fails with DPY-1003.

Fix

Move the lock-and-read step behind a dialect-aware DataAccessOps.lock_document_for_write method (mirrors the existing strategy pattern that keeps if backend_type checks out of business logic):

  • PostgreSQL — keeps the single-statement INSERT ... ON CONFLICT DO UPDATE ... RETURNING. DO UPDATE always takes the row lock atomically, preserving fix(retain): pre-extraction freshness recheck + serialize concurrent same-doc writers #1930's deadlock fix.
  • Oracle — splits into two statements: an idempotent insert (IGNORE_ROW_ON_DUPKEY_INDEX, the same idiom already used by enqueue_graph_maintenance) followed by SELECT content_hash ... FOR UPDATE. Concurrent same-document inserters block on the duplicate key until the holder commits, so writers still serialize.

Both return the pre-existing hash ('__pending__' for a freshly inserted row), driving the later-batch takeover check unchanged.

Tests

tests/test_lock_document_for_write.py:

  • PG functional coverage of the placeholder→stored-hash transition and (id, bank_id) isolation (runs in the default suite against pg).
  • Translator tests pinning the root cause (the old upsert rewrites to a MERGE that drops RETURNING) and that the Oracle fallback's SELECT ... FOR UPDATE translates cleanly.

End-to-end Oracle coverage comes from the existing test-*-client-oracle CI jobs that the issue reported failing.

Scope

Only the broken DO UPDATE ... RETURNING site is rerouted. The two ON CONFLICT DO NOTHING + SELECT FOR UPDATE sites (zero-facts / no-batch paths) already translate correctly on Oracle and are left unchanged.

The retain document-ownership gate used a single
`INSERT ... ON CONFLICT DO UPDATE ... RETURNING content_hash` upsert to
create-or-lock the document row and read its prior hash. PostgreSQL runs
this as-is, but the Oracle adapter rewrites `ON CONFLICT DO UPDATE` to a
`MERGE`, which cannot carry a `RETURNING` clause. The rewritten statement
returned no rows, so every retain 500'd with
`DPY-1003: the executed statement does not return rows`, turning the
`test-python-client-oracle` and `test-typescript-client-oracle` jobs red.

Move the lock-and-read step behind `DataAccessOps.lock_document_for_write`
so each backend implements it natively:
- PG: the same single-statement upsert (DO UPDATE always takes the row
  lock, avoiding the old two-step deadlock).
- Oracle: an idempotent insert (IGNORE_ROW_ON_DUPKEY_INDEX) followed by a
  `SELECT ... FOR UPDATE`, since MERGE can't RETURNING.

Adds regression tests: PG functional coverage of the placeholder→hash
transition and bank isolation, plus translator tests pinning the root
cause (MERGE drops RETURNING) and the Oracle fallback's clean rewrite.
@nicoloboschi
nicoloboschi merged commit 2535db2 into main Jun 4, 2026
78 of 79 checks passed
cdbartholomew added a commit that referenced this pull request Oct 20, 2026
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.

Oracle: retain 500s (DPY-1003) — #1930's ON CONFLICT DO UPDATE … RETURNING isn't Oracle-translated

1 participant