Skip to content

Commit 719f80a

Browse files
Fix several bugs in tsfile-cli and add tag filter commands (#837)
* fix(tsfile-cli): fix the case inconsistency in TABLE names * fix(tsfile-cli): fix `build.sh` command * fix(tsfile-cli): add `reader.queryByRow(...)` to `head/cat` cammand * fix(tsfile-cli): add `TagFilterBuilder` predicate variable * fix(tsfile-cli): format code * fix(tsfile-cli): rename `table_filter` as `target_table_name`
1 parent 0963453 commit 719f80a

15 files changed

Lines changed: 481 additions & 19 deletions

File tree

cpp/README-zh.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ sudo apt-get install -y cmake make g++ clang-format libuuid-dev
9999
bash build.sh
100100
```
101101

102+
`build.sh` 默认只编译,不执行安装。如果需要安装到 CMake 的安装前缀目录,显式传入 `install` 参数:
103+
104+
```bash
105+
bash build.sh install
106+
```
107+
102108
如果你安装了 Maven 工具,也可以运行:
103109

104110
```bash

cpp/build.sh

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
build_type=Release
2222
build_test=0
2323
build_bench=0
24+
do_install=0
2425
use_cpp11=1
2526
enable_cov=0
2627
debug_se=0
@@ -39,10 +40,37 @@ get_key_value() {
3940
echo "${1#*=}"
4041
}
4142

43+
usage()
44+
{
45+
cat <<EOF
46+
Usage: bash build.sh [install] [options]
47+
48+
Commands:
49+
install Run make install after a successful build.
50+
51+
Options:
52+
-t=<type>, -t <type> Build type: Debug, Release, RelWithDebInfo, MinSizeRel.
53+
-a=<ON|OFF> Enable or disable AddressSanitizer.
54+
-c=<ON|OFF> Enable or disable code coverage.
55+
--enable-antlr4=<ON|OFF>
56+
--disable-antlr4
57+
--enable-snappy=<ON|OFF>
58+
--disable-snappy
59+
--enable-lz4=<ON|OFF>
60+
--disable-lz4
61+
--enable-lzokay=<ON|OFF>
62+
--disable-lzokay
63+
--enable-zlib=<ON|OFF>
64+
--disable-zlib
65+
-h, --help Show this help message.
66+
EOF
67+
}
68+
4269
function print_config()
4370
{
4471
echo "build_type=$build_type"
4572
echo "build_test=$build_test"
73+
echo "do_install=$do_install"
4674
echo "use_cpp11=$use_cpp11"
4775
echo "enable_cov=$enable_cov"
4876
echo "enable_asan=$enable_asan"
@@ -68,6 +96,8 @@ parse_options()
6896
do_clean=1;;
6997
run_cov)
7098
run_cov_only=1;;
99+
install | --install)
100+
do_install=1;;
71101
-t=*)
72102
build_type=$(get_key_value "$1");;
73103
-t)
@@ -103,18 +133,19 @@ parse_options()
103133
enable_lzokay=OFF;;
104134
--disable-zlib)
105135
enable_zlib=OFF;;
106-
#-h | --help)
107-
# usage
108-
# exit 0;;
109-
#*)
110-
# echo "Unknown option '$1'"
111-
# exit 1;;
136+
-h | --help)
137+
usage
138+
exit 0;;
139+
*)
140+
echo "Unknown option '$1'"
141+
usage
142+
exit 1;;
112143
esac
113144
shift
114145
done
115146
}
116147

117-
parse_options $*
148+
parse_options "$@"
118149
print_config
119150

120151
if [[ ${run_cov_only} -eq 1 ]]
@@ -171,4 +202,9 @@ cmake ../../ \
171202
-DENABLE_ZLIB=$enable_zlib
172203

173204
VERBOSE=1 make
174-
VERBOSE=1 make install
205+
if [ ${do_install} -eq 1 ]
206+
then
207+
VERBOSE=1 make install
208+
else
209+
echo "Skip install. Pass 'install' to run 'make install'."
210+
fi

cpp/test/tools/cli_args_test.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,34 @@ TEST(ParseArgsTest, LimitOffsetAndTimeRange) {
9292
EXPECT_EQ(p.end, 200);
9393
}
9494

