Skip to content

Commit 6241a18

Browse files
committed
Add priority field
1 parent 1fa7caa commit 6241a18

File tree

3 files changed

+102
-34
lines changed

3 files changed

+102
-34
lines changed

packages/powersync_core/lib/src/bucket_storage.dart

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,14 @@ class BucketStorage {
3333

3434
Future<List<BucketState>> getBucketStates() async {
3535
final rows = await select(
36-
'SELECT name as bucket, cast(last_op as TEXT) as op_id FROM ps_buckets WHERE pending_delete = 0 AND name != \'\$local\'');
36+
'SELECT name as bucket, priority, cast(last_op as TEXT) as op_id FROM ps_buckets WHERE pending_delete = 0 AND name != \'\$local\'');
3737
return [
3838
for (var row in rows)
39-
BucketState(bucket: row['bucket'], opId: row['op_id'])
39+
BucketState(
40+
bucket: row['bucket'],
41+
priority: row['priority'],
42+
opId: row['op_id'],
43+
)
4044
];
4145
}
4246

@@ -100,8 +104,8 @@ class BucketStorage {
100104
return false;
101105
}
102106

103-
Future<SyncLocalDatabaseResult> syncLocalDatabase(
104-
Checkpoint checkpoint) async {
107+
Future<SyncLocalDatabaseResult> syncLocalDatabase(Checkpoint checkpoint,
108+
{int? forPriority}) async {
105109
final r = await validateChecksums(checkpoint);
106110

107111
if (!r.checkpointValid) {
@@ -124,7 +128,7 @@ class BucketStorage {
124128
// Not flushing here - the flush will happen in the next step
125129
}, flush: false);
126130

127-
final valid = await updateObjectsFromBuckets(checkpoint);
131+
final valid = await updateObjectsFromBuckets(forPriority: forPriority);
128132
if (!valid) {
129133
return SyncLocalDatabaseResult(ready: false);
130134
}
@@ -134,11 +138,11 @@ class BucketStorage {
134138
return SyncLocalDatabaseResult(ready: true);
135139
}
136140

137-
Future<bool> updateObjectsFromBuckets(Checkpoint checkpoint) async {
141+
Future<bool> updateObjectsFromBuckets({int? forPriority}) async {
138142
return writeTransaction((tx) async {
139143
await tx.execute(
140144
"INSERT INTO powersync_operations(op, data) VALUES(?, ?)",
141-
['sync_local', '']);
145+
['sync_local', forPriority]);
142146
final rs = await tx.execute('SELECT last_insert_rowid() as result');
143147
final result = rs[0]['result'];
144148
if (result == 1) {
@@ -321,9 +325,11 @@ class BucketStorage {
321325

322326
class BucketState {
323327
final String bucket;
328+
final int priority;
324329
final String opId;
325330

326-
const BucketState({required this.bucket, required this.opId});
331+
const BucketState(
332+
{required this.bucket, required this.priority, required this.opId});
327333

328334
@override
329335
String toString() {
@@ -332,17 +338,20 @@ class BucketState {
332338

333339
@override
334340
int get hashCode {
335-
return Object.hash(bucket, opId);
341+
return Object.hash(bucket, priority, opId);
336342
}
337343

338344
@override
339345
bool operator ==(Object other) {
340-
return other is BucketState && other.bucket == bucket && other.opId == opId;
346+
return other is BucketState &&
347+
other.priority == priority &&
348+
other.bucket == bucket &&
349+
other.opId == opId;
341350
}
342351
}
343352

344-
class SyncDataBatch {
345-
List<SyncBucketData> buckets;
353+
final class SyncDataBatch {
354+
final List<SyncBucketData> buckets;
346355

347356
SyncDataBatch(this.buckets);
348357
}

packages/powersync_core/lib/src/streaming_sync.dart

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -302,29 +302,36 @@ class StreamingSyncImplementation implements StreamingSync {
302302
_statusStreamController.add(newStatus);
303303
}
304304

305-
Future<bool> streamingSyncIteration(
306-
{AbortController? abortController}) async {
307-
adapter.startSession();
305+
Future<(List<BucketRequest>, Map<String, BucketDescription>)>
306+
_collectLocalBucketState() async {
308307
final bucketEntries = await adapter.getBucketStates();
309308

310-
Map<String, String> initialBucketStates = {};
309+
final initialRequests = [
310+
for (final entry in bucketEntries) BucketRequest(entry.bucket, entry.opId)
311+
];
312+
final localDescriptions = {
313+
for (final entry in bucketEntries)
314+
entry.bucket: (
315+
name: entry.bucket,
316+
priority: entry.priority,
317+
)
318+
};
311319

312-
for (final entry in bucketEntries) {
313-
initialBucketStates[entry.bucket] = entry.opId;
314-
}
320+
return (initialRequests, localDescriptions);
321+
}
315322

316-
final List<BucketRequest> buckets = [];
317-
for (var entry in initialBucketStates.entries) {
318-
buckets.add(BucketRequest(entry.key, entry.value));
319-
}
323+
Future<bool> streamingSyncIteration(
324+
{AbortController? abortController}) async {
325+
adapter.startSession();
326+
327+
var (bucketRequests, bucketMap) = await _collectLocalBucketState();
320328

321329
Checkpoint? targetCheckpoint;
322330
Checkpoint? validatedCheckpoint;
323331
Checkpoint? appliedCheckpoint;
324-
var bucketSet = Set<String>.from(initialBucketStates.keys);
325332

326333
var requestStream = streamingSyncRequest(
327-
StreamingSyncRequest(buckets, syncParameters, clientId!));
334+
StreamingSyncRequest(bucketRequests, syncParameters, clientId!));
328335

329336
var merged = addBroadcast(requestStream, _localPingController.stream);
330337

@@ -343,13 +350,16 @@ class StreamingSyncImplementation implements StreamingSync {
343350
switch (line) {
344351
case Checkpoint():
345352
targetCheckpoint = line;
346-
final Set<String> bucketsToDelete = {...bucketSet};
347-
final Set<String> newBuckets = {};
353+
final Set<String> bucketsToDelete = {...bucketMap.keys};
354+
final Map<String, BucketDescription> newBuckets = {};
348355
for (final checksum in line.checksums) {
349-
newBuckets.add(checksum.bucket);
356+
newBuckets[checksum.bucket] = (
357+
name: checksum.bucket,
358+
priority: checksum.priority,
359+
);
350360
bucketsToDelete.remove(checksum.bucket);
351361
}
352-
bucketSet = newBuckets;
362+
bucketMap = newBuckets;
353363
await adapter.removeBuckets([...bucketsToDelete]);
354364
_updateStatus(downloading: true);
355365
case StreamingSyncCheckpointComplete():
@@ -371,6 +381,27 @@ class StreamingSyncImplementation implements StreamingSync {
371381
lastSyncedAt: DateTime.now());
372382
}
373383

384+
validatedCheckpoint = targetCheckpoint;
385+
case StreamingSyncCheckpointPartiallyComplete(:final bucketPriority):
386+
final result = await adapter.syncLocalDatabase(targetCheckpoint!,
387+
forPriority: bucketPriority);
388+
if (!result.checkpointValid) {
389+
// This means checksums failed. Start again with a new checkpoint.
390+
// TODO: better back-off
391+
// await new Promise((resolve) => setTimeout(resolve, 50));
392+
return false;
393+
} else if (!result.ready) {
394+
// Checksums valid, but need more data for a consistent checkpoint.
395+
// Continue waiting.
396+
} else {
397+
appliedCheckpoint = targetCheckpoint;
398+
399+
_updateStatus(
400+
downloading: false,
401+
downloadError: _noError,
402+
lastSyncedAt: DateTime.now());
403+
}
404+
374405
validatedCheckpoint = targetCheckpoint;
375406
case StreamingSyncCheckpointDiff():
376407
// TODO: It may be faster to just keep track of the diff, instead of the entire checkpoint
@@ -397,7 +428,8 @@ class StreamingSyncImplementation implements StreamingSync {
397428
writeCheckpoint: diff.writeCheckpoint);
398429
targetCheckpoint = newCheckpoint;
399430

400-
bucketSet = Set.from(newBuckets.keys);
431+
bucketMap = newBuckets.map((name, checksum) =>
432+
MapEntry(name, (name: name, priority: checksum.priority)));
401433
await adapter.removeBuckets(diff.removedBuckets);
402434
adapter.setTargetCheckpoint(targetCheckpoint);
403435
case SyncBucketData():
@@ -424,6 +456,9 @@ class StreamingSyncImplementation implements StreamingSync {
424456
});
425457
}
426458
}
459+
case UnknownSyncLine(:final rawData):
460+
isolateLogger.fine('Ignoring unknown sync line: $rawData');
461+
break;
427462
case null: // Local ping
428463
if (targetCheckpoint == appliedCheckpoint) {
429464
_updateStatus(

packages/powersync_core/lib/src/sync_types.dart

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import 'dart:convert';
44
sealed class StreamingSyncLine {
55
const StreamingSyncLine();
66

7-
/// Parses a [StreamingSyncLine] from JSON, or return `null` if the line is
8-
/// not understood by this client.
9-
static StreamingSyncLine? fromJson(Map<String, dynamic> line) {
7+
/// Parses a [StreamingSyncLine] from JSON.
8+
static StreamingSyncLine fromJson(Map<String, dynamic> line) {
109
if (line.containsKey('checkpoint')) {
1110
return Checkpoint.fromJson(line['checkpoint']);
1211
} else if (line.containsKey('checkpoint_diff')) {
@@ -19,11 +18,18 @@ sealed class StreamingSyncLine {
1918
} else if (line.containsKey('token_expires_in')) {
2019
return StreamingSyncKeepalive.fromJson(line);
2120
} else {
22-
return null;
21+
return UnknownSyncLine(line);
2322
}
2423
}
2524
}
2625

26+
/// A message from the sync service that this client doesn't support.
27+
final class UnknownSyncLine implements StreamingSyncLine {
28+
final Map<String, dynamic> rawData;
29+
30+
const UnknownSyncLine(this.rawData);
31+
}
32+
2733
/// Indicates that a checkpoint is available, along with checksums for each
2834
/// bucket in the checkpoint.
2935
///
@@ -54,8 +60,11 @@ final class Checkpoint extends StreamingSyncLine {
5460
}
5561
}
5662

63+
typedef BucketDescription = ({String name, int priority});
64+
5765
class BucketChecksum {
5866
final String bucket;
67+
final int priority;
5968
final int checksum;
6069

6170
/// Count is informational only
@@ -64,12 +73,14 @@ class BucketChecksum {
6473

6574
const BucketChecksum(
6675
{required this.bucket,
76+
required this.priority,
6777
required this.checksum,
6878
this.count,
6979
this.lastOpId});
7080

7181
BucketChecksum.fromJson(Map<String, dynamic> json)
7282
: bucket = json['bucket'],
83+
priority = json['priority'],
7384
checksum = json['checksum'],
7485
count = json['count'],
7586
lastOpId = json['last_op_id'];
@@ -112,6 +123,19 @@ final class StreamingSyncCheckpointComplete extends StreamingSyncLine {
112123
: lastOpId = json['last_op_id'];
113124
}
114125

126+
/// Sent after all the [SyncBucketData] messages for a given priority within a
127+
/// checkpoint have been sent.
128+
final class StreamingSyncCheckpointPartiallyComplete extends StreamingSyncLine {
129+
String lastOpId;
130+
int bucketPriority;
131+
132+
StreamingSyncCheckpointPartiallyComplete(this.lastOpId, this.bucketPriority);
133+
134+
StreamingSyncCheckpointPartiallyComplete.fromJson(Map<String, dynamic> json)
135+
: lastOpId = json['last_op_id'],
136+
bucketPriority = json['priority'];
137+
}
138+
115139
/// Sent as a periodic ping to keep the connection alive and to notify the
116140
/// client about the remaining lifetime of the JWT.
117141
///

0 commit comments

Comments
 (0)