Skip to content

Commit 8d088f3

Browse files
committed
Fix duplicate entries in ps_sync_state
1 parent 56f37c8 commit 8d088f3

File tree

8 files changed

+220
-40
lines changed

8 files changed

+220
-40
lines changed

crates/core/src/migrations.rs

+21
Original file line numberDiff line numberDiff line change
@@ -333,5 +333,26 @@ json_object('sql', 'DELETE FROM ps_migration WHERE id >= 7')
333333
local_db.exec_safe(&stmt).into_db_result(local_db)?;
334334
}
335335

336+
if current_version < 8 && target_version >= 8 {
337+
let stmt = "\
338+
ALTER TABLE ps_sync_state RENAME TO ps_sync_state_old;
339+
CREATE TABLE ps_sync_state (
340+
priority INTEGER NOT NULL PRIMARY KEY,
341+
last_synced_at TEXT NOT NULL
342+
) STRICT;
343+
INSERT INTO ps_sync_state (priority, last_synced_at)
344+
SELECT priority, MAX(last_synced_at) FROM ps_sync_state_old GROUP BY priority;
345+
DROP TABLE ps_sync_state_old;
346+
INSERT INTO ps_migration(id, down_migrations) VALUES(8, json_array(
347+
json_object('sql', 'ALTER TABLE ps_sync_state RENAME TO ps_sync_state_new'),
348+
json_object('sql', 'CREATE TABLE ps_sync_state (\n priority INTEGER NOT NULL,\n last_synced_at TEXT NOT NULL\n) STRICT'),
349+
json_object('sql', 'INSERT INTO ps_sync_state SELECT * FROM ps_sync_state_new'),
350+
json_object('sql', 'DROP TABLE ps_sync_state_new'),
351+
json_object('sql', 'DELETE FROM ps_migration WHERE id >= 8')
352+
));
353+
";
354+
local_db.exec_safe(&stmt).into_db_result(local_db)?;
355+
}
356+
336357
Ok(())
337358
}

crates/core/src/view_admin.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ fn powersync_init_impl(
120120

121121
setup_internal_views(local_db)?;
122122

123-
powersync_migrate(ctx, 7)?;
123+
powersync_migrate(ctx, 8)?;
124124

125125
Ok(String::from(""))
126126
}

dart/pubspec.lock

