blob: 4610843d882d3d30eaebafb6dc80c971c7a271ab [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 "storage/index/analyzer_key_matcher.h"
#include <gtest/gtest.h>
#include "storage/index/inverted/inverted_index_iterator.h"
namespace doris::segment_v2 {
// New design:
// - Empty string "" means "user did not specify" (BE auto-selects index)
// - Non-empty string means "user specified this analyzer" (exact match)
// - "__default__" is no longer special - it's just another explicit key
class AnalyzerKeyMatcherTest : public testing::Test {
protected:
void SetUp() override {
// Create test entries
entries_.push_back({InvertedIndexReaderType::FULLTEXT, "chinese", nullptr});
entries_.push_back({InvertedIndexReaderType::FULLTEXT, "english", nullptr});
entries_.push_back({InvertedIndexReaderType::STRING_TYPE, "", nullptr}); // empty key reader
// Build index
for (size_t i = 0; i < entries_.size(); ++i) {
key_index_[entries_[i].analyzer_key].push_back(i);
}
}
std::vector<ReaderEntry> entries_;
std::unordered_map<std::string, std::vector<size_t>> key_index_;
};
// Test exact match
TEST_F(AnalyzerKeyMatcherTest, ExactMatch) {
auto result = AnalyzerKeyMatcher::match("chinese", entries_, key_index_);
EXPECT_FALSE(result.empty());
EXPECT_EQ(result.size(), 1);
EXPECT_EQ(result.candidates[0]->analyzer_key, "chinese");
EXPECT_FALSE(result.used_fallback);
}
TEST_F(AnalyzerKeyMatcherTest, ExactMatchEnglish) {
auto result = AnalyzerKeyMatcher::match("english", entries_, key_index_);
EXPECT_FALSE(result.empty());
EXPECT_EQ(result.size(), 1);
EXPECT_EQ(result.candidates[0]->analyzer_key, "english");
EXPECT_FALSE(result.used_fallback);
}
// Test empty string returns all readers for query-type-based selection
TEST_F(AnalyzerKeyMatcherTest, EmptyKeyReturnsAll) {
auto result = AnalyzerKeyMatcher::match("", entries_, key_index_);
// Empty string means "no specific analyzer requested", so return all readers
// to allow query-type-based selection (e.g., FULLTEXT for MATCH, STRING_TYPE for EQUAL)
EXPECT_FALSE(result.empty());
EXPECT_EQ(result.size(), 3);
EXPECT_TRUE(result.used_fallback);
}
// Test explicit key with no match returns empty
TEST_F(AnalyzerKeyMatcherTest, ExplicitKeyNoMatchReturnsEmpty) {
auto result = AnalyzerKeyMatcher::match("japanese", entries_, key_index_);
EXPECT_TRUE(result.empty());
EXPECT_FALSE(result.used_fallback);
}
// Test is_explicit
TEST_F(AnalyzerKeyMatcherTest, IsExplicit) {
EXPECT_TRUE(AnalyzerKeyMatcher::is_explicit("chinese"));
EXPECT_TRUE(AnalyzerKeyMatcher::is_explicit("english"));
EXPECT_TRUE(AnalyzerKeyMatcher::is_explicit("none")); // "none" is explicit
EXPECT_TRUE(AnalyzerKeyMatcher::is_explicit(
"__default__")); // "__default__" is explicit (just another key)
EXPECT_FALSE(AnalyzerKeyMatcher::is_explicit("")); // Only empty is not explicit
}
// Test allows_fallback
TEST_F(AnalyzerKeyMatcherTest, AllowsFallback) {
EXPECT_TRUE(AnalyzerKeyMatcher::allows_fallback("")); // Only empty allows fallback
EXPECT_FALSE(AnalyzerKeyMatcher::allows_fallback("chinese"));
EXPECT_FALSE(AnalyzerKeyMatcher::allows_fallback("english"));
EXPECT_FALSE(AnalyzerKeyMatcher::allows_fallback("none"));
EXPECT_FALSE(AnalyzerKeyMatcher::allows_fallback("__default__")); // Not special anymore
}
// Test empty entries
TEST_F(AnalyzerKeyMatcherTest, EmptyEntries) {
std::vector<ReaderEntry> empty_entries;
std::unordered_map<std::string, std::vector<size_t>> empty_index;
auto result = AnalyzerKeyMatcher::match("chinese", empty_entries, empty_index);
EXPECT_TRUE(result.empty());
}
// Test multiple entries with same analyzer key
class AnalyzerKeyMatcherMultiEntryTest : public testing::Test {
protected:
void SetUp() override {
// Multiple readers with same analyzer key (different types)
entries_.push_back({InvertedIndexReaderType::FULLTEXT, "chinese", nullptr});
entries_.push_back({InvertedIndexReaderType::STRING_TYPE, "chinese", nullptr});
for (size_t i = 0; i < entries_.size(); ++i) {
key_index_[entries_[i].analyzer_key].push_back(i);
}
}
std::vector<ReaderEntry> entries_;
std::unordered_map<std::string, std::vector<size_t>> key_index_;
};
TEST_F(AnalyzerKeyMatcherMultiEntryTest, MatchReturnsAllWithSameKey) {
auto result = AnalyzerKeyMatcher::match("chinese", entries_, key_index_);
EXPECT_FALSE(result.empty());
EXPECT_EQ(result.size(), 2);
EXPECT_FALSE(result.used_fallback);
// Both should be "chinese"
for (const auto* entry : result.candidates) {
EXPECT_EQ(entry->analyzer_key, "chinese");
}
}
// Test empty key fallback when no exact match
class AnalyzerKeyMatcherFallbackTest : public testing::Test {
protected:
void SetUp() override {
// Only specific entries (no empty key)
entries_.push_back({InvertedIndexReaderType::FULLTEXT, "chinese", nullptr});
entries_.push_back({InvertedIndexReaderType::FULLTEXT, "english", nullptr});
for (size_t i = 0; i < entries_.size(); ++i) {
key_index_[entries_[i].analyzer_key].push_back(i);
}
}
std::vector<ReaderEntry> entries_;
std::unordered_map<std::string, std::vector<size_t>> key_index_;
};
TEST_F(AnalyzerKeyMatcherFallbackTest, EmptyKeyFallbacksToAll) {
auto result = AnalyzerKeyMatcher::match("", entries_, key_index_);
// When empty string is requested, fallback to all readers
EXPECT_FALSE(result.empty());
EXPECT_EQ(result.size(), 2);
EXPECT_TRUE(result.used_fallback);
}
TEST_F(AnalyzerKeyMatcherFallbackTest, ExplicitKeyNoFallback) {
auto result = AnalyzerKeyMatcher::match("japanese", entries_, key_index_);
// Explicit key (not empty) should not fallback
EXPECT_TRUE(result.empty());
EXPECT_FALSE(result.used_fallback);
}
// Test "none" analyzer is an explicit key (no tokenization index)
class AnalyzerKeyMatcherNoneAnalyzerTest : public testing::Test {
protected:
void SetUp() override {
// Entry with empty key (default/no properties) and entry with "none" key
entries_.push_back({InvertedIndexReaderType::STRING_TYPE, "", nullptr});
entries_.push_back(
{InvertedIndexReaderType::STRING_TYPE, INVERTED_INDEX_PARSER_NONE, nullptr});
for (size_t i = 0; i < entries_.size(); ++i) {
key_index_[entries_[i].analyzer_key].push_back(i);
}
}
std::vector<ReaderEntry> entries_;
std::unordered_map<std::string, std::vector<size_t>> key_index_;
};
TEST_F(AnalyzerKeyMatcherNoneAnalyzerTest, NoneIsDistinctFromEmpty) {
// "none" is an explicit analyzer key, should do exact match
auto result_none = AnalyzerKeyMatcher::match(INVERTED_INDEX_PARSER_NONE, entries_, key_index_);
EXPECT_FALSE(result_none.empty());
EXPECT_EQ(result_none.size(), 1);
EXPECT_EQ(result_none.candidates[0]->analyzer_key, INVERTED_INDEX_PARSER_NONE);
EXPECT_FALSE(result_none.used_fallback);
// Empty string means "no specific analyzer", should return all readers
auto result_empty = AnalyzerKeyMatcher::match("", entries_, key_index_);
EXPECT_FALSE(result_empty.empty());
EXPECT_EQ(result_empty.size(), 2); // Should return both entries
EXPECT_TRUE(result_empty.used_fallback);
}
TEST_F(AnalyzerKeyMatcherNoneAnalyzerTest, NoneIsExplicit) {
// "none" is treated as an explicit analyzer key
EXPECT_TRUE(AnalyzerKeyMatcher::is_explicit(INVERTED_INDEX_PARSER_NONE));
EXPECT_FALSE(AnalyzerKeyMatcher::allows_fallback(INVERTED_INDEX_PARSER_NONE));
}
} // namespace doris::segment_v2