Skip to content

Commit b61f87c

Browse files
authored
Track download size in ps_buckets (#160)
1 parent e957857 commit b61f87c

File tree

8 files changed

+169
-20
lines changed

8 files changed

+169
-20
lines changed

crates/core/src/migrations.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::fix_data::apply_v035_fix;
1313
use crate::schema::inspection::ExistingView;
1414
use crate::sync::BucketPriority;
1515

16-
pub const LATEST_VERSION: i32 = 11;
16+
pub const LATEST_VERSION: i32 = 12;
1717

1818
pub fn powersync_migrate(
1919
ctx: *mut sqlite::context,
@@ -413,5 +413,16 @@ json_object('sql', 'DELETE FROM ps_migration WHERE id >= 11')
413413
local_db.exec_safe(stmt).into_db_result(local_db)?;
414414
}
415415

416+
if current_version < 12 && target_version >= 12 {
417+
let stmt = "\
418+
ALTER TABLE ps_buckets ADD COLUMN downloaded_size INTEGER NOT NULL DEFAULT 0;
419+
INSERT INTO ps_migration(id, down_migrations) VALUES(12, json_array(
420+
json_object('sql', 'ALTER TABLE ps_buckets DROP COLUMN downloaded_size'),
421+
json_object('sql', 'DELETE FROM ps_migration WHERE id >= 12')
422+
));
423+
";
424+
local_db.exec_safe(stmt).into_db_result(local_db)?;
425+
}
426+
416427
Ok(())
417428
}

crates/core/src/operations.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub fn insert_operation(db: *mut sqlite::sqlite3, data: &str) -> Result<(), Powe
2222
let adapter = StorageAdapter::new(db)?;
2323

2424
for line in &batch.buckets {
25-
insert_bucket_operations(&adapter, &line)?;
25+
insert_bucket_operations(&adapter, &line, 0)?;
2626
}
2727

2828
Ok(())

crates/core/src/sync/line.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use serde::Deserialize;
66
use serde::de::{Error, IgnoredAny, VariantAccess, Visitor};
77
use serde_with::{DisplayFromStr, serde_as};
88

9+
use crate::bson;
10+
use crate::error::PowerSyncError;
11+
912
use super::Checksum;
1013
use super::bucket_priority::BucketPriority;
1114

@@ -15,6 +18,49 @@ use super::bucket_priority::BucketPriority;
1518
/// internal copy).
1619
type SyncLineStr<'a> = Cow<'a, str>;
1720

21+
#[derive(Clone, Copy)]
22+
pub enum SyncLineSource<'a> {
23+
/// Sync lines that have been decoded from JSON.
24+
Text(&'a str),
25+
26+
/// Sync lines that have been decoded from BSON.
27+
Binary(&'a [u8]),
28+
}
29+
30+
impl<'a> SyncLineSource<'a> {
31+
pub fn len(&self) -> usize {
32+
match self {
33+
SyncLineSource::Text(text) => text.len(),
34+
SyncLineSource::Binary(binary) => binary.len(),
35+
}
36+
}
37+
}
38+
39+
pub struct SyncLineWithSource<'a> {
40+
pub source: SyncLineSource<'a>,
41+
pub line: SyncLine<'a>,
42+
}
43+
44+
impl<'a> SyncLineWithSource<'a> {
45+
pub fn from_text(source: &'a str) -> Result<Self, PowerSyncError> {
46+
let line = serde_json::from_str(source)
47+
.map_err(|e| PowerSyncError::sync_protocol_error("invalid text line", e))?;
48+
Ok(SyncLineWithSource {
49+
source: SyncLineSource::Text(source),
50+
line,
51+
})
52+
}
53+
54+
pub fn from_binary(source: &'a [u8]) -> Result<Self, PowerSyncError> {
55+
let line = bson::from_bytes(source)
56+
.map_err(|e| PowerSyncError::sync_protocol_error("invalid binary line", e))?;
57+
Ok(SyncLineWithSource {
58+
source: SyncLineSource::Binary(source),
59+
line,
60+
})
61+
}
62+
}
63+
1864
#[derive(Debug)]
1965

