1use crate::format::Manifest;
7use lance_core::{Error, Result, datatypes::Schema};
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_SPILLED_ROW_LINEAGE: u64 = 1 << 11;
86#[deprecated(since = "14.0.0", note = "use FLAG_SPILLED_ROW_LINEAGE instead")]
88pub const FLAG_UNSTABLE_SPILLED_ROW_LINEAGE: u64 = FLAG_SPILLED_ROW_LINEAGE;
89pub const FLAG_FRAGMENT_TREE: u64 = 1 << 12;
93pub const FLAG_INDEPENDENT_COVERING_FIELDS: u64 = 1 << 13;
105pub const FLAG_MANAGED_BLOBS: u64 = 1 << 14;
109
110pub const MANAGED_BLOBS_META_KEY: &str = "lance.blob.managed";
114
115pub const FLAG_NON_REUSABLE_FIELD_IDS: u64 = 1 << 15;
119pub const FLAG_ZONE_WRITE_SEEDS: u64 = 1 << 16;
125pub const FLAG_OBJECT_METADATA: u64 = 1 << 17;
130pub const FLAG_UNKNOWN: u64 = 1 << 18;
132
133const _: () = assert!(FLAG_COVERED_INDEX_METADATA < FLAG_UNKNOWN);
134const _: () = assert!(FLAG_COVERED_INDEX_METADATA >= 1 << 7);
137const _: () = assert!(FLAG_MIXED_DATA_FILE_VERSIONS < FLAG_UNKNOWN);
138const _: () = assert!(FLAG_NON_REUSABLE_FIELD_IDS < FLAG_UNKNOWN);
139const _: () = assert!(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS >= 1 << 9);
141const _: () = assert!(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS < FLAG_UNKNOWN);
142const _: () = assert!(FLAG_FRAGMENT_REUSE_INDEX < FLAG_UNKNOWN);
143const _: () = assert!(FLAG_SPILLED_ROW_LINEAGE < FLAG_UNKNOWN);
144const _: () = assert!(FLAG_FRAGMENT_TREE < FLAG_UNKNOWN);
145const _: () = assert!(FLAG_INDEPENDENT_COVERING_FIELDS < FLAG_UNKNOWN);
146
147const _: () = assert!(FLAG_MANAGED_BLOBS < FLAG_UNKNOWN);
148const _: () = assert!(FLAG_ZONE_WRITE_SEEDS < FLAG_UNKNOWN);
149const _: () = assert!(FLAG_OBJECT_METADATA < FLAG_UNKNOWN);
150
151pub(crate) const STICKY_PAIRED_FLAGS: u64 = FLAG_MIXED_DATA_FILE_VERSIONS
152 | FLAG_FRAGMENT_REUSE_INDEX
153 | FLAG_MANAGED_BLOBS
154 | FLAG_ZONE_WRITE_SEEDS;
155pub(crate) const STICKY_READER_FLAGS: u64 = STICKY_PAIRED_FLAGS;
156pub(crate) const STICKY_WRITER_FLAGS: u64 = STICKY_PAIRED_FLAGS | FLAG_NON_REUSABLE_FIELD_IDS;
157
158pub const ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV: &str = "LANCE_ENABLE_UNSTABLE_DATA_OVERLAY_FILES";
161
162pub fn apply_feature_flags(
164 manifest: &mut Manifest,
165 enable_stable_row_id: bool,
166 disable_transaction_file: bool,
167) -> Result<()> {
168 let covered_index_metadata = (manifest.reader_feature_flags | manifest.writer_feature_flags)
175 & FLAG_COVERED_INDEX_METADATA;
176 let sticky_paired_flags = validated_sticky_paired_flags(manifest)?;
177 let non_reusable_field_ids = manifest.max_allocated_field_id.is_some();
178 if non_reusable_field_ids {
179 manifest.writer_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
180 }
181 validate_non_reusable_field_id_flags(manifest)?;
182 manifest.reader_feature_flags = 0;
184 manifest.writer_feature_flags = 0;
185
186 let has_deletion_files = manifest
187 .fragments
188 .iter()
189 .any(|frag| frag.deletion_file.is_some());
190 if has_deletion_files {
191 manifest.reader_feature_flags |= FLAG_DELETION_FILES;
193 manifest.writer_feature_flags |= FLAG_DELETION_FILES;
194 }
195
196 let has_row_ids = manifest
198 .fragments
199 .iter()
200 .any(|frag| frag.row_id_meta.is_some());
201 if has_row_ids || enable_stable_row_id {
202 if !manifest
203 .fragments
204 .iter()
205 .all(|frag| frag.row_id_meta.is_some())
206 {
207 return Err(Error::invalid_input("All fragments must have row ids"));
208 }
209 manifest.reader_feature_flags |= FLAG_STABLE_ROW_IDS;
210 manifest.writer_feature_flags |= FLAG_STABLE_ROW_IDS;
211 }
212
213 if !manifest.config.is_empty() {
215 manifest.writer_feature_flags |= FLAG_TABLE_CONFIG;
216 }
217
218 if !manifest.base_paths.is_empty() {
220 manifest.reader_feature_flags |= FLAG_BASE_PATHS;
221 manifest.writer_feature_flags |= FLAG_BASE_PATHS;
222 }
223
224 let has_overlays = manifest
228 .fragments
229 .iter()
230 .any(|frag| !frag.overlays.is_empty());
231 if has_overlays {
232 manifest.reader_feature_flags |= FLAG_UNSTABLE_DATA_OVERLAY_FILES;
233 manifest.writer_feature_flags |= FLAG_UNSTABLE_DATA_OVERLAY_FILES;
234 }
235
236 let has_spilled_row_lineage = manifest
237 .fragments
238 .iter()
239 .any(|frag| frag.has_spilled_row_lineage());
240 if has_spilled_row_lineage {
241 manifest.reader_feature_flags |= FLAG_SPILLED_ROW_LINEAGE;
242 manifest.writer_feature_flags |= FLAG_SPILLED_ROW_LINEAGE;
243 }
244
245 if disable_transaction_file {
246 manifest.writer_feature_flags |= FLAG_DISABLE_TRANSACTION_FILE;
247 }
248
249 if non_reusable_field_ids {
250 manifest.writer_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
251 }
252
253 manifest.reader_feature_flags |= covered_index_metadata;
254 manifest.writer_feature_flags |= covered_index_metadata;
255 manifest.reader_feature_flags |= sticky_paired_flags;
256 manifest.writer_feature_flags |= sticky_paired_flags;
257
258 Ok(())
259}
260
261pub fn inherit_sticky_feature_flags(destination: &mut Manifest, source: &Manifest) -> Result<()> {
270 let sticky_flags = validated_sticky_paired_flags(source)?;
271 validate_non_reusable_field_id_flags(source)?;
272 destination.reader_feature_flags |= sticky_flags;
273 destination.writer_feature_flags |=
274 sticky_flags | (source.writer_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS);
275 Ok(())
276}
277
278pub fn managed_blobs_enabled(schema: &Schema) -> Result<bool> {
291 match schema
292 .metadata
293 .get(MANAGED_BLOBS_META_KEY)
294 .map(String::as_str)
295 {
296 None | Some("false") => Ok(false),
297 Some("true") => Ok(true),
298 Some(value) => Err(Error::invalid_input(format!(
299 "Schema metadata {MANAGED_BLOBS_META_KEY} must be true or false, got {value:?}"
300 ))),
301 }
302}
303
304fn data_overlay_files_enabled() -> bool {
307 cfg!(debug_assertions) || std::env::var_os(ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV).is_some()
308}
309
310#[doc(hidden)]
313pub fn frag_reuse_with_stable_row_ids_enabled() -> bool {
314 cfg!(debug_assertions)
315}
316
317fn mark_supported(flags: &mut u64, flag: u64, feature_enabled: bool) {
321 if !feature_enabled {
322 *flags &= !flag;
323 }
324}
325
326fn supported_flags_when(
330 overlay_enabled: bool,
331 frag_reuse_with_stable_row_ids_enabled: bool,
332) -> u64 {
333 let mut supported = FLAG_UNKNOWN - 1;
334 mark_supported(
335 &mut supported,
336 FLAG_UNSTABLE_DATA_OVERLAY_FILES,
337 overlay_enabled,
338 );
339 mark_supported(
340 &mut supported,
341 FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS,
342 frag_reuse_with_stable_row_ids_enabled,
343 );
344 mark_supported(&mut supported, FLAG_FRAGMENT_TREE, false);
346 mark_supported(&mut supported, FLAG_INDEPENDENT_COVERING_FIELDS, false);
347 mark_supported(&mut supported, FLAG_ZONE_WRITE_SEEDS, false);
348 mark_supported(&mut supported, FLAG_OBJECT_METADATA, false);
349 supported
350}
351
352fn supported_flags() -> u64 {
353 supported_flags_when(
354 data_overlay_files_enabled(),
355 frag_reuse_with_stable_row_ids_enabled(),
356 )
357}
358
359pub fn can_read_dataset(reader_flags: u64) -> bool {
360 reader_flags & !supported_flags() == 0
361}
362
363pub fn can_write_dataset(writer_flags: u64) -> bool {
364 writer_flags & !supported_flags() == 0
365}
366
367pub fn ensure_can_read_manifest(manifest: &Manifest) -> Result<()> {
370 validate_paired_feature_flags(manifest)?;
371 validate_non_reusable_field_id_flags(manifest)?;
372 if !can_read_dataset(manifest.reader_feature_flags) {
373 return Err(Error::not_supported_source(
374 format!(
375 "This dataset cannot be read by this version of Lance. Please upgrade \
376 Lance to read this dataset. Flags: {}",
377 manifest.reader_feature_flags
378 )
379 .into(),
380 ));
381 }
382 Ok(())
383}
384
385pub fn ensure_can_write_manifest(manifest: &Manifest) -> Result<()> {
388 validate_paired_feature_flags(manifest)?;
389 validate_non_reusable_field_id_flags(manifest)?;
390 if !can_write_dataset(manifest.writer_feature_flags) {
391 return Err(Error::not_supported_source(
392 format!(
393 "This dataset cannot be written by this version of Lance. Please upgrade \
394 Lance to write this dataset. Flags: {}",
395 manifest.writer_feature_flags
396 )
397 .into(),
398 ));
399 }
400 Ok(())
401}
402
403pub fn has_deprecated_v2_feature_flag(writer_flags: u64) -> bool {
404 writer_flags & FLAG_USE_V2_FORMAT_DEPRECATED != 0
405}
406
407pub fn validate_paired_feature_flags(manifest: &Manifest) -> Result<()> {
414 if (manifest.reader_feature_flags ^ manifest.writer_feature_flags) & FLAG_FRAGMENT_REUSE_INDEX
415 != 0
416 {
417 return Err(Error::corrupt_file_named(
418 "manifest",
419 "FRI requires both reader and writer feature flags",
420 ));
421 }
422
423 for (flag, name) in [
424 (FLAG_MIXED_DATA_FILE_VERSIONS, "mixed data-file-version"),
425 (FLAG_MANAGED_BLOBS, "Managed Blob"),
426 (FLAG_ZONE_WRITE_SEEDS, "zone write seed"),
427 ] {
428 let reader = manifest.reader_feature_flags & flag != 0;
429 let writer = manifest.writer_feature_flags & flag != 0;
430 if reader != writer {
431 return Err(Error::corrupt_file_named(
432 "manifest",
433 format!(
434 "Manifest has only one of the {name} reader and writer feature bits set, so its semantics are undefined"
435 ),
436 ));
437 }
438 }
439 Ok(())
440}
441
442pub fn validate_non_reusable_field_id_flags(manifest: &Manifest) -> Result<()> {
448 let activated = manifest.max_allocated_field_id.is_some();
449 let reader = manifest.reader_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS != 0;
450 let writer = manifest.writer_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS != 0;
451 if activated != writer {
452 return Err(Error::corrupt_file_named(
453 "manifest",
454 "Manifest non-reusable-field-ID high-water mark and writer feature flag disagree",
455 ));
456 }
457 if reader {
458 return Err(Error::corrupt_file_named(
459 "manifest",
460 "Manifest has a non-reusable-field-ID reader feature flag, but non-reusable field IDs only require writer support",
461 ));
462 }
463 Ok(())
464}
465
466fn validated_sticky_paired_flags(manifest: &Manifest) -> Result<u64> {
467 validate_paired_feature_flags(manifest)?;
468 Ok(manifest.reader_feature_flags & STICKY_PAIRED_FLAGS)
469}
470
471#[cfg(test)]
472mod tests {
473 #[rstest::rstest]
474 #[case::absent(None, false)]
475 #[case::disabled(Some("false"), false)]
476 #[case::enabled(Some("true"), true)]
477 fn managed_blobs_require_explicit_opt_in(#[case] value: Option<&str>, #[case] enabled: bool) {
478 let mut schema = empty_manifest().schema;
479 if let Some(value) = value {
480 schema
481 .metadata
482 .insert(MANAGED_BLOBS_META_KEY.to_string(), value.to_string());
483 }
484 assert_eq!(managed_blobs_enabled(&schema).unwrap(), enabled);
485 }
486
487 #[test]
488 fn managed_blobs_reject_invalid_opt_in() {
489 let mut schema = empty_manifest().schema;
490 schema
491 .metadata
492 .insert(MANAGED_BLOBS_META_KEY.to_string(), "invalid".to_string());
493 let error = managed_blobs_enabled(&schema).unwrap_err();
494 assert!(matches!(error, Error::InvalidInput { .. }));
495 assert!(
496 error
497 .to_string()
498 .contains("lance.blob.managed must be true or false")
499 );
500 }
501
502 #[test]
503 fn tagged_fri_flag_is_supported_and_sticky() {
504 let flag = super::FLAG_FRAGMENT_REUSE_INDEX;
505 assert!(super::can_read_dataset(flag));
506 assert!(super::can_write_dataset(flag));
507 let mut manifest = empty_manifest();
508 manifest.reader_feature_flags = flag;
509 assert!(super::ensure_can_read_manifest(&manifest).is_err());
510 manifest.writer_feature_flags = flag;
511 super::apply_feature_flags(&mut manifest, false, false).unwrap();
512 assert_eq!(manifest.reader_feature_flags & flag, flag);
513 assert_eq!(manifest.writer_feature_flags & flag, flag);
514 }
515
516 #[test]
522 fn test_covered_index_metadata_fences_older_builds_only() {
523 assert_eq!(
524 FLAG_COVERED_INDEX_METADATA, 128,
525 "the fence must sit on the boundary the released build shipped with"
526 );
527 assert!(
528 can_read_dataset(FLAG_COVERED_INDEX_METADATA),
529 "this build implements covering, so it must accept its own datasets"
530 );
531 assert!(can_write_dataset(FLAG_COVERED_INDEX_METADATA));
532 }
535
536 use super::*;
537 use crate::format::BasePath;
538
539 #[test]
540 fn test_object_metadata_flag_fences_writes_without_blocking_reads() {
541 let mut manifest = empty_manifest();
542 manifest.writer_feature_flags = FLAG_OBJECT_METADATA;
543 ensure_can_read_manifest(&manifest).unwrap();
544 let error = ensure_can_write_manifest(&manifest).unwrap_err();
545 assert!(matches!(error, Error::NotSupported { .. }));
546 assert!(error.to_string().contains("cannot be written"));
547 assert!(!can_read_dataset(FLAG_OBJECT_METADATA));
548 assert!(!can_write_dataset(FLAG_OBJECT_METADATA));
549 assert_eq!(FLAG_OBJECT_METADATA, 1 << 17);
550 assert!(!can_read_dataset(FLAG_ZONE_WRITE_SEEDS));
552 assert!(!can_write_dataset(FLAG_ZONE_WRITE_SEEDS));
553 assert_eq!(FLAG_UNKNOWN, 1 << 18);
554 assert!(!can_read_dataset(FLAG_UNKNOWN));
555 assert!(!can_write_dataset(FLAG_UNKNOWN));
556 }
557
558 #[test]
559 fn test_zone_write_seed_flag_is_reserved_and_sticky() {
560 assert_eq!(FLAG_ZONE_WRITE_SEEDS, 1 << 16);
561 assert!(!can_read_dataset(FLAG_ZONE_WRITE_SEEDS));
562 assert!(!can_write_dataset(FLAG_ZONE_WRITE_SEEDS));
563
564 let mut manifest = empty_manifest();
565 manifest.reader_feature_flags = FLAG_ZONE_WRITE_SEEDS;
566 manifest.writer_feature_flags = FLAG_ZONE_WRITE_SEEDS;
567 apply_feature_flags(&mut manifest, false, false).unwrap();
568 assert_eq!(manifest.reader_feature_flags, FLAG_ZONE_WRITE_SEEDS);
569 assert_eq!(manifest.writer_feature_flags, FLAG_ZONE_WRITE_SEEDS);
570
571 let read_error = ensure_can_read_manifest(&manifest).unwrap_err();
572 assert!(matches!(read_error, Error::NotSupported { .. }));
573 assert!(read_error.to_string().contains("Please upgrade"));
574 let write_error = ensure_can_write_manifest(&manifest).unwrap_err();
575 assert!(matches!(write_error, Error::NotSupported { .. }));
576 assert!(write_error.to_string().contains("Please upgrade"));
577
578 let mut destination = empty_manifest();
579 inherit_sticky_feature_flags(&mut destination, &manifest).unwrap();
580 assert_eq!(destination.reader_feature_flags, FLAG_ZONE_WRITE_SEEDS);
581 assert_eq!(destination.writer_feature_flags, FLAG_ZONE_WRITE_SEEDS);
582 }
583
584 #[test]
585 fn test_zone_write_seeds_require_both_feature_bits() {
586 for (reader, writer) in [(true, false), (false, true)] {
587 let mut manifest = empty_manifest();
588 manifest.reader_feature_flags = if reader { FLAG_ZONE_WRITE_SEEDS } else { 0 };
589 manifest.writer_feature_flags = if writer { FLAG_ZONE_WRITE_SEEDS } else { 0 };
590 let error = validate_paired_feature_flags(&manifest).unwrap_err();
591 assert!(matches!(error, Error::CorruptFile { .. }));
592 assert!(error.to_string().contains("zone write seed"));
593 }
594 }
595
596 #[test]
597 fn test_fragment_tree_flag_is_reserved_not_supported() {
598 assert_eq!(FLAG_FRAGMENT_TREE, 4096);
599 assert_eq!(
600 FLAG_FRAGMENT_TREE & (FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS | FLAG_FRAGMENT_REUSE_INDEX),
601 0
602 );
603 assert!(!can_read_dataset(FLAG_FRAGMENT_TREE));
604 assert!(!can_write_dataset(FLAG_FRAGMENT_TREE));
605 }
606
607 #[test]
608 fn test_frag_reuse_with_stable_row_ids_flag_release_gating() {
609 assert_eq!(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS, 512);
611 let without_support = supported_flags_when(true, false);
612 assert_ne!(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS & !without_support, 0);
613 assert_eq!(FLAG_STABLE_ROW_IDS & !without_support, 0);
614 let enabled = supported_flags_when(false, true);
615 assert_eq!(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS & !enabled, 0);
616 for unrelated in [
617 FLAG_UNSTABLE_DATA_OVERLAY_FILES,
618 FLAG_FRAGMENT_TREE,
619 FLAG_UNKNOWN,
620 ] {
621 assert_ne!(unrelated & !enabled, 0);
622 }
623 assert_eq!(
624 can_read_dataset(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS),
625 frag_reuse_with_stable_row_ids_enabled()
626 );
627 assert_eq!(
628 can_write_dataset(FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS),
629 frag_reuse_with_stable_row_ids_enabled()
630 );
631 }
632
633 #[test]
634 fn test_independent_covering_fields_flag_is_reserved() {
635 assert_eq!(FLAG_INDEPENDENT_COVERING_FIELDS, 8192);
636 let flags = FLAG_COVERED_INDEX_METADATA | FLAG_INDEPENDENT_COVERING_FIELDS;
637 assert!(!can_read_dataset(flags));
638 assert!(!can_write_dataset(flags));
639 }
640
641 #[test]
642 fn test_read_check() {
643 assert!(can_read_dataset(0));
644 assert!(can_read_dataset(super::FLAG_DELETION_FILES));
645 assert!(can_read_dataset(super::FLAG_STABLE_ROW_IDS));
646 assert!(can_read_dataset(super::FLAG_USE_V2_FORMAT_DEPRECATED));
647 assert!(can_read_dataset(super::FLAG_TABLE_CONFIG));
648 assert!(can_read_dataset(super::FLAG_BASE_PATHS));
649 assert!(can_read_dataset(super::FLAG_DISABLE_TRANSACTION_FILE));
650 assert!(can_read_dataset(super::FLAG_NON_REUSABLE_FIELD_IDS));
651 assert!(can_read_dataset(super::FLAG_MIXED_DATA_FILE_VERSIONS));
652 assert_eq!(
656 can_read_dataset(super::FLAG_UNSTABLE_DATA_OVERLAY_FILES),
657 data_overlay_files_enabled()
658 );
659 assert!(can_read_dataset(
660 super::FLAG_DELETION_FILES
661 | super::FLAG_STABLE_ROW_IDS
662 | super::FLAG_USE_V2_FORMAT_DEPRECATED
663 ));
664 assert!(!can_read_dataset(super::FLAG_UNKNOWN));
665 }
666
667 #[test]
668 fn test_data_overlay_flag_release_gating() {
669 let supported = supported_flags_when(false, false);
672 assert_eq!(supported & FLAG_UNSTABLE_DATA_OVERLAY_FILES, 0);
673 assert_eq!(FLAG_DELETION_FILES & !supported, 0);
674 assert_ne!(FLAG_UNSTABLE_DATA_OVERLAY_FILES & !supported, 0);
675 let supported = supported_flags_when(true, false);
677 assert_eq!(FLAG_UNSTABLE_DATA_OVERLAY_FILES & !supported, 0);
678 }
679
680 #[test]
681 fn test_spilled_row_lineage_flag_is_stable() {
682 assert_eq!(FLAG_SPILLED_ROW_LINEAGE, 2048);
683 let supported = supported_flags_when(false, false);
685 let flags = FLAG_STABLE_ROW_IDS | FLAG_SPILLED_ROW_LINEAGE;
686 assert_eq!(flags & !supported, 0);
687 assert!(can_read_dataset(flags));
688 assert!(can_write_dataset(flags));
689 for unsupported in [
690 FLAG_UNSTABLE_DATA_OVERLAY_FILES,
691 FLAG_FRAG_REUSE_WITH_STABLE_ROW_IDS,
692 FLAG_FRAGMENT_TREE,
693 FLAG_UNKNOWN,
694 ] {
695 assert_ne!(unsupported & !supported, 0);
696 }
697 }
698
699 #[test]
700 fn test_apply_feature_flags_sets_overlay_flag() {
701 use crate::format::overlay::{DataOverlayFile, OverlayCoverage};
702 use crate::format::{DataFile, DataStorageFormat, Fragment};
703 use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
704 use lance_core::datatypes::Schema;
705 use roaring::RoaringBitmap;
706 use std::collections::HashMap;
707 use std::sync::Arc;
708
709 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
710 "id",
711 arrow_schema::DataType::Int64,
712 false,
713 )]);
714 let schema = Schema::try_from(&arrow_schema).unwrap();
715 let mut fragment = Fragment::new(0);
716 fragment.overlays = vec![DataOverlayFile {
717 data_file: DataFile::new_legacy_from_fields("o.lance", vec![0], None),
718 coverage: OverlayCoverage::dense(RoaringBitmap::from_iter([0u32])),
719 committed_version: 1,
720 }];
721 let mut manifest = Manifest::new(
722 schema,
723 Arc::new(vec![fragment]),
724 DataStorageFormat::default(),
725 HashMap::new(),
726 );
727 apply_feature_flags(&mut manifest, false, false).unwrap();
728 assert_ne!(
729 manifest.reader_feature_flags & FLAG_UNSTABLE_DATA_OVERLAY_FILES,
730 0
731 );
732 assert_ne!(
733 manifest.writer_feature_flags & FLAG_UNSTABLE_DATA_OVERLAY_FILES,
734 0
735 );
736 }
737
738 #[test]
739 fn test_apply_feature_flags_sets_spilled_row_lineage_flag() {
740 use crate::format::{DataFile, DataStorageFormat, Fragment, ROW_ID_FIELD_ID, RowIdMeta};
741 use crate::rowids::version::RowDatasetVersionMeta;
742 use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
743 use lance_core::datatypes::Schema;
744 use std::collections::HashMap;
745 use std::sync::Arc;
746
747 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
748 "id",
749 arrow_schema::DataType::Int64,
750 false,
751 )]);
752 let schema = Schema::try_from(&arrow_schema).unwrap();
753 let spilled =
754 DataFile::new_legacy_from_fields("lineage.lance", vec![ROW_ID_FIELD_ID], None);
755 let mut spilled_fragments = Vec::new();
757 for kind in 0..3 {
758 let mut fragment = Fragment::new(kind);
759 fragment.files.push(spilled.clone());
760 fragment.row_id_meta = Some(RowIdMeta::Inline(vec![].into()));
763 match kind {
764 0 => fragment.row_id_meta = Some(RowIdMeta::Column),
765 1 => fragment.created_at_version_meta = Some(RowDatasetVersionMeta::Column),
766 _ => fragment.last_updated_at_version_meta = Some(RowDatasetVersionMeta::Column),
767 }
768 spilled_fragments.push(fragment);
769 }
770 for fragment in spilled_fragments {
771 let mut manifest = Manifest::new(
772 schema.clone(),
773 Arc::new(vec![fragment]),
774 DataStorageFormat::default(),
775 HashMap::new(),
776 );
777 apply_feature_flags(&mut manifest, true, false).unwrap();
778 assert_ne!(manifest.reader_feature_flags & FLAG_SPILLED_ROW_LINEAGE, 0);
779 assert_ne!(manifest.writer_feature_flags & FLAG_SPILLED_ROW_LINEAGE, 0);
780 }
781
782 let mut fragment = Fragment::new(0);
785 fragment.row_id_meta = Some(RowIdMeta::Inline(vec![].into()));
786 let mut manifest = Manifest::new(
787 schema,
788 Arc::new(vec![fragment]),
789 DataStorageFormat::default(),
790 HashMap::new(),
791 );
792 apply_feature_flags(&mut manifest, true, false).unwrap();
793 assert_eq!(manifest.reader_feature_flags & FLAG_SPILLED_ROW_LINEAGE, 0);
794 }
795
796 #[test]
797 fn test_write_check() {
798 assert!(can_write_dataset(0));
799 assert!(can_write_dataset(super::FLAG_DELETION_FILES));
800 assert!(can_write_dataset(super::FLAG_STABLE_ROW_IDS));
801 assert!(can_write_dataset(super::FLAG_USE_V2_FORMAT_DEPRECATED));
802 assert!(can_write_dataset(super::FLAG_TABLE_CONFIG));
803 assert!(can_write_dataset(super::FLAG_BASE_PATHS));
804 assert!(can_write_dataset(super::FLAG_DISABLE_TRANSACTION_FILE));
805 assert!(can_write_dataset(super::FLAG_NON_REUSABLE_FIELD_IDS));
806 assert!(can_write_dataset(super::FLAG_MIXED_DATA_FILE_VERSIONS));
807 assert_eq!(
811 can_write_dataset(super::FLAG_UNSTABLE_DATA_OVERLAY_FILES),
812 data_overlay_files_enabled()
813 );
814 assert!(can_write_dataset(
815 super::FLAG_DELETION_FILES
816 | super::FLAG_STABLE_ROW_IDS
817 | super::FLAG_USE_V2_FORMAT_DEPRECATED
818 | super::FLAG_TABLE_CONFIG
819 | super::FLAG_BASE_PATHS
820 ));
821 assert!(!can_write_dataset(super::FLAG_UNKNOWN));
822 }
823
824 #[test]
825 fn test_base_paths_feature_flags() {
826 use crate::format::{DataStorageFormat, Manifest};
827 use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
828 use lance_core::datatypes::Schema;
829 use std::collections::HashMap;
830 use std::sync::Arc;
831 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
833 "test_field",
834 arrow_schema::DataType::Int64,
835 false,
836 )]);
837 let schema = Schema::try_from(&arrow_schema).unwrap();
838 let mut normal_manifest = Manifest::new(
840 schema.clone(),
841 Arc::new(vec![]),
842 DataStorageFormat::default(),
843 HashMap::new(), );
845 apply_feature_flags(&mut normal_manifest, false, false).unwrap();
846 assert_eq!(normal_manifest.reader_feature_flags & FLAG_BASE_PATHS, 0);
847 assert_eq!(normal_manifest.writer_feature_flags & FLAG_BASE_PATHS, 0);
848 let mut base_paths: HashMap<u32, BasePath> = HashMap::new();
850 base_paths.insert(
851 1,
852 BasePath::new(
853 1,
854 "file:///path/to/original".to_string(),
855 Some("test_ref".to_string()),
856 true,
857 ),
858 );
859 let mut multi_base_manifest = Manifest::new(
860 schema,
861 Arc::new(vec![]),
862 DataStorageFormat::default(),
863 base_paths,
864 );
865 apply_feature_flags(&mut multi_base_manifest, false, false).unwrap();
866 assert_ne!(
867 multi_base_manifest.reader_feature_flags & FLAG_BASE_PATHS,
868 0
869 );
870 assert_ne!(
871 multi_base_manifest.writer_feature_flags & FLAG_BASE_PATHS,
872 0
873 );
874 }
875 #[test]
876 fn inheriting_carries_sticky_paired_bits_from_the_source() {
877 let mut source = empty_manifest();
878 source.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
879 source.writer_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
880 let mut destination = empty_manifest();
882
883 inherit_sticky_feature_flags(&mut destination, &source).unwrap();
884
885 assert_ne!(
886 destination.reader_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
887 0
888 );
889 assert_ne!(
890 destination.writer_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
891 0
892 );
893 }
894
895 #[test]
896 fn inheriting_preserves_non_reusable_field_id_writer_gate() {
897 let mut source = empty_manifest();
898 source.activate_non_reusable_field_ids();
899 source.writer_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
900 let mut destination = empty_manifest();
901
902 inherit_sticky_feature_flags(&mut destination, &source).unwrap();
903
904 assert_eq!(
905 destination.reader_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS,
906 0
907 );
908 assert_ne!(
909 destination.writer_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS,
910 0
911 );
912 }
913
914 #[test]
915 fn inheriting_refuses_a_half_set_source() {
916 for (reader, writer) in [
917 (FLAG_MIXED_DATA_FILE_VERSIONS, 0),
918 (0, FLAG_MIXED_DATA_FILE_VERSIONS),
919 ] {
920 let mut source = empty_manifest();
921 source.reader_feature_flags = reader;
922 source.writer_feature_flags = writer;
923 let mut destination = empty_manifest();
924
925 let err = inherit_sticky_feature_flags(&mut destination, &source).unwrap_err();
926
927 assert!(err.to_string().contains("only one of"), "{err}");
928 }
929 }
930
931 #[test]
932 fn apply_feature_flags_carries_sticky_paired_bits_across_its_reset() {
933 let mut manifest = empty_manifest();
934 manifest.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
935 manifest.writer_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
936
937 apply_feature_flags(&mut manifest, false, false).unwrap();
938
939 assert_ne!(
940 manifest.reader_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
941 0
942 );
943 assert_ne!(
944 manifest.writer_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
945 0
946 );
947 }
948
949 #[test]
950 fn apply_feature_flags_rejects_half_set_sticky_bits() {
951 let mut manifest = empty_manifest();
952 manifest.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
953
954 let err = apply_feature_flags(&mut manifest, false, false).unwrap_err();
955
956 assert!(matches!(err, Error::CorruptFile { .. }));
957 assert!(err.to_string().contains("only one of"), "{err}");
958 }
959
960 #[test]
961 fn paired_validation_rejects_half_set_mixed_version_capability() {
962 for (reader, writer) in [
963 (FLAG_MIXED_DATA_FILE_VERSIONS, 0),
964 (0, FLAG_MIXED_DATA_FILE_VERSIONS),
965 ] {
966 let mut manifest = empty_manifest();
967 manifest.reader_feature_flags = reader;
968 manifest.writer_feature_flags = writer;
969
970 let err = validate_paired_feature_flags(&manifest).unwrap_err();
971
972 assert!(err.to_string().contains("mixed data-file-version"), "{err}");
973 }
974 }
975
976 #[test]
977 fn writer_gate_accepts_mixed_capability_and_rejects_unknown_flags() {
978 let mut manifest = empty_manifest();
979 manifest.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
980 manifest.writer_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
981 ensure_can_write_manifest(&manifest).unwrap();
982
983 manifest.writer_feature_flags |= FLAG_UNKNOWN;
984 let err = ensure_can_write_manifest(&manifest).unwrap_err();
985 assert!(matches!(err, Error::NotSupported { .. }));
986 assert!(err.to_string().contains("cannot be written"), "{err}");
987 }
988
989 #[rstest::rstest]
990 fn apply_feature_flags_sets_writer_gate_for_explicit_non_reusable_field_id_activation(
991 #[values(false, true)] managed_blobs: bool,
992 ) {
993 let mut manifest = empty_manifest();
994 let managed_blob_flags = if managed_blobs { FLAG_MANAGED_BLOBS } else { 0 };
995 manifest.reader_feature_flags = managed_blob_flags;
996 manifest.writer_feature_flags = managed_blob_flags;
997 manifest.activate_non_reusable_field_ids();
998
999 apply_feature_flags(&mut manifest, false, false).unwrap();
1000
1001 assert_eq!(
1002 manifest.reader_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS,
1003 0
1004 );
1005 assert_ne!(
1006 manifest.writer_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS,
1007 0
1008 );
1009 assert_eq!(
1010 manifest.reader_feature_flags & FLAG_MANAGED_BLOBS,
1011 managed_blob_flags
1012 );
1013 assert_eq!(
1014 manifest.writer_feature_flags & FLAG_MANAGED_BLOBS,
1015 managed_blob_flags
1016 );
1017 }
1018
1019 #[test]
1020 fn apply_feature_flags_rejects_non_reusable_field_id_reader_flag() {
1021 let mut manifest = empty_manifest();
1022 manifest.activate_non_reusable_field_ids();
1023 manifest.reader_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
1024 manifest.writer_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
1025
1026 let err = apply_feature_flags(&mut manifest, false, false).unwrap_err();
1027
1028 assert!(
1029 err.to_string().contains("only require writer support"),
1030 "{err}"
1031 );
1032 }
1033
1034 #[test]
1035 fn non_reusable_field_id_marker_and_writer_gate_must_agree() {
1036 let mut activated_without_gate = empty_manifest();
1037 activated_without_gate.activate_non_reusable_field_ids();
1038 assert!(validate_non_reusable_field_id_flags(&activated_without_gate).is_err());
1039
1040 let mut gate_without_marker = empty_manifest();
1041 gate_without_marker.writer_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
1042 assert!(validate_non_reusable_field_id_flags(&gate_without_marker).is_err());
1043
1044 let mut writer_only = empty_manifest();
1045 writer_only.activate_non_reusable_field_ids();
1046 writer_only.writer_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
1047 validate_non_reusable_field_id_flags(&writer_only).unwrap();
1048
1049 let mut paired = writer_only.clone();
1050 paired.reader_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
1051 assert!(validate_non_reusable_field_id_flags(&paired).is_err());
1052
1053 let mut reader_without_activation = empty_manifest();
1054 reader_without_activation.reader_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
1055 assert!(validate_non_reusable_field_id_flags(&reader_without_activation).is_err());
1056 }
1057
1058 #[rstest::rstest]
1059 #[case::reader_only(true, false)]
1060 #[case::writer_only(false, true)]
1061 #[case::paired(true, true)]
1062 fn managed_capability_is_paired_and_sticky(#[case] reader: bool, #[case] writer: bool) {
1063 let mut source = empty_manifest();
1064 source.reader_feature_flags = if reader { FLAG_MANAGED_BLOBS } else { 0 };
1065 source.writer_feature_flags = if writer { FLAG_MANAGED_BLOBS } else { 0 };
1066 if reader != writer {
1067 for error in [
1068 ensure_can_read_manifest(&source).unwrap_err(),
1069 ensure_can_write_manifest(&source).unwrap_err(),
1070 ] {
1071 assert!(matches!(error, Error::CorruptFile { .. }));
1072 assert!(error.to_string().contains("Managed Blob"));
1073 }
1074 return;
1075 }
1076 ensure_can_read_manifest(&source).unwrap();
1077 ensure_can_write_manifest(&source).unwrap();
1078 let mut destination = empty_manifest();
1079 inherit_sticky_feature_flags(&mut destination, &source).unwrap();
1080 apply_feature_flags(&mut destination, false, false).unwrap();
1081 assert!(destination.has_managed_blobs());
1082 assert_eq!(
1083 destination.writer_feature_flags & FLAG_MANAGED_BLOBS,
1084 FLAG_MANAGED_BLOBS
1085 );
1086 assert_ne!(destination.reader_feature_flags & !(128 - 1), 0);
1088 }
1089
1090 fn empty_manifest() -> Manifest {
1091 use crate::format::DataStorageFormat;
1092 use arrow_schema::{DataType, Field as ArrowField, Schema as ArrowSchema};
1093 use lance_core::datatypes::Schema;
1094 use std::collections::HashMap;
1095 use std::sync::Arc;
1096
1097 let arrow_schema = ArrowSchema::new(vec![ArrowField::new("i", DataType::Int32, false)]);
1098 Manifest::new(
1099 Schema::try_from(&arrow_schema).unwrap(),
1100 Arc::new(vec![]),
1101 DataStorageFormat::default(),
1102 HashMap::new(),
1103 )
1104 }
1105
1106 #[test]
1107 fn paired_capabilities_are_below_the_unknown_boundary() {
1108 assert!(can_read_dataset(FLAG_COVERED_INDEX_METADATA));
1109 assert!(can_write_dataset(FLAG_COVERED_INDEX_METADATA));
1110 assert!(can_read_dataset(FLAG_MIXED_DATA_FILE_VERSIONS));
1111 assert!(can_write_dataset(FLAG_MIXED_DATA_FILE_VERSIONS));
1112 assert!(can_read_dataset(FLAG_MANAGED_BLOBS));
1113 assert!(can_write_dataset(FLAG_MANAGED_BLOBS));
1114 assert!(!can_read_dataset(FLAG_UNKNOWN));
1115 assert!(!can_write_dataset(FLAG_UNKNOWN));
1116 }
1117}