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 Batch, ExecError, ExecResult, ExpressionEvaluator, ExternalSort, PhysicalOperator, PhysicalRow,
22 RowProjectionValue, RowSchema, ScalarExpr, SortKey,
23};
24
25struct ColumnEvaluator;
26
27impl ExpressionEvaluator for ColumnEvaluator {
28 fn evaluate(&self, expression: &ScalarExpr, row: &dyn RowLookup) -> ExecResult<Value> {
29 let ScalarExpr::Column(column) = expression else {
30 return Err(ExecError::Other(
31 "set-operation sort key must be a column".into(),
32 ));
33 };
34 Ok(row.column(column).cloned().unwrap_or(Value::Null))
35 }
36}
37
38fn set_operation_types(left: &RowSchema, right: &RowSchema) -> ExecResult<Vec<Option<ColumnType>>> {
39 if left.len() != right.len() {
40 return Err(ExecError::Other(format!(
41 "set-operation inputs have different widths: {} and {}",
42 left.len(),
43 right.len()
44 )));
45 }
46 left.column_types()
47 .iter()
48 .zip(right.column_types())
49 .map(|(left, right)| match (left, right) {
50 (None, None) => Ok(None),
51 (Some(ty), None) | (None, Some(ty)) => Ok(Some(ty.clone())),
52 (Some(left), Some(right)) => uqa_execution_common_type(left, right).map(Some),
53 })
54 .collect()
55}
56
57fn uqa_execution_common_type(left: &ColumnType, right: &ColumnType) -> ExecResult<ColumnType> {
58 crate::common_type(left, right).map_err(ExecError::from)
59}
60
61fn coerce_set_value(
62 value: Value,
63 source_type: Option<&ColumnType>,
64 target_type: &ColumnType,
65) -> ExecResult<Value> {
66 let cast_target = match target_type {
67 ColumnType::Domain { base, .. } => base.as_ref(),
68 target => target,
69 };
70 let source_name = source_type.map(ColumnType::sql_name);
71 uqa_sql::expr::cast_value_from(&value, &cast_target.sql_name(), source_name.as_deref())
72 .map_err(ExecError::from)
73}
74
75struct AlignSchema<'a> {
76 child: Box<dyn PhysicalOperator + 'a>,
77 schema: RowSchema,
78 coercions: Vec<Option<ColumnType>>,
79}
80
81impl<'a> AlignSchema<'a> {
82 fn new(
83 child: Box<dyn PhysicalOperator + 'a>,
84 output: Vec<String>,
85 output_types: &[Option<ColumnType>],
86 ) -> ExecResult<Self> {
87 let source = child.schema().to_vec();
88 if source.len() != output.len() {
89 return Err(ExecError::Other(format!(
90 "set-operation inputs have different widths: {} and {}",
91 output.len(),
92 source.len()
93 )));
94 }
95 if output.len() != output_types.len() {
96 return Err(ExecError::Other(format!(
97 "set-operation output type width {} does not match input width {}",
98 output_types.len(),
99 output.len()
100 )));
101 }
102 let coercions = child
103 .row_schema()
104 .column_types()
105 .iter()
106 .zip(output_types)
107 .map(|(source, target)| {
108 target
109 .as_ref()
110 .filter(|target| source.as_ref() != Some(*target))
111 .cloned()
112 })
113 .collect();
114 let schema = RowSchema::with_types(output, output_types.to_vec());
115 Ok(Self {
116 child,
117 schema,
118 coercions,
119 })
120 }
121}
122
123impl PhysicalOperator for AlignSchema<'_> {
124 fn row_schema(&self) -> &RowSchema {
125 &self.schema
126 }
127
128 fn open(&mut self) -> ExecResult<()> {
129 self.child.open()
130 }
131
132 fn next(&mut self) -> ExecResult<Option<Batch>> {
133 let Some(batch) = self.child.next()? else {
134 return Ok(None);
135 };
136 let identity_layout = batch.schema.physical_width() == self.schema.physical_width()
137 && (0..batch.schema.len())
138 .all(|position| batch.schema.slot(position) == Some(position));
139 if self.coercions.iter().all(Option::is_none) && identity_layout {
140 let rows = batch
141 .rows
142 .into_iter()
143 .map(PhysicalRow::without_lock_origins)
144 .collect();
145 return Ok(Some(Batch::from_physical_rows(self.schema.clone(), rows)));
146 }
147 if self.coercions.iter().all(Option::is_none) {
148 let slots = (0..batch.schema.len())
149 .map(|position| {
150 batch.schema.slot(position).ok_or_else(|| {
151 ExecError::Other(format!(
152 "set-operation input column {position} has no physical slot"
153 ))
154 })
155 })
156 .collect::<ExecResult<Vec<_>>>()?;
157 let rows = batch
158 .rows
159 .into_iter()
160 .map(|row| row.project_slots(&slots).without_lock_origins())
161 .collect();
162 return Ok(Some(Batch::from_physical_rows(self.schema.clone(), rows)));
163 }
164 let mut rows = Vec::with_capacity(batch.rows.len());
165 for row in batch.rows {
166 let view = batch.schema.view(&row);
167 let values = self
168 .coercions
169 .iter()
170 .enumerate()
171 .map(|(position, target)| match target {
172 Some(target) => coerce_set_value(
173 view.value_at(position).cloned().unwrap_or(Value::Null),
174 batch.schema.column_type(position),
175 target,
176 )
177 .map(RowProjectionValue::Owned),
178 None => Ok(batch.schema.slot(position).map_or(
179 RowProjectionValue::Owned(Value::Null),
180 RowProjectionValue::InputSlot,
181 )),
182 })
183 .collect::<ExecResult<Vec<_>>>()?;
184 rows.push(row.project_with_values(values).without_lock_origins());
185 }
186 Ok(Some(Batch::from_physical_rows(self.schema.clone(), rows)))
187 }
188
189 fn close(&mut self) -> ExecResult<()> {
190 self.child.close()
191 }
192}
193
194struct RowGroup {
195 row: PhysicalRow,
196 count: usize,
197}
198
199struct RowCursor<'a> {
200 operator: Box<dyn PhysicalOperator + 'a>,
201 batch: std::vec::IntoIter<PhysicalRow>,
202 lookahead: Option<PhysicalRow>,
203 exhausted: bool,
204}
205
206impl<'a> RowCursor<'a> {
207 fn new(operator: Box<dyn PhysicalOperator + 'a>) -> Self {
208 Self {
209 operator,
210 batch: Vec::new().into_iter(),
211 lookahead: None,
212 exhausted: false,
213 }
214 }
215
216 fn open(&mut self) -> ExecResult<()> {
217 self.batch = Vec::new().into_iter();
218 self.lookahead = None;
219 self.exhausted = false;
220 self.operator.open()
221 }
222
223 fn next_row(&mut self) -> ExecResult<Option<PhysicalRow>> {
224 loop {
225 if let Some(row) = self.batch.next() {
226 return Ok(Some(row));
227 }
228 let Some(batch) = self.operator.next()? else {
229 self.exhausted = true;
230 return Ok(None);
231 };
232 self.batch = batch.rows.into_iter();
233 }
234 }
235
236 fn take_group(&mut self, schema: &RowSchema) -> ExecResult<Option<RowGroup>> {
237 let first = match self.lookahead.take() {
238 Some(row) => row,
239 None => match self.next_row()? {
240 Some(row) => row,
241 None => return Ok(None),
242 },
243 };
244 let mut count = 1_usize;
245 while let Some(row) = self.next_row()? {
246 if compare_rows(&first, &row, schema) == Ordering::Equal {
247 count = count
248 .checked_add(1)
249 .ok_or_else(|| ExecError::Other("set-operation group count overflow".into()))?;
250 } else {
251 self.lookahead = Some(row);
252 break;
253 }
254 }
255 Ok(Some(RowGroup { row: first, count }))
256 }
257
258 fn close(&mut self) -> ExecResult<()> {
259 self.batch = Vec::new().into_iter();
260 self.lookahead = None;
261 self.exhausted = true;
262 self.operator.close()
263 }
264}
265
266fn compare_rows(left: &PhysicalRow, right: &PhysicalRow, schema: &RowSchema) -> Ordering {
267 let left = schema.view(left);
268 let right = schema.view(right);
269 let null = Value::Null;
270 for position in 0..schema.len() {
271 let ordering = left
272 .value_at(position)
273 .unwrap_or(&null)
274 .cmp(right.value_at(position).unwrap_or(&null));
275 if ordering != Ordering::Equal {
276 return ordering;
277 }
278 }
279 Ordering::Equal
280}
281
282pub struct ExternalSetOperation<'a> {
288 left: RowCursor<'a>,
289 right: RowCursor<'a>,
290 kind: SetOpKind,
291 all: bool,
292 schema: RowSchema,
293 left_group: Option<RowGroup>,
294 right_group: Option<RowGroup>,
295 pending_row: Option<PhysicalRow>,
296 pending_count: usize,
297 union_all_left_done: bool,
298}
299
300impl<'a> ExternalSetOperation<'a> {
301 pub fn new(
302 left: Box<dyn PhysicalOperator + 'a>,
303 right: Box<dyn PhysicalOperator + 'a>,
304 kind: SetOpKind,
305 all: bool,
306 work_mem_bytes: usize,
307 ) -> ExecResult<Self> {
308 let output_types = set_operation_types(left.row_schema(), right.row_schema())?;
309 Self::new_with_types(left, right, kind, all, output_types, work_mem_bytes)
310 }
311
312 pub fn new_with_types(
313 left: Box<dyn PhysicalOperator + 'a>,
314 right: Box<dyn PhysicalOperator + 'a>,
315 kind: SetOpKind,
316 all: bool,
317 output_types: Vec<Option<ColumnType>>,
318 work_mem_bytes: usize,
319 ) -> ExecResult<Self> {
320 let output = left.schema().to_vec();
321 let left: Box<dyn PhysicalOperator + 'a> =
322 Box::new(AlignSchema::new(left, output.clone(), &output_types)?);
323 let right: Box<dyn PhysicalOperator + 'a> =
324 Box::new(AlignSchema::new(right, output.clone(), &output_types)?);
325 let (left, right) = if matches!((kind, all), (SetOpKind::Union, true)) {
326 (left, right)
327 } else {
328 let keys = output
329 .iter()
330 .map(|column| SortKey {
331 expr: ScalarExpr::Column(column.clone()),
332 descending: false,
333 nulls_first: Some(true),
334 })
335 .collect::<Vec<_>>();
336 let evaluator = Arc::new(ColumnEvaluator);
337 let per_input = (work_mem_bytes / 2).max(1);
340 (
341 Box::new(ExternalSort::new(
342 left,
343 keys.clone(),
344 evaluator.clone(),
345 None,
346 per_input,
347 )) as Box<dyn PhysicalOperator + 'a>,
348 Box::new(ExternalSort::new(right, keys, evaluator, None, per_input))
349 as Box<dyn PhysicalOperator + 'a>,
350 )
351 };
352 Ok(Self {
353 left: RowCursor::new(left),
354 right: RowCursor::new(right),
355 kind,
356 all,
357 schema: RowSchema::with_types(output, output_types),
358 left_group: None,
359 right_group: None,
360 pending_row: None,
361 pending_count: 0,
362 union_all_left_done: false,
363 })
364 }
365
366 fn next_union_all(&mut self) -> ExecResult<Option<Batch>> {
367 let mut rows = Vec::with_capacity(DEFAULT_BATCH_SIZE);
368 while rows.len() < DEFAULT_BATCH_SIZE {
369 let next = if self.union_all_left_done {
370 self.right.next_row()?
371 } else if let Some(row) = self.left.next_row()? {
372 Some(row)
373 } else {
374 self.union_all_left_done = true;
375 self.right.next_row()?
376 };
377 let Some(row) = next else {
378 break;
379 };
380 rows.push(row.without_lock_origins());
381 }
382 if rows.is_empty() {
383 Ok(None)
384 } else {
385 Ok(Some(Batch::from_physical_rows(self.schema.clone(), rows)))
386 }
387 }
388
389 fn load_groups(&mut self) -> ExecResult<()> {
390 if self.left_group.is_none() && !self.left.exhausted {
391 self.left_group = self.left.take_group(&self.schema)?;
392 }
393 if self.right_group.is_none() && !self.right.exhausted {
394 self.right_group = self.right.take_group(&self.schema)?;
395 }
396 Ok(())
397 }
398
399 fn take_left_group(&mut self) -> ExecResult<RowGroup> {
400 self.left_group
401 .take()
402 .ok_or_else(|| ExecError::Other("set-operation selected a missing left group".into()))
403 }
404
405 fn take_right_group(&mut self) -> ExecResult<RowGroup> {
406 self.right_group
407 .take()
408 .ok_or_else(|| ExecError::Other("set-operation selected a missing right group".into()))
409 }
410
411 fn choose_group(&mut self) -> ExecResult<Option<(PhysicalRow, usize)>> {
412 self.load_groups()?;
413 let ordering = match (&self.left_group, &self.right_group) {
414 (Some(left), Some(right)) => Some(compare_rows(&left.row, &right.row, &self.schema)),
415 (Some(_), None) => Some(Ordering::Less),
416 (None, Some(_)) => Some(Ordering::Greater),
417 (None, None) => None,
418 };
419 let Some(ordering) = ordering else {
420 return Ok(None);
421 };
422
423 let selected = match (self.kind, ordering) {
424 (SetOpKind::Union, Ordering::Less) => {
425 let group = self.take_left_group()?;
426 Some((group.row, 1))
427 }
428 (SetOpKind::Union, Ordering::Greater) => {
429 let group = self.take_right_group()?;
430 Some((group.row, 1))
431 }
432 (SetOpKind::Union, Ordering::Equal) => {
433 let group = self.take_left_group()?;
434 self.right_group = None;
435 Some((group.row, 1))
436 }
437 (SetOpKind::Intersect, Ordering::Less) => {
438 self.left_group = None;
439 None
440 }
441 (SetOpKind::Intersect | SetOpKind::Except, Ordering::Greater) => {
442 self.right_group = None;
443 None
444 }
445 (SetOpKind::Intersect, Ordering::Equal) => {
446 let left = self.take_left_group()?;
447 let right = self.take_right_group()?;
448 Some((
449 left.row,
450 if self.all {
451 left.count.min(right.count)
452 } else {
453 1
454 },
455 ))
456 }
457 (SetOpKind::Except, Ordering::Less) => {
458 let left = self.take_left_group()?;
459 Some((left.row, if self.all { left.count } else { 1 }))
460 }
461 (SetOpKind::Except, Ordering::Equal) => {
462 let left = self.take_left_group()?;
463 let right = self.take_right_group()?;
464 let count = if self.all {
465 left.count.saturating_sub(right.count)
466 } else {
467 0
468 };
469 Some((left.row, count))
470 }
471 };
472 Ok(selected.filter(|(_, count)| *count > 0))
473 }
474}
475
476impl PhysicalOperator for ExternalSetOperation<'_> {
477 fn row_schema(&self) -> &RowSchema {
478 &self.schema
479 }
480
481 fn open(&mut self) -> ExecResult<()> {
482 self.left_group = None;
483 self.right_group = None;
484 self.pending_row = None;
485 self.pending_count = 0;
486 self.union_all_left_done = false;
487 self.left.open()?;
488 self.right.open()
489 }
490
491 fn next(&mut self) -> ExecResult<Option<Batch>> {
492 if matches!((self.kind, self.all), (SetOpKind::Union, true)) {
493 return self.next_union_all();
494 }
495 let mut rows = Vec::with_capacity(DEFAULT_BATCH_SIZE);
496 while rows.len() < DEFAULT_BATCH_SIZE {
497 if self.pending_count > 0 {
498 let row = self.pending_row.as_ref().ok_or_else(|| {
499 ExecError::Other(
500 "set-operation has pending multiplicity without a pending row".into(),
501 )
502 })?;
503 rows.push(row.clone());
504 self.pending_count -= 1;
505 if self.pending_count == 0 {
506 self.pending_row = None;
507 }
508 continue;
509 }
510 match self.choose_group()? {
511 Some((row, count)) => {
512 self.pending_row = Some(row.without_lock_origins());
513 self.pending_count = count;
514 }
515 None if self.left.exhausted && self.right.exhausted => break,
516 None => {}
517 }
518 }
519 if rows.is_empty() {
520 Ok(None)
521 } else {
522 Ok(Some(Batch::from_physical_rows(self.schema.clone(), rows)))
523 }
524 }
525
526 fn close(&mut self) -> ExecResult<()> {
527 self.left_group = None;
528 self.right_group = None;
529 self.pending_row = None;
530 self.pending_count = 0;
531 let left = self.left.close();
532 let right = self.right.close();
533 crate::physical::with_cleanup(left, right, "close right set-operation input")
534 }
535}
536
537#[cfg(test)]
538mod tests {
539 use super::*;
540 use crate::physical::run_to_rows;
541 use crate::scan::TableScan;
542
543 fn row(value: i64) -> ResultRow {
544 [("v".into(), Value::Int(value))].into_iter().collect()
545 }
546
547 fn execute(kind: SetOpKind, all: bool, left: &[i64], right: &[i64]) -> Vec<i64> {
548 let left = TableScan::from_rows(vec!["v".into()], left.iter().copied().map(row).collect());
549 let right = TableScan::from_rows(
550 vec!["other".into()],
551 right
552 .iter()
553 .copied()
554 .map(|value| [("other".into(), Value::Int(value))].into_iter().collect())
555 .collect(),
556 );
557 let mut set =
558 ExternalSetOperation::new(Box::new(left), Box::new(right), kind, all, 1).unwrap();
559 run_to_rows(&mut set)
560 .unwrap()
561 .1
562 .into_iter()
563 .map(|row| match row.get("v") {
564 Some(Value::Int(value)) => *value,
565 value => panic!("unexpected set value: {value:?}"),
566 })
567 .collect()
568 }
569
570 #[test]
571 fn external_set_semantics_include_bag_multiplicity() {
572 assert_eq!(
573 execute(SetOpKind::Union, false, &[2, 1, 1], &[3, 2]),
574 vec![1, 2, 3]
575 );
576 assert_eq!(
577 execute(SetOpKind::Union, true, &[2, 1, 1], &[3, 2]),
578 vec![2, 1, 1, 3, 2]
579 );
580 assert_eq!(
581 execute(SetOpKind::Intersect, true, &[1, 1, 1, 2], &[1, 1, 3]),
582 vec![1, 1]
583 );
584 assert_eq!(
585 execute(SetOpKind::Except, true, &[1, 1, 1, 2], &[1, 1, 3]),
586 vec![1, 2]
587 );
588 assert_eq!(
589 execute(SetOpKind::Except, false, &[1, 1, 2], &[1, 3]),
590 vec![2]
591 );
592 }
593
594 #[test]
595 fn set_inputs_compact_schema_only_projections_before_alignment() {
596 let left = TableScan::from_rows(
597 vec!["v".into(), "hidden".into()],
598 vec![[
599 ("v".into(), Value::Int(1)),
600 ("hidden".into(), Value::Int(99)),
601 ]
602 .into_iter()
603 .collect()],
604 );
605 let left: Box<dyn PhysicalOperator> = Box::new(crate::ColumnSelection::with_positions(
606 Box::new(left),
607 vec![("v".into(), 0)],
608 ));
609 let right = TableScan::from_rows(vec!["v".into()], vec![row(2)]);
610 let mut set =
611 ExternalSetOperation::new(left, Box::new(right), SetOpKind::Union, true, 1).unwrap();
612 let rows = run_to_rows(&mut set).unwrap().1;
613 assert_eq!(rows, vec![row(1), row(2)]);
614 }
615}