1use 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
74pub 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}