1use std::str::FromStr;
19
20use serde_derive::{Deserialize, Serialize};
21
22use super::ByteBuf;
23use crate::encryption::{EncryptedInputFile, StandardKeyMetadata};
24use crate::error::Result;
25use crate::io::FileIO;
26use crate::spec::{Manifest, ManifestEntry};
27use crate::{Error, ErrorKind};
28
29#[derive(Debug, PartialEq, Clone, Eq, Hash)]
31pub struct ManifestFile {
32 pub manifest_path: String,
36 pub manifest_length: i64,
40 pub partition_spec_id: i32,
45 pub content: ManifestContentType,
50 pub sequence_number: i64,
55 pub min_sequence_number: i64,
60 pub added_snapshot_id: i64,
64 pub added_files_count: Option<u32>,
69 pub existing_files_count: Option<u32>,
74 pub deleted_files_count: Option<u32>,
79 pub added_rows_count: Option<u64>,
84 pub existing_rows_count: Option<u64>,
89 pub deleted_rows_count: Option<u64>,
94 pub partitions: Option<Vec<FieldSummary>>,
101 pub key_metadata: Option<Vec<u8>>,
105 pub first_row_id: Option<u64>,
109}
110
111impl ManifestFile {
112 pub fn has_added_files(&self) -> bool {
114 self.added_files_count.map(|c| c > 0).unwrap_or(true)
115 }
116
117 pub fn has_deleted_files(&self) -> bool {
119 self.deleted_files_count.map(|c| c > 0).unwrap_or(true)
120 }
121
122 pub fn has_existing_files(&self) -> bool {
124 self.existing_files_count.map(|c| c > 0).unwrap_or(true)
125 }
126}
127
128#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash, Default)]
130pub enum ManifestContentType {
131 #[default]
133 Data = 0,
134 Deletes = 1,
136}
137
138impl FromStr for ManifestContentType {
139 type Err = Error;
140
141 fn from_str(s: &str) -> Result<Self> {
142 match s {
143 "data" => Ok(ManifestContentType::Data),
144 "deletes" => Ok(ManifestContentType::Deletes),
145 _ => Err(Error::new(
146 ErrorKind::DataInvalid,
147 format!("Invalid manifest content type: {s}"),
148 )),
149 }
150 }
151}
152
153impl std::fmt::Display for ManifestContentType {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 match self {
156 ManifestContentType::Data => write!(f, "data"),
157 ManifestContentType::Deletes => write!(f, "deletes"),
158 }
159 }
160}
161
162impl TryFrom<i32> for ManifestContentType {
163 type Error = Error;
164
165 fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
166 match value {
167 0 => Ok(ManifestContentType::Data),
168 1 => Ok(ManifestContentType::Deletes),
169 _ => Err(Error::new(
170 ErrorKind::DataInvalid,
171 format!("Invalid manifest content type. Expected 0 or 1, got {value}"),
172 )),
173 }
174 }
175}
176
177impl ManifestFile {
178 pub async fn load_manifest(&self, file_io: &FileIO) -> Result<Manifest> {
182 let input = file_io.new_input(&self.manifest_path)?;
183 let avro = match &self.key_metadata {
184 Some(key_metadata_bytes) => {
185 let key_metadata = StandardKeyMetadata::decode(key_metadata_bytes)?;
186 EncryptedInputFile::new(input, key_metadata).read().await?
187 }
188 None => input.read().await?,
189 };
190
191 let (metadata, mut entries) = Manifest::try_from_avro_bytes(&avro)?;
192
193 for entry in &mut entries {
195 entry.inherit_data(self);
196 }
197
198 self.assign_first_row_ids(&mut entries)?;
199
200 Ok(Manifest::new(metadata, entries))
201 }
202
203 fn assign_first_row_ids(&self, entries: &mut [ManifestEntry]) -> Result<()> {
213 if self.content != ManifestContentType::Data {
218 if let Some(manifest_first_row_id) = self.first_row_id {
219 tracing::warn!(
220 "Ignoring first_row_id {manifest_first_row_id} on delete manifest {}",
221 self.manifest_path
222 );
223 }
224
225 return Ok(());
226 }
227
228 let Some(manifest_first_row_id) = self.first_row_id else {
229 for entry in entries {
232 entry.data_file.first_row_id = None;
233 }
234
235 return Ok(());
236 };
237
238 let mut next_row_id = i64::try_from(manifest_first_row_id).map_err(|_| {
239 Error::new(
240 ErrorKind::DataInvalid,
241 format!("Invalid first_row_id: {manifest_first_row_id} (exceeds i64::MAX)"),
242 )
243 })?;
244
245 for entry in entries {
246 if !entry.is_alive() {
247 continue;
248 }
249
250 if entry.data_file.first_row_id.is_none() {
251 let file_first_row_id = next_row_id;
252 entry.data_file.first_row_id = Some(file_first_row_id);
253 let record_count = entry.data_file.record_count;
254 next_row_id = file_first_row_id.checked_add_unsigned(record_count).ok_or_else(|| {
255 Error::new(
256 ErrorKind::DataInvalid,
257 format!(
258 "Row ID overflow assigning first_row_id in {}. File first_row_id: {file_first_row_id}, record count: {record_count}",
259 self.manifest_path
260 ),
261 )
262 })?;
263 }
264 }
265
266 Ok(())
267 }
268}
269
270#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default, Hash)]
274pub struct FieldSummary {
275 pub contains_null: bool,
280 pub contains_nan: Option<bool>,
284 pub lower_bound: Option<ByteBuf>,
288 pub upper_bound: Option<ByteBuf>,
292}
293
294#[cfg(test)]
295mod test {
296 use std::sync::Arc;
297
298 use super::{ManifestContentType, ManifestFile};
299 use crate::ErrorKind;
300 use crate::encryption::{EncryptedOutputFile, StandardKeyMetadata};
301 use crate::io::FileIO;
302 use crate::spec::{
303 DataContentType, DataFileBuilder, DataFileFormat, ManifestEntry, ManifestStatus,
304 ManifestWriterBuilder, NestedField, PartitionSpec, PrimitiveType, Schema, SchemaRef, Type,
305 };
306
307 #[test]
308 fn test_manifest_content_type_default() {
309 assert_eq!(ManifestContentType::default(), ManifestContentType::Data);
310 }
311
312 #[test]
313 fn test_manifest_content_type_default_value() {
314 assert_eq!(ManifestContentType::default() as i32, 0);
315 }
316
317 fn test_schema() -> SchemaRef {
319 Arc::new(
320 Schema::builder()
321 .with_fields(vec![Arc::new(NestedField::optional(
322 1,
323 "id",
324 Type::Primitive(PrimitiveType::Long),
325 ))])
326 .build()
327 .unwrap(),
328 )
329 }
330
331 async fn write_manifest(io: &FileIO, path: &str) -> ManifestFile {
334 let schema = test_schema();
335 let partition_spec = PartitionSpec::builder(schema.clone())
336 .with_spec_id(0)
337 .build()
338 .unwrap();
339
340 let output_file = io.new_output(path).unwrap();
341 let mut writer = ManifestWriterBuilder::new(output_file, Some(1), schema, partition_spec)
342 .build_v3_data();
343
344 writer
345 .add_entry(data_entry(ManifestStatus::Added, 100, None))
346 .unwrap();
347
348 writer.write_manifest_file().await.unwrap()
349 }
350
351 async fn write_encrypted_manifest(
354 io: &FileIO,
355 path: &str,
356 key_metadata: StandardKeyMetadata,
357 ) -> ManifestFile {
358 let schema = test_schema();
359 let partition_spec = PartitionSpec::builder(schema.clone())
360 .with_spec_id(0)
361 .build()
362 .unwrap();
363
364 let output_file = io.new_output(path).unwrap();
365 let encrypted_output = EncryptedOutputFile::new(output_file, key_metadata);
366
367 let mut writer = ManifestWriterBuilder::new_from_encrypted(
368 encrypted_output,
369 Some(1),
370 schema,
371 partition_spec,
372 )
373 .expect("Expected a valid writer")
374 .build_v3_data();
375
376 writer
377 .add_entry(data_entry(ManifestStatus::Added, 100, None))
378 .unwrap();
379
380 writer.write_manifest_file().await.unwrap()
381 }
382
383 #[tokio::test]
384 async fn test_load_manifest_decrypts_when_key_metadata_present() {
385 let key_metadata = StandardKeyMetadata::try_new(b"0123456789abcdef")
386 .unwrap()
387 .with_aad_prefix(b"test-aad-prefix!");
388 let encoded_key_metadata = key_metadata.encode().unwrap().to_vec();
389
390 let io = FileIO::new_with_memory();
391 let path = "memory:///test/encrypted_manifest.avro";
392 let manifest_file = write_encrypted_manifest(&io, path, key_metadata).await;
393 assert_eq!(manifest_file.key_metadata, Some(encoded_key_metadata));
394
395 let manifest = manifest_file.load_manifest(&io).await.unwrap();
396 assert_eq!(manifest.entries().len(), 1);
397 assert_eq!(
398 manifest.entries()[0].file_path(),
399 "s3://bucket/table/data/00000.parquet"
400 );
401 assert_eq!(manifest.entries()[0].data_file.record_count, 100);
402 }
403
404 #[tokio::test]
405 async fn test_load_manifest_fails_with_wrong_key() {
406 let key_metadata = StandardKeyMetadata::try_new(b"0123456789abcdef")
407 .unwrap()
408 .with_aad_prefix(b"test-aad-prefix!");
409
410 let io = FileIO::new_with_memory();
411 let path = "memory:///test/wrong_key_manifest.avro";
412 let mut manifest_file = write_encrypted_manifest(&io, path, key_metadata).await;
413
414 let wrong_key_metadata = StandardKeyMetadata::try_new(b"fedcba9876543210")
419 .unwrap()
420 .with_aad_prefix(b"test-aad-prefix!");
421 manifest_file.key_metadata = Some(wrong_key_metadata.encode().unwrap().to_vec());
422
423 let err = manifest_file
424 .load_manifest(&io)
425 .await
426 .expect_err("load_manifest must fail when decrypting with the wrong key");
427 assert_eq!(err.kind(), ErrorKind::Unexpected);
428 }
429
430 #[tokio::test]
431 async fn test_load_manifest_fails_with_wrong_aad() {
432 let key_metadata = StandardKeyMetadata::try_new(b"0123456789abcdef")
433 .unwrap()
434 .with_aad_prefix(b"test-aad-prefix!");
435
436 let io = FileIO::new_with_memory();
437 let path = "memory:///test/wrong_aad_manifest.avro";
438 let mut manifest_file = write_encrypted_manifest(&io, path, key_metadata).await;
439
440 let wrong_aad_metadata = StandardKeyMetadata::try_new(b"0123456789abcdef")
444 .unwrap()
445 .with_aad_prefix(b"wrong-aad-prefix");
446 manifest_file.key_metadata = Some(wrong_aad_metadata.encode().unwrap().to_vec());
447
448 let err = manifest_file
449 .load_manifest(&io)
450 .await
451 .expect_err("load_manifest must fail when decrypting with the wrong AAD prefix");
452 assert_eq!(err.kind(), ErrorKind::Unexpected);
453 }
454
455 fn data_entry(
458 status: ManifestStatus,
459 record_count: u64,
460 first_row_id: Option<i64>,
461 ) -> ManifestEntry {
462 let data_file = DataFileBuilder::default()
463 .content(DataContentType::Data)
464 .file_path("s3://bucket/table/data/00000.parquet".to_string())
465 .file_format(DataFileFormat::Parquet)
466 .file_size_in_bytes(4096)
467 .record_count(record_count)
468 .first_row_id(first_row_id)
469 .build()
470 .unwrap();
471
472 ManifestEntry::builder()
473 .status(status)
474 .data_file(data_file)
475 .build()
476 }
477
478 fn manifest_file(content: ManifestContentType, first_row_id: Option<u64>) -> ManifestFile {
481 ManifestFile {
482 manifest_path: "memory:///m.avro".to_string(),
483 manifest_length: 0,
484 partition_spec_id: 0,
485 content,
486 sequence_number: 0,
487 min_sequence_number: 0,
488 added_snapshot_id: 0,
489 added_files_count: None,
490 existing_files_count: None,
491 deleted_files_count: None,
492 added_rows_count: None,
493 existing_rows_count: None,
494 deleted_rows_count: None,
495 partitions: None,
496 key_metadata: None,
497 first_row_id,
498 }
499 }
500
501 #[test]
502 fn test_assign_first_row_ids_interleaved() {
503 let manifest = manifest_file(ManifestContentType::Data, Some(10));
504 let mut entries = vec![
505 data_entry(ManifestStatus::Added, 3, None),
506 data_entry(ManifestStatus::Added, 5, Some(100)),
509 data_entry(ManifestStatus::Deleted, 7, Some(999)),
512 data_entry(ManifestStatus::Existing, 2, None),
513 ];
514
515 manifest.assign_first_row_ids(&mut entries).unwrap();
516
517 assert_eq!(entries[0].data_file.first_row_id, Some(10));
518 assert_eq!(entries[1].data_file.first_row_id, Some(100));
519 assert_eq!(entries[2].data_file.first_row_id, Some(999));
520 assert_eq!(entries[3].data_file.first_row_id, Some(13));
522 }
523
524 #[test]
525 fn test_assign_first_row_ids_clears_without_manifest_first_row_id() {
526 let manifest = manifest_file(ManifestContentType::Data, None);
530 let mut entries = vec![
531 data_entry(ManifestStatus::Added, 3, None),
532 data_entry(ManifestStatus::Existing, 5, Some(100)),
533 ];
534
535 manifest.assign_first_row_ids(&mut entries).unwrap();
536
537 assert_eq!(entries[0].data_file.first_row_id, None);
538 assert_eq!(entries[1].data_file.first_row_id, None);
539 }
540
541 #[test]
542 fn test_assign_first_row_ids_ignores_delete_manifest() {
543 let manifest = manifest_file(ManifestContentType::Deletes, Some(10));
547 let mut entries = vec![data_entry(ManifestStatus::Added, 3, None)];
548
549 manifest.assign_first_row_ids(&mut entries).unwrap();
550
551 assert_eq!(entries[0].data_file.first_row_id, None);
552 }
553
554 #[test]
555 fn test_assign_first_row_ids_rejects_oversized_manifest_first_row_id() {
556 let manifest = manifest_file(ManifestContentType::Data, Some(i64::MAX as u64 + 1));
559 let mut entries = vec![data_entry(ManifestStatus::Added, 3, None)];
560
561 let err = manifest
562 .assign_first_row_ids(&mut entries)
563 .expect_err("an oversized manifest first_row_id must be rejected");
564 assert_eq!(err.kind(), ErrorKind::DataInvalid);
565 assert!(err.message().contains("Invalid first_row_id"));
566 }
567
568 #[test]
569 fn test_assign_first_row_ids_rejects_counter_overflow() {
570 let manifest = manifest_file(ManifestContentType::Data, Some(i64::MAX as u64));
573 let mut entries = vec![data_entry(ManifestStatus::Added, 1, None)];
574
575 let err = manifest
576 .assign_first_row_ids(&mut entries)
577 .expect_err("counter overflow past i64::MAX must be rejected");
578 assert_eq!(err.kind(), ErrorKind::DataInvalid);
579 assert!(err.message().contains("Row ID overflow"));
580 }
581
582 #[tokio::test]
583 async fn test_load_manifest_reads_written_entries() {
584 let io = FileIO::new_with_memory();
585 let path = "memory:///test/plaintext_manifest.avro";
586 let manifest_file = write_manifest(&io, path).await;
587 assert_eq!(manifest_file.key_metadata, None);
588
589 let manifest = manifest_file.load_manifest(&io).await.unwrap();
590 assert_eq!(manifest.entries().len(), 1);
591 assert_eq!(
592 manifest.entries()[0].file_path(),
593 "s3://bucket/table/data/00000.parquet"
594 );
595 assert_eq!(manifest.entries()[0].data_file.record_count, 100);
596 }
597
598 #[tokio::test]
603 async fn test_load_manifest_assigns_first_row_ids() {
604 let io = FileIO::new_with_memory();
605 let path = "memory:///test/first_row_id_manifest.avro";
606 let mut manifest_file = write_manifest(&io, path).await;
607
608 manifest_file.first_row_id = Some(1000);
610
611 let manifest = manifest_file.load_manifest(&io).await.unwrap();
612 assert_eq!(manifest.entries().len(), 1);
613 assert_eq!(manifest.entries()[0].data_file().first_row_id(), Some(1000));
614 }
615}