matdb 0.1.0

An experimental embedded SQL-like DBMS
Documentation
use std::cmp::Ordering;

use anyhow::Result;

use crate::{encoding::d_decode_row, value::Value, Db};

use super::{
    cell::fetch_cell,
    encoding::{decode_page, Page},
};

#[derive(Debug)]
pub struct RangeEnd<T> {
    pub inclusive: bool,
    pub value: T,
}

#[derive(Debug)]
pub struct Range<T> {
    pub backwards: bool,
    pub prefix: Vec<T>,

    pub start: Option<RangeEnd<T>>,
    pub end: Option<RangeEnd<T>>,
}

impl Range<Value> {
    fn while_f(&self, k: &[Value]) -> bool {
        assert!(!self.backwards);

        &self.prefix[..] == &k[0..self.prefix.len()]
            && self
                .end
                .as_ref()
                .map(|end_end| {
                    if end_end.inclusive {
                        &k[self.prefix.len()] <= &end_end.value
                    } else {
                        &k[self.prefix.len()] < &end_end.value
                    }
                })
                .unwrap_or(true)
    }
}

#[derive(Debug)]
pub struct RangeIterKV {
    table: String,
    tx_id: u64,

    range: Range<Value>,

    prev_key: Option<Vec<Value>>,
}

impl RangeIterKV {
    pub fn new(range: Range<Value>, tx_id: u64, table: String) -> Self {
        Self {
            table,
            tx_id,
            range,
            prev_key: None,
        }
    }

    pub fn new_simple(table: String, tx_id: u64, key: Vec<Value>) -> Self {
        Self {
            table,
            tx_id,
            range: Range {
                backwards: false,
                prefix: key,
                start: None,
                end: None,
            },
            prev_key: None,
        }
    }

    fn find_first(
        &mut self,
        db: &mut Db,
        root_page: u64,
    ) -> Result<Option<(Vec<Value>, Vec<Value>)>> {
        match decode_page(&db.get_page(root_page)?)? {
            Page::BTreeLeaf(_tx_id, cells) => {
                let mut id = 0;
                let final_id = loop {
                    if id >= cells.len() {
                        return Ok(None);
                    }

                    let current_key = d_decode_row(&fetch_cell(db, &cells[id].0)?)?;

                    match current_key[0..self.range.prefix.len()].cmp(&self.range.prefix) {
                        Ordering::Less => {
                            id += 1;
                            continue;
                        }
                        Ordering::Equal => {
                            if let Some(start_end) = &self.range.start {
                                match current_key[self.range.prefix.len()].cmp(&start_end.value) {
                                    Ordering::Less => {
                                        id += 1;
                                        continue;
                                    }
                                    Ordering::Equal => {
                                        if start_end.inclusive {
                                            break id;
                                        } else {
                                            id += 1;
                                            continue;
                                        }
                                    }
                                    Ordering::Greater => {
                                        break id;
                                    }
                                }
                            } else {
                                break id;
                            }
                        }
                        Ordering::Greater => {
                            return Ok(None);
                        }
                    }
                };

                let k = d_decode_row(&fetch_cell(db, &cells[final_id].0)?)?;

                if self.range.while_f(&k) {
                    Ok(Some((
                        k,
                        d_decode_row(&fetch_cell(db, &cells[final_id].1)?)?,
                    )))
                } else {
                    Ok(None)
                }
            }
            Page::BTreeInterior(_tx_id, (left, cells)) => {
                let mut id = 0;
                let final_id = loop {
                    if id >= cells.len() {
                        if !cells.is_empty() {
                            break id;
                        } else {
                            return Ok(None);
                        }
                    }

                    let current_key = d_decode_row(&fetch_cell(db, &cells[id].0)?)?;

                    match current_key[0..self.range.prefix.len()].cmp(&self.range.prefix) {
                        Ordering::Less => {
                            id += 1;
                            continue;
                        }
                        Ordering::Equal => {
                            if let Some(start_end) = &self.range.start {
                                match current_key[self.range.prefix.len()].cmp(&start_end.value) {
                                    Ordering::Less => {
                                        id += 1;
                                        continue;
                                    }
                                    Ordering::Equal => {
                                        if start_end.inclusive {
                                            break id;
                                        } else {
                                            id += 1;
                                            continue;
                                        }
                                    }
                                    Ordering::Greater => {
                                        break id;
                                    }
                                }
                            } else {
                                break id;
                            }
                        }
                        Ordering::Greater => {
                            break id;
                        }
                    }
                };

                self.find_first(
                    db,
                    if final_id == 0 {
                        left
                    } else {
                        cells[final_id - 1].1
                    },
                )
            }
            _ => unreachable!(),
        }
    }

    fn find_next(
        &mut self,
        db: &mut Db,
        root_page: u64,
        prev_key: Vec<Value>,
    ) -> Result<Option<(Vec<Value>, Vec<Value>)>> {
        match decode_page(&db.get_page(root_page)?)? {
            Page::BTreeLeaf(_tx_id, cells) => {
                if cells.is_empty() {
                    return Ok(None);
                }

                let id = match cells.binary_search_by(|probe| {
                    let k = d_decode_row(&fetch_cell(db, &probe.0).unwrap()).unwrap();
                    k.cmp(&prev_key)
                }) {
                    Ok(id) => id + 1,
                    Err(id) => id,
                };

                if id >= cells.len() {
                    return Ok(None);
                }

                let k = d_decode_row(&fetch_cell(db, &cells[id].0)?)?;

                if self.range.while_f(&k) {
                    Ok(Some((k, d_decode_row(&fetch_cell(db, &cells[id].1)?)?)))
                } else {
                    Ok(None)
                }
            }
            Page::BTreeInterior(_tx_id, (left, cells)) => {
                let id = match cells.binary_search_by(|probe| {
                    let k = d_decode_row(&fetch_cell(db, &probe.0).unwrap()).unwrap();
                    k.cmp(&prev_key)
                }) {
                    Ok(id) => id + 1,
                    Err(id) => id,
                };

                if id > cells.len() {
                    return Ok(None);
                }

                self.find_next(db, if id == 0 { left } else { cells[id - 1].1 }, prev_key)
            }
            _ => unreachable!(),
        }
    }

    pub fn next(&mut self, db: &mut Db) -> Result<Option<(Vec<Value>, Vec<Value>)>> {
        let root_page = db.table_to_kv_id_or_error(self.tx_id, &self.table)?;

        let res = if let Some(prev_key) = self.prev_key.take() {
            self.find_next(db, root_page, prev_key)?
        } else {
            self.find_first(db, root_page)?
        };

        self.prev_key = res.as_ref().map(|(k, _)| k.clone());

        Ok(res)
    }
}