Skip to main content

lance/dataset/write/merge_insert/
inserted_rows.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! Key existence tracking for merge insert conflict detection.
5
6use std::collections::HashSet;
7use std::collections::hash_map::DefaultHasher;
8use std::hash::{Hash, Hasher};
9
10use arrow_array::cast::AsArray;
11use arrow_array::{
12    Array, BinaryArray, LargeBinaryArray, LargeListArray, LargeStringArray, ListArray, RecordBatch,
13    StringArray, StructArray,
14};
15use arrow_schema::DataType;
16use deepsize::DeepSizeOf;
17use lance_core::Result;
18use lance_index::scalar::bloomfilter::sbbf::{Sbbf, SbbfBuilder};
19use lance_table::format::pb;
20
21// Default bloom filter config: 8192 items @ 0.00057 fpp -> 16KiB filter
22pub const BLOOM_FILTER_DEFAULT_NUMBER_OF_ITEMS: u64 = 8192;
23pub const BLOOM_FILTER_DEFAULT_PROBABILITY: f64 = 0.00057;
24
25/// Key value for conflict detection.
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub enum KeyValue {
28    String(String),
29    Int64(i64),
30    UInt64(u64),
31    Binary(Vec<u8>),
32    List(Vec<Self>),
33    Struct(Vec<Self>),
34    Composite(Vec<Self>),
35}
36
37impl KeyValue {
38    pub fn to_bytes(&self) -> Vec<u8> {
39        match self {
40            Self::String(s) => s.as_bytes().to_vec(),
41            Self::Int64(i) => i.to_le_bytes().to_vec(),
42            Self::UInt64(u) => u.to_le_bytes().to_vec(),
43            Self::Binary(b) => b.clone(),
44            Self::List(values) | Self::Struct(values) | Self::Composite(values) => {
45                let mut result = Vec::new();
46                for value in values {
47                    result.extend_from_slice(&value.to_bytes());
48                    result.push(0);
49                }
50                result
51            }
52        }
53    }
54
55    pub fn hash_value(&self) -> u64 {
56        let mut hasher = DefaultHasher::new();
57        self.to_bytes().hash(&mut hasher);
58        hasher.finish()
59    }
60}
61
62/// Builder for KeyExistenceFilter using Split Block Bloom Filter.
63#[derive(Debug, Clone)]
64pub struct KeyExistenceFilterBuilder {
65    sbbf: Sbbf,
66    field_ids: Vec<i32>,
67    item_count: usize,
68}
69
70impl KeyExistenceFilterBuilder {
71    pub fn new(field_ids: Vec<i32>) -> Self {
72        let sbbf = SbbfBuilder::new()
73            .expected_items(BLOOM_FILTER_DEFAULT_NUMBER_OF_ITEMS)
74            .false_positive_probability(BLOOM_FILTER_DEFAULT_PROBABILITY)
75            .build()
76            .expect("Failed to build SBBF");
77        Self {
78            sbbf,
79            field_ids,
80            item_count: 0,
81        }
82    }
83
84    pub fn insert(&mut self, key: KeyValue) -> Result<()> {
85        self.sbbf.insert(&key.to_bytes()[..]);
86        self.item_count += 1;
87        Ok(())
88    }
89
90    pub fn contains(&self, key: &KeyValue) -> bool {
91        self.sbbf.check(&key.to_bytes()[..])
92    }
93
94    pub fn might_intersect(&self, other: &Self) -> Result<bool> {
95        self.sbbf
96            .might_intersect(&other.sbbf)
97            .map_err(|e| lance_core::Error::invalid_input(e.to_string()))
98    }
99
100    pub fn field_ids(&self) -> &[i32] {
101        &self.field_ids
102    }
103
104    pub fn estimated_size_bytes(&self) -> usize {
105        self.sbbf.size_bytes()
106    }
107
108    pub fn len(&self) -> usize {
109        self.item_count
110    }
111
112    pub fn is_empty(&self) -> bool {
113        self.item_count == 0
114    }
115
116    pub fn build(&self) -> KeyExistenceFilter {
117        KeyExistenceFilter {
118            field_ids: self.field_ids.clone(),
119            filter: FilterType::Bloom {
120                bitmap: self.sbbf.to_bytes(),
121                num_bits: (self.sbbf.size_bytes() as u32) * 8,
122                number_of_items: BLOOM_FILTER_DEFAULT_NUMBER_OF_ITEMS,
123                probability: BLOOM_FILTER_DEFAULT_PROBABILITY,
124            },
125        }
126    }
127}
128
129impl From<&KeyExistenceFilterBuilder> for pb::transaction::KeyExistenceFilter {
130    fn from(builder: &KeyExistenceFilterBuilder) -> Self {
131        Self {
132            field_ids: builder.field_ids.clone(),
133            data: Some(pb::transaction::key_existence_filter::Data::Bloom(
134                pb::transaction::BloomFilter {
135                    bitmap: builder.sbbf.to_bytes(),
136                    num_bits: (builder.sbbf.size_bytes() as u32) * 8,
137                    number_of_items: BLOOM_FILTER_DEFAULT_NUMBER_OF_ITEMS,
138                    probability: BLOOM_FILTER_DEFAULT_PROBABILITY,
139                },
140            )),
141        }
142    }
143}
144
145/// Filter type for key existence data.
146#[derive(Debug, Clone, DeepSizeOf, PartialEq)]
147pub enum FilterType {
148    ExactSet(HashSet<u64>),
149    Bloom {
150        bitmap: Vec<u8>,
151        num_bits: u32,
152        number_of_items: u64,
153        probability: f64,
154    },
155}
156
157/// Tracks keys of inserted rows for conflict detection.
158/// Only created when ON columns match the schema's unenforced primary key.
159#[derive(Debug, Clone, DeepSizeOf, PartialEq)]
160pub struct KeyExistenceFilter {
161    pub field_ids: Vec<i32>,
162    pub filter: FilterType,
163}
164
165impl KeyExistenceFilter {
166    pub fn from_bloom_filter(bloom: &KeyExistenceFilterBuilder) -> Self {
167        bloom.build()
168    }
169
170    /// Check if two filters intersect. Returns (has_intersection, might_be_false_positive).
171    /// Errors if bloom filter configs don't match.
172    pub fn intersects(&self, other: &Self) -> Result<(bool, bool)> {
173        match (&self.filter, &other.filter) {
174            (FilterType::ExactSet(a), FilterType::ExactSet(b)) => {
175                Ok((a.iter().any(|h| b.contains(h)), false))
176            }
177            (FilterType::ExactSet(_), FilterType::Bloom { .. })
178            | (FilterType::Bloom { .. }, FilterType::ExactSet(_)) => {
179                // Can't compare different hash schemes, assume intersection
180                Ok((true, true))
181            }
182            (
183                FilterType::Bloom {
184                    bitmap: a_bits,
185                    number_of_items: a_num_items,
186                    probability: a_prob,
187                    ..
188                },
189                FilterType::Bloom {
190                    bitmap: b_bits,
191                    number_of_items: b_num_items,
192                    probability: b_prob,
193                    ..
194                },
195            ) => {
196                if a_num_items != b_num_items || (a_prob - b_prob).abs() > f64::EPSILON {
197                    return Err(lance_core::Error::invalid_input(format!(
198                        "Bloom filter config mismatch: ({}, {}) vs ({}, {})",
199                        a_num_items, a_prob, b_num_items, b_prob
200                    )));
201                }
202                let has = Sbbf::bytes_might_intersect(a_bits, b_bits)
203                    .map_err(|e| lance_core::Error::invalid_input(e.to_string()))?;
204                Ok((has, has))
205            }
206        }
207    }
208}
209
210impl From<&KeyExistenceFilter> for pb::transaction::KeyExistenceFilter {
211    fn from(filter: &KeyExistenceFilter) -> Self {
212        match &filter.filter {
213            FilterType::ExactSet(hashes) => Self {
214                field_ids: filter.field_ids.clone(),
215                data: Some(pb::transaction::key_existence_filter::Data::Exact(
216                    pb::transaction::ExactKeySetFilter {
217                        key_hashes: hashes.iter().copied().collect(),
218                    },
219                )),
220            },
221            FilterType::Bloom {
222                bitmap,
223                num_bits,
224                number_of_items,
225                probability,
226            } => Self {
227                field_ids: filter.field_ids.clone(),
228                data: Some(pb::transaction::key_existence_filter::Data::Bloom(
229                    pb::transaction::BloomFilter {
230                        bitmap: bitmap.clone(),
231                        num_bits: *num_bits,
232                        number_of_items: *number_of_items,
233                        probability: *probability,
234                    },
235                )),
236            },
237        }
238    }
239}
240
241impl TryFrom<&pb::transaction::KeyExistenceFilter> for KeyExistenceFilter {
242    type Error = lance_core::Error;
243
244    fn try_from(message: &pb::transaction::KeyExistenceFilter) -> Result<Self> {
245        let filter = match message.data.as_ref() {
246            Some(pb::transaction::key_existence_filter::Data::Exact(exact)) => {
247                FilterType::ExactSet(exact.key_hashes.iter().copied().collect())
248            }
249            Some(pb::transaction::key_existence_filter::Data::Bloom(b)) => {
250                // Use defaults for backwards compatibility
251                let number_of_items = if b.number_of_items == 0 {
252                    BLOOM_FILTER_DEFAULT_NUMBER_OF_ITEMS
253                } else {
254                    b.number_of_items
255                };
256                let probability = if b.probability == 0.0 {
257                    BLOOM_FILTER_DEFAULT_PROBABILITY
258                } else {
259                    b.probability
260                };
261                FilterType::Bloom {
262                    bitmap: b.bitmap.clone(),
263                    num_bits: b.num_bits,
264                    number_of_items,
265                    probability,
266                }
267            }
268            None => FilterType::ExactSet(HashSet::new()),
269        };
270        Ok(Self {
271            field_ids: message.field_ids.clone(),
272            filter,
273        })
274    }
275}
276
277/// Extract key value from a batch row. Returns None if null or unsupported type.
278pub fn extract_key_value_from_batch(
279    batch: &RecordBatch,
280    row_idx: usize,
281    on_columns: &[String],
282) -> Option<KeyValue> {
283    let mut parts: Vec<KeyValue> = Vec::with_capacity(on_columns.len());
284
285    for col_name in on_columns {
286        let (col_idx, _) = batch.schema().column_with_name(col_name)?;
287        let column = batch.column(col_idx);
288
289        if column.is_null(row_idx) {
290            return None;
291        }
292
293        let key_part = extract_key_value(column, row_idx)?;
294        parts.push(key_part);
295    }
296
297    if parts.is_empty() {
298        None
299    } else if parts.len() == 1 {
300        Some(parts.into_iter().next().unwrap())
301    } else {
302        Some(KeyValue::Composite(parts))
303    }
304}
305
306fn extract_key_value(array: &dyn Array, row_idx: usize) -> Option<KeyValue> {
307    let v = match array.data_type() {
308        DataType::Utf8 => {
309            let arr = array.as_any().downcast_ref::<StringArray>()?;
310            KeyValue::String(arr.value(row_idx).to_string())
311        }
312        DataType::LargeUtf8 => {
313            let arr = array.as_any().downcast_ref::<LargeStringArray>()?;
314            KeyValue::String(arr.value(row_idx).to_string())
315        }
316        DataType::UInt64 => {
317            let arr = array.as_primitive::<arrow_array::types::UInt64Type>();
318            KeyValue::UInt64(arr.value(row_idx))
319        }
320        DataType::Int64 => {
321            let arr = array.as_primitive::<arrow_array::types::Int64Type>();
322            KeyValue::Int64(arr.value(row_idx))
323        }
324        DataType::UInt32 => {
325            let arr = array.as_primitive::<arrow_array::types::UInt32Type>();
326            KeyValue::UInt64(arr.value(row_idx) as u64)
327        }
328        DataType::Int32 => {
329            let arr = array.as_primitive::<arrow_array::types::Int32Type>();
330            KeyValue::Int64(arr.value(row_idx) as i64)
331        }
332        DataType::Binary => {
333            let arr = array.as_any().downcast_ref::<BinaryArray>()?;
334            KeyValue::Binary(arr.value(row_idx).to_vec())
335        }
336        DataType::LargeBinary => {
337            let arr = array.as_any().downcast_ref::<LargeBinaryArray>()?;
338            KeyValue::Binary(arr.value(row_idx).to_vec())
339        }
340        DataType::List(_) => {
341            let list_array = array.as_any().downcast_ref::<ListArray>().unwrap();
342            let values = list_array.value(row_idx);
343
344            let mut elements = Vec::with_capacity(values.len());
345            for i in 0..values.len() {
346                if values.is_null(i) {
347                    return None;
348                }
349                let element = extract_key_value(&values, i)?;
350                elements.push(element);
351            }
352            KeyValue::List(elements)
353        }
354        DataType::LargeList(_) => {
355            let list_array = array.as_any().downcast_ref::<LargeListArray>().unwrap();
356            let values = list_array.value(row_idx);
357
358            let mut elements = Vec::with_capacity(values.len());
359            for i in 0..values.len() {
360                if values.is_null(i) {
361                    return None;
362                }
363                let element = extract_key_value(&values, i)?;
364                elements.push(element);
365            }
366            KeyValue::List(elements)
367        }
368        DataType::Struct(_) => {
369            let struct_array = array.as_any().downcast_ref::<StructArray>()?;
370            let mut elements = Vec::with_capacity(struct_array.num_columns());
371            for i in 0..struct_array.num_columns() {
372                let child = struct_array.column(i);
373                if child.is_null(row_idx) {
374                    return None;
375                }
376                let field_value = extract_key_value(child.as_ref(), row_idx)?;
377                elements.push(field_value);
378            }
379            KeyValue::Struct(elements)
380        }
381        _ => return None,
382    };
383    Some(v)
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389    use std::sync::Arc;
390
391    use arrow_array::builder::{Int32Builder, ListBuilder, StringBuilder};
392    use arrow_array::{Int32Array, RecordBatch, StringArray, StructArray};
393    use arrow_schema::{Field, Schema};
394
395    #[test]
396    fn test_extract_key_value_from_batch_list_int() {
397        let values_builder = Int32Builder::new();
398        let mut list_builder = ListBuilder::new(values_builder);
399
400        list_builder.append_value([Some(1), Some(2)]);
401        list_builder.append_value([Some(3), Some(4), Some(5)]);
402
403        let list_array = list_builder.finish();
404
405        let schema = Arc::new(Schema::new(vec![Field::new(
406            "id",
407            list_array.data_type().clone(),
408            false,
409        )]));
410
411        let batch = RecordBatch::try_new(schema, vec![Arc::new(list_array)])
412            .expect("batch should be valid");
413
414        let key0 = extract_key_value_from_batch(&batch, 0, &[String::from("id")])
415            .expect("first row should produce a key");
416        let key1 = extract_key_value_from_batch(&batch, 1, &[String::from("id")])
417            .expect("second row should produce a key");
418
419        match &key0 {
420            KeyValue::List(values) => {
421                assert_eq!(values.len(), 2);
422                assert_eq!(values[0], KeyValue::Int64(1));
423                assert_eq!(values[1], KeyValue::Int64(2));
424            }
425            other => panic!("expected list key, got {:?}", other),
426        }
427
428        match &key1 {
429            KeyValue::List(values) => {
430                assert_eq!(values.len(), 3);
431                assert_eq!(values[0], KeyValue::Int64(3));
432                assert_eq!(values[1], KeyValue::Int64(4));
433                assert_eq!(values[2], KeyValue::Int64(5));
434            }
435            other => panic!("expected list key, got {:?}", other),
436        }
437
438        assert_ne!(
439            key0.hash_value(),
440            key1.hash_value(),
441            "different list values should hash differently",
442        );
443    }
444
445    #[test]
446    fn test_extract_key_value_from_batch_empty_list() {
447        let values_builder = Int32Builder::new();
448        let mut list_builder = ListBuilder::new(values_builder);
449
450        list_builder.append_value(std::iter::empty::<Option<i32>>());
451
452        let list_array = list_builder.finish();
453
454        let schema = Arc::new(Schema::new(vec![Field::new(
455            "id",
456            list_array.data_type().clone(),
457            false,
458        )]));
459
460        let batch = RecordBatch::try_new(schema, vec![Arc::new(list_array)])
461            .expect("batch should be valid");
462
463        let key = extract_key_value_from_batch(&batch, 0, &[String::from("id")])
464            .expect("empty list should still produce a key");
465
466        match key {
467            KeyValue::List(values) => {
468                assert!(values.is_empty(), "expected empty list");
469            }
470            other => panic!("expected list key, got {:?}", other),
471        }
472    }
473
474    #[test]
475    fn test_extract_key_value_from_batch_list_utf8() {
476        let values_builder = StringBuilder::new();
477        let mut list_builder = ListBuilder::new(values_builder);
478
479        list_builder.append_value([Some("a"), Some("bc")]);
480        list_builder.append_value([Some("de")]);
481
482        let list_array = list_builder.finish();
483
484        let schema = Arc::new(Schema::new(vec![Field::new(
485            "id",
486            list_array.data_type().clone(),
487            false,
488        )]));
489
490        let batch = RecordBatch::try_new(schema, vec![Arc::new(list_array)])
491            .expect("batch should be valid");
492
493        let key0 = extract_key_value_from_batch(&batch, 0, &[String::from("id")])
494            .expect("first row should produce a key");
495        let key1 = extract_key_value_from_batch(&batch, 1, &[String::from("id")])
496            .expect("second row should produce a key");
497
498        match &key0 {
499            KeyValue::List(values) => {
500                assert_eq!(values.len(), 2);
501                assert_eq!(values[0], KeyValue::String("a".to_string()));
502                assert_eq!(values[1], KeyValue::String("bc".to_string()));
503            }
504            other => panic!("expected list key, got {:?}", other),
505        }
506
507        match &key1 {
508            KeyValue::List(values) => {
509                assert_eq!(values.len(), 1);
510                assert_eq!(values[0], KeyValue::String("de".to_string()));
511            }
512            other => panic!("expected list key, got {:?}", other),
513        }
514
515        assert_ne!(
516            key0.hash_value(),
517            key1.hash_value(),
518            "different list values should hash differently",
519        );
520    }
521
522    #[test]
523    fn test_extract_key_value_from_batch_list_with_null_child() {
524        let values_builder = Int32Builder::new();
525        let mut list_builder = ListBuilder::new(values_builder);
526
527        list_builder.append_value([Some(1), Some(2)]);
528        list_builder.append_value([Some(3), None]);
529
530        let list_array = list_builder.finish();
531
532        let schema = Arc::new(Schema::new(vec![Field::new(
533            "id",
534            list_array.data_type().clone(),
535            false,
536        )]));
537
538        let batch = RecordBatch::try_new(schema, vec![Arc::new(list_array)])
539            .expect("batch should be valid");
540
541        let key0 = extract_key_value_from_batch(&batch, 0, &[String::from("id")])
542            .expect("first row should produce a key");
543        let key1 = extract_key_value_from_batch(&batch, 1, &[String::from("id")]);
544
545        match &key0 {
546            KeyValue::List(values) => {
547                assert_eq!(values.len(), 2);
548                assert_eq!(values[0], KeyValue::Int64(1));
549                assert_eq!(values[1], KeyValue::Int64(2));
550            }
551            other => panic!("expected list key, got {:?}", other),
552        }
553
554        assert!(
555            key1.is_none(),
556            "list row with a null child should not produce a key",
557        );
558    }
559
560    #[test]
561    fn test_extract_key_value_from_batch_struct_int() {
562        let a_values = Int32Array::from(vec![1, 3]);
563        let b_values = Int32Array::from(vec![2, 4]);
564
565        let struct_array = StructArray::from(vec![
566            (
567                Arc::new(Field::new("a", arrow_schema::DataType::Int32, false)),
568                Arc::new(a_values) as Arc<dyn arrow_array::Array>,
569            ),
570            (
571                Arc::new(Field::new("b", arrow_schema::DataType::Int32, false)),
572                Arc::new(b_values) as Arc<dyn arrow_array::Array>,
573            ),
574        ]);
575
576        let schema = Arc::new(Schema::new(vec![Field::new(
577            "id",
578            struct_array.data_type().clone(),
579            false,
580        )]));
581
582        let batch = RecordBatch::try_new(schema, vec![Arc::new(struct_array)])
583            .expect("batch should be valid");
584
585        let key0 = extract_key_value_from_batch(&batch, 0, &[String::from("id")])
586            .expect("first row should produce a key");
587        let key1 = extract_key_value_from_batch(&batch, 1, &[String::from("id")])
588            .expect("second row should produce a key");
589
590        match &key0 {
591            KeyValue::Struct(values) => {
592                assert_eq!(values.len(), 2);
593                assert_eq!(values[0], KeyValue::Int64(1));
594                assert_eq!(values[1], KeyValue::Int64(2));
595            }
596            other => panic!("expected struct key, got {:?}", other),
597        }
598
599        match &key1 {
600            KeyValue::Struct(values) => {
601                assert_eq!(values.len(), 2);
602                assert_eq!(values[0], KeyValue::Int64(3));
603                assert_eq!(values[1], KeyValue::Int64(4));
604            }
605            other => panic!("expected struct key, got {:?}", other),
606        }
607
608        assert_ne!(
609            key0.hash_value(),
610            key1.hash_value(),
611            "different struct values should hash differently",
612        );
613    }
614
615    #[test]
616    fn test_extract_key_value_from_batch_struct_utf8() {
617        let first_names = StringArray::from(vec!["alice", "bob"]);
618        let last_names = StringArray::from(vec!["smith", "jones"]);
619
620        let struct_array = StructArray::from(vec![
621            (
622                Arc::new(Field::new("first", arrow_schema::DataType::Utf8, false)),
623                Arc::new(first_names) as Arc<dyn arrow_array::Array>,
624            ),
625            (
626                Arc::new(Field::new("last", arrow_schema::DataType::Utf8, false)),
627                Arc::new(last_names) as Arc<dyn arrow_array::Array>,
628            ),
629        ]);
630
631        let schema = Arc::new(Schema::new(vec![Field::new(
632            "id",
633            struct_array.data_type().clone(),
634            false,
635        )]));
636
637        let batch = RecordBatch::try_new(schema, vec![Arc::new(struct_array)])
638            .expect("batch should be valid");
639
640        let key0 = extract_key_value_from_batch(&batch, 0, &[String::from("id")])
641            .expect("first row should produce a key");
642        let key1 = extract_key_value_from_batch(&batch, 1, &[String::from("id")])
643            .expect("second row should produce a key");
644
645        match &key0 {
646            KeyValue::Struct(values) => {
647                assert_eq!(values.len(), 2);
648                assert_eq!(values[0], KeyValue::String("alice".to_string()));
649                assert_eq!(values[1], KeyValue::String("smith".to_string()));
650            }
651            other => panic!("expected struct key, got {:?}", other),
652        }
653
654        match &key1 {
655            KeyValue::Struct(values) => {
656                assert_eq!(values.len(), 2);
657                assert_eq!(values[0], KeyValue::String("bob".to_string()));
658                assert_eq!(values[1], KeyValue::String("jones".to_string()));
659            }
660            other => panic!("expected struct key, got {:?}", other),
661        }
662
663        assert_ne!(
664            key0.hash_value(),
665            key1.hash_value(),
666            "different struct values should hash differently",
667        );
668    }
669
670    #[test]
671    fn test_extract_key_value_from_batch_struct_with_null_child() {
672        let a_values = Int32Array::from(vec![Some(1), None]);
673        let b_values = Int32Array::from(vec![Some(2), Some(3)]);
674
675        let struct_array = StructArray::from(vec![
676            (
677                Arc::new(Field::new("a", arrow_schema::DataType::Int32, true)),
678                Arc::new(a_values) as Arc<dyn arrow_array::Array>,
679            ),
680            (
681                Arc::new(Field::new("b", arrow_schema::DataType::Int32, true)),
682                Arc::new(b_values) as Arc<dyn arrow_array::Array>,
683            ),
684        ]);
685
686        let schema = Arc::new(Schema::new(vec![Field::new(
687            "id",
688            struct_array.data_type().clone(),
689            false,
690        )]));
691
692        let batch = RecordBatch::try_new(schema, vec![Arc::new(struct_array)])
693            .expect("batch should be valid");
694
695        let key0 = extract_key_value_from_batch(&batch, 0, &[String::from("id")])
696            .expect("first row should produce a key");
697        let key1 = extract_key_value_from_batch(&batch, 1, &[String::from("id")]);
698
699        match &key0 {
700            KeyValue::Struct(values) => {
701                assert_eq!(values.len(), 2);
702                assert_eq!(values[0], KeyValue::Int64(1));
703                assert_eq!(values[1], KeyValue::Int64(2));
704            }
705            other => panic!("expected struct key, got {:?}", other),
706        }
707
708        assert!(
709            key1.is_none(),
710            "struct row with a null child should not produce a key",
711        );
712    }
713}