-
-
Notifications
You must be signed in to change notification settings - Fork 200
Open
Description
As per this discussion: #1372, it was discovered that the SqliteSyncProvider will always report conflicts when:
- A row exists in a client SQLite DB
- The row is updated elsewhere and synced to the client
- There is no _tracking entry for the row
With much digging and AI assistance the faulty code was identified:
Dotmim.Sync/Projects/Dotmim.Sync.Sqlite/Builders/SQLiteObjectNames.cs
Lines 358 to 361 in f4378ba
| stringBuilder.AppendLine($"WHERE ([side].[timestamp] < @sync_min_timestamp OR [side].[update_scope_id] = @sync_scope_id) "); | |
| stringBuilder.Append($"OR ({SqliteManagementUtils.WhereColumnIsNull(this.TableDescription.PrimaryKeys, "[base]")} "); | |
| stringBuilder.AppendLine($"AND ([side].[timestamp] < @sync_min_timestamp OR [side].[timestamp] IS NULL)) "); | |
| stringBuilder.Append($"OR @sync_force_write = 1"); |
This creates a WHERE clause:
LEFT JOIN [Row_tracking] AS [side] ON [side].[Id] = @Id
LEFT JOIN [Row] AS [base] ON [base].[Id] = @Id
WHERE ([side].[timestamp] <= @sync_min_timestamp OR [side].[update_scope_id] = @sync_scope_id)
OR ([base].[Id] IS NULL AND ([side].[timestamp] <= @sync_min_timestamp OR [side].[timestamp] IS NULL)) Which has the following issue:
Scenario A: The Row DOES NOT exist (New Record)
If [base].[Id] is NULL (it's a new insert):
[base].[Id] IS NULL is TRUE.
Inside the inner parentheses:
[side].[timestamp] <= ... is FALSE.
[side].[timestamp] IS NULL is TRUE (Because there is no tracking row).
The logic becomes: TRUE AND (FALSE OR TRUE) -> TRUE.
Outcome: The row IS selected. The sync proceeds to insert the new row.
Scenario B: The Row ALREADY exists
If [base].[Id] is NOT NULL (the row is there):
[base].[Id] IS NULL is FALSE.
The AND condition immediately fails.
Outcome: The row IS NOT selected. The sync skips this update (unless the @sync_force_write flag from the wider query was set to 1).
This is resolved by modifying the SQL builder to handle cases correctly:
// 1. Standard Update: Only allow if row exists (NOT NULL) AND Tracking conditions are met
// We verify [base] is NOT NULL to ensure we don't implicitly resurrect a tombstoned row here.
stringBuilder.Append($"WHERE (NOT ({SqliteManagementUtils.WhereColumnIsNull(this.TableDescription.PrimaryKeys, "[base]")}) ");
stringBuilder.AppendLine($"AND ([side].[timestamp] <= @sync_min_timestamp OR [side].[update_scope_id] = @sync_scope_id)) ");
// 2. Missing Tracking: Allow write if Tracking row is missing (New Inserts or Data Update)
stringBuilder.AppendLine($"OR ({SqliteManagementUtils.WhereColumnIsNull(this.TableDescription.PrimaryKeys, "[side]")}) ");
// 3. Force Write
stringBuilder.Append($"OR @sync_force_write = 1");Metadata
Metadata
Assignees
Labels
No labels