Skip to content

Row versions and archiving

A link stores two facts about its target: the row id and the target’s business-data version. This lets a record keep showing the exact supplier, product, address, or other linked data that it used, even after the shared row changes.

This matters most for global tables. One global row can be used by records in many accounting-period profiles, and those periods must not silently reinterpret historical records when shared master data is updated.

Every managed row has two counters:

Counter Purpose Changes when
row_revision Detects concurrent edits Any row update succeeds, including archiving
version Identifies a business-data generation ArchiveTableData snapshots the current generation

Ordinary updates do not advance version. They change the current generation in place, subject to impact confirmation. Archiving freezes that generation and advances the live row to the next version.

When a source row is saved, each link records the target’s current version in a hidden companion column. A later read resolves both that saved version and the target’s current state:

Status Meaning
CURRENT The saved version is still the live target version
SUPERSEDED The saved version is archived and a newer live version exists
UNAVAILABLE The saved version cannot be loaded
DELETED The target row is now soft-deleted
RESTRICTED The caller cannot read the target table; only configured display fields are exposed

For SUPERSEDED, the response contains the saved snapshot plus the newer live values and the fields that changed. GetTableDataVersion can also load one exact version of a row directly. A deleted row’s archived versions remain readable by version even though its live version is not returned as current data.

GetTableUpdateImpact accepts a profile, table, and row id and returns the sorted set of profiles whose live rows currently reference that row’s current version.

For a profile-local target, this normally identifies its own profile. For a global target, it can identify every profile using that shared version. References from global source tables count as affecting every non-global profile because the global source is visible from all of them.

An ordinary PutTableData refuses a non-empty update when the current version is referenced. The refusal includes the affected profiles. To proceed deliberately, call PutTableDataConfirmed with the update and exactly that profile set in expected_affected_profiles.

The server recalculates the impact inside the update transaction. Confirmation succeeds only if the supplied set still matches, preventing a stale warning from authorizing an impact that changed meanwhile.

Archive before changing historical master data

Section titled “Archive before changing historical master data”

Use ArchiveTableData with:

  • the profile through which the table is being accessed;
  • the table name and row id;
  • the expected_revision returned by the last read or write.

In one serializable transaction the server:

  1. locks the live, non-deleted row;
  2. verifies expected_revision;
  3. stores the complete row as JSON in table_row_archives, together with its table definition, row id, version, source revision, archive time, and acting user;
  4. reports the profiles currently using that version;
  5. increments the live row’s version, which also advances its row_revision.

The archived (table definition, row id, version) is unique. Attempting to archive the same generation twice returns an already-exists error.

Existing links keep their stored version and therefore resolve the archived snapshot. New links read the advanced live version. After archiving, the live row can be edited without changing what older linked records display.

product row, version 1
├── referenced by 2025 profile records
└── archive
├── immutable snapshot: version 1
└── live product advances to version 2
└── new 2026 profile records reference version 2

Soft deletion is allowed only when no live source row still points at the target’s current version. If current references exist, deletion fails and asks for the row to be archived first.

After archiving, old links point at the archived generation rather than the new current version. The live row may then be deleted if nothing references that new version. Its archived generations remain available for historical link resolution.

For master data shared between accounting-period profiles:

  1. query update impact before changing a row;
  2. if old period records must retain their values, archive the current version;
  3. update the newly advanced live version;
  4. let new-period records link to that version;
  5. use confirmed update only when changing all records that still reference the current generation is intentional.

Archiving is therefore a history boundary, not a backup mechanism. PostgreSQL backups protect the database as a whole; row archives preserve the business meaning of linked records inside the application.