This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 6bf64d6 feat(core): add data file meta with versioned serializers and
path factory (#80)
6bf64d6 is described below
commit 6bf64d601769e090fbb73e5bfe90a7bb023283b2
Author: Yonghao Fang <[email protected]>
AuthorDate: Tue Jun 16 09:11:43 2026 +0800
feat(core): add data file meta with versioned serializers and path factory
(#80)
---
src/paimon/core/io/data_file_meta.cpp | 314 +++++++++++++++++++++
src/paimon/core/io/data_file_meta.h | 166 +++++++++++
.../core/io/data_file_meta_09_serializer.cpp | 120 ++++++++
src/paimon/core/io/data_file_meta_09_serializer.h | 45 +++
.../core/io/data_file_meta_10_serializer.cpp | 129 +++++++++
src/paimon/core/io/data_file_meta_10_serializer.h | 49 ++++
.../core/io/data_file_meta_12_serializer.cpp | 134 +++++++++
src/paimon/core/io/data_file_meta_12_serializer.h | 49 ++++
...ta_file_meta_first_row_id_legacy_serializer.cpp | 140 +++++++++
...data_file_meta_first_row_id_legacy_serializer.h | 50 ++++
src/paimon/core/io/data_file_meta_serializer.cpp | 177 ++++++++++++
src/paimon/core/io/data_file_meta_serializer.h | 45 +++
.../core/io/data_file_meta_serializer_test.cpp | 93 ++++++
src/paimon/core/io/data_file_meta_test.cpp | 203 +++++++++++++
src/paimon/core/io/data_file_path_factory.cpp | 86 ++++++
src/paimon/core/io/data_file_path_factory.h | 112 ++++++++
src/paimon/core/io/data_file_path_factory_test.cpp | 145 ++++++++++
17 files changed, 2057 insertions(+)
diff --git a/src/paimon/core/io/data_file_meta.cpp
b/src/paimon/core/io/data_file_meta.cpp
new file mode 100644
index 0000000..86b4d62
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta.cpp
@@ -0,0 +1,314 @@
+/*
+ * 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 "paimon/core/io/data_file_meta.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <utility>
+
+#include "arrow/type_fwd.h"
+#include "fmt/format.h"
+#include "fmt/ranges.h"
+#include "paimon/common/utils/date_time_utils.h"
+#include "paimon/common/utils/path_util.h"
+#include "paimon/memory/bytes.h"
+#include "paimon/status.h"
+
+namespace arrow {
+class DataType;
+} // namespace arrow
+
+namespace paimon {
+
+const BinaryRow& DataFileMeta::EmptyMinKey() {
+ static const BinaryRow empty_row = BinaryRow::EmptyRow();
+ return empty_row;
+}
+
+const BinaryRow& DataFileMeta::EmptyMaxKey() {
+ static const BinaryRow empty_row = BinaryRow::EmptyRow();
+ return empty_row;
+}
+
+Result<std::shared_ptr<DataFileMeta>> DataFileMeta::ForAppend(
+ const std::string& file_name, int64_t file_size, int64_t row_count,
+ const SimpleStats& row_stats, int64_t min_sequence_number, int64_t
max_sequence_number,
+ int64_t schema_id, const std::optional<FileSource>& file_source,
+ const std::optional<std::vector<std::string>>& value_stats_cols,
+ const std::optional<std::string>& external_path, const
std::optional<int64_t>& first_row_id,
+ const std::optional<std::vector<std::string>>& write_cols) {
+ return ForAppend(file_name, file_size, row_count, row_stats,
min_sequence_number,
+ max_sequence_number, schema_id,
std::vector<std::optional<std::string>>(),
+ nullptr, file_source, value_stats_cols, external_path,
first_row_id,
+ write_cols);
+}
+
+Result<std::shared_ptr<DataFileMeta>> DataFileMeta::ForAppend(
+ const std::string& file_name, int64_t file_size, int64_t row_count,
+ const SimpleStats& row_stats, int64_t min_sequence_number, int64_t
max_sequence_number,
+ int64_t schema_id, const std::vector<std::optional<std::string>>&
extra_files,
+ const std::shared_ptr<Bytes>& embedded_index, const
std::optional<FileSource>& file_source,
+ const std::optional<std::vector<std::string>>& value_stats_cols,
+ const std::optional<std::string>& external_path, const
std::optional<int64_t>& first_row_id,
+ const std::optional<std::vector<std::string>>& write_cols) {
+ PAIMON_ASSIGN_OR_RAISE(int64_t local_micro,
DateTimeUtils::GetCurrentLocalTimeUs());
+ return std::make_shared<DataFileMeta>(
+ file_name, file_size, row_count, EmptyMinKey(), EmptyMaxKey(),
SimpleStats::EmptyStats(),
+ row_stats, min_sequence_number, max_sequence_number, schema_id,
DUMMY_LEVEL, extra_files,
+ Timestamp(/*millisecond=*/local_micro / 1000,
/*nano_of_millisecond=*/0), 0ll,
+ embedded_index, file_source, value_stats_cols, external_path,
first_row_id, write_cols);
+}
+
+Result<std::shared_ptr<DataFileMeta>> DataFileMeta::Upgrade(int32_t new_level)
const {
+ if (new_level <= level) {
+ return Status::Invalid(
+ fmt::format("new level {} should be greater than current level
{}", new_level, level));
+ }
+ return std::make_shared<DataFileMeta>(
+ file_name, file_size, row_count, min_key, max_key, key_stats,
value_stats,
+ min_sequence_number, max_sequence_number, schema_id, new_level,
extra_files, creation_time,
+ delete_row_count, embedded_index, file_source, value_stats_cols,
external_path,
+ first_row_id, write_cols);
+}
+
+std::shared_ptr<DataFileMeta> DataFileMeta::CopyWithExtraFiles(
+ const std::vector<std::optional<std::string>>& new_extra_files) const {
+ return std::make_shared<DataFileMeta>(
+ file_name, file_size, row_count, min_key, max_key, key_stats,
value_stats,
+ min_sequence_number, max_sequence_number, schema_id, level,
new_extra_files, creation_time,
+ delete_row_count, embedded_index, file_source, value_stats_cols,
external_path,
+ first_row_id, write_cols);
+}
+
+DataFileMeta::DataFileMeta(
+ const std::string& _file_name, int64_t _file_size, int64_t _row_count,
+ const BinaryRow& _min_key, const BinaryRow& _max_key, const SimpleStats&
_key_stats,
+ const SimpleStats& _value_stats, int64_t _min_sequence_number, int64_t
_max_sequence_number,
+ int64_t _schema_id, int32_t _level, const
std::vector<std::optional<std::string>>& _extra_files,
+ const Timestamp& _creation_time, const std::optional<int64_t>&
_delete_row_count,
+ const std::shared_ptr<Bytes>& _embedded_index, const
std::optional<FileSource>& _file_source,
+ const std::optional<std::vector<std::string>>& _value_stats_cols,
+ const std::optional<std::string>& _external_path, const
std::optional<int64_t>& _first_row_id,
+ const std::optional<std::vector<std::string>>& _write_cols)
+ : file_name(_file_name),
+ file_size(_file_size),
+ row_count(_row_count),
+ min_key(_min_key),
+ max_key(_max_key),
+ key_stats(_key_stats),
+ value_stats(_value_stats),
+ min_sequence_number(_min_sequence_number),
+ max_sequence_number(_max_sequence_number),
+ schema_id(_schema_id),
+ level(_level),
+ extra_files(_extra_files),
+ creation_time(_creation_time),
+ delete_row_count(_delete_row_count),
+ embedded_index(_embedded_index),
+ file_source(_file_source),
+ value_stats_cols(_value_stats_cols),
+ external_path(_external_path),
+ first_row_id(_first_row_id),
+ write_cols(_write_cols) {}
+
+Result<std::string> DataFileMeta::FileFormat() const {
+ size_t last_dot_index = file_name.find_last_of(".");
+ if (last_dot_index == std::string::npos || last_dot_index ==
file_name.length() - 1) {
+ return Status::Invalid("cannot find format from file ", file_name);
+ }
+ return file_name.substr(last_dot_index + 1);
+}
+
+std::optional<std::string> DataFileMeta::ExternalPathDir() const {
+ if (!external_path) {
+ return std::nullopt;
+ }
+ return PathUtil::GetParentDirPath(external_path.value());
+}
+
+Result<int64_t> DataFileMeta::CreationTimeEpochMillis() const {
+ PAIMON_ASSIGN_OR_RAISE(Timestamp utc_ts,
DateTimeUtils::ToUTCTimestamp(creation_time));
+ return utc_ts.GetMillisecond();
+}
+
+Result<std::optional<RoaringBitmap32>> DataFileMeta::ToFileSelection(
+ const std::optional<std::vector<Range>>& row_ranges) const {
+ if (!row_ranges) {
+ return std::optional<RoaringBitmap32>();
+ }
+ PAIMON_ASSIGN_OR_RAISE(int64_t start, NonNullFirstRowId());
+ int64_t end = start + row_count - 1;
+ Range file_range(start, end);
+
+ RoaringBitmap32 selection;
+ for (const auto& row_range : row_ranges.value()) {
+ auto intersect_result = Range::Intersection(file_range, row_range);
+ if (intersect_result) {
+
selection.AddRange(static_cast<int32_t>(intersect_result.value().from - start),
+
static_cast<int32_t>(intersect_result.value().to - start + 1));
+ }
+ }
+ if (selection.Cardinality() == row_count) {
+ // If all rows are selected, do not push down selection bitmap.
+ return std::optional<RoaringBitmap32>();
+ }
+ return std::optional<RoaringBitmap32>(selection);
+}
+
+bool DataFileMeta::operator==(const DataFileMeta& other) const {
+ if (this == &other) {
+ return true;
+ }
+ if ((embedded_index && !other.embedded_index) || (!embedded_index &&
other.embedded_index)) {
+ return false;
+ }
+ if (embedded_index && other.embedded_index && !(*embedded_index ==
*other.embedded_index)) {
+ return false;
+ }
+ return file_name == other.file_name && file_size == other.file_size &&
+ row_count == other.row_count && min_key == other.min_key && max_key
== other.max_key &&
+ key_stats == other.key_stats && value_stats == other.value_stats &&
+ min_sequence_number == other.min_sequence_number &&
+ max_sequence_number == other.max_sequence_number && schema_id ==
other.schema_id &&
+ level == other.level && extra_files == other.extra_files &&
+ creation_time == other.creation_time && delete_row_count ==
other.delete_row_count &&
+ file_source == other.file_source && value_stats_cols ==
other.value_stats_cols &&
+ external_path == other.external_path && first_row_id ==
other.first_row_id &&
+ write_cols == other.write_cols;
+}
+
+bool DataFileMeta::operator!=(const DataFileMeta& other) const {
+ return !(*this == other);
+}
+
+bool DataFileMeta::TEST_Equal(const DataFileMeta& other) const {
+ if (this == &other) {
+ return true;
+ }
+ auto compare_optional_ignore_name = [](const std::optional<std::string>&
lhs,
+ const std::optional<std::string>&
rhs) -> bool {
+ if (lhs != rhs) {
+ if (lhs == std::nullopt || rhs == std::nullopt) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ if (extra_files.size() != other.extra_files.size()) {
+ return false;
+ } else {
+ for (size_t i = 0; i < extra_files.size(); ++i) {
+ if (!compare_optional_ignore_name(extra_files[i],
other.extra_files[i])) {
+ return false;
+ }
+ }
+ }
+
+ if ((embedded_index && !other.embedded_index) || (!embedded_index &&
other.embedded_index)) {
+ return false;
+ }
+ if (embedded_index && other.embedded_index && !(*embedded_index ==
*other.embedded_index)) {
+ return false;
+ }
+ // ignore file_name, file_size, extra_files, creation_time and external
path
+ return row_count == other.row_count && min_key == other.min_key && max_key
== other.max_key &&
+ key_stats == other.key_stats && value_stats == other.value_stats &&
+ min_sequence_number == other.min_sequence_number &&
+ max_sequence_number == other.max_sequence_number && schema_id ==
other.schema_id &&
+ level == other.level && delete_row_count == other.delete_row_count
&&
+ file_source == other.file_source && value_stats_cols ==
other.value_stats_cols &&
+ compare_optional_ignore_name(external_path, other.external_path) &&
+ first_row_id == other.first_row_id && write_cols ==
other.write_cols;
+}
+
+std::string DataFileMeta::ToString() const {
+ std::vector<std::string> extra_files_str;
+ for (const auto& file : extra_files) {
+ if (file == std::nullopt) {
+ extra_files_str.emplace_back("null");
+ } else {
+ extra_files_str.emplace_back(file.value());
+ }
+ }
+
+ return fmt::format(
+ "{{fileName: {}, fileSize: {}, rowCount: {}, embeddedIndex: {},
minKey: {}, maxKey: "
+ "{}, "
+ "keyStats: {}, valueStats: {}, minSequenceNumber: {},
maxSequenceNumber: {}, schemaId: "
+ "{}, level: {}, extraFiles: {}, creationTime: {}, deleteRowCount: {},
fileSource: {}, "
+ "valueStatsCols: {}, externalPath: {}, firstRowId: {}, writeCols:
{}}}",
+ file_name, file_size, row_count,
+ embedded_index == nullptr ? "null"
+ : std::string(embedded_index->data(),
embedded_index->size()),
+ min_key.ToString(), max_key.ToString(), key_stats.ToString(),
value_stats.ToString(),
+ min_sequence_number, max_sequence_number, schema_id, level,
extra_files_str,
+ creation_time.ToString(),
+ delete_row_count == std::nullopt ? "null" :
std::to_string(delete_row_count.value()),
+ file_source == std::nullopt ? "null" : file_source.value().ToString(),
+ value_stats_cols == std::nullopt
+ ? "null"
+ : fmt::format("{}", fmt::join(value_stats_cols.value(), ", ")),
+ external_path == std::nullopt ? "null" : external_path.value(),
+ first_row_id == std::nullopt ? "null" :
std::to_string(first_row_id.value()),
+ write_cols == std::nullopt ? "null" : fmt::format("{}",
write_cols.value()));
+}
+
+int64_t DataFileMeta::GetMaxSequenceNumber(
+ const std::vector<std::shared_ptr<DataFileMeta>>& file_metas) {
+ int64_t ret = -1;
+ for (const auto& meta : file_metas) {
+ ret = std::max(ret, meta->max_sequence_number);
+ }
+ return ret;
+}
+
+const std::shared_ptr<arrow::DataType>& DataFileMeta::DataType() {
+ static std::shared_ptr<arrow::DataType> schema = arrow::struct_(
+ {arrow::field("_FILE_NAME", arrow::utf8(), /*nullable=*/false),
+ arrow::field("_FILE_SIZE", arrow::int64(), /*nullable=*/false),
+ arrow::field("_ROW_COUNT", arrow::int64(), /*nullable=*/false),
+ arrow::field("_MIN_KEY", arrow::binary(), /*nullable=*/false),
+ arrow::field("_MAX_KEY", arrow::binary(), /*nullable=*/false),
+ arrow::field("_KEY_STATS", SimpleStats::DataType(),
/*nullable=*/false),
+ arrow::field("_VALUE_STATS", SimpleStats::DataType(),
/*nullable=*/false),
+ arrow::field("_MIN_SEQUENCE_NUMBER", arrow::int64(),
/*nullable=*/false),
+ arrow::field("_MAX_SEQUENCE_NUMBER", arrow::int64(),
/*nullable=*/false),
+ arrow::field("_SCHEMA_ID", arrow::int64(), /*nullable=*/false),
+ arrow::field("_LEVEL", arrow::int32(), /*nullable=*/false),
+ arrow::field("_EXTRA_FILES",
+ arrow::list(arrow::field("item", arrow::utf8(),
/*nullable=*/false)),
+ /*nullable=*/false),
+ arrow::field("_CREATION_TIME",
arrow::timestamp(arrow::TimeUnit::MILLI),
+ /*nullable=*/true),
+ arrow::field("_DELETE_ROW_COUNT", arrow::int64(), /*nullable=*/true),
+ arrow::field("_EMBEDDED_FILE_INDEX", arrow::binary(),
/*nullable=*/true),
+ arrow::field("_FILE_SOURCE", arrow::int8(), /*nullable=*/true),
+ arrow::field("_VALUE_STATS_COLS",
+ arrow::list(arrow::field("item", arrow::utf8(),
/*nullable=*/false)),
+ /*nullable=*/true),
+ arrow::field("_EXTERNAL_PATH", arrow::utf8(), /*nullable=*/true),
+ arrow::field("_FIRST_ROW_ID", arrow::int64(), /*nullable=*/true),
+ arrow::field("_WRITE_COLS",
+ arrow::list(arrow::field("item", arrow::utf8(),
/*nullable=*/false)),
+ /*nullable=*/true)});
+ return schema;
+}
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta.h
b/src/paimon/core/io/data_file_meta.h
new file mode 100644
index 0000000..443e9c4
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta.h
@@ -0,0 +1,166 @@
+/*
+ * 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 <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "fmt/format.h"
+#include "paimon/common/data/binary_row.h"
+#include "paimon/core/manifest/file_source.h"
+#include "paimon/core/stats/simple_stats.h"
+#include "paimon/data/timestamp.h"
+#include "paimon/result.h"
+#include "paimon/status.h"
+#include "paimon/utils/range.h"
+#include "paimon/utils/roaring_bitmap32.h"
+
+namespace arrow {
+class DataType;
+} // namespace arrow
+
+namespace paimon {
+class Bytes;
+
+/// Metadata of a data file.
+struct DataFileMeta {
+ static const BinaryRow& EmptyMinKey();
+ static const BinaryRow& EmptyMaxKey();
+ static constexpr int32_t DUMMY_LEVEL = 0;
+
+ DataFileMeta(const std::string& _file_name, int64_t _file_size, int64_t
_row_count,
+ const BinaryRow& _min_key, const BinaryRow& _max_key,
+ const SimpleStats& _key_stats, const SimpleStats&
_value_stats,
+ int64_t _min_sequence_number, int64_t _max_sequence_number,
int64_t _schema_id,
+ int32_t _level, const
std::vector<std::optional<std::string>>& _extra_files,
+ const Timestamp& _creation_time, const
std::optional<int64_t>& _delete_row_count,
+ const std::shared_ptr<Bytes>& _embedded_index,
+ const std::optional<FileSource>& _file_source,
+ const std::optional<std::vector<std::string>>&
_value_stats_cols,
+ const std::optional<std::string>& _external_path,
+ const std::optional<int64_t>& _first_row_id,
+ const std::optional<std::vector<std::string>>& _write_cols);
+
+ static Result<std::shared_ptr<DataFileMeta>> ForAppend(
+ const std::string& file_name, int64_t file_size, int64_t row_count,
+ const SimpleStats& row_stats, int64_t min_sequence_number, int64_t
max_sequence_number,
+ int64_t schema_id, const std::optional<FileSource>& file_source,
+ const std::optional<std::vector<std::string>>& value_stats_cols,
+ const std::optional<std::string>& external_path, const
std::optional<int64_t>& first_row_id,
+ const std::optional<std::vector<std::string>>& write_cols);
+
+ static Result<std::shared_ptr<DataFileMeta>> ForAppend(
+ const std::string& file_name, int64_t file_size, int64_t row_count,
+ const SimpleStats& row_stats, int64_t min_sequence_number, int64_t
max_sequence_number,
+ int64_t schema_id, const std::vector<std::optional<std::string>>&
extra_files,
+ const std::shared_ptr<Bytes>& embedded_index, const
std::optional<FileSource>& file_source,
+ const std::optional<std::vector<std::string>>& value_stats_cols,
+ const std::optional<std::string>& external_path, const
std::optional<int64_t>& first_row_id,
+ const std::optional<std::vector<std::string>>& write_cols);
+
+ Result<std::shared_ptr<DataFileMeta>> Upgrade(int32_t new_level) const;
+
+ /// Create a copy of this DataFileMeta with the given extra files.
+ std::shared_ptr<DataFileMeta> CopyWithExtraFiles(
+ const std::vector<std::optional<std::string>>& new_extra_files) const;
+
+ std::optional<int64_t> AddRowCount() const {
+ return delete_row_count == std::nullopt ? std::optional<int64_t>()
+ : row_count -
delete_row_count.value();
+ }
+
+ Result<int64_t> CreationTimeEpochMillis() const;
+
+ Result<std::string> FileFormat() const;
+ std::optional<std::string> ExternalPathDir() const;
+
+ bool operator==(const DataFileMeta& other) const;
+ bool operator!=(const DataFileMeta& other) const;
+ bool TEST_Equal(const DataFileMeta& other) const;
+ std::string ToString() const;
+
+ void AssignSequenceNumber(int64_t _min_sequence_number, int64_t
_max_sequence_number) {
+ min_sequence_number = _min_sequence_number;
+ max_sequence_number = _max_sequence_number;
+ }
+
+ void AssignFirstRowId(int64_t _first_row_id) {
+ first_row_id = _first_row_id;
+ }
+
+ Result<int64_t> NonNullFirstRowId() const {
+ if (first_row_id) {
+ return first_row_id.value();
+ }
+ return Status::Invalid(fmt::format("First row id of {} should not be
null.", file_name));
+ }
+
+ // empty row_ranges indicates all rows in the file are needed, return null
bitmap
+ Result<std::optional<RoaringBitmap32>> ToFileSelection(
+ const std::optional<std::vector<Range>>& row_ranges) const;
+
+ static int64_t GetMaxSequenceNumber(
+ const std::vector<std::shared_ptr<DataFileMeta>>& file_metas);
+
+ static const std::shared_ptr<arrow::DataType>& DataType();
+
+ std::string file_name;
+ int64_t file_size;
+ // total number of rows (including add & delete) in this file
+ int64_t row_count;
+ BinaryRow min_key;
+ BinaryRow max_key;
+ SimpleStats key_stats;
+ SimpleStats value_stats;
+
+ int64_t min_sequence_number;
+ int64_t max_sequence_number;
+ int64_t schema_id;
+ int32_t level;
+
+ /// Usage:
+ ///
+ /// Paimon 0.2
+ /// Stores changelog files for `CoreOptions.ChangelogProducer#INPUT`.
Changelog
+ /// files are moved to `DataIncrement` since Paimon 0.3.
+ std::vector<std::optional<std::string>> extra_files;
+ Timestamp creation_time;
+
+ // row_count = add_row_count + delete_row_count
+ // Why don't we keep add_row_count and delete_row_count?
+ // Because in previous versions of DataFileMeta, we only keep row_count.
+ // We have to keep the compatibility.
+ std::optional<int64_t> delete_row_count;
+
+ // file index filter bytes, if it is small, store in data file meta
+ std::shared_ptr<Bytes> embedded_index;
+ std::optional<FileSource> file_source;
+ std::optional<std::vector<std::string>> value_stats_cols;
+
+ // external path of file, if it is null, it is in the default warehouse
path.
+ std::optional<std::string> external_path;
+
+ std::optional<int64_t> first_row_id;
+
+ std::optional<std::vector<std::string>> write_cols;
+};
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta_09_serializer.cpp
b/src/paimon/core/io/data_file_meta_09_serializer.cpp
new file mode 100644
index 0000000..7eaffc9
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_09_serializer.cpp
@@ -0,0 +1,120 @@
+/*
+ * 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 "paimon/core/io/data_file_meta_09_serializer.h"
+
+#include <cassert>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "arrow/api.h"
+#include "paimon/common/data/binary_string.h"
+#include "paimon/common/data/internal_row.h"
+#include "paimon/common/utils/internal_row_utils.h"
+#include "paimon/common/utils/serialization_utils.h"
+#include "paimon/core/manifest/file_source.h"
+#include "paimon/core/stats/simple_stats.h"
+#include "paimon/status.h"
+
+namespace paimon {
+class Bytes;
+class InternalArray;
+
+const std::shared_ptr<arrow::DataType>& DataFileMeta09Serializer::DataType() {
+ static std::shared_ptr<arrow::DataType> schema = arrow::struct_(
+ {arrow::field("_FILE_NAME", arrow::utf8(), /*nullable=*/false),
+ arrow::field("_FILE_SIZE", arrow::int64(), /*nullable=*/false),
+ arrow::field("_ROW_COUNT", arrow::int64(), /*nullable=*/false),
+ arrow::field("_MIN_KEY", arrow::binary(), /*nullable=*/false),
+ arrow::field("_MAX_KEY", arrow::binary(), /*nullable=*/false),
+ arrow::field("_KEY_STATS", SimpleStats::DataType(),
/*nullable=*/false),
+ arrow::field("_VALUE_STATS", SimpleStats::DataType(),
/*nullable=*/false),
+ arrow::field("_MIN_SEQUENCE_NUMBER", arrow::int64(),
/*nullable=*/false),
+ arrow::field("_MAX_SEQUENCE_NUMBER", arrow::int64(),
/*nullable=*/false),
+ arrow::field("_SCHEMA_ID", arrow::int64(), /*nullable=*/false),
+ arrow::field("_LEVEL", arrow::int32(), /*nullable=*/false),
+ arrow::field("_EXTRA_FILES",
+ arrow::list(arrow::field("item", arrow::utf8(),
/*nullable=*/false)),
+ /*nullable=*/false),
+ arrow::field("_CREATION_TIME",
arrow::timestamp(arrow::TimeUnit::MILLI),
+ /*nullable=*/true),
+ arrow::field("_DELETE_ROW_COUNT", arrow::int64(), /*nullable=*/true),
+ arrow::field("_EMBEDDED_FILE_INDEX", arrow::binary(),
/*nullable=*/true),
+ arrow::field("_FILE_SOURCE", arrow::int8(), /*nullable=*/true)});
+ return schema;
+}
+
+Result<BinaryRow> DataFileMeta09Serializer::ToRow(const
std::shared_ptr<DataFileMeta>& meta) const {
+ assert(false);
+ return Status::Invalid("DataFileMeta09Serializer to row is not valid");
+}
+
+Result<std::shared_ptr<DataFileMeta>> DataFileMeta09Serializer::FromRow(
+ const InternalRow& row) const {
+ auto file_name = row.GetString(0);
+ auto file_size = row.GetLong(1);
+ auto row_count = row.GetLong(2);
+ auto min_key = row.GetBinary(3);
+ auto max_key = row.GetBinary(4);
+ auto key_stats_row = row.GetRow(5, 3);
+ auto value_stats_row = row.GetRow(6, 3);
+ auto min_sequence_number = row.GetLong(7);
+ auto max_sequence_number = row.GetLong(8);
+ auto schema_id = row.GetLong(9);
+ auto level = row.GetInt(10);
+ std::shared_ptr<InternalArray> extra_files = row.GetArray(11);
+ auto creation_time = row.GetTimestamp(12, 3);
+
+ assert(min_key && max_key && key_stats_row && value_stats_row);
+ if (extra_files == nullptr) {
+ return Status::Invalid("extra files is empty");
+ }
+
+ std::optional<int64_t> delete_row_count;
+ if (!row.IsNullAt(13)) {
+ delete_row_count = row.GetLong(13);
+ }
+ std::shared_ptr<Bytes> embedded_file_index;
+ if (!row.IsNullAt(14)) {
+ embedded_file_index = row.GetBinary(14);
+ }
+
+ std::optional<FileSource> file_source;
+ if (!row.IsNullAt(15)) {
+ PAIMON_ASSIGN_OR_RAISE(file_source,
FileSource::FromByteValue(row.GetByte(15)));
+ }
+
+ PAIMON_ASSIGN_OR_RAISE(BinaryRow min_values,
SerializationUtils::DeserializeBinaryRow(min_key));
+ PAIMON_ASSIGN_OR_RAISE(BinaryRow max_values,
SerializationUtils::DeserializeBinaryRow(max_key));
+ PAIMON_ASSIGN_OR_RAISE(SimpleStats key_stats,
+ SimpleStats::FromRow(key_stats_row.get(),
pool_.get()));
+ PAIMON_ASSIGN_OR_RAISE(SimpleStats value_stats,
+ SimpleStats::FromRow(value_stats_row.get(),
pool_.get()));
+ return std::make_shared<DataFileMeta>(
+ file_name.ToString(), file_size, row_count, min_values, max_values,
key_stats, value_stats,
+ min_sequence_number, max_sequence_number, schema_id, level,
+ InternalRowUtils::FromStringArrayData(extra_files.get()),
creation_time, delete_row_count,
+ embedded_file_index, file_source,
+ /*value_stats_cols=*/std::nullopt, /*external_path=*/std::nullopt,
+ /*first_row_id=*/std::nullopt,
+ /*write_cols=*/std::nullopt);
+}
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta_09_serializer.h
b/src/paimon/core/io/data_file_meta_09_serializer.h
new file mode 100644
index 0000000..b80b895
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_09_serializer.h
@@ -0,0 +1,45 @@
+/*
+ * 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 <vector>
+
+#include "paimon/common/data/binary_row.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/core/utils/object_serializer.h"
+#include "paimon/result.h"
+
+namespace paimon {
+class InternalRow;
+class MemoryPool;
+
+/// Serializer for `DataFileMeta` with 0.9 version.
+class DataFileMeta09Serializer : public
ObjectSerializer<std::shared_ptr<DataFileMeta>> {
+ public:
+ static const std::shared_ptr<arrow::DataType>& DataType();
+
+ explicit DataFileMeta09Serializer(const std::shared_ptr<MemoryPool>& pool)
+ : ObjectSerializer<std::shared_ptr<DataFileMeta>>(DataType(), pool) {}
+
+ Result<BinaryRow> ToRow(const std::shared_ptr<DataFileMeta>& meta) const
override;
+ Result<std::shared_ptr<DataFileMeta>> FromRow(const InternalRow& row)
const override;
+};
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta_10_serializer.cpp
b/src/paimon/core/io/data_file_meta_10_serializer.cpp
new file mode 100644
index 0000000..fea60ee
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_10_serializer.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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 "paimon/core/io/data_file_meta_10_serializer.h"
+
+#include <cassert>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "arrow/api.h"
+#include "paimon/common/data/binary_string.h"
+#include "paimon/common/data/internal_row.h"
+#include "paimon/common/utils/internal_row_utils.h"
+#include "paimon/common/utils/serialization_utils.h"
+#include "paimon/core/manifest/file_source.h"
+#include "paimon/core/stats/simple_stats.h"
+#include "paimon/status.h"
+
+namespace paimon {
+class Bytes;
+class InternalArray;
+
+const std::shared_ptr<arrow::DataType>& DataFileMeta10Serializer::DataType() {
+ static std::shared_ptr<arrow::DataType> schema = arrow::struct_(
+ {arrow::field("_FILE_NAME", arrow::utf8(), /*nullable=*/false),
+ arrow::field("_FILE_SIZE", arrow::int64(), /*nullable=*/false),
+ arrow::field("_ROW_COUNT", arrow::int64(), /*nullable=*/false),
+ arrow::field("_MIN_KEY", arrow::binary(), /*nullable=*/false),
+ arrow::field("_MAX_KEY", arrow::binary(), /*nullable=*/false),
+ arrow::field("_KEY_STATS", SimpleStats::DataType(),
/*nullable=*/false),
+ arrow::field("_VALUE_STATS", SimpleStats::DataType(),
/*nullable=*/false),
+ arrow::field("_MIN_SEQUENCE_NUMBER", arrow::int64(),
/*nullable=*/false),
+ arrow::field("_MAX_SEQUENCE_NUMBER", arrow::int64(),
/*nullable=*/false),
+ arrow::field("_SCHEMA_ID", arrow::int64(), /*nullable=*/false),
+ arrow::field("_LEVEL", arrow::int32(), /*nullable=*/false),
+ arrow::field("_EXTRA_FILES",
+ arrow::list(arrow::field("item", arrow::utf8(),
/*nullable=*/false)),
+ /*nullable=*/false),
+ arrow::field("_CREATION_TIME",
arrow::timestamp(arrow::TimeUnit::MILLI),
+ /*nullable=*/true),
+ arrow::field("_DELETE_ROW_COUNT", arrow::int64(), /*nullable=*/true),
+ arrow::field("_EMBEDDED_FILE_INDEX", arrow::binary(),
/*nullable=*/true),
+ arrow::field("_FILE_SOURCE", arrow::int8(), /*nullable=*/true),
+ arrow::field("_VALUE_STATS_COLS",
+ arrow::list(arrow::field("item", arrow::utf8(),
/*nullable=*/false)),
+ /*nullable=*/true)});
+ return schema;
+}
+
+Result<BinaryRow> DataFileMeta10Serializer::ToRow(const
std::shared_ptr<DataFileMeta>& meta) const {
+ assert(false);
+ return Status::Invalid("to row for data file meta 10 serializer is
invalid");
+}
+
+Result<std::shared_ptr<DataFileMeta>> DataFileMeta10Serializer::FromRow(
+ const InternalRow& row) const {
+ auto file_name = row.GetString(0);
+ auto file_size = row.GetLong(1);
+ auto row_count = row.GetLong(2);
+ auto min_key = row.GetBinary(3);
+ auto max_key = row.GetBinary(4);
+ auto key_stats_row = row.GetRow(5, 3);
+ auto value_stats_row = row.GetRow(6, 3);
+ auto min_sequence_number = row.GetLong(7);
+ auto max_sequence_number = row.GetLong(8);
+ auto schema_id = row.GetLong(9);
+ auto level = row.GetInt(10);
+ std::shared_ptr<InternalArray> extra_files = row.GetArray(11);
+ auto creation_time = row.GetTimestamp(12, 3);
+
+ assert(min_key && max_key && key_stats_row && value_stats_row);
+ if (extra_files == nullptr) {
+ return Status::Invalid("extra files is empty");
+ }
+
+ std::optional<int64_t> delete_row_count;
+ if (!row.IsNullAt(13)) {
+ delete_row_count = row.GetLong(13);
+ }
+ std::shared_ptr<Bytes> embedded_file_index;
+ if (!row.IsNullAt(14)) {
+ embedded_file_index = row.GetBinary(14);
+ }
+
+ std::optional<FileSource> file_source;
+ if (!row.IsNullAt(15)) {
+ PAIMON_ASSIGN_OR_RAISE(file_source,
FileSource::FromByteValue(row.GetByte(15)));
+ }
+
+ std::optional<std::vector<std::string>> value_stats_cols;
+ if (!row.IsNullAt(16)) {
+ std::shared_ptr<InternalArray> array = row.GetArray(16);
+ if (array == nullptr) {
+ return Status::Invalid("invalid value stats cols");
+ }
+ value_stats_cols =
InternalRowUtils::FromNotNullStringArrayData(array.get());
+ }
+ PAIMON_ASSIGN_OR_RAISE(BinaryRow min_values,
SerializationUtils::DeserializeBinaryRow(min_key));
+ PAIMON_ASSIGN_OR_RAISE(BinaryRow max_values,
SerializationUtils::DeserializeBinaryRow(max_key));
+ PAIMON_ASSIGN_OR_RAISE(SimpleStats key_stats,
+ SimpleStats::FromRow(key_stats_row.get(),
pool_.get()));
+ PAIMON_ASSIGN_OR_RAISE(SimpleStats value_stats,
+ SimpleStats::FromRow(value_stats_row.get(),
pool_.get()));
+ return std::make_shared<DataFileMeta>(
+ file_name.ToString(), file_size, row_count, min_values, max_values,
key_stats, value_stats,
+ min_sequence_number, max_sequence_number, schema_id, level,
+ InternalRowUtils::FromStringArrayData(extra_files.get()),
creation_time, delete_row_count,
+ embedded_file_index, file_source,
std::optional<std::vector<std::string>>(value_stats_cols),
+ /*external_path=*/std::nullopt, /*first_row_id=*/std::nullopt,
/*write_cols=*/std::nullopt);
+}
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta_10_serializer.h
b/src/paimon/core/io/data_file_meta_10_serializer.h
new file mode 100644
index 0000000..236df31
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_10_serializer.h
@@ -0,0 +1,49 @@
+/*
+ * 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 <vector>
+
+#include "paimon/common/data/binary_row.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/core/utils/object_serializer.h"
+#include "paimon/result.h"
+
+namespace arrow {
+class DataType;
+} // namespace arrow
+
+namespace paimon {
+class InternalRow;
+class MemoryPool;
+
+/// Serializer for `DataFileMeta` with 1.0 snapshot version.
+class DataFileMeta10Serializer : public
ObjectSerializer<std::shared_ptr<DataFileMeta>> {
+ public:
+ static const std::shared_ptr<arrow::DataType>& DataType();
+
+ explicit DataFileMeta10Serializer(const std::shared_ptr<MemoryPool>& pool)
+ : ObjectSerializer<std::shared_ptr<DataFileMeta>>(DataType(), pool) {}
+
+ Result<BinaryRow> ToRow(const std::shared_ptr<DataFileMeta>& meta) const
override;
+ Result<std::shared_ptr<DataFileMeta>> FromRow(const InternalRow& row)
const override;
+};
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta_12_serializer.cpp
b/src/paimon/core/io/data_file_meta_12_serializer.cpp
new file mode 100644
index 0000000..fe46a85
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_12_serializer.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 "paimon/core/io/data_file_meta_12_serializer.h"
+
+#include <cassert>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "arrow/api.h"
+#include "paimon/common/data/binary_string.h"
+#include "paimon/common/data/internal_row.h"
+#include "paimon/common/utils/internal_row_utils.h"
+#include "paimon/common/utils/serialization_utils.h"
+#include "paimon/core/manifest/file_source.h"
+#include "paimon/core/stats/simple_stats.h"
+#include "paimon/status.h"
+namespace paimon {
+class Bytes;
+class InternalArray;
+
+const std::shared_ptr<arrow::DataType>& DataFileMeta12Serializer::DataType() {
+ static std::shared_ptr<arrow::DataType> schema = arrow::struct_(
+ {arrow::field("_FILE_NAME", arrow::utf8(), /*nullable=*/false),
+ arrow::field("_FILE_SIZE", arrow::int64(), /*nullable=*/false),
+ arrow::field("_ROW_COUNT", arrow::int64(), /*nullable=*/false),
+ arrow::field("_MIN_KEY", arrow::binary(), /*nullable=*/false),
+ arrow::field("_MAX_KEY", arrow::binary(), /*nullable=*/false),
+ arrow::field("_KEY_STATS", SimpleStats::DataType(),
/*nullable=*/false),
+ arrow::field("_VALUE_STATS", SimpleStats::DataType(),
/*nullable=*/false),
+ arrow::field("_MIN_SEQUENCE_NUMBER", arrow::int64(),
/*nullable=*/false),
+ arrow::field("_MAX_SEQUENCE_NUMBER", arrow::int64(),
/*nullable=*/false),
+ arrow::field("_SCHEMA_ID", arrow::int64(), /*nullable=*/false),
+ arrow::field("_LEVEL", arrow::int32(), /*nullable=*/false),
+
+ arrow::field("_EXTRA_FILES", arrow::list(arrow::field("item",
arrow::utf8(), false)),
+ /*nullable=*/false),
+ arrow::field("_CREATION_TIME",
arrow::timestamp(arrow::TimeUnit::MILLI),
+ /*nullable=*/true),
+ arrow::field("_DELETE_ROW_COUNT", arrow::int64(), /*nullable=*/true),
+ arrow::field("_EMBEDDED_FILE_INDEX", arrow::binary(),
/*nullable=*/true),
+ arrow::field("_FILE_SOURCE", arrow::int8(), /*nullable=*/true),
+ arrow::field("_VALUE_STATS_COLS",
+ arrow::list(arrow::field("f0", arrow::utf8(),
/*nullable=*/false)),
+ /*nullable=*/true),
+ arrow::field("_EXTERNAL_PATH", arrow::utf8(), /*nullable=*/true)});
+ return schema;
+}
+
+Result<BinaryRow> DataFileMeta12Serializer::ToRow(const
std::shared_ptr<DataFileMeta>& meta) const {
+ assert(false);
+ return Status::Invalid("to row for data file meta 12 serializer is
invalid");
+}
+
+Result<std::shared_ptr<DataFileMeta>> DataFileMeta12Serializer::FromRow(
+ const InternalRow& row) const {
+ auto file_name = row.GetString(0);
+ auto file_size = row.GetLong(1);
+ auto row_count = row.GetLong(2);
+ auto min_key = row.GetBinary(3);
+ auto max_key = row.GetBinary(4);
+ auto key_stats_row = row.GetRow(5, 3);
+ auto value_stats_row = row.GetRow(6, 3);
+ auto min_sequence_number = row.GetLong(7);
+ auto max_sequence_number = row.GetLong(8);
+ auto schema_id = row.GetLong(9);
+ auto level = row.GetInt(10);
+ std::shared_ptr<InternalArray> extra_files = row.GetArray(11);
+ auto creation_time = row.GetTimestamp(12, 3);
+
+ assert(min_key && max_key && key_stats_row && value_stats_row);
+ if (extra_files == nullptr) {
+ return Status::Invalid("extra files is empty");
+ }
+
+ std::optional<int64_t> delete_row_count;
+ if (!row.IsNullAt(13)) {
+ delete_row_count = row.GetLong(13);
+ }
+ std::shared_ptr<Bytes> embedded_file_index;
+ if (!row.IsNullAt(14)) {
+ embedded_file_index = row.GetBinary(14);
+ }
+
+ std::optional<FileSource> file_source;
+ if (!row.IsNullAt(15)) {
+ PAIMON_ASSIGN_OR_RAISE(file_source,
FileSource::FromByteValue(row.GetByte(15)));
+ }
+
+ std::optional<std::vector<std::string>> value_stats_cols;
+ if (!row.IsNullAt(16)) {
+ std::shared_ptr<InternalArray> array = row.GetArray(16);
+ if (array == nullptr) {
+ return Status::Invalid("invalid value stats cols");
+ }
+ value_stats_cols =
InternalRowUtils::FromNotNullStringArrayData(array.get());
+ }
+
+ std::optional<std::string> external_path;
+ if (!row.IsNullAt(17)) {
+ external_path = row.GetString(17).ToString();
+ }
+ PAIMON_ASSIGN_OR_RAISE(BinaryRow min_values,
SerializationUtils::DeserializeBinaryRow(min_key));
+ PAIMON_ASSIGN_OR_RAISE(BinaryRow max_values,
SerializationUtils::DeserializeBinaryRow(max_key));
+ PAIMON_ASSIGN_OR_RAISE(SimpleStats key_stats,
+ SimpleStats::FromRow(key_stats_row.get(),
pool_.get()));
+ PAIMON_ASSIGN_OR_RAISE(SimpleStats value_stats,
+ SimpleStats::FromRow(value_stats_row.get(),
pool_.get()));
+ return std::make_shared<DataFileMeta>(
+ file_name.ToString(), file_size, row_count, min_values, max_values,
key_stats, value_stats,
+ min_sequence_number, max_sequence_number, schema_id, level,
+ InternalRowUtils::FromStringArrayData(extra_files.get()),
creation_time, delete_row_count,
+ embedded_file_index, file_source,
std::optional<std::vector<std::string>>(value_stats_cols),
+ external_path, /*first_row_id=*/std::nullopt,
/*write_cols=*/std::nullopt);
+}
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta_12_serializer.h
b/src/paimon/core/io/data_file_meta_12_serializer.h
new file mode 100644
index 0000000..74c29fd
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_12_serializer.h
@@ -0,0 +1,49 @@
+/*
+ * 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 <vector>
+
+#include "paimon/common/data/binary_row.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/core/utils/object_serializer.h"
+#include "paimon/result.h"
+
+namespace arrow {
+class DataType;
+} // namespace arrow
+
+namespace paimon {
+class InternalRow;
+class MemoryPool;
+
+/// Serializer for `DataFileMeta` with 1.2 snapshot version.
+class DataFileMeta12Serializer : public
ObjectSerializer<std::shared_ptr<DataFileMeta>> {
+ public:
+ static const std::shared_ptr<arrow::DataType>& DataType();
+
+ explicit DataFileMeta12Serializer(const std::shared_ptr<MemoryPool>& pool)
+ : ObjectSerializer<std::shared_ptr<DataFileMeta>>(DataType(), pool) {}
+
+ Result<BinaryRow> ToRow(const std::shared_ptr<DataFileMeta>& meta) const
override;
+ Result<std::shared_ptr<DataFileMeta>> FromRow(const InternalRow& row)
const override;
+};
+
+} // namespace paimon
diff --git
a/src/paimon/core/io/data_file_meta_first_row_id_legacy_serializer.cpp
b/src/paimon/core/io/data_file_meta_first_row_id_legacy_serializer.cpp
new file mode 100644
index 0000000..a834e47
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_first_row_id_legacy_serializer.cpp
@@ -0,0 +1,140 @@
+/*
+ * 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 "paimon/core/io/data_file_meta_first_row_id_legacy_serializer.h"
+
+#include <cassert>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "arrow/api.h"
+#include "paimon/common/data/binary_string.h"
+#include "paimon/common/data/internal_row.h"
+#include "paimon/common/utils/internal_row_utils.h"
+#include "paimon/common/utils/serialization_utils.h"
+#include "paimon/core/manifest/file_source.h"
+#include "paimon/core/stats/simple_stats.h"
+#include "paimon/status.h"
+
+namespace paimon {
+class Bytes;
+class InternalArray;
+
+const std::shared_ptr<arrow::DataType>&
DataFileMetaFirstRowIdLegacySerializer::DataType() {
+ static std::shared_ptr<arrow::DataType> schema = arrow::struct_(
+ {arrow::field("_FILE_NAME", arrow::utf8(), /*nullable=*/false),
+ arrow::field("_FILE_SIZE", arrow::int64(), /*nullable=*/false),
+ arrow::field("_ROW_COUNT", arrow::int64(), /*nullable=*/false),
+ arrow::field("_MIN_KEY", arrow::binary(), /*nullable=*/false),
+ arrow::field("_MAX_KEY", arrow::binary(), /*nullable=*/false),
+ arrow::field("_KEY_STATS", SimpleStats::DataType(),
/*nullable=*/false),
+ arrow::field("_VALUE_STATS", SimpleStats::DataType(),
/*nullable=*/false),
+ arrow::field("_MIN_SEQUENCE_NUMBER", arrow::int64(),
/*nullable=*/false),
+ arrow::field("_MAX_SEQUENCE_NUMBER", arrow::int64(),
/*nullable=*/false),
+ arrow::field("_SCHEMA_ID", arrow::int64(), /*nullable=*/false),
+ arrow::field("_LEVEL", arrow::int32(), /*nullable=*/false),
+ arrow::field("_EXTRA_FILES",
+ arrow::list(arrow::field("item", arrow::utf8(),
/*nullable=*/false)),
+ /*nullable=*/false),
+ arrow::field("_CREATION_TIME",
arrow::timestamp(arrow::TimeUnit::NANO), /*nullable=*/true),
+ arrow::field("_DELETE_ROW_COUNT", arrow::int64(), /*nullable=*/true),
+ arrow::field("_EMBEDDED_FILE_INDEX", arrow::binary(),
/*nullable=*/true),
+ arrow::field("_FILE_SOURCE", arrow::int8(), /*nullable=*/true),
+ arrow::field("_VALUE_STATS_COLS",
+ arrow::list(arrow::field("item", arrow::utf8(),
/*nullable=*/false)),
+ /*nullable=*/true),
+ arrow::field("_EXTERNAL_PATH", arrow::utf8(), /*nullable=*/true),
+ arrow::field("_FIRST_ROW_ID", arrow::int64(), /*nullable=*/true)});
+ return schema;
+}
+
+Result<BinaryRow> DataFileMetaFirstRowIdLegacySerializer::ToRow(
+ const std::shared_ptr<DataFileMeta>& meta) const {
+ assert(false);
+ return Status::Invalid("to row for data file meta first row id legacy
serializer is invalid");
+}
+
+Result<std::shared_ptr<DataFileMeta>>
DataFileMetaFirstRowIdLegacySerializer::FromRow(
+ const InternalRow& row) const {
+ auto file_name = row.GetString(0);
+ auto file_size = row.GetLong(1);
+ auto row_count = row.GetLong(2);
+ auto min_key = row.GetBinary(3);
+ auto max_key = row.GetBinary(4);
+ auto key_stats_row = row.GetRow(5, 3);
+ auto value_stats_row = row.GetRow(6, 3);
+ auto min_sequence_number = row.GetLong(7);
+ auto max_sequence_number = row.GetLong(8);
+ auto schema_id = row.GetLong(9);
+ auto level = row.GetInt(10);
+ std::shared_ptr<InternalArray> extra_files = row.GetArray(11);
+ auto creation_time = row.GetTimestamp(12, 3);
+
+ assert(min_key && max_key && key_stats_row && value_stats_row);
+ if (extra_files == nullptr) {
+ return Status::Invalid("extra files is empty");
+ }
+
+ std::optional<int64_t> delete_row_count;
+ if (!row.IsNullAt(13)) {
+ delete_row_count = row.GetLong(13);
+ }
+ std::shared_ptr<Bytes> embedded_file_index;
+ if (!row.IsNullAt(14)) {
+ embedded_file_index = row.GetBinary(14);
+ }
+
+ std::optional<FileSource> file_source;
+ if (!row.IsNullAt(15)) {
+ PAIMON_ASSIGN_OR_RAISE(file_source,
FileSource::FromByteValue(row.GetByte(15)));
+ }
+
+ std::optional<std::vector<std::string>> value_stats_cols;
+ if (!row.IsNullAt(16)) {
+ std::shared_ptr<InternalArray> array = row.GetArray(16);
+ if (array == nullptr) {
+ return Status::Invalid("invalid value stats cols");
+ }
+ value_stats_cols =
InternalRowUtils::FromNotNullStringArrayData(array.get());
+ }
+
+ std::optional<std::string> external_path;
+ if (!row.IsNullAt(17)) {
+ external_path = row.GetString(17).ToString();
+ }
+ std::optional<int64_t> first_row_id;
+ if (!row.IsNullAt(18)) {
+ first_row_id = row.GetLong(18);
+ }
+ PAIMON_ASSIGN_OR_RAISE(BinaryRow min_values,
SerializationUtils::DeserializeBinaryRow(min_key));
+ PAIMON_ASSIGN_OR_RAISE(BinaryRow max_values,
SerializationUtils::DeserializeBinaryRow(max_key));
+ PAIMON_ASSIGN_OR_RAISE(SimpleStats key_stats,
+ SimpleStats::FromRow(key_stats_row.get(),
pool_.get()));
+ PAIMON_ASSIGN_OR_RAISE(SimpleStats value_stats,
+ SimpleStats::FromRow(value_stats_row.get(),
pool_.get()));
+ return std::make_shared<DataFileMeta>(
+ file_name.ToString(), file_size, row_count, min_values, max_values,
key_stats, value_stats,
+ min_sequence_number, max_sequence_number, schema_id, level,
+ InternalRowUtils::FromStringArrayData(extra_files.get()),
creation_time, delete_row_count,
+ embedded_file_index, file_source,
std::optional<std::vector<std::string>>(value_stats_cols),
+ external_path, first_row_id, /*write_cols=*/std::nullopt);
+}
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta_first_row_id_legacy_serializer.h
b/src/paimon/core/io/data_file_meta_first_row_id_legacy_serializer.h
new file mode 100644
index 0000000..e5a447c
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_first_row_id_legacy_serializer.h
@@ -0,0 +1,50 @@
+/*
+ * 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 <vector>
+
+#include "paimon/common/data/binary_row.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/core/utils/object_serializer.h"
+#include "paimon/result.h"
+
+namespace arrow {
+class DataType;
+} // namespace arrow
+
+namespace paimon {
+class InternalRow;
+class MemoryPool;
+
+/// Serializer for `DataFileMeta` with first row id as its last field
+class DataFileMetaFirstRowIdLegacySerializer
+ : public ObjectSerializer<std::shared_ptr<DataFileMeta>> {
+ public:
+ static const std::shared_ptr<arrow::DataType>& DataType();
+
+ explicit DataFileMetaFirstRowIdLegacySerializer(const
std::shared_ptr<MemoryPool>& pool)
+ : ObjectSerializer<std::shared_ptr<DataFileMeta>>(DataType(), pool) {}
+
+ Result<BinaryRow> ToRow(const std::shared_ptr<DataFileMeta>& meta) const
override;
+ Result<std::shared_ptr<DataFileMeta>> FromRow(const InternalRow& row)
const override;
+};
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta_serializer.cpp
b/src/paimon/core/io/data_file_meta_serializer.cpp
new file mode 100644
index 0000000..5602996
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_serializer.cpp
@@ -0,0 +1,177 @@
+/*
+ * 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 "paimon/core/io/data_file_meta_serializer.h"
+
+#include <cassert>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "paimon/common/data/binary_row_writer.h"
+#include "paimon/common/data/binary_string.h"
+#include "paimon/common/data/internal_row.h"
+#include "paimon/common/utils/internal_row_utils.h"
+#include "paimon/common/utils/serialization_utils.h"
+#include "paimon/core/manifest/file_source.h"
+#include "paimon/core/stats/simple_stats.h"
+#include "paimon/data/timestamp.h"
+#include "paimon/status.h"
+
+namespace paimon {
+
+class Bytes;
+class InternalArray;
+class MemoryPool;
+
+Result<BinaryRow> DataFileMetaSerializer::ToRow(const
std::shared_ptr<DataFileMeta>& meta) const {
+ BinaryRow row(20);
+ BinaryRowWriter writer(&row, 32 * 1024, pool_.get());
+ writer.WriteString(0, BinaryString::FromString(meta->file_name,
pool_.get()));
+ writer.WriteLong(1, meta->file_size);
+ writer.WriteLong(2, meta->row_count);
+ auto min_key_bytes = SerializationUtils::SerializeBinaryRow(meta->min_key,
pool_.get());
+ writer.WriteBinary(3, *min_key_bytes);
+ auto max_key_bytes = SerializationUtils::SerializeBinaryRow(meta->max_key,
pool_.get());
+ writer.WriteBinary(4, *max_key_bytes);
+ writer.WriteRow(5, meta->key_stats.ToRow());
+ writer.WriteRow(6, meta->value_stats.ToRow());
+ writer.WriteLong(7, meta->min_sequence_number);
+ writer.WriteLong(8, meta->max_sequence_number);
+ writer.WriteLong(9, meta->schema_id);
+ writer.WriteInt(10, meta->level);
+ writer.WriteArray(11,
InternalRowUtils::ToStringArrayData(meta->extra_files, pool_));
+ writer.WriteTimestamp(12, meta->creation_time, 3);
+ if (meta->delete_row_count == std::nullopt) {
+ writer.SetNullAt(13);
+ } else {
+ writer.WriteLong(13, meta->delete_row_count.value());
+ }
+ if (meta->embedded_index == nullptr) {
+ writer.SetNullAt(14);
+ } else {
+ writer.WriteBinary(14, *meta->embedded_index);
+ }
+ if (meta->file_source == std::nullopt) {
+ writer.SetNullAt(15);
+ } else {
+ writer.WriteByte(15, meta->file_source.value().ToByteValue());
+ }
+ if (meta->value_stats_cols == std::nullopt) {
+ writer.SetNullAt(16);
+ } else {
+ writer.WriteArray(
+ 16,
InternalRowUtils::ToNotNullStringArrayData(meta->value_stats_cols.value(),
pool_));
+ }
+ if (meta->external_path == std::nullopt) {
+ writer.SetNullAt(17);
+ } else {
+ writer.WriteString(17,
BinaryString::FromString(meta->external_path.value(), pool_.get()));
+ }
+ if (meta->first_row_id == std::nullopt) {
+ writer.SetNullAt(18);
+ } else {
+ writer.WriteLong(18, meta->first_row_id.value());
+ }
+ if (meta->write_cols == std::nullopt) {
+ writer.SetNullAt(19);
+ } else {
+ writer.WriteArray(
+ 19,
InternalRowUtils::ToNotNullStringArrayData(meta->write_cols.value(), pool_));
+ }
+ writer.Complete();
+ return row;
+}
+
+Result<std::shared_ptr<DataFileMeta>> DataFileMetaSerializer::FromRow(
+ const InternalRow& row) const {
+ auto file_name = row.GetString(0);
+ auto file_size = row.GetLong(1);
+ auto row_count = row.GetLong(2);
+ auto min_key = row.GetBinary(3);
+ auto max_key = row.GetBinary(4);
+ auto key_stats_row = row.GetRow(5, 3);
+ auto value_stats_row = row.GetRow(6, 3);
+ auto min_sequence_number = row.GetLong(7);
+ auto max_sequence_number = row.GetLong(8);
+ auto schema_id = row.GetLong(9);
+ auto level = row.GetInt(10);
+ std::shared_ptr<InternalArray> extra_files = row.GetArray(11);
+ auto creation_time = row.GetTimestamp(12, 3);
+
+ assert(min_key && max_key && key_stats_row && value_stats_row);
+ if (extra_files == nullptr) {
+ return Status::Invalid("extra files is empty");
+ }
+
+ std::optional<int64_t> delete_row_count;
+ if (!row.IsNullAt(13)) {
+ delete_row_count = row.GetLong(13);
+ }
+ std::shared_ptr<Bytes> embedded_file_index;
+ if (!row.IsNullAt(14)) {
+ embedded_file_index = row.GetBinary(14);
+ }
+
+ std::optional<FileSource> file_source;
+ if (!row.IsNullAt(15)) {
+ PAIMON_ASSIGN_OR_RAISE(file_source,
FileSource::FromByteValue(row.GetByte(15)));
+ }
+
+ std::optional<std::vector<std::string>> value_stats_cols;
+ if (!row.IsNullAt(16)) {
+ std::shared_ptr<InternalArray> array = row.GetArray(16);
+ if (array == nullptr) {
+ return Status::Invalid("invalid value stats cols");
+ }
+ value_stats_cols =
InternalRowUtils::FromNotNullStringArrayData(array.get());
+ }
+
+ std::optional<std::string> external_path;
+ if (!row.IsNullAt(17)) {
+ external_path = row.GetString(17).ToString();
+ }
+ std::optional<int64_t> first_row_id;
+ if (!row.IsNullAt(18)) {
+ first_row_id = row.GetLong(18);
+ }
+
+ std::optional<std::vector<std::string>> write_cols;
+ if (!row.IsNullAt(19)) {
+ std::shared_ptr<InternalArray> array = row.GetArray(19);
+ if (array == nullptr) {
+ return Status::Invalid("invalid write cols");
+ }
+ write_cols = InternalRowUtils::FromNotNullStringArrayData(array.get());
+ }
+ PAIMON_ASSIGN_OR_RAISE(BinaryRow min_values,
SerializationUtils::DeserializeBinaryRow(min_key));
+ PAIMON_ASSIGN_OR_RAISE(BinaryRow max_values,
SerializationUtils::DeserializeBinaryRow(max_key));
+ PAIMON_ASSIGN_OR_RAISE(SimpleStats key_stats,
+ SimpleStats::FromRow(key_stats_row.get(),
pool_.get()));
+ PAIMON_ASSIGN_OR_RAISE(SimpleStats value_stats,
+ SimpleStats::FromRow(value_stats_row.get(),
pool_.get()));
+ return std::make_shared<DataFileMeta>(
+ file_name.ToString(), file_size, row_count, min_values, max_values,
key_stats, value_stats,
+ min_sequence_number, max_sequence_number, schema_id, level,
+ InternalRowUtils::FromStringArrayData(extra_files.get()),
creation_time, delete_row_count,
+ embedded_file_index, file_source,
std::optional<std::vector<std::string>>(value_stats_cols),
+ external_path, first_row_id, write_cols);
+}
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta_serializer.h
b/src/paimon/core/io/data_file_meta_serializer.h
new file mode 100644
index 0000000..5713df1
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_serializer.h
@@ -0,0 +1,45 @@
+/*
+ * 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 <vector>
+
+#include "arrow/status.h"
+#include "paimon/common/data/binary_row.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/core/utils/object_serializer.h"
+#include "paimon/result.h"
+
+namespace paimon {
+class InternalRow;
+class MemoryPool;
+
+/// Serializer for `DataFileMeta`.
+class DataFileMetaSerializer : public
ObjectSerializer<std::shared_ptr<DataFileMeta>> {
+ public:
+ explicit DataFileMetaSerializer(const std::shared_ptr<MemoryPool>& pool)
+ :
ObjectSerializer<std::shared_ptr<DataFileMeta>>(DataFileMeta::DataType(), pool)
{}
+
+ Result<BinaryRow> ToRow(const std::shared_ptr<DataFileMeta>& meta) const
override;
+
+ Result<std::shared_ptr<DataFileMeta>> FromRow(const InternalRow& row)
const override;
+};
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_meta_serializer_test.cpp
b/src/paimon/core/io/data_file_meta_serializer_test.cpp
new file mode 100644
index 0000000..18ba257
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_serializer_test.cpp
@@ -0,0 +1,93 @@
+/*
+ * 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 "paimon/core/io/data_file_meta_serializer.h"
+
+#include <cstdint>
+#include <optional>
+#include <string>
+
+#include "arrow/api.h"
+#include "arrow/array/builder_base.h"
+#include "gtest/gtest.h"
+#include "paimon/common/io/memory_segment_output_stream.h"
+#include "paimon/common/memory/memory_segment_utils.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/core/stats/simple_stats.h"
+#include "paimon/data/timestamp.h"
+#include "paimon/io/byte_array_input_stream.h"
+#include "paimon/io/data_input_stream.h"
+#include "paimon/memory/bytes.h"
+#include "paimon/memory/memory_pool.h"
+#include "paimon/status.h"
+#include "paimon/testing/utils/testharness.h"
+
+namespace arrow {
+class Array;
+} // namespace arrow
+
+namespace paimon::test {
+class DataFileMetaSerializerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ memory_pool_ = GetDefaultPool();
+ }
+
+ private:
+ std::shared_ptr<DataFileMeta> GetDataFileMeta() {
+ return std::make_shared<DataFileMeta>(
+ "some_file_name", 1024, 8, DataFileMeta::EmptyMinKey(),
DataFileMeta::EmptyMaxKey(),
+ SimpleStats::EmptyStats(), SimpleStats::EmptyStats(),
/*min_seq_no=*/16,
+ /*max_seq_no=*/32,
+ /*schema_id=*/1, /*level=*/2,
/*extra_files=*/std::vector<std::optional<std::string>>(),
+ /*creation_time=*/Timestamp(0, 0), /*delete_row_count=*/3,
+ /*embedded_index=*/nullptr, /*file_source=*/std::nullopt,
+ /*value_stats_cols=*/std::nullopt,
/*external_path=*/std::optional<std::string>(),
+ /*first_row_id=*/std::nullopt, /*write_cols=*/std::nullopt);
+ }
+
+ const int32_t TRIES = 100;
+
+ std::shared_ptr<MemoryPool> memory_pool_;
+};
+
+TEST_F(DataFileMetaSerializerTest, TestToFromRow) {
+ DataFileMetaSerializer serializer(memory_pool_);
+ auto expected = GetDataFileMeta();
+ for (int32_t i = 0; i < TRIES; i++) {
+ ASSERT_OK_AND_ASSIGN(auto row, serializer.ToRow(expected));
+ ASSERT_OK_AND_ASSIGN(auto actual, serializer.FromRow(row));
+ ASSERT_EQ(expected->ToString(), actual->ToString());
+ }
+}
+
+TEST_F(DataFileMetaSerializerTest, TestSerialize) {
+ DataFileMetaSerializer serializer(memory_pool_);
+ auto expected = GetDataFileMeta();
+ for (int32_t i = 0; i < TRIES; i++) {
+ MemorySegmentOutputStream out(1024, memory_pool_);
+ ASSERT_OK(serializer.Serialize(expected, &out));
+ PAIMON_UNIQUE_PTR<Bytes> bytes = MemorySegmentUtils::CopyToBytes(
+ out.Segments(), 0, out.CurrentSize(), memory_pool_.get());
+ auto input_stream =
std::make_shared<ByteArrayInputStream>(bytes->data(), bytes->size());
+ DataInputStream in(input_stream);
+ ASSERT_OK_AND_ASSIGN(auto actual, serializer.Deserialize(&in));
+ ASSERT_EQ(expected->ToString(), actual->ToString());
+ }
+}
+} // namespace paimon::test
diff --git a/src/paimon/core/io/data_file_meta_test.cpp
b/src/paimon/core/io/data_file_meta_test.cpp
new file mode 100644
index 0000000..78b27b8
--- /dev/null
+++ b/src/paimon/core/io/data_file_meta_test.cpp
@@ -0,0 +1,203 @@
+/*
+ * 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 "paimon/core/io/data_file_meta.h"
+
+#include "gtest/gtest.h"
+#include "paimon/status.h"
+#include "paimon/testing/utils/testharness.h"
+
+namespace paimon::test {
+TEST(DataFileMetaTest, TestAddRowCount) {
+ DataFileMeta file_meta("data-80110e15-97b5-4bcf-ac09-6ca2659a4950-0.orc",
/*file_size=*/645,
+ /*row_count=*/5, BinaryRow::EmptyRow(),
BinaryRow::EmptyRow(),
+ SimpleStats::EmptyStats(),
SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/0,
/*max_sequence_number=*/4, /*schema_id=*/0,
+ /*level=*/0, /*extra_files=*/{},
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/2, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/std::nullopt,
/*first_row_id=*/std::nullopt,
+ /*write_cols=*/std::nullopt);
+ ASSERT_EQ(3, file_meta.AddRowCount().value());
+ // test null delete row count
+ file_meta.delete_row_count = std::nullopt;
+ ASSERT_EQ(std::nullopt, file_meta.AddRowCount());
+}
+
+TEST(DataFileMetaTest, TestFileFormat) {
+ DataFileMeta file_meta("data-80110e15-97b5-4bcf-ac09-6ca2659a4950-0.orc",
/*file_size=*/645,
+ /*row_count=*/5, BinaryRow::EmptyRow(),
BinaryRow::EmptyRow(),
+ SimpleStats::EmptyStats(),
SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/0,
/*max_sequence_number=*/4, /*schema_id=*/0,
+ /*level=*/0, /*extra_files=*/{},
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/2, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/std::nullopt,
/*first_row_id=*/std::nullopt,
+ /*write_cols=*/std::nullopt);
+ ASSERT_OK_AND_ASSIGN(auto file_format, file_meta.FileFormat());
+ ASSERT_EQ("orc", file_format);
+ file_meta.file_name =
"data-80110e15-97b5-4bcf-ac09-6ca2659a4950-0.parquet";
+ ASSERT_OK_AND_ASSIGN(file_format, file_meta.FileFormat());
+ ASSERT_EQ("parquet", file_format);
+ // test invalid data file name
+ file_meta.file_name = "data-80110e15-97b5-4bcf-ac09-6ca2659a4950-0";
+ ASSERT_NOK_WITH_MSG(file_meta.FileFormat(),
+ "cannot find format from file
data-80110e15-97b5-4bcf-ac09-6ca2659a4950-0");
+}
+
+TEST(DataFileMetaTest, TestExternalPathDir) {
+ DataFileMeta file_meta(
+ "data-80110e15-97b5-4bcf-ac09-6ca2659a4950-0.orc", /*file_size=*/645,
/*row_count=*/5,
+ BinaryRow::EmptyRow(), BinaryRow::EmptyRow(),
SimpleStats::EmptyStats(),
+ SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/0, /*max_sequence_number=*/4, /*schema_id=*/0,
+ /*level=*/0, /*extra_files=*/{},
/*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/0, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+
/*external_path=*/"file:/tmp/bucket-0/data-80110e15-97b5-4bcf-ac09-6ca2659a4950-0.orc",
+ /*first_row_id=*/std::nullopt, /*write_cols=*/std::nullopt);
+ ASSERT_EQ("file:/tmp/bucket-0", file_meta.ExternalPathDir().value());
+ file_meta.external_path = std::nullopt;
+ ASSERT_EQ(std::nullopt, file_meta.ExternalPathDir());
+}
+
+TEST(DataFileMetaTest, TestGetMaxSequenceNumber) {
+ auto file_meta1 = std::make_shared<DataFileMeta>(
+ "data-80110e15-97b5-4bcf-ac09-6ca2659a4950-0.orc", /*file_size=*/645,
+ /*row_count=*/5, BinaryRow::EmptyRow(), BinaryRow::EmptyRow(),
SimpleStats::EmptyStats(),
+ SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/0, /*max_sequence_number=*/4, /*schema_id=*/0,
+ /*level=*/0, /*extra_files=*/std::vector<std::optional<std::string>>(),
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/2, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/std::nullopt, /*first_row_id=*/std::nullopt,
/*write_cols=*/std::nullopt);
+ auto file_meta2 = std::make_shared<DataFileMeta>(
+ "data-80110e15-97b5-4bcf-ac09-6ca2659a4950-1.orc", /*file_size=*/645,
+ /*row_count=*/5, BinaryRow::EmptyRow(), BinaryRow::EmptyRow(),
SimpleStats::EmptyStats(),
+ SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/5, /*max_sequence_number=*/10, /*schema_id=*/0,
+ /*level=*/0, /*extra_files=*/std::vector<std::optional<std::string>>(),
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/2, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/std::nullopt, /*first_row_id=*/std::nullopt,
/*write_cols=*/std::nullopt);
+ ASSERT_EQ(4, DataFileMeta::GetMaxSequenceNumber({file_meta1}));
+ ASSERT_EQ(10, DataFileMeta::GetMaxSequenceNumber({file_meta1,
file_meta2}));
+ ASSERT_EQ(-1, DataFileMeta::GetMaxSequenceNumber({}));
+ ASSERT_EQ(file_meta1, file_meta1);
+ ASSERT_NE(file_meta1, file_meta2);
+}
+
+TEST(DataFileMetaTest, TestNonNullFirstRowId) {
+ {
+ auto file_meta = std::make_shared<DataFileMeta>(
+ "data-0.orc", /*file_size=*/645,
+ /*row_count=*/5, BinaryRow::EmptyRow(), BinaryRow::EmptyRow(),
+ SimpleStats::EmptyStats(), SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/0, /*max_sequence_number=*/4,
/*schema_id=*/0,
+ /*level=*/0,
/*extra_files=*/std::vector<std::optional<std::string>>(),
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/2, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/std::nullopt, /*first_row_id=*/100,
/*write_cols=*/std::nullopt);
+ ASSERT_OK_AND_ASSIGN(int64_t first_row_id,
file_meta->NonNullFirstRowId());
+ ASSERT_EQ(100, first_row_id);
+ }
+ {
+ auto file_meta = std::make_shared<DataFileMeta>(
+ "data-1.orc", /*file_size=*/645,
+ /*row_count=*/5, BinaryRow::EmptyRow(), BinaryRow::EmptyRow(),
+ SimpleStats::EmptyStats(), SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/0, /*max_sequence_number=*/4,
/*schema_id=*/0,
+ /*level=*/0,
/*extra_files=*/std::vector<std::optional<std::string>>(),
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/2, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/std::nullopt, /*first_row_id=*/std::nullopt,
+ /*write_cols=*/std::nullopt);
+ ASSERT_NOK_WITH_MSG(file_meta->NonNullFirstRowId(),
+ "First row id of data-1.orc should not be null.");
+ }
+}
+
+TEST(DataFileMetaTest, TestToFileSelection) {
+ // row id range [100, 110)
+ auto file_meta = std::make_shared<DataFileMeta>(
+ "data-0.orc", /*file_size=*/645,
+ /*row_count=*/10, BinaryRow::EmptyRow(), BinaryRow::EmptyRow(),
SimpleStats::EmptyStats(),
+ SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/100, /*max_sequence_number=*/109,
/*schema_id=*/0,
+ /*level=*/0, /*extra_files=*/std::vector<std::optional<std::string>>(),
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/0, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/std::nullopt, /*first_row_id=*/100,
/*write_cols=*/std::nullopt);
+
+ {
+ ASSERT_OK_AND_ASSIGN(std::optional<RoaringBitmap32> result,
+ file_meta->ToFileSelection(std::nullopt));
+ ASSERT_FALSE(result);
+ }
+ {
+ std::vector<Range> ranges = {Range(10l, 20l), Range(105l, 107l),
Range(500l, 520l)};
+ ASSERT_OK_AND_ASSIGN(std::optional<RoaringBitmap32> result,
+ file_meta->ToFileSelection(ranges));
+ ASSERT_TRUE(result);
+ ASSERT_EQ(result.value().ToString(), "{5,6,7}");
+ }
+ {
+ std::vector<Range> ranges = {};
+ ASSERT_OK_AND_ASSIGN(std::optional<RoaringBitmap32> result,
+ file_meta->ToFileSelection(ranges));
+ ASSERT_TRUE(result);
+ ASSERT_EQ(result.value().ToString(), "{}");
+ }
+ {
+ std::vector<Range> ranges = {Range(100l, 109l)};
+ ASSERT_OK_AND_ASSIGN(std::optional<RoaringBitmap32> result,
+ file_meta->ToFileSelection(ranges));
+ ASSERT_FALSE(result);
+ }
+}
+
+TEST(DataFileMetaTest, TestUpgrade) {
+ auto file_meta = std::make_shared<DataFileMeta>(
+ "data-0.orc", /*file_size=*/645,
+ /*row_count=*/10, BinaryRow::EmptyRow(), BinaryRow::EmptyRow(),
SimpleStats::EmptyStats(),
+ SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/100, /*max_sequence_number=*/109,
/*schema_id=*/0,
+ /*level=*/5, /*extra_files=*/std::vector<std::optional<std::string>>(),
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/0, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/std::nullopt, /*first_row_id=*/100,
/*write_cols=*/std::nullopt);
+ // test normal upgrade
+ ASSERT_OK_AND_ASSIGN(auto new_file_meta, file_meta->Upgrade(10));
+ ASSERT_EQ(new_file_meta->level, 10);
+ // check other members
+ file_meta->level = 10;
+ ASSERT_EQ(*new_file_meta, *file_meta);
+
+ // test invalid upgrade
+ ASSERT_NOK_WITH_MSG(file_meta->Upgrade(1),
+ "new level 1 should be greater than current level 10");
+}
+} // namespace paimon::test
diff --git a/src/paimon/core/io/data_file_path_factory.cpp
b/src/paimon/core/io/data_file_path_factory.cpp
new file mode 100644
index 0000000..1721436
--- /dev/null
+++ b/src/paimon/core/io/data_file_path_factory.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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 "paimon/core/io/data_file_path_factory.h"
+
+#include <optional>
+#include <utility>
+
+#include "paimon/common/utils/path_util.h"
+#include "paimon/common/utils/uuid.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/macros.h"
+#include "paimon/status.h"
+
+namespace paimon {
+
+const char DataFilePathFactory::CHANGELOG_FILE_PREFIX[] = "changelog-";
+const char DataFilePathFactory::INDEX_PATH_SUFFIX[] = ".index";
+
+Status DataFilePathFactory::Init(const std::string& parent, const std::string&
format_identifier,
+ const std::string& data_file_prefix,
+ std::unique_ptr<ExternalPathProvider>&&
external_path_provider) {
+ if (PAIMON_UNLIKELY(!UUID::Generate(&uuid_))) {
+ return Status::Invalid("fail to generate uuid for data file path
factory");
+ }
+ parent_ = parent;
+ path_count_.store(0);
+ format_identifier_ = format_identifier;
+ data_file_prefix_ = data_file_prefix;
+ external_path_provider_ = std::move(external_path_provider);
+ return Status::OK();
+}
+
+std::string DataFilePathFactory::NewPath(const std::string& prefix) const {
+ return NewPathFromName(NewFileName(prefix));
+}
+
+std::string DataFilePathFactory::ToPath(const std::string& file_name) const {
+ return PathUtil::JoinPath(parent_, file_name);
+}
+
+std::string DataFilePathFactory::ToPath(const std::shared_ptr<DataFileMeta>&
file_meta) const {
+ if (file_meta->external_path) {
+ return file_meta->external_path.value();
+ }
+ return PathUtil::JoinPath(parent_, file_meta->file_name);
+}
+
+std::string DataFilePathFactory::ToFileIndexPath(const std::string& file_path)
const {
+ std::string parent = PathUtil::GetParentDirPath(file_path);
+ return PathUtil::JoinPath(parent, PathUtil::GetName(file_path) +
INDEX_PATH_SUFFIX);
+}
+
+std::string DataFilePathFactory::ToAlignedPath(const std::string& file_name,
+ const
std::shared_ptr<DataFileMeta>& aligned) const {
+ auto external_path = aligned->ExternalPathDir();
+ return PathUtil::JoinPath(external_path ? external_path.value() : parent_,
file_name);
+}
+
+std::vector<std::string> DataFilePathFactory::CollectFiles(
+ const std::shared_ptr<DataFileMeta>& file_meta) const {
+ std::vector<std::string> paths;
+ paths.push_back(ToPath(file_meta));
+ for (const auto& extra_file : file_meta->extra_files) {
+ if (extra_file) {
+ paths.push_back(ToAlignedPath(extra_file.value(), file_meta));
+ }
+ }
+ return paths;
+}
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_path_factory.h
b/src/paimon/core/io/data_file_path_factory.h
new file mode 100644
index 0000000..b49154f
--- /dev/null
+++ b/src/paimon/core/io/data_file_path_factory.h
@@ -0,0 +1,112 @@
+/*
+ * 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 <atomic>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "paimon/common/fs/external_path_provider.h"
+#include "paimon/common/utils/path_util.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/core/utils/path_factory.h"
+
+namespace paimon {
+
+class Status;
+struct DataFileMeta;
+
+/// %Factory which produces new paths and converts paths for data files.
+class DataFilePathFactory : public PathFactory {
+ public:
+ static const char CHANGELOG_FILE_PREFIX[];
+ static const char INDEX_PATH_SUFFIX[];
+
+ Status Init(const std::string& parent, const std::string&
format_identifier,
+ const std::string& data_file_prefix,
+ std::unique_ptr<ExternalPathProvider>&&
external_path_provider);
+
+ const std::string& Parent() const {
+ return parent_;
+ }
+
+ const std::string& DataFilePrefix() const {
+ return data_file_prefix_;
+ }
+
+ std::string NewPath() const override {
+ return NewPath(data_file_prefix_);
+ }
+
+ std::string NewChangelogPath() const {
+ return NewPath(std::string(CHANGELOG_FILE_PREFIX));
+ }
+
+ std::string NewBlobPath() const {
+ return NewPathFromName(NewFileName(data_file_prefix_, ".blob"));
+ }
+
+ std::string NewPathFromName(const std::string& file_name) const {
+ if (external_path_provider_ != nullptr) {
+ return external_path_provider_->GetNextExternalDataPath(file_name);
+ }
+ return PathUtil::JoinPath(parent_, file_name);
+ }
+
+ std::string ToPath(const std::string& file_name) const override;
+ std::string ToPath(const std::shared_ptr<DataFileMeta>& file_meta) const;
+
+ const std::string& GetUUID() const {
+ return uuid_;
+ }
+
+ std::string ToFileIndexPath(const std::string& file_path) const;
+ std::string ToAlignedPath(const std::string& file_name,
+ const std::shared_ptr<DataFileMeta>& aligned)
const;
+
+ std::vector<std::string> CollectFiles(const std::shared_ptr<DataFileMeta>&
file_meta) const;
+ bool IsExternalPath() const {
+ return external_path_provider_ != nullptr;
+ }
+
+ private:
+ std::string NewPath(const std::string& prefix) const;
+
+ std::string NewFileName(const std::string& prefix) const {
+ // TODO(yonghao.fyh): add compress extension as java paimon if needed
+ std::string extension = "." + format_identifier_;
+ return NewFileName(prefix, extension);
+ }
+
+ std::string NewFileName(const std::string& prefix, const std::string&
extension) const {
+ return prefix + uuid_ + "-" + std::to_string(path_count_.fetch_add(1))
+ extension;
+ }
+
+ private:
+ std::string parent_;
+ std::string uuid_;
+ mutable std::atomic<int32_t> path_count_;
+ std::string format_identifier_;
+ std::string data_file_prefix_;
+ std::unique_ptr<ExternalPathProvider> external_path_provider_;
+};
+
+} // namespace paimon
diff --git a/src/paimon/core/io/data_file_path_factory_test.cpp
b/src/paimon/core/io/data_file_path_factory_test.cpp
new file mode 100644
index 0000000..293f14a
--- /dev/null
+++ b/src/paimon/core/io/data_file_path_factory_test.cpp
@@ -0,0 +1,145 @@
+/*
+ * 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 "paimon/core/io/data_file_path_factory.h"
+
+#include <optional>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "paimon/common/data/binary_row.h"
+#include "paimon/common/fs/external_path_provider.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/core/manifest/file_source.h"
+#include "paimon/core/stats/simple_stats.h"
+#include "paimon/data/timestamp.h"
+#include "paimon/result.h"
+#include "paimon/status.h"
+#include "paimon/testing/utils/testharness.h"
+
+namespace paimon::test {
+
+class DataFilePathFactoryTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Initialize the DataFilePathFactory with a parent directory and
format identifier
+ ASSERT_OK(factory_.Init(/*parent=*/"/tmp", /*format_identifier=*/"txt",
+ /*data_file_prefix=*/"data-",
/*external_path_provider=*/nullptr));
+ }
+
+ DataFilePathFactory factory_;
+};
+
+TEST_F(DataFilePathFactoryTest, TestNewPath) {
+ std::string path1 = factory_.NewPath();
+ std::string path2 = factory_.NewPath();
+
+ // Ensure that the paths are unique
+ ASSERT_NE(path1, path2);
+ ASSERT_TRUE(path1.find("/tmp/data-") != std::string::npos);
+ ASSERT_TRUE(path2.find("/tmp/data-") != std::string::npos);
+ // test Parent() and NewPathFromName()
+ ASSERT_EQ(factory_.Parent(), "/tmp");
+ ASSERT_EQ(factory_.NewPathFromName("index-file"), "/tmp/index-file");
+}
+
+TEST_F(DataFilePathFactoryTest, TestNewPathWithDataFilePrefixAndExternalPath) {
+ DataFilePathFactory factory;
+ ASSERT_OK_AND_ASSIGN(
+ std::unique_ptr<ExternalPathProvider> external_path_provider,
+ ExternalPathProvider::Create({"/tmp/external_path/"},
"p0=1/p1=0/bucket-0"));
+
+ ASSERT_OK(factory_.Init(/*parent=*/"/tmp/p0=1/p1=0/bucket-0/",
/*format_identifier=*/"txt",
+ /*data_file_prefix=*/"test-data-",
std::move(external_path_provider)));
+ std::string path1 = factory_.NewPath();
+ std::string path2 = factory_.NewPath();
+
+ // Ensure that the paths are unique
+ ASSERT_NE(path1, path2);
+ ASSERT_TRUE(path1.find("/tmp/external_path/p0=1/p1=0/bucket-0/test-data-")
!=
+ std::string::npos);
+ ASSERT_TRUE(path2.find("/tmp/external_path/p0=1/p1=0/bucket-0/test-data-")
!=
+ std::string::npos);
+
+ // test Parent() and NewPathFromName()
+ ASSERT_EQ(factory_.Parent(), "/tmp/p0=1/p1=0/bucket-0/");
+ ASSERT_EQ(factory_.NewPathFromName("index-file"),
+ "/tmp/external_path/p0=1/p1=0/bucket-0/index-file");
+}
+
+TEST_F(DataFilePathFactoryTest, TestToPath) {
+ std::string file_name = "example.txt";
+
+ ASSERT_EQ(factory_.ToPath(file_name), "/tmp/example.txt");
+
+ auto file_meta = std::make_shared<DataFileMeta>(
+ "example.txt", /*file_size=*/645, /*row_count=*/5,
BinaryRow::EmptyRow(),
+ BinaryRow::EmptyRow(), SimpleStats::EmptyStats(),
SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/0, /*max_sequence_number=*/4, /*schema_id=*/0,
+ /*level=*/0, /*extra_files=*/std::vector<std::optional<std::string>>(),
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/0, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/"file:/test/bucket-0/example.txt",
/*first_row_id=*/std::nullopt,
+ /*write_cols=*/std::nullopt);
+ ASSERT_EQ(factory_.ToPath(file_meta), "file:/test/bucket-0/example.txt");
+}
+
+TEST_F(DataFilePathFactoryTest, TestToFileIndexPath) {
+ std::string file_path = "/tmp/example.txt";
+ std::string index_path = factory_.ToFileIndexPath(file_path);
+
+ ASSERT_EQ(index_path, "/tmp/example.txt.index");
+}
+
+TEST_F(DataFilePathFactoryTest, TestToAlignedPath) {
+ auto file_meta = std::make_shared<DataFileMeta>(
+ "data-0.txt", /*file_size=*/645, /*row_count=*/5,
BinaryRow::EmptyRow(),
+ BinaryRow::EmptyRow(), SimpleStats::EmptyStats(),
SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/0, /*max_sequence_number=*/4, /*schema_id=*/0,
+ /*level=*/0, /*extra_files=*/std::vector<std::optional<std::string>>(),
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/0, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/"file:/test/bucket-0/data-0.txt",
/*first_row_id=*/std::nullopt,
+ /*write_cols=*/std::nullopt);
+
+ ASSERT_EQ(factory_.ToAlignedPath("index-0", file_meta),
"file:/test/bucket-0/index-0");
+
+ file_meta->external_path = std::nullopt;
+ ASSERT_EQ(factory_.ToAlignedPath("index-0", file_meta), "/tmp/index-0");
+}
+
+TEST_F(DataFilePathFactoryTest, TestCollectFiles) {
+ auto file_meta = std::make_shared<DataFileMeta>(
+ "data-0.txt", /*file_size=*/645, /*row_count=*/5,
BinaryRow::EmptyRow(),
+ BinaryRow::EmptyRow(), SimpleStats::EmptyStats(),
SimpleStats::EmptyStats(),
+ /*min_sequence_number=*/0, /*max_sequence_number=*/4, /*schema_id=*/0,
+ /*level=*/0, /*extra_files=*/std::vector<std::optional<std::string>>(),
+ /*creation_time=*/Timestamp(1737111915429ll, 0),
+ /*delete_row_count=*/0, /*embedded_index=*/nullptr,
FileSource::Append(),
+ /*value_stats_cols=*/std::nullopt,
+ /*external_path=*/std::nullopt, /*first_row_id=*/std::nullopt,
/*write_cols=*/std::nullopt);
+ ASSERT_EQ(factory_.CollectFiles(file_meta),
std::vector<std::string>({"/tmp/data-0.txt"}));
+
+ file_meta->extra_files = {"data-0.index", "data-1.index"};
+ ASSERT_EQ(
+ factory_.CollectFiles(file_meta),
+ std::vector<std::string>({"/tmp/data-0.txt", "/tmp/data-0.index",
"/tmp/data-1.index"}));
+}
+} // namespace paimon::test