neuxdb 0.1.0

A super simple, embedded, encrypted database like SQLite, using pipe-separated files and age encryption.
Documentation
use crate::core::storage::{read_table, write_table};
use crate::core::syntax::{ComparisonOp, WhereClause};
use crate::error::{NeuxDbError, Result};
use crate::types::Value;
pub fn update_rows(
    table: &str,
    set_col: &str,
    set_val: Value,
    condition: &WhereClause,
) -> Result<usize> {
    let (headers, mut rows) = read_table(table)?;
    let set_idx = headers
        .iter()
        .position(|c| c == set_col)
        .ok_or_else(|| NeuxDbError::ColumnNotFound(set_col.to_string(), table.to_string()))?;
    let mut updated = 0;
    for row in &mut rows {
        if eval_where(row, &headers, condition) {
            row[set_idx] = set_val.clone();
            updated += 1;
        }
    }
    if updated > 0 {
        write_table(table, &headers, &rows)?;
    }
    Ok(updated)
}
fn eval_where(row: &[Value], headers: &[String], clause: &WhereClause) -> bool {
    match clause {
        WhereClause::Condition {
            column,
            operator,
            value,
        } => {
            let idx = headers.iter().position(|h| h == column);
            if idx.is_none() {
                return false;
            }
            let cell = &row[idx.unwrap()];
            match operator {
                ComparisonOp::Eq => cell == value,
                ComparisonOp::Ne => cell != value,
                ComparisonOp::Lt => compare_values(cell, value) == std::cmp::Ordering::Less,
                ComparisonOp::Gt => compare_values(cell, value) == std::cmp::Ordering::Greater,
                ComparisonOp::Le => compare_values(cell, value) != std::cmp::Ordering::Greater,
                ComparisonOp::Ge => compare_values(cell, value) != std::cmp::Ordering::Less,
                ComparisonOp::Like => like_match(&cell.to_like_string(), &value.to_like_string()),
            }
        }
        WhereClause::And(a, b) => eval_where(row, headers, a) && eval_where(row, headers, b),
        WhereClause::Or(a, b) => eval_where(row, headers, a) || eval_where(row, headers, b),
    }
}
fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering {
    match (a, b) {
        (Value::Int(x), Value::Int(y)) => x.cmp(y),
        (Value::Text(x), Value::Text(y)) => x.cmp(y),
        _ => a.to_string().cmp(&b.to_string()),
    }
}
fn like_match(s: &str, pat: &str) -> bool {
    let mut s_iter = s.chars().peekable();
    let mut p_iter = pat.chars().peekable();
    loop {
        match p_iter.peek() {
            Some('%') => {
                p_iter.next();
                if p_iter.peek().is_none() {
                    return true;
                }
                while s_iter.peek().is_some() {
                    let rest_s: String = s_iter.clone().collect();
                    let rest_p: String = p_iter.clone().collect();
                    if like_match(&rest_s, &rest_p) {
                        return true;
                    }
                    s_iter.next();
                }
                return false;
            }
            Some('_') => {
                p_iter.next();
                if s_iter.next().is_none() {
                    return false;
                }
            }
            Some(&c) => {
                p_iter.next();
                if s_iter.next() != Some(c) {
                    return false;
                }
            }
            None => return s_iter.next().is_none(),
        }
    }
}