1use async_trait::async_trait;
5use bytes::Bytes;
6use chrono::prelude::*;
7use lance_core::deepsize::DeepSizeOf;
8use lance_file::datatypes::{Fields, FieldsWithMeta};
9use lance_file::version::{ConcreteFileVersion, stable_file_version};
10use lance_file::versions::v1::{
11 encoding::populate_schema_dictionaries, reader::FileReader as V1FileReader,
12};
13use lance_io::traits::{ProtoStruct, Reader};
14use object_store::path::Path;
15use prost::Message;
16use prost_types::Timestamp;
17use std::collections::{BTreeMap, HashMap};
18use std::ops::Range;
19use std::sync::Arc;
20
21use super::{Fragment, InlineRowIds, RowIdMeta};
22use crate::feature_flags::{FLAG_COVERED_INDEX_METADATA, STICKY_READER_FLAGS, STICKY_WRITER_FLAGS};
23use crate::feature_flags::{FLAG_STABLE_ROW_IDS, has_deprecated_v2_feature_flag};
24use crate::format::fragment::DataFileFieldInterner;
25use crate::format::pb;
26use lance_core::cache::LanceCache;
27use lance_core::datatypes::Schema;
28use lance_core::{Error, Result};
29use lance_io::object_store::{ObjectStore, ObjectStoreRegistry};
30use lance_io::utils::read_struct;
31
32#[derive(Debug, Clone, PartialEq, DeepSizeOf)]
39pub struct Manifest {
40 pub schema: Schema,
42
43 pub version: u64,
45
46 pub branch: Option<String>,
48
49 pub writer_version: Option<WriterVersion>,
51
52 pub fragments: Arc<Vec<Fragment>>,
57
58 pub version_aux_data: usize,
60
61 pub index_section: Option<usize>,
63
64 pub timestamp_nanos: u128,
66
67 pub tag: Option<String>,
69
70 pub reader_feature_flags: u64,
72
73 pub writer_feature_flags: u64,
75
76 pub max_fragment_id: Option<u32>,
79
80 pub max_allocated_field_id: Option<i32>,
83
84 pub transaction_file: Option<String>,
86
87 pub transaction_section: Option<usize>,
89
90 fragment_offsets: Vec<usize>,
93
94 pub next_row_id: u64,
96
97 pub data_storage_format: DataStorageFormat,
99
100 pub config: HashMap<String, String>,
102
103 pub table_metadata: HashMap<String, String>,
109
110 pub base_paths: HashMap<u32, BasePath>,
112}
113
114pub const DETACHED_VERSION_MASK: u64 = 0x8000_0000_0000_0000;
116
117pub fn is_detached_version(version: u64) -> bool {
118 version & DETACHED_VERSION_MASK != 0
119}
120
121fn compute_fragment_offsets(fragments: &[Fragment]) -> Vec<usize> {
122 fragments
123 .iter()
124 .map(|f| f.num_rows().unwrap_or_default())
125 .chain([0]) .scan(0_usize, |offset, len| {
127 let start = *offset;
128 *offset += len;
129 Some(start)
130 })
131 .collect()
132}
133
134#[derive(Default)]
135pub struct ManifestSummary {
136 pub total_fragments: u64,
137 pub total_data_files: u64,
138 pub total_files_size: u64,
139 pub total_deletion_files: u64,
140 pub total_data_file_rows: u64,
141 pub total_deletion_file_rows: u64,
142 pub total_rows: u64,
143}
144
145impl From<ManifestSummary> for BTreeMap<String, String> {
146 fn from(summary: ManifestSummary) -> Self {
147 let mut stats_map = Self::new();
148 stats_map.insert(
149 "total_fragments".to_string(),
150 summary.total_fragments.to_string(),
151 );
152 stats_map.insert(
153 "total_data_files".to_string(),
154 summary.total_data_files.to_string(),
155 );
156 stats_map.insert(
157 "total_files_size".to_string(),
158 summary.total_files_size.to_string(),
159 );
160 stats_map.insert(
161 "total_deletion_files".to_string(),
162 summary.total_deletion_files.to_string(),
163 );
164 stats_map.insert(
165 "total_data_file_rows".to_string(),
166 summary.total_data_file_rows.to_string(),
167 );
168 stats_map.insert(
169 "total_deletion_file_rows".to_string(),
170 summary.total_deletion_file_rows.to_string(),
171 );
172 stats_map.insert("total_rows".to_string(), summary.total_rows.to_string());
173 stats_map
174 }
175}
176
177impl Manifest {
178 pub fn has_managed_blobs(&self) -> bool {
180 self.reader_feature_flags & crate::feature_flags::FLAG_MANAGED_BLOBS != 0
181 }
182
183 pub fn new(
184 schema: Schema,
185 fragments: Arc<Vec<Fragment>>,
186 data_storage_format: DataStorageFormat,
187 base_paths: HashMap<u32, BasePath>,
188 ) -> Self {
189 let fragment_offsets = compute_fragment_offsets(&fragments);
190
191 Self {
192 schema,
193 version: 1,
194 branch: None,
195 writer_version: Some(WriterVersion::default()),
196 fragments,
197 version_aux_data: 0,
198 index_section: None,
199 timestamp_nanos: 0,
200 tag: None,
201 reader_feature_flags: 0, writer_feature_flags: 0, max_fragment_id: None,
204 max_allocated_field_id: None,
205 transaction_file: None,
206 transaction_section: None,
207 fragment_offsets,
208 next_row_id: 0,
209 data_storage_format,
210 config: HashMap::new(),
211 table_metadata: HashMap::new(),
212 base_paths,
213 }
214 }
215
216 pub fn new_from_previous(
217 previous: &Self,
218 schema: Schema,
219 fragments: Arc<Vec<Fragment>>,
220 ) -> Self {
221 let fragment_offsets = compute_fragment_offsets(&fragments);
222
223 Self {
224 schema,
225 version: previous.version + 1,
226 branch: previous.branch.clone(),
227 writer_version: Some(WriterVersion::default()),
228 fragments,
229 version_aux_data: 0,
230 index_section: None, timestamp_nanos: 0, tag: None,
233 reader_feature_flags: previous.reader_feature_flags & STICKY_READER_FLAGS,
234 writer_feature_flags: previous.writer_feature_flags & STICKY_WRITER_FLAGS,
235 max_fragment_id: previous.max_fragment_id,
236 max_allocated_field_id: previous.max_allocated_field_id,
237 transaction_file: None,
238 transaction_section: None,
239 fragment_offsets,
240 next_row_id: previous.next_row_id,
241 data_storage_format: previous.data_storage_format.clone(),
242 config: previous.config.clone(),
243 table_metadata: previous.table_metadata.clone(),
244 base_paths: previous.base_paths.clone(),
245 }
246 }
247
248 pub fn shallow_clone(
253 &self,
254 ref_name: Option<String>,
255 ref_path: String,
256 ref_base_id: u32,
257 branch_name: Option<String>,
258 transaction_file: String,
259 ) -> Self {
260 let cloned_fragments = self
261 .fragments
262 .as_ref()
263 .iter()
264 .map(|fragment| {
265 let mut cloned_fragment = fragment.clone();
266 for file in cloned_fragment.referenced_lance_files_mut() {
267 if file.base_id.is_none() {
268 file.base_id = Some(ref_base_id);
269 }
270 }
271
272 if let Some(deletion) = &mut cloned_fragment.deletion_file
273 && deletion.base_id.is_none()
274 {
275 deletion.base_id = Some(ref_base_id);
276 }
277 cloned_fragment
278 })
279 .collect::<Vec<_>>();
280
281 Self {
282 schema: self.schema.clone(),
283 version: self.version,
284 branch: branch_name,
285 writer_version: self.writer_version.clone(),
286 fragments: Arc::new(cloned_fragments),
287 version_aux_data: self.version_aux_data,
288 index_section: None, timestamp_nanos: self.timestamp_nanos,
290 tag: None,
291 reader_feature_flags: self.reader_feature_flags
304 & (FLAG_COVERED_INDEX_METADATA | STICKY_READER_FLAGS | FLAG_STABLE_ROW_IDS),
305 writer_feature_flags: self.writer_feature_flags
306 & (FLAG_COVERED_INDEX_METADATA | STICKY_WRITER_FLAGS | FLAG_STABLE_ROW_IDS),
307 max_fragment_id: self.max_fragment_id,
308 max_allocated_field_id: self.max_allocated_field_id,
309 transaction_file: Some(transaction_file),
310 transaction_section: None,
311 fragment_offsets: self.fragment_offsets.clone(),
312 next_row_id: self.next_row_id,
313 data_storage_format: self.data_storage_format.clone(),
314 config: self.config.clone(),
315 base_paths: {
316 let mut base_paths = self.base_paths.clone();
317 let base_path = BasePath::new(ref_base_id, ref_path, ref_name, true);
318 base_paths.insert(ref_base_id, base_path);
319 base_paths
320 },
321 table_metadata: self.table_metadata.clone(),
322 }
323 }
324
325 pub fn timestamp(&self) -> DateTime<Utc> {
327 let nanos = self.timestamp_nanos % 1_000_000_000;
328 let seconds = ((self.timestamp_nanos - nanos) / 1_000_000_000) as i64;
329 Utc.from_utc_datetime(
330 &DateTime::from_timestamp(seconds, nanos as u32)
331 .unwrap_or_default()
332 .naive_utc(),
333 )
334 }
335
336 pub fn set_timestamp(&mut self, nanos: u128) {
338 self.timestamp_nanos = nanos;
339 }
340
341 pub fn config_mut(&mut self) -> &mut HashMap<String, String> {
343 &mut self.config
344 }
345
346 pub fn table_metadata_mut(&mut self) -> &mut HashMap<String, String> {
348 &mut self.table_metadata
349 }
350
351 pub fn schema_metadata_mut(&mut self) -> &mut HashMap<String, String> {
353 &mut self.schema.metadata
354 }
355
356 pub fn field_metadata_mut(&mut self, field_id: i32) -> Option<&mut HashMap<String, String>> {
360 self.schema
361 .field_by_id_mut(field_id)
362 .map(|field| &mut field.metadata)
363 }
364
365 #[deprecated(note = "Use config_mut() for direct access to config HashMap")]
367 pub fn update_config(&mut self, upsert_values: impl IntoIterator<Item = (String, String)>) {
368 self.config.extend(upsert_values);
369 }
370
371 #[deprecated(note = "Use config_mut() for direct access to config HashMap")]
373 pub fn delete_config_keys(&mut self, delete_keys: &[&str]) {
374 self.config
375 .retain(|key, _| !delete_keys.contains(&key.as_str()));
376 }
377
378 #[deprecated(note = "Use schema_metadata_mut() for direct access to schema metadata HashMap")]
380 pub fn replace_schema_metadata(&mut self, new_metadata: HashMap<String, String>) {
381 self.schema.metadata = new_metadata;
382 }
383
384 #[deprecated(
388 note = "Use field_metadata_mut(field_id) for direct access to field metadata HashMap"
389 )]
390 pub fn replace_field_metadata(
391 &mut self,
392 field_id: i32,
393 new_metadata: HashMap<String, String>,
394 ) -> Result<()> {
395 if let Some(field) = self.schema.field_by_id_mut(field_id) {
396 field.metadata = new_metadata;
397 Ok(())
398 } else {
399 Err(Error::invalid_input(format!(
400 "Field with id {} does not exist for replace_field_metadata",
401 field_id
402 )))
403 }
404 }
405
406 pub fn detach_sparse_inline_row_ids(&mut self, buffer_len: usize) {
416 let inline_bytes: usize = self
417 .fragments
418 .iter()
419 .filter_map(|fragment| match &fragment.row_id_meta {
420 Some(RowIdMeta::Inline(data)) => Some(data.len()),
421 _ => None,
422 })
423 .sum();
424 if inline_bytes == 0 || inline_bytes.saturating_mul(4) >= buffer_len {
426 return;
427 }
428 for fragment in Arc::make_mut(&mut self.fragments) {
429 if let Some(RowIdMeta::Inline(data)) = &fragment.row_id_meta {
430 let copied = InlineRowIds::from(Bytes::copy_from_slice(data));
431 fragment.row_id_meta = Some(RowIdMeta::Inline(copied));
432 }
433 }
434 }
435
436 pub fn update_max_fragment_id(&mut self) {
438 if self.fragments.is_empty() {
440 return;
441 }
442
443 let max_fragment_id = self
444 .fragments
445 .iter()
446 .map(|f| f.id)
447 .max()
448 .unwrap() .try_into()
450 .unwrap();
451
452 match self.max_fragment_id {
453 None => {
454 self.max_fragment_id = Some(max_fragment_id);
456 }
457 Some(current_max) => {
458 if max_fragment_id > current_max {
461 self.max_fragment_id = Some(max_fragment_id);
462 }
463 }
464 }
465 }
466
467 pub fn max_fragment_id(&self) -> Option<u64> {
472 if let Some(max_id) = self.max_fragment_id {
473 Some(max_id.into())
475 } else {
476 self.fragments.iter().map(|f| f.id).max()
478 }
479 }
480
481 pub fn max_field_id(&self) -> i32 {
487 self.max_allocated_field_id
488 .unwrap_or_else(|| self.max_referenced_field_id())
489 }
490
491 pub(crate) fn max_referenced_field_id(&self) -> i32 {
492 let schema_max_id = self.schema.max_field_id().unwrap_or(-1);
493 let fragment_max_id = self
494 .fragments
495 .iter()
496 .flat_map(|fragment| {
497 fragment
498 .referenced_lance_files()
499 .flat_map(|file| file.fields.iter())
500 })
501 .copied()
502 .max()
503 .unwrap_or(-1);
504 schema_max_id.max(fragment_max_id)
505 }
506
507 pub fn uses_non_reusable_field_ids(&self) -> bool {
509 self.max_allocated_field_id.is_some()
510 }
511
512 pub fn activate_non_reusable_field_ids(&mut self) {
514 if self.max_allocated_field_id.is_none() {
515 self.max_allocated_field_id = Some(self.max_referenced_field_id());
516 }
517 }
518
519 pub fn update_max_field_id(&mut self) {
521 let max_referenced_field_id = self.max_referenced_field_id();
522 if let Some(max_allocated_field_id) = &mut self.max_allocated_field_id {
523 *max_allocated_field_id = (*max_allocated_field_id).max(max_referenced_field_id);
524 }
525 }
526
527 pub fn fragments_since(&self, since: &Self) -> Result<Vec<Fragment>> {
530 if since.version >= self.version {
531 return Err(Error::invalid_input(format!(
532 "fragments_since: given version {} is newer than manifest version {}",
533 since.version, self.version
534 )));
535 }
536 let start = since.max_fragment_id();
537 Ok(self
538 .fragments
539 .iter()
540 .filter(|&f| start.map(|s| f.id > s).unwrap_or(true))
541 .cloned()
542 .collect())
543 }
544
545 pub fn fragments_by_offset_range(&self, range: Range<usize>) -> Vec<(usize, &Fragment)> {
561 let start = range.start;
562 let end = range.end;
563 let idx = self
564 .fragment_offsets
565 .binary_search(&start)
566 .unwrap_or_else(|idx| idx - 1);
567
568 let mut fragments = vec![];
569 for i in idx..self.fragments.len() {
570 if self.fragment_offsets[i] >= end
571 || self.fragment_offsets[i] + self.fragments[i].num_rows().unwrap_or_default()
572 <= start
573 {
574 break;
575 }
576 fragments.push((self.fragment_offsets[i], &self.fragments[i]));
577 }
578
579 fragments
580 }
581
582 pub fn uses_stable_row_ids(&self) -> bool {
584 self.reader_feature_flags & FLAG_STABLE_ROW_IDS != 0
585 }
586
587 pub fn serialized(&self) -> Vec<u8> {
590 let pb_manifest: pb::Manifest = self.into();
591 pb_manifest.encode_to_vec()
592 }
593
594 pub fn summary(&self) -> ManifestSummary {
605 let mut summary =
607 self.fragments
608 .iter()
609 .fold(ManifestSummary::default(), |mut summary, f| {
610 summary.total_data_files += f.files.len() as u64;
612 if let Some(num_rows) = f.num_rows() {
614 summary.total_rows += num_rows as u64;
615 }
616 for data_file in &f.files {
618 if let Some(size_bytes) = data_file.file_size_bytes.get() {
619 summary.total_files_size += size_bytes.get();
620 }
621 }
622 if f.deletion_file.is_some() {
624 summary.total_deletion_files += 1;
625 }
626 if let Some(deletion_file) = &f.deletion_file
628 && let Some(num_deleted) = deletion_file.num_deleted_rows
629 {
630 summary.total_deletion_file_rows += num_deleted as u64;
631 }
632 summary
633 });
634 summary.total_fragments = self.fragments.len() as u64;
635 summary.total_data_file_rows = summary.total_rows + summary.total_deletion_file_rows;
636
637 summary
638 }
639}
640
641pub async fn populate_manifest_schema_dictionaries(
660 manifest: &mut Manifest,
661 reader: &dyn Reader,
662) -> Result<()> {
663 match manifest.data_storage_format.version {
664 ConcreteFileVersion::V1 => {
665 populate_schema_dictionaries(&mut manifest.schema, reader).await?;
666 }
667 ConcreteFileVersion::V2_0
668 | ConcreteFileVersion::V2_1
669 | ConcreteFileVersion::V2_2
670 | ConcreteFileVersion::V2_3 => {}
671 }
672 Ok(())
673}
674
675#[derive(Debug, Clone, PartialEq)]
676pub struct BasePath {
677 pub id: u32,
678 pub name: Option<String>,
679 pub is_dataset_root: bool,
680 pub path: String,
682}
683
684impl BasePath {
685 pub fn unused_id(bases: impl IntoIterator<Item = u32>) -> Result<u32> {
689 let mut ids = bases.into_iter().collect::<Vec<_>>();
690 ids.sort_unstable();
691 ids.dedup();
692 let mut candidate = 0u32;
693 for id in ids {
694 if id != candidate {
695 break;
696 }
697 candidate = candidate
698 .checked_add(1)
699 .ok_or_else(|| Error::invalid_input("All u32 base IDs are already registered"))?;
700 }
701 Ok(candidate)
702 }
703
704 pub fn new(id: u32, path: String, name: Option<String>, is_dataset_root: bool) -> Self {
713 Self {
714 id,
715 name,
716 is_dataset_root,
717 path,
718 }
719 }
720
721 pub fn extract_path(&self, registry: Arc<ObjectStoreRegistry>) -> Result<Path> {
725 ObjectStore::extract_path_from_uri(registry, &self.path)
726 }
727}
728
729impl DeepSizeOf for BasePath {
730 fn deep_size_of_children(&self, context: &mut lance_core::deepsize::Context) -> usize {
731 self.name.deep_size_of_children(context)
732 + self.path.deep_size_of_children(context) * 2
733 + size_of::<bool>()
734 }
735}
736
737#[derive(Debug, Clone, PartialEq, DeepSizeOf)]
738pub struct WriterVersion {
739 pub library: String,
740 pub version: String,
741 pub prerelease: Option<String>,
742 pub build_metadata: Option<String>,
743}
744
745#[derive(Debug, Clone, PartialEq, DeepSizeOf)]
746pub struct DataStorageFormat {
747 pub file_format: String,
748 pub version: ConcreteFileVersion,
749}
750
751const LANCE_FORMAT_NAME: &str = "lance";
752
753impl DataStorageFormat {
754 pub fn new(version: ConcreteFileVersion) -> Self {
755 Self {
756 file_format: LANCE_FORMAT_NAME.to_string(),
757 version,
758 }
759 }
760
761 pub fn lance_file_format(&self) -> ConcreteFileVersion {
763 self.version
764 }
765}
766
767impl Default for DataStorageFormat {
768 fn default() -> Self {
769 Self::new(stable_file_version())
770 }
771}
772
773impl TryFrom<pb::manifest::DataStorageFormat> for DataStorageFormat {
774 type Error = Error;
775
776 fn try_from(pb: pb::manifest::DataStorageFormat) -> Result<Self> {
777 Ok(Self {
778 file_format: pb.file_format,
779 version: ConcreteFileVersion::from_manifest_string(&pb.version)?,
780 })
781 }
782}
783
784#[derive(Debug, Clone)]
791pub struct ManifestBuildConfig {
792 pub auto_set_feature_flags: bool,
795 pub timestamp_nanos: u128,
797 pub use_stable_row_ids: bool,
800 pub use_legacy_format: Option<bool>,
803 pub storage_format: Option<DataStorageFormat>,
806 pub disable_transaction_file: bool,
808 pub migration_next_row_id: Option<u64>,
812 pub activate_non_reusable_field_ids: bool,
814 pub spilled_row_lineage: std::sync::Arc<crate::rowids::version::SpilledRowLineage>,
821}
822
823#[derive(Debug, Clone, Copy, PartialEq, Eq)]
824pub enum VersionPart {
825 Major,
826 Minor,
827 Patch,
828}
829
830fn bump_version(version: &mut semver::Version, part: VersionPart) {
831 match part {
832 VersionPart::Major => {
833 version.major += 1;
834 version.minor = 0;
835 version.patch = 0;
836 }
837 VersionPart::Minor => {
838 version.minor += 1;
839 version.patch = 0;
840 }
841 VersionPart::Patch => {
842 version.patch += 1;
843 }
844 }
845}
846
847impl WriterVersion {
848 fn split_version(full_version: &str) -> Option<(String, Option<String>, Option<String>)> {
858 let mut parsed = semver::Version::parse(full_version).ok()?;
859
860 let prerelease = if parsed.pre.is_empty() {
861 None
862 } else {
863 Some(parsed.pre.to_string())
864 };
865
866 let build_metadata = if parsed.build.is_empty() {
867 None
868 } else {
869 Some(parsed.build.to_string())
870 };
871
872 parsed.pre = semver::Prerelease::EMPTY;
874 parsed.build = semver::BuildMetadata::EMPTY;
875 Some((parsed.to_string(), prerelease, build_metadata))
876 }
877
878 #[deprecated(note = "Use `lance_lib_version()` instead")]
881 pub fn semver(&self) -> Option<(u32, u32, u32, Option<&str>)> {
882 let (version_part, tag) = if let Some(dash_idx) = self.version.find('-') {
884 (
885 &self.version[..dash_idx],
886 Some(&self.version[dash_idx + 1..]),
887 )
888 } else {
889 (self.version.as_str(), None)
890 };
891
892 let mut parts = version_part.split('.');
893 let major = parts.next().unwrap_or("0").parse().ok()?;
894 let minor = parts.next().unwrap_or("0").parse().ok()?;
895 let patch = parts.next().unwrap_or("0").parse().ok()?;
896
897 Some((major, minor, patch, tag))
898 }
899
900 pub fn lance_lib_version(&self) -> Option<semver::Version> {
908 if self.library != "lance" {
909 return None;
910 }
911
912 let mut version = semver::Version::parse(&self.version).ok()?;
913
914 if let Some(ref prerelease) = self.prerelease {
915 version.pre = semver::Prerelease::new(prerelease).ok()?;
916 }
917
918 if let Some(ref build_metadata) = self.build_metadata {
919 version.build = semver::BuildMetadata::new(build_metadata).ok()?;
920 }
921
922 Some(version)
923 }
924
925 #[deprecated(
926 note = "Use `lance_lib_version()` instead, which safely checks the library field and returns Option"
927 )]
928 #[allow(deprecated)]
929 pub fn semver_or_panic(&self) -> (u32, u32, u32, Option<&str>) {
930 self.semver()
931 .unwrap_or_else(|| panic!("Invalid writer version: {}", self.version))
932 }
933
934 #[deprecated(note = "Use `lance_lib_version()` and its `older_than` method instead.")]
940 pub fn older_than(&self, major: u32, minor: u32, patch: u32) -> bool {
941 let version = self
942 .lance_lib_version()
943 .expect("Not lance library or invalid version");
944 let other = semver::Version {
945 major: major.into(),
946 minor: minor.into(),
947 patch: patch.into(),
948 pre: semver::Prerelease::EMPTY,
949 build: semver::BuildMetadata::EMPTY,
950 };
951 version < other
952 }
953
954 #[deprecated(note = "This is meant for testing and will be made private in future version.")]
955 pub fn bump(&self, part: VersionPart, keep_tag: bool) -> Self {
956 let mut version = self.lance_lib_version().expect("Should be lance version");
957 bump_version(&mut version, part);
958 if !keep_tag {
959 version.pre = semver::Prerelease::EMPTY;
960 }
961 let (clean_version, prerelease, build_metadata) = Self::split_version(&version.to_string())
962 .expect("Bumped version should be valid semver");
963 Self {
964 library: self.library.clone(),
965 version: clean_version,
966 prerelease,
967 build_metadata,
968 }
969 }
970}
971
972impl Default for WriterVersion {
973 #[cfg(not(test))]
974 fn default() -> Self {
975 let full_version = env!("CARGO_PKG_VERSION");
976 let (version, prerelease, build_metadata) =
977 Self::split_version(full_version).expect("CARGO_PKG_VERSION should be valid semver");
978 Self {
979 library: "lance".to_string(),
980 version,
981 prerelease,
982 build_metadata,
983 }
984 }
985
986 #[cfg(test)]
988 #[allow(deprecated)]
989 fn default() -> Self {
990 let full_version = env!("CARGO_PKG_VERSION");
991 let (version, prerelease, build_metadata) =
992 Self::split_version(full_version).expect("CARGO_PKG_VERSION should be valid semver");
993 Self {
994 library: "lance".to_string(),
995 version,
996 prerelease,
997 build_metadata,
998 }
999 .bump(VersionPart::Patch, true)
1000 }
1001}
1002
1003impl ProtoStruct for Manifest {
1004 type Proto = pb::Manifest;
1005}
1006
1007impl From<pb::BasePath> for BasePath {
1008 fn from(p: pb::BasePath) -> Self {
1009 Self::new(p.id, p.path, p.name, p.is_dataset_root)
1010 }
1011}
1012
1013impl From<BasePath> for pb::BasePath {
1014 fn from(p: BasePath) -> Self {
1015 Self {
1016 id: p.id,
1017 name: p.name,
1018 is_dataset_root: p.is_dataset_root,
1019 path: p.path,
1020 }
1021 }
1022}
1023
1024impl TryFrom<pb::Manifest> for Manifest {
1025 type Error = Error;
1026
1027 fn try_from(p: pb::Manifest) -> Result<Self> {
1028 let timestamp_nanos = p.timestamp.map(|ts| {
1029 let sec = ts.seconds as u128 * 1e9 as u128;
1030 let nanos = ts.nanos as u128;
1031 sec + nanos
1032 });
1033 let writer_version = match p.writer_version {
1035 Some(pb::manifest::WriterVersion {
1036 library,
1037 version,
1038 prerelease,
1039 build_metadata,
1040 }) => Some(WriterVersion {
1041 library,
1042 version,
1043 prerelease,
1044 build_metadata,
1045 }),
1046 _ => None,
1047 };
1048 let mut interner = DataFileFieldInterner::default();
1049 let fragments = Arc::new(
1050 p.fragments
1051 .into_iter()
1052 .map(|f| interner.intern_fragment(f))
1053 .collect::<Result<Vec<_>>>()?,
1054 );
1055 let fragment_offsets = compute_fragment_offsets(fragments.as_slice());
1056 let fields_with_meta = FieldsWithMeta {
1057 fields: Fields(p.fields),
1058 metadata: p.schema_metadata,
1059 };
1060
1061 if FLAG_STABLE_ROW_IDS & p.reader_feature_flags != 0
1062 && !fragments.iter().all(|frag| frag.row_id_meta.is_some())
1063 {
1064 return Err(Error::internal("All fragments must have row ids"));
1065 }
1066
1067 let data_storage_format = match p.data_format {
1068 None => {
1069 if let Some(inferred_version) = Fragment::try_infer_version(fragments.as_ref())? {
1070 DataStorageFormat::new(inferred_version)
1072 } else {
1073 if has_deprecated_v2_feature_flag(p.writer_feature_flags) {
1075 DataStorageFormat::new(stable_file_version())
1076 } else {
1077 DataStorageFormat::new(ConcreteFileVersion::V1)
1078 }
1079 }
1080 }
1081 Some(format) => DataStorageFormat::try_from(format)?,
1082 };
1083
1084 let schema = Schema::try_from(fields_with_meta)?;
1085
1086 Ok(Self {
1087 schema,
1088 version: p.version,
1089 branch: p.branch,
1090 writer_version,
1091 version_aux_data: p.version_aux_data as usize,
1092 index_section: p.index_section.map(|i| i as usize),
1093 timestamp_nanos: timestamp_nanos.unwrap_or(0),
1094 tag: if p.tag.is_empty() { None } else { Some(p.tag) },
1095 reader_feature_flags: p.reader_feature_flags,
1096 writer_feature_flags: p.writer_feature_flags,
1097 max_fragment_id: p.max_fragment_id,
1098 max_allocated_field_id: p.max_allocated_field_id,
1099 fragments,
1100 transaction_file: if p.transaction_file.is_empty() {
1101 None
1102 } else {
1103 Some(p.transaction_file)
1104 },
1105 transaction_section: p
1106 .transaction_section
1107 .or(p.transaction_section_deprecated)
1108 .map(|i| i as usize),
1109 fragment_offsets,
1110 next_row_id: p.next_row_id,
1111 data_storage_format,
1112 config: p.config,
1113 table_metadata: p.table_metadata,
1114 base_paths: p
1115 .base_paths
1116 .iter()
1117 .map(|item| (item.id, item.clone().into()))
1118 .collect(),
1119 })
1120 }
1121}
1122
1123impl From<&Manifest> for pb::Manifest {
1124 fn from(m: &Manifest) -> Self {
1125 let timestamp_nanos = if m.timestamp_nanos == 0 {
1126 None
1127 } else {
1128 let nanos = m.timestamp_nanos % 1e9 as u128;
1129 let seconds = ((m.timestamp_nanos - nanos) / 1e9 as u128) as i64;
1130 Some(Timestamp {
1131 seconds,
1132 nanos: nanos as i32,
1133 })
1134 };
1135 let fields_with_meta: FieldsWithMeta = (&m.schema).into();
1136 Self {
1137 fields: fields_with_meta.fields.0,
1138 schema_metadata: m
1139 .schema
1140 .metadata
1141 .iter()
1142 .map(|(k, v)| (k.clone(), v.as_bytes().to_vec()))
1143 .collect(),
1144 version: m.version,
1145 branch: m.branch.clone(),
1146 clustering: Vec::new(),
1147 max_clustering_id: 0,
1148 writer_version: m
1149 .writer_version
1150 .as_ref()
1151 .map(|wv| pb::manifest::WriterVersion {
1152 library: wv.library.clone(),
1153 version: wv.version.clone(),
1154 prerelease: wv.prerelease.clone(),
1155 build_metadata: wv.build_metadata.clone(),
1156 }),
1157 fragments: m.fragments.iter().map(pb::DataFragment::from).collect(),
1158 fragment_tree: None,
1159 table_metadata: m.table_metadata.clone(),
1160 version_aux_data: m.version_aux_data as u64,
1161 index_section: m.index_section.map(|i| i as u64),
1162 timestamp: timestamp_nanos,
1163 tag: m.tag.clone().unwrap_or_default(),
1164 reader_feature_flags: m.reader_feature_flags,
1165 writer_feature_flags: m.writer_feature_flags,
1166 max_fragment_id: m.max_fragment_id,
1167 max_allocated_field_id: m.max_allocated_field_id,
1168 transaction_file: m.transaction_file.clone().unwrap_or_default(),
1169 next_row_id: m.next_row_id,
1170 data_format: Some(pb::manifest::DataStorageFormat {
1171 file_format: m.data_storage_format.file_format.clone(),
1172 version: m
1173 .data_storage_format
1174 .version
1175 .to_manifest_string()
1176 .to_string(),
1177 }),
1178 config: m.config.clone(),
1179 base_paths: m
1180 .base_paths
1181 .values()
1182 .map(|base_path| pb::BasePath {
1183 id: base_path.id,
1184 name: base_path.name.clone(),
1185 is_dataset_root: base_path.is_dataset_root,
1186 path: base_path.path.clone(),
1187 })
1188 .collect(),
1189 transaction_section: m.transaction_section.map(|i| i as u64),
1190 transaction_section_deprecated: None,
1191 }
1192 }
1193}
1194
1195#[async_trait]
1196pub trait SelfDescribingFileReader {
1197 async fn try_new_self_described(
1205 object_store: &ObjectStore,
1206 path: &Path,
1207 cache: Option<&LanceCache>,
1208 ) -> Result<Self>
1209 where
1210 Self: Sized,
1211 {
1212 let reader = object_store.open(path).await?;
1213 Self::try_new_self_described_from_reader(reader.into(), cache).await
1214 }
1215
1216 async fn try_new_self_described_from_reader(
1217 reader: Arc<dyn Reader>,
1218 cache: Option<&LanceCache>,
1219 ) -> Result<Self>
1220 where
1221 Self: Sized;
1222}
1223
1224#[async_trait]
1225impl SelfDescribingFileReader for V1FileReader {
1226 async fn try_new_self_described_from_reader(
1227 reader: Arc<dyn Reader>,
1228 cache: Option<&LanceCache>,
1229 ) -> Result<Self> {
1230 let metadata = Self::read_metadata(reader.as_ref(), cache).await?;
1231 let manifest_position = metadata.manifest_position.ok_or(Error::internal(format!(
1232 "Attempt to open file at {} as self-describing but it did not contain a manifest",
1233 reader.path(),
1234 )))?;
1235 let mut manifest: Manifest = read_struct(reader.as_ref(), manifest_position).await?;
1236 populate_manifest_schema_dictionaries(&mut manifest, reader.as_ref()).await?;
1237 let schema = manifest.schema;
1238 let max_field_id = schema.max_field_id().unwrap_or_default();
1239 Self::try_new_from_reader(
1240 reader.path(),
1241 reader.clone(),
1242 Some(metadata),
1243 schema,
1244 0,
1245 0,
1246 max_field_id,
1247 cache,
1248 )
1249 .await
1250 }
1251}
1252
1253#[cfg(test)]
1254mod tests {
1255 use crate::feature_flags::{FLAG_NON_REUSABLE_FIELD_IDS, FLAG_USE_V2_FORMAT_DEPRECATED};
1256 use crate::format::overlay::{DataOverlayFile, OverlayCoverage, TOMBSTONE_FIELD_ID};
1257 use crate::format::{DataFile, DeletionFile, DeletionFileType};
1258 use std::num::NonZero;
1259
1260 use super::*;
1261
1262 use arrow_schema::{DataType, Field as ArrowField, Schema as ArrowSchema};
1263 use lance_core::datatypes::Field;
1264 use roaring::RoaringBitmap;
1265
1266 #[test]
1267 fn clustering_proto_retains_definitions_references_and_generic_metadata() {
1268 let metadata = HashMap::from([
1269 ("org.example.cluster.bucket".into(), "[0, 100)".into()),
1270 (
1271 "org.example.unknown".into(),
1272 " opaque value: 東京 ".into(),
1273 ),
1274 ("org.example.empty".into(), String::new()),
1275 ]);
1276 let manifest = pb::Manifest {
1278 writer_feature_flags: crate::feature_flags::FLAG_OBJECT_METADATA,
1279 max_clustering_id: 7,
1280 clustering: vec![
1281 pb::Clustering {
1282 id: 1,
1283 columns: vec![0, 1],
1284 provider: "org.example.cluster".into(),
1285 version: 8,
1286 },
1287 pb::Clustering {
1288 id: 5,
1289 columns: vec![1],
1290 provider: "org.example.other".into(),
1291 version: 9,
1292 },
1293 ],
1294 table_metadata: HashMap::from([
1295 (
1296 "org.example.cluster.1.boundaries".into(),
1297 "[0, 100, 200]".into(),
1298 ),
1299 (
1300 "org.example.cluster.1.unknown_option".into(),
1301 " opaque value: 東京 ".into(),
1302 ),
1303 ]),
1304 fragments: vec![
1305 pb::DataFragment {
1306 id: 1,
1307 clustering_id: Some(1),
1308 metadata: metadata.clone(),
1309 ..Default::default()
1310 },
1311 pb::DataFragment {
1312 id: 2,
1313 clustering_id: Some(5),
1314 ..Default::default()
1315 },
1316 pb::DataFragment {
1317 id: 3,
1318 clustering_id: None,
1319 metadata: metadata.clone(),
1320 ..Default::default()
1321 },
1322 ],
1323 ..Default::default()
1324 };
1325 assert_eq!(
1326 pb::Manifest::decode(manifest.encode_to_vec().as_slice()).unwrap(),
1327 manifest
1328 );
1329 assert_eq!(manifest.reader_feature_flags, 0);
1330 let segments = pb::IndexSection {
1331 indices: vec![pb::IndexMetadata {
1332 name: "vectors".into(),
1333 fields: vec![1],
1334 clustering_id: Some(5),
1335 metadata,
1336 ..Default::default()
1337 }],
1338 };
1339 assert_eq!(
1340 pb::IndexSection::decode(segments.encode_to_vec().as_slice()).unwrap(),
1341 segments
1342 );
1343 assert_eq!(manifest.clustering.len(), 2);
1344 assert_eq!(manifest.max_clustering_id, 7);
1345 assert_eq!(manifest.fragments[2].clustering_id, None);
1346 let allocation_only = pb::Manifest {
1347 writer_feature_flags: crate::feature_flags::FLAG_OBJECT_METADATA,
1348 max_clustering_id: manifest.max_clustering_id,
1349 ..Default::default()
1350 };
1351 assert_eq!(
1352 pb::Manifest::decode(allocation_only.encode_to_vec().as_slice()).unwrap(),
1353 allocation_only
1354 );
1355 let zero = pb::DataFragment {
1357 clustering_id: Some(0),
1358 ..Default::default()
1359 };
1360 assert_eq!(
1361 pb::DataFragment::decode(zero.encode_to_vec().as_slice())
1362 .unwrap()
1363 .clustering_id,
1364 Some(0)
1365 );
1366 }
1367
1368 #[test]
1372 fn shallow_clone_stamps_base_id_on_overlay_files() {
1373 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
1374 "a",
1375 arrow_schema::DataType::Int64,
1376 false,
1377 )]);
1378 let schema = Schema::try_from(&arrow_schema).unwrap();
1379
1380 let mut fragment = Fragment::with_file_legacy(0, "base.lance", &schema, Some(10));
1381 fragment.overlays = vec![DataOverlayFile {
1382 data_file: DataFile::new_legacy_from_fields("overlay.lance", vec![0], None),
1383 coverage: OverlayCoverage::Shared(Arc::new(RoaringBitmap::from_iter([0_u32]))),
1384 committed_version: 1,
1385 }];
1386 let manifest = Manifest::new(
1387 schema,
1388 Arc::new(vec![fragment]),
1389 DataStorageFormat::default(),
1390 HashMap::new(),
1391 );
1392
1393 let cloned = manifest.shallow_clone(
1394 Some("parent".to_string()),
1395 "memory://parent".to_string(),
1396 7,
1397 None,
1398 String::new(),
1399 );
1400
1401 let fragment = &cloned.fragments[0];
1402 assert_eq!(fragment.files[0].base_id, Some(7));
1403 assert_eq!(
1404 fragment.overlays[0].data_file.base_id,
1405 Some(7),
1406 "the overlay's data file resolves against the parent as well"
1407 );
1408 }
1409
1410 #[test]
1411 fn old_empty_manifest_recovers_v1_or_current_stable() {
1412 let old_manifest = pb::Manifest {
1413 data_format: None,
1414 ..Default::default()
1415 };
1416 let recovered_v1 = Manifest::try_from(old_manifest.clone()).unwrap();
1417 assert_eq!(
1418 recovered_v1.data_storage_format.lance_file_format(),
1419 ConcreteFileVersion::V1
1420 );
1421
1422 let recovered_stable = Manifest::try_from(pb::Manifest {
1423 writer_feature_flags: FLAG_USE_V2_FORMAT_DEPRECATED,
1424 ..old_manifest
1425 })
1426 .unwrap();
1427 assert_eq!(
1428 recovered_stable.data_storage_format.lance_file_format(),
1429 stable_file_version()
1430 );
1431 }
1432
1433 #[test]
1434 fn manifest_persistence_rejects_selectors_and_public_aliases() {
1435 for version in ["stable", "next", "legacy", "0.3"] {
1436 let manifest = pb::Manifest {
1437 data_format: Some(pb::manifest::DataStorageFormat {
1438 file_format: LANCE_FORMAT_NAME.to_string(),
1439 version: version.to_string(),
1440 }),
1441 ..Default::default()
1442 };
1443 assert!(Manifest::try_from(manifest).is_err(), "accepted {version}");
1444 }
1445 }
1446
1447 #[test]
1448 fn manifest_codec_writes_canonical_exact_string() {
1449 let manifest = Manifest::new(
1450 Schema::default(),
1451 Arc::new(Vec::new()),
1452 DataStorageFormat::new(ConcreteFileVersion::V2_0),
1453 HashMap::new(),
1454 );
1455 let encoded = pb::Manifest::from(&manifest);
1456 assert_eq!(encoded.data_format.unwrap().version, "2.0");
1457 }
1458
1459 #[test]
1460 fn missing_format_infers_exact_version_and_rejects_mixed_files() {
1461 let v2_0 = Fragment::new(0).with_file(
1462 "v2_0.lance",
1463 vec![0],
1464 vec![0],
1465 ConcreteFileVersion::V2_0,
1466 None,
1467 );
1468 let manifest = Manifest::new(
1469 Schema::default(),
1470 Arc::new(vec![v2_0.clone()]),
1471 DataStorageFormat::new(ConcreteFileVersion::V1),
1472 HashMap::new(),
1473 );
1474 let mut encoded = pb::Manifest::from(&manifest);
1475 encoded.data_format = None;
1476 let recovered = Manifest::try_from(encoded).unwrap();
1477 assert_eq!(
1478 recovered.data_storage_format.lance_file_format(),
1479 ConcreteFileVersion::V2_0
1480 );
1481
1482 let v2_1 = Fragment::new(1).with_file(
1483 "v2_1.lance",
1484 vec![0],
1485 vec![0],
1486 ConcreteFileVersion::V2_1,
1487 None,
1488 );
1489 let mixed_manifest = Manifest::new(
1490 Schema::default(),
1491 Arc::new(vec![v2_0, v2_1]),
1492 DataStorageFormat::new(ConcreteFileVersion::V2_0),
1493 HashMap::new(),
1494 );
1495 let mut encoded = pb::Manifest::from(&mixed_manifest);
1496 encoded.data_format = None;
1497 let error = Manifest::try_from(encoded).unwrap_err();
1498 assert!(
1499 error
1500 .to_string()
1501 .contains("All data files must have the same version")
1502 );
1503 }
1504
1505 #[test]
1506 fn test_detach_sparse_inline_row_ids_copies_only_small_shares() {
1507 let arrow_schema = ArrowSchema::new(vec![ArrowField::new("a", DataType::Int32, false)]);
1508 let buffer = Bytes::from(vec![7u8; 64]);
1510 let fragments = [0..10, 20..30].into_iter().enumerate().map(|(id, range)| {
1511 let mut fragment = Fragment::new(id as u64);
1512 fragment.row_id_meta = Some(RowIdMeta::Inline(InlineRowIds::from(buffer.slice(range))));
1513 fragment
1514 });
1515 let mut manifest = Manifest::new(
1516 Schema::try_from(&arrow_schema).unwrap(),
1517 Arc::new(fragments.collect()),
1518 DataStorageFormat::default(),
1519 HashMap::new(),
1520 );
1521 let ptr = |manifest: &Manifest, i: usize| match &manifest.fragments[i].row_id_meta {
1522 Some(RowIdMeta::Inline(data)) => data.bytes().as_ptr(),
1523 _ => unreachable!(),
1524 };
1525 let in_buffer = |p: *const u8| buffer.as_ptr_range().contains(&p);
1526
1527 manifest.detach_sparse_inline_row_ids(80);
1529 assert!(in_buffer(ptr(&manifest, 0)) && in_buffer(ptr(&manifest, 1)));
1530
1531 manifest.detach_sparse_inline_row_ids(81);
1533 assert!(!in_buffer(ptr(&manifest, 0)) && !in_buffer(ptr(&manifest, 1)));
1534 let Some(RowIdMeta::Inline(copied)) = &manifest.fragments[1].row_id_meta else {
1535 unreachable!()
1536 };
1537 assert_eq!(&**copied, &buffer[20..30]);
1538 }
1539
1540 #[test]
1541 fn test_writer_version() {
1542 let wv = WriterVersion::default();
1543 assert_eq!(wv.library, "lance");
1544
1545 let cargo_version = env!("CARGO_PKG_VERSION");
1547 let expected_tag = if cargo_version.contains('-') {
1548 Some(cargo_version.split('-').nth(1).unwrap())
1549 } else {
1550 None
1551 };
1552
1553 let version_parts: Vec<&str> = wv.version.split('.').collect();
1555 assert_eq!(
1556 version_parts.len(),
1557 3,
1558 "Version should be major.minor.patch"
1559 );
1560 assert!(
1561 !wv.version.contains('-'),
1562 "Version field should not contain prerelease"
1563 );
1564
1565 assert_eq!(wv.prerelease.as_deref(), expected_tag);
1567 assert_eq!(wv.build_metadata, None);
1569
1570 let version = wv.lance_lib_version().unwrap();
1572 assert_eq!(
1573 version.major,
1574 env!("CARGO_PKG_VERSION_MAJOR").parse::<u64>().unwrap()
1575 );
1576 assert_eq!(
1577 version.minor,
1578 env!("CARGO_PKG_VERSION_MINOR").parse::<u64>().unwrap()
1579 );
1580 assert_eq!(
1581 version.patch,
1582 env!("CARGO_PKG_VERSION_PATCH").parse::<u64>().unwrap() + 1
1584 );
1585 assert_eq!(version.pre.as_str(), expected_tag.unwrap_or(""));
1586
1587 for part in &[VersionPart::Major, VersionPart::Minor, VersionPart::Patch] {
1588 let mut bumped_version = version.clone();
1589 bump_version(&mut bumped_version, *part);
1590 assert!(version < bumped_version);
1591 }
1592 }
1593
1594 #[test]
1595 fn test_writer_version_split() {
1596 let (version, prerelease, build_metadata) =
1598 WriterVersion::split_version("2.0.0-rc.1").unwrap();
1599 assert_eq!(version, "2.0.0");
1600 assert_eq!(prerelease, Some("rc.1".to_string()));
1601 assert_eq!(build_metadata, None);
1602
1603 let (version, prerelease, build_metadata) = WriterVersion::split_version("2.0.0").unwrap();
1605 assert_eq!(version, "2.0.0");
1606 assert_eq!(prerelease, None);
1607 assert_eq!(build_metadata, None);
1608
1609 let (version, prerelease, build_metadata) =
1611 WriterVersion::split_version("2.0.0-rc.1+build.123").unwrap();
1612 assert_eq!(version, "2.0.0");
1613 assert_eq!(prerelease, Some("rc.1".to_string()));
1614 assert_eq!(build_metadata, Some("build.123".to_string()));
1615
1616 let (version, prerelease, build_metadata) =
1618 WriterVersion::split_version("2.0.0+build.123").unwrap();
1619 assert_eq!(version, "2.0.0");
1620 assert_eq!(prerelease, None);
1621 assert_eq!(build_metadata, Some("build.123".to_string()));
1622
1623 assert!(WriterVersion::split_version("not-a-version").is_none());
1625 }
1626
1627 #[test]
1628 fn test_writer_version_comparison_with_prerelease() {
1629 let v1 = WriterVersion {
1630 library: "lance".to_string(),
1631 version: "2.0.0".to_string(),
1632 prerelease: Some("rc.1".to_string()),
1633 build_metadata: None,
1634 };
1635
1636 let v2 = WriterVersion {
1637 library: "lance".to_string(),
1638 version: "2.0.0".to_string(),
1639 prerelease: None,
1640 build_metadata: None,
1641 };
1642
1643 let semver1 = v1.lance_lib_version().unwrap();
1644 let semver2 = v2.lance_lib_version().unwrap();
1645
1646 assert!(semver1 < semver2);
1648 }
1649
1650 #[test]
1651 fn test_writer_version_with_build_metadata() {
1652 let v = WriterVersion {
1653 library: "lance".to_string(),
1654 version: "2.0.0".to_string(),
1655 prerelease: Some("rc.1".to_string()),
1656 build_metadata: Some("build.123".to_string()),
1657 };
1658
1659 let semver = v.lance_lib_version().unwrap();
1660 assert_eq!(semver.to_string(), "2.0.0-rc.1+build.123");
1661 assert_eq!(semver.major, 2);
1662 assert_eq!(semver.minor, 0);
1663 assert_eq!(semver.patch, 0);
1664 assert_eq!(semver.pre.as_str(), "rc.1");
1665 assert_eq!(semver.build.as_str(), "build.123");
1666 }
1667
1668 #[test]
1669 fn test_writer_version_non_semver() {
1670 let v = WriterVersion {
1672 library: "lance".to_string(),
1673 version: "custom-build-v1".to_string(),
1674 prerelease: None,
1675 build_metadata: None,
1676 };
1677
1678 assert!(v.lance_lib_version().is_none());
1680
1681 assert_eq!(v.library, "lance");
1683 assert_eq!(v.version, "custom-build-v1");
1684 }
1685
1686 #[test]
1687 #[allow(deprecated)]
1688 fn test_older_than_with_prerelease() {
1689 let v_rc = WriterVersion {
1691 library: "lance".to_string(),
1692 version: "2.0.0".to_string(),
1693 prerelease: Some("rc.1".to_string()),
1694 build_metadata: None,
1695 };
1696
1697 assert!(v_rc.older_than(2, 0, 0));
1699
1700 assert!(v_rc.older_than(2, 0, 1));
1702
1703 assert!(!v_rc.older_than(1, 9, 9));
1705
1706 let v_release = WriterVersion {
1707 library: "lance".to_string(),
1708 version: "2.0.0".to_string(),
1709 prerelease: None,
1710 build_metadata: None,
1711 };
1712
1713 assert!(!v_release.older_than(2, 0, 0));
1715
1716 assert!(v_release.older_than(2, 0, 1));
1718 }
1719
1720 #[test]
1721 fn test_fragments_by_offset_range() {
1722 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
1723 "a",
1724 arrow_schema::DataType::Int64,
1725 false,
1726 )]);
1727 let schema = Schema::try_from(&arrow_schema).unwrap();
1728 let fragments = vec![
1729 Fragment::with_file_legacy(0, "path1", &schema, Some(10)),
1730 Fragment::with_file_legacy(1, "path2", &schema, Some(15)),
1731 Fragment::with_file_legacy(2, "path3", &schema, Some(20)),
1732 ];
1733 let manifest = Manifest::new(
1734 schema,
1735 Arc::new(fragments),
1736 DataStorageFormat::default(),
1737 HashMap::new(),
1738 );
1739
1740 let actual = manifest.fragments_by_offset_range(0..10);
1741 assert_eq!(actual.len(), 1);
1742 assert_eq!(actual[0].0, 0);
1743 assert_eq!(actual[0].1.id, 0);
1744
1745 let actual = manifest.fragments_by_offset_range(5..15);
1746 assert_eq!(actual.len(), 2);
1747 assert_eq!(actual[0].0, 0);
1748 assert_eq!(actual[0].1.id, 0);
1749 assert_eq!(actual[1].0, 10);
1750 assert_eq!(actual[1].1.id, 1);
1751
1752 let actual = manifest.fragments_by_offset_range(15..50);
1753 assert_eq!(actual.len(), 2);
1754 assert_eq!(actual[0].0, 10);
1755 assert_eq!(actual[0].1.id, 1);
1756 assert_eq!(actual[1].0, 25);
1757 assert_eq!(actual[1].1.id, 2);
1758
1759 let actual = manifest.fragments_by_offset_range(45..100);
1761 assert!(actual.is_empty());
1762
1763 assert!(manifest.fragments_by_offset_range(200..400).is_empty());
1764 }
1765
1766 #[test]
1767 fn test_max_field_id() {
1768 let mut field0 =
1770 Field::try_from(ArrowField::new("a", arrow_schema::DataType::Int64, false)).unwrap();
1771 field0.set_id(-1, &mut 0);
1772 let mut field2 =
1773 Field::try_from(ArrowField::new("b", arrow_schema::DataType::Int64, false)).unwrap();
1774 field2.set_id(-1, &mut 2);
1775
1776 let schema = Schema {
1777 fields: vec![field0, field2],
1778 metadata: Default::default(),
1779 };
1780 let fragments = vec![
1781 Fragment {
1782 id: 0,
1783 files: vec![DataFile::new_legacy_from_fields(
1784 "path1",
1785 vec![0, 1, 2],
1786 None,
1787 )],
1788 overlays: vec![],
1789 deletion_file: None,
1790 row_id_meta: None,
1791 physical_rows: None,
1792 created_at_version_meta: None,
1793 last_updated_at_version_meta: None,
1794 },
1795 Fragment {
1796 id: 1,
1797 files: vec![
1798 DataFile::new_legacy_from_fields("path2", vec![0, 1, 43], None),
1799 DataFile::new_legacy_from_fields("path3", vec![2], None),
1800 ],
1801 overlays: vec![],
1802 deletion_file: None,
1803 row_id_meta: None,
1804 physical_rows: None,
1805 created_at_version_meta: None,
1806 last_updated_at_version_meta: None,
1807 },
1808 ];
1809
1810 let manifest = Manifest::new(
1811 schema,
1812 Arc::new(fragments),
1813 DataStorageFormat::default(),
1814 HashMap::new(),
1815 );
1816
1817 assert_eq!(manifest.max_field_id(), 43);
1818 }
1819
1820 #[test]
1821 fn test_max_field_id_includes_overlay_files() {
1822 let mut field0 =
1823 Field::try_from(ArrowField::new("a", arrow_schema::DataType::Int64, false)).unwrap();
1824 field0.set_id(-1, &mut 0);
1825 let schema = Schema {
1826 fields: vec![field0],
1827 metadata: Default::default(),
1828 };
1829
1830 let mut fragment = Fragment {
1831 id: 0,
1832 files: vec![DataFile::new_legacy_from_fields("path1", vec![0], None)],
1833 overlays: vec![],
1834 deletion_file: None,
1835 row_id_meta: None,
1836 physical_rows: None,
1837 created_at_version_meta: None,
1838 last_updated_at_version_meta: None,
1839 };
1840 fragment.overlays = vec![DataOverlayFile {
1841 data_file: DataFile::new_legacy_from_fields("overlay.lance", vec![43], None),
1842 coverage: OverlayCoverage::Shared(Arc::new(RoaringBitmap::from_iter([0_u32]))),
1843 committed_version: 1,
1844 }];
1845
1846 let manifest = Manifest::new(
1847 schema,
1848 Arc::new(vec![fragment]),
1849 DataStorageFormat::default(),
1850 HashMap::new(),
1851 );
1852
1853 assert_eq!(manifest.max_field_id(), 43);
1854 }
1855
1856 #[test]
1857 fn non_reusable_field_id_high_water_mark_survives_dropped_references_and_round_trip() {
1858 let arrow_schema = ArrowSchema::new(vec![
1859 ArrowField::new("a", arrow_schema::DataType::Int64, false),
1860 ArrowField::new("b", arrow_schema::DataType::Int64, false),
1861 ]);
1862 let schema = Schema::try_from(&arrow_schema).unwrap();
1863 let mut fragment = Fragment::new(0);
1864 fragment.files.push(DataFile::new(
1865 "ab.lance",
1866 vec![0, 1],
1867 vec![0, 1],
1868 ConcreteFileVersion::V2_0,
1869 None,
1870 None,
1871 ));
1872 let mut manifest = Manifest::new(
1873 schema,
1874 Arc::new(vec![fragment]),
1875 DataStorageFormat::default(),
1876 HashMap::new(),
1877 );
1878 manifest.activate_non_reusable_field_ids();
1879 manifest.writer_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
1880 assert_eq!(manifest.max_allocated_field_id, Some(1));
1881
1882 manifest.schema.fields.pop();
1883 let file = &mut Arc::make_mut(&mut manifest.fragments)[0].files[0];
1884 Arc::make_mut(&mut file.fields)[1] = TOMBSTONE_FIELD_ID;
1885 manifest.update_max_field_id();
1886
1887 assert_eq!(manifest.max_referenced_field_id(), 0);
1888 assert_eq!(manifest.max_field_id(), 1);
1889
1890 let encoded = manifest.serialized();
1891 let mut recovered =
1892 Manifest::try_from(pb::Manifest::decode(encoded.as_slice()).unwrap()).unwrap();
1893 assert_eq!(recovered.max_allocated_field_id, Some(1));
1894 assert_eq!(recovered.max_field_id(), 1);
1895 assert_eq!(
1896 recovered.fragments[0].files[0].fields.as_ref(),
1897 &[0, TOMBSTONE_FIELD_ID]
1898 );
1899 assert_eq!(
1900 recovered.fragments[0].files[0].column_indices.as_ref(),
1901 &[0, 1]
1902 );
1903 assert_eq!(
1904 recovered.reader_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS,
1905 0
1906 );
1907 assert_ne!(
1908 recovered.writer_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS,
1909 0
1910 );
1911
1912 recovered.schema.fields.push(
1913 Field::try_from(ArrowField::new("c", arrow_schema::DataType::Int64, false)).unwrap(),
1914 );
1915 let max_field_id = recovered.max_field_id();
1916 recovered
1917 .schema
1918 .try_set_field_id(Some(max_field_id))
1919 .unwrap();
1920 assert_eq!(recovered.schema.field("c").unwrap().id, 2);
1921 recovered.update_max_field_id();
1922 assert_eq!(recovered.max_allocated_field_id, Some(2));
1923 }
1924
1925 #[test]
1926 fn shallow_clone_preserves_non_reusable_field_id_allocation_state() {
1927 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
1928 "a",
1929 arrow_schema::DataType::Int64,
1930 false,
1931 )]);
1932 let schema = Schema::try_from(&arrow_schema).unwrap();
1933 let mut manifest = Manifest::new(
1934 schema,
1935 Arc::new(vec![]),
1936 DataStorageFormat::default(),
1937 HashMap::new(),
1938 );
1939 manifest.max_allocated_field_id = Some(41);
1940 manifest.writer_feature_flags |= FLAG_NON_REUSABLE_FIELD_IDS;
1941
1942 let cloned = manifest.shallow_clone(
1943 Some("parent".to_string()),
1944 "memory://parent".to_string(),
1945 7,
1946 None,
1947 String::new(),
1948 );
1949
1950 assert_eq!(cloned.max_allocated_field_id, Some(41));
1951 assert_eq!(cloned.reader_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS, 0);
1952 assert_ne!(cloned.writer_feature_flags & FLAG_NON_REUSABLE_FIELD_IDS, 0);
1953 }
1954
1955 #[test]
1956 fn test_config() {
1957 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
1958 "a",
1959 arrow_schema::DataType::Int64,
1960 false,
1961 )]);
1962 let schema = Schema::try_from(&arrow_schema).unwrap();
1963 let fragments = vec![
1964 Fragment::with_file_legacy(0, "path1", &schema, Some(10)),
1965 Fragment::with_file_legacy(1, "path2", &schema, Some(15)),
1966 Fragment::with_file_legacy(2, "path3", &schema, Some(20)),
1967 ];
1968 let mut manifest = Manifest::new(
1969 schema,
1970 Arc::new(fragments),
1971 DataStorageFormat::default(),
1972 HashMap::new(),
1973 );
1974
1975 let mut config = manifest.config.clone();
1976 config.insert("lance.test".to_string(), "value".to_string());
1977 config.insert("other-key".to_string(), "other-value".to_string());
1978
1979 manifest.config_mut().extend(config.clone());
1980 assert_eq!(manifest.config, config.clone());
1981
1982 config.remove("other-key");
1983 manifest.config_mut().remove("other-key");
1984 assert_eq!(manifest.config, config);
1985 }
1986
1987 #[rstest::rstest]
1988 #[case::only_current(Some(22), None, Some(22))]
1989 #[case::only_deprecated(None, Some(21), Some(21))]
1990 #[case::current_wins_over_deprecated(Some(22), Some(21), Some(22))]
1991 #[case::neither(None, None, None)]
1992 fn test_transaction_section_field_precedence(
1993 #[case] current: Option<u64>,
1994 #[case] deprecated: Option<u64>,
1995 #[case] expected: Option<usize>,
1996 ) {
1997 let arrow_schema = ArrowSchema::new(vec![ArrowField::new("a", DataType::Int64, false)]);
1998 let manifest = Manifest::new(
1999 Schema::try_from(&arrow_schema).unwrap(),
2000 Arc::new(vec![]),
2001 DataStorageFormat::default(),
2002 HashMap::new(),
2003 );
2004 let mut pb_manifest = pb::Manifest::from(&manifest);
2005 pb_manifest.transaction_section = current;
2006 pb_manifest.transaction_section_deprecated = deprecated;
2007
2008 let manifest = Manifest::try_from(pb_manifest).unwrap();
2009 assert_eq!(manifest.transaction_section, expected);
2010
2011 let pb_manifest = pb::Manifest::from(&manifest);
2013 assert_eq!(pb_manifest.transaction_section, expected.map(|p| p as u64));
2014 assert_eq!(pb_manifest.transaction_section_deprecated, None);
2015 }
2016
2017 #[test]
2018 fn test_manifest_summary() {
2019 let arrow_schema = ArrowSchema::new(vec![
2021 ArrowField::new("id", arrow_schema::DataType::Int64, false),
2022 ArrowField::new("name", arrow_schema::DataType::Utf8, true),
2023 ]);
2024 let schema = Schema::try_from(&arrow_schema).unwrap();
2025
2026 let empty_manifest = Manifest::new(
2027 schema.clone(),
2028 Arc::new(vec![]),
2029 DataStorageFormat::default(),
2030 HashMap::new(),
2031 );
2032
2033 let empty_summary = empty_manifest.summary();
2034 assert_eq!(empty_summary.total_rows, 0);
2035 assert_eq!(empty_summary.total_files_size, 0);
2036 assert_eq!(empty_summary.total_fragments, 0);
2037 assert_eq!(empty_summary.total_data_files, 0);
2038 assert_eq!(empty_summary.total_deletion_file_rows, 0);
2039 assert_eq!(empty_summary.total_data_file_rows, 0);
2040 assert_eq!(empty_summary.total_deletion_files, 0);
2041
2042 let empty_fragments = vec![
2044 Fragment::with_file_legacy(0, "empty_file1.lance", &schema, Some(0)),
2045 Fragment::with_file_legacy(1, "empty_file2.lance", &schema, Some(0)),
2046 ];
2047
2048 let empty_files_manifest = Manifest::new(
2049 schema.clone(),
2050 Arc::new(empty_fragments),
2051 DataStorageFormat::default(),
2052 HashMap::new(),
2053 );
2054
2055 let empty_files_summary = empty_files_manifest.summary();
2056 assert_eq!(empty_files_summary.total_rows, 0);
2057 assert_eq!(empty_files_summary.total_files_size, 0);
2058 assert_eq!(empty_files_summary.total_fragments, 2);
2059 assert_eq!(empty_files_summary.total_data_files, 2);
2060 assert_eq!(empty_files_summary.total_deletion_file_rows, 0);
2061 assert_eq!(empty_files_summary.total_data_file_rows, 0);
2062 assert_eq!(empty_files_summary.total_deletion_files, 0);
2063
2064 let real_fragments = vec![
2066 Fragment::with_file_legacy(0, "data_file1.lance", &schema, Some(100)),
2067 Fragment::with_file_legacy(1, "data_file2.lance", &schema, Some(250)),
2068 Fragment::with_file_legacy(2, "data_file3.lance", &schema, Some(75)),
2069 ];
2070
2071 let real_data_manifest = Manifest::new(
2072 schema.clone(),
2073 Arc::new(real_fragments),
2074 DataStorageFormat::default(),
2075 HashMap::new(),
2076 );
2077
2078 let real_data_summary = real_data_manifest.summary();
2079 assert_eq!(real_data_summary.total_rows, 425); assert_eq!(real_data_summary.total_files_size, 0); assert_eq!(real_data_summary.total_fragments, 3);
2082 assert_eq!(real_data_summary.total_data_files, 3);
2083 assert_eq!(real_data_summary.total_deletion_file_rows, 0);
2084 assert_eq!(real_data_summary.total_data_file_rows, 425);
2085 assert_eq!(real_data_summary.total_deletion_files, 0);
2086
2087 let mut fragment_with_deletion = Fragment::new(0)
2089 .with_file(
2090 "data_with_deletion.lance",
2091 vec![0, 1],
2092 vec![0, 1],
2093 stable_file_version(),
2094 NonZero::new(1000),
2095 )
2096 .with_physical_rows(50);
2097 fragment_with_deletion.deletion_file = Some(DeletionFile {
2098 read_version: 123,
2099 id: 456,
2100 file_type: DeletionFileType::Array,
2101 num_deleted_rows: Some(10),
2102 base_id: None,
2103 });
2104
2105 let manifest_with_deletion = Manifest::new(
2106 schema,
2107 Arc::new(vec![fragment_with_deletion]),
2108 DataStorageFormat::default(),
2109 HashMap::new(),
2110 );
2111
2112 let deletion_summary = manifest_with_deletion.summary();
2113 assert_eq!(deletion_summary.total_rows, 40); assert_eq!(deletion_summary.total_files_size, 1000);
2115 assert_eq!(deletion_summary.total_fragments, 1);
2116 assert_eq!(deletion_summary.total_data_files, 1);
2117 assert_eq!(deletion_summary.total_deletion_file_rows, 10);
2118 assert_eq!(deletion_summary.total_data_file_rows, 50);
2119 assert_eq!(deletion_summary.total_deletion_files, 1);
2120
2121 let stats_map: BTreeMap<String, String> = deletion_summary.into();
2123 assert_eq!(stats_map.len(), 7)
2124 }
2125}