run the dedupe deletes before the inserts and keep only the last duplicate per payload

This commit is contained in:
Santo Shakil 2026-08-04 17:47:58 +06:00
parent 037b6238f2
commit c32426240c
2 changed files with 44 additions and 3 deletions

View file

@ -198,9 +198,15 @@ class SyncStreamRepository extends DriftDatabaseRepository {
Future<void> updateAssetsV1(Iterable<SyncAssetV1> data, {String debugLabel = 'user'}) async {
try {
await _db.batch((batch) {
// Keep only the last asset per partial-index key; server order is authoritative
final deduped = <(String, String, String?), SyncAssetV1>{};
for (final asset in data) {
// Avoid SqliteException(2067) when server re-issues a new id for
// the same (ownerId, checksum). #22522 #27186
deduped[(asset.ownerId, asset.checksum, asset.libraryId)] = asset;
}
// Avoid SqliteException(2067) when server re-issues a new id for
// the same (ownerId, checksum). #22522 #27186
for (final asset in deduped.values) {
_enqueueRemoteAssetDedupe(
batch,
id: asset.id,
@ -208,7 +214,9 @@ class SyncStreamRepository extends DriftDatabaseRepository {
checksum: asset.checksum,
libraryId: asset.libraryId,
);
}
for (final asset in deduped.values) {
final companion = RemoteAssetEntityCompanion(
name: Value(asset.originalFileName),
type: Value(asset.type.toAssetType()),
@ -247,8 +255,14 @@ class SyncStreamRepository extends DriftDatabaseRepository {
Future<void> updateAssetsV2(Iterable<SyncAssetV2> data, {String debugLabel = 'user'}) async {
try {
await _db.batch((batch) {
// Keep only the last asset per partial-index key; server order is authoritative
final deduped = <(String, String, String?), SyncAssetV2>{};
for (final asset in data) {
// See updateAssetsV1 for why this dedupe is required. #22522 #27186
deduped[(asset.ownerId, asset.checksum, asset.libraryId)] = asset;
}
// See updateAssetsV1 for why this dedupe is required. #22522 #27186
for (final asset in deduped.values) {
_enqueueRemoteAssetDedupe(
batch,
id: asset.id,
@ -256,7 +270,9 @@ class SyncStreamRepository extends DriftDatabaseRepository {
checksum: asset.checksum,
libraryId: asset.libraryId,
);
}
for (final asset in deduped.values) {
final companion = RemoteAssetEntityCompanion(
name: Value(asset.originalFileName),
type: Value(asset.type.toAssetType()),

View file

@ -349,5 +349,30 @@ void main() {
expect(rows, hasLength(1));
expect(rows.single.id, equals('new-id'));
});
test('one payload with duplicate (ownerId, checksum) and null library keeps the last asset', () async {
await sut.updateUsersV1([_createUser()]);
await sut.updateAssetsV1([
_createAsset(id: 'first-id', checksum: 'AAA', fileName: 'photo.jpg'),
_createAsset(id: 'last-id', checksum: 'AAA', fileName: 'photo.jpg'),
]);
final rows = await db.remoteAssetEntity.select().get();
expect(rows, hasLength(1));
expect(rows.single.id, equals('last-id'));
});
test('one payload with duplicate (ownerId, libraryId, checksum) keeps the last asset', () async {
await sut.updateUsersV1([_createUser()]);
await sut.updateAssetsV1([
_createAsset(id: 'first-id', checksum: 'AAA', fileName: 'photo.jpg', libraryId: 'lib-1'),
_createAsset(id: 'last-id', checksum: 'AAA', fileName: 'photo.jpg', libraryId: 'lib-1'),
]);
final rows = await db.remoteAssetEntity.select().get();
expect(rows, hasLength(1));
expect(rows.single.id, equals('last-id'));
expect(rows.single.libraryId, equals('lib-1'));
});
});
}