Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f9bdb02
docs: add design spec for TsFile Unix-philosophy C++ CLI
SpriCoder Jun 1, 2026
f9d2d84
docs: add implementation plan for TsFile Unix CLI; refine spec
SpriCoder Jun 1, 2026
91baa30
Update TsFile CLI redesign spec
SpriCoder Jun 2, 2026
758d7e5
Add TsFile CLI redesign implementation plan
SpriCoder Jun 2, 2026
a392a56
Update tsfile CLI command surface
SpriCoder Jun 2, 2026
45b6aa3
Route ReadFile::open errors to stderr
SpriCoder Jun 2, 2026
9355288
Add tsfile CLI commands, formatters, build, and tests
SpriCoder Jun 2, 2026
59d6e4b
Consolidate TsFile CLI spec and plan
SpriCoder Jun 2, 2026
e3b4ce5
Extend tsfile stats with value summaries and shared stat helpers
SpriCoder Jun 2, 2026
8810f70
Add tsfile meta command
SpriCoder Jun 2, 2026
9e6ec59
Add tsfile count command
SpriCoder Jun 2, 2026
b3292b4
Add deterministic tsfile sample command
SpriCoder Jun 2, 2026
fdec8ee
Format tsfile CLI sources with clang-format
SpriCoder Jun 2, 2026
cd4efe2
Document CMake 4.x/ANTLR4 build constraint in CLI plan
SpriCoder Jun 2, 2026
e9738aa
Simplify sampled writer: trust caller-normalized limit
SpriCoder Jun 2, 2026
f16d969
Add tsfile-cli usage skill for inspecting .tsfile from the CLI
SpriCoder Jun 2, 2026
6bb1d9c
Rename CLI binary from tsfile to tsfile-cli
SpriCoder Jun 3, 2026
751daa9
Add tsfile CLI write (CSV/TSV import) design spec
SpriCoder Jun 3, 2026
0a698c6
Refine write spec: silent-by-default (-v summary) and opt-in --header…
SpriCoder Jun 3, 2026
9baaa22
Add tsfile CLI write implementation plan
SpriCoder Jun 3, 2026
dcef828
Add tsfile CLI input_format parsing layer
SpriCoder Jun 3, 2026
a151e65
Add tsfile CLI write argument parsing
SpriCoder Jun 3, 2026
591f3bb
Add tsfile CLI write command (CSV/TSV import)
SpriCoder Jun 3, 2026
9116ac4
Treat '-' as stdin positional, not an unknown flag
SpriCoder Jun 3, 2026
a575292
Format tsfile CLI write sources with clang-format
SpriCoder Jun 3, 2026
f41ccab
Update tsfile-cli skill to document the write command
SpriCoder Jun 3, 2026
9cee6c5
Condense tsfile-cli skill with formal notation
SpriCoder Jun 3, 2026
3ce68cc
Ignore .codegraph and cpp/root test-run artifacts
SpriCoder Jun 3, 2026
49b8cb3
Add QA learning log for tsfile CLI work
SpriCoder Jun 3, 2026
80ea038
Move tsfile-cli skill to cpp/tools/skills; stop tracking .claude
SpriCoder Jun 3, 2026
604dcc7
Stop tracking docs/superpowers and QA_Log.md (keep local, out of PR)
SpriCoder Jun 3, 2026
e3c0b5a
Add Apache license header to tsfile-cli skill doc (fix RAT check)
SpriCoder Jun 3, 2026
e47b151
Fix CLI test race: unique per-process temp filenames for parallel ctest
SpriCoder Jun 3, 2026
43f1a73
Simplify CLI test fixture: drop unused path param and atomic counter
SpriCoder Jun 3, 2026
4e1ffac
Add tsfile-cli tool README
SpriCoder Jun 3, 2026
d41cede
Expand tsfile-cli README: build-from-source and skill install
SpriCoder Jun 4, 2026
7bb6b62
Address PR #829 review feedback for tsfile-cli
SpriCoder Jun 5, 2026
0375385
Merge branch 'develop' into feat/tsfile-cli
SpriCoder Jun 5, 2026
57392f0
Harden tsfile-cli writes, errors, and input validation
SpriCoder Jun 5, 2026
7878aa2
Rename tsfile-cli stat_table.{h,cc} to statistics.{h,cc}
SpriCoder Jun 5, 2026
5509515
Add tests for tsfile-cli review hardening
SpriCoder Jun 5, 2026
a480e0f
Simplify scoped-command check in tsfile-cli flag validation
SpriCoder Jun 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,19 @@ build/*
cpp/third_party/zlib-1.3.1/treebuild.xml
cpp/third_party/zlib-1.3.1/zlib-1.3.1/treebuild.xml

# Claude Code
.claude/settings.local.json
.claude/todos/
.claude/worktrees/
.claude/scheduled_tasks.json
# Claude Code (local AI tooling — not uploaded; skill lives in cpp/tools/skills)
.claude/

# CodeGraph local index
.codegraph/

# Test-run artifacts (temp .tsfile/.dat written to the working dir or repo root)
cpp/cwrapper_*.tsfile
cpp/tsfile_writer_*.tsfile
cpp/*.dat
/*.tsfile
/*.dat

# AI workflow artifacts (kept local, not uploaded)
docs/superpowers/
/QA_Log.md
7 changes: 6 additions & 1 deletion cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ endif ()
option(BUILD_TEST "Build tests" ON)
message("cmake using: BUILD_TEST=${BUILD_TEST}")

option(BUILD_TOOLS "Build the tsfile command-line tools" ON)
message("cmake using: BUILD_TOOLS=${BUILD_TOOLS}")

option(ENABLE_ANTLR4 "Enable ANTLR4 runtime" ON)
message("cmake using: ENABLE_ANTLR4=${ENABLE_ANTLR4}")

Expand Down Expand Up @@ -262,6 +265,9 @@ endif ()
add_subdirectory(third_party)

add_subdirectory(src)
if (BUILD_TOOLS)
add_subdirectory(tools)
endif ()
if (BUILD_TEST)
add_subdirectory(test)
if (TESTS_ENABLED)
Expand All @@ -272,4 +278,3 @@ else()
endif ()

add_subdirectory(examples)

8 changes: 4 additions & 4 deletions cpp/src/file/read_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

#include <fcntl.h>
#include <sys/stat.h>

#include <iostream>
#ifdef _WIN32
#include <io.h>
#include <windows.h>
Expand Down Expand Up @@ -49,10 +51,8 @@ int ReadFile::open(const std::string& file_path) {
file_path_ = file_path;
fd_ = ::open(file_path_.c_str(), O_RDONLY);
if (fd_ < 0) {
std::cout << "open file " << file_path << " error :" << fd_
<< std::endl;
std::cout << "open error" << errno << " " << strerror(errno)
<< std::endl;
std::cerr << "open file " << file_path << " error: " << strerror(errno)
<< " (errno " << errno << ")" << std::endl;
return E_FILE_OPEN_ERR;
}

Expand Down
49 changes: 48 additions & 1 deletion cpp/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,33 @@ if (${DOWNLOADED})
endif ()
add_subdirectory("${GTEST_SRC_ROOT}" "${CMAKE_BINARY_DIR}/googletest-build"
EXCLUDE_FROM_ALL)
# AppleClang searches /usr/local/include before CMake's generated -isystem
# paths, so a system-installed GTest can shadow the vendored headers. Force
# the vendored include dirs ahead for GTest's own sources here; the same is
# done for the consuming TsFile_Test target below (where header resolution
# actually matters for the test code).
foreach (GTEST_TARGET gtest gtest_main gmock gmock_main)
if (TARGET ${GTEST_TARGET})
target_include_directories(${GTEST_TARGET} BEFORE PRIVATE
${GTEST_SRC_ROOT}/googletest/include
${GTEST_SRC_ROOT}/googletest
${GTEST_SRC_ROOT}/googlemock/include
${GTEST_SRC_ROOT}/googlemock)
if (APPLE AND NOT MSVC)
target_compile_options(${GTEST_TARGET} BEFORE PRIVATE
-iquote${GTEST_SRC_ROOT}/googletest/include
-iquote${GTEST_SRC_ROOT}/googletest
-I${GTEST_SRC_ROOT}/googletest/include
-I${GTEST_SRC_ROOT}/googletest
-std=c++14)
endif ()
endif ()
endforeach ()
# Remember the vendored GTest header roots so they can be forced ahead of any
# system installation when compiling TsFile_Test itself.
set(VENDORED_GTEST_INCLUDE_DIRS
${GTEST_SRC_ROOT}/googletest/include
${GTEST_SRC_ROOT}/googlemock/include)
set(TESTS_ENABLED ON PARENT_SCOPE)
else ()
message(WARNING "Failed to download googletest from all provided URLs, setting TESTS_ENABLED to OFF")
Expand Down Expand Up @@ -186,6 +213,11 @@ if (ENABLE_ZLIB)
list(APPEND TEST_SRCS ${ZLIB_TEST_SRCS})
endif()

if (BUILD_TOOLS)
file(GLOB_RECURSE TOOLS_TEST_SRCS "tools/*_test.cc")
list(APPEND TEST_SRCS ${TOOLS_TEST_SRCS})
endif ()

if (${COV_ENABLED})
message("Enable code cov...")
add_compile_options(-fprofile-arcs -ftest-coverage)
Expand All @@ -197,12 +229,27 @@ if (ENABLE_ANTLR4)
endif()

add_executable(TsFile_Test ${TEST_SRCS})
# Force the vendored GTest headers ahead of any system installation so the test
# code reliably compiles against the vendored 1.12.1 headers.
if (VENDORED_GTEST_INCLUDE_DIRS)
target_include_directories(TsFile_Test BEFORE PRIVATE
${VENDORED_GTEST_INCLUDE_DIRS})
endif ()
if (BUILD_TOOLS)
target_include_directories(TsFile_Test PRIVATE ${CMAKE_SOURCE_DIR}/tools)
endif ()
if (APPLE AND NOT MSVC)
target_compile_options(TsFile_Test PRIVATE -std=c++14)
endif ()
target_link_libraries(
TsFile_Test
GTest::gtest_main
GTest::gmock
tsfile
)
if (BUILD_TOOLS)
target_link_libraries(TsFile_Test tsfile_cli_obj)
endif ()

set_target_properties(TsFile_Test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${LIB_TSFILE_SDK_DIR})

Expand Down Expand Up @@ -232,4 +279,4 @@ if(WIN32)
gtest_discover_tests(TsFile_Test DISCOVERY_MODE PRE_TEST DISCOVERY_TIMEOUT 120)
else()
gtest_discover_tests(TsFile_Test)
endif()
endif()
182 changes: 182 additions & 0 deletions cpp/test/tools/cli_args_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* 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 "cli/cli_args.h"

#include <gtest/gtest.h>

#include <sstream>

#include "cli/run_cli.h"

TEST(RunCliTest, VersionFlagPrintsVersionAndReturnsOk) {
std::ostringstream out;
std::ostringstream err;
int code = tsfile_cli::run_cli({"--version"}, out, err);
EXPECT_EQ(code, 0);
EXPECT_NE(out.str().find("tsfile"), std::string::npos);
EXPECT_TRUE(err.str().empty());
}

TEST(RunCliTest, NoArgsPrintsUsageToErrAndReturnsUsageError) {
std::ostringstream out;
std::ostringstream err;
int code = tsfile_cli::run_cli({}, out, err);
EXPECT_EQ(code, 1);
EXPECT_NE(err.str().find("Usage"), std::string::npos);
}

TEST(RunCliTest, UnknownCommandIsUsageError) {
std::ostringstream out;
std::ostringstream err;
int code = tsfile_cli::run_cli({"frobnicate", "x.tsfile"}, out, err);
EXPECT_EQ(code, 1);
EXPECT_NE(err.str().find("Unknown command"), std::string::npos);
}

TEST(RunCliTest, LeadingOptionBeforeCommandIsClearError) {
std::ostringstream out;
std::ostringstream err;
int code =
tsfile_cli::run_cli({"-f", "json", "meta", "data.tsfile"}, out, err);
EXPECT_EQ(code, 1);
EXPECT_NE(err.str().find("command must come before options"),
std::string::npos)
<< err.str();
}

TEST(ParseArgsTest, CommandAndFilePositional) {
auto p = tsfile_cli::parse_args({"ls", "data.tsfile"});
EXPECT_TRUE(p.error.empty());
EXPECT_EQ(p.command, "ls");
EXPECT_EQ(p.file, "data.tsfile");
}

TEST(ParseArgsTest, FormatFlagParsed) {
auto p = tsfile_cli::parse_args({"cat", "-f", "json", "data.tsfile"});
EXPECT_TRUE(p.error.empty());
EXPECT_EQ(p.format, tsfile_cli::ParsedArgs::Format::kJson);
}

TEST(ParseArgsTest, MeasurementsSplitOnComma) {
auto p = tsfile_cli::parse_args({"cat", "-m", "s1,s2,s3", "data.tsfile"});
ASSERT_EQ(p.measurements.size(), 3u);
EXPECT_EQ(p.measurements[1], "s2");
}

TEST(ParseArgsTest, LimitOffsetAndTimeRange) {
auto p =
tsfile_cli::parse_args({"head", "-n", "5", "--offset", "2", "--start",
"100", "--end", "200", "data.tsfile"});
EXPECT_EQ(p.limit, 5);
EXPECT_EQ(p.offset, 2);
EXPECT_TRUE(p.has_start);
EXPECT_EQ(p.start, 100);
EXPECT_TRUE(p.has_end);
EXPECT_EQ(p.end, 200);
}

TEST(ParseArgsTest, UnknownFlagIsError) {
auto p = tsfile_cli::parse_args({"ls", "--bogus", "data.tsfile"});
EXPECT_FALSE(p.error.empty());
}

TEST(ParseArgsTest, BadFormatValueIsError) {
auto p = tsfile_cli::parse_args({"cat", "-f", "yaml", "data.tsfile"});
EXPECT_FALSE(p.error.empty());
}

TEST(ParseArgsTest, MissingFileIsAllowedAtParseTime) {
auto p = tsfile_cli::parse_args({"ls"});
EXPECT_TRUE(p.error.empty());
EXPECT_EQ(p.command, "ls");
EXPECT_TRUE(p.file.empty());
}

TEST(ParseArgsTest, WriteFlagsParsed) {
auto p = tsfile_cli::parse_args({"write", "--table", "t1", "--columns",
"s1:INT64:field", "-o", "out.tsfile", "-v",
"--header-match", "in.csv"});
EXPECT_TRUE(p.error.empty());
EXPECT_EQ(p.command, "write");
EXPECT_EQ(p.table, "t1");
EXPECT_EQ(p.columns, "s1:INT64:field");
EXPECT_EQ(p.output, "out.tsfile");
EXPECT_TRUE(p.verbose);
EXPECT_TRUE(p.header_match);
EXPECT_EQ(p.file, "in.csv");
}

TEST(ParseArgsTest, OutputFlagNeedsValue) {
auto p = tsfile_cli::parse_args({"write", "-o"});
EXPECT_FALSE(p.error.empty());
}

TEST(ParseArgsTest, DashIsStdinPositional) {
auto p =
tsfile_cli::parse_args({"write", "--table", "t1", "--columns",
"s1:INT64:field", "-o", "out.tsfile", "-"});
EXPECT_TRUE(p.error.empty());
EXPECT_EQ(p.file, "-");
}

TEST(ParseArgsTest, SeedFlagParsed) {
auto p = tsfile_cli::parse_args(
{"sample", "-m", "s1", "-n", "3", "--seed", "42", "data.tsfile"});
EXPECT_TRUE(p.error.empty());
EXPECT_EQ(p.command, "sample");
EXPECT_EQ(p.limit, 3);
EXPECT_TRUE(p.has_seed);
EXPECT_EQ(p.seed, 42);
}

TEST(ParseArgsTest, BadSeedValueIsError) {
auto p = tsfile_cli::parse_args(
{"sample", "--seed", "not_a_number", "data.tsfile"});
EXPECT_FALSE(p.error.empty());
EXPECT_NE(p.error.find("Invalid --seed"), std::string::npos);
}

TEST(RunCliTest, SelectIsNoLongerKnownCommand) {
std::ostringstream out;
std::ostringstream err;
int code = tsfile_cli::run_cli({"select", "x.tsfile"}, out, err);
EXPECT_EQ(code, 1);
EXPECT_NE(err.str().find("Unknown command"), std::string::npos);
}

TEST(RunCliTest, SeedOnCatIsUsageError) {
std::ostringstream out;
std::ostringstream err;
int code =
tsfile_cli::run_cli({"cat", "--seed", "7", "x.tsfile"}, out, err);
EXPECT_EQ(code, 1);
EXPECT_NE(err.str().find("--seed is only valid for sample"),
std::string::npos);
}

TEST(RunCliTest, OffsetOnSampleIsUsageError) {
std::ostringstream out;
std::ostringstream err;
int code =
tsfile_cli::run_cli({"sample", "--offset", "2", "x.tsfile"}, out, err);
EXPECT_EQ(code, 1);
EXPECT_NE(err.str().find("--offset is not valid for sample"),
std::string::npos);
}
Loading
Loading