fix(localdb): refuse AUTOINCREMENT keys at RegisterReplicated (LocalDb-005)
Each node assigns its own AUTOINCREMENT keys, so two different rows inserted on the two nodes share a pk_json and LWW silently keeps one on both nodes. RegisterReplicated now refuses such a table (detected from SQLite column metadata, before any trigger is installed). A plain INTEGER PRIMARY KEY is still accepted (application-assigned ids are legitimate); the requirement that replicated keys be unique across both nodes is documented in the RegisterReplicated remarks and README Limitations. No ScadaBridge or OtOpcUa replicated table is AUTOINCREMENT. (scadaproj#46) Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
| Last reviewed | 2026-11-09 |
|
||||
| Reviewer | Claude |
|
||||
| Commit reviewed | `91fb2b4` |
|
||||
| Open findings | 2 |
|
||||
| Open findings | 1 |
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -362,7 +362,7 @@ applied and that no dead-letter is written. README §Semantics is updated.
|
||||
| Severity | Low |
|
||||
| Priority | P3 |
|
||||
| Category | Documentation & XML docs |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `ZB.MOM.WW.LocalDb/src/ZB.MOM.WW.LocalDb/Internal/SqliteLocalDb.cs:197` |
|
||||
|
||||
**Description**
|
||||
@@ -393,7 +393,28 @@ at registration when the sole PK column is an `INTEGER PRIMARY KEY` rowid alias.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-11-09 (scadaproj#46, LocalDb `0.3.6`, unpublished). Re-verified with a throwaway 2-node probe:
|
||||
`ev(id INTEGER PRIMARY KEY AUTOINCREMENT, msg TEXT)`, `'from A'` inserted on A and `'from B'` on B, both nodes ended
|
||||
with `1:from B` only and no dead-letter. Fix, taking the review's safer option: `RegisterReplicated` (both overloads)
|
||||
now **refuses** a table whose primary key is `AUTOINCREMENT` with a `LocalDbRegistrationException` that names the
|
||||
column and the remedy, before any trigger is installed (`SqliteLocalDb.EnsureNoAutoincrementKey`). Detection reads
|
||||
SQLite's column metadata (`IsAutoIncrement` from `sqlite3_table_column_metadata`), not the `CREATE` text. A refusal
|
||||
rather than a warning, because `AUTOINCREMENT` only exists to have SQLite assign the key, which is never safe on a
|
||||
replicated table, and `SqliteLocalDb` has no logger to warn through.
|
||||
|
||||
A plain `INTEGER PRIMARY KEY` is still accepted, because an application may assign it from a node-scoped range;
|
||||
refusing it would break schemas that are correct today. Its hazard when left `NULL` is documented instead: the
|
||||
`RegisterReplicated` XML remarks and README §Limitations now say replicated primary keys must be unique across both
|
||||
nodes and recommend writer-minted TEXT GUIDs, and the quickstart notes it.
|
||||
|
||||
Compatibility: checked read-only on 2026-11-09. ScadaBridge's 12 replicated tables (`SiteLocalDbSetup.ReplicatedTables`)
|
||||
and OtOpcUa's 5 (`LocalDbSetup.ReplicatedTables`: the two deployment-cache tables, the alarm S&F events table, and
|
||||
the alarm condition state and comments tables) all use TEXT or composite keys, and none is `AUTOINCREMENT`. Both apps had already replaced their historical
|
||||
`INTEGER PRIMARY KEY AUTOINCREMENT` keys with GUIDs for this reason, so neither boot is affected.
|
||||
|
||||
Tests (`RegistrationTests`): `Register_AutoincrementKey_Throws` and `..._WithBaselineToo` fail pre-fix. Two guards pass
|
||||
both ways: a plain `INTEGER PRIMARY KEY` still registers, and a column named `autoincrement` with a `DEFAULT
|
||||
'AUTOINCREMENT'` is not mistaken for the keyword.
|
||||
|
||||
### LocalDb-006 — Column names containing `'` are captured but break the apply and snapshot SQL
|
||||
|
||||
|
||||
Reference in New Issue
Block a user