Skip to content

Commit 8442338

Browse files
committed
Check sqlite3 version on load
1 parent 52fecc2 commit 8442338

File tree

5 files changed

+62
-13
lines changed

5 files changed

+62
-13
lines changed

crates/core/src/constants.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use core::ffi::c_int;
2+
3+
pub const CORE_PKG_VERSION: &'static str = env!("CARGO_PKG_VERSION");
4+
pub const FULL_GIT_HASH: &'static str = env!("GIT_HASH");
5+
6+
// We need 3.44 or later to use an `ORDER BY` in an aggregate function invocation.
7+
//
8+
// When raising the minimum version requirement, also change the CI to ensure we're testing on the
9+
// oldest SQLite version we claim to support.
10+
pub const MIN_SQLITE_VERSION_NUMBER: c_int = 3044000;
11+
12+
pub fn short_git_hash() -> &'static str {
13+
&FULL_GIT_HASH[..8]
14+
}

crates/core/src/error.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
use core::{error::Error, fmt::Display};
1+
use core::{error::Error, ffi::c_int, fmt::Display};
22

33
use alloc::{
44
borrow::Cow,
55
boxed::Box,
66
string::{String, ToString},
77
};
88
use num_traits::FromPrimitive;
9-
use sqlite_nostd::{context, sqlite3, Connection, Context, ResultCode};
9+
use sqlite_nostd::{self as sqlite, context, sqlite3, Connection, Context, ResultCode};
1010
use thiserror::Error;
1111

12-
use crate::bson::BsonError;
12+
use crate::{
13+
bson::BsonError,
14+
constants::{CORE_PKG_VERSION, MIN_SQLITE_VERSION_NUMBER},
15+
};
1316

1417
/// A [RawPowerSyncError], but boxed.
1518
///
@@ -125,7 +128,8 @@ impl PowerSyncError {
125128
StateError { .. } => ResultCode::MISUSE,
126129
MissingClientId
127130
| SyncProtocolError { .. }
128-
| DownMigrationDidNotUpdateVersion { .. } => ResultCode::ABORT,
131+
| DownMigrationDidNotUpdateVersion { .. }
132+
| SqliteVersionMismatch { .. } => ResultCode::ABORT,
129133
LocalDataError { .. } => ResultCode::CORRUPT,
130134
Internal { .. } => ResultCode::INTERNAL,
131135
}
@@ -144,6 +148,20 @@ impl PowerSyncError {
144148
_ => false,
145149
}
146150
}
151+
152+
pub fn check_sqlite3_version() -> Result<(), PowerSyncError> {
153+
let actual_version = sqlite::libversion_number();
154+
155+
if actual_version < MIN_SQLITE_VERSION_NUMBER {
156+
Err(RawPowerSyncError::SqliteVersionMismatch {
157+
libversion_number: actual_version,
158+
libversion: sqlite::libversion(),
159+
}
160+
.into())
161+
} else {
162+
Ok(())
163+
}
164+
}
147165
}
148166

149167
impl Display for PowerSyncError {
@@ -221,6 +239,15 @@ enum RawPowerSyncError {
221239
/// A catch-all for remaining internal errors that are very unlikely to happen.
222240
#[error("Internal PowerSync error. {cause}")]
223241
Internal { cause: PowerSyncErrorCause },
242+
#[error(
243+
"Version {} of the PowerSync SQLite extension requires SQLite version number {} or later, but was loaded against {libversion} ({libversion_number})",
244+
CORE_PKG_VERSION,
245+
MIN_SQLITE_VERSION_NUMBER
246+
)]
247+
SqliteVersionMismatch {
248+
libversion_number: c_int,
249+
libversion: &'static str,
250+
},
224251
}
225252

226253
#[derive(Debug)]

crates/core/src/lib.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ extern crate alloc;
99

1010
use core::ffi::{c_char, c_int};
1111

12-
use alloc::sync::Arc;
12+
use alloc::{ffi::CString, format, sync::Arc};
1313
use sqlite::ResultCode;
1414
use sqlite_nostd as sqlite;
1515

16-
use crate::state::DatabaseState;
16+
use crate::{error::PowerSyncError, state::DatabaseState};
1717

1818
mod bson;
1919
mod checkpoint;
20+
mod constants;
2021
mod crud_vtab;
2122
mod diff;
2223
mod error;
@@ -42,21 +43,29 @@ mod vtab_util;
4243
#[no_mangle]
4344
pub extern "C" fn sqlite3_powersync_init(
4445
db: *mut sqlite::sqlite3,
45-
_err_msg: *mut *mut c_char,
46+
err_msg: *mut *mut c_char,
4647
api: *mut sqlite::api_routines,
4748
) -> c_int {
49+
debug_assert!(unsafe { *err_msg }.is_null());
4850
sqlite::EXTENSION_INIT2(api);
4951

5052
let result = init_extension(db);
5153

5254
return if let Err(code) = result {
53-
code as c_int
55+
if let Ok(desc) = CString::new(format!("Could not initialize PowerSync: {}", code)) {
56+
// Note: This is fine since we're using sqlite3_malloc to allocate in Rust
57+
unsafe { *err_msg = desc.into_raw() as *mut c_char };
58+
}
59+
60+
code.sqlite_error_code() as c_int
5461
} else {
5562
ResultCode::OK as c_int
5663
};
5764
}
5865

59-
fn init_extension(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
66+
fn init_extension(db: *mut sqlite::sqlite3) -> Result<(), PowerSyncError> {
67+
PowerSyncError::check_sqlite3_version()?;
68+
6069
let state = Arc::new(DatabaseState::new());
6170

6271
crate::version::register(db)?;

crates/core/src/version.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@ use sqlite::ResultCode;
88
use sqlite_nostd as sqlite;
99
use sqlite_nostd::{Connection, Context};
1010

11+
use crate::constants::{short_git_hash, CORE_PKG_VERSION};
1112
use crate::create_sqlite_text_fn;
1213
use crate::error::PowerSyncError;
1314

1415
fn powersync_rs_version_impl(
1516
_ctx: *mut sqlite::context,
1617
_args: &[*mut sqlite::value],
1718
) -> Result<String, ResultCode> {
18-
let cargo_version = env!("CARGO_PKG_VERSION");
19-
let full_hash = String::from(env!("GIT_HASH"));
20-
let version = format!("{}/{}", cargo_version, &full_hash[0..8]);
19+
let version = format!("{}/{}", CORE_PKG_VERSION, short_git_hash());
2120
Ok(version)
2221
}
2322

0 commit comments

Comments
 (0)