This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new dea1b99e528 [fix](hive) Match OpenCSVSerde semantics in FileScannerV2 
(#68027)
dea1b99e528 is described below

commit dea1b99e528f57b96d9b3bd9fa68f4dc3e726d32
Author: Gabriel <[email protected]>
AuthorDate: Thu Sep 17 10:29:42 2026 +0800

    [fix](hive) Match OpenCSVSerde semantics in FileScannerV2 (#68027)
    
    ### What problem does this PR solve?
    
    Hive CSV tables created by Trino can store character settings only in
    table parameters. Doris used SerDe parameters and a generic CSV parser,
    producing merged, shifted, or empty data columns even when partition
    values were correct.
    
    Resolve table-over-SerDe character precedence, first-Java-character
    normalization, defaults, and Hive's double-quote escape sentinel.
    Validate the effective character tuple and reject settings that cannot
    be represented by the existing character fields.
    
    Implement OpenCSV row semantics in FileScannerV2. The parser and
    physical-record context live under `be/src/format_v2/delimited_text/`,
    with tests under `be/test/format_v2/delimited_text/`. The legacy
    `be/src/format/` directory is unchanged from the PR base. Match OpenCSV
    2.3 field transitions, including leading whitespace, embedded and
    doubled quotes, disabled quote/escape settings, and literal NUL bytes.
    Preserve Hadoop physical records (LF, CRLF, and CR), completed fields
    before an unmatched quote, missing-field NULLs, explicit empty strings,
    and file-start BOM handling. Decode each field once and bound delimiter
    metadata by the requested column prefix. Neither table nor SerDe
    `line.delim` overrides CSV input framing.
    
    Version the contract at both boundaries: connector API 9.0 freezes the
    public scan-property names, types, and values; OpenCSV scans require
    query-wide BE execution version 15 or newer and exclude smooth-upgrade
    source backends. Requests without the optional semantic flag retain
    generic CSV decoding in FileScannerV2.
    
    The external regression uses Hive-compatible, non-NUL character
    properties because PostgreSQL-backed Hive metastores cannot store NUL in
    text metadata. Disabled quote/escape combinations remain covered by the
    Hive oracle and V2 unit tests; raw file records still include binary
    NULs.
    
    ### Release note
    
    Fix incorrect results when FileScannerV2 reads Hive OpenCSV tables with
    custom character properties and OpenCSV-specific quoting or escaping.
    Reject unsupported character metadata explicitly. OpenCSV scans require
    BE execution version 15 or newer; connector plugins must use API 9.0.
    
    ### Check List (For Author)
    
    - Test:
    - 480 Hive connector tests passed, including an actual Hive 3.1.3 oracle
    for 343 character tuples and 606 record cases with deterministic random
    inputs and binary NULs.
    - 142 connector SPI tests passed, including the API 9.0 pin and
    regenerated surface baselines. The metadata-method baseline is
    unchanged.
    - Prior FE Core validation passed 34 tests: 13 CSV wire/upgrade tests,
    13 plugin loading/version tests, and eight existing scan compatibility
    tests. Coverage includes an API 8.0 plugin jar, execution versions
    14/15, smooth-upgrade source rejection, and absent-field compatibility.
    This update does not modify those implementations.
    - 43 BE tests passed under ASAN. FileScannerV2 consumes the
    Hive-generated corpus across batches, projections, physical line
    endings, counts, split offsets, and BOMs, including disabled character
    settings and absent-flag generic decoding.
    - External regression explicitly enables FileScannerV2 and covers
    partitioned/unpartitioned layouts, table/SerDe properties, raw TEXTFILE
    records, projections, filters, counts, and metadata-only changes. Groovy
    compilation passed; the end-to-end external rerun remains pending CI.
    - A separate probe used Hive 3.1.3 SQL literal decoding and a real
    PostgreSQL 17 instance: the original fixture fails on NUL metadata; all
    12 property values in the four revised dialects persist successfully.
    - FE Checkstyle, clang-format 16 on all affected C++ files, header
    hygiene, License Eye, and whitespace checks passed. Repository
    clang-tidy remains blocked by the existing unmatched `NOLINTEND` in
    `core/types.h` and existing reader diagnostics. The V2 parser and
    physical-record helper have no clang-tidy diagnostics.
    - Behavior changed: Yes, Hive CSV property resolution and FileScannerV2
    row semantics now match OpenCSVSerde for the supported character
    settings.
    - Does this need documentation: No.
---
 .licenserc.yaml                                    |   4 +
 be/src/agent/be_exec_version_manager.cpp           |   4 +-
 be/src/agent/be_exec_version_manager.h             |   1 +
 be/src/format_v2/delimited_text/csv_reader.cpp     |  33 +-
 be/src/format_v2/delimited_text/csv_reader.h       |   4 +
 .../delimited_text/hive_csv_line_reader.cpp        |  40 ++
 .../delimited_text/hive_csv_line_reader.h          |  34 ++
 .../format_v2/delimited_text/hive_csv_parser.cpp   | 120 ++++
 be/src/format_v2/delimited_text/hive_csv_parser.h  |  45 ++
 .../delimited_text/hive_csv_reader_test.cpp        | 310 +++++++++++
 .../main/java/org/apache/doris/common/Config.java  |   4 +-
 fe/fe-connector/fe-connector-hive/pom.xml          |  15 +
 .../doris/connector/hive/HiveTextProperties.java   |  81 ++-
 .../hive/HiveCsvReaderCompatibilityTest.java       | 161 ++++++
 .../connector/hive/HiveScanBatchModeTest.java      |  25 +
 .../connector/hive/HiveTextPropertiesTest.java     | 169 ++++++
 .../src/test/resources/hive-csv-oracle.tsv         | 606 +++++++++++++++++++++
 .../connector/spi/scan/ScanNodePropertyKeys.java   |   3 +
 .../connector/spi/ConnectorPluginSurfaceTest.java  |  23 +-
 .../test/resources/connector-plugin-surface.txt    |  24 +
 fe/fe-connector/pom.xml                            |   2 +-
 .../datasource/scan/PluginDrivenScanNode.java      |   8 +
 .../PluginDrivenScanNodeCsvPropertiesTest.java     | 224 ++++++++
 .../PluginApiVersionWiringTest.java                |   8 +
 gensrc/thrift/PlanNodes.thrift                     |   4 +
 .../hive/test_csv_table_properties.groovy          | 266 +++++++++
 26 files changed, 2181 insertions(+), 37 deletions(-)

diff --git a/.licenserc.yaml b/.licenserc.yaml
index 7282390c0d1..92608eadef1 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -65,6 +65,10 @@ header:
     # the whole file and compares it to this one line for line, so a header 
would read back
     # as extra rows; it prints the regenerated path on mismatch instead.
     - "fe/fe-core/src/test/resources/access-control-behavior-baseline.txt"
+    # Hive-generated CSV oracle. The FE test compares every line with 
regenerated
+    # data, and both BE readers consume every line as a record; a header would
+    # become unexpected test data. Exclude only this fixture, not all TSV 
files.
+    - 
"fe/fe-connector/fe-connector-hive/src/test/resources/hive-csv-oracle.tsv"
     # Golden protocol traffic. MysqlPacketGoldenTest and 
FlightResultGoldenTest regenerate
     # each file in full and compare it to this one byte for byte, so a header 
would read back
     # as unexpected leading content; both tests print the regeneration command 
on mismatch.
diff --git a/be/src/agent/be_exec_version_manager.cpp 
b/be/src/agent/be_exec_version_manager.cpp
index aed123eca87..ded7a31240a 100644
--- a/be/src/agent/be_exec_version_manager.cpp
+++ b/be/src/agent/be_exec_version_manager.cpp
@@ -133,8 +133,10 @@ void 
BeExecVersionManager::check_function_compatibility(int current_be_exec_vers
 //   b. support Paimon default fixed-bucket routing in the external sink 
exchange.
 // 14: start from master
 //   a. support TIMESTAMP_NS in Thrift descriptors and PBlock exchange.
+// 15: start from master
+//   a. distinguish Hive OpenCSVSerde row semantics from generic CSV decoding 
during upgrades.
 
-const int BeExecVersionManager::max_be_exec_version = 
SUPPORT_TIMESTAMP_NS_VERSION;
+const int BeExecVersionManager::max_be_exec_version = 
SUPPORT_HIVE_OPEN_CSV_VERSION;
 const int BeExecVersionManager::min_be_exec_version = 0;
 std::map<std::string, std::set<int>> 
BeExecVersionManager::_function_change_map {};
 std::set<std::string> BeExecVersionManager::_function_restrict_map;
diff --git a/be/src/agent/be_exec_version_manager.h 
b/be/src/agent/be_exec_version_manager.h
index 32a317786a5..4a7ca9de6e4 100644
--- a/be/src/agent/be_exec_version_manager.h
+++ b/be/src/agent/be_exec_version_manager.h
@@ -34,6 +34,7 @@ constexpr inline int 
SUPPORT_ICEBERG_MERGE_CARDINALITY_VERSION = 11;
 constexpr inline int SUPPORT_ICEBERG_VARIANT_VERSION = 12;
 constexpr inline int SUPPORT_EXTERNAL_TABLE_SINK_HASH_VERSION = 13;
 constexpr inline int SUPPORT_TIMESTAMP_NS_VERSION = 14;
+constexpr inline int SUPPORT_HIVE_OPEN_CSV_VERSION = 15;
 
 class BeExecVersionManager {
 public:
diff --git a/be/src/format_v2/delimited_text/csv_reader.cpp 
b/be/src/format_v2/delimited_text/csv_reader.cpp
index bb6c55d3084..5bfd57346b9 100644
--- a/be/src/format_v2/delimited_text/csv_reader.cpp
+++ b/be/src/format_v2/delimited_text/csv_reader.cpp
@@ -26,6 +26,8 @@
 #include "core/data_type_serde/data_type_string_serde.h"
 #include "format/file_reader/new_plain_binary_line_reader.h"
 #include "format/file_reader/new_plain_text_line_reader.h"
+#include "format_v2/delimited_text/hive_csv_line_reader.h"
+#include "format_v2/delimited_text/hive_csv_parser.h"
 #include "gen_cpp/internal_service.pb.h"
 #include "runtime/descriptors.h"
 #include "runtime/runtime_state.h"
@@ -117,6 +119,17 @@ Status CsvReader::_init_format_state() {
     if (text_params.__isset.empty_field_as_null) {
         _empty_field_as_null = text_params.empty_field_as_null;
     }
+    if (_scan_params->file_attributes.hive_open_csv) {
+        size_t fields = _source_file_slot_descs.size();
+        for (int index : _scan_params->column_idxs) {
+            fields = std::max(fields, static_cast<size_t>(index) + 1);
+        }
+        _hive_csv_parser =
+                std::make_unique<HiveCsvParser>(_value_separator, _enclose, 
_escape, fields);
+        _options.escape_char = 0;
+        _options.converted_from_string = false;
+        _options.null_len = 0;
+    }
     return Status::OK();
 }
 
@@ -130,7 +143,9 @@ Status CsvReader::_create_decompressor() {
 Status CsvReader::_create_line_reader() {
     if (is_csv_text_format(_file_format_type)) {
         std::shared_ptr<TextLineReaderContextIf> text_line_reader_ctx;
-        if (_enclose == 0) {
+        if (_hive_csv_parser) {
+            text_line_reader_ctx = std::make_shared<HiveCsvLineReaderCtx>();
+        } else if (_enclose == 0) {
             text_line_reader_ctx = std::make_shared<PlainTextLineReaderCtx>(
                     _line_delimiter, _line_delimiter.size(), _keep_cr);
         } else {
@@ -164,6 +179,10 @@ Status CsvReader::_validate_line(const Slice& line) {
 
 void CsvReader::_split_line(const Slice& line) {
     _split_values.clear();
+    if (_hive_csv_parser) {
+        _hive_csv_parser->parse(line, &_split_values);
+        return;
+    }
     if (_file_format_type == TFileFormatType::FORMAT_PROTO) {
         auto** row_ptr = reinterpret_cast<PDataRow**>(line.data);
         PDataRow* row = *row_ptr;
@@ -212,10 +231,20 @@ void CsvReader::_split_line(const Slice& line) {
 Status CsvReader::_deserialize_one_cell(const RequestedColumn& column, 
IColumn* output,
                                         Slice value) {
     DORIS_CHECK(output != nullptr);
+    if (_hive_csv_parser && column.file_column_id.value() >= 
_split_values.size()) {
+        return _append_null(output);
+    }
     if (column.nullable_string_fast_path) {
         auto& null_column = assert_cast<ColumnNullable&>(*output);
         // String is the hottest CSV type. Avoid the generic nullable serde 
wrapper here:
         // deserialize directly into the nested string column and append the 
null map bit ourselves.
+        if (_hive_csv_parser) {
+            // Splitting and decoding are one operation for OpenCSV; a second 
CSV serde pass
+            // would unescape literal data and confuse empty strings with 
missing fields.
+            null_column.get_nested_column().insert_data(value.data, 
value.size);
+            null_column.get_null_map_data().push_back(0);
+            return Status::OK();
+        }
         if (_empty_field_as_null && value.size == 0) {
             null_column.insert_data(nullptr, 0);
             return Status::OK();
@@ -243,7 +272,7 @@ Status CsvReader::_deserialize_one_cell(const 
RequestedColumn& column, IColumn*
 }
 
 Slice CsvReader::_normalize_value(Slice value) const {
-    if (_empty_field_as_null && value.size == 0) {
+    if (!_hive_csv_parser && _empty_field_as_null && value.size == 0) {
         return Slice(_options.null_format, _options.null_len);
     }
     return value;
diff --git a/be/src/format_v2/delimited_text/csv_reader.h 
b/be/src/format_v2/delimited_text/csv_reader.h
index dd1d77d5a34..c8565e6792a 100644
--- a/be/src/format_v2/delimited_text/csv_reader.h
+++ b/be/src/format_v2/delimited_text/csv_reader.h
@@ -31,6 +31,8 @@ class SlotDescriptor;
 
 namespace doris::format::csv {
 
+class HiveCsvParser;
+
 // FileScannerV2 CSV reader.
 //
 // CSV files do not carry a physical schema. FE provides the table slot 
descriptors plus
@@ -62,9 +64,11 @@ private:
                                  Slice value) override;
     Slice _normalize_value(Slice value) const override;
     bool _can_split() const override;
+    bool _empty_line_as_record() const override { return _hive_csv_parser != 
nullptr; }
     void _on_bom_removed(size_t bom_size) override;
 
     TFileFormatType::type _file_format_type = 
TFileFormatType::FORMAT_CSV_PLAIN;
+    std::unique_ptr<HiveCsvParser> _hive_csv_parser;
     char _enclose = 0;
     bool _trim_double_quotes = false;
     bool _trim_tailing_spaces = false;
diff --git a/be/src/format_v2/delimited_text/hive_csv_line_reader.cpp 
b/be/src/format_v2/delimited_text/hive_csv_line_reader.cpp
new file mode 100644
index 00000000000..dcfc1da27b8
--- /dev/null
+++ b/be/src/format_v2/delimited_text/hive_csv_line_reader.cpp
@@ -0,0 +1,40 @@
+// 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/delimited_text/hive_csv_line_reader.h"
+
+namespace doris::format::csv {
+
+const uint8_t* HiveCsvLineReaderCtx::read_line(const uint8_t* start, size_t 
len) {
+    for (size_t i = 0; i < len; ++i) {
+        if (start[i] == '\n') {
+            _delimiter_length = 1;
+            return start + i;
+        }
+        if (start[i] == '\r') {
+            // Wait for lookahead when CR straddles input buffers. At EOF the 
parser removes it.
+            if (i + 1 == len) {
+                return nullptr;
+            }
+            _delimiter_length = start[i + 1] == '\n' ? 2 : 1;
+            return start + i;
+        }
+    }
+    return nullptr;
+}
+
+} // namespace doris::format::csv
diff --git a/be/src/format_v2/delimited_text/hive_csv_line_reader.h 
b/be/src/format_v2/delimited_text/hive_csv_line_reader.h
new file mode 100644
index 00000000000..7754fd31fb8
--- /dev/null
+++ b/be/src/format_v2/delimited_text/hive_csv_line_reader.h
@@ -0,0 +1,34 @@
+// 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 "format/file_reader/new_plain_text_line_reader.h"
+
+namespace doris::format::csv {
+
+class HiveCsvLineReaderCtx final : public TextLineReaderContextIf {
+public:
+    const uint8_t* read_line(const uint8_t* start, size_t len) override;
+    size_t line_delimiter_length() const override { return _delimiter_length; }
+    void refresh() override {}
+
+private:
+    size_t _delimiter_length = 1;
+};
+
+} // namespace doris::format::csv
diff --git a/be/src/format_v2/delimited_text/hive_csv_parser.cpp 
b/be/src/format_v2/delimited_text/hive_csv_parser.cpp
new file mode 100644
index 00000000000..9ebaef67477
--- /dev/null
+++ b/be/src/format_v2/delimited_text/hive_csv_parser.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 "format_v2/delimited_text/hive_csv_parser.h"
+
+#include <unicode/uchar.h>
+#include <unicode/utf8.h>
+
+#include <string_view>
+#include <utility>
+
+namespace doris::format::csv {
+namespace {
+
+UChar32 next_character(std::string_view input, size_t& pos) {
+    UChar32 character;
+    U8_NEXT(input, pos, input.size(), character);
+    return character;
+}
+
+} // namespace
+
+HiveCsvParser::HiveCsvParser(std::string separator, char quote, char escape, 
size_t field_limit)
+        : _separator(std::move(separator)),
+          _quote(quote),
+          _escape(escape),
+          _field_limit(field_limit) {}
+
+void HiveCsvParser::parse(const Slice& line, std::vector<Slice>* fields) {
+    fields->clear();
+    _decoded.clear();
+    _field_ends.clear();
+    std::string_view input(line.data, line.size);
+    // A terminal CR may reach here at EOF, before the line reader can look 
ahead for CRLF.
+    if (!input.empty() && input.back() == '\r') {
+        input.remove_suffix(1);
+    }
+    if (input.empty()) {
+        return;
+    }
+    bool in_quotes = false;
+    bool in_field = false;
+    bool all_whitespace = true;
+    bool previous_separator = false;
+    size_t field_start = 0;
+    size_t java_position = 0;
+    for (size_t pos = 0; pos < input.size() && _field_ends.size() < 
_field_limit;) {
+        size_t start = pos;
+        const UChar32 character = next_character(input, pos);
+        bool separator = input.substr(start, pos - start) == _separator;
+        if (character == _escape) {
+            // OpenCSV checks the escape branch even for NUL. An escape 
outside a field is
+            // discarded, while quotes/escapes inside a field consume their 
following byte.
+            if ((in_quotes || in_field) && pos < input.size() &&
+                (input[pos] == _quote || input[pos] == _escape)) {
+                _decoded.push_back(input[pos++]);
+                all_whitespace = all_whitespace && u_isWhitespace(input[pos - 
1]);
+                ++java_position;
+            }
+        } else if (character == _quote) {
+            if ((in_quotes || in_field) && pos < input.size() && input[pos] == 
_quote) {
+                _decoded.push_back(input[pos++]);
+                all_whitespace = all_whitespace && u_isWhitespace(_quote);
+                ++java_position;
+            } else {
+                // OpenCSV's embedded-quote rule uses the UTF-16 position in 
the entire record,
+                // not the field offset. Quotes still toggle state after a 
non-whitespace prefix.
+                if (java_position > 2 && !previous_separator && pos < 
input.size() &&
+                    !input.substr(pos).starts_with(_separator)) {
+                    if (_decoded.size() > field_start && all_whitespace) {
+                        _decoded.resize(field_start);
+                    } else {
+                        _decoded.push_back(_quote);
+                        all_whitespace = all_whitespace && 
u_isWhitespace(_quote);
+                    }
+                }
+                in_quotes = !in_quotes;
+            }
+            in_field = !in_field;
+        } else if (separator && !in_quotes) {
+            _field_ends.push_back(_decoded.size());
+            field_start = _decoded.size();
+            all_whitespace = true;
+            in_field = false;
+        } else {
+            _decoded.append(input.substr(start, pos - start));
+            all_whitespace = all_whitespace && u_isWhitespace(character);
+            in_field = true;
+        }
+        // Escaped lookahead cannot be a separator: FE validates distinct 
active characters.
+        previous_separator = separator;
+        java_position += character > 0xffff ? 2 : 1;
+    }
+    // CSVReader.readNext() returns completed fields at EOF and drops only the 
pending field.
+    // Do not merge the next physical record or discard fields completed 
before an unmatched quote.
+    if (!in_quotes && _field_ends.size() < _field_limit) {
+        _field_ends.push_back(_decoded.size());
+    }
+    size_t start = 0;
+    for (size_t end : _field_ends) {
+        fields->emplace_back(_decoded.data() + start, end - start);
+        start = end;
+    }
+}
+
+} // namespace doris::format::csv
diff --git a/be/src/format_v2/delimited_text/hive_csv_parser.h 
b/be/src/format_v2/delimited_text/hive_csv_parser.h
new file mode 100644
index 00000000000..2984dae485f
--- /dev/null
+++ b/be/src/format_v2/delimited_text/hive_csv_parser.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 <cstddef>
+#include <string>
+#include <vector>
+
+#include "util/slice.h"
+
+namespace doris::format::csv {
+
+// OpenCSVSerde runs one OpenCSV 2.3 reader per Hadoop TextInputFormat record.
+// Returned slices reference this parser's decoded buffer until the next 
parse().
+// The field limit bounds delimiter metadata by the requested column prefix.
+class HiveCsvParser {
+public:
+    HiveCsvParser(std::string separator, char quote, char escape, size_t 
field_limit);
+    void parse(const Slice& line, std::vector<Slice>* fields);
+
+private:
+    std::string _separator;
+    char _quote;
+    char _escape;
+    size_t _field_limit;
+    std::string _decoded;
+    std::vector<size_t> _field_ends;
+};
+
+} // namespace doris::format::csv
diff --git a/be/test/format_v2/delimited_text/hive_csv_reader_test.cpp 
b/be/test/format_v2/delimited_text/hive_csv_reader_test.cpp
new file mode 100644
index 00000000000..cc4eb8e03fb
--- /dev/null
+++ b/be/test/format_v2/delimited_text/hive_csv_reader_test.cpp
@@ -0,0 +1,310 @@
+// 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 <gtest/gtest.h>
+
+#include <array>
+#include <cstdlib>
+#include <filesystem>
+#include <fstream>
+#include <map>
+#include <optional>
+#include <sstream>
+
+#include "agent/be_exec_version_manager.h"
+#include "common/object_pool.h"
+#include "core/block/block.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/data_type/data_type_string.h"
+#include "format_v2/delimited_text/csv_reader.h"
+#include "format_v2/delimited_text/hive_csv_line_reader.h"
+#include "format_v2/delimited_text/hive_csv_parser.h"
+#include "io/io_common.h"
+#include "testutil/desc_tbl_builder.h"
+#include "testutil/mock/mock_runtime_state.h"
+
+namespace doris::format::csv {
+namespace {
+
+using OracleRow = std::array<std::optional<std::string>, 3>;
+struct OracleRecord {
+    std::string input;
+    OracleRow expected;
+};
+
+std::string unhex(const std::string& hex) {
+    if (hex == "EMPTY") {
+        return "";
+    }
+    std::string value;
+    for (size_t i = 0; i < hex.size(); i += 2) {
+        value.push_back(static_cast<char>(std::stoi(hex.substr(i, 2), nullptr, 
16)));
+    }
+    return value;
+}
+
+std::map<std::string, std::vector<OracleRecord>> read_oracle() {
+    // HiveCsvReaderCompatibilityTest regenerates and verifies this shared 
corpus with Hive 3.1.3.
+    const char* root = std::getenv("ROOT");
+    if (root == nullptr) {
+        ADD_FAILURE() << "Set ROOT to the repository root, as run-be-ut.sh 
does";
+        return {};
+    }
+    std::ifstream input(
+            std::string(root) +
+            
"/fe/fe-connector/fe-connector-hive/src/test/resources/hive-csv-oracle.tsv");
+    EXPECT_TRUE(input.is_open());
+    std::map<std::string, std::vector<OracleRecord>> corpus;
+    std::string line;
+    while (std::getline(input, line)) {
+        std::istringstream fields(line);
+        std::string tuple;
+        std::string raw;
+        std::getline(fields, tuple, '\t');
+        std::getline(fields, raw, '\t');
+        OracleRecord record {.input = unhex(raw), .expected = {}};
+        for (auto& value : record.expected) {
+            std::string encoded;
+            std::getline(fields, encoded, '\t');
+            if (encoded != "NULL") {
+                value = unhex(encoded);
+            }
+        }
+        corpus[unhex(tuple)].push_back(std::move(record));
+    }
+    EXPECT_EQ(corpus.size(), 6);
+    return corpus;
+}
+
+TEST(HiveCsvParserTest, HiveRecordOracle) {
+    for (const auto& [tuple, records] : read_oracle()) {
+        std::string separator = tuple.substr(0, tuple.size() - 2);
+        for (size_t limit : {1, 2, 3}) {
+            HiveCsvParser parser(separator, tuple[tuple.size() - 2], 
tuple.back(), limit);
+            for (const auto& record : records) {
+                std::vector<Slice> fields;
+                parser.parse(Slice(record.input), &fields);
+                ASSERT_LE(fields.size(), limit);
+                for (size_t i = 0; i < limit; ++i) {
+                    std::optional<std::string> actual;
+                    if (i < fields.size()) {
+                        actual = fields[i].to_string();
+                    }
+                    EXPECT_EQ(actual, record.expected[i]) << "field " << i;
+                }
+            }
+        }
+    }
+}
+
+TEST(HiveCsvCompatibilityTest, AdvertisesOpenCsvExecutionVersion) {
+    // Version 15 identifies the backend generation that implements the 
OpenCSV semantic flag.
+    EXPECT_GE(BeExecVersionManager::get_newest_version(), 15);
+    EXPECT_TRUE(BeExecVersionManager::check_be_exec_version(15).ok());
+    EXPECT_TRUE(BeExecVersionManager::check_be_exec_version(14).ok());
+}
+
+TEST(HiveCsvLineReaderTest, CrLfAcrossBuffersAndQuotes) {
+    HiveCsvLineReaderCtx context;
+    const std::string input = "qleft\r\nrightq|tail\n";
+    const auto* data = reinterpret_cast<const uint8_t*>(input.data());
+    EXPECT_EQ(context.read_line(data, 6), nullptr);
+    EXPECT_EQ(context.read_line(data, input.size()), data + 5);
+    EXPECT_EQ(context.line_delimiter_length(), 2);
+    context.refresh();
+    EXPECT_EQ(context.read_line(data + 7, input.size() - 7), data + 
input.size() - 1);
+    EXPECT_EQ(context.line_delimiter_length(), 1);
+    const std::string cr = "left\rright";
+    data = reinterpret_cast<const uint8_t*>(cr.data());
+    EXPECT_EQ(context.read_line(data, cr.size()), data + 4);
+    EXPECT_EQ(context.line_delimiter_length(), 1);
+}
+
+TFileScanRangeParams hive_csv_params(const std::string& tuple, const 
std::vector<int>& projection,
+                                     const std::vector<SlotDescriptor*>& slots,
+                                     bool hive_open_csv = true) {
+    TFileScanRangeParams params;
+    params.__set_format_type(TFileFormatType::FORMAT_CSV_PLAIN);
+    params.__set_file_type(TFileType::FILE_LOCAL);
+    params.__set_compress_type(TFileCompressType::PLAIN);
+    params.__set_column_idxs(projection);
+    if (hive_open_csv) {
+        params.file_attributes.__set_hive_open_csv(true);
+    } else {
+        // Thrift marks explicit defaults as set on construction; model an 
older sender omitting the field.
+        params.file_attributes.__isset.hive_open_csv = false;
+    }
+    params.file_attributes.__set_header_type("");
+    params.file_attributes.__set_trim_double_quotes(true);
+    auto& text = params.file_attributes.text_params;
+    text.__set_column_separator(tuple.substr(0, tuple.size() - 2));
+    text.__set_line_delimiter("\n");
+    text.__set_enclose(tuple[tuple.size() - 2]);
+    text.__set_escape(tuple.back());
+    text.__set_null_format("");
+    text.__set_empty_field_as_null(false);
+    params.file_attributes.__isset.text_params = true;
+    params.__isset.file_attributes = true;
+    for (auto* slot : slots) {
+        TFileScanSlotInfo info;
+        info.__set_slot_id(slot->id());
+        info.__set_is_file_slot(true);
+        params.required_slots.push_back(info);
+    }
+    return params;
+}
+
+void check_reader_rows(CsvReader* reader, const std::vector<SlotDescriptor*>& 
slots,
+                       const DataTypePtr& type, const 
std::vector<OracleRecord>& records,
+                       const std::vector<int>& projection, size_t 
skipped_rows) {
+    size_t row_index = skipped_rows;
+    bool eof = false;
+    while (!eof) {
+        Block block;
+        for (auto* slot : slots) {
+            block.insert({type->create_column(), type, slot->col_name()});
+        }
+        size_t rows = 0;
+        auto status = reader->get_block(&block, &rows, &eof);
+        ASSERT_TRUE(status.ok()) << status;
+        for (size_t row = 0; row < rows; ++row, ++row_index) {
+            ASSERT_LT(row_index, records.size());
+            for (size_t col = 0; col < projection.size(); ++col) {
+                const auto& column =
+                        static_cast<const 
ColumnNullable&>(*block.get_by_position(col).column);
+                std::optional<std::string> actual;
+                if (!column.is_null_at(row)) {
+                    actual = 
column.get_nested_column().get_data_at(row).to_string();
+                }
+                EXPECT_EQ(actual, records[row_index].expected[projection[col]])
+                        << "record " << row_index << " column " << 
projection[col];
+            }
+        }
+    }
+    EXPECT_EQ(row_index, records.size());
+}
+
+void check_reader_count(CsvReader* reader, size_t expected) {
+    format::FileAggregateRequest request;
+    request.agg_type = TPushAggOp::COUNT;
+    format::FileAggregateResult result;
+    ASSERT_TRUE(reader->get_aggregate_result(request, &result).ok());
+    EXPECT_EQ(result.count, expected);
+}
+
+class HiveOpenCsvReaderTest : public testing::Test {
+protected:
+    void SetUp() override {
+        _directory = std::filesystem::temp_directory_path() / 
"hive_csv_reader_v2";
+        std::filesystem::create_directories(_directory);
+    }
+    void TearDown() override { std::filesystem::remove_all(_directory); }
+
+    void check_file(const std::string& tuple, const std::vector<OracleRecord>& 
records,
+                    const std::string& delimiter, const std::vector<int>& 
projection,
+                    int64_t start_offset = 0, size_t skipped_rows = 0, bool 
count = false,
+                    bool hive_open_csv = true) {
+        const auto path = (_directory / "records.csv").string();
+        {
+            std::ofstream output(path, std::ios::binary);
+            for (const auto& record : records) {
+                output << record.input << delimiter;
+            }
+        }
+        MockRuntimeState state;
+        // Tiny batches exercise parser-buffer lifetime and state reset 
between blocks.
+        state._query_options.__set_batch_size(7);
+        state._query_options.__set_keep_carriage_return(true);
+        RuntimeProfile profile("hive_csv_reader");
+        ObjectPool pool;
+        DescriptorTblBuilder builder(&pool);
+        auto& tuple_builder = builder.declare_tuple();
+        auto type = make_nullable(std::make_shared<DataTypeString>());
+        for (int index : projection) {
+            tuple_builder << TupleDescBuilder::SlotType {type, "c" + 
std::to_string(index)};
+        }
+        auto slots = builder.build()->get_tuple_descriptor(0)->slots();
+        auto params = hive_csv_params(tuple, projection, slots, hive_open_csv);
+        ASSERT_EQ(params.file_attributes.__isset.hive_open_csv, hive_open_csv);
+        const auto file_size = std::filesystem::file_size(path);
+        auto properties = std::make_shared<io::FileSystemProperties>();
+        properties->system_type = TFileType::FILE_LOCAL;
+        auto description = std::make_unique<io::FileDescription>();
+        description->path = path;
+        description->range_start_offset = start_offset;
+        description->range_size = file_size - start_offset;
+        description->file_size = file_size;
+        CsvReader reader(properties, description, nullptr, &profile, &params, 
slots);
+        ASSERT_TRUE(reader.init(&state).ok());
+        auto request = std::make_shared<format::FileScanRequest>();
+        for (size_t i = 0; i < projection.size(); ++i) {
+            format::LocalColumnId id(projection[i]);
+            
request->non_predicate_columns.push_back(format::LocalColumnIndex::top_level(id));
+            request->local_positions.emplace(id, format::LocalIndex(i));
+        }
+        ASSERT_TRUE(reader.open(request).ok());
+        if (count) {
+            check_reader_count(&reader, records.size() - skipped_rows);
+        } else {
+            check_reader_rows(&reader, slots, type, records, projection, 
skipped_rows);
+        }
+        ASSERT_TRUE(reader.close().ok());
+    }
+
+    std::filesystem::path _directory;
+};
+
+TEST_F(HiveOpenCsvReaderTest, HiveRecordOracleAcrossBatchesAndProjections) {
+    for (const auto& [tuple, records] : read_oracle()) {
+        for (const std::string delimiter : {"\n", "\r\n", "\r"}) {
+            check_file(tuple, records, delimiter, {0, 1, 2});
+            check_file(tuple, records, delimiter, {2, 0});
+            check_file(tuple, records, delimiter, {0, 1, 2}, 0, 0, true);
+        }
+    }
+}
+
+TEST_F(HiveOpenCsvReaderTest, 
UnterminatedQuotesDoNotJoinPhysicalRecordsOrSplits) {
+    std::vector<OracleRecord> records = {
+            {.input = "qleft", .expected = {}},
+            {.input = "rightq|tail", .expected = {}},
+            {.input = "left|qunclosed", .expected = {"lft", {}, {}}},
+            {.input = "abcqleft|rightq|tail", .expected = {"abcqlft|right", 
"tail", {}}}};
+    check_file("|qe", records, "\n", {0, 1, 2});
+    check_file("|qe", records, "\n", {2, 0}, 2, 1);
+}
+
+TEST_F(HiveOpenCsvReaderTest, BomIsOnlyStrippedAtFileStart) {
+    std::vector<OracleRecord> records(9, {.input = "plain", .expected = 
{"plain", {}, {}}});
+    records[0].input = "\xef\xbb\xbfplain";
+    records[7] = {.input = "\xef\xbb\xbfplain", .expected = 
{"\xef\xbb\xbfplain", {}, {}}};
+    check_file("|qe", records, "\n", {0, 1, 2});
+}
+
+TEST_F(HiveOpenCsvReaderTest, AbsentSemanticFlagRetainsLegacyDecoding) {
+    // The same bytes distinguish a pre-flag request from the OpenCSV opt-in 
on a new backend.
+    std::string tuple(",\0e", 3);
+    std::vector<OracleRecord> records = {
+            {.input = "eeabc,tail,last", .expected = {"eabc", "tail", 
"last"}}};
+    check_file(tuple, records, "\n", {0, 1, 2}, 0, 0, false, false);
+    records[0].expected[0] = "abc";
+    check_file(tuple, records, "\n", {0, 1, 2});
+}
+
+} // namespace
+} // namespace doris::format::csv
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java 
b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index 9cf5ac9d09b..3a4b757f3c2 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -2008,9 +2008,11 @@ public class Config extends ConfigBase {
      * Max data version of backends serialize block.
      */
     public static final int TIMESTAMP_NS_MIN_BE_EXEC_VERSION = 14;
+    // Older backends ignore the optional OpenCSV flag and would silently use 
different row semantics.
+    public static final int HIVE_OPEN_CSV_MIN_BE_EXEC_VERSION = 15;
 
     @ConfField(mutable = false)
-    public static int max_be_exec_version = TIMESTAMP_NS_MIN_BE_EXEC_VERSION;
+    public static int max_be_exec_version = HIVE_OPEN_CSV_MIN_BE_EXEC_VERSION;
 
     /**
      * Min data version of backends serialize block.
diff --git a/fe/fe-connector/fe-connector-hive/pom.xml 
b/fe/fe-connector/fe-connector-hive/pom.xml
index 2152c990f53..4cb30703c3f 100644
--- a/fe/fe-connector/fe-connector-hive/pom.xml
+++ b/fe/fe-connector/fe-connector-hive/pom.xml
@@ -114,6 +114,14 @@ under the License.
             <artifactId>log4j-api</artifactId>
         </dependency>
 
+        <!-- Match the parser used by Hive 3.1.3 for reader-compatibility 
tests only. -->
+        <dependency>
+            <groupId>net.sf.opencsv</groupId>
+            <artifactId>opencsv</artifactId>
+            <version>2.3</version>
+            <scope>test</scope>
+        </dependency>
+
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter</artifactId>
@@ -146,6 +154,13 @@ under the License.
     <build>
         <finalName>doris-fe-connector-hive</finalName>
         <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <!-- Hive 3's real SerDe initializes StringInternUtils, 
which reflects on URI on Java 17. -->
+                    <argLine>@{argLine} 
--add-opens=java.base/java.net=ALL-UNNAMED</argLine>
+                </configuration>
+            </plugin>
             <plugin>
                 <artifactId>maven-assembly-plugin</artifactId>
                 <configuration>
diff --git 
a/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveTextProperties.java
 
b/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveTextProperties.java
index bcbaaeb535b..498ff5a8c27 100644
--- 
a/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveTextProperties.java
+++ 
b/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveTextProperties.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.connector.hive;
 
+import org.apache.doris.connector.spi.DorisConnectorException;
 import org.apache.doris.connector.spi.scan.ScanNodePropertyKeys;
 
 import java.util.HashMap;
@@ -37,8 +38,8 @@ import java.util.Map;
  * {@link HiveCatalogProperties} (per catalog) and {@link HmsConf} (per FE). 
Its input keys are remote HMS
  * table parameters and its output keys are a BE payload, so both sets belong 
to the single class that
  * reads them, which is this one. That is also why its two numeric parses stay 
lenient: a garbage
- * {@code skip.header.line.count} or delimiter in somebody else's Hive table 
must not fail the query the
- * way a garbage value a user typed into {@code CREATE CATALOG} now does.</p>
+ * {@code skip.header.line.count} or LazySimpleSerDe delimiter keeps its 
historical fallback. CSV
+ * character properties instead follow OpenCSVSerde validation and the BE 
wire-format limits.</p>
  */
 public final class HiveTextProperties {
 
@@ -103,7 +104,7 @@ public final class HiveTextProperties {
      *
      * @param serDeLib  the SerDe library class name
      * @param sdParams  the StorageDescriptor / SerDeInfo parameters
-     * @param tableParams the table-level parameters (for 
skip.header.line.count)
+     * @param tableParams the table-level SerDe properties and 
skip.header.line.count
      * @return map of text properties, empty if not a text-based format
      */
     public static Map<String, String> extract(String serDeLib,
@@ -117,7 +118,7 @@ public final class HiveTextProperties {
         if (HIVE_TEXT_SERDE.equals(serDeLib) || multiDelimit) {
             extractTextSerDeProps(sdParams, tableParams, result, multiDelimit);
         } else if (HIVE_OPEN_CSV_SERDE.equals(serDeLib)) {
-            extractCsvSerDeProps(sdParams, result);
+            extractCsvSerDeProps(sdParams, tableParams, result);
         } else if (HIVE_JSON_SERDE.equals(serDeLib) || 
LEGACY_HIVE_JSON_SERDE.equals(serDeLib)
                 || OPENX_JSON_SERDE.equals(serDeLib)) {
             extractJsonSerDeProps(serDeLib, sdParams, tableParams, result);
@@ -160,19 +161,35 @@ public final class HiveTextProperties {
     }
 
     private static void extractCsvSerDeProps(Map<String, String> params,
-            Map<String, String> result) {
-        result.put(ScanNodePropertyKeys.TEXT_COLUMN_SEPARATOR,
-                getParamOrDefault(params, SEPARATOR_CHAR, ","));
-        result.put(ScanNodePropertyKeys.TEXT_LINE_DELIMITER, 
getLineDelimiter(params));
-        String quoteChar = getParamOrDefault(params, QUOTE_CHAR, "\"");
+            Map<String, String> tableParams, Map<String, String> result) {
+        // Trino stores CSV settings in table parameters. Honor Hive's 
table-over-SerDe precedence
+        // so valid CSV files are not silently split with the default 
delimiter and quote characters.
+        String separator = getCsvCharacter(params, tableParams, 
SEPARATOR_CHAR, ',');
+        String quoteChar = getCsvCharacter(params, tableParams, QUOTE_CHAR, 
'"');
+        String escapeChar = getCsvCharacter(params, tableParams, ESCAPE_CHAR, 
'"');
+        // Hive treats the writer-default double quote as a sentinel: 
newReader selects the constructor
+        // whose effective escape is backslash. Resolve it before validating 
the parser's character tuple.
+        if ("\"".equals(escapeChar)) {
+            escapeChar = "\\";
+        }
+        if ("\0".equals(separator)) {
+            throw new DorisConnectorException("Invalid OpenCSVSerde property 
'separatorChar': must not be NUL");
+        }
+        // OpenCSV requires distinct active characters; NUL disables 
quote/escape and may be shared by both.
+        if (separator.equals(quoteChar) || separator.equals(escapeChar)
+                || (!"\0".equals(quoteChar) && quoteChar.equals(escapeChar))) {
+            throw new DorisConnectorException("Invalid OpenCSVSerde 
configuration: "
+                    + "separatorChar, quoteChar and escapeChar must be 
distinct when non-NUL");
+        }
+        result.put(ScanNodePropertyKeys.TEXT_COLUMN_SEPARATOR, separator);
+        // TextInputFormat owns physical records; neither table nor SerDe 
line.delim changes its reader.
+        result.put(ScanNodePropertyKeys.TEXT_LINE_DELIMITER, 
DEFAULT_LINE_DELIM);
+        result.put(ScanNodePropertyKeys.TEXT_HIVE_OPEN_CSV, "true");
+        // Smooth-upgrade sources may still use the pre-flag reader at the new 
query execution version.
+        result.put(ScanNodePropertyKeys.REQUIRED_CURRENT_BACKEND_SEMANTICS, 
"Hive OpenCSVSerde semantics");
         result.put(ScanNodePropertyKeys.TEXT_ENCLOSE, quoteChar);
-        // #65501: BE strips the wrapping quotes only when the enclose char is 
exactly the double-quote '"'.
-        // The connector owns this CSV serde semantics, so decide here and 
pass an explicit flag; the generic
-        // PluginDrivenScanNode then just applies it instead of trimming for 
any enclose char. Compare the
-        // first byte, matching how the node sets enclose 
(enclose.getBytes()[0]) and BE's getEnclose() == '"'.
-        boolean trimDoubleQuotes = !quoteChar.isEmpty() && 
quoteChar.getBytes()[0] == (byte) '"';
-        result.put(ScanNodePropertyKeys.TEXT_TRIM_DOUBLE_QUOTES, 
String.valueOf(trimDoubleQuotes));
-        String escapeChar = getParamOrDefault(params, ESCAPE_CHAR, "\\");
+        // BE's extra double-quote trimming is valid only for the effective 
double-quote enclosure.
+        result.put(ScanNodePropertyKeys.TEXT_TRIM_DOUBLE_QUOTES, 
String.valueOf("\"".equals(quoteChar)));
         result.put(ScanNodePropertyKeys.TEXT_ESCAPE, escapeChar);
         result.put(ScanNodePropertyKeys.TEXT_NULL_FORMAT, "");
     }
@@ -203,10 +220,6 @@ public final class HiveTextProperties {
         return supportMultiChar ? delim : getByte(delim, DEFAULT_FIELD_DELIM);
     }
 
-    private static String getLineDelimiter(Map<String, String> params) {
-        return getParamOrDefault(params, LINE_DELIM, "\n");
-    }
-
     /**
      * Looks up a SerDe property mirroring legacy {@code 
HiveMetaStoreClientHelper.getSerdeProperty}:
      * table parameters take precedence over StorageDescriptor/SerDeInfo 
parameters, and the keys are
@@ -257,12 +270,28 @@ public final class HiveTextProperties {
         }
     }
 
-    private static String getParamOrDefault(Map<String, String> params,
-            String key, String defaultVal) {
-        if (params == null) {
-            return defaultVal;
+    private static String getCsvCharacter(Map<String, String> params, 
Map<String, String> tableParams,
+            String key, char defaultValue) {
+        String value = serdeVal(params, tableParams, key);
+        if (value == null) {
+            return Character.toString(defaultValue);
+        }
+        if (value.isEmpty()) {
+            throw new DorisConnectorException("Invalid OpenCSVSerde property 
'" + key + "': value must not be empty");
+        }
+        // Hive uses charAt(0), not a complete string, Unicode code point, or 
numeric byte value.
+        // Validate only that effective character, after resolving 
table-over-SerDe precedence.
+        char character = value.charAt(0);
+        if (Character.isSurrogate(character)) {
+            throw new DorisConnectorException("Unsupported OpenCSVSerde 
property '" + key
+                    + "': the first Java character must not be a surrogate");
+        }
+        // Separator is a Thrift string, but quote/escape are i8 fields 
consumed as single UTF-8 bytes.
+        // Reject unrepresentable characters instead of silently passing only 
their first encoded byte.
+        if (!SEPARATOR_CHAR.equals(key) && character > 0x7f) {
+            throw new DorisConnectorException("Unsupported OpenCSVSerde 
property '" + key
+                    + "': the first character must be ASCII (a single UTF-8 
byte)");
         }
-        String val = params.get(key);
-        return (val != null) ? val : defaultVal;
+        return Character.toString(character);
     }
 }
diff --git 
a/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveCsvReaderCompatibilityTest.java
 
b/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveCsvReaderCompatibilityTest.java
new file mode 100644
index 00000000000..38c0557ef95
--- /dev/null
+++ 
b/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveCsvReaderCompatibilityTest.java
@@ -0,0 +1,161 @@
+// 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.
+
+package org.apache.doris.connector.hive;
+
+import org.apache.doris.connector.spi.DorisConnectorException;
+import org.apache.doris.connector.spi.scan.ScanNodePropertyKeys;
+
+import au.com.bytecode.opencsv.CSVReader;
+import org.apache.hadoop.hive.serde2.OpenCSVSerde;
+import org.apache.hadoop.hive.serde2.SerDeException;
+import org.apache.hadoop.io.Text;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HexFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+
+class HiveCsvReaderCompatibilityTest {
+    @Test
+    void testExplicitDefaultEscapeReadsLikeHive() throws Exception {
+        for (String quote : new String[] {"\"", "q"}) {
+            String record = quote + "left\\" + quote + "middle" + quote + 
",tail";
+            Map<String, String> properties = Map.of("quoteChar", quote, 
"escapeChar", "\"suffix");
+            List<?> expected = (List<?>) hiveSerde(properties).deserialize(new 
Text(record));
+            Assertions.assertEquals(List.of("left" + quote + "middle", 
"tail"), expected);
+            for (Map<String, String> extracted : List.of(
+                    
HiveTextProperties.extract(HiveTextProperties.HIVE_OPEN_CSV_SERDE, Map.of(), 
properties),
+                    
HiveTextProperties.extract(HiveTextProperties.HIVE_OPEN_CSV_SERDE, properties, 
Map.of()))) {
+                Assertions.assertEquals("\\", 
extracted.get(ScanNodePropertyKeys.TEXT_ESCAPE));
+                try (CSVReader reader = new CSVReader(new StringReader(record),
+                        
extracted.get(ScanNodePropertyKeys.TEXT_COLUMN_SEPARATOR).charAt(0),
+                        
extracted.get(ScanNodePropertyKeys.TEXT_ENCLOSE).charAt(0),
+                        
extracted.get(ScanNodePropertyKeys.TEXT_ESCAPE).charAt(0))) {
+                    Assertions.assertEquals(expected, 
Arrays.asList(reader.readNext()));
+                }
+            }
+        }
+    }
+
+    @Test
+    void testCharacterTuplesMatchHiveReaderAcceptance() throws Exception {
+        // Use Hive's real reader as the oracle, including its writer-default 
escape sentinel and NUL rules.
+        char[] characters = {'\0', ',', '|', '"', '\\', 'q', 'e'};
+        for (char separator : characters) {
+            for (char quote : characters) {
+                for (char escape : characters) {
+                    Map<String, String> properties = Map.of("separatorChar", 
String.valueOf(separator),
+                            "quoteChar", String.valueOf(quote), "escapeChar", 
String.valueOf(escape));
+                    OpenCSVSerde serde = hiveSerde(properties);
+                    boolean accepted;
+                    try {
+                        serde.deserialize(new Text(""));
+                        accepted = true;
+                    } catch (SerDeException e) {
+                        
Assertions.assertInstanceOf(UnsupportedOperationException.class, e.getCause());
+                        accepted = false;
+                    }
+                    assertAcceptance(accepted, Map.of(), properties);
+                    assertAcceptance(accepted, properties, Map.of());
+                    assertAcceptance(accepted, Map.of("quoteChar", 
String.valueOf(quote)),
+                            Map.of("separatorChar", String.valueOf(separator), 
"escapeChar", String.valueOf(escape)));
+                }
+            }
+        }
+    }
+
+    @Test
+    void testRecordOracle() throws Exception {
+        // This corpus is also consumed by FileScannerV2. Generate 
expectations with Hive itself,
+        // including null trailing fields and binary NULs, rather than a 
second hand-written parser.
+        List<String> records = new ArrayList<>(List.of("x|  qa|bq|c", 
"qaqqbq,tail", "eeabc,tail",
+                "qleft", "rightq|tail", "abcqleft|rightq|tail", "a\0b,tail", 
"", ",", "||",
+                "left|qunclosed", "qleftq|tail", "qleftqqrightq|tail", "x|\t 
qa|bq|c",
+                "x|\u2003\u2003qa|bq|c", "x|\u00a0qa|bq|c", 
"éqleft|rightq|tail",
+                "😀qleft|rightq|tail", "abcqleftérightqétail", "qéqétail", 
"\"\"\"x\"\",tail"));
+        Random random = new Random(7321);
+        String alphabet = "aqe|, \\" + '"' + '\0';
+        for (int row = 0; row < 80; row++) {
+            StringBuilder record = new StringBuilder();
+            for (int i = 0, length = random.nextInt(24); i < length; i++) {
+                
record.append(alphabet.charAt(random.nextInt(alphabet.length())));
+            }
+            records.add(record.toString());
+        }
+        List<String> corpus = new ArrayList<>();
+        for (String tuple : List.of("|qe", ",q\0", ",\0e", ",\0\0", ",\"\\", 
"éqe")) {
+            OpenCSVSerde serde = hiveSerde(Map.of("separatorChar", 
tuple.substring(0, 1),
+                    "quoteChar", tuple.substring(1, 2), "escapeChar", 
tuple.substring(2, 3)), 3);
+            for (String record : records) {
+                List<?> result = (List<?>) serde.deserialize(new Text(record));
+                StringBuilder encoded = new 
StringBuilder(hex(tuple)).append('\t').append(hex(record));
+                for (Object value : result) {
+                    encoded.append('\t').append(value == null ? "NULL" : 
hex(value.toString()));
+                }
+                corpus.add(encoded.toString());
+            }
+        }
+        String output = System.getProperty("hive.csv.oracle.output");
+        if (output != null) {
+            Files.write(Path.of(output), corpus, StandardCharsets.UTF_8);
+        }
+        try (var input = 
getClass().getResourceAsStream("/hive-csv-oracle.tsv")) {
+            Assertions.assertNotNull(input);
+            Assertions.assertEquals(corpus,
+                    new String(input.readAllBytes(), 
StandardCharsets.UTF_8).lines().toList());
+        }
+    }
+
+    private static String hex(String value) {
+        return value.isEmpty() ? "EMPTY" : 
HexFormat.of().formatHex(value.getBytes(StandardCharsets.UTF_8));
+    }
+
+    private static void assertAcceptance(boolean accepted, Map<String, String> 
serdeProperties,
+            Map<String, String> tableProperties) {
+        if (accepted) {
+            Assertions.assertDoesNotThrow(() -> HiveTextProperties.extract(
+                    HiveTextProperties.HIVE_OPEN_CSV_SERDE, serdeProperties, 
tableProperties));
+        } else {
+            Assertions.assertThrows(DorisConnectorException.class, () -> 
HiveTextProperties.extract(
+                    HiveTextProperties.HIVE_OPEN_CSV_SERDE, serdeProperties, 
tableProperties));
+        }
+    }
+
+    private static OpenCSVSerde hiveSerde(Map<String, String> properties) 
throws SerDeException {
+        return hiveSerde(properties, 2);
+    }
+
+    private static OpenCSVSerde hiveSerde(Map<String, String> properties, int 
columns) throws SerDeException {
+        Properties serdeProperties = new Properties();
+        serdeProperties.setProperty("columns", columns == 2 ? "first,second" : 
"first,second,third");
+        serdeProperties.setProperty("columns.types", columns == 2 ? 
"string:string" : "string:string:string");
+        serdeProperties.putAll(properties);
+        OpenCSVSerde serde = new OpenCSVSerde();
+        serde.initialize(null, serdeProperties);
+        return serde;
+    }
+}
diff --git 
a/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveScanBatchModeTest.java
 
b/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveScanBatchModeTest.java
index 720d9b91066..2c1937e9739 100644
--- 
a/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveScanBatchModeTest.java
+++ 
b/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveScanBatchModeTest.java
@@ -31,6 +31,7 @@ import 
org.apache.doris.connector.spi.scan.ConnectorScanPlanProvider;
 import org.apache.doris.connector.spi.scan.ConnectorScanProfile;
 import org.apache.doris.connector.spi.scan.ConnectorScanRange;
 import org.apache.doris.connector.spi.scan.ConnectorScanRequest;
+import org.apache.doris.connector.spi.scan.ScanNodePropertyKeys;
 import org.apache.doris.filesystem.FileSystem;
 import org.apache.doris.thrift.TFileCompressType;
 import org.apache.doris.thrift.TFileScanRangeParams;
@@ -418,6 +419,30 @@ public class HiveScanBatchModeTest {
         Assertions.assertEquals("aliasAK", 
props.get("location.s3.access_key"));
     }
 
+    @Test
+    public void 
getScanNodePropertiesUsesCsvTableParametersForPartitionedAndUnpartitionedTables()
 {
+        HiveScanPlanProvider provider = provider(new FakeHmsClient(), new 
CountingLister());
+        for (List<String> partitionKeys : 
Arrays.asList(Collections.<String>emptyList(),
+                Collections.singletonList("bucket"))) {
+            HiveTableHandle handle = new HiveTableHandle.Builder("db", 
"csv_table", HiveTableType.HIVE)
+                    .inputFormat("org.apache.hadoop.mapred.TextInputFormat")
+                    .serializationLib(HiveTextProperties.HIVE_OPEN_CSV_SERDE)
+                    .partitionKeyNames(partitionKeys)
+                    .tableParameters(Map.of("separatorChar", "s", "quoteChar", 
"q", "escapeChar", "e"))
+                    .build();
+            Map<String, String> props = provider.getScanNodeProperties(
+                    new FakeSession(), handle, Collections.emptyList(), 
Optional.empty());
+            Assertions.assertAll(
+                    () -> Assertions.assertEquals("csv", 
props.get(ScanNodePropertyKeys.FILE_FORMAT_TYPE)),
+                    () -> Assertions.assertEquals("s", 
props.get(ScanNodePropertyKeys.TEXT_COLUMN_SEPARATOR)),
+                    () -> Assertions.assertEquals("q", 
props.get(ScanNodePropertyKeys.TEXT_ENCLOSE)),
+                    () -> Assertions.assertEquals("e", 
props.get(ScanNodePropertyKeys.TEXT_ESCAPE)),
+                    () -> Assertions.assertEquals("false", 
props.get(ScanNodePropertyKeys.TEXT_TRIM_DOUBLE_QUOTES)));
+            Assertions.assertEquals(partitionKeys.isEmpty() ? null : "bucket",
+                    props.get(ScanNodePropertyKeys.PATH_PARTITION_KEYS));
+        }
+    }
+
     // ============ #65437: scan-level transactional_hive marker 
(FileScannerV2 exclusion) ============
     // Sibling to supportsBatchScan's ACID exclusion above. BE picks 
FileScannerV2 from SCAN-LEVEL params before the
     // per-range splits arrive, and V2 does not apply ACID delete deltas. A 
full-ACID hive scan must therefore carry
diff --git 
a/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveTextPropertiesTest.java
 
b/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveTextPropertiesTest.java
index 8723ad4d0a4..702ae5c287d 100644
--- 
a/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveTextPropertiesTest.java
+++ 
b/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveTextPropertiesTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.doris.connector.hive;
 
+import org.apache.doris.connector.spi.DorisConnectorException;
+
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -176,4 +178,171 @@ public class HiveTextPropertiesTest {
         Map<String, String> r = HiveTextProperties.extract(OPEN_CSV_SERDE, 
sd("quoteChar", "\""), new HashMap<>());
         Assertions.assertEquals("true", r.get(PREFIX + "trim_double_quotes"));
     }
+
+    @Test
+    public void testCsvTableParameters() {
+        Map<String, String> result = 
HiveTextProperties.extract(OPEN_CSV_SERDE, sd(),
+                sd("separatorChar", "s", "quoteChar", "q", "escapeChar", "e"));
+        assertCsvProperties(result, "s", "q", "e", "false");
+    }
+
+    @Test
+    public void testCsvTableParametersOverrideSerdeParameters() {
+        Map<String, String> result = HiveTextProperties.extract(OPEN_CSV_SERDE,
+                sd("separatorChar", ",", "quoteChar", "\"", "escapeChar", 
"\\"),
+                sd("separatorChar", "s", "quoteChar", "q", "escapeChar", "e"));
+        assertCsvProperties(result, "s", "q", "e", "false");
+    }
+
+    @Test
+    public void testCsvPropertiesFallBackIndividually() {
+        Map<String, String> result = HiveTextProperties.extract(OPEN_CSV_SERDE,
+                sd("separatorChar", "s", "quoteChar", "q"), sd("quoteChar", 
"\""));
+        assertCsvProperties(result, "s", "\"", "\\", "true");
+    }
+
+    @Test
+    public void testCsvSerdeParameters() {
+        Map<String, String> result = HiveTextProperties.extract(OPEN_CSV_SERDE,
+                sd("separatorChar", "s", "quoteChar", "q", "escapeChar", "e"), 
null);
+        assertCsvProperties(result, "s", "q", "e", "false");
+    }
+
+    @Test
+    public void testCsvDefaultsWithMissingParameterMaps() {
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE, null, 
null),
+                ",", "\"", "\\", "true");
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE, null,
+                sd("separatorChar", "s", "quoteChar", "q", "escapeChar", "e")),
+                "s", "q", "e", "false");
+    }
+
+    @Test
+    public void testCsvIgnoresTableAndSerdeRecordDelimiters() {
+        for (String value : new String[] {"|", "", "\r\n"}) {
+            Map<String, String> result = 
HiveTextProperties.extract(OPEN_CSV_SERDE, sd(), sd("line.delim", value));
+            Assertions.assertEquals("\n", result.get(PREFIX + 
"line_delimiter"));
+        }
+        Map<String, String> result = HiveTextProperties.extract(OPEN_CSV_SERDE,
+                sd("line.delim", "\r\n"), sd("line.delim", "|"));
+        Assertions.assertEquals("\n", result.get(PREFIX + "line_delimiter"));
+    }
+
+    @Test
+    public void testCsvUsesFirstJavaCharacterFromEitherPropertySource() {
+        Map<String, String> properties = sd("separatorChar", "ss", 
"quoteChar", "qq", "escapeChar", "ee");
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE, sd(), 
properties),
+                "s", "q", "e", "false");
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE, 
properties, sd()),
+                "s", "q", "e", "false");
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE, sd(),
+                sd("separatorChar", "99", "quoteChar", "\"suffix", 
"escapeChar", "eé")),
+                "9", "\"", "e", "true");
+    }
+
+    @Test
+    public void testCsvRejectsEmptyCharacterPropertiesInsteadOfFallingBack() {
+        for (String key : new String[] {"separatorChar", "quoteChar", 
"escapeChar"}) {
+            DorisConnectorException tableError = 
Assertions.assertThrows(DorisConnectorException.class,
+                    () -> HiveTextProperties.extract(OPEN_CSV_SERDE, sd(key, 
"x"), sd(key, "")));
+            Assertions.assertTrue(tableError.getMessage().contains(key));
+            Assertions.assertTrue(tableError.getMessage().contains("empty"));
+            Assertions.assertThrows(DorisConnectorException.class,
+                    () -> HiveTextProperties.extract(OPEN_CSV_SERDE, sd(key, 
""), sd()));
+        }
+    }
+
+    @Test
+    public void testCsvRejectsNonAsciiQuoteAndEscapeFromEitherSource() {
+        for (String key : new String[] {"quoteChar", "escapeChar"}) {
+            for (String value : new String[] {"é", "中", "😀"}) {
+                Assertions.assertThrows(DorisConnectorException.class,
+                        () -> HiveTextProperties.extract(OPEN_CSV_SERDE, sd(), 
sd(key, value)), key);
+                Assertions.assertThrows(DorisConnectorException.class,
+                        () -> HiveTextProperties.extract(OPEN_CSV_SERDE, 
sd(key, value), sd()), key);
+            }
+        }
+    }
+
+    @Test
+    public void testCsvPreservesUtf8SeparatorButRejectsSurrogates() {
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE, sd(), 
sd("separatorChar", "ésuffix")),
+                "é", "\"", "\\", "true");
+        for (String value : new String[] {"😀", 
String.valueOf(Character.MIN_HIGH_SURROGATE),
+                String.valueOf(Character.MIN_LOW_SURROGATE)}) {
+            Assertions.assertThrows(DorisConnectorException.class,
+                    () -> HiveTextProperties.extract(OPEN_CSV_SERDE, sd(), 
sd("separatorChar", value)));
+        }
+    }
+
+    @Test
+    public void testCsvValidatesOnlyTheEffectiveCharacterProperties() {
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE,
+                sd("separatorChar", "", "quoteChar", "é", "escapeChar", ""),
+                sd("separatorChar", "ss", "quoteChar", "qq", "escapeChar", 
"ee")),
+                "s", "q", "e", "false");
+    }
+
+    @Test
+    public void testCsvDefaultEscapeSentinelFromEitherPropertySource() {
+        for (String value : new String[] {"\"", "\"suffix"}) {
+            assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE, 
sd(), sd("escapeChar", value)),
+                    ",", "\"", "\\", "true");
+            assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE, 
sd("escapeChar", value), sd()),
+                    ",", "\"", "\\", "true");
+        }
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE,
+                sd("escapeChar", "e"), sd("escapeChar", "\"", "quoteChar", 
"q")),
+                ",", "q", "\\", "false");
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE,
+                sd("escapeChar", "\""), sd("escapeChar", "e")), ",", "\"", 
"e", "true");
+    }
+
+    @Test
+    public void testCsvRejectsConflictingEffectiveCharacters() {
+        String[][] pairs = {{"separatorChar", "quoteChar"}, {"separatorChar", 
"escapeChar"},
+                {"quoteChar", "escapeChar"}};
+        for (String[] pair : pairs) {
+            Map<String, String> properties = sd(pair[0], "|first", pair[1], 
"|second");
+            Assertions.assertThrows(DorisConnectorException.class,
+                    () -> HiveTextProperties.extract(OPEN_CSV_SERDE, sd(), 
properties));
+            Assertions.assertThrows(DorisConnectorException.class,
+                    () -> HiveTextProperties.extract(OPEN_CSV_SERDE, 
properties, sd()));
+            Assertions.assertThrows(DorisConnectorException.class,
+                    () -> HiveTextProperties.extract(OPEN_CSV_SERDE, 
sd(pair[0], "|"), sd(pair[1], "|")));
+        }
+        // Resolve the writer-default escape sentinel before checking 
parser-character conflicts.
+        Assertions.assertThrows(DorisConnectorException.class,
+                () -> HiveTextProperties.extract(OPEN_CSV_SERDE,
+                        sd("separatorChar", "\\"), sd("escapeChar", "\"")));
+        Assertions.assertThrows(DorisConnectorException.class,
+                () -> HiveTextProperties.extract(OPEN_CSV_SERDE,
+                        sd("quoteChar", "\\"), sd("escapeChar", "\"")));
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE, sd(),
+                sd("separatorChar", "\"", "quoteChar", "q", "escapeChar", 
"\"")),
+                "\"", "q", "\\", "false");
+    }
+
+    @Test
+    public void testCsvRejectsNulSeparatorButAllowsDisabledQuoteAndEscape() {
+        Assertions.assertThrows(DorisConnectorException.class,
+                () -> HiveTextProperties.extract(OPEN_CSV_SERDE, sd(), 
sd("separatorChar", "\0")));
+        Assertions.assertThrows(DorisConnectorException.class,
+                () -> HiveTextProperties.extract(OPEN_CSV_SERDE, 
sd("separatorChar", "\0"), sd()));
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE, sd(),
+                sd("quoteChar", "\0", "escapeChar", "\0")), ",", "\0", "\0", 
"false");
+        assertCsvProperties(HiveTextProperties.extract(OPEN_CSV_SERDE,
+                sd("quoteChar", "\0", "escapeChar", "\0"), sd()), ",", "\0", 
"\0", "false");
+    }
+
+    private static void assertCsvProperties(Map<String, String> result,
+            String separator, String quote, String escape, String 
trimDoubleQuotes) {
+        Assertions.assertAll(
+                () -> Assertions.assertEquals(separator, result.get(PREFIX + 
"column_separator")),
+                () -> Assertions.assertEquals(quote, result.get(PREFIX + 
"enclose")),
+                () -> Assertions.assertEquals(escape, result.get(PREFIX + 
"escape")),
+                () -> Assertions.assertEquals(trimDoubleQuotes, 
result.get(PREFIX + "trim_double_quotes")),
+                () -> Assertions.assertEquals("\n", result.get(PREFIX + 
"line_delimiter")),
+                () -> Assertions.assertEquals("", result.get(PREFIX + 
"null_format")));
+    }
 }
diff --git 
a/fe/fe-connector/fe-connector-hive/src/test/resources/hive-csv-oracle.tsv 
b/fe/fe-connector/fe-connector-hive/src/test/resources/hive-csv-oracle.tsv
new file mode 100644
index 00000000000..c07372f3d64
--- /dev/null
+++ b/fe/fe-connector/fe-connector-hive/src/test/resources/hive-csv-oracle.tsv
@@ -0,0 +1,606 @@
+7c7165 787c202071617c62717c63  78      617c62  63
+7c7165 7161717162712c7461696c  617162712c7461696c      NULL    NULL
+7c7165 65656162632c7461696c    6162632c7461696c        NULL    NULL
+7c7165 716c656674      NULL    NULL    NULL
+7c7165 7269676874717c7461696c  NULL    NULL    NULL
+7c7165 616263716c6566747c7269676874717c7461696c        
616263716c66747c7269676874      7461696c        NULL
+7c7165 6100622c7461696c        6100622c7461696c        NULL    NULL
+7c7165 EMPTY   NULL    NULL    NULL
+7c7165 2c      2c      NULL    NULL
+7c7165 7c7c    EMPTY   EMPTY   EMPTY
+7c7165 6c6566747c71756e636c6f736564    6c6674  NULL    NULL
+7c7165 716c656674717c7461696c  6c6674  7461696c        NULL
+7c7165 716c65667471717269676874717c7461696c    6c6674717269676874      
7461696c        NULL
+7c7165 787c092071617c62717c63  78      617c62  63
+7c7165 787ce28083e2808371617c62717c63  78      617c62  63
+7c7165 787cc2a071617c62717c63  78      c2a071617c62    63
+7c7165 c3a9716c6566747c7269676874717c7461696c  c3a96c66747c7269676874  
7461696c        NULL
+7c7165 f09f9880716c6566747c7269676874717c7461696c      
f09f98806c66747c7269676874      7461696c        NULL
+7c7165 616263716c656674c3a9726967687471c3a97461696c    
616263716c6674c3a9726967687471c3a97461696c      NULL    NULL
+7c7165 71c3a971c3a97461696c    c3a9c3a97461696c        NULL    NULL
+7c7165 2222227822222c7461696c  2222227822222c7461696c  NULL    NULL
+7c7165 6520007c205c2000616120202c5c2c  2000    205c2000616120202c5c2c  NULL
+7c7165 20      20      NULL    NULL
+7c7165 EMPTY   NULL    NULL    NULL
+7c7165 222061205c7c20717c65005c205c71715c00617c71      222061205c      
207c005c205c715c00617c  NULL
+7c7165 007161202c7c7161612071652c2071  0061202c7c616120712c20  NULL    NULL
+7c7165 71007c0065657c00616100207c7c    NULL    NULL    NULL
+7c7165 22710022617161612020    220022617161612020      NULL    NULL
+7c7165 5c7c6161617c20716520205c2c      5c      616161  NULL
+7c7165 65      EMPTY   NULL    NULL
+7c7165 00652c227c6561222200222c2222207165617c2265005c  002c22  NULL    NULL
+7c7165 5c7165225c71226161      5c225c71226161  NULL    NULL
+7c7165 7c005c2c007c22612c2c65  EMPTY   005c2c00        22612c2c
+7c7165 612c5c20205c7c5c7c00226520717c2c655c655c2c6122  612c5c20205c    5c      
NULL
+7c7165 007c6161200022615c202061227122  00      NULL    NULL
+7c7165 2c5c20002c5c5c2c225c5c71        NULL    NULL    NULL
+7c7165 2265652022712c712c00657120202261207c5c  22652022712c712c00712020226120  
5c      NULL
+7c7165 657c5c225c0065225c65656565      EMPTY   5c225c00225c6565        NULL
+7c7165 2c5c715c5c20225c71227c5c00656120717c7c5c61      2c5c5c5c20225c7122      
NULL    NULL
+7c7165 206561612c20000065652265205c00  2061612c2000006522205c00        NULL    
NULL
+7c7165 00002c2c5c20615c5c00    00002c2c5c20615c5c00    NULL    NULL
+7c7165 717c6161222c    NULL    NULL    NULL
+7c7165 0020226171655c5c        NULL    NULL    NULL
+7c7165 5c007c22        5c00    22      NULL
+7c7165 0022202261      0022202261      NULL    NULL
+7c7165 6500617c220020  0061    220020  NULL
+7c7165 20655c7c5c      205c    5c      NULL
+7c7165 20612c2c71006171        20612c2c710061  NULL    NULL
+7c7165 202222717c7c5c7120202222226565617c205c  2022227c7c5c7120202222226561    
205c    NULL
+7c7165 205c2c007120207c5c005c20002c22007c5c5c5c22      NULL    NULL    NULL
+7c7165 7c20712200652c7c2c2c    EMPTY   NULL    NULL
+7c7165 65202c005c2061007161227c5c7c2c717c65657c61      
202c005c2061007161227c5c7c2c    EMPTY   61
+7c7165 00227c7c7c715c652c0020712c71615c        0022    EMPTY   EMPTY
+7c7165 007c    00      EMPTY   NULL
+7c7165 2c0020206120617171716561612c5c20        NULL    NULL    NULL
+7c7165 657161205c22    NULL    NULL    NULL
+7c7165 655c202c65207c7165612271225c657c        5c202c20        612271225c      
EMPTY
+7c7165 6520612022612c2261      20612022612c2261        NULL    NULL
+7c7165 2c2c71007c71615c7c22715c2271    2c2c007c615c    22715c22        NULL
+7c7165 EMPTY   NULL    NULL    NULL
+7c7165 6561225c        61225c  NULL    NULL
+7c7165 7c617c225c22206520      EMPTY   61      225c222020
+7c7165 EMPTY   NULL    NULL    NULL
+7c7165 61      61      NULL    NULL
+7c7165 227c00  22      00      NULL
+7c7165 61612c00615c2c5c5c226520202c002061007c5c71      
61612c00615c2c5c5c2220202c00206100      NULL    NULL
+7c7165 2000    2000    NULL    NULL
+7c7165 22005c7c5c6171657c71656120006161        22005c  5c61717c6120006161      
NULL
+7c7165 617c2c  61      2c      NULL
+7c7165 715c612c2c61    NULL    NULL    NULL
+7c7165 2c207c2c225c2c7161      2c20    NULL    NULL
+7c7165 657c652c2c6500002200656565207c6520      EMPTY   2c2c000022006520        
20
+7c7165 7122005c22207171612020712c2c61615c206122712c65  NULL    NULL    NULL
+7c7165 2c715c71657c61716122657c        2c5c71  NULL    NULL
+7c7165 00      00      NULL    NULL
+7c7165 2061    2061    NULL    NULL
+7c7165 205c2000        205c2000        NULL    NULL
+7c7165 71655c2c207c20715c0000006500227c6161    5c2c207c20715c0000000022        
6161    NULL
+7c7165 5c6571  5c71    NULL    NULL
+7c7165 007c2c652c7171  00      2c2c71  NULL
+7c7165 00615c  00615c  NULL    NULL
+7c7165 71656522006565225c00    NULL    NULL    NULL
+7c7165 002c7165205c65227c6165716565207c716171612c2c    NULL    NULL    NULL
+7c7165 222c71206120005c612c2c  NULL    NULL    NULL
+7c7165 7c227c617c7c22206500712022002c7c00652c5c        EMPTY   22      61
+7c7165 61612c225c5c20652222205c2071222261      NULL    NULL    NULL
+7c7165 61      61      NULL    NULL
+7c7165 205c2c5c2c      205c2c5c2c      NULL    NULL
+7c7165 2c6500007c22    2c0000  22      NULL
+7c7165 5c7c2c65202c5c7c610022227c225c  5c      2c202c5c        61002222
+7c7165 655c2c002c7165716561205c7100207c2c7171225c2c20  
5c2c002c717161205c710020        2c71225c2c20    NULL
+7c7165 2c22    2c22    NULL    NULL
+7c7165 6571    NULL    NULL    NULL
+7c7165 5c205c  5c205c  NULL    NULL
+7c7165 65715c0071615c7c2271207c5c7161207c00207c65      5c0071615c      
2271207c5c716120        0020
+7c7165 205c20615c220000222c615c5c2c00225c71    NULL    NULL    NULL
+7c7165 2c7c5c20657c5c7c202c    2c      5c20    5c
+7c7165 5c65002c005c5c2c2220    5c002c005c5c2c2220      NULL    NULL
+7c7165 71      NULL    NULL    NULL
+7c7165 71617c7c65615c61715c2c652200    617c7c615c61715c2c2200  NULL    NULL
+7c7165 7c7165715c5c5c612065655c        EMPTY   NULL    NULL
+2c7100 787c202071617c62717c63  787c202071617c62717c63  NULL    NULL
+2c7100 7161717162712c7461696c  617162  7461696c        NULL
+2c7100 65656162632c7461696c    6565616263      7461696c        NULL
+2c7100 716c656674      NULL    NULL    NULL
+2c7100 7269676874717c7461696c  NULL    NULL    NULL
+2c7100 616263716c6566747c7269676874717c7461696c        
616263716c6566747c7269676874717c7461696c        NULL    NULL
+2c7100 6100622c7461696c        6162    7461696c        NULL
+2c7100 EMPTY   NULL    NULL    NULL
+2c7100 2c      EMPTY   EMPTY   NULL
+2c7100 7c7c    7c7c    NULL    NULL
+2c7100 6c6566747c71756e636c6f736564    NULL    NULL    NULL
+2c7100 716c656674717c7461696c  6c656674717c7461696c    NULL    NULL
+2c7100 716c65667471717269676874717c7461696c    
6c656674717269676874717c7461696c        NULL    NULL
+2c7100 787c092071617c62717c63  787c092071617c62717c63  NULL    NULL
+2c7100 787ce28083e2808371617c62717c63  787ce28083e2808371617c62717c63  NULL    
NULL
+2c7100 787cc2a071617c62717c63  787cc2a071617c62717c63  NULL    NULL
+2c7100 c3a9716c6566747c7269676874717c7461696c  
c3a96c6566747c7269676874717c7461696c    NULL    NULL
+2c7100 f09f9880716c6566747c7269676874717c7461696c      
f09f98806c6566747c7269676874717c7461696c        NULL    NULL
+2c7100 616263716c656674c3a9726967687471c3a97461696c    
616263716c656674c3a9726967687471c3a97461696c    NULL    NULL
+2c7100 71c3a971c3a97461696c    c3a9c3a97461696c        NULL    NULL
+2c7100 2222227822222c7461696c  222222782222    7461696c        NULL
+2c7100 6520007c205c2000616120202c5c2c  65207c205c2061612020    5c      EMPTY
+2c7100 20      20      NULL    NULL
+2c7100 EMPTY   NULL    NULL    NULL
+2c7100 222061205c7c20717c65005c205c71715c00617c71      
222061205c7c20717c655c205c715c617c      NULL    NULL
+2c7100 007161202c7c7161612071652c2071  61202c7c7161612071652c20        NULL    
NULL
+2c7100 71007c0065657c00616100207c7c    NULL    NULL    NULL
+2c7100 22710022617161612020    2222617161612020        NULL    NULL
+2c7100 5c7c6161617c20716520205c2c      NULL    NULL    NULL
+2c7100 65      65      NULL    NULL
+2c7100 00652c227c6561222200222c2222207165617c2265005c  65      227c6561222222  
NULL
+2c7100 5c7165225c71226161      5c65225c71226161        NULL    NULL
+2c7100 7c005c2c007c22612c2c65  7c5c    7c2261  EMPTY
+2c7100 612c5c20205c7c5c7c00226520717c2c655c655c2c6122  61      NULL    NULL
+2c7100 007c6161200022615c202061227122  NULL    NULL    NULL
+2c7100 2c5c20002c5c5c2c225c5c71        EMPTY   5c20    5c5c
+2c7100 2265652022712c712c00657120202261207c5c  22656520222c    NULL    NULL
+2c7100 657c5c225c0065225c65656565      657c5c225c65225c65656565        NULL    
NULL
+2c7100 2c5c715c5c20225c71227c5c00656120717c7c5c61      EMPTY   NULL    NULL
+2c7100 206561612c20000065652265205c00  20656161        200065652265205c        
NULL
+2c7100 00002c2c5c20615c5c00    EMPTY   EMPTY   5c20615c5c
+2c7100 717c6161222c    NULL    NULL    NULL
+2c7100 0020226171655c5c        NULL    NULL    NULL
+2c7100 5c007c22        5c7c22  NULL    NULL
+2c7100 0022202261      22202261        NULL    NULL
+2c7100 6500617c220020  65617c2220      NULL    NULL
+2c7100 20655c7c5c      20655c7c5c      NULL    NULL
+2c7100 20612c2c71006171        2061    EMPTY   61
+2c7100 202222717c7c5c7120202222226565617c205c  
202222717c7c5c7120202222226565617c205c  NULL    NULL
+2c7100 205c2c007120207c5c005c20002c22007c5c5c5c22      205c    NULL    NULL
+2c7100 7c20712200652c7c2c2c    NULL    NULL    NULL
+2c7100 65202c005c2061007161227c5c7c2c717c65657c61      6520    
5c20617161227c5c7c      NULL
+2c7100 00227c7c7c715c652c0020712c71615c        227c7c7c715c652c20      NULL    
NULL
+2c7100 007c    7c      NULL    NULL
+2c7100 2c0020206120617171716561612c5c20        EMPTY   NULL    NULL
+2c7100 657161205c22    NULL    NULL    NULL
+2c7100 655c202c65207c7165612271225c657c        655c20  
65207c7165612271225c657c        NULL
+2c7100 6520612022612c2261      652061202261    2261    NULL
+2c7100 2c2c71007c71615c7c22715c2271    EMPTY   EMPTY   7c71615c7c22715c22
+2c7100 EMPTY   NULL    NULL    NULL
+2c7100 6561225c        6561225c        NULL    NULL
+2c7100 7c617c225c22206520      7c617c225c22206520      NULL    NULL
+2c7100 EMPTY   NULL    NULL    NULL
+2c7100 61      61      NULL    NULL
+2c7100 227c00  227c    NULL    NULL
+2c7100 61612c00615c2c5c5c226520202c002061007c5c71      6161    615c    
5c5c22652020
+2c7100 2000    20      NULL    NULL
+2c7100 22005c7c5c6171657c71656120006161        225c7c5c6171657c716561206161    
NULL    NULL
+2c7100 617c2c  617c    EMPTY   NULL
+2c7100 715c612c2c61    NULL    NULL    NULL
+2c7100 2c207c2c225c2c7161      EMPTY   207c    225c
+2c7100 657c652c2c6500002200656565207c6520      657c65  EMPTY   
650022656565207c6520
+2c7100 7122005c22207171612020712c2c61615c206122712c65  225c222071612020        
EMPTY   NULL
+2c7100 2c715c71657c61716122657c        EMPTY   NULL    NULL
+2c7100 00      EMPTY   NULL    NULL
+2c7100 2061    2061    NULL    NULL
+2c7100 205c2000        205c20  NULL    NULL
+2c7100 71655c2c207c20715c0000006500227c6161    655c2c207c20715c0065227c6161    
NULL    NULL
+2c7100 5c6571  NULL    NULL    NULL
+2c7100 007c2c652c7171  7c      65      EMPTY
+2c7100 00615c  615c    NULL    NULL
+2c7100 71656522006565225c00    NULL    NULL    NULL
+2c7100 002c7165205c65227c6165716565207c716171612c2c    EMPTY   
65205c65227c6165716565207c71617161      EMPTY
+2c7100 222c71206120005c612c2c  22      NULL    NULL
+2c7100 7c227c617c7c22206500712022002c7c00652c5c        
7c227c617c7c222065712022        7c65    5c
+2c7100 61612c225c5c20652222205c2071222261      6161    NULL    NULL
+2c7100 61      61      NULL    NULL
+2c7100 205c2c5c2c      205c    5c      EMPTY
+2c7100 2c6500007c22    EMPTY   65007c22        NULL
+2c7100 5c7c2c65202c5c7c610022227c225c  5c7c    6520    5c7c6122227c225c
+2c7100 655c2c002c7165716561205c7100207c2c7171225c2c20  655c    EMPTY   NULL
+2c7100 2c22    EMPTY   22      NULL
+2c7100 6571    NULL    NULL    NULL
+2c7100 5c205c  5c205c  NULL    NULL
+2c7100 65715c0071615c7c2271207c5c7161207c00207c65      NULL    NULL    NULL
+2c7100 205c20615c220000222c615c5c2c00225c71    205c20615c220022        615c5c  
NULL
+2c7100 2c7c5c20657c5c7c202c    EMPTY   7c5c20657c5c7c20        EMPTY
+2c7100 5c65002c005c5c2c2220    5c65    5c5c    2220
+2c7100 71      NULL    NULL    NULL
+2c7100 71617c7c65615c61715c2c652200    617c7c65615c61715c      6522    NULL
+2c7100 7c7165715c5c5c612065655c        7c65715c5c5c612065655c  NULL    NULL
+2c0065 787c202071617c62717c63  787c202071617c62717c63  NULL    NULL
+2c0065 7161717162712c7461696c  716171716271    7461696c        NULL
+2c0065 65656162632c7461696c    616263  7461696c        NULL
+2c0065 716c656674      716c6674        NULL    NULL
+2c0065 7269676874717c7461696c  7269676874717c7461696c  NULL    NULL
+2c0065 616263716c6566747c7269676874717c7461696c        
616263716c66747c7269676874717c7461696c  NULL    NULL
+2c0065 6100622c7461696c        NULL    NULL    NULL
+2c0065 EMPTY   NULL    NULL    NULL
+2c0065 2c      EMPTY   EMPTY   NULL
+2c0065 7c7c    7c7c    NULL    NULL
+2c0065 6c6566747c71756e636c6f736564    6c66747c71756e636c6f7364        NULL    
NULL
+2c0065 716c656674717c7461696c  716c6674717c7461696c    NULL    NULL
+2c0065 716c65667471717269676874717c7461696c    
716c667471717269676874717c7461696c      NULL    NULL
+2c0065 787c092071617c62717c63  787c092071617c62717c63  NULL    NULL
+2c0065 787ce28083e2808371617c62717c63  787ce28083e2808371617c62717c63  NULL    
NULL
+2c0065 787cc2a071617c62717c63  787cc2a071617c62717c63  NULL    NULL
+2c0065 c3a9716c6566747c7269676874717c7461696c  
c3a9716c66747c7269676874717c7461696c    NULL    NULL
+2c0065 f09f9880716c6566747c7269676874717c7461696c      
f09f9880716c66747c7269676874717c7461696c        NULL    NULL
+2c0065 616263716c656674c3a9726967687471c3a97461696c    
616263716c6674c3a9726967687471c3a97461696c      NULL    NULL
+2c0065 71c3a971c3a97461696c    71c3a971c3a97461696c    NULL    NULL
+2c0065 2222227822222c7461696c  222222782222    7461696c        NULL
+2c0065 6520007c205c2000616120202c5c2c  207c205c200061612020    5c      EMPTY
+2c0065 20      20      NULL    NULL
+2c0065 EMPTY   NULL    NULL    NULL
+2c0065 222061205c7c20717c65005c205c71715c00617c71      NULL    NULL    NULL
+2c0065 007161202c7c7161612071652c2071  NULL    NULL    NULL
+2c0065 71007c0065657c00616100207c7c    717c007c00616100207c7c  NULL    NULL
+2c0065 22710022617161612020    NULL    NULL    NULL
+2c0065 5c7c6161617c20716520205c2c      5c7c6161617c207120205c  EMPTY   NULL
+2c0065 65      EMPTY   NULL    NULL
+2c0065 00652c227c6561222200222c2222207165617c2265005c  2c227c6122220022        
22222071617c22005c      NULL
+2c0065 5c7165225c71226161      5c71225c71226161        NULL    NULL
+2c0065 7c005c2c007c22612c2c65  7c5c2c7c2261    EMPTY   EMPTY
+2c0065 612c5c20205c7c5c7c00226520717c2c655c655c2c6122  61      NULL    NULL
+2c0065 007c6161200022615c202061227122  7c6161200022615c202061227122    NULL    
NULL
+2c0065 2c5c20002c5c5c2c225c5c71        EMPTY   NULL    NULL
+2c0065 2265652022712c712c00657120202261207c5c  2265202271      71      NULL
+2c0065 657c5c225c0065225c65656565      NULL    NULL    NULL
+2c0065 2c5c715c5c20225c71227c5c00656120717c7c5c61      EMPTY   NULL    NULL
+2c0065 206561612c20000065652265205c00  206161  NULL    NULL
+2c0065 00002c2c5c20615c5c00    EMPTY   EMPTY   NULL
+2c0065 717c6161222c    717c616122      EMPTY   NULL
+2c0065 0020226171655c5c        NULL    NULL    NULL
+2c0065 5c007c22        NULL    NULL    NULL
+2c0065 0022202261      NULL    NULL    NULL
+2c0065 6500617c220020  617c220020      NULL    NULL
+2c0065 20655c7c5c      205c7c5c        NULL    NULL
+2c0065 20612c2c71006171        2061    EMPTY   NULL
+2c0065 202222717c7c5c7120202222226565617c205c  
202222717c7c5c71202022222265617c205c    NULL    NULL
+2c0065 205c2c007120207c5c005c20002c22007c5c5c5c22      205c    
7120207c5c005c202c22007c5c5c5c22        NULL
+2c0065 7c20712200652c7c2c2c    NULL    NULL    NULL
+2c0065 65202c005c2061007161227c5c7c2c717c65657c61      20      
5c2061007161227c5c7c    717c657c61
+2c0065 00227c7c7c715c652c0020712c71615c        227c7c7c715c2c2071      71615c  
NULL
+2c0065 007c    NULL    NULL    NULL
+2c0065 2c0020206120617171716561612c5c20        EMPTY   NULL    NULL
+2c0065 657161205c22    7161205c22      NULL    NULL
+2c0065 655c202c65207c7165612271225c657c        5c20    207c71612271225c7c      
NULL
+2c0065 6520612022612c2261      2061202261      2261    NULL
+2c0065 2c2c71007c71615c7c22715c2271    EMPTY   EMPTY   NULL
+2c0065 EMPTY   NULL    NULL    NULL
+2c0065 6561225c        61225c  NULL    NULL
+2c0065 7c617c225c22206520      7c617c225c222020        NULL    NULL
+2c0065 EMPTY   NULL    NULL    NULL
+2c0065 61      61      NULL    NULL
+2c0065 227c00  NULL    NULL    NULL
+2c0065 61612c00615c2c5c5c226520202c002061007c5c71      6161    NULL    NULL
+2c0065 2000    NULL    NULL    NULL
+2c0065 22005c7c5c6171657c71656120006161        225c7c5c61717c716120006161      
NULL    NULL
+2c0065 617c2c  617c    EMPTY   NULL
+2c0065 715c612c2c61    715c61  EMPTY   61
+2c0065 2c207c2c225c2c7161      EMPTY   207c    225c
+2c0065 657c652c2c6500002200656565207c6520      7c      EMPTY   NULL
+2c0065 7122005c22207171612020712c2c61615c206122712c65  NULL    NULL    NULL
+2c0065 2c715c71657c61716122657c        EMPTY   715c717c617161227c      NULL
+2c0065 00      NULL    NULL    NULL
+2c0065 2061    2061    NULL    NULL
+2c0065 205c2000        NULL    NULL    NULL
+2c0065 71655c2c207c20715c0000006500227c6161    715c    NULL    NULL
+2c0065 5c6571  5c71    NULL    NULL
+2c0065 007c2c652c7171  NULL    NULL    NULL
+2c0065 00615c  NULL    NULL    NULL
+2c0065 71656522006565225c00    7165220065225c  NULL    NULL
+2c0065 002c7165205c65227c6165716565207c716171612c2c    NULL    NULL    NULL
+2c0065 222c71206120005c612c2c  22      NULL    NULL
+2c0065 7c227c617c7c22206500712022002c7c00652c5c        
7c227c617c7c2220007120222c7c00  5c      NULL
+2c0065 61612c225c5c20652222205c2071222261      6161    
225c5c202222205c2071222261      NULL
+2c0065 61      61      NULL    NULL
+2c0065 205c2c5c2c      205c    5c      EMPTY
+2c0065 2c6500007c22    EMPTY   007c22  NULL
+2c0065 5c7c2c65202c5c7c610022227c225c  5c7c    20      NULL
+2c0065 655c2c002c7165716561205c7100207c2c7171225c2c20  5c      
2c717161205c7100207c    7171225c
+2c0065 2c22    EMPTY   22      NULL
+2c0065 6571    71      NULL    NULL
+2c0065 5c205c  5c205c  NULL    NULL
+2c0065 65715c0071615c7c2271207c5c7161207c00207c65      
715c0071615c7c2271207c5c7161207c00207c  NULL    NULL
+2c0065 205c20615c220000222c615c5c2c00225c71    205c20615c220022        615c5c  
NULL
+2c0065 2c7c5c20657c5c7c202c    EMPTY   7c5c207c5c7c20  EMPTY
+2c0065 5c65002c005c5c2c2220    5c00    NULL    NULL
+2c0065 71      71      NULL    NULL
+2c0065 71617c7c65615c61715c2c652200    71617c7c615c61715c      NULL    NULL
+2c0065 7c7165715c5c5c612065655c        7c71715c5c5c6120655c    NULL    NULL
+2c0000 787c202071617c62717c63  787c202071617c62717c63  NULL    NULL
+2c0000 7161717162712c7461696c  716171716271    7461696c        NULL
+2c0000 65656162632c7461696c    6565616263      7461696c        NULL
+2c0000 716c656674      716c656674      NULL    NULL
+2c0000 7269676874717c7461696c  7269676874717c7461696c  NULL    NULL
+2c0000 616263716c6566747c7269676874717c7461696c        
616263716c6566747c7269676874717c7461696c        NULL    NULL
+2c0000 6100622c7461696c        6162    7461696c        NULL
+2c0000 EMPTY   NULL    NULL    NULL
+2c0000 2c      EMPTY   EMPTY   NULL
+2c0000 7c7c    7c7c    NULL    NULL
+2c0000 6c6566747c71756e636c6f736564    6c6566747c71756e636c6f736564    NULL    
NULL
+2c0000 716c656674717c7461696c  716c656674717c7461696c  NULL    NULL
+2c0000 716c65667471717269676874717c7461696c    
716c65667471717269676874717c7461696c    NULL    NULL
+2c0000 787c092071617c62717c63  787c092071617c62717c63  NULL    NULL
+2c0000 787ce28083e2808371617c62717c63  787ce28083e2808371617c62717c63  NULL    
NULL
+2c0000 787cc2a071617c62717c63  787cc2a071617c62717c63  NULL    NULL
+2c0000 c3a9716c6566747c7269676874717c7461696c  
c3a9716c6566747c7269676874717c7461696c  NULL    NULL
+2c0000 f09f9880716c6566747c7269676874717c7461696c      
f09f9880716c6566747c7269676874717c7461696c      NULL    NULL
+2c0000 616263716c656674c3a9726967687471c3a97461696c    
616263716c656674c3a9726967687471c3a97461696c    NULL    NULL
+2c0000 71c3a971c3a97461696c    71c3a971c3a97461696c    NULL    NULL
+2c0000 2222227822222c7461696c  222222782222    7461696c        NULL
+2c0000 6520007c205c2000616120202c5c2c  65207c205c2061612020    5c      EMPTY
+2c0000 20      20      NULL    NULL
+2c0000 EMPTY   NULL    NULL    NULL
+2c0000 222061205c7c20717c65005c205c71715c00617c71      
222061205c7c20717c655c205c71715c617c71  NULL    NULL
+2c0000 007161202c7c7161612071652c2071  716120  7c716161207165  2071
+2c0000 71007c0065657c00616100207c7c    717c65657c6161207c7c    NULL    NULL
+2c0000 22710022617161612020    227122617161612020      NULL    NULL
+2c0000 5c7c6161617c20716520205c2c      5c7c6161617c20716520205c        EMPTY   
NULL
+2c0000 65      65      NULL    NULL
+2c0000 00652c227c6561222200222c2222207165617c2265005c  65      227c6561222222  
2222207165617c22655c
+2c0000 5c7165225c71226161      5c7165225c71226161      NULL    NULL
+2c0000 7c005c2c007c22612c2c65  7c5c    7c2261  EMPTY
+2c0000 612c5c20205c7c5c7c00226520717c2c655c655c2c6122  61      
5c20205c7c5c7c226520717c        655c655c
+2c0000 007c6161200022615c202061227122  7c61612022615c202061227122      NULL    
NULL
+2c0000 2c5c20002c5c5c2c225c5c71        EMPTY   5c20    5c5c
+2c0000 2265652022712c712c00657120202261207c5c  226565202271    71      
657120202261207c5c
+2c0000 657c5c225c0065225c65656565      657c5c225c65225c65656565        NULL    
NULL
+2c0000 2c5c715c5c20225c71227c5c00656120717c7c5c61      EMPTY   
5c715c5c20225c71227c5c656120717c7c5c61  NULL
+2c0000 206561612c20000065652265205c00  20656161        200065652265205c        
NULL
+2c0000 00002c2c5c20615c5c00    EMPTY   EMPTY   5c20615c5c
+2c0000 717c6161222c    717c616122      EMPTY   NULL
+2c0000 0020226171655c5c        20226171655c5c  NULL    NULL
+2c0000 5c007c22        5c7c22  NULL    NULL
+2c0000 0022202261      22202261        NULL    NULL
+2c0000 6500617c220020  65617c2220      NULL    NULL
+2c0000 20655c7c5c      20655c7c5c      NULL    NULL
+2c0000 20612c2c71006171        2061    EMPTY   716171
+2c0000 202222717c7c5c7120202222226565617c205c  
202222717c7c5c7120202222226565617c205c  NULL    NULL
+2c0000 205c2c007120207c5c005c20002c22007c5c5c5c22      205c    7120207c5c5c20  
227c5c5c5c22
+2c0000 7c20712200652c7c2c2c    7c20712265      7c      EMPTY
+2c0000 65202c005c2061007161227c5c7c2c717c65657c61      6520    
5c20617161227c5c7c      717c65657c61
+2c0000 00227c7c7c715c652c0020712c71615c        227c7c7c715c65  2071    71615c
+2c0000 007c    7c      NULL    NULL
+2c0000 2c0020206120617171716561612c5c20        EMPTY   2020612061717171656161  
5c20
+2c0000 657161205c22    657161205c22    NULL    NULL
+2c0000 655c202c65207c7165612271225c657c        655c20  
65207c7165612271225c657c        NULL
+2c0000 6520612022612c2261      652061202261    2261    NULL
+2c0000 2c2c71007c71615c7c22715c2271    EMPTY   EMPTY   717c71615c7c22715c2271
+2c0000 EMPTY   NULL    NULL    NULL
+2c0000 6561225c        6561225c        NULL    NULL
+2c0000 7c617c225c22206520      7c617c225c22206520      NULL    NULL
+2c0000 EMPTY   NULL    NULL    NULL
+2c0000 61      61      NULL    NULL
+2c0000 227c00  227c    NULL    NULL
+2c0000 61612c00615c2c5c5c226520202c002061007c5c71      6161    615c    
5c5c22652020
+2c0000 2000    20      NULL    NULL
+2c0000 22005c7c5c6171657c71656120006161        225c7c5c6171657c716561206161    
NULL    NULL
+2c0000 617c2c  617c    EMPTY   NULL
+2c0000 715c612c2c61    715c61  EMPTY   61
+2c0000 2c207c2c225c2c7161      EMPTY   207c    225c
+2c0000 657c652c2c6500002200656565207c6520      657c65  EMPTY   
650022656565207c6520
+2c0000 7122005c22207171612020712c2c61615c206122712c65  71225c2220717161202071  
EMPTY   61615c20612271
+2c0000 2c715c71657c61716122657c        EMPTY   715c71657c61716122657c  NULL
+2c0000 00      EMPTY   NULL    NULL
+2c0000 2061    2061    NULL    NULL
+2c0000 205c2000        205c20  NULL    NULL
+2c0000 71655c2c207c20715c0000006500227c6161    71655c  207c20715c0065227c6161  
NULL
+2c0000 5c6571  5c6571  NULL    NULL
+2c0000 007c2c652c7171  7c      65      7171
+2c0000 00615c  615c    NULL    NULL
+2c0000 71656522006565225c00    716565226565225c        NULL    NULL
+2c0000 002c7165205c65227c6165716565207c716171612c2c    EMPTY   
7165205c65227c6165716565207c71617161    EMPTY
+2c0000 222c71206120005c612c2c  22      712061205c61    EMPTY
+2c0000 7c227c617c7c22206500712022002c7c00652c5c        
7c227c617c7c222065712022        7c65    5c
+2c0000 61612c225c5c20652222205c2071222261      6161    
225c5c20652222205c2071222261    NULL
+2c0000 61      61      NULL    NULL
+2c0000 205c2c5c2c      205c    5c      EMPTY
+2c0000 2c6500007c22    EMPTY   65007c22        NULL
+2c0000 5c7c2c65202c5c7c610022227c225c  5c7c    6520    5c7c6122227c225c
+2c0000 655c2c002c7165716561205c7100207c2c7171225c2c20  655c    EMPTY   
7165716561205c71207c
+2c0000 2c22    EMPTY   22      NULL
+2c0000 6571    6571    NULL    NULL
+2c0000 5c205c  5c205c  NULL    NULL
+2c0000 65715c0071615c7c2271207c5c7161207c00207c65      
65715c71615c7c2271207c5c7161207c207c65  NULL    NULL
+2c0000 205c20615c220000222c615c5c2c00225c71    205c20615c220022        615c5c  
225c71
+2c0000 2c7c5c20657c5c7c202c    EMPTY   7c5c20657c5c7c20        EMPTY
+2c0000 5c65002c005c5c2c2220    5c65    5c5c    2220
+2c0000 71      71      NULL    NULL
+2c0000 71617c7c65615c61715c2c652200    71617c7c65615c61715c    6522    NULL
+2c0000 7c7165715c5c5c612065655c        7c7165715c5c5c612065655c        NULL    
NULL
+2c225c 787c202071617c62717c63  787c202071617c62717c63  NULL    NULL
+2c225c 7161717162712c7461696c  716171716271    7461696c        NULL
+2c225c 65656162632c7461696c    6565616263      7461696c        NULL
+2c225c 716c656674      716c656674      NULL    NULL
+2c225c 7269676874717c7461696c  7269676874717c7461696c  NULL    NULL
+2c225c 616263716c6566747c7269676874717c7461696c        
616263716c6566747c7269676874717c7461696c        NULL    NULL
+2c225c 6100622c7461696c        610062  7461696c        NULL
+2c225c EMPTY   NULL    NULL    NULL
+2c225c 2c      EMPTY   EMPTY   NULL
+2c225c 7c7c    7c7c    NULL    NULL
+2c225c 6c6566747c71756e636c6f736564    6c6566747c71756e636c6f736564    NULL    
NULL
+2c225c 716c656674717c7461696c  716c656674717c7461696c  NULL    NULL
+2c225c 716c65667471717269676874717c7461696c    
716c65667471717269676874717c7461696c    NULL    NULL
+2c225c 787c092071617c62717c63  787c092071617c62717c63  NULL    NULL
+2c225c 787ce28083e2808371617c62717c63  787ce28083e2808371617c62717c63  NULL    
NULL
+2c225c 787cc2a071617c62717c63  787cc2a071617c62717c63  NULL    NULL
+2c225c c3a9716c6566747c7269676874717c7461696c  
c3a9716c6566747c7269676874717c7461696c  NULL    NULL
+2c225c f09f9880716c6566747c7269676874717c7461696c      
f09f9880716c6566747c7269676874717c7461696c      NULL    NULL
+2c225c 616263716c656674c3a9726967687471c3a97461696c    
616263716c656674c3a9726967687471c3a97461696c    NULL    NULL
+2c225c 71c3a971c3a97461696c    71c3a971c3a97461696c    NULL    NULL
+2c225c 2222227822222c7461696c  NULL    NULL    NULL
+2c225c 6520007c205c2000616120202c5c2c  6520007c20200061612020  EMPTY   EMPTY
+2c225c 20      20      NULL    NULL
+2c225c EMPTY   NULL    NULL    NULL
+2c225c 222061205c7c20717c65005c205c71715c00617c71      NULL    NULL    NULL
+2c225c 007161202c7c7161612071652c2071  00716120        7c716161207165  2071
+2c225c 71007c0065657c00616100207c7c    71007c0065657c00616100207c7c    NULL    
NULL
+2c225c 22710022617161612020    710022617161612020      NULL    NULL
+2c225c 5c7c6161617c20716520205c2c      7c6161617c2071652020    EMPTY   NULL
+2c225c 65      65      NULL    NULL
+2c225c 00652c227c6561222200222c2222207165617c2265005c  0065    7c65612200      
NULL
+2c225c 5c7165225c71226161      71652271226161  NULL    NULL
+2c225c 7c005c2c007c22612c2c65  7c00    NULL    NULL
+2c225c 612c5c20205c7c5c7c00226520717c2c655c655c2c6122  61      
20207c7c00226520717c2c65652c61  NULL
+2c225c 007c6161200022615c202061227122  NULL    NULL    NULL
+2c225c 2c5c20002c5c5c2c225c5c71        EMPTY   2000    EMPTY
+2c225c 2265652022712c712c00657120202261207c5c  6565202271      71      NULL
+2c225c 657c5c225c0065225c65656565      NULL    NULL    NULL
+2c225c 2c5c715c5c20225c71227c5c00656120717c7c5c61      EMPTY   
715c202271227c00656120717c7c61  NULL
+2c225c 206561612c20000065652265205c00  20656161        NULL    NULL
+2c225c 00002c2c5c20615c5c00    0000    EMPTY   20615c00
+2c225c 717c6161222c    NULL    NULL    NULL
+2c225c 0020226171655c5c        NULL    NULL    NULL
+2c225c 5c007c22        NULL    NULL    NULL
+2c225c 0022202261      00202261        NULL    NULL
+2c225c 6500617c220020  NULL    NULL    NULL
+2c225c 20655c7c5c      20657c  NULL    NULL
+2c225c 20612c2c71006171        2061    EMPTY   71006171
+2c225c 202222717c7c5c7120202222226565617c205c  NULL    NULL    NULL
+2c225c 205c2c007120207c5c005c20002c22007c5c5c5c22      20      
007120207c002000        NULL
+2c225c 7c20712200652c7c2c2c    NULL    NULL    NULL
+2c225c 65202c005c2061007161227c5c7c2c717c65657c61      6520    NULL    NULL
+2c225c 00227c7c7c715c652c0020712c71615c        NULL    NULL    NULL
+2c225c 007c    007c    NULL    NULL
+2c225c 2c0020206120617171716561612c5c20        EMPTY   
002020612061717171656161        20
+2c225c 657161205c22    6571612022      NULL    NULL
+2c225c 655c202c65207c7165612271225c657c        6520    65207c716561227122657c  
NULL
+2c225c 6520612022612c2261      6520612022612c61        NULL    NULL
+2c225c 2c2c71007c71615c7c22715c2271    EMPTY   EMPTY   NULL
+2c225c EMPTY   NULL    NULL    NULL
+2c225c 6561225c        NULL    NULL    NULL
+2c225c 7c617c225c22206520      NULL    NULL    NULL
+2c225c EMPTY   NULL    NULL    NULL
+2c225c 61      61      NULL    NULL
+2c225c 227c00  NULL    NULL    NULL
+2c225c 61612c00615c2c5c5c226520202c002061007c5c71      6161    0061    NULL
+2c225c 2000    2000    NULL    NULL
+2c225c 22005c7c5c6171657c71656120006161        NULL    NULL    NULL
+2c225c 617c2c  617c    EMPTY   NULL
+2c225c 715c612c2c61    7161    EMPTY   61
+2c225c 2c207c2c225c2c7161      EMPTY   207c    NULL
+2c225c 657c652c2c6500002200656565207c6520      657c65  EMPTY   NULL
+2c225c 7122005c22207171612020712c2c61615c206122712c65  
710022207171612020712c2c616120612271    65      NULL
+2c225c 2c715c71657c61716122657c        EMPTY   NULL    NULL
+2c225c 00      00      NULL    NULL
+2c225c 2061    2061    NULL    NULL
+2c225c 205c2000        202000  NULL    NULL
+2c225c 71655c2c207c20715c0000006500227c6161    7165    NULL    NULL
+2c225c 5c6571  6571    NULL    NULL
+2c225c 007c2c652c7171  007c    65      7171
+2c225c 00615c  0061    NULL    NULL
+2c225c 71656522006565225c00    716565220065652200      NULL    NULL
+2c225c 002c7165205c65227c6165716565207c716171612c2c    00      NULL    NULL
+2c225c 222c71206120005c612c2c  NULL    NULL    NULL
+2c225c 7c227c617c7c22206500712022002c7c00652c5c        NULL    NULL    NULL
+2c225c 61612c225c5c20652222205c2071222261      6161    NULL    NULL
+2c225c 61      61      NULL    NULL
+2c225c 205c2c5c2c      20      EMPTY   EMPTY
+2c225c 2c6500007c22    EMPTY   NULL    NULL
+2c225c 5c7c2c65202c5c7c610022227c225c  7c      6520    NULL
+2c225c 655c2c002c7165716561205c7100207c2c7171225c2c20  65      00      
7165716561207100207c
+2c225c 2c22    EMPTY   NULL    NULL
+2c225c 6571    6571    NULL    NULL
+2c225c 5c205c  20      NULL    NULL
+2c225c 65715c0071615c7c2271207c5c7161207c00207c65      NULL    NULL    NULL
+2c225c 205c20615c220000222c615c5c2c00225c71    2020612200002c615c2c002271      
NULL    NULL
+2c225c 2c7c5c20657c5c7c202c    EMPTY   7c20657c7c20    EMPTY
+2c225c 5c65002c005c5c2c2220    6500    005c    NULL
+2c225c 71      71      NULL    NULL
+2c225c 71617c7c65615c61715c2c652200    71617c7c65616171        NULL    NULL
+2c225c 7c7165715c5c5c612065655c        7c7165715c61206565      NULL    NULL
+c3a97165       787c202071617c62717c63  787c202071617c62717c63  NULL    NULL
+c3a97165       7161717162712c7461696c  617162712c7461696c      NULL    NULL
+c3a97165       65656162632c7461696c    6162632c7461696c        NULL    NULL
+c3a97165       716c656674      NULL    NULL    NULL
+c3a97165       7269676874717c7461696c  NULL    NULL    NULL
+c3a97165       616263716c6566747c7269676874717c7461696c        
616263716c66747c7269676874717c7461696c  NULL    NULL
+c3a97165       6100622c7461696c        6100622c7461696c        NULL    NULL
+c3a97165       EMPTY   NULL    NULL    NULL
+c3a97165       2c      2c      NULL    NULL
+c3a97165       7c7c    7c7c    NULL    NULL
+c3a97165       6c6566747c71756e636c6f736564    NULL    NULL    NULL
+c3a97165       716c656674717c7461696c  6c6674717c7461696c      NULL    NULL
+c3a97165       716c65667471717269676874717c7461696c    
6c6674717269676874717c7461696c  NULL    NULL
+c3a97165       787c092071617c62717c63  787c092071617c62717c63  NULL    NULL
+c3a97165       787ce28083e2808371617c62717c63  787ce28083e2808371617c62717c63  
NULL    NULL
+c3a97165       787cc2a071617c62717c63  787cc2a071617c62717c63  NULL    NULL
+c3a97165       c3a9716c6566747c7269676874717c7461696c  EMPTY   
6c66747c7269676874717c7461696c  NULL
+c3a97165       f09f9880716c6566747c7269676874717c7461696c      
f09f98806c66747c7269676874717c7461696c  NULL    NULL
+c3a97165       616263716c656674c3a9726967687471c3a97461696c    
616263716c6674c3a97269676874    7461696c        NULL
+c3a97165       71c3a971c3a97461696c    c3a9    7461696c        NULL
+c3a97165       2222227822222c7461696c  2222227822222c7461696c  NULL    NULL
+c3a97165       6520007c205c2000616120202c5c2c  20007c205c2000616120202c5c2c    
NULL    NULL
+c3a97165       20      20      NULL    NULL
+c3a97165       EMPTY   NULL    NULL    NULL
+c3a97165       222061205c7c20717c65005c205c71715c00617c71      
222061205c7c20717c005c205c715c00617c    NULL    NULL
+c3a97165       007161202c7c7161612071652c2071  0061202c7c71616120712c20        
NULL    NULL
+c3a97165       71007c0065657c00616100207c7c    NULL    NULL    NULL
+c3a97165       22710022617161612020    220022617161612020      NULL    NULL
+c3a97165       5c7c6161617c20716520205c2c      NULL    NULL    NULL
+c3a97165       65      EMPTY   NULL    NULL
+c3a97165       00652c227c6561222200222c2222207165617c2265005c  NULL    NULL    
NULL
+c3a97165       5c7165225c71226161      5c225c71226161  NULL    NULL
+c3a97165       7c005c2c007c22612c2c65  7c005c2c007c22612c2c    NULL    NULL
+c3a97165       612c5c20205c7c5c7c00226520717c2c655c655c2c6122  NULL    NULL    
NULL
+c3a97165       007c6161200022615c202061227122  NULL    NULL    NULL
+c3a97165       2c5c20002c5c5c2c225c5c71        NULL    NULL    NULL
+c3a97165       2265652022712c712c00657120202261207c5c  
22652022712c712c007120202261207c5c      NULL    NULL
+c3a97165       657c5c225c0065225c65656565      7c5c225c00225c6565      NULL    
NULL
+c3a97165       2c5c715c5c20225c71227c5c00656120717c7c5c61      NULL    NULL    
NULL
+c3a97165       206561612c20000065652265205c00  2061612c2000006522205c00        
NULL    NULL
+c3a97165       00002c2c5c20615c5c00    00002c2c5c20615c5c00    NULL    NULL
+c3a97165       717c6161222c    NULL    NULL    NULL
+c3a97165       0020226171655c5c        NULL    NULL    NULL
+c3a97165       5c007c22        5c007c22        NULL    NULL
+c3a97165       0022202261      0022202261      NULL    NULL
+c3a97165       6500617c220020  00617c220020    NULL    NULL
+c3a97165       20655c7c5c      205c7c5c        NULL    NULL
+c3a97165       20612c2c71006171        20612c2c710061  NULL    NULL
+c3a97165       202222717c7c5c7120202222226565617c205c  
202222717c7c5c71202022222265617c205c    NULL    NULL
+c3a97165       205c2c007120207c5c005c20002c22007c5c5c5c22      NULL    NULL    
NULL
+c3a97165       7c20712200652c7c2c2c    NULL    NULL    NULL
+c3a97165       65202c005c2061007161227c5c7c2c717c65657c61      
202c005c2061007161227c5c7c2c717c657c61  NULL    NULL
+c3a97165       00227c7c7c715c652c0020712c71615c        NULL    NULL    NULL
+c3a97165       007c    007c    NULL    NULL
+c3a97165       2c0020206120617171716561612c5c20        NULL    NULL    NULL
+c3a97165       657161205c22    NULL    NULL    NULL
+c3a97165       655c202c65207c7165612271225c657c        
5c202c207c71612271225c7c        NULL    NULL
+c3a97165       6520612022612c2261      20612022612c2261        NULL    NULL
+c3a97165       2c2c71007c71615c7c22715c2271    2c2c007c71615c7c22715c22        
NULL    NULL
+c3a97165       EMPTY   NULL    NULL    NULL
+c3a97165       6561225c        61225c  NULL    NULL
+c3a97165       7c617c225c22206520      7c617c225c222020        NULL    NULL
+c3a97165       EMPTY   NULL    NULL    NULL
+c3a97165       61      61      NULL    NULL
+c3a97165       227c00  227c00  NULL    NULL
+c3a97165       61612c00615c2c5c5c226520202c002061007c5c71      NULL    NULL    
NULL
+c3a97165       2000    2000    NULL    NULL
+c3a97165       22005c7c5c6171657c71656120006161        
22005c7c5c61717c716120006161    NULL    NULL
+c3a97165       617c2c  617c2c  NULL    NULL
+c3a97165       715c612c2c61    NULL    NULL    NULL
+c3a97165       2c207c2c225c2c7161      NULL    NULL    NULL
+c3a97165       657c652c2c6500002200656565207c6520      7c2c2c0000220065207c20  
NULL    NULL
+c3a97165       7122005c22207171612020712c2c61615c206122712c65  NULL    NULL    
NULL
+c3a97165       2c715c71657c61716122657c        NULL    NULL    NULL
+c3a97165       00      00      NULL    NULL
+c3a97165       2061    2061    NULL    NULL
+c3a97165       205c2000        205c2000        NULL    NULL
+c3a97165       71655c2c207c20715c0000006500227c6161    
5c2c207c20715c00000000227c6161  NULL    NULL
+c3a97165       5c6571  5c71    NULL    NULL
+c3a97165       007c2c652c7171  007c2c2c71      NULL    NULL
+c3a97165       00615c  00615c  NULL    NULL
+c3a97165       71656522006565225c00    NULL    NULL    NULL
+c3a97165       002c7165205c65227c6165716565207c716171612c2c    NULL    NULL    
NULL
+c3a97165       222c71206120005c612c2c  NULL    NULL    NULL
+c3a97165       7c227c617c7c22206500712022002c7c00652c5c        NULL    NULL    
NULL
+c3a97165       61612c225c5c20652222205c2071222261      NULL    NULL    NULL
+c3a97165       61      61      NULL    NULL
+c3a97165       205c2c5c2c      205c2c5c2c      NULL    NULL
+c3a97165       2c6500007c22    2c00007c22      NULL    NULL
+c3a97165       5c7c2c65202c5c7c610022227c225c  5c7c2c202c5c7c610022227c225c    
NULL    NULL
+c3a97165       655c2c002c7165716561205c7100207c2c7171225c2c20  
5c2c002c717161205c7100207c2c71225c2c20  NULL    NULL
+c3a97165       2c22    2c22    NULL    NULL
+c3a97165       6571    NULL    NULL    NULL
+c3a97165       5c205c  5c205c  NULL    NULL
+c3a97165       65715c0071615c7c2271207c5c7161207c00207c65      
5c0071615c7c2271207c5c7161207c00207c    NULL    NULL
+c3a97165       205c20615c220000222c615c5c2c00225c71    NULL    NULL    NULL
+c3a97165       2c7c5c20657c5c7c202c    2c7c5c207c5c7c202c      NULL    NULL
+c3a97165       5c65002c005c5c2c2220    5c002c005c5c2c2220      NULL    NULL
+c3a97165       71      NULL    NULL    NULL
+c3a97165       71617c7c65615c61715c2c652200    617c7c615c61715c2c2200  NULL    
NULL
+c3a97165       7c7165715c5c5c612065655c        NULL    NULL    NULL
diff --git 
a/fe/fe-connector/fe-connector-spi/src/main/java/org/apache/doris/connector/spi/scan/ScanNodePropertyKeys.java
 
b/fe/fe-connector/fe-connector-spi/src/main/java/org/apache/doris/connector/spi/scan/ScanNodePropertyKeys.java
index fff00e0cbec..ab27eea6012 100644
--- 
a/fe/fe-connector/fe-connector-spi/src/main/java/org/apache/doris/connector/spi/scan/ScanNodePropertyKeys.java
+++ 
b/fe/fe-connector/fe-connector-spi/src/main/java/org/apache/doris/connector/spi/scan/ScanNodePropertyKeys.java
@@ -132,6 +132,9 @@ public final class ScanNodePropertyKeys {
     /** Quote character enclosing a field; a single character. */
     public static final String TEXT_ENCLOSE = TEXT_PROPERTY_PREFIX + "enclose";
 
+    /** {@code "true"} for Hive OpenCSV field states and physical record 
boundaries. */
+    public static final String TEXT_HIVE_OPEN_CSV = TEXT_PROPERTY_PREFIX + 
"hive_open_csv";
+
     /** {@code "true"} to strip the enclosing quotes from field values. */
     public static final String TEXT_TRIM_DOUBLE_QUOTES = TEXT_PROPERTY_PREFIX 
+ "trim_double_quotes";
 
diff --git 
a/fe/fe-connector/fe-connector-spi/src/test/java/org/apache/doris/connector/spi/ConnectorPluginSurfaceTest.java
 
b/fe/fe-connector/fe-connector-spi/src/test/java/org/apache/doris/connector/spi/ConnectorPluginSurfaceTest.java
index 22ba216e0a2..9def858c616 100644
--- 
a/fe/fe-connector/fe-connector-spi/src/test/java/org/apache/doris/connector/spi/ConnectorPluginSurfaceTest.java
+++ 
b/fe/fe-connector/fe-connector-spi/src/test/java/org/apache/doris/connector/spi/ConnectorPluginSurfaceTest.java
@@ -20,6 +20,7 @@ package org.apache.doris.connector.spi;
 import org.apache.doris.connector.spi.handle.ConnectorColumnHandle;
 import org.apache.doris.connector.spi.handle.ConnectorWriteHandle;
 import org.apache.doris.connector.spi.scan.ConnectorScanPlanProvider;
+import org.apache.doris.connector.spi.scan.ScanNodePropertyKeys;
 import org.apache.doris.connector.spi.write.ConnectorWritePlanProvider;
 
 import org.junit.jupiter.api.Assertions;
@@ -29,7 +30,9 @@ import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.List;
@@ -45,8 +48,9 @@ import java.util.TreeSet;
  * on a plugin author and nothing fails when either surface quietly changes. 
The plugin API version in
  * {@code <connector.plugin.api.version>} is the contract that says which FE a 
given plugin may load into,
  * and the rule attached to it is blunt: <em>any</em> change to the surface 
below — adding a type, method, or
- * enum constant just as much as removing or re-signing one — is a MAJOR 
change. No unit test can prove somebody
- * bumped the property (a test sees only the current state, never the delta), 
so this is a speed bump, not a
+ * enum constant or engine-read property key just as much as removing or 
re-signing one — is a MAJOR change.
+ * No unit test can prove somebody bumped the property (a test sees only the 
current state, never the delta),
+ * so this is a speed bump, not a
  * gate: it makes the change visible in review, in the same commit, with the 
reason spelled out in the
  * failure message.
  *
@@ -81,8 +85,8 @@ public class ConnectorPluginSurfaceTest {
             Assertions.assertNotNull(in, "missing connector plugin API version 
resource");
             version.load(in);
         }
-        // Latest-schema publication is explicit in major 8; older engines 
cannot honor the opt-in contract.
-        Assertions.assertEquals("8.0", version.getProperty("api.version"));
+        // OpenCSV scan properties require major 9: inlined keys cannot fail 
JVM linkage on an older FE.
+        Assertions.assertEquals("9.0", version.getProperty("api.version"));
     }
 
     /** Root entry points plus provider/handle types returned to connector 
plugins. */
@@ -106,7 +110,7 @@ public class ConnectorPluginSurfaceTest {
             Arrays.asList(ConnectorCapability.class);
 
     @Test
-    public void pluginApiSurfaceMatchesRecordedBaseline() throws IOException {
+    public void pluginApiSurfaceMatchesRecordedBaseline() throws IOException, 
IllegalAccessException {
         TreeSet<String> actual = renderSurface();
         TreeSet<String> expected = readBaseline();
 
@@ -130,8 +134,15 @@ public class ConnectorPluginSurfaceTest {
      * happens to declare it: what matters is what a plugin can call on the 
type it was handed, so moving a
      * default method up or down a super-interface chain is not by itself a 
surface change.
      */
-    private static TreeSet<String> renderSurface() {
+    private static TreeSet<String> renderSurface() throws 
IllegalAccessException {
         TreeSet<String> rendered = new TreeSet<>();
+        // String constants are inlined into plugins, so both their names and 
wire values are API surface.
+        for (Field field : ScanNodePropertyKeys.class.getFields()) {
+            Assertions.assertEquals(String.class, field.getType());
+            Assertions.assertTrue(Modifier.isStatic(field.getModifiers()) && 
Modifier.isFinal(field.getModifiers()));
+            rendered.add(ScanNodePropertyKeys.class.getName() + "#field:" + 
field.getName()
+                    + ":" + field.getType().getTypeName() + "=" + 
field.get(null));
+        }
         for (Class<? extends Enum<?>> frozen : FROZEN_ENUM_TYPES) {
             for (Enum<?> constant : frozen.getEnumConstants()) {
                 rendered.add(frozen.getName() + "#enum:" + constant.name());
diff --git 
a/fe/fe-connector/fe-connector-spi/src/test/resources/connector-plugin-surface.txt
 
b/fe/fe-connector/fe-connector-spi/src/test/resources/connector-plugin-surface.txt
index 663b6a8b7fd..f3d8d8cb237 100644
--- 
a/fe/fe-connector/fe-connector-spi/src/test/resources/connector-plugin-surface.txt
+++ 
b/fe/fe-connector/fe-connector-spi/src/test/resources/connector-plugin-surface.txt
@@ -121,6 +121,30 @@ 
org.apache.doris.connector.spi.scan.ConnectorScanPlanProvider#supportsSystemTabl
 
org.apache.doris.connector.spi.scan.ConnectorScanPlanProvider#supportsSystemTableTimeTravel():boolean
 
org.apache.doris.connector.spi.scan.ConnectorScanPlanProvider#supportsTableSample():boolean
 
org.apache.doris.connector.spi.scan.ConnectorScanPlanProvider#usesHiveParquetInt96TimeZone():boolean
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:FILE_FORMAT_TYPE:java.lang.String=file_format_type
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:LOCATION_PREFIX:java.lang.String=location.
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:PATH_PARTITION_KEYS:java.lang.String=path_partition_keys
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:REMOTE_QUERY:java.lang.String=query
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:REQUIRED_CURRENT_BACKEND_SEMANTICS:java.lang.String=required_current_backend_semantics
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:SYNTHETIC_ALL_CONJUNCTS_PUSHED:java.lang.String=__all_conjuncts_pushed
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:SYNTHETIC_EXPLAIN_VERBOSE:java.lang.String=__explain_verbose
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:SYNTHETIC_NATIVE_READ_SPLITS:java.lang.String=__native_read_splits
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:SYNTHETIC_PUSHDOWN_LIMIT:java.lang.String=__pushdown_limit
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:SYNTHETIC_TOTAL_READ_SPLITS:java.lang.String=__total_read_splits
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_COLLECTION_DELIMITER:java.lang.String=hive.text.collection_delimiter
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_COLUMN_SEPARATOR:java.lang.String=hive.text.column_separator
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_ENCLOSE:java.lang.String=hive.text.enclose
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_ESCAPE:java.lang.String=hive.text.escape
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_HIVE_OPEN_CSV:java.lang.String=hive.text.hive_open_csv
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_IS_JSON:java.lang.String=hive.text.is_json
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_LINE_DELIMITER:java.lang.String=hive.text.line_delimiter
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_MAPKV_DELIMITER:java.lang.String=hive.text.mapkv_delimiter
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_NULL_FORMAT:java.lang.String=hive.text.null_format
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_OPENX_IGNORE_MALFORMED:java.lang.String=hive.text.openx_ignore_malformed
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_PROPERTY_PREFIX:java.lang.String=hive.text.
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_SERDE_LIB:java.lang.String=hive.text.serde_lib
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_SKIP_LINES:java.lang.String=hive.text.skip_lines
+org.apache.doris.connector.spi.scan.ScanNodePropertyKeys#field:TEXT_TRIM_DOUBLE_QUOTES:java.lang.String=hive.text.trim_double_quotes
 
org.apache.doris.connector.spi.write.ConnectorWritePlanProvider#appendExplainInfo(java.lang.StringBuilder,java.lang.String,org.apache.doris.connector.spi.ConnectorSession,org.apache.doris.connector.spi.handle.ConnectorWriteHandle):void
 
org.apache.doris.connector.spi.write.ConnectorWritePlanProvider#getSyntheticWriteColumns(org.apache.doris.connector.spi.ConnectorSession,org.apache.doris.connector.spi.handle.ConnectorTableHandle):java.util.List
 
org.apache.doris.connector.spi.write.ConnectorWritePlanProvider#getWriteColumns(org.apache.doris.connector.spi.ConnectorSession,org.apache.doris.connector.spi.handle.ConnectorTableHandle,java.util.Optional):java.util.Optional
diff --git a/fe/fe-connector/pom.xml b/fe/fe-connector/pom.xml
index f7449826034..c00ef144e59 100644
--- a/fe/fe-connector/pom.xml
+++ b/fe/fe-connector/pom.xml
@@ -55,7 +55,7 @@ under the License.
           of the latter two means bumping this property as well (and 
fe-extension-spi means bumping
           all five families).
         -->
-        <connector.plugin.api.version>8.0</connector.plugin.api.version>
+        <connector.plugin.api.version>9.0</connector.plugin.api.version>
     </properties>
 
     <modules>
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/PluginDrivenScanNode.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/PluginDrivenScanNode.java
index f3c24c0bb7f..dca931a3255 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/PluginDrivenScanNode.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/PluginDrivenScanNode.java
@@ -1102,6 +1102,14 @@ public class PluginDrivenScanNode extends 
FileQueryScanNode {
             attrs.setTrimDoubleQuotes(true);
         }
 
+        if ("true".equals(props.get(ScanNodePropertyKeys.TEXT_HIVE_OPEN_CSV))) 
{
+            // The optional wire field alone cannot fence old readers during a 
rolling upgrade.
+            if (Config.be_exec_version < 
Config.HIVE_OPEN_CSV_MIN_BE_EXEC_VERSION) {
+                throw new UserException("Hive OpenCSVSerde requires backend 
execution version "
+                        + Config.HIVE_OPEN_CSV_MIN_BE_EXEC_VERSION + " or 
newer during rolling upgrade");
+            }
+            attrs.setHiveOpenCsv(true);
+        }
         attrs.setTextParams(textParams);
         attrs.setHeaderType("");
         
attrs.setEnableTextValidateUtf8(sessionVariable.enableTextValidateUtf8);
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/scan/PluginDrivenScanNodeCsvPropertiesTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/scan/PluginDrivenScanNodeCsvPropertiesTest.java
new file mode 100644
index 00000000000..503cb68df41
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/scan/PluginDrivenScanNodeCsvPropertiesTest.java
@@ -0,0 +1,224 @@
+// 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.
+
+package org.apache.doris.datasource.scan;
+
+import org.apache.doris.common.Config;
+import org.apache.doris.common.UserException;
+import org.apache.doris.common.jmockit.Deencapsulation;
+import org.apache.doris.connector.hive.HiveTextProperties;
+import org.apache.doris.connector.spi.DorisConnectorException;
+import org.apache.doris.connector.spi.scan.ScanNodePropertyKeys;
+import org.apache.doris.qe.SessionVariable;
+import org.apache.doris.system.Backend;
+import org.apache.doris.thrift.TFileAttributes;
+import org.apache.doris.thrift.TFileTextScanRangeParams;
+
+import org.apache.thrift.TDeserializer;
+import org.apache.thrift.TSerializer;
+import org.apache.thrift.protocol.TCompactProtocol;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class PluginDrivenScanNodeCsvPropertiesTest {
+    private static final String CSV_SERDE = 
"org.apache.hadoop.hive.serde2.OpenCSVSerde";
+
+    @Test
+    void testNormalizedCharactersSurviveScanNodeAndThrift() throws Exception {
+        Map<String, String> properties = Map.of("separatorChar", "ss", 
"quoteChar", "qq", "escapeChar", "ee",
+                "line.delim", "|");
+        assertWireProperties(toWire(Map.of(), properties), "s", 'q', 'e', 
false);
+        assertWireProperties(toWire(properties, Map.of("line.delim", "|")), 
"s", 'q', 'e', false);
+    }
+
+    @Test
+    void testEffectiveTablePropertiesOverrideInvalidSerdeProperties() throws 
Exception {
+        assertWireProperties(toWire(Map.of("separatorChar", "", "quoteChar", 
"é", "escapeChar", ""),
+                Map.of("separatorChar", "ss", "quoteChar", "qq", "escapeChar", 
"ee")),
+                "s", 'q', 'e', false);
+    }
+
+    @Test
+    void testDefaultsAndNormalizedDoubleQuote() throws Exception {
+        assertWireProperties(toWire(Map.of(), Map.of()), ",", '"', '\\', true);
+        assertWireProperties(toWire(Map.of(),
+                Map.of("separatorChar", "99", "quoteChar", "\"suffix", 
"escapeChar", "eé")),
+                "9", '"', 'e', true);
+    }
+
+    @Test
+    void testUtf8SeparatorRemainsACompleteCharacter() throws Exception {
+        assertWireProperties(toWire(Map.of(), Map.of("separatorChar", 
"ésuffix")), "é", '"', '\\', true);
+    }
+
+    @Test
+    void testInvalidCharactersFailBeforeThriftConstruction() {
+        for (String key : new String[] {"separatorChar", "quoteChar", 
"escapeChar"}) {
+            Assertions.assertThrows(DorisConnectorException.class, () -> 
toWire(Map.of(), Map.of(key, "")));
+        }
+        for (String key : new String[] {"quoteChar", "escapeChar"}) {
+            Assertions.assertThrows(DorisConnectorException.class, () -> 
toWire(Map.of(), Map.of(key, "é")));
+        }
+        Assertions.assertThrows(DorisConnectorException.class,
+                () -> toWire(Map.of(), Map.of("separatorChar", "😀")));
+    }
+
+    @Test
+    void testDefaultEscapeSentinelSurvivesThriftAsBackslash() throws Exception 
{
+        for (String quote : new String[] {"\"", "q"}) {
+            Map<String, String> properties = Map.of("quoteChar", quote, 
"escapeChar", "\"suffix");
+            assertWireProperties(toWire(Map.of(), properties), ",", 
quote.charAt(0), '\\', "\"".equals(quote));
+            assertWireProperties(toWire(properties, Map.of()), ",", 
quote.charAt(0), '\\', "\"".equals(quote));
+        }
+    }
+
+    @Test
+    void testCharacterConflictsFailBeforeThriftConstruction() {
+        for (Map<String, String> properties : List.of(
+                Map.of("separatorChar", "|", "quoteChar", "|"),
+                Map.of("separatorChar", "|", "escapeChar", "|"),
+                Map.of("quoteChar", "q", "escapeChar", "q"),
+                Map.of("separatorChar", "\\", "escapeChar", "\""),
+                Map.of("separatorChar", "\0"))) {
+            Assertions.assertThrows(DorisConnectorException.class, () -> 
toWire(Map.of(), properties));
+            Assertions.assertThrows(DorisConnectorException.class, () -> 
toWire(properties, Map.of()));
+        }
+    }
+
+    @Test
+    void testDisabledQuoteAndEscapeSurviveThrift() throws Exception {
+        Map<String, String> properties = Map.of("quoteChar", "\0", 
"escapeChar", "\0");
+        assertWireProperties(toWire(Map.of(), properties), ",", '\0', '\0', 
false);
+        assertWireProperties(toWire(properties, Map.of()), ",", '\0', '\0', 
false);
+    }
+
+    @Test
+    void testOtherHiveSerdesDoNotEnableOpenCsvParsing() throws Exception {
+        for (String serde : List.of(HiveTextProperties.HIVE_TEXT_SERDE, 
HiveTextProperties.HIVE_JSON_SERDE)) {
+            TFileAttributes attributes = 
propertiesToWire(HiveTextProperties.extract(serde, Map.of(), Map.of()));
+            Assertions.assertFalse(attributes.isSetHiveOpenCsv());
+            Assertions.assertFalse(attributes.isHiveOpenCsv());
+        }
+    }
+
+    @Test
+    void testOpenCsvRejectsOldExecutionVersion() {
+        int original = Config.be_exec_version;
+        try {
+            Config.be_exec_version = 14;
+            UserException error = Assertions.assertThrows(UserException.class, 
() -> toWire(Map.of(), Map.of()));
+            Assertions.assertTrue(error.getMessage().contains("OpenCSV"));
+            Assertions.assertTrue(error.getMessage().contains("15"));
+        } finally {
+            Config.be_exec_version = original;
+        }
+    }
+
+    @Test
+    void testOpenCsvAcceptsSupportingExecutionVersion() throws Exception {
+        int original = Config.be_exec_version;
+        try {
+            Config.be_exec_version = 15;
+            Assertions.assertTrue(toWire(Map.of(), Map.of()).isHiveOpenCsv());
+        } finally {
+            Config.be_exec_version = original;
+        }
+    }
+
+    @Test
+    void testAbsentSemanticFlagRetainsLegacyWireContract() throws Exception {
+        int original = Config.be_exec_version;
+        try {
+            Config.be_exec_version = 14;
+            Map<String, String> legacy = new 
HashMap<>(HiveTextProperties.extract(CSV_SERDE, Map.of(), Map.of()));
+            legacy.remove(ScanNodePropertyKeys.TEXT_HIVE_OPEN_CSV);
+            
legacy.remove(ScanNodePropertyKeys.REQUIRED_CURRENT_BACKEND_SEMANTICS);
+            TFileAttributes attributes = propertiesToWire(legacy);
+            Assertions.assertFalse(attributes.isSetHiveOpenCsv());
+            Assertions.assertFalse(attributes.isHiveOpenCsv());
+            for (String serde : List.of(HiveTextProperties.HIVE_TEXT_SERDE, 
HiveTextProperties.HIVE_JSON_SERDE)) {
+                Map<String, String> properties = 
HiveTextProperties.extract(serde, Map.of(), Map.of());
+                
Assertions.assertFalse(properties.containsKey(ScanNodePropertyKeys.REQUIRED_CURRENT_BACKEND_SEMANTICS));
+                
Assertions.assertFalse(propertiesToWire(properties).isSetHiveOpenCsv());
+            }
+        } finally {
+            Config.be_exec_version = original;
+        }
+    }
+
+    @Test
+    void testOpenCsvRejectsSmoothUpgradeSourceBeforeScheduling() {
+        int original = Config.be_exec_version;
+        try {
+            Config.be_exec_version = 15;
+            PluginDrivenScanNode node = 
Mockito.mock(PluginDrivenScanNode.class, Mockito.CALLS_REAL_METHODS);
+            Deencapsulation.setField(node, "scanNodeProperties", 
HiveTextProperties.extract(CSV_SERDE, Map.of(), Map.of()));
+            Backend source = new Backend(7L, "127.0.0.1", 9050);
+            source.setSmoothUpgradeSrc(true);
+            FederationBackendPolicy policy = 
Mockito.mock(FederationBackendPolicy.class);
+            Mockito.when(policy.getBackends()).thenReturn(List.of(new 
Backend(8L, "127.0.0.1", 9051), source));
+            Deencapsulation.setField(node, "backendPolicy", policy);
+            UserException error = Assertions.assertThrows(UserException.class, 
node::createScanRangeLocations);
+            Assertions.assertTrue(error.getMessage().contains("OpenCSV"));
+            Assertions.assertTrue(error.getMessage().contains("smooth upgrade 
source"));
+            Assertions.assertTrue(error.getMessage().contains("backend 7"));
+        } finally {
+            Config.be_exec_version = original;
+        }
+    }
+
+    private static TFileAttributes toWire(Map<String, String> serdeProperties,
+            Map<String, String> tableProperties) throws Exception {
+        return propertiesToWire(HiveTextProperties.extract(CSV_SERDE, 
serdeProperties, tableProperties));
+    }
+
+    private static TFileAttributes propertiesToWire(Map<String, String> 
properties) throws Exception {
+        PluginDrivenScanNode node = Mockito.mock(PluginDrivenScanNode.class, 
Mockito.CALLS_REAL_METHODS);
+        Deencapsulation.setField(node, "scanNodeProperties", properties);
+        Deencapsulation.setField(node, "sessionVariable", new 
SessionVariable());
+        // Exercise the real scan-node consumer and wire encoding: map 
assertions cannot detect byte truncation.
+        TFileAttributes attributes = node.getFileAttributes();
+        TFileAttributes decoded = new TFileAttributes();
+        new TDeserializer(new TCompactProtocol.Factory()).deserialize(decoded,
+                new TSerializer(new 
TCompactProtocol.Factory()).serialize(attributes));
+        return decoded;
+    }
+
+    private static void assertWireProperties(TFileAttributes attributes, 
String separator,
+            char quote, char escape, boolean trim) {
+        assertWireProperties(attributes, separator, quote, escape, trim, "\n");
+    }
+
+    private static void assertWireProperties(TFileAttributes attributes, 
String separator,
+            char quote, char escape, boolean trim, String lineDelimiter) {
+        Assertions.assertTrue(attributes.isHiveOpenCsv());
+        TFileTextScanRangeParams text = attributes.getTextParams();
+        Assertions.assertEquals(separator, text.getColumnSeparator());
+        Assertions.assertEquals(lineDelimiter, text.getLineDelimiter());
+        Assertions.assertTrue(text.isSetEnclose());
+        Assertions.assertEquals((byte) quote, text.getEnclose());
+        Assertions.assertTrue(text.isSetEscape());
+        Assertions.assertEquals((byte) escape, text.getEscape());
+        Assertions.assertEquals("", text.getNullFormat());
+        Assertions.assertEquals(trim, attributes.isTrimDoubleQuotes());
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/pluginapiversion/PluginApiVersionWiringTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/pluginapiversion/PluginApiVersionWiringTest.java
index b8fe0b2d634..2ae56fb6736 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/pluginapiversion/PluginApiVersionWiringTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/pluginapiversion/PluginApiVersionWiringTest.java
@@ -107,6 +107,14 @@ public class PluginApiVersionWiringTest {
                 "a plugin built against another major must not become a 
routable catalog type");
     }
 
+    @Test
+    public void connectorPluginWithoutOpenCsvContractIsRefused() throws 
IOException {
+        ConnectorPluginManager manager = new ConnectorPluginManager();
+        
manager.loadPlugins(Collections.singletonList(connectorPluginRoot("8.0")));
+        
Assertions.assertFalse(manager.getRegisteredTypes().contains("version_probe"),
+                "API 8 plugins omit the OpenCSV semantic flag and must not 
load on an API 9 engine");
+    }
+
     @Test
     public void connectorPluginDeclaringNothingIsRefused() throws IOException {
         // The regression this whole change exists for: before, a plugin that 
said nothing about its API
diff --git a/gensrc/thrift/PlanNodes.thrift b/gensrc/thrift/PlanNodes.thrift
index 8e443ab0f03..2bb119c149c 100644
--- a/gensrc/thrift/PlanNodes.thrift
+++ b/gensrc/thrift/PlanNodes.thrift
@@ -313,6 +313,10 @@ struct TFileAttributes {
     // org.openx.data.jsonserde.JsonSerDe
     13: optional bool openx_json_ignore_malformed = false;
 
+    // Hive OpenCSVSerde has different field states and physical record 
boundaries from load CSV.
+    // Requires BE execution version >= 15 and excludes smooth-upgrade source 
backends.
+    14: optional bool hive_open_csv = false;
+
     // for cloud copy into
     1001: optional bool ignore_csv_redundant_col;
 }
diff --git 
a/regression-test/suites/external_table_p0/hive/test_csv_table_properties.groovy
 
b/regression-test/suites/external_table_p0/hive/test_csv_table_properties.groovy
new file mode 100644
index 00000000000..178f8cfad14
--- /dev/null
+++ 
b/regression-test/suites/external_table_p0/hive/test_csv_table_properties.groovy
@@ -0,0 +1,266 @@
+// 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.
+
+suite("test_csv_table_properties", 
"p0,external,hive,external_docker,external_docker_hive") {
+    String enabled = context.config.otherConfigs.get("enableHiveTest")
+    if (enabled == null || !enabled.equalsIgnoreCase("true")) {
+        logger.info("disable Hive test.")
+        return
+    }
+
+    def checkV2 = { Closure check ->
+        def originalScannerV2 = sql("SHOW VARIABLES LIKE 
'enable_file_scanner_v2'")[0][1]
+        try {
+            sql "SET enable_file_scanner_v2 = true"
+            check()
+        } finally {
+            sql "SET enable_file_scanner_v2 = ${originalScannerV2}"
+        }
+    }
+    setHivePrefix("hive3")
+    String externalEnvIp = context.config.otherConfigs.get("externalEnvIp")
+    String hmsPort = context.config.otherConfigs.get("hive3HmsPort")
+    hive_docker "CREATE DATABASE IF NOT EXISTS csv_table_properties_db"
+    hive_docker "DROP TABLE IF EXISTS csv_table_properties_db.source_rows"
+    hive_docker """
+        CREATE TABLE csv_table_properties_db.source_rows (
+            row_id STRING, label STRING, payload STRING, group_id INT
+        ) STORED AS PARQUET
+    """
+    hive_docker """
+        INSERT INTO csv_table_properties_db.source_rows VALUES
+            ('1', 'Alpha', 'quiet sequoias, escape q and e', 1),
+            ('2', 'Omega', 'edge | requests, end', 4),
+            ('3', 'Beta', '"literal quotes", ss qq ee', 4),
+            ('4', 'Empty', '', 1)
+    """
+    hive_docker "SET hive.exec.dynamic.partition=true"
+    hive_docker "SET hive.exec.dynamic.partition.mode=nonstrict"
+
+    sql "DROP CATALOG IF EXISTS csv_table_properties_catalog"
+    sql """
+        CREATE CATALOG csv_table_properties_catalog PROPERTIES (
+            'type' = 'hms',
+            'hive.metastore.uris' = 'thrift://${externalEnvIp}:${hmsPort}'
+        )
+    """
+    sql "USE csv_table_properties_catalog.csv_table_properties_db"
+
+    // These are the four physical layouts used by CSV CTAS/INSERT product 
tests. Partition
+    // values live outside the file, so a delimiter bug can corrupt data 
columns while they stay correct.
+    def layouts = [
+        [table: "csv_three_columns", columns: "row_id STRING, label STRING, 
payload STRING",
+            projection: "row_id, label, payload", aggregate: "max(label), 
max(payload)", partitioned: false],
+        [table: "csv_partitioned_ids", columns: "row_id STRING, label STRING",
+            projection: "row_id, label, group_id", aggregate: "max(label), 
max(group_id)", partitioned: true],
+        [table: "csv_two_columns", columns: "label STRING, payload STRING",
+            projection: "label, payload", aggregate: "max(label), 
max(payload)", partitioned: false],
+        [table: "csv_partitioned_payload", columns: "label STRING, payload 
STRING",
+            projection: "label, payload, group_id", aggregate: "max(label), 
max(payload), max(group_id)",
+            partitioned: true]
+    ]
+    for (def layout : layouts) {
+        hive_docker "DROP TABLE IF EXISTS 
csv_table_properties_db.${layout.table}"
+        // Keep character settings ONLY in TBLPROPERTIES: SERDEPROPERTIES 
would hide the lookup bug.
+        hive_docker """
+            CREATE TABLE csv_table_properties_db.${layout.table} 
(${layout.columns})
+            ${layout.partitioned ? 'PARTITIONED BY (group_id INT)' : ''}
+            ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde'
+            STORED AS TEXTFILE
+            TBLPROPERTIES ('separatorChar'='s', 'quoteChar'='q', 
'escapeChar'='e')
+        """
+        hive_docker """
+            INSERT INTO csv_table_properties_db.${layout.table}
+            ${layout.partitioned ? 'PARTITION (group_id)' : ''}
+            SELECT ${layout.projection} FROM 
csv_table_properties_db.source_rows
+        """
+
+        // Hive's text writer honors line.delim even though its CSV reader 
ignores it. Set this only
+        // after INSERT so the fixture keeps newline records and tests a 
read-side metadata override.
+        hive_docker """
+            ALTER TABLE csv_table_properties_db.${layout.table} SET 
TBLPROPERTIES ('line.delim'='|')
+        """
+        def sourceRows = hive_docker """
+            SELECT ${layout.projection} FROM 
csv_table_properties_db.source_rows ORDER BY label
+        """
+        def hiveRows = hive_docker """
+            SELECT ${layout.projection} FROM 
csv_table_properties_db.${layout.table} ORDER BY label
+        """
+        assertEquals(sourceRows, hiveRows, "Hive CSV fixture must preserve the 
source rows: ${layout.table}")
+
+        sql "REFRESH DATABASE 
csv_table_properties_catalog.csv_table_properties_db"
+
+        // Compare against the source data in Hive, not another CSV read with 
the same delimiter bug.
+        def queries = [
+            "SELECT ${layout.projection} FROM %s ORDER BY label",
+            "SELECT ${layout.aggregate} FROM %s",
+            "SELECT ${layout.projection} FROM %s WHERE label = 'Omega' ORDER 
BY label"
+        ]
+        // The same files must remain readable after a metadata-only change to 
multi-character values:
+        // OpenCSVSerde takes the first Java character of each property.
+        for (boolean multiCharacter : [false, true]) {
+            if (multiCharacter) {
+                hive_docker """
+                    ALTER TABLE csv_table_properties_db.${layout.table} SET 
TBLPROPERTIES (
+                        'separatorChar'='ss', 'quoteChar'='qq', 
'escapeChar'='ee'
+                    )
+                """
+                sql "REFRESH DATABASE 
csv_table_properties_catalog.csv_table_properties_db"
+            }
+            for (String query : queries) {
+                def expected = hive_docker(String.format(query, 
"csv_table_properties_db.source_rows"))
+                checkV2 {
+                    def actual = sql(String.format(query, layout.table))
+                    assertEquals(expected, actual)
+                }
+            }
+        }
+    }
+
+    // Hive accepts these characters, but Doris must reject them before 
truncating their UTF-8 bytes.
+    // Empty values are covered by unit tests: Hive itself rejects them while 
validating ALTER TABLE.
+    for (def invalid : [
+        [key: "quoteChar", value: "é", message: "the first character must be 
ASCII"],
+        [key: "escapeChar", value: "é", message: "the first character must be 
ASCII"]
+    ]) {
+        hive_docker """
+            ALTER TABLE csv_table_properties_db.csv_two_columns
+            SET TBLPROPERTIES ('${invalid.key}'='${invalid.value}')
+        """
+        sql "REFRESH DATABASE 
csv_table_properties_catalog.csv_table_properties_db"
+        test {
+            sql "SELECT label, payload FROM csv_two_columns ORDER BY label"
+            exception "OpenCSVSerde property '${invalid.key}': 
${invalid.message}"
+        }
+        hive_docker """
+            ALTER TABLE csv_table_properties_db.csv_two_columns SET 
TBLPROPERTIES (
+                'separatorChar'='s', 'quoteChar'='q', 'escapeChar'='e'
+            )
+        """
+    }
+    // Write with an explicit backslash, then change only the metadata to 
Hive's double-quote sentinel.
+    // This leaves backslash-escaped embedded quotes in the file and exercises 
Hive's reader constructor choice.
+    String sqlBackslash = "\\\\"
+    for (boolean tableProperties : [true, false]) {
+        String table = tableProperties ? "csv_default_escape_table" : 
"csv_default_escape_serde"
+        hive_docker "DROP TABLE IF EXISTS csv_table_properties_db.${table}"
+        hive_docker """
+            CREATE TABLE csv_table_properties_db.${table} (label STRING, 
payload STRING)
+            ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde'
+            ${tableProperties ? '' : "WITH SERDEPROPERTIES 
('escapeChar'='${sqlBackslash}')"}
+            STORED AS TEXTFILE
+            ${tableProperties ? "TBLPROPERTIES 
('escapeChar'='${sqlBackslash}')" : ''}
+        """
+        hive_docker """
+            INSERT INTO csv_table_properties_db.${table}
+            SELECT label, payload FROM csv_table_properties_db.source_rows
+        """
+        for (String sentinel : ['"', '"suffix']) {
+            hive_docker """
+                ALTER TABLE csv_table_properties_db.${table}
+                SET ${tableProperties ? 'TBLPROPERTIES' : 'SERDEPROPERTIES'} 
('escapeChar'='${sentinel}')
+            """
+            sql "REFRESH DATABASE 
csv_table_properties_catalog.csv_table_properties_db"
+            def expected = hive_docker "SELECT label, payload FROM 
csv_table_properties_db.source_rows ORDER BY label"
+            def hiveRows = hive_docker "SELECT label, payload FROM 
csv_table_properties_db.${table} ORDER BY label"
+            assertEquals(expected, hiveRows)
+            checkV2 {
+                assertEquals(hiveRows, sql("SELECT label, payload FROM 
${table} ORDER BY label"))
+            }
+        }
+    }
+
+    // Write raw single-column TEXTFILE rows before switching SerDe. The CSV 
writer would normalize
+    // embedded quotes and hide reader-state bugs; hex literals also preserve 
binary NULs in fixtures.
+    def rawRecords = [
+        "x|  qa|bq|c", "abcqleft|rightq|tail", "qleft\nrightq|tail", 
"left|qunclosed",
+        "qaqqbq,tail", "eeabc,tail", "a\u0000b,tail", "", "|", ",", 
"qleftq|tail",
+        "x|\u2003\u2003qa|bq|c", "a\u0000\u0000b,tail", "a\\b,tail",
+        '"a""b",tail', '"a\\"b",tail', "abcqleftérightqétail"
+    ]
+    String rawExpressions = rawRecords.collect {
+        "decode(unhex('${it.getBytes('UTF-8').encodeHex()}'), 'UTF-8')"
+    }.join(", ")
+    // PostgreSQL-backed Hive metastores cannot persist NUL in text 
properties. Disabled quote/escape
+    // settings remain covered by the Hive SerDe oracle and V2 reader unit 
tests, without a metastore.
+    def dialects = [
+        [table: "csv_raw_custom", separator: "|", quote: "q", escape: "e"],
+        [table: "csv_raw_backslash", separator: ",", quote: "q", escape: 
sqlBackslash],
+        [table: "csv_raw_default", separator: ",", quote: '"', escape: 
sqlBackslash],
+        [table: "csv_raw_utf8", separator: "é", quote: "q", escape: "e"]
+    ]
+    for (def dialect : dialects) {
+        hive_docker "DROP TABLE IF EXISTS 
csv_table_properties_db.${dialect.table}"
+        hive_docker """
+            CREATE TABLE csv_table_properties_db.${dialect.table} (raw_record 
STRING) STORED AS TEXTFILE
+        """
+        hive_docker """
+            INSERT INTO csv_table_properties_db.${dialect.table}
+            SELECT explode(array(${rawExpressions}))
+        """
+        hive_docker """
+            ALTER TABLE csv_table_properties_db.${dialect.table}
+            REPLACE COLUMNS (first_value STRING, second_value STRING, 
third_value STRING)
+        """
+        hive_docker """
+            ALTER TABLE csv_table_properties_db.${dialect.table}
+            SET SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde'
+        """
+        hive_docker """
+            ALTER TABLE csv_table_properties_db.${dialect.table} SET 
TBLPROPERTIES (
+                'separatorChar'='${dialect.separator}', 
'quoteChar'='${dialect.quote}',
+                'escapeChar'='${dialect.escape}'
+            )
+        """
+        sql "REFRESH DATABASE 
csv_table_properties_catalog.csv_table_properties_db"
+        for (String query : [
+            "SELECT coalesce(hex(first_value), 'NULL'), 
coalesce(hex(second_value), 'NULL'), " +
+                "coalesce(hex(third_value), 'NULL') FROM %s ORDER BY 1, 2, 3",
+            "SELECT count(*), count(first_value), count(second_value), 
count(third_value) FROM %s",
+            "SELECT coalesce(hex(third_value), 'NULL'), hex(first_value) FROM 
%s " +
+                "WHERE first_value IS NOT NULL ORDER BY 1, 2"
+        ]) {
+            def expected = hive_docker(String.format(query, 
"csv_table_properties_db.${dialect.table}"))
+            checkV2 {
+                assertEquals(expected, sql(String.format(query, 
dialect.table)))
+            }
+        }
+    }
+
+    // These metadata values initialize successfully in Hive, but its reader 
rejects the effective tuple.
+    for (String properties : [
+        "'separatorChar'='q', 'quoteChar'='q', 'escapeChar'='e'",
+        "'separatorChar'='e', 'quoteChar'='q', 'escapeChar'='e'",
+        "'separatorChar'='s', 'quoteChar'='e', 'escapeChar'='e'",
+        "'separatorChar'='${sqlBackslash}', 'quoteChar'='q', 'escapeChar'='\"'"
+    ]) {
+        hive_docker """
+            ALTER TABLE csv_table_properties_db.csv_two_columns SET 
TBLPROPERTIES (${properties})
+        """
+        sql "REFRESH DATABASE 
csv_table_properties_catalog.csv_table_properties_db"
+        test {
+            sql "SELECT label, payload FROM csv_two_columns ORDER BY label"
+            exception "separatorChar, quoteChar and escapeChar must be 
distinct when non-NUL"
+        }
+    }
+    hive_docker """
+        ALTER TABLE csv_table_properties_db.csv_two_columns SET TBLPROPERTIES (
+            'separatorChar'='s', 'quoteChar'='q', 'escapeChar'='e'
+        )
+    """
+    sql "REFRESH DATABASE csv_table_properties_catalog.csv_table_properties_db"
+}


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

Reply via email to