Skip to main content

sqlite3_ext/ffi/
mod.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_snake_case)]
3#![allow(dead_code)]
4
5use crate::{value::Blob, Error};
6pub use linking::*;
7pub use sqlite3types::*;
8use std::{
9    ffi::{c_void, CString},
10    os::raw::{c_char, c_int},
11    ptr,
12};
13
14mod sqlite3funcs;
15mod sqlite3types;
16
17mod linking {
18    include!(concat!(env!("OUT_DIR"), "/linking.rs"));
19}
20
21/// We have to do this trampoline construct because the cfg attributes are evaluated in the
22/// context of the transcribed crate.
23#[cfg(modern_sqlite)]
24#[macro_export]
25#[doc(hidden)]
26macro_rules! sqlite3_match_version_trampoline {
27    ($($tail:tt)*) => { $crate::sqlite3_match_version!(@modern () $($tail)*) };
28}
29
30#[cfg(not(modern_sqlite))]
31#[macro_export]
32#[doc(hidden)]
33macro_rules! sqlite3_match_version_trampoline {
34    ($($tail:tt)*) => { $crate::sqlite3_match_version!(@old $($tail)*) };
35}
36
37/// Selectively enable features which require a particular SQLite version.
38///
39/// This macro mimics a match expression, except each pattern is a minimum supported version
40/// rather than an exact match. It performs a check for the given SQLite version both at
41/// compile time and at runtime. If both checks pass, the expression is evaluated, otherwise
42/// the following match arms are checked.
43///
44/// The minimum supported version of SQLite is 3.6.8. It is a compile error to attempt to match
45/// against an older version of SQLite using this macro (this helps avoid typos where digits
46/// are accidentally omitted from a version number).
47///
48/// This macro is particularly useful when interacting with ffi methods, since these may be
49/// missing on older versions of SQLite, which would cause a compilation error.
50///
51/// A fallback arm is always required when using this macro. For cases where no fallback is
52/// possible, use [sqlite3_require_version](crate::sqlite3_require_version).
53///
54/// # Examples
55///
56/// ```no_run
57/// use sqlite3_ext::{sqlite3_match_version, ffi};
58/// use std::ffi::c_void;
59///
60/// fn alloc_memory_with_sqlite3(len: usize) -> *mut c_void {
61///     unsafe {
62///         sqlite3_match_version! {
63///             3_008_007 => ffi::sqlite3_malloc64(len as _),
64///             _ => ffi::sqlite3_malloc(len as _),
65///         }
66///     }
67/// }
68/// ```
69#[macro_export]
70macro_rules! sqlite3_match_version {
71    // Comma optional: version => { block }
72    (@modern ($($body:tt)*) $ver:literal => { $($block:tt)* } $($tail:tt)* ) => {
73        $crate::sqlite3_match_version!(
74            @modern ( $($body)* $ver.. => {
75                $crate::sqlite3_match_version!(@verify $ver);
76                $($block)*
77            })
78            $($tail)*
79        )
80    };
81    (@old $ver:literal => { $($block:tt)* } $($tail:tt)* ) => {{
82        $crate::sqlite3_match_version!(@verify $ver);
83        $crate::sqlite3_match_version!(@old $($tail)*)
84    }};
85
86    // Comma required: version => expr,
87    (@modern ($($body:tt)*) $ver:literal => $expr:expr, $($tail:tt)* ) => {
88        $crate::sqlite3_match_version!(
89            @modern ( $($body)* $ver.. => {
90                $crate::sqlite3_match_version!(@verify $ver);
91                $expr
92            })
93            $($tail)*
94        )
95    };
96    (@old $ver:literal => $expr:expr, $($tail:tt)* ) => {{
97        $crate::sqlite3_match_version!(@verify $ver);
98        $crate::sqlite3_match_version!(@old $($tail)*)
99    }};
100
101    // Comma missing (no fallback): version => expr
102    (@modern ($($body:tt)*) $ver:literal => $expr:expr ) => {
103        compile_error!("non-exhaustive patterns: missing a wildcard pattern");
104    };
105    (@old $ver:literal => $expr:expr ) => {
106        compile_error!("non-exhaustive patterns: missing a wildcard pattern");
107    };
108
109    // Finish the match with a fallback
110    (@modern ($($body:tt)*) _ => $expr:expr $(,)? ) => {
111        match $crate::SQLITE_VERSION.as_i32() {
112            $($body)*
113            _ => $expr
114        }
115    };
116    (@old _ => $expr:expr $(,)? ) => {
117        $expr
118    };
119
120    // Strip a leading comma
121    (@modern ($($body:tt)*) , $($tail:tt)* ) => {
122        $crate::sqlite3_match_version!(@modern ( $($body)* ) $($tail)*)
123    };
124    (@old , $($tail:tt)* ) => {
125        $crate::sqlite3_match_version!(@old $($tail)*)
126    };
127
128    (@verify $version:literal) => {
129        /// Static assertions to verify that there are no mising/extra digits in the
130        /// version number.
131        #[cfg(debug_assertions)]
132        const _: () = {
133            assert!($version >= 3_006_008, stringify!($version is earlier than 3.6.8 (the minimum supported version of SQLite)));
134            assert!($version < 4_000_000, stringify!($version is newer than 4.0.0 (which is not a valid version of SQLite3)));
135        };
136    };
137
138    // Base case, with a guard that it has to look like the start of a match
139    ( $x:literal => $($tail:tt)* ) => {
140        $crate::sqlite3_match_version_trampoline!($x => $($tail)*)
141    };
142}
143
144/// Guard an expression behind an SQLite version.
145///
146/// This macro evaluates the SQLite version at compile time and at runtime. If both checks
147/// pass, the provided expression is evaluated. Otherwise, the macro evaluates to
148/// [Error::VersionNotSatisfied](crate::Error::VersionNotSatisfied).
149///
150/// This macro is particularly useful when interacting with ffi methods, since these may be
151/// missing on older versions of SQLite, which would cause a compilation error.
152///
153/// If no expression is provided, it defaults to `Ok(())`.
154///
155/// # Examples
156///
157/// ```no_run
158/// use sqlite3_ext::{*, ffi};
159/// use std::ffi::CStr;
160///
161/// pub fn sourceid() -> Result<&'static str> {
162///     sqlite3_require_version!(3_021_000, {
163///         let ret = unsafe { CStr::from_ptr(ffi::sqlite3_sourceid()) };
164///         Ok(ret.to_str().expect("sqlite3_sourceid"))
165///     })
166/// }
167/// ```
168#[macro_export]
169macro_rules! sqlite3_require_version {
170    ($version:literal) => {
171        $crate::sqlite3_require_version!($version, Ok(()))
172    };
173
174    ($version:literal, $expr:expr) => {
175        $crate::sqlite3_match_version! {
176            $version => {
177                let ret: Result<_> = $expr;
178                ret
179            }
180            _ => Err(Error::VersionNotSatisfied($version)),
181        }
182    };
183}
184
185/// Create the special marker value SQLITE_TRANSIENT.
186///
187/// # Safety
188///
189/// Per rustc, "it is undefined behavior to use this value". `¯\_(ツ)_/¯`
190pub const unsafe fn sqlite_transient() -> Option<unsafe extern "C" fn(arg1: *mut c_void)> {
191    std::mem::transmute(-1 as isize as usize)
192}
193
194/// Clone the provided string into a nul-terminated string created by sqlite3_malloc. This
195/// function returns a NULL pointer if the input string is empty. SQLite interfaces generally
196/// understand this to mean "no string", but other consumers may expect a 0-length string.
197pub fn str_to_sqlite3(val: &str) -> Result<*mut c_char, Error> {
198    if val.is_empty() {
199        return Ok(ptr::null_mut());
200    }
201    let len: usize = val.len().checked_add(1).ok_or(crate::types::SQLITE_NOMEM)?;
202    unsafe {
203        let ptr: *mut c_char = sqlite3_match_version! {
204            3_008_007 => sqlite3_malloc64(len as _) as _,
205            _ => sqlite3_malloc(len as _) as _,
206        };
207        if !ptr.is_null() {
208            ptr::copy_nonoverlapping(val.as_ptr(), ptr as _, len as _);
209            *ptr.add(len - 1) = 0;
210            Ok(ptr)
211        } else {
212            Err(crate::types::SQLITE_NOMEM)
213        }
214    }
215}
216
217pub unsafe fn handle_error(err: impl Into<Error>, msg: *mut *mut c_char) -> c_int {
218    err.into().into_sqlite(msg)
219}
220
221pub unsafe fn handle_result(result: Result<(), Error>, msg: *mut *mut c_char) -> c_int {
222    match result {
223        Ok(_) => SQLITE_OK,
224        Err(e) => handle_error(e, msg),
225    }
226}
227
228pub fn is_version(min: c_int) -> bool {
229    let found = unsafe { sqlite3_libversion_number() };
230    found >= min
231}
232
233pub unsafe extern "C" fn drop_boxed<T>(data: *mut c_void) {
234    drop(Box::<T>::from_raw(data as _));
235}
236
237pub unsafe extern "C" fn drop_cstring(data: *mut c_void) {
238    drop(CString::from_raw(data as _));
239}
240
241pub unsafe extern "C" fn drop_blob(data: *mut c_void) {
242    drop(Blob::from_raw(data));
243}
244
245#[cfg(test)]
246mod test {
247    use crate::sqlite3_match_version;
248
249    fn test_patterns() {
250        let s = sqlite3_match_version! {
251            3_008_008 => "expr,",
252            3_008_007 => { "{expr}" }
253            3_008_006 => { "{expr}," },
254            _ => "fall,",
255        };
256        assert_eq!(s, "expr,");
257        let s = sqlite3_match_version! {
258            3_008_006 => "expr,",
259            _ => "fall"
260        };
261        assert_eq!(s, "expr,");
262    }
263}