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}