1use crate::format::key_existence::KeyExistenceFilter;
12use crate::format::pb;
13use crate::format::{BasePath, Fragment, IndexFile, IndexMetadata, overlay::DataOverlayFile};
14use crate::system_index::mem_wal::CompactedSsTable;
15use crate::transaction::{
16 DataOverlayGroup, DataReplacementGroup, Operation, RewriteGroup, RewrittenIndex, Transaction,
17 UpdateMap, UpdateMapEntry, UpdateMode, UpdatedFragmentOffsets, translate_config_updates,
18 translate_schema_metadata_updates,
19};
20use lance_core::datatypes::Schema;
21use lance_core::{Error, Result};
22use lance_file::datatypes::Fields;
23use roaring::RoaringBitmap;
24use std::collections::HashMap;
25use std::sync::Arc;
26use uuid::Uuid;
27
28impl From<&DataReplacementGroup> for pb::transaction::DataReplacementGroup {
29 fn from(DataReplacementGroup(fragment_id, new_file): &DataReplacementGroup) -> Self {
30 Self {
31 fragment_id: *fragment_id,
32 new_file: Some(new_file.into()),
33 }
34 }
35}
36
37impl TryFrom<pb::transaction::DataReplacementGroup> for DataReplacementGroup {
40 type Error = Error;
41
42 fn try_from(message: pb::transaction::DataReplacementGroup) -> Result<Self> {
43 Ok(Self(
44 message.fragment_id,
45 message
46 .new_file
47 .ok_or(Error::invalid_input(
48 "DataReplacementGroup must have a new_file",
49 ))?
50 .try_into()?,
51 ))
52 }
53}
54
55impl From<&DataOverlayGroup> for pb::transaction::DataOverlayGroup {
56 fn from(group: &DataOverlayGroup) -> Self {
57 Self {
58 fragment_id: group.fragment_id,
59 overlays: group
60 .overlays
61 .iter()
62 .map(pb::DataOverlayFile::from)
63 .collect(),
64 }
65 }
66}
67
68impl TryFrom<pb::transaction::DataOverlayGroup> for DataOverlayGroup {
69 type Error = Error;
70
71 fn try_from(message: pb::transaction::DataOverlayGroup) -> Result<Self> {
72 Ok(Self {
73 fragment_id: message.fragment_id,
74 overlays: message
75 .overlays
76 .into_iter()
77 .map(DataOverlayFile::try_from)
78 .collect::<Result<Vec<_>>>()?,
79 })
80 }
81}
82
83impl TryFrom<pb::Transaction> for Transaction {
84 type Error = Error;
85
86 fn try_from(message: pb::Transaction) -> Result<Self> {
87 let operation = match message.operation {
88 Some(pb::transaction::Operation::Append(pb::transaction::Append { fragments })) => {
89 Operation::Append {
90 fragments: fragments
91 .into_iter()
92 .map(Fragment::try_from)
93 .collect::<Result<Vec<_>>>()?,
94 }
95 }
96 Some(pb::transaction::Operation::Clone(pb::transaction::Clone {
97 is_shallow,
98 ref_name,
99 ref_version,
100 ref_path,
101 branch_name,
102 })) => Operation::Clone {
103 is_shallow,
104 ref_name,
105 ref_version,
106 ref_path,
107 branch_name,
108 },
109 Some(pb::transaction::Operation::Delete(pb::transaction::Delete {
110 updated_fragments,
111 deleted_fragment_ids,
112 predicate,
113 })) => Operation::Delete {
114 updated_fragments: updated_fragments
115 .into_iter()
116 .map(Fragment::try_from)
117 .collect::<Result<Vec<_>>>()?,
118 deleted_fragment_ids,
119 predicate,
120 },
121 Some(pb::transaction::Operation::Overwrite(pb::transaction::Overwrite {
122 fragments,
123 schema,
124 schema_metadata: _schema_metadata, config_upsert_values,
126 initial_bases,
127 })) => {
128 let config_upsert_option = if config_upsert_values.is_empty() {
129 None
130 } else {
131 Some(config_upsert_values)
132 };
133
134 Operation::Overwrite {
135 fragments: fragments
136 .into_iter()
137 .map(Fragment::try_from)
138 .collect::<Result<Vec<_>>>()?,
139 schema: Schema::try_from(&Fields(schema))?,
140 config_upsert_values: config_upsert_option,
141 initial_bases: if initial_bases.is_empty() {
142 None
143 } else {
144 Some(initial_bases.into_iter().map(BasePath::from).collect())
145 },
146 }
147 }
148 Some(pb::transaction::Operation::ReserveFragments(
149 pb::transaction::ReserveFragments { num_fragments },
150 )) => Operation::ReserveFragments { num_fragments },
151 Some(pb::transaction::Operation::Rewrite(pb::transaction::Rewrite {
152 old_fragments,
153 new_fragments,
154 groups,
155 rewritten_indices,
156 })) => {
157 let groups = if !groups.is_empty() {
158 groups
159 .into_iter()
160 .map(RewriteGroup::try_from)
161 .collect::<Result<_>>()?
162 } else {
163 vec![RewriteGroup {
164 old_fragments: old_fragments
165 .into_iter()
166 .map(Fragment::try_from)
167 .collect::<Result<Vec<_>>>()?,
168 new_fragments: new_fragments
169 .into_iter()
170 .map(Fragment::try_from)
171 .collect::<Result<Vec<_>>>()?,
172 }]
173 };
174 let rewritten_indices = rewritten_indices
175 .iter()
176 .map(RewrittenIndex::try_from)
177 .collect::<Result<_>>()?;
178
179 Operation::Rewrite {
180 groups,
181 rewritten_indices,
182 frag_reuse_index: None,
183 }
184 }
185 Some(pb::transaction::Operation::CreateIndex(pb::transaction::CreateIndex {
186 new_indices,
187 removed_indices,
188 })) => Operation::CreateIndex {
189 new_indices: new_indices
190 .into_iter()
191 .map(IndexMetadata::try_from)
192 .collect::<Result<_>>()?,
193 removed_indices: removed_indices
194 .into_iter()
195 .map(IndexMetadata::try_from)
196 .collect::<Result<_>>()?,
197 },
198 Some(pb::transaction::Operation::Merge(pb::transaction::Merge {
199 fragments,
200 schema,
201 schema_metadata: _schema_metadata, preserves_nullability,
203 })) => Operation::Merge {
204 fragments: fragments
205 .into_iter()
206 .map(Fragment::try_from)
207 .collect::<Result<Vec<_>>>()?,
208 schema: Schema::try_from(&Fields(schema))?,
209 preserves_nullability,
213 },
214 Some(pb::transaction::Operation::Restore(pb::transaction::Restore { version })) => {
215 Operation::Restore { version }
216 }
217 Some(pb::transaction::Operation::Update(pb::transaction::Update {
218 removed_fragment_ids,
219 updated_fragments,
220 new_fragments,
221 fields_modified,
222 compacted_sstables,
223 fields_for_preserving_frag_bitmap,
224 update_mode,
225 inserted_rows,
226 updated_fragment_offsets,
227 updated_fragment_offset_bitmaps,
228 })) => Operation::Update {
229 removed_fragment_ids,
230 updated_fragments: updated_fragments
231 .into_iter()
232 .map(Fragment::try_from)
233 .collect::<Result<Vec<_>>>()?,
234 new_fragments: new_fragments
235 .into_iter()
236 .map(Fragment::try_from)
237 .collect::<Result<Vec<_>>>()?,
238 fields_modified,
239 compacted_sstables: compacted_sstables
240 .into_iter()
241 .map(|m| CompactedSsTable::try_from(m).unwrap())
242 .collect(),
243 fields_for_preserving_frag_bitmap,
244 update_mode: match update_mode {
245 0 => Some(UpdateMode::RewriteRows),
246 1 => Some(UpdateMode::RewriteColumns),
247 _ => Some(UpdateMode::RewriteRows),
248 },
249 inserted_rows_filter: inserted_rows
250 .map(|ik| KeyExistenceFilter::try_from(&ik))
251 .transpose()?,
252 updated_fragment_offsets: {
253 let m: HashMap<u64, RoaringBitmap> =
256 if !updated_fragment_offset_bitmaps.is_empty() {
257 updated_fragment_offset_bitmaps
258 .into_iter()
259 .filter(|(_, bytes)| !bytes.is_empty())
260 .map(|(id, bytes)| {
261 let bitmap = RoaringBitmap::deserialize_from(bytes.as_slice())
262 .map_err(|e| {
263 Error::invalid_input(format!(
264 "invalid updated_fragment_offset_bitmaps \
265 for fragment {id}: {e}"
266 ))
267 })?;
268 Ok((id, bitmap))
269 })
270 .collect::<Result<HashMap<_, _>>>()?
271 } else {
272 updated_fragment_offsets
273 .into_iter()
274 .filter(|(_, list)| !list.values.is_empty())
275 .map(|(id, list)| (id, RoaringBitmap::from_iter(list.values)))
276 .collect()
277 };
278 if m.is_empty() {
279 None
280 } else {
281 Some(UpdatedFragmentOffsets(m))
282 }
283 },
284 },
285 Some(pb::transaction::Operation::Project(pb::transaction::Project {
286 schema,
287 preserves_nullability,
288 })) => Operation::Project {
289 schema: Schema::try_from(&Fields(schema))?,
290 preserves_nullability,
294 },
295 Some(pb::transaction::Operation::UpdateConfig(update_config)) => {
296 let has_new_fields = update_config.config_updates.is_some()
298 || update_config.table_metadata_updates.is_some()
299 || update_config.schema_metadata_updates.is_some()
300 || !update_config.field_metadata_updates.is_empty();
301
302 let has_old_fields = !update_config.upsert_values.is_empty()
304 || !update_config.delete_keys.is_empty()
305 || !update_config.schema_metadata.is_empty()
306 || !update_config.field_metadata.is_empty();
307
308 if has_new_fields && has_old_fields {
310 return Err(Error::invalid_input_source(
311 "Cannot mix old and new style UpdateConfig fields".into(),
312 ));
313 }
314
315 if has_old_fields {
316 let config_updates = if !update_config.upsert_values.is_empty()
318 || !update_config.delete_keys.is_empty()
319 {
320 Some(translate_config_updates(
321 &update_config.upsert_values,
322 &update_config.delete_keys,
323 ))
324 } else {
325 None
326 };
327
328 let schema_metadata_updates = if !update_config.schema_metadata.is_empty() {
329 Some(translate_schema_metadata_updates(
330 &update_config.schema_metadata,
331 ))
332 } else {
333 None
334 };
335
336 let field_metadata_updates = update_config
337 .field_metadata
338 .into_iter()
339 .map(|(field_id, field_meta_update)| {
340 (
341 field_id as i32,
342 translate_schema_metadata_updates(&field_meta_update.metadata),
343 )
344 })
345 .collect();
346
347 Operation::UpdateConfig {
348 config_updates,
349 table_metadata_updates: None,
350 schema_metadata_updates,
351 field_metadata_updates,
352 }
353 } else {
354 Operation::UpdateConfig {
356 config_updates: update_config.config_updates.as_ref().map(UpdateMap::from),
357 table_metadata_updates: update_config
358 .table_metadata_updates
359 .as_ref()
360 .map(UpdateMap::from),
361 schema_metadata_updates: update_config
362 .schema_metadata_updates
363 .as_ref()
364 .map(UpdateMap::from),
365 field_metadata_updates: update_config
366 .field_metadata_updates
367 .iter()
368 .map(|(field_id, pb_update_map)| {
369 (*field_id, UpdateMap::from(pb_update_map))
370 })
371 .collect(),
372 }
373 }
374 }
375 Some(pb::transaction::Operation::DataReplacement(
376 pb::transaction::DataReplacement { replacements },
377 )) => Operation::DataReplacement {
378 replacements: replacements
379 .into_iter()
380 .map(DataReplacementGroup::try_from)
381 .collect::<Result<Vec<_>>>()?,
382 },
383 Some(pb::transaction::Operation::UpdateMemWalState(
384 pb::transaction::UpdateMemWalState { compacted_sstables },
385 )) => Operation::UpdateMemWalState {
386 compacted_sstables: compacted_sstables
387 .into_iter()
388 .map(CompactedSsTable::try_from)
389 .collect::<Result<_>>()?,
390 },
391 Some(pb::transaction::Operation::UpdateBases(pb::transaction::UpdateBases {
392 new_bases,
393 })) => Operation::UpdateBases {
394 new_bases: new_bases.into_iter().map(BasePath::from).collect(),
395 },
396 Some(pb::transaction::Operation::DataOverlay(pb::transaction::DataOverlay {
397 groups,
398 })) => Operation::DataOverlay {
399 groups: groups
400 .into_iter()
401 .map(DataOverlayGroup::try_from)
402 .collect::<Result<Vec<_>>>()?,
403 },
404 None => {
405 return Err(Error::internal(
406 "Transaction message did not contain an operation".to_string(),
407 ));
408 }
409 };
410 Ok(Self {
411 read_version: message.read_version,
412 uuid: message.uuid.clone(),
413 operation,
414 tag: if message.tag.is_empty() {
415 None
416 } else {
417 Some(message.tag.clone())
418 },
419 transaction_properties: if message.transaction_properties.is_empty() {
420 None
421 } else {
422 Some(Arc::new(message.transaction_properties))
423 },
424 })
425 }
426}
427
428impl TryFrom<&pb::transaction::rewrite::RewrittenIndex> for RewrittenIndex {
429 type Error = Error;
430
431 fn try_from(message: &pb::transaction::rewrite::RewrittenIndex) -> Result<Self> {
432 Ok(Self {
433 old_id: message
434 .old_id
435 .as_ref()
436 .map(Uuid::try_from)
437 .ok_or_else(|| {
438 Error::invalid_input("required field (old_id) missing from message".to_string())
439 })??,
440 new_id: message
441 .new_id
442 .as_ref()
443 .map(Uuid::try_from)
444 .ok_or_else(|| {
445 Error::invalid_input("required field (new_id) missing from message".to_string())
446 })??,
447 new_index_details: message
448 .new_index_details
449 .as_ref()
450 .ok_or_else(|| {
451 Error::invalid_input("new_index_details is a required field".to_string())
452 })?
453 .clone(),
454 new_index_version: message.new_index_version,
455 new_index_files: if message.new_index_files.is_empty() {
456 None
457 } else {
458 Some(
459 message
460 .new_index_files
461 .iter()
462 .map(|f| IndexFile {
463 path: f.path.clone(),
464 size_bytes: f.size_bytes,
465 })
466 .collect(),
467 )
468 },
469 })
470 }
471}
472
473impl TryFrom<pb::transaction::rewrite::RewriteGroup> for RewriteGroup {
474 type Error = Error;
475
476 fn try_from(message: pb::transaction::rewrite::RewriteGroup) -> Result<Self> {
477 Ok(Self {
478 old_fragments: message
479 .old_fragments
480 .into_iter()
481 .map(Fragment::try_from)
482 .collect::<Result<Vec<_>>>()?,
483 new_fragments: message
484 .new_fragments
485 .into_iter()
486 .map(Fragment::try_from)
487 .collect::<Result<Vec<_>>>()?,
488 })
489 }
490}
491
492impl From<&Transaction> for pb::Transaction {
493 fn from(value: &Transaction) -> Self {
494 let operation = match &value.operation {
495 Operation::Append { fragments } => {
496 pb::transaction::Operation::Append(pb::transaction::Append {
497 fragments: fragments.iter().map(pb::DataFragment::from).collect(),
498 })
499 }
500 Operation::Clone {
501 is_shallow,
502 ref_name,
503 ref_version,
504 ref_path,
505 branch_name,
506 } => pb::transaction::Operation::Clone(pb::transaction::Clone {
507 is_shallow: *is_shallow,
508 ref_name: ref_name.clone(),
509 ref_version: *ref_version,
510 ref_path: ref_path.clone(),
511 branch_name: branch_name.clone(),
512 }),
513 Operation::Delete {
514 updated_fragments,
515 deleted_fragment_ids,
516 predicate,
517 } => pb::transaction::Operation::Delete(pb::transaction::Delete {
518 updated_fragments: updated_fragments
519 .iter()
520 .map(pb::DataFragment::from)
521 .collect(),
522 deleted_fragment_ids: deleted_fragment_ids.clone(),
523 predicate: predicate.clone(),
524 }),
525 Operation::Overwrite {
526 fragments,
527 schema,
528 config_upsert_values,
529 initial_bases,
530 } => {
531 pb::transaction::Operation::Overwrite(pb::transaction::Overwrite {
532 fragments: fragments.iter().map(pb::DataFragment::from).collect(),
533 schema: Fields::from(schema).0,
534 schema_metadata: Default::default(), config_upsert_values: config_upsert_values
536 .clone()
537 .unwrap_or(Default::default()),
538 initial_bases: initial_bases
539 .as_ref()
540 .map(|paths| {
541 paths
542 .iter()
543 .cloned()
544 .map(|bp: BasePath| -> pb::BasePath { bp.into() })
545 .collect::<Vec<pb::BasePath>>()
546 })
547 .unwrap_or_default(),
548 })
549 }
550 Operation::ReserveFragments { num_fragments } => {
551 pb::transaction::Operation::ReserveFragments(pb::transaction::ReserveFragments {
552 num_fragments: *num_fragments,
553 })
554 }
555 Operation::Rewrite {
556 groups,
557 rewritten_indices,
558 frag_reuse_index: _,
559 } => pb::transaction::Operation::Rewrite(pb::transaction::Rewrite {
560 groups: groups
561 .iter()
562 .map(pb::transaction::rewrite::RewriteGroup::from)
563 .collect(),
564 rewritten_indices: rewritten_indices
565 .iter()
566 .map(|rewritten| rewritten.into())
567 .collect(),
568 ..Default::default()
569 }),
570 Operation::CreateIndex {
571 new_indices,
572 removed_indices,
573 } => pb::transaction::Operation::CreateIndex(pb::transaction::CreateIndex {
574 new_indices: new_indices.iter().map(pb::IndexMetadata::from).collect(),
575 removed_indices: removed_indices
576 .iter()
577 .map(pb::IndexMetadata::from)
578 .collect(),
579 }),
580 Operation::Merge {
581 fragments,
582 schema,
583 preserves_nullability,
584 } => pb::transaction::Operation::Merge(pb::transaction::Merge {
585 fragments: fragments.iter().map(pb::DataFragment::from).collect(),
586 schema: Fields::from(schema).0,
587 schema_metadata: Default::default(), preserves_nullability: *preserves_nullability,
589 }),
590 Operation::Restore { version } => {
591 pb::transaction::Operation::Restore(pb::transaction::Restore { version: *version })
592 }
593 Operation::Update {
594 removed_fragment_ids,
595 updated_fragments,
596 new_fragments,
597 fields_modified,
598 compacted_sstables,
599 fields_for_preserving_frag_bitmap,
600 update_mode,
601 inserted_rows_filter,
602 updated_fragment_offsets,
603 } => pb::transaction::Operation::Update(pb::transaction::Update {
604 removed_fragment_ids: removed_fragment_ids.clone(),
605 updated_fragments: updated_fragments
606 .iter()
607 .map(pb::DataFragment::from)
608 .collect(),
609 new_fragments: new_fragments.iter().map(pb::DataFragment::from).collect(),
610 fields_modified: fields_modified.clone(),
611 compacted_sstables: compacted_sstables
612 .iter()
613 .map(pb::CompactedSsTable::from)
614 .collect(),
615 fields_for_preserving_frag_bitmap: fields_for_preserving_frag_bitmap.clone(),
616 update_mode: update_mode
617 .as_ref()
618 .map(|mode| match mode {
619 UpdateMode::RewriteRows => 0,
620 UpdateMode::RewriteColumns => 1,
621 })
622 .unwrap_or(0),
623 inserted_rows: inserted_rows_filter.as_ref().map(|ik| ik.into()),
624 updated_fragment_offsets: HashMap::new(),
626 updated_fragment_offset_bitmaps: updated_fragment_offsets
628 .as_ref()
629 .map(|UpdatedFragmentOffsets(m)| {
630 m.iter()
631 .filter(|(_, b)| !b.is_empty())
632 .map(|(frag_id, b)| {
633 let mut buf = Vec::new();
634 b.serialize_into(&mut buf)
635 .expect("RoaringBitmap serialization cannot fail");
636 (*frag_id, buf)
637 })
638 .collect::<HashMap<_, _>>()
639 })
640 .unwrap_or_default(),
641 }),
642 Operation::Project {
643 schema,
644 preserves_nullability,
645 } => pb::transaction::Operation::Project(pb::transaction::Project {
646 schema: Fields::from(schema).0,
647 preserves_nullability: *preserves_nullability,
648 }),
649 Operation::UpdateConfig {
650 config_updates,
651 table_metadata_updates,
652 schema_metadata_updates,
653 field_metadata_updates,
654 } => pb::transaction::Operation::UpdateConfig(pb::transaction::UpdateConfig {
655 config_updates: config_updates
656 .as_ref()
657 .map(pb::transaction::UpdateMap::from),
658 table_metadata_updates: table_metadata_updates
659 .as_ref()
660 .map(pb::transaction::UpdateMap::from),
661 schema_metadata_updates: schema_metadata_updates
662 .as_ref()
663 .map(pb::transaction::UpdateMap::from),
664 field_metadata_updates: field_metadata_updates
665 .iter()
666 .map(|(field_id, update_map)| {
667 (*field_id, pb::transaction::UpdateMap::from(update_map))
668 })
669 .collect(),
670 upsert_values: Default::default(),
672 delete_keys: Default::default(),
673 schema_metadata: Default::default(),
674 field_metadata: Default::default(),
675 }),
676 Operation::DataReplacement { replacements } => {
677 pb::transaction::Operation::DataReplacement(pb::transaction::DataReplacement {
678 replacements: replacements
679 .iter()
680 .map(pb::transaction::DataReplacementGroup::from)
681 .collect(),
682 })
683 }
684 Operation::DataOverlay { groups } => {
685 pb::transaction::Operation::DataOverlay(pb::transaction::DataOverlay {
686 groups: groups
687 .iter()
688 .map(pb::transaction::DataOverlayGroup::from)
689 .collect(),
690 })
691 }
692 Operation::UpdateMemWalState { compacted_sstables } => {
693 pb::transaction::Operation::UpdateMemWalState(pb::transaction::UpdateMemWalState {
694 compacted_sstables: compacted_sstables
695 .iter()
696 .map(pb::CompactedSsTable::from)
697 .collect::<Vec<_>>(),
698 })
699 }
700 Operation::UpdateBases { new_bases } => {
701 pb::transaction::Operation::UpdateBases(pb::transaction::UpdateBases {
702 new_bases: new_bases
703 .iter()
704 .cloned()
705 .map(|bp: BasePath| -> pb::BasePath { bp.into() })
706 .collect::<Vec<pb::BasePath>>(),
707 })
708 }
709 };
710
711 let transaction_properties = value
712 .transaction_properties
713 .as_ref()
714 .map(|arc| arc.as_ref().clone())
715 .unwrap_or_default();
716 Self {
717 read_version: value.read_version,
718 uuid: value.uuid.clone(),
719 operation: Some(operation),
720 tag: value.tag.clone().unwrap_or("".to_string()),
721 transaction_properties,
722 }
723 }
724}
725
726impl From<&RewrittenIndex> for pb::transaction::rewrite::RewrittenIndex {
727 fn from(value: &RewrittenIndex) -> Self {
728 Self {
729 old_id: Some((&value.old_id).into()),
730 new_id: Some((&value.new_id).into()),
731 new_index_details: Some(value.new_index_details.clone()),
732 new_index_version: value.new_index_version,
733 new_index_files: value
734 .new_index_files
735 .as_ref()
736 .map(|files| {
737 files
738 .iter()
739 .map(|f| pb::IndexFile {
740 path: f.path.clone(),
741 size_bytes: f.size_bytes,
742 })
743 .collect()
744 })
745 .unwrap_or_default(),
746 }
747 }
748}
749
750impl From<&RewriteGroup> for pb::transaction::rewrite::RewriteGroup {
751 fn from(value: &RewriteGroup) -> Self {
752 Self {
753 old_fragments: value
754 .old_fragments
755 .iter()
756 .map(pb::DataFragment::from)
757 .collect(),
758 new_fragments: value
759 .new_fragments
760 .iter()
761 .map(pb::DataFragment::from)
762 .collect(),
763 }
764 }
765}
766
767impl From<&UpdateMap> for pb::transaction::UpdateMap {
768 fn from(update_map: &UpdateMap) -> Self {
769 Self {
770 update_entries: update_map
771 .update_entries
772 .iter()
773 .map(|entry| pb::transaction::UpdateMapEntry {
774 key: entry.key.clone(),
775 value: entry.value.clone(),
776 })
777 .collect(),
778 replace: update_map.replace,
779 }
780 }
781}
782
783impl From<&pb::transaction::UpdateMap> for UpdateMap {
784 fn from(pb_update_map: &pb::transaction::UpdateMap) -> Self {
785 Self {
786 update_entries: pb_update_map
787 .update_entries
788 .iter()
789 .map(|entry| UpdateMapEntry {
790 key: entry.key.clone(),
791 value: entry.value.clone(),
792 })
793 .collect(),
794 replace: pb_update_map.replace,
795 }
796 }
797}
798
799impl From<&Transaction> for crate::format::Transaction {
800 fn from(value: &Transaction) -> Self {
801 let pb_transaction: pb::Transaction = value.into();
802 Self {
803 inner: pb_transaction,
804 }
805 }
806}
807
808#[cfg(test)]
809mod tests {
810 use super::*;
811 use crate::format::DataFile;
812 use crate::format::overlay::OverlayCoverage;
813
814 #[test]
815 fn test_data_overlay_operation_roundtrips() {
816 let mut bitmap = roaring::RoaringBitmap::new();
819 bitmap.insert(1);
820 bitmap.insert(4);
821 let overlay = DataOverlayFile {
822 data_file: DataFile::new_legacy_from_fields("overlay-0.lance", vec![3], None),
823 coverage: OverlayCoverage::dense(bitmap.clone()),
824 committed_version: 6,
825 };
826 let pb_overlay = pb::DataOverlayFile::from(&overlay);
827
828 let message = pb::Transaction {
829 read_version: 1,
830 uuid: Uuid::new_v4().to_string(),
831 operation: Some(pb::transaction::Operation::DataOverlay(
832 pb::transaction::DataOverlay {
833 groups: vec![pb::transaction::DataOverlayGroup {
834 fragment_id: 7,
835 overlays: vec![pb_overlay],
836 }],
837 },
838 )),
839 ..Default::default()
840 };
841
842 let txn = Transaction::try_from(message).unwrap();
843 match txn.operation {
844 Operation::DataOverlay { groups } => {
845 assert_eq!(groups.len(), 1);
846 assert_eq!(groups[0].fragment_id, 7);
847 assert_eq!(groups[0].overlays.len(), 1);
848 assert_eq!(groups[0].overlays[0].committed_version, 6);
849 assert_eq!(
850 *groups[0].overlays[0].coverage_for_field(0).unwrap(),
851 bitmap
852 );
853 }
854 other => panic!("expected DataOverlay, got {other:?}"),
855 }
856 }
857}