1use crate::format::Manifest;
7use lance_core::{Error, Result};
8
9pub const FLAG_DELETION_FILES: u64 = 1 << 0;
12pub const FLAG_STABLE_ROW_IDS: u64 = 1 << 1;
15pub const FLAG_USE_V2_FORMAT_DEPRECATED: u64 = 1 << 2;
17pub const FLAG_TABLE_CONFIG: u64 = 1 << 3;
19pub const FLAG_BASE_PATHS: u64 = 1 << 4;
21pub const FLAG_DISABLE_TRANSACTION_FILE: u64 = 1 << 5;
23pub const FLAG_UNSTABLE_DATA_OVERLAY_FILES: u64 = 1 << 6;
33pub const FLAG_COVERED_INDEX_METADATA: u64 = 1 << 7;
53pub const FLAG_MIXED_DATA_FILE_VERSIONS: u64 = 1 << 8;
57pub const FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS: u64 = 1 << 9;
62pub const FLAG_FRAGMENT_REUSE_INDEX: u64 = 1 << 10;
66pub const FLAG_UNSTABLE_SPILLED_ROW_LINEAGE: u64 = 1 << 11;
82pub const FLAG_FRAGMENT_TREE: u64 = 1 << 12;
86pub const FLAG_INDEPENDENT_COVERING_FIELDS: u64 = 1 << 13;
98pub const FLAG_UNKNOWN: u64 = 1 << 14;
100
101const _: () = assert!(FLAG_COVERED_INDEX_METADATA < FLAG_UNKNOWN);
102const _: () = assert!(FLAG_COVERED_INDEX_METADATA >= 1 << 7);
105const _: () = assert!(FLAG_MIXED_DATA_FILE_VERSIONS < FLAG_UNKNOWN);
106const _: () = assert!(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS >= 1 << 9);
108const _: () = assert!(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS < FLAG_UNKNOWN);
109const _: () = assert!(FLAG_FRAGMENT_REUSE_INDEX < FLAG_UNKNOWN);
110const _: () = assert!(FLAG_UNSTABLE_SPILLED_ROW_LINEAGE < FLAG_UNKNOWN);
111const _: () = assert!(FLAG_FRAGMENT_TREE < FLAG_UNKNOWN);
112const _: () = assert!(FLAG_INDEPENDENT_COVERING_FIELDS < FLAG_UNKNOWN);
113
114pub(crate) const STICKY_PAIRED_FLAGS: u64 =
115 FLAG_MIXED_DATA_FILE_VERSIONS | FLAG_FRAGMENT_REUSE_INDEX;
116
117pub const ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV: &str = "LANCE_ENABLE_UNSTABLE_DATA_OVERLAY_FILES";
120
121pub const ENABLE_UNSTABLE_SPILLED_ROW_LINEAGE_ENV: &str =
125 "LANCE_ENABLE_UNSTABLE_SPILLED_ROW_LINEAGE";
126
127pub fn apply_feature_flags(
129 manifest: &mut Manifest,
130 enable_stable_row_id: bool,
131 disable_transaction_file: bool,
132) -> Result<()> {
133 let covered_index_metadata = (manifest.reader_feature_flags | manifest.writer_feature_flags)
139 & FLAG_COVERED_INDEX_METADATA;
140 let sticky_paired_flags = validated_sticky_paired_flags(manifest)?;
141
142 manifest.reader_feature_flags = 0;
144 manifest.writer_feature_flags = 0;
145
146 let has_deletion_files = manifest
147 .fragments
148 .iter()
149 .any(|frag| frag.deletion_file.is_some());
150 if has_deletion_files {
151 manifest.reader_feature_flags |= FLAG_DELETION_FILES;
153 manifest.writer_feature_flags |= FLAG_DELETION_FILES;
154 }
155
156 let has_row_ids = manifest
158 .fragments
159 .iter()
160 .any(|frag| frag.row_id_meta.is_some());
161 if has_row_ids || enable_stable_row_id {
162 if !manifest
163 .fragments
164 .iter()
165 .all(|frag| frag.row_id_meta.is_some())
166 {
167 return Err(Error::invalid_input("All fragments must have row ids"));
168 }
169 manifest.reader_feature_flags |= FLAG_STABLE_ROW_IDS;
170 manifest.writer_feature_flags |= FLAG_STABLE_ROW_IDS;
171 }
172
173 if !manifest.config.is_empty() {
175 manifest.writer_feature_flags |= FLAG_TABLE_CONFIG;
176 }
177
178 if !manifest.base_paths.is_empty() {
180 manifest.reader_feature_flags |= FLAG_BASE_PATHS;
181 manifest.writer_feature_flags |= FLAG_BASE_PATHS;
182 }
183
184 let has_overlays = manifest
188 .fragments
189 .iter()
190 .any(|frag| !frag.overlays.is_empty());
191 if has_overlays {
192 manifest.reader_feature_flags |= FLAG_UNSTABLE_DATA_OVERLAY_FILES;
193 manifest.writer_feature_flags |= FLAG_UNSTABLE_DATA_OVERLAY_FILES;
194 }
195
196 let has_spilled_row_lineage = manifest
197 .fragments
198 .iter()
199 .any(|frag| frag.has_spilled_row_lineage());
200 if has_spilled_row_lineage {
201 manifest.reader_feature_flags |= FLAG_UNSTABLE_SPILLED_ROW_LINEAGE;
202 manifest.writer_feature_flags |= FLAG_UNSTABLE_SPILLED_ROW_LINEAGE;
203 }
204
205 if disable_transaction_file {
206 manifest.writer_feature_flags |= FLAG_DISABLE_TRANSACTION_FILE;
207 }
208
209 manifest.reader_feature_flags |= covered_index_metadata;
210 manifest.writer_feature_flags |= covered_index_metadata;
211 manifest.reader_feature_flags |= sticky_paired_flags;
212 manifest.writer_feature_flags |= sticky_paired_flags;
213
214 Ok(())
215}
216
217pub fn inherit_sticky_feature_flags(destination: &mut Manifest, source: &Manifest) -> Result<()> {
228 let sticky_flags = validated_sticky_paired_flags(source)?;
229 destination.reader_feature_flags |= sticky_flags;
230 destination.writer_feature_flags |= sticky_flags;
231 Ok(())
232}
233
234fn data_overlay_files_enabled() -> bool {
237 cfg!(debug_assertions) || std::env::var_os(ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV).is_some()
238}
239
240pub fn spilled_row_lineage_enabled() -> bool {
244 cfg!(debug_assertions) || std::env::var_os(ENABLE_UNSTABLE_SPILLED_ROW_LINEAGE_ENV).is_some()
245}
246
247#[doc(hidden)]
250pub fn frag_reuse_with_stable_row_ids_enabled() -> bool {
251 cfg!(debug_assertions)
252}
253
254fn mark_supported(flags: &mut u64, flag: u64, feature_enabled: bool) {
258 if !feature_enabled {
259 *flags &= !flag;
260 }
261}
262
263fn supported_flags_when(
267 overlay_enabled: bool,
268 spilled_row_lineage_enabled: bool,
269 frag_reuse_with_stable_row_ids_enabled: bool,
270) -> u64 {
271 let mut supported = FLAG_UNKNOWN - 1;
272 mark_supported(
273 &mut supported,
274 FLAG_UNSTABLE_DATA_OVERLAY_FILES,
275 overlay_enabled,
276 );
277 mark_supported(
278 &mut supported,
279 FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS,
280 frag_reuse_with_stable_row_ids_enabled,
281 );
282 mark_supported(
283 &mut supported,
284 FLAG_UNSTABLE_SPILLED_ROW_LINEAGE,
285 spilled_row_lineage_enabled,
286 );
287 mark_supported(&mut supported, FLAG_FRAGMENT_TREE, false);
289 mark_supported(&mut supported, FLAG_INDEPENDENT_COVERING_FIELDS, false);
290 supported
291}
292
293fn supported_flags() -> u64 {
294 supported_flags_when(
295 data_overlay_files_enabled(),
296 spilled_row_lineage_enabled(),
297 frag_reuse_with_stable_row_ids_enabled(),
298 )
299}
300
301pub fn can_read_dataset(reader_flags: u64) -> bool {
302 reader_flags & !supported_flags() == 0
303}
304
305pub fn can_write_dataset(writer_flags: u64) -> bool {
306 writer_flags & !supported_flags() == 0
307}
308
309pub fn ensure_can_read_manifest(manifest: &Manifest) -> Result<()> {
312 validate_paired_feature_flags(manifest)?;
313 if !can_read_dataset(manifest.reader_feature_flags) {
314 return Err(Error::not_supported_source(
315 format!(
316 "This dataset cannot be read by this version of Lance. Please upgrade \
317 Lance to read this dataset. Flags: {}",
318 manifest.reader_feature_flags
319 )
320 .into(),
321 ));
322 }
323 Ok(())
324}
325
326pub fn ensure_can_write_manifest(manifest: &Manifest) -> Result<()> {
329 validate_paired_feature_flags(manifest)?;
330 if !can_write_dataset(manifest.writer_feature_flags) {
331 return Err(Error::not_supported_source(
332 format!(
333 "This dataset cannot be written by this version of Lance. Please upgrade \
334 Lance to write this dataset. Flags: {}",
335 manifest.writer_feature_flags
336 )
337 .into(),
338 ));
339 }
340 Ok(())
341}
342
343pub fn has_deprecated_v2_feature_flag(writer_flags: u64) -> bool {
344 writer_flags & FLAG_USE_V2_FORMAT_DEPRECATED != 0
345}
346
347pub fn validate_paired_feature_flags(manifest: &Manifest) -> Result<()> {
354 if (manifest.reader_feature_flags ^ manifest.writer_feature_flags) & FLAG_FRAGMENT_REUSE_INDEX
355 != 0
356 {
357 return Err(Error::corrupt_file_named(
358 "manifest",
359 "FRI requires both reader and writer feature flags",
360 ));
361 }
362
363 let reader = manifest.reader_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS != 0;
364 let writer = manifest.writer_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS != 0;
365 if reader != writer {
366 return Err(Error::corrupt_file_named(
367 "manifest",
368 "Manifest has only one of the mixed data-file-version reader and writer feature bits set, \
369 so its semantics are undefined",
370 ));
371 }
372 Ok(())
373}
374
375fn validated_sticky_paired_flags(manifest: &Manifest) -> Result<u64> {
376 validate_paired_feature_flags(manifest)?;
377 Ok(manifest.reader_feature_flags & STICKY_PAIRED_FLAGS)
378}
379
380#[cfg(test)]
381mod tests {
382 #[test]
383 fn tagged_fri_flag_is_supported_and_sticky() {
384 let flag = super::FLAG_FRAGMENT_REUSE_INDEX;
385 assert!(super::can_read_dataset(flag));
386 assert!(super::can_write_dataset(flag));
387 let mut manifest = empty_manifest();
388 manifest.reader_feature_flags = flag;
389 assert!(super::ensure_can_read_manifest(&manifest).is_err());
390 manifest.writer_feature_flags = flag;
391 super::apply_feature_flags(&mut manifest, false, false).unwrap();
392 assert_eq!(manifest.reader_feature_flags & flag, flag);
393 assert_eq!(manifest.writer_feature_flags & flag, flag);
394 }
395
396 #[test]
402 fn test_covered_index_metadata_fences_older_builds_only() {
403 assert_eq!(
404 FLAG_COVERED_INDEX_METADATA, 128,
405 "the fence must sit on the boundary the released build shipped with"
406 );
407 assert!(
408 can_read_dataset(FLAG_COVERED_INDEX_METADATA),
409 "this build implements covering, so it must accept its own datasets"
410 );
411 assert!(can_write_dataset(FLAG_COVERED_INDEX_METADATA));
412 }
415
416 use super::*;
417 use crate::format::BasePath;
418
419 #[test]
420 fn test_fragment_tree_flag_is_reserved_not_supported() {
421 assert_eq!(FLAG_FRAGMENT_TREE, 4096);
422 assert_eq!(
423 FLAG_FRAGMENT_TREE & (FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS | FLAG_FRAGMENT_REUSE_INDEX),
424 0
425 );
426 assert!(!can_read_dataset(FLAG_FRAGMENT_TREE));
427 assert!(!can_write_dataset(FLAG_FRAGMENT_TREE));
428 }
429
430 #[test]
431 fn test_frag_reuse_with_stable_row_ids_flag_release_gating() {
432 assert_eq!(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS, 512);
434 let without_support = supported_flags_when(true, true, false);
435 assert_ne!(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS & !without_support, 0);
436 assert_eq!(FLAG_STABLE_ROW_IDS & !without_support, 0);
437 let enabled = supported_flags_when(false, false, true);
438 assert_eq!(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS & !enabled, 0);
439 for unrelated in [
440 FLAG_UNSTABLE_DATA_OVERLAY_FILES,
441 FLAG_UNSTABLE_SPILLED_ROW_LINEAGE,
442 FLAG_FRAGMENT_TREE,
443 FLAG_UNKNOWN,
444 ] {
445 assert_ne!(unrelated & !enabled, 0);
446 }
447 assert_eq!(
448 can_read_dataset(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS),
449 frag_reuse_with_stable_row_ids_enabled()
450 );
451 assert_eq!(
452 can_write_dataset(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS),
453 frag_reuse_with_stable_row_ids_enabled()
454 );
455 }
456
457 #[test]
458 fn test_independent_covering_fields_flag_is_reserved() {
459 assert_eq!(FLAG_INDEPENDENT_COVERING_FIELDS, 8192);
460 let flags = FLAG_COVERED_INDEX_METADATA | FLAG_INDEPENDENT_COVERING_FIELDS;
461 assert!(!can_read_dataset(flags));
462 assert!(!can_write_dataset(flags));
463 }
464
465 #[test]
466 fn test_read_check() {
467 assert!(can_read_dataset(0));
468 assert!(can_read_dataset(super::FLAG_DELETION_FILES));
469 assert!(can_read_dataset(super::FLAG_STABLE_ROW_IDS));
470 assert!(can_read_dataset(super::FLAG_USE_V2_FORMAT_DEPRECATED));
471 assert!(can_read_dataset(super::FLAG_TABLE_CONFIG));
472 assert!(can_read_dataset(super::FLAG_BASE_PATHS));
473 assert!(can_read_dataset(super::FLAG_DISABLE_TRANSACTION_FILE));
474 assert_eq!(
478 can_read_dataset(super::FLAG_UNSTABLE_DATA_OVERLAY_FILES),
479 data_overlay_files_enabled()
480 );
481 assert!(can_read_dataset(
482 super::FLAG_DELETION_FILES
483 | super::FLAG_STABLE_ROW_IDS
484 | super::FLAG_USE_V2_FORMAT_DEPRECATED
485 ));
486 assert!(!can_read_dataset(super::FLAG_UNKNOWN));
487 }
488
489 #[test]
490 fn test_data_overlay_flag_release_gating() {
491 let supported = supported_flags_when(false, false, false);
494 assert_eq!(supported & FLAG_UNSTABLE_DATA_OVERLAY_FILES, 0);
495 assert_eq!(FLAG_DELETION_FILES & !supported, 0);
496 assert_ne!(FLAG_UNSTABLE_DATA_OVERLAY_FILES & !supported, 0);
497 let supported = supported_flags_when(true, false, false);
499 assert_eq!(FLAG_UNSTABLE_DATA_OVERLAY_FILES & !supported, 0);
500 }
501
502 #[test]
503 fn test_spilled_row_lineage_flag_release_gating() {
504 assert_eq!(FLAG_UNSTABLE_SPILLED_ROW_LINEAGE, 2048);
509 let supported = supported_flags_when(true, false, false);
512 assert_ne!(FLAG_UNSTABLE_SPILLED_ROW_LINEAGE & !supported, 0);
513 assert_eq!(FLAG_MIXED_DATA_FILE_VERSIONS & !supported, 0);
514 let supported = supported_flags_when(true, true, false);
515 assert_eq!(FLAG_UNSTABLE_SPILLED_ROW_LINEAGE & !supported, 0);
516 }
517
518 #[test]
519 fn test_apply_feature_flags_sets_overlay_flag() {
520 use crate::format::overlay::{DataOverlayFile, OverlayCoverage};
521 use crate::format::{DataFile, DataStorageFormat, Fragment};
522 use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
523 use lance_core::datatypes::Schema;
524 use roaring::RoaringBitmap;
525 use std::collections::HashMap;
526 use std::sync::Arc;
527
528 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
529 "id",
530 arrow_schema::DataType::Int64,
531 false,
532 )]);
533 let schema = Schema::try_from(&arrow_schema).unwrap();
534 let mut fragment = Fragment::new(0);
535 fragment.overlays = vec![DataOverlayFile {
536 data_file: DataFile::new_legacy_from_fields("o.lance", vec![0], None),
537 coverage: OverlayCoverage::dense(RoaringBitmap::from_iter([0u32])),
538 committed_version: 1,
539 }];
540 let mut manifest = Manifest::new(
541 schema,
542 Arc::new(vec![fragment]),
543 DataStorageFormat::default(),
544 HashMap::new(),
545 );
546 apply_feature_flags(&mut manifest, false, false).unwrap();
547 assert_ne!(
548 manifest.reader_feature_flags & FLAG_UNSTABLE_DATA_OVERLAY_FILES,
549 0
550 );
551 assert_ne!(
552 manifest.writer_feature_flags & FLAG_UNSTABLE_DATA_OVERLAY_FILES,
553 0
554 );
555 }
556
557 #[test]
558 fn test_apply_feature_flags_sets_spilled_row_lineage_flag() {
559 use crate::format::{DataFile, DataStorageFormat, Fragment, ROW_ID_FIELD_ID, RowIdMeta};
560 use crate::rowids::version::RowDatasetVersionMeta;
561 use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
562 use lance_core::datatypes::Schema;
563 use std::collections::HashMap;
564 use std::sync::Arc;
565
566 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
567 "id",
568 arrow_schema::DataType::Int64,
569 false,
570 )]);
571 let schema = Schema::try_from(&arrow_schema).unwrap();
572 let spilled =
573 DataFile::new_legacy_from_fields("lineage.lance", vec![ROW_ID_FIELD_ID], None);
574 let mut spilled_fragments = Vec::new();
576 for kind in 0..3 {
577 let mut fragment = Fragment::new(kind);
578 fragment.files.push(spilled.clone());
579 fragment.row_id_meta = Some(RowIdMeta::Inline(vec![].into()));
582 match kind {
583 0 => fragment.row_id_meta = Some(RowIdMeta::Column),
584 1 => fragment.created_at_version_meta = Some(RowDatasetVersionMeta::Column),
585 _ => fragment.last_updated_at_version_meta = Some(RowDatasetVersionMeta::Column),
586 }
587 spilled_fragments.push(fragment);
588 }
589 for fragment in spilled_fragments {
590 let mut manifest = Manifest::new(
591 schema.clone(),
592 Arc::new(vec![fragment]),
593 DataStorageFormat::default(),
594 HashMap::new(),
595 );
596 apply_feature_flags(&mut manifest, true, false).unwrap();
597 assert_ne!(
598 manifest.reader_feature_flags & FLAG_UNSTABLE_SPILLED_ROW_LINEAGE,
599 0
600 );
601 assert_ne!(
602 manifest.writer_feature_flags & FLAG_UNSTABLE_SPILLED_ROW_LINEAGE,
603 0
604 );
605 }
606
607 let mut fragment = Fragment::new(0);
610 fragment.row_id_meta = Some(RowIdMeta::Inline(vec![].into()));
611 let mut manifest = Manifest::new(
612 schema,
613 Arc::new(vec![fragment]),
614 DataStorageFormat::default(),
615 HashMap::new(),
616 );
617 apply_feature_flags(&mut manifest, true, false).unwrap();
618 assert_eq!(
619 manifest.reader_feature_flags & FLAG_UNSTABLE_SPILLED_ROW_LINEAGE,
620 0
621 );
622 }
623
624 #[test]
625 fn test_write_check() {
626 assert!(can_write_dataset(0));
627 assert!(can_write_dataset(super::FLAG_DELETION_FILES));
628 assert!(can_write_dataset(super::FLAG_STABLE_ROW_IDS));
629 assert!(can_write_dataset(super::FLAG_USE_V2_FORMAT_DEPRECATED));
630 assert!(can_write_dataset(super::FLAG_TABLE_CONFIG));
631 assert!(can_write_dataset(super::FLAG_BASE_PATHS));
632 assert!(can_write_dataset(super::FLAG_DISABLE_TRANSACTION_FILE));
633 assert_eq!(
637 can_write_dataset(super::FLAG_UNSTABLE_DATA_OVERLAY_FILES),
638 data_overlay_files_enabled()
639 );
640 assert!(can_write_dataset(
641 super::FLAG_DELETION_FILES
642 | super::FLAG_STABLE_ROW_IDS
643 | super::FLAG_USE_V2_FORMAT_DEPRECATED
644 | super::FLAG_TABLE_CONFIG
645 | super::FLAG_BASE_PATHS
646 ));
647 assert!(!can_write_dataset(super::FLAG_UNKNOWN));
648 }
649
650 #[test]
651 fn test_base_paths_feature_flags() {
652 use crate::format::{DataStorageFormat, Manifest};
653 use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
654 use lance_core::datatypes::Schema;
655 use std::collections::HashMap;
656 use std::sync::Arc;
657 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
659 "test_field",
660 arrow_schema::DataType::Int64,
661 false,
662 )]);
663 let schema = Schema::try_from(&arrow_schema).unwrap();
664 let mut normal_manifest = Manifest::new(
666 schema.clone(),
667 Arc::new(vec![]),
668 DataStorageFormat::default(),
669 HashMap::new(), );
671 apply_feature_flags(&mut normal_manifest, false, false).unwrap();
672 assert_eq!(normal_manifest.reader_feature_flags & FLAG_BASE_PATHS, 0);
673 assert_eq!(normal_manifest.writer_feature_flags & FLAG_BASE_PATHS, 0);
674 let mut base_paths: HashMap<u32, BasePath> = HashMap::new();
676 base_paths.insert(
677 1,
678 BasePath::new(
679 1,
680 "file:///path/to/original".to_string(),
681 Some("test_ref".to_string()),
682 true,
683 ),
684 );
685 let mut multi_base_manifest = Manifest::new(
686 schema,
687 Arc::new(vec![]),
688 DataStorageFormat::default(),
689 base_paths,
690 );
691 apply_feature_flags(&mut multi_base_manifest, false, false).unwrap();
692 assert_ne!(
693 multi_base_manifest.reader_feature_flags & FLAG_BASE_PATHS,
694 0
695 );
696 assert_ne!(
697 multi_base_manifest.writer_feature_flags & FLAG_BASE_PATHS,
698 0
699 );
700 }
701 #[test]
702 fn inheriting_carries_sticky_paired_bits_from_the_source() {
703 let mut source = empty_manifest();
704 source.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
705 source.writer_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
706 let mut destination = empty_manifest();
708
709 inherit_sticky_feature_flags(&mut destination, &source).unwrap();
710
711 assert_ne!(
712 destination.reader_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
713 0
714 );
715 assert_ne!(
716 destination.writer_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
717 0
718 );
719 }
720
721 #[test]
722 fn inheriting_refuses_a_half_set_source() {
723 for (reader, writer) in [
724 (FLAG_MIXED_DATA_FILE_VERSIONS, 0),
725 (0, FLAG_MIXED_DATA_FILE_VERSIONS),
726 ] {
727 let mut source = empty_manifest();
728 source.reader_feature_flags = reader;
729 source.writer_feature_flags = writer;
730 let mut destination = empty_manifest();
731
732 let err = inherit_sticky_feature_flags(&mut destination, &source).unwrap_err();
733
734 assert!(err.to_string().contains("only one of"), "{err}");
735 }
736 }
737
738 #[test]
739 fn apply_feature_flags_carries_sticky_paired_bits_across_its_reset() {
740 let mut manifest = empty_manifest();
741 manifest.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
742 manifest.writer_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
743
744 apply_feature_flags(&mut manifest, false, false).unwrap();
745
746 assert_ne!(
747 manifest.reader_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
748 0
749 );
750 assert_ne!(
751 manifest.writer_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
752 0
753 );
754 }
755
756 #[test]
757 fn apply_feature_flags_rejects_half_set_sticky_bits() {
758 let mut manifest = empty_manifest();
759 manifest.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
760
761 let err = apply_feature_flags(&mut manifest, false, false).unwrap_err();
762
763 assert!(matches!(err, Error::CorruptFile { .. }));
764 assert!(err.to_string().contains("only one of"), "{err}");
765 }
766
767 #[test]
768 fn paired_validation_rejects_half_set_mixed_version_capability() {
769 for (reader, writer) in [
770 (FLAG_MIXED_DATA_FILE_VERSIONS, 0),
771 (0, FLAG_MIXED_DATA_FILE_VERSIONS),
772 ] {
773 let mut manifest = empty_manifest();
774 manifest.reader_feature_flags = reader;
775 manifest.writer_feature_flags = writer;
776
777 let err = validate_paired_feature_flags(&manifest).unwrap_err();
778
779 assert!(err.to_string().contains("mixed data-file-version"), "{err}");
780 }
781 }
782
783 #[test]
784 fn writer_gate_accepts_mixed_capability_and_rejects_unknown_flags() {
785 let mut manifest = empty_manifest();
786 manifest.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
787 manifest.writer_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
788 ensure_can_write_manifest(&manifest).unwrap();
789
790 manifest.writer_feature_flags |= FLAG_UNKNOWN;
791 let err = ensure_can_write_manifest(&manifest).unwrap_err();
792 assert!(matches!(err, Error::NotSupported { .. }));
793 assert!(err.to_string().contains("cannot be written"), "{err}");
794 }
795
796 fn empty_manifest() -> Manifest {
797 use crate::format::DataStorageFormat;
798 use arrow_schema::{DataType, Field as ArrowField, Schema as ArrowSchema};
799 use lance_core::datatypes::Schema;
800 use std::collections::HashMap;
801 use std::sync::Arc;
802
803 let arrow_schema = ArrowSchema::new(vec![ArrowField::new("i", DataType::Int32, false)]);
804 Manifest::new(
805 Schema::try_from(&arrow_schema).unwrap(),
806 Arc::new(vec![]),
807 DataStorageFormat::default(),
808 HashMap::new(),
809 )
810 }
811
812 #[test]
813 fn mixed_capability_is_below_the_unknown_boundary() {
814 assert!(can_read_dataset(FLAG_COVERED_INDEX_METADATA));
815 assert!(can_write_dataset(FLAG_COVERED_INDEX_METADATA));
816 assert!(can_read_dataset(FLAG_MIXED_DATA_FILE_VERSIONS));
817 assert!(can_write_dataset(FLAG_MIXED_DATA_FILE_VERSIONS));
818 assert!(!can_read_dataset(FLAG_UNKNOWN));
819 }
820}