Skip to content

transaction: resolve URL-borne conditional criteria (PUT/DELETE [type]?[criteria]) inside the atomic scope #859

Description

@aacruzgon

Summary

PUT [type]?[criteria] and DELETE [type]?[criteria] entries are resolved in batch bundles (#511), but in a transaction any non-GET entry whose URL carries a query string still declines the whole bundle with 400 not-supported before anything executes (crates/rest/src/handlers/batch.rs, transaction guard). The spec's own transaction example (crates/fhir/tests/data/json/R4/bundle-transaction.json, entries 3 and 6) uses both forms.

Why it is not a small change

  • The backends' transaction executors (process_bundle_entry_tx in sqlite/postgres, process_bundle_entry_transaction in mongodb) address PUT and DELETE through parse_url, which is query-blind and takes the last two path segments; Patient?identifier=x yields a single segment and fails.
  • ConditionalStorage::conditional_update/delete write through the backend, not the open transaction. Calling them from inside process_transaction would commit outside the bundle's atomic scope.
  • batch/transaction: conditional interactions are refused rather than resolved — wire bundle entries to ConditionalStorage #511 added the in-transaction search primitive (SqliteBackend::search_with_connection, PostgresBackend::search_with_client, used by find_matching_resources_in_tx), so the match side is available. What is missing is the resolve-then-write inside the transaction for PUT (update the match or create) and DELETE (delete the match), plus MongoDB's equivalent on its session-scoped matcher.
  • R4 §3.1.0.11.2: identities resolved from conditional update/delete that overlap with other entries in the bundle SHALL fail the transaction. That needs a pre-resolution pass over the ordered entries before any write, in the shape fix(transaction): resolve conditional references before execution #467 chose for conditional references.

Proposed shape

  1. Split each backend's PUT/DELETE arm on conditional_criteria(url); on criteria, resolve via the in-transaction matcher, then update/create or delete the match through the transaction, answering with the same status mapping the batch arm uses (200/201/412 for PUT, 204/204/412 for DELETE). Reuse multiple_matches_entry from core::preconditions.
  2. Add the overlap pre-pass in process_transaction: resolve every conditional entry's target first, fail the bundle if two entries resolve to the same Type/id or a conditional target collides with an explicit instance entry.
  3. Drop the REST-layer transaction guard for URL criteria (keep it for the offloaded-search composites, where the local index is empty, or move that refusal into the backends as batch/transaction: conditional interactions are refused rather than resolved — wire bundle entries to ConditionalStorage #511 did for ifNoneExist).
  4. Percent-decode criteria before they reach parse_simple_search_params; the batch arm's normalize_criteria is the precedent.

Tests to add

  • batch_conformance: transaction PUT Patient?identifier=… updating a seeded match, creating on no match, 412 on several; same for DELETE; the spec fixture as a whole once $op entries (separate issue) are handled.
  • persistence transactions suite: rollback of an earlier create when a later conditional entry is ambiguous; overlap pre-pass rejecting two entries resolving to one resource.

Split out of #511, where batch resolution and in-transaction ifNoneExist landed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions