1use super::Manifest;
19use crate::encryption::{EncryptedInputFile, StandardKeyMetadata};
20use crate::error::{Result, invalid_data};
21use crate::io::FileIO;
22use crate::spec::{ManifestContentType, ManifestEntry, ManifestFile};
23
24pub struct ManifestReader {
27 file_io: FileIO,
28}
29
30impl ManifestReader {
31 pub(crate) fn new(file_io: FileIO) -> Self {
33 Self { file_io }
34 }
35
36 pub async fn read(self, manifest_file: &ManifestFile) -> Result<Manifest> {
39 let input_file = self.file_io.new_input(&manifest_file.manifest_path)?;
40 let key_metadata = manifest_file
41 .key_metadata
42 .as_deref()
43 .map(StandardKeyMetadata::decode)
44 .transpose()?;
45 let bytes = match key_metadata {
46 Some(key_metadata) => {
47 EncryptedInputFile::new(input_file, key_metadata)
48 .read()
49 .await?
50 }
51 None => input_file.read().await?,
52 };
53
54 let (metadata, mut entries) =
55 Manifest::try_from_avro_bytes(&bytes, Some(&manifest_file.manifest_path))?;
56
57 for entry in &mut entries {
58 entry.inherit_data(manifest_file);
59 }
60
61 self.assign_first_row_ids(manifest_file, &mut entries)?;
62
63 Ok(Manifest::new(metadata, entries))
64 }
65
66 fn assign_first_row_ids(
76 &self,
77 manifest_file: &ManifestFile,
78 entries: &mut [ManifestEntry],
79 ) -> Result<()> {
80 if manifest_file.content != ManifestContentType::Data {
85 if let Some(manifest_first_row_id) = manifest_file.first_row_id {
86 tracing::warn!(
87 "Ignoring first_row_id {manifest_first_row_id} on delete manifest {}",
88 manifest_file.manifest_path
89 );
90 }
91
92 return Ok(());
93 }
94
95 let Some(manifest_first_row_id) = manifest_file.first_row_id else {
96 for entry in entries {
99 entry.data_file.first_row_id = None;
100 }
101
102 return Ok(());
103 };
104
105 let mut next_row_id = i64::try_from(manifest_first_row_id).map_err(|_| {
106 invalid_data!("Invalid first_row_id: {manifest_first_row_id} (exceeds i64::MAX)")
107 })?;
108
109 for entry in entries {
110 if !entry.is_alive() {
111 continue;
112 }
113
114 if entry.data_file.first_row_id.is_none() {
115 let file_first_row_id = next_row_id;
116 entry.data_file.first_row_id = Some(file_first_row_id);
117 let record_count = entry.data_file.record_count;
118 next_row_id = file_first_row_id.checked_add_unsigned(record_count).ok_or_else(|| {
119 invalid_data!("Row ID overflow assigning first_row_id in {}. File first_row_id: {file_first_row_id}, record count: {record_count}",
120 manifest_file.manifest_path)
121 })?;
122 }
123 }
124
125 Ok(())
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use std::collections::HashMap;
132 use std::sync::Arc;
133
134 use super::*;
135 use crate::ErrorKind;
136 use crate::encryption::{EncryptedOutputFile, StandardKeyMetadata};
137 use crate::io::FileIO;
138 use crate::spec::{
139 DataContentType, DataFile, DataFileBuilder, DataFileFormat, ManifestEntry, ManifestStatus,
140 ManifestWriterBuilder, NestedField, PartitionSpec, PrimitiveType, Schema, Struct, Type,
141 };
142
143 #[tokio::test]
144 async fn test_read_plaintext_manifest_inherits_entries() {
145 let schema = test_schema();
146 let partition_spec = PartitionSpec::builder(schema.clone())
147 .with_spec_id(0)
148 .build()
149 .unwrap();
150
151 let io = FileIO::new_with_memory();
152 let path = "memory:///table/metadata/plain.avro";
153 let mut writer = ManifestWriterBuilder::new(
154 io.new_output(path).unwrap(),
155 Some(1),
156 schema.clone(),
157 partition_spec,
158 )
159 .build_v2_data();
160 writer.add_entry(test_entry()).unwrap();
161 let manifest_file = writer.write_manifest_file().await.unwrap();
163
164 let manifest = ManifestReader::new(io).read(&manifest_file).await.unwrap();
165 assert_eq!(manifest.entries().len(), 1);
166 assert_eq!(
167 manifest.entries()[0].data_file().file_path(),
168 "memory:///table/data/00000.parquet"
169 );
170 assert_eq!(
172 manifest.entries()[0].sequence_number(),
173 Some(manifest_file.sequence_number)
174 );
175 assert_eq!(
176 manifest.entries()[0].snapshot_id(),
177 Some(manifest_file.added_snapshot_id)
178 );
179 }
180
181 #[tokio::test]
185 async fn test_read_v1_manifest_defaults_sequence_number_to_zero() {
186 let schema = test_schema();
187 let partition_spec = PartitionSpec::builder(schema.clone())
188 .with_spec_id(0)
189 .build()
190 .unwrap();
191
192 let io = FileIO::new_with_memory();
193 let path = "memory:///table/metadata/v1.avro";
194 let mut writer = ManifestWriterBuilder::new(
195 io.new_output(path).unwrap(),
196 Some(1),
197 schema.clone(),
198 partition_spec,
199 )
200 .build_v1();
201 writer.add_entry(test_entry()).unwrap();
202 let mut manifest_file = writer.write_manifest_file().await.unwrap();
203 manifest_file.sequence_number = 36;
204
205 let manifest = ManifestReader::new(io).read(&manifest_file).await.unwrap();
206 assert_eq!(manifest.entries()[0].sequence_number(), Some(0));
207 assert_eq!(manifest.entries()[0].file_sequence_number, Some(0));
208 }
209
210 #[tokio::test]
211 async fn test_read_encrypted_manifest_roundtrip() {
212 let schema = test_schema();
213 let partition_spec = PartitionSpec::builder(schema.clone())
214 .with_spec_id(0)
215 .build()
216 .unwrap();
217
218 let io = FileIO::new_with_memory();
219 let path = "memory:///table/metadata/encrypted.avro";
220 let encrypted_output =
221 EncryptedOutputFile::new(io.new_output(path).unwrap(), key_metadata());
222
223 let mut writer = ManifestWriterBuilder::new_from_encrypted(
224 encrypted_output,
225 Some(1),
226 schema.clone(),
227 partition_spec,
228 )
229 .unwrap()
230 .build_v3_data();
231 writer.add_entry(test_entry()).unwrap();
232 let manifest_file = writer.write_manifest_file().await.unwrap();
234 assert!(manifest_file.key_metadata.is_some());
235
236 let manifest = ManifestReader::new(io.clone())
238 .read(&manifest_file)
239 .await
240 .unwrap();
241 assert_eq!(manifest.entries().len(), 1);
242 assert_eq!(
243 manifest.entries()[0].data_file().file_path(),
244 "memory:///table/data/00000.parquet"
245 );
246
247 let mut plaintext_entry = manifest_file.clone();
249 plaintext_entry.key_metadata = None;
250 assert!(
251 ManifestReader::new(io)
252 .read(&plaintext_entry)
253 .await
254 .is_err(),
255 "encrypted manifest must not parse as plaintext"
256 );
257 }
258
259 fn test_schema() -> Arc<Schema> {
260 Arc::new(
261 Schema::builder()
262 .with_fields(vec![Arc::new(NestedField::optional(
263 1,
264 "id",
265 Type::Primitive(PrimitiveType::Long),
266 ))])
267 .build()
268 .unwrap(),
269 )
270 }
271
272 fn test_entry() -> ManifestEntry {
273 ManifestEntry {
274 status: ManifestStatus::Added,
275 snapshot_id: None,
276 sequence_number: None,
277 file_sequence_number: None,
278 data_file: DataFile {
279 content: DataContentType::Data,
280 file_path: "memory:///table/data/00000.parquet".to_string(),
281 file_format: DataFileFormat::Parquet,
282 partition: Struct::empty(),
283 record_count: 1,
284 file_size_in_bytes: 4096,
285 column_sizes: HashMap::new(),
286 value_counts: HashMap::new(),
287 null_value_counts: HashMap::new(),
288 nan_value_counts: HashMap::new(),
289 lower_bounds: HashMap::new(),
290 upper_bounds: HashMap::new(),
291 key_metadata: None,
292 split_offsets: None,
293 equality_ids: None,
294 sort_order_id: None,
295 partition_spec_id: 0,
296 first_row_id: None,
297 referenced_data_file: None,
298 content_offset: None,
299 content_size_in_bytes: None,
300 },
301 }
302 }
303
304 fn key_metadata() -> StandardKeyMetadata {
305 StandardKeyMetadata::try_new(b"0123456789abcdef").unwrap()
306 }
307
308 fn test_reader() -> ManifestReader {
311 ManifestReader::new(FileIO::new_with_memory())
312 }
313
314 fn data_entry(
317 status: ManifestStatus,
318 record_count: u64,
319 first_row_id: Option<i64>,
320 ) -> ManifestEntry {
321 let data_file = DataFileBuilder::default()
322 .content(DataContentType::Data)
323 .file_path("s3://bucket/table/data/00000.parquet".to_string())
324 .file_format(DataFileFormat::Parquet)
325 .file_size_in_bytes(4096)
326 .record_count(record_count)
327 .first_row_id(first_row_id)
328 .build()
329 .unwrap();
330
331 ManifestEntry::builder()
332 .status(status)
333 .data_file(data_file)
334 .build()
335 }
336
337 fn manifest_file(content: ManifestContentType, first_row_id: Option<u64>) -> ManifestFile {
340 ManifestFile {
341 manifest_path: "memory:///m.avro".to_string(),
342 manifest_length: 0,
343 partition_spec_id: 0,
344 content,
345 sequence_number: 0,
346 min_sequence_number: 0,
347 added_snapshot_id: 0,
348 added_files_count: None,
349 existing_files_count: None,
350 deleted_files_count: None,
351 added_rows_count: None,
352 existing_rows_count: None,
353 deleted_rows_count: None,
354 partitions: None,
355 key_metadata: None,
356 first_row_id,
357 }
358 }
359
360 #[test]
361 fn test_assign_first_row_ids_interleaved() {
362 let manifest = manifest_file(ManifestContentType::Data, Some(10));
363 let mut entries = vec![
364 data_entry(ManifestStatus::Added, 3, None),
365 data_entry(ManifestStatus::Added, 5, Some(100)),
368 data_entry(ManifestStatus::Deleted, 7, Some(999)),
371 data_entry(ManifestStatus::Existing, 2, None),
372 ];
373
374 test_reader()
375 .assign_first_row_ids(&manifest, &mut entries)
376 .unwrap();
377
378 assert_eq!(entries[0].data_file.first_row_id, Some(10));
379 assert_eq!(entries[1].data_file.first_row_id, Some(100));
380 assert_eq!(entries[2].data_file.first_row_id, Some(999));
381 assert_eq!(entries[3].data_file.first_row_id, Some(13));
383 }
384
385 #[test]
386 fn test_assign_first_row_ids_clears_without_manifest_first_row_id() {
387 let manifest = manifest_file(ManifestContentType::Data, None);
391 let mut entries = vec![
392 data_entry(ManifestStatus::Added, 3, None),
393 data_entry(ManifestStatus::Existing, 5, Some(100)),
394 ];
395
396 test_reader()
397 .assign_first_row_ids(&manifest, &mut entries)
398 .unwrap();
399
400 assert_eq!(entries[0].data_file.first_row_id, None);
401 assert_eq!(entries[1].data_file.first_row_id, None);
402 }
403
404 #[test]
405 fn test_assign_first_row_ids_ignores_delete_manifest() {
406 let manifest = manifest_file(ManifestContentType::Deletes, Some(10));
410 let mut entries = vec![data_entry(ManifestStatus::Added, 3, None)];
411
412 test_reader()
413 .assign_first_row_ids(&manifest, &mut entries)
414 .unwrap();
415
416 assert_eq!(entries[0].data_file.first_row_id, None);
417 }
418
419 #[test]
420 fn test_assign_first_row_ids_rejects_oversized_manifest_first_row_id() {
421 let manifest = manifest_file(ManifestContentType::Data, Some(i64::MAX as u64 + 1));
424 let mut entries = vec![data_entry(ManifestStatus::Added, 3, None)];
425
426 let err = test_reader()
427 .assign_first_row_ids(&manifest, &mut entries)
428 .expect_err("an oversized manifest first_row_id must be rejected");
429 assert_eq!(err.kind(), ErrorKind::DataInvalid);
430 assert!(err.message().contains("Invalid first_row_id"));
431 }
432
433 #[test]
434 fn test_assign_first_row_ids_rejects_counter_overflow() {
435 let manifest = manifest_file(ManifestContentType::Data, Some(i64::MAX as u64));
438 let mut entries = vec![data_entry(ManifestStatus::Added, 1, None)];
439
440 let err = test_reader()
441 .assign_first_row_ids(&manifest, &mut entries)
442 .expect_err("counter overflow past i64::MAX must be rejected");
443 assert_eq!(err.kind(), ErrorKind::DataInvalid);
444 assert!(err.message().contains("Row ID overflow"));
445 }
446}