From 6ded1d05505c0733fcb5e92b8f06f1a640eeeb29 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 28 Jul 2026 10:49:31 +0200 Subject: [PATCH 1/4] Update core extension to 0.5.1 --- CHANGELOG.md | 4 +++ Cargo.lock | 8 ++--- powersync/Cargo.toml | 4 +-- powersync/src/db/connection.rs | 2 +- powersync/src/db/core_extension.rs | 4 +-- powersync/src/db/internal.rs | 47 ++++++++++++++++++++---------- powersync/src/sync/upload.rs | 34 ++++++++++----------- powersync/tests/crud_test.rs | 6 +++- 8 files changed, 66 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2cdfdd..ae7370f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.7 (unreleased) + +- Update PowerSync core extension to version 0.5.1. + ## 0.0.6 - Skip creating `ps_crud` entries when clearing raw tables. diff --git a/Cargo.lock b/Cargo.lock index 15d6c4e..b68f5c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3117,9 +3117,9 @@ dependencies = [ [[package]] name = "powersync_core" -version = "0.4.12" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f9c7f0117bda7f68ca872528e0c2c78de94d34f81a6e167e2046e8578bd080" +checksum = "ca826497b4096dc869569970712ad77c96108de46ad8237e671b61a937a47536" dependencies = [ "bytes", "const_format", @@ -3137,9 +3137,9 @@ dependencies = [ [[package]] name = "powersync_sqlite_nostd" -version = "0.4.12" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a5a95198e2ab901965138fced0315621073ef557a04b5552a51774658abf13" +checksum = "38c81cb5ccc5d718ed7d304eb102198af2e5b6a707fed188dffed00f5efcee35" dependencies = [ "bindgen", "num-derive 0.4.2", diff --git a/powersync/Cargo.toml b/powersync/Cargo.toml index 1654ffa..8903b49 100644 --- a/powersync/Cargo.toml +++ b/powersync/Cargo.toml @@ -44,8 +44,8 @@ thiserror = "2.0.16" tokio = { version = "1", features = ["time", "rt"], optional = true } url = "2.5.7" serde_with = "3.15.0" -powersync_core = { version = "=0.4.12", features = ["static"] } -powersync_sqlite_nostd = { version = "=0.4.12", features = ["static"] } +powersync_core = { version = "=0.5.1", features = ["static"] } +powersync_sqlite_nostd = { version = "=0.5.1", features = ["static"] } num-traits = "0.2.19" [dev-dependencies] diff --git a/powersync/src/db/connection.rs b/powersync/src/db/connection.rs index 4c3ff1a..0f72521 100644 --- a/powersync/src/db/connection.rs +++ b/powersync/src/db/connection.rs @@ -49,7 +49,7 @@ impl SqliteConnection { pub fn exec(&self, stmt: &CStr) -> Result<(), PowerSyncError> { unsafe { // Safety: We know the stmt is null-terminated. - self.handle().exec(stmt.as_ptr()) + self.handle().exec(stmt) } .map_err(|rc| RawPowerSyncError::RawSqlite { code: rc, diff --git a/powersync/src/db/core_extension.rs b/powersync/src/db/core_extension.rs index 15c543f..f35e66e 100644 --- a/powersync/src/db/core_extension.rs +++ b/powersync/src/db/core_extension.rs @@ -12,8 +12,8 @@ pub struct CoreExtensionVersion { impl CoreExtensionVersion { /// The minimum version of the core extension supported by the native SDK. - pub const MINIMUM: Self = Self::new(0, 4, 7); - pub const MAXIMUM_EXCLUSIVE: Self = Self::new(0, 5, 0); + pub const MINIMUM: Self = Self::new(0, 5, 1); + pub const MAXIMUM_EXCLUSIVE: Self = Self::new(0, 6, 0); pub const fn new(major: u32, minor: u32, patch: u32) -> Self { Self { diff --git a/powersync/src/db/internal.rs b/powersync/src/db/internal.rs index 71ff231..153ff06 100644 --- a/powersync/src/db/internal.rs +++ b/powersync/src/db/internal.rs @@ -1,4 +1,4 @@ -use crate::db::connection::{SqliteConnection, TransactionGuard, exec_stmt}; +use crate::db::connection::{TransactionGuard, exec_stmt}; use crate::schema::SchemaOrCustom; use crate::{ db::{ @@ -12,7 +12,7 @@ use crate::{ use event_listener::EventListener; use futures_lite::future::yield_now; use futures_lite::{FutureExt, Stream, StreamExt, ready}; -use powersync_sqlite_nostd::{Destructor, ResultCode}; +use powersync_sqlite_nostd::{ColumnType, Destructor, ResultCode}; use std::sync::{Mutex, Weak}; use std::time::Duration; use std::{ @@ -62,14 +62,17 @@ impl InnerPowerSyncState { let pool = &self.env.pool; self.did_initialize .run(|| async { - let conn = pool.writer().await; - let conn = conn.sqlite_connection(); + let mut conn = pool.writer().await; + let conn = conn.sqlite_connection_mut(); CoreExtensionVersion::check_from_db(conn)?; - conn.exec(c"SELECT powersync_init()")?; + let tx = TransactionGuard::new(conn)?; + tx.inner.exec(c"SELECT powersync_init()")?; - self.update_schema_internal(conn)?; - self.status.update(|old| old.resolve_offline_state(conn))?; + self.update_schema_internal(&tx)?; + self.status + .update(|old| old.resolve_offline_state(tx.inner))?; + tx.commit()?; Ok(()) }) @@ -77,13 +80,13 @@ impl InnerPowerSyncState { .clone() } - fn update_schema_internal(&self, conn: &SqliteConnection) -> Result<(), PowerSyncError> { + fn update_schema_internal(&self, conn: &TransactionGuard) -> Result<(), PowerSyncError> { if let SchemaOrCustom::Schema(schema) = self.schema.as_ref() { schema.validate()?; }; let serialized_schema = serde_json::to_string(&self.schema)?; - let stmt = conn.prepare("SELECT powersync_replace_schema(?)")?; + let stmt = conn.inner.prepare("SELECT powersync_replace_schema(?)")?; // Fine because we drop the statement before the serialized schema stmt.bind_text(1, &serialized_schema, Destructor::STATIC)?; exec_stmt(stmt)?; @@ -118,15 +121,29 @@ impl InnerPowerSyncState { } } - Self::set_local_target_op(writer.inner, target_op)?; + Self::target_checkpoint_request_id(&writer, Some(target_op))?; writer.commit() } - pub fn set_local_target_op(writer: &SqliteConnection, op: i64) -> Result<(), PowerSyncError> { - let stmt = writer.prepare("UPDATE ps_buckets SET target_op = ? WHERE name = ?")?; - stmt.bind_int64(1, op)?; - stmt.bind_text(2, "$local", Destructor::STATIC)?; - exec_stmt(stmt) + pub fn target_checkpoint_request_id( + writer: &TransactionGuard, + update: Option, + ) -> Result, PowerSyncError> { + let stmt = writer.inner.prepare("SELECT powersync_control(?, ?);")?; + stmt.bind_text(1, "target_checkpoint_request_id", Destructor::STATIC)?; + if let Some(update) = update { + stmt.bind_int64(2, update)?; + } else { + stmt.bind_null(2)?; + } + let ResultCode::ROW = stmt.step()? else { + panic!("Scalar statement not return a row") + }; + + Ok(match stmt.column_type(0)? { + ColumnType::Integer => Some(stmt.column_int64(0)), + _ => None, + }) } pub async fn reader(&self) -> Result { diff --git a/powersync/src/sync/upload.rs b/powersync/src/sync/upload.rs index 6dd9691..8c9dc9e 100644 --- a/powersync/src/sync/upload.rs +++ b/powersync/src/sync/upload.rs @@ -308,8 +308,10 @@ impl<'a> CrudUpload<'a> { }) } - fn ps_crud_sequence(conn: &SqliteConnection) -> Result, PowerSyncError> { - let seq_before = conn.prepare("SELECT seq FROM main.sqlite_sequence WHERE name = ?")?; + fn ps_crud_sequence(tx: &TransactionGuard) -> Result, PowerSyncError> { + let seq_before = tx + .inner + .prepare("SELECT seq FROM main.sqlite_sequence WHERE name = ?")?; seq_before.bind_text(1, "ps_crud", Destructor::STATIC)?; let ResultCode::ROW = seq_before.step()? else { @@ -322,21 +324,17 @@ impl<'a> CrudUpload<'a> { async fn sequence_for_checkpoint( &self, ) -> Result, PowerSyncError> { - let reader = self.db.reader().await?; - let reader = reader.sqlite_connection(); - { - let stmt = - reader.prepare("SELECT 1 FROM ps_buckets WHERE name = ? AND target_op = ?")?; - stmt.bind_text(1, "$local", Destructor::STATIC)?; - stmt.bind_int64(2, MAX_OP_ID)?; + let mut reader = self.db.reader().await?; + let reader = reader.sqlite_connection_mut(); + let read_tx = TransactionGuard::new(reader)?; - let ResultCode::ROW = stmt.step()? else { - // Nothing to update. - return Ok(None); - }; - } + let Some(MAX_OP_ID) = InnerPowerSyncState::target_checkpoint_request_id(&read_tx, None)? + else { + // Nothing to update. + return Ok(None); + }; - let seq_before = Self::ps_crud_sequence(reader)?; + let seq_before = Self::ps_crud_sequence(&read_tx)?; Ok(seq_before.map(|seq_before| PendingCheckpointRequest { crud_sequence: seq_before, })) @@ -369,8 +367,8 @@ impl PendingCheckpointRequest { return Ok(()); } - let seq_after = CrudUpload::ps_crud_sequence(writer.inner)? - .expect("sqlite sequence should not be empty"); + let seq_after = + CrudUpload::ps_crud_sequence(&writer)?.expect("sqlite sequence should not be empty"); if seq_after != self.crud_sequence { debug!( @@ -380,7 +378,7 @@ impl PendingCheckpointRequest { return Ok(()); } - InnerPowerSyncState::set_local_target_op(writer.inner, op_id)?; + InnerPowerSyncState::target_checkpoint_request_id(&writer, Some(op_id))?; writer.commit()?; Ok(()) diff --git a/powersync/tests/crud_test.rs b/powersync/tests/crud_test.rs index bfb1f0b..523d380 100644 --- a/powersync/tests/crud_test.rs +++ b/powersync/tests/crud_test.rs @@ -303,9 +303,13 @@ fn raw_table_clear() { // Running powersync_clear should delete from users { - let writer = db.writer().await.unwrap(); + let mut writer = db.writer().await.unwrap(); + let writer = writer.transaction().unwrap(); + let mut stmt = writer.prepare("SELECT powersync_clear(0)").unwrap(); stmt.query_one(params![], |_| Ok(())).unwrap(); + drop(stmt); + writer.commit().unwrap(); } assert_eq!( From f9c3e425144dc97827be8bb88cb37cf7abcb64ef Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 28 Jul 2026 10:51:15 +0200 Subject: [PATCH 2/4] Fix outdated comment --- powersync/src/db/connection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powersync/src/db/connection.rs b/powersync/src/db/connection.rs index 0f72521..e0f286e 100644 --- a/powersync/src/db/connection.rs +++ b/powersync/src/db/connection.rs @@ -48,7 +48,7 @@ impl SqliteConnection { /// Executes a SQL statement without parameters. pub fn exec(&self, stmt: &CStr) -> Result<(), PowerSyncError> { unsafe { - // Safety: We know the stmt is null-terminated. + // Safety: We're not doing anything that could close the connection. self.handle().exec(stmt) } .map_err(|rc| RawPowerSyncError::RawSqlite { From 6e85af913b8a4cf6b9a47267034f366a46c77869 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 28 Jul 2026 10:59:56 +0200 Subject: [PATCH 3/4] typo --- powersync/src/db/internal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powersync/src/db/internal.rs b/powersync/src/db/internal.rs index 153ff06..ad0fb33 100644 --- a/powersync/src/db/internal.rs +++ b/powersync/src/db/internal.rs @@ -137,7 +137,7 @@ impl InnerPowerSyncState { stmt.bind_null(2)?; } let ResultCode::ROW = stmt.step()? else { - panic!("Scalar statement not return a row") + panic!("Scalar statement did not return a row") }; Ok(match stmt.column_type(0)? { From 09594d27b0a1172a175db00c5c637d491ed3c178 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 28 Jul 2026 11:10:36 +0200 Subject: [PATCH 4/4] AI review --- powersync/src/sync/upload.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/powersync/src/sync/upload.rs b/powersync/src/sync/upload.rs index 8c9dc9e..2b0000e 100644 --- a/powersync/src/sync/upload.rs +++ b/powersync/src/sync/upload.rs @@ -328,11 +328,11 @@ impl<'a> CrudUpload<'a> { let reader = reader.sqlite_connection_mut(); let read_tx = TransactionGuard::new(reader)?; - let Some(MAX_OP_ID) = InnerPowerSyncState::target_checkpoint_request_id(&read_tx, None)? - else { + let current_target = InnerPowerSyncState::target_checkpoint_request_id(&read_tx, None)?; + if current_target != Some(MAX_OP_ID) { // Nothing to update. return Ok(None); - }; + } let seq_before = Self::ps_crud_sequence(&read_tx)?; Ok(seq_before.map(|seq_before| PendingCheckpointRequest {