From 1292f300937f85835c410c0f32ba403479257f5f Mon Sep 17 00:00:00 2001 From: kodeschreiber Date: Mon, 22 Apr 2024 22:21:48 -0400 Subject: [PATCH 1/2] Revised from previous PR: - Added the "sqlite3_key_v2" and "sqlite3_rekey_v2" functions - Added a feature gate called "encryption" to disable the feature if not used --- Cargo.toml | 1 + Cargo.toml.orig | 43 +++++ src/connection.rs | 36 ++++ src/connection.rs.orig | 368 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 448 insertions(+) create mode 100644 Cargo.toml.orig create mode 100644 src/connection.rs.orig diff --git a/Cargo.toml b/Cargo.toml index ffd0b83f..93408750 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ keywords = ["database"] default = ["linkage"] bundled = ["sqlite3-sys/bundled"] extension = [] +encryption = [] linkage = ["sqlite3-sys/linkage"] [dependencies.sqlite3-sys] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 00000000..ffd0b83f --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,43 @@ +[package] +name = "sqlite" +version = "0.35.0" +edition = "2021" +license = "Apache-2.0 OR MIT" +authors = [ + "Alec Moskvin ", + "Angel Leon ", + "Cecile Tonglet ", + "Daniel Dulaney ", + "Ivan Stankovic ", + "Ivan Ukhov ", + "Jake Kerr ", + "James Haywood ", + "Jayson Reis ", + "Jonatan Lemes ", + "Pierre Krieger ", + "Sean Klein ", + "Sophie Tauchert <999eagle@999eagle.moe>", + "Tomoki Aonuma ", + "Vincenzo Palazzo ", + "Yorhel ", +] +description = "The package provides an interface to SQLite." +documentation = "https://docs.rs/sqlite" +homepage = "https://github.com/stainless-steel/sqlite" +repository = "https://github.com/stainless-steel/sqlite" +readme = "README.md" +categories = ["api-bindings", "database"] +keywords = ["database"] + +[features] +default = ["linkage"] +bundled = ["sqlite3-sys/bundled"] +extension = [] +linkage = ["sqlite3-sys/linkage"] + +[dependencies.sqlite3-sys] +version = "0.17" +default-features = false + +[dev-dependencies] +temporary = "0.6" diff --git a/src/connection.rs b/src/connection.rs index b8481a75..83e18e63 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -224,6 +224,42 @@ impl Connection { Ok(()) } + /// Set the encryption key. + #[cfg(feature = "encryption")] + #[inline] + pub fn set_encryption_key>(&self, key: T) -> Result<()> { + unsafe { + ok!( + self.raw.0, + ffi::sqlite3_key_v2( + self.raw.0, + std::ptr::null() as *const c_char, + str_to_cstr!(key.as_ref()).as_ptr() as *const c_void, + key.as_ref().len() as c_int, + ) + ); + } + Ok(()) + } + + /// Change the encryption key. + #[cfg(feature = "encryption")] + #[inline] + pub fn reset_encryption_key>(&self, new_key: T) -> Result<()> { + unsafe { + ok!( + self.raw.0, + ffi::sqlite3_rekey_v2( + self.raw.0, + std::ptr::null() as *const c_char, + str_to_cstr!(new_key.as_ref()).as_ptr() as *const c_void, + new_key.as_ref().len() as c_int, + ) + ); + } + Ok(()) + } + /// Return the number of rows inserted, updated, or deleted by the most recent INSERT, UPDATE, /// or DELETE statement. #[inline] diff --git a/src/connection.rs.orig b/src/connection.rs.orig new file mode 100644 index 00000000..b8481a75 --- /dev/null +++ b/src/connection.rs.orig @@ -0,0 +1,368 @@ +use core::ffi::{c_char, c_int, c_void}; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::path::Path; + +use crate::error::Result; +use crate::statement::Statement; + +/// A connection. +pub struct Connection { + raw: Raw, + busy_callback: Option bool + Send>>, + phantom: PhantomData, +} + +/// A thread-safe connection. +pub struct ConnectionThreadSafe(Connection); + +/// Flags for opening a connection. +#[derive(Clone, Copy, Debug)] +pub struct OpenFlags(c_int); + +struct Raw(*mut ffi::sqlite3); + +impl Connection { + /// Open a read-write connection to a new or existing database. + pub fn open>(path: T) -> Result { + Connection::open_with_flags(path, OpenFlags::new().with_create().with_read_write()) + } + + /// Open a connection with specific flags. + pub fn open_with_flags>(path: T, flags: OpenFlags) -> Result { + let mut raw = std::ptr::null_mut(); + unsafe { + let code = ffi::sqlite3_open_v2( + path_to_cstr!(path.as_ref()).as_ptr(), + &mut raw, + flags.0, + std::ptr::null(), + ); + match code { + ffi::SQLITE_OK => {} + code => match crate::error::last(raw) { + Some(error) => { + ffi::sqlite3_close(raw); + return Err(error); + } + _ => { + ffi::sqlite3_close(raw); + return Err(crate::error::Error { + code: Some(code as isize), + message: None, + }); + } + }, + } + } + Ok(Connection { + raw: Raw(raw), + busy_callback: None, + phantom: PhantomData, + }) + } + + /// Open a thread-safe read-write connection to a new or existing database. + pub fn open_thread_safe>(path: T) -> Result { + Connection::open_with_flags( + path, + OpenFlags::new() + .with_create() + .with_read_write() + .with_full_mutex(), + ) + .map(ConnectionThreadSafe) + } + + /// Open a thread-safe connection with specific flags. + pub fn open_thread_safe_with_flags>( + path: T, + flags: OpenFlags, + ) -> Result { + Connection::open_with_flags(path, flags.with_full_mutex()).map(ConnectionThreadSafe) + } + + /// Execute a statement without processing the resulting rows if any. + #[inline] + pub fn execute>(&self, statement: T) -> Result<()> { + unsafe { + ok!( + self.raw.0, + ffi::sqlite3_exec( + self.raw.0, + str_to_cstr!(statement.as_ref()).as_ptr(), + None, + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + ); + } + Ok(()) + } + + /// Execute a statement and process the resulting rows as plain text. + /// + /// The callback is triggered for each row. If the callback returns `false`, no more rows will + /// be processed. For large queries and non-string data types, prepared statement are highly + /// preferable; see `prepare`. + #[inline] + pub fn iterate, F>(&self, statement: T, callback: F) -> Result<()> + where + F: FnMut(&[(&str, Option<&str>)]) -> bool, + { + unsafe { + let callback = Box::new(callback); + ok!( + self.raw.0, + ffi::sqlite3_exec( + self.raw.0, + str_to_cstr!(statement.as_ref()).as_ptr(), + Some(process_callback::), + &*callback as *const F as *mut F as *mut _, + std::ptr::null_mut(), + ) + ); + } + Ok(()) + } + + /// Create a prepared statement. + #[inline] + pub fn prepare>(&self, statement: T) -> Result> { + crate::statement::new(self.raw.0, statement) + } + + /// Set a callback for handling busy events. + /// + /// The callback is triggered when the database cannot perform an operation due to processing + /// of some other request. If the callback returns `true`, the operation will be repeated. + pub fn set_busy_handler(&mut self, callback: F) -> Result<()> + where + F: FnMut(usize) -> bool + Send + 'static, + { + self.remove_busy_handler()?; + unsafe { + let callback = Box::new(callback); + let result = ffi::sqlite3_busy_handler( + self.raw.0, + Some(busy_callback::), + &*callback as *const F as *mut F as *mut _, + ); + self.busy_callback = Some(callback); + ok!(self.raw.0, result); + } + Ok(()) + } + + /// Set an implicit callback for handling busy events that tries to repeat rejected operations + /// until a timeout expires. + #[inline] + pub fn set_busy_timeout(&mut self, milliseconds: usize) -> Result<()> { + unsafe { + ok!( + self.raw.0, + ffi::sqlite3_busy_timeout(self.raw.0, milliseconds as c_int) + ); + } + Ok(()) + } + + /// Remove the callback handling busy events. + #[inline] + pub fn remove_busy_handler(&mut self) -> Result<()> { + self.busy_callback = None; + unsafe { + ok!( + self.raw.0, + ffi::sqlite3_busy_handler(self.raw.0, None, std::ptr::null_mut()) + ); + } + Ok(()) + } + + /// Enable loading extensions. + #[cfg(feature = "extension")] + #[inline] + pub fn enable_extension(&self) -> Result<()> { + unsafe { + ok!( + self.raw.0, + ffi::sqlite3_enable_load_extension(self.raw.0, 1 as c_int) + ); + } + Ok(()) + } + + /// Disable loading extensions. + #[cfg(feature = "extension")] + #[inline] + pub fn disable_extension(&self) -> Result<()> { + unsafe { + ok!( + self.raw.0, + ffi::sqlite3_enable_load_extension(self.raw.0, 0 as c_int) + ); + } + Ok(()) + } + + /// Load an extension. + #[cfg(feature = "extension")] + #[inline] + pub fn load_extension>(&self, name: T) -> Result<()> { + unsafe { + ok!( + self.raw.0, + ffi::sqlite3_load_extension( + self.raw.0, + str_to_cstr!(name.as_ref()).as_ptr() as *const c_char, + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + ); + } + Ok(()) + } + + /// Return the number of rows inserted, updated, or deleted by the most recent INSERT, UPDATE, + /// or DELETE statement. + #[inline] + pub fn change_count(&self) -> usize { + unsafe { ffi::sqlite3_changes(self.raw.0) as usize } + } + + /// Return the total number of rows inserted, updated, and deleted by all INSERT, UPDATE, and + /// DELETE statements since the connection was opened. + #[inline] + pub fn total_change_count(&self) -> usize { + unsafe { ffi::sqlite3_total_changes(self.raw.0) as usize } + } + + #[doc(hidden)] + #[inline] + pub fn as_raw(&self) -> *mut ffi::sqlite3 { + self.raw.0 + } +} + +impl Drop for Connection { + #[inline] + #[allow(unused_must_use)] + fn drop(&mut self) { + self.remove_busy_handler(); + unsafe { ffi::sqlite3_close(self.raw.0) }; + } +} + +impl OpenFlags { + /// Create flags for opening a database connection. + #[inline] + pub fn new() -> Self { + OpenFlags(0) + } + + /// Create the database if it does not already exist. + pub fn with_create(mut self) -> Self { + self.0 |= ffi::SQLITE_OPEN_CREATE; + self + } + + /// Open the database in the serialized [threading mode][1]. + /// + /// [1]: https://www.sqlite.org/threadsafe.html + pub fn with_full_mutex(mut self) -> Self { + self.0 |= ffi::SQLITE_OPEN_FULLMUTEX; + self + } + + /// Opens the database in the multi-thread [threading mode][1]. + /// + /// [1]: https://www.sqlite.org/threadsafe.html + pub fn with_no_mutex(mut self) -> Self { + self.0 |= ffi::SQLITE_OPEN_NOMUTEX; + self + } + + /// Open the database for reading only. + pub fn with_read_only(mut self) -> Self { + self.0 |= ffi::SQLITE_OPEN_READONLY; + self + } + + /// Open the database for reading and writing. + pub fn with_read_write(mut self) -> Self { + self.0 |= ffi::SQLITE_OPEN_READWRITE; + self + } + + /// Allow the path to be interpreted as a URI. + pub fn with_uri(mut self) -> Self { + self.0 |= ffi::SQLITE_OPEN_URI; + self + } +} + +impl Default for OpenFlags { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Deref for ConnectionThreadSafe { + type Target = Connection; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ConnectionThreadSafe { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +unsafe impl Sync for ConnectionThreadSafe {} + +unsafe impl Send for Raw {} + +extern "C" fn busy_callback(callback: *mut c_void, attempts: c_int) -> c_int +where + F: FnMut(usize) -> bool, +{ + unsafe { c_int::from((*(callback as *mut F))(attempts as usize)) } +} + +extern "C" fn process_callback( + callback: *mut c_void, + count: c_int, + values: *mut *mut c_char, + columns: *mut *mut c_char, +) -> c_int +where + F: FnMut(&[(&str, Option<&str>)]) -> bool, +{ + unsafe { + let mut pairs = Vec::with_capacity(count as usize); + for index in 0..(count as isize) { + let column = { + let pointer = *columns.offset(index); + debug_assert!(!pointer.is_null()); + c_str_to_str!(pointer).unwrap() + }; + let value = { + let pointer = *values.offset(index); + if pointer.is_null() { + None + } else { + Some(c_str_to_str!(pointer).unwrap()) + } + }; + pairs.push((column, value)); + } + c_int::from(!(*(callback as *mut F))(&pairs)) + } +} From cf40be122c28e00e692cc0eec25e0ce1df6881e0 Mon Sep 17 00:00:00 2001 From: kodeschreiber Date: Tue, 23 Apr 2024 22:41:08 -0400 Subject: [PATCH 2/2] Removed excess orig files --- Cargo.toml.orig | 43 ----- src/connection.rs.orig | 368 ----------------------------------------- 2 files changed, 411 deletions(-) delete mode 100644 Cargo.toml.orig delete mode 100644 src/connection.rs.orig diff --git a/Cargo.toml.orig b/Cargo.toml.orig deleted file mode 100644 index ffd0b83f..00000000 --- a/Cargo.toml.orig +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "sqlite" -version = "0.35.0" -edition = "2021" -license = "Apache-2.0 OR MIT" -authors = [ - "Alec Moskvin ", - "Angel Leon ", - "Cecile Tonglet ", - "Daniel Dulaney ", - "Ivan Stankovic ", - "Ivan Ukhov ", - "Jake Kerr ", - "James Haywood ", - "Jayson Reis ", - "Jonatan Lemes ", - "Pierre Krieger ", - "Sean Klein ", - "Sophie Tauchert <999eagle@999eagle.moe>", - "Tomoki Aonuma ", - "Vincenzo Palazzo ", - "Yorhel ", -] -description = "The package provides an interface to SQLite." -documentation = "https://docs.rs/sqlite" -homepage = "https://github.com/stainless-steel/sqlite" -repository = "https://github.com/stainless-steel/sqlite" -readme = "README.md" -categories = ["api-bindings", "database"] -keywords = ["database"] - -[features] -default = ["linkage"] -bundled = ["sqlite3-sys/bundled"] -extension = [] -linkage = ["sqlite3-sys/linkage"] - -[dependencies.sqlite3-sys] -version = "0.17" -default-features = false - -[dev-dependencies] -temporary = "0.6" diff --git a/src/connection.rs.orig b/src/connection.rs.orig deleted file mode 100644 index b8481a75..00000000 --- a/src/connection.rs.orig +++ /dev/null @@ -1,368 +0,0 @@ -use core::ffi::{c_char, c_int, c_void}; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::path::Path; - -use crate::error::Result; -use crate::statement::Statement; - -/// A connection. -pub struct Connection { - raw: Raw, - busy_callback: Option bool + Send>>, - phantom: PhantomData, -} - -/// A thread-safe connection. -pub struct ConnectionThreadSafe(Connection); - -/// Flags for opening a connection. -#[derive(Clone, Copy, Debug)] -pub struct OpenFlags(c_int); - -struct Raw(*mut ffi::sqlite3); - -impl Connection { - /// Open a read-write connection to a new or existing database. - pub fn open>(path: T) -> Result { - Connection::open_with_flags(path, OpenFlags::new().with_create().with_read_write()) - } - - /// Open a connection with specific flags. - pub fn open_with_flags>(path: T, flags: OpenFlags) -> Result { - let mut raw = std::ptr::null_mut(); - unsafe { - let code = ffi::sqlite3_open_v2( - path_to_cstr!(path.as_ref()).as_ptr(), - &mut raw, - flags.0, - std::ptr::null(), - ); - match code { - ffi::SQLITE_OK => {} - code => match crate::error::last(raw) { - Some(error) => { - ffi::sqlite3_close(raw); - return Err(error); - } - _ => { - ffi::sqlite3_close(raw); - return Err(crate::error::Error { - code: Some(code as isize), - message: None, - }); - } - }, - } - } - Ok(Connection { - raw: Raw(raw), - busy_callback: None, - phantom: PhantomData, - }) - } - - /// Open a thread-safe read-write connection to a new or existing database. - pub fn open_thread_safe>(path: T) -> Result { - Connection::open_with_flags( - path, - OpenFlags::new() - .with_create() - .with_read_write() - .with_full_mutex(), - ) - .map(ConnectionThreadSafe) - } - - /// Open a thread-safe connection with specific flags. - pub fn open_thread_safe_with_flags>( - path: T, - flags: OpenFlags, - ) -> Result { - Connection::open_with_flags(path, flags.with_full_mutex()).map(ConnectionThreadSafe) - } - - /// Execute a statement without processing the resulting rows if any. - #[inline] - pub fn execute>(&self, statement: T) -> Result<()> { - unsafe { - ok!( - self.raw.0, - ffi::sqlite3_exec( - self.raw.0, - str_to_cstr!(statement.as_ref()).as_ptr(), - None, - std::ptr::null_mut(), - std::ptr::null_mut(), - ) - ); - } - Ok(()) - } - - /// Execute a statement and process the resulting rows as plain text. - /// - /// The callback is triggered for each row. If the callback returns `false`, no more rows will - /// be processed. For large queries and non-string data types, prepared statement are highly - /// preferable; see `prepare`. - #[inline] - pub fn iterate, F>(&self, statement: T, callback: F) -> Result<()> - where - F: FnMut(&[(&str, Option<&str>)]) -> bool, - { - unsafe { - let callback = Box::new(callback); - ok!( - self.raw.0, - ffi::sqlite3_exec( - self.raw.0, - str_to_cstr!(statement.as_ref()).as_ptr(), - Some(process_callback::), - &*callback as *const F as *mut F as *mut _, - std::ptr::null_mut(), - ) - ); - } - Ok(()) - } - - /// Create a prepared statement. - #[inline] - pub fn prepare>(&self, statement: T) -> Result> { - crate::statement::new(self.raw.0, statement) - } - - /// Set a callback for handling busy events. - /// - /// The callback is triggered when the database cannot perform an operation due to processing - /// of some other request. If the callback returns `true`, the operation will be repeated. - pub fn set_busy_handler(&mut self, callback: F) -> Result<()> - where - F: FnMut(usize) -> bool + Send + 'static, - { - self.remove_busy_handler()?; - unsafe { - let callback = Box::new(callback); - let result = ffi::sqlite3_busy_handler( - self.raw.0, - Some(busy_callback::), - &*callback as *const F as *mut F as *mut _, - ); - self.busy_callback = Some(callback); - ok!(self.raw.0, result); - } - Ok(()) - } - - /// Set an implicit callback for handling busy events that tries to repeat rejected operations - /// until a timeout expires. - #[inline] - pub fn set_busy_timeout(&mut self, milliseconds: usize) -> Result<()> { - unsafe { - ok!( - self.raw.0, - ffi::sqlite3_busy_timeout(self.raw.0, milliseconds as c_int) - ); - } - Ok(()) - } - - /// Remove the callback handling busy events. - #[inline] - pub fn remove_busy_handler(&mut self) -> Result<()> { - self.busy_callback = None; - unsafe { - ok!( - self.raw.0, - ffi::sqlite3_busy_handler(self.raw.0, None, std::ptr::null_mut()) - ); - } - Ok(()) - } - - /// Enable loading extensions. - #[cfg(feature = "extension")] - #[inline] - pub fn enable_extension(&self) -> Result<()> { - unsafe { - ok!( - self.raw.0, - ffi::sqlite3_enable_load_extension(self.raw.0, 1 as c_int) - ); - } - Ok(()) - } - - /// Disable loading extensions. - #[cfg(feature = "extension")] - #[inline] - pub fn disable_extension(&self) -> Result<()> { - unsafe { - ok!( - self.raw.0, - ffi::sqlite3_enable_load_extension(self.raw.0, 0 as c_int) - ); - } - Ok(()) - } - - /// Load an extension. - #[cfg(feature = "extension")] - #[inline] - pub fn load_extension>(&self, name: T) -> Result<()> { - unsafe { - ok!( - self.raw.0, - ffi::sqlite3_load_extension( - self.raw.0, - str_to_cstr!(name.as_ref()).as_ptr() as *const c_char, - std::ptr::null_mut(), - std::ptr::null_mut(), - ) - ); - } - Ok(()) - } - - /// Return the number of rows inserted, updated, or deleted by the most recent INSERT, UPDATE, - /// or DELETE statement. - #[inline] - pub fn change_count(&self) -> usize { - unsafe { ffi::sqlite3_changes(self.raw.0) as usize } - } - - /// Return the total number of rows inserted, updated, and deleted by all INSERT, UPDATE, and - /// DELETE statements since the connection was opened. - #[inline] - pub fn total_change_count(&self) -> usize { - unsafe { ffi::sqlite3_total_changes(self.raw.0) as usize } - } - - #[doc(hidden)] - #[inline] - pub fn as_raw(&self) -> *mut ffi::sqlite3 { - self.raw.0 - } -} - -impl Drop for Connection { - #[inline] - #[allow(unused_must_use)] - fn drop(&mut self) { - self.remove_busy_handler(); - unsafe { ffi::sqlite3_close(self.raw.0) }; - } -} - -impl OpenFlags { - /// Create flags for opening a database connection. - #[inline] - pub fn new() -> Self { - OpenFlags(0) - } - - /// Create the database if it does not already exist. - pub fn with_create(mut self) -> Self { - self.0 |= ffi::SQLITE_OPEN_CREATE; - self - } - - /// Open the database in the serialized [threading mode][1]. - /// - /// [1]: https://www.sqlite.org/threadsafe.html - pub fn with_full_mutex(mut self) -> Self { - self.0 |= ffi::SQLITE_OPEN_FULLMUTEX; - self - } - - /// Opens the database in the multi-thread [threading mode][1]. - /// - /// [1]: https://www.sqlite.org/threadsafe.html - pub fn with_no_mutex(mut self) -> Self { - self.0 |= ffi::SQLITE_OPEN_NOMUTEX; - self - } - - /// Open the database for reading only. - pub fn with_read_only(mut self) -> Self { - self.0 |= ffi::SQLITE_OPEN_READONLY; - self - } - - /// Open the database for reading and writing. - pub fn with_read_write(mut self) -> Self { - self.0 |= ffi::SQLITE_OPEN_READWRITE; - self - } - - /// Allow the path to be interpreted as a URI. - pub fn with_uri(mut self) -> Self { - self.0 |= ffi::SQLITE_OPEN_URI; - self - } -} - -impl Default for OpenFlags { - #[inline] - fn default() -> Self { - Self::new() - } -} - -impl Deref for ConnectionThreadSafe { - type Target = Connection; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for ConnectionThreadSafe { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -unsafe impl Sync for ConnectionThreadSafe {} - -unsafe impl Send for Raw {} - -extern "C" fn busy_callback(callback: *mut c_void, attempts: c_int) -> c_int -where - F: FnMut(usize) -> bool, -{ - unsafe { c_int::from((*(callback as *mut F))(attempts as usize)) } -} - -extern "C" fn process_callback( - callback: *mut c_void, - count: c_int, - values: *mut *mut c_char, - columns: *mut *mut c_char, -) -> c_int -where - F: FnMut(&[(&str, Option<&str>)]) -> bool, -{ - unsafe { - let mut pairs = Vec::with_capacity(count as usize); - for index in 0..(count as isize) { - let column = { - let pointer = *columns.offset(index); - debug_assert!(!pointer.is_null()); - c_str_to_str!(pointer).unwrap() - }; - let value = { - let pointer = *values.offset(index); - if pointer.is_null() { - None - } else { - Some(c_str_to_str!(pointer).unwrap()) - } - }; - pairs.push((column, value)); - } - c_int::from(!(*(callback as *mut F))(&pairs)) - } -}