lance_table/
feature_flags.rs1use crate::format::Manifest;
7use lance_core::{Error, Result};
8
9pub const FLAG_DELETION_FILES: u64 = 1;
12pub const FLAG_STABLE_ROW_IDS: u64 = 2;
15pub const FLAG_USE_V2_FORMAT_DEPRECATED: u64 = 4;
17pub const FLAG_TABLE_CONFIG: u64 = 8;
19pub const FLAG_BASE_PATHS: u64 = 16;
21pub const FLAG_DISABLE_TRANSACTION_FILE: u64 = 32;
23pub const FLAG_UNSTABLE_DATA_OVERLAY_FILES: u64 = 64;
33pub const FLAG_COVERED_INDEX_METADATA: u64 = 128;
53pub const FLAG_UNKNOWN: u64 = 256;
55
56const _: () = assert!(FLAG_COVERED_INDEX_METADATA < FLAG_UNKNOWN);
60const _: () = assert!(FLAG_COVERED_INDEX_METADATA >= 128);
63
64pub const ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV: &str = "LANCE_ENABLE_UNSTABLE_DATA_OVERLAY_FILES";
67
68pub fn apply_feature_flags(
70 manifest: &mut Manifest,
71 enable_stable_row_id: bool,
72 disable_transaction_file: bool,
73) -> Result<()> {
74 let covered_index_metadata = (manifest.reader_feature_flags | manifest.writer_feature_flags)
80 & FLAG_COVERED_INDEX_METADATA;
81
82 manifest.reader_feature_flags = 0;
84 manifest.writer_feature_flags = 0;
85
86 let has_deletion_files = manifest
87 .fragments
88 .iter()
89 .any(|frag| frag.deletion_file.is_some());
90 if has_deletion_files {
91 manifest.reader_feature_flags |= FLAG_DELETION_FILES;
93 manifest.writer_feature_flags |= FLAG_DELETION_FILES;
94 }
95
96 let has_row_ids = manifest
98 .fragments
99 .iter()
100 .any(|frag| frag.row_id_meta.is_some());
101 if has_row_ids || enable_stable_row_id {
102 if !manifest
103 .fragments
104 .iter()
105 .all(|frag| frag.row_id_meta.is_some())
106 {
107 return Err(Error::invalid_input("All fragments must have row ids"));
108 }
109 manifest.reader_feature_flags |= FLAG_STABLE_ROW_IDS;
110 manifest.writer_feature_flags |= FLAG_STABLE_ROW_IDS;
111 }
112
113 if !manifest.config.is_empty() {
115 manifest.writer_feature_flags |= FLAG_TABLE_CONFIG;
116 }
117
118 if !manifest.base_paths.is_empty() {
120 manifest.reader_feature_flags |= FLAG_BASE_PATHS;
121 manifest.writer_feature_flags |= FLAG_BASE_PATHS;
122 }
123
124 let has_overlays = manifest
128 .fragments
129 .iter()
130 .any(|frag| !frag.overlays.is_empty());
131 if has_overlays {
132 manifest.reader_feature_flags |= FLAG_UNSTABLE_DATA_OVERLAY_FILES;
133 manifest.writer_feature_flags |= FLAG_UNSTABLE_DATA_OVERLAY_FILES;
134 }
135
136 if disable_transaction_file {
137 manifest.writer_feature_flags |= FLAG_DISABLE_TRANSACTION_FILE;
138 }
139
140 manifest.reader_feature_flags |= covered_index_metadata;
141 manifest.writer_feature_flags |= covered_index_metadata;
142
143 Ok(())
144}
145
146fn data_overlay_files_enabled() -> bool {
149 cfg!(debug_assertions) || std::env::var_os(ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV).is_some()
150}
151
152fn mark_supported(flags: &mut u64, flag: u64, feature_enabled: bool) {
156 if !feature_enabled {
157 *flags &= !flag;
158 }
159}
160
161fn supported_flags_when(overlay_enabled: bool) -> u64 {
165 let mut supported = FLAG_UNKNOWN - 1;
166 mark_supported(
167 &mut supported,
168 FLAG_UNSTABLE_DATA_OVERLAY_FILES,
169 overlay_enabled,
170 );
171 supported
172}
173
174fn supported_flags() -> u64 {
175 supported_flags_when(data_overlay_files_enabled())
176}
177
178pub fn can_read_dataset(reader_flags: u64) -> bool {
179 reader_flags & !supported_flags() == 0
180}
181
182pub fn can_write_dataset(writer_flags: u64) -> bool {
183 writer_flags & !supported_flags() == 0
184}
185
186pub fn has_deprecated_v2_feature_flag(writer_flags: u64) -> bool {
187 writer_flags & FLAG_USE_V2_FORMAT_DEPRECATED != 0
188}
189
190#[cfg(test)]
191mod tests {
192 #[test]
198 fn test_covered_index_metadata_fences_older_builds_only() {
199 assert_eq!(
200 FLAG_COVERED_INDEX_METADATA, 128,
201 "the fence must sit on the boundary the released build shipped with"
202 );
203 assert!(
204 can_read_dataset(FLAG_COVERED_INDEX_METADATA),
205 "this build implements covering, so it must accept its own datasets"
206 );
207 assert!(can_write_dataset(FLAG_COVERED_INDEX_METADATA));
208 }
211
212 use super::*;
213 use crate::format::BasePath;
214
215 #[test]
216 fn test_read_check() {
217 assert!(can_read_dataset(0));
218 assert!(can_read_dataset(super::FLAG_DELETION_FILES));
219 assert!(can_read_dataset(super::FLAG_STABLE_ROW_IDS));
220 assert!(can_read_dataset(super::FLAG_USE_V2_FORMAT_DEPRECATED));
221 assert!(can_read_dataset(super::FLAG_TABLE_CONFIG));
222 assert!(can_read_dataset(super::FLAG_BASE_PATHS));
223 assert!(can_read_dataset(super::FLAG_DISABLE_TRANSACTION_FILE));
224 assert_eq!(
228 can_read_dataset(super::FLAG_UNSTABLE_DATA_OVERLAY_FILES),
229 data_overlay_files_enabled()
230 );
231 assert!(can_read_dataset(
232 super::FLAG_DELETION_FILES
233 | super::FLAG_STABLE_ROW_IDS
234 | super::FLAG_USE_V2_FORMAT_DEPRECATED
235 ));
236 assert!(!can_read_dataset(super::FLAG_UNKNOWN));
237 }
238
239 #[test]
240 fn test_data_overlay_flag_release_gating() {
241 let supported = supported_flags_when(false);
244 assert_eq!(supported & FLAG_UNSTABLE_DATA_OVERLAY_FILES, 0);
245 assert_eq!(FLAG_DELETION_FILES & !supported, 0);
246 assert_ne!(FLAG_UNSTABLE_DATA_OVERLAY_FILES & !supported, 0);
247 let supported = supported_flags_when(true);
249 assert_eq!(FLAG_UNSTABLE_DATA_OVERLAY_FILES & !supported, 0);
250 }
251
252 #[test]
253 fn test_apply_feature_flags_sets_overlay_flag() {
254 use crate::format::overlay::{DataOverlayFile, OverlayCoverage};
255 use crate::format::{DataFile, DataStorageFormat, Fragment};
256 use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
257 use lance_core::datatypes::Schema;
258 use roaring::RoaringBitmap;
259 use std::collections::HashMap;
260 use std::sync::Arc;
261
262 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
263 "id",
264 arrow_schema::DataType::Int64,
265 false,
266 )]);
267 let schema = Schema::try_from(&arrow_schema).unwrap();
268 let mut fragment = Fragment::new(0);
269 fragment.overlays = vec![DataOverlayFile {
270 data_file: DataFile::new_legacy_from_fields("o.lance", vec![0], None),
271 coverage: OverlayCoverage::dense(RoaringBitmap::from_iter([0u32])),
272 committed_version: 1,
273 }];
274 let mut manifest = Manifest::new(
275 schema,
276 Arc::new(vec![fragment]),
277 DataStorageFormat::default(),
278 HashMap::new(),
279 );
280 apply_feature_flags(&mut manifest, false, false).unwrap();
281 assert_ne!(
282 manifest.reader_feature_flags & FLAG_UNSTABLE_DATA_OVERLAY_FILES,
283 0
284 );
285 assert_ne!(
286 manifest.writer_feature_flags & FLAG_UNSTABLE_DATA_OVERLAY_FILES,
287 0
288 );
289 }
290
291 #[test]
292 fn test_write_check() {
293 assert!(can_write_dataset(0));
294 assert!(can_write_dataset(super::FLAG_DELETION_FILES));
295 assert!(can_write_dataset(super::FLAG_STABLE_ROW_IDS));
296 assert!(can_write_dataset(super::FLAG_USE_V2_FORMAT_DEPRECATED));
297 assert!(can_write_dataset(super::FLAG_TABLE_CONFIG));
298 assert!(can_write_dataset(super::FLAG_BASE_PATHS));
299 assert!(can_write_dataset(super::FLAG_DISABLE_TRANSACTION_FILE));
300 assert_eq!(
304 can_write_dataset(super::FLAG_UNSTABLE_DATA_OVERLAY_FILES),
305 data_overlay_files_enabled()
306 );
307 assert!(can_write_dataset(
308 super::FLAG_DELETION_FILES
309 | super::FLAG_STABLE_ROW_IDS
310 | super::FLAG_USE_V2_FORMAT_DEPRECATED
311 | super::FLAG_TABLE_CONFIG
312 | super::FLAG_BASE_PATHS
313 ));
314 assert!(!can_write_dataset(super::FLAG_UNKNOWN));
315 }
316
317 #[test]
318 fn test_base_paths_feature_flags() {
319 use crate::format::{DataStorageFormat, Manifest};
320 use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
321 use lance_core::datatypes::Schema;
322 use std::collections::HashMap;
323 use std::sync::Arc;
324 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
326 "test_field",
327 arrow_schema::DataType::Int64,
328 false,
329 )]);
330 let schema = Schema::try_from(&arrow_schema).unwrap();
331 let mut normal_manifest = Manifest::new(
333 schema.clone(),
334 Arc::new(vec![]),
335 DataStorageFormat::default(),
336 HashMap::new(), );
338 apply_feature_flags(&mut normal_manifest, false, false).unwrap();
339 assert_eq!(normal_manifest.reader_feature_flags & FLAG_BASE_PATHS, 0);
340 assert_eq!(normal_manifest.writer_feature_flags & FLAG_BASE_PATHS, 0);
341 let mut base_paths: HashMap<u32, BasePath> = HashMap::new();
343 base_paths.insert(
344 1,
345 BasePath::new(
346 1,
347 "file:///path/to/original".to_string(),
348 Some("test_ref".to_string()),
349 true,
350 ),
351 );
352 let mut multi_base_manifest = Manifest::new(
353 schema,
354 Arc::new(vec![]),
355 DataStorageFormat::default(),
356 base_paths,
357 );
358 apply_feature_flags(&mut multi_base_manifest, false, false).unwrap();
359 assert_ne!(
360 multi_base_manifest.reader_feature_flags & FLAG_BASE_PATHS,
361 0
362 );
363 assert_ne!(
364 multi_base_manifest.writer_feature_flags & FLAG_BASE_PATHS,
365 0
366 );
367 }
368}