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 => Operation::Unknown {},
407 };
408 Ok(Self {
409 read_version: message.read_version,
410 uuid: message.uuid.clone(),
411 operation,
412 tag: if message.tag.is_empty() {
413 None
414 } else {
415 Some(message.tag.clone())
416 },
417 transaction_properties: if message.transaction_properties.is_empty() {
418 None
419 } else {
420 Some(Arc::new(message.transaction_properties))
421 },
422 })
423 }
424}
425
426impl TryFrom<&pb::transaction::rewrite::RewrittenIndex> for RewrittenIndex {
427 type Error = Error;
428
429 fn try_from(message: &pb::transaction::rewrite::RewrittenIndex) -> Result<Self> {
430 Ok(Self {
431 old_id: message
432 .old_id
433 .as_ref()
434 .map(Uuid::try_from)
435 .ok_or_else(|| {
436 Error::invalid_input("required field (old_id) missing from message".to_string())
437 })??,
438 new_id: message
439 .new_id
440 .as_ref()
441 .map(Uuid::try_from)
442 .ok_or_else(|| {
443 Error::invalid_input("required field (new_id) missing from message".to_string())
444 })??,
445 new_index_details: message
446 .new_index_details
447 .as_ref()
448 .ok_or_else(|| {
449 Error::invalid_input("new_index_details is a required field".to_string())
450 })?
451 .clone(),
452 new_index_version: message.new_index_version,
453 new_index_files: if message.new_index_files.is_empty() {
454 None
455 } else {
456 Some(
457 message
458 .new_index_files
459 .iter()
460 .map(|f| IndexFile {
461 path: f.path.clone(),
462 size_bytes: f.size_bytes,
463 })
464 .collect(),
465 )
466 },
467 })
468 }
469}
470
471impl TryFrom<pb::transaction::rewrite::RewriteGroup> for RewriteGroup {
472 type Error = Error;
473
474 fn try_from(message: pb::transaction::rewrite::RewriteGroup) -> Result<Self> {
475 Ok(Self {
476 old_fragments: message
477 .old_fragments
478 .into_iter()
479 .map(Fragment::try_from)
480 .collect::<Result<Vec<_>>>()?,
481 new_fragments: message
482 .new_fragments
483 .into_iter()
484 .map(Fragment::try_from)
485 .collect::<Result<Vec<_>>>()?,
486 })
487 }
488}
489
490impl TryFrom<&Transaction> for pb::Transaction {
491 type Error = Error;
492
493 fn try_from(value: &Transaction) -> Result<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 Operation::Unknown { .. } => {
710 return Err(Error::not_supported(format!(
711 "Transaction {} has an operation written by a newer version of Lance \
712 and cannot be re-encoded by this version",
713 value.uuid
714 )));
715 }
716 };
717
718 let transaction_properties = value
719 .transaction_properties
720 .as_ref()
721 .map(|arc| arc.as_ref().clone())
722 .unwrap_or_default();
723 Ok(Self {
724 read_version: value.read_version,
725 uuid: value.uuid.clone(),
726 operation: Some(operation),
727 tag: value.tag.clone().unwrap_or("".to_string()),
728 transaction_properties,
729 })
730 }
731}
732
733impl From<&RewrittenIndex> for pb::transaction::rewrite::RewrittenIndex {
734 fn from(value: &RewrittenIndex) -> Self {
735 Self {
736 old_id: Some((&value.old_id).into()),
737 new_id: Some((&value.new_id).into()),
738 new_index_details: Some(value.new_index_details.clone()),
739 new_index_version: value.new_index_version,
740 new_index_files: value
741 .new_index_files
742 .as_ref()
743 .map(|files| {
744 files
745 .iter()
746 .map(|f| pb::IndexFile {
747 path: f.path.clone(),
748 size_bytes: f.size_bytes,
749 })
750 .collect()
751 })
752 .unwrap_or_default(),
753 }
754 }
755}
756
757impl From<&RewriteGroup> for pb::transaction::rewrite::RewriteGroup {
758 fn from(value: &RewriteGroup) -> Self {
759 Self {
760 old_fragments: value
761 .old_fragments
762 .iter()
763 .map(pb::DataFragment::from)
764 .collect(),
765 new_fragments: value
766 .new_fragments
767 .iter()
768 .map(pb::DataFragment::from)
769 .collect(),
770 }
771 }
772}
773
774impl From<&UpdateMap> for pb::transaction::UpdateMap {
775 fn from(update_map: &UpdateMap) -> Self {
776 Self {
777 update_entries: update_map
778 .update_entries
779 .iter()
780 .map(|entry| pb::transaction::UpdateMapEntry {
781 key: entry.key.clone(),
782 value: entry.value.clone(),
783 })
784 .collect(),
785 replace: update_map.replace,
786 }
787 }
788}
789
790impl From<&pb::transaction::UpdateMap> for UpdateMap {
791 fn from(pb_update_map: &pb::transaction::UpdateMap) -> Self {
792 Self {
793 update_entries: pb_update_map
794 .update_entries
795 .iter()
796 .map(|entry| UpdateMapEntry {
797 key: entry.key.clone(),
798 value: entry.value.clone(),
799 })
800 .collect(),
801 replace: pb_update_map.replace,
802 }
803 }
804}
805
806impl TryFrom<&Transaction> for crate::format::Transaction {
807 type Error = Error;
808
809 fn try_from(value: &Transaction) -> Result<Self> {
810 Ok(Self {
811 inner: pb::Transaction::try_from(value)?,
812 })
813 }
814}
815
816#[cfg(test)]
817mod tests {
818 use super::*;
819 use crate::format::DataFile;
820 use crate::format::overlay::OverlayCoverage;
821
822 #[test]
823 fn test_rewrite_frag_reuse_update_stays_in_memory() {
824 let entry = crate::transaction::test_support::sample_index_metadata(
828 crate::system_index::frag_reuse::FRAG_REUSE_INDEX_NAME,
829 );
830 let transaction = Transaction::new(
831 1,
832 Operation::Rewrite {
833 groups: vec![],
834 rewritten_indices: vec![],
835 frag_reuse_index: Some(entry),
836 },
837 None,
838 );
839 let decoded =
840 Transaction::try_from(pb::Transaction::try_from(&transaction).unwrap()).unwrap();
841 match decoded.operation {
842 Operation::Rewrite {
843 frag_reuse_index, ..
844 } => {
845 assert!(frag_reuse_index.is_none());
846 }
847 other => panic!("expected Rewrite, got {other:?}"),
848 }
849 }
850
851 #[test]
852 fn test_data_overlay_operation_roundtrips() {
853 let mut bitmap = roaring::RoaringBitmap::new();
856 bitmap.insert(1);
857 bitmap.insert(4);
858 let overlay = DataOverlayFile {
859 data_file: DataFile::new_legacy_from_fields("overlay-0.lance", vec![3], None),
860 coverage: OverlayCoverage::dense(bitmap.clone()),
861 committed_version: 6,
862 };
863 let pb_overlay = pb::DataOverlayFile::from(&overlay);
864
865 let message = pb::Transaction {
866 read_version: 1,
867 uuid: Uuid::new_v4().to_string(),
868 operation: Some(pb::transaction::Operation::DataOverlay(
869 pb::transaction::DataOverlay {
870 groups: vec![pb::transaction::DataOverlayGroup {
871 fragment_id: 7,
872 overlays: vec![pb_overlay],
873 }],
874 },
875 )),
876 ..Default::default()
877 };
878
879 let txn = Transaction::try_from(message).unwrap();
880 match txn.operation {
881 Operation::DataOverlay { groups } => {
882 assert_eq!(groups.len(), 1);
883 assert_eq!(groups[0].fragment_id, 7);
884 assert_eq!(groups[0].overlays.len(), 1);
885 assert_eq!(groups[0].overlays[0].committed_version, 6);
886 assert_eq!(
887 *groups[0].overlays[0].coverage_for_field(0).unwrap(),
888 bitmap
889 );
890 }
891 other => panic!("expected DataOverlay, got {other:?}"),
892 }
893 }
894}