1use super::Manifest;
19use crate::encryption::{EncryptedInputFile, StandardKeyMetadata};
20use crate::error::Result;
21use crate::io::FileIO;
22use crate::spec::{ManifestContentType, ManifestEntry, ManifestFile};
23use crate::{Error, ErrorKind};
24
25pub struct ManifestReader {
28 file_io: FileIO,
29}
30
31impl ManifestReader {
32 pub(crate) fn new(file_io: FileIO) -> Self {
34 Self { file_io }
35 }
36
37 pub async fn read(self, manifest_file: &ManifestFile) -> Result<Manifest> {
40 let input_file = self.file_io.new_input(&manifest_file.manifest_path)?;
41 let key_metadata = manifest_file
42 .key_metadata
43 .as_deref()
44 .map(StandardKeyMetadata::decode)
45 .transpose()?;
46 let bytes = match key_metadata {
47 Some(key_metadata) => {
48 EncryptedInputFile::new(input_file, key_metadata)
49 .read()
50 .await?
51 }
52 None => input_file.read().await?,
53 };
54
55 let (metadata, mut entries) = Manifest::try_from_avro_bytes(&bytes)?;
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 Error::new(
107 ErrorKind::DataInvalid,
108 format!("Invalid first_row_id: {manifest_first_row_id} (exceeds i64::MAX)"),
109 )
110 })?;
111
112 for entry in entries {
113 if !entry.is_alive() {
114 continue;
115 }
116
117 if entry.data_file.first_row_id.is_none() {
118 let file_first_row_id = next_row_id;
119 entry.data_file.first_row_id = Some(file_first_row_id);
120 let record_count = entry.data_file.record_count;
121 next_row_id = file_first_row_id.checked_add_unsigned(record_count).ok_or_else(|| {
122 Error::new(
123 ErrorKind::DataInvalid,
124 format!(
125 "Row ID overflow assigning first_row_id in {}. File first_row_id: {file_first_row_id}, record count: {record_count}",
126 manifest_file.manifest_path
127 ),
128 )
129 })?;
130 }
131 }
132
133 Ok(())
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use std::collections::HashMap;
140 use std::sync::Arc;
141
142 use super::*;
143 use crate::encryption::{EncryptedOutputFile, StandardKeyMetadata};
144 use crate::io::FileIO;
145 use crate::spec::{
146 DataContentType, DataFile, DataFileBuilder, DataFileFormat, ManifestEntry, ManifestStatus,
147 ManifestWriterBuilder, NestedField, PartitionSpec, PrimitiveType, Schema, Struct, Type,
148 };
149
150 #[tokio::test]
151 async fn test_read_plaintext_manifest_inherits_entries() {
152 let schema = test_schema();
153 let partition_spec = PartitionSpec::builder(schema.clone())
154 .with_spec_id(0)
155 .build()
156 .unwrap();
157
158 let io = FileIO::new_with_memory();
159 let path = "memory:///table/metadata/plain.avro";
160 let mut writer = ManifestWriterBuilder::new(
161 io.new_output(path).unwrap(),
162 Some(1),
163 schema.clone(),
164 partition_spec,
165 )
166 .build_v2_data();
167 writer.add_entry(test_entry()).unwrap();
168 let manifest_file = writer.write_manifest_file().await.unwrap();
170
171 let manifest = ManifestReader::new(io).read(&manifest_file).await.unwrap();
172 assert_eq!(manifest.entries().len(), 1);
173 assert_eq!(
174 manifest.entries()[0].data_file().file_path(),
175 "memory:///table/data/00000.parquet"
176 );
177 assert_eq!(
179 manifest.entries()[0].sequence_number(),
180 Some(manifest_file.sequence_number)
181 );
182 assert_eq!(
183 manifest.entries()[0].snapshot_id(),
184 Some(manifest_file.added_snapshot_id)
185 );
186 }
187
188 #[tokio::test]
189 async fn test_read_encrypted_manifest_roundtrip() {
190 let schema = test_schema();
191 let partition_spec = PartitionSpec::builder(schema.clone())
192 .with_spec_id(0)
193 .build()
194 .unwrap();
195
196 let io = FileIO::new_with_memory();
197 let path = "memory:///table/metadata/encrypted.avro";
198 let encrypted_output =
199 EncryptedOutputFile::new(io.new_output(path).unwrap(), key_metadata());
200
201 let mut writer = ManifestWriterBuilder::new_from_encrypted(
202 encrypted_output,
203 Some(1),
204 schema.clone(),
205 partition_spec,
206 )
207 .unwrap()
208 .build_v3_data();
209 writer.add_entry(test_entry()).unwrap();
210 let manifest_file = writer.write_manifest_file().await.unwrap();
212 assert!(manifest_file.key_metadata.is_some());
213
214 let manifest = ManifestReader::new(io.clone())
216 .read(&manifest_file)
217 .await
218 .unwrap();
219 assert_eq!(manifest.entries().len(), 1);
220 assert_eq!(
221 manifest.entries()[0].data_file().file_path(),
222 "memory:///table/data/00000.parquet"
223 );
224
225 let mut plaintext_entry = manifest_file.clone();
227 plaintext_entry.key_metadata = None;
228 assert!(
229 ManifestReader::new(io)
230 .read(&plaintext_entry)
231 .await
232 .is_err(),
233 "encrypted manifest must not parse as plaintext"
234 );
235 }
236
237 fn test_schema() -> Arc<Schema> {
238 Arc::new(
239 Schema::builder()
240 .with_fields(vec![Arc::new(NestedField::optional(
241 1,
242 "id",
243 Type::Primitive(PrimitiveType::Long),
244 ))])
245 .build()
246 .unwrap(),
247 )
248 }
249
250 fn test_entry() -> ManifestEntry {
251 ManifestEntry {
252 status: ManifestStatus::Added,
253 snapshot_id: None,
254 sequence_number: None,
255 file_sequence_number: None,
256 data_file: DataFile {
257 content: DataContentType::Data,
258 file_path: "memory:///table/data/00000.parquet".to_string(),
259 file_format: DataFileFormat::Parquet,
260 partition: Struct::empty(),
261 record_count: 1,
262 file_size_in_bytes: 4096,
263 column_sizes: HashMap::new(),
264 value_counts: HashMap::new(),
265 null_value_counts: HashMap::new(),
266 nan_value_counts: HashMap::new(),
267 lower_bounds: HashMap::new(),
268 upper_bounds: HashMap::new(),
269 key_metadata: None,
270 split_offsets: None,
271 equality_ids: None,
272 sort_order_id: None,
273 partition_spec_id: 0,
274 first_row_id: None,
275 referenced_data_file: None,
276 content_offset: None,
277 content_size_in_bytes: None,
278 },
279 }
280 }
281
282 fn key_metadata() -> StandardKeyMetadata {
283 StandardKeyMetadata::try_new(b"0123456789abcdef").unwrap()
284 }
285
286 fn test_reader() -> ManifestReader {
289 ManifestReader::new(FileIO::new_with_memory())
290 }
291
292 fn data_entry(
295 status: ManifestStatus,
296 record_count: u64,
297 first_row_id: Option<i64>,
298 ) -> ManifestEntry {
299 let data_file = DataFileBuilder::default()
300 .content(DataContentType::Data)
301 .file_path("s3://bucket/table/data/00000.parquet".to_string())
302 .file_format(DataFileFormat::Parquet)
303 .file_size_in_bytes(4096)
304 .record_count(record_count)
305 .first_row_id(first_row_id)
306 .build()
307 .unwrap();
308
309 ManifestEntry::builder()
310 .status(status)
311 .data_file(data_file)
312 .build()
313 }
314
315 fn manifest_file(content: ManifestContentType, first_row_id: Option<u64>) -> ManifestFile {
318 ManifestFile {
319 manifest_path: "memory:///m.avro".to_string(),
320 manifest_length: 0,
321 partition_spec_id: 0,
322 content,
323 sequence_number: 0,
324 min_sequence_number: 0,
325 added_snapshot_id: 0,
326 added_files_count: None,
327 existing_files_count: None,
328 deleted_files_count: None,
329 added_rows_count: None,
330 existing_rows_count: None,
331 deleted_rows_count: None,
332 partitions: None,
333 key_metadata: None,
334 first_row_id,
335 }
336 }
337
338 #[test]
339 fn test_assign_first_row_ids_interleaved() {
340 let manifest = manifest_file(ManifestContentType::Data, Some(10));
341 let mut entries = vec![
342 data_entry(ManifestStatus::Added, 3, None),
343 data_entry(ManifestStatus::Added, 5, Some(100)),
346 data_entry(ManifestStatus::Deleted, 7, Some(999)),
349 data_entry(ManifestStatus::Existing, 2, None),
350 ];
351
352 test_reader()
353 .assign_first_row_ids(&manifest, &mut entries)
354 .unwrap();
355
356 assert_eq!(entries[0].data_file.first_row_id, Some(10));
357 assert_eq!(entries[1].data_file.first_row_id, Some(100));
358 assert_eq!(entries[2].data_file.first_row_id, Some(999));
359 assert_eq!(entries[3].data_file.first_row_id, Some(13));
361 }
362
363 #[test]
364 fn test_assign_first_row_ids_clears_without_manifest_first_row_id() {
365 let manifest = manifest_file(ManifestContentType::Data, None);
369 let mut entries = vec![
370 data_entry(ManifestStatus::Added, 3, None),
371 data_entry(ManifestStatus::Existing, 5, Some(100)),
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, None);
379 assert_eq!(entries[1].data_file.first_row_id, None);
380 }
381
382 #[test]
383 fn test_assign_first_row_ids_ignores_delete_manifest() {
384 let manifest = manifest_file(ManifestContentType::Deletes, Some(10));
388 let mut entries = vec![data_entry(ManifestStatus::Added, 3, None)];
389
390 test_reader()
391 .assign_first_row_ids(&manifest, &mut entries)
392 .unwrap();
393
394 assert_eq!(entries[0].data_file.first_row_id, None);
395 }
396
397 #[test]
398 fn test_assign_first_row_ids_rejects_oversized_manifest_first_row_id() {
399 let manifest = manifest_file(ManifestContentType::Data, Some(i64::MAX as u64 + 1));
402 let mut entries = vec![data_entry(ManifestStatus::Added, 3, None)];
403
404 let err = test_reader()
405 .assign_first_row_ids(&manifest, &mut entries)
406 .expect_err("an oversized manifest first_row_id must be rejected");
407 assert_eq!(err.kind(), ErrorKind::DataInvalid);
408 assert!(err.message().contains("Invalid first_row_id"));
409 }
410
411 #[test]
412 fn test_assign_first_row_ids_rejects_counter_overflow() {
413 let manifest = manifest_file(ManifestContentType::Data, Some(i64::MAX as u64));
416 let mut entries = vec![data_entry(ManifestStatus::Added, 1, None)];
417
418 let err = test_reader()
419 .assign_first_row_ids(&manifest, &mut entries)
420 .expect_err("counter overflow past i64::MAX must be rejected");
421 assert_eq!(err.kind(), ErrorKind::DataInvalid);
422 assert!(err.message().contains("Row ID overflow"));
423 }
424}