Skip to content

Commit 8d6dd90

Browse files
committed
Allow calling powersync_init on readonly connections
1 parent d6c157e commit 8d6dd90

6 files changed

Lines changed: 100 additions & 21 deletions

File tree

crates/core/src/pre_close_vtab.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,24 +71,61 @@ extern "C" fn update(
7171
_argv: *mut *mut sqlite::value,
7272
_p_row_id: *mut sqlite::int64,
7373
) -> c_int {
74-
0
74+
// This table isn't meant to be written to.
75+
ResultCode::MISUSE as c_int
76+
}
77+
78+
#[repr(transparent)]
79+
struct EmptyCursor(sqlite::vtab_cursor);
80+
81+
extern "C" fn best_index(_vtab: *mut sqlite::vtab, _index_info: *mut sqlite::index_info) -> c_int {
82+
// No rows are ever returned, so there's nothing to plan for.
83+
ResultCode::OK as c_int
84+
}
85+
86+
extern "C" fn open(vtab: *mut sqlite::vtab, cursor: *mut *mut sqlite::vtab_cursor) -> c_int {
87+
let c = Box::into_raw(Box::new(EmptyCursor(sqlite::vtab_cursor { pVtab: vtab })));
88+
unsafe { *cursor = c.cast::<sqlite::vtab_cursor>() };
89+
90+
ResultCode::OK as c_int
91+
}
92+
93+
extern "C" fn close(cursor: *mut sqlite::vtab_cursor) -> c_int {
94+
unsafe {
95+
drop(Box::from_raw(cursor as *mut EmptyCursor));
96+
}
97+
ResultCode::OK as c_int
98+
}
99+
100+
extern "C" fn filter(
101+
_cursor: *mut sqlite::vtab_cursor,
102+
_idx_num: c_int,
103+
_idx_str: *const c_char,
104+
_argc: c_int,
105+
_argv: *mut *mut sqlite::value,
106+
) -> c_int {
107+
ResultCode::OK as c_int
108+
}
109+
110+
extern "C" fn eof(_cursor: *mut sqlite::vtab_cursor) -> c_int {
111+
1
75112
}
76113

77114
// Insert-only virtual table.
78-
// The primary functionality here is in update.
79-
// connect and disconnect configures the table and allocates the required resources.
115+
// The primary functionality here is in connect and disconnect - selecting from it is a no-op
116+
// used to trigger those at the right times, and writes are rejected in update.
80117
static MODULE: sqlite::module = sqlite::module {
81118
iVersion: 0,
82119
xCreate: None,
83120
xConnect: Some(connect),
84-
xBestIndex: Some(vtab_no_best_index),
121+
xBestIndex: Some(best_index),
85122
xDisconnect: Some(disconnect),
86123
xDestroy: None,
87-
xOpen: Some(vtab_no_open),
88-
xClose: Some(vtab_no_close),
89-
xFilter: Some(vtab_no_filter),
124+
xOpen: Some(open),
125+
xClose: Some(close),
126+
xFilter: Some(filter),
90127
xNext: Some(vtab_no_next),
91-
xEof: Some(vtab_no_eof),
128+
xEof: Some(eof),
92129
xColumn: Some(vtab_no_column),
93130
xRowid: Some(vtab_no_rowid),
94131
xUpdate: Some(update),

crates/core/src/view_admin.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,17 @@ fn powersync_init_impl(
3636
_args: &[*mut sqlite::value],
3737
) -> Result<String, PowerSyncError> {
3838
let db = ctx.db_handle();
39-
verify_in_transaction(db)?;
40-
powersync_migrate(ctx, LATEST_VERSION)?;
39+
if let Some(true) = sqlite::db_readonly(db, c"main") {
40+
// Called on readonly connection, don't try to migrate.
41+
} else {
42+
verify_in_transaction(db)?;
43+
powersync_migrate(ctx, LATEST_VERSION)?;
44+
}
4145

4246
// Register the powersync_internal_close vtab to implement a "pre-close hook".
4347
// See `pre_close_vtab.rs` for more details on how that works.
4448
ctx.db_handle()
45-
.exec(c"INSERT INTO powersync_internal_close(_) VALUES (null)")?;
49+
.exec(c"SELECT 1 FROM powersync_internal_close;")?;
4650

4751
Ok(String::from(""))
4852
}

crates/sqlite_nostd/src/capi.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,13 @@ mod aliased {
4949
sqlite3_column_value as column_value, sqlite3_commit_hook as commit_hook,
5050
sqlite3_context_db_handle as context_db_handle,
5151
sqlite3_create_function_v2 as create_function_v2,
52-
sqlite3_create_module_v2 as create_module_v2, sqlite3_declare_vtab as declare_vtab,
53-
sqlite3_errcode as errcode, sqlite3_errmsg as errmsg, sqlite3_error_offset as error_offset,
54-
sqlite3_exec as exec, sqlite3_finalize as finalize, sqlite3_free as free,
55-
sqlite3_get_autocommit as get_autocommit, sqlite3_get_auxdata as get_auxdata,
56-
sqlite3_libversion as libversion, sqlite3_libversion_number as libversion_number,
57-
sqlite3_malloc as malloc, sqlite3_malloc64 as malloc64, sqlite3_mutex_alloc as mutex_alloc,
52+
sqlite3_create_module_v2 as create_module_v2, sqlite3_db_readonly as db_readonly,
53+
sqlite3_declare_vtab as declare_vtab, sqlite3_errcode as errcode, sqlite3_errmsg as errmsg,
54+
sqlite3_error_offset as error_offset, sqlite3_exec as exec, sqlite3_finalize as finalize,
55+
sqlite3_free as free, sqlite3_get_autocommit as get_autocommit,
56+
sqlite3_get_auxdata as get_auxdata, sqlite3_libversion as libversion,
57+
sqlite3_libversion_number as libversion_number, sqlite3_malloc as malloc,
58+
sqlite3_malloc64 as malloc64, sqlite3_mutex_alloc as mutex_alloc,
5859
sqlite3_mutex_enter as mutex_enter, sqlite3_mutex_free as mutex_free,
5960
sqlite3_mutex_leave as mutex_leave, sqlite3_mutex_try as mutex_try,
6061
sqlite3_next_stmt as next_stmt, sqlite3_open as open, sqlite3_prepare_v2 as prepare_v2,
@@ -148,6 +149,16 @@ pub fn bind_blob(
148149
}
149150
}
150151

152+
pub fn db_readonly(connection: *mut sqlite3, db: &CStr) -> Option<bool> {
153+
let res = unsafe { invoke_sqlite!(db_readonly, connection, db.as_ptr()) };
154+
155+
match res {
156+
1 => Some(true), // Readonly
157+
0 => Some(false), // Read/write
158+
_ => None, // Not the name of a database on the connection.
159+
}
160+
}
161+
151162
pub fn changes64(db: *mut sqlite3) -> int64 {
152163
unsafe { invoke_sqlite!(changes64, db) }
153164
}

dart/test/sync_test.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,6 +2132,30 @@ CREATE TRIGGER users_ref_delete
21322132
expect(vfs.openFiles, isZero);
21332133
});
21342134

