pzhdfy commented on code in PR #66227:
URL: https://github.com/apache/doris/pull/66227#discussion_r4023669543


##########
be/src/format_v2/table/paimon_rust_table_reader.cpp:
##########
@@ -0,0 +1,771 @@
+// 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 "format_v2/table/paimon_rust_table_reader.h"
+
+#include <algorithm>
+#include <string_view>
+#include <utility>
+
+#include "arrow/c/abi.h"
+#include "arrow/c/bridge.h"
+#include "arrow/record_batch.h"
+#include "arrow/result.h"
+#include "common/logging.h"
+#include "core/block/block.h"
+#include "core/block/column_with_type_and_name.h"
+#include "core/column/column_const.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "format/table/paimon_rust_predicate_converter.h"
+#include "format_v2/column_mapper.h"
+#include "runtime/descriptors.h"
+#include "runtime/file_scan_profile.h"
+#include "runtime/runtime_state.h"
+#include "util/string_util.h"
+#include "util/timezone_utils.h"
+#include "util/url_coding.h"
+
+extern "C" {
+#include "paimon_rust/paimon.h"
+}
+
+namespace doris::format::paimon {
+
+namespace {
+constexpr const char* VALUE_KIND_FIELD = "_VALUE_KIND";
+
+// ---------------------------------------------------------------------------
+// RAII wrappers over the paimon-rust C handles. Each handle is an opaque
+// pointer owned by Rust and released by a matching paimon_*_free function.
+// ---------------------------------------------------------------------------
+#define PAIMON_OWNED(type, freefn)                                  \
+    struct type##_deleter {                                         \
+        void operator()(paimon_##type* p) const {                   \
+            if (p) {                                                \
+                freefn(p);                                          \
+            }                                                       \
+        }                                                           \
+    };                                                              \
+    using type##_ptr = std::unique_ptr<paimon_##type, type##_deleter>
+
+PAIMON_OWNED(table, paimon_table_free);
+PAIMON_OWNED(read_builder, paimon_read_builder_free);
+PAIMON_OWNED(plan, paimon_plan_free);
+PAIMON_OWNED(table_read, paimon_table_read_free);
+PAIMON_OWNED(record_batch_reader, paimon_record_batch_reader_free);
+PAIMON_OWNED(error, paimon_error_free);
+
+#undef PAIMON_OWNED
+
+// One Arrow batch (schema + array containers). Owning it requires a two-step
+// teardown that the unique_ptr deleters above can't express: first invoke the
+// Arrow C Data Interface `release` callback on each struct (hands buffers back
+// to the producer), then free the container structs via 
paimon_arrow_batch_free.
+class ArrowBatch {
+public:
+    explicit ArrowBatch(paimon_arrow_batch batch) : batch_(batch) {}
+    ~ArrowBatch() {
+        auto* schema = static_cast<ArrowSchema*>(batch_.schema);
+        auto* array = static_cast<ArrowArray*>(batch_.array);
+        if (array && array->release) {
+            array->release(array);
+        }
+        if (schema && schema->release) {
+            schema->release(schema);
+        }
+        paimon_arrow_batch_free(batch_);
+    }
+
+    ArrowBatch(const ArrowBatch&) = delete;
+    ArrowBatch& operator=(const ArrowBatch&) = delete;
+
+    ArrowSchema* schema() const { return 
static_cast<ArrowSchema*>(batch_.schema); }
+    ArrowArray* array() const { return static_cast<ArrowArray*>(batch_.array); 
}
+
+private:
+    paimon_arrow_batch batch_;
+};
+
+// Render a paimon_error into a string. Takes ownership of `err` via RAII so it
+// is freed on every return path. Safe to call with nullptr.
+std::string consume_error(paimon_error* err) {
+    error_ptr owned(err);
+    if (!owned) {
+        return "unknown error";
+    }
+    std::string msg;
+    if (owned->message.data != nullptr && owned->message.len > 0) {
+        msg.assign(reinterpret_cast<const char*>(owned->message.data), 
owned->message.len);
+    }
+    return "code=" + std::to_string(owned->code) + ", msg=" + msg;
+}
+
+// Render storage options for diagnostics. Values of sensitive keys (secret /
+// password / token / access key) are masked so credentials never hit the log.
+std::string format_options(const std::map<std::string, std::string>& options) {
+    std::string out;
+    for (const auto& kv : options) {
+        if (!out.empty()) {
+            out += ", ";
+        }
+        std::string_view key = kv.first;
+        const bool sensitive = key.find("secret") != std::string_view::npos ||
+                               key.find("password") != std::string_view::npos 
||
+                               key.find("token") != std::string_view::npos ||
+                               key.find("access.key") != 
std::string_view::npos ||
+                               key.find("access-key") != 
std::string_view::npos;
+        out += kv.first;
+        out += '=';
+        out += sensitive ? "***" : kv.second;
+    }
+    return out;
+}
+
+} // namespace
+
+// Paimon-rust handles. Order of members matters: destruction runs in reverse
+// declaration order, and the read_builder depends on the table while the arrow
+// reader depends on the whole pipeline above it. So the table MUST be declared
+// first (destroyed last) and the record batch reader last.
+struct PaimonRustTableReader::PaimonHandles {
+    table_ptr table;
+    read_builder_ptr read_builder;
+    plan_ptr plan;
+    table_read_ptr table_read;
+    record_batch_reader_ptr reader;
+};
+
+PaimonRustTableReader::PaimonRustTableReader() = default;
+
+PaimonRustTableReader::~PaimonRustTableReader() = default;
+
+Status PaimonRustTableReader::init(format::TableReadOptions&& options) {
+    RETURN_IF_ERROR(format::TableReader::init(std::move(options)));
+    {
+        // Base and derived scopes must not overlap on the same counter: 
RuntimeProfile timers
+        // add deltas, so nested use would double-count instead of extending 
lifecycle coverage.
+        SCOPED_TIMER(_profile.total_timer);
+        SCOPED_TIMER(_profile.init_timer);
+        TimezoneUtils::find_cctz_time_zone(TimezoneUtils::default_time_zone, 
_ctz);

Review Comment:
    _ctz now comes from RuntimeState::timezone_obj(), mirroring lance_reader 
and the JNI path. NTZ is unaffected — the serde decodes timezone-naive arrow 
timestamps in UTC regardless of _ctz, so wall-clock semantics are preserved. 
The predicate converter's timestamp literals are also hardened to 
cctz::utc_time_zone() instead of a "GMT" lookup that could fall back to the 
fixed +08:00 default. Covered by 
PaimonRustTableReaderTest.MaterializesInSessionTimezone and JNI/rust LTZ 
comparisons under '+00:00' and '+08:00' in the regression suite.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/source/PaimonScanNode.java:
##########
@@ -411,10 +412,52 @@ private void setPaimonParams(TFileRangeDesc rangeDesc, 
PaimonSplit paimonSplit)
 
         String fileFormat = getFileFormat(paimonSplit.getPathString());
         if (split != null) {
+            // use jni reader / paimon-cpp reader / paimon-rust reader
             rangeDesc.setFormatType(TFileFormatType.FORMAT_JNI);
-            // A logical DataSplit may span multiple files, so keep it intact 
for the JNI reader.
-            fileDesc.setReaderType(TPaimonReaderType.PAIMON_JNI);
-            fileDesc.setPaimonSplit(PaimonUtil.encodeObjectToString(split));
+            // paimon-cpp and paimon-rust both consume Paimon native binary 
serialization,
+            // which only supports DataSplit. Any other split type falls back 
to JNI.
+            boolean nativeSplit = split instanceof DataSplit;
+            // paimon-rust additionally requires a FileStoreTable: BE opens 
the table
+            // via paimon_table_from_schema_json, which needs the resolved 
TableSchema
+            // that only FileStoreTable exposes via schema(). If the table is 
not a
+            // FileStoreTable (e.g. a sys table backed by DataSplit), we 
cannot ship a
+            // schema JSON, so fall back to CPP / JNI rather than sending an 
incomplete
+            // PAIMON_RUST request that BE would reject.
+            Table paimonTable = source.getPaimonTable();
+            boolean canUseRust = sessionVariable.isEnablePaimonRustReader() && 
nativeSplit

Review Comment:
    Went with the fallback: canUseRust now also requires 
enable_file_scanner_v2, so with V2 disabled FE selects JNI instead of encoding 
a PAIMON_RUST request the V1 scanner rejects. The suite's V2-disabled leg is 
now consistent — it runs through JNI by FE selection. Covered by 
PaimonScanNodeTest.testRustReaderSelectionRequiresFileScannerV2, and the PR 
description has been updated to state the contract.



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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to