citadeldb-sql 0.16.1

SQL parser, planner, and executor for Citadel encrypted database
Documentation
use super::*;
use crate::parser::{
    BinOp, Expr, SelectColumn, SelectStmt, WindowFrame, WindowFrameBound, WindowFrameUnits,
    WindowSpec,
};
use crate::types::Value;

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

fn empty_window_fn(name: &str) -> Expr {
    Expr::WindowFunction {
        name: name.into(),
        args: vec![],
        spec: WindowSpec {
            partition_by: vec![],
            order_by: vec![],
            frame: None,
        },
    }
}

fn empty_select() -> SelectStmt {
    SelectStmt {
        columns: vec![],
        from: "t".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,
    }
}

#[test]
fn has_window_function_direct() {
    assert!(has_window_function(&empty_window_fn("ROW_NUMBER")));
}

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

#[test]
fn has_window_function_column_false() {
    assert!(!has_window_function(&Expr::Column("x".into())));
}

#[test]
fn has_window_function_binary_op_propagates_left() {
    let e = Expr::BinaryOp {
        left: Box::new(empty_window_fn("ROW_NUMBER")),
        op: BinOp::Add,
        right: Box::new(Expr::Literal(i(1))),
    };
    assert!(has_window_function(&e));
}

#[test]
fn has_window_function_binary_op_propagates_right() {
    let e = Expr::BinaryOp {
        left: Box::new(Expr::Literal(i(1))),
        op: BinOp::Add,
        right: Box::new(empty_window_fn("RANK")),
    };
    assert!(has_window_function(&e));
}

#[test]
fn has_window_function_inside_function_args() {
    let e = Expr::Function {
        name: "ABS".into(),
        args: vec![empty_window_fn("LAG")],
        distinct: false,
    };
    assert!(has_window_function(&e));
}

#[test]
fn has_window_function_in_case_branch() {
    let e = Expr::Case {
        operand: None,
        conditions: vec![(
            Expr::Literal(Value::Boolean(true)),
            empty_window_fn("ROW_NUMBER"),
        )],
        else_result: None,
    };
    assert!(has_window_function(&e));
}

#[test]
fn has_any_window_function_on_select() {
    let mut sel = empty_select();
    sel.columns = vec![SelectColumn::Expr {
        expr: empty_window_fn("ROW_NUMBER"),
        alias: None,
    }];
    assert!(has_any_window_function(&sel));
}

#[test]
fn has_any_window_function_no_window_columns_returns_false() {
    let mut sel = empty_select();
    sel.columns = vec![SelectColumn::Expr {
        expr: Expr::Column("x".into()),
        alias: None,
    }];
    assert!(!has_any_window_function(&sel));
}

#[test]
fn has_any_window_function_all_columns_ignored() {
    let mut sel = empty_select();
    sel.columns = vec![SelectColumn::AllColumns];
    assert!(!has_any_window_function(&sel));
}

#[test]
fn resolve_frame_explicit_passes_through() {
    let frame = WindowFrame {
        units: WindowFrameUnits::Rows,
        start: WindowFrameBound::Preceding(Box::new(Expr::Literal(i(1)))),
        end: WindowFrameBound::Following(Box::new(Expr::Literal(i(1)))),
    };
    let spec = WindowSpec {
        partition_by: vec![],
        order_by: vec![],
        frame: Some(frame.clone()),
    };
    let r = resolve_frame(&spec);
    assert!(matches!(r.units, WindowFrameUnits::Rows));
}

#[test]
fn resolve_frame_default_no_order_by_unbounded() {
    let spec = WindowSpec {
        partition_by: vec![],
        order_by: vec![],
        frame: None,
    };
    let r = resolve_frame(&spec);
    assert!(matches!(r.start, WindowFrameBound::UnboundedPreceding));
    assert!(matches!(r.end, WindowFrameBound::UnboundedFollowing));
}

#[test]
fn resolve_frame_default_with_order_by_ends_at_current_row() {
    use crate::parser::OrderByItem;
    let spec = WindowSpec {
        partition_by: vec![],
        order_by: vec![OrderByItem {
            expr: Expr::Column("x".into()),
            descending: false,
            nulls_first: None,
        }],
        frame: None,
    };
    let r = resolve_frame(&spec);
    assert!(matches!(r.start, WindowFrameBound::UnboundedPreceding));
    assert!(matches!(r.end, WindowFrameBound::CurrentRow));
}

#[test]
fn rows_frame_indices_unbounded_both_sides() {
    let frame = WindowFrame {
        units: WindowFrameUnits::Rows,
        start: WindowFrameBound::UnboundedPreceding,
        end: WindowFrameBound::UnboundedFollowing,
    };
    let (s, e) = rows_frame_indices(&frame, 2, 5).unwrap();
    assert_eq!((s, e), (0, 4));
}

#[test]
fn rows_frame_indices_current_row_only() {
    let frame = WindowFrame {
        units: WindowFrameUnits::Rows,
        start: WindowFrameBound::CurrentRow,
        end: WindowFrameBound::CurrentRow,
    };
    let (s, e) = rows_frame_indices(&frame, 2, 5).unwrap();
    assert_eq!((s, e), (2, 2));
}

