//! Differential oracle. The same logical query is expressed for both inf and
//! CozoScript over identical integer/string data; the results must match. This
//! pins inf-eval's semantics to a battle-tested datalog engine on the subset the
//! two share (integers, bounded recursion, joins, aggregation, negation).

#![cfg(test)]

use cozo::{DataValue, DbInstance};
use inf_eval::{eval, Ctx};
use inf_parse::parse;
use inf_plan::plan;
use inf_source::LocalSource;
use inf_value::Value;

#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)]
enum Norm {
    Int(i64),
    Str(String),
    List(Vec<Norm>),
    Other(String),
}

fn norm_inf(v: &Value) -> Norm {
    match v {
        Value::Int(i) => Norm::Int(*i),
        Value::Bool(b) => Norm::Int(*b as i64),
        Value::Bytes(b) => Norm::Str(String::from_utf8_lossy(b).into_owned()),
        Value::List(l) => Norm::List(l.iter().map(norm_inf).collect()),
        other => Norm::Other(format!("{other:?}")),
    }
}

fn norm_cozo(v: &DataValue) -> Norm {
    if let Some(i) = v.get_int() {
        Norm::Int(i)
    } else if let Some(s) = v.get_str() {
        Norm::Str(s.to_string())
    } else if let Some(sl) = v.get_slice() {
        Norm::List(sl.iter().map(norm_cozo).collect())
    } else {
        Norm::Other(format!("{v:?}"))
    }
}

fn sorted(mut rows: Vec<Vec<Norm>>) -> Vec<Vec<Norm>> {
    rows.sort();
    rows
}

/// inf side: relations `num{id, val}` and `edge{a, b}`, integer data.
fn inf_source() -> LocalSource {
    let mut s = LocalSource::new();
    let n = |a: i64, b: i64| vec![Value::Int(a), Value::Int(b)];
    s.add("num", &["id", "val"], vec![n(1, 10), n(2, 3), n(3, 20), n(4, 8)]);
    s.add("edge", &["a", "b"], vec![n(1, 2), n(2, 3), n(1, 4)]);
    s
}

/// cozo side: the identical data, loaded via CozoScript.
fn cozo_db() -> DbInstance {
    let db = DbInstance::new("mem", "", "").unwrap();
    db.run_default(":create num {id => val}").unwrap();
    db.run_default(":create edge {a, b}").unwrap();
    db.run_default("?[id, val] <- [[1,10],[2,3],[3,20],[4,8]] :put num {id, val}").unwrap();
    db.run_default("?[a, b] <- [[1,2],[2,3],[1,4]] :put edge {a, b}").unwrap();
    db
}

fn check(inf_q: &str, cozo_q: &str) {
    let ir = plan(&parse(inf_q).expect("inf parse")).expect("inf plan");
    let out = eval(&ir, &inf_source(), &Ctx::default()).expect("inf eval");
    let inf_rows = sorted(out.rows.iter().map(|r| r.iter().map(norm_inf).collect()).collect());

    let nr = cozo_db().run_default(cozo_q).expect("cozo run");
    let cozo_rows = sorted(nr.rows.iter().map(|r| r.iter().map(norm_cozo).collect()).collect());

    assert_eq!(inf_rows, cozo_rows, "\n  inf:  {inf_q}\n  cozo: {cozo_q}");
}

#[test]
fn filter() {
    check(
        "?[id, val] := num{id, val}, gt(val, 5)",
        "?[id, val] := *num{id, val}, val > 5",
    );
}

#[test]
fn join() {
    check(
        "?[a, val] := edge{a, b}, num{id: b, val}",
        "?[a, val] := *edge{a, b}, *num{id: b, val}",
    );
}

#[test]
fn aggregation_count() {
    check(
        "?[a, count(b)] := edge{a, b}",
        "?[a, count(b)] := *edge{a, b}",
    );
}

#[test]
fn transitive_closure() {
    check(
        "reach[x] := edge{a, b: x}, eq(a, 1)\nreach[x] := reach[mid], edge{a: mid, b: x}\n?[x] := reach[x]",
        "reach[x] := *edge{a, b: x}, a == 1\nreach[x] := reach[mid], *edge{a: mid, b: x}\n?[x] := reach[x]",
    );
}

#[test]
fn negation() {
    // ids whose value is not > 5 (i.e. the small ones)
    check(
        "big[id] := num{id, val}, gt(val, 5)\n?[id] := num{id, val}, not big[id]",
        "big[id] := *num{id, val}, val > 5\n?[id] := *num{id, val}, not big[id]",
    );
}

Homonyms