95+
TEST(ParseArgsTest, TagFilterParsed) {
96+
auto p = tsfile_cli::parse_args(
97+
{"cat", "--tag-filter", "id1", "eq", "dev_a", "data.tsfile"});
98+
EXPECT_TRUE(p.error.empty());
99+
EXPECT_TRUE(p.has_tag_filter);
100+
EXPECT_EQ(p.tag_filter_op, tsfile_cli::ParsedArgs::TagFilterOp::kEq);
101+
EXPECT_EQ(p.tag_filter_column, "id1");
102+
EXPECT_EQ(p.tag_filter_value, "dev_a");
103+
}
104+
105+
TEST(ParseArgsTest, TagBetweenParsed) {
106+
auto p = tsfile_cli::parse_args(
107+
{"cat", "--tag-between", "id1", "dev_a", "dev_c", "data.tsfile"});
108+
EXPECT_TRUE(p.error.empty());
109+
EXPECT_TRUE(p.has_tag_filter);
110+
EXPECT_EQ(p.tag_filter_op, tsfile_cli::ParsedArgs::TagFilterOp::kBetween);
111+
EXPECT_EQ(p.tag_filter_column, "id1");
112+
EXPECT_EQ(p.tag_filter_value, "dev_a");
113+
EXPECT_EQ(p.tag_filter_value2, "dev_c");
114+
}
115+
116+
TEST(ParseArgsTest, DuplicateTagFilterIsError) {
117+
auto p = tsfile_cli::parse_args({"cat", "--tag-filter", "id1", "eq",
118+
"dev_a", "--tag-between", "id1", "a", "z",
119+
"data.tsfile"});
120+
EXPECT_FALSE(p.error.empty());
121+
}
122+
95123
TEST(ParseArgsTest, UnknownFlagIsError) {
96124
auto p = tsfile_cli::parse_args({"ls", "--bogus", "data.tsfile"});
97125
EXPECT_FALSE(p.error.empty());

cpp/test/tools/cli_test_util.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,49 @@ inline std::string write_table_fixture() {
9999
return out_path;
100100
}
101101

102+
inline std::string write_tag_filter_fixture() {
103+
storage::libtsfile_init();
104+
std::string out_path =
105+
unique_temp_path("tsfile_cli_tag_filter_fixture", ".tsfile");
106+
std::string table_name = "t1";
107+
108+
storage::WriteFile file;
109+
int flags = O_WRONLY | O_CREAT | O_TRUNC;
110+
#ifdef _WIN32
111+
flags |= O_BINARY;
112+
#endif
113+
file.create(out_path, flags, 0666);
114+
115+
auto* schema = new storage::TableSchema(
116+
table_name,
117+
{
118+
common::ColumnSchema("id1", common::STRING, common::UNCOMPRESSED,
119+
common::PLAIN, common::ColumnCategory::TAG),
120+
common::ColumnSchema("s1", common::INT64, common::UNCOMPRESSED,
121+
common::PLAIN, common::ColumnCategory::FIELD),
122+
});
123+
124+
auto* writer = new storage::TsFileTableWriter(&file, schema);
125+
storage::Tablet tablet(
126+
table_name, {"id1", "s1"}, {common::STRING, common::INT64},
127+
{common::ColumnCategory::TAG, common::ColumnCategory::FIELD}, 10);
128+
129+
const char* tags[] = {"dev_a", "dev_b", "dev_b", "dev_c"};
130+
for (int row = 0; row < 4; ++row) {
131+
tablet.add_timestamp(row, static_cast<int64_t>(row));
132+
tablet.add_value(row, "id1", tags[row]);
133+
tablet.add_value(row, "s1", static_cast<int64_t>((row + 1) * 10));
134+
}
135+
136+
writer->write_table(tablet);
137+
writer->flush();
138+
writer->close();
139+
140+
delete writer;
141+
delete schema;
142+
return out_path;
143+
}
144+
102145
} // namespace tsfile_cli_test
103146

104147
#endif // TSFILE_CLI_TEST_UTIL_H

cpp/test/tools/command_e2e_test.cc

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ struct Fixture {
3434
~Fixture() { std::remove(path.c_str()); }
3535
};
3636

