Help · section 35 of 44

Concurrency Protection

Mirrors the in-app Help. The live copy in your console and `dlake guide help` are always current.

Optimistic-concurrency + soft-delete for a table:

  • UPDATEs must echo the row’s current TimeStamp (rowversion) as dl_expected_ts — a strict AFTER trigger rejects stale writes (“Row was modified”).
  • DELETEs are intercepted at the DB layer (INSTEAD OF) and become dl_deleted = 1 soft deletes; a sweep purges flagged rows on demand. Through the DAB API a direct DELETE is rejected with DAB_CONCURRENCY_BLOCKED_DELETE — soft-delete instead by PATCHing dl_deleted=true together with dl_expected_ts (the row’s current TimeStamp).
  • Specific accounts can be exempted by an administrator.

Soft-deleted rows stay visible on every read surface. A soft delete flags the row; it does not hide it. The Data API, the MCP data tools, the CLI and raw SQL all keep returning dl_deleted = 1 rows until a sweep purges them — nothing filters them out server-side, by design, so that a consumer can see what was deleted and when. dl_deleted is the filter, and applying it is the consumer’s job. An active row is one where dl_deleted is NULL or 0: the column is only written once a row is touched, so an untouched active row carries NULL, and a bare dl_deleted eq false (or = 0) silently hides every untouched row. Ask for active rows like this:

SurfaceActive-rows filter
Data API (REST, OData)?$filter=dl_deleted eq null or dl_deleted eq false
MCP read_recordsfilter: dl_deleted eq null or dl_deleted eq false
CLIdlake tool read_records --entity <Entity> --filter "dl_deleted eq null or dl_deleted eq false"
SQL (dlake query, SQL Editor, query tool)WHERE ISNULL(dl_deleted, 0) = 0

The Data Browser and the table detail Data tab hide soft-deleted rows by default and offer a Show deleted toggle, which reports how many of the loaded rows are flagged. The column only exists on concurrency-protected tables, so on any other table these filters do not apply.

Enable/disable per table from the table detail page. The protection columns (dl_expected_ts is virtual — you send it, it’s never stored; dl_deleted) are server-managed. Enable and disable regenerate and restart DAB for you — necessary both for the new dl_* columns and because DAB has to see the new triggers (see Triggers and the Data API). If the response says the redeploy FAILED, restart DAB yourself: writes to that table are failing until you do.