diff --git a/crates/ourios-querier/src/compile.rs b/crates/ourios-querier/src/compile.rs index c46fac22..c92e8dcd 100644 --- a/crates/ourios-querier/src/compile.rs +++ b/crates/ourios-querier/src/compile.rs @@ -63,7 +63,7 @@ use datafusion::dataframe::DataFrame; use datafusion::functions::expr_fn::{coalesce, get_field, regexp_like, starts_with}; use datafusion::functions_aggregate::expr_fn::{avg, max, min, sum}; use datafusion::functions_nested::expr_fn::array_element; -use datafusion::logical_expr::{Expr, cast, is_not_true, is_true, not, try_cast}; +use datafusion::logical_expr::{Expr, cast, is_not_true, not, try_cast}; use datafusion::prelude::{col, lit}; use ourios_core::alias::AliasMap; @@ -1019,9 +1019,12 @@ fn body_equality( let physical_eq = physical_present.then(|| string_kind.clone().and(col(columns::BODY).eq(body_lit()))); - // `IS TRUE` mirrors the `!=` totalisation: an unknowable row never - // matches equality. The arm is further gated to NULL physical bodies: - // when a body is retained the stored bytes are the truth (an + // No `IS TRUE` here, deliberately: under a filter NULL and false are + // equivalent (both drop the row), so equality is already total — and + // wrapping the arm would defeat the row-group pruning the template + // ids exist to enable (the `!=` branch, where NULL genuinely differs + // from false, uses `IS NOT TRUE`). The arm is gated to NULL physical + // bodies: when a body is retained the stored bytes are the truth (an // overflow-spilled param's truncated stored value could otherwise // false-match a crafted literal — `reconstruct` refuses exactly that // case), and for non-lossy retention §3.3 makes the physical arm @@ -1034,7 +1037,7 @@ fn body_equality( } else { lit(true) }) - .and(is_true(arm)) + .and(arm) }); match (physical_eq, template_eq) { (Some(p), Some(t)) => PredExpr::Filter(p.or(t)), diff --git a/crates/ourios-querier/tests/it/rfc0044_body_equality.rs b/crates/ourios-querier/tests/it/rfc0044_body_equality.rs index 1b0c7b6f..3f52844a 100644 --- a/crates/ourios-querier/tests/it/rfc0044_body_equality.rs +++ b/crates/ourios-querier/tests/it/rfc0044_body_equality.rs @@ -296,6 +296,73 @@ async fn rfc0044_overflow_spill_cannot_false_match_a_crafted_literal() { assert_eq!(truth.rows, 1, "the retained body is the truth"); } +/// Scenario RFC0044.7 — pruning still engages: two hour-partitioned +/// files, the matched template only in the first. The second file's row +/// group is skippable by both arms (its `template_id` statistics exclude +/// every candidate and its body column is all-NULL), so the scan touches +/// only the matching file — and the result is still complete. +/// See `docs/rfcs/0044-template-aware-body-equality.md` §5. +#[tokio::test] +async fn rfc0044_7_pruning_engages_across_partitions() { + const HOUR: u64 = 3_600_000_000_000; + let bucket = tempfile::TempDir::new().expect("temp"); + write_audit( + bucket.path(), + &[ + created("t", 1, "claude_code.api_request", TS0), + created("t", 9, "claude_code.tool_result", TS0 + 1), + ], + ); + write_all( + bucket.path(), + &[ + mined("t", 1, TS0 + 10, &[], &["", ""]), + // A different template a partition-hour later: its own file. + mined("t", 9, TS0 + HOUR + 10, &[], &["", ""]), + ], + ); + let result = run(bucket.path(), r#"body == "claude_code.api_request""#).await; + assert_eq!(result.rows, 1, "the matching record returns, completely"); + assert!( + result.stats.row_groups_pruned >= 1, + "the non-candidate partition is pruned: {:?}", + result.stats, + ); +} + +/// Scenario RFC0044.8 (full) — a literal matching no template and no +/// retained body returns empty with every row group pruned: correct +/// empties stay cheap. +/// See `docs/rfcs/0044-template-aware-body-equality.md` §5. +#[tokio::test] +async fn rfc0044_8_correct_empties_prune_everything() { + const HOUR: u64 = 3_600_000_000_000; + let bucket = tempfile::TempDir::new().expect("temp"); + write_audit( + bucket.path(), + &[created("t", 1, "claude_code.api_request", TS0)], + ); + write_all( + bucket.path(), + &[ + mined("t", 1, TS0 + 10, &[], &["", ""]), + mined("t", 1, TS0 + HOUR + 10, &[], &["", ""]), + ], + ); + let result = run(bucket.path(), r#"body == "claude_code.no_such_event""#).await; + assert_eq!(result.rows, 0); + assert_eq!( + result.stats.row_groups_scanned, 0, + "an unmatched literal must not scan anything: {:?}", + result.stats, + ); + assert_eq!( + result.stats.row_groups_pruned, 2, + "both partitions' row groups are pruned, not elided: {:?}", + result.stats, + ); +} + /// The empty-set half of RFC0044.8 — a literal matching no template and /// no retained body returns empty (the full pruning assertion lands with /// the partitioned fixtures in a later slice).