use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use crate::error::{Error, Result};
use crate::value::Value;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct VTabSchema {
pub columns: Vec<String>,
}
impl VTabSchema {
pub fn new<I, S>(columns: I) -> VTabSchema
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
VTabSchema {
columns: columns.into_iter().map(Into::into).collect(),
}
}
pub fn len(&self) -> usize {
self.columns.len()
}
pub fn is_empty(&self) -> bool {
self.columns.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstraintOp {
Eq,
Gt,
Le,
Lt,
Ge,
Match,
Like,
Glob,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndexConstraint {
pub column: usize,
pub op: ConstraintOp,
pub usable: bool,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct IndexPlan {
pub idx_num: i32,
pub idx_str: Option<String>,
pub estimated_cost: f64,
pub argv_index: Vec<u32>,
pub omit: Vec<bool>,
pub order_by_consumed: bool,
}
pub trait VTabRow {
fn column(&self, i: usize) -> Value;
fn rowid(&self) -> i64;
}
pub trait VTabCursor {
type Row: VTabRow;
fn next(&mut self) -> Result<Option<Self::Row>>;
}
pub trait VTabModule {
type Cursor: VTabCursor;
fn connect(&self, args: &[&str]) -> Result<VTabSchema>;
fn best_index(&self, _constraints: &[IndexConstraint]) -> Result<IndexPlan> {
Ok(IndexPlan {
estimated_cost: f64::from(u32::MAX),
..IndexPlan::default()
})
}
fn open(&self, args: &[&str], plan: &IndexPlan) -> Result<Self::Cursor>;
fn filter(
&self,
cursor: Self::Cursor,
_plan: &IndexPlan,
_argv: &[Value],
) -> Result<Self::Cursor> {
Ok(cursor)
}
}
pub trait DynVTabModule {
fn dyn_connect(&self, args: &[&str]) -> Result<VTabSchema>;
fn dyn_best_index(&self, constraints: &[IndexConstraint]) -> Result<IndexPlan>;
fn dyn_open(
&self,
args: &[&str],
plan: &IndexPlan,
argv: &[Value],
) -> Result<Box<dyn DynCursor>>;
}
pub trait DynRow {
fn dyn_column(&self, i: usize) -> Value;
fn dyn_rowid(&self) -> i64;
}
impl<R: VTabRow> DynRow for R {
fn dyn_column(&self, i: usize) -> Value {
VTabRow::column(self, i)
}
fn dyn_rowid(&self) -> i64 {
VTabRow::rowid(self)
}
}
pub trait DynCursor {
fn dyn_next(&mut self) -> Result<Option<Box<dyn DynRow>>>;
}
impl<C: VTabCursor> DynCursor for C
where
C::Row: 'static,
{
fn dyn_next(&mut self) -> Result<Option<Box<dyn DynRow>>> {
Ok(VTabCursor::next(self)?.map(|r| Box::new(r) as Box<dyn DynRow>))
}
}
impl<M> DynVTabModule for M
where
M: VTabModule,
M::Cursor: 'static,
{
fn dyn_connect(&self, args: &[&str]) -> Result<VTabSchema> {
VTabModule::connect(self, args)
}
fn dyn_best_index(&self, constraints: &[IndexConstraint]) -> Result<IndexPlan> {
VTabModule::best_index(self, constraints)
}
fn dyn_open(
&self,
args: &[&str],
plan: &IndexPlan,
argv: &[Value],
) -> Result<Box<dyn DynCursor>> {
let cursor = VTabModule::open(self, args, plan)?;
let cursor = VTabModule::filter(self, cursor, plan, argv)?;
Ok(Box::new(cursor) as Box<dyn DynCursor>)
}
}
#[derive(Default)]
pub struct VTabRegistry {
modules: BTreeMap<String, Box<dyn DynVTabModule>>,
}
impl VTabRegistry {
pub fn new() -> VTabRegistry {
VTabRegistry {
modules: BTreeMap::new(),
}
}
pub fn register(&mut self, name: &str, module: Box<dyn DynVTabModule>) -> Result<()> {
let key = name.to_ascii_lowercase();
if self.modules.contains_key(&key) {
return Err(Error::Constraint(alloc::format!(
"virtual table module \"{name}\" is already registered"
)));
}
self.modules.insert(key, module);
Ok(())
}
pub fn get(&self, name: &str) -> Option<&dyn DynVTabModule> {
self.modules
.get(&name.to_ascii_lowercase())
.map(AsRef::as_ref)
}
pub fn unregister(&mut self, name: &str) -> Option<Box<dyn DynVTabModule>> {
self.modules.remove(&name.to_ascii_lowercase())
}
pub fn len(&self) -> usize {
self.modules.len()
}
pub fn is_empty(&self) -> bool {
self.modules.is_empty()
}
}
impl VTabRegistry {
pub fn with_builtins() -> VTabRegistry {
let mut reg = VTabRegistry::new();
reg.register("series", Box::new(SeriesModule))
.expect("fresh registry has no name collisions");
reg
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct SeriesModule;
mod series_plan {
pub const SCAN: i32 = 0;
pub const LOWER: i32 = 1 << 0;
pub const UPPER: i32 = 1 << 1;
}
#[derive(Debug)]
pub struct SeriesCursor {
next: i64,
stop: i64,
step: i64,
next_rowid: i64,
done: bool,
generated: usize,
}
impl SeriesCursor {
pub fn generated(&self) -> usize {
self.generated
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SeriesRow {
value: i64,
rowid: i64,
}
impl VTabRow for SeriesRow {
fn column(&self, i: usize) -> Value {
match i {
0 => Value::Integer(self.value),
_ => Value::Null,
}
}
fn rowid(&self) -> i64 {
self.rowid
}
}
impl VTabCursor for SeriesCursor {
type Row = SeriesRow;
fn next(&mut self) -> Result<Option<SeriesRow>> {
if self.done {
return Ok(None);
}
let in_range = if self.step > 0 {
self.next <= self.stop
} else {
self.next >= self.stop
};
if !in_range {
self.done = true;
return Ok(None);
}
let row = SeriesRow {
value: self.next,
rowid: self.next_rowid,
};
self.generated += 1;
self.next_rowid += 1;
match self.next.checked_add(self.step) {
Some(n) => self.next = n,
None => self.done = true, }
Ok(Some(row))
}
}
fn advance_to(start: i64, step: i64, target: i64) -> i64 {
debug_assert!(step != 0);
let delta = i128::from(target) - i128::from(start);
let step128 = i128::from(step);
if (step > 0 && delta <= 0) || (step < 0 && delta >= 0) {
return start;
}
let k = {
let q = delta / step128;
let r = delta % step128;
if r != 0 {
q + 1
} else {
q
}
};
match i128::from(start).checked_add(k * step128) {
Some(v) if v >= i128::from(i64::MIN) && v <= i128::from(i64::MAX) => v as i64,
_ => start,
}
}
impl SeriesModule {
fn parse_arg(s: &str) -> Result<i64> {
s.trim()
.parse::<i64>()
.map_err(|_| Error::Error(alloc::format!("series(): invalid integer argument {s:?}")))
}
}
impl VTabModule for SeriesModule {
type Cursor = SeriesCursor;
fn connect(&self, args: &[&str]) -> Result<VTabSchema> {
if args.is_empty() {
return Err(Error::Error(
"series() requires at least a start argument".into(),
));
}
if args.len() > 3 {
return Err(Error::Error("series() takes at most 3 arguments".into()));
}
for a in args {
SeriesModule::parse_arg(a)?;
}
Ok(VTabSchema::new(["value"]))
}
fn best_index(&self, constraints: &[IndexConstraint]) -> Result<IndexPlan> {
let mut argv_index = alloc::vec![0u32; constraints.len()];
let mut idx_num = series_plan::SCAN;
let mut next_arg = 1u32;
for (i, c) in constraints.iter().enumerate() {
if c.column != 0 || !c.usable {
continue;
}
let bound = match c.op {
ConstraintOp::Eq => series_plan::LOWER | series_plan::UPPER,
ConstraintOp::Ge | ConstraintOp::Gt => series_plan::LOWER,
ConstraintOp::Le | ConstraintOp::Lt => series_plan::UPPER,
_ => continue,
};
argv_index[i] = next_arg;
next_arg += 1;
idx_num |= bound;
}
if idx_num == series_plan::SCAN {
return Ok(IndexPlan {
estimated_cost: f64::from(u32::MAX),
..IndexPlan::default()
});
}
let mut sides = String::new();
for c in constraints {
if c.column != 0 || !c.usable {
continue;
}
match c.op {
ConstraintOp::Eq => sides.push('='),
ConstraintOp::Ge | ConstraintOp::Gt => sides.push('>'),
ConstraintOp::Le | ConstraintOp::Lt => sides.push('<'),
_ => {}
}
}
Ok(IndexPlan {
idx_num,
idx_str: Some(sides),
estimated_cost: 100.0,
argv_index,
omit: Vec::new(),
order_by_consumed: false,
})
}
fn filter(
&self,
mut cursor: SeriesCursor,
plan: &IndexPlan,
argv: &[Value],
) -> Result<SeriesCursor> {
if plan.idx_num == series_plan::SCAN {
return Ok(cursor);
}
let sides = plan.idx_str.as_deref().unwrap_or("");
let mut lower: Option<i64> = None;
let mut upper: Option<i64> = None;
for (side, v) in sides.chars().zip(argv.iter()) {
let n = match v {
Value::Integer(i) => *i,
_ => continue,
};
match side {
'>' => lower = Some(lower.map_or(n, |cur| cur.max(n))),
'<' => upper = Some(upper.map_or(n, |cur| cur.min(n))),
'=' => {
lower = Some(lower.map_or(n, |cur| cur.max(n)));
upper = Some(upper.map_or(n, |cur| cur.min(n)));
}
_ => {}
}
}
let ascending = cursor.step > 0;
if ascending {
if let Some(lo) = lower {
cursor.next = advance_to(cursor.next, cursor.step, lo);
}
if let Some(hi) = upper {
cursor.stop = cursor.stop.min(hi);
}
} else {
if let Some(hi) = upper {
cursor.next = advance_to(cursor.next, cursor.step, hi);
}
if let Some(lo) = lower {
cursor.stop = cursor.stop.max(lo);
}
}
Ok(cursor)
}
fn open(&self, args: &[&str], plan: &IndexPlan) -> Result<SeriesCursor> {
let _ = plan;
let start = args
.first()
.map(|a| SeriesModule::parse_arg(a))
.transpose()?;
let Some(start) = start else {
return Err(Error::Error(
"series() requires at least a start argument".into(),
));
};
let stop = match args.get(1) {
Some(a) => SeriesModule::parse_arg(a)?,
None => start,
};
let step = match args.get(2) {
Some(a) => SeriesModule::parse_arg(a)?,
None => 1,
};
SeriesModule::scan(start, stop, step)
}
}
impl SeriesModule {
pub fn scan(start: i64, stop: i64, step: i64) -> Result<SeriesCursor> {
if step == 0 {
return Err(Error::Error("series(): step must be non-zero".into()));
}
Ok(SeriesCursor {
next: start,
stop,
step,
next_rowid: 1,
done: false,
generated: 0,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn drain(mut cur: SeriesCursor) -> Vec<(i64, i64)> {
let mut out = Vec::new();
while let Some(row) = cur.next().unwrap() {
assert_eq!(row.column(0), Value::Integer(row.value));
assert_eq!(row.column(1), Value::Null);
out.push((row.rowid(), row.value));
}
out
}
#[test]
fn connect_declares_value_column() {
let m = SeriesModule;
let schema = m.connect(&["1", "5"]).unwrap();
assert_eq!(schema.columns, vec![String::from("value")]);
assert_eq!(schema.len(), 1);
assert!(!schema.is_empty());
}
#[test]
fn connect_validates_arguments() {
let m = SeriesModule;
assert!(m.connect(&[]).is_err()); assert!(m.connect(&["1", "2", "3", "4"]).is_err()); assert!(m.connect(&["notanint"]).is_err()); assert!(m.connect(&["10"]).is_ok());
}
#[test]
fn cursor_iterates_ascending() {
let cur = SeriesModule::scan(1, 5, 1).unwrap();
assert_eq!(drain(cur), vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]);
}
#[test]
fn cursor_iterates_with_step() {
let cur = SeriesModule::scan(0, 10, 3).unwrap();
assert_eq!(drain(cur), vec![(1, 0), (2, 3), (3, 6), (4, 9)]);
}
#[test]
fn cursor_iterates_descending() {
let cur = SeriesModule::scan(3, 1, -1).unwrap();
assert_eq!(drain(cur), vec![(1, 3), (2, 2), (3, 1)]);
}
#[test]
fn empty_range_yields_no_rows() {
let cur = SeriesModule::scan(5, 1, 1).unwrap();
assert_eq!(drain(cur), vec![]);
}
#[test]
fn step_zero_is_rejected() {
assert!(SeriesModule::scan(1, 5, 0).is_err());
}
#[test]
fn next_keeps_returning_none_after_end() {
let mut cur = SeriesModule::scan(1, 1, 1).unwrap();
assert!(cur.next().unwrap().is_some());
assert!(cur.next().unwrap().is_none());
assert!(cur.next().unwrap().is_none());
}
#[test]
fn default_best_index_is_a_full_scan_plan() {
let m = SeriesModule;
let plan = m.best_index(&[]).unwrap();
assert_eq!(plan.idx_num, 0);
assert_eq!(plan.idx_str, None);
assert!(plan.argv_index.is_empty());
assert!(plan.estimated_cost > 1.0);
}
#[test]
fn advance_to_aligns_to_grid() {
assert_eq!(advance_to(0, 2, 3), 4);
assert_eq!(advance_to(0, 2, 4), 4);
assert_eq!(advance_to(5, 1, 2), 5);
assert_eq!(advance_to(10, -2, 7), 6);
assert_eq!(advance_to(10, -2, 8), 8);
assert_eq!(advance_to(1, 1, 3), 3);
}
#[test]
fn best_index_pushes_value_constraints() {
let m = SeriesModule;
let cons = [
IndexConstraint {
column: 0,
op: ConstraintOp::Ge,
usable: true,
},
IndexConstraint {
column: 0,
op: ConstraintOp::Le,
usable: true,
},
];
let plan = m.best_index(&cons).unwrap();
assert_eq!(plan.idx_num, series_plan::LOWER | series_plan::UPPER);
assert_eq!(plan.argv_index, vec![1, 2]);
assert_eq!(plan.idx_str.as_deref(), Some("><"));
assert!(plan.estimated_cost < f64::from(u32::MAX));
let unusable = [IndexConstraint {
column: 0,
op: ConstraintOp::Ge,
usable: false,
}];
let plan = m.best_index(&unusable).unwrap();
assert_eq!(plan.idx_num, series_plan::SCAN);
assert_eq!(plan.argv_index, Vec::<u32>::new());
}
#[test]
fn filter_narrows_generation() {
let m = SeriesModule;
let cons = [
IndexConstraint {
column: 0,
op: ConstraintOp::Ge,
usable: true,
},
IndexConstraint {
column: 0,
op: ConstraintOp::Le,
usable: true,
},
];
let plan = m.best_index(&cons).unwrap();
let cur = SeriesModule::scan(0, 100, 1).unwrap();
let mut cur = m
.filter(cur, &plan, &[Value::Integer(3), Value::Integer(5)])
.unwrap();
let mut out = Vec::new();
while let Some(row) = cur.next().unwrap() {
out.push((row.rowid(), row.value));
}
assert_eq!(out, vec![(1, 3), (2, 4), (3, 5)]);
assert_eq!(cur.generated(), 3, "only 3..=5 generated, not 0..=100");
}
#[test]
fn filter_equality_stays_on_grid() {
let m = SeriesModule;
let cons = [IndexConstraint {
column: 0,
op: ConstraintOp::Eq,
usable: true,
}];
let plan = m.best_index(&cons).unwrap();
let cur = SeriesModule::scan(0, 10, 2).unwrap();
let cur = m.filter(cur, &plan, &[Value::Integer(3)]).unwrap();
assert_eq!(drain(cur), vec![]);
let cur = SeriesModule::scan(0, 10, 2).unwrap();
let cur = m.filter(cur, &plan, &[Value::Integer(6)]).unwrap();
assert_eq!(drain(cur), vec![(1, 6)]);
}
#[test]
fn registry_register_get_roundtrip() {
let mut reg = VTabRegistry::new();
assert!(reg.is_empty());
reg.register("series", Box::new(SeriesModule)).unwrap();
assert_eq!(reg.len(), 1);
assert!(!reg.is_empty());
assert!(reg.get("series").is_some());
assert!(reg.get("SERIES").is_some());
assert!(reg.get("missing").is_none());
}
#[test]
fn registry_rejects_duplicate_names() {
let mut reg = VTabRegistry::new();
reg.register("series", Box::new(SeriesModule)).unwrap();
let err = reg.register("SERIES", Box::new(SeriesModule)).unwrap_err();
assert!(matches!(err, Error::Constraint(_)));
}
#[test]
fn registry_unregister() {
let mut reg = VTabRegistry::new();
reg.register("series", Box::new(SeriesModule)).unwrap();
assert!(reg.unregister("Series").is_some());
assert!(reg.is_empty());
assert!(reg.unregister("series").is_none());
}
#[test]
fn dyn_module_end_to_end() {
let mut reg = VTabRegistry::new();
reg.register("series", Box::new(SeriesModule)).unwrap();
let module = reg.get("series").expect("registered");
let schema = module.dyn_connect(&["2", "8", "2"]).unwrap();
assert_eq!(schema.columns, vec![String::from("value")]);
let plan = module.dyn_best_index(&[]).unwrap();
let mut cur = module.dyn_open(&["2", "8", "2"], &plan, &[]).unwrap();
let mut seen = Vec::new();
while let Some(row) = cur.dyn_next().unwrap() {
seen.push(row.dyn_column(0));
}
assert_eq!(
seen,
vec![
Value::Integer(2),
Value::Integer(4),
Value::Integer(6),
Value::Integer(8),
]
);
}
#[test]
fn dyn_cursor_yields_rows() {
let cur = SeriesModule::scan(10, 12, 1).unwrap();
let mut dyn_cur: Box<dyn DynCursor> = Box::new(cur);
let mut seen = Vec::new();
while let Some(row) = dyn_cur.dyn_next().unwrap() {
seen.push((row.dyn_rowid(), row.dyn_column(0)));
}
assert_eq!(
seen,
vec![
(1, Value::Integer(10)),
(2, Value::Integer(11)),
(3, Value::Integer(12)),
]
);
}
}