fix(localdb): capture triggers survive consumer upserts (explicit ON CONFLICT upsert form, outer-statement override)

This commit is contained in:
Joseph Doherty
2026-07-18 00:57:27 -04:00
parent 801b042208
commit 154d584190
4 changed files with 88 additions and 31 deletions
@@ -18,6 +18,15 @@ internal static class TriggerSqlGenerator
private const string TombstoneTail = "1, strftime('%Y-%m-%dT%H:%M:%fZ','now')";
// Explicit upsert, NOT `INSERT OR REPLACE`: SQLite replaces a trigger-body statement's
// OR-conflict algorithm with the OUTER statement's conflict handling, so under a consumer
// `INSERT ... ON CONFLICT DO UPDATE` an OR REPLACE here degraded to a plain INSERT and hit
// SQLITE_CONSTRAINT_PRIMARYKEY on the existing row_version row. An explicit ON CONFLICT
// clause is not overridden. (The SELECT's WHERE clause also disambiguates the upsert parse.)
private const string RowVersionConflictClause =
"ON CONFLICT(table_name, pk_json) DO UPDATE SET hlc = excluded.hlc, node_id = excluded.node_id, " +
"is_tombstone = excluded.is_tombstone, tombstone_utc = excluded.tombstone_utc";
public static string TriggerName(string table, string suffix) => $"__localdb_{table}_{suffix}";
/// <summary>DROP IF EXISTS + CREATE for all three triggers, ready to run in one transaction.</summary>
@@ -49,8 +58,9 @@ internal static class TriggerSqlGenerator
zb_hlc_next(),
(SELECT node_id FROM __localdb_meta WHERE id = 1),
1);
INSERT OR REPLACE INTO __localdb_row_version (table_name, pk_json, hlc, node_id, is_tombstone, tombstone_utc)
SELECT table_name, pk_json, hlc, node_id, {TombstoneTail} FROM __localdb_oplog WHERE seq = last_insert_rowid();
INSERT INTO __localdb_row_version (table_name, pk_json, hlc, node_id, is_tombstone, tombstone_utc)
SELECT table_name, pk_json, hlc, node_id, {TombstoneTail} FROM __localdb_oplog WHERE seq = last_insert_rowid()
{RowVersionConflictClause};
""");
@@ -72,8 +82,9 @@ internal static class TriggerSqlGenerator
zb_hlc_next(),
(SELECT node_id FROM __localdb_meta WHERE id = 1),
0);
INSERT OR REPLACE INTO __localdb_row_version (table_name, pk_json, hlc, node_id, is_tombstone, tombstone_utc)
SELECT table_name, pk_json, hlc, node_id, 0, NULL FROM __localdb_oplog WHERE seq = last_insert_rowid();
INSERT INTO __localdb_row_version (table_name, pk_json, hlc, node_id, is_tombstone, tombstone_utc)
SELECT table_name, pk_json, hlc, node_id, 0, NULL FROM __localdb_oplog WHERE seq = last_insert_rowid()
{RowVersionConflictClause};
""";
@@ -92,9 +103,10 @@ internal static class TriggerSqlGenerator
(SELECT node_id FROM __localdb_meta WHERE id = 1),
1
WHERE {changed};
INSERT OR REPLACE INTO __localdb_row_version (table_name, pk_json, hlc, node_id, is_tombstone, tombstone_utc)
INSERT INTO __localdb_row_version (table_name, pk_json, hlc, node_id, is_tombstone, tombstone_utc)
SELECT table_name, pk_json, hlc, node_id, {TombstoneTail} FROM __localdb_oplog
WHERE seq = last_insert_rowid() AND ({changed});
WHERE seq = last_insert_rowid() AND ({changed})
{RowVersionConflictClause};
""";
}