diff --git a/src/iceberg/inspect/metadata_table.cc b/src/iceberg/inspect/metadata_table.cc
index 5e9504003..54af967e2 100644
--- a/src/iceberg/inspect/metadata_table.cc
+++ b/src/iceberg/inspect/metadata_table.cc
@@ -35,6 +35,13 @@ MetadataTable::MetadataTable(std::shared_ptr
source_table,
MetadataTable::~MetadataTable() = default;
+bool MetadataTable::supports_time_travel() const noexcept { return false; }
+
+Result MetadataTable::Scan(
+ std::optional /* snapshot_selection */) {
+ return NotSupported("Scan is not supported for this metadata table type");
+}
+
Result> MetadataTable::Make(std::shared_ptr table,
Kind kind) {
if (table == nullptr) [[unlikely]] {
@@ -46,9 +53,24 @@ Result> MetadataTable::Make(std::shared_ptr
+#include
+#include
+#include "iceberg/arrow_c_data.h"
#include "iceberg/iceberg_export.h"
#include "iceberg/result.h"
#include "iceberg/table_identifier.h"
#include "iceberg/type_fwd.h"
+#include "iceberg/util/timepoint.h"
namespace iceberg {
+/// \brief Parameters for snapshot selection (time travel).
+struct SnapshotSelection {
+ /// \brief The snapshot ID to read.
+ std::optional snapshot_id;
+ /// \brief Read the snapshot that was current at this timestamp.
+ std::optional as_of_timestamp;
+ /// \brief Read the snapshot referenced by this named ref (branch or tag).
+ std::optional ref_name;
+};
+
/// \brief Base class for Iceberg metadata tables.
class ICEBERG_EXPORT MetadataTable {
public:
enum class Kind {
- kSnapshots,
+ kEntries,
+ kFiles,
+ kDataFiles,
+ kDeleteFiles,
kHistory,
+ kMetadataLogEntries,
+ kSnapshots,
+ kRefs,
+ kManifests,
+ kPartitions,
+ kAllDataFiles,
+ kAllDeleteFiles,
+ kAllFiles,
+ kAllManifests,
+ kAllEntries,
+ kPositionDeletes,
};
static Result> Make(std::shared_ptr table,
@@ -43,6 +71,24 @@ class ICEBERG_EXPORT MetadataTable {
virtual Kind kind() const noexcept = 0;
+ /// \brief Whether this metadata table supports time-travel queries.
+ ///
+ /// The default implementation returns false. Subclasses that support
+ /// reading historical snapshots should override to return true.
+ virtual bool supports_time_travel() const noexcept;
+
+ /// \brief Scan the metadata table and return all rows as an Arrow struct array.
+ ///
+ /// The returned ArrowArray is a struct array where each element is one row.
+ /// The caller takes ownership and must call ArrowArrayRelease when done.
+ ///
+ /// \param snapshot_selection optional snapshot selection for time travel.
+ /// Pass std::nullopt to use the current snapshot.
+ ///
+ /// The default implementation returns NotSupported. Subclasses override this
+ /// to materialize their data.
+ virtual Result Scan(std::optional snapshot_selection);
+
const TableIdentifier& name() const { return identifier_; }
const std::shared_ptr& schema() const { return schema_; }
diff --git a/src/iceberg/test/metadata_table_test.cc b/src/iceberg/test/metadata_table_test.cc
index 1e0a664c3..8caab2c55 100644
--- a/src/iceberg/test/metadata_table_test.cc
+++ b/src/iceberg/test/metadata_table_test.cc
@@ -19,6 +19,8 @@
#include "iceberg/inspect/metadata_table.h"
+#include
+
#include
#include
@@ -117,4 +119,61 @@ TEST_F(MetadataTableTest, FactoryRejectsNullSourceTable) {
EXPECT_THAT(result, HasErrorMessage("Table cannot be null"));
}
+TEST_F(MetadataTableTest, AllKindEnumValues) {
+ // Verify all 16 Kind enum values are present and distinct.
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kEntries), 0);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kFiles), 1);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kDataFiles), 2);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kDeleteFiles), 3);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kHistory), 4);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kMetadataLogEntries), 5);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kSnapshots), 6);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kRefs), 7);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kManifests), 8);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kPartitions), 9);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kAllDataFiles), 10);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kAllDeleteFiles), 11);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kAllFiles), 12);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kAllManifests), 13);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kAllEntries), 14);
+ EXPECT_EQ(static_cast(MetadataTable::Kind::kPositionDeletes), 15);
+}
+
+TEST_F(MetadataTableTest, UnimplementedKindsReturnNotSupported) {
+ // All new (not-yet-implemented) kinds should return NotSupported from the factory.
+ std::vector unimplemented = {
+ MetadataTable::Kind::kEntries,
+ MetadataTable::Kind::kFiles,
+ MetadataTable::Kind::kDataFiles,
+ MetadataTable::Kind::kDeleteFiles,
+ MetadataTable::Kind::kMetadataLogEntries,
+ MetadataTable::Kind::kRefs,
+ MetadataTable::Kind::kManifests,
+ MetadataTable::Kind::kPartitions,
+ MetadataTable::Kind::kAllDataFiles,
+ MetadataTable::Kind::kAllDeleteFiles,
+ MetadataTable::Kind::kAllFiles,
+ MetadataTable::Kind::kAllManifests,
+ MetadataTable::Kind::kAllEntries,
+ MetadataTable::Kind::kPositionDeletes,
+ };
+
+ for (auto kind : unimplemented) {
+ auto result = MetadataTable::Make(source_table_, kind);
+ EXPECT_THAT(result, IsError(ErrorKind::kNotSupported))
+ << "Kind " << static_cast(kind) << " should return NotSupported";
+ }
+}
+
+TEST_F(MetadataTableTest, DefaultSupportsTimeTravelReturnsFalse) {
+ // The base class default implementation should return false.
+ EXPECT_FALSE(snapshots_table_->supports_time_travel());
+}
+
+TEST_F(MetadataTableTest, DefaultScanReturnsNotSupported) {
+ // The base class default Scan() should return NotSupported.
+ auto result = snapshots_table_->Scan(std::nullopt);
+ EXPECT_THAT(result, IsError(ErrorKind::kNotSupported));
+}
+
} // namespace iceberg