blob: b9f601cc1c6fbd1130cddbe61c0fee9db74ddc69 [file]
/*
* 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.
*/
/// \file metadata_table_test_base.h
/// Shared test base for all metadata table tests.
///
/// Provides common helpers (ReadAllBatches, MakeTestSnapshots,
/// MakeTableWithSnapshots) and the MockFileIO + MockCatalog fixture that
/// every metadata table test needs.
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <arrow/array.h>
#include <arrow/c/bridge.h>
#include <arrow/record_batch.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "iceberg/constants.h"
#include "iceberg/schema.h"
#include "iceberg/schema_field.h"
#include "iceberg/schema_internal.h"
#include "iceberg/snapshot.h"
#include "iceberg/table.h"
#include "iceberg/table_identifier.h"
#include "iceberg/table_metadata.h"
#include "iceberg/test/matchers.h"
#include "iceberg/test/mock_catalog.h"
#include "iceberg/test/mock_io.h"
#include "iceberg/type.h"
#include "iceberg/util/timepoint.h"
namespace iceberg {
/// \brief Base class for all metadata table tests.
///
/// Provides MockFileIO and MockCatalog instances plus helpers shared across
/// metadata table tests (SnapshotsTable, HistoryTable, RefsTable, ...).
class MetadataTableTestBase : public ::testing::Test {
protected:
void SetUp() override {
io_ = std::make_shared<MockFileIO>();
catalog_ = std::make_shared<MockCatalog>();
auto schema = std::make_shared<Schema>(
std::vector<SchemaField>{SchemaField::MakeRequired(1, "id", int64()),
SchemaField::MakeOptional(2, "name", string())},
1);
metadata_ = std::make_shared<TableMetadata>(
TableMetadata{.format_version = 2,
.schemas = {schema},
.current_schema_id = 1,
.current_snapshot_id = kInvalidSnapshotId});
TableIdentifier source_ident{.ns = Namespace{.levels = {"db"}},
.name = "source_table"};
ICEBERG_UNWRAP_OR_FAIL(table_, Table::Make(source_ident, metadata_,
"s3://bucket/meta.json", io_, catalog_));
}
/// \brief Import and consume a Scan()-produced ArrowArrayStream.
static Result<std::vector<std::shared_ptr<::arrow::RecordBatch>>> ReadAllBatches(
ArrowArrayStream&& stream) {
auto reader_result = ::arrow::ImportRecordBatchReader(&stream);
if (!reader_result.ok()) {
if (stream.release != nullptr) {
stream.release(&stream);
}
return InvalidArrowData(reader_result.status().ToString());
}
auto batches_result = reader_result.ValueUnsafe()->ToRecordBatches();
if (!batches_result.ok()) {
return InvalidArrowData(batches_result.status().ToString());
}
return std::move(batches_result).MoveValueUnsafe();
}
/// \brief Create two snapshots matching the Java TestDataTaskParser test data.
///
/// Snapshot 1: id=1, no parent, timestamp=1234567890000, operation="append"
/// Snapshot 2: id=2, parent=1, timestamp=9876543210000, operation="append"
static std::pair<std::shared_ptr<Snapshot>, std::shared_ptr<Snapshot>>
MakeTestSnapshots() {
std::unordered_map<std::string, std::string> summary1{
{"added-data-files", "1"}, {"added-records", "1"},
{"added-files-size", "10"}, {"changed-partition-count", "1"},
{"total-records", "1"}, {"total-files-size", "10"},
{"total-data-files", "1"}, {"total-delete-files", "0"},
{"total-position-deletes", "0"}, {"total-equality-deletes", "0"},
{"operation", "append"},
};
std::unordered_map<std::string, std::string> summary2{
{"added-data-files", "1"}, {"added-records", "1"},
{"added-files-size", "10"}, {"changed-partition-count", "1"},
{"total-records", "2"}, {"total-files-size", "20"},
{"total-data-files", "2"}, {"total-delete-files", "0"},
{"total-position-deletes", "0"}, {"total-equality-deletes", "0"},
{"operation", "append"},
};
auto snap1 = std::make_shared<Snapshot>(Snapshot{
.snapshot_id = 1,
.parent_snapshot_id = std::nullopt,
.sequence_number = 1,
.timestamp_ms = TimePointMsFromUnixMs(1234567890000),
.manifest_list = "file:/tmp/manifest1.avro",
.summary = std::move(summary1),
.schema_id = 1,
});
auto snap2 = std::make_shared<Snapshot>(Snapshot{
.snapshot_id = 2,
.parent_snapshot_id = 1,
.sequence_number = 2,
.timestamp_ms = TimePointMsFromUnixMs(9876543210000),
.manifest_list = "file:/tmp/manifest2.avro",
.summary = std::move(summary2),
.schema_id = 1,
});
return {snap1, snap2};
}
/// \brief Create a Table with the given snapshots.
Result<std::shared_ptr<Table>> MakeTableWithSnapshots(
std::vector<std::shared_ptr<Snapshot>> snapshots, int64_t current_snapshot_id,
std::string metadata_location = "s3://bucket/meta.json") {
auto schema = std::make_shared<Schema>(
std::vector<SchemaField>{SchemaField::MakeRequired(1, "id", int64()),
SchemaField::MakeOptional(2, "name", string())},
1);
auto metadata = std::make_shared<TableMetadata>(TableMetadata{
.format_version = 2,
.schemas = {schema},
.current_schema_id = 1,
.current_snapshot_id = current_snapshot_id,
.snapshots = std::move(snapshots),
});
TableIdentifier ident{.ns = Namespace{.levels = {"db"}}, .name = "test_table"};
return Table::Make(ident, metadata, std::move(metadata_location), io_, catalog_);
}
std::shared_ptr<MockFileIO> io_;
std::shared_ptr<MockCatalog> catalog_;
std::shared_ptr<TableMetadata> metadata_;
std::shared_ptr<Table> table_;
};
} // namespace iceberg