Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions cpp/src/reader/aligned_chunk_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1020,9 +1020,10 @@ int AlignedChunkReader::decode_tv_buf_into_tsblock_by_datatype(
ret = E_NOT_SUPPORT;
ASSERT(false);
}
if (ret_tsblock->get_row_count() == 0 && ret == E_OK) {
ret = E_NO_MORE_DATA;
}
// An empty result only means every row of this page was filtered out; the
// chunk may still hold further pages. Reporting E_NO_MORE_DATA here made
// the series iterator advance past the rest of the chunk (or end the
// series when no successor chunk existed).
return ret;
}

Expand Down
12 changes: 12 additions & 0 deletions cpp/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,15 @@ endif ()
if (${COV_ENABLED})
message("Enable code cov...")
add_compile_options(-fprofile-arcs -ftest-coverage)
# This executable is instrumented too, so it needs the coverage runtime on
# its own link line. libtsfile links it PRIVATE, which does not propagate
# to consumers, and the linker does not search a shared library's own
# dependencies for these symbols.
if (APPLE)
set(TSFILE_TEST_COV_LINK_LIB --coverage)
else ()
set(TSFILE_TEST_COV_LINK_LIB gcov)
endif ()
endif ()

add_executable(TsFile_Test ${TEST_SRCS})
Expand Down Expand Up @@ -333,6 +342,9 @@ endif ()
if (BUILD_TOOLS)
target_link_libraries(TsFile_Test tsfile_cli_obj)
endif ()
if (${COV_ENABLED})
target_link_libraries(TsFile_Test ${TSFILE_TEST_COV_LINK_LIB})
endif ()

set_target_properties(TsFile_Test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${LIB_TSFILE_SDK_DIR})

Expand Down
109 changes: 109 additions & 0 deletions cpp/test/reader/table_view/tsfile_reader_table_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,115 @@ TEST_P(TsFileTableReaderTimeFilterTest, QueryContinuesAfterEmptyPageInChunk) {
}
}

// Regression for #1038: a page whose rows are all rejected by the time filter
// must not end the series. Requesting the never-written measurement "ghost"
// (and omitting "v" on one device) forces the per-column aligned read path,
// where a decode that produced no rows used to be reported as
// E_NO_MORE_DATA, so the rest of the chunk — including the matching final
// page — was skipped.
TEST_P(TsFileTableReaderTimeFilterTest,
TimeInContinuesAfterFullyFilteredAlignedPage) {
g_config_value_.page_writer_max_point_num_ = 5;
g_config_value_.tsblock_max_memory_ = 4096;

std::vector<ColumnSchema> columns;
columns.emplace_back("id", STRING, UNCOMPRESSED, PLAIN,
ColumnCategory::TAG);
for (const char* name : {"v", "w", "ghost"}) {
columns.emplace_back(name, INT64, UNCOMPRESSED, PLAIN,
ColumnCategory::FIELD);
}
TableSchema table_schema("t", columns);
TsFileTableWriter writer(&write_file_, &table_schema);
for (int chunk = 0; chunk < 3; ++chunk) {
for (int device = 0; device < 3; ++device) {
const bool has_v = device != 1;
std::vector<std::string> names{"id", "w"};
std::vector<TSDataType> types{STRING, INT64};
std::vector<ColumnCategory> categories{ColumnCategory::TAG,
ColumnCategory::FIELD};
if (has_v) {
names.emplace_back("v");
types.push_back(INT64);
categories.push_back(ColumnCategory::FIELD);
}
Tablet tablet("t", names, types, categories, 7);
for (int row = 0; row < 7; ++row) {
const int64_t time = chunk * 7 + row;
ASSERT_EQ(tablet.add_timestamp(row, time), common::E_OK);
const std::string id = "d" + std::to_string(device);
ASSERT_EQ(tablet.add_value(row, "id", id.c_str()),
common::E_OK);
if (time % 3 != 0) {
ASSERT_EQ(tablet.add_value(row, "w",
int64_t(device * 1000 + time)),
common::E_OK);
}
if (has_v && time % 4 != 0) {
ASSERT_EQ(tablet.add_value(row, "v",
int64_t(device * 100 + time)),
common::E_OK);
}
}
ASSERT_EQ(writer.write_table(tablet), common::E_OK);
// Each flush ends a chunk; the third one has no successor chunk.
ASSERT_EQ(writer.flush(), common::E_OK);
}
}
ASSERT_EQ(writer.close(), common::E_OK);

LocalRandomAccessReadFile read_file;
ASSERT_EQ(read_file.open(file_name_), common::E_OK);
// A 4096-byte budget holds the decoder's 129-row batch for these blocks.
TableQueryExecutor executor(&read_file);
const std::vector<std::string> query_columns{"v", "ghost", "w", "v"};
auto collect_times = [&](Filter* time_filter, std::vector<int64_t>& times) {
ResultSet* result = nullptr;
ASSERT_EQ(executor.query("t", query_columns, time_filter, nullptr,
nullptr, 0, -1, result),
common::E_OK);
bool has_next = false;
int ret = common::E_OK;
while ((ret = result->next(has_next)) == common::E_OK && has_next) {
times.push_back(result->get_value<int64_t>(1));
}
EXPECT_EQ(ret, common::E_OK);
executor.destroy_query_data_set(result);
};

const std::vector<std::vector<int64_t>> selected_time_lists{
// Fully filtered pages in earlier chunks (with successors) and in the
// final chunk followed by the matching page [19, 20].
{0, 2, 3, 6, 7, 10, 13, 19, 20},
// Only the matching page of the final chunk is selected.
{19, 20},
// Matching rows in the first chunk only; every later page is empty.
{0}};
for (const auto& selected_times : selected_time_lists) {
SCOPED_TRACE(::testing::Message()
<< "parallel=" << GetParam() << ", selected_times="
<< ::testing::PrintToString(selected_times));
std::vector<int64_t> times;
collect_times(new TimeIn(selected_times, false), times);
std::vector<int64_t> expected;
for (int device = 0; device < 3; ++device) {
expected.insert(expected.end(), selected_times.begin(),
selected_times.end());
}
EXPECT_EQ(times, expected);
}

// All-matching control: every row of every device is returned.
std::vector<int64_t> times;
collect_times(nullptr, times);
EXPECT_EQ(times.size(), 63u);

// No-matching control: the series is empty and still terminates.
times.clear();
collect_times(new TimeIn({100}, false), times);
EXPECT_TRUE(times.empty());
}

INSTANTIATE_TEST_SUITE_P(SerialAndParallel, TsFileTableReaderTimeFilterTest,
::testing::Bool());

Expand Down
Loading