Skip to content

Expand support for update metadata and tracking old values #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: update-metadata
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "sqlite-rs-embedded"]
path = sqlite-rs-embedded
url = https://github.com/vlcn-io/sqlite-rs-embedded.git
url = https://github.com/powersync-ja/sqlite-rs-embedded.git
61 changes: 45 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ num-traits = { version = "0.2.15", default-features = false }
num-derive = "0.3"
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
streaming-iterator = { version = "0.1.9", default-features = false, features = ["alloc"] }

[dependencies.uuid]
version = "1.4.1"
Expand Down
7 changes: 3 additions & 4 deletions crates/core/src/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_int;
use core::slice;

use serde::{Deserialize, Serialize};
use serde_json as json;
Expand Down Expand Up @@ -59,9 +58,9 @@ GROUP BY bucket_list.bucket",
while statement.step()? == ResultCode::ROW {
let name = statement.column_text(0)?;
// checksums with column_int are wrapped to i32 by SQLite
let add_checksum = statement.column_int(1)?;
let oplog_checksum = statement.column_int(2)?;
let expected_checksum = statement.column_int(3)?;
let add_checksum = statement.column_int(1);
let oplog_checksum = statement.column_int(2);
let expected_checksum = statement.column_int(3);

// wrapping add is like +, but safely overflows
let checksum = oplog_checksum.wrapping_add(add_checksum);
Expand Down
29 changes: 16 additions & 13 deletions crates/core/src/crud_vtab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ extern crate alloc;
use alloc::boxed::Box;
use alloc::string::String;
use core::ffi::{c_char, c_int, c_void};
use core::slice;

use sqlite::{Connection, ResultCode, Value};
use sqlite_nostd as sqlite;
Expand All @@ -29,7 +28,7 @@ struct VirtualTable {
base: sqlite::vtab,
db: *mut sqlite::sqlite3,
current_tx: Option<i64>,
insert_statement: Option<ManagedStmt>
insert_statement: Option<ManagedStmt>,
}

extern "C" fn connect(
Expand All @@ -40,8 +39,7 @@ extern "C" fn connect(
vtab: *mut *mut sqlite::vtab,
_err: *mut *mut c_char,
) -> c_int {
if let Err(rc) = sqlite::declare_vtab(db, "CREATE TABLE powersync_crud_(data TEXT);")
{
if let Err(rc) = sqlite::declare_vtab(db, "CREATE TABLE powersync_crud_(data TEXT);") {
return rc as c_int;
}

Expand All @@ -54,7 +52,7 @@ extern "C" fn connect(
},
db,
current_tx: None,
insert_statement: None
insert_statement: None,
}));
*vtab = tab.cast::<sqlite::vtab>();
let _ = sqlite::vtab_config(db, 0);
Expand All @@ -69,17 +67,17 @@ extern "C" fn disconnect(vtab: *mut sqlite::vtab) -> c_int {
ResultCode::OK as c_int
}


fn begin_impl(tab: &mut VirtualTable) -> Result<(), SQLiteError> {
let db = tab.db;

let insert_statement = db.prepare_v3("INSERT INTO ps_crud(tx_id, data) VALUES (?1, ?2)", 0)?;
tab.insert_statement = Some(insert_statement);

// language=SQLite
let statement = db.prepare_v2("UPDATE ps_tx SET next_tx = next_tx + 1 WHERE id = 1 RETURNING next_tx")?;
let statement =
db.prepare_v2("UPDATE ps_tx SET next_tx = next_tx + 1 WHERE id = 1 RETURNING next_tx")?;
if statement.step()? == ResultCode::ROW {
let tx_id = statement.column_int64(0)? - 1;
let tx_id = statement.column_int64(0) - 1;
tab.current_tx = Some(tx_id);
} else {
return Err(SQLiteError::from(ResultCode::ABORT));
Expand Down Expand Up @@ -109,23 +107,27 @@ extern "C" fn rollback(vtab: *mut sqlite::vtab) -> c_int {
ResultCode::OK as c_int
}

fn insert_operation(
vtab: *mut sqlite::vtab, data: &str) -> Result<(), SQLiteError> {
fn insert_operation(vtab: *mut sqlite::vtab, data: &str) -> Result<(), SQLiteError> {
let tab = unsafe { &mut *(vtab.cast::<VirtualTable>()) };
if tab.current_tx.is_none() {
return Err(SQLiteError(ResultCode::MISUSE, Some(String::from("No tx_id"))));
return Err(SQLiteError(
ResultCode::MISUSE,
Some(String::from("No tx_id")),
));
}
let current_tx = tab.current_tx.unwrap();
// language=SQLite
let statement = tab.insert_statement.as_ref().ok_or(SQLiteError::from(NULL))?;
let statement = tab
.insert_statement
.as_ref()
.ok_or(SQLiteError::from(NULL))?;
statement.bind_int64(1, current_tx)?;
statement.bind_text(2, data, sqlite::Destructor::STATIC)?;
statement.exec()?;

Ok(())
}


extern "C" fn update(
vtab: *mut sqlite::vtab,
argc: c_int,
Expand Down Expand Up @@ -178,6 +180,7 @@ static MODULE: sqlite_nostd::module = sqlite_nostd::module {
xRelease: None,
xRollbackTo: None,
xShadowName: None,
xIntegrity: None,
};

pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
Expand Down
37 changes: 31 additions & 6 deletions crates/core/src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ extern crate alloc;
use alloc::format;
use alloc::string::{String, ToString};
use core::ffi::c_int;
use core::slice;

use sqlite::ResultCode;
use sqlite_nostd as sqlite;
Expand All @@ -20,11 +19,20 @@ fn powersync_diff_impl(
) -> Result<String, SQLiteError> {
let data_old = args[0].text();
let data_new = args[1].text();
let ignore_removed = args.get(2).map_or(false, |v| v.int() != 0);

diff_objects(data_old, data_new)
diff_objects_with_options(data_old, data_new, ignore_removed)
}

pub fn diff_objects(data_old: &str, data_new: &str) -> Result<String, SQLiteError> {
/// Returns a JSON object containing entries from [data_new] that are not present in [data_old].
///
/// When [ignore_removed_columns] is set, columns that are present in [data_old] but not in
/// [data_new] will not be present in the returned object. Otherwise, they will be set to `null`.
fn diff_objects_with_options(
data_old: &str,
data_new: &str,
ignore_removed_columns: bool,
) -> Result<String, SQLiteError> {
let v_new: json::Value = json::from_str(data_new)?;
let v_old: json::Value = json::from_str(data_old)?;

Expand All @@ -39,9 +47,11 @@ pub fn diff_objects(data_old: &str, data_new: &str) -> Result<String, SQLiteErro
}

// Add missing nulls to left
for key in right.keys() {
if !left.contains_key(key) {
left.insert(key.clone(), json::Value::Null);
if !ignore_removed_columns {
for key in right.keys() {
if !left.contains_key(key) {
left.insert(key.clone(), json::Value::Null);
}
}
}

Expand Down Expand Up @@ -76,13 +86,28 @@ pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
None,
)?;

db.create_function_v2(
"powersync_diff",
3,
sqlite::UTF8 | sqlite::DETERMINISTIC,
None,
Some(powersync_diff),
None,
None,
None,
)?;

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

fn diff_objects(data_old: &str, data_new: &str) -> Result<String, SQLiteError> {
diff_objects_with_options(data_old, data_new, false)
}

#[test]
fn basic_diff_test() {
assert_eq!(diff_objects("{}", "{}").unwrap(), "{}");
Expand Down
1 change: 0 additions & 1 deletion crates/core/src/json_merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ extern crate alloc;
use alloc::format;
use alloc::string::{String, ToString};
use core::ffi::c_int;
use core::slice;

use sqlite::ResultCode;
use sqlite_nostd as sqlite;
Expand Down
1 change: 0 additions & 1 deletion crates/core/src/kv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ extern crate alloc;
use alloc::format;
use alloc::string::{String, ToString};
use core::ffi::c_int;
use core::slice;

use sqlite::ResultCode;
use sqlite_nostd as sqlite;
Expand Down
4 changes: 2 additions & 2 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ mod macros;
mod migrations;
mod operations;
mod operations_vtab;
mod schema_management;
mod schema;
mod sync_local;
mod sync_types;
mod util;
Expand Down Expand Up @@ -62,7 +62,7 @@ fn init_extension(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
crate::checkpoint::register(db)?;
crate::kv::register(db)?;

crate::schema_management::register(db)?;
crate::schema::register(db)?;
crate::operations_vtab::register(db)?;
crate::crud_vtab::register(db)?;

Expand Down
Loading