2066
pub enum SyncLine<'a> {

crates/core/src/sync/operations.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ use super::{
1616
storage_adapter::{BucketInfo, StorageAdapter},
1717
};
1818

19+
/// If known, `size` should be the size of the buffer from which data has been decoded in bytes.
1920
pub fn insert_bucket_operations(
2021
adapter: &StorageAdapter,
2122
data: &DataLine,
23+
size: usize,
2224
) -> Result<(), PowerSyncError> {
2325
let db = adapter.db;
2426
let BucketInfo {
@@ -194,14 +196,16 @@ WHERE bucket = ?1",
194196
SET last_op = ?2,
195197
add_checksum = (add_checksum + ?3) & 0xffffffff,
196198
op_checksum = (op_checksum + ?4) & 0xffffffff,
197-
count_since_last = count_since_last + ?5
199+
count_since_last = count_since_last + ?5,
200+
downloaded_size = downloaded_size + ?6
198201
WHERE id = ?1",
199202
)?;
200203
statement.bind_int64(1, bucket_id)?;
201204
statement.bind_int64(2, *last_op)?;
202205
statement.bind_int(3, add_checksum.bitcast_i32())?;
203206
statement.bind_int(4, op_checksum.bitcast_i32())?;
204207
statement.bind_int(5, added_ops)?;
208+
statement.bind_int64(6, size as i64)?;
205209

206210
statement.exec()?;
207211
}

