diff options
6 files changed, 191 insertions, 0 deletions
diff --git a/searchlib/CMakeLists.txt b/searchlib/CMakeLists.txt index 07045684d6e..eb199a7ba44 100644 --- a/searchlib/CMakeLists.txt +++ b/searchlib/CMakeLists.txt @@ -126,6 +126,7 @@ vespa_define_module( src/tests/engine/proto_converter src/tests/engine/proto_rpc_adapter src/tests/expression/attributenode + src/tests/expression/current_index_setup src/tests/features src/tests/features/beta src/tests/features/bm25 diff --git a/searchlib/src/tests/expression/current_index_setup/CMakeLists.txt b/searchlib/src/tests/expression/current_index_setup/CMakeLists.txt new file mode 100644 index 00000000000..dc50dea4ce3 --- /dev/null +++ b/searchlib/src/tests/expression/current_index_setup/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(searchlib_current_index_setup_test_app TEST + SOURCES + current_index_setup_test.cpp + DEPENDS + searchlib + GTest::GTest +) +vespa_add_test(NAME searchlib_current_index_setup_test_app COMMAND searchlib_current_index_setup_test_app) diff --git a/searchlib/src/tests/expression/current_index_setup/current_index_setup_test.cpp b/searchlib/src/tests/expression/current_index_setup/current_index_setup_test.cpp new file mode 100644 index 00000000000..20818e576dd --- /dev/null +++ b/searchlib/src/tests/expression/current_index_setup/current_index_setup_test.cpp @@ -0,0 +1,58 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/searchlib/expression/current_index_setup.h> +#include <vespa/vespalib/gtest/gtest.h> + +using search::expression::CurrentIndex; +using search::expression::CurrentIndexSetup; + +TEST(CurrentIndexSetupTest, bound_structs_can_be_resolved) { + CurrentIndexSetup setup; + CurrentIndex foo_idx; + CurrentIndex bar_idx; + setup.bind("foo", foo_idx); + setup.bind("foo.bar", bar_idx); + EXPECT_EQ(setup.resolve("plain"), nullptr); + EXPECT_EQ(setup.resolve("foo.a"), &foo_idx); + EXPECT_EQ(setup.resolve("foo.b"), &foo_idx); + EXPECT_EQ(setup.resolve("foo.c"), &foo_idx); + EXPECT_EQ(setup.resolve("foo.bar.x"), &bar_idx); + EXPECT_EQ(setup.resolve("foo.bar.y"), &bar_idx); + EXPECT_EQ(setup.resolve("baz.f"), nullptr); + EXPECT_EQ(setup.resolve("foo.baz.f"), nullptr); +} + +TEST(CurrentIndexSetupTest, unbound_struct_usage_can_be_captured) { + CurrentIndexSetup setup; + CurrentIndexSetup::Usage usage; + CurrentIndex foo_idx; + setup.bind("foo", foo_idx); + EXPECT_FALSE(usage.has_single_unbound_struct()); + { + CurrentIndexSetup::Usage::Bind capture_guard(setup, usage); + EXPECT_EQ(setup.resolve("foo.a"), &foo_idx); + EXPECT_EQ(setup.resolve("bar.a"), nullptr); + EXPECT_EQ(setup.resolve("bar.b"), nullptr); + EXPECT_EQ(setup.resolve("plain"), nullptr); + } + EXPECT_EQ(setup.resolve("baz.a"), nullptr); + EXPECT_TRUE(usage.has_single_unbound_struct()); + EXPECT_EQ(usage.get_unbound_struct_name(), "bar"); +} + +TEST(CurrentIndexSetupTest, multi_unbound_struct_conflict_can_be_captured) { + CurrentIndexSetup setup; + CurrentIndexSetup::Usage usage; + EXPECT_FALSE(usage.has_single_unbound_struct()); + { + CurrentIndexSetup::Usage::Bind capture_guard(setup, usage); + EXPECT_FALSE(usage.has_single_unbound_struct()); + EXPECT_EQ(setup.resolve("foo.a"), nullptr); + EXPECT_TRUE(usage.has_single_unbound_struct()); + EXPECT_EQ(setup.resolve("bar.a"), nullptr); + EXPECT_FALSE(usage.has_single_unbound_struct()); + } + EXPECT_FALSE(usage.has_single_unbound_struct()); +} + +GTEST_MAIN_RUN_ALL_TESTS() diff --git a/searchlib/src/vespa/searchlib/expression/CMakeLists.txt b/searchlib/src/vespa/searchlib/expression/CMakeLists.txt index 9a3e377e5ef..6942a827fe9 100644 --- a/searchlib/src/vespa/searchlib/expression/CMakeLists.txt +++ b/searchlib/src/vespa/searchlib/expression/CMakeLists.txt @@ -4,6 +4,7 @@ vespa_add_library(searchlib_expression OBJECT attribute_map_lookup_node.cpp attributenode.cpp attributeresult.cpp + current_index_setup.cpp enumattributeresult.cpp perdocexpression.cpp expressiontree.cpp diff --git a/searchlib/src/vespa/searchlib/expression/current_index_setup.cpp b/searchlib/src/vespa/searchlib/expression/current_index_setup.cpp new file mode 100644 index 00000000000..6660728dea7 --- /dev/null +++ b/searchlib/src/vespa/searchlib/expression/current_index_setup.cpp @@ -0,0 +1,74 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "current_index_setup.h" +#include <vespa/vespalib/stllike/hash_map.hpp> +#include <cassert> + +namespace search::expression { + +void +CurrentIndexSetup::Usage::notify_unbound_struct_usage(vespalib::stringref name) +{ + _unbound.insert(name); +} + +CurrentIndexSetup::Usage::Usage() + : _unbound() +{ +} + +CurrentIndexSetup::Usage::~Usage() = default; + +vespalib::stringref +CurrentIndexSetup::Usage::get_unbound_struct_name() const +{ + assert(has_single_unbound_struct()); + return *_unbound.begin(); +} + +CurrentIndexSetup::Usage::Bind::Bind(CurrentIndexSetup &setup, Usage &usage) noexcept + : _setup(setup) +{ + auto prev = setup.capture(std::addressof(usage)); + assert(prev == nullptr); // no nesting +} + +CurrentIndexSetup::Usage::Bind::~Bind() +{ + [[maybe_unused]] auto prev = _setup.capture(nullptr); +} + +CurrentIndexSetup::CurrentIndexSetup() + : _bound(), _usage(nullptr) +{ +} + +CurrentIndexSetup::~CurrentIndexSetup() = default; + +const CurrentIndex * +CurrentIndexSetup::resolve(vespalib::stringref field_name) const +{ + size_t pos = field_name.rfind('.'); + if (pos > field_name.size()) { + return nullptr; + } + auto struct_name = field_name.substr(0, pos); + auto entry = _bound.find(struct_name); + if (entry == _bound.end()) { + if (_usage != nullptr) { + _usage->notify_unbound_struct_usage(struct_name); + } + return nullptr; + } + return entry->second; +} + +void +CurrentIndexSetup::bind(vespalib::stringref struct_name, const CurrentIndex &index) +{ + auto res = _bound.insert(std::make_pair(vespalib::string(struct_name), + std::addressof(index))); + assert(res.second); // struct must be either bound or unbound +} + +} diff --git a/searchlib/src/vespa/searchlib/expression/current_index_setup.h b/searchlib/src/vespa/searchlib/expression/current_index_setup.h new file mode 100644 index 00000000000..4f835d064bb --- /dev/null +++ b/searchlib/src/vespa/searchlib/expression/current_index_setup.h @@ -0,0 +1,48 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "currentindex.h" +#include <vespa/vespalib/stllike/string.h> +#include <vespa/vespalib/stllike/hash_map.h> +#include <vespa/vespalib/stllike/hash_set.h> +#include <utility> + +namespace search::expression { + +class CurrentIndexSetup { +public: + class Usage { + private: + friend class CurrentIndexSetup; + vespalib::hash_set<vespalib::string> _unbound; + void notify_unbound_struct_usage(vespalib::stringref name); + public: + Usage(); + ~Usage(); + [[nodiscard]] bool has_single_unbound_struct() const noexcept { + return (_unbound.size() == 1); + } + vespalib::stringref get_unbound_struct_name() const; + class Bind { + private: + CurrentIndexSetup &_setup; + public: + Bind(CurrentIndexSetup &setup, Usage &usage) noexcept; + ~Bind(); + }; + }; +private: + vespalib::hash_map<vespalib::string, const CurrentIndex *> _bound; + Usage *_usage; + [[nodiscard]] Usage *capture(Usage *usage) noexcept { + return std::exchange(_usage, usage); + } +public: + CurrentIndexSetup(); + ~CurrentIndexSetup(); + [[nodiscard]] const CurrentIndex *resolve(vespalib::stringref field_name) const; + void bind(vespalib::stringref struct_name, const CurrentIndex &index); +}; + +} |