From 9547a34534ea54eaf4ae9b93a260522ab393be96 Mon Sep 17 00:00:00 2001 From: Xinyao Zhang <43081360+zhangxinyao88@users.noreply.github.com> Date: Thu, 22 Oct 2026 08:10:40 -0400 Subject: [PATCH 1/3] [C++] Preserve TableSchema state in moves Default the move constructor to retain the column lookup index, virtual-table flag and pending tree depth along with the other members. Cover writer registration and readback after a move, duplicate-column rejection, virtual lookups and tree-level finalization. --- cpp/src/common/schema.h | 6 +- cpp/test/common/schema_test.cc | 55 +++++++++++++++++++ .../table_view/tsfile_writer_table_test.cc | 45 +++++++++++++++ 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/cpp/src/common/schema.h b/cpp/src/common/schema.h index ed59b27b1e..8e64ccbb67 100644 --- a/cpp/src/common/schema.h +++ b/cpp/src/common/schema.h @@ -254,11 +254,7 @@ class TableSchema { } } - TableSchema(TableSchema&& other) noexcept - : table_name_(std::move(other.table_name_)), - column_schemas_(std::move(other.column_schemas_)), - column_categories_(std::move(other.column_categories_)), - updatable_(other.updatable_) {} + TableSchema(TableSchema&& other) noexcept = default; TableSchema(const TableSchema& other) noexcept : table_name_(other.table_name_), diff --git a/cpp/test/common/schema_test.cc b/cpp/test/common/schema_test.cc index 1c908ab51f..e3768aab4b 100644 --- a/cpp/test/common/schema_test.cc +++ b/cpp/test/common/schema_test.cc @@ -71,6 +71,61 @@ TEST(MeasurementSchemaGroupTest, DefaultConstructor) { EXPECT_FALSE(group.is_aligned_); } +TEST(TableSchemaTest, MovePreservesColumnIndex) { + TableSchema original( + "Table", + {new MeasurementSchema("Value", common::INT64, common::PLAIN, + common::UNCOMPRESSED)}, + {common::ColumnCategory::FIELD}); + TableSchema moved(std::move(original)); + + EXPECT_EQ(moved.get_table_name(), "table"); + ASSERT_EQ(moved.get_columns_num(), 1); + EXPECT_EQ(moved.get_column_pos_index_num(), 1); + EXPECT_EQ(moved.find_column_index("VALUE"), 0); + EXPECT_EQ(moved.get_data_types(), + std::vector({common::INT64})); + EXPECT_EQ( + moved.get_column_categories(), + std::vector({common::ColumnCategory::FIELD})); +} + +TEST(TableSchemaTest, MovePreservesVirtualTableCaseSensitivity) { + TableSchema original( + "Tree", + {common::ColumnSchema("Device", common::STRING, common::UNCOMPRESSED, + common::PLAIN, common::ColumnCategory::TAG)}, + true); + TableSchema moved(std::move(original)); + + EXPECT_TRUE(moved.is_virtual_table()); + EXPECT_EQ(moved.get_table_name(), "Tree"); + EXPECT_EQ(moved.find_column_index("Device"), 0); + EXPECT_EQ(moved.find_column_index("device"), -1); + EXPECT_EQ(moved.find_id_column_order("Device"), 0); + EXPECT_EQ(moved.find_id_column_order("device"), -1); +} + +TEST(TableSchemaTest, MovePreservesPendingTreeLevels) { + common::PageArena arena; + ChunkGroupMeta metadata(&arena); + auto device = std::make_shared( + std::vector{"root", "device", "sensor"}); + ASSERT_EQ(device->segment_num(), 3); + ASSERT_EQ(metadata.init(device), common::E_OK); + TableSchema original; + original.update(&metadata); + TableSchema moved(std::move(original)); + moved.finalize_column_schema(); + + EXPECT_EQ(moved.get_measurement_names(), + std::vector({"__level1", "__level2"})); + EXPECT_EQ( + moved.get_column_categories(), + std::vector(2, common::ColumnCategory::TAG)); + EXPECT_EQ(moved.find_id_column_order("__level2"), 1); +} + #if DEBUG_SE TEST(TableSchemaTest, BasicTest) { using namespace storage; diff --git a/cpp/test/writer/table_view/tsfile_writer_table_test.cc b/cpp/test/writer/table_view/tsfile_writer_table_test.cc index 9e716558f5..eea31bda14 100644 --- a/cpp/test/writer/table_view/tsfile_writer_table_test.cc +++ b/cpp/test/writer/table_view/tsfile_writer_table_test.cc @@ -133,6 +133,51 @@ class TsFileWriterTableTest : public ::testing::Test { } }; +TEST_F(TsFileWriterTableTest, RegisterMovedSchemaAndReadWrittenValue) { + TableSchema original( + "moved", {new MeasurementSchema("value", INT64, PLAIN, UNCOMPRESSED)}, + {ColumnCategory::FIELD}); + auto schema = std::make_shared(std::move(original)); + TsFileWriter writer; + ASSERT_EQ(writer.init(&write_file_), E_OK); + ASSERT_EQ(writer.register_table(schema), E_OK); + + Tablet tablet(schema->get_measurement_names(), schema->get_data_types(), 1); + tablet.set_table_name("moved"); + ASSERT_EQ(tablet.add_timestamp(0, 17), E_OK); + ASSERT_EQ(tablet.add_value(0, "value", int64_t(42)), E_OK); + ASSERT_EQ(writer.write_table(tablet), E_OK); + ASSERT_EQ(writer.close(), E_OK); + + TsFileReader reader; + ASSERT_EQ(reader.open(file_name_), E_OK); + ResultSet* result = nullptr; + ASSERT_EQ(reader.query("moved", {"value"}, 0, INT64_MAX, result), E_OK); + ASSERT_NE(result, nullptr); + bool has_next = false; + ASSERT_EQ(result->next(has_next), E_OK); + ASSERT_TRUE(has_next); + EXPECT_EQ(result->get_value("time"), 17); + EXPECT_EQ(result->get_value("value"), 42); + ASSERT_EQ(result->next(has_next), E_OK); + EXPECT_FALSE(has_next); + reader.destroy_query_data_set(result); + ASSERT_EQ(reader.close(), E_OK); +} + +TEST_F(TsFileWriterTableTest, MovedSchemaStillRejectsDuplicateColumns) { + TableSchema original( + "duplicates", + {new MeasurementSchema("Value", INT64, PLAIN, UNCOMPRESSED), + new MeasurementSchema("value", INT64, PLAIN, UNCOMPRESSED)}, + {ColumnCategory::FIELD, ColumnCategory::FIELD}); + auto schema = std::make_shared(std::move(original)); + TsFileWriter writer; + ASSERT_EQ(writer.init(&write_file_), E_OK); + EXPECT_EQ(writer.register_table(schema), E_INVALID_ARG); + EXPECT_EQ(writer.close(), E_OK); +} + TEST_F(TsFileWriterTableTest, WriteTableTest) { auto table_schema = gen_table_schema(0); auto tsfile_table_writer_ = From 70fcb8b621306724e45b2270b032e4a73b9ab819 Mon Sep 17 00:00:00 2001 From: Xinyao Zhang <43081360+zhangxinyao88@users.noreply.github.com> Date: Fri, 23 Oct 2026 08:11:22 -0400 Subject: [PATCH 2/3] [C++] Move schema members explicitly on v141 Use a complete initializer list for the supported legacy MSVC toolset instead of a defaulted noexcept move constructor. Preserve the index, virtual-table flag and pending tree depth along with the other members. Check measurement ownership in the move regression so that a deep-copy fallback cannot satisfy the column-index assertion. --- cpp/src/common/schema.h | 10 +++++++++- cpp/test/common/schema_test.cc | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cpp/src/common/schema.h b/cpp/src/common/schema.h index 8e64ccbb67..feb2eb7990 100644 --- a/cpp/src/common/schema.h +++ b/cpp/src/common/schema.h @@ -254,7 +254,15 @@ class TableSchema { } } - TableSchema(TableSchema&& other) noexcept = default; + // Keep this explicit for the supported MSVC v141 toolset. + TableSchema(TableSchema&& other) noexcept + : table_name_(std::move(other.table_name_)), + column_schemas_(std::move(other.column_schemas_)), + column_categories_(std::move(other.column_categories_)), + column_pos_index_(std::move(other.column_pos_index_)), + is_virtual_table_(other.is_virtual_table_), + max_level_(other.max_level_), + updatable_(other.updatable_) {} TableSchema(const TableSchema& other) noexcept : table_name_(other.table_name_), diff --git a/cpp/test/common/schema_test.cc b/cpp/test/common/schema_test.cc index e3768aab4b..4308bc4a9c 100644 --- a/cpp/test/common/schema_test.cc +++ b/cpp/test/common/schema_test.cc @@ -77,11 +77,13 @@ TEST(TableSchemaTest, MovePreservesColumnIndex) { {new MeasurementSchema("Value", common::INT64, common::PLAIN, common::UNCOMPRESSED)}, {common::ColumnCategory::FIELD}); + auto measurement = original.get_measurement_schemas().front(); TableSchema moved(std::move(original)); EXPECT_EQ(moved.get_table_name(), "table"); ASSERT_EQ(moved.get_columns_num(), 1); EXPECT_EQ(moved.get_column_pos_index_num(), 1); + EXPECT_EQ(moved.get_measurement_schemas().front(), measurement); EXPECT_EQ(moved.find_column_index("VALUE"), 0); EXPECT_EQ(moved.get_data_types(), std::vector({common::INT64})); From 6851f39aa4109a26c4fe2e09ed9c320737271f9c Mon Sep 17 00:00:00 2001 From: Xinyao Zhang <43081360+zhangxinyao88@users.noreply.github.com> Date: Sun, 25 Oct 2026 09:56:39 -0400 Subject: [PATCH 3/3] Provision pinned clang-format for Linux CI jobs Install clang-format 17.0.6 in a runner-local virtual environment rather than assuming /usr/bin/clang-format-17 exists. Keep the Python 3.9 Linux job on Ubuntu 22.04, where setup-python provides its binary, while retaining all nine Python matrix combinations. Generated-by: OpenAI Codex --- .github/workflows/unit-test-cpp.yml | 14 ++++++++++---- .github/workflows/unit-test-python.yml | 13 +++++++++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/unit-test-cpp.yml b/.github/workflows/unit-test-cpp.yml index 7a0605c8bf..3e46d69208 100644 --- a/.github/workflows/unit-test-cpp.yml +++ b/.github/workflows/unit-test-cpp.yml @@ -80,6 +80,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v7 + - name: Set up Python for clang-format + if: runner.os == 'Linux' + uses: actions/setup-python@v7 + with: + python-version: "3.11" + - name: Set up JDK 17 uses: actions/setup-java@v6.0.1 with: @@ -121,10 +127,10 @@ jobs: elif command -v yum >/dev/null 2>&1; then sudo yum install -y libuuid-devel fi - sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-17 100 - sudo update-alternatives --set clang-format /usr/bin/clang-format-17 - sudo apt-get update - sudo apt-get install -y uuid-dev + python -m venv "$RUNNER_TEMP/clang-format" + "$RUNNER_TEMP/clang-format/bin/python" -m pip install --only-binary=:all: clang-format==17.0.6 + "$RUNNER_TEMP/clang-format/bin/clang-format" --version + echo "$RUNNER_TEMP/clang-format/bin" >> "$GITHUB_PATH" elif [[ "$RUNNER_OS" == "Windows" ]]; then choco install llvm --version 17.0.6 --force else diff --git a/.github/workflows/unit-test-python.yml b/.github/workflows/unit-test-python.yml index b01c093414..8547740342 100644 --- a/.github/workflows/unit-test-python.yml +++ b/.github/workflows/unit-test-python.yml @@ -45,6 +45,13 @@ jobs: matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] python-version: ["3.9", "3.11", "3.14"] + exclude: + - os: ubuntu-latest + python-version: "3.9" + include: + # Python 3.9 has no setup-python build for Ubuntu 26.04. + - os: ubuntu-22.04 + python-version: "3.9" runs-on: ${{ matrix.os }} steps: @@ -92,8 +99,10 @@ jobs: shell: bash run: | if [[ "$RUNNER_OS" == "Linux" ]]; then - sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-17 100 - sudo update-alternatives --set clang-format /usr/bin/clang-format-17 + python -m venv "$RUNNER_TEMP/clang-format" + "$RUNNER_TEMP/clang-format/bin/python" -m pip install --only-binary=:all: clang-format==17.0.6 + "$RUNNER_TEMP/clang-format/bin/clang-format" --version + echo "$RUNNER_TEMP/clang-format/bin" >> "$GITHUB_PATH" sudo apt-get update sudo apt-get install -y uuid-dev elif [[ "$RUNNER_OS" == "Windows" ]]; then