radixdb_executor/operators/
count_integer_antijoin.rs1use rustc_hash::FxHashSet;
10
11use crate::context::ExecutionContext;
12use crate::operator::{ColumnInfo, Operator, RowRef};
13use radixdb_core::{Error, Result, Row, Value};
14use radixdb_storage::traits::Table;
15
16const DEFAULT_INTEGER_ANTIJOIN_PK_BATCH_SIZE: usize = 65_536;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum IntegerAntiJoinLookup {
23 PrimaryKey,
24 Column(usize),
25}
26
27pub struct CountIntegerAntiJoinOperator {
28 outer: Box<dyn Operator>,
29 inner: Box<dyn Table>,
30 outer_key_index: usize,
31 lookup: IntegerAntiJoinLookup,
32 batch_size: usize,
33 schema: Vec<ColumnInfo>,
34 result: Option<Row>,
35 context: Option<ExecutionContext>,
36 opened: bool,
37}
38
39impl CountIntegerAntiJoinOperator {
40 pub fn new(
41 outer: Box<dyn Operator>,
42 inner: Box<dyn Table>,
43 outer_key_index: usize,
44 lookup: IntegerAntiJoinLookup,
45 ) -> Self {
46 Self {
47 outer,
48 inner,
49 outer_key_index,
50 lookup,
51 batch_size: DEFAULT_INTEGER_ANTIJOIN_PK_BATCH_SIZE,
52 schema: vec![ColumnInfo::new("count")],
53 result: None,
54 context: None,
55 opened: false,
56 }
57 }
58
59 pub fn with_context(mut self, context: &ExecutionContext) -> Self {
60 self.context = Some(context.clone());
61 self
62 }
63
64 fn check_cancelled(&self) -> Result<()> {
65 if let Some(context) = &self.context {
66 context.check_cancelled()?;
67 }
68 Ok(())
69 }
70
71 fn count_with_primary_key_probe(&mut self) -> Result<u64> {
72 let mut keys = Vec::with_capacity(self.batch_size);
73 let mut matches = Vec::with_capacity(self.batch_size);
74 let mut unmatched = 0_u64;
75 let mut outer_rows = 0_u64;
76 let mut outer_key_rows = 0_u64;
77
78 while let Some(row) = self.outer.next()? {
79 outer_rows = outer_rows.saturating_add(1);
80 if outer_rows.is_multiple_of(256) {
81 self.check_cancelled()?;
82 }
83 match row.get(self.outer_key_index) {
84 Some(Value::Integer(key)) => {
85 outer_key_rows = outer_key_rows.saturating_add(1);
86 keys.push(*key);
87 if keys.len() == self.batch_size {
88 unmatched =
89 unmatched.saturating_add(self.flush_pk_batch(&mut keys, &mut matches)?);
90 }
91 }
92 Some(value) if value.is_null() => {
93 unmatched = unmatched.saturating_add(1);
96 }
97 Some(_) => {
98 return Err(Error::internal(
99 "count integer anti-join received a non-integer outer key",
100 ));
101 }
102 None => {
103 return Err(Error::internal(
104 "count integer anti-join outer row omitted its key",
105 ));
106 }
107 }
108 }
109 unmatched = unmatched.saturating_add(self.flush_pk_batch(&mut keys, &mut matches)?);
110 radixdb_storage::instrumentation::record_join_outer_rows(outer_rows, outer_key_rows);
111 Ok(unmatched)
112 }
113
114 fn flush_pk_batch(&self, keys: &mut Vec<i64>, matches: &mut Vec<bool>) -> Result<u64> {
115 if keys.is_empty() {
116 return Ok(0);
117 }
118 matches.clear();
119 matches.resize(keys.len(), false);
120 let hits = self.inner.probe_visible_row_ids(keys, matches)?;
121 radixdb_storage::instrumentation::record_join_pk_probe(keys.len() as u64, hits as u64, 0);
122 let unmatched = keys.len().saturating_sub(hits) as u64;
123 keys.clear();
124 Ok(unmatched)
125 }
126
127 fn count_with_integer_key_set(&mut self, inner_key_index: usize) -> Result<u64> {
128 let mut scanner = self.inner.scan_exact_projection(&[inner_key_index], None)?;
129 let mut inner_keys = FxHashSet::default();
130 let mut inner_rows = 0_u64;
131 while scanner.next() {
132 inner_rows = inner_rows.saturating_add(1);
133 if inner_rows.is_multiple_of(256) {
134 self.check_cancelled()?;
135 }
136 match scanner.row().get(0) {
137 Some(Value::Integer(key)) => {
138 inner_keys.insert(*key);
139 }
140 Some(value) if value.is_null() => {}
141 Some(_) => {
142 let _ = scanner.close();
143 return Err(Error::internal(
144 "count integer anti-join received a non-integer inner key",
145 ));
146 }
147 None => {
148 let _ = scanner.close();
149 return Err(Error::internal(
150 "count integer anti-join inner row omitted its key",
151 ));
152 }
153 }
154 }
155 let scan_error = scanner.err().cloned();
156 let close_result = scanner.close();
157 if let Some(error) = scan_error {
158 return Err(error);
159 }
160 close_result?;
161
162 let mut unmatched = 0_u64;
163 let mut outer_rows = 0_u64;
164 let mut outer_key_rows = 0_u64;
165 while let Some(row) = self.outer.next()? {
166 outer_rows = outer_rows.saturating_add(1);
167 if outer_rows.is_multiple_of(256) {
168 self.check_cancelled()?;
169 }
170 match row.get(self.outer_key_index) {
171 Some(Value::Integer(key)) => {
172 outer_key_rows = outer_key_rows.saturating_add(1);
173 if !inner_keys.contains(key) {
174 unmatched = unmatched.saturating_add(1);
175 }
176 }
177 Some(value) if value.is_null() => {
178 unmatched = unmatched.saturating_add(1);
179 }
180 Some(_) => {
181 return Err(Error::internal(
182 "count integer anti-join received a non-integer outer key",
183 ));
184 }
185 None => {
186 return Err(Error::internal(
187 "count integer anti-join outer row omitted its key",
188 ));
189 }
190 }
191 }
192 radixdb_storage::instrumentation::record_join_outer_rows(outer_rows, outer_key_rows);
193 Ok(unmatched)
194 }
195}
196
197impl Operator for CountIntegerAntiJoinOperator {
198 fn open(&mut self) -> Result<()> {
199 if let Err(error) = self.outer.open() {
200 let _ = self.outer.close();
201 return Err(error);
202 }
203 self.check_cancelled()?;
204 let computation = match self.lookup {
205 IntegerAntiJoinLookup::PrimaryKey => self.count_with_primary_key_probe(),
206 IntegerAntiJoinLookup::Column(index) => self.count_with_integer_key_set(index),
207 };
208 let count = match computation {
209 Ok(count) => count,
210 Err(error) => {
211 let _ = self.outer.close();
212 return Err(error);
213 }
214 };
215 self.result = Some(Row::from_values(vec![Value::Integer(
216 i64::try_from(count).unwrap_or(i64::MAX),
217 )]));
218 self.opened = true;
219 Ok(())
220 }
221
222 fn next(&mut self) -> Result<Option<RowRef>> {
223 if !self.opened {
224 return Err(Error::internal(
225 "CountIntegerAntiJoinOperator::next called before open",
226 ));
227 }
228 Ok(self.result.take().map(RowRef::Owned))
229 }
230
231 fn close(&mut self) -> Result<()> {
232 self.outer.close()
233 }
234
235 fn schema(&self) -> &[ColumnInfo] {
236 &self.schema
237 }
238
239 fn estimated_rows(&self) -> Option<usize> {
240 Some(1)
241 }
242
243 fn name(&self) -> &str {
244 "CountIntegerAntiJoin"
245 }
246}