citadeldb-sql 0.16.1

SQL parser, planner, and executor for Citadel encrypted database
Documentation
use super::*;
use crate::parser::{
    CompoundSelect, DeleteStmt, Expr, InsertSource, InsertStmt, QueryBody, SelectColumn,
    SelectStmt, SetOp, UpdateStmt,
};
use crate::types::{ExecutionResult, QueryResult, Value};

fn empty_select(from: &str) -> SelectStmt {
    SelectStmt {
        columns: vec![SelectColumn::AllColumns],
        from: from.into(),
        from_alias: None,
        from_subquery: None,
        from_args: None,
        from_json_table: None,
        joins: vec![],
        distinct: false,
        where_clause: None,
        order_by: vec![],
        limit: None,
        offset: None,
        group_by: vec![],
        having: None,
    }
}

fn i(n: i64) -> Value {
    Value::Integer(n)
}

fn qr(columns: Vec<&str>, rows: Vec<Vec<Value>>) -> QueryResult {
    QueryResult {
        columns: columns.into_iter().map(String::from).collect(),
        rows,
    }
}

fn scalar_subq(from: &str) -> Expr {
    Expr::ScalarSubquery(Box::new(empty_select(from)))
}

#[test]
fn has_subquery_literal_false() {
    assert!(!has_subquery(&Expr::Literal(i(1))));
}

#[test]
fn has_subquery_scalar_subquery() {
    assert!(has_subquery(&scalar_subq("t")));
}

#[test]
fn stmt_has_subquery_in_where() {
    let mut s = empty_select("t");
    s.where_clause = Some(scalar_subq("inner"));
    assert!(stmt_has_subquery(&s));
}

#[test]
fn stmt_has_subquery_in_columns() {
    let mut s = empty_select("t");
    s.columns = vec![SelectColumn::Expr {
        expr: scalar_subq("inner"),
        alias: None,
    }];
    assert!(stmt_has_subquery(&s));
}

#[test]
fn stmt_has_subquery_in_having() {
    let mut s = empty_select("t");
    s.having = Some(scalar_subq("inner"));
    assert!(stmt_has_subquery(&s));
}

#[test]
fn stmt_has_subquery_none_returns_false() {
    let s = empty_select("t");
    assert!(!stmt_has_subquery(&s));
}

#[test]
fn update_has_subquery_in_where() {
    let s = UpdateStmt {
        table: "t".into(),
        assignments: vec![("v".into(), Expr::Literal(i(1)))],
        where_clause: Some(scalar_subq("inner")),
        returning: None,
    };
    assert!(update_has_subquery(&s));
}

#[test]
fn update_has_subquery_in_assignment() {
    let s = UpdateStmt {
        table: "t".into(),
        assignments: vec![("v".into(), scalar_subq("inner"))],
        where_clause: None,
        returning: None,
    };
    assert!(update_has_subquery(&s));
}

#[test]
fn update_has_subquery_none() {
    let s = UpdateStmt {
        table: "t".into(),
        assignments: vec![("v".into(), Expr::Literal(i(1)))],
        where_clause: Some(Expr::Literal(i(1))),
        returning: None,
    };
    assert!(!update_has_subquery(&s));
}

#[test]
fn delete_has_subquery_in_where() {
    let s = DeleteStmt {
        table: "t".into(),
        where_clause: Some(scalar_subq("inner")),
        returning: None,
    };
    assert!(delete_has_subquery(&s));
}

#[test]
fn delete_has_subquery_none() {
    let s = DeleteStmt {
        table: "t".into(),
        where_clause: None,
        returning: None,
    };
    assert!(!delete_has_subquery(&s));
}

#[test]
fn insert_has_subquery_in_values() {
    let s = InsertStmt {
        table: "t".into(),
        columns: vec!["id".into()],
        source: InsertSource::Values(vec![vec![scalar_subq("inner")]]),
        on_conflict: None,
        returning: None,
    };
    assert!(insert_has_subquery(&s));
}

