linkrs_query/executor/streaming/operators/
set_operator.rs1use std::collections::HashSet;
2use std::sync::Arc;
3
4use crate::executor::base::{MemoryBudget, MemoryTracker};
5use crate::executor::streaming::chunk::DataChunk;
6use crate::executor::streaming::executor::StreamingExecutor;
7use crate::executor::streaming::operators::source_operator::OperatorConfig;
8use crate::executor::streaming::runtime::ExecutionRuntime;
9use crate::executor::streaming::slot::SlotLayout;
10use crate::executor::streaming::spill::COLLECTOR_RUN_ROWS_MAX;
11use linkrs_core::error::QueryError;
12use linkrs_core::Value;
13
14#[derive(Debug)]
15pub enum SetOperatorKind {
16 Union {
17 seen_rows: HashSet<String>,
18 left_consumed: bool,
19 memory_tracker: MemoryTracker,
20 },
21 UnionAll {
22 left_consumed: bool,
23 },
24 Intersect {
25 left_rows: Vec<Vec<Value>>,
26 right_rows: HashSet<String>,
27 left_buffered: bool,
28 right_buffered: bool,
29 emitted: bool,
30 memory_tracker: MemoryTracker,
31 },
32 Except {
33 exclude_rows: HashSet<String>,
34 right_buffered: bool,
35 memory_tracker: MemoryTracker,
36 },
37 Minus {
38 exclude_rows: HashSet<String>,
39 right_buffered: bool,
40 memory_tracker: MemoryTracker,
41 },
42}
43
44#[derive(Debug)]
50pub struct SetOperator {
51 pub kind: SetOperatorKind,
52 pub runtime: Option<Arc<ExecutionRuntime>>,
53 pub output_layout: Arc<SlotLayout>,
54 pub config: OperatorConfig,
55 spilled_bytes: u64,
56 spilled_rows: u64,
57 spill_count: u64,
58}
59
60impl SetOperator {
61 pub fn from_spec(
62 spec: &super::spec::SetSpec,
63 budget: &MemoryBudget,
64 output_layout: Arc<SlotLayout>,
65 ) -> Self {
66 let kind = match spec {
67 super::spec::SetSpec::Union => SetOperatorKind::Union {
68 seen_rows: std::collections::HashSet::new(),
69 left_consumed: false,
70 memory_tracker: MemoryTracker::new(budget.clone()),
71 },
72 super::spec::SetSpec::UnionAll => SetOperatorKind::UnionAll {
73 left_consumed: false,
74 },
75 super::spec::SetSpec::Intersect => SetOperatorKind::Intersect {
76 left_rows: Vec::new(),
77 right_rows: std::collections::HashSet::new(),
78 left_buffered: false,
79 right_buffered: false,
80 emitted: false,
81 memory_tracker: MemoryTracker::new(budget.clone()),
82 },
83 super::spec::SetSpec::Except => SetOperatorKind::Except {
84 exclude_rows: std::collections::HashSet::new(),
85 right_buffered: false,
86 memory_tracker: MemoryTracker::new(budget.clone()),
87 },
88 super::spec::SetSpec::Minus => SetOperatorKind::Minus {
89 exclude_rows: std::collections::HashSet::new(),
90 right_buffered: false,
91 memory_tracker: MemoryTracker::new(budget.clone()),
92 },
93 };
94 Self::new(kind, output_layout)
95 }
96
97 pub fn new(kind: SetOperatorKind, output_layout: Arc<SlotLayout>) -> Self {
98 Self {
99 kind,
100 runtime: None,
101 output_layout,
102 config: OperatorConfig::default(),
103 spilled_bytes: 0,
104 spilled_rows: 0,
105 spill_count: 0,
106 }
107 }
108
109 pub fn inject_context(
112 &mut self,
113 runtime: Option<&Arc<ExecutionRuntime>>,
114 config: OperatorConfig,
115 ) {
116 if let Some(rt) = runtime {
117 self.runtime = Some(rt.clone());
118 }
119 self.config = config;
120 }
121
122 pub fn memory_tracker(&self) -> &MemoryTracker {
123 match &self.kind {
124 SetOperatorKind::Union { memory_tracker, .. }
125 | SetOperatorKind::Intersect { memory_tracker, .. }
126 | SetOperatorKind::Except { memory_tracker, .. }
127 | SetOperatorKind::Minus { memory_tracker, .. } => memory_tracker,
128 SetOperatorKind::UnionAll { .. } => {
129 panic!("memory_tracker called on variant without memory tracking")
130 }
131 }
132 }
133
134 pub fn open(
135 &mut self,
136 left: &mut StreamingExecutor,
137 right: &mut StreamingExecutor,
138 ) -> Result<(), QueryError> {
139 match &mut self.kind {
140 SetOperatorKind::Union { .. }
141 | SetOperatorKind::UnionAll { .. }
142 | SetOperatorKind::Intersect { .. }
143 | SetOperatorKind::Except { .. }
144 | SetOperatorKind::Minus { .. } => {
145 left.open()?;
146 right.open()?;
147 Ok(())
148 }
149 }
150 }
151
152 pub fn next(
153 &mut self,
154 left: &mut StreamingExecutor,
155 right: &mut StreamingExecutor,
156 ) -> Result<Option<DataChunk>, QueryError> {
157 match &mut self.kind {
158 SetOperatorKind::Union {
159 seen_rows,
160 left_consumed,
161 memory_tracker,
162 ..
163 } => loop {
164 if let Some(rt) = self.runtime.as_ref() {
165 rt.ensure_not_cancelled()?;
166 }
167 if !*left_consumed {
168 if let Some(mut chunk) = left.advance()? {
169 chunk.normalize_for_opaque("Set");
170 let mut result_rows = Vec::new();
171 for row in chunk.rows {
172 let row_str = format!("{:?}", row);
173 if !seen_rows.contains(&row_str) {
174 memory_tracker.try_reserve(row_str.len())?;
175 seen_rows.insert(row_str);
176 result_rows.push(row);
177 }
178 }
179 if !result_rows.is_empty() {
180 return Ok(Some(DataChunk::new_with_layout(
181 result_rows,
182 Arc::clone(&self.output_layout),
183 )));
184 }
185 continue;
186 } else {
187 *left_consumed = true;
188 }
189 }
190
191 if let Some(mut chunk) = right.advance()? {
192 chunk.normalize_for_opaque("Set");
193 let mut result_rows = Vec::new();
194 for row in chunk.rows {
195 let row_str = format!("{:?}", row);
196 if !seen_rows.contains(&row_str) {
197 memory_tracker.try_reserve(row_str.len())?;
198 seen_rows.insert(row_str);
199 result_rows.push(row);
200 }
201 }
202 if !result_rows.is_empty() {
203 return Ok(Some(DataChunk::new_with_layout(
204 result_rows,
205 Arc::clone(&self.output_layout),
206 )));
207 }
208 continue;
209 }
210 return Ok(None);
211 },
212
213 SetOperatorKind::UnionAll { left_consumed, .. } => loop {
214 if !*left_consumed {
215 if let Some(mut chunk) = left.advance()? {
216 chunk.normalize_for_opaque("Set");
217 if !chunk.is_empty() {
218 return Ok(Some(DataChunk::new_with_layout(
219 chunk.rows,
220 Arc::clone(&self.output_layout),
221 )));
222 }
223 } else {
224 *left_consumed = true;
225 }
226 }
227
228 if let Some(mut chunk) = right.advance()? {
229 chunk.normalize_for_opaque("Set");
230 if !chunk.is_empty() {
231 return Ok(Some(DataChunk::new_with_layout(
232 chunk.rows,
233 Arc::clone(&self.output_layout),
234 )));
235 }
236 } else {
237 return Ok(None);
238 }
239 },
240
241 SetOperatorKind::Intersect {
242 left_rows,
243 right_rows,
244 left_buffered,
245 right_buffered,
246 emitted,
247 memory_tracker,
248 ..
249 } => {
250 if *emitted {
251 return Ok(None);
252 }
253 if !*left_buffered {
254 while let Some(mut chunk) = left.advance()? {
255 chunk.normalize_for_opaque("Set");
256 if let Some(rt) = self.runtime.as_ref() {
257 rt.ensure_not_cancelled()?;
258 }
259 for row in &chunk.rows {
260 memory_tracker.try_reserve_row(row)?;
261 }
262 left_rows.extend(chunk.rows);
263 }
264 *left_buffered = true;
265 }
266
267 if !*right_buffered {
268 while let Some(mut chunk) = right.advance()? {
269 chunk.normalize_for_opaque("Set");
270 if let Some(rt) = self.runtime.as_ref() {
271 rt.ensure_not_cancelled()?;
272 }
273 for row in chunk.rows {
274 let row_str = format!("{:?}", row);
275 memory_tracker.try_reserve(row_str.len())?;
276 right_rows.insert(row_str);
277 }
278 }
279 *right_buffered = true;
280 }
281
282 let result_rows: Vec<Vec<Value>> = left_rows
283 .iter()
284 .filter(|row| right_rows.contains(&format!("{:?}", row)))
285 .cloned()
286 .collect();
287
288 if result_rows.is_empty() {
289 Ok(None)
290 } else {
291 *emitted = true;
292 Ok(Some(DataChunk::new_with_layout(
293 result_rows,
294 Arc::clone(&self.output_layout),
295 )))
296 }
297 }
298
299 SetOperatorKind::Except {
300 exclude_rows,
301 right_buffered,
302 memory_tracker,
303 ..
304 } => loop {
305 if let Some(rt) = self.runtime.as_ref() {
306 rt.ensure_not_cancelled()?;
307 }
308 if !*right_buffered {
309 while let Some(mut chunk) = right.advance()? {
310 chunk.normalize_for_opaque("Set");
311 for row in chunk.rows {
312 let row_str = format!("{:?}", row);
313 memory_tracker.try_reserve(row_str.len())?;
314 exclude_rows.insert(row_str);
315 }
316 }
317 *right_buffered = true;
318 }
319
320 if let Some(mut chunk) = left.advance()? {
321 chunk.normalize_for_opaque("Set");
322 let result_rows: Vec<Vec<Value>> = chunk
323 .rows
324 .into_iter()
325 .filter(|row| !exclude_rows.contains(&format!("{:?}", row)))
326 .collect();
327
328 if result_rows.is_empty() {
329 continue;
330 }
331 return Ok(Some(DataChunk::new_with_layout(
332 result_rows,
333 Arc::clone(&self.output_layout),
334 )));
335 }
336 return Ok(None);
337 },
338
339 SetOperatorKind::Minus {
340 exclude_rows,
341 right_buffered,
342 memory_tracker,
343 ..
344 } => {
345 if !*right_buffered {
346 while let Some(mut chunk) = right.advance()? {
347 chunk.normalize_for_opaque("Set");
348 if let Some(rt) = self.runtime.as_ref() {
349 rt.ensure_not_cancelled()?;
350 }
351 for row in chunk.rows {
352 let row_str = format!("{:?}", row);
353 memory_tracker.try_reserve(row_str.len())?;
354 exclude_rows.insert(row_str);
355 }
356 }
357 *right_buffered = true;
358 }
359
360 loop {
361 if let Some(rt) = self.runtime.as_ref() {
362 rt.ensure_not_cancelled()?;
363 }
364 if let Some(mut chunk) = left.advance()? {
365 chunk.normalize_for_opaque("Set");
366 let mut result_rows = Vec::new();
367 for row in chunk.rows {
368 let row_str = format!("{:?}", row);
369 if !exclude_rows.contains(&row_str) {
370 result_rows.push(row);
371 }
372 }
373 if !result_rows.is_empty() {
374 return Ok(Some(DataChunk::new_with_layout(
375 result_rows,
376 Arc::clone(&self.output_layout),
377 )));
378 }
379 continue;
380 }
381 return Ok(None);
382 }
383 }
384 }
385 }
386
387 pub fn stop(&mut self) -> Result<(), QueryError> {
388 Ok(())
389 }
390
391 pub fn reset(
394 &mut self,
395 left: &mut StreamingExecutor,
396 right: &mut StreamingExecutor,
397 ) -> Result<bool, QueryError> {
398 match &mut self.kind {
399 SetOperatorKind::Union {
400 seen_rows,
401 left_consumed,
402 ..
403 } => {
404 seen_rows.clear();
405 *left_consumed = false;
406 }
407 SetOperatorKind::UnionAll { left_consumed } => *left_consumed = false,
408 SetOperatorKind::Intersect {
409 left_rows,
410 right_rows,
411 left_buffered,
412 right_buffered,
413 emitted,
414 ..
415 } => {
416 left_rows.clear();
417 right_rows.clear();
418 *left_buffered = false;
419 *right_buffered = false;
420 *emitted = false;
421 }
422 SetOperatorKind::Except {
423 exclude_rows,
424 right_buffered,
425 ..
426 }
427 | SetOperatorKind::Minus {
428 exclude_rows,
429 right_buffered,
430 ..
431 } => {
432 exclude_rows.clear();
433 *right_buffered = false;
434 }
435 }
436 left.reset()?;
437 right.reset()?;
438 Ok(false)
439 }
440
441 pub fn close(&mut self) -> Result<(), QueryError> {
442 match &mut self.kind {
443 SetOperatorKind::Union {
444 seen_rows,
445 memory_tracker,
446 ..
447 } => {
448 memory_tracker.reset();
449 seen_rows.clear();
450 }
451 SetOperatorKind::UnionAll { .. } => {}
452 SetOperatorKind::Intersect {
453 left_rows,
454 right_rows,
455 memory_tracker,
456 ..
457 } => {
458 memory_tracker.reset();
459 left_rows.clear();
460 right_rows.clear();
461 }
462 SetOperatorKind::Except {
463 exclude_rows,
464 memory_tracker,
465 ..
466 }
467 | SetOperatorKind::Minus {
468 exclude_rows,
469 memory_tracker,
470 ..
471 } => {
472 memory_tracker.reset();
473 exclude_rows.clear();
474 }
475 }
476 Ok(())
477 }
478
479 pub fn spill_with_manager(
480 &mut self,
481 sm: &crate::executor::streaming::spill::SpillManager,
482 ) -> Result<(), linkrs_core::error::QueryError> {
483 match &mut self.kind {
484 SetOperatorKind::Union {
485 seen_rows,
486 memory_tracker,
487 ..
488 } => {
489 if !seen_rows.is_empty() {
490 let rows: Vec<Vec<linkrs_core::Value>> = seen_rows
491 .iter()
492 .map(|s| vec![linkrs_core::Value::string(s.clone())])
493 .collect();
494 let fp = crate::executor::streaming::spill::schema_fingerprint(
500 &self.output_layout.names(),
501 );
502 for chunk in rows.chunks(COLLECTOR_RUN_ROWS_MAX as usize) {
503 let mut writer = sm.create_run_writer(fp)?;
504 writer.write_rows(chunk)?;
505 let run = sm.finalize_run(writer)?;
506 self.spilled_rows += run.row_count;
507 self.spilled_bytes += run.byte_size;
508 self.spill_count += 1;
509 if let Some(rt) = self.runtime.as_ref() {
510 rt.columnar_stats()
511 .record_spill(run.row_count, run.byte_size);
512 }
513 }
514 seen_rows.clear();
515 memory_tracker.reset();
516 }
517 Ok(())
518 }
519 SetOperatorKind::Intersect {
520 left_rows,
521 right_rows,
522 memory_tracker,
523 ..
524 } => {
525 if !left_rows.is_empty() {
526 let fp = crate::executor::streaming::spill::schema_fingerprint(
527 &self.output_layout.names(),
528 );
529 while !left_rows.is_empty() {
530 let take = (COLLECTOR_RUN_ROWS_MAX as usize).min(left_rows.len());
531 let chunk: Vec<Vec<linkrs_core::Value>> = left_rows.drain(..take).collect();
532 let mut writer = sm.create_run_writer(fp)?;
533 writer.write_rows(&chunk)?;
534 let run = sm.finalize_run(writer)?;
535 self.spilled_rows += run.row_count;
536 self.spilled_bytes += run.byte_size;
537 self.spill_count += 1;
538 if let Some(rt) = self.runtime.as_ref() {
539 rt.columnar_stats()
540 .record_spill(run.row_count, run.byte_size);
541 }
542 }
543 memory_tracker.reset();
544 }
545 if !right_rows.is_empty() {
546 right_rows.clear();
547 }
548 Ok(())
549 }
550 SetOperatorKind::Except {
551 exclude_rows,
552 memory_tracker,
553 ..
554 }
555 | SetOperatorKind::Minus {
556 exclude_rows,
557 memory_tracker,
558 ..
559 } => {
560 if !exclude_rows.is_empty() {
561 exclude_rows.clear();
562 memory_tracker.reset();
563 }
564 Ok(())
565 }
566 SetOperatorKind::UnionAll { .. } => Ok(()),
567 }
568 }
569
570 pub fn spilled_bytes(&self) -> u64 {
571 self.spilled_bytes
572 }
573
574 pub fn spilled_rows(&self) -> u64 {
575 self.spilled_rows
576 }
577
578 pub fn spill_count(&self) -> u64 {
579 self.spill_count
580 }
581}