use std::borrow::Cow::{self, Borrowed, Owned};
use std::marker::PhantomData;
use std::marker::Sync;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::slice;
use crate::context::set_result;
use crate::error::error_from_sqlite_code;
use crate::ffi;
pub use crate::ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
pub enum VTabKind {
Default,
Eponymous,
EponymousOnly,
}
#[repr(transparent)]
pub struct Module<'vtab, T: VTab<'vtab>> {
base: ffi::sqlite3_module,
phantom: PhantomData<&'vtab T>,
}
unsafe impl<'vtab, T: VTab<'vtab>> Send for Module<'vtab, T> {}
unsafe impl<'vtab, T: VTab<'vtab>> Sync for Module<'vtab, T> {}
union ModuleZeroHack {
bytes: [u8; std::mem::size_of::<ffi::sqlite3_module>()],
module: ffi::sqlite3_module,
}
const ZERO_MODULE: ffi::sqlite3_module = unsafe {
ModuleZeroHack {
bytes: [0_u8; std::mem::size_of::<ffi::sqlite3_module>()],
}
.module
};
macro_rules! module {
($lt:lifetime, $vt:ty, $ct:ty, $xc:expr, $xd:expr, $xu:expr) => {
#[allow(clippy::needless_update)]
&Module {
base: ffi::sqlite3_module {
iVersion: 2,
xCreate: $xc,
xConnect: Some(rust_connect::<$vt>),
xBestIndex: Some(rust_best_index::<$vt>),
xDisconnect: Some(rust_disconnect::<$vt>),
xDestroy: $xd,
xOpen: Some(rust_open::<$vt>),
xClose: Some(rust_close::<$ct>),
xFilter: Some(rust_filter::<$ct>),
xNext: Some(rust_next::<$ct>),
xEof: Some(rust_eof::<$ct>),
xColumn: Some(rust_column::<$ct>),
xRowid: Some(rust_rowid::<$ct>), xUpdate: $xu,
xBegin: None,
xSync: None,
xCommit: None,
xRollback: None,
xFindFunction: None,
xRename: None,
xSavepoint: None,
xRelease: None,
xRollbackTo: None,
..ZERO_MODULE
},
phantom: PhantomData::<&$lt $vt>,
}
};
}
#[must_use]
pub fn update_module<'vtab, T: UpdateVTab<'vtab>>() -> &'static Module<'vtab, T> {
match T::KIND {
VTabKind::EponymousOnly => {
module!('vtab, T, T::Cursor, None, None, Some(rust_update::<T>))
}
VTabKind::Eponymous => {
module!('vtab, T, T::Cursor, Some(rust_connect::<T>), Some(rust_disconnect::<T>), Some(rust_update::<T>))
}
_ => {
module!('vtab, T, T::Cursor, Some(rust_create::<T>), Some(rust_destroy::<T>), Some(rust_update::<T>))
}
}
}
#[must_use]
pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T> {
match T::KIND {
VTabKind::EponymousOnly => eponymous_only_module(),
VTabKind::Eponymous => {
module!('vtab, T, T::Cursor, Some(rust_connect::<T>), Some(rust_disconnect::<T>), None)
}
_ => {
module!('vtab, T, T::Cursor, Some(rust_create::<T>), Some(rust_destroy::<T>), None)
}
}
}
#[must_use]
pub fn eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, T> {
module!('vtab, T, T::Cursor, None, None, None)
}
#[repr(i32)]
#[non_exhaustive]
#[cfg(feature = "modern_sqlite")] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum VTabConfig {
ConstraintSupport = 1,
Innocuous = 2,
DirectOnly = 3,
}
pub struct VTabConnection(*mut ffi::sqlite3);
impl VTabConnection {
#[cfg(feature = "modern_sqlite")] #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
pub fn config(&mut self, config: VTabConfig) -> Result<()> {
crate::error::check(unsafe { ffi::sqlite3_vtab_config(self.0, config as c_int) })
}
pub unsafe fn handle(&mut self) -> *mut ffi::sqlite3 {
self.0
}
}
pub unsafe trait VTab<'vtab>: Sized {
type Aux;
type Cursor: VTabCursor;
fn connect(
db: &mut VTabConnection,
aux: Option<&Self::Aux>,
args: &[&[u8]],
) -> Result<(String, Self)>;
fn best_index(&self, info: &mut IndexInfo) -> Result<()>;
fn open(&'vtab mut self) -> Result<Self::Cursor>;
}
pub trait CreateVTab<'vtab>: VTab<'vtab> {
const KIND: VTabKind;
fn create(
db: &mut VTabConnection,
aux: Option<&Self::Aux>,
args: &[&[u8]],
) -> Result<(String, Self)> {
Self::connect(db, aux, args)
}
fn destroy(&self) -> Result<()> {
Ok(())
}
}
pub trait UpdateVTab<'vtab>: CreateVTab<'vtab> {
fn delete(&mut self, arg: ValueRef<'_>) -> Result<()>;
fn insert(&mut self, args: &Values<'_>) -> Result<i64>;
fn update(&mut self, args: &Values<'_>) -> Result<()>;
}
#[derive(Debug, Eq, PartialEq)]
#[allow(non_snake_case, non_camel_case_types, missing_docs)]
#[allow(clippy::upper_case_acronyms)]
pub enum IndexConstraintOp {
SQLITE_INDEX_CONSTRAINT_EQ,
SQLITE_INDEX_CONSTRAINT_GT,
SQLITE_INDEX_CONSTRAINT_LE,
SQLITE_INDEX_CONSTRAINT_LT,
SQLITE_INDEX_CONSTRAINT_GE,
SQLITE_INDEX_CONSTRAINT_MATCH,
SQLITE_INDEX_CONSTRAINT_LIKE, SQLITE_INDEX_CONSTRAINT_GLOB, SQLITE_INDEX_CONSTRAINT_REGEXP, SQLITE_INDEX_CONSTRAINT_NE, SQLITE_INDEX_CONSTRAINT_ISNOT, SQLITE_INDEX_CONSTRAINT_ISNOTNULL, SQLITE_INDEX_CONSTRAINT_ISNULL, SQLITE_INDEX_CONSTRAINT_IS, SQLITE_INDEX_CONSTRAINT_LIMIT, SQLITE_INDEX_CONSTRAINT_OFFSET, SQLITE_INDEX_CONSTRAINT_FUNCTION(u8), }
impl From<u8> for IndexConstraintOp {
fn from(code: u8) -> IndexConstraintOp {
match code {
2 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ,
4 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GT,
8 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LE,
16 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LT,
32 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GE,
64 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_MATCH,
65 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIKE,
66 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GLOB,
67 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_REGEXP,
68 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_NE,
69 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOT,
70 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOTNULL,
71 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNULL,
72 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_IS,
73 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIMIT,
74 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_OFFSET,
v => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_FUNCTION(v),
}
}
}
#[cfg(feature = "modern_sqlite")] bitflags::bitflags! {
#[repr(C)]
pub struct IndexFlags: ::std::os::raw::c_int {
const NONE = 0;
const SQLITE_INDEX_SCAN_UNIQUE = ffi::SQLITE_INDEX_SCAN_UNIQUE;
}
}
#[derive(Debug)]
pub struct IndexInfo(*mut ffi::sqlite3_index_info);
impl IndexInfo {
#[inline]
pub fn constraints_and_usages(&mut self) -> IndexConstraintAndUsageIter<'_> {
let constraints =
unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
let constraint_usages = unsafe {
slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
};
IndexConstraintAndUsageIter {
iter: constraints.iter().zip(constraint_usages.iter_mut()),
}
}
#[inline]
#[must_use]
pub fn constraints(&self) -> IndexConstraintIter<'_> {
let constraints =
unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
IndexConstraintIter {
iter: constraints.iter(),
}
}
#[inline]
#[must_use]
pub fn order_bys(&self) -> OrderByIter<'_> {
let order_bys =
unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) };
OrderByIter {
iter: order_bys.iter(),
}
}
#[inline]
#[must_use]
pub fn num_of_order_by(&self) -> usize {
unsafe { (*self.0).nOrderBy as usize }
}
#[inline]
pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage<'_> {
let constraint_usages = unsafe {
slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
};
IndexConstraintUsage(&mut constraint_usages[constraint_idx])
}
#[inline]
pub fn set_idx_num(&mut self, idx_num: c_int) {
unsafe {
(*self.0).idxNum = idx_num;
}
}
pub fn set_idx_str(&mut self, idx_str: &str) {
unsafe {
(*self.0).idxStr = alloc(idx_str);
(*self.0).needToFreeIdxStr = 1;
}
}
#[inline]
pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
unsafe {
(*self.0).orderByConsumed = if order_by_consumed { 1 } else { 0 };
}
}
#[inline]
pub fn set_estimated_cost(&mut self, estimated_ost: f64) {
unsafe {
(*self.0).estimatedCost = estimated_ost;
}
}
#[cfg(feature = "modern_sqlite")] #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
#[inline]
pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
unsafe {
(*self.0).estimatedRows = estimated_rows;
}
}
#[cfg(feature = "modern_sqlite")] #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
#[inline]
pub fn set_idx_flags(&mut self, flags: IndexFlags) {
unsafe { (*self.0).idxFlags = flags.bits() };
}
#[cfg(feature = "modern_sqlite")] #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
#[inline]
pub fn col_used(&self) -> u64 {
unsafe { (*self.0).colUsed }
}
#[cfg(feature = "modern_sqlite")] #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
pub fn collation(&self, constraint_idx: usize) -> Result<&str> {
use std::ffi::CStr;
let idx = constraint_idx as c_int;
let collation = unsafe { ffi::sqlite3_vtab_collation(self.0, idx) };
if collation.is_null() {
return Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_MISUSE),
Some(format!("{} is out of range", constraint_idx)),
));
}
Ok(unsafe { CStr::from_ptr(collation) }.to_str()?)
}
}
pub struct IndexConstraintAndUsageIter<'a> {
iter: std::iter::Zip<
slice::Iter<'a, ffi::sqlite3_index_constraint>,
slice::IterMut<'a, ffi::sqlite3_index_constraint_usage>,
>,
}
impl<'a> Iterator for IndexConstraintAndUsageIter<'a> {
type Item = (IndexConstraint<'a>, IndexConstraintUsage<'a>);
#[inline]
fn next(&mut self) -> Option<(IndexConstraint<'a>, IndexConstraintUsage<'a>)> {
self.iter
.next()
.map(|raw| (IndexConstraint(raw.0), IndexConstraintUsage(raw.1)))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
pub struct IndexConstraintIter<'a> {
iter: slice::Iter<'a, ffi::sqlite3_index_constraint>,
}
impl<'a> Iterator for IndexConstraintIter<'a> {
type Item = IndexConstraint<'a>;
#[inline]
fn next(&mut self) -> Option<IndexConstraint<'a>> {
self.iter.next().map(IndexConstraint)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
impl IndexConstraint<'_> {
#[inline]
#[must_use]
pub fn column(&self) -> c_int {
self.0.iColumn
}
#[inline]
#[must_use]
pub fn operator(&self) -> IndexConstraintOp {
IndexConstraintOp::from(self.0.op)
}
#[inline]
#[must_use]
pub fn is_usable(&self) -> bool {
self.0.usable != 0
}
}
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
impl IndexConstraintUsage<'_> {
#[inline]
pub fn set_argv_index(&mut self, argv_index: c_int) {
self.0.argvIndex = argv_index;
}
#[inline]
pub fn set_omit(&mut self, omit: bool) {
self.0.omit = if omit { 1 } else { 0 };
}
}
pub struct OrderByIter<'a> {
iter: slice::Iter<'a, ffi::sqlite3_index_info_sqlite3_index_orderby>,
}
impl<'a> Iterator for OrderByIter<'a> {
type Item = OrderBy<'a>;
#[inline]
fn next(&mut self) -> Option<OrderBy<'a>> {
self.iter.next().map(OrderBy)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
impl OrderBy<'_> {
#[inline]
#[must_use]
pub fn column(&self) -> c_int {
self.0.iColumn
}
#[inline]
#[must_use]
pub fn is_order_by_desc(&self) -> bool {
self.0.desc != 0
}
}
pub unsafe trait VTabCursor: Sized {
fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Values<'_>) -> Result<()>;
fn next(&mut self) -> Result<()>;
fn eof(&self) -> bool;
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()>;
fn rowid(&self) -> Result<i64>;
}
pub struct Context(*mut ffi::sqlite3_context);
impl Context {
#[inline]
pub fn set_result<T: ToSql>(&mut self, value: &T) -> Result<()> {
let t = value.to_sql()?;
unsafe { set_result(self.0, &t) };
Ok(())
}
}
pub struct Values<'a> {
args: &'a [*mut ffi::sqlite3_value],
}
impl Values<'_> {
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.args.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
pub fn get<T: FromSql>(&self, idx: usize) -> Result<T> {
let arg = self.args[idx];
let value = unsafe { ValueRef::from_value(arg) };
FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => Error::InvalidFilterParameterType(idx, value.data_type()),
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err)
}
FromSqlError::InvalidBlobSize { .. } => {
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
}
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
})
}
#[cfg(feature = "array")]
#[cfg_attr(docsrs, doc(cfg(feature = "array")))]
fn get_array(&self, idx: usize) -> Option<array::Array> {
use crate::types::Value;
let arg = self.args[idx];
let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) };
if ptr.is_null() {
None
} else {
Some(unsafe {
let rc = array::Array::from_raw(ptr as *const Vec<Value>);
let array = rc.clone();
array::Array::into_raw(rc); array
})
}
}
#[inline]
#[must_use]
pub fn iter(&self) -> ValueIter<'_> {
ValueIter {
iter: self.args.iter(),
}
}
}
impl<'a> IntoIterator for &'a Values<'a> {
type IntoIter = ValueIter<'a>;
type Item = ValueRef<'a>;
#[inline]
fn into_iter(self) -> ValueIter<'a> {
self.iter()
}
}
pub struct ValueIter<'a> {
iter: slice::Iter<'a, *mut ffi::sqlite3_value>,
}
impl<'a> Iterator for ValueIter<'a> {
type Item = ValueRef<'a>;
#[inline]
fn next(&mut self) -> Option<ValueRef<'a>> {
self.iter
.next()
.map(|&raw| unsafe { ValueRef::from_value(raw) })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl Connection {
#[inline]
pub fn create_module<'vtab, T: VTab<'vtab>>(
&self,
module_name: &str,
module: &'static Module<'vtab, T>,
aux: Option<T::Aux>,
) -> Result<()> {
self.db.borrow_mut().create_module(module_name, module, aux)
}
}
impl InnerConnection {
fn create_module<'vtab, T: VTab<'vtab>>(
&mut self,
module_name: &str,
module: &'static Module<'vtab, T>,
aux: Option<T::Aux>,
) -> Result<()> {
use crate::version;
if version::version_number() < 3_009_000 && module.base.xCreate.is_none() {
return Err(Error::ModuleError(format!(
"Eponymous-only virtual table not supported by SQLite version {}",
version::version()
)));
}
let c_name = str_to_cstring(module_name)?;
let r = match aux {
Some(aux) => {
let boxed_aux: *mut T::Aux = Box::into_raw(Box::new(aux));
unsafe {
ffi::sqlite3_create_module_v2(
self.db(),
c_name.as_ptr(),
&module.base,
boxed_aux.cast::<c_void>(),
Some(free_boxed_value::<T::Aux>),
)
}
}
None => unsafe {
ffi::sqlite3_create_module_v2(
self.db(),
c_name.as_ptr(),
&module.base,
ptr::null_mut(),
None,
)
},
};
self.decode_result(r)
}
}
#[must_use]
pub fn escape_double_quote(identifier: &str) -> Cow<'_, str> {
if identifier.contains('"') {
Owned(identifier.replace('"', "\"\""))
} else {
Borrowed(identifier)
}
}
#[must_use]
pub fn dequote(s: &str) -> &str {
if s.len() < 2 {
return s;
}
match s.bytes().next() {
Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() {
Some(e) if e == b => &s[1..s.len() - 1], _ => s,
},
_ => s,
}
}
#[must_use]
pub fn parse_boolean(s: &str) -> Option<bool> {
if s.eq_ignore_ascii_case("yes")
|| s.eq_ignore_ascii_case("on")
|| s.eq_ignore_ascii_case("true")
|| s.eq("1")
{
Some(true)
} else if s.eq_ignore_ascii_case("no")
|| s.eq_ignore_ascii_case("off")
|| s.eq_ignore_ascii_case("false")
|| s.eq("0")
{
Some(false)
} else {
None
}
}
pub fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
let arg = std::str::from_utf8(c_slice)?.trim();
let mut split = arg.split('=');
if let Some(key) = split.next() {
if let Some(value) = split.next() {
let param = key.trim();
let value = dequote(value);
return Ok((param, value));
}
}
Err(Error::ModuleError(format!("illegal argument: '{}'", arg)))
}
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
drop(Box::from_raw(p.cast::<T>()));
}
unsafe extern "C" fn rust_create<'vtab, T>(
db: *mut ffi::sqlite3,
aux: *mut c_void,
argc: c_int,
argv: *const *const c_char,
pp_vtab: *mut *mut ffi::sqlite3_vtab,
err_msg: *mut *mut c_char,
) -> c_int
where
T: CreateVTab<'vtab>,
{
use std::ffi::CStr;
let mut conn = VTabConnection(db);
let aux = aux.cast::<T::Aux>();
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args
.iter()
.map(|&cs| CStr::from_ptr(cs).to_bytes()) .collect::<Vec<_>>();
match T::create(&mut conn, aux.as_ref(), &vec[..]) {
Ok((sql, vtab)) => match std::ffi::CString::new(sql) {
Ok(c_sql) => {
let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
if rc == ffi::SQLITE_OK {
let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
*pp_vtab = boxed_vtab.cast::<ffi::sqlite3_vtab>();
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
*err_msg = alloc(&err.to_string());
rc
}
}
Err(err) => {
*err_msg = alloc(&err.to_string());
ffi::SQLITE_ERROR
}
},
Err(Error::SqliteFailure(err, s)) => {
if let Some(s) = s {
*err_msg = alloc(&s);
}
err.extended_code
}
Err(err) => {
*err_msg = alloc(&err.to_string());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_connect<'vtab, T>(
db: *mut ffi::sqlite3,
aux: *mut c_void,
argc: c_int,
argv: *const *const c_char,
pp_vtab: *mut *mut ffi::sqlite3_vtab,
err_msg: *mut *mut c_char,
) -> c_int
where
T: VTab<'vtab>,
{
use std::ffi::CStr;
let mut conn = VTabConnection(db);
let aux = aux.cast::<T::Aux>();
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args
.iter()
.map(|&cs| CStr::from_ptr(cs).to_bytes()) .collect::<Vec<_>>();
match T::connect(&mut conn, aux.as_ref(), &vec[..]) {
Ok((sql, vtab)) => match std::ffi::CString::new(sql) {
Ok(c_sql) => {
let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
if rc == ffi::SQLITE_OK {
let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
*pp_vtab = boxed_vtab.cast::<ffi::sqlite3_vtab>();
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
*err_msg = alloc(&err.to_string());
rc
}
}
Err(err) => {
*err_msg = alloc(&err.to_string());
ffi::SQLITE_ERROR
}
},
Err(Error::SqliteFailure(err, s)) => {
if let Some(s) = s {
*err_msg = alloc(&s);
}
err.extended_code
}
Err(err) => {
*err_msg = alloc(&err.to_string());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_best_index<'vtab, T>(
vtab: *mut ffi::sqlite3_vtab,
info: *mut ffi::sqlite3_index_info,
) -> c_int
where
T: VTab<'vtab>,
{
let vt = vtab.cast::<T>();
let mut idx_info = IndexInfo(info);
match (*vt).best_index(&mut idx_info) {
Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, &err.to_string());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_disconnect<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
where
T: VTab<'vtab>,
{
if vtab.is_null() {
return ffi::SQLITE_OK;
}
let vtab = vtab.cast::<T>();
drop(Box::from_raw(vtab));
ffi::SQLITE_OK
}
unsafe extern "C" fn rust_destroy<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
where
T: CreateVTab<'vtab>,
{
if vtab.is_null() {
return ffi::SQLITE_OK;
}
let vt = vtab.cast::<T>();
match (*vt).destroy() {
Ok(_) => {
drop(Box::from_raw(vt));
ffi::SQLITE_OK
}
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, &err.to_string());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_open<'vtab, T: 'vtab>(
vtab: *mut ffi::sqlite3_vtab,
pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor,
) -> c_int
where
T: VTab<'vtab>,
{
let vt = vtab.cast::<T>();
match (*vt).open() {
Ok(cursor) => {
let boxed_cursor: *mut T::Cursor = Box::into_raw(Box::new(cursor));
*pp_cursor = boxed_cursor.cast::<ffi::sqlite3_vtab_cursor>();
ffi::SQLITE_OK
}
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, &err.to_string());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_close<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
let cr = cursor.cast::<C>();
drop(Box::from_raw(cr));
ffi::SQLITE_OK
}
unsafe extern "C" fn rust_filter<C>(
cursor: *mut ffi::sqlite3_vtab_cursor,
idx_num: c_int,
idx_str: *const c_char,
argc: c_int,
argv: *mut *mut ffi::sqlite3_value,
) -> c_int
where
C: VTabCursor,
{
use std::ffi::CStr;
use std::str;
let idx_name = if idx_str.is_null() {
None
} else {
let c_slice = CStr::from_ptr(idx_str).to_bytes();
Some(str::from_utf8_unchecked(c_slice))
};
let args = slice::from_raw_parts_mut(argv, argc as usize);
let values = Values { args };
let cr = cursor as *mut C;
cursor_error(cursor, (*cr).filter(idx_num, idx_name, &values))
}
unsafe extern "C" fn rust_next<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
cursor_error(cursor, (*cr).next())
}
unsafe extern "C" fn rust_eof<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
let cr = cursor.cast::<C>();
(*cr).eof() as c_int
}
unsafe extern "C" fn rust_column<C>(
cursor: *mut ffi::sqlite3_vtab_cursor,
ctx: *mut ffi::sqlite3_context,
i: c_int,
) -> c_int
where
C: VTabCursor,
{
let cr = cursor.cast::<C>();
let mut ctxt = Context(ctx);
result_error(ctx, (*cr).column(&mut ctxt, i))
}
unsafe extern "C" fn rust_rowid<C>(
cursor: *mut ffi::sqlite3_vtab_cursor,
p_rowid: *mut ffi::sqlite3_int64,
) -> c_int
where
C: VTabCursor,
{
let cr = cursor.cast::<C>();
match (*cr).rowid() {
Ok(rowid) => {
*p_rowid = rowid;
ffi::SQLITE_OK
}
err => cursor_error(cursor, err),
}
}
unsafe extern "C" fn rust_update<'vtab, T: 'vtab>(
vtab: *mut ffi::sqlite3_vtab,
argc: c_int,
argv: *mut *mut ffi::sqlite3_value,
p_rowid: *mut ffi::sqlite3_int64,
) -> c_int
where
T: UpdateVTab<'vtab>,
{
assert!(argc >= 1);
let args = slice::from_raw_parts_mut(argv, argc as usize);
let vt = vtab.cast::<T>();
let r = if args.len() == 1 {
(*vt).delete(ValueRef::from_value(args[0]))
} else if ffi::sqlite3_value_type(args[0]) == ffi::SQLITE_NULL {
let values = Values { args };
match (*vt).insert(&values) {
Ok(rowid) => {
*p_rowid = rowid;
Ok(())
}
Err(e) => Err(e),
}
} else {
let values = Values { args };
(*vt).update(&values)
};
match r {
Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, &err.to_string());
ffi::SQLITE_ERROR
}
}
}
#[cold]
unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
match result {
Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg((*cursor).pVtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg((*cursor).pVtab, &err.to_string());
ffi::SQLITE_ERROR
}
}
}
#[cold]
unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
if !(*vtab).zErrMsg.is_null() {
ffi::sqlite3_free((*vtab).zErrMsg.cast::<c_void>());
}
(*vtab).zErrMsg = alloc(err_msg);
}
#[cold]
unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int {
match result {
Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => {
match err.extended_code {
ffi::SQLITE_TOOBIG => {
ffi::sqlite3_result_error_toobig(ctx);
}
ffi::SQLITE_NOMEM => {
ffi::sqlite3_result_error_nomem(ctx);
}
code => {
ffi::sqlite3_result_error_code(ctx, code);
if let Some(Ok(cstr)) = s.map(|s| str_to_cstring(&s)) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
}
};
err.extended_code
}
Err(err) => {
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_ERROR);
if let Ok(cstr) = str_to_cstring(&err.to_string()) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
ffi::SQLITE_ERROR
}
}
}
fn alloc(s: &str) -> *mut c_char {
crate::util::SqliteMallocString::from_str(s).into_raw()
}
#[cfg(feature = "array")]
#[cfg_attr(docsrs, doc(cfg(feature = "array")))]
pub mod array;
#[cfg(feature = "csvtab")]
#[cfg_attr(docsrs, doc(cfg(feature = "csvtab")))]
pub mod csvtab;
#[cfg(feature = "series")]
#[cfg_attr(docsrs, doc(cfg(feature = "series")))]
pub mod series; #[cfg(test)]
mod vtablog;
#[cfg(test)]
mod test {
#[test]
fn test_dequote() {
assert_eq!("", super::dequote(""));
assert_eq!("'", super::dequote("'"));
assert_eq!("\"", super::dequote("\""));
assert_eq!("'\"", super::dequote("'\""));
assert_eq!("", super::dequote("''"));
assert_eq!("", super::dequote("\"\""));
assert_eq!("x", super::dequote("'x'"));
assert_eq!("x", super::dequote("\"x\""));
assert_eq!("x", super::dequote("x"));
}
#[test]
fn test_parse_boolean() {
assert_eq!(None, super::parse_boolean(""));
assert_eq!(Some(true), super::parse_boolean("1"));
assert_eq!(Some(true), super::parse_boolean("yes"));
assert_eq!(Some(true), super::parse_boolean("on"));
assert_eq!(Some(true), super::parse_boolean("true"));
assert_eq!(Some(false), super::parse_boolean("0"));
assert_eq!(Some(false), super::parse_boolean("no"));
assert_eq!(Some(false), super::parse_boolean("off"));
assert_eq!(Some(false), super::parse_boolean("false"));
}
}