sqlite_loadable/
api.rs

1//! (Mostly) safe wrappers around low-level sqlite3 C API.
2//!
3//! Uses the unsafe low-level API's defined in [`crate::ext`].
4//!
5//! Useful when working with sqlite3_value or sqlite3_context.
6#![allow(clippy::not_unsafe_ptr_arg_deref)]
7
8use crate::ext::{
9    sqlite3ext_get_auxdata, sqlite3ext_result_blob, sqlite3ext_result_double,
10    sqlite3ext_result_error, sqlite3ext_result_error_code, sqlite3ext_result_int,
11    sqlite3ext_result_int64, sqlite3ext_result_null, sqlite3ext_result_pointer,
12    sqlite3ext_result_subtype, sqlite3ext_result_text, sqlite3ext_set_auxdata,
13    sqlite3ext_value_blob, sqlite3ext_value_bytes, sqlite3ext_value_double, sqlite3ext_value_int,
14    sqlite3ext_value_int64, sqlite3ext_value_pointer, sqlite3ext_value_text, sqlite3ext_value_type,
15};
16use crate::Error;
17use sqlite3ext_sys::sqlite3_mprintf;
18use sqlite3ext_sys::{
19    sqlite3_context, sqlite3_value, SQLITE_BLOB, SQLITE_FLOAT, SQLITE_INTEGER, SQLITE_NULL,
20    SQLITE_TEXT,
21};
22use std::os::raw::c_int;
23use std::slice::from_raw_parts;
24use std::str::Utf8Error;
25use std::{
26    ffi::{CStr, CString, NulError},
27    os::raw::{c_char, c_void},
28};
29
30/// Ergonomic wrapper around a raw sqlite3_value. It is the caller's reponsibility
31/// to ensure that a given pointer points to a valid sqlite3_value object.
32/// There seems to be a 5-10% perf cost when using Value vs calling functions on
33/// raw pointers
34pub struct Value {
35    value: *mut sqlite3_value,
36    value_type: ValueType,
37}
38
39impl Value {
40    /// Create a Value struct from a borrowed sqlite3_value pointer
41    pub fn from(value: &*mut sqlite3_value) -> crate::Result<Value> {
42        let value_type = value_type(value);
43        Ok(Value {
44            value: value.to_owned(),
45            value_type,
46        })
47    }
48    /// Create a Value struct from a sqlite3_value pointer slice
49    /// at the given index.
50    pub fn at(values: &[*mut sqlite3_value], at: usize) -> Option<Value> {
51        let value = values.get(at)?;
52        let value_type = value_type(value);
53        Some(Value {
54            value: value.to_owned(),
55            value_type,
56        })
57    }
58
59    /// Ensure that the value's type isn't SQLITE_NULL - return the
60    /// given error as an Err.
61    pub fn notnull_or(&self, error: Error) -> crate::Result<&Self> {
62        if self.value_type != ValueType::Null {
63            Ok(self)
64        } else {
65            Err(error)
66        }
67    }
68
69    /// Ensure that the value's type isn't SQLITE_NULL - otherwise
70    /// call the error function and return as Err.
71    pub fn notnull_or_else<F>(&self, err: F) -> crate::Result<&Self>
72    where
73        F: FnOnce() -> Error,
74    {
75        if self.value_type != ValueType::Null {
76            Ok(self)
77        } else {
78            Err(err())
79        }
80    }
81
82    /// Returns the UTF8 representation of the underlying sqlite_value.
83    /// Fails if the value type is SQLITE_NULL, or if there's a UTF8
84    /// error on the resulting string.
85    pub fn text_or_else<F>(&self, error: F) -> crate::Result<&str>
86    where
87        F: FnOnce(Error) -> Error,
88    {
89        match value_text(&self.value) {
90            Ok(value) => Ok(value),
91            Err(err) => Err(error(err.into())),
92        }
93    }
94}
95
96/// Possible error cases when calling [`mprintf`], aka the sqlite3_mprintf function.
97#[derive(Debug)]
98pub enum MprintfError {
99    Nul(NulError),
100    Oom,
101}
102
103/// Calls [`sqlite3_mprintf`](https://sqlite.org/c3ref/mprintf.html) on the
104/// given string, with memory allocated by sqlite3.
105/// Meant to be passed into sqlite APIs that require sqlite-allocated strings,
106/// like virtual table's `zErrMsg` or xBestIndex's `idxStr`
107pub fn mprintf(base: &str) -> Result<*mut c_char, MprintfError> {
108    let cbase = CString::new(base.as_bytes()).map_err(MprintfError::Nul)?;
109
110    let result = unsafe { sqlite3_mprintf(cbase.as_ptr()) };
111    if result.is_null() {
112        Err(MprintfError::Oom)
113    } else {
114        Ok(result)
115    }
116}
117
118/// Returns the [`sqlite3_value_blob`](https://www.sqlite.org/c3ref/value_blob.html) result
119/// from the given sqlite3_value, as a u8 slice.
120pub fn value_blob<'a>(value: &*mut sqlite3_value) -> &'a [u8] {
121    let n = value_bytes(value);
122    let b = unsafe { sqlite3ext_value_blob(value.to_owned()) };
123    return unsafe { from_raw_parts(b.cast::<u8>(), n as usize) };
124}
125
126/// Returns the [`sqlite3_value_bytes`](https://www.sqlite.org/c3ref/value_blob.html) result
127/// from the given sqlite3_value, as i32.
128pub fn value_bytes(value: &*mut sqlite3_value) -> i32 {
129    unsafe { sqlite3ext_value_bytes(value.to_owned()) }
130}
131
132/// Returns the [`sqlite3_value_text`](https://www.sqlite.org/c3ref/value_blob.html) result
133/// from the given sqlite3_value, as a str. If the number of bytes of the underlying value
134/// is 0, then an empty string is returned. A UTF8 Error is returned if there are problems
135/// encoding the string.
136pub fn value_text<'a>(value: &*mut sqlite3_value) -> Result<&'a str, Utf8Error> {
137    let n = value_bytes(value);
138    if n == 0 {
139        return Ok("");
140    }
141    unsafe {
142        let c_string = sqlite3ext_value_text(value.to_owned());
143        // TODO can i32 always fit as usize? maybe not all architectures...
144        std::str::from_utf8(from_raw_parts(c_string, n as usize))
145    }
146}
147
148pub fn value_text_notnull<'a>(value: &*mut sqlite3_value) -> Result<&'a str, Error> {
149    if value_type(value) == ValueType::Null {
150        return Err(Error::new_message("Unexpected null value"));
151    }
152    let c_string = unsafe { sqlite3ext_value_text(value.to_owned()) };
153    let string = unsafe { CStr::from_ptr(c_string as *const c_char) };
154    Ok(string.to_str()?)
155}
156
157/// [`sqlite3_value_pointer`](https://www.sqlite.org/bindptr.html)
158///
159/// # Safety
160/// Calls [`Box::from_raw`]
161pub unsafe fn value_pointer<T>(value: &*mut sqlite3_value, c_name: &[u8]) -> Option<Box<T>> {
162    let result = sqlite3ext_value_pointer(
163        value.to_owned(),
164        c_name.as_ptr().cast::<c_char>().cast_mut(),
165    );
166
167    if result.is_null() {
168        return None;
169    }
170
171    Some(Box::from_raw(result.cast::<T>()))
172}
173
174/// Returns the [`sqlite3_value_int`](https://www.sqlite.org/c3ref/value_blob.html) result
175/// from the given sqlite3_value, as i32.
176pub fn value_int(value: &*mut sqlite3_value) -> i32 {
177    unsafe { sqlite3ext_value_int(value.to_owned()) }
178}
179
180/// Returns the [`sqlite3_value_int64`](https://www.sqlite.org/c3ref/value_blob.html) result
181/// from the given sqlite3_value, as i64.
182pub fn value_int64(value: &*mut sqlite3_value) -> i64 {
183    unsafe { sqlite3ext_value_int64(value.to_owned()) }
184}
185
186/// Returns the [`sqlite3_value_double`](https://www.sqlite.org/c3ref/value_blob.html) result
187/// from the given sqlite3_value, as f64.
188pub fn value_double(value: &*mut sqlite3_value) -> f64 {
189    unsafe { sqlite3ext_value_double(value.to_owned()) }
190}
191
192/// Possible values that sqlite3_value_type will return for a value.
193#[derive(Eq, PartialEq)]
194pub enum ValueType {
195    /// text or a string, aka SQLITE_TEXT
196    Text,
197    /// Integer, aka  SQLITE_INTEGER
198    Integer,
199    /// Float/double, aka  SQLITE_FLOAT
200    Float,
201    /// blob, aka  SQLITE_BLOB
202    Blob,
203    /// NULL, aka  SQLITE_NULL
204    Null,
205}
206
207/// Returns the [`sqlite3_value_type`](https://www.sqlite.org/c3ref/value_blob.html)
208/// result of the given value, one of TEXT/INT/FLOAT/BLOB/NULL.
209pub fn value_type(value: &*mut sqlite3_value) -> ValueType {
210    let raw_type = unsafe { sqlite3ext_value_type(value.to_owned()) };
211    // "as u32" because bindings for constants are u32 for some reason???
212    match raw_type as u32 {
213        SQLITE_TEXT => ValueType::Text,
214        SQLITE_INTEGER => ValueType::Integer,
215        SQLITE_FLOAT => ValueType::Float,
216        SQLITE_BLOB => ValueType::Blob,
217        SQLITE_NULL => ValueType::Null,
218        // rationale: SQLite is never going to add a new value type as
219        // long as sqlite3 is version 3. Certain extensions also make
220        // this same extension, so we can as well
221        _ => unreachable!(),
222    }
223}
224
225/// Calls [`sqlite3_result_text`](https://www.sqlite.org/c3ref/result_blob.html)
226/// to represent that a function returns a string with the given value. Fails if
227/// the string length is larger than i32 maximum value.
228pub fn result_text<S: AsRef<str>>(context: *mut sqlite3_context, text: S) -> crate::Result<()> {
229    let bytes = text.as_ref().as_bytes();
230    unsafe {
231        // Rational: why not use CString::new here? Turns out, SQLite strings can have NUL characters
232        // inside of strings. It fucks with LENGTH()/QUOTE(), but is totally valid. So, we should allow
233        // returning strings with NULL values, as the "n" parameter sets the size limit of the string.
234        // <https://www.sqlite.org/nulinstr.html>
235        let s = CString::from_vec_unchecked(bytes.into());
236
237        let n: i32 = bytes
238            .len()
239            .try_into()
240            .map_err(|_| Error::new_message("i32 overflow, string to large"))?;
241        // CString and into_raw() is needed here, that way we can pass in a proper destructor so
242        // SQLite can drop the allocated memory (avoids segfaults)
243        sqlite3ext_result_text(context, s.into_raw(), n, Some(result_text_destructor));
244    }
245    Ok(())
246}
247unsafe extern "C" fn result_text_destructor(raw: *mut c_void) {
248    drop(CString::from_raw(raw.cast::<c_char>()));
249}
250
251/// Calls [`sqlite3_result_int`](https://www.sqlite.org/c3ref/result_blob.html)
252/// to represent that a function returns an int32 with the given value.
253pub fn result_int(context: *mut sqlite3_context, i: i32) {
254    unsafe { sqlite3ext_result_int(context, i) };
255}
256
257///[`sqlite3_result_int64`](https://www.sqlite.org/c3ref/result_blob.html)
258/// to represent that a function returns an int64 with the given value.
259pub fn result_int64(context: *mut sqlite3_context, i: i64) {
260    unsafe { sqlite3ext_result_int64(context, i) };
261}
262
263/// Calls [`sqlite3_result_double`](https://www.sqlite.org/c3ref/result_blob.html)
264/// to represent that a function returns a double/float with the given value.
265pub fn result_double(context: *mut sqlite3_context, i: f64) {
266    unsafe { sqlite3ext_result_double(context, i) };
267}
268
269/// Calls [`sqlite3_result_blob`](https://www.sqlite.org/c3ref/result_blob.html)
270/// to represent that a function returns a blob with the given value.
271pub fn result_blob(context: *mut sqlite3_context, blob: &[u8]) {
272    // TODO try_into(), err on too big (check against limit? idk)
273    let len = blob.len() as c_int;
274    unsafe { sqlite3ext_result_blob(context, blob.as_ptr().cast::<c_void>(), len) };
275}
276
277/// Calls [`sqlite3_result_null`](https://www.sqlite.org/c3ref/result_blob.html)
278/// to represent that a function returns null with the given value.
279pub fn result_null(context: *mut sqlite3_context) {
280    unsafe { sqlite3ext_result_null(context) };
281}
282
283/// Calls [`sqlite3_result_error`](https://www.sqlite.org/c3ref/result_blob.html)
284/// to represent that a function returns an error with the given value.
285/// Note: You can typically rely on [`crate::Result`] to do this for you.
286pub fn result_error(context: *mut sqlite3_context, text: &str) -> crate::Result<()> {
287    let s = CString::new(text.as_bytes())?;
288    let n = text.len() as i32;
289
290    unsafe { sqlite3ext_result_error(context, s.into_raw(), n) };
291    Ok(())
292}
293
294/// Calls [`sqlite3_result_error_code`](https://www.sqlite.org/c3ref/result_blob.html)
295/// to represent that a function returns xx with the given value.
296pub fn result_error_code(context: *mut sqlite3_context, code: i32) {
297    unsafe { sqlite3ext_result_error_code(context, code) };
298}
299
300/// Calls [`result_int`] with `value=1` for true, or `value=0` for false.
301pub fn result_bool(context: *mut sqlite3_context, value: bool) {
302    if value {
303        result_int(context, 1)
304    } else {
305        result_int(context, 0)
306    }
307}
308
309/// Result the given JSON as a value that other SQLite JSON functions expect: a stringified
310/// text result with subtype of 'J'.
311pub fn result_json(context: *mut sqlite3_context, value: serde_json::Value) -> crate::Result<()> {
312    result_text(context, value.to_string().as_str())?;
313    // https://github.com/sqlite/sqlite/blob/master/src/json.c#L88-L89
314    result_subtype(context, b'J');
315    Ok(())
316}
317
318/// Calls [`sqlite3_result_subtype`](https://www.sqlite.org/c3ref/result_subtype.html)
319pub fn result_subtype(context: *mut sqlite3_context, subtype: u8) {
320    // Explanation for u8: "Only the lower 8 bits of the subtype T are preserved
321    // in current versions of SQLite; higher order bits are discarded"
322    unsafe { sqlite3ext_result_subtype(context, subtype.into()) };
323}
324
325unsafe extern "C" fn pointer_destroy<T>(pointer: *mut c_void) {
326    drop(Box::from_raw(pointer.cast::<T>()))
327}
328
329/// [sqlite3_result_pointer](https://www.sqlite.org/bindptr.html)
330pub fn result_pointer<T>(context: *mut sqlite3_context, name: &[u8], object: T) {
331    let b = Box::new(object);
332    let pointer = Box::into_raw(b).cast::<c_void>();
333    unsafe {
334        sqlite3ext_result_pointer(
335            context,
336            pointer,
337            name.as_ptr().cast::<c_char>().cast_mut(),
338            Some(pointer_destroy::<T>),
339        )
340    };
341}
342
343// TODO maybe take in a Box<T>?
344/// [`sqlite3_set_auxdata`](https://www.sqlite.org/c3ref/get_auxdata.html)
345pub fn auxdata_set(
346    context: *mut sqlite3_context,
347    col: i32,
348    p: *mut c_void,
349    d: Option<unsafe extern "C" fn(*mut c_void)>,
350) {
351    unsafe {
352        sqlite3ext_set_auxdata(context, col, p, d);
353    }
354}
355
356// TODO maybe return a Box<T>?
357/// [`sqlite3_get_auxdata`](https://www.sqlite.org/c3ref/get_auxdata.html)
358pub fn auxdata_get(context: *mut sqlite3_context, col: i32) -> *mut c_void {
359    unsafe { sqlite3ext_get_auxdata(context, col) }
360}
361
362/// A columns "affinity". <https://www.sqlite.org/datatype3.html#type_affinity>
363/* TODO maybe include extra affinities?
364- JSON - parse as text, see if it's JSON, if so then set subtype
365- boolean - 1 or 0, then 1 or 0. What about YES/NO or TRUE/FALSE or T/F?
366- datetime - idk man
367- interval - idk man
368 */
369pub enum ColumnAffinity {
370    /// "char", "clob", or "text"
371    Text,
372    /// "int"
373    Integer,
374    /// "real", "floa", or "doub"
375    Real,
376    /// "blob" or empty
377    Blob,
378    /// else, no other matches
379    Numeric,
380}
381
382impl ColumnAffinity {
383    /// Determines a column's affinity based on its declared typed, from
384    /// <https://www.sqlite.org/datatype3.html#determination_of_column_affinity>
385    pub fn from_declared_type(declared_type: &str) -> Self {
386        let lowered = declared_type.trim().to_lowercase();
387        // "If the declared type contains the string "INT" then it is assigned INTEGER affinity."
388        if lowered.contains("int") {
389            return ColumnAffinity::Integer;
390        };
391
392        // "If the declared type of the column contains any of the strings "CHAR",
393        // "CLOB", or "TEXT" then that column has TEXT affinity.
394        // Notice that the type VARCHAR contains the string "CHAR" and is
395        // thus assigned TEXT affinity."
396
397        if lowered.contains("char") || lowered.contains("clob") || lowered.contains("text") {
398            return ColumnAffinity::Text;
399        };
400
401        // "If the declared type for a column contains the string "BLOB" or if no
402        // type is specified then the column has affinity BLOB."
403
404        if lowered.contains("blob") || lowered.is_empty() {
405            return ColumnAffinity::Blob;
406        };
407
408        // "If the declared type for a column contains any of the strings "REAL",
409        // "FLOA", or "DOUB" then the column has REAL affinity."
410        if lowered.contains("real") || lowered.contains("floa") || lowered.contains("doub") {
411            return ColumnAffinity::Real;
412        };
413
414        // "Otherwise, the affinity is NUMERIC"
415        ColumnAffinity::Numeric
416    }
417
418    /// Result the given value on the given sqlite3_context, while applying
419    /// the proper affinity rules. It may instead result as an i32, i64,
420    /// or f64 numberor default back to just text.
421
422    pub fn result_text(&self, context: *mut sqlite3_context, value: &str) -> crate::Result<()> {
423        match self {
424            ColumnAffinity::Numeric => {
425                if let Ok(value) = value.parse::<i32>() {
426                    result_int(context, value)
427                } else if let Ok(value) = value.parse::<i64>() {
428                    result_int64(context, value)
429                } else if let Ok(value) = value.parse::<f64>() {
430                    result_double(context, value);
431                } else {
432                    result_text(context, value)?;
433                }
434            }
435            ColumnAffinity::Integer => {
436                if let Ok(value) = value.parse::<i32>() {
437                    result_int(context, value)
438                } else if let Ok(value) = value.parse::<i64>() {
439                    result_int64(context, value)
440                } else {
441                    result_text(context, value)?;
442                }
443            }
444            ColumnAffinity::Real => {
445                if let Ok(value) = value.parse::<f64>() {
446                    result_double(context, value);
447                } else {
448                    result_text(context, value)?;
449                }
450            }
451            ColumnAffinity::Blob | ColumnAffinity::Text => result_text(context, value)?,
452        };
453        Ok(())
454    }
455}
456
457/// A columns "extended affinity". The traditional affinity does
458/// not include supplementary "types" that SQLite doesn't support
459/// out of the box, like JSON, boolean, or datetime. This is an
460/// experimental extension to tradition affinities, and may change
461/// anytime.
462/* TODO maybe include extra affinities?
463- JSON - parse as text, see if it's JSON, if so then set subtype
464- boolean - 1 or 0, then 1 or 0. What about YES/NO or TRUE/FALSE or T/F?
465- datetime - idk man
466- interval - idk man
467*/
468pub enum ExtendedColumnAffinity {
469    /// "char", "clob", or "text"
470    Text,
471    /// "int"
472    Integer,
473    /// "real", "floa", or "doub"
474    Real,
475    /// "blob" or empty
476    Blob,
477    /// 0 or 1
478    Boolean,
479    Json,
480    Datetime,
481    Date,
482    Time,
483    /// else, no other matches
484    Numeric,
485}
486
487impl ExtendedColumnAffinity {
488    // https://www.sqlite.org/datatype3.html#determination_of_column_affinity
489    pub fn extended_column_affinity_from_type(declared_type: &str) -> Self {
490        let lowered = declared_type.to_lowercase();
491        // "If the declared type contains the string "INT" then it is assigned INTEGER affinity."
492        if lowered.contains("int") {
493            return ExtendedColumnAffinity::Integer;
494        };
495
496        // "If the declared type of the column contains any of the strings "CHAR",
497        // "CLOB", or "TEXT" then that column has TEXT affinity.
498        // Notice that the type VARCHAR contains the string "CHAR" and is
499        // thus assigned TEXT affinity."
500
501        if lowered.contains("char") || lowered.contains("clob") || lowered.contains("text") {
502            return ExtendedColumnAffinity::Text;
503        };
504
505        // "If the declared type for a column contains the string "BLOB" or if no
506        // type is specified then the column has affinity BLOB."
507
508        if lowered.contains("blob") || lowered.is_empty() {
509            return ExtendedColumnAffinity::Blob;
510        };
511
512        // "If the declared type for a column contains any of the strings "REAL",
513        // "FLOA", or "DOUB" then the column has REAL affinity."
514        if lowered.contains("real") || lowered.contains("floa") || lowered.contains("doub") {
515            return ExtendedColumnAffinity::Real;
516        };
517        if lowered.contains("json") {
518            return ExtendedColumnAffinity::Json;
519        };
520        if lowered.contains("boolean") {
521            return ExtendedColumnAffinity::Boolean;
522        };
523
524        // "Otherwise, the affinity is NUMERIC"
525        ExtendedColumnAffinity::Numeric
526    }
527}