Skip to content

Commit 11a94be

Browse files
committed
Disallow local writes during sync
1 parent df5853b commit 11a94be

4 files changed

Lines changed: 73 additions & 9 deletions

File tree

crates/core/src/crud_vtab.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ extern crate alloc;
22

33
use alloc::boxed::Box;
44
use alloc::string::String;
5+
use alloc::sync::Arc;
56
use const_format::formatcp;
67
use core::ffi::{c_char, c_int, c_void, CStr};
7-
use core::ptr::null_mut;
8+
use core::sync::atomic::Ordering;
89
use serde::Serialize;
910
use serde_json::value::RawValue;
1011

@@ -15,6 +16,7 @@ use sqlite_nostd::{self as sqlite, ColumnType};
1516
use crate::error::SQLiteError;
1617
use crate::ext::SafeManagedStmt;
1718
use crate::schema::TableInfoFlags;
19+
use crate::state::DatabaseState;
1820
use crate::util::MAX_OP_ID;
1921
use crate::vtab_util::*;
2022

@@ -41,6 +43,7 @@ struct VirtualTable {
4143
db: *mut sqlite::sqlite3,
4244
current_tx: Option<ActiveCrudTransaction>,
4345
is_simple: bool,
46+
state: Arc<DatabaseState>,
4447
}
4548

4649
struct ActiveCrudTransaction {
@@ -79,6 +82,15 @@ impl VirtualTable {
7982
.as_ref()
8083
.ok_or_else(|| SQLiteError(ResultCode::MISUSE, Some(String::from("No tx_id"))))?;
8184

85+
if self.state.is_in_sync_local.load(Ordering::Relaxed) {
86+
// Don't collect CRUD writes while we're syncing the local database - writes made here
87+
// aren't writes we should upload.
88+
// This normally doesn't happen because we insert directly into the data tables, but
89+
// users might have custom raw tables used for sycing with triggers on them to call
90+
// this function. And those specifically should not trigger here.
91+
return Ok(());
92+
}
93+
8294
match &current_tx.mode {
8395
CrudTransactionMode::Manual { stmt } => {
8496
// Columns are (data TEXT, options INT HIDDEN)
@@ -213,7 +225,7 @@ SELECT * FROM insertion WHERE (NOT (?3 & {})) OR data->>'op' != 'PATCH' OR data-
213225

214226
extern "C" fn connect(
215227
db: *mut sqlite::sqlite3,
216-
_aux: *mut c_void,
228+
aux: *mut c_void,
217229
argc: c_int,
218230
argv: *const *const c_char,
219231
vtab: *mut *mut sqlite::vtab,
@@ -244,6 +256,14 @@ extern "C" fn connect(
244256
pModule: core::ptr::null(),
245257
zErrMsg: core::ptr::null_mut(),
246258
},
259+
state: {
260+
// Increase refcount - we can't use from_raw alone because we don't own the aux
261+
// data (connect could be called multiple times).
262+
let state = Arc::from_raw(aux as *mut DatabaseState);
263+
let clone = state.clone();
264+
core::mem::forget(state);
265+
clone
266+
},
247267
db,
248268
current_tx: None,
249269
is_simple,
@@ -335,20 +355,20 @@ static MODULE: sqlite_nostd::module = sqlite_nostd::module {
335355
xIntegrity: None,
336356
};
337357

338-
pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
358+
pub fn register(db: *mut sqlite::sqlite3, state: Arc<DatabaseState>) -> Result<(), ResultCode> {
339359
sqlite::convert_rc(sqlite::create_module_v2(
340360
db,
341361
SIMPLE_NAME.as_ptr(),
342362
&MODULE,
343-
null_mut(),
344-
None,
363+
Arc::into_raw(state.clone()) as *mut c_void,
364+
Some(DatabaseState::destroy_arc),
345365
))?;
346366
sqlite::convert_rc(sqlite::create_module_v2(
347367
db,
348368
MANUAL_NAME.as_ptr(),
349369
&MODULE,
350-
null_mut(),
351-
None,
370+
Arc::into_raw(state) as *mut c_void,
371+
Some(DatabaseState::destroy_arc),
352372
))?;
353373

354374
Ok(())

crates/core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ fn init_extension(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
7373

7474
crate::schema::register(db)?;
7575
crate::operations_vtab::register(db, state.clone())?;
76-
crate::crud_vtab::register(db)?;
76+
crate::crud_vtab::register(db, state)?;
7777

7878
Ok(())
7979
}

crates/core/src/sync/storage_adapter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::{assert_matches::debug_assert_matches, fmt::Display};
22

3-
use alloc::{string::ToString, sync::Arc, vec::Vec};
3+
use alloc::{string::ToString, vec::Vec};
44
use serde::Serialize;
55
use sqlite_nostd::{self as sqlite, Connection, ManagedStmt, ResultCode};
66
use streaming_iterator::StreamingIterator;

dart/test/sync_test.dart

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,50 @@ END;
780780

781781
expect(db.select('SELECT * FROM users'), isEmpty);
782782
});
783+
784+
test("can't use crud vtab during sync", () {
785+
db.execute(
786+
'CREATE TABLE users (id TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL) STRICT;');
787+
788+
invokeControl(
789+
'start',
790+
json.encode({
791+
'schema': {
792+
'raw_tables': [
793+
{
794+
'name': 'users',
795+
'put': {
796+
// Inserting into powersync_crud_ during a sync operation is
797+
// forbidden, that vtab should only collect local writes.
798+
'sql': "INSERT INTO powersync_crud_(data) VALUES (?);",
799+
'params': [
800+
{'Column': 'name'}
801+
],
802+
},
803+
'delete': {
804+
'sql': 'DELETE FROM users WHERE id = ?',
805+
'params': ['Id'],
806+
},
807+
}
808+
],
809+
'tables': [],
810+
},
811+
}),
812+
);
813+
814+
// Insert
815+
pushCheckpoint(buckets: [bucketDescription('a')]);
816+
pushSyncData(
817+
'a',
818+
'1',
819+
'my_user',
820+
'PUT',
821+
{'name': 'First user'},
822+
objectType: 'users',
823+
);
824+
825+
expect(pushCheckpointComplete, throwsA(isA<SqliteException>()));
826+
});
783827
});
784828
}
785829

0 commit comments

Comments
 (0)