This is an automated email from the ASF dual-hosted git repository.
westonpace pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/master by this push:
new 4ade394a7a ARROW-16677: [C++] Support nesting of function registries
(#13252)
4ade394a7a is described below
commit 4ade394a7a0fa22ecb8ef2e3b4dc8bb42e599274
Author: rtpsw <[email protected]>
AuthorDate: Sat Jun 18 06:16:49 2022 +0300
ARROW-16677: [C++] Support nesting of function registries (#13252)
Currently, only a default function-registry is supported. Modifying this
registry has global effects, which is often undesirable. Support for nesting
function-registries will provide scoping for such modifications.
Authored-by: Yaron Gvili <[email protected]>
Signed-off-by: Weston Pace <[email protected]>
---
cpp/src/arrow/compute/registry.cc | 177 +++++++++++++++++++++++++++------
cpp/src/arrow/compute/registry.h | 61 +++++++++---
cpp/src/arrow/compute/registry_test.cc | 169 ++++++++++++++++++++++++++++---
3 files changed, 351 insertions(+), 56 deletions(-)
diff --git a/cpp/src/arrow/compute/registry.cc
b/cpp/src/arrow/compute/registry.cc
index 7e1975d3b6..fe7c6fa8ad 100644
--- a/cpp/src/arrow/compute/registry.cc
+++ b/cpp/src/arrow/compute/registry.cc
@@ -34,52 +34,62 @@ namespace compute {
class FunctionRegistry::FunctionRegistryImpl {
public:
- Status AddFunction(std::shared_ptr<Function> function, bool allow_overwrite)
{
-#ifndef NDEBUG
- // This validates docstrings extensively, so don't waste time on it
- // in release builds.
- RETURN_NOT_OK(function->Validate());
-#endif
+ explicit FunctionRegistryImpl(FunctionRegistryImpl* parent = NULLPTR)
+ : parent_(parent) {}
+ ~FunctionRegistryImpl() {}
- std::lock_guard<std::mutex> mutation_guard(lock_);
+ Status CanAddFunction(std::shared_ptr<Function> function, bool
allow_overwrite) {
+ if (parent_ != NULLPTR) {
+ RETURN_NOT_OK(parent_->CanAddFunction(function, allow_overwrite));
+ }
+ return DoAddFunction(function, allow_overwrite, /*add=*/false);
+ }
- const std::string& name = function->name();
- auto it = name_to_function_.find(name);
- if (it != name_to_function_.end() && !allow_overwrite) {
- return Status::KeyError("Already have a function registered with name:
", name);
+ Status AddFunction(std::shared_ptr<Function> function, bool allow_overwrite)
{
+ if (parent_ != NULLPTR) {
+ RETURN_NOT_OK(parent_->CanAddFunction(function, allow_overwrite));
}
- name_to_function_[name] = std::move(function);
- return Status::OK();
+ return DoAddFunction(function, allow_overwrite, /*add=*/true);
+ }
+
+ Status CanAddAlias(const std::string& target_name, const std::string&
source_name) {
+ if (parent_ != NULLPTR) {
+ RETURN_NOT_OK(parent_->CanAddFunctionName(target_name,
+ /*allow_overwrite=*/false));
+ }
+ return DoAddAlias(target_name, source_name, /*add=*/false);
}
Status AddAlias(const std::string& target_name, const std::string&
source_name) {
- std::lock_guard<std::mutex> mutation_guard(lock_);
+ if (parent_ != NULLPTR) {
+ RETURN_NOT_OK(parent_->CanAddFunctionName(target_name,
+ /*allow_overwrite=*/false));
+ }
+ return DoAddAlias(target_name, source_name, /*add=*/true);
+ }
- auto it = name_to_function_.find(source_name);
- if (it == name_to_function_.end()) {
- return Status::KeyError("No function registered with name: ",
source_name);
+ Status CanAddFunctionOptionsType(const FunctionOptionsType* options_type,
+ bool allow_overwrite = false) {
+ if (parent_ != NULLPTR) {
+ RETURN_NOT_OK(parent_->CanAddFunctionOptionsType(options_type,
allow_overwrite));
}
- name_to_function_[target_name] = it->second;
- return Status::OK();
+ return DoAddFunctionOptionsType(options_type, allow_overwrite,
/*add=*/false);
}
Status AddFunctionOptionsType(const FunctionOptionsType* options_type,
bool allow_overwrite = false) {
- std::lock_guard<std::mutex> mutation_guard(lock_);
-
- const std::string name = options_type->type_name();
- auto it = name_to_options_type_.find(name);
- if (it != name_to_options_type_.end() && !allow_overwrite) {
- return Status::KeyError(
- "Already have a function options type registered with name: ", name);
+ if (parent_ != NULLPTR) {
+ RETURN_NOT_OK(parent_->CanAddFunctionOptionsType(options_type,
allow_overwrite));
}
- name_to_options_type_[name] = options_type;
- return Status::OK();
+ return DoAddFunctionOptionsType(options_type, allow_overwrite,
/*add=*/true);
}
Result<std::shared_ptr<Function>> GetFunction(const std::string& name) const
{
auto it = name_to_function_.find(name);
if (it == name_to_function_.end()) {
+ if (parent_ != NULLPTR) {
+ return parent_->GetFunction(name);
+ }
return Status::KeyError("No function registered with name: ", name);
}
return it->second;
@@ -87,6 +97,9 @@ class FunctionRegistry::FunctionRegistryImpl {
std::vector<std::string> GetFunctionNames() const {
std::vector<std::string> results;
+ if (parent_ != NULLPTR) {
+ results = parent_->GetFunctionNames();
+ }
for (auto it : name_to_function_) {
results.push_back(it.first);
}
@@ -98,14 +111,96 @@ class FunctionRegistry::FunctionRegistryImpl {
const std::string& name) const {
auto it = name_to_options_type_.find(name);
if (it == name_to_options_type_.end()) {
+ if (parent_ != NULLPTR) {
+ return parent_->GetFunctionOptionsType(name);
+ }
return Status::KeyError("No function options type registered with name:
", name);
}
return it->second;
}
- int num_functions() const { return
static_cast<int>(name_to_function_.size()); }
+ int num_functions() const {
+ return (parent_ == NULLPTR ? 0 : parent_->num_functions()) +
+ static_cast<int>(name_to_function_.size());
+ }
private:
+ // must not acquire mutex
+ Status CanAddFunctionName(const std::string& name, bool allow_overwrite) {
+ if (parent_ != NULLPTR) {
+ RETURN_NOT_OK(parent_->CanAddFunctionName(name, allow_overwrite));
+ }
+ if (!allow_overwrite) {
+ auto it = name_to_function_.find(name);
+ if (it != name_to_function_.end()) {
+ return Status::KeyError("Already have a function registered with name:
", name);
+ }
+ }
+ return Status::OK();
+ }
+
+ // must not acquire mutex
+ Status CanAddOptionsTypeName(const std::string& name, bool allow_overwrite) {
+ if (parent_ != NULLPTR) {
+ RETURN_NOT_OK(parent_->CanAddOptionsTypeName(name, allow_overwrite));
+ }
+ if (!allow_overwrite) {
+ auto it = name_to_options_type_.find(name);
+ if (it != name_to_options_type_.end()) {
+ return Status::KeyError(
+ "Already have a function options type registered with name: ",
name);
+ }
+ }
+ return Status::OK();
+ }
+
+ Status DoAddFunction(std::shared_ptr<Function> function, bool
allow_overwrite,
+ bool add) {
+#ifndef NDEBUG
+ // This validates docstrings extensively, so don't waste time on it
+ // in release builds.
+ RETURN_NOT_OK(function->Validate());
+#endif
+
+ std::lock_guard<std::mutex> mutation_guard(lock_);
+
+ const std::string& name = function->name();
+ RETURN_NOT_OK(CanAddFunctionName(name, allow_overwrite));
+ if (add) {
+ name_to_function_[name] = std::move(function);
+ }
+ return Status::OK();
+ }
+
+ Status DoAddAlias(const std::string& target_name, const std::string&
source_name,
+ bool add) {
+ // source name must exist in this registry or the parent
+ // check outside mutex, in case GetFunction leads to mutex acquisition
+ ARROW_ASSIGN_OR_RAISE(auto func, GetFunction(source_name));
+
+ std::lock_guard<std::mutex> mutation_guard(lock_);
+
+ // target name must be available in this registry and the parent
+ RETURN_NOT_OK(CanAddFunctionName(target_name, /*allow_overwrite=*/false));
+ if (add) {
+ name_to_function_[target_name] = func;
+ }
+ return Status::OK();
+ }
+
+ Status DoAddFunctionOptionsType(const FunctionOptionsType* options_type,
+ bool allow_overwrite, bool add) {
+ std::lock_guard<std::mutex> mutation_guard(lock_);
+
+ const std::string name = options_type->type_name();
+ RETURN_NOT_OK(CanAddOptionsTypeName(name, /*allow_overwrite=*/false));
+ if (add) {
+ name_to_options_type_[options_type->type_name()] = options_type;
+ }
+ return Status::OK();
+ }
+
+ FunctionRegistryImpl* parent_;
std::mutex lock_;
std::unordered_map<std::string, std::shared_ptr<Function>> name_to_function_;
std::unordered_map<std::string, const FunctionOptionsType*>
name_to_options_type_;
@@ -115,20 +210,42 @@ std::unique_ptr<FunctionRegistry>
FunctionRegistry::Make() {
return std::unique_ptr<FunctionRegistry>(new FunctionRegistry());
}
-FunctionRegistry::FunctionRegistry() { impl_.reset(new
FunctionRegistryImpl()); }
+std::unique_ptr<FunctionRegistry> FunctionRegistry::Make(FunctionRegistry*
parent) {
+ return std::unique_ptr<FunctionRegistry>(new FunctionRegistry(
+ new FunctionRegistry::FunctionRegistryImpl(parent->impl_.get())));
+}
+
+FunctionRegistry::FunctionRegistry() : FunctionRegistry(new
FunctionRegistryImpl()) {}
+
+FunctionRegistry::FunctionRegistry(FunctionRegistryImpl* impl) {
impl_.reset(impl); }
FunctionRegistry::~FunctionRegistry() {}
+Status FunctionRegistry::CanAddFunction(std::shared_ptr<Function> function,
+ bool allow_overwrite) {
+ return impl_->CanAddFunction(std::move(function), allow_overwrite);
+}
+
Status FunctionRegistry::AddFunction(std::shared_ptr<Function> function,
bool allow_overwrite) {
return impl_->AddFunction(std::move(function), allow_overwrite);
}
+Status FunctionRegistry::CanAddAlias(const std::string& target_name,
+ const std::string& source_name) {
+ return impl_->CanAddAlias(target_name, source_name);
+}
+
Status FunctionRegistry::AddAlias(const std::string& target_name,
const std::string& source_name) {
return impl_->AddAlias(target_name, source_name);
}
+Status FunctionRegistry::CanAddFunctionOptionsType(
+ const FunctionOptionsType* options_type, bool allow_overwrite) {
+ return impl_->CanAddFunctionOptionsType(options_type, allow_overwrite);
+}
+
Status FunctionRegistry::AddFunctionOptionsType(const FunctionOptionsType*
options_type,
bool allow_overwrite) {
return impl_->AddFunctionOptionsType(options_type, allow_overwrite);
diff --git a/cpp/src/arrow/compute/registry.h b/cpp/src/arrow/compute/registry.h
index e83036db6a..16a7625594 100644
--- a/cpp/src/arrow/compute/registry.h
+++ b/cpp/src/arrow/compute/registry.h
@@ -47,35 +47,64 @@ class ARROW_EXPORT FunctionRegistry {
public:
~FunctionRegistry();
- /// \brief Construct a new registry. Most users only need to use the global
- /// registry
+ /// \brief Construct a new registry.
+ ///
+ /// Most users only need to use the global registry.
static std::unique_ptr<FunctionRegistry> Make();
- /// \brief Add a new function to the registry. Returns Status::KeyError if a
- /// function with the same name is already registered
+ /// \brief Construct a new nested registry with the given parent.
+ ///
+ /// Most users only need to use the global registry. The returned registry
never changes
+ /// its parent, even when an operation allows overwritting.
+ static std::unique_ptr<FunctionRegistry> Make(FunctionRegistry* parent);
+
+ /// \brief Check whether a new function can be added to the registry.
+ ///
+ /// \returns Status::KeyError if a function with the same name is already
registered.
+ Status CanAddFunction(std::shared_ptr<Function> function, bool
allow_overwrite = false);
+
+ /// \brief Add a new function to the registry.
+ ///
+ /// \returns Status::KeyError if a function with the same name is already
registered.
Status AddFunction(std::shared_ptr<Function> function, bool allow_overwrite
= false);
- /// \brief Add aliases for the given function name. Returns Status::KeyError
if the
- /// function with the given name is not registered
+ /// \brief Check whether an alias can be added for the given function name.
+ ///
+ /// \returns Status::KeyError if the function with the given name is not
registered.
+ Status CanAddAlias(const std::string& target_name, const std::string&
source_name);
+
+ /// \brief Add alias for the given function name.
+ ///
+ /// \returns Status::KeyError if the function with the given name is not
registered.
Status AddAlias(const std::string& target_name, const std::string&
source_name);
- /// \brief Add a new function options type to the registry. Returns
Status::KeyError if
- /// a function options type with the same name is already registered
+ /// \brief Check whether a new function options type can be added to the
registry.
+ ///
+ /// \returns Status::KeyError if a function options type with the same name
is already
+ /// registered.
+ Status CanAddFunctionOptionsType(const FunctionOptionsType* options_type,
+ bool allow_overwrite = false);
+
+ /// \brief Add a new function options type to the registry.
+ ///
+ /// \returns Status::KeyError if a function options type with the same name
is already
+ /// registered.
Status AddFunctionOptionsType(const FunctionOptionsType* options_type,
bool allow_overwrite = false);
- /// \brief Retrieve a function by name from the registry
+ /// \brief Retrieve a function by name from the registry.
Result<std::shared_ptr<Function>> GetFunction(const std::string& name) const;
- /// \brief Return vector of all entry names in the registry. Helpful for
- /// displaying a manifest of available functions
+ /// \brief Return vector of all entry names in the registry.
+ ///
+ /// Helpful for displaying a manifest of available functions.
std::vector<std::string> GetFunctionNames() const;
- /// \brief Retrieve a function options type by name from the registry
+ /// \brief Retrieve a function options type by name from the registry.
Result<const FunctionOptionsType*> GetFunctionOptionsType(
const std::string& name) const;
- /// \brief The number of currently registered functions
+ /// \brief The number of currently registered functions.
int num_functions() const;
private:
@@ -84,9 +113,13 @@ class ARROW_EXPORT FunctionRegistry {
// Use PIMPL pattern to not have std::unordered_map here
class FunctionRegistryImpl;
std::unique_ptr<FunctionRegistryImpl> impl_;
+
+ explicit FunctionRegistry(FunctionRegistryImpl* impl);
+
+ class NestedFunctionRegistryImpl;
};
-/// \brief Return the process-global function registry
+/// \brief Return the process-global function registry.
ARROW_EXPORT FunctionRegistry* GetFunctionRegistry();
} // namespace compute
diff --git a/cpp/src/arrow/compute/registry_test.cc
b/cpp/src/arrow/compute/registry_test.cc
index faf47a46f6..937515af4a 100644
--- a/cpp/src/arrow/compute/registry_test.cc
+++ b/cpp/src/arrow/compute/registry_test.cc
@@ -27,37 +27,44 @@
#include "arrow/status.h"
#include "arrow/testing/gtest_util.h"
#include "arrow/util/macros.h"
+#include "arrow/util/make_unique.h"
namespace arrow {
namespace compute {
-class TestRegistry : public ::testing::Test {
- public:
- void SetUp() { registry_ = FunctionRegistry::Make(); }
+using MakeFunctionRegistry =
std::function<std::unique_ptr<FunctionRegistry>()>;
+using GetNumFunctions = std::function<int()>;
+using GetFunctionNames = std::function<std::vector<std::string>()>;
+using TestRegistryParams =
+ std::tuple<MakeFunctionRegistry, GetNumFunctions, GetFunctionNames,
std::string>;
- protected:
- std::unique_ptr<FunctionRegistry> registry_;
-};
+struct TestRegistry : public ::testing::TestWithParam<TestRegistryParams> {};
-TEST_F(TestRegistry, CreateBuiltInRegistry) {
+TEST(TestRegistry, CreateBuiltInRegistry) {
// This does DCHECK_OK internally for now so this will fail in debug builds
// if there is a problem initializing the global function registry
FunctionRegistry* registry = GetFunctionRegistry();
ARROW_UNUSED(registry);
}
-TEST_F(TestRegistry, Basics) {
- ASSERT_EQ(0, registry_->num_functions());
+TEST_P(TestRegistry, Basics) {
+ auto registry_factory = std::get<0>(GetParam());
+ auto registry_ = registry_factory();
+ auto get_num_funcs = std::get<1>(GetParam());
+ int n_funcs = get_num_funcs();
+ auto get_func_names = std::get<2>(GetParam());
+ std::vector<std::string> func_names = get_func_names();
+ ASSERT_EQ(n_funcs, registry_->num_functions());
std::shared_ptr<Function> func = std::make_shared<ScalarFunction>(
"f1", Arity::Unary(), /*doc=*/FunctionDoc::Empty());
ASSERT_OK(registry_->AddFunction(func));
- ASSERT_EQ(1, registry_->num_functions());
+ ASSERT_EQ(n_funcs + 1, registry_->num_functions());
func = std::make_shared<VectorFunction>("f0", Arity::Binary(),
/*doc=*/FunctionDoc::Empty());
ASSERT_OK(registry_->AddFunction(func));
- ASSERT_EQ(2, registry_->num_functions());
+ ASSERT_EQ(n_funcs + 2, registry_->num_functions());
ASSERT_OK_AND_ASSIGN(std::shared_ptr<const Function> f1,
registry_->GetFunction("f1"));
ASSERT_EQ("f1", f1->name());
@@ -75,7 +82,11 @@ TEST_F(TestRegistry, Basics) {
ASSERT_OK_AND_ASSIGN(f1, registry_->GetFunction("f1"));
ASSERT_EQ(Function::SCALAR_AGGREGATE, f1->kind());
- std::vector<std::string> expected_names = {"f0", "f1"};
+ std::vector<std::string> expected_names(func_names);
+ for (auto name : {"f0", "f1"}) {
+ expected_names.push_back(name);
+ }
+ std::sort(expected_names.begin(), expected_names.end());
ASSERT_EQ(expected_names, registry_->GetFunctionNames());
// Aliases
@@ -85,5 +96,139 @@ TEST_F(TestRegistry, Basics) {
ASSERT_EQ(func, f2);
}
+INSTANTIATE_TEST_SUITE_P(
+ TestRegistry, TestRegistry,
+ testing::Values(
+ std::make_tuple(
+ static_cast<MakeFunctionRegistry>([]() { return
FunctionRegistry::Make(); }),
+ []() { return 0; }, []() { return std::vector<std::string>{}; },
"default"),
+ std::make_tuple(
+ static_cast<MakeFunctionRegistry>([]() {
+ return FunctionRegistry::Make(GetFunctionRegistry());
+ }),
+ []() { return GetFunctionRegistry()->num_functions(); },
+ []() { return GetFunctionRegistry()->GetFunctionNames(); },
"nested")));
+
+TEST(TestRegistry, RegisterTempFunctions) {
+ auto default_registry = GetFunctionRegistry();
+ constexpr int rounds = 3;
+ for (int i = 0; i < rounds; i++) {
+ auto registry = FunctionRegistry::Make(default_registry);
+ for (std::string func_name : {"f1", "f2"}) {
+ std::shared_ptr<Function> func = std::make_shared<ScalarFunction>(
+ func_name, Arity::Unary(), /*doc=*/FunctionDoc::Empty());
+ ASSERT_OK(registry->CanAddFunction(func));
+ ASSERT_OK(registry->AddFunction(func));
+ ASSERT_RAISES(KeyError, registry->CanAddFunction(func));
+ ASSERT_RAISES(KeyError, registry->AddFunction(func));
+ ASSERT_OK(default_registry->CanAddFunction(func));
+ }
+ }
+}
+
+TEST(TestRegistry, RegisterTempAliases) {
+ auto default_registry = GetFunctionRegistry();
+ std::vector<std::string> func_names = default_registry->GetFunctionNames();
+ constexpr int rounds = 3;
+ for (int i = 0; i < rounds; i++) {
+ auto registry = FunctionRegistry::Make(default_registry);
+ for (std::string func_name : func_names) {
+ std::string alias_name = "alias_of_" + func_name;
+ std::shared_ptr<Function> func = std::make_shared<ScalarFunction>(
+ func_name, Arity::Unary(), /*doc=*/FunctionDoc::Empty());
+ ASSERT_RAISES(KeyError, registry->GetFunction(alias_name));
+ ASSERT_OK(registry->CanAddAlias(alias_name, func_name));
+ ASSERT_OK(registry->AddAlias(alias_name, func_name));
+ ASSERT_OK(registry->GetFunction(alias_name));
+ ASSERT_OK(default_registry->GetFunction(func_name));
+ ASSERT_RAISES(KeyError, default_registry->GetFunction(alias_name));
+ }
+ }
+}
+
+template <int kExampleSeqNum>
+class ExampleOptions : public FunctionOptions {
+ public:
+ explicit ExampleOptions(std::shared_ptr<Scalar> value);
+ std::shared_ptr<Scalar> value;
+};
+
+template <int kExampleSeqNum>
+class ExampleOptionsType : public FunctionOptionsType {
+ public:
+ static const FunctionOptionsType* GetInstance() {
+ static std::unique_ptr<FunctionOptionsType> instance(
+ new ExampleOptionsType<kExampleSeqNum>());
+ return instance.get();
+ }
+ const char* type_name() const override {
+ static std::string name = std::string("example") +
std::to_string(kExampleSeqNum);
+ return name.c_str();
+ }
+ std::string Stringify(const FunctionOptions& options) const override {
+ return type_name();
+ }
+ bool Compare(const FunctionOptions& options,
+ const FunctionOptions& other) const override {
+ return true;
+ }
+ std::unique_ptr<FunctionOptions> Copy(const FunctionOptions& options) const
override {
+ const auto& opts = static_cast<const
ExampleOptions<kExampleSeqNum>&>(options);
+ return
arrow::internal::make_unique<ExampleOptions<kExampleSeqNum>>(opts.value);
+ }
+};
+template <int kExampleSeqNum>
+ExampleOptions<kExampleSeqNum>::ExampleOptions(std::shared_ptr<Scalar> value)
+ : FunctionOptions(ExampleOptionsType<kExampleSeqNum>::GetInstance()),
+ value(std::move(value)) {}
+
+TEST(TestRegistry, RegisterTempFunctionOptionsType) {
+ auto default_registry = GetFunctionRegistry();
+ std::vector<const FunctionOptionsType*> options_types = {
+ ExampleOptionsType<1>::GetInstance(),
+ ExampleOptionsType<2>::GetInstance(),
+ };
+ constexpr int rounds = 3;
+ for (int i = 0; i < rounds; i++) {
+ auto registry = FunctionRegistry::Make(default_registry);
+ for (auto options_type : options_types) {
+ ASSERT_OK(registry->CanAddFunctionOptionsType(options_type));
+ ASSERT_OK(registry->AddFunctionOptionsType(options_type));
+ ASSERT_RAISES(KeyError,
registry->CanAddFunctionOptionsType(options_type));
+ ASSERT_RAISES(KeyError, registry->AddFunctionOptionsType(options_type));
+ ASSERT_OK(default_registry->CanAddFunctionOptionsType(options_type));
+ }
+ }
+}
+
+TEST(TestRegistry, RegisterNestedFunctions) {
+ auto default_registry = GetFunctionRegistry();
+ std::shared_ptr<Function> func1 = std::make_shared<ScalarFunction>(
+ "f1", Arity::Unary(), /*doc=*/FunctionDoc::Empty());
+ std::shared_ptr<Function> func2 = std::make_shared<ScalarFunction>(
+ "f2", Arity::Unary(), /*doc=*/FunctionDoc::Empty());
+ constexpr int rounds = 3;
+ for (int i = 0; i < rounds; i++) {
+ auto registry1 = FunctionRegistry::Make(default_registry);
+
+ ASSERT_OK(registry1->CanAddFunction(func1));
+ ASSERT_OK(registry1->AddFunction(func1));
+
+ for (int j = 0; j < rounds; j++) {
+ auto registry2 = FunctionRegistry::Make(registry1.get());
+
+ ASSERT_OK(registry2->CanAddFunction(func2));
+ ASSERT_OK(registry2->AddFunction(func2));
+ ASSERT_RAISES(KeyError, registry2->CanAddFunction(func2));
+ ASSERT_RAISES(KeyError, registry2->AddFunction(func2));
+ ASSERT_OK(default_registry->CanAddFunction(func2));
+ }
+
+ ASSERT_RAISES(KeyError, registry1->CanAddFunction(func1));
+ ASSERT_RAISES(KeyError, registry1->AddFunction(func1));
+ ASSERT_OK(default_registry->CanAddFunction(func1));
+ }
+}
+
} // namespace compute
} // namespace arrow