1use async_trait::async_trait;
5use chrono::prelude::*;
6use lance_core::deepsize::DeepSizeOf;
7use lance_file::datatypes::{Fields, FieldsWithMeta};
8use lance_file::version::{ConcreteFileVersion, stable_file_version};
9use lance_file::versions::v1::{
10 encoding::populate_schema_dictionaries, reader::FileReader as V1FileReader,
11};
12use lance_io::traits::{ProtoStruct, Reader};
13use object_store::path::Path;
14use prost::Message;
15use prost_types::Timestamp;
16use std::collections::{BTreeMap, HashMap};
17use std::ops::Range;
18use std::sync::Arc;
19
20use super::Fragment;
21use crate::feature_flags::{FLAG_COVERED_INDEX_METADATA, STICKY_PAIRED_FLAGS};
22use crate::feature_flags::{FLAG_STABLE_ROW_IDS, has_deprecated_v2_feature_flag};
23use crate::format::fragment::DataFileFieldInterner;
24use crate::format::pb;
25use lance_core::cache::LanceCache;
26use lance_core::datatypes::Schema;
27use lance_core::{Error, Result};
28use lance_io::object_store::{ObjectStore, ObjectStoreRegistry};
29use lance_io::utils::read_struct;
30
31#[derive(Debug, Clone, PartialEq, DeepSizeOf)]
38pub struct Manifest {
39 pub schema: Schema,
41
42 pub version: u64,
44
45 pub branch: Option<String>,
47
48 pub writer_version: Option<WriterVersion>,
50
51 pub fragments: Arc<Vec<Fragment>>,
56
57 pub version_aux_data: usize,
59
60 pub index_section: Option<usize>,
62
63 pub timestamp_nanos: u128,
65
66 pub tag: Option<String>,
68
69 pub reader_feature_flags: u64,
71
72 pub writer_feature_flags: u64,
74
75 pub max_fragment_id: Option<u32>,
78
79 pub transaction_file: Option<String>,
81
82 pub transaction_section: Option<usize>,
84
85 fragment_offsets: Vec<usize>,
88
89 pub next_row_id: u64,
91
92 pub data_storage_format: DataStorageFormat,
94
95 pub config: HashMap<String, String>,
97
98 pub table_metadata: HashMap<String, String>,
104
105 pub base_paths: HashMap<u32, BasePath>,
107}
108
109pub const DETACHED_VERSION_MASK: u64 = 0x8000_0000_0000_0000;
111
112pub fn is_detached_version(version: u64) -> bool {
113 version & DETACHED_VERSION_MASK != 0
114}
115
116fn compute_fragment_offsets(fragments: &[Fragment]) -> Vec<usize> {
117 fragments
118 .iter()
119 .map(|f| f.num_rows().unwrap_or_default())
120 .chain([0]) .scan(0_usize, |offset, len| {
122 let start = *offset;
123 *offset += len;
124 Some(start)
125 })
126 .collect()
127}
128
129#[derive(Default)]
130pub struct ManifestSummary {
131 pub total_fragments: u64,
132 pub total_data_files: u64,
133 pub total_files_size: u64,
134 pub total_deletion_files: u64,
135 pub total_data_file_rows: u64,
136 pub total_deletion_file_rows: u64,
137 pub total_rows: u64,
138}
139
140impl From<ManifestSummary> for BTreeMap<String, String> {
141 fn from(summary: ManifestSummary) -> Self {
142 let mut stats_map = Self::new();
143 stats_map.insert(
144 "total_fragments".to_string(),
145 summary.total_fragments.to_string(),
146 );
147 stats_map.insert(
148 "total_data_files".to_string(),
149 summary.total_data_files.to_string(),
150 );
151 stats_map.insert(
152 "total_files_size".to_string(),
153 summary.total_files_size.to_string(),
154 );
155 stats_map.insert(
156 "total_deletion_files".to_string(),
157 summary.total_deletion_files.to_string(),
158 );
159 stats_map.insert(
160 "total_data_file_rows".to_string(),
161 summary.total_data_file_rows.to_string(),
162 );
163 stats_map.insert(
164 "total_deletion_file_rows".to_string(),
165 summary.total_deletion_file_rows.to_string(),
166 );
167 stats_map.insert("total_rows".to_string(), summary.total_rows.to_string());
168 stats_map
169 }
170}
171
172impl Manifest {
173 pub fn new(
174 schema: Schema,
175 fragments: Arc<Vec<Fragment>>,
176 data_storage_format: DataStorageFormat,
177 base_paths: HashMap<u32, BasePath>,
178 ) -> Self {
179 let fragment_offsets = compute_fragment_offsets(&fragments);
180
181 Self {
182 schema,
183 version: 1,
184 branch: None,
185 writer_version: Some(WriterVersion::default()),
186 fragments,
187 version_aux_data: 0,
188 index_section: None,
189 timestamp_nanos: 0,
190 tag: None,
191 reader_feature_flags: 0, writer_feature_flags: 0, max_fragment_id: None,
194 transaction_file: None,
195 transaction_section: None,
196 fragment_offsets,
197 next_row_id: 0,
198 data_storage_format,
199 config: HashMap::new(),
200 table_metadata: HashMap::new(),
201 base_paths,
202 }
203 }
204
205 pub fn new_from_previous(
206 previous: &Self,
207 schema: Schema,
208 fragments: Arc<Vec<Fragment>>,
209 ) -> Self {
210 let fragment_offsets = compute_fragment_offsets(&fragments);
211
212 Self {
213 schema,
214 version: previous.version + 1,
215 branch: previous.branch.clone(),
216 writer_version: Some(WriterVersion::default()),
217 fragments,
218 version_aux_data: 0,
219 index_section: None, timestamp_nanos: 0, tag: None,
222 reader_feature_flags: previous.reader_feature_flags & STICKY_PAIRED_FLAGS,
223 writer_feature_flags: previous.writer_feature_flags & STICKY_PAIRED_FLAGS,
224 max_fragment_id: previous.max_fragment_id,
225 transaction_file: None,
226 transaction_section: None,
227 fragment_offsets,
228 next_row_id: previous.next_row_id,
229 data_storage_format: previous.data_storage_format.clone(),
230 config: previous.config.clone(),
231 table_metadata: previous.table_metadata.clone(),
232 base_paths: previous.base_paths.clone(),
233 }
234 }
235
236 pub fn shallow_clone(
241 &self,
242 ref_name: Option<String>,
243 ref_path: String,
244 ref_base_id: u32,
245 branch_name: Option<String>,
246 transaction_file: String,
247 ) -> Self {
248 let cloned_fragments = self
249 .fragments
250 .as_ref()
251 .iter()
252 .map(|fragment| {
253 let mut cloned_fragment = fragment.clone();
254 for file in cloned_fragment.referenced_lance_files_mut() {
255 if file.base_id.is_none() {
256 file.base_id = Some(ref_base_id);
257 }
258 }
259
260 if let Some(deletion) = &mut cloned_fragment.deletion_file
261 && deletion.base_id.is_none()
262 {
263 deletion.base_id = Some(ref_base_id);
264 }
265 cloned_fragment
266 })
267 .collect::<Vec<_>>();
268
269 Self {
270 schema: self.schema.clone(),
271 version: self.version,
272 branch: branch_name,
273 writer_version: self.writer_version.clone(),
274 fragments: Arc::new(cloned_fragments),
275 version_aux_data: self.version_aux_data,
276 index_section: None, timestamp_nanos: self.timestamp_nanos,
278 tag: None,
279 reader_feature_flags: self.reader_feature_flags
289 & (FLAG_COVERED_INDEX_METADATA | STICKY_PAIRED_FLAGS),
290 writer_feature_flags: self.writer_feature_flags
291 & (FLAG_COVERED_INDEX_METADATA | STICKY_PAIRED_FLAGS),
292 max_fragment_id: self.max_fragment_id,
293 transaction_file: Some(transaction_file),
294 transaction_section: None,
295 fragment_offsets: self.fragment_offsets.clone(),
296 next_row_id: self.next_row_id,
297 data_storage_format: self.data_storage_format.clone(),
298 config: self.config.clone(),
299 base_paths: {
300 let mut base_paths = self.base_paths.clone();
301 let base_path = BasePath::new(ref_base_id, ref_path, ref_name, true);
302 base_paths.insert(ref_base_id, base_path);
303 base_paths
304 },
305 table_metadata: self.table_metadata.clone(),
306 }
307 }
308
309 pub fn timestamp(&self) -> DateTime<Utc> {
311 let nanos = self.timestamp_nanos % 1_000_000_000;
312 let seconds = ((self.timestamp_nanos - nanos) / 1_000_000_000) as i64;
313 Utc.from_utc_datetime(
314 &DateTime::from_timestamp(seconds, nanos as u32)
315 .unwrap_or_default()
316 .naive_utc(),
317 )
318 }
319
320 pub fn set_timestamp(&mut self, nanos: u128) {
322 self.timestamp_nanos = nanos;
323 }
324
325 pub fn config_mut(&mut self) -> &mut HashMap<String, String> {
327 &mut self.config
328 }
329
330 pub fn table_metadata_mut(&mut self) -> &mut HashMap<String, String> {
332 &mut self.table_metadata
333 }
334
335 pub fn schema_metadata_mut(&mut self) -> &mut HashMap<String, String> {
337 &mut self.schema.metadata
338 }
339
340 pub fn field_metadata_mut(&mut self, field_id: i32) -> Option<&mut HashMap<String, String>> {
344 self.schema
345 .field_by_id_mut(field_id)
346 .map(|field| &mut field.metadata)
347 }
348
349 #[deprecated(note = "Use config_mut() for direct access to config HashMap")]
351 pub fn update_config(&mut self, upsert_values: impl IntoIterator<Item = (String, String)>) {
352 self.config.extend(upsert_values);
353 }
354
355 #[deprecated(note = "Use config_mut() for direct access to config HashMap")]
357 pub fn delete_config_keys(&mut self, delete_keys: &[&str]) {
358 self.config
359 .retain(|key, _| !delete_keys.contains(&key.as_str()));
360 }
361
362 #[deprecated(note = "Use schema_metadata_mut() for direct access to schema metadata HashMap")]
364 pub fn replace_schema_metadata(&mut self, new_metadata: HashMap<String, String>) {
365 self.schema.metadata = new_metadata;
366 }
367
368 #[deprecated(
372 note = "Use field_metadata_mut(field_id) for direct access to field metadata HashMap"
373 )]
374 pub fn replace_field_metadata(
375 &mut self,
376 field_id: i32,
377 new_metadata: HashMap<String, String>,
378 ) -> Result<()> {
379 if let Some(field) = self.schema.field_by_id_mut(field_id) {
380 field.metadata = new_metadata;
381 Ok(())
382 } else {
383 Err(Error::invalid_input(format!(
384 "Field with id {} does not exist for replace_field_metadata",
385 field_id
386 )))
387 }
388 }
389
390 pub fn update_max_fragment_id(&mut self) {
392 if self.fragments.is_empty() {
394 return;
395 }
396
397 let max_fragment_id = self
398 .fragments
399 .iter()
400 .map(|f| f.id)
401 .max()
402 .unwrap() .try_into()
404 .unwrap();
405
406 match self.max_fragment_id {
407 None => {
408 self.max_fragment_id = Some(max_fragment_id);
410 }
411 Some(current_max) => {
412 if max_fragment_id > current_max {
415 self.max_fragment_id = Some(max_fragment_id);
416 }
417 }
418 }
419 }
420
421 pub fn max_fragment_id(&self) -> Option<u64> {
426 if let Some(max_id) = self.max_fragment_id {
427 Some(max_id.into())
429 } else {
430 self.fragments.iter().map(|f| f.id).max()
432 }
433 }
434
435 pub fn max_field_id(&self) -> i32 {
441 let schema_max_id = self.schema.max_field_id().unwrap_or(-1);
442 let fragment_max_id = self
443 .fragments
444 .iter()
445 .flat_map(|fragment| {
446 fragment
447 .referenced_lance_files()
448 .flat_map(|file| file.fields.iter())
449 })
450 .copied()
451 .max()
452 .unwrap_or(-1);
453 schema_max_id.max(fragment_max_id)
454 }
455
456 pub fn fragments_since(&self, since: &Self) -> Result<Vec<Fragment>> {
459 if since.version >= self.version {
460 return Err(Error::invalid_input(format!(
461 "fragments_since: given version {} is newer than manifest version {}",
462 since.version, self.version
463 )));
464 }
465 let start = since.max_fragment_id();
466 Ok(self
467 .fragments
468 .iter()
469 .filter(|&f| start.map(|s| f.id > s).unwrap_or(true))
470 .cloned()
471 .collect())
472 }
473
474 pub fn fragments_by_offset_range(&self, range: Range<usize>) -> Vec<(usize, &Fragment)> {
490 let start = range.start;
491 let end = range.end;
492 let idx = self
493 .fragment_offsets
494 .binary_search(&start)
495 .unwrap_or_else(|idx| idx - 1);
496
497 let mut fragments = vec![];
498 for i in idx..self.fragments.len() {
499 if self.fragment_offsets[i] >= end
500 || self.fragment_offsets[i] + self.fragments[i].num_rows().unwrap_or_default()
501 <= start
502 {
503 break;
504 }
505 fragments.push((self.fragment_offsets[i], &self.fragments[i]));
506 }
507
508 fragments
509 }
510
511 pub fn uses_stable_row_ids(&self) -> bool {
513 self.reader_feature_flags & FLAG_STABLE_ROW_IDS != 0
514 }
515
516 pub fn serialized(&self) -> Vec<u8> {
519 let pb_manifest: pb::Manifest = self.into();
520 pb_manifest.encode_to_vec()
521 }
522
523 pub fn summary(&self) -> ManifestSummary {
534 let mut summary =
536 self.fragments
537 .iter()
538 .fold(ManifestSummary::default(), |mut summary, f| {
539 summary.total_data_files += f.files.len() as u64;
541 if let Some(num_rows) = f.num_rows() {
543 summary.total_rows += num_rows as u64;
544 }
545 for data_file in &f.files {
547 if let Some(size_bytes) = data_file.file_size_bytes.get() {
548 summary.total_files_size += size_bytes.get();
549 }
550 }
551 if f.deletion_file.is_some() {
553 summary.total_deletion_files += 1;
554 }
555 if let Some(deletion_file) = &f.deletion_file
557 && let Some(num_deleted) = deletion_file.num_deleted_rows
558 {
559 summary.total_deletion_file_rows += num_deleted as u64;
560 }
561 summary
562 });
563 summary.total_fragments = self.fragments.len() as u64;
564 summary.total_data_file_rows = summary.total_rows + summary.total_deletion_file_rows;
565
566 summary
567 }
568}
569
570pub async fn populate_manifest_schema_dictionaries(
589 manifest: &mut Manifest,
590 reader: &dyn Reader,
591) -> Result<()> {
592 match manifest.data_storage_format.version {
593 ConcreteFileVersion::V1 => {
594 populate_schema_dictionaries(&mut manifest.schema, reader).await?;
595 }
596 ConcreteFileVersion::V2_0
597 | ConcreteFileVersion::V2_1
598 | ConcreteFileVersion::V2_2
599 | ConcreteFileVersion::V2_3 => {}
600 }
601 Ok(())
602}
603
604#[derive(Debug, Clone, PartialEq)]
605pub struct BasePath {
606 pub id: u32,
607 pub name: Option<String>,
608 pub is_dataset_root: bool,
609 pub path: String,
611}
612
613impl BasePath {
614 pub fn new(id: u32, path: String, name: Option<String>, is_dataset_root: bool) -> Self {
623 Self {
624 id,
625 name,
626 is_dataset_root,
627 path,
628 }
629 }
630
631 pub fn extract_path(&self, registry: Arc<ObjectStoreRegistry>) -> Result<Path> {
635 ObjectStore::extract_path_from_uri(registry, &self.path)
636 }
637}
638
639impl DeepSizeOf for BasePath {
640 fn deep_size_of_children(&self, context: &mut lance_core::deepsize::Context) -> usize {
641 self.name.deep_size_of_children(context)
642 + self.path.deep_size_of_children(context) * 2
643 + size_of::<bool>()
644 }
645}
646
647#[derive(Debug, Clone, PartialEq, DeepSizeOf)]
648pub struct WriterVersion {
649 pub library: String,
650 pub version: String,
651 pub prerelease: Option<String>,
652 pub build_metadata: Option<String>,
653}
654
655#[derive(Debug, Clone, PartialEq, DeepSizeOf)]
656pub struct DataStorageFormat {
657 pub file_format: String,
658 pub version: ConcreteFileVersion,
659}
660
661const LANCE_FORMAT_NAME: &str = "lance";
662
663impl DataStorageFormat {
664 pub fn new(version: ConcreteFileVersion) -> Self {
665 Self {
666 file_format: LANCE_FORMAT_NAME.to_string(),
667 version,
668 }
669 }
670
671 pub fn lance_file_format(&self) -> ConcreteFileVersion {
673 self.version
674 }
675}
676
677impl Default for DataStorageFormat {
678 fn default() -> Self {
679 Self::new(stable_file_version())
680 }
681}
682
683impl TryFrom<pb::manifest::DataStorageFormat> for DataStorageFormat {
684 type Error = Error;
685
686 fn try_from(pb: pb::manifest::DataStorageFormat) -> Result<Self> {
687 Ok(Self {
688 file_format: pb.file_format,
689 version: ConcreteFileVersion::from_manifest_string(&pb.version)?,
690 })
691 }
692}
693
694#[derive(Debug, Clone)]
701pub struct ManifestBuildConfig {
702 pub auto_set_feature_flags: bool,
705 pub timestamp_nanos: u128,
707 pub use_stable_row_ids: bool,
710 pub use_legacy_format: Option<bool>,
713 pub storage_format: Option<DataStorageFormat>,
716 pub disable_transaction_file: bool,
718 pub migration_next_row_id: Option<u64>,
722}
723
724#[derive(Debug, Clone, Copy, PartialEq, Eq)]
725pub enum VersionPart {
726 Major,
727 Minor,
728 Patch,
729}
730
731fn bump_version(version: &mut semver::Version, part: VersionPart) {
732 match part {
733 VersionPart::Major => {
734 version.major += 1;
735 version.minor = 0;
736 version.patch = 0;
737 }
738 VersionPart::Minor => {
739 version.minor += 1;
740 version.patch = 0;
741 }
742 VersionPart::Patch => {
743 version.patch += 1;
744 }
745 }
746}
747
748impl WriterVersion {
749 fn split_version(full_version: &str) -> Option<(String, Option<String>, Option<String>)> {
759 let mut parsed = semver::Version::parse(full_version).ok()?;
760
761 let prerelease = if parsed.pre.is_empty() {
762 None
763 } else {
764 Some(parsed.pre.to_string())
765 };
766
767 let build_metadata = if parsed.build.is_empty() {
768 None
769 } else {
770 Some(parsed.build.to_string())
771 };
772
773 parsed.pre = semver::Prerelease::EMPTY;
775 parsed.build = semver::BuildMetadata::EMPTY;
776 Some((parsed.to_string(), prerelease, build_metadata))
777 }
778
779 #[deprecated(note = "Use `lance_lib_version()` instead")]
782 pub fn semver(&self) -> Option<(u32, u32, u32, Option<&str>)> {
783 let (version_part, tag) = if let Some(dash_idx) = self.version.find('-') {
785 (
786 &self.version[..dash_idx],
787 Some(&self.version[dash_idx + 1..]),
788 )
789 } else {
790 (self.version.as_str(), None)
791 };
792
793 let mut parts = version_part.split('.');
794 let major = parts.next().unwrap_or("0").parse().ok()?;
795 let minor = parts.next().unwrap_or("0").parse().ok()?;
796 let patch = parts.next().unwrap_or("0").parse().ok()?;
797
798 Some((major, minor, patch, tag))
799 }
800
801 pub fn lance_lib_version(&self) -> Option<semver::Version> {
809 if self.library != "lance" {
810 return None;
811 }
812
813 let mut version = semver::Version::parse(&self.version).ok()?;
814
815 if let Some(ref prerelease) = self.prerelease {
816 version.pre = semver::Prerelease::new(prerelease).ok()?;
817 }
818
819 if let Some(ref build_metadata) = self.build_metadata {
820 version.build = semver::BuildMetadata::new(build_metadata).ok()?;
821 }
822
823 Some(version)
824 }
825
826 #[deprecated(
827 note = "Use `lance_lib_version()` instead, which safely checks the library field and returns Option"
828 )]
829 #[allow(deprecated)]
830 pub fn semver_or_panic(&self) -> (u32, u32, u32, Option<&str>) {
831 self.semver()
832 .unwrap_or_else(|| panic!("Invalid writer version: {}", self.version))
833 }
834
835 #[deprecated(note = "Use `lance_lib_version()` and its `older_than` method instead.")]
841 pub fn older_than(&self, major: u32, minor: u32, patch: u32) -> bool {
842 let version = self
843 .lance_lib_version()
844 .expect("Not lance library or invalid version");
845 let other = semver::Version {
846 major: major.into(),
847 minor: minor.into(),
848 patch: patch.into(),
849 pre: semver::Prerelease::EMPTY,
850 build: semver::BuildMetadata::EMPTY,
851 };
852 version < other
853 }
854
855 #[deprecated(note = "This is meant for testing and will be made private in future version.")]
856 pub fn bump(&self, part: VersionPart, keep_tag: bool) -> Self {
857 let mut version = self.lance_lib_version().expect("Should be lance version");
858 bump_version(&mut version, part);
859 if !keep_tag {
860 version.pre = semver::Prerelease::EMPTY;
861 }
862 let (clean_version, prerelease, build_metadata) = Self::split_version(&version.to_string())
863 .expect("Bumped version should be valid semver");
864 Self {
865 library: self.library.clone(),
866 version: clean_version,
867 prerelease,
868 build_metadata,
869 }
870 }
871}
872
873impl Default for WriterVersion {
874 #[cfg(not(test))]
875 fn default() -> Self {
876 let full_version = env!("CARGO_PKG_VERSION");
877 let (version, prerelease, build_metadata) =
878 Self::split_version(full_version).expect("CARGO_PKG_VERSION should be valid semver");
879 Self {
880 library: "lance".to_string(),
881 version,
882 prerelease,
883 build_metadata,
884 }
885 }
886
887 #[cfg(test)]
889 #[allow(deprecated)]
890 fn default() -> Self {
891 let full_version = env!("CARGO_PKG_VERSION");
892 let (version, prerelease, build_metadata) =
893 Self::split_version(full_version).expect("CARGO_PKG_VERSION should be valid semver");
894 Self {
895 library: "lance".to_string(),
896 version,
897 prerelease,
898 build_metadata,
899 }
900 .bump(VersionPart::Patch, true)
901 }
902}
903
904impl ProtoStruct for Manifest {
905 type Proto = pb::Manifest;
906}
907
908impl From<pb::BasePath> for BasePath {
909 fn from(p: pb::BasePath) -> Self {
910 Self::new(p.id, p.path, p.name, p.is_dataset_root)
911 }
912}
913
914impl From<BasePath> for pb::BasePath {
915 fn from(p: BasePath) -> Self {
916 Self {
917 id: p.id,
918 name: p.name,
919 is_dataset_root: p.is_dataset_root,
920 path: p.path,
921 }
922 }
923}
924
925impl TryFrom<pb::Manifest> for Manifest {
926 type Error = Error;
927
928 fn try_from(p: pb::Manifest) -> Result<Self> {
929 let timestamp_nanos = p.timestamp.map(|ts| {
930 let sec = ts.seconds as u128 * 1e9 as u128;
931 let nanos = ts.nanos as u128;
932 sec + nanos
933 });
934 let writer_version = match p.writer_version {
936 Some(pb::manifest::WriterVersion {
937 library,
938 version,
939 prerelease,
940 build_metadata,
941 }) => Some(WriterVersion {
942 library,
943 version,
944 prerelease,
945 build_metadata,
946 }),
947 _ => None,
948 };
949 let mut interner = DataFileFieldInterner::default();
950 let fragments = Arc::new(
951 p.fragments
952 .into_iter()
953 .map(|f| interner.intern_fragment(f))
954 .collect::<Result<Vec<_>>>()?,
955 );
956 let fragment_offsets = compute_fragment_offsets(fragments.as_slice());
957 let fields_with_meta = FieldsWithMeta {
958 fields: Fields(p.fields),
959 metadata: p.schema_metadata,
960 };
961
962 if FLAG_STABLE_ROW_IDS & p.reader_feature_flags != 0
963 && !fragments.iter().all(|frag| frag.row_id_meta.is_some())
964 {
965 return Err(Error::internal("All fragments must have row ids"));
966 }
967
968 let data_storage_format = match p.data_format {
969 None => {
970 if let Some(inferred_version) = Fragment::try_infer_version(fragments.as_ref())? {
971 DataStorageFormat::new(inferred_version)
973 } else {
974 if has_deprecated_v2_feature_flag(p.writer_feature_flags) {
976 DataStorageFormat::new(stable_file_version())
977 } else {
978 DataStorageFormat::new(ConcreteFileVersion::V1)
979 }
980 }
981 }
982 Some(format) => DataStorageFormat::try_from(format)?,
983 };
984
985 let schema = Schema::try_from(fields_with_meta)?;
986
987 Ok(Self {
988 schema,
989 version: p.version,
990 branch: p.branch,
991 writer_version,
992 version_aux_data: p.version_aux_data as usize,
993 index_section: p.index_section.map(|i| i as usize),
994 timestamp_nanos: timestamp_nanos.unwrap_or(0),
995 tag: if p.tag.is_empty() { None } else { Some(p.tag) },
996 reader_feature_flags: p.reader_feature_flags,
997 writer_feature_flags: p.writer_feature_flags,
998 max_fragment_id: p.max_fragment_id,
999 fragments,
1000 transaction_file: if p.transaction_file.is_empty() {
1001 None
1002 } else {
1003 Some(p.transaction_file)
1004 },
1005 transaction_section: p.transaction_section.map(|i| i as usize),
1006 fragment_offsets,
1007 next_row_id: p.next_row_id,
1008 data_storage_format,
1009 config: p.config,
1010 table_metadata: p.table_metadata,
1011 base_paths: p
1012 .base_paths
1013 .iter()
1014 .map(|item| (item.id, item.clone().into()))
1015 .collect(),
1016 })
1017 }
1018}
1019
1020impl From<&Manifest> for pb::Manifest {
1021 fn from(m: &Manifest) -> Self {
1022 let timestamp_nanos = if m.timestamp_nanos == 0 {
1023 None
1024 } else {
1025 let nanos = m.timestamp_nanos % 1e9 as u128;
1026 let seconds = ((m.timestamp_nanos - nanos) / 1e9 as u128) as i64;
1027 Some(Timestamp {
1028 seconds,
1029 nanos: nanos as i32,
1030 })
1031 };
1032 let fields_with_meta: FieldsWithMeta = (&m.schema).into();
1033 Self {
1034 fields: fields_with_meta.fields.0,
1035 schema_metadata: m
1036 .schema
1037 .metadata
1038 .iter()
1039 .map(|(k, v)| (k.clone(), v.as_bytes().to_vec()))
1040 .collect(),
1041 version: m.version,
1042 branch: m.branch.clone(),
1043 writer_version: m
1044 .writer_version
1045 .as_ref()
1046 .map(|wv| pb::manifest::WriterVersion {
1047 library: wv.library.clone(),
1048 version: wv.version.clone(),
1049 prerelease: wv.prerelease.clone(),
1050 build_metadata: wv.build_metadata.clone(),
1051 }),
1052 fragments: m.fragments.iter().map(pb::DataFragment::from).collect(),
1053 table_metadata: m.table_metadata.clone(),
1054 version_aux_data: m.version_aux_data as u64,
1055 index_section: m.index_section.map(|i| i as u64),
1056 timestamp: timestamp_nanos,
1057 tag: m.tag.clone().unwrap_or_default(),
1058 reader_feature_flags: m.reader_feature_flags,
1059 writer_feature_flags: m.writer_feature_flags,
1060 max_fragment_id: m.max_fragment_id,
1061 transaction_file: m.transaction_file.clone().unwrap_or_default(),
1062 next_row_id: m.next_row_id,
1063 data_format: Some(pb::manifest::DataStorageFormat {
1064 file_format: m.data_storage_format.file_format.clone(),
1065 version: m
1066 .data_storage_format
1067 .version
1068 .to_manifest_string()
1069 .to_string(),
1070 }),
1071 config: m.config.clone(),
1072 base_paths: m
1073 .base_paths
1074 .values()
1075 .map(|base_path| pb::BasePath {
1076 id: base_path.id,
1077 name: base_path.name.clone(),
1078 is_dataset_root: base_path.is_dataset_root,
1079 path: base_path.path.clone(),
1080 })
1081 .collect(),
1082 transaction_section: m.transaction_section.map(|i| i as u64),
1083 }
1084 }
1085}
1086
1087#[async_trait]
1088pub trait SelfDescribingFileReader {
1089 async fn try_new_self_described(
1097 object_store: &ObjectStore,
1098 path: &Path,
1099 cache: Option<&LanceCache>,
1100 ) -> Result<Self>
1101 where
1102 Self: Sized,
1103 {
1104 let reader = object_store.open(path).await?;
1105 Self::try_new_self_described_from_reader(reader.into(), cache).await
1106 }
1107
1108 async fn try_new_self_described_from_reader(
1109 reader: Arc<dyn Reader>,
1110 cache: Option<&LanceCache>,
1111 ) -> Result<Self>
1112 where
1113 Self: Sized;
1114}
1115
1116#[async_trait]
1117impl SelfDescribingFileReader for V1FileReader {
1118 async fn try_new_self_described_from_reader(
1119 reader: Arc<dyn Reader>,
1120 cache: Option<&LanceCache>,
1121 ) -> Result<Self> {
1122 let metadata = Self::read_metadata(reader.as_ref(), cache).await?;
1123 let manifest_position = metadata.manifest_position.ok_or(Error::internal(format!(
1124 "Attempt to open file at {} as self-describing but it did not contain a manifest",
1125 reader.path(),
1126 )))?;
1127 let mut manifest: Manifest = read_struct(reader.as_ref(), manifest_position).await?;
1128 populate_manifest_schema_dictionaries(&mut manifest, reader.as_ref()).await?;
1129 let schema = manifest.schema;
1130 let max_field_id = schema.max_field_id().unwrap_or_default();
1131 Self::try_new_from_reader(
1132 reader.path(),
1133 reader.clone(),
1134 Some(metadata),
1135 schema,
1136 0,
1137 0,
1138 max_field_id,
1139 cache,
1140 )
1141 .await
1142 }
1143}
1144
1145#[cfg(test)]
1146mod tests {
1147 use crate::feature_flags::FLAG_USE_V2_FORMAT_DEPRECATED;
1148 use crate::format::overlay::{DataOverlayFile, OverlayCoverage};
1149 use crate::format::{DataFile, DeletionFile, DeletionFileType};
1150 use std::num::NonZero;
1151
1152 use super::*;
1153
1154 use arrow_schema::{Field as ArrowField, Schema as ArrowSchema};
1155 use lance_core::datatypes::Field;
1156 use roaring::RoaringBitmap;
1157
1158 #[test]
1162 fn shallow_clone_stamps_base_id_on_overlay_files() {
1163 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
1164 "a",
1165 arrow_schema::DataType::Int64,
1166 false,
1167 )]);
1168 let schema = Schema::try_from(&arrow_schema).unwrap();
1169
1170 let mut fragment = Fragment::with_file_legacy(0, "base.lance", &schema, Some(10));
1171 fragment.overlays = vec![DataOverlayFile {
1172 data_file: DataFile::new_legacy_from_fields("overlay.lance", vec![0], None),
1173 coverage: OverlayCoverage::Shared(Arc::new(RoaringBitmap::from_iter([0_u32]))),
1174 committed_version: 1,
1175 }];
1176 let manifest = Manifest::new(
1177 schema,
1178 Arc::new(vec![fragment]),
1179 DataStorageFormat::default(),
1180 HashMap::new(),
1181 );
1182
1183 let cloned = manifest.shallow_clone(
1184 Some("parent".to_string()),
1185 "memory://parent".to_string(),
1186 7,
1187 None,
1188 String::new(),
1189 );
1190
1191 let fragment = &cloned.fragments[0];
1192 assert_eq!(fragment.files[0].base_id, Some(7));
1193 assert_eq!(
1194 fragment.overlays[0].data_file.base_id,
1195 Some(7),
1196 "the overlay's data file resolves against the parent as well"
1197 );
1198 }
1199
1200 #[test]
1201 fn old_empty_manifest_recovers_v1_or_current_stable() {
1202 let old_manifest = pb::Manifest {
1203 data_format: None,
1204 ..Default::default()
1205 };
1206 let recovered_v1 = Manifest::try_from(old_manifest.clone()).unwrap();
1207 assert_eq!(
1208 recovered_v1.data_storage_format.lance_file_format(),
1209 ConcreteFileVersion::V1
1210 );
1211
1212 let recovered_stable = Manifest::try_from(pb::Manifest {
1213 writer_feature_flags: FLAG_USE_V2_FORMAT_DEPRECATED,
1214 ..old_manifest
1215 })
1216 .unwrap();
1217 assert_eq!(
1218 recovered_stable.data_storage_format.lance_file_format(),
1219 stable_file_version()
1220 );
1221 }
1222
1223 #[test]
1224 fn manifest_persistence_rejects_selectors_and_public_aliases() {
1225 for version in ["stable", "next", "legacy", "0.3"] {
1226 let manifest = pb::Manifest {
1227 data_format: Some(pb::manifest::DataStorageFormat {
1228 file_format: LANCE_FORMAT_NAME.to_string(),
1229 version: version.to_string(),
1230 }),
1231 ..Default::default()
1232 };
1233 assert!(Manifest::try_from(manifest).is_err(), "accepted {version}");
1234 }
1235 }
1236
1237 #[test]
1238 fn manifest_codec_writes_canonical_exact_string() {
1239 let manifest = Manifest::new(
1240 Schema::default(),
1241 Arc::new(Vec::new()),
1242 DataStorageFormat::new(ConcreteFileVersion::V2_0),
1243 HashMap::new(),
1244 );
1245 let encoded = pb::Manifest::from(&manifest);
1246 assert_eq!(encoded.data_format.unwrap().version, "2.0");
1247 }
1248
1249 #[test]
1250 fn missing_format_infers_exact_version_and_rejects_mixed_files() {
1251 let v2_0 = Fragment::new(0).with_file(
1252 "v2_0.lance",
1253 vec![0],
1254 vec![0],
1255 ConcreteFileVersion::V2_0,
1256 None,
1257 );
1258 let manifest = Manifest::new(
1259 Schema::default(),
1260 Arc::new(vec![v2_0.clone()]),
1261 DataStorageFormat::new(ConcreteFileVersion::V1),
1262 HashMap::new(),
1263 );
1264 let mut encoded = pb::Manifest::from(&manifest);
1265 encoded.data_format = None;
1266 let recovered = Manifest::try_from(encoded).unwrap();
1267 assert_eq!(
1268 recovered.data_storage_format.lance_file_format(),
1269 ConcreteFileVersion::V2_0
1270 );
1271
1272 let v2_1 = Fragment::new(1).with_file(
1273 "v2_1.lance",
1274 vec![0],
1275 vec![0],
1276 ConcreteFileVersion::V2_1,
1277 None,
1278 );
1279 let mixed_manifest = Manifest::new(
1280 Schema::default(),
1281 Arc::new(vec![v2_0, v2_1]),
1282 DataStorageFormat::new(ConcreteFileVersion::V2_0),
1283 HashMap::new(),
1284 );
1285 let mut encoded = pb::Manifest::from(&mixed_manifest);
1286 encoded.data_format = None;
1287 let error = Manifest::try_from(encoded).unwrap_err();
1288 assert!(
1289 error
1290 .to_string()
1291 .contains("All data files must have the same version")
1292 );
1293 }
1294
1295 #[test]
1296 fn test_writer_version() {
1297 let wv = WriterVersion::default();
1298 assert_eq!(wv.library, "lance");
1299
1300 let cargo_version = env!("CARGO_PKG_VERSION");
1302 let expected_tag = if cargo_version.contains('-') {
1303 Some(cargo_version.split('-').nth(1).unwrap())
1304 } else {
1305 None
1306 };
1307
1308 let version_parts: Vec<&str> = wv.version.split('.').collect();
1310 assert_eq!(
1311 version_parts.len(),
1312 3,
1313 "Version should be major.minor.patch"
1314 );
1315 assert!(
1316 !wv.version.contains('-'),
1317 "Version field should not contain prerelease"
1318 );
1319
1320 assert_eq!(wv.prerelease.as_deref(), expected_tag);
1322 assert_eq!(wv.build_metadata, None);
1324
1325 let version = wv.lance_lib_version().unwrap();
1327 assert_eq!(
1328 version.major,
1329 env!("CARGO_PKG_VERSION_MAJOR").parse::<u64>().unwrap()
1330 );
1331 assert_eq!(
1332 version.minor,
1333 env!("CARGO_PKG_VERSION_MINOR").parse::<u64>().unwrap()
1334 );
1335 assert_eq!(
1336 version.patch,
1337 env!("CARGO_PKG_VERSION_PATCH").parse::<u64>().unwrap() + 1
1339 );
1340 assert_eq!(version.pre.as_str(), expected_tag.unwrap_or(""));
1341
1342 for part in &[VersionPart::Major, VersionPart::Minor, VersionPart::Patch] {
1343 let mut bumped_version = version.clone();
1344 bump_version(&mut bumped_version, *part);
1345 assert!(version < bumped_version);
1346 }
1347 }
1348
1349 #[test]
1350 fn test_writer_version_split() {
1351 let (version, prerelease, build_metadata) =
1353 WriterVersion::split_version("2.0.0-rc.1").unwrap();
1354 assert_eq!(version, "2.0.0");
1355 assert_eq!(prerelease, Some("rc.1".to_string()));
1356 assert_eq!(build_metadata, None);
1357
1358 let (version, prerelease, build_metadata) = WriterVersion::split_version("2.0.0").unwrap();
1360 assert_eq!(version, "2.0.0");
1361 assert_eq!(prerelease, None);
1362 assert_eq!(build_metadata, None);
1363
1364 let (version, prerelease, build_metadata) =
1366 WriterVersion::split_version("2.0.0-rc.1+build.123").unwrap();
1367 assert_eq!(version, "2.0.0");
1368 assert_eq!(prerelease, Some("rc.1".to_string()));
1369 assert_eq!(build_metadata, Some("build.123".to_string()));
1370
1371 let (version, prerelease, build_metadata) =
1373 WriterVersion::split_version("2.0.0+build.123").unwrap();
1374 assert_eq!(version, "2.0.0");
1375 assert_eq!(prerelease, None);
1376 assert_eq!(build_metadata, Some("build.123".to_string()));
1377
1378 assert!(WriterVersion::split_version("not-a-version").is_none());
1380 }
1381
1382 #[test]
1383 fn test_writer_version_comparison_with_prerelease() {
1384 let v1 = WriterVersion {
1385 library: "lance".to_string(),
1386 version: "2.0.0".to_string(),
1387 prerelease: Some("rc.1".to_string()),
1388 build_metadata: None,
1389 };
1390
1391 let v2 = WriterVersion {
1392 library: "lance".to_string(),
1393 version: "2.0.0".to_string(),
1394 prerelease: None,
1395 build_metadata: None,
1396 };
1397
1398 let semver1 = v1.lance_lib_version().unwrap();
1399 let semver2 = v2.lance_lib_version().unwrap();
1400
1401 assert!(semver1 < semver2);
1403 }
1404
1405 #[test]
1406 fn test_writer_version_with_build_metadata() {
1407 let v = WriterVersion {
1408 library: "lance".to_string(),
1409 version: "2.0.0".to_string(),
1410 prerelease: Some("rc.1".to_string()),
1411 build_metadata: Some("build.123".to_string()),
1412 };
1413
1414 let semver = v.lance_lib_version().unwrap();
1415 assert_eq!(semver.to_string(), "2.0.0-rc.1+build.123");
1416 assert_eq!(semver.major, 2);
1417 assert_eq!(semver.minor, 0);
1418 assert_eq!(semver.patch, 0);
1419 assert_eq!(semver.pre.as_str(), "rc.1");
1420 assert_eq!(semver.build.as_str(), "build.123");
1421 }
1422
1423 #[test]
1424 fn test_writer_version_non_semver() {
1425 let v = WriterVersion {
1427 library: "lance".to_string(),
1428 version: "custom-build-v1".to_string(),
1429 prerelease: None,
1430 build_metadata: None,
1431 };
1432
1433 assert!(v.lance_lib_version().is_none());
1435
1436 assert_eq!(v.library, "lance");
1438 assert_eq!(v.version, "custom-build-v1");
1439 }
1440
1441 #[test]
1442 #[allow(deprecated)]
1443 fn test_older_than_with_prerelease() {
1444 let v_rc = WriterVersion {
1446 library: "lance".to_string(),
1447 version: "2.0.0".to_string(),
1448 prerelease: Some("rc.1".to_string()),
1449 build_metadata: None,
1450 };
1451
1452 assert!(v_rc.older_than(2, 0, 0));
1454
1455 assert!(v_rc.older_than(2, 0, 1));
1457
1458 assert!(!v_rc.older_than(1, 9, 9));
1460
1461 let v_release = WriterVersion {
1462 library: "lance".to_string(),
1463 version: "2.0.0".to_string(),
1464 prerelease: None,
1465 build_metadata: None,
1466 };
1467
1468 assert!(!v_release.older_than(2, 0, 0));
1470
1471 assert!(v_release.older_than(2, 0, 1));
1473 }
1474
1475 #[test]
1476 fn test_fragments_by_offset_range() {
1477 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
1478 "a",
1479 arrow_schema::DataType::Int64,
1480 false,
1481 )]);
1482 let schema = Schema::try_from(&arrow_schema).unwrap();
1483 let fragments = vec![
1484 Fragment::with_file_legacy(0, "path1", &schema, Some(10)),
1485 Fragment::with_file_legacy(1, "path2", &schema, Some(15)),
1486 Fragment::with_file_legacy(2, "path3", &schema, Some(20)),
1487 ];
1488 let manifest = Manifest::new(
1489 schema,
1490 Arc::new(fragments),
1491 DataStorageFormat::default(),
1492 HashMap::new(),
1493 );
1494
1495 let actual = manifest.fragments_by_offset_range(0..10);
1496 assert_eq!(actual.len(), 1);
1497 assert_eq!(actual[0].0, 0);
1498 assert_eq!(actual[0].1.id, 0);
1499
1500 let actual = manifest.fragments_by_offset_range(5..15);
1501 assert_eq!(actual.len(), 2);
1502 assert_eq!(actual[0].0, 0);
1503 assert_eq!(actual[0].1.id, 0);
1504 assert_eq!(actual[1].0, 10);
1505 assert_eq!(actual[1].1.id, 1);
1506
1507 let actual = manifest.fragments_by_offset_range(15..50);
1508 assert_eq!(actual.len(), 2);
1509 assert_eq!(actual[0].0, 10);
1510 assert_eq!(actual[0].1.id, 1);
1511 assert_eq!(actual[1].0, 25);
1512 assert_eq!(actual[1].1.id, 2);
1513
1514 let actual = manifest.fragments_by_offset_range(45..100);
1516 assert!(actual.is_empty());
1517
1518 assert!(manifest.fragments_by_offset_range(200..400).is_empty());
1519 }
1520
1521 #[test]
1522 fn test_max_field_id() {
1523 let mut field0 =
1525 Field::try_from(ArrowField::new("a", arrow_schema::DataType::Int64, false)).unwrap();
1526 field0.set_id(-1, &mut 0);
1527 let mut field2 =
1528 Field::try_from(ArrowField::new("b", arrow_schema::DataType::Int64, false)).unwrap();
1529 field2.set_id(-1, &mut 2);
1530
1531 let schema = Schema {
1532 fields: vec![field0, field2],
1533 metadata: Default::default(),
1534 };
1535 let fragments = vec![
1536 Fragment {
1537 id: 0,
1538 files: vec![DataFile::new_legacy_from_fields(
1539 "path1",
1540 vec![0, 1, 2],
1541 None,
1542 )],
1543 overlays: vec![],
1544 deletion_file: None,
1545 row_id_meta: None,
1546 physical_rows: None,
1547 created_at_version_meta: None,
1548 last_updated_at_version_meta: None,
1549 },
1550 Fragment {
1551 id: 1,
1552 files: vec![
1553 DataFile::new_legacy_from_fields("path2", vec![0, 1, 43], None),
1554 DataFile::new_legacy_from_fields("path3", vec![2], None),
1555 ],
1556 overlays: vec![],
1557 deletion_file: None,
1558 row_id_meta: None,
1559 physical_rows: None,
1560 created_at_version_meta: None,
1561 last_updated_at_version_meta: None,
1562 },
1563 ];
1564
1565 let manifest = Manifest::new(
1566 schema,
1567 Arc::new(fragments),
1568 DataStorageFormat::default(),
1569 HashMap::new(),
1570 );
1571
1572 assert_eq!(manifest.max_field_id(), 43);
1573 }
1574
1575 #[test]
1576 fn test_max_field_id_includes_overlay_files() {
1577 let mut field0 =
1578 Field::try_from(ArrowField::new("a", arrow_schema::DataType::Int64, false)).unwrap();
1579 field0.set_id(-1, &mut 0);
1580 let schema = Schema {
1581 fields: vec![field0],
1582 metadata: Default::default(),
1583 };
1584
1585 let mut fragment = Fragment {
1586 id: 0,
1587 files: vec![DataFile::new_legacy_from_fields("path1", vec![0], None)],
1588 overlays: vec![],
1589 deletion_file: None,
1590 row_id_meta: None,
1591 physical_rows: None,
1592 created_at_version_meta: None,
1593 last_updated_at_version_meta: None,
1594 };
1595 fragment.overlays = vec![DataOverlayFile {
1596 data_file: DataFile::new_legacy_from_fields("overlay.lance", vec![43], None),
1597 coverage: OverlayCoverage::Shared(Arc::new(RoaringBitmap::from_iter([0_u32]))),
1598 committed_version: 1,
1599 }];
1600
1601 let manifest = Manifest::new(
1602 schema,
1603 Arc::new(vec![fragment]),
1604 DataStorageFormat::default(),
1605 HashMap::new(),
1606 );
1607
1608 assert_eq!(manifest.max_field_id(), 43);
1609 }
1610
1611 #[test]
1612 fn test_config() {
1613 let arrow_schema = ArrowSchema::new(vec![ArrowField::new(
1614 "a",
1615 arrow_schema::DataType::Int64,
1616 false,
1617 )]);
1618 let schema = Schema::try_from(&arrow_schema).unwrap();
1619 let fragments = vec![
1620 Fragment::with_file_legacy(0, "path1", &schema, Some(10)),
1621 Fragment::with_file_legacy(1, "path2", &schema, Some(15)),
1622 Fragment::with_file_legacy(2, "path3", &schema, Some(20)),
1623 ];
1624 let mut manifest = Manifest::new(
1625 schema,
1626 Arc::new(fragments),
1627 DataStorageFormat::default(),
1628 HashMap::new(),
1629 );
1630
1631 let mut config = manifest.config.clone();
1632 config.insert("lance.test".to_string(), "value".to_string());
1633 config.insert("other-key".to_string(), "other-value".to_string());
1634
1635 manifest.config_mut().extend(config.clone());
1636 assert_eq!(manifest.config, config.clone());
1637
1638 config.remove("other-key");
1639 manifest.config_mut().remove("other-key");
1640 assert_eq!(manifest.config, config);
1641 }
1642
1643 #[test]
1644 fn test_manifest_summary() {
1645 let arrow_schema = ArrowSchema::new(vec![
1647 ArrowField::new("id", arrow_schema::DataType::Int64, false),
1648 ArrowField::new("name", arrow_schema::DataType::Utf8, true),
1649 ]);
1650 let schema = Schema::try_from(&arrow_schema).unwrap();
1651
1652 let empty_manifest = Manifest::new(
1653 schema.clone(),
1654 Arc::new(vec![]),
1655 DataStorageFormat::default(),
1656 HashMap::new(),
1657 );
1658
1659 let empty_summary = empty_manifest.summary();
1660 assert_eq!(empty_summary.total_rows, 0);
1661 assert_eq!(empty_summary.total_files_size, 0);
1662 assert_eq!(empty_summary.total_fragments, 0);
1663 assert_eq!(empty_summary.total_data_files, 0);
1664 assert_eq!(empty_summary.total_deletion_file_rows, 0);
1665 assert_eq!(empty_summary.total_data_file_rows, 0);
1666 assert_eq!(empty_summary.total_deletion_files, 0);
1667
1668 let empty_fragments = vec![
1670 Fragment::with_file_legacy(0, "empty_file1.lance", &schema, Some(0)),
1671 Fragment::with_file_legacy(1, "empty_file2.lance", &schema, Some(0)),
1672 ];
1673
1674 let empty_files_manifest = Manifest::new(
1675 schema.clone(),
1676 Arc::new(empty_fragments),
1677 DataStorageFormat::default(),
1678 HashMap::new(),
1679 );
1680
1681 let empty_files_summary = empty_files_manifest.summary();
1682 assert_eq!(empty_files_summary.total_rows, 0);
1683 assert_eq!(empty_files_summary.total_files_size, 0);
1684 assert_eq!(empty_files_summary.total_fragments, 2);
1685 assert_eq!(empty_files_summary.total_data_files, 2);
1686 assert_eq!(empty_files_summary.total_deletion_file_rows, 0);
1687 assert_eq!(empty_files_summary.total_data_file_rows, 0);
1688 assert_eq!(empty_files_summary.total_deletion_files, 0);
1689
1690 let real_fragments = vec![
1692 Fragment::with_file_legacy(0, "data_file1.lance", &schema, Some(100)),
1693 Fragment::with_file_legacy(1, "data_file2.lance", &schema, Some(250)),
1694 Fragment::with_file_legacy(2, "data_file3.lance", &schema, Some(75)),
1695 ];
1696
1697 let real_data_manifest = Manifest::new(
1698 schema.clone(),
1699 Arc::new(real_fragments),
1700 DataStorageFormat::default(),
1701 HashMap::new(),
1702 );
1703
1704 let real_data_summary = real_data_manifest.summary();
1705 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);
1708 assert_eq!(real_data_summary.total_data_files, 3);
1709 assert_eq!(real_data_summary.total_deletion_file_rows, 0);
1710 assert_eq!(real_data_summary.total_data_file_rows, 425);
1711 assert_eq!(real_data_summary.total_deletion_files, 0);
1712
1713 let mut fragment_with_deletion = Fragment::new(0)
1715 .with_file(
1716 "data_with_deletion.lance",
1717 vec![0, 1],
1718 vec![0, 1],
1719 stable_file_version(),
1720 NonZero::new(1000),
1721 )
1722 .with_physical_rows(50);
1723 fragment_with_deletion.deletion_file = Some(DeletionFile {
1724 read_version: 123,
1725 id: 456,
1726 file_type: DeletionFileType::Array,
1727 num_deleted_rows: Some(10),
1728 base_id: None,
1729 });
1730
1731 let manifest_with_deletion = Manifest::new(
1732 schema,
1733 Arc::new(vec![fragment_with_deletion]),
1734 DataStorageFormat::default(),
1735 HashMap::new(),
1736 );
1737
1738 let deletion_summary = manifest_with_deletion.summary();
1739 assert_eq!(deletion_summary.total_rows, 40); assert_eq!(deletion_summary.total_files_size, 1000);
1741 assert_eq!(deletion_summary.total_fragments, 1);
1742 assert_eq!(deletion_summary.total_data_files, 1);
1743 assert_eq!(deletion_summary.total_deletion_file_rows, 10);
1744 assert_eq!(deletion_summary.total_data_file_rows, 50);
1745 assert_eq!(deletion_summary.total_deletion_files, 1);
1746
1747 let stats_map: BTreeMap<String, String> = deletion_summary.into();
1749 assert_eq!(stats_map.len(), 7)
1750 }
1751}