crates/core/src/sync/streaming_sync.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use alloc::{
1818
use futures_lite::FutureExt;
1919

2020
use crate::{
21-
bson,
2221
error::{PowerSyncError, PowerSyncErrorCause},
2322
kv::client_id,
2423
state::DatabaseState,
@@ -28,7 +27,7 @@ use crate::{
2827
interface::{CloseSyncStream, StartSyncStream, StreamSubscriptionRequest},
2928
line::{
3029
BucketSubscriptionReason, DataLine, StreamDescription, StreamSubscriptionError,
31-
StreamSubscriptionErrorCause,
30+
StreamSubscriptionErrorCause, SyncLineWithSource,
3231
},
3332
subscriptions::LocallyTrackedSubscription,
3433
sync_status::{ActiveStreamSubscription, Timestamp},
@@ -268,8 +267,10 @@ impl StreamingSyncIteration {
268267
&self,
269268
target: &SyncTarget,
270269
event: &mut ActiveEvent,
271-
line: &'a SyncLine<'a>,
270+
line: &'a SyncLineWithSource<'a>,
272271
) -> Result<SyncStateMachineTransition<'a>, PowerSyncError> {
272+
let SyncLineWithSource { source, line } = line;
273+
273274
Ok(match line {
274275
SyncLine::Checkpoint(checkpoint) => {
275276
let (to_delete, updated_target) = target.track_checkpoint(&checkpoint);
@@ -386,7 +387,7 @@ impl StreamingSyncIteration {
386387
}
387388
}
388389
SyncLine::Data(data_line) => {
389-
insert_bucket_operations(&self.adapter, &data_line)?;
390+
insert_bucket_operations(&self.adapter, &data_line, source.len())?;
390391
SyncStateMachineTransition::DataLineSaved { line: data_line }
391392
}
392393
SyncLine::KeepAlive(token) => {
@@ -492,7 +493,7 @@ impl StreamingSyncIteration {
492493
&mut self,
493494
target: &mut SyncTarget,
494495
event: &mut ActiveEvent,
495-
line: &SyncLine,
496+
line: &SyncLineWithSource,
496497
) -> Result<Option<CloseSyncStream>, PowerSyncError> {
497498
let transition = self.prepare_handling_sync_line(target, event, line)?;
498499
Ok(self.apply_transition(target, event, transition))
@@ -506,7 +507,7 @@ impl StreamingSyncIteration {
506507
let hide_disconnect = loop {
507508
let event = Self::receive_event().await;
508509

509-
let line: SyncLine = match event.event {
510+
let line: SyncLineWithSource = match event.event {
510511
SyncEvent::Initialize { .. } => {
511512
panic!("Initialize should only be emited once")
512513
}
@@ -515,10 +516,8 @@ impl StreamingSyncIteration {
515516
.update(|s| s.disconnect(), &mut event.instructions);
516517
break false;
517518
}
518-
SyncEvent::TextLine { data } => serde_json::from_str(data)
519-
.map_err(|e| PowerSyncError::sync_protocol_error("invalid text line", e))?,
520-
SyncEvent::BinaryLine { data } => bson::from_bytes(data)
521-
.map_err(|e| PowerSyncError::sync_protocol_error("invalid binary line", e))?,
519+
SyncEvent::TextLine { data } => SyncLineWithSource::from_text(data)?,
520+
SyncEvent::BinaryLine { data } => SyncLineWithSource::from_binary(data)?,
522521
SyncEvent::UploadFinished => {
523522
self.try_applying_write_after_completed_upload(event)?;
524523

dart/test/sync_test.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,22 @@ CREATE TRIGGER users_ref_delete
13911391
db.dispose();
13921392
expect(vfs.openFiles, isZero);
13931393
});
1394+
1395+
test('tracks download size', () {
1396+
invokeControl('start', null);
1397+
pushCheckpoint(buckets: [bucketDescription('a', count: 2)]);
1398+
1399+
void expectDownloadSize(int size) {
1400+
final [row] = db.select('SELECT downloaded_size FROM ps_buckets');
1401+
expect(row[0], size);
1402+
}
1403+
1404+
pushSyncData('a', '1', 'row-0', 'PUT', {'col': 'hi'});
1405+
expectDownloadSize(isBson ? 185 : 186);
1406+
1407+
pushSyncData('a', '1', 'row-1', 'PUT', {'col': 'hi again'});
1408+
expectDownloadSize(isBson ? 376 : 378);
1409+
});
13941410
}
13951411

13961412
final priorityBuckets = [

dart/test/utils/fix_035_fixtures.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ const dataBroken = '''
1818

1919
/// Data after applying the migration fix, but before sync_local
2020
const dataMigrated = '''
21-
;INSERT INTO ps_buckets(id, name, last_applied_op, last_op, target_op, add_checksum, op_checksum, pending_delete, count_at_last, count_since_last) VALUES
22-
(1, 'b1', 0, 0, 0, 0, 120, 0, 0, 0),
23-
(2, 'b2', 0, 0, 0, 0, 3, 0, 0, 0)
21+
;INSERT INTO ps_buckets(id, name, last_applied_op, last_op, target_op, add_checksum, op_checksum, pending_delete, count_at_last, count_since_last, downloaded_size) VALUES
22+
(1, 'b1', 0, 0, 0, 0, 120, 0, 0, 0, 0),
23+
(2, 'b2', 0, 0, 0, 0, 3, 0, 0, 0, 0)
2424
;INSERT INTO ps_oplog(bucket, op_id, row_type, row_id, key, data, hash) VALUES
2525
(1, 1, 'todos', 't1', '', '{}', 100),
2626
(1, 2, 'todos', 't2', '', '{}', 20),
@@ -39,9 +39,9 @@ const dataMigrated = '''
3939

4040
/// Data after applying the migration fix and sync_local
4141
const dataFixed = '''
42-
;INSERT INTO ps_buckets(id, name, last_applied_op, last_op, target_op, add_checksum, op_checksum, pending_delete, count_at_last, count_since_last) VALUES
43-
(1, 'b1', 0, 0, 0, 0, 120, 0, 0, 0),
44-
(2, 'b2', 0, 0, 0, 0, 3, 0, 0, 0)
42+
;INSERT INTO ps_buckets(id, name, last_applied_op, last_op, target_op, add_checksum, op_checksum, pending_delete, count_at_last, count_since_last, downloaded_size) VALUES
43+
(1, 'b1', 0, 0, 0, 0, 120, 0, 0, 0, 0),
44+
(2, 'b2', 0, 0, 0, 0, 3, 0, 0, 0, 0)
4545
;INSERT INTO ps_oplog(bucket, op_id, row_type, row_id, key, data, hash) VALUES
4646
(1, 1, 'todos', 't1', '', '{}', 100),
4747
(1, 2, 'todos', 't2', '', '{}', 20),

dart/test/utils/migration_fixtures.dart

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// The current database version
2-
const databaseVersion = 11;
2+
const databaseVersion = 12;
33

44
/// This is the base database state that we expect at various schema versions.
55
/// Generated by loading the specific library version, and exporting the schema.
@@ -414,6 +414,67 @@ const expectedState = <int, String>{
414414
;INSERT INTO ps_migration(id, down_migrations) VALUES(9, '[{"sql":"ALTER TABLE ps_buckets DROP COLUMN count_at_last"},{"sql":"ALTER TABLE ps_buckets DROP COLUMN count_since_last"},{"sql":"DELETE FROM ps_migration WHERE id >= 9"}]')
415415
;INSERT INTO ps_migration(id, down_migrations) VALUES(10, '[{"sql":"SELECT powersync_drop_view(view.name)\n FROM sqlite_master view\n WHERE view.type = ''view''\n AND view.sql GLOB ''*-- powersync-auto-generated''"},{"sql":"DELETE FROM ps_migration WHERE id >= 10"}]')
416416
;INSERT INTO ps_migration(id, down_migrations) VALUES(11, '[{"sql":"DROP TABLE ps_stream_subscriptions"},{"sql":"DELETE FROM ps_migration WHERE id >= 11"}]')
417+
''',
418+
12: r'''
419+
;CREATE TABLE ps_buckets(
420+
id INTEGER PRIMARY KEY,
421+
name TEXT NOT NULL,
422+
last_applied_op INTEGER NOT NULL DEFAULT 0,
423+
last_op INTEGER NOT NULL DEFAULT 0,
424+
target_op INTEGER NOT NULL DEFAULT 0,
425+
add_checksum INTEGER NOT NULL DEFAULT 0,
426+
op_checksum INTEGER NOT NULL DEFAULT 0,
427+
pending_delete INTEGER NOT NULL DEFAULT 0
428+
, count_at_last INTEGER NOT NULL DEFAULT 0, count_since_last INTEGER NOT NULL DEFAULT 0, downloaded_size INTEGER NOT NULL DEFAULT 0) STRICT
429+
;CREATE TABLE ps_crud (id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT, tx_id INTEGER)
430+
;CREATE TABLE ps_kv(key TEXT PRIMARY KEY NOT NULL, value BLOB)
431+
;CREATE TABLE ps_migration(id INTEGER PRIMARY KEY, down_migrations TEXT)
432+
;CREATE TABLE ps_oplog(
433+
bucket INTEGER NOT NULL,
434+
op_id INTEGER NOT NULL,
435+
row_type TEXT,
436+
row_id TEXT,
437+
key TEXT,
438+
data TEXT,
439+
hash INTEGER NOT NULL) STRICT
440+
;CREATE TABLE ps_stream_subscriptions (
441+
id INTEGER NOT NULL PRIMARY KEY,
442+
stream_name TEXT NOT NULL,
443+
active INTEGER NOT NULL DEFAULT FALSE,
444+
is_default INTEGER NOT NULL DEFAULT FALSE,
445+
local_priority INTEGER,
446+
local_params TEXT NOT NULL DEFAULT 'null',
447+
ttl INTEGER,
448+
expires_at INTEGER,
449+
last_synced_at INTEGER,
450+
UNIQUE (stream_name, local_params)
451+
) STRICT
452+
;CREATE TABLE ps_sync_state (
453+
priority INTEGER NOT NULL PRIMARY KEY,
454+
last_synced_at TEXT NOT NULL
455+
) STRICT
456+
;CREATE TABLE ps_tx(id INTEGER PRIMARY KEY NOT NULL, current_tx INTEGER, next_tx INTEGER)
457+
;CREATE TABLE ps_untyped(type TEXT NOT NULL, id TEXT NOT NULL, data TEXT, PRIMARY KEY (type, id))
458+
;CREATE TABLE ps_updated_rows(
459+
row_type TEXT,
460+
row_id TEXT,
461+
PRIMARY KEY(row_type, row_id)) STRICT, WITHOUT ROWID
462+
;CREATE UNIQUE INDEX ps_buckets_name ON ps_buckets (name)
463+
;CREATE INDEX ps_oplog_key ON ps_oplog (bucket, key)
464+
;CREATE INDEX ps_oplog_opid ON ps_oplog (bucket, op_id)
465+
;CREATE INDEX ps_oplog_row ON ps_oplog (row_type, row_id)
466+
;INSERT INTO ps_migration(id, down_migrations) VALUES(1, null)
467+
;INSERT INTO ps_migration(id, down_migrations) VALUES(2, '[{"sql":"DELETE FROM ps_migration WHERE id >= 2","params":[]},{"sql":"DROP TABLE ps_tx","params":[]},{"sql":"ALTER TABLE ps_crud DROP COLUMN tx_id","params":[]}]')
468+
;INSERT INTO ps_migration(id, down_migrations) VALUES(3, '[{"sql":"DELETE FROM ps_migration WHERE id >= 3"},{"sql":"DROP TABLE ps_kv"}]')
469+
;INSERT INTO ps_migration(id, down_migrations) VALUES(4, '[{"sql":"DELETE FROM ps_migration WHERE id >= 4"},{"sql":"ALTER TABLE ps_buckets DROP COLUMN op_checksum"},{"sql":"ALTER TABLE ps_buckets DROP COLUMN remove_operations"}]')
470+
;INSERT INTO ps_migration(id, down_migrations) VALUES(5, '[{"sql":"SELECT powersync_drop_view(view.name)\n FROM sqlite_master view\n WHERE view.type = ''view''\n AND view.sql GLOB ''*-- powersync-auto-generated''"},{"sql":"ALTER TABLE ps_buckets RENAME TO ps_buckets_5"},{"sql":"ALTER TABLE ps_oplog RENAME TO ps_oplog_5"},{"sql":"CREATE TABLE ps_buckets(\n name TEXT PRIMARY KEY,\n last_applied_op INTEGER NOT NULL DEFAULT 0,\n last_op INTEGER NOT NULL DEFAULT 0,\n target_op INTEGER NOT NULL DEFAULT 0,\n add_checksum INTEGER NOT NULL DEFAULT 0,\n pending_delete INTEGER NOT NULL DEFAULT 0\n, op_checksum INTEGER NOT NULL DEFAULT 0, remove_operations INTEGER NOT NULL DEFAULT 0)"},{"sql":"INSERT INTO ps_buckets(name, last_applied_op, last_op, target_op, add_checksum, op_checksum, pending_delete)\n SELECT name, last_applied_op, last_op, target_op, add_checksum, op_checksum, pending_delete FROM ps_buckets_5"},{"sql":"CREATE TABLE ps_oplog(\n bucket TEXT NOT NULL,\n op_id INTEGER NOT NULL,\n op INTEGER NOT NULL,\n row_type TEXT,\n row_id TEXT,\n key TEXT,\n data TEXT,\n hash INTEGER NOT NULL,\n superseded INTEGER NOT NULL)"},{"sql":"CREATE INDEX ps_oplog_by_row ON ps_oplog (row_type, row_id) WHERE superseded = 0"},{"sql":"CREATE INDEX ps_oplog_by_opid ON ps_oplog (bucket, op_id)"},{"sql":"CREATE INDEX ps_oplog_by_key ON ps_oplog (bucket, key) WHERE superseded = 0"},{"sql":"INSERT INTO ps_oplog(bucket, op_id, op, row_type, row_id, key, data, hash, superseded)\n SELECT ps_buckets_5.name, oplog.op_id, 3, oplog.row_type, oplog.row_id, oplog.key, oplog.data, oplog.hash, 0\n FROM ps_oplog_5 oplog\n JOIN ps_buckets_5\n ON ps_buckets_5.id = oplog.bucket"},{"sql":"DROP TABLE ps_oplog_5"},{"sql":"DROP TABLE ps_buckets_5"},{"sql":"INSERT INTO ps_oplog(bucket, op_id, op, row_type, row_id, hash, superseded)\n SELECT ''$local'', 1, 4, r.row_type, r.row_id, 0, 0\n FROM ps_updated_rows r"},{"sql":"INSERT OR REPLACE INTO ps_buckets(name, pending_delete, last_op, target_op) VALUES(''$local'', 1, 0, 9223372036854775807)"},{"sql":"DROP TABLE ps_updated_rows"},{"sql":"DELETE FROM ps_migration WHERE id >= 5"}]')
471+
;INSERT INTO ps_migration(id, down_migrations) VALUES(6, '[{"sql":"DELETE FROM ps_migration WHERE id >= 6"}]')
472+
;INSERT INTO ps_migration(id, down_migrations) VALUES(7, '[{"sql":"INSERT OR REPLACE INTO ps_kv(key, value) SELECT ''last_synced_at'', last_synced_at FROM ps_sync_state WHERE priority = 2147483647"},{"sql":"DROP TABLE ps_sync_state"},{"sql":"DELETE FROM ps_migration WHERE id >= 7"}]')
473+
;INSERT INTO ps_migration(id, down_migrations) VALUES(8, '[{"sql":"ALTER TABLE ps_sync_state RENAME TO ps_sync_state_new"},{"sql":"CREATE TABLE ps_sync_state (\n priority INTEGER NOT NULL,\n last_synced_at TEXT NOT NULL\n) STRICT"},{"sql":"INSERT INTO ps_sync_state SELECT * FROM ps_sync_state_new"},{"sql":"DROP TABLE ps_sync_state_new"},{"sql":"DELETE FROM ps_migration WHERE id >= 8"}]')
474+
;INSERT INTO ps_migration(id, down_migrations) VALUES(9, '[{"sql":"ALTER TABLE ps_buckets DROP COLUMN count_at_last"},{"sql":"ALTER TABLE ps_buckets DROP COLUMN count_since_last"},{"sql":"DELETE FROM ps_migration WHERE id >= 9"}]')
475+
;INSERT INTO ps_migration(id, down_migrations) VALUES(10, '[{"sql":"SELECT powersync_drop_view(view.name)\n FROM sqlite_master view\n WHERE view.type = ''view''\n AND view.sql GLOB ''*-- powersync-auto-generated''"},{"sql":"DELETE FROM ps_migration WHERE id >= 10"}]')
476+
;INSERT INTO ps_migration(id, down_migrations) VALUES(11, '[{"sql":"DROP TABLE ps_stream_subscriptions"},{"sql":"DELETE FROM ps_migration WHERE id >= 11"}]')
477+
;INSERT INTO ps_migration(id, down_migrations) VALUES(12, '[{"sql":"ALTER TABLE ps_buckets DROP COLUMN downloaded_size"},{"sql":"DELETE FROM ps_migration WHERE id >= 12"}]')
417478
''',
418479
};
419480

@@ -527,6 +588,17 @@ const data1 = <int, String>{
527588
(2, 3, 'lists', 'l1', '', '{}', 3)
528589
;INSERT INTO ps_updated_rows(row_type, row_id) VALUES
529590
('lists', 'l2')
591+
''',
592+
12: r'''
593+
;INSERT INTO ps_buckets(id, name, last_applied_op, last_op, target_op, add_checksum, op_checksum, pending_delete, count_at_last, count_since_last, downloaded_size) VALUES
594+
(1, 'b1', 0, 0, 0, 0, 120, 0, 0, 0, 0),
595+
(2, 'b2', 0, 0, 0, 1005, 3, 0, 0, 0, 0)
596+
;INSERT INTO ps_oplog(bucket, op_id, row_type, row_id, key, data, hash) VALUES
597+
(1, 1, 'todos', 't1', '', '{}', 100),
598+
(1, 2, 'todos', 't2', '', '{}', 20),
599+
(2, 3, 'lists', 'l1', '', '{}', 3)
600+
;INSERT INTO ps_updated_rows(row_type, row_id) VALUES
601+
('lists', 'l2')
530602
'''
531603
};
532604

@@ -573,6 +645,7 @@ final dataDown1 = <int, String>{
573645
8: data1[5]!,
574646
9: data1[9]!,
575647
10: data1[9]!,
648+
11: data1[10]!,
576649
};
577650

578651
final finalData1 = data1[databaseVersion]!;

0 commit comments

Comments
 (0)