From 162ff79c7d83bff9190adb1e525cd34110eb2791 Mon Sep 17 00:00:00 2001 From: 761417898 <761417898@qq.com> Date: Sun, 12 Apr 2026 11:58:29 +0800 Subject: [PATCH] Fix tree queryByRow device ID parsing for multi-segment paths Path splitting now joins device nodes and parses via StringArrayDeviceID(string) to match write-time normalization. Add C++ and Python regression tests. Made-with: Cursor --- cpp/src/common/path.cc | 15 ++++++-- .../tsfile_tree_query_by_row_test.cc | 26 ++++++++++++++ python/tests/test_query_by_row.py | 36 +++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/cpp/src/common/path.cc b/cpp/src/common/path.cc index fae3bff26..d70a9d6c6 100644 --- a/cpp/src/common/path.cc +++ b/cpp/src/common/path.cc @@ -19,6 +19,8 @@ #include "common/path.h" +#include "common/constant/tsfile_constant.h" + #ifdef ENABLE_ANTLR4 #include "parser/path_nodes_generator.h" #endif @@ -47,8 +49,17 @@ Path::Path(const std::string& path_sc, bool if_split) { IDeviceID::split_string(path_sc, '.'); #endif if (nodes.size() > 1) { - device_id_ = std::make_shared( - std::vector(nodes.begin(), nodes.end() - 1)); + // Join nodes, then parse like write path / Java Path (not + // per-segment vector). + std::string device_joined; + for (size_t i = 0; i + 1 < nodes.size(); ++i) { + if (i > 0) { + device_joined += PATH_SEPARATOR_CHAR; + } + device_joined += nodes[i]; + } + device_id_ = + std::make_shared(device_joined); measurement_ = nodes[nodes.size() - 1]; full_path_ = device_id_->get_device_name() + "." + measurement_; } else { diff --git a/cpp/test/reader/tree_view/tsfile_tree_query_by_row_test.cc b/cpp/test/reader/tree_view/tsfile_tree_query_by_row_test.cc index 74845e441..53549ce3f 100644 --- a/cpp/test/reader/tree_view/tsfile_tree_query_by_row_test.cc +++ b/cpp/test/reader/tree_view/tsfile_tree_query_by_row_test.cc @@ -133,6 +133,32 @@ TEST_F(TreeQueryByRowTest, NoOffsetNoLimit) { reader.close(); } +// Device id with three dot-separated parts (e.g. root.sg1.FeederA) must resolve +// to the same StringArrayDeviceID normalization as write path; queryByRow must +// not return E_DEVICE_NOT_EXIST. +TEST_F(TreeQueryByRowTest, QueryByRow_MultiSegmentDeviceId) { + std::vector devices = {"root.sg1.FeederA"}; + std::vector measurements = {"s1"}; + int num_rows = 10; + write_test_file(devices, measurements, num_rows); + + TsFileTreeReader reader; + ASSERT_EQ(E_OK, reader.open(file_name_)); + + ResultSet* result = nullptr; + ASSERT_EQ(E_OK, reader.queryByRow(devices, measurements, 0, 5, result)); + ASSERT_NE(result, nullptr); + + auto timestamps = collect_timestamps(result); + ASSERT_EQ(timestamps.size(), 5u); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(timestamps[i], i); + } + + reader.destroy_query_data_set(result); + reader.close(); +} + // Test: offset skips leading rows. TEST_F(TreeQueryByRowTest, OffsetOnly) { std::vector devices = {"d1"}; diff --git a/python/tests/test_query_by_row.py b/python/tests/test_query_by_row.py index e45cd1b20..ea33a8b36 100644 --- a/python/tests/test_query_by_row.py +++ b/python/tests/test_query_by_row.py @@ -69,6 +69,42 @@ def test_query_tree_by_row_offset_limit(): os.remove(file_path) +def test_query_tree_by_row_multi_segment_device(): + file_path = "python_tree_query_by_row_multiseg_test.tsfile" + if os.path.exists(file_path): + os.remove(file_path) + + try: + device_id = "root.sg1.FeederA" + measurement_names = ["s1"] + num_rows = 10 + + writer = TsFileWriter(file_path) + for measurement in measurement_names: + writer.register_timeseries(device_id, TimeseriesSchema(measurement, TSDataType.INT64)) + + for t in range(num_rows): + fields = [Field(measurement_names[0], t * 100, TSDataType.INT64)] + writer.write_row_record(RowRecord(device_id, t, fields)) + + writer.close() + + reader = TsFileReader(file_path) + limit = 5 + with reader.query_tree_by_row([device_id], measurement_names, 0, limit) as result: + row = 0 + while result.next(): + ts = result.get_value_by_index(1) + assert ts == row + assert result.get_value_by_index(2) == ts * 100 + row += 1 + assert row == limit + reader.close() + finally: + if os.path.exists(file_path): + os.remove(file_path) + + def test_query_table_by_row_offset_limit(): file_path = "python_table_query_by_row_test.tsfile" if os.path.exists(file_path):