cyb/optica/src/lib.rs
soft3/strata/src/lib.rs
cyb/honeycrisp/src/lib.rs
warriors/trisha/honeycrisp/lib.rs
warriors/trisha/wgpu/lib.rs
soft3/glia/import/lib.rs
soft3/foculus/src/lib.rs
soft3/nox/rs/lib.rs
soft3/cybergraph/src/lib.rs
soft3/tru/rs/lib.rs
soft3/mudra/src/lib.rs
soft3/glia/run/lib.rs
cyb/prysm/rs/lib.rs
warriors/trisha/rs/lib.rs
cyb/src-tauri/src/lib.rs
soft3/mir/src/lib.rs
soft3/lens/src/lib.rs
neural/trident/src/lib.rs
neural/rune/rs/subject/lib.rs
cyb/cyb/cyb-services/src/lib.rs
soft3/strata/nebu/rs/lib.rs
soft3/lens/core/src/lib.rs
neural/rs/mir-format/src/lib.rs
soft3/zheng/rs/src/lib.rs
neural/rune/rs/interp/lib.rs
soft3/radio/iroh-willow/src/lib.rs
neural/rune/rs/parse/lib.rs
neural/eidos/rs/src/lib.rs
neural/rs/darwin-sys/src/lib.rs
soft3/radio/iroh-gossip/src/lib.rs
soft3/radio/iroh-ffi/src/lib.rs
soft3/radio/iroh-car/src/lib.rs
soft3/radio/iroh-relay/src/lib.rs
soft3/bbg/rs/src/lib.rs
soft3/radio/iroh-docs/src/lib.rs
soft3/lens/ikat/src/lib.rs
neural/rune/rs/lex/lib.rs
cyb/honeycrisp/aruminium/src/lib.rs
soft3/hemera/rs/src/lib.rs
neural/rune/rs/ast/lib.rs
soft3/radio/iroh-blobs/src/lib.rs
cyb/honeycrisp/acpu/src/lib.rs
soft3/lens/porphyry/src/lib.rs
cyb/honeycrisp/rane/src/lib.rs
neural/rune/rs/compile/lib.rs
neural/rune/rs/parse-pure/lib.rs
neural/rs/codegen/src/lib.rs
soft3/lens/binius/src/lib.rs
neural/rune/rs/prysm/lib.rs
neural/rs/link/src/lib.rs
neural/rune/rs/mold/lib.rs
soft3/strata/proof/src/lib.rs
soft3/lens/brakedown/src/lib.rs
soft3/strata/kuro/rs/lib.rs
soft3/lens/assayer/src/lib.rs
neural/rs/core/src/lib.rs
neural/rs/macros/src/lib.rs
soft3/radio/cyber-bao/src/lib.rs
soft3/strata/compute/src/lib.rs
soft3/radio/iroh-base/src/lib.rs
soft3/radio/iroh-dns-server/src/lib.rs
neural/rune/rs/lower/lib.rs
soft3/strata/ext/src/lib.rs
soft3/strata/core/src/lib.rs
soft3/hemera/wgsl/src/lib.rs
soft3/radio/iroh/src/lib.rs
cyb/honeycrisp/unimem/src/lib.rs
cyb/evy/crates/evy_engine_tasks/src/lib.rs
cyb/evy/crates/evy_dialect/src/lib.rs
cyb/wysm/crates/wasi/src/lib.rs
cyb/wysm/crates/fuzz/src/lib.rs
soft3/strata/genies/rs/src/lib.rs
cyb/evy/crates/evy_platform_caps/src/lib.rs
soft3/strata/jali/wgsl/src/lib.rs
cyb/evy/forks/bevy_transform/src/lib.rs
soft3/tape/impl/rust/src/lib.rs
cyb/wysm/crates/wasmi/src/lib.rs
cyb/evy/forks/bevy_render/src/lib.rs
cyb/evy/crates/evy_ecs_storage/src/lib.rs
cyb/evy/forks/naga/src/lib.rs
soft3/strata/trop/wgsl/src/lib.rs
cyb/wysm/crates/c_api/artifact/lib.rs
cyb/evy/forks/bevy_ecs/src/lib.rs
cyb/wysm/crates/ir/src/lib.rs
cyb/evy/forks/bevy_animation/src/lib.rs
cyb/evy/forks/bevy_sprite_render/src/lib.rs
cyb/wysm/crates/c_api/src/lib.rs
neural/inf/rs/parse/src/lib.rs
soft3/strata/trop/rs/src/lib.rs
soft3/strata/kuro/wgsl/src/lib.rs
neural/trident/editor/zed/src/lib.rs
cyb/evy/forks/bevy_mesh/src/lib.rs
cyb/evy/crates/evy_radio/src/lib.rs
cyb/evy/forks/bevy_anti_alias/src/lib.rs
soft3/strata/jali/rs/src/lib.rs
cyb/wysm/crates/wast/src/lib.rs
neural/inf/rs/plan/src/lib.rs
neural/rs/tests/macro-integration/src/lib.rs
soft3/radio/iroh-ffi/iroh-js/src/lib.rs
cyb/evy/forks/bevy_image/src/lib.rs
cyb/evy/forks/bevy_post_process/src/lib.rs
neural/inf/rs/source/src/lib.rs
cyb/wysm/crates/core/src/lib.rs
cyb/evy/crates/evy_diagnostic/src/lib.rs
cyb/evy/crates/evy_engine_dispatch/src/lib.rs
cyb/evy/forks/bevy_pbr/src/lib.rs
cyb/evy/forks/bevy_gizmos/src/lib.rs
cyb/evy/forks/bevy_gizmos_render/src/lib.rs
soft3/radio/iroh/bench/src/lib.rs
neural/inf/rs/lex/src/lib.rs
neural/inf/rs/ast/src/lib.rs
soft3/strata/genies/wgsl/src/lib.rs
soft3/strata/nebu/wgsl/src/lib.rs
cyb/wysm/crates/collections/src/lib.rs
neural/inf/rs/lower/src/lib.rs
cyb/evy/forks/bevy_sprite/src/lib.rs
cyb/evy/forks/bevy_diagnostic/src/lib.rs
neural/inf/rs/eval/src/lib.rs
cyb/wysm/crates/c_api/macro/lib.rs
cyb/evy/forks/bevy_tasks/src/lib.rs
cyb/evy/forks/bevy_core_pipeline/src/lib.rs
cyb/evy/crates/evy_prysm_core/src/lib.rs
neural/inf/rs/value/src/lib.rs
cyb/evy/crates/evy_engine_core/src/lib.rs
soft3/radio/tests/integration/src/lib.rs
bootloader/go-cyber/cw/packages/cyber-std-test/src/lib.rs
bootloader/go-cyber/cw/contracts/std-test/src/lib.rs
bootloader/go-cyber/cw/contracts/graph-filter/src/lib.rs
bootloader/go-cyber/cw/packages/cyber-std/src/lib.rs

Graph