diff --git a/Cargo.lock b/Cargo.lock index 4c08108..9480ede 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -158,6 +159,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "libc" version = "0.2.147" @@ -239,6 +249,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "powersync_core" version = "0.4.0" @@ -254,6 +270,7 @@ dependencies = [ "sqlite_nostd", "streaming-iterator", "uuid", + "zstd-safe", ] [[package]] @@ -494,3 +511,22 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 3839d97..a6f9512 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -23,6 +23,7 @@ const_format = "0.2.34" futures-lite = { version = "2.6.0", default-features = false, features = ["alloc"] } rustc-hash = { version = "2.1", default-features = false } streaming-iterator = { version = "0.1.9", default-features = false, features = ["alloc"] } +zstd-safe = { version = "7.2.4", default-features = false } [dependencies.uuid] version = "1.4.1" diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 76edd45..7530d7c 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -34,6 +34,7 @@ mod version; mod view_admin; mod views; mod vtab_util; +mod zstd; #[no_mangle] pub extern "C" fn sqlite3_powersync_init( @@ -62,6 +63,7 @@ fn init_extension(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> { crate::view_admin::register(db)?; crate::checkpoint::register(db)?; crate::kv::register(db)?; + crate::zstd::register(db)?; sync::register(db)?; crate::schema::register(db)?; diff --git a/crates/core/src/zstd.rs b/crates/core/src/zstd.rs new file mode 100644 index 0000000..22ec9ad --- /dev/null +++ b/crates/core/src/zstd.rs @@ -0,0 +1,46 @@ +extern crate alloc; + +use alloc::string::String; +use core::ffi::c_int; + +use sqlite::ResultCode; +use sqlite_nostd::{self as sqlite, Value}; +use sqlite_nostd::{Connection, Context}; + +use crate::create_sqlite_text_fn; +use crate::error::SQLiteError; +use zstd_safe::DCtx; + +fn powersync_zstd_impl( + _ctx: *mut sqlite::context, + args: &[*mut sqlite::value], +) -> Result { + let arg = args.get(0).ok_or(ResultCode::MISMATCH)?.blob(); + let dict = args.get(1).ok_or(ResultCode::MISMATCH)?.blob(); + // TODO: Use a form of streaming decompression to avoid pre-allocating a large buffer. + let mut dest = alloc::vec![0u8; 1024 * 20]; + let mut ctx = DCtx::create(); + let size = ctx + .decompress_using_dict(&mut dest[..], arg, dict) + .map_err(|_| ResultCode::CORRUPT)?; + dest.truncate(size); + let text = String::from_utf8(dest).map_err(|_| ResultCode::MISUSE)?; + Ok(text) +} + +create_sqlite_text_fn!(powersync_zstd, powersync_zstd_impl, "zstd_decompress_text"); + +pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> { + db.create_function_v2( + "zstd_decompress_text", + 2, + sqlite::UTF8, + None, + Some(powersync_zstd), + None, + None, + None, + )?; + + Ok(()) +}