lxy-9602 commented on code in PR #210:
URL: https://github.com/apache/paimon-cpp/pull/210#discussion_r3796458971


##########
include/paimon/file_index/file_index_format.h:
##########
@@ -88,6 +91,8 @@ class MemoryPool;
 class PAIMON_EXPORT FileIndexFormat {
  public:
     class Reader;
+    class Writer;
+    using ColumnIndexes = std::map<std::string, std::map<std::string, 
std::shared_ptr<Bytes>>>;
     /// Creates a `Reader` to parse a index file (may contain multiple 
indexes) from the given input

Review Comment:
   Please add a comment explaining what `ColumnIndexes` represents.



##########
src/paimon/common/file_index/file_index_format.cpp:
##########
@@ -39,6 +41,102 @@ namespace paimon {
 class InputStream;
 class MemoryPool;
 
+class FileIndexFormatWriterImpl : public FileIndexFormat::Writer {
+ public:
+    explicit FileIndexFormatWriterImpl(const std::shared_ptr<OutputStream>& 
output_stream)
+        : output_stream_(output_stream) {
+        assert(output_stream_);
+    }
+
+    Status WriteColumnIndexes(const FileIndexFormat::ColumnIndexes& indexes) 
override {
+        if (written_) {
+            return Status::Invalid("File index column indexes have already 
been written");
+        }
+        int64_t header_length = sizeof(int64_t) + sizeof(int32_t) * 3 + 
sizeof(int32_t);

Review Comment:
   Please add comments for header_length, why not `sizeof(int32_t) * 4`



##########
src/paimon/common/file_index/file_index_format_test.cpp:
##########


Review Comment:
   The bytes in this test look very similar to those in 
`TestWriteEmptyIndexGoldenBytes`; both are for the empty case. Can we reuse 
them instead of duplicating them?



##########
src/paimon/common/io/memory_segment_output_stream.cpp:
##########
@@ -54,12 +54,20 @@ void MemorySegmentOutputStream::WriteString(const 
std::string& str) {
 }
 
 void MemorySegmentOutputStream::Write(const char* data, uint32_t size) {
-    auto bytes = std::make_shared<Bytes>(size, pool_.get());
-    if (size != 0) {
-        memcpy(bytes->data(), data, size);

Review Comment:
   Why doesn’t `Write(const char* data, uint32_t size)` delegate to 
`Write(const MemorySegment& segment, int32_t offset, int32_t len)`? 
`MemorySegment` can be constructed via `WrapView`.



##########
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");

Review Comment:
   Why isn’t `ValidateValueInRange` used here?



##########
src/paimon/common/file_index/file_index_format.cpp:
##########
@@ -39,6 +41,102 @@ namespace paimon {
 class InputStream;
 class MemoryPool;
 
+class FileIndexFormatWriterImpl : public FileIndexFormat::Writer {
+ public:
+    explicit FileIndexFormatWriterImpl(const std::shared_ptr<OutputStream>& 
output_stream)
+        : output_stream_(output_stream) {
+        assert(output_stream_);
+    }
+
+    Status WriteColumnIndexes(const FileIndexFormat::ColumnIndexes& indexes) 
override {
+        if (written_) {
+            return Status::Invalid("File index column indexes have already 
been written");
+        }
+        int64_t header_length = sizeof(int64_t) + sizeof(int32_t) * 3 + 
sizeof(int32_t);
+        int64_t body_length = 0;
+        for (const auto& [column_name, column_indexes] : indexes) {
+            
PAIMON_RETURN_NOT_OK(ValidateValueInRange<uint16_t>(column_name.size(),
+                                                                "file index 
column name length"));
+            header_length +=
+                sizeof(uint16_t) + static_cast<int64_t>(column_name.size()) + 
sizeof(int32_t);
+            for (const auto& [index_type, bytes] : column_indexes) {
+                
PAIMON_RETURN_NOT_OK(ValidateValueInRange<uint16_t>(index_type.size(),
+                                                                    "file 
index type name length"));
+                header_length += sizeof(uint16_t) + 
static_cast<int64_t>(index_type.size()) +
+                                 sizeof(int32_t) * 2;
+                if (bytes) {
+                    PAIMON_RETURN_NOT_OK(AddChecked(bytes->size(), 
&body_length, "index body"));
+                }
+            }
+        }
+        PAIMON_RETURN_NOT_OK(
+            ValidateValueInRange<int32_t>(header_length, "file index header 
length"));
+        PAIMON_RETURN_NOT_OK(
+            ValidateValueInRange<int32_t>(indexes.size(), "file index column 
count"));
+        PAIMON_RETURN_NOT_OK(AddChecked(header_length, &body_length, "file 
index size"));
+
+        DataOutputStream data_output(output_stream_);
+        
PAIMON_RETURN_NOT_OK(data_output.WriteValue<int64_t>(FileIndexFormat::MAGIC));
+        
PAIMON_RETURN_NOT_OK(data_output.WriteValue<int32_t>(FileIndexFormat::V_1));
+        
PAIMON_RETURN_NOT_OK(data_output.WriteValue<int32_t>(static_cast<int32_t>(header_length)));
+        
PAIMON_RETURN_NOT_OK(data_output.WriteValue<int32_t>(static_cast<int32_t>(indexes.size())));
+
+        int64_t body_offset = header_length;
+        for (const auto& [column_name, column_indexes] : indexes) {
+            PAIMON_RETURN_NOT_OK(data_output.WriteString(column_name));
+            PAIMON_RETURN_NOT_OK(
+                ValidateValueInRange<int32_t>(column_indexes.size(), "column 
index count"));
+            PAIMON_RETURN_NOT_OK(
+                
data_output.WriteValue<int32_t>(static_cast<int32_t>(column_indexes.size())));
+            for (const auto& [index_type, bytes] : column_indexes) {
+                PAIMON_RETURN_NOT_OK(data_output.WriteString(index_type));
+                if (bytes == nullptr) {
+                    PAIMON_RETURN_NOT_OK(
+                        
data_output.WriteValue<int32_t>(FileIndexFormat::EMPTY_INDEX_FLAG));
+                    PAIMON_RETURN_NOT_OK(data_output.WriteValue<int32_t>(0));
+                    continue;
+                }
+                PAIMON_RETURN_NOT_OK(
+                    
data_output.WriteValue<int32_t>(static_cast<int32_t>(body_offset)));
+                PAIMON_RETURN_NOT_OK(
+                    
data_output.WriteValue<int32_t>(static_cast<int32_t>(bytes->size())));
+                body_offset += static_cast<int64_t>(bytes->size());
+            }
+        }
+        PAIMON_RETURN_NOT_OK(data_output.WriteValue<int32_t>(0));
+        for (const auto& [column_name, column_indexes] : indexes) {
+            for (const auto& [index_type, bytes] : column_indexes) {
+                if (bytes) {
+                    PAIMON_RETURN_NOT_OK(data_output.WriteBytes(bytes));
+                }
+            }
+        }
+        written_ = true;
+        return Status::OK();
+    }
+
+    Status Close() override {
+        if (closed_) {
+            return Status::OK();
+        }
+        closed_ = true;
+        PAIMON_RETURN_NOT_OK(output_stream_->Flush());
+        return output_stream_->Close();
+    }
+
+ private:
+    template <typename T>
+    static Status AddChecked(T value, int64_t* total, const char* name) {
+        PAIMON_RETURN_NOT_OK(ValidateValueInRange<int32_t>(value, name));

Review Comment:
   If `total` is an output param, please move to the last.



##########
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:
   This implementation feels a bit unusual. Would it make sense to add a helper 
on `Bytes` instead, for example:
   
   ```cpp
   static std::shared_ptr<Bytes> AllocateShared(
       size_t size, std::shared_ptr<MemoryPool> pool);
   ```



-- 
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]

Reply via email to