37+
struct TagFilterFixture {
38+
std::string path = tsfile_cli_test::write_tag_filter_fixture();
39+
~TagFilterFixture() { std::remove(path.c_str()); }
40+
};
41+
3742
size_t count_lines(const std::string& s) {
3843
size_t n = 0;
3944
for (char c : s) {
@@ -134,6 +139,28 @@ TEST(CliE2E, CatReturnsAllRows) {
134139
EXPECT_NE(out.str().find("time\ts1\n"), std::string::npos);
135140
}
136141

142+
TEST(CliE2E, CatPushesDownOffsetAndLimit) {
143+
Fixture f;
144+
std::ostringstream out;
145+
std::ostringstream err;
146+
int code = tsfile_cli::run_cli(
147+
{"cat", "-m", "s1", "--offset", "2", "-n", "2", "-f", "tsv", f.path},
148+
out, err);
149+
EXPECT_EQ(code, 0);
150+
EXPECT_EQ(out.str(), "time\ts1\n2\t20\n3\t30\n");
151+
}
152+
153+
TEST(CliE2E, HeadPushesDownOffsetAndLimit) {
154+
Fixture f;
155+
std::ostringstream out;
156+
std::ostringstream err;
157+
int code = tsfile_cli::run_cli(
158+
{"head", "-m", "s1", "--offset", "1", "-n", "3", "-f", "tsv", f.path},
159+
out, err);
160+
EXPECT_EQ(code, 0);
161+
EXPECT_EQ(out.str(), "time\ts1\n1\t10\n2\t20\n3\t30\n");
162+
}
163+
137164
TEST(CliE2E, CatWithTimeRange) {
138165
Fixture f;
139166
std::ostringstream out;
@@ -145,6 +172,65 @@ TEST(CliE2E, CatWithTimeRange) {
145172
EXPECT_EQ(out.str(), "time\ts1\n2\t20\n3\t30\n");
146173
}
147174

175+
TEST(CliE2E, CatAppliesOffsetAfterTimeRange) {
176+
Fixture f;
177+
std::ostringstream out;
178+
std::ostringstream err;
179+
int code =
180+
tsfile_cli::run_cli({"cat", "-m", "s1", "--start", "1", "--end", "4",
181+
"--offset", "1", "-n", "2", "-f", "tsv", f.path},
182+
out, err);
183+
EXPECT_EQ(code, 0);
184+
EXPECT_EQ(out.str(), "time\ts1\n2\t20\n3\t30\n");
185+
}
186+
187+
TEST(CliE2E, CatFiltersRowsByTagEq) {
188+
TagFilterFixture f;
189+
std::ostringstream out;
190+
std::ostringstream err;
191+
int code = tsfile_cli::run_cli({"cat", "-m", "s1", "--tag-filter", "id1",
192+
"eq", "dev_b", "-f", "tsv", f.path},
193+
out, err);
194+
EXPECT_EQ(code, 0) << err.str();
195+
EXPECT_EQ(out.str(), "time\ts1\n1\t20\n2\t30\n");
196+
}
197+
198+
TEST(CliE2E, HeadFiltersRowsByTagBetween) {
199+
TagFilterFixture f;
200+
std::ostringstream out;
201+
std::ostringstream err;
202+
int code =
203+
tsfile_cli::run_cli({"head", "-m", "s1", "--tag-between", "id1",
204+
"dev_b", "dev_c", "-n", "10", "-f", "tsv", f.path},
205+
out, err);
206+
EXPECT_EQ(code, 0) << err.str();
207+
EXPECT_EQ(out.str(), "time\ts1\n1\t20\n2\t30\n3\t40\n");
208+
}
209+
210+
TEST(CliE2E, SampleFiltersRowsByTagEq) {
211+
TagFilterFixture f;
212+
std::ostringstream out;
213+
std::ostringstream err;
214+
int code = tsfile_cli::run_cli(
215+
{"sample", "-m", "s1", "--tag-filter", "id1", "eq", "dev_b", "-n", "10",
216+
"--seed", "1", "-f", "tsv", f.path},
217+
out, err);
218+
EXPECT_EQ(code, 0) << err.str();
219+
EXPECT_EQ(out.str(), "time\ts1\n1\t20\n2\t30\n");
220+
}
221+
222+
TEST(CliE2E, TagFilterRejectsFieldColumn) {
223+
TagFilterFixture f;
224+
std::ostringstream out;
225+
std::ostringstream err;
226+
int code = tsfile_cli::run_cli({"cat", "-m", "s1", "--tag-filter", "s1",
227+
"eq", "20", "-f", "tsv", f.path},
228+
out, err);
229+
EXPECT_EQ(code, 1);
230+
EXPECT_NE(err.str().find("invalid tag filter column"), std::string::npos)
231+
<< err.str();
232+
}
233+
148234
TEST(CliE2E, CatJsonIsNdjson) {
149235
Fixture f;
150236
std::ostringstream out;
@@ -181,6 +267,39 @@ TEST(CliE2E, CountReportsSeriesCountsAndTotal) {
181267
EXPECT_NE(out.str().find("total\t\t"), std::string::npos);
182268
}
183269

270+
TEST(CliE2E, MetadataTableFilterIsCaseInsensitive) {
271+
Fixture f;
272+
273+
std::ostringstream schema_out;
274+
std::ostringstream schema_err;
275+
EXPECT_EQ(
276+
tsfile_cli::run_cli({"schema", "-t", "TABLE1", "-f", "tsv", f.path},
277+
schema_out, schema_err),
278+
0);
279+
EXPECT_NE(schema_out.str().find("table1\ts1\tINT64"), std::string::npos)
280+
<< schema_out.str();
281+
282+
std::ostringstream count_out;
283+
std::ostringstream count_err;
284+
EXPECT_EQ(
285+
tsfile_cli::run_cli({"count", "-t", "TABLE1", "-f", "tsv", f.path},
286+
count_out, count_err),
287+
0);
288+
EXPECT_NE(count_out.str().find("table1.id1_field_1.id2_field_2\ts1\t5"),
289+
std::string::npos)
290+
<< count_out.str();
291+
292+
std::ostringstream stats_out;
293+
std::ostringstream stats_err;
294+
EXPECT_EQ(
295+
tsfile_cli::run_cli({"stats", "-t", "TABLE1", "-f", "tsv", f.path},
296+
stats_out, stats_err),
297+
0);
298+
EXPECT_NE(stats_out.str().find("table1.id1_field_1.id2_field_2\ts1\t5"),
299+
std::string::npos)
300+
<< stats_out.str();
301+
}
302+
184303
TEST(CliE2E, SampleIsReproducibleWithSeed) {
185304
Fixture f;
186305
std::ostringstream out1;

cpp/tools/README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Choose any one of the following.
4444
```bash
4545
bash build.sh -t=Debug # -> cpp/build/Debug/bin/tsfile-cli
4646
bash build.sh # Release (default) -> cpp/build/Release/bin/tsfile-cli
47+
bash build.sh install # Release build, then run make install
4748
```
4849

4950
**2. Maven (builds the whole C++ module).** From the repository root:
@@ -77,9 +78,10 @@ Verify the binary:
7778
```
7879
7980
The executable links the `tsfile` shared library built alongside it. To run it from
80-
anywhere, either run it in place by its full path, or use CMake's install step
81-
(`cmake --install .` / `make install`), which installs the binary to `<prefix>/bin` and
82-
`libtsfile` to `<prefix>/lib`.
81+
anywhere, either run it in place by its full path, or explicitly install it with
82+
`bash build.sh install`, `cmake --install .`, or `make install`. The install step places
83+
the binary under `<prefix>/bin` and `libtsfile` under `<prefix>/lib`. The build script
84+
does not install by default.
8385

8486
## Usage
8587

@@ -117,6 +119,7 @@ Shared options:
117119
| `-n, --limit N` / `--offset N` | Max rows / rows to skip (`head`, `cat`; `--offset` not valid for `sample`) |
118120
| `--start <ms>` / `--end <ms>` | Inclusive epoch-millisecond time range (`head`, `cat`, `sample`) |
119121
| `--seed N` | Reproducible sampling seed (`sample` only) |
122+
| `--tag-filter C OP V` / `--tag-between C L U` / `--tag-not-between C L U` | Table TAG predicate for `head`, `cat`, `sample`; `OP` is `eq`, `neq`, `lt`, `lteq`, `gt`, `gteq`, `regexp`, or `not-regexp` |
120123
| `--no-header` | Omit the header row |
121124
| `--model tree\|table` | Force the model (otherwise auto-detected) |
122125

@@ -130,6 +133,7 @@ BIN=cpp/build/Debug/bin/tsfile-cli
130133
$BIN ls -f tsv data.tsfile # list tables / devices
131134
$BIN meta data.tsfile # quick file overview
132135
$BIN count -t table1 -f tsv data.tsfile # row counts, no page scan
136+
$BIN cat -t table1 --tag-filter device eq dev_1 -m temp -f tsv data.tsfile
133137
$BIN cat -m temp,humidity --start 1700000000000 -f csv data.tsfile | head
134138
$BIN sample -m temp -n 20 --seed 42 -f json data.tsfile | jq .
135139
```

0 commit comments

Comments
 (0)