From b90972edb04c391cab409dd4245e221484d780d8 Mon Sep 17 00:00:00 2001 From: Hakaou Date: Sun, 18 Oct 2026 01:37:11 +0200 Subject: [PATCH] Make InsertData independent of block boundaries and order The expectation branched on which block was being read and indexed TEST_DATA with the running row number. When the server returns the eight rows in one block, that reads past TEST_DATA (UBSan: index 4 out of bounds, client_ut.cpp:625-627). The reads happened to return the expected values, which nothing guarantees. The select had no ORDER BY either. When the server keeps the two inserted blocks separate, it may return the second one first. The select now orders by id, and every received row is compared against both datasets sorted by id. --- ut/client_ut.cpp | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/ut/client_ut.cpp b/ut/client_ut.cpp index 79ee9ca8..fd4b2abf 100644 --- a/ut/client_ut.cpp +++ b/ut/client_ut.cpp @@ -16,6 +16,7 @@ #include +#include #include #include #include @@ -552,22 +553,20 @@ TEST_P(ClientCase, InsertData) { client_->Execute( "CREATE TEMPORARY TABLE IF NOT EXISTS test_clickhouse_cpp_insert (id UInt64, name String, f Bool)"); - const struct { + struct Row { uint64_t id; std::string name; bool f; - } TEST_DATA[] = { + }; + + const std::vector TEST_DATA = { { 1, "id", true }, { 3, "foo", false }, { 5, "bar", true }, { 7, "name", false }, }; - const struct { - uint64_t id; - std::string name; - bool f; - } TEST_DATA2[] = { + const std::vector TEST_DATA2 = { { 2, "holden", true }, { 4, "naomi", false }, { 6, "amos", true }, @@ -610,33 +609,28 @@ TEST_P(ClientCase, InsertData) { } /// Select values inserted in the previous steps. + std::vector expected_rows(TEST_DATA); + expected_rows.insert(expected_rows.end(), TEST_DATA2.begin(), TEST_DATA2.end()); + std::sort(expected_rows.begin(), expected_rows.end(), [](const Row& left, const Row& right) { return left.id < right.id; }); + size_t row = 0; - client_->Select("SELECT id, name, f FROM test_clickhouse_cpp_insert", [TEST_DATA, TEST_DATA2, &row](const Block& block) + client_->Select("SELECT id, name, f FROM test_clickhouse_cpp_insert ORDER BY id", [&expected_rows, &row](const Block& block) { if (block.GetRowCount() == 0) { return; } EXPECT_EQ("id", block.GetColumnName(0)); EXPECT_EQ("name", block.GetColumnName(1)); - size_t block_two_row_num = sizeof(TEST_DATA)/sizeof(TEST_DATA[0]); - if (row < block_two_row_num) { - for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { - EXPECT_EQ(TEST_DATA[row].id, (*block[0]->As())[c]); - EXPECT_EQ(TEST_DATA[row].name, (*block[1]->As())[c]); - EXPECT_EQ(MakeClientBoolValue(TEST_DATA[row].f), (*block[2]->As())[c]); - } - } else { - for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { - EXPECT_EQ(TEST_DATA2[row-block_two_row_num].id, (*block[0]->As())[c]); - EXPECT_EQ(TEST_DATA2[row-block_two_row_num].name, (*block[1]->As())[c]); - EXPECT_EQ(MakeClientBoolValue(TEST_DATA2[row-block_two_row_num].f), (*block[2]->As())[c]); - } + for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { + ASSERT_LT(row, expected_rows.size()); + EXPECT_EQ(expected_rows[row].id, (*block[0]->As())[c]); + EXPECT_EQ(expected_rows[row].name, (*block[1]->As())[c]); + EXPECT_EQ(MakeClientBoolValue(expected_rows[row].f), (*block[2]->As())[c]); } } ); - auto exp = sizeof(TEST_DATA)/sizeof(TEST_DATA[0]) + sizeof(TEST_DATA2)/sizeof(TEST_DATA2[0]); - EXPECT_EQ(exp, row); + EXPECT_EQ(expected_rows.size(), row); } TEST_P(ClientCase, BeginInsertDoesNotAllowCallbacks) {