+44-33
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,18 @@ packages:
55
dependency: transitive
66
description:
77
name: _fe_analyzer_shared
8-
sha256: "03f6da266a27a4538a69295ec142cb5717d7d4e5727b84658b63e1e1509bac9c"
8+
sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57
99
url: "https://pub.dev"
1010
source: hosted
11-
version: "79.0.0"
12-
_macros:
13-
dependency: transitive
14-
description: dart
15-
source: sdk
16-
version: "0.3.3"
11+
version: "80.0.0"
1712
analyzer:
1813
dependency: transitive
1914
description:
2015
name: analyzer
21-
sha256: c9040fc56483c22a5e04a9f6a251313118b1a3c42423770623128fa484115643
16+
sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e"
2217
url: "https://pub.dev"
2318
source: hosted
24-
version: "7.2.0"
19+
version: "7.3.0"
2520
args:
2621
dependency: transitive
2722
description:
@@ -34,10 +29,10 @@ packages:
3429
dependency: transitive
3530
description:
3631
name: async
37-
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
32+
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
3833
url: "https://pub.dev"
3934
source: hosted
40-
version: "2.12.0"
35+
version: "2.13.0"
4136
boolean_selector:
4237
dependency: transitive
4338
description:
@@ -46,6 +41,14 @@ packages:
4641
url: "https://pub.dev"
4742
source: hosted
4843
version: "2.1.2"
44+
clock:
45+
dependency: transitive
46+
description:
47+
name: clock
48+
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
49+
url: "https://pub.dev"
50+
source: hosted
51+
version: "1.1.2"
4952
collection:
5053
dependency: transitive
5154
description:
@@ -78,16 +81,24 @@ packages:
7881
url: "https://pub.dev"
7982
source: hosted
8083
version: "3.0.6"
84+
fake_async:
85+
dependency: "direct dev"
86+
description:
87+
name: fake_async
88+
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
89+
url: "https://pub.dev"
90+
source: hosted
91+
version: "1.3.3"
8192
ffi:
8293
dependency: transitive
8394
description:
8495
name: ffi
85-
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
96+
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
8697
url: "https://pub.dev"
8798
source: hosted
88-
version: "2.1.3"
99+
version: "2.1.4"
89100
file:
90-
dependency: transitive
101+
dependency: "direct dev"
91102
description:
92103
name: file
93104
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
@@ -138,10 +149,10 @@ packages:
138149
dependency: transitive
139150
description:
140151
name: js
141-
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
152+
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
142153
url: "https://pub.dev"
143154
source: hosted
144-
version: "0.7.1"
155+
version: "0.7.2"
145156
logging:
146157
dependency: transitive
147158
description:
@@ -150,14 +161,6 @@ packages:
150161
url: "https://pub.dev"
151162
source: hosted
152163
version: "1.3.0"
153-
macros:
154-
dependency: transitive
155-
description:
156-
name: macros
157-
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
158-
url: "https://pub.dev"
159-
source: hosted
160-
version: "0.1.3-main.0"
161164
matcher:
162165
dependency: transitive
163166
description:
@@ -250,10 +253,10 @@ packages:
250253
dependency: transitive
251254
description:
252255
name: shelf_web_socket
253-
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
256+
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
254257
url: "https://pub.dev"
255258
source: hosted
256-
version: "2.0.1"
259+
version: "3.0.0"
257260
source_map_stack_trace:
258261
dependency: transitive
259262
description:
@@ -282,10 +285,18 @@ packages:
282285
dependency: "direct main"
283286
description:
284287
name: sqlite3
285-
sha256: "35d3726fe18ab1463403a5cc8d97dbc81f2a0b08082e8173851363fcc97b6627"
288+
sha256: "32b632dda27d664f85520093ed6f735ae5c49b5b75345afb8b19411bc59bb53d"
286289
url: "https://pub.dev"
287290
source: hosted
288-
version: "2.7.2"
291+
version: "2.7.4"
292+
sqlite3_test:
293+
dependency: "direct dev"
294+
description:
295+
name: sqlite3_test
296+
sha256: "0b6f76541385cbe0cebf9454854f78dc9aa18b8cb512d8e597e63385e61d4f45"
297+
url: "https://pub.dev"
298+
source: hosted
299+
version: "0.1.1"
289300
stack_trace:
290301
dependency: transitive
291302
description:
@@ -322,10 +333,10 @@ packages:
322333
dependency: "direct dev"
323334
description:
324335
name: test
325-
sha256: "8391fbe68d520daf2314121764d38e37f934c02fd7301ad18307bd93bd6b725d"
336+
sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e"
326337
url: "https://pub.dev"
327338
source: hosted
328-
version: "1.25.14"
339+
version: "1.25.15"
329340
test_api:
330341
dependency: transitive
331342
description:
@@ -370,10 +381,10 @@ packages:
370381
dependency: transitive
371382
description:
372383
name: web
373-
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
384+
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
374385
url: "https://pub.dev"
375386
source: hosted
376-
version: "1.1.0"
387+
version: "1.1.1"
377388
web_socket:
378389
dependency: transitive
379390
description:
@@ -407,4 +418,4 @@ packages:
407418
source: hosted
408419
version: "3.1.3"
409420
sdks:
410-
dart: ">=3.5.0 <4.0.0"
421+
dart: ">=3.7.0 <4.0.0"

dart/pubspec.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ dependencies:
88
sqlite3: ^2.4.5
99
dev_dependencies:
1010
test: ^1.25.0
11+
file: ^7.0.1
12+
sqlite3_test: ^0.1.1
13+
fake_async: ^1.3.3

