use std::borrow::Cow::{self, Borrowed, Owned};
use std::ffi::{c_char, c_int, c_void, CStr};
use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr;
use std::slice;
use crate::ffi::sqlite3_free;
use crate::context::set_result;
use crate::error::{check, error_from_sqlite_code, to_sqlite_error};
use crate::ffi;
pub use crate::ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
use crate::util::{alloc, free_boxed_value};
use crate::{str_to_cstring, Connection, Error, InnerConnection, Name, Result};
pub enum VTabKind {
Default,
Eponymous,
EponymousOnly,
}
#[repr(transparent)]
pub struct Module<'vtab, T: VTab<'vtab>> {
base: ffi::sqlite3_module,
phantom: PhantomData<&'vtab T>,
}
union ModuleZeroHack {
bytes: [u8; size_of::<ffi::sqlite3_module>()],
module: ffi::sqlite3_module,
}
const ZERO_MODULE: ffi::sqlite3_module = unsafe {
ModuleZeroHack {
bytes: [0_u8; size_of::<ffi::sqlite3_module>()],
}
.module
};
impl<'vtab, T: VTab<'vtab>> Module<'vtab, T> {
#[must_use]
pub const fn eponymous_only_module() -> Self {
Module {
base: ffi::sqlite3_module {
iVersion: 1,
xCreate: None,
xConnect: Some(rust_connect::<T>),
xBestIndex: Some(rust_best_index::<T>),
xDisconnect: Some(rust_disconnect::<T>),
xDestroy: None,
xOpen: Some(rust_open::<T>),
xClose: Some(rust_close::<T::Cursor>),
xFilter: Some(rust_filter::<T::Cursor>),
xNext: Some(rust_next::<T::Cursor>),
xEof: Some(rust_eof::<T::Cursor>),
xColumn: Some(rust_column::<T::Cursor>),
xRowid: Some(rust_rowid::<T::Cursor>),
xUpdate: None,
xBegin: None,
xSync: None,
xCommit: None,
xRollback: None,
xFindFunction: None,
xRename: None,
..ZERO_MODULE
},
phantom: PhantomData::<&'vtab T>,
}
}
#[must_use]
pub const fn read_only_module() -> Self
where
T: CreateVTab<'vtab>,
{
let mut module = Self::eponymous_only_module();
match T::KIND {
VTabKind::EponymousOnly => module,
VTabKind::Eponymous => {
module.base.xCreate = module.base.xConnect;
module.base.xDestroy = module.base.xDisconnect;
module
}
_ => {
module.base.xCreate = Some(rust_create::<T>);
module.base.xDestroy = Some(rust_destroy::<T>);
module
}
}
}
#[must_use]
pub const fn update_module() -> Self
where
T: UpdateVTab<'vtab>,
{
let mut module = Self::read_only_module();
module.base.xUpdate = Some(rust_update::<T>);
module
}
#[must_use]
pub const fn update_module_with_tx() -> Self
where
T: TransactionVTab<'vtab>,
{
let mut module = Self::update_module();
module.base.xBegin = Some(rust_begin::<T>);
module.base.xSync = Some(rust_sync::<T>);
module.base.xCommit = Some(rust_commit::<T>);
module.base.xRollback = Some(rust_rollback::<T>);
module
}
pub const fn without_rowid(mut self) -> Self {
self.base.xRowid = None;
self
}
pub const fn without_sync(mut self) -> Self
where
T: TransactionVTab<'vtab>,
{
self.base.xSync = None;
self
}
}
#[repr(i32)]
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum VTabConfig {
ConstraintSupport = 1,
Innocuous = 2,
DirectOnly = 3,
UsesAllSchemas = 4,
}
pub struct VTabConnection(*mut ffi::sqlite3);
impl VTabConnection {
pub fn config(&mut self, config: VTabConfig) -> Result<()> {
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: Send + Sync + 'static;
type Cursor: VTabCursor;
fn connect(
db: &mut VTabConnection,
aux: Option<&Self::Aux>,
module_name: &[u8],
database_name: &[u8],
table_name: &[u8],
args: &[&[u8]],
) -> Result<(Cow<'static, CStr>, Self)>;
fn best_index(&self, info: &mut IndexInfo) -> Result<bool>;
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>,
module_name: &[u8],
database_name: &[u8],
table_name: &[u8],
args: &[&[u8]],
) -> Result<(Cow<'static, CStr>, Self)> {
Self::connect(db, aux, module_name, database_name, table_name, args)
}
fn destroy(&self) -> Result<()> {
Ok(())
}
}
pub trait UpdateVTab<'vtab>: CreateVTab<'vtab> {
fn delete(&mut self, arg: ValueRef<'_>) -> Result<()>;
fn insert(&mut self, args: &Inserts<'_>) -> Result<i64>;
fn update(&mut self, args: &Updates<'_>) -> Result<()>;
}
pub trait TransactionVTab<'vtab>: UpdateVTab<'vtab> {
fn begin(&mut self) -> Result<()> {
Ok(())
}
fn sync(&mut self) -> Result<()> {
Ok(())
}
fn commit(&mut self) -> Result<()> {
Ok(())
}
fn rollback(&mut self) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Eq, PartialEq)]
#[allow(missing_docs)]
#[expect(non_camel_case_types)]
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) -> Self {
match code {
2 => Self::SQLITE_INDEX_CONSTRAINT_EQ,
4 => Self::SQLITE_INDEX_CONSTRAINT_GT,
8 => Self::SQLITE_INDEX_CONSTRAINT_LE,
16 => Self::SQLITE_INDEX_CONSTRAINT_LT,
32 => Self::SQLITE_INDEX_CONSTRAINT_GE,
64 => Self::SQLITE_INDEX_CONSTRAINT_MATCH,
65 => Self::SQLITE_INDEX_CONSTRAINT_LIKE,
66 => Self::SQLITE_INDEX_CONSTRAINT_GLOB,
67 => Self::SQLITE_INDEX_CONSTRAINT_REGEXP,
68 => Self::SQLITE_INDEX_CONSTRAINT_NE,
69 => Self::SQLITE_INDEX_CONSTRAINT_ISNOT,
70 => Self::SQLITE_INDEX_CONSTRAINT_ISNOTNULL,
71 => Self::SQLITE_INDEX_CONSTRAINT_ISNULL,
72 => Self::SQLITE_INDEX_CONSTRAINT_IS,
73 => Self::SQLITE_INDEX_CONSTRAINT_LIMIT,
74 => Self::SQLITE_INDEX_CONSTRAINT_OFFSET,
v => Self::SQLITE_INDEX_CONSTRAINT_FUNCTION(v),
}
}
}
bitflags::bitflags! {
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct IndexFlags: c_int {
const NONE = 0;
const SQLITE_INDEX_SCAN_UNIQUE = ffi::SQLITE_INDEX_SCAN_UNIQUE;
const SQLITE_INDEX_SCAN_HEX = 0x0000_0002; }
}
#[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 {
if (*self.0).needToFreeIdxStr == 1 {
sqlite3_free((*self.0).idxStr as _);
}
(*self.0).idxStr = alloc(idx_str);
(*self.0).needToFreeIdxStr = 1;
}
}
pub fn set_idx_cstr(&mut self, idx_str: &'static CStr) {
unsafe {
if (*self.0).needToFreeIdxStr == 1 {
sqlite3_free((*self.0).idxStr as _);
}
(*self.0).idxStr = idx_str.as_ptr() as _;
(*self.0).needToFreeIdxStr = 0;
}
}
#[inline]
pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
unsafe {
(*self.0).orderByConsumed = order_by_consumed as c_int;
}
}
#[inline]
pub fn set_estimated_cost(&mut self, estimated_ost: f64) {
unsafe {
(*self.0).estimatedCost = estimated_ost;
}
}
#[inline]
pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
unsafe {
(*self.0).estimatedRows = estimated_rows;
}
}
#[inline]
pub fn set_idx_flags(&mut self, flags: IndexFlags) {
unsafe { (*self.0).idxFlags = flags.bits() };
}
#[inline]
pub fn col_used(&self) -> u64 {
unsafe { (*self.0).colUsed }
}
pub fn collation(&self, constraint_idx: usize) -> Result<&str> {
let idx = constraint_idx as c_int;
let collation = unsafe { ffi::sqlite3_vtab_collation(self.0, idx) };
if collation.is_null() {
return Err(err!(ffi::SQLITE_MISUSE, "{constraint_idx} is out of range"));
}
Ok(unsafe { CStr::from_ptr(collation) }.to_str()?)
}
#[must_use]
#[cfg(feature = "modern_sqlite")] pub fn distinct(&self) -> DistinctMode {
match unsafe { ffi::sqlite3_vtab_distinct(self.0) } {
0 => DistinctMode::Ordered,
1 => DistinctMode::Grouped,
2 => DistinctMode::Distinct,
3 => DistinctMode::DistinctOrdered,
_ => DistinctMode::Ordered,
}
}
#[cfg(feature = "modern_sqlite")] pub fn rhs_value(&self, constraint_idx: usize) -> Result<Option<ValueRef<'_>>> {
let idx = constraint_idx as c_int;
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
let rc = unsafe { ffi::sqlite3_vtab_rhs_value(self.0, idx, &mut p_value) };
if rc == ffi::SQLITE_NOTFOUND {
return Ok(None);
}
check(rc)?;
assert!(!p_value.is_null());
Ok(Some(unsafe { ValueRef::from_value(p_value) }))
}
#[cfg(feature = "modern_sqlite")] pub fn is_in_constraint(&self, constraint_idx: usize) -> Result<bool> {
self.check_constraint_index(constraint_idx)?;
let idx = constraint_idx as c_int;
Ok(unsafe { ffi::sqlite3_vtab_in(self.0, idx, -1) != 0 })
}
#[cfg(feature = "modern_sqlite")] pub fn set_in_constraint(&mut self, constraint_idx: usize, filter_all: bool) -> Result<bool> {
self.check_constraint_index(constraint_idx)?;
let idx = constraint_idx as c_int;
Ok(unsafe { ffi::sqlite3_vtab_in(self.0, idx, filter_all as c_int) != 0 })
}
#[cfg(feature = "modern_sqlite")] fn check_constraint_index(&self, idx: usize) -> Result<()> {
if idx >= unsafe { (*self.0).nConstraint } as usize {
return Err(err!(ffi::SQLITE_MISUSE, "{idx} is out of range"));
}
Ok(())
}
}
#[non_exhaustive]
#[derive(Debug, Eq, PartialEq)]
pub enum DistinctMode {
Ordered,
Grouped,
Distinct,
DistinctOrdered,
}
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 = omit as std::ffi::c_uchar;
}
}
pub struct OrderByIter<'a> {
iter: slice::Iter<'a, ffi::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_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: &Filters<'_>) -> 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(())
}
#[inline]
#[must_use]
pub fn no_change(&self) -> bool {
unsafe { ffi::sqlite3_vtab_nochange(self.0) != 0 }
}
pub unsafe fn get_connection(&self) -> Result<ConnectionRef<'_>> {
let handle = ffi::sqlite3_context_db_handle(self.0);
Ok(ConnectionRef {
conn: Connection::from_handle(handle)?,
phantom: PhantomData,
})
}
}
pub struct ConnectionRef<'ctx> {
conn: Connection,
phantom: PhantomData<&'ctx Context>,
}
impl Deref for ConnectionRef<'_> {
type Target = Connection;
#[inline]
fn deref(&self) -> &Connection {
&self.conn
}
}
pub struct Filters<'a> {
values: Values<'a>,
}
impl<'a> Deref for Filters<'a> {
type Target = Values<'a>;
fn deref(&self) -> &Self::Target {
&self.values
}
}
#[cfg(feature = "modern_sqlite")] impl<'a> Filters<'a> {
pub fn in_values(&self, idx: usize) -> Result<InValues<'_>> {
let list = self.args[idx];
Ok(InValues {
list,
phantom: PhantomData,
first: true,
})
}
}
#[cfg(feature = "modern_sqlite")] pub struct InValues<'a> {
list: *mut ffi::sqlite3_value,
phantom: PhantomData<Filters<'a>>,
first: bool,
}
#[cfg(feature = "modern_sqlite")] impl<'a> fallible_iterator::FallibleIterator for InValues<'a> {
type Error = Error;
type Item = ValueRef<'a>;
fn next(&mut self) -> Result<Option<Self::Item>> {
let mut val: *mut ffi::sqlite3_value = ptr::null_mut();
let rc = unsafe {
if self.first {
self.first = false;
ffi::sqlite3_vtab_in_first(self.list, &mut val)
} else {
ffi::sqlite3_vtab_in_next(self.list, &mut val)
}
};
match rc {
ffi::SQLITE_OK => Ok(Some(unsafe { ValueRef::from_value(val) })),
ffi::SQLITE_DONE => Ok(None),
_ => Err(error_from_sqlite_code(rc, None)),
}
}
}
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),
FromSqlError::Utf8Error(err) => Error::Utf8Error(idx, err),
})
}
#[cfg(feature = "pointer")]
pub unsafe fn get_pointer<'a, T: 'static>(
&self,
idx: usize,
ptr_type: &'static CStr,
) -> Option<&'a T> {
let arg = self.args[idx];
debug_assert_eq!(unsafe { ffi::sqlite3_value_type(arg) }, ffi::SQLITE_NULL);
unsafe {
ffi::sqlite3_value_pointer(arg, ptr_type.as_ptr())
.cast::<T>()
.as_ref()
}
}
#[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()
}
}
pub struct Inserts<'a> {
values: Values<'a>,
}
impl<'a> Deref for Inserts<'a> {
type Target = Values<'a>;
fn deref(&self) -> &Self::Target {
&self.values
}
}
impl Inserts<'_> {
#[must_use]
pub unsafe fn on_conflict(&self, db: *mut ffi::sqlite3) -> ConflictMode {
ConflictMode::from(unsafe { ffi::sqlite3_vtab_on_conflict(db) })
}
}
pub struct Updates<'a> {
values: Values<'a>,
}
impl<'a> Deref for Updates<'a> {
type Target = Values<'a>;
fn deref(&self) -> &Self::Target {
&self.values
}
}
impl Updates<'_> {
#[inline]
#[must_use]
pub fn no_change(&self, idx: usize) -> bool {
unsafe { ffi::sqlite3_value_nochange(self.values.args[idx]) != 0 }
}
#[must_use]
pub unsafe fn on_conflict(&self, db: *mut ffi::sqlite3) -> ConflictMode {
ConflictMode::from(unsafe { ffi::sqlite3_vtab_on_conflict(db) })
}
}
#[non_exhaustive]
#[derive(Debug, Eq, PartialEq)]
pub enum ConflictMode {
Rollback,
Ignore,
Fail,
Abort,
Replace,
}
impl From<c_int> for ConflictMode {
fn from(value: c_int) -> Self {
match value {
ffi::SQLITE_ROLLBACK => ConflictMode::Rollback,
ffi::SQLITE_IGNORE => ConflictMode::Ignore,
ffi::SQLITE_FAIL => ConflictMode::Fail,
ffi::SQLITE_ABORT => ConflictMode::Abort,
ffi::SQLITE_REPLACE => ConflictMode::Replace,
_ => unreachable!("sqlite3_vtab_on_conflict returned invalid value"),
}
}
}
impl Connection {
#[inline]
pub fn create_module<'vtab, T: VTab<'vtab>, M: Name>(
&self,
module_name: M,
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>, M: Name>(
&mut self,
module_name: M,
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 = module_name.as_cstr()?;
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(mut s: &str) -> Cow<'_, str> {
let mut chars = s.chars();
let (Some(first), Some(last)) = (chars.next(), chars.next_back()) else {
return Cow::Borrowed(s);
};
if (first == '"' || first == '\'' || first == '`' || first == '[')
&& (last == first || first == '[' && last == ']')
{
s = chars.as_str();
if first != '[' && s.contains(first) {
let mut owned = String::with_capacity(s.len());
let mut escaped = false;
for c in s.chars() {
if c == first {
if !escaped {
escaped = true;
continue;
} else {
escaped = false;
}
} else if escaped {
return Cow::Borrowed(s);
}
owned.push(c);
}
if !escaped {
return Cow::Owned(owned);
}
}
}
Cow::Borrowed(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, Cow<'_, str>)> {
let arg = std::str::from_utf8(c_slice)?.trim();
match arg.split_once('=') {
Some((key, value)) => {
let param = key.trim();
let value = dequote(value.trim());
Ok((param, value))
}
_ => Err(Error::ModuleError(format!("illegal argument: '{arg}'"))),
}
}
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 sqlite3_vtab,
err_msg: *mut *mut c_char,
) -> c_int
where
T: CreateVTab<'vtab>,
{
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[0], vec[1], vec[2], &vec[3..]) {
Ok((sql, vtab)) => {
let rc = ffi::sqlite3_declare_vtab(db, sql.as_ptr());
if rc == ffi::SQLITE_OK {
let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
*pp_vtab = boxed_vtab.cast::<sqlite3_vtab>();
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
to_sqlite_error(&err, err_msg)
}
}
Err(err) => to_sqlite_error(&err, err_msg),
}
}
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 sqlite3_vtab,
err_msg: *mut *mut c_char,
) -> c_int
where
T: VTab<'vtab>,
{
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[0], vec[1], vec[2], &vec[3..]) {
Ok((sql, vtab)) => {
let rc = ffi::sqlite3_declare_vtab(db, sql.as_ptr());
if rc == ffi::SQLITE_OK {
let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
*pp_vtab = boxed_vtab.cast::<sqlite3_vtab>();
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
to_sqlite_error(&err, err_msg)
}
}
Err(err) => to_sqlite_error(&err, err_msg),
}
}
unsafe extern "C" fn rust_best_index<'vtab, T>(
vtab: *mut 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(true) => ffi::SQLITE_OK,
Ok(false) => ffi::SQLITE_CONSTRAINT,
err => vtab_error(vtab, err),
}
}
unsafe extern "C" fn rust_disconnect<'vtab, T>(vtab: *mut 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 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 => vtab_error(vtab, err),
}
}
unsafe extern "C" fn rust_open<'vtab, T>(
vtab: *mut sqlite3_vtab,
pp_cursor: *mut *mut sqlite3_vtab_cursor,
) -> c_int
where
T: VTab<'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::<sqlite3_vtab_cursor>();
ffi::SQLITE_OK
}
err => vtab_error(vtab, err),
}
}
unsafe extern "C" fn rust_close<C>(cursor: *mut 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 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::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, &Filters { values }))
}
unsafe extern "C" fn rust_next<C>(cursor: *mut 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 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 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 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: *mut sqlite3_vtab,
argc: c_int,
argv: *mut *mut ffi::sqlite3_value,
p_rowid: *mut ffi::sqlite3_int64,
) -> c_int
where
T: UpdateVTab<'vtab> + '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(&Inserts { values }) {
Ok(rowid) => {
*p_rowid = rowid;
Ok(())
}
Err(e) => Err(e),
}
} else {
let values = Values { args };
(*vt).update(&Updates { values })
};
vtab_error(vtab, r)
}
unsafe extern "C" fn rust_begin<'vtab, T>(vtab: *mut sqlite3_vtab) -> c_int
where
T: TransactionVTab<'vtab>,
{
let vt = vtab.cast::<T>();
vtab_error(vtab, (*vt).begin())
}
unsafe extern "C" fn rust_sync<'vtab, T>(vtab: *mut sqlite3_vtab) -> c_int
where
T: TransactionVTab<'vtab>,
{
let vt = vtab.cast::<T>();
vtab_error(vtab, (*vt).sync())
}
unsafe extern "C" fn rust_commit<'vtab, T>(vtab: *mut sqlite3_vtab) -> c_int
where
T: TransactionVTab<'vtab>,
{
let vt = vtab.cast::<T>();
vtab_error(vtab, (*vt).commit())
}
unsafe extern "C" fn rust_rollback<'vtab, T>(vtab: *mut sqlite3_vtab) -> c_int
where
T: TransactionVTab<'vtab>,
{
let vt = vtab.cast::<T>();
vtab_error(vtab, (*vt).rollback())
}
unsafe fn cursor_error<T>(cursor: *mut sqlite3_vtab_cursor, result: Result<T>) -> c_int {
vtab_error((*cursor).pVtab, result)
}
unsafe fn vtab_error<T>(vtab: *mut sqlite3_vtab, 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(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, &err.to_string());
ffi::SQLITE_ERROR
}
}
}
#[cold]
unsafe fn set_err_msg(vtab: *mut 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
}
}
}
#[cfg(feature = "array")]
pub mod array;
#[cfg(feature = "csvtab")]
pub mod csvtab;
#[cfg(feature = "series")]
pub mod series; #[cfg(all(test, feature = "modern_sqlite", not(miri)))]
mod vtablog;
#[cfg(test)]
mod test {
use std::borrow::Cow;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use wasm_bindgen_test::wasm_bindgen_test as 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!("", super::dequote("``"));
assert_eq!("", super::dequote("[]"));
assert_eq!("x", super::dequote("'x'"));
assert_eq!("x", super::dequote("\"x\""));
assert_eq!("x", super::dequote("x"));
assert_eq!("x", super::dequote("`x`"));
assert_eq!("x", super::dequote("[x]"));
assert_eq!("x'", super::dequote("'x'''"));
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"));
}
#[test]
fn test_parse_parameters() {
assert_eq!(
Ok(("key", Cow::Borrowed("value"))),
super::parameter(b"key='value'")
);
assert_eq!(
Ok(("key", Cow::Borrowed("foo=bar"))),
super::parameter(b"key='foo=bar'")
);
}
}