#[test]
fn rows_frame_indices_preceding_following() {
    let frame = WindowFrame {
        units: WindowFrameUnits::Rows,
        start: WindowFrameBound::Preceding(Box::new(Expr::Literal(i(1)))),
        end: WindowFrameBound::Following(Box::new(Expr::Literal(i(1)))),
    };
    let (s, e) = rows_frame_indices(&frame, 2, 5).unwrap();
    assert_eq!((s, e), (1, 3));
}

#[test]
fn rows_frame_indices_preceding_clamps_to_zero() {
    let frame = WindowFrame {
        units: WindowFrameUnits::Rows,
        start: WindowFrameBound::Preceding(Box::new(Expr::Literal(i(10)))),
        end: WindowFrameBound::CurrentRow,
    };
    let (s, e) = rows_frame_indices(&frame, 2, 5).unwrap();
    assert_eq!((s, e), (0, 2));
}

#[test]
fn rows_frame_indices_following_clamps_to_n_minus_1() {
    let frame = WindowFrame {
        units: WindowFrameUnits::Rows,
        start: WindowFrameBound::CurrentRow,
        end: WindowFrameBound::Following(Box::new(Expr::Literal(i(10)))),
    };
    let (s, e) = rows_frame_indices(&frame, 2, 5).unwrap();
    assert_eq!((s, e), (2, 4));
}

#[test]
fn extract_window_fns_replaces_with_slot_column() {
    let original = empty_window_fn("ROW_NUMBER");
    let mut counter = 0;
    let mut out = Vec::new();
    let rewritten = extract_window_fns(&original, &mut counter, &mut out);
    assert_eq!(out.len(), 1);
    assert_eq!(counter, 1);
    if let Expr::Column(name) = rewritten {
        assert_eq!(name, "__win_0");
    } else {
        panic!("expected column reference for slot");
    }
}

#[test]
fn extract_window_fns_passes_non_window_expressions_through() {
    let e = Expr::Literal(i(5));
    let mut counter = 0;
    let mut out = Vec::new();
    let rewritten = extract_window_fns(&e, &mut counter, &mut out);
    assert!(out.is_empty());
    assert!(matches!(rewritten, Expr::Literal(Value::Integer(5))));
}

#[test]
fn extract_window_fns_inside_binary_op() {
    let e = Expr::BinaryOp {
        left: Box::new(empty_window_fn("RANK")),
        op: BinOp::Add,
        right: Box::new(Expr::Literal(i(1))),
    };
    let mut counter = 0;
    let mut out = Vec::new();
    let _ = extract_window_fns(&e, &mut counter, &mut out);
    assert_eq!(out.len(), 1);
    assert_eq!(counter, 1);
}

#[test]
fn extract_window_fns_inside_coalesce() {
    let e = Expr::Coalesce(vec![empty_window_fn("LAG"), Expr::Literal(i(0))]);
    let mut counter = 0;
    let mut out = Vec::new();
    let _ = extract_window_fns(&e, &mut counter, &mut out);
    assert_eq!(out.len(), 1);
}

#[test]
fn extract_window_fns_multiple_slots_incrementing() {
    let e = Expr::BinaryOp {
        left: Box::new(empty_window_fn("FIRST_VALUE")),
        op: BinOp::Sub,
        right: Box::new(empty_window_fn("LAST_VALUE")),
    };
    let mut counter = 0;
    let mut out = Vec::new();
    let _ = extract_window_fns(&e, &mut counter, &mut out);
    assert_eq!(out.len(), 2);
    assert_eq!(counter, 2);
    assert_eq!(out[0].0, "__win_0");
    assert_eq!(out[1].0, "__win_1");
}

#[test]
fn extract_window_fns_inside_case_else() {
    let e = Expr::Case {
        operand: None,
        conditions: vec![(Expr::Literal(Value::Boolean(true)), Expr::Literal(i(0)))],
        else_result: Some(Box::new(empty_window_fn("ROW_NUMBER"))),
    };
    let mut counter = 0;
    let mut out = Vec::new();
    let _ = extract_window_fns(&e, &mut counter, &mut out);
    assert_eq!(out.len(), 1);
}

#[test]
fn extract_window_fns_inside_function_args() {
    let e = Expr::Function {
        name: "ABS".into(),
        args: vec![empty_window_fn("ROW_NUMBER")],
        distinct: false,
    };
    let mut counter = 0;
    let mut out = Vec::new();
    let _ = extract_window_fns(&e, &mut counter, &mut out);
    assert_eq!(out.len(), 1);
}

#[test]
fn extract_window_fns_inside_unary_op() {
    let e = Expr::UnaryOp {
        op: crate::parser::UnaryOp::Neg,
        expr: Box::new(empty_window_fn("RANK")),
    };
    let mut counter = 0;
    let mut out = Vec::new();
    let _ = extract_window_fns(&e, &mut counter, &mut out);
    assert_eq!(out.len(), 1);
}

#[test]
fn extract_window_fns_inside_cast() {
    let e = Expr::Cast {
        expr: Box::new(empty_window_fn("ROW_NUMBER")),
        data_type: crate::types::DataType::Real,
    };
    let mut counter = 0;
    let mut out = Vec::new();
    let _ = extract_window_fns(&e, &mut counter, &mut out);
    assert_eq!(out.len(), 1);
}