use crate::{ffi, sqlite3_match_version, sqlite3_require_version, types::*, value::*};
use std::{ffi::CStr, ptr};
#[repr(transparent)]
pub struct IndexInfo {
base: ffi::sqlite3_index_info,
}
impl IndexInfo {
pub fn constraints(&self) -> IndexInfoConstraintIterator<'_> {
IndexInfoConstraintIterator::new(self)
}
pub fn order_by(&self) -> IndexInfoOrderByIterator<'_> {
IndexInfoOrderByIterator::new(self)
}
pub fn distinct_mode(&self) -> DistinctMode {
sqlite3_match_version! {
3_038_000 => {
let ret = unsafe { ffi::sqlite3_vtab_distinct(&self.base as *const _ as _) };
DistinctMode(ret)
},
_ => DistinctMode(0),
}
}
pub fn index_num(&self) -> i32 {
self.base.idxNum
}
pub fn set_index_num(&mut self, val: i32) {
self.base.idxNum = val;
}
pub fn index_str(&self) -> Option<&str> {
if self.base.idxStr.is_null() {
None
} else {
let cstr = unsafe { CStr::from_ptr(self.base.idxStr) };
cstr.to_str().ok()
}
}
pub fn set_index_str(&mut self, val: Option<&str>) -> Result<()> {
if self.base.needToFreeIdxStr != 0 {
unsafe { ffi::sqlite3_free(self.base.idxStr as _) };
}
match val {
None => {
self.base.idxStr = ptr::null_mut();
self.base.needToFreeIdxStr = 0;
}
Some(x) => {
self.base.idxStr = ffi::str_to_sqlite3(x)?;
self.base.needToFreeIdxStr = 1;
}
}
Ok(())
}
pub fn set_index_str_static(&mut self, val: &'static CStr) {
if self.base.needToFreeIdxStr != 0 {
unsafe { ffi::sqlite3_free(self.base.idxStr as _) };
}
self.base.idxStr = val.as_ptr() as _;
self.base.needToFreeIdxStr = 0;
}
pub fn order_by_consumed(&self) -> bool {
self.base.orderByConsumed != 0
}
pub fn set_order_by_consumed(&mut self, val: bool) {
self.base.orderByConsumed = val as _;
}
pub fn estimated_cost(&self) -> f64 {
self.base.estimatedCost
}
pub fn set_estimated_cost(&mut self, val: f64) {
self.base.estimatedCost = val;
}
pub fn estimated_rows(&self) -> Result<i64> {
sqlite3_require_version!(3_008_002, Ok(self.base.estimatedRows))
}
pub fn set_estimated_rows(&mut self, val: i64) {
let _ = val;
sqlite3_match_version! {
3_008_220 => self.base.estimatedRows = val,
_ => (),
}
}
pub fn scan_flags(&self) -> Result<usize> {
sqlite3_require_version!(3_009_000, Ok(self.base.idxFlags as _))
}
pub fn set_scan_flags(&mut self, val: usize) {
let _ = val;
sqlite3_match_version! {
3_009_000 => self.base.idxFlags = val as _,
_ => (),
}
}
pub fn columns_used(&self) -> Result<u64> {
sqlite3_require_version!(3_010_000, Ok(self.base.colUsed))
}
}
#[derive(Copy, Clone)]
pub struct IndexInfoConstraint<'a> {
index_info: &'a IndexInfo,
position: usize,
}
impl IndexInfoConstraint<'_> {
fn constraint(&self) -> &ffi::sqlite3_index_info_sqlite3_index_constraint {
unsafe { &*self.index_info.base.aConstraint.add(self.position) }
}
fn usage(&self) -> &ffi::sqlite3_index_info_sqlite3_index_constraint_usage {
unsafe { &mut *self.index_info.base.aConstraintUsage.add(self.position) }
}
fn usage_mut(&mut self) -> &mut ffi::sqlite3_index_info_sqlite3_index_constraint_usage {
unsafe { &mut *self.index_info.base.aConstraintUsage.add(self.position) }
}
pub fn column(&self) -> i32 {
self.constraint().iColumn as _
}
pub fn op(&self) -> ConstraintOp {
ConstraintOp::from_sqlite(self.constraint().op)
}
pub fn usable(&self) -> bool {
self.constraint().usable != 0
}
pub fn rhs(&self) -> Result<&ValueRef> {
sqlite3_match_version! {
3_038_000 => unsafe {
let mut ret: *mut ffi::sqlite3_value = ptr::null_mut();
Error::from_sqlite(ffi::sqlite3_vtab_rhs_value(
&self.index_info.base as *const _ as _,
self.position as _,
&mut ret,
))?;
Ok(&*(ret as *const crate::value::ValueRef))
},
_ => Err(SQLITE_NOTFOUND),
}
}
pub fn collation(&self) -> Result<&str> {
sqlite3_require_version!(3_022_000, {
let ret = unsafe {
CStr::from_ptr(ffi::sqlite3_vtab_collation(
&self.index_info.base as *const _ as _,
self.position as _,
))
};
Ok(ret.to_str()?)
})
}
pub fn argv_index(&self) -> Option<u32> {
match self.usage().argvIndex {
0 => None,
x => Some((x - 1) as _),
}
}
pub fn set_argv_index(&mut self, idx: Option<u32>) {
self.usage_mut().argvIndex = match idx {
None => 0,
Some(i) => (i + 1) as _,
};
}
pub fn omit(&self) -> bool {
self.usage().omit != 0
}
pub fn set_omit(&mut self, val: bool) {
self.usage_mut().omit = val as _;
}
pub fn value_list_available(&self) -> bool {
sqlite3_match_version! {
3_038_000 => unsafe {
ffi::sqlite3_vtab_in(
&self.index_info.base as *const _ as _,
self.position as _,
-1,
) != 0
},
_ => false,
}
}
pub fn set_value_list_wanted(&mut self, val: bool) -> bool {
let _ = val;
sqlite3_match_version! {
3_038_000 => unsafe {
ffi::sqlite3_vtab_in(
&self.index_info.base as *const _ as _,
self.position as _,
if val { 1 } else { 0 },
) != 0
},
_ => false,
}
}
}
#[derive(Copy, Clone)]
pub struct IndexInfoOrderBy<'a> {
index_info: &'a IndexInfo,
position: usize,
}
impl IndexInfoOrderBy<'_> {
fn base(&self) -> &ffi::sqlite3_index_info_sqlite3_index_orderby {
unsafe { &*self.index_info.base.aOrderBy.add(self.position) }
}
pub fn column(&self) -> i32 {
self.base().iColumn as _
}
pub fn desc(&self) -> bool {
self.base().desc != 0
}
}
macro_rules! make_iterator(
($t:ident, $n:ident) => {
paste::paste! {
pub struct [<$t Iterator>]<'a> {
current: $t<'a>,
}
impl<'a> [<$t Iterator>]<'a> {
fn new(index_info: &'a IndexInfo) -> Self {
Self {
current: $t {
index_info,
position: usize::MAX,
},
}
}
}
impl<'a> Iterator for [<$t Iterator>]<'a> {
type Item = $t<'a>;
fn next(&mut self) -> Option<Self::Item> {
let pos = self.current.position.wrapping_add(1);
if pos < self.current.index_info.base.$n as usize {
self.current.position = pos;
Some(self.current)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.current.index_info.base.$n as usize
- self.current.position.wrapping_add(1);
(remaining, Some(remaining))
}
}
}
}
);
make_iterator!(IndexInfoConstraint, nConstraint);
make_iterator!(IndexInfoOrderBy, nOrderBy);
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum ConstraintOp {
Eq,
GT,
LE,
LT,
GE,
Match,
Like,
Glob,
Regexp,
NE,
IsNot,
IsNotNull,
IsNull,
Is,
Limit,
Offset,
Function(u8),
}
impl ConstraintOp {
pub(crate) fn assert_valid_function_constraint(&self) {
if let ConstraintOp::Function(val) = *self {
if val >= 150 {
return;
}
}
panic!("invalid function constraint")
}
fn from_sqlite(val: u8) -> ConstraintOp {
match val as _ {
2 => ConstraintOp::Eq,
4 => ConstraintOp::GT,
8 => ConstraintOp::LE,
16 => ConstraintOp::LT,
32 => ConstraintOp::GE,
64 => ConstraintOp::Match,
65 => ConstraintOp::Like,
66 => ConstraintOp::Glob,
67 => ConstraintOp::Regexp,
68 => ConstraintOp::NE,
69 => ConstraintOp::IsNot,
70 => ConstraintOp::IsNotNull,
71 => ConstraintOp::IsNull,
72 => ConstraintOp::Is,
73 => ConstraintOp::Limit,
74 => ConstraintOp::Offset,
150..=255 => ConstraintOp::Function(val),
_ => panic!("invalid constraint op"),
}
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
pub struct DistinctMode(pub i32);
impl DistinctMode {
pub fn may_return_unordered(&self) -> bool {
self.0 == 1 || self.0 == 2
}
pub fn may_omit_duplicates(&self) -> bool {
self.0 == 2 || self.0 == 3
}
}
impl From<i32> for DistinctMode {
fn from(val: i32) -> Self {
Self(val)
}
}
impl std::fmt::Debug for IndexInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let mut ds = f.debug_struct("IndexInfo");
ds.field(
"constraints",
&self.constraints().collect::<Vec<_>>().as_slice(),
)
.field("order_by", &self.order_by().collect::<Vec<_>>().as_slice())
.field("index_num", &self.index_num())
.field("index_str", &self.index_str())
.field("order_by_consumed", &self.order_by_consumed())
.field("estimated_cost", &self.estimated_cost());
self.estimated_rows()
.map(|v| ds.field("estimated_rows", &v))
.ok();
self.scan_flags().map(|v| ds.field("scan_flags", &v)).ok();
self.columns_used()
.map(|v| ds.field("columns_used", &v))
.ok();
ds.finish()
}
}
impl std::fmt::Debug for IndexInfoConstraint<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let mut ds = f.debug_struct("IndexInfoConstraint");
ds.field("column", &self.column())
.field("op", &self.op())
.field("usable", &self.usable());
sqlite3_match_version! {
3_038_000 => {
ds.field("rhs", &self.rhs());
}
_ => (),
}
sqlite3_match_version! {
3_022_000 => {
ds.field("collation", &self.collation());
}
_ => (),
}
ds.field("argv_index", &self.argv_index())
.field("omit", &self.omit())
.finish()
}
}
impl std::fmt::Debug for IndexInfoOrderBy<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("IndexInfoOrderBy")
.field("column", &self.column())
.field("desc", &self.desc())
.finish()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn distinct_mode() {
assert!(!DistinctMode(0).may_return_unordered());
assert!(!DistinctMode(0).may_omit_duplicates());
assert!(DistinctMode(1).may_return_unordered());
assert!(!DistinctMode(1).may_omit_duplicates());
assert!(DistinctMode(2).may_return_unordered());
assert!(DistinctMode(2).may_omit_duplicates());
assert!(!DistinctMode(3).may_return_unordered());
assert!(DistinctMode(3).may_omit_duplicates());
}
}