blob: 5555ce3c52918af16c9c97441f7542baec91e626 [file]
/*
* 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 <gtest/gtest.h>
#include <chrono>
#include <random>
#include <vector>
#include "common/global.h"
#include "common/record.h"
#include "common/schema.h"
#include "common/tablet.h"
#include "file/tsfile_io_writer.h"
#include "file/write_file.h"
#include "reader/table_result_set.h"
#include "reader/tsfile_reader.h"
#include "writer/chunk_writer.h"
#include "writer/tsfile_table_writer.h"
using namespace storage;
using namespace common;
class TsFileTableReaderBatchTest : public ::testing::TestWithParam<bool> {
protected:
void SetUp() override {
libtsfile_init();
set_parallel_read_enabled(GetParam());
file_name_ = std::string("tsfile_reader_table_batch_test_") +
generate_random_string(10) + std::string(".tsfile");
remove(file_name_.c_str());
int flags = O_WRONLY | O_CREAT | O_TRUNC;
#ifdef _WIN32
flags |= O_BINARY;
#endif
mode_t mode = 0666;
write_file_.create(file_name_, flags, mode);
}
void TearDown() override {
remove(file_name_.c_str());
libtsfile_destroy();
}
std::string file_name_;
WriteFile write_file_;
public:
static std::string generate_random_string(int length) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 61);
const std::string chars =
"0123456789"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::string random_string;
for (int i = 0; i < length; ++i) {
random_string += chars[dis(gen)];
}
return random_string;
}
static TableSchema* gen_table_schema_no_tag() {
// Generate table schema with only FIELD columns (no TAG columns)
std::vector<MeasurementSchema*> measurement_schemas;
std::vector<ColumnCategory> column_categories;
int measurement_schema_num = 5; // 5 field columns
for (int i = 0; i < measurement_schema_num; i++) {
measurement_schemas.emplace_back(new MeasurementSchema(
"s" + std::to_string(i), TSDataType::INT64, TSEncoding::PLAIN,
CompressionType::UNCOMPRESSED));
column_categories.emplace_back(ColumnCategory::FIELD);
}
return new TableSchema("testTableNoTag", measurement_schemas,
column_categories);
}
static storage::Tablet gen_tablet_no_tag(TableSchema* table_schema,
int num_rows) {
// Generate tablet without tags (only field columns)
storage::Tablet tablet(table_schema->get_table_name(),
table_schema->get_measurement_names(),
table_schema->get_data_types(),
table_schema->get_column_categories(), num_rows);
for (int i = 0; i < num_rows; i++) {
tablet.add_timestamp(i, i);
auto column_schemas = table_schema->get_measurement_schemas();
for (const auto& column_schema : column_schemas) {
if (column_schema->data_type_ == TSDataType::INT64) {
tablet.add_value(i, column_schema->measurement_name_,
static_cast<int64_t>(i));
}
}
}
return tablet;
}
static TableSchema* gen_table_schema() {
std::vector<MeasurementSchema*> measurement_schemas;
std::vector<ColumnCategory> column_categories;
int id_schema_num = 2;
int measurement_schema_num = 3;
for (int i = 0; i < id_schema_num; i++) {
measurement_schemas.emplace_back(new MeasurementSchema(
"id" + std::to_string(i), TSDataType::STRING, TSEncoding::PLAIN,
CompressionType::UNCOMPRESSED));
column_categories.emplace_back(ColumnCategory::TAG);
}
for (int i = 0; i < measurement_schema_num; i++) {
measurement_schemas.emplace_back(new MeasurementSchema(
"s" + std::to_string(i), TSDataType::INT64, TSEncoding::PLAIN,
CompressionType::UNCOMPRESSED));
column_categories.emplace_back(ColumnCategory::FIELD);
}
return new TableSchema("testTable", measurement_schemas,
column_categories);
}
static storage::Tablet gen_tablet(TableSchema* table_schema, int offset,
int device_num,
int num_timestamp_per_device = 10) {
storage::Tablet tablet(table_schema->get_table_name(),
table_schema->get_measurement_names(),
table_schema->get_data_types(),
table_schema->get_column_categories(),
device_num * num_timestamp_per_device);
char* literal = new char[std::strlen("device_id") + 1];
std::strcpy(literal, "device_id");
String literal_str(literal, std::strlen("device_id"));
for (int i = 0; i < device_num; i++) {
for (int l = 0; l < num_timestamp_per_device; l++) {
int row_index = i * num_timestamp_per_device + l;
tablet.add_timestamp(row_index, row_index);
auto column_schemas = table_schema->get_measurement_schemas();
for (const auto& column_schema : column_schemas) {
switch (column_schema->data_type_) {
case TSDataType::INT64:
tablet.add_value(row_index,
column_schema->measurement_name_,
static_cast<int64_t>(i));
break;
case TSDataType::STRING:
tablet.add_value(row_index,
column_schema->measurement_name_,
literal_str);
break;
default:
break;
}
}
}
}
delete[] literal;
return tablet;
}
};
TEST_P(TsFileTableReaderBatchTest, BatchQueryWithSmallBatchSize) {
auto table_schema = gen_table_schema();
auto tsfile_table_writer_ =
std::make_shared<TsFileTableWriter>(&write_file_, table_schema);
const int device_num = 2;
const int points_per_device = 50;
auto tablet = gen_tablet(table_schema, 0, device_num, points_per_device);
ASSERT_EQ(tsfile_table_writer_->write_table(tablet), common::E_OK);
ASSERT_EQ(tsfile_table_writer_->flush(), common::E_OK);
ASSERT_EQ(tsfile_table_writer_->close(), common::E_OK);
storage::TsFileReader reader;
int ret = reader.open(file_name_);
ASSERT_EQ(ret, common::E_OK);
ResultSet* tmp_result_set = nullptr;
const int batch_size = 20;
ret = reader.query(table_schema->get_table_name(),
table_schema->get_measurement_names(), 0, 1000000000000,
tmp_result_set, batch_size);
ASSERT_EQ(ret, common::E_OK);
ASSERT_NE(tmp_result_set, nullptr);
auto* table_result_set = dynamic_cast<TableResultSet*>(tmp_result_set);
ASSERT_NE(table_result_set, nullptr);
int total_rows = 0;
int block_count = 0;
common::TsBlock* block = nullptr;
char* literal = new char[std::strlen("device_id") + 1];
std::strcpy(literal, "device_id");
String expected_string(literal, std::strlen("device_id"));
std::vector<int64_t> int64_sums(3, 0);
while ((ret = table_result_set->get_next_tsblock(block)) == common::E_OK) {
ASSERT_NE(block, nullptr);
block_count++;
uint32_t row_count = block->get_row_count();
total_rows += row_count;
ASSERT_EQ(row_count, batch_size);
common::RowIterator row_iterator(block);
while (row_iterator.has_next()) {
uint32_t len;
bool null;
int int64_col_idx = 0;
for (uint32_t col_idx = 1;
col_idx < row_iterator.get_column_count(); ++col_idx) {
const char* value = row_iterator.read(col_idx, &len, &null);
ASSERT_FALSE(null);
TSDataType data_type = row_iterator.get_data_type(col_idx);
if (data_type == TSDataType::INT64) {
int64_t int_val = *reinterpret_cast<const int64_t*>(value);
int64_sums[int64_col_idx] += int_val;
int64_col_idx++;
} else if (data_type == TSDataType::STRING) {
String str_value(value, len);
ASSERT_EQ(str_value.compare(expected_string), 0);
}
}
row_iterator.next();
}
}
EXPECT_EQ(total_rows, device_num * points_per_device);
EXPECT_GT(block_count, 1);
for (size_t i = 0; i < int64_sums.size(); i++) {
EXPECT_EQ(int64_sums[i], 50);
}
delete[] literal;
reader.destroy_query_data_set(table_result_set);
ASSERT_EQ(reader.close(), common::E_OK);
delete table_schema;
}
TEST_P(TsFileTableReaderBatchTest, BatchQueryWithLargeBatchSize) {
auto table_schema = gen_table_schema();
auto tsfile_table_writer_ =
std::make_shared<TsFileTableWriter>(&write_file_, table_schema);
const int device_num = 1;
const int points_per_device = 120;
auto tablet = gen_tablet(table_schema, 0, device_num, points_per_device);
ASSERT_EQ(tsfile_table_writer_->write_table(tablet), common::E_OK);
ASSERT_EQ(tsfile_table_writer_->flush(), common::E_OK);
ASSERT_EQ(tsfile_table_writer_->close(), common::E_OK);
storage::TsFileReader reader;
int ret = reader.open(file_name_);
ASSERT_EQ(ret, common::E_OK);
ResultSet* tmp_result_set = nullptr;
const int batch_size = 100;
ret = reader.query(table_schema->get_table_name(),
table_schema->get_measurement_names(), 0, 1000000000000,
tmp_result_set, batch_size);
ASSERT_EQ(ret, common::E_OK);
ASSERT_NE(tmp_result_set, nullptr);
auto* table_result_set = dynamic_cast<TableResultSet*>(tmp_result_set);
ASSERT_NE(table_result_set, nullptr);
int total_rows = 0;
int block_count = 0;
common::TsBlock* block = nullptr;
while (table_result_set->get_next_tsblock(block) == common::E_OK) {
ASSERT_NE(block, nullptr);
block_count++;
uint32_t row_count = block->get_row_count();
total_rows += row_count;
ASSERT_EQ(row_count, block_count == 1 ? batch_size : 20);
}
EXPECT_EQ(total_rows, device_num * points_per_device);
EXPECT_GE(block_count, 2);
reader.destroy_query_data_set(table_result_set);
ASSERT_EQ(reader.close(), common::E_OK);
delete table_schema;
}
TEST_P(TsFileTableReaderBatchTest, BatchQueryVerifyDataCorrectness) {
auto table_schema = gen_table_schema();
auto tsfile_table_writer_ =
std::make_shared<TsFileTableWriter>(&write_file_, table_schema);
const int device_num = 1;
const int points_per_device = 30;
auto tablet = gen_tablet(table_schema, 0, device_num, points_per_device);
ASSERT_EQ(tsfile_table_writer_->write_table(tablet), common::E_OK);
ASSERT_EQ(tsfile_table_writer_->flush(), common::E_OK);
ASSERT_EQ(tsfile_table_writer_->close(), common::E_OK);
storage::TsFileReader reader;
int ret = reader.open(file_name_);
ASSERT_EQ(ret, common::E_OK);
ResultSet* tmp_result_set = nullptr;
const int batch_size = 10;
ret = reader.query(table_schema->get_table_name(),
table_schema->get_measurement_names(), 0, 1000000000000,
tmp_result_set, batch_size);
ASSERT_EQ(ret, common::E_OK);
ASSERT_NE(tmp_result_set, nullptr);
auto* table_result_set = dynamic_cast<TableResultSet*>(tmp_result_set);
ASSERT_NE(table_result_set, nullptr);
int expected_timestamp = 0;
common::TsBlock* block = nullptr;
while (table_result_set->get_next_tsblock(block) == common::E_OK) {
ASSERT_NE(block, nullptr);
common::RowIterator row_iterator(block);
while (row_iterator.has_next()) {
uint32_t len;
bool null;
int64_t timestamp = *reinterpret_cast<const int64_t*>(
row_iterator.read(0, &len, &null));
ASSERT_FALSE(null);
EXPECT_EQ(timestamp, expected_timestamp);
for (uint32_t col_idx = 2;
col_idx < row_iterator.get_column_count(); ++col_idx) {
const char* value = row_iterator.read(col_idx, &len, &null);
if (!null && row_iterator.get_data_type(col_idx) == INT64) {
int64_t int_val = *reinterpret_cast<const int64_t*>(value);
EXPECT_EQ(int_val, 0);
}
}
row_iterator.next();
expected_timestamp++;
}
}
EXPECT_EQ(expected_timestamp, device_num * points_per_device);
reader.destroy_query_data_set(table_result_set);
ASSERT_EQ(reader.close(), common::E_OK);
delete table_schema;
}
TEST_P(TsFileTableReaderBatchTest, PerformanceComparisonSinglePointVsBatch) {
// Create table schema without tags (only fields)
auto table_schema = gen_table_schema_no_tag();
auto tsfile_table_writer_ =
std::make_shared<TsFileTableWriter>(&write_file_, table_schema);
// Write a large amount of data
const int total_rows = 1000000;
auto tablet = gen_tablet_no_tag(table_schema, total_rows);
ASSERT_EQ(tsfile_table_writer_->write_table(tablet), common::E_OK);
ASSERT_EQ(tsfile_table_writer_->flush(), common::E_OK);
ASSERT_EQ(tsfile_table_writer_->close(), common::E_OK);
// Test 1: Single point query (using next() method)
{
storage::TsFileReader reader;
int ret = reader.open(file_name_);
ASSERT_EQ(ret, common::E_OK);
ResultSet* tmp_result_set = nullptr;
// Single point query: don't specify batch_size (or use 0)
auto start_time = std::chrono::high_resolution_clock::now();
ret = reader.query(table_schema->get_table_name(),
table_schema->get_measurement_names(), 0,
1000000000000, tmp_result_set);
ASSERT_EQ(ret, common::E_OK);
ASSERT_NE(tmp_result_set, nullptr);
auto* table_result_set = dynamic_cast<TableResultSet*>(tmp_result_set);
ASSERT_NE(table_result_set, nullptr);
int total_rows_read = 0;
bool has_next = false;
// Use next() method for single point query
while (IS_SUCC(table_result_set->next(has_next)) && has_next) {
total_rows_read++;
}
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - start_time);
EXPECT_EQ(total_rows_read, total_rows);
std::cout << "\n=== Single Point Query (using next() method) ==="
<< std::endl;
std::cout << "Total rows read: " << total_rows_read << std::endl;
std::cout << "Time taken: " << duration.count() << " ms" << std::endl;
std::cout << "Throughput: "
<< (total_rows_read * 5 * 1000.0 / duration.count())
<< " rows/sec" << std::endl;
reader.destroy_query_data_set(table_result_set);
ASSERT_EQ(reader.close(), common::E_OK);
}
// Test 2: Batch query (batch_size = 1000)
{
storage::TsFileReader reader;
int ret = reader.open(file_name_);
ASSERT_EQ(ret, common::E_OK);
ResultSet* tmp_result_set = nullptr;
const int batch_size = 10000; // Batch query
auto start_time = std::chrono::high_resolution_clock::now();
ret = reader.query(table_schema->get_table_name(),
table_schema->get_measurement_names(), 0,
1000000000000, tmp_result_set, batch_size);
ASSERT_EQ(ret, common::E_OK);
ASSERT_NE(tmp_result_set, nullptr);
auto* table_result_set = dynamic_cast<TableResultSet*>(tmp_result_set);
ASSERT_NE(table_result_set, nullptr);
int total_rows_read = 0;
common::TsBlock* block = nullptr;
int block_count = 0;
while ((ret = table_result_set->get_next_tsblock(block)) ==
common::E_OK) {
ASSERT_NE(block, nullptr);
block_count++;
total_rows_read += block->get_row_count();
}
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - start_time);
EXPECT_EQ(total_rows_read, total_rows);
std::cout << "\n=== Batch Query (batch_size=10000) ===" << std::endl;
std::cout << "Total rows read: " << total_rows_read << std::endl;
std::cout << "Block count: " << block_count << std::endl;
std::cout << "Time taken: " << duration.count() << " ms" << std::endl;
std::cout << "Throughput: "
<< (total_rows_read * 5 * 1000.0 / duration.count())
<< " rows/sec" << std::endl;
reader.destroy_query_data_set(table_result_set);
ASSERT_EQ(reader.close(), common::E_OK);
}
delete table_schema;
}
INSTANTIATE_TEST_SUITE_P(Serial, TsFileTableReaderBatchTest,
::testing::Values(false));
INSTANTIATE_TEST_SUITE_P(Parallel, TsFileTableReaderBatchTest,
::testing::Values(true));