1use std::collections::HashMap;
8use std::sync::Arc;
9
10use arrow_array::cast::AsArray as _;
11use arrow_array::types::{
12 Int8Type, Int16Type, Int32Type, Int64Type, UInt8Type, UInt16Type, UInt32Type, UInt64Type,
13};
14use arrow_array::{Array, ArrayRef, ArrowPrimitiveType, RecordBatch, UInt32Array};
15use arrow_row::{RowConverter, Rows, SortField};
16use arrow_schema::{DataType, Field, Schema, SchemaRef};
17use arrow_select::concat::concat_batches;
18use arrow_select::take::take;
19use futures::{StreamExt as _, TryStreamExt as _, stream};
20use tokio::sync::mpsc;
21use tokio::task::{JoinHandle, spawn};
22use vortex_array::expr::{
23 Expression as VortexExpression, and, and_collect, col, gt_eq, lit, lt_eq,
24};
25use vortex_array::scalar::Scalar;
26
27use crate::scandal::RecordBatchStream;
28use crate::scandal::error::ScandalError;
29use crate::scandal::types::FragmentReceipt;
30use crate::scandal_backend::{RowEdit, reader_stream};
31use crate::table_write::ROW_ID_COLUMN;
32use crate::{Error, Expression, IntoArrowStream, ScanOptions, Table, TableWrite};
33
34#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct MergeActions {
37 pub matched: Matched,
39 pub columns: Option<Vec<String>>,
41 pub not_matched: NotMatched,
43 pub not_matched_by_source: BySource,
45}
46
47impl Default for MergeActions {
48 fn default() -> Self {
50 Self {
51 matched: Matched::Patch,
52 columns: None,
53 not_matched: NotMatched::Insert,
54 not_matched_by_source: BySource::Keep,
55 }
56 }
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub enum Matched {
62 Patch,
64 Delete,
66 Ignore,
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub enum NotMatched {
73 Insert,
75 Ignore,
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq)]
81pub enum BySource {
82 Keep,
84 Delete,
86}
87
88#[derive(Clone, Debug)]
90pub struct MergedRows {
91 pub table: Table,
93 pub inserted: u64,
95 pub updated: u64,
97 pub deleted: u64,
99}
100
101impl Table {
102 pub async fn merge_rows(
112 &self,
113 source: impl IntoArrowStream,
114 on: &[&str],
115 actions: MergeActions,
116 ) -> Result<MergedRows, Error> {
117 let reader = source.into_arrow_stream()?;
118 let source_schema = reader.schema();
119 let batches = reader_stream(reader).try_collect::<Vec<_>>().await?;
120 let source = concat_batches(&source_schema, &batches).map_err(Error::arrow)?;
121 let schema = self.schema().await?;
122 let merge = Merge::new(&schema, source, on, &actions)?;
123 merge.write(self).await
124 }
125}
126
127struct Merge {
129 source: RecordBatch,
130 on: Vec<String>,
131 keys: RowConverter,
132 index: HashMap<Box<[u8]>, usize>,
134 matched: Matched,
135 not_matched: NotMatched,
136 by_source: BySource,
137 updates: Option<Updates>,
140 table_schema: SchemaRef,
142}
143
144struct Updates {
146 schema: SchemaRef,
148 converter: RowConverter,
149 values: Rows,
150}
151
152#[derive(Debug, Default)]
154struct BatchEdits {
155 updates: Option<RecordBatch>,
157 deletions: Option<RecordBatch>,
159}
160
161impl Merge {
162 fn new(
164 schema: &Schema,
165 source: RecordBatch,
166 on: &[&str],
167 actions: &MergeActions,
168 ) -> Result<Self, Error> {
169 if on.is_empty() {
170 return Err(Error::invalid(
171 "a merge matches rows on at least one column",
172 ));
173 }
174 if u32::try_from(source.num_rows()).is_err() {
175 return Err(Error::invalid("a merge source holds fewer than 2^32 rows"));
176 }
177 let source_schema = source.schema();
178 for key in on {
179 same_type(schema, &source_schema, key)?;
180 }
181 let key_columns = columns(&source, on)?;
182 let keys = converter(&key_columns)?;
183 let index = key_index(&keys, &key_columns)?;
184 let updates = match actions.matched {
185 Matched::Patch => Updates::new(schema, &source, on, actions)?,
186 Matched::Delete | Matched::Ignore => None,
187 };
188 if actions.not_matched == NotMatched::Insert {
189 check_insertable(schema, &source_schema)?;
190 }
191 Ok(Self {
192 source,
193 on: on.iter().map(|key| (*key).to_owned()).collect(),
194 keys,
195 index,
196 matched: actions.matched,
197 not_matched: actions.not_matched,
198 by_source: actions.not_matched_by_source,
199 updates,
200 table_schema: Arc::new(schema.clone()),
201 })
202 }
203
204 async fn write(self, table: &Table) -> Result<MergedRows, Error> {
207 let mut write = table.writer().await?;
208 let mut matched_sources = vec![false; self.source.num_rows()];
209 let (mut updated, mut deleted) = (0, 0);
210 if self.reads_table() {
211 let scan = table
212 .scan(ScanOptions {
213 columns: Some(self.scanned_columns()),
214 filter: self.key_filter()?,
215 row_id_column: Some(ROW_ID_COLUMN.to_owned()),
216 ..Default::default()
217 })
218 .await?;
219 let mut updates = self
220 .updates
221 .as_ref()
222 .map(|updates| LazyPart::new(RowEdit::Patch, Arc::clone(&updates.schema)));
223 let mut deletions = LazyPart::new(RowEdit::Delete, row_id_schema());
224 let mut batches = scan.into_stream();
225 while let Some(batch) = batches.next_batch().await {
226 let edits = self.edit_batch(&batch?, &mut matched_sources)?;
227 if let (Some(part), Some(rows)) = (updates.as_mut(), edits.updates) {
228 updated += rows.num_rows() as u64;
229 part.send(&mut write, rows).await?;
230 }
231 if let Some(rows) = edits.deletions {
232 deleted += rows.num_rows() as u64;
233 deletions.send(&mut write, rows).await?;
234 }
235 }
236 if let Some(part) = updates {
237 write.add_receipts(part.finish().await?);
238 }
239 write.add_receipts(deletions.finish().await?);
240 }
241 let inserts = self.inserted_rows(&matched_sources)?;
242 let inserted = inserts.as_ref().map_or(0, |rows| rows.num_rows() as u64);
243 if let Some(rows) = inserts {
244 write.append(rows).await?;
245 }
246 if inserted + updated + deleted == 0 {
247 return Ok(MergedRows {
248 table: table.clone(),
249 inserted,
250 updated,
251 deleted,
252 });
253 }
254 Ok(MergedRows {
255 table: write.finish().await?,
256 inserted,
257 updated,
258 deleted,
259 })
260 }
261
262 fn reads_table(&self) -> bool {
264 let changes_matches = self.matched == Matched::Delete || self.updates.is_some();
265 self.by_source == BySource::Delete
266 || (!self.index.is_empty()
267 && (changes_matches || self.not_matched == NotMatched::Insert))
268 }
269
270 fn scanned_columns(&self) -> Vec<String> {
272 let mut scanned = self.on.clone();
273 if let Some(updates) = &self.updates {
274 scanned.extend(
275 updates
276 .updated_columns()
277 .filter(|column| !self.on.contains(column))
278 .cloned(),
279 );
280 }
281 scanned
282 }
283
284 fn key_filter(&self) -> Result<Option<Expression>, Error> {
287 if self.by_source == BySource::Delete {
288 return Ok(None);
289 }
290 let terms = self
291 .on
292 .iter()
293 .filter_map(|key| {
294 let column = self.source.column_by_name(key)?;
295 let (min, max) = key_bounds(column.as_ref())?;
296 Some(and(
297 gt_eq(col(key.as_str()), min),
298 lt_eq(col(key.as_str()), max),
299 ))
300 })
301 .collect::<Vec<_>>();
302 Ok(and_collect(terms).map(Expression::new).transpose()?)
303 }
304
305 fn edit_batch(
308 &self,
309 batch: &RecordBatch,
310 matched_sources: &mut [bool],
311 ) -> Result<BatchEdits, Error> {
312 let row_ids = batch
313 .column_by_name(ROW_ID_COLUMN)
314 .ok_or_else(|| Error::Protocol(format!("the scan returned no `{ROW_ID_COLUMN}`")))?;
315 let key_columns = columns(batch, &self.on)?;
316 let keys = self
317 .keys
318 .convert_columns(&key_columns)
319 .map_err(Error::arrow)?;
320 let (mut changed, mut sources, mut deleted) = (Vec::new(), Vec::new(), Vec::new());
321 for row in 0..batch.num_rows() {
322 let source = key_columns
323 .iter()
324 .all(|key| key.is_valid(row))
325 .then(|| self.index.get(keys.row(row).as_ref()))
326 .flatten();
327 let row = u32::try_from(row).map_err(Error::arrow)?;
328 match source {
329 Some(&source) => {
330 matched_sources[source] = true;
331 match self.matched {
332 Matched::Patch => {
333 changed.push(row);
334 sources.push(source as u32);
335 }
336 Matched::Delete => deleted.push(row),
337 Matched::Ignore => {}
338 }
339 }
340 None if self.by_source == BySource::Delete => deleted.push(row),
341 None => {}
342 }
343 }
344 let updates = match &self.updates {
345 Some(updates) => {
346 updates.changed_rows(batch, row_ids, changed, sources, &self.source)?
347 }
348 None => None,
349 };
350 Ok(BatchEdits {
351 updates,
352 deletions: row_id_rows(row_ids, deleted)?,
353 })
354 }
355
356 fn inserted_rows(&self, matched_sources: &[bool]) -> Result<Option<RecordBatch>, Error> {
359 if self.not_matched == NotMatched::Ignore {
360 return Ok(None);
361 }
362 let unmatched = (0..self.source.num_rows() as u32)
363 .filter(|row| !matched_sources[*row as usize])
364 .collect::<Vec<_>>();
365 if unmatched.is_empty() {
366 return Ok(None);
367 }
368 let indices = UInt32Array::from(unmatched);
369 let columns =
370 self.table_schema
371 .fields()
372 .iter()
373 .map(|field| {
374 let column = self.source.column_by_name(field.name()).ok_or_else(|| {
375 Error::invalid(format!("source has no `{}`", field.name()))
376 })?;
377 take(column.as_ref(), &indices, None).map_err(Error::arrow)
378 })
379 .collect::<Result<Vec<_>, _>>()?;
380 RecordBatch::try_new(Arc::clone(&self.table_schema), columns)
381 .map(Some)
382 .map_err(Error::arrow)
383 }
384}
385
386impl Updates {
387 fn new(
389 schema: &Schema,
390 source: &RecordBatch,
391 on: &[&str],
392 actions: &MergeActions,
393 ) -> Result<Option<Self>, Error> {
394 let source_schema = source.schema();
395 let names = match &actions.columns {
396 Some(names) => {
397 if let Some(key) = names.iter().find(|name| on.contains(&name.as_str())) {
398 return Err(Error::invalid(format!(
399 "a merge does not update its key `{key}`"
400 )));
401 }
402 names.clone()
403 }
404 None => source_schema
405 .fields()
406 .iter()
407 .map(|field| field.name().clone())
408 .filter(|name| !on.contains(&name.as_str()))
409 .collect(),
410 };
411 if names.is_empty() {
412 return Ok(None);
413 }
414 let mut fields = vec![row_id_field()];
415 for name in &names {
416 fields.push(Arc::new(same_type(schema, &source_schema, name)?.clone()));
417 }
418 let source_columns = columns(source, &names)?;
419 let converter = converter(&source_columns)?;
420 let values = converter
421 .convert_columns(&source_columns)
422 .map_err(Error::arrow)?;
423 Ok(Some(Self {
424 schema: Arc::new(Schema::new(fields)),
425 converter,
426 values,
427 }))
428 }
429
430 fn updated_columns(&self) -> impl Iterator<Item = &String> {
432 self.schema.fields()[1..].iter().map(|field| field.name())
433 }
434
435 fn changed_rows(
438 &self,
439 batch: &RecordBatch,
440 row_ids: &ArrayRef,
441 table_rows: Vec<u32>,
442 source_rows: Vec<u32>,
443 source: &RecordBatch,
444 ) -> Result<Option<RecordBatch>, Error> {
445 if table_rows.is_empty() {
446 return Ok(None);
447 }
448 let names = self.updated_columns().cloned().collect::<Vec<_>>();
449 let current = self
450 .converter
451 .convert_columns(&columns(batch, &names)?)
452 .map_err(Error::arrow)?;
453 let (table_rows, source_rows): (Vec<u32>, Vec<u32>) = table_rows
454 .into_iter()
455 .zip(source_rows)
456 .filter(|(table, source)| {
457 current.row(*table as usize) != self.values.row(*source as usize)
458 })
459 .unzip();
460 if table_rows.is_empty() {
461 return Ok(None);
462 }
463 let (table_rows, source_rows) = (
464 UInt32Array::from(table_rows),
465 UInt32Array::from(source_rows),
466 );
467 let mut values = vec![take(row_ids.as_ref(), &table_rows, None).map_err(Error::arrow)?];
468 for column in columns(source, &names)? {
469 values.push(take(column.as_ref(), &source_rows, None).map_err(Error::arrow)?);
470 }
471 RecordBatch::try_new(Arc::clone(&self.schema), values)
472 .map(Some)
473 .map_err(Error::arrow)
474 }
475}
476
477struct LazyPart {
480 edit: RowEdit,
481 schema: SchemaRef,
482 request: Option<StartedPart>,
483}
484
485type StartedPart = (
486 mpsc::Sender<Result<RecordBatch, ScandalError>>,
487 JoinHandle<Result<Vec<FragmentReceipt>, Error>>,
488);
489
490const PART_BUFFER: usize = 2;
492
493impl LazyPart {
494 fn new(edit: RowEdit, schema: SchemaRef) -> Self {
495 Self {
496 edit,
497 schema,
498 request: None,
499 }
500 }
501
502 async fn send(&mut self, write: &mut TableWrite, rows: RecordBatch) -> Result<(), Error> {
504 let (sender, request) = match self.request.take() {
505 Some(started) => started,
506 None => {
507 let (sender, receiver) = mpsc::channel(PART_BUFFER);
508 let request = write.start_edit(
509 self.edit,
510 Arc::clone(&self.schema),
511 receiver_stream(receiver),
512 )?;
513 (sender, spawn(request))
514 }
515 };
516 if sender.send(Ok(rows)).await.is_err() {
517 return Err(match joined(request).await {
518 Err(error) => error,
519 Ok(_) => Error::Protocol("a write request ended before reading its rows".into()),
520 });
521 }
522 self.request = Some((sender, request));
523 Ok(())
524 }
525
526 async fn finish(self) -> Result<Vec<FragmentReceipt>, Error> {
528 let Some((sender, request)) = self.request else {
529 return Ok(Vec::new());
530 };
531 drop(sender);
532 joined(request).await
533 }
534}
535
536async fn joined(
537 request: JoinHandle<Result<Vec<FragmentReceipt>, Error>>,
538) -> Result<Vec<FragmentReceipt>, Error> {
539 request
540 .await
541 .map_err(|error| Error::Protocol(format!("a write request stopped: {error}")))?
542}
543
544fn receiver_stream(
545 receiver: mpsc::Receiver<Result<RecordBatch, ScandalError>>,
546) -> RecordBatchStream {
547 stream::unfold(receiver, |mut receiver| async move {
548 receiver.recv().await.map(|batch| (batch, receiver))
549 })
550 .boxed()
551}
552
553fn row_id_field() -> Arc<Field> {
554 Arc::new(Field::new(ROW_ID_COLUMN, DataType::UInt64, false))
555}
556
557fn row_id_schema() -> SchemaRef {
558 Arc::new(Schema::new(vec![row_id_field()]))
559}
560
561fn row_id_rows(row_ids: &ArrayRef, rows: Vec<u32>) -> Result<Option<RecordBatch>, Error> {
563 if rows.is_empty() {
564 return Ok(None);
565 }
566 let ids = take(row_ids.as_ref(), &UInt32Array::from(rows), None).map_err(Error::arrow)?;
567 RecordBatch::try_new(row_id_schema(), vec![ids])
568 .map(Some)
569 .map_err(Error::arrow)
570}
571
572fn columns(batch: &RecordBatch, names: &[impl AsRef<str>]) -> Result<Vec<ArrayRef>, Error> {
574 names
575 .iter()
576 .map(|name| {
577 batch
578 .column_by_name(name.as_ref())
579 .cloned()
580 .ok_or_else(|| Error::invalid(format!("no column `{}`", name.as_ref())))
581 })
582 .collect()
583}
584
585fn same_type<'a>(table: &'a Schema, source: &Schema, column: &str) -> Result<&'a Field, Error> {
587 let field = table
588 .field_with_name(column)
589 .map_err(|_| Error::invalid(format!("the table has no column `{column}`")))?;
590 let given = source
591 .field_with_name(column)
592 .map_err(|_| Error::invalid(format!("the merge source has no column `{column}`")))?;
593 if field.data_type() != given.data_type() {
594 return Err(Error::invalid(format!(
595 "the merge source's `{column}` holds {}, not the stored {}",
596 given.data_type(),
597 field.data_type()
598 )));
599 }
600 Ok(field)
601}
602
603fn check_insertable(table: &Schema, source: &Schema) -> Result<(), Error> {
606 for field in table.fields() {
607 same_type(table, source, field.name())?;
608 }
609 if let Some(extra) = source
610 .fields()
611 .iter()
612 .find(|field| table.field_with_name(field.name()).is_err())
613 {
614 return Err(Error::invalid(format!(
615 "inserting needs the table's columns only; the source also holds `{}`",
616 extra.name()
617 )));
618 }
619 Ok(())
620}
621
622fn converter(columns: &[ArrayRef]) -> Result<RowConverter, Error> {
623 RowConverter::new(
624 columns
625 .iter()
626 .map(|column| SortField::new(column.data_type().clone()))
627 .collect(),
628 )
629 .map_err(Error::arrow)
630}
631
632fn key_index(
634 converter: &RowConverter,
635 keys: &[ArrayRef],
636) -> Result<HashMap<Box<[u8]>, usize>, Error> {
637 let rows = converter.convert_columns(keys).map_err(Error::arrow)?;
638 let mut index = HashMap::with_capacity(rows.num_rows());
639 for row in 0..rows.num_rows() {
640 if !keys.iter().all(|key| key.is_valid(row)) {
641 continue;
642 }
643 if let Some(earlier) = index.insert(Box::from(rows.row(row).as_ref()), row) {
644 return Err(Error::invalid(format!(
645 "the merge source holds one key in rows {earlier} and {row}"
646 )));
647 }
648 }
649 Ok(index)
650}
651
652fn key_bounds(column: &dyn Array) -> Option<(VortexExpression, VortexExpression)> {
654 match column.data_type() {
655 DataType::Int8 => primitive_bounds::<Int8Type>(column),
656 DataType::Int16 => primitive_bounds::<Int16Type>(column),
657 DataType::Int32 => primitive_bounds::<Int32Type>(column),
658 DataType::Int64 => primitive_bounds::<Int64Type>(column),
659 DataType::UInt8 => primitive_bounds::<UInt8Type>(column),
660 DataType::UInt16 => primitive_bounds::<UInt16Type>(column),
661 DataType::UInt32 => primitive_bounds::<UInt32Type>(column),
662 DataType::UInt64 => primitive_bounds::<UInt64Type>(column),
663 DataType::Utf8 => string_bounds(column.as_string::<i32>().iter()),
664 DataType::LargeUtf8 => string_bounds(column.as_string::<i64>().iter()),
665 DataType::Utf8View => string_bounds(column.as_string_view().iter()),
666 _ => None,
667 }
668}
669
670fn primitive_bounds<T: ArrowPrimitiveType>(
671 column: &dyn Array,
672) -> Option<(VortexExpression, VortexExpression)>
673where
674 T::Native: Into<Scalar> + PartialOrd,
675{
676 let (min, max) = bounds(column.as_primitive::<T>().iter().flatten())?;
677 Some((lit(min), lit(max)))
678}
679
680fn string_bounds<'a>(
681 values: impl Iterator<Item = Option<&'a str>>,
682) -> Option<(VortexExpression, VortexExpression)> {
683 let (min, max) = bounds(values.flatten())?;
684 Some((lit(min), lit(max)))
685}
686
687fn bounds<T: PartialOrd + Copy>(values: impl Iterator<Item = T>) -> Option<(T, T)> {
688 values.fold(None, |bounds, value| match bounds {
689 None => Some((value, value)),
690 Some((min, max)) => Some((
691 if value < min { value } else { min },
692 if value > max { value } else { max },
693 )),
694 })
695}
696
697#[cfg(test)]
698mod tests {
699 use arrow_array::{Int64Array, StringArray, UInt64Array};
700
701 use super::*;
702
703 fn table_schema() -> Schema {
704 Schema::new(vec![
705 Field::new("id", DataType::Int64, true),
706 Field::new("label", DataType::Utf8, true),
707 ])
708 }
709
710 fn source(ids: Vec<Option<i64>>, labels: Vec<&str>) -> RecordBatch {
711 RecordBatch::try_new(
712 Arc::new(table_schema()),
713 vec![
714 Arc::new(Int64Array::from(ids)),
715 Arc::new(StringArray::from(labels)),
716 ],
717 )
718 .unwrap()
719 }
720
721 fn table_rows(ids: Vec<Option<i64>>, labels: Vec<&str>, row_ids: Vec<u64>) -> RecordBatch {
723 let schema = Schema::new(vec![
724 Field::new("id", DataType::Int64, true),
725 Field::new("label", DataType::Utf8, true),
726 Field::new(ROW_ID_COLUMN, DataType::UInt64, false),
727 ]);
728 RecordBatch::try_new(
729 Arc::new(schema),
730 vec![
731 Arc::new(Int64Array::from(ids)),
732 Arc::new(StringArray::from(labels)),
733 Arc::new(UInt64Array::from(row_ids)),
734 ],
735 )
736 .unwrap()
737 }
738
739 fn row_ids(rows: &RecordBatch) -> Vec<u64> {
740 rows.column_by_name(ROW_ID_COLUMN)
741 .unwrap()
742 .as_primitive::<UInt64Type>()
743 .values()
744 .to_vec()
745 }
746
747 #[test]
748 fn matches_patch_changed_rows_and_insert_unmatched_source_rows() {
749 let merge = Merge::new(
750 &table_schema(),
751 source(
752 vec![Some(1), Some(2), None, Some(4)],
753 vec!["a", "B", "n", "d"],
754 ),
755 &["id"],
756 &MergeActions::default(),
757 )
758 .unwrap();
759 let mut matched = vec![false; 4];
760 let edits = merge
761 .edit_batch(
762 &table_rows(
763 vec![Some(1), Some(2), Some(2), None, Some(3)],
764 vec!["a", "b", "x", "n", "c"],
765 vec![10, 11, 12, 13, 14],
766 ),
767 &mut matched,
768 )
769 .unwrap();
770 let updates = edits.updates.unwrap();
772 assert_eq!(row_ids(&updates), [11, 12]);
773 assert_eq!(
774 updates
775 .column(1)
776 .as_string::<i32>()
777 .iter()
778 .collect::<Vec<_>>(),
779 [Some("B"), Some("B")]
780 );
781 assert!(edits.deletions.is_none());
782 assert_eq!(matched, [true, true, false, false]);
783 let inserted = merge.inserted_rows(&matched).unwrap().unwrap();
784 assert_eq!(
785 inserted
786 .column(1)
787 .as_string::<i32>()
788 .iter()
789 .collect::<Vec<_>>(),
790 [Some("n"), Some("d")]
791 );
792 assert_eq!(inserted.schema().as_ref(), &table_schema());
793 }
794
795 #[test]
796 fn deletes_follow_the_matched_and_by_source_actions() {
797 let actions = MergeActions {
798 matched: Matched::Delete,
799 columns: None,
800 not_matched: NotMatched::Ignore,
801 not_matched_by_source: BySource::Delete,
802 };
803 let merge = Merge::new(
804 &table_schema(),
805 source(vec![Some(1)], vec!["a"]),
806 &["id"],
807 &actions,
808 )
809 .unwrap();
810 assert!(merge.key_filter().unwrap().is_none());
811 let mut matched = vec![false];
812 let edits = merge
813 .edit_batch(
814 &table_rows(
815 vec![Some(1), Some(2), None],
816 vec!["a", "b", "c"],
817 vec![7, 8, 9],
818 ),
819 &mut matched,
820 )
821 .unwrap();
822 assert!(edits.updates.is_none());
823 assert_eq!(row_ids(&edits.deletions.unwrap()), [7, 8, 9]);
824 assert!(merge.inserted_rows(&matched).unwrap().is_none());
825 }
826
827 #[test]
828 fn sources_with_duplicate_keys_or_other_types_are_refused() {
829 let actions = MergeActions::default();
830 let duplicate = source(vec![Some(1), Some(1)], vec!["a", "b"]);
831 assert!(Merge::new(&table_schema(), duplicate, &["id"], &actions).is_err());
832 let nulls = source(vec![None, None], vec!["a", "b"]);
833 assert!(Merge::new(&table_schema(), nulls, &["id"], &actions).is_ok());
834 let retyped = Schema::new(vec![
835 Field::new("id", DataType::Int32, true),
836 Field::new("label", DataType::Utf8, true),
837 ]);
838 assert!(
839 Merge::new(
840 &retyped,
841 source(vec![Some(1)], vec!["a"]),
842 &["id"],
843 &actions
844 )
845 .is_err()
846 );
847 let keyed_update = MergeActions {
848 columns: Some(vec!["id".to_owned()]),
849 ..MergeActions::default()
850 };
851 assert!(
852 Merge::new(
853 &table_schema(),
854 source(vec![Some(1)], vec!["a"]),
855 &["id"],
856 &keyed_update
857 )
858 .is_err()
859 );
860 }
861
862 #[test]
863 fn integer_and_string_keys_bound_the_scan() {
864 let merge = Merge::new(
865 &table_schema(),
866 source(vec![Some(4), None, Some(-2)], vec!["a", "b", "c"]),
867 &["id", "label"],
868 &MergeActions {
869 matched: Matched::Ignore,
870 ..MergeActions::default()
871 },
872 )
873 .unwrap();
874 assert!(merge.key_filter().unwrap().is_some());
875 assert_eq!(merge.scanned_columns(), ["id", "label"]);
876 let ids = Int64Array::from(vec![Some(4), None, Some(-2)]);
877 let (min, max) = key_bounds(&ids).unwrap();
878 assert_eq!((min, max), (lit(-2i64), lit(4i64)));
879 assert!(key_bounds(&Int64Array::from(vec![None::<i64>])).is_none());
880 }
881}