Skip to main content

lance_table/
feature_flags.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! Feature flags
5
6use crate::format::Manifest;
7use lance_core::{Error, Result};
8
9/// Fragments may contain deletion files, which record the tombstones of
10/// soft-deleted rows.
11pub const FLAG_DELETION_FILES: u64 = 1 << 0;
12/// Row ids are stable for both moves and updates. Fragments contain an index
13/// mapping row ids to row addresses.
14pub const FLAG_STABLE_ROW_IDS: u64 = 1 << 1;
15/// Files are written with the new v2 format (this flag is no longer used)
16pub const FLAG_USE_V2_FORMAT_DEPRECATED: u64 = 1 << 2;
17/// Table config is present
18pub const FLAG_TABLE_CONFIG: u64 = 1 << 3;
19/// Dataset uses multiple base paths (for shallow clones or multi-base datasets)
20pub const FLAG_BASE_PATHS: u64 = 1 << 4;
21/// Disable writing transaction file under _transaction/, this flag is set when we only want to write inline transaction in manifest
22pub const FLAG_DISABLE_TRANSACTION_FILE: u64 = 1 << 5;
23/// Fragments contain data overlay files, which supply new values for a subset of
24/// cells without rewriting base data files. A reader that does not understand
25/// overlays must refuse the dataset, since ignoring an overlay would silently
26/// return stale base values.
27///
28/// Data overlay files are not yet a released feature: in release builds this flag
29/// is treated as unknown (so a release reader/writer refuses an overlay dataset)
30/// unless [`ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV`] is set, which lets benchmarks opt in.
31/// Debug builds always understand it so tests exercise the path.
32pub const FLAG_UNSTABLE_DATA_OVERLAY_FILES: u64 = 1 << 6;
33/// Some index declares covering columns: `IndexMetadata.covering_fields` names
34/// columns the index carries values for but is not keyed on.
35///
36/// Covering makes `fields` mean "keyed columns followed by carried columns"
37/// rather than "the columns this index is searched on". A reader without this
38/// bit still selects a vector index by testing membership of `fields`, so it
39/// would answer a query on a merely-carried column with an index keyed on a
40/// different column and return wrong neighbours with no error. A writer without
41/// it would maintain the index as though every entry of `fields` were keyed.
42/// Both must refuse the table.
43///
44/// This takes the bit reclaimed from the retired MemWAL index-catchup flag
45/// (<https://github.com/lance-format/lance/pull/8680>), which is the boundary the
46/// current released build treats as unknown -- so that build refuses a covering
47/// dataset without needing a change of its own. Builds from the window where the
48/// bit was allocated to index catch-up (v11.0.0-beta.4 through beta.17) still
49/// count it as supported and will open a covering dataset rather than refuse it;
50/// that exposure comes with the reclamation and is inherited by whichever flag
51/// takes the bit.
52pub const FLAG_COVERED_INDEX_METADATA: u64 = 1 << 7;
53/// A dataset may reference recognized V2 data files with different exact
54/// versions. Readers and writers must both understand the per-file version
55/// contract before either can safely access the dataset.
56pub const FLAG_MIXED_DATA_FILE_VERSIONS: u64 = 1 << 8;
57/// The first bit that is unknown as a feature flag
58pub const FLAG_UNKNOWN: u64 = 1 << 9;
59
60const _: () = assert!(FLAG_COVERED_INDEX_METADATA < FLAG_UNKNOWN);
61// The fence needs a bit the current released build already refuses, which means
62// at or above the boundary that build shipped with (bit 7).
63const _: () = assert!(FLAG_COVERED_INDEX_METADATA >= 1 << 7);
64const _: () = assert!(FLAG_MIXED_DATA_FILE_VERSIONS < FLAG_UNKNOWN);
65
66pub(crate) const STICKY_PAIRED_FLAGS: u64 = FLAG_MIXED_DATA_FILE_VERSIONS;
67
68/// Environment variable that opts a release build into reading and writing data
69/// overlay files before the feature is generally released.
70pub const ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV: &str = "LANCE_ENABLE_UNSTABLE_DATA_OVERLAY_FILES";
71
72/// Set the reader and writer feature flags in the manifest based on the contents of the manifest.
73pub fn apply_feature_flags(
74    manifest: &mut Manifest,
75    enable_stable_row_id: bool,
76    disable_transaction_file: bool,
77) -> Result<()> {
78    // Carried across the reset: a `Manifest` only points at its index section,
79    // so whether any index declares covering columns is not visible here. `build_manifest` decides it from the index list it is
80    // committing and sets the bit after calling this; without the carry the
81    // second call, from `write_manifest_file`, would clear that decision
82    // immediately before the write.
83    let covered_index_metadata = (manifest.reader_feature_flags | manifest.writer_feature_flags)
84        & FLAG_COVERED_INDEX_METADATA;
85    let sticky_paired_flags = validated_sticky_paired_flags(manifest)?;
86
87    // Reset flags
88    manifest.reader_feature_flags = 0;
89    manifest.writer_feature_flags = 0;
90
91    let has_deletion_files = manifest
92        .fragments
93        .iter()
94        .any(|frag| frag.deletion_file.is_some());
95    if has_deletion_files {
96        // Both readers and writers need to be able to read deletion files
97        manifest.reader_feature_flags |= FLAG_DELETION_FILES;
98        manifest.writer_feature_flags |= FLAG_DELETION_FILES;
99    }
100
101    // If any fragment has row ids, they must all have row ids.
102    let has_row_ids = manifest
103        .fragments
104        .iter()
105        .any(|frag| frag.row_id_meta.is_some());
106    if has_row_ids || enable_stable_row_id {
107        if !manifest
108            .fragments
109            .iter()
110            .all(|frag| frag.row_id_meta.is_some())
111        {
112            return Err(Error::invalid_input("All fragments must have row ids"));
113        }
114        manifest.reader_feature_flags |= FLAG_STABLE_ROW_IDS;
115        manifest.writer_feature_flags |= FLAG_STABLE_ROW_IDS;
116    }
117
118    // Test whether any table metadata has been set
119    if !manifest.config.is_empty() {
120        manifest.writer_feature_flags |= FLAG_TABLE_CONFIG;
121    }
122
123    // Check if this dataset uses multiple base paths (for shallow clones or multi-base datasets)
124    if !manifest.base_paths.is_empty() {
125        manifest.reader_feature_flags |= FLAG_BASE_PATHS;
126        manifest.writer_feature_flags |= FLAG_BASE_PATHS;
127    }
128
129    // Overlay files change cell values on read, so a reader that ignores them
130    // would return stale base values. Both readers and writers must understand
131    // them.
132    let has_overlays = manifest
133        .fragments
134        .iter()
135        .any(|frag| !frag.overlays.is_empty());
136    if has_overlays {
137        manifest.reader_feature_flags |= FLAG_UNSTABLE_DATA_OVERLAY_FILES;
138        manifest.writer_feature_flags |= FLAG_UNSTABLE_DATA_OVERLAY_FILES;
139    }
140
141    if disable_transaction_file {
142        manifest.writer_feature_flags |= FLAG_DISABLE_TRANSACTION_FILE;
143    }
144
145    manifest.reader_feature_flags |= covered_index_metadata;
146    manifest.writer_feature_flags |= covered_index_metadata;
147    manifest.reader_feature_flags |= sticky_paired_flags;
148    manifest.writer_feature_flags |= sticky_paired_flags;
149
150    Ok(())
151}
152
153/// Carry sticky paired capabilities from the manifest a new one is derived
154/// from.
155///
156/// [`apply_feature_flags`] carries these bits across its own reset, but it only
157/// ever sees one manifest. Constructors preserve these flags, and this helper
158/// also validates that the source is not half-set before a derived manifest is
159/// committed.
160///
161/// A half-set state is refused rather than normalized: one bit set means a
162/// legacy reader or a legacy writer is still permitted, which is neither mode.
163pub fn inherit_sticky_feature_flags(destination: &mut Manifest, source: &Manifest) -> Result<()> {
164    let sticky_flags = validated_sticky_paired_flags(source)?;
165    destination.reader_feature_flags |= sticky_flags;
166    destination.writer_feature_flags |= sticky_flags;
167    Ok(())
168}
169
170/// Whether this build understands data overlay files: always in debug builds,
171/// and in release builds only when [`ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV`] is set.
172fn data_overlay_files_enabled() -> bool {
173    cfg!(debug_assertions) || std::env::var_os(ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV).is_some()
174}
175
176/// Clear `flag` from `flags` when its gating feature is not enabled in this
177/// build; leave it set otherwise. One call per unstable flag, so support for
178/// several unstable features chains cleanly.
179fn mark_supported(flags: &mut u64, flag: u64, feature_enabled: bool) {
180    if !feature_enabled {
181        *flags &= !flag;
182    }
183}
184
185/// The feature-flag bits this build understands, given whether overlay support
186/// is enabled. Split out from [`supported_flags`] so the policy is testable
187/// without toggling the build profile or environment.
188fn supported_flags_when(overlay_enabled: bool) -> u64 {
189    let mut supported = FLAG_UNKNOWN - 1;
190    mark_supported(
191        &mut supported,
192        FLAG_UNSTABLE_DATA_OVERLAY_FILES,
193        overlay_enabled,
194    );
195    supported
196}
197
198fn supported_flags() -> u64 {
199    supported_flags_when(data_overlay_files_enabled())
200}
201
202pub fn can_read_dataset(reader_flags: u64) -> bool {
203    reader_flags & !supported_flags() == 0
204}
205
206pub fn can_write_dataset(writer_flags: u64) -> bool {
207    writer_flags & !supported_flags() == 0
208}
209
210/// Refuse reads from manifests whose required reader features this build does
211/// not support or whose paired capabilities are inconsistent.
212pub fn ensure_can_read_manifest(manifest: &Manifest) -> Result<()> {
213    validate_paired_feature_flags(manifest)?;
214    if !can_read_dataset(manifest.reader_feature_flags) {
215        return Err(Error::not_supported_source(
216            format!(
217                "This dataset cannot be read by this version of Lance. Please upgrade \
218                 Lance to read this dataset. Flags: {}",
219                manifest.reader_feature_flags
220            )
221            .into(),
222        ));
223    }
224    Ok(())
225}
226
227/// Refuse writes to manifests whose required writer features this build does
228/// not support or whose paired capabilities are inconsistent.
229pub fn ensure_can_write_manifest(manifest: &Manifest) -> Result<()> {
230    validate_paired_feature_flags(manifest)?;
231    if !can_write_dataset(manifest.writer_feature_flags) {
232        return Err(Error::not_supported_source(
233            format!(
234                "This dataset cannot be written by this version of Lance. Please upgrade \
235                 Lance to write this dataset. Flags: {}",
236                manifest.writer_feature_flags
237            )
238            .into(),
239        ));
240    }
241    Ok(())
242}
243
244pub fn has_deprecated_v2_feature_flag(writer_flags: u64) -> bool {
245    writer_flags & FLAG_USE_V2_FORMAT_DEPRECATED != 0
246}
247
248/// Refuse a manifest whose paired reader and writer capability bits disagree.
249///
250/// One word set and the other not is neither mode: it would let a legacy reader
251/// or a legacy writer through on a table where the other half is enforcing. The
252/// commit path refuses to *produce* this, so seeing it on read means the
253/// manifest was written by something that did not.
254pub fn validate_paired_feature_flags(manifest: &Manifest) -> Result<()> {
255    let reader = manifest.reader_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS != 0;
256    let writer = manifest.writer_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS != 0;
257    if reader != writer {
258        return Err(Error::corrupt_file_named(
259            "manifest",
260            "Manifest has only one of the mixed data-file-version reader and writer feature bits set, \
261             so its semantics are undefined",
262        ));
263    }
264    Ok(())
265}
266
267fn validated_sticky_paired_flags(manifest: &Manifest) -> Result<u64> {
268    validate_paired_feature_flags(manifest)?;
269    Ok(manifest.reader_feature_flags & STICKY_PAIRED_FLAGS)
270}
271
272#[cfg(test)]
273mod tests {
274    /// The covering fence only works if the bit is one the current released
275    /// build already rejects. That build's unknown boundary is 128, so the bit
276    /// has to be 128 and this build has to have moved its own boundary past it
277    /// -- otherwise either that build accepts a covering dataset, or we refuse
278    /// our own.
279    #[test]
280    fn test_covered_index_metadata_fences_older_builds_only() {
281        assert_eq!(
282            FLAG_COVERED_INDEX_METADATA, 128,
283            "the fence must sit on the boundary the released build shipped with"
284        );
285        assert!(
286            can_read_dataset(FLAG_COVERED_INDEX_METADATA),
287            "this build implements covering, so it must accept its own datasets"
288        );
289        assert!(can_write_dataset(FLAG_COVERED_INDEX_METADATA));
290        // A build whose boundary is still 128 refuses the bit, which is the fence;
291        // the module-level `const _` assertion keeps it at or above that boundary.
292    }
293
294    use super::*;
295    use crate::format::BasePath;
296
297    #[test]
298    fn test_read_check() {
299        assert!(can_read_dataset(0));
300        assert!(can_read_dataset(super::FLAG_DELETION_FILES));
301        assert!(can_read_dataset(super::FLAG_STABLE_ROW_IDS));
302        assert!(can_read_dataset(super::FLAG_USE_V2_FORMAT_DEPRECATED));
303        assert!(can_read_dataset(super::FLAG_TABLE_CONFIG));
304        assert!(can_read_dataset(super::FLAG_BASE_PATHS));
305        assert!(can_read_dataset(super::FLAG_DISABLE_TRANSACTION_FILE));
306        // Overlay support is gated on the build profile / env opt-in, so the
307        // flag is readable exactly when overlays are enabled (see
308        // test_data_overlay_flag_release_gating for the full policy).
309        assert_eq!(
310            can_read_dataset(super::FLAG_UNSTABLE_DATA_OVERLAY_FILES),
311            data_overlay_files_enabled()
312        );
313        assert!(can_read_dataset(
314            super::FLAG_DELETION_FILES
315                | super::FLAG_STABLE_ROW_IDS
316                | super::FLAG_USE_V2_FORMAT_DEPRECATED
317        ));
318        assert!(!can_read_dataset(super::FLAG_UNKNOWN));
319    }
320
321    #[test]
322    fn test_data_overlay_flag_release_gating() {
323        // Release default (overlays disabled): the overlay flag is treated as
324        // unknown so the dataset is refused, while other known flags still pass.
325        let supported = supported_flags_when(false);
326        assert_eq!(supported & FLAG_UNSTABLE_DATA_OVERLAY_FILES, 0);
327        assert_eq!(FLAG_DELETION_FILES & !supported, 0);
328        assert_ne!(FLAG_UNSTABLE_DATA_OVERLAY_FILES & !supported, 0);
329        // Enabled (debug or env opt-in): the overlay flag is understood.
330        let supported = supported_flags_when(true);
331        assert_eq!(FLAG_UNSTABLE_DATA_OVERLAY_FILES & !supported, 0);
332    }
333
334    #[test]
335    fn test_apply_feature_flags_sets_overlay_flag() {
336        use crate::format::overlay::{DataOverlayFile, OverlayCoverage};
337        use crate::format::{DataFile, DataStorageFormat, Fragment};
338        use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
339        use lance_core::datatypes::Schema;
340        use roaring::RoaringBitmap;
341        use std::collections::HashMap;
342        use std::sync::Arc;
343
344        let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
345            "id",
346            arrow_schema::DataType::Int64,
347            false,
348        )]);
349        let schema = Schema::try_from(&arrow_schema).unwrap();
350        let mut fragment = Fragment::new(0);
351        fragment.overlays = vec![DataOverlayFile {
352            data_file: DataFile::new_legacy_from_fields("o.lance", vec![0], None),
353            coverage: OverlayCoverage::dense(RoaringBitmap::from_iter([0u32])),
354            committed_version: 1,
355        }];
356        let mut manifest = Manifest::new(
357            schema,
358            Arc::new(vec![fragment]),
359            DataStorageFormat::default(),
360            HashMap::new(),
361        );
362        apply_feature_flags(&mut manifest, false, false).unwrap();
363        assert_ne!(
364            manifest.reader_feature_flags & FLAG_UNSTABLE_DATA_OVERLAY_FILES,
365            0
366        );
367        assert_ne!(
368            manifest.writer_feature_flags & FLAG_UNSTABLE_DATA_OVERLAY_FILES,
369            0
370        );
371    }
372
373    #[test]
374    fn test_write_check() {
375        assert!(can_write_dataset(0));
376        assert!(can_write_dataset(super::FLAG_DELETION_FILES));
377        assert!(can_write_dataset(super::FLAG_STABLE_ROW_IDS));
378        assert!(can_write_dataset(super::FLAG_USE_V2_FORMAT_DEPRECATED));
379        assert!(can_write_dataset(super::FLAG_TABLE_CONFIG));
380        assert!(can_write_dataset(super::FLAG_BASE_PATHS));
381        assert!(can_write_dataset(super::FLAG_DISABLE_TRANSACTION_FILE));
382        // Overlay support is gated on the build profile / env opt-in, so the
383        // flag is writable exactly when overlays are enabled (see
384        // test_data_overlay_flag_release_gating for the full policy).
385        assert_eq!(
386            can_write_dataset(super::FLAG_UNSTABLE_DATA_OVERLAY_FILES),
387            data_overlay_files_enabled()
388        );
389        assert!(can_write_dataset(
390            super::FLAG_DELETION_FILES
391                | super::FLAG_STABLE_ROW_IDS
392                | super::FLAG_USE_V2_FORMAT_DEPRECATED
393                | super::FLAG_TABLE_CONFIG
394                | super::FLAG_BASE_PATHS
395        ));
396        assert!(!can_write_dataset(super::FLAG_UNKNOWN));
397    }
398
399    #[test]
400    fn test_base_paths_feature_flags() {
401        use crate::format::{DataStorageFormat, Manifest};
402        use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
403        use lance_core::datatypes::Schema;
404        use std::collections::HashMap;
405        use std::sync::Arc;
406        // Create a basic schema for testing
407        let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
408            "test_field",
409            arrow_schema::DataType::Int64,
410            false,
411        )]);
412        let schema = Schema::try_from(&arrow_schema).unwrap();
413        // Test 1: Normal dataset (no base_paths) should not have FLAG_BASE_PATHS
414        let mut normal_manifest = Manifest::new(
415            schema.clone(),
416            Arc::new(vec![]),
417            DataStorageFormat::default(),
418            HashMap::new(), // Empty base_paths
419        );
420        apply_feature_flags(&mut normal_manifest, false, false).unwrap();
421        assert_eq!(normal_manifest.reader_feature_flags & FLAG_BASE_PATHS, 0);
422        assert_eq!(normal_manifest.writer_feature_flags & FLAG_BASE_PATHS, 0);
423        // Test 2: Dataset with base_paths (shallow clone or multi-base) should have FLAG_BASE_PATHS
424        let mut base_paths: HashMap<u32, BasePath> = HashMap::new();
425        base_paths.insert(
426            1,
427            BasePath::new(
428                1,
429                "file:///path/to/original".to_string(),
430                Some("test_ref".to_string()),
431                true,
432            ),
433        );
434        let mut multi_base_manifest = Manifest::new(
435            schema,
436            Arc::new(vec![]),
437            DataStorageFormat::default(),
438            base_paths,
439        );
440        apply_feature_flags(&mut multi_base_manifest, false, false).unwrap();
441        assert_ne!(
442            multi_base_manifest.reader_feature_flags & FLAG_BASE_PATHS,
443            0
444        );
445        assert_ne!(
446            multi_base_manifest.writer_feature_flags & FLAG_BASE_PATHS,
447            0
448        );
449    }
450    #[test]
451    fn inheriting_carries_sticky_paired_bits_from_the_source() {
452        let mut source = empty_manifest();
453        source.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
454        source.writer_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
455        // A fresh destination models any derived manifest before inheritance.
456        let mut destination = empty_manifest();
457
458        inherit_sticky_feature_flags(&mut destination, &source).unwrap();
459
460        assert_ne!(
461            destination.reader_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
462            0
463        );
464        assert_ne!(
465            destination.writer_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
466            0
467        );
468    }
469
470    #[test]
471    fn inheriting_refuses_a_half_set_source() {
472        for (reader, writer) in [
473            (FLAG_MIXED_DATA_FILE_VERSIONS, 0),
474            (0, FLAG_MIXED_DATA_FILE_VERSIONS),
475        ] {
476            let mut source = empty_manifest();
477            source.reader_feature_flags = reader;
478            source.writer_feature_flags = writer;
479            let mut destination = empty_manifest();
480
481            let err = inherit_sticky_feature_flags(&mut destination, &source).unwrap_err();
482
483            assert!(err.to_string().contains("only one of"), "{err}");
484        }
485    }
486
487    #[test]
488    fn apply_feature_flags_carries_sticky_paired_bits_across_its_reset() {
489        let mut manifest = empty_manifest();
490        manifest.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
491        manifest.writer_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
492
493        apply_feature_flags(&mut manifest, false, false).unwrap();
494
495        assert_ne!(
496            manifest.reader_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
497            0
498        );
499        assert_ne!(
500            manifest.writer_feature_flags & FLAG_MIXED_DATA_FILE_VERSIONS,
501            0
502        );
503    }
504
505    #[test]
506    fn apply_feature_flags_rejects_half_set_sticky_bits() {
507        let mut manifest = empty_manifest();
508        manifest.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
509
510        let err = apply_feature_flags(&mut manifest, false, false).unwrap_err();
511
512        assert!(matches!(err, Error::CorruptFile { .. }));
513        assert!(err.to_string().contains("only one of"), "{err}");
514    }
515
516    #[test]
517    fn paired_validation_rejects_half_set_mixed_version_capability() {
518        for (reader, writer) in [
519            (FLAG_MIXED_DATA_FILE_VERSIONS, 0),
520            (0, FLAG_MIXED_DATA_FILE_VERSIONS),
521        ] {
522            let mut manifest = empty_manifest();
523            manifest.reader_feature_flags = reader;
524            manifest.writer_feature_flags = writer;
525
526            let err = validate_paired_feature_flags(&manifest).unwrap_err();
527
528            assert!(err.to_string().contains("mixed data-file-version"), "{err}");
529        }
530    }
531
532    #[test]
533    fn writer_gate_accepts_mixed_capability_and_rejects_unknown_flags() {
534        let mut manifest = empty_manifest();
535        manifest.reader_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
536        manifest.writer_feature_flags = FLAG_MIXED_DATA_FILE_VERSIONS;
537        ensure_can_write_manifest(&manifest).unwrap();
538
539        manifest.writer_feature_flags |= FLAG_UNKNOWN;
540        let err = ensure_can_write_manifest(&manifest).unwrap_err();
541        assert!(matches!(err, Error::NotSupported { .. }));
542        assert!(err.to_string().contains("cannot be written"), "{err}");
543    }
544
545    fn empty_manifest() -> Manifest {
546        use crate::format::DataStorageFormat;
547        use arrow_schema::{DataType, Field as ArrowField, Schema as ArrowSchema};
548        use lance_core::datatypes::Schema;
549        use std::collections::HashMap;
550        use std::sync::Arc;
551
552        let arrow_schema = ArrowSchema::new(vec![ArrowField::new("i", DataType::Int32, false)]);
553        Manifest::new(
554            Schema::try_from(&arrow_schema).unwrap(),
555            Arc::new(vec![]),
556            DataStorageFormat::default(),
557            HashMap::new(),
558        )
559    }
560
561    #[test]
562    fn mixed_capability_is_below_the_unknown_boundary() {
563        assert!(can_read_dataset(FLAG_COVERED_INDEX_METADATA));
564        assert!(can_write_dataset(FLAG_COVERED_INDEX_METADATA));
565        assert!(can_read_dataset(FLAG_MIXED_DATA_FILE_VERSIONS));
566        assert!(can_write_dataset(FLAG_MIXED_DATA_FILE_VERSIONS));
567        assert!(!can_read_dataset(FLAG_UNKNOWN));
568    }
569}