Files
ScadaBridge/docs/plans/sql/AddConfigGraphRowVersions.sql
T
Joseph Doherty a4806576af feat(pending-updates): short-circuit an unchanged fleet on a DB rowversion watermark (#62)
Owner decision (b), 2026-11-09. PendingUpdatesEvaluator re-flattened every
deployed instance on each cache-TTL expiry even when nothing had changed.

- Migration AddConfigGraphRowVersions: a non-concurrency rowversion column
  + index on the 18 configuration tables the evaluation reads (Templates +
  5 member tables, Instances + 4 override/binding tables, DataConnections,
  DeployedConfigSnapshots, Sites, SharedScripts, ExternalSystemDefinitions,
  ExternalSystemMethods, DatabaseConnectionDefinitions). SQL Server only in
  the model; SQLite test contexts are untouched. Script:
  docs/plans/sql/AddConfigGraphRowVersions.sql. Joins the coordinated
  central-pair migration window (#107 item 4).
- IConfigGraphWatermarkReader / ConfigGraphWatermarkReader: ONE query of
  MAX(RowVersion) + COUNT_BIG(*) per table (count catches deletes, which
  raise no rowversion). rowversion is engine-bumped for any writer, so a
  peer central node's edit moves it — not the per-process watermark MG-3
  removed.
- The secret store (outside the DB) gets its own stamp: SHA-256 over
  ListAsync(includeDeleted: true) metadata.
- Evaluator re-serves the last CLEAN snapshot while watermark + secret
  stamp are unchanged; every PendingUpdatesFullEvaluationEvery'th (default
  10, validated >= 1, 1 = off) is full regardless — the safety net for a
  same-KEK re-wrap or an input table missing from the set. Always full when
  the token is unavailable/unreadable, after an evaluation that could not
  compute a site's artifact hash, on Re-check, and after a deploy completion.
  It only re-serves a real earlier answer, never manufactures one.

Tests: SQL Server (real migrated DB) — token stable without writes, moves
for a peer-connection INSERT/UPDATE and a non-max DELETE, moves for a local
EF write, a detached Update still saves (not a concurrency token), and a
guard that every table the evaluator's repository reads touch is covered.
Evaluator — reuse without flatten (re-served answer still shows the stale
instance), watermark move, 4 secret changes, every-Nth, 5 no-token modes,
unclean evaluation not reused, cache Re-check and deploy completion force
full. Mutation-proven: token compare, clean check, cache force flag, row
count, and an uncovered read each turn tests red.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 22:38:55 -05:00

85 lines
3.6 KiB
Transact-SQL

-- AddConfigGraphRowVersions (2026-11-10, ScadaBridge#62). A rowversion column + index on the 18 configuration
-- tables feeding the pending-updates evaluation, so PendingUpdatesEvaluator can skip the fleet flatten when
-- MAX(RowVersion)+COUNT_BIG(*) over them is unchanged. Additive: SQL Server fills every existing row as the
-- column is added; old binaries ignore the column. Apply after AddAuditLogHashChain in the coordinated
-- central-pair migration window (ScadaBridge#107 item 4).
BEGIN TRANSACTION;
ALTER TABLE [TemplateScripts] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [Templates] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [TemplateNativeAlarmSources] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [TemplateCompositions] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [TemplateAttributes] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [TemplateAlarms] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [Sites] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [SharedScripts] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [Instances] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [InstanceNativeAlarmSourceOverrides] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [InstanceConnectionBindings] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [InstanceAttributeOverrides] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [InstanceAlarmOverrides] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [ExternalSystemMethods] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [ExternalSystemDefinitions] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [DeployedConfigSnapshots] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [DataConnections] ADD [RowVersion] rowversion NOT NULL;
ALTER TABLE [DatabaseConnectionDefinitions] ADD [RowVersion] rowversion NOT NULL;
CREATE INDEX [IX_TemplateScripts_RowVersion] ON [TemplateScripts] ([RowVersion]);
CREATE INDEX [IX_Templates_RowVersion] ON [Templates] ([RowVersion]);
CREATE INDEX [IX_TemplateNativeAlarmSources_RowVersion] ON [TemplateNativeAlarmSources] ([RowVersion]);
CREATE INDEX [IX_TemplateCompositions_RowVersion] ON [TemplateCompositions] ([RowVersion]);
CREATE INDEX [IX_TemplateAttributes_RowVersion] ON [TemplateAttributes] ([RowVersion]);
CREATE INDEX [IX_TemplateAlarms_RowVersion] ON [TemplateAlarms] ([RowVersion]);
CREATE INDEX [IX_Sites_RowVersion] ON [Sites] ([RowVersion]);
CREATE INDEX [IX_SharedScripts_RowVersion] ON [SharedScripts] ([RowVersion]);
CREATE INDEX [IX_Instances_RowVersion] ON [Instances] ([RowVersion]);
CREATE INDEX [IX_InstanceNativeAlarmSourceOverrides_RowVersion] ON [InstanceNativeAlarmSourceOverrides] ([RowVersion]);
CREATE INDEX [IX_InstanceConnectionBindings_RowVersion] ON [InstanceConnectionBindings] ([RowVersion]);
CREATE INDEX [IX_InstanceAttributeOverrides_RowVersion] ON [InstanceAttributeOverrides] ([RowVersion]);
CREATE INDEX [IX_InstanceAlarmOverrides_RowVersion] ON [InstanceAlarmOverrides] ([RowVersion]);
CREATE INDEX [IX_ExternalSystemMethods_RowVersion] ON [ExternalSystemMethods] ([RowVersion]);
CREATE INDEX [IX_ExternalSystemDefinitions_RowVersion] ON [ExternalSystemDefinitions] ([RowVersion]);
CREATE INDEX [IX_DeployedConfigSnapshots_RowVersion] ON [DeployedConfigSnapshots] ([RowVersion]);
CREATE INDEX [IX_DataConnections_RowVersion] ON [DataConnections] ([RowVersion]);
CREATE INDEX [IX_DatabaseConnectionDefinitions_RowVersion] ON [DatabaseConnectionDefinitions] ([RowVersion]);
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20261110032839_AddConfigGraphRowVersions', N'10.0.13');
COMMIT;
GO