From 39035fcccc2c4ecd109cffee655fc9aa279a95a7 Mon Sep 17 00:00:00 2001 From: ColinLee Date: Wed, 30 Sep 2026 11:35:02 +0800 Subject: [PATCH] fix(cpp): emit bare measurement names for tree-model result columns Tree-model head/cat/export emitted fully-qualified "device.measurement" column names (and NDJSON keys), but the CLI requirement specifies bare measurement names: e.g. `head -d root.factory.d1 -m temp -m status` should print `time temp status`, and `head -m temp -f ndjson` should emit {"time":"...","temp":...}. Strip the device prefix at the output layer only for tree-model row queries, leaving the reader's get_column_name contract and the table model untouched. --- cpp/test/tools/command_e2e_test.cc | 27 +++++++++++++ cpp/test/tools/golden/tree_cat_csv.txt | 2 +- cpp/test/tools/golden/tree_cat_ndjson.txt | 4 +- cpp/test/tools/golden/tree_cat_table.txt | 6 +-- cpp/test/tools/golden/tree_export_csv.txt | 2 +- cpp/test/tools/golden/tree_export_ndjson.txt | 40 +++++++++---------- cpp/test/tools/golden/tree_export_table.txt | 42 ++++++++++---------- cpp/test/tools/golden/tree_head_csv.txt | 2 +- cpp/test/tools/golden/tree_head_ndjson.txt | 4 +- cpp/test/tools/golden/tree_head_table.txt | 6 +-- cpp/test/tools/model_format_e2e_test.cc | 3 +- cpp/tools/commands/row_query.cc | 11 +++-- cpp/tools/format/result_set_format.cc | 12 +++++- cpp/tools/format/result_set_format.h | 3 +- 14 files changed, 101 insertions(+), 63 deletions(-) diff --git a/cpp/test/tools/command_e2e_test.cc b/cpp/test/tools/command_e2e_test.cc index 083000991..64b369f9a 100644 --- a/cpp/test/tools/command_e2e_test.cc +++ b/cpp/test/tools/command_e2e_test.cc @@ -662,6 +662,33 @@ TEST(CliE2E, TreeCountAndStatsUseDeviceTimestampUnion) { << stats_out.str(); } +TEST(CliE2E, TreeHeadWithoutDeviceUsesBareMeasurementNames) { + // A single-device tree file queried without -d resolves the only device + // implicitly. Result columns must still be bare measurement names (FIELD), + // not "device.measurement" (TsFile-143 / requirement 3.1.4.2 examples). + SparseTreeFixture f; + + std::ostringstream ndjson_out; + std::ostringstream ndjson_err; + EXPECT_EQ(tsfile_cli::run_cli( + {"head", "-m", "left", "-n", "1", "-f", "ndjson", f.path}, + ndjson_out, ndjson_err), + 0) + << ndjson_err.str(); + EXPECT_EQ(ndjson_out.str(), "{\"time\":\"0\",\"left\":10}\n"); + + std::ostringstream csv_out; + std::ostringstream csv_err; + EXPECT_EQ( + tsfile_cli::run_cli({"head", "-f", "csv", f.path}, csv_out, csv_err), 0) + << csv_err.str(); + EXPECT_EQ(csv_out.str(), + "time,left,right\n" + "0,10,\\N\n" + "1,\\N,true\n" + "2,20,false\n"); +} + TEST(CliE2E, MetadataTableFilterIsCaseInsensitive) { Fixture f; diff --git a/cpp/test/tools/golden/tree_cat_csv.txt b/cpp/test/tools/golden/tree_cat_csv.txt index 4b161f03a..7da86bc06 100644 --- a/cpp/test/tools/golden/tree_cat_csv.txt +++ b/cpp/test/tools/golden/tree_cat_csv.txt @@ -1,3 +1,3 @@ -time,root.test.d1.m1,root.test.d1.m2,root.test.d1.m3,root.test.d1.m4,root.test.d1.m5 +time,m1,m2,m3,m4,m5 0,0,0.5,value_0,0,100.5 1,1,1.5,value_1,2,101.5 diff --git a/cpp/test/tools/golden/tree_cat_ndjson.txt b/cpp/test/tools/golden/tree_cat_ndjson.txt index 82abeb86a..dce1d2c05 100644 --- a/cpp/test/tools/golden/tree_cat_ndjson.txt +++ b/cpp/test/tools/golden/tree_cat_ndjson.txt @@ -1,2 +1,2 @@ -{"time":"0","root.test.d1.m1":0,"root.test.d1.m2":0.5,"root.test.d1.m3":"value_0","root.test.d1.m4":0,"root.test.d1.m5":100.5} -{"time":"1","root.test.d1.m1":1,"root.test.d1.m2":1.5,"root.test.d1.m3":"value_1","root.test.d1.m4":2,"root.test.d1.m5":101.5} +{"time":"0","m1":0,"m2":0.5,"m3":"value_0","m4":0,"m5":100.5} +{"time":"1","m1":1,"m2":1.5,"m3":"value_1","m4":2,"m5":101.5} diff --git a/cpp/test/tools/golden/tree_cat_table.txt b/cpp/test/tools/golden/tree_cat_table.txt index 5f5c40281..29db29b40 100644 --- a/cpp/test/tools/golden/tree_cat_table.txt +++ b/cpp/test/tools/golden/tree_cat_table.txt @@ -1,3 +1,3 @@ -time root.test.d1.m1 root.test.d1.m2 root.test.d1.m3 root.test.d1.m4 root.test.d1.m5 -0 0 0.5 value_0 0 100.5 -1 1 1.5 value_1 2 101.5 +time m1 m2 m3 m4 m5 +0 0 0.5 value_0 0 100.5 +1 1 1.5 value_1 2 101.5 diff --git a/cpp/test/tools/golden/tree_export_csv.txt b/cpp/test/tools/golden/tree_export_csv.txt index 5fd69cf44..ffcbe98fe 100644 --- a/cpp/test/tools/golden/tree_export_csv.txt +++ b/cpp/test/tools/golden/tree_export_csv.txt @@ -1,4 +1,4 @@ -time,root.test.d1.m1,root.test.d1.m2,root.test.d1.m3,root.test.d1.m4,root.test.d1.m5 +time,m1,m2,m3,m4,m5 0,0,0.5,value_0,0,100.5 1,1,1.5,value_1,2,101.5 2,2,2.5,value_2,4,102.5 diff --git a/cpp/test/tools/golden/tree_export_ndjson.txt b/cpp/test/tools/golden/tree_export_ndjson.txt index 600536e32..f92adaaa7 100644 --- a/cpp/test/tools/golden/tree_export_ndjson.txt +++ b/cpp/test/tools/golden/tree_export_ndjson.txt @@ -1,20 +1,20 @@ -{"time":"0","root.test.d1.m1":0,"root.test.d1.m2":0.5,"root.test.d1.m3":"value_0","root.test.d1.m4":0,"root.test.d1.m5":100.5} -{"time":"1","root.test.d1.m1":1,"root.test.d1.m2":1.5,"root.test.d1.m3":"value_1","root.test.d1.m4":2,"root.test.d1.m5":101.5} -{"time":"2","root.test.d1.m1":2,"root.test.d1.m2":2.5,"root.test.d1.m3":"value_2","root.test.d1.m4":4,"root.test.d1.m5":102.5} -{"time":"3","root.test.d1.m1":3,"root.test.d1.m2":3.5,"root.test.d1.m3":"value_3","root.test.d1.m4":6,"root.test.d1.m5":103.5} -{"time":"4","root.test.d1.m1":4,"root.test.d1.m2":4.5,"root.test.d1.m3":"value_4","root.test.d1.m4":8,"root.test.d1.m5":104.5} -{"time":"5","root.test.d1.m1":5,"root.test.d1.m2":5.5,"root.test.d1.m3":"value_5","root.test.d1.m4":10,"root.test.d1.m5":105.5} -{"time":"6","root.test.d1.m1":6,"root.test.d1.m2":6.5,"root.test.d1.m3":"value_6","root.test.d1.m4":12,"root.test.d1.m5":106.5} -{"time":"7","root.test.d1.m1":7,"root.test.d1.m2":7.5,"root.test.d1.m3":"value_7","root.test.d1.m4":14,"root.test.d1.m5":107.5} -{"time":"8","root.test.d1.m1":8,"root.test.d1.m2":8.5,"root.test.d1.m3":"value_8","root.test.d1.m4":16,"root.test.d1.m5":108.5} -{"time":"9","root.test.d1.m1":9,"root.test.d1.m2":9.5,"root.test.d1.m3":"value_9","root.test.d1.m4":18,"root.test.d1.m5":109.5} -{"time":"10","root.test.d1.m1":10,"root.test.d1.m2":10.5,"root.test.d1.m3":"value_10","root.test.d1.m4":20,"root.test.d1.m5":110.5} -{"time":"11","root.test.d1.m1":11,"root.test.d1.m2":11.5,"root.test.d1.m3":"value_11","root.test.d1.m4":22,"root.test.d1.m5":111.5} -{"time":"12","root.test.d1.m1":12,"root.test.d1.m2":12.5,"root.test.d1.m3":"value_12","root.test.d1.m4":24,"root.test.d1.m5":112.5} -{"time":"13","root.test.d1.m1":13,"root.test.d1.m2":13.5,"root.test.d1.m3":"value_13","root.test.d1.m4":26,"root.test.d1.m5":113.5} -{"time":"14","root.test.d1.m1":14,"root.test.d1.m2":14.5,"root.test.d1.m3":"value_14","root.test.d1.m4":28,"root.test.d1.m5":114.5} -{"time":"15","root.test.d1.m1":15,"root.test.d1.m2":15.5,"root.test.d1.m3":"value_15","root.test.d1.m4":30,"root.test.d1.m5":115.5} -{"time":"16","root.test.d1.m1":16,"root.test.d1.m2":16.5,"root.test.d1.m3":"value_16","root.test.d1.m4":32,"root.test.d1.m5":116.5} -{"time":"17","root.test.d1.m1":17,"root.test.d1.m2":17.5,"root.test.d1.m3":"value_17","root.test.d1.m4":34,"root.test.d1.m5":117.5} -{"time":"18","root.test.d1.m1":18,"root.test.d1.m2":18.5,"root.test.d1.m3":"value_18","root.test.d1.m4":36,"root.test.d1.m5":118.5} -{"time":"19","root.test.d1.m1":19,"root.test.d1.m2":19.5,"root.test.d1.m3":"value_19","root.test.d1.m4":38,"root.test.d1.m5":119.5} +{"time":"0","m1":0,"m2":0.5,"m3":"value_0","m4":0,"m5":100.5} +{"time":"1","m1":1,"m2":1.5,"m3":"value_1","m4":2,"m5":101.5} +{"time":"2","m1":2,"m2":2.5,"m3":"value_2","m4":4,"m5":102.5} +{"time":"3","m1":3,"m2":3.5,"m3":"value_3","m4":6,"m5":103.5} +{"time":"4","m1":4,"m2":4.5,"m3":"value_4","m4":8,"m5":104.5} +{"time":"5","m1":5,"m2":5.5,"m3":"value_5","m4":10,"m5":105.5} +{"time":"6","m1":6,"m2":6.5,"m3":"value_6","m4":12,"m5":106.5} +{"time":"7","m1":7,"m2":7.5,"m3":"value_7","m4":14,"m5":107.5} +{"time":"8","m1":8,"m2":8.5,"m3":"value_8","m4":16,"m5":108.5} +{"time":"9","m1":9,"m2":9.5,"m3":"value_9","m4":18,"m5":109.5} +{"time":"10","m1":10,"m2":10.5,"m3":"value_10","m4":20,"m5":110.5} +{"time":"11","m1":11,"m2":11.5,"m3":"value_11","m4":22,"m5":111.5} +{"time":"12","m1":12,"m2":12.5,"m3":"value_12","m4":24,"m5":112.5} +{"time":"13","m1":13,"m2":13.5,"m3":"value_13","m4":26,"m5":113.5} +{"time":"14","m1":14,"m2":14.5,"m3":"value_14","m4":28,"m5":114.5} +{"time":"15","m1":15,"m2":15.5,"m3":"value_15","m4":30,"m5":115.5} +{"time":"16","m1":16,"m2":16.5,"m3":"value_16","m4":32,"m5":116.5} +{"time":"17","m1":17,"m2":17.5,"m3":"value_17","m4":34,"m5":117.5} +{"time":"18","m1":18,"m2":18.5,"m3":"value_18","m4":36,"m5":118.5} +{"time":"19","m1":19,"m2":19.5,"m3":"value_19","m4":38,"m5":119.5} diff --git a/cpp/test/tools/golden/tree_export_table.txt b/cpp/test/tools/golden/tree_export_table.txt index 557713b5d..5d5e39c2c 100644 --- a/cpp/test/tools/golden/tree_export_table.txt +++ b/cpp/test/tools/golden/tree_export_table.txt @@ -1,21 +1,21 @@ -time root.test.d1.m1 root.test.d1.m2 root.test.d1.m3 root.test.d1.m4 root.test.d1.m5 -0 0 0.5 value_0 0 100.5 -1 1 1.5 value_1 2 101.5 -2 2 2.5 value_2 4 102.5 -3 3 3.5 value_3 6 103.5 -4 4 4.5 value_4 8 104.5 -5 5 5.5 value_5 10 105.5 -6 6 6.5 value_6 12 106.5 -7 7 7.5 value_7 14 107.5 -8 8 8.5 value_8 16 108.5 -9 9 9.5 value_9 18 109.5 -10 10 10.5 value_10 20 110.5 -11 11 11.5 value_11 22 111.5 -12 12 12.5 value_12 24 112.5 -13 13 13.5 value_13 26 113.5 -14 14 14.5 value_14 28 114.5 -15 15 15.5 value_15 30 115.5 -16 16 16.5 value_16 32 116.5 -17 17 17.5 value_17 34 117.5 -18 18 18.5 value_18 36 118.5 -19 19 19.5 value_19 38 119.5 +time m1 m2 m3 m4 m5 +0 0 0.5 value_0 0 100.5 +1 1 1.5 value_1 2 101.5 +2 2 2.5 value_2 4 102.5 +3 3 3.5 value_3 6 103.5 +4 4 4.5 value_4 8 104.5 +5 5 5.5 value_5 10 105.5 +6 6 6.5 value_6 12 106.5 +7 7 7.5 value_7 14 107.5 +8 8 8.5 value_8 16 108.5 +9 9 9.5 value_9 18 109.5 +10 10 10.5 value_10 20 110.5 +11 11 11.5 value_11 22 111.5 +12 12 12.5 value_12 24 112.5 +13 13 13.5 value_13 26 113.5 +14 14 14.5 value_14 28 114.5 +15 15 15.5 value_15 30 115.5 +16 16 16.5 value_16 32 116.5 +17 17 17.5 value_17 34 117.5 +18 18 18.5 value_18 36 118.5 +19 19 19.5 value_19 38 119.5 diff --git a/cpp/test/tools/golden/tree_head_csv.txt b/cpp/test/tools/golden/tree_head_csv.txt index 4b161f03a..7da86bc06 100644 --- a/cpp/test/tools/golden/tree_head_csv.txt +++ b/cpp/test/tools/golden/tree_head_csv.txt @@ -1,3 +1,3 @@ -time,root.test.d1.m1,root.test.d1.m2,root.test.d1.m3,root.test.d1.m4,root.test.d1.m5 +time,m1,m2,m3,m4,m5 0,0,0.5,value_0,0,100.5 1,1,1.5,value_1,2,101.5 diff --git a/cpp/test/tools/golden/tree_head_ndjson.txt b/cpp/test/tools/golden/tree_head_ndjson.txt index 82abeb86a..dce1d2c05 100644 --- a/cpp/test/tools/golden/tree_head_ndjson.txt +++ b/cpp/test/tools/golden/tree_head_ndjson.txt @@ -1,2 +1,2 @@ -{"time":"0","root.test.d1.m1":0,"root.test.d1.m2":0.5,"root.test.d1.m3":"value_0","root.test.d1.m4":0,"root.test.d1.m5":100.5} -{"time":"1","root.test.d1.m1":1,"root.test.d1.m2":1.5,"root.test.d1.m3":"value_1","root.test.d1.m4":2,"root.test.d1.m5":101.5} +{"time":"0","m1":0,"m2":0.5,"m3":"value_0","m4":0,"m5":100.5} +{"time":"1","m1":1,"m2":1.5,"m3":"value_1","m4":2,"m5":101.5} diff --git a/cpp/test/tools/golden/tree_head_table.txt b/cpp/test/tools/golden/tree_head_table.txt index 5f5c40281..29db29b40 100644 --- a/cpp/test/tools/golden/tree_head_table.txt +++ b/cpp/test/tools/golden/tree_head_table.txt @@ -1,3 +1,3 @@ -time root.test.d1.m1 root.test.d1.m2 root.test.d1.m3 root.test.d1.m4 root.test.d1.m5 -0 0 0.5 value_0 0 100.5 -1 1 1.5 value_1 2 101.5 +time m1 m2 m3 m4 m5 +0 0 0.5 value_0 0 100.5 +1 1 1.5 value_1 2 101.5 diff --git a/cpp/test/tools/model_format_e2e_test.cc b/cpp/test/tools/model_format_e2e_test.cc index f782ce569..6e877aa93 100644 --- a/cpp/test/tools/model_format_e2e_test.cc +++ b/cpp/test/tools/model_format_e2e_test.cc @@ -288,8 +288,7 @@ TEST(ComplexTreeFixture, CsvAnswersMatchByteForByte) { const std::string path = tsfile_cli_test::write_complex_tree_fixture(); expect_cli_exact( {"head", "-d", "root.test.d1", "-n", "2", "-f", "csv", path}, 0, - "time,root.test.d1.m1,root.test.d1.m2,root.test.d1.m3," - "root.test.d1.m4,root.test.d1.m5\n" + "time,m1,m2,m3,m4,m5\n" "0,0,0.5,value_0,0,100.5\n" "1,1,1.5,value_1,2,101.5\n", ""); diff --git a/cpp/tools/commands/row_query.cc b/cpp/tools/commands/row_query.cc index 828bb6c55..22a2fab21 100644 --- a/cpp/tools/commands/row_query.cc +++ b/cpp/tools/commands/row_query.cc @@ -280,6 +280,7 @@ int run_row_query(const ParsedArgs& args, storage::TsFileReader& reader, int qret = 0; const bool push_down = can_push_down_row_window(args, offset, limit); std::unique_ptr tag_filter; + std::string tree_device_prefix; if (is_table_model(args, reader)) { std::string table_name = args.table; @@ -333,6 +334,7 @@ int run_row_query(const ParsedArgs& args, storage::TsFileReader& reader, } effective_args.device = devices[0]->get_device_name(); } + tree_device_prefix = effective_args.device; std::vector paths; int selection_ret = resolve_tree_paths(effective_args, reader, paths, err); @@ -361,10 +363,11 @@ int run_row_query(const ParsedArgs& args, storage::TsFileReader& reader, // that callers could mistake for a complete result. The final write is // still checked separately so stdout errors remain runtime failures. std::ostringstream staged; - int wret = push_down ? emit_result_set(rs, fmt, args.no_header, staged, 0, - -1, emitted_rows) - : emit_result_set(rs, fmt, args.no_header, staged, - offset, limit, emitted_rows); + int wret = push_down + ? emit_result_set(rs, fmt, args.no_header, staged, 0, -1, + emitted_rows, tree_device_prefix) + : emit_result_set(rs, fmt, args.no_header, staged, offset, + limit, emitted_rows, tree_device_prefix); reader.destroy_query_data_set(rs); if (wret == common::E_OK) { const std::string bytes = staged.str(); diff --git a/cpp/tools/format/result_set_format.cc b/cpp/tools/format/result_set_format.cc index ea0b350b4..111e8d4bc 100644 --- a/cpp/tools/format/result_set_format.cc +++ b/cpp/tools/format/result_set_format.cc @@ -74,15 +74,23 @@ std::string cell_to_string(storage::ResultSet* rs, uint32_t i, int emit_result_set(storage::ResultSet* rs, OutputFormat fmt, bool no_header, std::ostream& out, long long offset, long long limit, - long long* emitted_rows) { + long long* emitted_rows, + const std::string& strip_device_prefix) { auto meta = rs->get_metadata(); const uint32_t ncol = meta->get_column_count(); + const std::string device_prefix = + strip_device_prefix.empty() ? std::string() : strip_device_prefix + "."; std::vector header; std::vector types; header.reserve(ncol); types.reserve(ncol); for (uint32_t i = 1; i <= ncol; ++i) { - header.push_back(meta->get_column_name(i)); + std::string name = meta->get_column_name(i); + if (!device_prefix.empty() && name.size() > device_prefix.size() && + name.compare(0, device_prefix.size(), device_prefix) == 0) { + name = name.substr(device_prefix.size()); + } + header.push_back(std::move(name)); types.push_back(meta->get_column_type(i)); } diff --git a/cpp/tools/format/result_set_format.h b/cpp/tools/format/result_set_format.h index 40814a47b..2f00d1b45 100644 --- a/cpp/tools/format/result_set_format.h +++ b/cpp/tools/format/result_set_format.h @@ -34,7 +34,8 @@ std::string cell_to_string(storage::ResultSet* rs, uint32_t col_index, int emit_result_set(storage::ResultSet* rs, OutputFormat fmt, bool no_header, std::ostream& out, long long offset = 0, - long long limit = -1, long long* emitted_rows = nullptr); + long long limit = -1, long long* emitted_rows = nullptr, + const std::string& strip_device_prefix = ""); int emit_result_set_sampled(storage::ResultSet* rs, OutputFormat fmt, bool no_header, std::ostream& out, long long limit,