1use std::cmp::Ordering;
10use std::sync::Arc;
11
12use uqa_core::Value;
13use uqa_sql::ast::{ColumnType, SetOpKind};
14use uqa_sql::expr::RowLookup;
15
16#[cfg(test)]
17use uqa_sql::ResultRow;
18
19use crate::batch::DEFAULT_BATCH_SIZE;
20use crate::{
21 BackwardScanSupport, Batch, ExecError, ExecResult, ExpressionEvaluator, ExternalSort,
22 PhysicalOperator, PhysicalRow, PhysicalScanDirection, RowProjectionValue, RowSchema,
23 ScalarExpr, SortKey,
24};
25
26struct ColumnEvaluator;
27
28impl ExpressionEvaluator for ColumnEvaluator {
29 fn evaluate(&self, expression: &ScalarExpr, row: &dyn RowLookup) -> ExecResult<Value> {
30 let ScalarExpr::Column(column) = expression else {
31 return Err(ExecError::Other(
32 "set-operation sort key must be a column".into(),
33 ));
34 };
35 Ok(row.column(column).cloned().unwrap_or(Value::Null))
36 }
37}
38
39fn set_operation_types(left: &RowSchema, right: &RowSchema) -> ExecResult<Vec<Option<ColumnType>>> {
40 if left.len() != right.len() {
41 return Err(ExecError::Other(format!(
42 "set-operation inputs have different widths: {} and {}",
43 left.len(),
44 right.len()
45 )));
46 }
47 left.column_types()
48 .iter()
49 .zip(right.column_types())
50 .map(|(left, right)| match (left, right) {
51 (None, None) => Ok(None),
52 (Some(ty), None) | (None, Some(ty)) => Ok(Some(ty.clone())),
53 (Some(left), Some(right)) => uqa_execution_common_type(left, right).map(Some),
54 })
55 .collect()
56}
57
58fn uqa_execution_common_type(left: &ColumnType, right: &ColumnType) -> ExecResult<ColumnType> {
59 crate::common_type(left, right).map_err(ExecError::from)
60}
61
62fn coerce_set_value(
63 value: Value,
64 source_type: Option<&ColumnType>,
65 target_type: &ColumnType,
66) -> ExecResult<Value> {
67 let cast_target = match target_type {
68 ColumnType::Domain { base, .. } => base.as_ref(),
69 target => target,
70 };
71 let source_name = source_type.map(ColumnType::sql_name);
72 uqa_sql::expr::cast_value_from(&value, &cast_target.sql_name(), source_name.as_deref())
73 .map_err(ExecError::from)
74}
75
76struct AlignSchema<'a> {
77 child: Box<dyn PhysicalOperator + 'a>,
78 schema: RowSchema,
79 coercions: Vec<Option<ColumnType>>,
80}
81
82impl<'a> AlignSchema<'a> {
83 fn new(
84 child: Box<dyn PhysicalOperator + 'a>,
85 output: Vec<String>,
86 output_types: &[Option<ColumnType>],
87 ) -> ExecResult<Self> {
88 let source = child.schema().to_vec();
89 if source.len() != output.len() {
90 return Err(ExecError::Other(format!(
91 "set-operation inputs have different widths: {} and {}",
92 output.len(),
93 source.len()
94 )));
95 }
96 if output.len() != output_types.len() {
97 return Err(ExecError::Other(format!(
98 "set-operation output type width {} does not match input width {}",
99 output_types.len(),
100 output.len()
101 )));
102 }
103 let coercions = child
104 .row_schema()
105 .column_types()
106 .iter()
107 .zip(output_types)
108 .map(|(source, target)| {
109 target
110 .as_ref()
111 .filter(|target| source.as_ref() != Some(*target))
112 .cloned()
113 })
114 .collect();
115 let schema = RowSchema::with_types(output, output_types.to_vec());
116 Ok(Self {
117 child,
118 schema,
119 coercions,
120 })
121 }
122}
123
124impl PhysicalOperator for AlignSchema<'_> {
125 fn row_schema(&self) -> &RowSchema {
126 &self.schema
127 }
128
129 fn backward_scan_support(&self) -> BackwardScanSupport {
130 self.child.backward_scan_support()
131 }
132
133 fn open(&mut self) -> ExecResult<()> {
134 self.child.open()
135 }
136
137 fn next(&mut self) -> ExecResult<Option<Batch>> {
138 let Some(batch) = self.child.next()? else {
139 return Ok(None);
140 };
141 self.align_batch(batch).map(Some)
142 }
143
144 fn next_direction(&mut self, direction: PhysicalScanDirection) -> ExecResult<Option<Batch>> {
145 let Some(batch) = self.child.next_direction(direction)? else {
146 return Ok(None);
147 };
148 self.align_batch(batch).map(Some)
149 }
150
151 fn rewind(&mut self) -> ExecResult<()> {
152 self.child.rewind()
153 }
154
155 fn close(&mut self) -> ExecResult<()> {
156 self.child.close()
157 }
158}
159
160impl AlignSchema<'_> {
161 fn align_batch(&self, batch: Batch) -> ExecResult<Batch> {
162 let identity_layout = batch.schema.physical_width() == self.schema.physical_width()
163 && (0..batch.schema.len())
164 .all(|position| batch.schema.slot(position) == Some(position));
165 if self.coercions.iter().all(Option::is_none) && identity_layout {
166 let rows = batch
167 .rows
168 .into_iter()
169 .map(PhysicalRow::without_lock_origins)
170 .collect();
171 return Ok(Batch::from_physical_rows(self.schema.clone(), rows));
172 }
173 if self.coercions.iter().all(Option::is_none) {
174 let slots = (0..batch.schema.len())
175 .map(|position| {
176 batch.schema.slot(position).ok_or_else(|| {
177 ExecError::Other(format!(
178 "set-operation input column {position} has no physical slot"
179 ))
180 })
181 })
182 .collect::<ExecResult<Vec<_>>>()?;
183 let rows = batch
184 .rows
185 .into_iter()
186 .map(|row| row.project_slots(&slots).without_lock_origins())
187 .collect();
188 return Ok(Batch::from_physical_rows(self.schema.clone(), rows));
189 }
190 let mut rows = Vec::with_capacity(batch.rows.len());
191 for row in batch.rows {
192 let view = batch.schema.view(&row);
193 let values = self
194 .coercions
195 .iter()
196 .enumerate()
197 .map(|(position, target)| match target {
198 Some(target) => coerce_set_value(
199 view.value_at(position).cloned().unwrap_or(Value::Null),
200 batch.schema.column_type(position),
201 target,
202 )
203 .map(RowProjectionValue::Owned),
204 None => Ok(batch.schema.slot(position).map_or(
205 RowProjectionValue::Owned(Value::Null),
206 RowProjectionValue::InputSlot,
207 )),
208 })
209 .collect::<ExecResult<Vec<_>>>()?;
210 rows.push(row.project_with_values(values).without_lock_origins());
211 }
212 Ok(Batch::from_physical_rows(self.schema.clone(), rows))
213 }
214}
215
216struct RowGroup {
217 row: PhysicalRow,
218 count: usize,
219}
220
221struct RowCursor<'a> {
222 operator: Box<dyn PhysicalOperator + 'a>,
223 batch: std::vec::IntoIter<PhysicalRow>,
224 lookahead: Option<PhysicalRow>,
225 exhausted: bool,
226}
227
228impl<'a> RowCursor<'a> {
229 fn new(operator: Box<dyn PhysicalOperator + 'a>) -> Self {
230 Self {
231 operator,
232 batch: Vec::new().into_iter(),
233 lookahead: None,
234 exhausted: false,
235 }
236 }
237
238 fn open(&mut self) -> ExecResult<()> {
239 self.batch = Vec::new().into_iter();
240 self.lookahead = None;
241 self.exhausted = false;
242 self.operator.open()
243 }
244
245 fn next_row(&mut self) -> ExecResult<Option<PhysicalRow>> {
246 loop {
247 if let Some(row) = self.batch.next() {
248 return Ok(Some(row));
249 }
250 let Some(batch) = self.operator.next()? else {
251 self.exhausted = true;
252 return Ok(None);
253 };
254 self.batch = batch.rows.into_iter();
255 }
256 }
257
258 fn backward_scan_support(&self) -> BackwardScanSupport {
259 self.operator.backward_scan_support()
260 }
261
262 fn next_direction_row(
263 &mut self,
264 direction: PhysicalScanDirection,
265 ) -> ExecResult<Option<PhysicalRow>> {
266 if self.batch.len() != 0 || self.lookahead.is_some() {
267 return Err(ExecError::Other(
268 "set-operation input cannot mix batched and directional pulls".into(),
269 ));
270 }
271 let Some(batch) = self.operator.next_direction(direction)? else {
272 self.exhausted = true;
273 return Ok(None);
274 };
275 let mut rows = batch.rows.into_iter();
276 let row = rows.next();
277 if rows.next().is_some() {
278 return Err(ExecError::Other(
279 "directional set-operation input returned more than one row".into(),
280 ));
281 }
282 self.exhausted = row.is_none();
283 Ok(row)
284 }
285
286 fn rewind(&mut self) -> ExecResult<()> {
287 self.batch = Vec::new().into_iter();
288 self.lookahead = None;
289 self.exhausted = false;
290 self.operator.rewind()
291 }
292
293 fn take_group(&mut self, schema: &RowSchema) -> ExecResult<Option<RowGroup>> {
294 let first = match self.lookahead.take() {
295 Some(row) => row,
296 None => match self.next_row()? {
297 Some(row) => row,
298 None => return Ok(None),
299 },
300 };
301 let mut count = 1_usize;
302 while let Some(row) = self.next_row()? {
303 if compare_rows(&first, &row, schema) == Ordering::Equal {
304 count = count
305 .checked_add(1)
306 .ok_or_else(|| ExecError::Other("set-operation group count overflow".into()))?;
307 } else {
308 self.lookahead = Some(row);
309 break;
310 }
311 }
312 Ok(Some(RowGroup { row: first, count }))
313 }
314
315 fn close(&mut self) -> ExecResult<()> {
316 self.batch = Vec::new().into_iter();
317 self.lookahead = None;
318 self.exhausted = true;
319 self.operator.close()
320 }
321}
322
323#[derive(Clone, Copy)]
324enum DirectionalAppendPosition {
325 BeforeFirst,
326 Left,
327 Right,
328 AfterLast,
329}
330
331fn compare_rows(left: &PhysicalRow, right: &PhysicalRow, schema: &RowSchema) -> Ordering {
332 let left = schema.view(left);
333 let right = schema.view(right);
334 let null = Value::Null;
335 for position in 0..schema.len() {
336 let ordering = left
337 .value_at(position)
338 .unwrap_or(&null)
339 .cmp(right.value_at(position).unwrap_or(&null));
340 if ordering != Ordering::Equal {
341 return ordering;
342 }
343 }
344 Ordering::Equal
345}
346
347pub struct ExternalSetOperation<'a> {
353 left: RowCursor<'a>,
354 right: RowCursor<'a>,
355 kind: SetOpKind,
356 all: bool,
357 schema: RowSchema,
358 left_group: Option<RowGroup>,
359 right_group: Option<RowGroup>,
360 pending_row: Option<PhysicalRow>,
361 pending_count: usize,
362 union_all_left_done: bool,
363 incremental_union_all: bool,
364 directional_position: DirectionalAppendPosition,
365}
366
367impl<'a> ExternalSetOperation<'a> {
368 pub fn new(
369 left: Box<dyn PhysicalOperator + 'a>,
370 right: Box<dyn PhysicalOperator + 'a>,
371 kind: SetOpKind,
372 all: bool,
373 work_mem_bytes: usize,
374 ) -> ExecResult<Self> {
375 let output_types = set_operation_types(left.row_schema(), right.row_schema())?;
376 Self::new_with_types(left, right, kind, all, output_types, work_mem_bytes)
377 }
378
379 pub fn new_with_types(
380 left: Box<dyn PhysicalOperator + 'a>,
381 right: Box<dyn PhysicalOperator + 'a>,
382 kind: SetOpKind,
383 all: bool,
384 output_types: Vec<Option<ColumnType>>,
385 work_mem_bytes: usize,
386 ) -> ExecResult<Self> {
387 Self::new_with_types_and_mode(left, right, kind, all, output_types, work_mem_bytes, false)
388 }
389
390 pub fn new_directional_with_types(
392 left: Box<dyn PhysicalOperator + 'a>,
393 right: Box<dyn PhysicalOperator + 'a>,
394 kind: SetOpKind,
395 all: bool,
396 output_types: Vec<Option<ColumnType>>,
397 work_mem_bytes: usize,
398 ) -> ExecResult<Self> {
399 Self::new_with_types_and_mode(left, right, kind, all, output_types, work_mem_bytes, true)
400 }
401
402 fn new_with_types_and_mode(
403 left: Box<dyn PhysicalOperator + 'a>,
404 right: Box<dyn PhysicalOperator + 'a>,
405 kind: SetOpKind,
406 all: bool,
407 output_types: Vec<Option<ColumnType>>,
408 work_mem_bytes: usize,
409 incremental_union_all: bool,
410 ) -> ExecResult<Self> {
411 let output = left.schema().to_vec();
412 let left: Box<dyn PhysicalOperator + 'a> =
413 Box::new(AlignSchema::new(left, output.clone(), &output_types)?);
414 let right: Box<dyn PhysicalOperator + 'a> =
415 Box::new(AlignSchema::new(right, output.clone(), &output_types)?);
416 let (left, right) = if matches!((kind, all), (SetOpKind::Union, true)) {
417 (left, right)
418 } else {
419 let keys = output
420 .iter()
421 .map(|column| SortKey {
422 expr: ScalarExpr::Column(column.clone()),
423 descending: false,
424 nulls_first: Some(true),
425 })
426 .collect::<Vec<_>>();
427 let evaluator = Arc::new(ColumnEvaluator);
428 let per_input = (work_mem_bytes / 2).max(1);
431 (
432 Box::new(ExternalSort::new(
433 left,
434 keys.clone(),
435 evaluator.clone(),
436 None,
437 per_input,
438 )) as Box<dyn PhysicalOperator + 'a>,
439 Box::new(ExternalSort::new(right, keys, evaluator, None, per_input))
440 as Box<dyn PhysicalOperator + 'a>,
441 )
442 };
443 Ok(Self {
444 left: RowCursor::new(left),
445 right: RowCursor::new(right),
446 kind,
447 all,
448 schema: RowSchema::with_types(output, output_types),
449 left_group: None,
450 right_group: None,
451 pending_row: None,
452 pending_count: 0,
453 union_all_left_done: false,
454 incremental_union_all,
455 directional_position: DirectionalAppendPosition::BeforeFirst,
456 })
457 }
458
459 fn next_union_all(&mut self) -> ExecResult<Option<Batch>> {
460 let mut rows = Vec::with_capacity(DEFAULT_BATCH_SIZE);
461 while rows.len() < DEFAULT_BATCH_SIZE {
462 let next = if self.union_all_left_done {
463 self.right.next_row()?
464 } else if let Some(row) = self.left.next_row()? {
465 Some(row)
466 } else {
467 self.union_all_left_done = true;
468 self.right.next_row()?
469 };
470 let Some(row) = next else {
471 break;
472 };
473 rows.push(row.without_lock_origins());
474 }
475 if rows.is_empty() {
476 Ok(None)
477 } else {
478 Ok(Some(Batch::from_physical_rows(self.schema.clone(), rows)))
479 }
480 }
481
482 fn next_union_all_row(&mut self) -> ExecResult<Option<Batch>> {
483 let row = if self.union_all_left_done {
484 self.right.next_row()?
485 } else if let Some(row) = self.left.next_row()? {
486 Some(row)
487 } else {
488 self.union_all_left_done = true;
489 self.right.next_row()?
490 };
491 Ok(row.map(|row| self.directional_row_batch(row)))
492 }
493
494 fn directional_row_batch(&self, row: PhysicalRow) -> Batch {
495 Batch::from_physical_rows(self.schema.clone(), vec![row.without_lock_origins()])
496 }
497
498 fn next_union_all_direction(
499 &mut self,
500 direction: PhysicalScanDirection,
501 ) -> ExecResult<Option<Batch>> {
502 let mut position = match (self.directional_position, direction) {
503 (DirectionalAppendPosition::BeforeFirst, PhysicalScanDirection::Backward)
504 | (DirectionalAppendPosition::AfterLast, PhysicalScanDirection::Forward) => {
505 return Ok(None)
506 }
507 (DirectionalAppendPosition::BeforeFirst, PhysicalScanDirection::Forward)
508 | (DirectionalAppendPosition::Left, _) => DirectionalAppendPosition::Left,
509 (DirectionalAppendPosition::AfterLast, PhysicalScanDirection::Backward)
510 | (DirectionalAppendPosition::Right, _) => DirectionalAppendPosition::Right,
511 };
512 loop {
513 let row = match position {
514 DirectionalAppendPosition::Left => self.left.next_direction_row(direction)?,
515 DirectionalAppendPosition::Right => self.right.next_direction_row(direction)?,
516 DirectionalAppendPosition::BeforeFirst | DirectionalAppendPosition::AfterLast => {
517 unreachable!()
518 }
519 };
520 if let Some(row) = row {
521 self.directional_position = position;
522 return Ok(Some(self.directional_row_batch(row)));
523 }
524 position = match (position, direction) {
525 (DirectionalAppendPosition::Left, PhysicalScanDirection::Forward) => {
526 DirectionalAppendPosition::Right
527 }
528 (DirectionalAppendPosition::Right, PhysicalScanDirection::Backward) => {
529 DirectionalAppendPosition::Left
530 }
531 (DirectionalAppendPosition::Left, PhysicalScanDirection::Backward) => {
532 self.directional_position = DirectionalAppendPosition::BeforeFirst;
533 return Ok(None);
534 }
535 (DirectionalAppendPosition::Right, PhysicalScanDirection::Forward) => {
536 self.directional_position = DirectionalAppendPosition::AfterLast;
537 return Ok(None);
538 }
539 (
540 DirectionalAppendPosition::BeforeFirst | DirectionalAppendPosition::AfterLast,
541 _,
542 ) => unreachable!(),
543 };
544 }
545 }
546
547 fn load_groups(&mut self) -> ExecResult<()> {
548 if self.left_group.is_none() && !self.left.exhausted {
549 self.left_group = self.left.take_group(&self.schema)?;
550 }
551 if self.right_group.is_none() && !self.right.exhausted {
552 self.right_group = self.right.take_group(&self.schema)?;
553 }
554 Ok(())
555 }
556
557 fn take_left_group(&mut self) -> ExecResult<RowGroup> {
558 self.left_group
559 .take()
560 .ok_or_else(|| ExecError::Other("set-operation selected a missing left group".into()))
561 }
562
563 fn take_right_group(&mut self) -> ExecResult<RowGroup> {
564 self.right_group
565 .take()
566 .ok_or_else(|| ExecError::Other("set-operation selected a missing right group".into()))
567 }
568
569 fn choose_group(&mut self) -> ExecResult<Option<(PhysicalRow, usize)>> {
570 self.load_groups()?;
571 let ordering = match (&self.left_group, &self.right_group) {
572 (Some(left), Some(right)) => Some(compare_rows(&left.row, &right.row, &self.schema)),
573 (Some(_), None) => Some(Ordering::Less),
574 (None, Some(_)) => Some(Ordering::Greater),
575 (None, None) => None,
576 };
577 let Some(ordering) = ordering else {
578 return Ok(None);
579 };
580
581 let selected = match (self.kind, ordering) {
582 (SetOpKind::Union, Ordering::Less) => {
583 let group = self.take_left_group()?;
584 Some((group.row, 1))
585 }
586 (SetOpKind::Union, Ordering::Greater) => {
587 let group = self.take_right_group()?;
588 Some((group.row, 1))
589 }
590 (SetOpKind::Union, Ordering::Equal) => {
591 let group = self.take_left_group()?;
592 self.right_group = None;
593 Some((group.row, 1))
594 }
595 (SetOpKind::Intersect, Ordering::Less) => {
596 self.left_group = None;
597 None
598 }
599 (SetOpKind::Intersect | SetOpKind::Except, Ordering::Greater) => {
600 self.right_group = None;
601 None
602 }
603 (SetOpKind::Intersect, Ordering::Equal) => {
604 let left = self.take_left_group()?;
605 let right = self.take_right_group()?;
606 Some((
607 left.row,
608 if self.all {
609 left.count.min(right.count)
610 } else {
611 1
612 },
613 ))
614 }
615 (SetOpKind::Except, Ordering::Less) => {
616 let left = self.take_left_group()?;
617 Some((left.row, if self.all { left.count } else { 1 }))
618 }
619 (SetOpKind::Except, Ordering::Equal) => {
620 let left = self.take_left_group()?;
621 let right = self.take_right_group()?;
622 let count = if self.all {
623 left.count.saturating_sub(right.count)
624 } else {
625 0
626 };
627 Some((left.row, count))
628 }
629 };
630 Ok(selected.filter(|(_, count)| *count > 0))
631 }
632}
633
634impl PhysicalOperator for ExternalSetOperation<'_> {
635 fn row_schema(&self) -> &RowSchema {
636 &self.schema
637 }
638
639 fn backward_scan_support(&self) -> BackwardScanSupport {
640 if matches!((self.kind, self.all), (SetOpKind::Union, true))
641 && self.left.backward_scan_support() == BackwardScanSupport::Native
642 && self.right.backward_scan_support() == BackwardScanSupport::Native
643 {
644 BackwardScanSupport::Native
645 } else {
646 BackwardScanSupport::Unsupported
647 }
648 }
649
650 fn open(&mut self) -> ExecResult<()> {
651 self.left_group = None;
652 self.right_group = None;
653 self.pending_row = None;
654 self.pending_count = 0;
655 self.union_all_left_done = false;
656 self.directional_position = DirectionalAppendPosition::BeforeFirst;
657 self.left.open()?;
658 self.right.open()
659 }
660
661 fn next(&mut self) -> ExecResult<Option<Batch>> {
662 if matches!((self.kind, self.all), (SetOpKind::Union, true)) {
663 return if self.incremental_union_all {
664 self.next_union_all_row()
665 } else {
666 self.next_union_all()
667 };
668 }
669 let mut rows = Vec::with_capacity(DEFAULT_BATCH_SIZE);
670 while rows.len() < DEFAULT_BATCH_SIZE {
671 if self.pending_count > 0 {
672 let row = self.pending_row.as_ref().ok_or_else(|| {
673 ExecError::Other(
674 "set-operation has pending multiplicity without a pending row".into(),
675 )
676 })?;
677 rows.push(row.clone());
678 self.pending_count -= 1;
679 if self.pending_count == 0 {
680 self.pending_row = None;
681 }
682 continue;
683 }
684 match self.choose_group()? {
685 Some((row, count)) => {
686 self.pending_row = Some(row.without_lock_origins());
687 self.pending_count = count;
688 }
689 None if self.left.exhausted && self.right.exhausted => break,
690 None => {}
691 }
692 }
693 if rows.is_empty() {
694 Ok(None)
695 } else {
696 Ok(Some(Batch::from_physical_rows(self.schema.clone(), rows)))
697 }
698 }
699
700 fn next_direction(&mut self, direction: PhysicalScanDirection) -> ExecResult<Option<Batch>> {
701 if self.backward_scan_support() == BackwardScanSupport::Native {
702 return self.next_union_all_direction(direction);
703 }
704 if direction == PhysicalScanDirection::Forward {
705 return if matches!((self.kind, self.all), (SetOpKind::Union, true)) {
706 self.next_union_all_row()
707 } else {
708 self.next()
709 };
710 }
711 Err(ExecError::Other(
712 "set operation does not support backwards scanning".into(),
713 ))
714 }
715
716 fn rewind(&mut self) -> ExecResult<()> {
717 if self.backward_scan_support() != BackwardScanSupport::Native {
718 return Err(ExecError::Other(
719 "set operation does not support rewind".into(),
720 ));
721 }
722 self.left.rewind()?;
723 self.right.rewind()?;
724 self.directional_position = DirectionalAppendPosition::BeforeFirst;
725 Ok(())
726 }
727
728 fn close(&mut self) -> ExecResult<()> {
729 self.left_group = None;
730 self.right_group = None;
731 self.pending_row = None;
732 self.pending_count = 0;
733 self.directional_position = DirectionalAppendPosition::BeforeFirst;
734 let left = self.left.close();
735 let right = self.right.close();
736 crate::physical::with_cleanup(left, right, "close right set-operation input")
737 }
738}
739
740#[cfg(test)]
741mod tests {
742 use super::*;
743 use crate::physical::run_to_rows;
744 use crate::scan::TableScan;
745
746 fn row(value: i64) -> ResultRow {
747 [("v".into(), Value::Int(value))].into_iter().collect()
748 }
749
750 fn execute(kind: SetOpKind, all: bool, left: &[i64], right: &[i64]) -> Vec<i64> {
751 let left = TableScan::from_rows(vec!["v".into()], left.iter().copied().map(row).collect());
752 let right = TableScan::from_rows(
753 vec!["other".into()],
754 right
755 .iter()
756 .copied()
757 .map(|value| [("other".into(), Value::Int(value))].into_iter().collect())
758 .collect(),
759 );
760 let mut set =
761 ExternalSetOperation::new(Box::new(left), Box::new(right), kind, all, 1).unwrap();
762 run_to_rows(&mut set)
763 .unwrap()
764 .1
765 .into_iter()
766 .map(|row| match row.get("v") {
767 Some(Value::Int(value)) => *value,
768 value => panic!("unexpected set value: {value:?}"),
769 })
770 .collect()
771 }
772
773 #[test]
774 fn external_set_semantics_include_bag_multiplicity() {
775 assert_eq!(
776 execute(SetOpKind::Union, false, &[2, 1, 1], &[3, 2]),
777 vec![1, 2, 3]
778 );
779 assert_eq!(
780 execute(SetOpKind::Union, true, &[2, 1, 1], &[3, 2]),
781 vec![2, 1, 1, 3, 2]
782 );
783 assert_eq!(
784 execute(SetOpKind::Intersect, true, &[1, 1, 1, 2], &[1, 1, 3]),
785 vec![1, 1]
786 );
787 assert_eq!(
788 execute(SetOpKind::Except, true, &[1, 1, 1, 2], &[1, 1, 3]),
789 vec![1, 2]
790 );
791 assert_eq!(
792 execute(SetOpKind::Except, false, &[1, 1, 2], &[1, 3]),
793 vec![2]
794 );
795 }
796
797 #[test]
798 fn set_inputs_compact_schema_only_projections_before_alignment() {
799 let left = TableScan::from_rows(
800 vec!["v".into(), "hidden".into()],
801 vec![[
802 ("v".into(), Value::Int(1)),
803 ("hidden".into(), Value::Int(99)),
804 ]
805 .into_iter()
806 .collect()],
807 );
808 let left: Box<dyn PhysicalOperator> = Box::new(crate::ColumnSelection::with_positions(
809 Box::new(left),
810 vec![("v".into(), 0)],
811 ));
812 let right = TableScan::from_rows(vec!["v".into()], vec![row(2)]);
813 let mut set =
814 ExternalSetOperation::new(left, Box::new(right), SetOpKind::Union, true, 1).unwrap();
815 let rows = run_to_rows(&mut set).unwrap().1;
816 assert_eq!(rows, vec![row(1), row(2)]);
817 }
818
819 fn directional_value(batch: Option<Batch>) -> Option<i64> {
820 let batch = batch?;
821 let row = batch.rows.first()?;
822 match batch.schema.view(row).value_at(0) {
823 Some(Value::Int(value)) => Some(*value),
824 value => panic!("unexpected directional set value: {value:?}"),
825 }
826 }
827
828 #[test]
829 fn union_all_scans_children_in_reverse_order_across_the_boundary() {
830 let left: Box<dyn PhysicalOperator> = Box::new(crate::ScrollMaterialize::new(Box::new(
831 TableScan::from_rows(vec!["v".into()], vec![row(1), row(2)]),
832 )));
833 let right: Box<dyn PhysicalOperator> = Box::new(crate::ScrollMaterialize::new(Box::new(
834 TableScan::from_rows(vec!["v".into()], vec![row(3), row(4)]),
835 )));
836 let mut set = ExternalSetOperation::new(left, right, SetOpKind::Union, true, 1).unwrap();
837 assert_eq!(set.backward_scan_support(), BackwardScanSupport::Native);
838 set.open().unwrap();
839 for (direction, expected) in [
840 (PhysicalScanDirection::Forward, 1),
841 (PhysicalScanDirection::Forward, 2),
842 (PhysicalScanDirection::Forward, 3),
843 (PhysicalScanDirection::Backward, 2),
844 (PhysicalScanDirection::Forward, 3),
845 ] {
846 assert_eq!(
847 directional_value(set.next_direction(direction).unwrap()),
848 Some(expected)
849 );
850 }
851 set.rewind().unwrap();
852 assert_eq!(
853 directional_value(set.next_direction(PhysicalScanDirection::Forward).unwrap()),
854 Some(1)
855 );
856 set.close().unwrap();
857 }
858
859 #[test]
860 fn directional_union_all_materialization_pulls_one_row_at_a_time() {
861 let left: Box<dyn PhysicalOperator> =
862 Box::new(TableScan::from_rows(vec!["v".into()], vec![row(1), row(2)]));
863 let right: Box<dyn PhysicalOperator> =
864 Box::new(TableScan::from_rows(vec!["v".into()], vec![row(3)]));
865 let mut set = ExternalSetOperation::new_directional_with_types(
866 left,
867 right,
868 SetOpKind::Union,
869 true,
870 vec![None],
871 1,
872 )
873 .unwrap();
874 assert_eq!(
875 set.backward_scan_support(),
876 BackwardScanSupport::Unsupported
877 );
878 set.open().unwrap();
879 for expected in [1, 2, 3] {
880 let batch = set.next().unwrap().unwrap();
881 assert_eq!(batch.rows.len(), 1);
882 assert_eq!(directional_value(Some(batch)), Some(expected));
883 }
884 assert!(set.next().unwrap().is_none());
885 set.close().unwrap();
886 }
887}