#[test]
fn insert_has_subquery_select_source_returns_false() {
    let sq = crate::parser::SelectQuery {
        ctes: vec![],
        body: QueryBody::Select(Box::new(empty_select("src"))),
        recursive: false,
    };
    let s = InsertStmt {
        table: "t".into(),
        columns: vec!["id".into()],
        source: InsertSource::Select(Box::new(sq)),
        on_conflict: None,
        returning: None,
    };
    assert!(!insert_has_subquery(&s));
}

#[test]
fn apply_set_operation_union_all_concatenates() {
    let left = qr(vec!["x"], vec![vec![i(1)], vec![i(2)]]);
    let right = qr(vec!["x"], vec![vec![i(2)], vec![i(3)]]);
    let comp = CompoundSelect {
        op: SetOp::Union,
        all: true,
        left: Box::new(QueryBody::Select(Box::new(empty_select("a")))),
        right: Box::new(QueryBody::Select(Box::new(empty_select("b")))),
        order_by: vec![],
        limit: None,
        offset: None,
    };
    let result = apply_set_operation(&comp, left, right).unwrap();
    if let ExecutionResult::Query(q) = result {
        assert_eq!(q.rows.len(), 4);
    } else {
        panic!("expected Query result");
    }
}

#[test]
fn apply_set_operation_union_dedupes() {
    let left = qr(vec!["x"], vec![vec![i(1)], vec![i(2)]]);
    let right = qr(vec!["x"], vec![vec![i(2)], vec![i(3)]]);
    let comp = CompoundSelect {
        op: SetOp::Union,
        all: false,
        left: Box::new(QueryBody::Select(Box::new(empty_select("a")))),
        right: Box::new(QueryBody::Select(Box::new(empty_select("b")))),
        order_by: vec![],
        limit: None,
        offset: None,
    };
    let result = apply_set_operation(&comp, left, right).unwrap();
    if let ExecutionResult::Query(q) = result {
        assert_eq!(q.rows.len(), 3);
    } else {
        panic!("expected Query result");
    }
}

#[test]
fn apply_set_operation_intersect_keeps_common() {
    let left = qr(vec!["x"], vec![vec![i(1)], vec![i(2)], vec![i(3)]]);
    let right = qr(vec!["x"], vec![vec![i(2)], vec![i(3)], vec![i(4)]]);
    let comp = CompoundSelect {
        op: SetOp::Intersect,
        all: false,
        left: Box::new(QueryBody::Select(Box::new(empty_select("a")))),
        right: Box::new(QueryBody::Select(Box::new(empty_select("b")))),
        order_by: vec![],
        limit: None,
        offset: None,
    };
    let result = apply_set_operation(&comp, left, right).unwrap();
    if let ExecutionResult::Query(q) = result {
        assert_eq!(q.rows.len(), 2);
    } else {
        panic!("expected Query result");
    }
}

#[test]
fn apply_set_operation_except_removes_right() {
    let left = qr(vec!["x"], vec![vec![i(1)], vec![i(2)], vec![i(3)]]);
    let right = qr(vec!["x"], vec![vec![i(2)]]);
    let comp = CompoundSelect {
        op: SetOp::Except,
        all: false,
        left: Box::new(QueryBody::Select(Box::new(empty_select("a")))),
        right: Box::new(QueryBody::Select(Box::new(empty_select("b")))),
        order_by: vec![],
        limit: None,
        offset: None,
    };
    let result = apply_set_operation(&comp, left, right).unwrap();
    if let ExecutionResult::Query(q) = result {
        assert_eq!(q.rows.len(), 2);
        assert!(q.rows.contains(&vec![i(1)]));
        assert!(q.rows.contains(&vec![i(3)]));
    } else {
        panic!("expected Query result");
    }
}

#[test]
fn apply_set_operation_column_count_mismatch_errors() {
    let left = qr(vec!["a", "b"], vec![vec![i(1), i(2)]]);
    let right = qr(vec!["c"], vec![vec![i(3)]]);
    let comp = CompoundSelect {
        op: SetOp::Union,
        all: false,
        left: Box::new(QueryBody::Select(Box::new(empty_select("a")))),
        right: Box::new(QueryBody::Select(Box::new(empty_select("b")))),
        order_by: vec![],
        limit: None,
        offset: None,
    };
    assert!(apply_set_operation(&comp, left, right).is_err());
}

