From 34a7d18a9c054baa79a9140854687a49b8af2a32 Mon Sep 17 00:00:00 2001 From: 761417898 <761417898@qq.com> Date: Fri, 7 Nov 2025 14:19:51 +0800 Subject: [PATCH 1/4] "Implement TagFilter functionality with support for all operations except LikePattern" --- cpp/src/reader/device_meta_iterator.cc | 17 +- cpp/src/reader/filter/filter.h | 4 + cpp/src/reader/filter/tag_filter.cc | 284 ++++++++++++++ cpp/src/reader/filter/tag_filter.h | 182 +++++++++ cpp/src/reader/tsfile_reader.cc | 14 +- cpp/src/reader/tsfile_reader.h | 17 + cpp/test/reader/filter/tag_filter_test.cc | 359 ++++++++++++++++++ .../table_view/tsfile_reader_table_test.cc | 6 +- 8 files changed, 876 insertions(+), 7 deletions(-) create mode 100644 cpp/src/reader/filter/tag_filter.cc create mode 100644 cpp/src/reader/filter/tag_filter.h create mode 100644 cpp/test/reader/filter/tag_filter_test.cc diff --git a/cpp/src/reader/device_meta_iterator.cc b/cpp/src/reader/device_meta_iterator.cc index 4f47341c1..0d8f201f2 100644 --- a/cpp/src/reader/device_meta_iterator.cc +++ b/cpp/src/reader/device_meta_iterator.cc @@ -19,6 +19,8 @@ #include "device_meta_iterator.h" +#include "filter/tag_filter.h" + namespace storage { bool DeviceMetaIterator::has_next() { if (!result_cache_.empty()) { @@ -74,9 +76,18 @@ int DeviceMetaIterator::load_leaf_device(MetaIndexNode* meta_index_node) { const auto& leaf_children = meta_index_node->children_; for (size_t i = 0; i < leaf_children.size(); i++) { std::shared_ptr child = leaf_children[i]; - // const auto& device_id = child->name_; - if (id_filter_ != nullptr /*TODO: !id_filter_->satisfy(device_id)*/) { - continue; + if (id_filter_ != nullptr) { + auto deviceid = child->get_device_id(); + auto device_id_seg_ptr = deviceid->get_segments(); + std::vector device_id_seg; + std::transform(device_id_seg_ptr.begin(), device_id_seg_ptr.end(), + std::back_inserter(device_id_seg), + [](const std::string* ptr) -> std::string { + return ptr ? *ptr : std::string{}; + }); + if (!id_filter_->satisfyRow(0, device_id_seg)) { + continue; + } } int32_t start_offset = child->get_offset(); int32_t end_offset = i + 1 < leaf_children.size() diff --git a/cpp/src/reader/filter/filter.h b/cpp/src/reader/filter/filter.h index 1846df5a4..d18e391ad 100644 --- a/cpp/src/reader/filter/filter.h +++ b/cpp/src/reader/filter/filter.h @@ -54,6 +54,10 @@ class Filter { ASSERT(false); return false; } + virtual bool satisfyRow(int time, std::vector segments) const { + ASSERT(false); + return false; + } virtual std::vector* get_time_ranges() { ASSERT(false); return nullptr; diff --git a/cpp/src/reader/filter/tag_filter.cc b/cpp/src/reader/filter/tag_filter.cc new file mode 100644 index 000000000..702e7a3ab --- /dev/null +++ b/cpp/src/reader/filter/tag_filter.cc @@ -0,0 +1,284 @@ +/* + * 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 "tag_filter.h" + +#include +#include + +namespace storage { + +// TagFilter base class implementation +TagFilter::TagFilter(int col_idx, std::string tag_value) + : col_idx_(col_idx), value_(std::move(tag_value)), value2_("") {} + +TagFilter::~TagFilter() = default; + +bool TagFilter::satisfyRow(int time, std::vector segments) const { + if (time != 0) return false; + return satisfyRow(segments); +} + +bool TagFilter::satisfyRow(std::vector segments) const { + ASSERT(false); + return false; +} + +// TagEq implementation +TagEq::TagEq(int col_idx, std::string tag_value) + : TagFilter(col_idx, std::move(tag_value)) {} + +bool TagEq::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size()) return false; + return segments[col_idx_] == value_; +} + +// TagNeq implementation +TagNeq::TagNeq(int col_idx, std::string tag_value) + : TagFilter(col_idx, std::move(tag_value)) {} + +bool TagNeq::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size()) return false; + return segments[col_idx_] != value_; +} + +// TagLt implementation +TagLt::TagLt(int col_idx, std::string tag_value) + : TagFilter(col_idx, std::move(tag_value)) {} + +bool TagLt::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size()) return false; + return segments[col_idx_] < value_; +} + +// TagLteq implementation +TagLteq::TagLteq(int col_idx, std::string tag_value) + : TagFilter(col_idx, std::move(tag_value)) {} + +bool TagLteq::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size()) return false; + return segments[col_idx_] <= value_; +} + +// TagGt implementation +TagGt::TagGt(int col_idx, std::string tag_value) + : TagFilter(col_idx, std::move(tag_value)) {} + +bool TagGt::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size()) return false; + return segments[col_idx_] > value_; +} + +// TagGteq implementation +TagGteq::TagGteq(int col_idx, std::string tag_value) + : TagFilter(col_idx, std::move(tag_value)) {} + +bool TagGteq::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size()) return false; + return segments[col_idx_] >= value_; +} + +// TagRegExp implementation +TagRegExp::TagRegExp(int col_idx, std::string tag_value) + : TagFilter(col_idx, std::move(tag_value)) {} + +bool TagRegExp::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size()) return false; + try { + std::regex pattern(value_); + return std::regex_search(segments[col_idx_], pattern); + } catch (const std::regex_error&) { + return false; + } +} + +// TagNotRegExp implementation +TagNotRegExp::TagNotRegExp(int col_idx, std::string tag_value) + : TagFilter(col_idx, std::move(tag_value)) {} + +bool TagNotRegExp::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size()) return false; + try { + std::regex pattern(value_); + return !std::regex_search(segments[col_idx_], pattern); + } catch (const std::regex_error&) { + return true; + } +} + +// TagBetween implementation +TagBetween::TagBetween(int col_idx, std::string lower_value, + std::string upper_value) + : TagFilter(col_idx, std::move(lower_value)) { + value2_ = std::move(upper_value); +} + +bool TagBetween::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size()) return false; + const std::string& segment_value = segments[col_idx_]; + return segment_value >= value_ && segment_value <= value2_; +} + +// TagNotBetween implementation +TagNotBetween::TagNotBetween(int col_idx, std::string lower_value, + std::string upper_value) + : TagFilter(col_idx, std::move(lower_value)) { + value2_ = std::move(upper_value); +} + +bool TagNotBetween::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size()) return false; + const std::string& segment_value = segments[col_idx_]; + return segment_value < value_ || segment_value > value2_; +} + +// TagAnd implementation +TagAnd::TagAnd(TagFilter* left, TagFilter* right) + : TagFilter(-1, ""), left_(left), right_(right) {} + +TagAnd::~TagAnd() { + delete left_; + delete right_; +} + +bool TagAnd::satisfyRow(std::vector segments) const { + return left_->satisfyRow(segments) && right_->satisfyRow(segments); +} + +// TagOr implementation +TagOr::TagOr(TagFilter* left, TagFilter* right) + : TagFilter(-1, ""), left_(left), right_(right) {} + +TagOr::~TagOr() { + delete left_; + delete right_; +} + +bool TagOr::satisfyRow(std::vector segments) const { + return left_->satisfyRow(segments) || right_->satisfyRow(segments); +} + +// TagNot implementation +TagNot::TagNot(TagFilter* filter) : TagFilter(-1, ""), filter_(filter) {} + +TagNot::~TagNot() { delete filter_; } + +bool TagNot::satisfyRow(std::vector segments) const { + return !filter_->satisfyRow(segments); +} + +// TagFilterBuilder implementation +TagFilterBuilder::TagFilterBuilder(TableSchema* schema) + : table_schema_(schema) {} + +Filter* TagFilterBuilder::eq(const std::string& columnName, + const std::string& value) { + auto idx = get_id_column_index(columnName); + if (idx < 0) return nullptr; + return new TagEq(idx, value); +} + +Filter* TagFilterBuilder::neq(const std::string& columnName, + const std::string& value) { + auto idx = get_id_column_index(columnName); + if (idx < 0) return nullptr; + return new TagNeq(idx, value); +} + +Filter* TagFilterBuilder::lt(const std::string& columnName, + const std::string& value) { + auto idx = get_id_column_index(columnName); + if (idx < 0) return nullptr; + return new TagLt(idx, value); +} + +Filter* TagFilterBuilder::lteq(const std::string& columnName, + const std::string& value) { + auto idx = get_id_column_index(columnName); + if (idx < 0) return nullptr; + return new TagLteq(idx, value); +} + +Filter* TagFilterBuilder::gt(const std::string& columnName, + const std::string& value) { + auto idx = get_id_column_index(columnName); + if (idx < 0) return nullptr; + return new TagGt(idx, value); +} + +Filter* TagFilterBuilder::gteq(const std::string& columnName, + const std::string& value) { + auto idx = get_id_column_index(columnName); + if (idx < 0) return nullptr; + return new TagGteq(idx, value); +} + +Filter* TagFilterBuilder::reg_exp(const std::string& columnName, + const std::string& value) { + auto idx = get_id_column_index(columnName); + if (idx < 0) return nullptr; + return new TagRegExp(idx, value); +} + +Filter* TagFilterBuilder::not_reg_exp(const std::string& columnName, + const std::string& value) { + auto idx = get_id_column_index(columnName); + if (idx < 0) return nullptr; + return new TagNotRegExp(idx, value); +} + +Filter* TagFilterBuilder::between_and(const std::string& columnName, + const std::string& lower, + const std::string& upper) { + auto idx = get_id_column_index(columnName); + if (idx < 0) return nullptr; + return new TagBetween(idx, lower, upper); +} + +Filter* TagFilterBuilder::not_between_and(const std::string& columnName, + const std::string& lower, + const std::string& upper) { + auto idx = get_id_column_index(columnName); + if (idx < 0) return nullptr; + return new TagNotBetween(idx, lower, upper); +} + +Filter* TagFilterBuilder::and_filter(Filter* left, Filter* right) { + return new TagAnd(dynamic_cast(left), + dynamic_cast(right)); +} + +Filter* TagFilterBuilder::or_filter(Filter* left, Filter* right) { + return new TagOr(dynamic_cast(left), + dynamic_cast(right)); +} + +Filter* TagFilterBuilder::not_filter(Filter* filter) { + return new TagNot(dynamic_cast(filter)); +} + +int TagFilterBuilder::get_id_column_index(const std::string& columnName) { + int idColumnOrder = table_schema_->find_id_column_order(columnName); + if (idColumnOrder == -1) { + return -1; + } + return idColumnOrder + 1; +} + +} // namespace storage \ No newline at end of file diff --git a/cpp/src/reader/filter/tag_filter.h b/cpp/src/reader/filter/tag_filter.h new file mode 100644 index 000000000..162e346d1 --- /dev/null +++ b/cpp/src/reader/filter/tag_filter.h @@ -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. + */ + +#ifndef READER_FILTER_TAG_FILTER_H +#define READER_FILTER_TAG_FILTER_H + +#include +#include +#include + +#include "common/allocator/my_string.h" +#include "common/schema.h" +#include "reader/filter/filter.h" + +struct table_schema; +namespace storage { +class TagFilter : public Filter { + public: + TagFilter(int col_idx, std::string tag_value); + ~TagFilter() override; + + virtual bool satisfyRow(int time, std::vector segments) const; + virtual bool satisfyRow(std::vector segments) const; + + std::string value_; + std::string value2_; // For range queries + int col_idx_; +}; + +// Equality comparison +class TagEq : public TagFilter { + public: + TagEq(int col_idx, std::string tag_value); + bool satisfyRow(std::vector segments) const override; +}; + +// Inequality comparison +class TagNeq : public TagFilter { + public: + TagNeq(int col_idx, std::string tag_value); + bool satisfyRow(std::vector segments) const override; +}; + +// Less than comparison +class TagLt : public TagFilter { + public: + TagLt(int col_idx, std::string tag_value); + bool satisfyRow(std::vector segments) const override; +}; + +// Less than or equal comparison +class TagLteq : public TagFilter { + public: + TagLteq(int col_idx, std::string tag_value); + bool satisfyRow(std::vector segments) const override; +}; + +// Greater than comparison +class TagGt : public TagFilter { + public: + TagGt(int col_idx, std::string tag_value); + bool satisfyRow(std::vector segments) const override; +}; + +// Greater than or equal comparison +class TagGteq : public TagFilter { + public: + TagGteq(int col_idx, std::string tag_value); + bool satisfyRow(std::vector segments) const override; +}; + +// Regular expression match +class TagRegExp : public TagFilter { + public: + TagRegExp(int col_idx, std::string tag_value); + bool satisfyRow(std::vector segments) const override; +}; + +// Regular expression not match +class TagNotRegExp : public TagFilter { + public: + TagNotRegExp(int col_idx, std::string tag_value); + bool satisfyRow(std::vector segments) const override; +}; + +// Range query [value_, value2_] +class TagBetween : public TagFilter { + public: + TagBetween(int col_idx, std::string lower_value, std::string upper_value); + bool satisfyRow(std::vector segments) const override; +}; + +// Not in range +class TagNotBetween : public TagFilter { + public: + TagNotBetween(int col_idx, std::string lower_value, + std::string upper_value); + bool satisfyRow(std::vector segments) const override; +}; + +// Logical AND operation (binary) +class TagAnd : public TagFilter { + public: + TagAnd(TagFilter* left, TagFilter* right); + ~TagAnd(); + bool satisfyRow(std::vector segments) const override; + + private: + TagFilter* left_; + TagFilter* right_; +}; + +// Logical OR operation (binary) +class TagOr : public TagFilter { + public: + TagOr(TagFilter* left, TagFilter* right); + ~TagOr(); + bool satisfyRow(std::vector segments) const override; + + private: + TagFilter* left_; + TagFilter* right_; +}; + +// Logical NOT operation +class TagNot : public TagFilter { + public: + TagNot(TagFilter* filter); + ~TagNot(); + bool satisfyRow(std::vector segments) const override; + + private: + TagFilter* filter_; +}; + +class TagFilterBuilder { + TableSchema* table_schema_; + + public: + explicit TagFilterBuilder(TableSchema* schema); + + Filter* eq(const std::string& columnName, const std::string& value); + Filter* neq(const std::string& columnName, const std::string& value); + Filter* lt(const std::string& columnName, const std::string& value); + Filter* lteq(const std::string& columnName, const std::string& value); + Filter* gt(const std::string& columnName, const std::string& value); + Filter* gteq(const std::string& columnName, const std::string& value); + Filter* reg_exp(const std::string& columnName, const std::string& value); + Filter* not_reg_exp(const std::string& columnName, + const std::string& value); + Filter* between_and(const std::string& columnName, const std::string& lower, + const std::string& upper); + Filter* not_between_and(const std::string& columnName, + const std::string& lower, const std::string& upper); + + // Logical operations + static Filter* and_filter(Filter* left, Filter* right); + static Filter* or_filter(Filter* left, Filter* right); + static Filter* not_filter(Filter* filter); + + private: + int get_id_column_index(const std::string& columnName); +}; + +} // namespace storage +#endif // READER_FILTER_TAG_FILTER_H \ No newline at end of file diff --git a/cpp/src/reader/tsfile_reader.cc b/cpp/src/reader/tsfile_reader.cc index 5dba982f6..8118052b5 100644 --- a/cpp/src/reader/tsfile_reader.cc +++ b/cpp/src/reader/tsfile_reader.cc @@ -88,6 +88,14 @@ int TsFileReader::query(const std::string& table_name, const std::vector& columns_names, int64_t start_time, int64_t end_time, ResultSet*& result_set) { + return this->query(table_name, columns_names, start_time, end_time, + result_set, nullptr); +} + +int TsFileReader::query(const std::string& table_name, + const std::vector& columns_names, + int64_t start_time, int64_t end_time, + ResultSet*& result_set, Filter* tag_filter) { int ret = E_OK; TsFileMeta* tsfile_meta = tsfile_executor_->get_tsfile_meta(); if (tsfile_meta == nullptr) { @@ -102,9 +110,9 @@ int TsFileReader::query(const std::string& table_name, std::vector data_types = table_schema->get_data_types(); Filter* time_filter = new TimeBetween(start_time, end_time, false); - ret = - table_query_executor_->query(to_lower(table_name), columns_names, - time_filter, nullptr, nullptr, result_set); + ret = table_query_executor_->query(to_lower(table_name), columns_names, + time_filter, tag_filter, nullptr, + result_set); return ret; } diff --git a/cpp/src/reader/tsfile_reader.h b/cpp/src/reader/tsfile_reader.h index dc5105c0b..c4a615f1e 100644 --- a/cpp/src/reader/tsfile_reader.h +++ b/cpp/src/reader/tsfile_reader.h @@ -96,6 +96,23 @@ class TsFileReader { int query(const std::string &table_name, const std::vector &columns_names, int64_t start_time, int64_t end_time, ResultSet *&result_set); + + /** + * @brief query the tsfile by the table name, columns names, start time + * and end time, tag filter. this method is used to query the tsfile by the + * table model. + * + * @param [in] table_name the table name + * @param [in] columns_names the columns names + * @param [in] start_time the start time + * @param [in] end_time the end time + * @param [in] tag_filter the tag filter + * @param [out] result_set the result set + */ + int query(const std::string &table_name, + const std::vector &columns_names, int64_t start_time, + int64_t end_time, ResultSet *&result_set, Filter *tag_filter); + /** * @brief destroy the result set, this method should be called after the * query is finished and result_set diff --git a/cpp/test/reader/filter/tag_filter_test.cc b/cpp/test/reader/filter/tag_filter_test.cc new file mode 100644 index 000000000..fefd8bc63 --- /dev/null +++ b/cpp/test/reader/filter/tag_filter_test.cc @@ -0,0 +1,359 @@ +#include "reader/filter/tag_filter.h" + +#include + +#include "common/schema.h" + +using namespace storage; + +class TagFilterTest : public ::testing::Test { + protected: + void SetUp() override { + std::vector column_schemas; + column_schemas.emplace_back("name", common::TSDataType::TEXT, + common::ColumnCategory::TAG); + column_schemas.emplace_back("age", common::TSDataType::TEXT, + common::ColumnCategory::TAG); + column_schemas.emplace_back("department", common::TSDataType::TEXT, + common::ColumnCategory::TAG); + column_schemas.emplace_back("status", common::TSDataType::TEXT, + common::ColumnCategory::TAG); + column_schemas.emplace_back("score", common::TSDataType::TEXT, + common::ColumnCategory::TAG); + + schema_ = new TableSchema("test_table", column_schemas); + builder_ = new TagFilterBuilder(schema_); + } + + void TearDown() override { + delete builder_; + delete schema_; + } + + // Helper method to create segments starting from index 1 + static std::vector createSegments( + const std::string& name, const std::string& age, + const std::string& department, const std::string& status = "", + const std::string& score = "") { + std::vector segments; + segments.emplace_back(""); // index 0 - placeholder or device name + segments.push_back(name); + segments.push_back(age); + segments.push_back(department); + segments.push_back(status); + segments.push_back(score); + return segments; + } + + TableSchema* schema_ = nullptr; + TagFilterBuilder* builder_ = nullptr; +}; + +// Equality filter +TEST_F(TagFilterTest, TagEqFilter) { + auto filter = builder_->eq("name", "john"); + ASSERT_NE(filter, nullptr); + + auto segments = createSegments("john", "25", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("alice", "25", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + delete filter; +} + +// Inequality filter +TEST_F(TagFilterTest, TagNeqFilter) { + auto filter = builder_->neq("name", "john"); + ASSERT_NE(filter, nullptr); + + auto segments = + createSegments("alice", "25", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "25", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + delete filter; +} + +// Less than filter +TEST_F(TagFilterTest, TagLtFilter) { + auto filter = builder_->lt("age", "30"); + ASSERT_NE(filter, nullptr); + + auto segments = createSegments("john", "25", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "35", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + delete filter; +} + +// Less than or equal filter +TEST_F(TagFilterTest, TagLteqFilter) { + auto filter = builder_->lteq("age", "30"); + ASSERT_NE(filter, nullptr); + + auto segments = createSegments("john", "30", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "25", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "35", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + delete filter; +} + +// Greater than filter +TEST_F(TagFilterTest, TagGtFilter) { + auto filter = builder_->gt("age", "30"); + ASSERT_NE(filter, nullptr); + + auto segments = createSegments("john", "35", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "25", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + delete filter; +} + +// Greater than or equal filter +TEST_F(TagFilterTest, TagGteqFilter) { + auto filter = builder_->gteq("age", "30"); + ASSERT_NE(filter, nullptr); + + auto segments = createSegments("john", "30", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "35", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "25", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + delete filter; +} + +// Between filter +TEST_F(TagFilterTest, TagBetweenFilter) { + auto filter = builder_->between_and("age", "25", "35"); + ASSERT_NE(filter, nullptr); + + auto segments = createSegments("john", "25", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "30", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "35", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "20", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "40", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + delete filter; +} + +// Not between filter +TEST_F(TagFilterTest, TagNotBetweenFilter) { + auto filter = builder_->not_between_and("age", "25", "35"); + ASSERT_NE(filter, nullptr); + + auto segments = createSegments("john", "20", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "40", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "25", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + segments = createSegments("john", "30", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + delete filter; +} + +// AND filter +TEST_F(TagFilterTest, TagAndFilter) { + auto left_filter = builder_->gteq("age", "25"); + auto right_filter = builder_->eq("department", "engineering"); + auto and_filter = builder_->and_filter(left_filter, right_filter); + ASSERT_NE(and_filter, nullptr); + + auto segments = createSegments("john", "30", "engineering", "active", "95"); + EXPECT_TRUE(and_filter->satisfyRow(0, segments)); + + segments = createSegments("john", "20", "engineering", "active", "95"); + EXPECT_FALSE(and_filter->satisfyRow(0, segments)); + + segments = createSegments("john", "30", "sales", "active", "95"); + EXPECT_FALSE(and_filter->satisfyRow(0, segments)); + + delete and_filter; +} + +// OR filter +TEST_F(TagFilterTest, TagOrFilter) { + auto left_filter = builder_->lt("age", "25"); + auto right_filter = builder_->eq("department", "engineering"); + auto or_filter = builder_->or_filter(left_filter, right_filter); + ASSERT_NE(or_filter, nullptr); + + auto segments = createSegments("john", "20", "engineering", "active", "95"); + EXPECT_TRUE(or_filter->satisfyRow(0, segments)); + + segments = createSegments("john", "20", "sales", "active", "95"); + EXPECT_TRUE(or_filter->satisfyRow(0, segments)); + + segments = createSegments("john", "30", "engineering", "active", "95"); + EXPECT_TRUE(or_filter->satisfyRow(0, segments)); + + segments = createSegments("john", "30", "sales", "active", "95"); + EXPECT_FALSE(or_filter->satisfyRow(0, segments)); + + delete or_filter; +} + +// NOT filter +TEST_F(TagFilterTest, TagNotFilter) { + auto base_filter = builder_->eq("status", "active"); + auto not_filter = builder_->not_filter(base_filter); + ASSERT_NE(not_filter, nullptr); + + auto segments = + createSegments("john", "30", "engineering", "inactive", "95"); + EXPECT_TRUE(not_filter->satisfyRow(0, segments)); + + segments = createSegments("john", "30", "engineering", "active", "95"); + EXPECT_FALSE(not_filter->satisfyRow(0, segments)); + + delete not_filter; +} + +// Complex nested filters +TEST_F(TagFilterTest, ComplexNestedFilters) { + auto age_filter = builder_->gteq("age", "25"); + auto dept_filter = builder_->eq("department", "engineering"); + auto score_filter = builder_->gt("score", "90"); + + auto and_filter = builder_->and_filter(age_filter, dept_filter); + auto complex_filter = builder_->or_filter(and_filter, score_filter); + ASSERT_NE(complex_filter, nullptr); + + auto segments = createSegments("john", "30", "engineering", "active", "85"); + EXPECT_TRUE(complex_filter->satisfyRow(0, segments)); + + segments = createSegments("alice", "20", "sales", "active", "95"); + EXPECT_TRUE(complex_filter->satisfyRow(0, segments)); + + segments = createSegments("bob", "20", "sales", "active", "85"); + EXPECT_FALSE(complex_filter->satisfyRow(0, segments)); + + delete complex_filter; +} + +// Invalid column name +TEST_F(TagFilterTest, InvalidColumnName) { + auto filter = builder_->eq("invalid_column", "value"); + EXPECT_EQ(filter, nullptr); +} + +// Boundary conditions +TEST_F(TagFilterTest, BoundaryConditions) { + auto filter = builder_->eq("name", "test"); + ASSERT_NE(filter, nullptr); + + std::vector empty_segments; + EXPECT_FALSE(filter->satisfyRow(0, empty_segments)); + + std::vector small_segments = {""}; + EXPECT_FALSE(filter->satisfyRow(0, small_segments)); + + std::vector minimal_segments = {"", "test"}; + EXPECT_TRUE(filter->satisfyRow(0, minimal_segments)); + + delete filter; +} + +// Time parameter test +TEST_F(TagFilterTest, TimeParameterTest) { + auto filter = builder_->eq("name", "john"); + ASSERT_NE(filter, nullptr); + + auto segments = createSegments("john", "25", "engineering", "active", "95"); + + EXPECT_TRUE(filter->satisfyRow(0, segments)); + EXPECT_FALSE(filter->satisfyRow(1, segments)); + + delete filter; +} + +// Basic regex match and not match +TEST_F(TagFilterTest, TagRegExpBasic) { + auto filter = builder_->reg_exp("name", "^j.*"); + ASSERT_NE(filter, nullptr); + + auto segments = createSegments("john", "25", "engineering", "active", "95"); + EXPECT_TRUE(filter->satisfyRow(0, segments)); + + segments = createSegments("alice", "25", "engineering", "active", "95"); + EXPECT_FALSE(filter->satisfyRow(0, segments)); + + auto not_filter = builder_->not_reg_exp("name", "^j.*"); + segments = createSegments("alice", "25", "engineering", "active", "95"); + EXPECT_TRUE(not_filter->satisfyRow(0, segments)); + + delete filter; + delete not_filter; +} + +// Complex regex pattern with logical operations +TEST_F(TagFilterTest, TagRegExpComplexLogic) { + // Match names starting with j OR age in 20s AND department contains "eng" + auto name_filter = builder_->reg_exp("name", "^j.*"); + auto age_filter = builder_->reg_exp("age", "^2[0-9]$"); + auto dept_filter = builder_->reg_exp("department", ".*eng.*"); + + auto age_dept_and = builder_->and_filter(age_filter, dept_filter); + auto complex_or = builder_->or_filter(name_filter, age_dept_and); + + auto segments = createSegments("john", "35", "sales", "active", "95"); + EXPECT_TRUE(complex_or->satisfyRow(0, segments)); // name matches + + segments = createSegments("alice", "25", "engineering", "active", "95"); + EXPECT_TRUE(complex_or->satisfyRow(0, segments)); // age and dept match + + segments = createSegments("bob", "35", "sales", "active", "95"); + EXPECT_FALSE(complex_or->satisfyRow(0, segments)); // no match + + delete complex_or; +} + +// Edge cases: invalid regex and boundary values +TEST_F(TagFilterTest, TagRegExpEdgeCases) { + // Invalid regex should not crash + auto invalid_filter = builder_->reg_exp("name", "[invalid[pattern"); + ASSERT_NE(invalid_filter, nullptr); + + auto segments = createSegments("test", "25", "engineering", "active", "95"); + EXPECT_FALSE( + invalid_filter->satisfyRow(0, segments)); // handles gracefully + + // Empty pattern matches everything + auto empty_filter = builder_->reg_exp("name", ""); + segments = createSegments("any", "25", "engineering", "active", "95"); + EXPECT_TRUE(empty_filter->satisfyRow(0, segments)); + + delete invalid_filter; + delete empty_filter; +} \ No newline at end of file diff --git a/cpp/test/reader/table_view/tsfile_reader_table_test.cc b/cpp/test/reader/table_view/tsfile_reader_table_test.cc index 02398c683..4386b1835 100644 --- a/cpp/test/reader/table_view/tsfile_reader_table_test.cc +++ b/cpp/test/reader/table_view/tsfile_reader_table_test.cc @@ -25,6 +25,7 @@ #include "common/tablet.h" #include "file/tsfile_io_writer.h" #include "file/write_file.h" +#include "reader/filter/tag_filter.h" #include "reader/table_result_set.h" #include "reader/tsfile_reader.h" #include "writer/chunk_writer.h" @@ -148,9 +149,11 @@ class TsFileTableReaderTest : public ::testing::Test { ASSERT_EQ(ret, common::E_OK); ResultSet* tmp_result_set = nullptr; + Filter* tag_filter = + TagFilterBuilder(table_schema).eq("id0", "device_id"); ret = reader.query(table_schema->get_table_name(), table_schema->get_measurement_names(), 0, end_time, - tmp_result_set); + tmp_result_set, tag_filter); auto* table_result_set = (TableResultSet*)tmp_result_set; char* literal = new char[std::strlen("device_id") + 1]; std::strcpy(literal, "device_id"); @@ -197,6 +200,7 @@ class TsFileTableReaderTest : public ::testing::Test { delete[] literal; ASSERT_EQ(reader.close(), common::E_OK); delete table_schema; + delete tag_filter; } }; From 668144ea5ef3f02859883ea67a7ed6c06cd93cc0 Mon Sep 17 00:00:00 2001 From: 761417898 <761417898@qq.com> Date: Fri, 7 Nov 2025 14:33:38 +0800 Subject: [PATCH 2/4] add license --- cpp/test/reader/filter/tag_filter_test.cc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/cpp/test/reader/filter/tag_filter_test.cc b/cpp/test/reader/filter/tag_filter_test.cc index fefd8bc63..1f1ca935a 100644 --- a/cpp/test/reader/filter/tag_filter_test.cc +++ b/cpp/test/reader/filter/tag_filter_test.cc @@ -1,3 +1,22 @@ +/* + * 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 a + * + * 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 "reader/filter/tag_filter.h" #include From 2df8c379e50f169d12674e0b76701ba36e42d591 Mon Sep 17 00:00:00 2001 From: 761417898 <761417898@qq.com> Date: Wed, 26 Nov 2025 12:03:55 +0800 Subject: [PATCH 3/4] fix some issues --- cpp/src/reader/device_meta_iterator.cc | 11 +-- cpp/src/reader/filter/filter.h | 3 +- cpp/src/reader/filter/tag_filter.cc | 75 ++++++++++--------- cpp/src/reader/filter/tag_filter.h | 44 +++++++----- cpp/test/reader/filter/tag_filter_test.cc | 87 ++++++++++++++++------- 5 files changed, 135 insertions(+), 85 deletions(-) diff --git a/cpp/src/reader/device_meta_iterator.cc b/cpp/src/reader/device_meta_iterator.cc index 0d8f201f2..a59f511db 100644 --- a/cpp/src/reader/device_meta_iterator.cc +++ b/cpp/src/reader/device_meta_iterator.cc @@ -77,15 +77,8 @@ int DeviceMetaIterator::load_leaf_device(MetaIndexNode* meta_index_node) { for (size_t i = 0; i < leaf_children.size(); i++) { std::shared_ptr child = leaf_children[i]; if (id_filter_ != nullptr) { - auto deviceid = child->get_device_id(); - auto device_id_seg_ptr = deviceid->get_segments(); - std::vector device_id_seg; - std::transform(device_id_seg_ptr.begin(), device_id_seg_ptr.end(), - std::back_inserter(device_id_seg), - [](const std::string* ptr) -> std::string { - return ptr ? *ptr : std::string{}; - }); - if (!id_filter_->satisfyRow(0, device_id_seg)) { + if (!id_filter_->satisfyRow( + 0, child->get_device_id()->get_segments())) { continue; } } diff --git a/cpp/src/reader/filter/filter.h b/cpp/src/reader/filter/filter.h index d18e391ad..f39dddbae 100644 --- a/cpp/src/reader/filter/filter.h +++ b/cpp/src/reader/filter/filter.h @@ -54,7 +54,8 @@ class Filter { ASSERT(false); return false; } - virtual bool satisfyRow(int time, std::vector segments) const { + virtual bool satisfyRow(int time, + std::vector segments) const { ASSERT(false); return false; } diff --git a/cpp/src/reader/filter/tag_filter.cc b/cpp/src/reader/filter/tag_filter.cc index 702e7a3ab..c4c56a1da 100644 --- a/cpp/src/reader/filter/tag_filter.cc +++ b/cpp/src/reader/filter/tag_filter.cc @@ -30,12 +30,11 @@ TagFilter::TagFilter(int col_idx, std::string tag_value) TagFilter::~TagFilter() = default; -bool TagFilter::satisfyRow(int time, std::vector segments) const { - if (time != 0) return false; +bool TagFilter::satisfyRow(int time, std::vector segments) const { return satisfyRow(segments); } -bool TagFilter::satisfyRow(std::vector segments) const { +bool TagFilter::satisfyRow(std::vector segments) const { ASSERT(false); return false; } @@ -44,65 +43,71 @@ bool TagFilter::satisfyRow(std::vector segments) const { TagEq::TagEq(int col_idx, std::string tag_value) : TagFilter(col_idx, std::move(tag_value)) {} -bool TagEq::satisfyRow(std::vector segments) const { +bool TagEq::satisfyRow(std::vector segments) const { if (col_idx_ >= segments.size()) return false; - return segments[col_idx_] == value_; + return *segments[col_idx_] == value_; } // TagNeq implementation TagNeq::TagNeq(int col_idx, std::string tag_value) : TagFilter(col_idx, std::move(tag_value)) {} -bool TagNeq::satisfyRow(std::vector segments) const { +bool TagNeq::satisfyRow(std::vector segments) const { if (col_idx_ >= segments.size()) return false; - return segments[col_idx_] != value_; + return *segments[col_idx_] != value_; } // TagLt implementation TagLt::TagLt(int col_idx, std::string tag_value) : TagFilter(col_idx, std::move(tag_value)) {} -bool TagLt::satisfyRow(std::vector segments) const { +bool TagLt::satisfyRow(std::vector segments) const { if (col_idx_ >= segments.size()) return false; - return segments[col_idx_] < value_; + return *segments[col_idx_] < value_; } // TagLteq implementation TagLteq::TagLteq(int col_idx, std::string tag_value) : TagFilter(col_idx, std::move(tag_value)) {} -bool TagLteq::satisfyRow(std::vector segments) const { +bool TagLteq::satisfyRow(std::vector segments) const { if (col_idx_ >= segments.size()) return false; - return segments[col_idx_] <= value_; + return *segments[col_idx_] <= value_; } // TagGt implementation TagGt::TagGt(int col_idx, std::string tag_value) : TagFilter(col_idx, std::move(tag_value)) {} -bool TagGt::satisfyRow(std::vector segments) const { +bool TagGt::satisfyRow(std::vector segments) const { if (col_idx_ >= segments.size()) return false; - return segments[col_idx_] > value_; + return *segments[col_idx_] > value_; } // TagGteq implementation TagGteq::TagGteq(int col_idx, std::string tag_value) : TagFilter(col_idx, std::move(tag_value)) {} -bool TagGteq::satisfyRow(std::vector segments) const { +bool TagGteq::satisfyRow(std::vector segments) const { if (col_idx_ >= segments.size()) return false; - return segments[col_idx_] >= value_; + return *segments[col_idx_] >= value_; } // TagRegExp implementation TagRegExp::TagRegExp(int col_idx, std::string tag_value) - : TagFilter(col_idx, std::move(tag_value)) {} + : TagFilter(col_idx, std::move(tag_value)) { + try { + pattern_ = std::regex(value_); + is_valid_pattern_ = true; + } catch (const std::regex_error& e) { + is_valid_pattern_ = false; + } +} -bool TagRegExp::satisfyRow(std::vector segments) const { - if (col_idx_ >= segments.size()) return false; +bool TagRegExp::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size() || !is_valid_pattern_) return false; try { - std::regex pattern(value_); - return std::regex_search(segments[col_idx_], pattern); + return std::regex_search(*segments[col_idx_], pattern_); } catch (const std::regex_error&) { return false; } @@ -110,13 +115,19 @@ bool TagRegExp::satisfyRow(std::vector segments) const { // TagNotRegExp implementation TagNotRegExp::TagNotRegExp(int col_idx, std::string tag_value) - : TagFilter(col_idx, std::move(tag_value)) {} + : TagFilter(col_idx, std::move(tag_value)) { + try { + pattern_ = std::regex(value_); + is_valid_pattern_ = true; + } catch (const std::regex_error& e) { + is_valid_pattern_ = false; + } +} -bool TagNotRegExp::satisfyRow(std::vector segments) const { - if (col_idx_ >= segments.size()) return false; +bool TagNotRegExp::satisfyRow(std::vector segments) const { + if (col_idx_ >= segments.size() || !is_valid_pattern_) return false; try { - std::regex pattern(value_); - return !std::regex_search(segments[col_idx_], pattern); + return !std::regex_search(*segments[col_idx_], pattern_); } catch (const std::regex_error&) { return true; } @@ -129,9 +140,9 @@ TagBetween::TagBetween(int col_idx, std::string lower_value, value2_ = std::move(upper_value); } -bool TagBetween::satisfyRow(std::vector segments) const { +bool TagBetween::satisfyRow(std::vector segments) const { if (col_idx_ >= segments.size()) return false; - const std::string& segment_value = segments[col_idx_]; + const std::string& segment_value = *segments[col_idx_]; return segment_value >= value_ && segment_value <= value2_; } @@ -142,9 +153,9 @@ TagNotBetween::TagNotBetween(int col_idx, std::string lower_value, value2_ = std::move(upper_value); } -bool TagNotBetween::satisfyRow(std::vector segments) const { +bool TagNotBetween::satisfyRow(std::vector segments) const { if (col_idx_ >= segments.size()) return false; - const std::string& segment_value = segments[col_idx_]; + const std::string& segment_value = *segments[col_idx_]; return segment_value < value_ || segment_value > value2_; } @@ -157,7 +168,7 @@ TagAnd::~TagAnd() { delete right_; } -bool TagAnd::satisfyRow(std::vector segments) const { +bool TagAnd::satisfyRow(std::vector segments) const { return left_->satisfyRow(segments) && right_->satisfyRow(segments); } @@ -170,7 +181,7 @@ TagOr::~TagOr() { delete right_; } -bool TagOr::satisfyRow(std::vector segments) const { +bool TagOr::satisfyRow(std::vector segments) const { return left_->satisfyRow(segments) || right_->satisfyRow(segments); } @@ -179,7 +190,7 @@ TagNot::TagNot(TagFilter* filter) : TagFilter(-1, ""), filter_(filter) {} TagNot::~TagNot() { delete filter_; } -bool TagNot::satisfyRow(std::vector segments) const { +bool TagNot::satisfyRow(std::vector segments) const { return !filter_->satisfyRow(segments); } diff --git a/cpp/src/reader/filter/tag_filter.h b/cpp/src/reader/filter/tag_filter.h index 162e346d1..c7d8843fd 100644 --- a/cpp/src/reader/filter/tag_filter.h +++ b/cpp/src/reader/filter/tag_filter.h @@ -35,8 +35,8 @@ class TagFilter : public Filter { TagFilter(int col_idx, std::string tag_value); ~TagFilter() override; - virtual bool satisfyRow(int time, std::vector segments) const; - virtual bool satisfyRow(std::vector segments) const; + virtual bool satisfyRow(int time, std::vector segments) const; + virtual bool satisfyRow(std::vector segments) const; std::string value_; std::string value2_; // For range queries @@ -47,63 +47,69 @@ class TagFilter : public Filter { class TagEq : public TagFilter { public: TagEq(int col_idx, std::string tag_value); - bool satisfyRow(std::vector segments) const override; + bool satisfyRow(std::vector segments) const override; }; // Inequality comparison class TagNeq : public TagFilter { public: TagNeq(int col_idx, std::string tag_value); - bool satisfyRow(std::vector segments) const override; + bool satisfyRow(std::vector segments) const override; }; // Less than comparison class TagLt : public TagFilter { public: TagLt(int col_idx, std::string tag_value); - bool satisfyRow(std::vector segments) const override; + bool satisfyRow(std::vector segments) const override; }; // Less than or equal comparison class TagLteq : public TagFilter { public: TagLteq(int col_idx, std::string tag_value); - bool satisfyRow(std::vector segments) const override; + bool satisfyRow(std::vector segments) const override; }; // Greater than comparison class TagGt : public TagFilter { public: TagGt(int col_idx, std::string tag_value); - bool satisfyRow(std::vector segments) const override; + bool satisfyRow(std::vector segments) const override; }; // Greater than or equal comparison class TagGteq : public TagFilter { public: TagGteq(int col_idx, std::string tag_value); - bool satisfyRow(std::vector segments) const override; + bool satisfyRow(std::vector segments) const override; }; // Regular expression match class TagRegExp : public TagFilter { + std::regex pattern_; + bool is_valid_pattern_ = false; + public: TagRegExp(int col_idx, std::string tag_value); - bool satisfyRow(std::vector segments) const override; + bool satisfyRow(std::vector segments) const override; }; // Regular expression not match class TagNotRegExp : public TagFilter { + std::regex pattern_; + bool is_valid_pattern_ = false; + public: TagNotRegExp(int col_idx, std::string tag_value); - bool satisfyRow(std::vector segments) const override; + bool satisfyRow(std::vector segments) const override; }; // Range query [value_, value2_] class TagBetween : public TagFilter { public: TagBetween(int col_idx, std::string lower_value, std::string upper_value); - bool satisfyRow(std::vector segments) const override; + bool satisfyRow(std::vector segments) const override; }; // Not in range @@ -111,15 +117,15 @@ class TagNotBetween : public TagFilter { public: TagNotBetween(int col_idx, std::string lower_value, std::string upper_value); - bool satisfyRow(std::vector segments) const override; + bool satisfyRow(std::vector segments) const override; }; // Logical AND operation (binary) class TagAnd : public TagFilter { public: TagAnd(TagFilter* left, TagFilter* right); - ~TagAnd(); - bool satisfyRow(std::vector segments) const override; + ~TagAnd() override; + bool satisfyRow(std::vector segments) const override; private: TagFilter* left_; @@ -130,8 +136,8 @@ class TagAnd : public TagFilter { class TagOr : public TagFilter { public: TagOr(TagFilter* left, TagFilter* right); - ~TagOr(); - bool satisfyRow(std::vector segments) const override; + ~TagOr() override; + bool satisfyRow(std::vector segments) const override; private: TagFilter* left_; @@ -141,9 +147,9 @@ class TagOr : public TagFilter { // Logical NOT operation class TagNot : public TagFilter { public: - TagNot(TagFilter* filter); - ~TagNot(); - bool satisfyRow(std::vector segments) const override; + explicit TagNot(TagFilter* filter); + ~TagNot() override; + bool satisfyRow(std::vector segments) const override; private: TagFilter* filter_; diff --git a/cpp/test/reader/filter/tag_filter_test.cc b/cpp/test/reader/filter/tag_filter_test.cc index 1f1ca935a..0274d2424 100644 --- a/cpp/test/reader/filter/tag_filter_test.cc +++ b/cpp/test/reader/filter/tag_filter_test.cc @@ -50,20 +50,27 @@ class TagFilterTest : public ::testing::Test { } // Helper method to create segments starting from index 1 - static std::vector createSegments( + static std::vector createSegments( const std::string& name, const std::string& age, const std::string& department, const std::string& status = "", const std::string& score = "") { - std::vector segments; - segments.emplace_back(""); // index 0 - placeholder or device name - segments.push_back(name); - segments.push_back(age); - segments.push_back(department); - segments.push_back(status); - segments.push_back(score); + std::vector segments; + segments.emplace_back(nullptr); // index 0 - placeholder or device name + segments.push_back(new std::string(name)); + segments.push_back(new std::string(age)); + segments.push_back(new std::string(department)); + segments.push_back(new std::string(status)); + segments.push_back(new std::string(score)); return segments; } + // Helper method to cleanup segments + static void cleanupSegments(std::vector& segments) { + for (size_t i = 1; i < segments.size(); i++) { + delete segments[i]; + } + } + TableSchema* schema_ = nullptr; TagFilterBuilder* builder_ = nullptr; }; @@ -75,9 +82,11 @@ TEST_F(TagFilterTest, TagEqFilter) { auto segments = createSegments("john", "25", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("alice", "25", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete filter; } @@ -90,9 +99,11 @@ TEST_F(TagFilterTest, TagNeqFilter) { auto segments = createSegments("alice", "25", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "25", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete filter; } @@ -104,9 +115,11 @@ TEST_F(TagFilterTest, TagLtFilter) { auto segments = createSegments("john", "25", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "35", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete filter; } @@ -118,12 +131,15 @@ TEST_F(TagFilterTest, TagLteqFilter) { auto segments = createSegments("john", "30", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "25", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "35", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete filter; } @@ -135,9 +151,11 @@ TEST_F(TagFilterTest, TagGtFilter) { auto segments = createSegments("john", "35", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "25", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete filter; } @@ -149,12 +167,15 @@ TEST_F(TagFilterTest, TagGteqFilter) { auto segments = createSegments("john", "30", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "35", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "25", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete filter; } @@ -166,18 +187,23 @@ TEST_F(TagFilterTest, TagBetweenFilter) { auto segments = createSegments("john", "25", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "30", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "35", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "20", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "40", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete filter; } @@ -189,15 +215,19 @@ TEST_F(TagFilterTest, TagNotBetweenFilter) { auto segments = createSegments("john", "20", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "40", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "25", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "30", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete filter; } @@ -211,12 +241,15 @@ TEST_F(TagFilterTest, TagAndFilter) { auto segments = createSegments("john", "30", "engineering", "active", "95"); EXPECT_TRUE(and_filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "20", "engineering", "active", "95"); EXPECT_FALSE(and_filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "30", "sales", "active", "95"); EXPECT_FALSE(and_filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete and_filter; } @@ -230,15 +263,19 @@ TEST_F(TagFilterTest, TagOrFilter) { auto segments = createSegments("john", "20", "engineering", "active", "95"); EXPECT_TRUE(or_filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "20", "sales", "active", "95"); EXPECT_TRUE(or_filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "30", "engineering", "active", "95"); EXPECT_TRUE(or_filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "30", "sales", "active", "95"); EXPECT_FALSE(or_filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete or_filter; } @@ -252,9 +289,11 @@ TEST_F(TagFilterTest, TagNotFilter) { auto segments = createSegments("john", "30", "engineering", "inactive", "95"); EXPECT_TRUE(not_filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("john", "30", "engineering", "active", "95"); EXPECT_FALSE(not_filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete not_filter; } @@ -271,12 +310,15 @@ TEST_F(TagFilterTest, ComplexNestedFilters) { auto segments = createSegments("john", "30", "engineering", "active", "85"); EXPECT_TRUE(complex_filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("alice", "20", "sales", "active", "95"); EXPECT_TRUE(complex_filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("bob", "20", "sales", "active", "85"); EXPECT_FALSE(complex_filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete complex_filter; } @@ -292,27 +334,16 @@ TEST_F(TagFilterTest, BoundaryConditions) { auto filter = builder_->eq("name", "test"); ASSERT_NE(filter, nullptr); - std::vector empty_segments; + std::vector empty_segments; EXPECT_FALSE(filter->satisfyRow(0, empty_segments)); - std::vector small_segments = {""}; + std::vector small_segments = {nullptr}; EXPECT_FALSE(filter->satisfyRow(0, small_segments)); - std::vector minimal_segments = {"", "test"}; + std::vector minimal_segments = {nullptr, + new std::string("test")}; EXPECT_TRUE(filter->satisfyRow(0, minimal_segments)); - - delete filter; -} - -// Time parameter test -TEST_F(TagFilterTest, TimeParameterTest) { - auto filter = builder_->eq("name", "john"); - ASSERT_NE(filter, nullptr); - - auto segments = createSegments("john", "25", "engineering", "active", "95"); - - EXPECT_TRUE(filter->satisfyRow(0, segments)); - EXPECT_FALSE(filter->satisfyRow(1, segments)); + delete minimal_segments[1]; delete filter; } @@ -324,13 +355,16 @@ TEST_F(TagFilterTest, TagRegExpBasic) { auto segments = createSegments("john", "25", "engineering", "active", "95"); EXPECT_TRUE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); segments = createSegments("alice", "25", "engineering", "active", "95"); EXPECT_FALSE(filter->satisfyRow(0, segments)); + cleanupSegments(segments); auto not_filter = builder_->not_reg_exp("name", "^j.*"); segments = createSegments("alice", "25", "engineering", "active", "95"); EXPECT_TRUE(not_filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete filter; delete not_filter; @@ -348,12 +382,15 @@ TEST_F(TagFilterTest, TagRegExpComplexLogic) { auto segments = createSegments("john", "35", "sales", "active", "95"); EXPECT_TRUE(complex_or->satisfyRow(0, segments)); // name matches + cleanupSegments(segments); segments = createSegments("alice", "25", "engineering", "active", "95"); EXPECT_TRUE(complex_or->satisfyRow(0, segments)); // age and dept match + cleanupSegments(segments); segments = createSegments("bob", "35", "sales", "active", "95"); EXPECT_FALSE(complex_or->satisfyRow(0, segments)); // no match + cleanupSegments(segments); delete complex_or; } @@ -367,11 +404,13 @@ TEST_F(TagFilterTest, TagRegExpEdgeCases) { auto segments = createSegments("test", "25", "engineering", "active", "95"); EXPECT_FALSE( invalid_filter->satisfyRow(0, segments)); // handles gracefully + cleanupSegments(segments); // Empty pattern matches everything auto empty_filter = builder_->reg_exp("name", ""); segments = createSegments("any", "25", "engineering", "active", "95"); EXPECT_TRUE(empty_filter->satisfyRow(0, segments)); + cleanupSegments(segments); delete invalid_filter; delete empty_filter; From 4d66f0af4a8ccdce1199183f059f7cdf8ee8f141 Mon Sep 17 00:00:00 2001 From: 761417898 <761417898@qq.com> Date: Wed, 26 Nov 2025 15:25:46 +0800 Subject: [PATCH 4/4] demo for TsFile-CPP TagFilter --- cpp/examples/cpp_examples/cpp_examples.h | 1 + cpp/examples/cpp_examples/demo_read.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cpp/examples/cpp_examples/cpp_examples.h b/cpp/examples/cpp_examples/cpp_examples.h index b0512e9a2..5466fbe62 100644 --- a/cpp/examples/cpp_examples/cpp_examples.h +++ b/cpp/examples/cpp_examples/cpp_examples.h @@ -26,6 +26,7 @@ #include "file/write_file.h" #include "reader/expression.h" #include "reader/filter/filter.h" +#include "reader/filter/tag_filter.h" #include "reader/qds_with_timegenerator.h" #include "reader/qds_without_timegenerator.h" #include "reader/tsfile_reader.h" diff --git a/cpp/examples/cpp_examples/demo_read.cpp b/cpp/examples/cpp_examples/demo_read.cpp index efef0c9cc..f90efe350 100644 --- a/cpp/examples/cpp_examples/demo_read.cpp +++ b/cpp/examples/cpp_examples/demo_read.cpp @@ -40,8 +40,12 @@ int demo_read() { columns.emplace_back("id2"); columns.emplace_back("s1"); + auto table_schema = reader.get_table_schema(table_name); + storage::Filter* tag_filter1 = storage::TagFilterBuilder(table_schema.get()).eq("id1", "id1_filed_1"); + storage::Filter* tag_filter2 = storage::TagFilterBuilder(table_schema.get()).eq("id2", "id1_filed_2"); + storage::Filter* tag_filter = storage::TagFilterBuilder(table_schema.get()).and_filter(tag_filter1, tag_filter2); // Column vector contains the columns you want to select. - HANDLE_ERROR(reader.query(table_name, columns, 0, 100, temp_ret)); + HANDLE_ERROR(reader.query(table_name, columns, 0, 100, temp_ret, tag_filter)); // Get query handler. auto ret = dynamic_cast(temp_ret); @@ -98,5 +102,6 @@ int demo_read() { // Close reader. reader.close(); + delete tag_filter; return 0; }