icydb-core 0.264.5

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
use super::*;
use crate::{
    db::{
        RequestExecutionRoot,
        predicate::{
            CoercionId, CoercionSpec, CompareFieldsPredicate, CompareOp, ComparePredicate,
        },
        query::admission::input::MAX_QUERY_INPUT_DEPTH,
        test_support::request_with_limit,
    },
    value::{Value, ValueEnum},
};
use icydb_diagnostic_code::{DiagnosticExecutionLane as Lane, DiagnosticFactTag};

fn project(
    source: &Predicate,
    root: &RequestExecutionRoot,
) -> Result<ExplainPredicate, QueryError> {
    PreparationWork::run(&root.scope(), Lane::Diagnostic, |work| {
        ExplainPredicate::from_predicate(source, work)
    })
}

fn coercion() -> CoercionSpec {
    CoercionSpec::new(CoercionId::NumericWiden)
}

fn cases() -> Vec<(Predicate, ExplainPredicate)> {
    let value = Value::List(vec![
        Value::Enum(ValueEnum::test_payload(
            3,
            4,
            Value::Map(vec![(Value::Nat64(9), Value::Blob(vec![1, 2, 3]))]),
        )),
        Value::Text("quote's λ".into()),
    ]);
    vec![
        (Predicate::True, ExplainPredicate::True),
        (Predicate::False, ExplainPredicate::False),
        (
            Predicate::IsNull { field: "a".into() },
            ExplainPredicate::IsNull { field: "a".into() },
        ),
        (
            Predicate::IsNotNull { field: "b".into() },
            ExplainPredicate::IsNotNull { field: "b".into() },
        ),
        (
            Predicate::IsMissing { field: "c".into() },
            ExplainPredicate::IsMissing { field: "c".into() },
        ),
        (
            Predicate::IsEmpty { field: "d".into() },
            ExplainPredicate::IsEmpty { field: "d".into() },
        ),
        (
            Predicate::IsNotEmpty { field: "e".into() },
            ExplainPredicate::IsNotEmpty { field: "e".into() },
        ),
        (
            Predicate::TextContains {
                field: "f".into(),
                value: Value::Text("needle".into()),
            },
            ExplainPredicate::TextContains {
                field: "f".into(),
                value: Value::Text("needle".into()),
            },
        ),
        (
            Predicate::TextContainsCi {
                field: "g".into(),
                value: Value::Text("Needle".into()),
            },
            ExplainPredicate::TextContainsCi {
                field: "g".into(),
                value: Value::Text("Needle".into()),
            },
        ),
        (
            Predicate::Compare(ComparePredicate {
                field: "h".into(),
                op: CompareOp::In,
                value: value.clone(),
                coercion: coercion(),
            }),
            ExplainPredicate::Compare {
                field: "h".into(),
                op: CompareOp::In,
                value,
                coercion: coercion(),
            },
        ),
        (
            Predicate::CompareFields(CompareFieldsPredicate {
                left_field: "a".into(),
                op: CompareOp::Eq,
                right_field: "z".into(),
                coercion: coercion(),
            }),
            ExplainPredicate::CompareFields {
                left_field: "a".into(),
                op: CompareOp::Eq,
                right_field: "z".into(),
                coercion: coercion(),
            },
        ),
    ]
}

#[test]
fn predicate_projection_preserves_every_family() {
    let (sources, expected): (Vec<_>, Vec<_>) = cases().into_iter().unzip();
    let source = Predicate::And(vec![
        Predicate::Or(sources),
        Predicate::Not(Box::new(Predicate::True)),
        Predicate::And(vec![]),
        Predicate::Or(vec![]),
    ]);
    let expected = ExplainPredicate::And(vec![
        ExplainPredicate::Or(expected),
        ExplainPredicate::Not(Box::new(ExplainPredicate::True)),
        ExplainPredicate::And(vec![]),
        ExplainPredicate::Or(vec![]),
    ]);
    let root = request_with_limit(Resource::TemporaryBytes, 16_000_000);
    let explain = project(&source, &root).unwrap();
    assert_eq!(explain, expected);
    assert!(root.observed(Resource::NestedValueSteps) > 0);
    assert_eq!(root.observed(Resource::RowsVisited), 0);
}

#[test]
fn predicate_projection_charges_field_and_payload_backing_exactly() {
    let source = Predicate::Compare(ComparePredicate {
        field: "abc".into(),
        op: CompareOp::Eq,
        value: Value::Text("payload".into()),
        coercion: CoercionSpec::new(CoercionId::Strict),
    });
    let bytes = 10;
    let root = request_with_limit(Resource::TemporaryBytes, bytes);
    assert!(project(&source, &root).is_ok());
    assert_eq!(root.observed(Resource::TemporaryBytes), bytes);
    let short = request_with_limit(Resource::TemporaryBytes, bytes - 1);
    let error = project(&source, &short).unwrap_err();
    assert!(error.diagnostic_facts().contains(&(
        DiagnosticFactTag::BudgetResource,
        Resource::TemporaryBytes.raw(),
    )));
    assert_eq!(short.observed(Resource::TemporaryBytes), bytes);
}

#[test]
fn predicate_projection_admits_container_backing_before_child_visits() {
    let children = vec![Predicate::True; 8];
    let bytes = 8 * size_of::<ExplainPredicate>() as u64;
    for source in [Predicate::And(children.clone()), Predicate::Or(children)] {
        let short = request_with_limit(
            Resource::TemporaryBytes,
            8 * size_of::<ExplainPredicate>() as u64 - 1,
        );
        assert!(project(&source, &short).is_err());
        assert_eq!(short.observed(Resource::PredicateExpressionSteps), 1);
        let exact = request_with_limit(Resource::TemporaryBytes, bytes);
        assert!(project(&source, &exact).is_ok());
        assert_eq!(exact.observed(Resource::TemporaryBytes), bytes);
        assert_eq!(exact.observed(Resource::PredicateExpressionSteps), 9);
    }
}

#[test]
fn predicate_projection_retains_admitted_depth_and_stops_at_exhausted_visit() {
    let mut source = Predicate::True;
    for _ in 1..MAX_QUERY_INPUT_DEPTH {
        source = Predicate::Not(Box::new(source));
    }
    let exact = request_with_limit(
        Resource::PredicateExpressionSteps,
        MAX_QUERY_INPUT_DEPTH as u64,
    );
    let projected = project(&source, &exact).unwrap();
    let mut node = &projected;
    for _ in 1..MAX_QUERY_INPUT_DEPTH {
        let ExplainPredicate::Not(child) = node else {
            panic!("expected nested predicate")
        };
        node = child;
    }
    assert_eq!(*node, ExplainPredicate::True);
    assert_eq!(
        exact.observed(Resource::PredicateExpressionSteps),
        MAX_QUERY_INPUT_DEPTH as u64
    );
    let short = request_with_limit(
        Resource::PredicateExpressionSteps,
        MAX_QUERY_INPUT_DEPTH as u64 - 1,
    );
    assert!(project(&source, &short).is_err());
    assert_eq!(
        short.observed(Resource::PredicateExpressionSteps),
        MAX_QUERY_INPUT_DEPTH as u64
    );
}