From b0ea8acce4cc2e6444c42f6e8359377b1aca0f61 Mon Sep 17 00:00:00 2001 From: Ralf Kistner Date: Fri, 29 Dec 2023 14:09:39 +0200 Subject: [PATCH 1/2] Add failing test. --- .../powersync/test/bucket_storage_test.dart | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/packages/powersync/test/bucket_storage_test.dart b/packages/powersync/test/bucket_storage_test.dart index 8d4f6e79..aad21265 100644 --- a/packages/powersync/test/bucket_storage_test.dart +++ b/packages/powersync/test/bucket_storage_test.dart @@ -674,5 +674,118 @@ void main() { {'description': 'server updated'} ])); }); + + test('should revert a failing update', () async { + await bucketStorage.saveSyncData(SyncDataBatch([ + SyncBucketData( + bucket: 'bucket1', + data: [putAsset1_1, putAsset2_2, putAsset1_3], + ), + ])); + + await syncLocalChecked(Checkpoint( + lastOpId: '3', + writeCheckpoint: '3', + checksums: [BucketChecksum(bucket: 'bucket1', checksum: 6)])); + + // Local save + db.execute('INSERT INTO assets(id, description) VALUES(?, ?)', + ['O3', 'inserted']); + final batch = bucketStorage.getCrudBatch(); + await batch!.complete(); + await bucketStorage.updateLocalTarget(() async { + return '4'; + }); + + expect( + db.select('SELECT description FROM assets WHERE id = \'O3\''), + equals([ + {'description': 'inserted'} + ])); + + await syncLocalChecked(Checkpoint( + lastOpId: '3', + writeCheckpoint: '4', + checksums: [BucketChecksum(bucket: 'bucket1', checksum: 6)])); + + expect(db.select('SELECT description FROM assets WHERE id = \'O3\''), + equals([])); + }); + + test('should revert a failing delete', () async { + await bucketStorage.saveSyncData(SyncDataBatch([ + SyncBucketData( + bucket: 'bucket1', + data: [putAsset1_1, putAsset2_2, putAsset1_3], + ), + ])); + + await syncLocalChecked(Checkpoint( + lastOpId: '3', + writeCheckpoint: '3', + checksums: [BucketChecksum(bucket: 'bucket1', checksum: 6)])); + + // Local save + db.execute('DELETE FROM assets WHERE id = ?', ['O2']); + + expect(db.select('SELECT description FROM assets WHERE id = \'O2\''), + equals([])); + // Simulate a permissions error when uploading - data should be preserved. + final batch = bucketStorage.getCrudBatch(); + await batch!.complete(); + + await bucketStorage.updateLocalTarget(() async { + return '4'; + }); + + await syncLocalChecked(Checkpoint( + lastOpId: '3', + writeCheckpoint: '4', + checksums: [BucketChecksum(bucket: 'bucket1', checksum: 6)])); + + expect( + db.select('SELECT description FROM assets WHERE id = \'O2\''), + equals([ + {'description': 'bar'} + ])); + }); + + test('should revert a failing insert', () async { + await bucketStorage.saveSyncData(SyncDataBatch([ + SyncBucketData( + bucket: 'bucket1', + data: [putAsset1_1, putAsset2_2, putAsset1_3], + ), + ])); + + await syncLocalChecked(Checkpoint( + lastOpId: '3', + writeCheckpoint: '3', + checksums: [BucketChecksum(bucket: 'bucket1', checksum: 6)])); + + // Local save + db.execute('DELETE FROM assets WHERE id = ?', ['O2']); + + expect(db.select('SELECT description FROM assets WHERE id = \'O2\''), + equals([])); + // Simulate a permissions error when uploading - data should be preserved. + final batch = bucketStorage.getCrudBatch(); + await batch!.complete(); + + await bucketStorage.updateLocalTarget(() async { + return '4'; + }); + + await syncLocalChecked(Checkpoint( + lastOpId: '3', + writeCheckpoint: '4', + checksums: [BucketChecksum(bucket: 'bucket1', checksum: 6)])); + + expect( + db.select('SELECT description FROM assets WHERE id = \'O2\''), + equals([ + {'description': 'bar'} + ])); + }); }); } From 71613af90fb52ed423abe1a4b9d60124b58beb8d Mon Sep 17 00:00:00 2001 From: Ralf Kistner Date: Fri, 29 Dec 2023 14:13:16 +0200 Subject: [PATCH 2/2] Fix reverting deletes. --- packages/powersync/lib/src/schema_logic.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/powersync/lib/src/schema_logic.dart b/packages/powersync/lib/src/schema_logic.dart index d8b9b8b0..af4a4d4d 100644 --- a/packages/powersync/lib/src/schema_logic.dart +++ b/packages/powersync/lib/src/schema_logic.dart @@ -53,6 +53,15 @@ FOR EACH ROW BEGIN DELETE FROM $internalNameE WHERE id = OLD.id; INSERT INTO ps_crud(tx_id, data) SELECT current_tx, json_object('op', 'DELETE', 'type', ${quoteString(type)}, 'id', OLD.id) FROM ps_tx WHERE id = 1; + INSERT INTO ps_oplog(bucket, op_id, op, row_type, row_id, hash, superseded) + SELECT '\$local', + 1, + 'REMOVE', + ${quoteString(type)}, + OLD.id, + 0, + 0; + INSERT OR REPLACE INTO ps_buckets(name, pending_delete, last_op, target_op) VALUES('\$local', 1, 0, $maxOpId); END""", """ CREATE TRIGGER ${quoteIdentifier('ps_view_insert_$viewName')}