citadeldb-sql 0.16.1

SQL parser, planner, and executor for Citadel encrypted database
Documentation
use super::*;
use crate::parser::{
    CompoundSelect, Expr, QueryBody, SelectColumn, SelectQuery, SelectStmt, SetOp,
};
use crate::types::{DataType, Value};

fn empty_select() -> SelectStmt {
    SelectStmt {
        columns: vec![SelectColumn::AllColumns],
        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 encode_pk_key_integer_round_trip() {
    let mut buf = Vec::new();
    encode_pk_key(&Value::Integer(42), &mut buf);
    let decoded = decode_pk_value(&buf).unwrap();
    assert_eq!(decoded, Value::Integer(42));
}

#[test]
fn encode_pk_key_text_round_trip() {
    let mut buf = Vec::new();
    encode_pk_key(&Value::Text("hello".into()), &mut buf);
    let decoded = decode_pk_value(&buf).unwrap();
    assert_eq!(decoded, Value::Text("hello".into()));
}

#[test]
fn encode_pk_key_clears_buffer_first() {
    let mut buf = vec![1, 2, 3];
    encode_pk_key(&Value::Integer(1), &mut buf);
    let decoded = decode_pk_value(&buf).unwrap();
    assert_eq!(decoded, Value::Integer(1));
}

#[test]
fn derive_columns_infers_type_from_rows() {
    let names = vec!["id".into(), "label".into()];
    let rows = vec![vec![Value::Integer(1), Value::Text("a".into())]];
    let cols = derive_columns(&names, &rows);
    assert_eq!(cols.len(), 2);
    assert_eq!(cols[0].data_type, DataType::Integer);
    assert_eq!(cols[1].data_type, DataType::Text);
}

#[test]
fn derive_columns_first_column_not_nullable() {
    let names = vec!["pk".into(), "val".into()];
    let rows = vec![vec![Value::Integer(1), Value::Integer(2)]];
    let cols = derive_columns(&names, &rows);
    assert!(!cols[0].nullable);
    assert!(cols[1].nullable);
}

#[test]
fn derive_columns_lowercases_names() {
    let names = vec!["MixedCase".into()];
    let rows = vec![vec![Value::Integer(1)]];
    let cols = derive_columns(&names, &rows);
    assert_eq!(cols[0].name, "mixedcase");
}

#[test]
fn derive_columns_falls_back_to_text_when_all_null() {
    let names = vec!["x".into()];
    let rows = vec![vec![Value::Null], vec![Value::Null]];
    let cols = derive_columns(&names, &rows);
    assert_eq!(cols[0].data_type, DataType::Text);
}

#[test]
fn derive_columns_skips_null_when_inferring_type() {
    let names = vec!["x".into()];
    let rows = vec![vec![Value::Null], vec![Value::Integer(7)]];
    let cols = derive_columns(&names, &rows);
    assert_eq!(cols[0].data_type, DataType::Integer);
}

#[test]
fn reject_non_deterministic_now_rejected() {
    let mut sel = empty_select();
    sel.columns = vec![SelectColumn::Expr {
        expr: Expr::Function {
            name: "NOW".into(),
            args: vec![],
            distinct: false,
        },
        alias: None,
    }];
    let sq = SelectQuery {
        ctes: vec![],
        body: QueryBody::Select(Box::new(sel)),
        recursive: false,
    };
    assert!(reject_non_deterministic(&sq).is_err());
}

#[test]
fn reject_non_deterministic_random_rejected() {
    let mut sel = empty_select();
    sel.where_clause = Some(Expr::Function {
        name: "random".into(),
        args: vec![],
        distinct: false,
    });
    let sq = SelectQuery {
        ctes: vec![],
        body: QueryBody::Select(Box::new(sel)),
        recursive: false,
    };
    assert!(reject_non_deterministic(&sq).is_err());
}

#[test]
fn reject_non_deterministic_deterministic_ok() {
    let mut sel = empty_select();
    sel.columns = vec![SelectColumn::Expr {
        expr: Expr::Function {
            name: "UPPER".into(),
            args: vec![Expr::Column("name".into())],
            distinct: false,
        },
        alias: None,
    }];
    let sq = SelectQuery {
        ctes: vec![],
        body: QueryBody::Select(Box::new(sel)),
        recursive: false,
    };
    assert!(reject_non_deterministic(&sq).is_ok());
}

#[test]
fn reject_non_deterministic_dml_body_rejected() {
    let sq = SelectQuery {
        ctes: vec![],
        body: QueryBody::Insert(Box::new(crate::parser::InsertStmt {
            table: "t".into(),
            columns: vec![],
            source: crate::parser::InsertSource::Values(vec![]),
            on_conflict: None,
            returning: None,
        })),
        recursive: false,
    };
    assert!(reject_non_deterministic(&sq).is_err());
}

#[test]
fn reject_non_deterministic_compound_walks_both_sides() {
    let mut bad = empty_select();
    bad.where_clause = Some(Expr::Function {
        name: "NOW".into(),
        args: vec![],
        distinct: false,
    });
    let comp = CompoundSelect {
        op: SetOp::Union,
        all: false,
        left: Box::new(QueryBody::Select(Box::new(empty_select()))),
        right: Box::new(QueryBody::Select(Box::new(bad))),
        order_by: vec![],
        limit: None,
        offset: None,
    };
    let sq = SelectQuery {
        ctes: vec![],
        body: QueryBody::Compound(Box::new(comp)),
        recursive: false,
    };
    assert!(reject_non_deterministic(&sq).is_err());
}

#[test]
fn references_matview_in_from_clause() {
    assert!(references_matview("SELECT * FROM mv WHERE x = 1", "mv"));
}

#[test]
fn references_matview_in_join_clause() {
    assert!(references_matview("SELECT * FROM t JOIN mv ON x = y", "mv"));
}

#[test]
fn references_matview_case_insensitive() {
    assert!(references_matview("SELECT * FROM MV", "mv"));
}

#[test]
fn references_matview_not_referenced_returns_false() {
    assert!(!references_matview("SELECT * FROM other", "mv"));
}