1use std::collections::HashSet;
2use std::sync::Arc;
3
4use crate::executor::base::MemoryTracker;
5use crate::executor::streaming::chunk::{use_columnar_path, DataChunk};
6use crate::executor::streaming::executor::FullOuterJoinPhase;
7use crate::executor::streaming::executor::StreamingExecutor;
8use crate::executor::streaming::operators::source_operator::OperatorConfig;
9use crate::executor::streaming::runtime::ExecutionRuntime;
10use crate::executor::streaming::slot::SlotLayout;
11use linkrs_core::error::QueryError;
12use linkrs_core::types::expr::Expression;
13use linkrs_core::Value;
14
15mod cross_join;
16pub mod grace_join;
17mod hash_join;
18mod merge_join;
19mod nested_loop_join;
20mod semi_join;
21
22pub use hash_join::{HashJoinBuildSide, JoinKeyValue};
23
24fn build_combined_names(
25 left_col_names: &[String],
26 right_col_names: &[String],
27 fallback_right_width: usize,
28) -> Vec<String> {
29 let mut names = left_col_names.to_vec();
30 if !right_col_names.is_empty() {
31 names.extend_from_slice(right_col_names);
32 } else {
33 for i in 0..fallback_right_width {
34 names.push(format!("right_{}", i));
35 }
36 }
37 names
38}
39
40pub(crate) fn finalize_join_output(
51 mut chunk: DataChunk,
52 runtime: &Option<Arc<ExecutionRuntime>>,
53) -> DataChunk {
54 if use_columnar_path(runtime) {
55 chunk.columnar_build_deferred = true;
56 }
57 chunk
58}
59
60#[derive(Debug)]
61pub enum JoinOperatorKind {
62 HashJoin {
63 join_condition: Option<Expression>,
64 hash_keys: Vec<Expression>,
65 probe_keys: Vec<Expression>,
66 build_side: HashJoinBuildSide,
67 build_done: bool,
68 memory_tracker: MemoryTracker,
69 right_col_names: Vec<String>,
70 build_side_select: super::spec::BuildSide,
71 grace: grace_join::GraceJoinState,
72 },
73 HashLeftJoin {
74 join_condition: Option<Expression>,
75 hash_keys: Vec<Expression>,
76 probe_keys: Vec<Expression>,
77 build_side: HashJoinBuildSide,
78 build_done: bool,
79 memory_tracker: MemoryTracker,
80 right_col_names: Vec<String>,
81 build_side_select: super::spec::BuildSide,
82 grace: grace_join::GraceJoinState,
83 },
84 NestedLoopJoin {
85 join_condition: Option<Expression>,
86 build_side_tuples: Vec<Vec<Value>>,
87 build_done: bool,
88 memory_tracker: MemoryTracker,
89 right_col_names: Vec<String>,
90 },
91 InnerJoin {
92 join_condition: Option<Expression>,
93 build_side_tuples: Vec<Vec<Value>>,
94 build_done: bool,
95 memory_tracker: MemoryTracker,
96 right_col_names: Vec<String>,
97 },
98 LeftJoin {
99 join_condition: Option<Expression>,
100 build_side_tuples: Vec<Vec<Value>>,
101 build_done: bool,
102 memory_tracker: MemoryTracker,
103 right_col_names: Vec<String>,
104 },
105 RightJoin {
106 join_condition: Option<Expression>,
107 build_side_tuples: Vec<Vec<Value>>,
108 right_consumed: bool,
109 memory_tracker: MemoryTracker,
110 right_col_names: Vec<String>,
111 },
112 FullOuterJoin {
113 join_condition: Option<Expression>,
114 left_rows: Vec<Vec<Value>>,
115 right_rows: Vec<Vec<Value>>,
116 matched_right_indices: HashSet<usize>,
117 result_iter: Option<std::vec::IntoIter<Vec<Value>>>,
118 phase: FullOuterJoinPhase,
119 memory_tracker: MemoryTracker,
120 right_col_names: Vec<String>,
121 },
122 CrossJoin {
123 all_left_rows: Vec<Vec<Value>>,
124 all_right_rows: Vec<Vec<Value>>,
125 left_consumed: bool,
126 right_consumed: bool,
127 memory_tracker: MemoryTracker,
128 right_col_names: Vec<String>,
129 output_done: bool,
130 },
131 SemiJoin {
132 join_condition: Option<Expression>,
133 anti: bool,
135 right_rows: Vec<Vec<Value>>,
136 right_consumed: bool,
137 memory_tracker: MemoryTracker,
138 right_col_names: Vec<String>,
139 },
140}
141
142#[derive(Debug)]
148pub struct JoinOperator {
149 pub kind: JoinOperatorKind,
150 pub runtime: Option<Arc<ExecutionRuntime>>,
151 pub output_layout: Arc<SlotLayout>,
152 pub config: OperatorConfig,
153}
154
155impl JoinOperator {
156 pub fn new(kind: JoinOperatorKind, output_layout: Arc<SlotLayout>) -> Self {
157 Self {
158 kind,
159 runtime: None,
160 output_layout,
161 config: OperatorConfig::default(),
162 }
163 }
164
165 pub fn inject_context(
168 &mut self,
169 runtime: Option<&Arc<ExecutionRuntime>>,
170 config: OperatorConfig,
171 ) {
172 if let Some(rt) = runtime {
173 self.runtime = Some(rt.clone());
174 }
175 self.config = config;
176 }
177
178 pub fn from_spec(
179 spec: &super::spec::JoinSpec,
180 memory_budget: &crate::executor::base::MemoryBudget,
181 output_layout: Arc<SlotLayout>,
182 ) -> Self {
183 let kind = match spec {
184 super::spec::JoinSpec::HashJoin {
185 join_condition,
186 hash_keys,
187 probe_keys,
188 build_side,
189 } => JoinOperatorKind::HashJoin {
190 join_condition: join_condition.clone(),
191 hash_keys: hash_keys.clone(),
192 probe_keys: probe_keys.clone(),
193 build_side: HashJoinBuildSide::new(),
194 build_done: false,
195 memory_tracker: crate::executor::base::MemoryTracker::new(memory_budget.clone()),
196 right_col_names: Vec::new(),
197 build_side_select: *build_side,
198 grace: grace_join::GraceJoinState::default(),
199 },
200 super::spec::JoinSpec::HashLeftJoin {
201 join_condition,
202 hash_keys,
203 probe_keys,
204 build_side,
205 } => JoinOperatorKind::HashLeftJoin {
206 join_condition: join_condition.clone(),
207 hash_keys: hash_keys.clone(),
208 probe_keys: probe_keys.clone(),
209 build_side: HashJoinBuildSide::new(),
210 build_done: false,
211 memory_tracker: crate::executor::base::MemoryTracker::new(memory_budget.clone()),
212 right_col_names: Vec::new(),
213 build_side_select: *build_side,
214 grace: grace_join::GraceJoinState::default(),
215 },
216 super::spec::JoinSpec::NestedLoopJoin { join_condition } => {
217 JoinOperatorKind::NestedLoopJoin {
218 join_condition: join_condition.clone(),
219 build_side_tuples: Vec::new(),
220 build_done: false,
221 memory_tracker: crate::executor::base::MemoryTracker::new(
222 memory_budget.clone(),
223 ),
224 right_col_names: Vec::new(),
225 }
226 }
227 super::spec::JoinSpec::InnerJoin { join_condition } => JoinOperatorKind::InnerJoin {
228 join_condition: join_condition.clone(),
229 build_side_tuples: Vec::new(),
230 build_done: false,
231 memory_tracker: crate::executor::base::MemoryTracker::new(memory_budget.clone()),
232 right_col_names: Vec::new(),
233 },
234 super::spec::JoinSpec::LeftJoin { join_condition } => JoinOperatorKind::LeftJoin {
235 join_condition: join_condition.clone(),
236 build_side_tuples: Vec::new(),
237 build_done: false,
238 memory_tracker: crate::executor::base::MemoryTracker::new(memory_budget.clone()),
239 right_col_names: Vec::new(),
240 },
241 super::spec::JoinSpec::RightJoin { join_condition } => JoinOperatorKind::RightJoin {
242 join_condition: join_condition.clone(),
243 build_side_tuples: Vec::new(),
244 right_consumed: false,
245 memory_tracker: crate::executor::base::MemoryTracker::new(memory_budget.clone()),
246 right_col_names: Vec::new(),
247 },
248 super::spec::JoinSpec::FullOuterJoin { join_condition } => {
249 JoinOperatorKind::FullOuterJoin {
250 join_condition: join_condition.clone(),
251 left_rows: Vec::new(),
252 right_rows: Vec::new(),
253 matched_right_indices: std::collections::HashSet::new(),
254 result_iter: None,
255 phase: FullOuterJoinPhase::BuildingRight,
256 memory_tracker: crate::executor::base::MemoryTracker::new(
257 memory_budget.clone(),
258 ),
259 right_col_names: Vec::new(),
260 }
261 }
262 super::spec::JoinSpec::CrossJoin => JoinOperatorKind::CrossJoin {
263 all_left_rows: Vec::new(),
264 all_right_rows: Vec::new(),
265 left_consumed: false,
266 right_consumed: false,
267 memory_tracker: crate::executor::base::MemoryTracker::new(memory_budget.clone()),
268 right_col_names: Vec::new(),
269 output_done: false,
270 },
271 super::spec::JoinSpec::SemiJoin {
272 join_condition,
273 anti,
274 } => JoinOperatorKind::SemiJoin {
275 join_condition: join_condition.clone(),
276 anti: *anti,
277 right_rows: Vec::new(),
278 right_consumed: false,
279 memory_tracker: crate::executor::base::MemoryTracker::new(memory_budget.clone()),
280 right_col_names: Vec::new(),
281 },
282 };
283 Self::new(kind, output_layout)
284 }
285
286 pub fn memory_tracker(&self) -> &MemoryTracker {
287 match &self.kind {
288 JoinOperatorKind::HashJoin { memory_tracker, .. }
289 | JoinOperatorKind::HashLeftJoin { memory_tracker, .. }
290 | JoinOperatorKind::NestedLoopJoin { memory_tracker, .. }
291 | JoinOperatorKind::InnerJoin { memory_tracker, .. }
292 | JoinOperatorKind::LeftJoin { memory_tracker, .. }
293 | JoinOperatorKind::RightJoin { memory_tracker, .. }
294 | JoinOperatorKind::FullOuterJoin { memory_tracker, .. }
295 | JoinOperatorKind::CrossJoin { memory_tracker, .. }
296 | JoinOperatorKind::SemiJoin { memory_tracker, .. } => memory_tracker,
297 }
298 }
299
300 pub fn open(
301 &mut self,
302 left: &mut StreamingExecutor,
303 right: &mut StreamingExecutor,
304 ) -> Result<(), QueryError> {
305 left.open()?;
306 right.open()?;
307 Ok(())
308 }
309
310 pub fn next(
311 &mut self,
312 left: &mut StreamingExecutor,
313 right: &mut StreamingExecutor,
314 ) -> Result<Option<DataChunk>, QueryError> {
315 let runtime = &self.runtime;
316 let output_layout = &self.output_layout;
317 match &mut self.kind {
318 JoinOperatorKind::HashJoin {
319 join_condition,
320 hash_keys,
321 probe_keys,
322 build_side,
323 build_done,
324 memory_tracker,
325 right_col_names,
326 build_side_select,
327 grace,
328 } => hash_join::next_hash_join(
329 join_condition,
330 hash_keys,
331 probe_keys,
332 build_side,
333 build_done,
334 memory_tracker,
335 right_col_names,
336 *build_side_select,
337 grace,
338 left,
339 right,
340 runtime,
341 output_layout,
342 ),
343 JoinOperatorKind::HashLeftJoin {
344 join_condition,
345 hash_keys,
346 probe_keys,
347 build_side,
348 build_done,
349 memory_tracker,
350 right_col_names,
351 build_side_select,
352 grace,
353 } => hash_join::next_hash_left_join(
354 join_condition,
355 hash_keys,
356 probe_keys,
357 build_side,
358 build_done,
359 memory_tracker,
360 right_col_names,
361 *build_side_select,
362 grace,
363 left,
364 right,
365 runtime,
366 output_layout,
367 ),
368 JoinOperatorKind::NestedLoopJoin {
369 join_condition,
370 build_side_tuples,
371 build_done,
372 memory_tracker,
373 right_col_names,
374 } => nested_loop_join::next_nested_loop_join(
375 join_condition,
376 build_side_tuples,
377 build_done,
378 memory_tracker,
379 right_col_names,
380 left,
381 right,
382 runtime,
383 output_layout,
384 ),
385 JoinOperatorKind::InnerJoin {
386 join_condition,
387 build_side_tuples,
388 build_done,
389 memory_tracker,
390 right_col_names,
391 } => merge_join::next_inner_join(
392 join_condition,
393 build_side_tuples,
394 build_done,
395 memory_tracker,
396 right_col_names,
397 left,
398 right,
399 runtime,
400 output_layout,
401 ),
402 JoinOperatorKind::LeftJoin {
403 join_condition,
404 build_side_tuples,
405 build_done,
406 memory_tracker,
407 right_col_names,
408 } => merge_join::next_left_join(
409 join_condition,
410 build_side_tuples,
411 build_done,
412 memory_tracker,
413 right_col_names,
414 left,
415 right,
416 runtime,
417 output_layout,
418 ),
419 JoinOperatorKind::RightJoin {
420 join_condition,
421 build_side_tuples,
422 right_consumed,
423 memory_tracker,
424 right_col_names,
425 } => merge_join::next_right_join(
426 join_condition,
427 build_side_tuples,
428 right_consumed,
429 memory_tracker,
430 right_col_names,
431 left,
432 right,
433 runtime,
434 output_layout,
435 ),
436 JoinOperatorKind::FullOuterJoin {
437 join_condition,
438 left_rows,
439 right_rows,
440 matched_right_indices,
441 result_iter,
442 phase,
443 memory_tracker,
444 right_col_names,
445 } => merge_join::next_full_outer_join(
446 join_condition,
447 left_rows,
448 right_rows,
449 matched_right_indices,
450 result_iter,
451 phase,
452 memory_tracker,
453 right_col_names,
454 left,
455 right,
456 runtime,
457 output_layout,
458 ),
459 JoinOperatorKind::CrossJoin {
460 all_left_rows,
461 all_right_rows,
462 left_consumed,
463 right_consumed,
464 memory_tracker,
465 right_col_names,
466 output_done,
467 } => cross_join::next_cross_join(
468 all_left_rows,
469 all_right_rows,
470 left_consumed,
471 right_consumed,
472 output_done,
473 memory_tracker,
474 right_col_names,
475 left,
476 right,
477 runtime,
478 output_layout,
479 ),
480 JoinOperatorKind::SemiJoin {
481 join_condition,
482 anti,
483 right_rows,
484 right_consumed,
485 memory_tracker,
486 right_col_names,
487 } => semi_join::next_semi_join(
488 join_condition,
489 *anti,
490 right_rows,
491 right_consumed,
492 memory_tracker,
493 right_col_names,
494 left,
495 right,
496 runtime,
497 output_layout,
498 ),
499 }
500 }
501
502 pub fn stop(&mut self) -> Result<(), QueryError> {
503 Ok(())
504 }
505
506 pub fn reset(
509 &mut self,
510 left: &mut StreamingExecutor,
511 right: &mut StreamingExecutor,
512 ) -> Result<bool, QueryError> {
513 match &mut self.kind {
514 JoinOperatorKind::HashJoin {
515 build_side,
516 build_done,
517 right_col_names,
518 grace,
519 memory_tracker,
520 ..
521 }
522 | JoinOperatorKind::HashLeftJoin {
523 build_side,
524 build_done,
525 right_col_names,
526 grace,
527 memory_tracker,
528 ..
529 } => {
530 grace.cleanup_files();
531 *grace = grace_join::GraceJoinState::default();
532 *build_side = HashJoinBuildSide::new();
533 *build_done = false;
534 right_col_names.clear();
535 memory_tracker.reset();
536 }
537 JoinOperatorKind::NestedLoopJoin {
538 build_side_tuples,
539 build_done,
540 right_col_names,
541 ..
542 }
543 | JoinOperatorKind::InnerJoin {
544 build_side_tuples,
545 build_done,
546 right_col_names,
547 ..
548 }
549 | JoinOperatorKind::LeftJoin {
550 build_side_tuples,
551 build_done,
552 right_col_names,
553 ..
554 } => {
555 build_side_tuples.clear();
556 *build_done = false;
557 right_col_names.clear();
558 }
559 JoinOperatorKind::RightJoin {
560 build_side_tuples,
561 right_consumed,
562 right_col_names,
563 ..
564 } => {
565 build_side_tuples.clear();
566 *right_consumed = false;
567 right_col_names.clear();
568 }
569 JoinOperatorKind::FullOuterJoin {
570 left_rows,
571 right_rows,
572 matched_right_indices,
573 result_iter,
574 phase,
575 right_col_names,
576 ..
577 } => {
578 left_rows.clear();
579 right_rows.clear();
580 matched_right_indices.clear();
581 *result_iter = None;
582 *phase = FullOuterJoinPhase::BuildingRight;
583 right_col_names.clear();
584 }
585 JoinOperatorKind::CrossJoin {
586 all_left_rows,
587 all_right_rows,
588 left_consumed,
589 right_consumed,
590 right_col_names,
591 output_done,
592 ..
593 } => {
594 all_left_rows.clear();
595 all_right_rows.clear();
596 *left_consumed = false;
597 *right_consumed = false;
598 right_col_names.clear();
599 *output_done = false;
600 }
601 JoinOperatorKind::SemiJoin {
602 right_rows,
603 right_consumed,
604 right_col_names,
605 ..
606 } => {
607 right_rows.clear();
608 *right_consumed = false;
609 right_col_names.clear();
610 }
611 }
612 left.reset()?;
613 right.reset()?;
614 Ok(false)
615 }
616
617 pub fn close(&mut self) -> Result<(), QueryError> {
618 match &mut self.kind {
619 JoinOperatorKind::HashJoin {
620 build_side,
621 memory_tracker,
622 grace,
623 ..
624 } => {
625 grace.cleanup_files();
626 hash_join::close(memory_tracker, build_side)
627 }
628 JoinOperatorKind::HashLeftJoin {
629 build_side,
630 memory_tracker,
631 grace,
632 ..
633 } => {
634 grace.cleanup_files();
635 hash_join::close(memory_tracker, build_side)
636 }
637 JoinOperatorKind::NestedLoopJoin {
638 build_side_tuples,
639 memory_tracker,
640 ..
641 } => nested_loop_join::close(memory_tracker, build_side_tuples),
642 JoinOperatorKind::InnerJoin {
643 build_side_tuples,
644 memory_tracker,
645 ..
646 } => nested_loop_join::close(memory_tracker, build_side_tuples),
647 JoinOperatorKind::LeftJoin {
648 build_side_tuples,
649 memory_tracker,
650 ..
651 } => nested_loop_join::close(memory_tracker, build_side_tuples),
652 JoinOperatorKind::RightJoin {
653 build_side_tuples,
654 memory_tracker,
655 ..
656 } => nested_loop_join::close(memory_tracker, build_side_tuples),
657 JoinOperatorKind::FullOuterJoin {
658 left_rows,
659 right_rows,
660 memory_tracker,
661 ..
662 } => merge_join::close_full_outer(memory_tracker, left_rows, right_rows),
663 JoinOperatorKind::CrossJoin {
664 all_left_rows,
665 all_right_rows,
666 memory_tracker,
667 ..
668 } => cross_join::close_cross(memory_tracker, all_left_rows, all_right_rows),
669 JoinOperatorKind::SemiJoin {
670 right_rows,
671 memory_tracker,
672 ..
673 } => semi_join::close_semi(memory_tracker, right_rows),
674 }
675 }
676
677 pub fn spill_with_manager(
685 &mut self,
686 sm: &Arc<crate::executor::streaming::spill::SpillManager>,
687 ) -> Result<(), linkrs_core::error::QueryError> {
688 let (build_side, build_done, memory_tracker, right_col_names, grace) = match &mut self.kind
689 {
690 JoinOperatorKind::HashJoin {
691 build_side,
692 build_done,
693 memory_tracker,
694 right_col_names,
695 grace,
696 ..
697 }
698 | JoinOperatorKind::HashLeftJoin {
699 build_side,
700 build_done,
701 memory_tracker,
702 right_col_names,
703 grace,
704 ..
705 } => (
706 build_side,
707 build_done,
708 memory_tracker,
709 right_col_names,
710 grace,
711 ),
712 _ => return Ok(()),
713 };
714 if *build_done || grace.partitioned.is_some() {
715 return Ok(());
716 }
717 if build_side.row_count() == 0 && grace.pending_build.is_none() {
718 return Ok(());
719 }
720 grace_join::spill_build_side(
721 Arc::clone(sm),
722 build_side,
723 memory_tracker,
724 right_col_names,
725 grace,
726 )
727 }
728
729 pub fn spilled_bytes(&self) -> u64 {
730 match &self.kind {
731 JoinOperatorKind::HashJoin { grace, .. }
732 | JoinOperatorKind::HashLeftJoin { grace, .. } => grace.spilled_bytes,
733 _ => 0,
734 }
735 }
736
737 pub fn spill_count(&self) -> u64 {
738 match &self.kind {
739 JoinOperatorKind::HashJoin { grace, .. }
740 | JoinOperatorKind::HashLeftJoin { grace, .. } => grace.spill_runs,
741 _ => 0,
742 }
743 }
744
745 pub fn spilled_rows(&self) -> u64 {
746 match &self.kind {
747 JoinOperatorKind::HashJoin { grace, .. }
748 | JoinOperatorKind::HashLeftJoin { grace, .. } => grace.spilled_rows,
749 _ => 0,
750 }
751 }
752}