| /* |
| * 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 <memory> |
| #include <optional> |
| #include <span> |
| #include <string> |
| #include <string_view> |
| #include <unordered_map> |
| #include <variant> |
| |
| #include "iceberg/iceberg_export.h" |
| #include "iceberg/manifest/manifest_list.h" |
| #include "iceberg/result.h" |
| #include "iceberg/type_fwd.h" |
| #include "iceberg/util/lazy.h" |
| #include "iceberg/util/timepoint.h" |
| |
| namespace iceberg { |
| |
| /// \brief The type of snapshot reference |
| enum class SnapshotRefType { |
| /// Branches are mutable named references that can be updated by committing a new |
| /// snapshot as the branch’s referenced snapshot using the Commit Conflict Resolution |
| /// and Retry procedures. |
| kBranch, |
| /// Tags are labels for individual snapshots |
| kTag, |
| }; |
| |
| /// \brief Get the relative snapshot reference type name |
| ICEBERG_EXPORT constexpr std::string_view ToString(SnapshotRefType type) noexcept { |
| switch (type) { |
| case SnapshotRefType::kBranch: |
| return "branch"; |
| case SnapshotRefType::kTag: |
| return "tag"; |
| } |
| std::unreachable(); |
| } |
| /// \brief Get the relative snapshot reference type from name |
| ICEBERG_EXPORT constexpr Result<SnapshotRefType> SnapshotRefTypeFromString( |
| std::string_view str) noexcept { |
| if (str == "branch") return SnapshotRefType::kBranch; |
| if (str == "tag") return SnapshotRefType::kTag; |
| return InvalidArgument("Invalid snapshot reference type: {}", str); |
| } |
| |
| /// \brief A reference to a snapshot, either a branch or a tag. |
| struct ICEBERG_EXPORT SnapshotRef { |
| static constexpr std::string_view kMainBranch = "main"; |
| |
| struct ICEBERG_EXPORT Branch { |
| /// A positive number for the minimum number of snapshots to keep in a branch while |
| /// expiring snapshots. Defaults to table property |
| /// history.expire.min-snapshots-to-keep. |
| std::optional<int32_t> min_snapshots_to_keep; |
| /// A positive number for the max age of snapshots to keep when |
| /// expiring, including the latest snapshot. Defaults to table property |
| /// history.expire.max-snapshot-age-ms. |
| std::optional<int64_t> max_snapshot_age_ms; |
| /// For snapshot references except the main branch, a positive number for the max age |
| /// of the snapshot reference to keep while expiring snapshots. Defaults to table |
| /// property history.expire.max-ref-age-ms. The main branch never expires. |
| std::optional<int64_t> max_ref_age_ms; |
| |
| /// \brief Compare two branches for equality. |
| friend bool operator==(const Branch& lhs, const Branch& rhs) { |
| return lhs.Equals(rhs); |
| } |
| |
| private: |
| /// \brief Compare two branches for equality. |
| bool Equals(const Branch& other) const; |
| }; |
| |
| struct ICEBERG_EXPORT Tag { |
| /// For snapshot references except the main branch, a positive number for the max age |
| /// of the snapshot reference to keep while expiring snapshots. Defaults to table |
| /// property history.expire.max-ref-age-ms. The main branch never expires. |
| std::optional<int64_t> max_ref_age_ms; |
| |
| /// \brief Compare two tags for equality. |
| friend bool operator==(const Tag& lhs, const Tag& rhs) { return lhs.Equals(rhs); } |
| |
| private: |
| /// \brief Compare two tags for equality. |
| bool Equals(const Tag& other) const; |
| }; |
| |
| /// A reference's snapshot ID. The tagged snapshot or latest snapshot of a branch. |
| int64_t snapshot_id; |
| /// Snapshot retention policy |
| std::variant<Branch, Tag> retention; |
| |
| SnapshotRefType type() const noexcept; |
| |
| std::optional<int64_t> max_ref_age_ms() const noexcept; |
| |
| /// \brief Create a branch reference |
| /// |
| /// \param snapshot_id The snapshot ID for the branch |
| /// \param min_snapshots_to_keep Optional minimum number of snapshots to keep |
| /// \param max_snapshot_age_ms Optional maximum snapshot age in milliseconds |
| /// \param max_ref_age_ms Optional maximum reference age in milliseconds |
| /// \return A Result containing a unique_ptr to the SnapshotRef, or an error if |
| /// validation failed |
| static Result<std::unique_ptr<SnapshotRef>> MakeBranch( |
| int64_t snapshot_id, std::optional<int32_t> min_snapshots_to_keep = std::nullopt, |
| std::optional<int64_t> max_snapshot_age_ms = std::nullopt, |
| std::optional<int64_t> max_ref_age_ms = std::nullopt); |
| |
| /// \brief Create a tag reference |
| /// |
| /// \param snapshot_id The snapshot ID for the tag |
| /// \param max_ref_age_ms Optional maximum reference age in milliseconds |
| /// \return A Result containing a unique_ptr to the SnapshotRef, or an error if |
| /// validation failed |
| static Result<std::unique_ptr<SnapshotRef>> MakeTag( |
| int64_t snapshot_id, std::optional<int64_t> max_ref_age_ms = std::nullopt); |
| |
| /// \brief Clone this SnapshotRef with an optional new snapshot ID |
| /// |
| /// \param new_snapshot_id Optional new snapshot ID. If not provided, uses the current |
| /// snapshot_id |
| /// \return A unique_ptr to the cloned SnapshotRef |
| std::unique_ptr<SnapshotRef> Clone( |
| std::optional<int64_t> new_snapshot_id = std::nullopt) const; |
| |
| /// \brief Validate the SnapshotRef |
| Status Validate() const; |
| |
| /// \brief Compare two snapshot refs for equality |
| friend bool operator==(const SnapshotRef& lhs, const SnapshotRef& rhs) { |
| return lhs.Equals(rhs); |
| } |
| |
| private: |
| /// \brief Compare two snapshot refs for equality. |
| bool Equals(const SnapshotRef& other) const; |
| }; |
| |
| /// \brief Optional Snapshot Summary Fields |
| struct ICEBERG_EXPORT SnapshotSummaryFields { |
| /// \brief The operation field key |
| inline static const std::string kOperation = "operation"; |
| /// \brief The first row id field key |
| inline static const std::string kFirstRowId = "first-row-id"; |
| /// \brief The added rows field key |
| inline static const std::string kAddedRows = "added-rows"; |
| |
| /// Metrics, see https://iceberg.apache.org/spec/#metrics |
| |
| /// \brief Number of data files added in the snapshot |
| inline static const std::string kAddedDataFiles = "added-data-files"; |
| /// \brief Number of data files deleted in the snapshot |
| inline static const std::string kDeletedDataFiles = "deleted-data-files"; |
| /// \brief Total number of live data files in the snapshot |
| inline static const std::string kTotalDataFiles = "total-data-files"; |
| /// \brief Number of positional/equality delete files and deletion vectors added in the |
| /// snapshot |
| inline static const std::string kAddedDeleteFiles = "added-delete-files"; |
| /// \brief Number of equality delete files added in the snapshot |
| inline static const std::string kAddedEqDeleteFiles = "added-equality-delete-files"; |
| /// \brief Number of equality delete files removed in the snapshot |
| inline static const std::string kRemovedEqDeleteFiles = "removed-equality-delete-files"; |
| /// \brief Number of position delete files added in the snapshot |
| inline static const std::string kAddedPosDeleteFiles = "added-position-delete-files"; |
| /// \brief Number of position delete files removed in the snapshot |
| inline static const std::string kRemovedPosDeleteFiles = |
| "removed-position-delete-files"; |
| /// \brief Number of deletion vectors added in the snapshot |
| inline static const std::string kAddedDVs = "added-dvs"; |
| /// \brief Number of deletion vectors removed in the snapshot |
| inline static const std::string kRemovedDVs = "removed-dvs"; |
| /// \brief Number of positional/equality delete files and deletion vectors removed in |
| /// the snapshot |
| inline static const std::string kRemovedDeleteFiles = "removed-delete-files"; |
| /// \brief Total number of live positional/equality delete files and deletion vectors in |
| /// the snapshot |
| inline static const std::string kTotalDeleteFiles = "total-delete-files"; |
| /// \brief Number of records added in the snapshot |
| inline static const std::string kAddedRecords = "added-records"; |
| /// \brief Number of records deleted in the snapshot |
| inline static const std::string kDeletedRecords = "deleted-records"; |
| /// \brief Total number of records in the snapshot |
| inline static const std::string kTotalRecords = "total-records"; |
| /// \brief The size of files added in the snapshot |
| inline static const std::string kAddedFileSize = "added-files-size"; |
| /// \brief The size of files removed in the snapshot |
| inline static const std::string kRemovedFileSize = "removed-files-size"; |
| /// \brief Total size of live files in the snapshot |
| inline static const std::string kTotalFileSize = "total-files-size"; |
| /// \brief Number of position delete records added in the snapshot |
| inline static const std::string kAddedPosDeletes = "added-position-deletes"; |
| /// \brief Number of position delete records removed in the snapshot |
| inline static const std::string kRemovedPosDeletes = "removed-position-deletes"; |
| /// \brief Total number of position delete records in the snapshot |
| inline static const std::string kTotalPosDeletes = "total-position-deletes"; |
| /// \brief Number of equality delete records added in the snapshot |
| inline static const std::string kAddedEqDeletes = "added-equality-deletes"; |
| /// \brief Number of equality delete records removed in the snapshot |
| inline static const std::string kRemovedEqDeletes = "removed-equality-deletes"; |
| /// \brief Total number of equality delete records in the snapshot |
| inline static const std::string kTotalEqDeletes = "total-equality-deletes"; |
| /// \brief Number of duplicate files deleted (duplicates are files recorded more than |
| /// once in the manifest) |
| inline static const std::string kDeletedDuplicatedFiles = "deleted-duplicate-files"; |
| /// \brief Number of partitions with files added or removed in the snapshot |
| inline static const std::string kChangedPartitionCountProp = "changed-partition-count"; |
| /// \brief Number of manifest files created in the snapshot |
| inline static const std::string kManifestsCreated = "manifests-created"; |
| /// \brief Number of manifest files kept in the snapshot |
| inline static const std::string kManifestsKept = "manifests-kept"; |
| /// \brief Number of manifest files replaced in the snapshot |
| inline static const std::string kManifestsReplaced = "manifests-replaced"; |
| /// \brief Number of manifest entries processed in the snapshot |
| inline static const std::string kEntriesProcessed = "entries-processed"; |
| /// \brief Partition summaries prefix |
| inline static const std::string kChangedPartitionPrefix = "partitions."; |
| /// \brief Whether partition summaries are included |
| inline static const std::string kPartitionSummaryProp = "partition-summaries-included"; |
| |
| /// Other Fields, see https://iceberg.apache.org/spec/#other-fields |
| |
| /// \brief The Write-Audit-Publish id of a staged snapshot |
| inline static const std::string kWAPId = "wap.id"; |
| /// \brief The Write-Audit-Publish id of a snapshot already been published |
| inline static const std::string kPublishedWAPId = "published-wap-id"; |
| /// \brief The original id of a cherry-picked snapshot |
| inline static const std::string kSourceSnapshotId = "source-snapshot-id"; |
| /// \brief Name of the engine that created the snapshot |
| inline static const std::string kEngineName = "engine-name"; |
| /// \brief Version of the engine that created the snapshot |
| inline static const std::string kEngineVersion = "engine-version"; |
| }; |
| |
| /// \brief Helper class for building snapshot summaries. |
| /// |
| /// This class provides methods to track changes to data and delete files, |
| /// and produces a map of summary properties for snapshot metadata. |
| class ICEBERG_EXPORT SnapshotSummaryBuilder { |
| private: |
| /// \brief Metrics tracking for added and removed files |
| class UpdateMetrics { |
| public: |
| void Clear(); |
| void AddTo(std::unordered_map<std::string, std::string>& builder) const; |
| void AddedFile(const DataFile& file); |
| void RemovedFile(const DataFile& file); |
| void AddedManifest(const ManifestFile& manifest); |
| void Merge(const UpdateMetrics& other); |
| |
| private: |
| int64_t added_size_{0}; |
| int64_t removed_size_{0}; |
| int32_t added_files_{0}; |
| int32_t removed_files_{0}; |
| int32_t added_eq_delete_files_{0}; |
| int32_t removed_eq_delete_files_{0}; |
| int32_t added_pos_delete_files_{0}; |
| int32_t removed_pos_delete_files_{0}; |
| int32_t added_dvs_{0}; |
| int32_t removed_dvs_{0}; |
| int32_t added_delete_files_{0}; |
| int32_t removed_delete_files_{0}; |
| int64_t added_records_{0}; |
| int64_t deleted_records_{0}; |
| int64_t added_pos_deletes_{0}; |
| int64_t removed_pos_deletes_{0}; |
| int64_t added_eq_deletes_{0}; |
| int64_t removed_eq_deletes_{0}; |
| bool trust_size_and_delete_counts_{true}; |
| }; |
| |
| public: |
| SnapshotSummaryBuilder() = default; |
| |
| /// \brief Clear all tracked metrics and properties |
| void Clear(); |
| |
| /// \brief Set the maximum number of changed partitions before partition summaries will |
| /// be excluded. |
| /// |
| /// If the number of changed partitions is over this max, summaries will not be |
| /// included. If the number of changed partitions is <= this limit, then partition-level |
| /// summaries will be included in the summary if they are available, and |
| /// "partition-summaries-included" will be set to "true". |
| /// |
| /// \param max Maximum number of changed partitions |
| void SetPartitionSummaryLimit(int32_t max); |
| |
| /// \brief Increment the count of duplicate files deleted by a specific amount |
| /// |
| /// \param increment Amount to increment by. Defaults to 1. |
| void IncrementDuplicateDeletes(int32_t increment = 1); |
| |
| /// \brief Track a data file being added to the snapshot |
| /// |
| /// \param spec The partition spec |
| /// \param file The data file being added |
| /// \return Status indicating success or error |
| Status AddedFile(const PartitionSpec& spec, const DataFile& file); |
| |
| /// \brief Track a data file being deleted from the snapshot |
| /// |
| /// \param spec The partition spec |
| /// \param file The data file being deleted |
| /// \return Status indicating success or error |
| Status DeletedFile(const PartitionSpec& spec, const DataFile& file); |
| |
| /// \brief Track a manifest being added |
| /// |
| /// \param manifest The manifest file being added |
| void AddedManifest(const ManifestFile& manifest); |
| |
| /// \brief Set a custom summary property |
| /// |
| /// \param property Property name |
| /// \param value Property value |
| void Set(const std::string& property, const std::string& value); |
| |
| /// \brief Merge another builder's metrics into this one |
| /// |
| /// \param other The builder to merge from |
| void Merge(const SnapshotSummaryBuilder& other); |
| |
| /// \brief Build the final summary map |
| /// |
| /// \return Map of summary properties |
| std::unordered_map<std::string, std::string> Build() const; |
| |
| private: |
| Status UpdatePartitions(const PartitionSpec& spec, const DataFile& file, |
| bool is_addition); |
| std::string PartitionSummary(const UpdateMetrics& metrics) const; |
| |
| std::unordered_map<std::string, std::string> properties_; |
| std::unordered_map<std::string, UpdateMetrics> partition_metrics_; |
| UpdateMetrics metrics_; |
| int32_t max_changed_partitions_for_summaries_{0}; |
| int64_t deleted_duplicate_files_{0}; |
| bool trust_partition_metrics_{true}; |
| }; |
| |
| /// \brief Data operation that produce snapshots. |
| /// |
| /// A snapshot can return the operation that created the snapshot to help other components |
| /// ignore snapshots that are not needed for some tasks. For example, snapshot expiration |
| /// does not need to clean up deleted files for appends, which have no deleted files. |
| struct ICEBERG_EXPORT DataOperation { |
| /// \brief Only data files were added and no files were removed. |
| inline static const std::string kAppend = "append"; |
| /// \brief Data and delete files were added and removed without changing table data; |
| /// i.e. compaction, change the data file format, or relocating data files. |
| inline static const std::string kReplace = "replace"; |
| /// \brief Data and delete files were added and removed in a logical overwrite |
| /// operation. |
| inline static const std::string kOverwrite = "overwrite"; |
| /// \brief Data files were removed and their contents logically deleted and/or delete |
| /// files were added to delete rows. |
| inline static const std::string kDelete = "delete"; |
| }; |
| |
| /// \brief A snapshot of the data in a table at a point in time. |
| /// |
| /// A snapshot consist of one or more file manifests, and the complete table contents is |
| /// the union of all the data files in those manifests. |
| /// |
| /// Snapshots are created by table operations. |
| struct ICEBERG_EXPORT Snapshot { |
| /// A unique long ID. |
| int64_t snapshot_id; |
| /// The snapshot ID of the snapshot's parent. Omitted for any snapshot with no parent. |
| std::optional<int64_t> parent_snapshot_id; |
| /// A monotonically increasing long that tracks the order of changes to a table. |
| int64_t sequence_number; |
| /// A timestamp when the snapshot was created, used for garbage collection and table |
| /// inspection. |
| TimePointMs timestamp_ms; |
| /// The location of a manifest list for this snapshot that tracks manifest files with |
| /// additional metadata. |
| std::string manifest_list; |
| /// A string map that summaries the snapshot changes, including operation. |
| std::unordered_map<std::string, std::string> summary; |
| /// ID of the table's current schema when the snapshot was created. |
| std::optional<int32_t> schema_id; |
| |
| /// \brief Create a new Snapshot instance with validation on the inputs. |
| static Result<std::unique_ptr<Snapshot>> Make( |
| int64_t sequence_number, int64_t snapshot_id, |
| std::optional<int64_t> parent_snapshot_id, TimePointMs timestamp_ms, |
| std::string operation, std::unordered_map<std::string, std::string> summary, |
| std::optional<int32_t> schema_id, std::string manifest_list, |
| std::optional<int64_t> first_row_id = std::nullopt, |
| std::optional<int64_t> added_rows = std::nullopt); |
| |
| /// \brief Return the name of the DataOperations data operation that produced this |
| /// snapshot. |
| /// |
| /// \return the operation that produced this snapshot, or nullopt if the operation is |
| /// unknown. |
| std::optional<std::string_view> Operation() const; |
| |
| /// \brief The row-id of the first newly added row in this snapshot. |
| /// |
| /// All rows added in this snapshot will have a row-id assigned to them greater than |
| /// this value. All rows with a row-id less than this value were created in a snapshot |
| /// that was added to the table (but not necessarily committed to this branch) in the |
| /// past. |
| /// |
| /// \return the first row-id to be used in this snapshot or nullopt when row lineage |
| /// is not supported |
| Result<std::optional<int64_t>> FirstRowId() const; |
| |
| /// \brief The upper bound of number of rows with assigned row IDs in this snapshot. |
| /// |
| /// It can be used safely to increment the table's `next-row-id` during a commit. It |
| /// can be more than the number of rows added in this snapshot and include some |
| /// existing rows. |
| /// |
| /// This field is optional but is required when the table version supports row lineage. |
| /// |
| /// \return the upper bound of number of rows with assigned row IDs in this snapshot |
| /// or nullopt if the value was not stored. |
| Result<std::optional<int64_t>> AddedRows() const; |
| |
| /// \brief Compare two snapshots for equality. |
| friend bool operator==(const Snapshot& lhs, const Snapshot& rhs) { |
| return lhs.Equals(rhs); |
| } |
| |
| private: |
| /// \brief Compare two snapshots for equality. |
| bool Equals(const Snapshot& other) const; |
| }; |
| |
| /// \brief A snapshot with cached manifest loading capabilities. |
| /// |
| /// This class wraps a Snapshot pointer and provides lazy-loading of manifests. |
| class ICEBERG_EXPORT SnapshotCache { |
| public: |
| explicit SnapshotCache(const Snapshot* snapshot) : snapshot_(snapshot) {} |
| |
| /// \brief Get the underlying Snapshot reference |
| const Snapshot& snapshot() const { return *snapshot_; } |
| |
| /// \brief Returns all ManifestFile instances for either data or delete manifests |
| /// in this snapshot. |
| /// |
| /// \param file_io The FileIO instance to use for reading the manifest list |
| /// \return A span of ManifestFile instances, or an error |
| Result<std::span<ManifestFile>> Manifests(std::shared_ptr<FileIO> file_io) const; |
| |
| /// \brief Returns a ManifestFile for each data manifest in this snapshot. |
| /// |
| /// \param file_io The FileIO instance to use for reading the manifest list |
| /// \return A span of ManifestFile instances, or an error |
| Result<std::span<ManifestFile>> DataManifests(std::shared_ptr<FileIO> file_io) const; |
| |
| /// \brief Returns a ManifestFile for each delete manifest in this snapshot. |
| /// |
| /// \param file_io The FileIO instance to use for reading the manifest list |
| /// \return A span of ManifestFile instances, or an error |
| Result<std::span<ManifestFile>> DeleteManifests(std::shared_ptr<FileIO> file_io) const; |
| |
| private: |
| /// \brief Cache structure for storing loaded manifests |
| /// |
| /// \note Manifests are stored in a single vector with data manifests at the head |
| /// and delete manifests at the tail, separated by the number of data manifests. |
| using ManifestsCache = std::pair<std::vector<ManifestFile>, size_t>; |
| |
| /// \brief Initialize manifests cache by loading them from the manifest list file. |
| /// \param snapshot The snapshot to initialize the manifests cache for |
| /// \param file_io The FileIO instance to use for reading the manifest list |
| /// \return A result containing the manifests cache |
| static Result<ManifestsCache> InitManifestsCache(const Snapshot* snapshot, |
| std::shared_ptr<FileIO> file_io); |
| |
| /// The underlying snapshot data |
| const Snapshot* snapshot_; |
| |
| /// Lazy-loaded manifests cache |
| Lazy<InitManifestsCache> manifests_cache_; |
| }; |
| |
| } // namespace iceberg |