Skip to main content

uqa_operators/
boolean.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Boolean operators: union, intersect, complement.
8
9use std::sync::Arc;
10
11use uqa_core::{IndexStats, Payload, PostingEntry, PostingList};
12
13use crate::base::{missing_backend, ExecutionContext, Operator, OperatorResult};
14
15pub struct UnionOperator {
16    pub operands: Vec<Arc<dyn Operator>>,
17}
18
19impl UnionOperator {
20    pub fn new(operands: Vec<Arc<dyn Operator>>) -> Self {
21        Self { operands }
22    }
23}
24
25impl Operator for UnionOperator {
26    fn execute(&self, ctx: &ExecutionContext) -> OperatorResult {
27        let mut result = PostingList::new();
28        for operand in &self.operands {
29            result = result.merge_union(&operand.execute(ctx)?);
30        }
31        Ok(result)
32    }
33
34    fn cost_estimate(&self, stats: &IndexStats) -> f64 {
35        self.operands.iter().map(|op| op.cost_estimate(stats)).sum()
36    }
37}
38
39pub struct IntersectOperator {
40    pub operands: Vec<Arc<dyn Operator>>,
41}
42
43impl IntersectOperator {
44    pub fn new(operands: Vec<Arc<dyn Operator>>) -> Self {
45        Self { operands }
46    }
47}
48
49impl Operator for IntersectOperator {
50    fn execute(&self, ctx: &ExecutionContext) -> OperatorResult {
51        let mut iter = self.operands.iter();
52        let Some(first) = iter.next() else {
53            return Ok(PostingList::new());
54        };
55        let mut acc = first.execute(ctx)?;
56        for op in iter {
57            if acc.is_empty() {
58                return Ok(acc);
59            }
60            acc = acc.merge_intersection_owned(&op.execute(ctx)?);
61        }
62        Ok(acc)
63    }
64
65    fn cost_estimate(&self, stats: &IndexStats) -> f64 {
66        self.operands
67            .iter()
68            .map(|op| op.cost_estimate(stats))
69            .fold(f64::INFINITY, f64::min)
70            .max(0.0)
71    }
72}
73
74/// Complement with respect to the universal set drawn from
75/// `ctx.document_store.doc_ids()`.
76pub struct ComplementOperator {
77    pub operand: Arc<dyn Operator>,
78}
79
80impl ComplementOperator {
81    pub fn new(operand: Arc<dyn Operator>) -> Self {
82        Self { operand }
83    }
84}
85
86impl Operator for ComplementOperator {
87    fn execute(&self, ctx: &ExecutionContext) -> OperatorResult {
88        let result = self.operand.execute(ctx)?;
89        let Some(doc_store) = ctx.document_store.as_ref() else {
90            return Err(missing_backend("document-store", "boolean complement"));
91        };
92        let universal_entries: Vec<PostingEntry> = doc_store
93            .doc_ids()?
94            .into_iter()
95            .map(|id| PostingEntry::new(id, Payload::default()))
96            .collect();
97        let universal = PostingList::from_sorted_unchecked(universal_entries);
98        Ok(universal.exclude(&result))
99    }
100
101    fn cost_estimate(&self, stats: &IndexStats) -> f64 {
102        stats.total_docs as f64
103    }
104}