zjw1111 commented on code in PR #210: URL: https://github.com/apache/paimon-cpp/pull/210#discussion_r3811189978
########## src/paimon/core/io/file_index_options.cpp: ########## @@ -0,0 +1,102 @@ +/* + * 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/file_index_options.h" + +#include <set> +#include <utility> + +#include "fmt/format.h" +#include "paimon/common/utils/string_utils.h" +#include "paimon/core/core_options.h" +#include "paimon/defs.h" +#include "paimon/status.h" + +namespace paimon { +namespace { + +constexpr char kFileIndexPrefix[] = "file-index."; +constexpr char kColumnsSuffix[] = ".columns"; + +} // namespace + +Result<FileIndexOptions> FileIndexOptions::FromCoreOptions(const CoreOptions& options) { + FileIndexOptions result; + const std::map<std::string, std::string>& raw_options = options.ToMap(); + result.in_manifest_threshold_ = options.FileIndexInManifestThreshold(); + + std::set<std::pair<std::string, std::string>> declared; + for (const auto& [key, value] : raw_options) { + if (!StringUtils::StartsWith(key, kFileIndexPrefix) || + !StringUtils::EndsWith(key, kColumnsSuffix)) { + continue; + } + const size_t index_type_length = + key.size() - std::string(kFileIndexPrefix).size() - std::string(kColumnsSuffix).size(); + const std::string index_type = + key.substr(std::string(kFileIndexPrefix).size(), index_type_length); + if (index_type.empty()) { + return Status::Invalid(fmt::format("Invalid file index option {}", key)); + } + for (std::string column_name : StringUtils::Split(value, ",", /*ignore_empty=*/false)) { + StringUtils::Trim(&column_name); Review Comment: We intentionally keep ignore_empty=false so leading or middle empty column entries, such as f1,,f2, are rejected instead of silently ignored. Java String.split drops trailing empty tokens, so malformed values such as f1,f2,, currently behave differently. I reverted the ad-hoc special case and added a TODO to align this together with ConfigParser::ParseList, so list option parsing can be changed consistently. ########## src/paimon/common/io/byte_array_output_stream.cpp: ########## @@ -0,0 +1,79 @@ +/* + * 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/common/io/byte_array_output_stream.h" + +#include <algorithm> +#include <limits> +#include <vector> + +#include "paimon/common/memory/memory_segment_utils.h" +#include "paimon/common/utils/math.h" +#include "paimon/memory/bytes.h" +#include "paimon/memory/memory_pool.h" + +namespace paimon { + +ByteArrayOutputStream::ByteArrayOutputStream(int32_t initial_capacity, + const std::shared_ptr<MemoryPool>& pool) + : pool_(pool), output_(initial_capacity, pool_) {} + +Result<int64_t> ByteArrayOutputStream::Write(const char* buffer, int64_t size) { + if (closed_) { + return Status::Invalid("Byte array output stream is closed"); + } + PAIMON_RETURN_NOT_OK(ValidateValueNonNegative(size, "write length")); + if (buffer == nullptr && size > 0) { + return Status::Invalid("Write buffer must not be null when size is positive"); + } + int64_t remaining = size; + while (remaining > 0) { + uint32_t to_write = static_cast<uint32_t>(std::min<int64_t>( + remaining, static_cast<int64_t>(std::numeric_limits<uint32_t>::max()))); + output_.Write(buffer, to_write); + buffer += to_write; + remaining -= to_write; + } + position_ += size; + return size; +} + +Status ByteArrayOutputStream::Close() { + closed_ = true; + return Status::OK(); +} + +Result<std::shared_ptr<Bytes>> ByteArrayOutputStream::Finish() { + PAIMON_RETURN_NOT_OK(Close()); + if (result_) { + return result_; + } + // TODO(jinli.zjw): Support int64_t lengths in MemorySegmentUtils::CopyToBytes and remove this + // limit. + if (position_ > std::numeric_limits<int32_t>::max()) { + return Status::Invalid("Byte array output stream size exceeds INT32_MAX"); + } + const std::vector<MemorySegment>& segments = output_.Segments(); + result_ = std::shared_ptr<Bytes>(new Bytes(static_cast<size_t>(position_), pool_.get()), + [pool = pool_](Bytes* bytes) { delete bytes; }); + MemorySegmentUtils::CopyToBytes(segments, /*offset=*/0, result_.get(), Review Comment: Thanks. I removed the custom deleter instead of adding AllocateShared. ByteArrayOutputStream::Finish now receives a MemoryPool* and constructs the result with std::make_shared<Bytes>. The API documents that the caller must keep the pool alive until the returned Bytes is destroyed, and the upper layer guarantees that lifetime. With this ownership contract, a helper that captures shared_ptr<MemoryPool> is no longer needed. ########## src/paimon/core/io/data_file_index_writer.h: ########## @@ -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. + */ + +#pragma once + +#include <memory> +#include <optional> +#include <string> +#include <vector> + +#include "paimon/core/io/file_index_options.h" +#include "paimon/result.h" + +namespace arrow { +class Field; +class Schema; +class StructArray; +} // namespace arrow + +namespace paimon { + +class Bytes; +class DataFilePathFactory; +class FileIndexWriter; +class FileSystem; +class MemoryPool; + +struct FileIndexWriteResult { + std::shared_ptr<Bytes> embedded_index; + std::vector<std::optional<std::string>> extra_files; +}; + +/// Builds every configured column index for one data file. +class DataFileIndexWriter { + public: + static Result<std::unique_ptr<DataFileIndexWriter>> Create( + const std::shared_ptr<arrow::Schema>& logical_schema, const FileIndexOptions& options, + const std::shared_ptr<FileSystem>& file_system, + const std::shared_ptr<DataFilePathFactory>& path_factory, + const std::shared_ptr<MemoryPool>& pool); + + Status AddBatch(const std::shared_ptr<arrow::StructArray>& logical_batch); + + Result<FileIndexWriteResult> Finish(const std::string& data_file_path); + + void Abort(); + + const std::optional<std::string>& ExternalIndexPath() const { + return external_index_path_; + } + + private: + struct IndexWriterEntry { + std::string column_name; + std::string index_type; + int32_t field_index; + std::shared_ptr<arrow::Field> field; + std::shared_ptr<FileIndexWriter> writer; + }; + + DataFileIndexWriter(std::vector<IndexWriterEntry>&& writers, int64_t in_manifest_threshold, + const std::shared_ptr<FileSystem>& file_system, + const std::shared_ptr<DataFilePathFactory>& path_factory, + const std::shared_ptr<MemoryPool>& pool); + + Result<std::shared_ptr<Bytes>> SerializeContainer(); + Status WriteExternal(const std::string& path, const std::shared_ptr<Bytes>& bytes); + + std::vector<IndexWriterEntry> writers_; + int64_t in_manifest_threshold_; + std::shared_ptr<FileSystem> file_system_; + std::shared_ptr<DataFilePathFactory> path_factory_; + std::shared_ptr<MemoryPool> pool_; Review Comment: Yes, this is about destruction order. Members are destroyed in reverse declaration order. The earlier members do not rely on DataFileIndexWriter::pool_ during destruction: each current FileIndexWriter keeps its own shared_ptr<MemoryPool>. pool_ here is used directly only while serializing the container, so declaring it after writers_ is safe. If a future member borrows this pool instead of owning it, pool_ should be declared before that member. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