dart/test/migration_test.dart

+29
Original file line numberDiff line numberDiff line change
@@ -246,5 +246,34 @@ ${fixtures.schema5.trim()}
246246
end''';
247247
expect(schema, equals(expected));
248248
});
249+
250+
test('schema 7 -> 8 migrates last_synced_at data', () {
251+
db.execute(fixtures.expectedState[7]!);
252+
253+
for (var i = 0; i < 10; i++) {
254+
db.execute(
255+
'INSERT OR REPLACE INTO ps_sync_state (priority, last_synced_at) VALUES (?, ?);',
256+
[2147483647, '2025-03-05 14:58:${i.toString().padLeft(2, '0')}'],
257+
);
258+
259+
db.execute(
260+
'INSERT OR REPLACE INTO ps_sync_state (priority, last_synced_at) VALUES (?, ?);',
261+
[3, '2025-03-05 13:58:${i.toString().padLeft(2, '0')}'],
262+
);
263+
}
264+
265+
db.execute('SELECT powersync_test_migration(8);');
266+
267+
expect(db.select('SELECT * FROM ps_sync_state ORDER BY priority'), [
268+
{
269+
'priority': 3,
270+
'last_synced_at': '2025-03-05 13:58:09',
271+
},
272+
{
273+
'priority': 2147483647,
274+
'last_synced_at': '2025-03-05 14:58:09',
275+
}
276+
]);
277+
});
249278
});
250279
}

dart/test/sync_test.dart

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
import 'dart:convert';
22

3+
import 'package:fake_async/fake_async.dart';
4+
import 'package:file/local.dart';
35
import 'package:sqlite3/common.dart';
6+
import 'package:sqlite3/sqlite3.dart';
7+
import 'package:sqlite3_test/sqlite3_test.dart';
48
import 'package:test/test.dart';
59

610
import 'utils/native_test_utils.dart';
711

812
void main() {
13+
final vfs = TestSqliteFileSystem(fs: const LocalFileSystem());
14+
15+
setUpAll(() {
16+
loadExtension();
17+
sqlite3.registerVirtualFileSystem(vfs, makeDefault: false);
18+
});
19+
tearDownAll(() => sqlite3.unregisterVirtualFileSystem(vfs));
20+
921
group('sync tests', () {
1022
late CommonDatabase db;
1123

1224
setUp(() async {
13-
db = openTestDatabase()
25+
db = openTestDatabase(vfs)
1426
..select('select powersync_init();')
1527
..select('select powersync_replace_schema(?)', [json.encode(_schema)]);
1628
});
@@ -187,6 +199,38 @@ void main() {
187199
}
188200
});
189201

202+
test('can sync multiple times', () {
203+
fakeAsync((controller) {
204+
for (var i = 0; i < 10; i++) {
205+
for (var prio in const [1, 2, 3, null]) {
206+
pushCheckpointComplete('1', null, [], priority: prio);
207+
208+
// Make sure there's only a single row in last_synced_at
209+
expect(
210+
db.select(
211+
"SELECT datetime(last_synced_at, 'localtime') AS last_synced_at FROM ps_sync_state WHERE priority = ?",
212+
[prio ?? 2147483647]),
213+
[
214+
{'last_synced_at': '2025-03-01 ${10 + i}:00:00'}
215+
],
216+
);
217+
218+
if (prio == null) {
219+
expect(
220+
db.select(
221+
"SELECT datetime(powersync_last_synced_at(), 'localtime') AS last_synced_at"),
222+
[
223+
{'last_synced_at': '2025-03-01 ${10 + i}:00:00'}
224+
],
225+
);
226+
}
227+
}
228+
229+
controller.elapse(const Duration(hours: 1));
230+
}
231+
}, initialTime: DateTime(2025, 3, 1, 10));
232+
});
233+
190234
test('clearing database clears sync status', () {
191235
pushSyncData('prio1', '1', 'row-0', 'PUT', {'col': 'hi'});
192236

0 commit comments

Comments
 (0)