|
2 | 2 |
|
3 | 3 | use std::cell::Cell;
|
4 | 4 | use std::cell::RefCell;
|
| 5 | +use std::ffi::c_char; |
| 6 | +use std::ffi::c_void; |
| 7 | +use std::ffi::CStr; |
5 | 8 | use std::ffi::CString;
|
6 | 9 | use std::ptr::null;
|
7 | 10 | use std::rc::Rc;
|
8 | 11 |
|
9 | 12 | use deno_core::op2;
|
| 13 | +use deno_core::serde_v8; |
| 14 | +use deno_core::v8; |
10 | 15 | use deno_core::GarbageCollected;
|
11 | 16 | use deno_core::OpState;
|
12 | 17 | use deno_permissions::PermissionsContainer;
|
@@ -41,6 +46,13 @@ impl Default for DatabaseSyncOptions {
|
41 | 46 | }
|
42 | 47 | }
|
43 | 48 |
|
| 49 | +#[derive(Deserialize)] |
| 50 | +#[serde(rename_all = "camelCase")] |
| 51 | +struct ApplyChangesetOptions<'a> { |
| 52 | + filter: Option<serde_v8::Value<'a>>, |
| 53 | + on_conflict: Option<serde_v8::Value<'a>>, |
| 54 | +} |
| 55 | + |
44 | 56 | pub struct DatabaseSync {
|
45 | 57 | conn: Rc<RefCell<Option<rusqlite::Connection>>>,
|
46 | 58 | options: DatabaseSyncOptions,
|
@@ -197,6 +209,119 @@ impl DatabaseSync {
|
197 | 209 | })
|
198 | 210 | }
|
199 | 211 |
|
| 212 | + // Applies a changeset to the database. |
| 213 | + // |
| 214 | + // This method is a wrapper around `sqlite3changeset_apply()`. |
| 215 | + #[reentrant] |
| 216 | + fn apply_changeset<'a>( |
| 217 | + &self, |
| 218 | + scope: &mut v8::HandleScope<'a>, |
| 219 | + #[buffer] changeset: &[u8], |
| 220 | + #[serde] options: Option<ApplyChangesetOptions<'a>>, |
| 221 | + ) -> Result<bool, SqliteError> { |
| 222 | + struct HandlerCtx<'a, 'b> { |
| 223 | + scope: &'a mut v8::HandleScope<'b>, |
| 224 | + confict: Option<v8::Local<'b, v8::Function>>, |
| 225 | + filter: Option<v8::Local<'b, v8::Function>>, |
| 226 | + } |
| 227 | + |
| 228 | + // Conflict handler callback for `sqlite3changeset_apply()`. |
| 229 | + unsafe extern "C" fn conflict_handler( |
| 230 | + p_ctx: *mut c_void, |
| 231 | + e_conflict: i32, |
| 232 | + _: *mut libsqlite3_sys::sqlite3_changeset_iter, |
| 233 | + ) -> i32 { |
| 234 | + let ctx = &mut *(p_ctx as *mut HandlerCtx); |
| 235 | + |
| 236 | + if let Some(conflict) = &mut ctx.confict { |
| 237 | + let recv = v8::undefined(ctx.scope).into(); |
| 238 | + let args = [v8::Integer::new(ctx.scope, e_conflict).into()]; |
| 239 | + |
| 240 | + let ret = conflict.call(ctx.scope, recv, &args).unwrap(); |
| 241 | + return ret |
| 242 | + .int32_value(ctx.scope) |
| 243 | + .unwrap_or(libsqlite3_sys::SQLITE_CHANGESET_ABORT); |
| 244 | + } |
| 245 | + |
| 246 | + libsqlite3_sys::SQLITE_CHANGESET_ABORT |
| 247 | + } |
| 248 | + |
| 249 | + // Filter handler callback for `sqlite3changeset_apply()`. |
| 250 | + unsafe extern "C" fn filter_handler( |
| 251 | + p_ctx: *mut c_void, |
| 252 | + z_tab: *const c_char, |
| 253 | + ) -> i32 { |
| 254 | + let ctx = &mut *(p_ctx as *mut HandlerCtx); |
| 255 | + |
| 256 | + if let Some(filter) = &mut ctx.filter { |
| 257 | + let tab = CStr::from_ptr(z_tab).to_str().unwrap(); |
| 258 | + |
| 259 | + let recv = v8::undefined(ctx.scope).into(); |
| 260 | + let args = [v8::String::new(ctx.scope, tab).unwrap().into()]; |
| 261 | + |
| 262 | + let ret = filter.call(ctx.scope, recv, &args).unwrap(); |
| 263 | + return ret.boolean_value(ctx.scope) as i32; |
| 264 | + } |
| 265 | + |
| 266 | + 1 |
| 267 | + } |
| 268 | + |
| 269 | + let db = self.conn.borrow(); |
| 270 | + let db = db.as_ref().ok_or(SqliteError::AlreadyClosed)?; |
| 271 | + |
| 272 | + // It is safe to use scope in the handlers because they are never |
| 273 | + // called after the call to `sqlite3changeset_apply()`. |
| 274 | + let mut ctx = HandlerCtx { |
| 275 | + scope, |
| 276 | + confict: None, |
| 277 | + filter: None, |
| 278 | + }; |
| 279 | + |
| 280 | + if let Some(options) = options { |
| 281 | + if let Some(filter) = options.filter { |
| 282 | + let filter_cb: v8::Local<v8::Function> = filter |
| 283 | + .v8_value |
| 284 | + .try_into() |
| 285 | + .map_err(|_| SqliteError::InvalidCallback("filter"))?; |
| 286 | + ctx.filter = Some(filter_cb); |
| 287 | + } |
| 288 | + |
| 289 | + if let Some(on_conflict) = options.on_conflict { |
| 290 | + let on_conflict_cb: v8::Local<v8::Function> = on_conflict |
| 291 | + .v8_value |
| 292 | + .try_into() |
| 293 | + .map_err(|_| SqliteError::InvalidCallback("onConflict"))?; |
| 294 | + ctx.confict = Some(on_conflict_cb); |
| 295 | + } |
| 296 | + } |
| 297 | + |
| 298 | + // SAFETY: lifetime of the connection is guaranteed by reference |
| 299 | + // counting. |
| 300 | + let raw_handle = unsafe { db.handle() }; |
| 301 | + |
| 302 | + // SAFETY: `changeset` points to a valid memory location and its |
| 303 | + // length is correct. `ctx` is stack allocated and its lifetime is |
| 304 | + // longer than the call to `sqlite3changeset_apply()`. |
| 305 | + unsafe { |
| 306 | + let r = libsqlite3_sys::sqlite3changeset_apply( |
| 307 | + raw_handle, |
| 308 | + changeset.len() as i32, |
| 309 | + changeset.as_ptr() as *mut _, |
| 310 | + Some(filter_handler), |
| 311 | + Some(conflict_handler), |
| 312 | + &mut ctx as *mut _ as *mut c_void, |
| 313 | + ); |
| 314 | + |
| 315 | + if r == libsqlite3_sys::SQLITE_OK { |
| 316 | + return Ok(true); |
| 317 | + } else if r == libsqlite3_sys::SQLITE_ABORT { |
| 318 | + return Ok(false); |
| 319 | + } |
| 320 | + |
| 321 | + Err(SqliteError::ChangesetApplyFailed) |
| 322 | + } |
| 323 | + } |
| 324 | + |
200 | 325 | // Creates and attaches a session to the database.
|
201 | 326 | //
|
202 | 327 | // This method is a wrapper around `sqlite3session_create()` and
|
|
0 commit comments