#![allow(clippy::not_unsafe_ptr_arg_deref)]
use crate::ext::{
sqlite3ext_get_auxdata, sqlite3ext_result_blob, sqlite3ext_result_double,
sqlite3ext_result_error, sqlite3ext_result_error_code, sqlite3ext_result_int,
sqlite3ext_result_int64, sqlite3ext_result_null, sqlite3ext_result_pointer,
sqlite3ext_result_subtype, sqlite3ext_result_text, sqlite3ext_set_auxdata,
sqlite3ext_value_blob, sqlite3ext_value_bytes, sqlite3ext_value_double, sqlite3ext_value_int,
sqlite3ext_value_int64, sqlite3ext_value_pointer, sqlite3ext_value_text, sqlite3ext_value_type,
};
use crate::Error;
use sqlite3ext_sys::sqlite3_mprintf;
use sqlite3ext_sys::{
sqlite3_context, sqlite3_value, SQLITE_BLOB, SQLITE_FLOAT, SQLITE_INTEGER, SQLITE_NULL,
SQLITE_TEXT,
};
use std::os::raw::c_int;
use std::slice::from_raw_parts;
use std::str::Utf8Error;
use std::{
ffi::{CStr, CString, NulError},
os::raw::{c_char, c_void},
};
pub struct Value {
value: *mut sqlite3_value,
value_type: ValueType,
}
impl Value {
pub fn from(value: &*mut sqlite3_value) -> crate::Result<Value> {
let value_type = value_type(value);
Ok(Value {
value: value.to_owned(),
value_type,
})
}
pub fn at(values: &[*mut sqlite3_value], at: usize) -> Option<Value> {
let value = values.get(at)?;
let value_type = value_type(value);
Some(Value {
value: value.to_owned(),
value_type,
})
}
pub fn notnull_or(&self, error: Error) -> crate::Result<&Self> {
if self.value_type != ValueType::Null {
Ok(self)
} else {
Err(error)
}
}
pub fn notnull_or_else<F>(&self, err: F) -> crate::Result<&Self>
where
F: FnOnce() -> Error,
{
if self.value_type != ValueType::Null {
Ok(self)
} else {
Err(err())
}
}
pub fn text_or_else<F>(&self, error: F) -> crate::Result<&str>
where
F: FnOnce(Error) -> Error,
{
match value_text(&self.value) {
Ok(value) => Ok(value),
Err(err) => Err(error(err.into())),
}
}
}
#[derive(Debug)]
pub enum MprintfError {
Nul(NulError),
Oom,
}
pub fn mprintf(base: &str) -> Result<*mut c_char, MprintfError> {
let cbase = CString::new(base.as_bytes()).map_err(MprintfError::Nul)?;
let result = unsafe { sqlite3_mprintf(cbase.as_ptr()) };
if result.is_null() {
Err(MprintfError::Oom)
} else {
Ok(result)
}
}
pub fn value_blob<'a>(value: &*mut sqlite3_value) -> &'a [u8] {
let n = value_bytes(value);
let b = unsafe { sqlite3ext_value_blob(value.to_owned()) };
return unsafe { from_raw_parts(b.cast::<u8>(), n as usize) };
}
pub fn value_bytes(value: &*mut sqlite3_value) -> i32 {
unsafe { sqlite3ext_value_bytes(value.to_owned()) }
}
pub fn value_text<'a>(value: &*mut sqlite3_value) -> Result<&'a str, Utf8Error> {
let n = value_bytes(value);
if n == 0 {
return Ok("");
}
unsafe {
let c_string = sqlite3ext_value_text(value.to_owned());
std::str::from_utf8(from_raw_parts(c_string, n as usize))
}
}
pub fn value_text_notnull<'a>(value: &*mut sqlite3_value) -> Result<&'a str, Error> {
if value_type(value) == ValueType::Null {
return Err(Error::new_message("Unexpected null value"));
}
let c_string = unsafe { sqlite3ext_value_text(value.to_owned()) };
let string = unsafe { CStr::from_ptr(c_string as *const c_char) };
Ok(string.to_str()?)
}
pub unsafe fn value_pointer<T>(value: &*mut sqlite3_value, c_name: &[u8]) -> Option<Box<T>> {
let result = sqlite3ext_value_pointer(
value.to_owned(),
c_name.as_ptr().cast::<c_char>().cast_mut(),
);
if result.is_null() {
return None;
}
Some(Box::from_raw(result.cast::<T>()))
}
pub fn value_int(value: &*mut sqlite3_value) -> i32 {
unsafe { sqlite3ext_value_int(value.to_owned()) }
}
pub fn value_int64(value: &*mut sqlite3_value) -> i64 {
unsafe { sqlite3ext_value_int64(value.to_owned()) }
}
pub fn value_double(value: &*mut sqlite3_value) -> f64 {
unsafe { sqlite3ext_value_double(value.to_owned()) }
}
#[derive(Eq, PartialEq)]
pub enum ValueType {
Text,
Integer,
Float,
Blob,
Null,
}
pub fn value_type(value: &*mut sqlite3_value) -> ValueType {
let raw_type = unsafe { sqlite3ext_value_type(value.to_owned()) };
match raw_type as u32 {
SQLITE_TEXT => ValueType::Text,
SQLITE_INTEGER => ValueType::Integer,
SQLITE_FLOAT => ValueType::Float,
SQLITE_BLOB => ValueType::Blob,
SQLITE_NULL => ValueType::Null,
_ => unreachable!(),
}
}
pub fn result_text<S: AsRef<str>>(context: *mut sqlite3_context, text: S) -> crate::Result<()> {
let bytes = text.as_ref().as_bytes();
unsafe {
let s = CString::from_vec_unchecked(bytes.into());
let n: i32 = bytes
.len()
.try_into()
.map_err(|_| Error::new_message("i32 overflow, string to large"))?;
sqlite3ext_result_text(context, s.into_raw(), n, Some(result_text_destructor));
}
Ok(())
}
unsafe extern "C" fn result_text_destructor(raw: *mut c_void) {
drop(CString::from_raw(raw.cast::<c_char>()));
}
pub fn result_int(context: *mut sqlite3_context, i: i32) {
unsafe { sqlite3ext_result_int(context, i) };
}
pub fn result_int64(context: *mut sqlite3_context, i: i64) {
unsafe { sqlite3ext_result_int64(context, i) };
}
pub fn result_double(context: *mut sqlite3_context, i: f64) {
unsafe { sqlite3ext_result_double(context, i) };
}
pub fn result_blob(context: *mut sqlite3_context, blob: &[u8]) {
let len = blob.len() as c_int;
unsafe { sqlite3ext_result_blob(context, blob.as_ptr().cast::<c_void>(), len) };
}
pub fn result_null(context: *mut sqlite3_context) {
unsafe { sqlite3ext_result_null(context) };
}
pub fn result_error(context: *mut sqlite3_context, text: &str) -> crate::Result<()> {
let s = CString::new(text.as_bytes())?;
let n = text.len() as i32;
unsafe { sqlite3ext_result_error(context, s.into_raw(), n) };
Ok(())
}
pub fn result_error_code(context: *mut sqlite3_context, code: i32) {
unsafe { sqlite3ext_result_error_code(context, code) };
}
pub fn result_bool(context: *mut sqlite3_context, value: bool) {
if value {
result_int(context, 1)
} else {
result_int(context, 0)
}
}
pub fn result_json(context: *mut sqlite3_context, value: serde_json::Value) -> crate::Result<()> {
result_text(context, value.to_string().as_str())?;
result_subtype(context, b'J');
Ok(())
}
pub fn result_subtype(context: *mut sqlite3_context, subtype: u8) {
unsafe { sqlite3ext_result_subtype(context, subtype.into()) };
}
unsafe extern "C" fn pointer_destroy<T>(pointer: *mut c_void) {
drop(Box::from_raw(pointer.cast::<T>()))
}
pub fn result_pointer<T>(context: *mut sqlite3_context, name: &[u8], object: T) {
let b = Box::new(object);
let pointer = Box::into_raw(b).cast::<c_void>();
unsafe {
sqlite3ext_result_pointer(
context,
pointer,
name.as_ptr().cast::<c_char>().cast_mut(),
Some(pointer_destroy::<T>),
)
};
}
pub fn auxdata_set(
context: *mut sqlite3_context,
col: i32,
p: *mut c_void,
d: Option<unsafe extern "C" fn(*mut c_void)>,
) {
unsafe {
sqlite3ext_set_auxdata(context, col, p, d);
}
}
pub fn auxdata_get(context: *mut sqlite3_context, col: i32) -> *mut c_void {
unsafe { sqlite3ext_get_auxdata(context, col) }
}
pub enum ColumnAffinity {
Text,
Integer,
Real,
Blob,
Numeric,
}
impl ColumnAffinity {
pub fn from_declared_type(declared_type: &str) -> Self {
let lowered = declared_type.trim().to_lowercase();
if lowered.contains("int") {
return ColumnAffinity::Integer;
};
if lowered.contains("char") || lowered.contains("clob") || lowered.contains("text") {
return ColumnAffinity::Text;
};
if lowered.contains("blob") || lowered.is_empty() {
return ColumnAffinity::Blob;
};
if lowered.contains("real") || lowered.contains("floa") || lowered.contains("doub") {
return ColumnAffinity::Real;
};
ColumnAffinity::Numeric
}
pub fn result_text(&self, context: *mut sqlite3_context, value: &str) -> crate::Result<()> {
match self {
ColumnAffinity::Numeric => {
if let Ok(value) = value.parse::<i32>() {
result_int(context, value)
} else if let Ok(value) = value.parse::<i64>() {
result_int64(context, value)
} else if let Ok(value) = value.parse::<f64>() {
result_double(context, value);
} else {
result_text(context, value)?;
}
}
ColumnAffinity::Integer => {
if let Ok(value) = value.parse::<i32>() {
result_int(context, value)
} else if let Ok(value) = value.parse::<i64>() {
result_int64(context, value)
} else {
result_text(context, value)?;
}
}
ColumnAffinity::Real => {
if let Ok(value) = value.parse::<f64>() {
result_double(context, value);
} else {
result_text(context, value)?;
}
}
ColumnAffinity::Blob | ColumnAffinity::Text => result_text(context, value)?,
};
Ok(())
}
}
pub enum ExtendedColumnAffinity {
Text,
Integer,
Real,
Blob,
Boolean,
Json,
Datetime,
Date,
Time,
Numeric,
}
impl ExtendedColumnAffinity {
pub fn extended_column_affinity_from_type(declared_type: &str) -> Self {
let lowered = declared_type.to_lowercase();
if lowered.contains("int") {
return ExtendedColumnAffinity::Integer;
};
if lowered.contains("char") || lowered.contains("clob") || lowered.contains("text") {
return ExtendedColumnAffinity::Text;
};
if lowered.contains("blob") || lowered.is_empty() {
return ExtendedColumnAffinity::Blob;
};
if lowered.contains("real") || lowered.contains("floa") || lowered.contains("doub") {
return ExtendedColumnAffinity::Real;
};
if lowered.contains("json") {
return ExtendedColumnAffinity::Json;
};
if lowered.contains("boolean") {
return ExtendedColumnAffinity::Boolean;
};
ExtendedColumnAffinity::Numeric
}
}