Skip to content

Commit 99e4850

Browse files
authored
Merge pull request #46 from powersync-ja/fix-existing-data
Fix existing data (remove dangling rows)
2 parents ebfbbef + c2d2ed0 commit 99e4850

15 files changed

+231
-15
lines changed

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ inherits = "release"
2424
inherits = "wasm"
2525

2626
[workspace.package]
27-
version = "0.3.5"
27+
version = "0.3.6"
2828
edition = "2021"
2929
authors = ["JourneyApps"]
3030
keywords = ["sqlite", "powersync"]

android/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66
}
77

88
group = "co.powersync"
9-
version = "0.3.5"
9+
version = "0.3.6"
1010
description = "PowerSync Core SQLite Extension"
1111

1212
repositories {

android/src/prefab/prefab.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"name": "powersync_sqlite_core",
33
"schema_version": 2,
44
"dependencies": [],
5-
"version": "0.3.5"
5+
"version": "0.3.6"
66
}

crates/core/src/fix035.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use alloc::format;
2+
3+
use crate::error::{PSResult, SQLiteError};
4+
use sqlite_nostd as sqlite;
5+
use sqlite_nostd::{Connection, ResultCode};
6+
7+
use crate::ext::SafeManagedStmt;
8+
use crate::util::quote_identifier;
9+
10+
// Apply a data migration to fix any existing data affected by the issue
11+
// fixed in v0.3.5.
12+
//
13+
// The issue was that the `ps_updated_rows` table was not being populated
14+
// with remove operations in some cases. This causes the rows to be removed
15+
// from ps_oplog, but not from the ps_data__tables, resulting in dangling rows.
16+
//
17+
// The fix here is to find these dangling rows, and add them to ps_updated_rows.
18+
// The next time the sync_local operation is run, these rows will be removed.
19+
pub fn apply_v035_fix(db: *mut sqlite::sqlite3) -> Result<i64, SQLiteError> {
20+
// language=SQLite
21+
let statement = db
22+
.prepare_v2("SELECT name, powersync_external_table_name(name) FROM sqlite_master WHERE type='table' AND name GLOB 'ps_data__*'")
23+
.into_db_result(db)?;
24+
25+
while statement.step()? == ResultCode::ROW {
26+
let full_name = statement.column_text(0)?;
27+
let short_name = statement.column_text(1)?;
28+
let quoted = quote_identifier(full_name);
29+
30+
// language=SQLite
31+
let statement = db.prepare_v2(&format!(
32+
"
33+
INSERT OR IGNORE INTO ps_updated_rows(row_type, row_id)
34+
SELECT ?1, id FROM {}
35+
WHERE NOT EXISTS (
36+
SELECT 1 FROM ps_oplog
37+
WHERE row_type = ?1 AND row_id = {}.id
38+
);",
39+
quoted, quoted
40+
))?;
41+
statement.bind_text(1, short_name, sqlite::Destructor::STATIC)?;
42+
43+
statement.exec()?;
44+
}
45+
46+
Ok(1)
47+
}

crates/core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod crud_vtab;
1717
mod diff;
1818
mod error;
1919
mod ext;
20+
mod fix035;
2021
mod kv;
2122
mod macros;
2223
mod migrations;

crates/core/src/migrations.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use sqlite_nostd as sqlite;
99
use sqlite_nostd::{Connection, Context};
1010

1111
use crate::error::{PSResult, SQLiteError};
12+
use crate::fix035::apply_v035_fix;
1213

1314
pub fn powersync_migrate(
1415
ctx: *mut sqlite::context,
@@ -283,5 +284,24 @@ VALUES(5,
283284
.into_db_result(local_db)?;
284285
}
285286

287+
if current_version < 6 && target_version >= 6 {
288+
if current_version != 0 {
289+
// Remove dangling rows, but skip if the database is created from scratch.
290+
apply_v035_fix(local_db)?;
291+
}
292+
293+
local_db
294+
.exec_safe(
295+
"\
296+
INSERT INTO ps_migration(id, down_migrations)
297+
VALUES(6,
298+
json_array(
299+
json_object('sql', 'DELETE FROM ps_migration WHERE id >= 6')
300+
));
301+
",
302+
)
303+
.into_db_result(local_db)?;
304+
}
305+
286306
Ok(())
287307
}

crates/core/src/operations.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ INSERT OR IGNORE INTO ps_updated_rows(row_type, row_id) VALUES(?1, ?2)",
129129

130130
while supersede_statement.step()? == ResultCode::ROW {
131131
// Superseded (deleted) a previous operation, add the checksum
132-
let superseded_op = supersede_statement.column_int64(0)?;
133132
let supersede_checksum = supersede_statement.column_int(1)?;
134133
add_checksum = add_checksum.wrapping_add(supersede_checksum);
135134
op_checksum = op_checksum.wrapping_sub(supersede_checksum);

crates/core/src/view_admin.rs

Lines changed: 1 addition & 1 deletion
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, 5)?;
123+
powersync_migrate(ctx, 6)?;
124124

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

dart/test/migration_test.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:test/test.dart';
55

66
import 'utils/native_test_utils.dart';
77
import 'utils/migration_fixtures.dart' as fixtures;
8+
import 'utils/fix_035_fixtures.dart' as fix035;
89
import 'utils/schema.dart';
910

1011
void main() {
@@ -175,5 +176,40 @@ void main() {
175176
'${fixtures.expectedState[3]!.replaceAll(RegExp(r';INSERT INTO ps_migration.*'), '').trim()}\n${fixtures.schemaDown3.trim()}';
176177
expect(schema, equals(expected));
177178
});
179+
180+
test('migrate from 5 with broken data', () async {
181+
var tableSchema = {
182+
'tables': [
183+
{
184+
'name': 'lists',
185+
'columns': [
186+
{'name': 'description', 'type': 'TEXT'}
187+
]
188+
},
189+
{
190+
'name': 'todos',
191+
'columns': [
192+
{'name': 'description', 'type': 'TEXT'}
193+
]
194+
}
195+
]
196+
};
197+
db.select('select powersync_init()');
198+
db.select(
199+
'select powersync_replace_schema(?)', [jsonEncode(tableSchema)]);
200+
201+
db.select('select powersync_test_migration(5)');
202+
db.execute(fix035.dataBroken);
203+
204+
db.select('select powersync_init()');
205+
final data = getData(db);
206+
expect(data, equals(fix035.dataMigrated.trim()));
207+
208+
db.select('insert into powersync_operations(op, data) values(?, ?)',
209+
['sync_local', '']);
210+
211+
final data2 = getData(db);
212+
expect(data2, equals(fix035.dataFixed.trim()));
213+
});
178214
});
179215
}

0 commit comments

Comments
 (0)