This is an automated email from the ASF dual-hosted git repository.
jiangtian pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/tsfile.git
The following commit(s) were added to refs/heads/develop by this push:
new cb595432 fix(cpp): skip missing device/measurement in tree queryByRow
(#777)
cb595432 is described below
commit cb59543280364df451660400a80498f12667953e
Author: Hongzhi Gao <[email protected]>
AuthorDate: Mon Apr 13 09:44:04 2026 +0800
fix(cpp): skip missing device/measurement in tree queryByRow (#777)
Align QDSWithoutTimeGenerator with Java: treat E_DEVICE_NOT_EXIST and
E_MEASUREMENT_NOT_EXIST as skippable paths and fix column index mapping
when some paths are omitted. Add C++ and Python regression tests.
Made-with: Cursor
---
cpp/src/reader/qds_without_timegenerator.cc | 22 +++++++-----
.../tree_view/tsfile_tree_query_by_row_test.cc | 38 ++++++++++++++++++++
python/tests/test_query_by_row.py | 40 ++++++++++++++++++++++
3 files changed, 91 insertions(+), 9 deletions(-)
diff --git a/cpp/src/reader/qds_without_timegenerator.cc
b/cpp/src/reader/qds_without_timegenerator.cc
index 0710b587..b895f2e3 100644
--- a/cpp/src/reader/qds_without_timegenerator.cc
+++ b/cpp/src/reader/qds_without_timegenerator.cc
@@ -19,6 +19,7 @@
#include "qds_without_timegenerator.h"
+#include "utils/errno_define.h"
#include "utils/util_define.h"
using namespace common;
@@ -66,17 +67,20 @@ int QDSWithoutTimeGenerator::init_internal(TsFileIOReader*
io_reader,
TsFileSeriesScanIterator* ssi = nullptr;
ret = io_reader_->alloc_ssi(paths[i].device_id_, paths[i].measurement_,
ssi, pa_, global_time_filter);
- if (ret != 0) {
+ if (ret == E_MEASUREMENT_NOT_EXIST || ret == E_DEVICE_NOT_EXIST) {
+ continue;
+ }
+ if (ret != E_OK) {
return ret;
- } else {
- index_lookup_.insert({paths[i].measurement_, i + 1});
- if (paths[i].full_path_ != paths[i].measurement_) {
- index_lookup_.insert({paths[i].full_path_, i + 1});
- }
- ssi_vec_.push_back(ssi);
- valid_paths.push_back(paths[i]);
- column_names.push_back(paths[i].full_path_);
}
+ size_t col_idx = ssi_vec_.size();
+ index_lookup_.insert({paths[i].measurement_, col_idx + 1});
+ if (paths[i].full_path_ != paths[i].measurement_) {
+ index_lookup_.insert({paths[i].full_path_, col_idx + 1});
+ }
+ ssi_vec_.push_back(ssi);
+ valid_paths.push_back(paths[i]);
+ column_names.push_back(paths[i].full_path_);
}
size_t path_count = valid_paths.size();
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 74845e44..1e958253 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,44 @@ TEST_F(TreeQueryByRowTest, NoOffsetNoLimit) {
reader.close();
}
+// queryByRow skips paths whose device or measurement is missing in the file;
+// only existing series are returned (aligned with Java tree reader).
+TEST_F(TreeQueryByRowTest, QueryByRow_SkipsMissingDeviceAndMeasurement) {
+ std::vector<std::string> devices = {"d1"};
+ std::vector<std::string> measurements = {"s1"};
+ const int num_rows = 5;
+ write_test_file(devices, measurements, num_rows);
+
+ TsFileTreeReader reader;
+ ASSERT_EQ(E_OK, reader.open(file_name_));
+
+ ResultSet* result = nullptr;
+ std::vector<std::string> q_devices = {"d1", "d999"};
+ std::vector<std::string> q_meas = {"s1", "ghost_m"};
+ ASSERT_EQ(E_OK, reader.queryByRow(q_devices, q_meas, 0, -1, result));
+ ASSERT_NE(result, nullptr);
+
+ auto meta = result->get_metadata();
+ ASSERT_EQ(2u, meta->get_column_count());
+
+ bool has_next = false;
+ int row_count = 0;
+ while (IS_SUCC(result->next(has_next)) && has_next) {
+ RowRecord* rr = result->get_row_record();
+ int64_t ts = rr->get_timestamp();
+ ASSERT_EQ(ts, static_cast<int64_t>(row_count));
+ Field* f = rr->get_field(1);
+ ASSERT_NE(f, nullptr);
+ ASSERT_EQ(f->type_, INT64);
+ EXPECT_EQ(f->get_value<int64_t>(), static_cast<int64_t>(ts * 100 + 0));
+ row_count++;
+ }
+ EXPECT_EQ(row_count, num_rows);
+
+ reader.destroy_query_data_set(result);
+ reader.close();
+}
+
// Test: offset skips leading rows.
TEST_F(TreeQueryByRowTest, OffsetOnly) {
std::vector<std::string> devices = {"d1"};
diff --git a/python/tests/test_query_by_row.py
b/python/tests/test_query_by_row.py
index e45cd1b2..3adae996 100644
--- a/python/tests/test_query_by_row.py
+++ b/python/tests/test_query_by_row.py
@@ -110,3 +110,43 @@ def test_query_table_by_row_offset_limit():
if os.path.exists(file_path):
os.remove(file_path)
+
+def test_query_tree_by_row_skips_missing_device_and_measurement():
+ """Tree queryByRow: missing device or measurement paths are skipped
(Java-aligned)."""
+ file_path = "python_tree_query_by_row_skip_missing.tsfile"
+ if os.path.exists(file_path):
+ os.remove(file_path)
+
+ try:
+ device_ids = ["d1"]
+ measurement_names = ["s1"]
+ num_rows = 5
+
+ writer = TsFileWriter(file_path)
+ for device_id in device_ids:
+ for measurement in measurement_names:
+ writer.register_timeseries(device_id,
TimeseriesSchema(measurement, TSDataType.INT64))
+
+ for t in range(num_rows):
+ fields = [Field("s1", t * 100 + 0, TSDataType.INT64)]
+ writer.write_row_record(RowRecord(device_ids[0], t, fields))
+
+ writer.close()
+
+ reader = TsFileReader(file_path)
+ q_devices = ["d1", "d999"]
+ q_measurements = ["s1", "ghost_m"]
+ with reader.query_tree_by_row(q_devices, q_measurements, 0, -1) as
result:
+ assert result.get_metadata().get_column_num() == 2
+ row = 0
+ while result.next():
+ ts = result.get_value_by_index(1)
+ assert ts == row
+ assert result.get_value_by_index(2) == row * 100 + 0
+ row += 1
+ assert row == num_rows
+ reader.close()
+ finally:
+ if os.path.exists(file_path):
+ os.remove(file_path)
+