2135+
test('resolving the offline sync status allows closing the database', () {
2136+
final vfs = TrackingFileSystem(
2137+
parent: InMemoryFileSystem(), name: 'sync-test-resolve-offline');
2138+
sqlite3.registerVirtualFileSystem(vfs);
2139+
addTearDown(() => sqlite3.unregisterVirtualFileSystem(vfs));
2140+
2141+
{
2142+
db = openTestDatabase(vfs: vfs, fileName: '/test.db')
2143+
..executeInTx('select powersync_init();');
2144+
expect(vfs.openFiles, isNonZero);
2145+
db.close();
2146+
expect(vfs.openFiles, isZero);
2147+
}
2148+
2149+
db = openTestDatabase(
2150+
vfs: vfs, fileName: '/test.db', mode: OpenMode.readOnly)
2151+
..executeInTx('select powersync_init();');
2152+
2153+
db.execute('SELECT powersync_offline_sync_status();');
2154+
expect(vfs.openFiles, isNonZero);
2155+
db.close();
2156+
expect(vfs.openFiles, isZero);
2157+
});
2158+
21352159
test('tracks download size', () {
21362160
invokeControl('start', null);
21372161
pushCheckpoint(buckets: [bucketDescription('a', count: 2)]);

dart/test/utils/native_test_utils.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ var didLoadExtension = false;
1717

1818
String? testingWithSanitizers = null;
1919

20-
CommonDatabase openTestDatabase(
21-
{VirtualFileSystem? vfs, String fileName = ':memory:'}) {
20+
CommonDatabase openTestDatabase({
21+
VirtualFileSystem? vfs,
22+
String fileName = ':memory:',
23+
OpenMode mode = OpenMode.readWriteCreate,
24+
}) {
2225
if (!didLoadExtension) {
2326
loadExtension();
2427
}
@@ -35,7 +38,7 @@ CommonDatabase openTestDatabase(
3538
vfs = inMemory;
3639
}
3740

38-
final db = sqlite3.open(fileName, vfs: vfs?.name);
41+
final db = sqlite3.open(fileName, vfs: vfs?.name, mode: mode);
3942
addTearDown(db.close);
4043
return db;
4144
}

dart/test/utils/tracking_vfs.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ final class TrackingFileSystem extends BaseVirtualFileSystem {
3232
final result = parent.xOpen(path, flags);
3333
openFiles++;
3434
return (
35-
outFlags: result.outFlags,
35+
outFlags: flags,
3636
file: TrackingFile(
3737
result.file, this, flags & SqlFlag.SQLITE_OPEN_DELETEONCLOSE != 0),
3838
);

0 commit comments

Comments
 (0)