This is an automated email from the ASF dual-hosted git repository.

fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 9177557  feat: add manifest related structures (#91)
9177557 is described below

commit 91775578cddf19c26f648e913fbbc3dcc1ae0610
Author: Junwang Zhao <[email protected]>
AuthorDate: Wed May 21 03:00:52 2025 +0800

    feat: add manifest related structures (#91)
    
    Add DataFile, ManifestEntry, ManifestFile, and ManifestList to Iceberg
    core.
    Support for parsing these data structures from Avro file will be added
    in future PRs.
    
    ---------
    
    Signed-off-by: Junwang Zhao <[email protected]>
    Co-authored-by: Fokko Driesprong <[email protected]>
---
 src/iceberg/CMakeLists.txt                      |   2 +
 src/iceberg/file_format.h                       |  11 +
 src/iceberg/manifest_entry.cc                   |  66 +++++
 src/iceberg/manifest_entry.h                    | 325 ++++++++++++++++++++++++
 src/iceberg/{file_format.h => manifest_list.cc} |  44 ++--
 src/iceberg/manifest_list.h                     | 235 +++++++++++++++++
 src/iceberg/manifest_reader.h                   |   6 +-
 src/iceberg/type_fwd.h                          |   5 +
 8 files changed, 667 insertions(+), 27 deletions(-)

diff --git a/src/iceberg/CMakeLists.txt b/src/iceberg/CMakeLists.txt
index 0dfb577..0cb6bec 100644
--- a/src/iceberg/CMakeLists.txt
+++ b/src/iceberg/CMakeLists.txt
@@ -23,6 +23,8 @@ set(ICEBERG_SOURCES
     expression/expression.cc
     file_reader.cc
     json_internal.cc
+    manifest_entry.cc
+    manifest_list.cc
     metadata_columns.cc
     name_mapping.cc
     partition_field.cc
diff --git a/src/iceberg/file_format.h b/src/iceberg/file_format.h
index 883782c..eebb76d 100644
--- a/src/iceberg/file_format.h
+++ b/src/iceberg/file_format.h
@@ -25,6 +25,7 @@
 #include <string_view>
 
 #include "iceberg/iceberg_export.h"
+#include "iceberg/result.h"
 
 namespace iceberg {
 
@@ -50,4 +51,14 @@ ICEBERG_EXPORT inline std::string_view 
ToString(FileFormatType format_type) {
   }
 }
 
+/// \brief Convert a string to a FileFormatType
+ICEBERG_EXPORT constexpr Result<FileFormatType> FileFormatTypeFromString(
+    std::string_view str) noexcept {
+  if (str == "parquet") return FileFormatType::kParquet;
+  if (str == "avro") return FileFormatType::kAvro;
+  if (str == "orc") return FileFormatType::kOrc;
+  if (str == "puffin") return FileFormatType::kPuffin;
+  return InvalidArgument("Invalid file format type: {}", str);
+}
+
 }  // namespace iceberg
diff --git a/src/iceberg/manifest_entry.cc b/src/iceberg/manifest_entry.cc
new file mode 100644
index 0000000..16df2f0
--- /dev/null
+++ b/src/iceberg/manifest_entry.cc
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "iceberg/manifest_entry.h"
+
+#include <memory>
+#include <vector>
+
+#include "iceberg/schema_field.h"
+#include "iceberg/type.h"
+
+namespace iceberg {
+
+std::shared_ptr<StructType> DataFile::Type(std::shared_ptr<StructType> 
partition_type) {
+  return std::make_shared<StructType>(std::vector<SchemaField>{
+      kContent,
+      kFilePath,
+      kFileFormat,
+      SchemaField::MakeRequired(102, "partition", std::move(partition_type)),
+      kRecordCount,
+      kFileSize,
+      kColumnSizes,
+      kValueCounts,
+      kNullValueCounts,
+      kNanValueCounts,
+      kLowerBounds,
+      kUpperBounds,
+      kKeyMetadata,
+      kSplitOffsets,
+      kEqualityIds,
+      kSortOrderId,
+      kFirstRowId,
+      kReferencedDataFile,
+      kContentOffset,
+      kContentSize});
+}
+
+std::shared_ptr<StructType> ManifestEntry::TypeFromPartitionType(
+    std::shared_ptr<StructType> partition_type) {
+  return TypeFromDataFileType(DataFile::Type(std::move(partition_type)));
+}
+
+std::shared_ptr<StructType> ManifestEntry::TypeFromDataFileType(
+    std::shared_ptr<StructType> datafile_type) {
+  return std::make_shared<StructType>(std::vector<SchemaField>{
+      kStatus, kSnapshotId, kSequenceNumber, kFileSequenceNumber,
+      SchemaField::MakeRequired(2, "data_file", std::move(datafile_type))});
+}
+
+}  // namespace iceberg
diff --git a/src/iceberg/manifest_entry.h b/src/iceberg/manifest_entry.h
new file mode 100644
index 0000000..c039352
--- /dev/null
+++ b/src/iceberg/manifest_entry.h
@@ -0,0 +1,325 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#pragma once
+
+#include <any>
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "iceberg/file_format.h"
+#include "iceberg/iceberg_export.h"
+#include "iceberg/result.h"
+#include "iceberg/schema_field.h"
+#include "iceberg/type.h"
+
+namespace iceberg {
+
+enum class ManifestStatus {
+  kExisting = 0,
+  kAdded = 1,
+  kDeleted = 2,
+};
+
+/// \brief Get the relative manifest status type from int
+ICEBERG_EXPORT constexpr Result<ManifestStatus> ManifestStatusFromInt(
+    int status) noexcept {
+  switch (status) {
+    case 0:
+      return ManifestStatus::kExisting;
+    case 1:
+      return ManifestStatus::kAdded;
+    case 2:
+      return ManifestStatus::kDeleted;
+    default:
+      return InvalidArgument("Invalid manifest status: {}", status);
+  }
+}
+
+enum class DataFileContent {
+  kData = 0,
+  kPositionDeletes = 1,
+  kEqualityDeletes = 2,
+};
+
+/// \brief Get the relative data file content type from int
+ICEBERG_EXPORT constexpr Result<DataFileContent> DataFileContentFromInt(
+    int content) noexcept {
+  switch (content) {
+    case 0:
+      return DataFileContent::kData;
+    case 1:
+      return DataFileContent::kPositionDeletes;
+    case 2:
+      return DataFileContent::kEqualityDeletes;
+    default:
+      return InvalidArgument("Invalid data file content: {}", content);
+  }
+}
+
+/// \brief DataFile carries data file path, partition tuple, metrics, ...
+struct ICEBERG_EXPORT DataFile {
+  /// Field id: 134
+  /// Type of content stored by the data file: data, equality deletes, or 
position
+  /// deletes (all v1 files are data files)
+  DataFileContent content;
+  /// Field id: 100
+  /// Full URI for the file with FS scheme
+  std::string file_path;
+  /// Field id: 101
+  /// File format type, avro, orc, parquet, or puffin
+  FileFormatType file_format;
+  /// Field id: 102
+  /// Partition data tuple, schema based on the partition spec output using 
partition
+  /// field ids for the struct field ids
+  /// TODO(zhjwpku): use StructLike to represent partition data tuple
+  std::any partition;
+  /// Field id: 103
+  /// Number of records in this file, or the cardinality of a deletion vector
+  int64_t record_count = 0;
+  /// Field id: 104
+  /// Total file size in bytes
+  int64_t file_size_in_bytes = 0;
+  /// Field id: 108
+  /// Key field id: 117
+  /// Value field id: 118
+  /// Map from column id to the total size on disk of all regions that store 
the column.
+  /// Does not include bytes necessary to read other columns, like footers. 
Leave null for
+  /// row-oriented formats (Avro)
+  std::map<int32_t, int64_t> column_sizes;
+  /// Field id: 109
+  /// Key field id: 119
+  /// Value field id: 120
+  /// Map from column id to number of values in the column (including null and 
NaN values)
+  std::map<int32_t, int64_t> value_counts;
+  /// Field id: 110
+  /// Key field id: 121
+  /// Value field id: 122
+  /// Map from column id to number of null values in the column
+  std::map<int32_t, int64_t> null_value_counts;
+  /// Field id: 137
+  /// Key field id: 138
+  /// Value field id: 139
+  /// Map from column id to number of NaN values in the column
+  std::map<int32_t, int64_t> nan_value_counts;
+  /// Field id: 125
+  /// Key field id: 126
+  /// Value field id: 127
+  /// Map from column id to lower bound in the column serialized as binary.
+  /// Each value must be less than or equal to all non-null, non-NaN values in 
the column
+  /// for the file.
+  std::map<int32_t, std::vector<uint8_t>> lower_bounds;
+  /// Field id: 128
+  /// Key field id: 129
+  /// Value field id: 130
+  /// Map from column id to upper bound in the column serialized as binary.
+  /// Each value must be greater than or equal to all non-null, non-NaN values 
in the
+  /// column for the file.
+  std::map<int32_t, std::vector<uint8_t>> upper_bounds;
+  /// Field id: 131
+  /// Implementation-specific key metadata for encryption
+  std::vector<uint8_t> key_metadata;
+  /// Field id: 132
+  /// Element Field id: 133
+  /// Split offsets for the data file. For example, all row group offsets in a 
Parquet
+  /// file. Must be sorted ascending.
+  std::vector<int64_t> split_offsets;
+  /// Field id: 135
+  /// Element Field id: 136
+  /// Field ids used to determine row equality in equality delete files. 
Required when
+  /// content=2 and should be null otherwise. Fields with ids listed in this 
column must
+  /// be present in the delete file.
+  std::vector<int32_t> equality_ids;
+  /// Field id: 140
+  /// ID representing sort order for this file
+  ///
+  /// If sort order ID is missing or unknown, then the order is assumed to be 
unsorted.
+  /// Only data files and equality delete files should be written with a 
non-null order
+  /// id. Position deletes are required to be sorted by file and position, not 
a table
+  /// order, and should set sort order id to null. Readers must ignore sort 
order id for
+  /// position delete files.
+  std::optional<int32_t> sort_order_id;
+  /// This field is not included in spec, so it is not serialized into the 
manifest file.
+  /// It is just store in memory representation used in process.
+  int32_t partition_spec_id;
+  /// Field id: 142
+  /// The _row_id for the first row in the data file.
+  ///
+  /// Reference:
+  /// - [First Row ID
+  /// 
Inheritance](https://github.com/apache/iceberg/blob/main/format/spec.md#first-row-id-inheritance)
+  std::optional<int64_t> first_row_id;
+  /// Field id: 143
+  /// Fully qualified location (URI with FS scheme) of a data file that all 
deletes
+  /// reference.
+  ///
+  /// Position delete metadata can use referenced_data_file when all deletes 
tracked by
+  /// the entry are in a single data file. Setting the referenced file is 
required for
+  /// deletion vectors.
+  std::optional<std::string> referenced_data_file;
+  /// Field id: 144
+  /// The offset in the file where the content starts.
+  ///
+  /// The content_offset and content_size_in_bytes fields are used to 
reference a specific
+  /// blob for direct access to a deletion vector. For deletion vectors, these 
values are
+  /// required and must exactly match the offset and length stored in the 
Puffin footer
+  /// for the deletion vector blob.
+  std::optional<int64_t> content_offset;
+  /// Field id: 145
+  /// The length of a referenced content stored in the file; required if 
content_offset is
+  /// present
+  std::optional<int64_t> content_size_in_bytes;
+
+  inline static const SchemaField kContent = SchemaField::MakeRequired(
+      134, "content", std::make_shared<IntType>(),
+      "Contents of the file: 0=data, 1=position deletes, 2=equality deletes");
+  inline static const SchemaField kFilePath = SchemaField::MakeRequired(
+      100, "file_path", std::make_shared<StringType>(), "Location URI with FS 
scheme");
+  inline static const SchemaField kFileFormat =
+      SchemaField::MakeRequired(101, "file_format", 
std::make_shared<IntType>(),
+                                "File format name: avro, orc, or parquet");
+  inline static const SchemaField kRecordCount = SchemaField::MakeRequired(
+      103, "record_count", std::make_shared<LongType>(), "Number of records in 
the file");
+  inline static const SchemaField kFileSize =
+      SchemaField::MakeRequired(104, "file_size_in_bytes", 
std::make_shared<LongType>(),
+                                "Total file size in bytes");
+  inline static const SchemaField kColumnSizes = SchemaField::MakeOptional(
+      108, "column_sizes",
+      std::make_shared<MapType>(
+          SchemaField::MakeRequired(117, std::string(MapType::kKeyName),
+                                    std::make_shared<IntType>()),
+          SchemaField::MakeRequired(118, std::string(MapType::kValueName),
+                                    std::make_shared<LongType>())),
+      "Map of column id to total size on disk");
+  inline static const SchemaField kValueCounts = SchemaField::MakeOptional(
+      109, "value_counts",
+      std::make_shared<MapType>(
+          SchemaField::MakeRequired(119, std::string(MapType::kKeyName),
+                                    std::make_shared<IntType>()),
+          SchemaField::MakeRequired(120, std::string(MapType::kValueName),
+                                    std::make_shared<LongType>())),
+      "Map of column id to total count, including null and NaN");
+  inline static const SchemaField kNullValueCounts = SchemaField::MakeOptional(
+      110, "null_value_counts",
+      std::make_shared<MapType>(
+          SchemaField::MakeRequired(121, std::string(MapType::kKeyName),
+                                    std::make_shared<IntType>()),
+          SchemaField::MakeRequired(122, std::string(MapType::kValueName),
+                                    std::make_shared<LongType>())),
+      "Map of column id to null value count");
+  inline static const SchemaField kNanValueCounts = SchemaField::MakeOptional(
+      137, "nan_value_counts",
+      std::make_shared<MapType>(
+          SchemaField::MakeRequired(138, std::string(MapType::kKeyName),
+                                    std::make_shared<IntType>()),
+          SchemaField::MakeRequired(139, std::string(MapType::kValueName),
+                                    std::make_shared<LongType>())),
+      "Map of column id to number of NaN values in the column");
+  inline static const SchemaField kLowerBounds = SchemaField::MakeOptional(
+      125, "lower_bounds",
+      std::make_shared<MapType>(
+          SchemaField::MakeRequired(126, std::string(MapType::kKeyName),
+                                    std::make_shared<IntType>()),
+          SchemaField::MakeRequired(127, std::string(MapType::kValueName),
+                                    std::make_shared<BinaryType>())),
+      "Map of column id to lower bound");
+  inline static const SchemaField kUpperBounds = SchemaField::MakeOptional(
+      128, "upper_bounds",
+      std::make_shared<MapType>(
+          SchemaField::MakeRequired(129, std::string(MapType::kKeyName),
+                                    std::make_shared<IntType>()),
+          SchemaField::MakeRequired(130, std::string(MapType::kValueName),
+                                    std::make_shared<BinaryType>())),
+      "Map of column id to upper bound");
+  inline static const SchemaField kKeyMetadata =
+      SchemaField::MakeOptional(131, "key_metadata", 
std::make_shared<BinaryType>(),
+                                "Encryption key metadata blob");
+  inline static const SchemaField kSplitOffsets = SchemaField::MakeOptional(
+      132, "split_offsets",
+      std::make_shared<ListType>(SchemaField::MakeRequired(
+          133, std::string(ListType::kElementName), 
std::make_shared<LongType>())),
+      "Splittable offsets");
+  inline static const SchemaField kEqualityIds = SchemaField::MakeOptional(
+      135, "equality_ids",
+      std::make_shared<ListType>(SchemaField::MakeRequired(
+          136, std::string(ListType::kElementName), 
std::make_shared<IntType>())),
+      "Equality comparison field IDs");
+  inline static const SchemaField kSortOrderId = SchemaField::MakeOptional(
+      140, "sort_order_id", std::make_shared<IntType>(), "Sort order ID");
+  inline static const SchemaField kFirstRowId =
+      SchemaField::MakeOptional(142, "first_row_id", 
std::make_shared<LongType>(),
+                                "Starting row ID to assign to new rows");
+  inline static const SchemaField kReferencedDataFile = 
SchemaField::MakeOptional(
+      143, "referenced_data_file", std::make_shared<StringType>(),
+      "Fully qualified location (URI with FS scheme) of a data file that all 
deletes "
+      "reference");
+  inline static const SchemaField kContentOffset =
+      SchemaField::MakeOptional(144, "content_offset", 
std::make_shared<LongType>(),
+                                "The offset in the file where the content 
starts");
+  inline static const SchemaField kContentSize = SchemaField::MakeOptional(
+      145, "content_size_in_bytes", std::make_shared<LongType>(),
+      "The length of referenced content stored in the file");
+
+  static std::shared_ptr<StructType> Type(std::shared_ptr<StructType> 
partition_type);
+};
+
+/// \brief A manifest is an immutable Avro file that lists data files or 
delete files,
+/// along with each file's partition data tuple, metrics, and tracking 
information.
+
+/// \brief The schema of a manifest file
+struct ICEBERG_EXPORT ManifestEntry {
+  /// Field id: 0
+  /// Used to track additions and deletions. Deletes are informational only 
and not used
+  /// in scans.
+  ManifestStatus status;
+  /// Field id: 1
+  /// Snapshot id where the file was added, or deleted if status is 2. 
Inherited when
+  /// null.
+  std::optional<int64_t> snapshot_id;
+  /// Field id: 3
+  /// Data sequence number of the file. Inherited when null and status is 1 
(added).
+  std::optional<int64_t> sequence_number;
+  /// Field id: 4
+  /// File sequence number indicating when the file was added. Inherited when 
null and
+  /// status is 1 (added).
+  std::optional<int64_t> file_sequence_number;
+  /// Field id: 2
+  /// File path, partition tuple, metrics, ...
+  DataFile data_file;
+
+  inline static const SchemaField kStatus =
+      SchemaField::MakeRequired(0, "status", std::make_shared<IntType>());
+  inline static const SchemaField kSnapshotId =
+      SchemaField::MakeOptional(1, "snapshot_id", 
std::make_shared<LongType>());
+  inline static const SchemaField kSequenceNumber =
+      SchemaField::MakeOptional(3, "sequence_number", 
std::make_shared<LongType>());
+  inline static const SchemaField kFileSequenceNumber =
+      SchemaField::MakeOptional(4, "file_sequence_number", 
std::make_shared<LongType>());
+
+  static std::shared_ptr<StructType> TypeFromPartitionType(
+      std::shared_ptr<StructType> partition_type);
+  static std::shared_ptr<StructType> TypeFromDataFileType(
+      std::shared_ptr<StructType> datafile_type);
+};
+
+}  // namespace iceberg
diff --git a/src/iceberg/file_format.h b/src/iceberg/manifest_list.cc
similarity index 52%
copy from src/iceberg/file_format.h
copy to src/iceberg/manifest_list.cc
index 883782c..a0cf205 100644
--- a/src/iceberg/file_format.h
+++ b/src/iceberg/manifest_list.cc
@@ -17,37 +17,31 @@
  * under the License.
  */
 
-#pragma once
+#include "iceberg/manifest_list.h"
 
-/// \file iceberg/file_format.h
-/// File format used by Iceberg.
+#include <vector>
 
-#include <string_view>
-
-#include "iceberg/iceberg_export.h"
+#include "iceberg/type.h"
 
 namespace iceberg {
 
-/// \brief File format type
-enum class ICEBERG_EXPORT FileFormatType {
-  kParquet,
-  kAvro,
-  kOrc,
-  kPuffin,
-};
+const StructType& PartitionFieldSummary::Type() {
+  static const StructType kInstance{{
+      PartitionFieldSummary::kConsTainsNull,
+      PartitionFieldSummary::kContainsNaN,
+      PartitionFieldSummary::kLowerBound,
+      PartitionFieldSummary::kUpperBound,
+  }};
+  return kInstance;
+}
 
-/// \brief Convert a FileFormatType to a string
-ICEBERG_EXPORT inline std::string_view ToString(FileFormatType format_type) {
-  switch (format_type) {
-    case FileFormatType::kParquet:
-      return "parquet";
-    case FileFormatType::kAvro:
-      return "avro";
-    case FileFormatType::kOrc:
-      return "orc";
-    case FileFormatType::kPuffin:
-      return "puffin";
-  }
+const StructType& ManifestFile::Type() {
+  static const StructType kInstance(
+      {kManifestPath, kManifestLength, kPartitionSpecId, kContent, 
kSequenceNumber,
+       kMinSequenceNumber, kAddedSnapshotId, kAddedFilesCount, 
kExistingFilesCount,
+       kDeletedFilesCount, kAddedRowsCount, kExistingRowsCount, 
kDeletedRowsCount,
+       kPartitions, kKeyMetadata, kFirstRowId});
+  return kInstance;
 }
 
 }  // namespace iceberg
diff --git a/src/iceberg/manifest_list.h b/src/iceberg/manifest_list.h
new file mode 100644
index 0000000..af2b8db
--- /dev/null
+++ b/src/iceberg/manifest_list.h
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#pragma once
+
+/// \file iceberg/manifest_list.h
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+
+#include "iceberg/iceberg_export.h"
+#include "iceberg/result.h"
+#include "iceberg/schema_field.h"
+#include "iceberg/type.h"
+
+namespace iceberg {
+
+/// \brief The type of files tracked by the manifest, either data or delete 
files; 0 for
+/// all v1 manifests
+enum class ManifestContent {
+  /// The manifest content is data.
+  kData = 0,
+  /// The manifest content is deletes.
+  kDeletes = 1,
+};
+
+/// \brief Get the relative manifest content type name
+ICEBERG_EXPORT constexpr std::string_view ManifestContentToString(
+    ManifestContent type) noexcept {
+  switch (type) {
+    case ManifestContent::kData:
+      return "data";
+    case ManifestContent::kDeletes:
+      return "deletes";
+  }
+}
+
+/// \brief Get the relative manifest content type from name
+ICEBERG_EXPORT constexpr Result<ManifestContent> ManifestContentFromString(
+    std::string_view str) noexcept {
+  if (str == "data") return ManifestContent::kData;
+  if (str == "deletes") return ManifestContent::kDeletes;
+  return InvalidArgument("Invalid manifest content type: {}", str);
+}
+
+/// \brief Field summary for partition field in the spec.
+///
+/// Each field of this corresponds to a field in the manifest file's partition 
spec.
+struct ICEBERG_EXPORT PartitionFieldSummary {
+  /// Field id: 509
+  /// Whether the manifest contains at least one partition with a null value 
for the field
+  bool contains_null;
+  /// Field id: 518
+  /// Whether the manifest contains at least one partition with a NaN value 
for the field
+  std::optional<bool> contains_nan;
+  /// Field id: 510
+  /// Lower bound for the non-null, non-NaN values in the partition field, or 
null if all
+  /// values are null or NaN
+  std::optional<std::vector<uint8_t>> lower_bound;
+  /// Field id: 511
+  /// Upper bound for the non-null, non-NaN values in the partition field, or 
null if all
+  /// values are null or NaN
+  std::optional<std::vector<uint8_t>> upper_bound;
+
+  inline static const SchemaField kConsTainsNull =
+      SchemaField::MakeRequired(509, "contains_null", 
std::make_shared<BooleanType>(),
+                                "True if any file has a null partition value");
+  inline static const SchemaField kContainsNaN =
+      SchemaField::MakeOptional(518, "contains_nan", 
std::make_shared<BooleanType>(),
+                                "True if any file has a nan partition value");
+  inline static const SchemaField kLowerBound =
+      SchemaField::MakeOptional(510, "lower_bound", 
std::make_shared<BinaryType>(),
+                                "Partition lower bound for all files");
+  inline static const SchemaField kUpperBound =
+      SchemaField::MakeOptional(511, "upper_bound", 
std::make_shared<BinaryType>(),
+                                "Partition upper bound for all files");
+
+  static const StructType& Type();
+};
+
+/// \brief Entry in a manifest list.
+struct ICEBERG_EXPORT ManifestFile {
+  /// Field id: 500
+  /// Location of the manifest file
+  std::string manifest_path;
+  /// Field id: 501
+  /// Length of the manifest file in bytes
+  int64_t manifest_length;
+  /// Field id: 502
+  /// ID of a partition spec used to write the manifest; must be listed in 
table metadata
+  /// partition-specs
+  int32_t partition_spec_id;
+  /// Field id: 517
+  /// The type of files tracked by the manifest, either data or delete files; 
0 for all v1
+  /// manifests
+  ManifestContent content;
+  /// Field id: 515
+  /// The sequence number when the manifest was added to the table; use 0 when 
reading v1
+  /// manifest lists
+  int64_t sequence_number;
+  /// Field id: 516
+  /// The minimum data sequence number of all live data or delete files in the 
manifest;
+  /// use 0 when reading v1 manifest lists
+  int64_t min_sequence_number;
+  /// Field id: 503
+  /// ID of the snapshot where the manifest file was added
+  int64_t added_snapshot_id;
+  /// Field id: 504
+  /// Number of entries in the manifest that have status ADDED (1), when null 
this is
+  /// assumed to be non-zero
+  std::optional<int32_t> added_files_count;
+  /// Field id: 505
+  /// Number of entries in the manifest that have status EXISTING (0), when 
null this is
+  /// assumed to be non-zero
+  std::optional<int32_t> existing_files_count;
+  /// Field id: 506
+  /// Number of entries in the manifest that have status DELETED (2), when 
null this is
+  /// assumed to be non-zero
+  std::optional<int32_t> deleted_files_count;
+  /// Field id: 512
+  /// Number of rows in all of files in the manifest that have status ADDED, 
when null
+  /// this is assumed to be non-zero
+  std::optional<int64_t> added_rows_count;
+  /// Field id: 513
+  /// Number of rows in all of files in the manifest that have status 
EXISTING, when null
+  /// this is assumed to be non-zero
+  std::optional<int64_t> existing_rows_count;
+  /// Field id: 514
+  /// Number of rows in all of files in the manifest that have status DELETED, 
when null
+  /// this is assumed to be non-zero
+  std::optional<int64_t> deleted_rows_count;
+  /// Field id: 507
+  /// Element field id: 508
+  /// A list of field summaries for each partition field in the spec. Each 
field in the
+  /// list corresponds to a field in the manifest file's partition spec.
+  std::vector<PartitionFieldSummary> partitions;
+  /// Field id: 519
+  /// Implementation-specific key metadata for encryption
+  std::vector<uint8_t> key_metadata;
+  /// Field id: 520
+  /// The starting _row_id to assign to rows added by ADDED data files
+  int64_t first_row_id;
+
+  /// \brief Checks if this manifest file contains entries with ADDED status.
+  bool has_added_files() const { return added_files_count.value_or(1) > 0; }
+
+  /// \brief Checks if this manifest file contains entries with EXISTING 
status.
+  bool has_existing_files() const { return existing_files_count.value_or(1) > 
0; }
+
+  /// \brief Checks if this manifest file contains entries with DELETED status
+  bool has_deleted_files() const { return deleted_files_count.value_or(1) > 0; 
}
+
+  inline static const SchemaField kManifestPath =
+      SchemaField::MakeRequired(500, "manifest_path", 
std::make_shared<StringType>(),
+                                "Location URI with FS scheme");
+  inline static const SchemaField kManifestLength = SchemaField::MakeRequired(
+      501, "manifest_length", std::make_shared<LongType>(), "Total file size 
in bytes");
+  inline static const SchemaField kPartitionSpecId = SchemaField::MakeRequired(
+      502, "partition_spec_id", std::make_shared<IntType>(), "Spec ID used to 
write");
+  inline static const SchemaField kContent =
+      SchemaField::MakeOptional(517, "content", std::make_shared<IntType>(),
+                                "Contents of the manifest: 0=data, 1=deletes");
+  inline static const SchemaField kSequenceNumber =
+      SchemaField::MakeOptional(515, "sequence_number", 
std::make_shared<LongType>(),
+                                "Sequence number when the manifest was added");
+  inline static const SchemaField kMinSequenceNumber =
+      SchemaField::MakeOptional(516, "min_sequence_number", 
std::make_shared<LongType>(),
+                                "Lowest sequence number in the manifest");
+  inline static const SchemaField kAddedSnapshotId =
+      SchemaField::MakeRequired(503, "added_snapshot_id", 
std::make_shared<LongType>(),
+                                "Snapshot ID that added the manifest");
+  inline static const SchemaField kAddedFilesCount = SchemaField::MakeOptional(
+      504, "added_files_count", std::make_shared<IntType>(), "Added entry 
count");
+  inline static const SchemaField kExistingFilesCount = 
SchemaField::MakeOptional(
+      505, "existing_files_count", std::make_shared<IntType>(), "Existing 
entry count");
+  inline static const SchemaField kDeletedFilesCount = 
SchemaField::MakeOptional(
+      506, "deleted_files_count", std::make_shared<IntType>(), "Deleted entry 
count");
+  inline static const SchemaField kAddedRowsCount = SchemaField::MakeOptional(
+      512, "added_rows_count", std::make_shared<LongType>(), "Added rows 
count");
+  inline static const SchemaField kExistingRowsCount = 
SchemaField::MakeOptional(
+      513, "existing_rows_count", std::make_shared<LongType>(), "Existing rows 
count");
+  inline static const SchemaField kDeletedRowsCount = 
SchemaField::MakeOptional(
+      514, "deleted_rows_count", std::make_shared<LongType>(), "Deleted rows 
count");
+  inline static const SchemaField kPartitions = SchemaField::MakeOptional(
+      507, "partitions",
+      std::make_shared<ListType>(SchemaField::MakeRequired(
+          508, std::string(ListType::kElementName),
+          std::make_shared<StructType>(PartitionFieldSummary::Type()))),
+      "Summary for each partition");
+  inline static const SchemaField kKeyMetadata =
+      SchemaField::MakeOptional(519, "key_metadata", 
std::make_shared<BinaryType>(),
+                                "Encryption key metadata blob");
+  inline static const SchemaField kFirstRowId = SchemaField::MakeOptional(
+      520, "first_row_id", std::make_shared<LongType>(),
+      "Starting row ID to assign to new rows in ADDED data files");
+
+  static const StructType& Type();
+};
+
+/// Snapshots are embedded in table metadata, but the list of manifests for a 
snapshot are
+/// stored in a separate manifest list file.
+///
+/// A new manifest list is written for each attempt to commit a snapshot 
because the list
+/// of manifests always changes to produce a new snapshot. When a manifest 
list is
+/// written, the (optimistic) sequence number of the snapshot is written for 
all new
+/// manifest files tracked by the list.
+///
+/// A manifest list includes summary metadata that can be used to avoid 
scanning all of
+/// the manifests in a snapshot when planning a table scan. This includes the 
number of
+/// added, existing, and deleted files, and a summary of values for each field 
of the
+/// partition spec used to write the manifest.
+struct ICEBERG_EXPORT ManifestList {
+  /// Entries in a manifest list.
+  std::vector<ManifestFile> entries;
+};
+
+}  // namespace iceberg
diff --git a/src/iceberg/manifest_reader.h b/src/iceberg/manifest_reader.h
index a735036..6b81eb9 100644
--- a/src/iceberg/manifest_reader.h
+++ b/src/iceberg/manifest_reader.h
@@ -26,13 +26,15 @@
 #include <span>
 
 #include "iceberg/file_reader.h"
+#include "iceberg/iceberg_export.h"
+#include "iceberg/type_fwd.h"
 
 namespace iceberg {
 
 /// \brief Read manifest entries from a manifest file.
 class ICEBERG_EXPORT ManifestReader {
  public:
-  virtual Result<std::span<std::unique_ptr<class ManifestEntry>>> Entries() 
const = 0;
+  virtual Result<std::span<std::unique_ptr<ManifestEntry>>> Entries() const = 
0;
 
  private:
   std::unique_ptr<StructLikeReader> reader_;
@@ -41,7 +43,7 @@ class ICEBERG_EXPORT ManifestReader {
 /// \brief Read manifest files from a manifest list file.
 class ICEBERG_EXPORT ManifestListReader {
  public:
-  virtual Result<std::span<std::unique_ptr<class ManifestFile>>> Files() const 
= 0;
+  virtual Result<std::span<std::unique_ptr<ManifestFile>>> Files() const = 0;
 
  private:
   std::unique_ptr<StructLikeReader> reader_;
diff --git a/src/iceberg/type_fwd.h b/src/iceberg/type_fwd.h
index 39e4688..9fc6bd6 100644
--- a/src/iceberg/type_fwd.h
+++ b/src/iceberg/type_fwd.h
@@ -121,4 +121,9 @@ class UpdateRequirement;
 class AppendFiles;
 class TableScan;
 
+struct DataFile;
+struct ManifestEntry;
+struct ManifestFile;
+struct ManifestList;
+
 }  // namespace iceberg

Reply via email to