wangyong9999 commented on code in PR #315:
URL: https://github.com/apache/paimon-cpp/pull/315#discussion_r3996417094


##########
src/paimon/core/operation/orphan_files_cleaner_impl.cpp:
##########
@@ -81,7 +81,7 @@ bool OrphanFilesCleanerImpl::SupportToClean(const 
std::string& file_name) {
             return true;
         }
     }
-    static std::vector<std::string> supported_formats = {".orc", ".parquet", 
".avro"};
+    static std::vector<std::string> supported_formats = {".orc", ".parquet", 
".avro", ".lance"};
     for (const auto& format : supported_formats) {

Review Comment:
   Thanks! Added .mosaic alongside .lance, with positive and negative filename 
tests.
   
   Commit: c45f394c.



##########
src/paimon/core/schema/schema_validation.cpp:
##########
@@ -802,6 +804,89 @@ Status SchemaValidation::ValidateMosaicDataFields(const 
TableSchema& schema,
     return Status::OK();
 }
 
+Status SchemaValidation::ValidateLanceDataField(const 
std::shared_ptr<arrow::Field>& field) {
+    if (VariantTypeUtils::IsVariantField(field)) {
+        return Status::Invalid("Lance file format does not support type 
VARIANT");
+    }
+    if (BlobUtils::IsBlobField(field)) {
+        return Status::Invalid("Lance file format does not support type BLOB");
+    }
+
+    const std::shared_ptr<arrow::DataType>& type = field->type();
+    switch (type->id()) {
+        case arrow::Type::BOOL:
+        case arrow::Type::INT8:
+        case arrow::Type::INT16:
+        case arrow::Type::INT32:
+        case arrow::Type::INT64:
+        case arrow::Type::FLOAT:
+        case arrow::Type::DOUBLE:
+        case arrow::Type::DATE32:
+        case arrow::Type::STRING:
+        case arrow::Type::BINARY:
+        case arrow::Type::TIME32:
+        case arrow::Type::DECIMAL128:
+        case arrow::Type::FIXED_SIZE_LIST:
+            return Status::OK();
+        case arrow::Type::TIMESTAMP: {
+            const auto& timestamp_type = checked_cast<const 
arrow::TimestampType&>(*type);
+            if (!timestamp_type.timezone().empty()) {
+                return Status::Invalid(
+                    "Lance file format does not support type 
LOCAL_ZONED_TIMESTAMP");
+            }
+            return Status::OK();
+        }
+        case arrow::Type::LIST:
+            return ValidateLanceDataField(type->field(0));
+        case arrow::Type::STRUCT:
+            if (field->nullable()) {
+                return Status::Invalid(fmt::format(
+                    "Lance file format does not support nullable ROW field 
'{}'", field->name()));
+            }

Review Comment:
   Removed the schema-level nullable ROW restriction to match Java. A real 
Paimon Java/Lance 0.39 round trip confirms that actual null parents lose 
validity. The C++ writer now rejects those values while accepting nullable 
declarations and null children. Slice and ARRAY<ROW> regressions pass; the file 
version is unchanged.
   
   Commit: c45f394c.



##########
src/paimon/core/schema/schema_validation.cpp:
##########
@@ -802,6 +804,89 @@ Status SchemaValidation::ValidateMosaicDataFields(const 
TableSchema& schema,
     return Status::OK();
 }
 
+Status SchemaValidation::ValidateLanceDataField(const 
std::shared_ptr<arrow::Field>& field) {
+    if (VariantTypeUtils::IsVariantField(field)) {
+        return Status::Invalid("Lance file format does not support type 
VARIANT");
+    }
+    if (BlobUtils::IsBlobField(field)) {
+        return Status::Invalid("Lance file format does not support type BLOB");
+    }
+
+    const std::shared_ptr<arrow::DataType>& type = field->type();
+    switch (type->id()) {
+        case arrow::Type::BOOL:
+        case arrow::Type::INT8:
+        case arrow::Type::INT16:
+        case arrow::Type::INT32:
+        case arrow::Type::INT64:
+        case arrow::Type::FLOAT:
+        case arrow::Type::DOUBLE:
+        case arrow::Type::DATE32:
+        case arrow::Type::STRING:
+        case arrow::Type::BINARY:
+        case arrow::Type::TIME32:
+        case arrow::Type::DECIMAL128:

Review Comment:
   Removed TIME32 from the Lance schema/statistics cases and format fixture. 
Regression tests verify rejection at TableSchema::Create, where the C++ stack 
already rejects TIME32. Full-stack TIME support stays outside this PR.
   
   Commit: c45f394c.



##########
test/test_data/lance/README.md:
##########
@@ -0,0 +1,22 @@
+# Lance compatibility fixture
+
+`java_lance_0_39_0.lance` was written with Apache Paimon Java commit
+`63ffd403648e1cf9848e32bb29a398c1b3b16ab1` and `com.lancedb:lance-core:0.39.0`.
+
+Its schema is `id INT, name STRING, score DOUBLE`, and its rows are:
+
+```text
+1, alpha, 1.25
+2, beta, -2.5
+3, null, null
+4, delta, 4.75
+```

Review Comment:
   Added a real Paimon Java table with schema, two snapshots, manifests and 
Lance files, plus TestLanceJavaCompatibility. It covers 25 columns: supported 
scalars, timestamp/decimal precisions, nested lists, ROW and VECTOR, including 
nulls and reordered projection. Java readback and C++ end-to-end tests pass. 
The generator and README document reproduction and the null-ROW limitation; 
Python-generated compatibility is not claimed.
   
   Commit: c45f394c.



##########
test/inte/write_and_read_inte_test.cpp:
##########
@@ -359,7 +359,7 @@ TEST_P(WriteAndReadInteTest, TestAppendSimple) {
 
 TEST_P(WriteAndReadInteTest, TestAppendVector) {
     auto [file_format, file_system] = GetParam();
-    if (file_format != "parquet") {
+    if (file_format != "parquet" && file_format != "lance") {

Review Comment:
   Added Lance to the nested-pruning suite: 12 applicable cases pass, including 
deep structs, missing-field/type errors and row-tracking fields. MAP/ORC-only 
cases are explicit skips. Read/write, compaction and data-evolution suites also 
pass, along with the new Java-table compatibility test. Existing LIST-of-STRUCT 
partial-projection restrictions are unchanged.
   
   Commit: c45f394c.



##########
src/paimon/format/lance/lance_file_batch_reader.cpp:
##########
@@ -0,0 +1,304 @@
+/*
+ * 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/format/lance/lance_file_batch_reader.h"
+
+#include <utility>
+
+#include "arrow/api.h"
+#include "arrow/c/bridge.h"
+#include "arrow/c/helpers.h"
+#include "fmt/format.h"
+#include "paimon/common/metrics/metrics_impl.h"
+#include "paimon/common/utils/arrow/arrow_utils.h"
+#include "paimon/common/utils/arrow/mem_utils.h"
+#include "paimon/common/utils/arrow/status_utils.h"
+#include "paimon/common/utils/checked_cast.h"
+#include "paimon/core/utils/nested_projection_utils.h"
+#include "paimon/format/lance/lance_utils.h"
+#include "paimon/fs/file_system.h"
+
+namespace paimon::lance {
+
+LanceFileBatchReader::LanceFileBatchReader(const std::shared_ptr<InputStream>& 
input,
+                                           int32_t batch_size, uint32_t 
batch_readahead,
+                                           PaimonLanceReader* reader,
+                                           const 
std::shared_ptr<arrow::Schema>& file_schema,
+                                           uint64_t total_rows,
+                                           const std::shared_ptr<MemoryPool>& 
pool,
+                                           const 
std::shared_ptr<arrow::MemoryPool>& arrow_pool)
+    : input_(input),

Review Comment:
   You're right: pool_ was redundant. Removed it and the duplicate reader pool 
argument. The Arrow adapter retains the Paimon pool for C++ alignment 
allocations; Rust/Lance buffers use Rust's allocator.
   
   Commit: c45f394c.



##########
src/paimon/format/lance/lance_file_batch_reader.cpp:
##########
@@ -0,0 +1,304 @@
+/*
+ * 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/format/lance/lance_file_batch_reader.h"
+
+#include <utility>
+
+#include "arrow/api.h"
+#include "arrow/c/bridge.h"
+#include "arrow/c/helpers.h"
+#include "fmt/format.h"
+#include "paimon/common/metrics/metrics_impl.h"
+#include "paimon/common/utils/arrow/arrow_utils.h"
+#include "paimon/common/utils/arrow/mem_utils.h"
+#include "paimon/common/utils/arrow/status_utils.h"
+#include "paimon/common/utils/checked_cast.h"
+#include "paimon/core/utils/nested_projection_utils.h"
+#include "paimon/format/lance/lance_utils.h"
+#include "paimon/fs/file_system.h"
+
+namespace paimon::lance {
+
+LanceFileBatchReader::LanceFileBatchReader(const std::shared_ptr<InputStream>& 
input,
+                                           int32_t batch_size, uint32_t 
batch_readahead,
+                                           PaimonLanceReader* reader,
+                                           const 
std::shared_ptr<arrow::Schema>& file_schema,
+                                           uint64_t total_rows,
+                                           const std::shared_ptr<MemoryPool>& 
pool,
+                                           const 
std::shared_ptr<arrow::MemoryPool>& arrow_pool)
+    : input_(input),
+      batch_size_(batch_size),
+      batch_readahead_(batch_readahead),
+      reader_(reader),
+      file_schema_(file_schema),
+      total_rows_(total_rows),
+      pool_(pool),
+      arrow_pool_(arrow_pool),
+      metrics_(std::make_shared<MetricsImpl>()) {}
+
+Result<std::unique_ptr<LanceFileBatchReader>> LanceFileBatchReader::Create(
+    const std::shared_ptr<InputStream>& input, int32_t batch_size, uint32_t 
batch_readahead,
+    const std::map<std::string, std::string>& options, const 
std::shared_ptr<MemoryPool>& pool,
+    const std::shared_ptr<arrow::MemoryPool>& arrow_pool) {
+    if (input == nullptr || pool == nullptr || arrow_pool == nullptr || 
batch_size <= 0 ||
+        batch_readahead == 0) {
+        return Status::Invalid(
+            "Lance reader requires non-null input and memory pools, positive "
+            "batch size, and positive batch readahead");
+    }
+    PAIMON_ASSIGN_OR_RAISE(std::string uri, input->GetUri());
+    if (uri.empty()) {
+        return Status::Invalid("Lance reader input URI is empty");
+    }
+    LanceStorageOptions storage_options = GetLanceStorageOptions(options, uri);
+    std::vector<const char*> keys;
+    std::vector<const char*> values;
+    keys.reserve(storage_options.size());
+    values.reserve(storage_options.size());
+    for (const auto& [key, value] : storage_options) {
+        keys.push_back(key.c_str());
+        values.push_back(value.c_str());
+    }
+    PaimonLanceReader* reader = nullptr;
+    if (paimon_lance_reader_open(uri.c_str(), keys.data(), values.data(), 
keys.size(), &reader) !=
+        0) {
+        return LanceFfiError("open Lance reader");
+    }
+
+    ::ArrowSchema ffi_schema = {};
+    if (paimon_lance_reader_export_schema(reader, &ffi_schema) != 0) {
+        paimon_lance_reader_free(reader);
+        return LanceFfiError("read Lance schema");
+    }
+    arrow::Result<std::shared_ptr<arrow::Schema>> schema_result = 
arrow::ImportSchema(&ffi_schema);
+    if (!schema_result.ok()) {
+        paimon_lance_reader_free(reader);
+        return ToPaimonStatus(schema_result.status());
+    }
+    uint64_t total_rows = 0;
+    if (paimon_lance_reader_num_rows(reader, &total_rows) != 0) {
+        paimon_lance_reader_free(reader);
+        return LanceFfiError("read Lance row count");
+    }

Review Comment:
   Agreed. Added a ScopeGuard immediately after opening the FFI reader, 
releasing it only after ownership transfers to LanceFileBatchReader. This 
removes the repeated cleanup calls.
   
   Commit: c45f394c.



##########
src/paimon/format/lance/lance_file_batch_reader.cpp:
##########
@@ -0,0 +1,304 @@
+/*
+ * 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/format/lance/lance_file_batch_reader.h"
+
+#include <utility>
+
+#include "arrow/api.h"
+#include "arrow/c/bridge.h"
+#include "arrow/c/helpers.h"
+#include "fmt/format.h"
+#include "paimon/common/metrics/metrics_impl.h"
+#include "paimon/common/utils/arrow/arrow_utils.h"
+#include "paimon/common/utils/arrow/mem_utils.h"
+#include "paimon/common/utils/arrow/status_utils.h"
+#include "paimon/common/utils/checked_cast.h"
+#include "paimon/core/utils/nested_projection_utils.h"
+#include "paimon/format/lance/lance_utils.h"
+#include "paimon/fs/file_system.h"
+
+namespace paimon::lance {
+
+LanceFileBatchReader::LanceFileBatchReader(const std::shared_ptr<InputStream>& 
input,
+                                           int32_t batch_size, uint32_t 
batch_readahead,
+                                           PaimonLanceReader* reader,
+                                           const 
std::shared_ptr<arrow::Schema>& file_schema,
+                                           uint64_t total_rows,
+                                           const std::shared_ptr<MemoryPool>& 
pool,
+                                           const 
std::shared_ptr<arrow::MemoryPool>& arrow_pool)
+    : input_(input),
+      batch_size_(batch_size),
+      batch_readahead_(batch_readahead),
+      reader_(reader),
+      file_schema_(file_schema),
+      total_rows_(total_rows),
+      pool_(pool),
+      arrow_pool_(arrow_pool),
+      metrics_(std::make_shared<MetricsImpl>()) {}
+
+Result<std::unique_ptr<LanceFileBatchReader>> LanceFileBatchReader::Create(
+    const std::shared_ptr<InputStream>& input, int32_t batch_size, uint32_t 
batch_readahead,
+    const std::map<std::string, std::string>& options, const 
std::shared_ptr<MemoryPool>& pool,
+    const std::shared_ptr<arrow::MemoryPool>& arrow_pool) {
+    if (input == nullptr || pool == nullptr || arrow_pool == nullptr || 
batch_size <= 0 ||
+        batch_readahead == 0) {
+        return Status::Invalid(
+            "Lance reader requires non-null input and memory pools, positive "
+            "batch size, and positive batch readahead");
+    }
+    PAIMON_ASSIGN_OR_RAISE(std::string uri, input->GetUri());
+    if (uri.empty()) {
+        return Status::Invalid("Lance reader input URI is empty");
+    }
+    LanceStorageOptions storage_options = GetLanceStorageOptions(options, uri);
+    std::vector<const char*> keys;
+    std::vector<const char*> values;
+    keys.reserve(storage_options.size());
+    values.reserve(storage_options.size());
+    for (const auto& [key, value] : storage_options) {
+        keys.push_back(key.c_str());
+        values.push_back(value.c_str());
+    }
+    PaimonLanceReader* reader = nullptr;
+    if (paimon_lance_reader_open(uri.c_str(), keys.data(), values.data(), 
keys.size(), &reader) !=
+        0) {
+        return LanceFfiError("open Lance reader");
+    }
+
+    ::ArrowSchema ffi_schema = {};
+    if (paimon_lance_reader_export_schema(reader, &ffi_schema) != 0) {
+        paimon_lance_reader_free(reader);
+        return LanceFfiError("read Lance schema");
+    }
+    arrow::Result<std::shared_ptr<arrow::Schema>> schema_result = 
arrow::ImportSchema(&ffi_schema);
+    if (!schema_result.ok()) {
+        paimon_lance_reader_free(reader);
+        return ToPaimonStatus(schema_result.status());
+    }
+    uint64_t total_rows = 0;
+    if (paimon_lance_reader_num_rows(reader, &total_rows) != 0) {
+        paimon_lance_reader_free(reader);
+        return LanceFfiError("read Lance row count");
+    }
+    return std::unique_ptr<LanceFileBatchReader>(new LanceFileBatchReader(
+        input, batch_size, batch_readahead, reader, 
std::move(schema_result).MoveValueUnsafe(),
+        total_rows, pool, arrow_pool));
+}
+
+LanceFileBatchReader::~LanceFileBatchReader() {
+    CloseInternal();
+}
+
+Status LanceFileBatchReader::OpenStream() {
+    if (batch_reader_ != nullptr || (has_selection_ && 
selection_row_ids_.empty())) {
+        return Status::OK();
+    }
+    std::vector<const char*> projection_pointers;
+    projection_pointers.reserve(projection_names_.size());
+    for (const std::string& name : projection_names_) {
+        projection_pointers.push_back(name.c_str());
+    }
+    if (paimon_lance_reader_open_stream(reader_, 
static_cast<uint32_t>(batch_size_),
+                                        batch_readahead_, 
projection_pointers.data(),
+                                        projection_pointers.size(), 
has_selection_,
+                                        selection_starts_.data(), 
selection_ends_.data(),
+                                        selection_starts_.size(), 
&batch_reader_) != 0) {
+        return LanceFfiError("open Lance batch reader");
+    }
+    return Status::OK();
+}
+
+Result<BatchReader::ReadBatch> LanceFileBatchReader::AlignBatch(ReadBatch 
batch) const {
+    if (read_schema_ == nullptr) {
+        return batch;
+    }
+    auto& [ffi_array, ffi_schema] = batch;
+    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> array,
+                                      arrow::ImportArray(ffi_array.get(), 
ffi_schema.get()));
+    if (array->type_id() != arrow::Type::STRUCT) {
+        return Status::Invalid("Lance reader returned a non-struct Arrow 
batch");
+    }
+    auto struct_array = checked_pointer_cast<arrow::StructArray>(array);
+    arrow::ArrayVector fields;
+    fields.reserve(read_schema_->num_fields());
+    for (const std::shared_ptr<arrow::Field>& field : read_schema_->fields()) {
+        std::shared_ptr<arrow::Array> child = 
struct_array->GetFieldByName(field->name());
+        if (child == nullptr) {
+            PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
+                child,
+                arrow::MakeArrayOfNull(field->type(), struct_array->length(), 
arrow_pool_.get()));
+        } else if (!child->type()->Equals(field->type()) &&
+                   ArrowUtils::EqualsIgnoreNullable(child->type(), 
field->type())) {
+            std::shared_ptr<arrow::ArrayData> data = child->data()->Copy();
+            data->type = field->type();
+            child = arrow::MakeArray(std::move(data));
+        } else if (!child->type()->Equals(field->type())) {
+            PAIMON_ASSIGN_OR_RAISE(child, 
NestedProjectionUtils::AlignArrayToReadType(
+                                              child, field->type(), 
arrow_pool_.get()));
+        }

Review Comment:
   A direct replacement currently rejects Lance's fixed-size-list 
element-nullability differences; the helper's equality fast path can also 
retain source field metadata. Kept the small alignment loop for VECTOR and 
requested-field contracts and added an explanatory comment. Nested pruning and 
metadata/nullability regressions pass.
   
   Commit: c45f394c.



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