use inillucent_base::DbResult;
use inillucent_value::{Affinity, Value};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConstraintOp {
Eq,
Gt,
Le,
Lt,
Ge,
Match,
Like,
Glob,
Regexp,
Ne,
IsNot,
IsNotNull,
IsNull,
Is,
}
impl ConstraintOp {
pub fn code(self) -> i32 {
match self {
ConstraintOp::Eq => 2,
ConstraintOp::Gt => 4,
ConstraintOp::Le => 8,
ConstraintOp::Lt => 16,
ConstraintOp::Ge => 32,
ConstraintOp::Match => 64,
ConstraintOp::Like => 65,
ConstraintOp::Glob => 66,
ConstraintOp::Regexp => 67,
ConstraintOp::Ne => 68,
ConstraintOp::IsNot => 69,
ConstraintOp::IsNotNull => 70,
ConstraintOp::IsNull => 71,
ConstraintOp::Is => 72,
}
}
pub fn has_value(self) -> bool {
!matches!(self, ConstraintOp::IsNull | ConstraintOp::IsNotNull)
}
}
pub const ROWID_COLUMN: i32 = -1;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ConstraintSpec {
pub column: i32,
pub op: ConstraintOp,
pub usable: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OrderSpec {
pub column: i32,
pub descending: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ConstraintUsage {
pub argument: usize,
pub omit: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct IndexQuery {
pub constraints: Vec<ConstraintSpec>,
pub order_by: Vec<OrderSpec>,
pub usage: Vec<ConstraintUsage>,
pub index_number: i32,
pub index_string: String,
pub ordered: bool,
pub estimated_cost: f64,
pub estimated_rows: i64,
}
impl IndexQuery {
pub fn new(constraints: Vec<ConstraintSpec>, order_by: Vec<OrderSpec>) -> IndexQuery {
let usage = vec![ConstraintUsage::default(); constraints.len()];
IndexQuery {
constraints,
order_by,
usage,
index_number: 0,
index_string: String::new(),
ordered: false,
estimated_cost: 5.0e98,
estimated_rows: 25,
}
}
pub fn use_constraint(&mut self, index: usize, omit: bool) -> usize {
let next = self
.usage
.iter()
.map(|usage| usage.argument)
.max()
.unwrap_or(0)
.saturating_add(1);
if let Some(usage) = self.usage.get_mut(index) {
usage.argument = next;
usage.omit = omit;
}
next
}
pub fn argument_order(&self) -> Vec<usize> {
let mut claimed: Vec<(usize, usize)> = self
.usage
.iter()
.enumerate()
.filter(|(_, usage)| usage.argument > 0)
.map(|(index, usage)| (usage.argument, index))
.collect();
claimed.sort_unstable();
claimed.into_iter().map(|(_, index)| index).collect()
}
}
#[derive(Clone, Debug)]
pub struct FilterPlan {
pub index_number: i32,
pub index_string: String,
pub arguments: Vec<Value<'static>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DeclaredColumn {
pub name: Vec<u8>,
pub declared_type: Vec<u8>,
pub affinity: Affinity,
pub collation: Vec<u8>,
pub hidden: bool,
}
impl DeclaredColumn {
pub fn visible(name: &str) -> DeclaredColumn {
DeclaredColumn {
name: name.as_bytes().to_vec(),
declared_type: Vec::new(),
affinity: Affinity::Blob,
collation: b"binary".to_vec(),
hidden: false,
}
}
pub fn hidden(name: &str) -> DeclaredColumn {
DeclaredColumn {
hidden: true,
..DeclaredColumn::visible(name)
}
}
pub fn typed(mut self, declared: &str) -> DeclaredColumn {
self.declared_type = declared.as_bytes().to_vec();
self.affinity = inillucent_value::affinity::for_column(declared.as_bytes());
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Declaration {
pub columns: Vec<DeclaredColumn>,
pub without_rowid: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ShadowRoot {
pub suffix: Vec<u8>,
pub root: u32,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ModuleArguments {
pub database: usize,
pub schema: Vec<u8>,
pub table: Vec<u8>,
pub module: Vec<u8>,
pub arguments: Vec<Vec<u8>>,
pub shadows: Vec<ShadowRoot>,
}
impl ModuleArguments {
pub fn shadow(&self, suffix: &[u8]) -> Option<u32> {
self.shadows
.iter()
.find(|shadow| shadow.suffix == suffix)
.map(|shadow| shadow.root)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ShadowTable {
pub suffix: Vec<u8>,
pub create_sql: String,
pub owner: Option<Vec<u8>>,
}
#[derive(Clone, Debug)]
pub enum Change {
Delete(Value<'static>),
Insert {
rowid: Value<'static>,
values: Vec<Value<'static>>,
},
Update {
old_rowid: Value<'static>,
new_rowid: Value<'static>,
values: Vec<Value<'static>>,
},
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ModuleRef {
pub name: Vec<u8>,
pub folded: Vec<u8>,
pub arguments: Vec<Vec<u8>>,
}
pub trait ShadowStore {
fn read_row(&mut self, root: u32, rowid: i64) -> DbResult<Option<Vec<Value<'static>>>>;
fn write_row(&mut self, root: u32, rowid: i64, values: &[Value<'static>]) -> DbResult<()>;
fn delete_row(&mut self, root: u32, rowid: i64) -> DbResult<()>;
fn max_rowid(&mut self, root: u32) -> DbResult<i64>;
fn scan(
&mut self,
root: u32,
body: &mut dyn FnMut(i64, &[Value<'static>]) -> DbResult<bool>,
) -> DbResult<()>;
fn scan_from(
&mut self,
root: u32,
from: i64,
body: &mut dyn FnMut(i64, &[Value<'static>]) -> DbResult<bool>,
) -> DbResult<()> {
self.scan(root, &mut |rowid, values| {
if rowid < from {
return Ok(true);
}
body(rowid, values)
})
}
fn read_keyed(
&mut self,
root: u32,
key: &[Value<'static>],
columns: usize,
) -> DbResult<Option<Vec<Value<'static>>>>;
fn write_keyed(
&mut self,
root: u32,
key_columns: usize,
values: &[Value<'static>],
) -> DbResult<()>;
fn delete_keyed(&mut self, root: u32, key: &[Value<'static>]) -> DbResult<()>;
fn scan_keyed(
&mut self,
root: u32,
key_columns: usize,
body: &mut dyn FnMut(&[Value<'static>]) -> DbResult<bool>,
) -> DbResult<()>;
fn scan_keyed_from(
&mut self,
root: u32,
key_columns: usize,
from: &[Value<'static>],
body: &mut dyn FnMut(&[Value<'static>]) -> DbResult<bool>,
) -> DbResult<()> {
self.scan_keyed(root, key_columns, &mut |values| {
if key_sorts_below(values, from) {
return Ok(true);
}
body(values)
})
}
}
pub fn key_sorts_below(row: &[Value<'static>], from: &[Value<'static>]) -> bool {
use std::cmp::Ordering;
for (left, right) in row.iter().zip(from.iter()) {
match inillucent_value::compare::compare_values(
left,
right,
inillucent_value::Collation::Binary,
) {
Ordering::Less => return true,
Ordering::Greater => return false,
Ordering::Equal => continue,
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn operator_codes_match_the_published_constants() {
assert_eq!(ConstraintOp::Eq.code(), 2);
assert_eq!(ConstraintOp::Gt.code(), 4);
assert_eq!(ConstraintOp::Le.code(), 8);
assert_eq!(ConstraintOp::Lt.code(), 16);
assert_eq!(ConstraintOp::Ge.code(), 32);
assert_eq!(ConstraintOp::Match.code(), 64);
assert_eq!(ConstraintOp::Is.code(), 72);
}
#[test]
fn argument_positions_are_claimed_in_order() {
let mut query = IndexQuery::new(
vec![
ConstraintSpec {
column: 0,
op: ConstraintOp::Eq,
usable: true,
},
ConstraintSpec {
column: 1,
op: ConstraintOp::Gt,
usable: true,
},
],
Vec::new(),
);
assert_eq!(query.use_constraint(1, true), 1);
assert_eq!(query.use_constraint(0, false), 2);
assert_eq!(query.argument_order(), vec![1, 0]);
assert!(query.usage[1].omit);
assert!(!query.usage[0].omit);
}
#[test]
fn the_default_cost_is_deliberately_enormous() {
let query = IndexQuery::new(Vec::new(), Vec::new());
assert!(query.estimated_cost > 1.0e90);
}
#[test]
fn the_null_tests_carry_no_value() {
assert!(!ConstraintOp::IsNull.has_value());
assert!(!ConstraintOp::IsNotNull.has_value());
assert!(ConstraintOp::Eq.has_value());
}
}