#[test]
fn materialize_expr_in_subquery_converts_to_in_set() {
    let inner_qr = qr(vec!["x"], vec![vec![i(1)], vec![i(2)]]);
    let e = Expr::InSubquery {
        expr: Box::new(Expr::Column("v".into())),
        subquery: Box::new(empty_select("inner")),
        negated: false,
    };
    let mut exec_sub = |_: &SelectStmt| Ok(inner_qr.clone());
    let result = materialize_expr(&e, &mut exec_sub).unwrap();
    assert!(matches!(result, Expr::InSet { .. }));
}

#[test]
fn materialize_expr_scalar_subquery_becomes_literal() {
    let inner_qr = qr(vec!["x"], vec![vec![i(42)]]);
    let e = scalar_subq("inner");
    let mut exec_sub = |_: &SelectStmt| Ok(inner_qr.clone());
    let result = materialize_expr(&e, &mut exec_sub).unwrap();
    assert!(matches!(result, Expr::Literal(Value::Integer(42))));
}

#[test]
fn materialize_expr_scalar_subquery_empty_becomes_null() {
    let inner_qr = qr(vec!["x"], vec![]);
    let e = scalar_subq("inner");
    let mut exec_sub = |_: &SelectStmt| Ok(inner_qr.clone());
    let result = materialize_expr(&e, &mut exec_sub).unwrap();
    assert!(matches!(result, Expr::Literal(Value::Null)));
}

#[test]
fn materialize_expr_exists_true() {
    let inner_qr = qr(vec!["x"], vec![vec![i(1)]]);
    let e = Expr::Exists {
        subquery: Box::new(empty_select("inner")),
        negated: false,
    };
    let mut exec_sub = |_: &SelectStmt| Ok(inner_qr.clone());
    let result = materialize_expr(&e, &mut exec_sub).unwrap();
    assert!(matches!(result, Expr::Literal(Value::Boolean(true))));
}

#[test]
fn materialize_expr_not_exists_false_when_rows_present() {
    let inner_qr = qr(vec!["x"], vec![vec![i(1)]]);
    let e = Expr::Exists {
        subquery: Box::new(empty_select("inner")),
        negated: true,
    };
    let mut exec_sub = |_: &SelectStmt| Ok(inner_qr.clone());
    let result = materialize_expr(&e, &mut exec_sub).unwrap();
    assert!(matches!(result, Expr::Literal(Value::Boolean(false))));
}

#[test]
fn materialize_expr_exists_false_for_empty_subquery() {
    let inner_qr = qr(vec!["x"], vec![]);
    let e = Expr::Exists {
        subquery: Box::new(empty_select("inner")),
        negated: false,
    };
    let mut exec_sub = |_: &SelectStmt| Ok(inner_qr.clone());
    let result = materialize_expr(&e, &mut exec_sub).unwrap();
    assert!(matches!(result, Expr::Literal(Value::Boolean(false))));
}

#[test]
fn materialize_expr_scalar_multiple_rows_errors() {
    let inner_qr = qr(vec!["x"], vec![vec![i(1)], vec![i(2)]]);
    let e = scalar_subq("inner");
    let mut exec_sub = |_: &SelectStmt| Ok(inner_qr.clone());
    assert!(materialize_expr(&e, &mut exec_sub).is_err());
}

#[test]
fn materialize_expr_pass_through_literal() {
    let mut exec_sub = |_: &SelectStmt| {
        Ok(QueryResult {
            columns: vec![],
            rows: vec![],
        })
    };
    let e = Expr::Literal(i(5));
    let result = materialize_expr(&e, &mut exec_sub).unwrap();
    assert!(matches!(result, Expr::Literal(Value::Integer(5))));
}

#[test]
fn materialize_query_body_pass_through_dml() {
    let body = QueryBody::Insert(Box::new(InsertStmt {
        table: "t".into(),
        columns: vec![],
        source: InsertSource::Values(vec![]),
        on_conflict: None,
        returning: None,
    }));
    let mut exec_sub = |_: &SelectStmt| {
        Ok(QueryResult {
            columns: vec![],
            rows: vec![],
        })
    };
    let result = materialize_query_body(&body, &mut exec_sub).unwrap();
    assert!(matches!(result, QueryBody::Insert(_)));
}