From a268cff333585a3a9773fe7f55e446659f8db9da Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 26 Jan 2026 14:47:48 -0600 Subject: [PATCH 01/13] CXX-3236 Make options parameter moveable --- src/mongocxx/include/mongocxx/v1/pool.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mongocxx/include/mongocxx/v1/pool.hpp b/src/mongocxx/include/mongocxx/v1/pool.hpp index f2422dde2c..70c1449e57 100644 --- a/src/mongocxx/include/mongocxx/v1/pool.hpp +++ b/src/mongocxx/include/mongocxx/v1/pool.hpp @@ -97,7 +97,7 @@ class pool { /// @throws mongocxx::v1::exception if a client-side error is encountered. /// /// @{ - explicit MONGOCXX_ABI_EXPORT_CDECL() pool(v1::uri const& uri, options const& opts); + explicit MONGOCXX_ABI_EXPORT_CDECL() pool(v1::uri const& uri, options opts); explicit MONGOCXX_ABI_EXPORT_CDECL() pool(v1::uri const& uri); /// @} From 1e595e5108e8c44876d7271ceaa7228ba2c593dc Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 26 Jan 2026 14:47:48 -0600 Subject: [PATCH 02/13] CXX-3236 Fix special member function decls --- src/mongocxx/include/mongocxx/v1/pool.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mongocxx/include/mongocxx/v1/pool.hpp b/src/mongocxx/include/mongocxx/v1/pool.hpp index 70c1449e57..ddc7adf12e 100644 --- a/src/mongocxx/include/mongocxx/v1/pool.hpp +++ b/src/mongocxx/include/mongocxx/v1/pool.hpp @@ -80,14 +80,14 @@ class pool { MONGOCXX_ABI_EXPORT_CDECL(pool&) operator=(pool&& other) noexcept; /// - /// Copy construction. + /// This class is immovable. /// - MONGOCXX_ABI_EXPORT_CDECL() pool(pool const& other); + pool(pool const& other) = delete; /// - /// Copy assignment. + /// This class is immovable. /// - MONGOCXX_ABI_EXPORT_CDECL(pool&) operator=(pool const& other); + pool& operator=(pool const& other) = delete; /// /// Initialize this pool with the given URI. @@ -262,14 +262,14 @@ class pool::entry { MONGOCXX_ABI_EXPORT_CDECL(entry&) operator=(entry&& other) noexcept; /// - /// Copy construction. + /// This class is not copyable. /// - MONGOCXX_ABI_EXPORT_CDECL() entry(entry const& other); + entry(entry const& other) = delete; /// - /// Copy assignment. + /// This class is not copyable. /// - MONGOCXX_ABI_EXPORT_CDECL(entry&) operator=(entry const& other); + entry& operator=(entry const& other) = delete; /// /// Access the managed client. From 96070a3226f44e54dd3cb05cea6b1fc17e9df9f6 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 26 Jan 2026 14:47:49 -0600 Subject: [PATCH 03/13] CXX-3236 use string_view for database name parameters --- src/mongocxx/include/mongocxx/v1/pool.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mongocxx/include/mongocxx/v1/pool.hpp b/src/mongocxx/include/mongocxx/v1/pool.hpp index ddc7adf12e..d0e48409e8 100644 --- a/src/mongocxx/include/mongocxx/v1/pool.hpp +++ b/src/mongocxx/include/mongocxx/v1/pool.hpp @@ -24,12 +24,12 @@ #include #include +#include #include // IWYU pragma: export #include #include -#include #include namespace mongocxx { @@ -297,7 +297,7 @@ class pool::entry { /// /// Equivalent to `(*this)->database(name)`. /// - mongocxx::v1::database operator[](std::string name); + v1::database operator[](bsoncxx::v1::stdx::string_view name); }; } // namespace v1 From d79b77c22b10c1424238ffbe3f1e07b6247a5edd Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 26 Jan 2026 14:47:49 -0600 Subject: [PATCH 04/13] CXX-3236 Add `.database()` for consistency with v1::client --- src/mongocxx/include/mongocxx/v1/pool.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mongocxx/include/mongocxx/v1/pool.hpp b/src/mongocxx/include/mongocxx/v1/pool.hpp index d0e48409e8..4230765d9d 100644 --- a/src/mongocxx/include/mongocxx/v1/pool.hpp +++ b/src/mongocxx/include/mongocxx/v1/pool.hpp @@ -297,7 +297,11 @@ class pool::entry { /// /// Equivalent to `(*this)->database(name)`. /// - v1::database operator[](bsoncxx::v1::stdx::string_view name); + /// @{ + MONGOCXX_ABI_EXPORT_CDECL(v1::database) database(bsoncxx::v1::stdx::string_view name); + MONGOCXX_ABI_EXPORT_CDECL(v1::database) operator[](bsoncxx::v1::stdx::string_view name); + /// @} + /// }; } // namespace v1 From a28bf2f96b5037bd585f9864b6800d70097aa4d8 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 26 Jan 2026 14:47:49 -0600 Subject: [PATCH 05/13] CXX-3236 Make ctor explicitness consistent with v1::client --- src/mongocxx/include/mongocxx/v1/pool.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mongocxx/include/mongocxx/v1/pool.hpp b/src/mongocxx/include/mongocxx/v1/pool.hpp index 4230765d9d..5f8c6e3583 100644 --- a/src/mongocxx/include/mongocxx/v1/pool.hpp +++ b/src/mongocxx/include/mongocxx/v1/pool.hpp @@ -97,9 +97,9 @@ class pool { /// @throws mongocxx::v1::exception if a client-side error is encountered. /// /// @{ - explicit MONGOCXX_ABI_EXPORT_CDECL() pool(v1::uri const& uri, options opts); + MONGOCXX_ABI_EXPORT_CDECL() pool(v1::uri const& uri, options opts); - explicit MONGOCXX_ABI_EXPORT_CDECL() pool(v1::uri const& uri); + /* explicit(false) */ MONGOCXX_ABI_EXPORT_CDECL() pool(v1::uri const& uri); /// @} /// @@ -213,7 +213,7 @@ class pool::options { /// /// Initialize with client options to apply to a pool. /// - MONGOCXX_ABI_EXPORT_CDECL() options(v1::client::options opts); + /* explicit(false) */ MONGOCXX_ABI_EXPORT_CDECL() options(v1::client::options opts); /// /// Default initialization. From c3a61e2779ea832615839fb9d68bf951eee9db9b Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 26 Jan 2026 14:47:49 -0600 Subject: [PATCH 06/13] CXX-3236 Add moved-from state check to v1::pool --- src/mongocxx/include/mongocxx/v1/pool.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mongocxx/include/mongocxx/v1/pool.hpp b/src/mongocxx/include/mongocxx/v1/pool.hpp index 5f8c6e3583..9647b410e0 100644 --- a/src/mongocxx/include/mongocxx/v1/pool.hpp +++ b/src/mongocxx/include/mongocxx/v1/pool.hpp @@ -112,6 +112,11 @@ class pool { /// explicit MONGOCXX_ABI_EXPORT_CDECL() pool(); + /// + /// Return true when `*this` is NOT in an assign-or-destroy-only state. + /// + explicit MONGOCXX_ABI_EXPORT_CDECL() operator bool() const; + /// /// Return a client object associated with this pool. /// From f5a938db0ee8de89b381b3eac7a87ad8ed6e3e77 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 26 Jan 2026 14:47:49 -0600 Subject: [PATCH 07/13] CXX-3237 Move internal helpers into related components --- .../include/mongocxx/v1/server_api.hpp | 2 + src/mongocxx/lib/mongocxx/v1/apm.cpp | 182 ++++++++++- src/mongocxx/lib/mongocxx/v1/apm.hh | 4 + .../mongocxx/v1/auto_encryption_options.cpp | 52 +++- .../mongocxx/v1/auto_encryption_options.hh | 13 + src/mongocxx/lib/mongocxx/v1/client.cpp | 291 +----------------- src/mongocxx/lib/mongocxx/v1/server_api.cpp | 33 +- src/mongocxx/lib/mongocxx/v1/server_api.hh | 40 +++ src/mongocxx/lib/mongocxx/v1/tls.cpp | 29 ++ src/mongocxx/lib/mongocxx/v1/tls.hh | 7 + 10 files changed, 354 insertions(+), 299 deletions(-) create mode 100644 src/mongocxx/lib/mongocxx/v1/server_api.hh diff --git a/src/mongocxx/include/mongocxx/v1/server_api.hpp b/src/mongocxx/include/mongocxx/v1/server_api.hpp index 4f1633c212..b562519ead 100644 --- a/src/mongocxx/include/mongocxx/v1/server_api.hpp +++ b/src/mongocxx/include/mongocxx/v1/server_api.hpp @@ -172,6 +172,8 @@ class server_api { friend std::error_code make_error_code(errc v) { return {static_cast(v), error_category()}; } + + class internal; }; } // namespace v1 diff --git a/src/mongocxx/lib/mongocxx/v1/apm.cpp b/src/mongocxx/lib/mongocxx/v1/apm.cpp index 1ab33f8c89..c8d646d8ae 100644 --- a/src/mongocxx/lib/mongocxx/v1/apm.cpp +++ b/src/mongocxx/lib/mongocxx/v1/apm.cpp @@ -17,21 +17,23 @@ // #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include namespace mongocxx { @@ -323,5 +325,161 @@ auto apm::internal::server_heartbeat_succeeded(apm& self) -> fn_type +void exception_guard(char const* source, Fn fn) noexcept { + try { + fn(); + } catch (...) { + std::cerr << "fatal error: APM callback " << source << " exited via an exception" << std::endl; + std::terminate(); + } +} + +void command_started(mongoc_apm_command_started_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_command_started_get_context(v)); + auto const event = v1::events::command_started::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::command_started(context)(event); }); +} + +void command_failed(mongoc_apm_command_failed_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_command_failed_get_context(v)); + auto const event = v1::events::command_failed::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::command_failed(context)(event); }); +} + +void command_succeeded(mongoc_apm_command_succeeded_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_command_succeeded_get_context(v)); + auto const event = v1::events::command_succeeded::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::command_succeeded(context)(event); }); +} + +void server_opening(mongoc_apm_server_opening_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_server_opening_get_context(v)); + auto const event = v1::events::server_opening::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::server_opening(context)(event); }); +} + +void server_closed(mongoc_apm_server_closed_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_server_closed_get_context(v)); + auto const event = v1::events::server_closed::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::server_closed(context)(event); }); +} + +void server_description_changed(mongoc_apm_server_changed_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_server_changed_get_context(v)); + auto const event = v1::events::server_description_changed::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::server_description_changed(context)(event); }); +} + +void topology_opening(mongoc_apm_topology_opening_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_topology_opening_get_context(v)); + auto const event = v1::events::topology_opening::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::topology_opening(context)(event); }); +} + +void topology_closed(mongoc_apm_topology_closed_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_topology_closed_get_context(v)); + auto const event = v1::events::topology_closed::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::topology_closed(context)(event); }); +} + +void topology_description_changed(mongoc_apm_topology_changed_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_topology_changed_get_context(v)); + auto const event = v1::events::topology_description_changed::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::topology_description_changed(context)(event); }); +} + +void server_heartbeat_started(mongoc_apm_server_heartbeat_started_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_server_heartbeat_started_get_context(v)); + auto const event = v1::events::server_heartbeat_started::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::server_heartbeat_started(context)(event); }); +} + +void server_heartbeat_failed(mongoc_apm_server_heartbeat_failed_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_server_heartbeat_failed_get_context(v)); + auto const event = v1::events::server_heartbeat_failed::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::server_heartbeat_failed(context)(event); }); +} + +void server_heartbeat_succeeded(mongoc_apm_server_heartbeat_succeeded_t const* v) noexcept { + auto const& context = *static_cast(libmongoc::apm_server_heartbeat_succeeded_get_context(v)); + auto const event = v1::events::server_heartbeat_succeeded::internal::make(v); + exception_guard(__func__, [&] { v1::apm::internal::server_heartbeat_succeeded(context)(event); }); +} + +class apm_callbacks { + public: + mongoc_apm_callbacks_t* _callbacks = libmongoc::apm_callbacks_new(); + + ~apm_callbacks() { + libmongoc::apm_callbacks_destroy(_callbacks); + } + + apm_callbacks(apm_callbacks&& other) = delete; + apm_callbacks& operator=(apm_callbacks&& other) = delete; + apm_callbacks(apm_callbacks const& other) = delete; + apm_callbacks& operator=(apm_callbacks const& other) = delete; + + explicit apm_callbacks(v1::apm const& apm) { + if (v1::apm::internal::command_started(apm)) { + libmongoc::apm_set_command_started_cb(_callbacks, command_started); + } + + if (v1::apm::internal::command_failed(apm)) { + libmongoc::apm_set_command_failed_cb(_callbacks, command_failed); + } + + if (v1::apm::internal::command_succeeded(apm)) { + libmongoc::apm_set_command_succeeded_cb(_callbacks, command_succeeded); + } + + if (v1::apm::internal::server_opening(apm)) { + libmongoc::apm_set_server_opening_cb(_callbacks, server_opening); + } + + if (v1::apm::internal::server_closed(apm)) { + libmongoc::apm_set_server_closed_cb(_callbacks, server_closed); + } + + if (v1::apm::internal::server_description_changed(apm)) { + libmongoc::apm_set_server_changed_cb(_callbacks, server_description_changed); + } + + if (v1::apm::internal::topology_opening(apm)) { + libmongoc::apm_set_topology_opening_cb(_callbacks, topology_opening); + } + + if (v1::apm::internal::topology_closed(apm)) { + libmongoc::apm_set_topology_closed_cb(_callbacks, topology_closed); + } + + if (v1::apm::internal::topology_description_changed(apm)) { + libmongoc::apm_set_topology_changed_cb(_callbacks, topology_description_changed); + } + + if (v1::apm::internal::server_heartbeat_started(apm)) { + libmongoc::apm_set_server_heartbeat_started_cb(_callbacks, server_heartbeat_started); + } + + if (v1::apm::internal::server_heartbeat_failed(apm)) { + libmongoc::apm_set_server_heartbeat_failed_cb(_callbacks, server_heartbeat_failed); + } + + if (v1::apm::internal::server_heartbeat_succeeded(apm)) { + libmongoc::apm_set_server_heartbeat_succeeded_cb(_callbacks, server_heartbeat_succeeded); + } + } +}; + +} // namespace + +void apm::internal::set_apm_callbacks(mongoc_client_t* client, v1::apm& apm) { + libmongoc::client_set_apm_callbacks(client, apm_callbacks{apm}._callbacks, &apm); +} + } // namespace v1 } // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/apm.hh b/src/mongocxx/lib/mongocxx/v1/apm.hh index d775ca08bc..f2144a330c 100644 --- a/src/mongocxx/lib/mongocxx/v1/apm.hh +++ b/src/mongocxx/lib/mongocxx/v1/apm.hh @@ -35,6 +35,8 @@ #include +#include + namespace mongocxx { namespace v1 { @@ -68,6 +70,8 @@ class apm::internal { static fn_type& server_heartbeat_started(apm& self); static fn_type& server_heartbeat_failed(apm& self); static fn_type& server_heartbeat_succeeded(apm& self); + + static void set_apm_callbacks(mongoc_client_t* client, v1::apm& apm); }; } // namespace v1 diff --git a/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.cpp b/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.cpp index 9a296ac4ee..bf64772e00 100644 --- a/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.cpp +++ b/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.cpp @@ -16,12 +16,17 @@ // -#include #include #include #include +#include + +#include + +#include +#include #include namespace mongocxx { @@ -213,5 +218,50 @@ bsoncxx::v1::stdx::optional& auto_encryption_optio return impl::with(self)._extra_options; } +std::unique_ptr +auto_encryption_options::internal::to_mongoc(v1::auto_encryption_options const& self) { + std::unique_ptr ret{ + libmongoc::auto_encryption_opts_new()}; + + auto const ptr = ret.get(); + + if (auto const& opt = self.key_vault_client()) { + libmongoc::auto_encryption_opts_set_keyvault_client(ptr, v1::client::internal::as_mongoc(*opt)); + } + + if (auto const& opt = self.key_vault_pool()) { + (void)opt; // TODO: v1::pool (CXX-3237) + } + + if (auto const& opt = self.key_vault_namespace()) { + libmongoc::auto_encryption_opts_set_keyvault_namespace(ptr, opt->first.c_str(), opt->second.c_str()); + } + + if (auto const& opt = self.kms_providers()) { + libmongoc::auto_encryption_opts_set_kms_providers(ptr, scoped_bson_view{*opt}.bson()); + } + + if (auto const& opt = self.tls_opts()) { + libmongoc::auto_encryption_opts_set_tls_opts(ptr, scoped_bson_view{*opt}.bson()); + } + + if (auto const& opt = self.schema_map()) { + libmongoc::auto_encryption_opts_set_schema_map(ptr, scoped_bson_view{*opt}.bson()); + } + + if (auto const& opt = self.encrypted_fields_map()) { + libmongoc::auto_encryption_opts_set_encrypted_fields_map(ptr, scoped_bson_view{*opt}.bson()); + } + + libmongoc::auto_encryption_opts_set_bypass_auto_encryption(ptr, self.bypass_auto_encryption()); + libmongoc::auto_encryption_opts_set_bypass_query_analysis(ptr, self.bypass_query_analysis()); + + if (auto const& opt = self.extra_options()) { + libmongoc::auto_encryption_opts_set_extra(ptr, scoped_bson_view{*opt}.bson()); + } + + return ret; +} + } // namespace v1 } // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.hh b/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.hh index a5ca2c61ac..326916a729 100644 --- a/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.hh +++ b/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.hh @@ -22,6 +22,10 @@ #include +#include + +#include + namespace mongocxx { namespace v1 { @@ -34,6 +38,15 @@ class auto_encryption_options::internal { static bsoncxx::v1::stdx::optional& encrypted_fields_map( auto_encryption_options& self); static bsoncxx::v1::stdx::optional& extra_options(auto_encryption_options& self); + + struct mongoc_auto_encryption_opts_deleter { + void operator()(mongoc_auto_encryption_opts_t* ptr) const noexcept { + libmongoc::auto_encryption_opts_destroy(ptr); + } + }; + + static std::unique_ptr to_mongoc( + v1::auto_encryption_options const& self); }; } // namespace v1 diff --git a/src/mongocxx/lib/mongocxx/v1/client.cpp b/src/mongocxx/lib/mongocxx/v1/client.cpp index 2d16acfbdd..752534a935 100644 --- a/src/mongocxx/lib/mongocxx/v1/client.cpp +++ b/src/mongocxx/lib/mongocxx/v1/client.cpp @@ -23,8 +23,6 @@ #include #include -#include -#include #include @@ -33,28 +31,13 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include #include #include -#include -#include #include -#include #include #include #include @@ -72,269 +55,6 @@ namespace v1 { using code = client::errc; -namespace { - -// An APM callback exiting via an exception is documented as being undefined behavior. -// For QoI, terminate the program before allowing the exception to bypass libmongoc code. -template -void exception_guard(char const* source, Fn fn) noexcept { - try { - fn(); - } catch (...) { - std::cerr << "fatal error: APM callback " << source << " exited via an exception" << std::endl; - std::terminate(); - } -} - -void command_started(mongoc_apm_command_started_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_command_started_get_context(v)); - auto const event = v1::events::command_started::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::command_started(context)(event); }); -} - -void command_failed(mongoc_apm_command_failed_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_command_failed_get_context(v)); - auto const event = v1::events::command_failed::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::command_failed(context)(event); }); -} - -void command_succeeded(mongoc_apm_command_succeeded_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_command_succeeded_get_context(v)); - auto const event = v1::events::command_succeeded::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::command_succeeded(context)(event); }); -} - -void server_opening(mongoc_apm_server_opening_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_server_opening_get_context(v)); - auto const event = v1::events::server_opening::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::server_opening(context)(event); }); -} - -void server_closed(mongoc_apm_server_closed_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_server_closed_get_context(v)); - auto const event = v1::events::server_closed::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::server_closed(context)(event); }); -} - -void server_description_changed(mongoc_apm_server_changed_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_server_changed_get_context(v)); - auto const event = v1::events::server_description_changed::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::server_description_changed(context)(event); }); -} - -void topology_opening(mongoc_apm_topology_opening_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_topology_opening_get_context(v)); - auto const event = v1::events::topology_opening::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::topology_opening(context)(event); }); -} - -void topology_closed(mongoc_apm_topology_closed_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_topology_closed_get_context(v)); - auto const event = v1::events::topology_closed::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::topology_closed(context)(event); }); -} - -void topology_description_changed(mongoc_apm_topology_changed_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_topology_changed_get_context(v)); - auto const event = v1::events::topology_description_changed::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::topology_description_changed(context)(event); }); -} - -void server_heartbeat_started(mongoc_apm_server_heartbeat_started_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_server_heartbeat_started_get_context(v)); - auto const event = v1::events::server_heartbeat_started::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::server_heartbeat_started(context)(event); }); -} - -void server_heartbeat_failed(mongoc_apm_server_heartbeat_failed_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_server_heartbeat_failed_get_context(v)); - auto const event = v1::events::server_heartbeat_failed::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::server_heartbeat_failed(context)(event); }); -} - -void server_heartbeat_succeeded(mongoc_apm_server_heartbeat_succeeded_t const* v) noexcept { - auto const& context = *static_cast(libmongoc::apm_server_heartbeat_succeeded_get_context(v)); - auto const event = v1::events::server_heartbeat_succeeded::internal::make(v); - exception_guard(__func__, [&] { v1::apm::internal::server_heartbeat_succeeded(context)(event); }); -} - -class apm_callbacks { - public: - mongoc_apm_callbacks_t* _callbacks = libmongoc::apm_callbacks_new(); - - ~apm_callbacks() { - libmongoc::apm_callbacks_destroy(_callbacks); - } - - apm_callbacks(apm_callbacks&& other) = delete; - apm_callbacks& operator=(apm_callbacks&& other) = delete; - apm_callbacks(apm_callbacks const& other) = delete; - apm_callbacks& operator=(apm_callbacks const& other) = delete; - - explicit apm_callbacks(v1::apm const& apm) { - if (v1::apm::internal::command_started(apm)) { - libmongoc::apm_set_command_started_cb(_callbacks, command_started); - } - - if (v1::apm::internal::command_failed(apm)) { - libmongoc::apm_set_command_failed_cb(_callbacks, command_failed); - } - - if (v1::apm::internal::command_succeeded(apm)) { - libmongoc::apm_set_command_succeeded_cb(_callbacks, command_succeeded); - } - - if (v1::apm::internal::server_opening(apm)) { - libmongoc::apm_set_server_opening_cb(_callbacks, server_opening); - } - - if (v1::apm::internal::server_closed(apm)) { - libmongoc::apm_set_server_closed_cb(_callbacks, server_closed); - } - - if (v1::apm::internal::server_description_changed(apm)) { - libmongoc::apm_set_server_changed_cb(_callbacks, server_description_changed); - } - - if (v1::apm::internal::topology_opening(apm)) { - libmongoc::apm_set_topology_opening_cb(_callbacks, topology_opening); - } - - if (v1::apm::internal::topology_closed(apm)) { - libmongoc::apm_set_topology_closed_cb(_callbacks, topology_closed); - } - - if (v1::apm::internal::topology_description_changed(apm)) { - libmongoc::apm_set_topology_changed_cb(_callbacks, topology_description_changed); - } - - if (v1::apm::internal::server_heartbeat_started(apm)) { - libmongoc::apm_set_server_heartbeat_started_cb(_callbacks, server_heartbeat_started); - } - - if (v1::apm::internal::server_heartbeat_failed(apm)) { - libmongoc::apm_set_server_heartbeat_failed_cb(_callbacks, server_heartbeat_failed); - } - - if (v1::apm::internal::server_heartbeat_succeeded(apm)) { - libmongoc::apm_set_server_heartbeat_succeeded_cb(_callbacks, server_heartbeat_succeeded); - } - } -}; - -struct mongoc_auto_encryption_opts_deleter { - void operator()(mongoc_auto_encryption_opts_t* ptr) const noexcept { - libmongoc::auto_encryption_opts_destroy(ptr); - } -}; - -std::unique_ptr to_mongoc( - v1::auto_encryption_options const& opts) { - std::unique_ptr ret{ - libmongoc::auto_encryption_opts_new()}; - - auto const ptr = ret.get(); - - if (auto const& opt = opts.key_vault_client()) { - libmongoc::auto_encryption_opts_set_keyvault_client(ptr, v1::client::internal::as_mongoc(*opt)); - } - - if (auto const& opt = opts.key_vault_pool()) { - (void)opt; // TODO: v1::pool (CXX-3237) - } - - if (auto const& opt = opts.key_vault_namespace()) { - libmongoc::auto_encryption_opts_set_keyvault_namespace(ptr, opt->first.c_str(), opt->second.c_str()); - } - - if (auto const& opt = opts.kms_providers()) { - libmongoc::auto_encryption_opts_set_kms_providers(ptr, scoped_bson_view{*opt}.bson()); - } - - if (auto const& opt = opts.tls_opts()) { - libmongoc::auto_encryption_opts_set_tls_opts(ptr, scoped_bson_view{*opt}.bson()); - } - - if (auto const& opt = opts.schema_map()) { - libmongoc::auto_encryption_opts_set_schema_map(ptr, scoped_bson_view{*opt}.bson()); - } - - if (auto const& opt = opts.encrypted_fields_map()) { - libmongoc::auto_encryption_opts_set_encrypted_fields_map(ptr, scoped_bson_view{*opt}.bson()); - } - - libmongoc::auto_encryption_opts_set_bypass_auto_encryption(ptr, opts.bypass_auto_encryption()); - libmongoc::auto_encryption_opts_set_bypass_query_analysis(ptr, opts.bypass_query_analysis()); - - if (auto const& opt = opts.extra_options()) { - libmongoc::auto_encryption_opts_set_extra(ptr, scoped_bson_view{*opt}.bson()); - } - - return ret; -} - -struct mongoc_server_api_deleter { - void operator()(mongoc_server_api_t* ptr) const noexcept { - libmongoc::server_api_destroy(ptr); - } -}; - -std::unique_ptr to_mongoc(v1::server_api const& api) { - mongoc_server_api_version_t version = {}; - - if (!libmongoc::server_api_version_from_string( - v1::server_api::version_to_string(api.get_version()).c_str(), &version)) { - // Invariant: enforced by `v1::server_api::errc::invalid_version`. - MONGOCXX_PRIVATE_UNREACHABLE; - } - - std::unique_ptr ret{libmongoc::server_api_new(version)}; - - auto const ptr = ret.get(); - - if (auto const opt = api.strict()) { - libmongoc::server_api_strict(ptr, *opt); - } - - if (auto const opt = api.deprecation_errors()) { - libmongoc::server_api_deprecation_errors(ptr, *opt); - } - - return ret; -} - -#if MONGOCXX_SSL_IS_ENABLED() -mongoc_ssl_opt_t to_mongoc(v1::tls const& opts) { - mongoc_ssl_opt_t ret = {}; - - if (auto const& opt = v1::tls::internal::pem_file(opts)) { - ret.pem_file = opt->c_str(); - } - - if (auto const& opt = v1::tls::internal::pem_password(opts)) { - ret.pem_pwd = opt->c_str(); - } - - if (auto const& opt = v1::tls::internal::ca_file(opts)) { - ret.ca_file = opt->c_str(); - } - - if (auto const& opt = v1::tls::internal::ca_dir(opts)) { - ret.ca_dir = opt->c_str(); - } - - if (auto const& opt = v1::tls::internal::crl_file(opts)) { - ret.crl_file = opt->c_str(); - } - - ret.weak_cert_validation = opts.allow_invalid_certificates().value_or(false); - - return ret; -} -#endif - -} // namespace - class client::impl { public: mongoc_client_t* _client; @@ -411,7 +131,8 @@ client::client(v1::uri uri, options opts) : client{new impl{v1::uri::internal::a if (auto const& opt = options::internal::auto_encryption_opts(opts)) { bson_error_t error = {}; - if (!libmongoc::client_enable_auto_encryption(_client, to_mongoc(*opt).get(), &error)) { + if (!libmongoc::client_enable_auto_encryption( + _client, v1::auto_encryption_options::internal::to_mongoc(*opt).get(), &error)) { v1::throw_exception(error); } } @@ -419,7 +140,7 @@ client::client(v1::uri uri, options opts) : client{new impl{v1::uri::internal::a if (auto const& opt = options::internal::server_api_opts(opts)) { bson_error_t error = {}; - if (!libmongoc::client_set_server_api(_client, to_mongoc(*opt).get(), &error)) { + if (!libmongoc::client_set_server_api(_client, v1::server_api::internal::to_mongoc(*opt).get(), &error)) { v1::throw_exception(error); } } @@ -433,7 +154,7 @@ client::client(v1::uri uri, options opts) : client{new impl{v1::uri::internal::a throw v1::exception::internal::make(errc::tls_not_enabled); } - auto const v = to_mongoc(*opt); + auto const v = v1::tls::internal::to_mongoc(*opt); libmongoc::client_set_ssl_opts(_client, &v); } #else @@ -741,7 +462,7 @@ client client::internal::make(mongoc_client_t* client) { void client::internal::set_apm(client& self, v1::apm v) { auto& _apm = impl::with(self)._apm; _apm = std::move(v); - libmongoc::client_set_apm_callbacks(impl::with(self)._client, apm_callbacks{_apm}._callbacks, &_apm); + v1::apm::internal::set_apm_callbacks(impl::with(self)._client, _apm); } void client::internal::disown(client& self) { diff --git a/src/mongocxx/lib/mongocxx/v1/server_api.cpp b/src/mongocxx/lib/mongocxx/v1/server_api.cpp index 913344db79..440998bd63 100644 --- a/src/mongocxx/lib/mongocxx/v1/server_api.cpp +++ b/src/mongocxx/lib/mongocxx/v1/server_api.cpp @@ -12,21 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include // #include #include +#include + #include +#include #include #include #include #include +#include #include namespace mongocxx { @@ -204,5 +208,32 @@ std::error_category const& server_api::error_category() { return instance.value(); } +#if MONGOCXX_SSL_IS_ENABLED() +std::unique_ptr server_api::internal::to_mongoc( + v1::server_api const& api) { + mongoc_server_api_version_t version = {}; + + if (!libmongoc::server_api_version_from_string( + v1::server_api::version_to_string(api.get_version()).c_str(), &version)) { + // Invariant: enforced by `v1::server_api::errc::invalid_version`. + MONGOCXX_PRIVATE_UNREACHABLE; + } + + std::unique_ptr ret{libmongoc::server_api_new(version)}; + + auto const ptr = ret.get(); + + if (auto const opt = api.strict()) { + libmongoc::server_api_strict(ptr, *opt); + } + + if (auto const opt = api.deprecation_errors()) { + libmongoc::server_api_deprecation_errors(ptr, *opt); + } + + return ret; +} +#endif + } // namespace v1 } // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/server_api.hh b/src/mongocxx/lib/mongocxx/v1/server_api.hh new file mode 100644 index 0000000000..4055033146 --- /dev/null +++ b/src/mongocxx/lib/mongocxx/v1/server_api.hh @@ -0,0 +1,40 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed 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. + +#pragma once + +#include // IWYU pragma: export + +// + +#include + +#include + +namespace mongocxx { +namespace v1 { + +class server_api::internal { + public: + struct mongoc_server_api_deleter { + void operator()(mongoc_server_api_t* ptr) const noexcept { + libmongoc::server_api_destroy(ptr); + } + }; + + static std::unique_ptr to_mongoc(v1::server_api const& self); +}; + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/tls.cpp b/src/mongocxx/lib/mongocxx/v1/tls.cpp index ecc40ff479..854be0c63f 100644 --- a/src/mongocxx/lib/mongocxx/v1/tls.cpp +++ b/src/mongocxx/lib/mongocxx/v1/tls.cpp @@ -21,6 +21,7 @@ #include +#include #include namespace mongocxx { @@ -178,5 +179,33 @@ bsoncxx::v1::stdx::optional& tls::internal::crl_file(tls& self) { return impl::with(self)._crl_file; } +mongoc_ssl_opt_t tls::internal::to_mongoc(v1::tls const& self) { + mongoc_ssl_opt_t ret = {}; + + if (auto const& opt = v1::tls::internal::pem_file(self)) { + ret.pem_file = opt->c_str(); + } + + if (auto const& opt = v1::tls::internal::pem_password(self)) { + ret.pem_pwd = opt->c_str(); + } + + if (auto const& opt = v1::tls::internal::ca_file(self)) { + ret.ca_file = opt->c_str(); + } + + if (auto const& opt = v1::tls::internal::ca_dir(self)) { + ret.ca_dir = opt->c_str(); + } + + if (auto const& opt = v1::tls::internal::crl_file(self)) { + ret.crl_file = opt->c_str(); + } + + ret.weak_cert_validation = self.allow_invalid_certificates().value_or(false); + + return ret; +} + } // namespace v1 } // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/tls.hh b/src/mongocxx/lib/mongocxx/v1/tls.hh index 08d2238b8f..6cdc35aa56 100644 --- a/src/mongocxx/lib/mongocxx/v1/tls.hh +++ b/src/mongocxx/lib/mongocxx/v1/tls.hh @@ -22,6 +22,9 @@ #include +#include +#include + namespace mongocxx { namespace v1 { @@ -38,6 +41,10 @@ class tls::internal { static bsoncxx::v1::stdx::optional& ca_file(tls& self); static bsoncxx::v1::stdx::optional& ca_dir(tls& self); static bsoncxx::v1::stdx::optional& crl_file(tls& self); + +#if MONGOCXX_SSL_IS_ENABLED() + static mongoc_ssl_opt_t to_mongoc(v1::tls const& self); +#endif }; } // namespace v1 From 5ea266e38daa35aea9282d744aac5b2b0bfc0485 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 26 Jan 2026 14:47:49 -0600 Subject: [PATCH 08/13] v1: pool (CXX-3237, CXX-3238) --- src/mongocxx/include/mongocxx/v1/pool.hpp | 28 +- .../v_noabi/mongocxx/options/pool-fwd.hpp | 7 +- .../v_noabi/mongocxx/options/pool.hpp | 22 +- .../mongocxx/v_noabi/mongocxx/pool-fwd.hpp | 7 +- .../mongocxx/v_noabi/mongocxx/pool.hpp | 66 +- src/mongocxx/lib/mongocxx/v1/apm.cpp | 4 + src/mongocxx/lib/mongocxx/v1/apm.hh | 1 + .../mongocxx/v1/auto_encryption_options.cpp | 3 +- src/mongocxx/lib/mongocxx/v1/client.cpp | 6 +- src/mongocxx/lib/mongocxx/v1/client.hh | 2 +- src/mongocxx/lib/mongocxx/v1/pool.cpp | 407 +++++++- src/mongocxx/lib/mongocxx/v1/pool.hh | 47 + src/mongocxx/lib/mongocxx/v1/tls.cpp | 3 + .../lib/mongocxx/v_noabi/mongocxx/client.cpp | 4 +- .../lib/mongocxx/v_noabi/mongocxx/client.hh | 2 +- .../mongocxx/options/auto_encryption.cpp | 2 +- .../v_noabi/mongocxx/options/pool.cpp | 14 - .../lib/mongocxx/v_noabi/mongocxx/pool.cpp | 151 ++- .../lib/mongocxx/v_noabi/mongocxx/pool.hh | 28 +- src/mongocxx/test/CMakeLists.txt | 1 + src/mongocxx/test/v1/client.cpp | 35 +- src/mongocxx/test/v1/pool.cpp | 934 ++++++++++++++++++ 22 files changed, 1609 insertions(+), 165 deletions(-) create mode 100644 src/mongocxx/lib/mongocxx/v1/pool.hh create mode 100644 src/mongocxx/test/v1/pool.cpp diff --git a/src/mongocxx/include/mongocxx/v1/pool.hpp b/src/mongocxx/include/mongocxx/v1/pool.hpp index 9647b410e0..0eed7ac882 100644 --- a/src/mongocxx/include/mongocxx/v1/pool.hpp +++ b/src/mongocxx/include/mongocxx/v1/pool.hpp @@ -31,6 +31,7 @@ #include #include +#include namespace mongocxx { namespace v1 { @@ -80,12 +81,12 @@ class pool { MONGOCXX_ABI_EXPORT_CDECL(pool&) operator=(pool&& other) noexcept; /// - /// This class is immovable. + /// This class is not copyable. /// pool(pool const& other) = delete; /// - /// This class is immovable. + /// This class is not copyable. /// pool& operator=(pool const& other) = delete; @@ -171,6 +172,11 @@ class pool { friend std::error_code make_error_code(errc v) { return {static_cast(v), error_category()}; } + + class internal; + + private: + /* explicit(false) */ pool(void* impl); }; /// @@ -232,6 +238,8 @@ class pool::options { /// Return the current client options. /// MONGOCXX_ABI_EXPORT_CDECL(v1::client::options) client_opts() const; + + class internal; }; /// @@ -279,12 +287,12 @@ class pool::entry { /// /// Access the managed client. /// - MONGOCXX_ABI_EXPORT_CDECL(client*) operator->(); + MONGOCXX_ABI_EXPORT_CDECL(v1::client*) operator->(); /// /// Access the managed client. /// - MONGOCXX_ABI_EXPORT_CDECL(client&) operator*(); + MONGOCXX_ABI_EXPORT_CDECL(v1::client&) operator*(); /// /// Explicitly release the managed client object back to the associated pool. @@ -307,11 +315,23 @@ class pool::entry { MONGOCXX_ABI_EXPORT_CDECL(v1::database) operator[](bsoncxx::v1::stdx::string_view name); /// @} /// + + class internal; + + private: + /* explicit(false) */ entry(void* impl); }; } // namespace v1 } // namespace mongocxx +namespace std { + +template <> +struct is_error_code_enum : true_type {}; + +} // namespace std + #include /// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/pool-fwd.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/pool-fwd.hpp index decaec0823..b7e9bba53b 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/pool-fwd.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/pool-fwd.hpp @@ -14,6 +14,8 @@ #pragma once +#include + #include namespace mongocxx { @@ -29,7 +31,7 @@ class pool; namespace mongocxx { namespace options { -using ::mongocxx::v_noabi::options::pool; +using v_noabi::options::pool; } // namespace options } // namespace mongocxx @@ -40,3 +42,6 @@ using ::mongocxx::v_noabi::options::pool; /// @file /// Declares @ref mongocxx::v_noabi::options::pool. /// +/// @par Includes +/// - @ref mongocxx/v1/pool-fwd.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/pool.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/pool.hpp index 5f3f67b453..a596ec6df3 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/pool.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/pool.hpp @@ -16,6 +16,12 @@ #include // IWYU pragma: export +// + +#include // IWYU pragma: export + +#include + #include #include @@ -29,6 +35,11 @@ namespace options { /// class pool { public: + /// + /// Default initialization. + /// + pool() = default; + /// /// Constructs a new pool options object. Note that options::pool is implictly convertible from /// options::client. @@ -36,17 +47,19 @@ class pool { /// @param client_opts /// The client options. /// - MONGOCXX_ABI_EXPORT_CDECL() pool(client client_opts = client()); + pool(v_noabi::options::client client_opts) : _client_opts{std::move(client_opts)} {} /// /// The current client options. /// /// @return The client options. /// - MONGOCXX_ABI_EXPORT_CDECL(client const&) client_opts() const; + v_noabi::options::client const& client_opts() const { + return _client_opts; + } private: - client _client_opts; + v_noabi::options::client _client_opts; }; } // namespace options @@ -59,3 +72,6 @@ class pool { /// @file /// Provides @ref mongocxx::v_noabi::options::pool. /// +/// @par Includes +/// - @ref mongocxx/v1/pool.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pool-fwd.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pool-fwd.hpp index defb47eb56..cc235a65cc 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pool-fwd.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pool-fwd.hpp @@ -14,6 +14,8 @@ #pragma once +#include + #include namespace mongocxx { @@ -26,7 +28,7 @@ class pool; namespace mongocxx { -using ::mongocxx::v_noabi::pool; +using v_noabi::pool; } // namespace mongocxx @@ -36,3 +38,6 @@ using ::mongocxx::v_noabi::pool; /// @file /// Declares @ref mongocxx::v_noabi::pool. /// +/// @par Includes +/// - @ref mongocxx/v1/pool-fwd.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pool.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pool.hpp index 4ddf6af3da..2bc1b5cdcb 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pool.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pool.hpp @@ -14,13 +14,21 @@ #pragma once +#include // IWYU pragma: export + +// + +#include // IWYU pragma: export + +#include #include #include +#include -#include -#include // IWYU pragma: export +#include // IWYU pragma: keep: backward compatibility, to be removed. #include +#include #include #include @@ -52,6 +60,9 @@ namespace v_noabi { /// connection pool should be used even if the application itself is single-threaded. /// class pool { + private: + v1::pool _pool; + public: /// /// Creates a pool associated with a connection string. @@ -65,13 +76,13 @@ class pool { /// or /// provided client options). explicit MONGOCXX_ABI_EXPORT_CDECL() pool( - uri const& mongodb_uri = mongocxx::v_noabi::uri(), - options::pool const& options = options::pool()); + v_noabi::uri const& mongodb_uri = {}, + v_noabi::options::pool const& options = {}); /// /// Destroys a pool. /// - MONGOCXX_ABI_EXPORT_CDECL() ~pool(); + ~pool() = default; pool(pool&&) = delete; pool& operator=(pool&&) = delete; @@ -90,35 +101,43 @@ class pool { class entry { public: /// Access a member of the client instance. - MONGOCXX_ABI_EXPORT_CDECL(client*) operator->() const& noexcept; - client* operator->() && = delete; + v_noabi::client* operator->() const& noexcept { + return _client.get(); + } + v_noabi::client* operator->() && = delete; /// Retrieve a reference to the client. - MONGOCXX_ABI_EXPORT_CDECL(client&) operator*() const& noexcept; - client& operator*() && = delete; + v_noabi::client& operator*() const& noexcept { + return *_client; + } + v_noabi::client& operator*() && = delete; /// Assign nullptr to this entry to release its client to the pool. - MONGOCXX_ABI_EXPORT_CDECL(entry&) operator=(std::nullptr_t) noexcept; + entry& operator=(std::nullptr_t) noexcept { + _client.reset(); + return *this; + } /// Return true if this entry has a client acquired from the pool. - explicit MONGOCXX_ABI_EXPORT_CDECL() operator bool() const noexcept; + explicit operator bool() const noexcept { + return _client != nullptr; + } // Allows the pool_entry["db_name"] syntax to be used to access a database within the // entry's underlying client. mongocxx::v_noabi::database operator[](bsoncxx::v_noabi::string::view_or_value name) const& { - return (**this)[name]; + return (*_client)[name]; } - mongocxx::v_noabi::database operator[](bsoncxx::v_noabi::string::view_or_value name) && = delete; - private: - friend ::mongocxx::v_noabi::pool; + class internal; - using unique_client = std::unique_ptr>; + private: + using ptr_type = std::unique_ptr>; - explicit entry(unique_client); + ptr_type _client; - unique_client _client; + /* explicit(false) */ entry(ptr_type client) : _client{std::move(client)} {} }; /// @@ -133,13 +152,7 @@ class pool { /// MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional) try_acquire(); - private: - friend ::mongocxx::v_noabi::options::auto_encryption; - - void _release(client* client); - - class impl; - std::unique_ptr const _impl; + class internal; }; } // namespace v_noabi @@ -151,3 +164,6 @@ class pool { /// @file /// Provides @ref mongocxx::v_noabi::pool. /// +/// @par Includes +/// - @ref mongocxx/v1/pool.hpp +/// diff --git a/src/mongocxx/lib/mongocxx/v1/apm.cpp b/src/mongocxx/lib/mongocxx/v1/apm.cpp index c8d646d8ae..cb8eb31df0 100644 --- a/src/mongocxx/lib/mongocxx/v1/apm.cpp +++ b/src/mongocxx/lib/mongocxx/v1/apm.cpp @@ -481,5 +481,9 @@ void apm::internal::set_apm_callbacks(mongoc_client_t* client, v1::apm& apm) { libmongoc::client_set_apm_callbacks(client, apm_callbacks{apm}._callbacks, &apm); } +void apm::internal::set_apm_callbacks(mongoc_client_pool_t* pool, v1::apm& apm) { + libmongoc::client_pool_set_apm_callbacks(pool, apm_callbacks{apm}._callbacks, &apm); +} + } // namespace v1 } // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/apm.hh b/src/mongocxx/lib/mongocxx/v1/apm.hh index f2144a330c..c0255904f3 100644 --- a/src/mongocxx/lib/mongocxx/v1/apm.hh +++ b/src/mongocxx/lib/mongocxx/v1/apm.hh @@ -72,6 +72,7 @@ class apm::internal { static fn_type& server_heartbeat_succeeded(apm& self); static void set_apm_callbacks(mongoc_client_t* client, v1::apm& apm); + static void set_apm_callbacks(mongoc_client_pool_t* pool, v1::apm& apm); }; } // namespace v1 diff --git a/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.cpp b/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.cpp index bf64772e00..9476fc165b 100644 --- a/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.cpp +++ b/src/mongocxx/lib/mongocxx/v1/auto_encryption_options.cpp @@ -22,6 +22,7 @@ #include #include +#include #include @@ -230,7 +231,7 @@ auto_encryption_options::internal::to_mongoc(v1::auto_encryption_options const& } if (auto const& opt = self.key_vault_pool()) { - (void)opt; // TODO: v1::pool (CXX-3237) + libmongoc::auto_encryption_opts_set_keyvault_client_pool(ptr, v1::pool::internal::as_mongoc(*opt)); } if (auto const& opt = self.key_vault_namespace()) { diff --git a/src/mongocxx/lib/mongocxx/v1/client.cpp b/src/mongocxx/lib/mongocxx/v1/client.cpp index 752534a935..8fa9189e9a 100644 --- a/src/mongocxx/lib/mongocxx/v1/client.cpp +++ b/src/mongocxx/lib/mongocxx/v1/client.cpp @@ -22,7 +22,6 @@ #include #include -#include #include @@ -31,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -465,8 +465,8 @@ void client::internal::set_apm(client& self, v1::apm v) { v1::apm::internal::set_apm_callbacks(impl::with(self)._client, _apm); } -void client::internal::disown(client& self) { - impl::with(self)._client = nullptr; +mongoc_client_t* client::internal::release(client& self) { + return exchange(impl::with(self)._client, nullptr); } mongoc_client_t const* client::internal::as_mongoc(client const& self) { diff --git a/src/mongocxx/lib/mongocxx/v1/client.hh b/src/mongocxx/lib/mongocxx/v1/client.hh index b365baf3b1..9974f71024 100644 --- a/src/mongocxx/lib/mongocxx/v1/client.hh +++ b/src/mongocxx/lib/mongocxx/v1/client.hh @@ -37,7 +37,7 @@ class client::internal { static void set_apm(client& self, v1::apm v); - static void disown(client& self); + static mongoc_client_t* release(client& self); static mongoc_client_t const* as_mongoc(client const& self); static MONGOCXX_ABI_EXPORT_CDECL_TESTING(mongoc_client_t*) as_mongoc(client& self); diff --git a/src/mongocxx/lib/mongocxx/v1/pool.cpp b/src/mongocxx/lib/mongocxx/v1/pool.cpp index b247aa8b4d..fc81b7e1e8 100644 --- a/src/mongocxx/lib/mongocxx/v1/pool.cpp +++ b/src/mongocxx/lib/mongocxx/v1/pool.cpp @@ -12,4 +12,409 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include + +// + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace mongocxx { +namespace v1 { + +using code = pool::errc; + +class pool::impl { + public: + mongoc_client_pool_t* _pool; + v1::apm _apm; + + ~impl() { + libmongoc::client_pool_destroy(_pool); + } + + impl(impl&& other) noexcept = delete; + impl& operator=(impl&& other) noexcept = delete; + impl(impl const& other) = delete; + impl& operator=(impl const& other) = delete; + + explicit impl(mongoc_uri_t const* uri) + : _pool{[&] { + bson_error_t error = {}; + + if (auto const ptr = libmongoc::client_pool_new_with_error(uri, &error)) { + return ptr; + } + + v1::throw_exception(error); + }()} {} + + explicit impl(mongoc_client_pool_t* pool) : _pool{pool} {} + + static impl const& with(pool const& self) { + return *static_cast(self._impl); + } + + static impl const* with(pool const* self) { + return static_cast(self->_impl); + } + + static impl& with(pool& self) { + return *static_cast(self._impl); + } + + static impl* with(pool* self) { + return static_cast(self->_impl); + } + + static impl* with(void* ptr) { + return static_cast(ptr); + } +}; + +// NOLINTBEGIN(cppcoreguidelines-owning-memory): owning void* for ABI stability. + +pool::~pool() { + delete impl::with(this); +} + +pool::pool(pool&& other) noexcept : _impl{exchange(other._impl, nullptr)} {} + +pool& pool::operator=(pool&& other) noexcept { + if (this != &other) { + delete impl::with(exchange(_impl, exchange(other._impl, nullptr))); + } + + return *this; +} + +pool::pool(v1::uri const& uri, options opts) : pool{uri} { + auto const _pool = impl::with(this)->_pool; + + auto& client_opts = options::internal::client_opts(opts); + + if (auto& opt = v1::client::options::internal::apm_opts(client_opts)) { + internal::set_apm(*this, std::move(*opt)); + } + + if (auto const& opt = v1::client::options::internal::auto_encryption_opts(client_opts)) { + bson_error_t error = {}; + + if (!libmongoc::client_pool_enable_auto_encryption( + _pool, v1::auto_encryption_options::internal::to_mongoc(*opt).get(), &error)) { + v1::throw_exception(error); + } + } + + if (auto const& opt = v1::client::options::internal::server_api_opts(client_opts)) { + bson_error_t error = {}; + + if (!libmongoc::client_pool_set_server_api(_pool, v1::server_api::internal::to_mongoc(*opt).get(), &error)) { + v1::throw_exception(error); + } + } + + { + auto const tls_enabled = uri.tls(); + +#if MONGOCXX_SSL_IS_ENABLED() + if (auto const& opt = v1::client::options::internal::tls_opts(client_opts)) { + if (!tls_enabled) { + throw v1::exception::internal::make(v1::client::errc::tls_not_enabled); + } + + auto const v = v1::tls::internal::to_mongoc(*opt); + libmongoc::client_pool_set_ssl_opts(_pool, &v); + } +#else + if (tls_enabled || v1::client::options::internal::tls_opts(client_opts)) { + throw v1::exception::internal::make(v1::client::errc::tls_not_supported); + } +#endif + } +} + +pool::pool(v1::uri const& uri) : _impl{new impl{v1::uri::internal::as_mongoc(uri)}} {} + +// NOLINTEND(cppcoreguidelines-owning-memory) + +pool::pool() : pool{v1::uri{}} {} + +pool::operator bool() const { + return _impl != nullptr; +} + +pool::entry pool::acquire() { + auto const _pool = impl::with(this)->_pool; + + if (auto const ptr = libmongoc::client_pool_pop(_pool)) { + return entry::internal::make(_pool, ptr); + } + + throw v1::exception::internal::make(code::wait_queue_timeout); +} + +bsoncxx::v1::stdx::optional pool::try_acquire() { + auto const _pool = impl::with(this)->_pool; + + if (auto const ptr = libmongoc::client_pool_try_pop(_pool)) { + return entry::internal::make(_pool, ptr); + } + + return {}; +} + +std::error_category const& pool::error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "mongocxx::v1::pool"; + } + + std::string message(int v) const noexcept override { + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::wait_queue_timeout: + return "failed to acquire a client object due to waitQueueTimeoutMS"; + default: + return std::string(this->name()) + ':' + std::to_string(v); + } + } + + bool equivalent(int v, std::error_condition const& ec) const noexcept override { + if (ec.category() == v1::source_error_category()) { + using condition = v1::source_errc; + + auto const source = static_cast(ec.value()); + + switch (static_cast(v)) { + case code::wait_queue_timeout: + return source == condition::mongocxx; + + case code::zero: + default: + return false; + } + } + + if (ec.category() == v1::type_error_category()) { + using condition = v1::type_errc; + + auto const type = static_cast(ec.value()); + + switch (static_cast(v)) { + case code::wait_queue_timeout: + return type == condition::runtime_error; + + case code::zero: + default: + return false; + } + } + + return false; + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +pool::pool(void* impl) : _impl{impl} {} + +class pool::options::impl { + public: + v1::client::options _client_opts; + + explicit impl(v1::client::options client_opts) : _client_opts{std::move(client_opts)} {} + + impl() = default; + + static impl const& with(options const& other) { + return *static_cast(other._impl); + } + + static impl const* with(options const* other) { + return static_cast(other->_impl); + } + + static impl& with(options& other) { + return *static_cast(other._impl); + } + + static impl* with(options* other) { + return static_cast(other->_impl); + } + + static impl* with(void* ptr) { + return static_cast(ptr); + } +}; + +// NOLINTBEGIN(cppcoreguidelines-owning-memory): owning void* for ABI stability. + +pool::options::~options() { + delete impl::with(_impl); +} + +pool::options::options(options&& other) noexcept : _impl{exchange(other._impl, nullptr)} {} + +pool::options& pool::options::operator=(options&& other) noexcept { + if (this != &other) { + delete impl::with(exchange(_impl, exchange(other._impl, nullptr))); + } + + return *this; +} + +pool::options::options(options const& other) : _impl{new impl{impl::with(other)}} {} + +pool::options& pool::options::operator=(options const& other) { + if (this != &other) { + delete impl::with(exchange(_impl, new impl{impl::with(other)})); + } + + return *this; +} + +pool::options::options(v1::client::options opts) : _impl{new impl{std::move(opts)}} {} + +pool::options::options() : _impl{new impl{}} {} + +// NOLINTEND(cppcoreguidelines-owning-memory) + +v1::client::options pool::options::client_opts() const { + return impl::with(this)->_client_opts; +} + +class pool::entry::impl { + public: + mongoc_client_pool_t* _pool; + v1::client _client; + + ~impl() { + libmongoc::client_pool_push(_pool, v1::client::internal::release(_client)); + } + + impl(impl&& other) noexcept = delete; + impl& operator=(impl&& other) noexcept = delete; + impl(impl const& other) = delete; + impl& operator=(impl const& other) = delete; + + impl(mongoc_client_pool_t* pool, v1::client client) : _pool{pool}, _client{std::move(client)} {} + + static impl const& with(entry const& other) { + return *static_cast(other._impl); + } + + static impl const* with(entry const* other) { + return static_cast(other->_impl); + } + + static impl& with(entry& other) { + return *static_cast(other._impl); + } + + static impl* with(entry* other) { + return static_cast(other->_impl); + } + + static impl* with(void* ptr) { + return static_cast(ptr); + } +}; + +// NOLINTBEGIN(cppcoreguidelines-owning-memory): owning void* for ABI stability. + +pool::entry::~entry() { + delete impl::with(_impl); + _impl = nullptr; // scan-build: warning: Use of memory after it is freed [cplusplus.NewDelete] +} + +pool::entry::entry(entry&& other) noexcept : _impl{exchange(other._impl, nullptr)} {} + +pool::entry& pool::entry::entry::operator=(entry&& other) noexcept { + if (this != &other) { + delete impl::with(exchange(_impl, exchange(other._impl, nullptr))); + } + + return *this; +} + +// NOLINTEND(cppcoreguidelines-owning-memory) + +pool::entry::entry(void* impl) : _impl{impl} {} + +v1::client* pool::entry::operator->() { + return &impl::with(this)->_client; +} + +v1::client& pool::entry::operator*() { + return impl::with(this)->_client; +} + +pool::entry& pool::entry::operator=(std::nullptr_t) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory): owning void* for ABI stability. + delete impl::with(exchange(_impl, nullptr)); + return *this; +} + +pool::entry::operator bool() const { + return _impl != nullptr; +} + +v1::database pool::entry::database(bsoncxx::v1::stdx::string_view name) { + return impl::with(this)->_client.database(name); +} + +v1::database pool::entry::operator[](bsoncxx::v1::stdx::string_view name) { + return this->database(name); +} + +pool pool::internal::make(mongoc_client_pool_t* ptr) { + return {new impl{ptr}}; +} + +mongoc_client_pool_t* pool::internal::as_mongoc(pool& self) { + return impl::with(self)._pool; +} + +void pool::internal::set_apm(pool& self, v1::apm v) { + auto& _apm = impl::with(self)._apm; + _apm = std::move(v); + v1::apm::internal::set_apm_callbacks(impl::with(self)._pool, _apm); +} + +v1::client::options& pool::options::internal::client_opts(options& self) { + return impl::with(self)._client_opts; +} + +pool::entry pool::entry::internal::make(mongoc_client_pool_t* pool, mongoc_client_t* client) { + return {new impl{pool, v1::client::internal::make(client)}}; +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/pool.hh b/src/mongocxx/lib/mongocxx/v1/pool.hh new file mode 100644 index 0000000000..e28932d693 --- /dev/null +++ b/src/mongocxx/lib/mongocxx/v1/pool.hh @@ -0,0 +1,47 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed 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 // IWYU pragma: export + +// + +#include + +#include +#include + +namespace mongocxx { +namespace v1 { + +class pool::internal { + public: + static MONGOCXX_ABI_EXPORT_CDECL_TESTING(pool) make(mongoc_client_pool_t* ptr); + + static MONGOCXX_ABI_EXPORT_CDECL_TESTING(mongoc_client_pool_t*) as_mongoc(pool& self); + + static void set_apm(pool& self, v1::apm v); +}; + +class pool::options::internal { + public: + static v1::client::options& client_opts(options& self); +}; + +class pool::entry::internal { + public: + static MONGOCXX_ABI_EXPORT_CDECL(entry) make(mongoc_client_pool_t* pool, mongoc_client_t* client); +}; + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/tls.cpp b/src/mongocxx/lib/mongocxx/v1/tls.cpp index 854be0c63f..4f7477df34 100644 --- a/src/mongocxx/lib/mongocxx/v1/tls.cpp +++ b/src/mongocxx/lib/mongocxx/v1/tls.cpp @@ -22,6 +22,7 @@ #include #include +#include #include namespace mongocxx { @@ -179,6 +180,7 @@ bsoncxx::v1::stdx::optional& tls::internal::crl_file(tls& self) { return impl::with(self)._crl_file; } +#if MONGOCXX_SSL_IS_ENABLED() mongoc_ssl_opt_t tls::internal::to_mongoc(v1::tls const& self) { mongoc_ssl_opt_t ret = {}; @@ -206,6 +208,7 @@ mongoc_ssl_opt_t tls::internal::to_mongoc(v1::tls const& self) { return ret; } +#endif } // namespace v1 } // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp index 1a07a39967..ca09870599 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp @@ -343,8 +343,8 @@ v_noabi::change_stream client::_watch( v1::client::internal::as_mongoc(check_moved_from(_client)), pipeline.bson(), opts.bson())); } -void client::internal::disown(client& self) { - v1::client::internal::disown(check_moved_from(self._client)); +mongoc_client_t* client::internal::release(client& self) { + return v1::client::internal::release(check_moved_from(self._client)); } v1::client& client::internal::as_v1(client& self) { diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.hh index e0d263d292..72a17ceb2a 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.hh @@ -25,7 +25,7 @@ namespace v_noabi { class client::internal { public: - static void disown(client& self); + static mongoc_client_t* release(client& self); static v1::client& as_v1(client& self); static mongoc_client_t* as_mongoc(client& self); diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/auto_encryption.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/auto_encryption.cpp index 1d9a0c4e55..b47b7958ff 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/auto_encryption.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/auto_encryption.cpp @@ -143,7 +143,7 @@ auto_encryption::internal::to_mongoc(auto_encryption const& opts) { } if (auto const& opt = opts.key_vault_pool()) { - libmongoc::auto_encryption_opts_set_keyvault_client_pool(ptr, (*opt)->_impl->client_pool_t); + libmongoc::auto_encryption_opts_set_keyvault_client_pool(ptr, v_noabi::pool::internal::as_mongoc(**opt)); } if (auto const& opt = opts.key_vault_namespace()) { diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/pool.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/pool.cpp index 04c68f2d25..0d6b7419a7 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/pool.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/pool.cpp @@ -13,17 +13,3 @@ // limitations under the License. #include - -namespace mongocxx { -namespace v_noabi { -namespace options { - -pool::pool(client client_opts) : _client_opts(std::move(client_opts)) {} - -client const& pool::client_opts() const { - return _client_opts; -} - -} // namespace options -} // namespace v_noabi -} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.cpp index 358c8ca17f..bab31a99f3 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.cpp @@ -12,142 +12,127 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include + +#include // #include +#include +#include #include +#include + #include #include #include -#include +#include +#include #include #include -#include #include #include #include -#include #include -#include +#include +#include #include namespace mongocxx { namespace v_noabi { -// Attempts to create a new client pool using the uri. Throws an exception upon error. -static mongoc_client_pool_t* construct_client_pool(mongoc_uri_t const* uri) { - bson_error_t error; - auto pool = libmongoc::client_pool_new_with_error(uri, &error); - if (!pool) { - // If constructing a client pool failed, throw an exception from the bson_error_t. - throw_exception(error); - } - - return pool; -} - -void pool::_release(client* client) { - libmongoc::client_pool_push(_impl->client_pool_t, v_noabi::client::internal::as_mongoc(*client)); - // prevent client destructor from destroying the underlying mongoc_client_t - v_noabi::client::internal::disown(*client); - delete client; // NOLINT(cppcoreguidelines-owning-memory): custom deleter. -} - -pool::~pool() = default; +pool::pool(v_noabi::uri const& mongodb_uri, v_noabi::options::pool const& options) try + : _pool{v_noabi::uri::internal::as_v1(mongodb_uri)} { + auto const ptr = v1::pool::internal::as_mongoc(_pool); -pool::pool(uri const& uri, options::pool const& options) - : _impl{bsoncxx::make_unique(construct_client_pool(v_noabi::uri::internal::as_mongoc(uri)))} { -#if MONGOCXX_SSL_IS_ENABLED() - if (auto const& opts = options.client_opts().tls_opts()) { - if (!uri.tls()) { - throw v_noabi::exception{ - v_noabi::error_code::k_invalid_parameter, "cannot set TLS options if 'tls=true' not in URI"}; - } + auto const& client_opts = options.client_opts(); - auto const v = v_noabi::options::tls::internal::to_mongoc(*opts); - libmongoc::client_pool_set_ssl_opts(_impl->client_pool_t, &v.opt); + if (auto const& opt = client_opts.apm_opts()) { + v1::pool::internal::set_apm(_pool, v_noabi::to_v1(*opt)); } -#else - if (uri.tls() || options.client_opts().tls_opts().has_value()) - throw v_noabi::exception{v_noabi::error_code::k_ssl_not_supported}; -#endif - if (auto const& opts = options.client_opts().auto_encryption_opts()) { + if (auto const& opt = client_opts.auto_encryption_opts()) { bson_error_t error = {}; if (!libmongoc::client_pool_enable_auto_encryption( - _impl->client_pool_t, v_noabi::options::auto_encryption::internal::to_mongoc(*opts).get(), &error)) { - v_noabi::throw_exception(error); + ptr, v_noabi::options::auto_encryption::internal::to_mongoc(*opt).get(), &error)) { + v_noabi::throw_exception(error); } } - if (auto const& opts = options.client_opts().apm_opts()) { - _impl->listeners = *opts; - libmongoc::client_pool_set_apm_callbacks( - _impl->client_pool_t, - v_noabi::options::make_apm_callbacks(_impl->listeners).get(), - static_cast(&_impl->listeners)); - } - - if (auto const& opts = options.client_opts().server_api_opts()) { + if (auto const& opt = client_opts.server_api_opts()) { bson_error_t error = {}; if (!libmongoc::client_pool_set_server_api( - _impl->client_pool_t, v_noabi::options::server_api::internal::to_mongoc(*opts).get(), &error)) { + ptr, v_noabi::options::server_api::internal::to_mongoc(*opt).get(), &error)) { v_noabi::throw_exception(error); } } -} -client* pool::entry::operator->() const& noexcept { - return _client.get(); -} + { + auto const tls_enabled = mongodb_uri.tls(); -client& pool::entry::operator*() const& noexcept { - return *_client; +#if MONGOCXX_SSL_IS_ENABLED() + if (auto const& opt = client_opts.tls_opts()) { + if (!tls_enabled) { + throw v_noabi::exception{ + v_noabi::error_code::k_invalid_parameter, "cannot set TLS options if 'tls=true' not in URI"}; + } + + auto const v = v_noabi::options::tls::internal::to_mongoc(*opt); + libmongoc::client_pool_set_ssl_opts(ptr, &v.opt); + } +#else + if (tls_enabled || client_opts.tls_opts()) { + throw v_noabi::exception{v_noabi::error_code::k_ssl_not_supported}; + } +#endif + } +} catch (v1::exception const& ex) { + v_noabi::throw_exception(ex); } -pool::entry& pool::entry::operator=(std::nullptr_t) noexcept { - _client = nullptr; - return *this; -} +pool::entry pool::acquire() { + auto const ptr = v1::pool::internal::as_mongoc(_pool); -pool::entry::operator bool() const noexcept { - return static_cast(_client); + if (auto const client = libmongoc::client_pool_pop(ptr)) { + return entry::internal::make(v1::client::internal::make(client), ptr); + } + + throw v_noabi::exception{ + v_noabi::error_code::k_pool_wait_queue_timeout, + "failed to acquire client, possibly due to parameter 'waitQueueTimeoutMS' limits."}; } -// construct a pool entry from a pointer to a client -pool::entry::entry(pool::entry::unique_client p) : _client(std::move(p)) {} +bsoncxx::v_noabi::stdx::optional pool::try_acquire() { + bsoncxx::v_noabi::stdx::optional ret; -pool::entry pool::acquire() { - auto const cli = libmongoc::client_pool_pop(_impl->client_pool_t); - if (!cli) { - throw exception{ - error_code::k_pool_wait_queue_timeout, - "failed to acquire client, possibly due to parameter 'waitQueueTimeoutMS' limits."}; + auto const ptr = v1::pool::internal::as_mongoc(_pool); + + if (auto const client = libmongoc::client_pool_try_pop(ptr)) { + return entry::internal::make(v1::client::internal::make(client), ptr); } - return entry(entry::unique_client(new v_noabi::client{v1::client::internal::make(cli)}, [this](client* client) { - _release(client); - })); + return ret; } -bsoncxx::v_noabi::stdx::optional pool::try_acquire() { - auto const cli = libmongoc::client_pool_try_pop(_impl->client_pool_t); - if (!cli) { - return bsoncxx::v_noabi::stdx::nullopt; - } +mongoc_client_pool_t* pool::internal::as_mongoc(pool& self) { + return v1::pool::internal::as_mongoc(self._pool); +} - return entry(entry::unique_client(new v_noabi::client{v1::client::internal::make(cli)}, [this](client* client) { - _release(client); - })); +pool::entry pool::entry::internal::make(v_noabi::client client, mongoc_client_pool_t* pool) { + return ptr_type{new v_noabi::client{std::move(client)}, [pool](v_noabi::client* ptr) { + if (ptr) { + libmongoc::client_pool_push(pool, v_noabi::client::internal::release(*ptr)); + delete ptr; // NOLINT(cppcoreguidelines-owning-memory): custom deleter. + } + }}; } } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.hh index e68ce06d2e..1cf9abca5a 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.hh @@ -14,33 +14,25 @@ #pragma once -#include -#include - #include // IWYU pragma: export +// + +#include + #include namespace mongocxx { namespace v_noabi { -class pool::impl { +class pool::internal { public: - impl(mongoc_client_pool_t* pool) : client_pool_t(pool) {} - - ~impl() { - libmongoc::client_pool_destroy(client_pool_t); - } - - impl(impl&&) = delete; - impl& operator=(impl&&) = delete; - - impl(impl const&) = delete; - impl& operator=(impl const&) = delete; + static mongoc_client_pool_t* as_mongoc(pool& self); +}; - mongoc_client_pool_t* client_pool_t; - std::list tls_options; - options::apm listeners; +class pool::entry::internal { + public: + static entry make(v_noabi::client client, mongoc_client_pool_t* pool); }; } // namespace v_noabi diff --git a/src/mongocxx/test/CMakeLists.txt b/src/mongocxx/test/CMakeLists.txt index ecd3622546..a5456af1d3 100644 --- a/src/mongocxx/test/CMakeLists.txt +++ b/src/mongocxx/test/CMakeLists.txt @@ -138,6 +138,7 @@ set(mongocxx_test_sources_v1 v1/insert_one_result.cpp v1/logger.cpp v1/pipeline.cpp + v1/pool.cpp v1/range_options.cpp v1/read_concern.cpp v1/read_preference.cpp diff --git a/src/mongocxx/test/v1/client.cpp b/src/mongocxx/test/v1/client.cpp index 03d6e434bd..c8a8104826 100644 --- a/src/mongocxx/test/v1/client.cpp +++ b/src/mongocxx/test/v1/client.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -816,11 +817,7 @@ TEST_CASE("auto_encryption_opts", "[mongocxx][v1][client]") { identity_type kv_client_identity; auto const kv_client_id = reinterpret_cast(&kv_client_identity); auto kv_client = v1::client::internal::make(kv_client_id); - mocks.client_destroy->interpose([&](mongoc_client_t* ptr) -> void { - if (ptr != kv_client_id) { - FAIL_CHECK("unexpected mongoc_client_t"); - } - }); + mocks.client_destroy->interpose([&](mongoc_client_t* ptr) -> void { CHECK(ptr == kv_client_id); }); int counter = 0; auto set_keyvault_client = libmongoc::auto_encryption_opts_set_keyvault_client.create_instance(); @@ -848,7 +845,33 @@ TEST_CASE("auto_encryption_opts", "[mongocxx][v1][client]") { } SECTION("key_vault_pool") { - // TODO: v1::pool (CXX-3237) + auto pool_destroy = libmongoc::client_pool_destroy.create_instance(); + + identity_type kv_pool_identity; + auto const kv_pool_id = reinterpret_cast(&kv_pool_identity); + auto kv_pool = v1::pool::internal::make(kv_pool_id); + pool_destroy->interpose([&](mongoc_client_pool_t* ptr) -> void { CHECK(ptr == kv_pool_id); }); + + int counter = 0; + auto set_keyvault_client_pool = libmongoc::auto_encryption_opts_set_keyvault_client_pool.create_instance(); + set_keyvault_client_pool->interpose( + [&](mongoc_auto_encryption_opts_t* ptr, mongoc_client_pool_t* kv_ptr) -> void { + CHECK(ptr == opts_id); + CHECK(kv_ptr == kv_pool_id); + ++counter; + }); + + { + v1::auto_encryption_options auto_encryption_opts; + CHECK_NOTHROW(auto_encryption_opts.key_vault_pool(&kv_pool)); + + client::options opts; + CHECK_NOTHROW(opts.auto_encryption_opts(std::move(auto_encryption_opts))); + (void)mocks.make(std::move(opts)); + } + + CHECK(counter == 1); + CHECK(enable_count == 1); } SECTION("key_vault_namespace") { diff --git a/src/mongocxx/test/v1/pool.cpp b/src/mongocxx/test/v1/pool.cpp new file mode 100644 index 0000000000..0ea4f6eb7a --- /dev/null +++ b/src/mongocxx/test/v1/pool.cpp @@ -0,0 +1,934 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed 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 + +// + +#include // IWYU pragma: keep: `.apm_opts()` +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +namespace mongocxx { +namespace v1 { + +using code = mongocxx::v1::pool::errc; + +TEST_CASE("error code", "[bsoncxx][v1][pool][error]") { + using mongocxx::v1::source_errc; + using mongocxx::v1::type_errc; + + auto const& category = mongocxx::v1::pool::error_category(); + CHECK_THAT(category.name(), Catch::Matchers::Equals("mongocxx::v1::pool")); + + auto const zero_errc = make_error_condition(static_cast(0)); + + SECTION("unknown") { + std::error_code const ec = static_cast(-1); + + CHECK(ec.category() == category); + CHECK(ec.value() == -1); + CHECK(ec); + CHECK(ec.message() == std::string(category.name()) + ":-1"); + } + + SECTION("zero") { + std::error_code const ec = code::zero; + + CHECK(ec.category() == category); + CHECK(ec.value() == 0); + CHECK_FALSE(ec); + CHECK(ec.message() == "zero"); + + CHECK(ec != zero_errc); + CHECK(ec != source_errc::zero); + CHECK(ec != type_errc::zero); + } + + SECTION("non-zero") { + std::error_code const ec = code::wait_queue_timeout; + + CHECK(ec.category() == category); + CHECK(ec.value() != static_cast(code::zero)); + CHECK(ec); + CHECK(ec.message() != "zero"); + + CHECK(ec != zero_errc); + CHECK(ec != source_errc::zero); + CHECK(ec != type_errc::zero); + } + + SECTION("source") { + CHECK(make_error_code(code::wait_queue_timeout) == source_errc::mongocxx); + } + + SECTION("type") { + CHECK(make_error_code(code::wait_queue_timeout) == type_errc::runtime_error); + } +} + +namespace { + +struct identity_type {}; + +// Represent a successfully-constructed mocked `v1::client` object. +struct pool_mocks_type { + using uri_destroy_type = decltype(libmongoc::uri_destroy.create_instance()); + using uri_copy_type = decltype(libmongoc::uri_copy.create_instance()); + using uri_get_tls_type = decltype(libmongoc::uri_get_tls.create_instance()); + using pool_destroy_type = decltype(libmongoc::client_pool_destroy.create_instance()); + using pool_new_type = decltype(libmongoc::client_pool_new_with_error.create_instance()); + using pop_type = decltype(libmongoc::client_pool_pop.create_instance()); + using try_pop_type = decltype(libmongoc::client_pool_try_pop.create_instance()); + + identity_type uri_identity; + identity_type pool_identity; + + mongoc_uri_t* uri_id = reinterpret_cast(&uri_identity); + mongoc_client_pool_t* pool_id = reinterpret_cast(&pool_identity); + + uri_destroy_type uri_destroy = libmongoc::uri_destroy.create_instance(); + uri_copy_type uri_copy = libmongoc::uri_copy.create_instance(); + uri_get_tls_type uri_get_tls = libmongoc::uri_get_tls.create_instance(); + pool_destroy_type destroy = libmongoc::client_pool_destroy.create_instance(); + pool_new_type new_with_error = libmongoc::client_pool_new_with_error.create_instance(); + + pop_type pop = libmongoc::client_pool_pop.create_instance(); + try_pop_type try_pop = libmongoc::client_pool_try_pop.create_instance(); + + v1::uri uri = v1::uri::internal::make(uri_id); + + ~pool_mocks_type() = default; + pool_mocks_type(pool_mocks_type&& other) noexcept = delete; + pool_mocks_type& operator=(pool_mocks_type&& other) noexcept = delete; + pool_mocks_type(pool_mocks_type const& other) = delete; + pool_mocks_type& operator=(pool_mocks_type const& other) = delete; + + pool_mocks_type() { + uri_destroy + ->interpose([&](mongoc_uri_t* ptr) { + if (ptr) { + CHECK(ptr == uri_id); + } + }) + .forever(); + + uri_copy + ->interpose([&](mongoc_uri_t const* ptr) -> mongoc_uri_t* { + CHECK(ptr == uri_id); + FAIL("should not reach this point"); + return nullptr; + }) + .forever(); + + uri_get_tls + ->interpose([&](mongoc_uri_t const* ptr) -> bool { + CHECK(ptr == uri_id); + return false; + }) + .forever(); + + destroy->interpose([&](mongoc_client_pool_t* ptr) { CHECK(ptr == pool_id); }).forever(); + + new_with_error + ->interpose([&](mongoc_uri_t const* uri, bson_error_t* error) -> mongoc_client_pool_t* { + CHECK(uri == uri_id); + CHECK(error != nullptr); + return pool_id; + }) + .forever(); + + pop->interpose([&](mongoc_client_pool_t* ptr) -> mongoc_client_t* { + CHECK(ptr == pool_id); + FAIL("should not reach this point"); + return nullptr; + }) + .forever(); + + try_pop + ->interpose([&](mongoc_client_pool_t* ptr) -> mongoc_client_t* { + CHECK(ptr == pool_id); + FAIL("should not reach this point"); + return nullptr; + }) + .forever(); + } + + template + v1::pool make(Args&&... args) { + return {std::move(uri), std::forward(args)...}; + } +}; + +} // namespace + +TEST_CASE("exceptions", "[mongocxx][v1][pool]") { + pool_mocks_type mocks; + + SECTION("mongocxx") { +#if MONGOCXX_SSL_IS_ENABLED() + SECTION("tls_not_enabled") { + mocks.uri_get_tls->interpose([&](mongoc_uri_t const* uri) -> bool { + CHECK(uri == mocks.uri_id); + return false; + }); + + auto set_ssl_opts = libmongoc::client_pool_set_ssl_opts.create_instance(); + set_ssl_opts->interpose([&](mongoc_client_pool_t* pool, mongoc_ssl_opt_t const* opts) -> void { + CHECK(pool == mocks.pool_id); + CHECK(opts != nullptr); + FAIL("should not reach this point"); + }); + + client::options opts; + CHECK_NOTHROW(opts.tls_opts(v1::tls{})); + CHECK_THROWS_WITH_CODE(mocks.make(pool::options{std::move(opts)}), v1::client::errc::tls_not_enabled); + } +#else + SECTION("tls_not_supported") { + auto const tls_enabled = GENERATE(false, true); + CAPTURE(tls_enabled); + + auto const tls_opts_set = GENERATE(false, true); + CAPTURE(tls_opts_set); + + if (tls_enabled || tls_opts_set) { + mocks.uri_get_tls->interpose([&](mongoc_uri_t const* uri) -> bool { + CHECK(uri == mocks.uri_id); + return tls_enabled; + }); + + client::options opts; + CHECK_NOTHROW(opts.tls_opts(v1::tls{})); + CHECK_THROWS_WITH_CODE(mocks.make(pool::options{std::move(opts)}), v1::client::errc::tls_not_supported); + } + } +#endif + + SECTION("wait_queue_timeout") { + mocks.pop->interpose([&](mongoc_client_pool_t* ptr) -> mongoc_client_t* { + CHECK(ptr == mocks.pool_id); + return nullptr; + }); + + auto pool = mocks.make(); + + CHECK_THROWS_WITH_CODE(pool.acquire(), code::wait_queue_timeout); + } + } + + SECTION("mongoc") { + auto const v = GENERATE(1, 2, 3); + auto const msg = GENERATE("one", "two", "three"); + + auto const set_error = [&](bson_error_t* error) { + REQUIRE(error != nullptr); + bson_set_error(error, MONGOC_ERROR_CLIENT, static_cast(v), "%s", msg); + error->reserved = 2; // MONGOC_ERROR_CATEGORY + }; + + SECTION("new_with_error") { + mocks.new_with_error->interpose([&](mongoc_uri_t const* uri, bson_error_t* error) -> mongoc_client_pool_t* { + CHECK(uri == mocks.uri_id); + set_error(error); + return nullptr; + }); + + try { + (void)mocks.make(); + FAIL("should not reach this point"); + } catch (v1::exception const& ex) { + CHECK(ex.code() == v1::source_errc::mongoc); + CHECK(ex.code().value() == static_cast(v)); + CHECK_THAT(ex.what(), Catch::Matchers::ContainsSubstring(msg)); + } + } + + SECTION("enable_auto_encryption") { + auto enable_auto_encryption = libmongoc::client_pool_enable_auto_encryption.create_instance(); + enable_auto_encryption->interpose( + [&](mongoc_client_pool_t* ptr, mongoc_auto_encryption_opts_t* opts, bson_error_t* error) -> bool { + CHECK(ptr == mocks.pool_id); + CHECK(opts != nullptr); + set_error(error); + return false; + }); + + client::options opts; + CHECK_NOTHROW(opts.auto_encryption_opts(v1::auto_encryption_options{})); + + try { + (void)mocks.make(std::move(opts)); + FAIL("should not reach this point"); + } catch (v1::exception const& ex) { + CHECK(ex.code() == v1::source_errc::mongoc); + CHECK(ex.code().value() == static_cast(v)); + CHECK_THAT(ex.what(), Catch::Matchers::ContainsSubstring(msg)); + } + } + + SECTION("set_server_api") { + auto set_server_api = libmongoc::client_pool_set_server_api.create_instance(); + set_server_api->interpose( + [&](mongoc_client_pool_t* ptr, mongoc_server_api_t const* api, bson_error_t* error) -> bool { + CHECK(ptr == mocks.pool_id); + CHECK(api != nullptr); + set_error(error); + return false; + }); + + client::options opts; + CHECK_NOTHROW(opts.server_api_opts(v1::server_api{v1::server_api::version::k_version_1})); + + try { + (void)mocks.make(std::move(opts)); + FAIL("should not reach this point"); + } catch (v1::exception const& ex) { + CHECK(ex.code() == v1::source_errc::mongoc); + CHECK(ex.code().value() == static_cast(v)); + CHECK_THAT(ex.what(), Catch::Matchers::ContainsSubstring(msg)); + } + } + } +} + +TEST_CASE("ownership", "[mongocxx][v1][pool]") { + identity_type id1; + identity_type id2; + + auto const pool1 = reinterpret_cast(&id1); + auto const pool2 = reinterpret_cast(&id2); + + int destroy_count = 0; + + auto destroy = libmongoc::client_pool_destroy.create_instance(); + destroy + ->interpose([&](mongoc_client_pool_t* ptr) -> void { + if (ptr != pool1 && ptr != pool2) { + FAIL_CHECK("unexpected mongoc_client_pool_t"); + } + ++destroy_count; + }) + .forever(); + + auto source = pool::internal::make(pool1); + auto target = pool::internal::make(pool2); + + REQUIRE(pool::internal::as_mongoc(source) == pool1); + REQUIRE(pool::internal::as_mongoc(target) == pool2); + + REQUIRE(source); + REQUIRE(target); + + SECTION("move") { + { + auto move = std::move(source); + + // source is in an assign-or-move-only state. + CHECK_FALSE(source); + + CHECK(move); + CHECK(pool::internal::as_mongoc(move) == pool1); + CHECK(destroy_count == 0); + + target = std::move(move); + + // move is in an assign-or-move-only state. + CHECK_FALSE(move); + + CHECK(target); + CHECK(pool::internal::as_mongoc(target) == pool1); + CHECK(destroy_count == 1); + } + + CHECK(destroy_count == 1); + } +} + +TEST_CASE("ownership", "[mongocxx][v1][pool][options]") { + auto source_value = std::string{"source"}; + auto target_value = std::string{"target"}; + + pool::options source = v1::client::options{}.tls_opts(std::move(v1::tls{}.ca_file(source_value))); + pool::options target = v1::client::options{}.tls_opts(std::move(v1::tls{}.ca_file(target_value))); + + auto const ca_file = [](pool::options& opts) -> std::string { + return std::string{opts.client_opts().tls_opts().value().ca_file().value()}; + }; + + REQUIRE(ca_file(source) == "source"); + REQUIRE(ca_file(target) == "target"); + + SECTION("move") { + auto move = std::move(source); + + // source is in an assign-or-move-only state. + + CHECK(ca_file(move) == source_value); + + target = std::move(move); + + // source is in an assign-or-move-only state. + + CHECK(ca_file(target) == source_value); + } + + SECTION("copy") { + auto copy = source; + + CHECK(ca_file(source) == source_value); + CHECK(ca_file(copy) == source_value); + + target = copy; + + CHECK(ca_file(copy) == source_value); + CHECK(ca_file(target) == source_value); + } +} + +TEST_CASE("ownership", "[mongocxx][v1][pool][entry]") { + identity_type id1; + identity_type id2; + + auto const pool1 = reinterpret_cast(&id1); + auto const pool2 = reinterpret_cast(&id2); + + identity_type client1_id; + identity_type client2_id; + + auto const client1 = reinterpret_cast(&client1_id); + auto const client2 = reinterpret_cast(&client2_id); + + auto client_destroy = libmongoc::client_destroy.create_instance(); + auto push = libmongoc::client_pool_push.create_instance(); + + client_destroy + ->interpose([&](mongoc_client_t* ptr) { + CHECK(ptr == nullptr); // Always released back into mongoc_client_pool_t. + }) + .forever(); + + int push_count = 0; + push->interpose([&](mongoc_client_pool_t* ptr, mongoc_client_t* client) -> void { + if (ptr != pool1 && ptr != pool2) { + FAIL_CHECK("unexpected mongoc_client_pool_t"); + } + + if (client != client1 && client != client2) { + FAIL_CHECK("unexpected mongoc_client_t"); + } + + ++push_count; + }) + .forever(); + + auto source = pool::entry::internal::make(pool1, client1); + auto target = pool::entry::internal::make(pool2, client2); + + REQUIRE(source); + REQUIRE(target); + + REQUIRE(v1::client::internal::as_mongoc(*source) == client1); + REQUIRE(v1::client::internal::as_mongoc(*target) == client2); + + REQUIRE(push_count == 0); + + SECTION("move") { + { + auto move = std::move(source); + + // source is in an assign-or-move-only state. + CHECK_FALSE(source); + + REQUIRE(move); + REQUIRE(*move); + CHECK(v1::client::internal::as_mongoc(*move) == client1); + CHECK(v1::client::internal::as_mongoc(*move.operator->()) == client1); + CHECK(push_count == 0); + + target = std::move(move); + + // move is in an assign-or-move-only state. + CHECK_FALSE(move); + + REQUIRE(target); + REQUIRE(*target); + CHECK(v1::client::internal::as_mongoc(*target) == client1); + CHECK(v1::client::internal::as_mongoc(*target.operator->()) == client1); + CHECK(push_count == 1); + } + + CHECK(push_count == 1); + } +} + +TEST_CASE("default", "[mongocxx][v1][pool]") { + v1::pool const pool; + + CHECK(pool); +} + +TEST_CASE("default", "[mongocxx][v1][pool][options]") { + auto const client_opts = pool::options{}.client_opts(); + + CHECK_FALSE(client_opts.tls_opts().has_value()); + CHECK_FALSE(client_opts.auto_encryption_opts().has_value()); + CHECK_FALSE(client_opts.apm_opts().has_value()); + CHECK_FALSE(client_opts.server_api_opts().has_value()); +} + +namespace { + +struct callback_fn_type { + template + void operator()(Event const&) const noexcept { + FAIL_CHECK("should not reach this point"); + } +}; + +struct callback_mock_fn_type { + mongoc_apm_callbacks_t* callbacks_id; + int* counter; + + callback_mock_fn_type(mongoc_apm_callbacks_t* callbacks_id, int* counter) + : callbacks_id{callbacks_id}, counter{counter} { + REQUIRE(callbacks_id != nullptr); + REQUIRE(counter != nullptr); + } + + template + void operator()(mongoc_apm_callbacks_t* ptr, Callback cb) { + CHECK(ptr == callbacks_id); + CHECK(cb != nullptr); + *counter += 1; + } +}; + +} // namespace + +// Defer thorough test coverage to v1::client::options. +TEST_CASE("apm_opts", "[mongocxx][v1][pool]") { + pool_mocks_type mocks; + + identity_type callbacks_identity; + auto const callbacks_id = reinterpret_cast(&callbacks_identity); + + v1::apm callbacks; + callbacks.on_command_started(callback_fn_type{}); + + auto callbacks_destroy = libmongoc::apm_callbacks_destroy.create_instance(); + auto callbacks_new = libmongoc::apm_callbacks_new.create_instance(); + auto set_apm_callbacks = libmongoc::client_pool_set_apm_callbacks.create_instance(); + auto set_command_started = libmongoc::apm_set_command_started_cb.create_instance(); + + callbacks_destroy->interpose([&](mongoc_apm_callbacks_t* ptr) { CHECK(ptr == callbacks_id); }); + callbacks_new->interpose([&]() -> mongoc_apm_callbacks_t* { return callbacks_id; }).forever(); + set_apm_callbacks->interpose( + [&](mongoc_client_pool_t* ptr, mongoc_apm_callbacks_t* callbacks, void* context) -> bool { + CHECK(ptr == mocks.pool_id); + CHECK(callbacks == callbacks_id); + CHECK(context != nullptr); + return true; + }); + + int counter = 0; + set_command_started->interpose(callback_mock_fn_type{callbacks_id, &counter}); + + { + client::options opts; + CHECK_NOTHROW(opts.apm_opts(std::move(callbacks))); + (void)mocks.make(std::move(opts)); + } + + CHECK(counter == 1); +} + +// Defer thorough test coverage to v1::client::options. +TEST_CASE("auto_encryption_opts", "[mongocxx][v1][pool]") { + // Workaround baffling segmentation faults during destruction of the `mocks` local variable when compiled with GCC + // on RHEL 8 ARM64. Not observed on any other target platform. Compiling with Clang or enabling ASAN suppresses this + // runtime error. This issue seems to only affect this specific test case. (???) + std::unique_ptr mocks_owner{new pool_mocks_type{}}; + auto& mocks = *mocks_owner; + + identity_type opts_identity; + auto const opts_id = reinterpret_cast(&opts_identity); + + auto opts_destroy = libmongoc::auto_encryption_opts_destroy.create_instance(); + auto opts_new = libmongoc::auto_encryption_opts_new.create_instance(); + auto enable_auto_encryption = libmongoc::client_pool_enable_auto_encryption.create_instance(); + + opts_destroy->interpose([&](mongoc_auto_encryption_opts_t* ptr) { CHECK(ptr == opts_id); }).forever(); + + opts_new->interpose([&]() -> mongoc_auto_encryption_opts_t* { return opts_id; }).forever(); + + int enable_count = 0; + enable_auto_encryption->interpose( + [&](mongoc_client_pool_t* ptr, mongoc_auto_encryption_opts_t* opts, bson_error_t* error) -> bool { + CHECK(ptr == mocks.pool_id); + CHECK(opts == opts_id); + CHECK(error != nullptr); + ++enable_count; + return true; + }); + + { + client::options opts; + CHECK_NOTHROW(opts.auto_encryption_opts(v1::auto_encryption_options{})); + (void)mocks.make(std::move(opts)); + } + + CHECK(enable_count == 1); +} + +// Defer thorough test coverage to v1::client::options. +TEST_CASE("server_api_opts", "[mongocxx][v1][pool]") { + pool_mocks_type mocks; + + identity_type server_api_identity; + auto const server_api_id = reinterpret_cast(&server_api_identity); + + auto server_api_destroy = libmongoc::server_api_destroy.create_instance(); + auto server_api_new = libmongoc::server_api_new.create_instance(); + auto set_server_api = libmongoc::client_pool_set_server_api.create_instance(); + + server_api_destroy->interpose([&](mongoc_server_api_t* ptr) { CHECK(ptr == server_api_id); }).forever(); + + server_api_new + ->interpose([&](mongoc_server_api_version_t version) -> mongoc_server_api_t* { + CHECK(version == mongoc_server_api_version_t::MONGOC_SERVER_API_V1); + return server_api_id; + }) + .forever(); + + int set_counter = 0; + set_server_api->interpose( + [&](mongoc_client_pool_t* ptr, mongoc_server_api_t const* api, bson_error_t* error) -> bool { + CHECK(ptr == mocks.pool_id); + CHECK(api == server_api_id); + CHECK(error != nullptr); + ++set_counter; + return true; + }); + + v1::server_api api{v1::server_api::version::k_version_1}; + + auto const input = GENERATE(false, true); + + CHECK_NOTHROW(api.strict(input)); + + int counter = 0; + + auto strict = libmongoc::server_api_strict.create_instance(); + strict->interpose([&](mongoc_server_api_t* ptr, bool v) { + CHECK(ptr == server_api_id); + CHECK(v == input); + ++counter; + }); + + { + client::options opts; + CHECK_NOTHROW(opts.server_api_opts(std::move(api))); + (void)mocks.make(std::move(opts)); + } + + CHECK(counter == 1); + CHECK(set_counter == 1); +} + +// Defer thorough test coverage to v1::client::options. +TEST_CASE("tls_opts", "[mongocxx][v1][pool]") { +#if MONGOCXX_SSL_IS_ENABLED() + pool_mocks_type mocks; + + auto set_ssl_opts = libmongoc::client_pool_set_ssl_opts.create_instance(); + set_ssl_opts + ->interpose([&](mongoc_client_pool_t* ptr, mongoc_ssl_opt_t const* opts) { + CHECK(ptr == mocks.pool_id); + CHECK(opts != nullptr); + FAIL("should not reach this point"); + }) + .forever(); + + auto const value = GENERATE(false, true); + + int set_counter = 0; + set_ssl_opts->interpose([&](mongoc_client_pool_t* ptr, mongoc_ssl_opt_t const* opts) -> void { + CHECK(ptr == mocks.pool_id); + REQUIRE(opts != nullptr); + CHECK(opts->weak_cert_validation == value); + ++set_counter; + }); + + int get_counter = 0; + mocks.uri_get_tls->interpose([&](mongoc_uri_t const* uri) -> bool { + CHECK(uri == mocks.uri_id); + ++get_counter; + return true; + }); + + { + v1::tls tls_opts; + CHECK_NOTHROW(tls_opts.allow_invalid_certificates(std::move(value))); + + v1::client::options opts; + CHECK_NOTHROW(opts.tls_opts(std::move(tls_opts))); + (void)mocks.make(std::move(opts)); + } + + CHECK(set_counter == 1); + CHECK(get_counter == 1); +#else + SKIP(std::error_code{code::tls_not_supported}.message()); +#endif +} + +namespace { + +struct entry_mocks_type { + using client_destroy_type = decltype(libmongoc::client_destroy.create_instance()); + using push_type = decltype(libmongoc::client_pool_push.create_instance()); + + client_destroy_type client_destroy = libmongoc::client_destroy.create_instance(); + push_type push = libmongoc::client_pool_push.create_instance(); + + identity_type client_identity; + mongoc_client_t* client_id = reinterpret_cast(&client_identity); + + int push_count = 0; + + explicit entry_mocks_type(pool_mocks_type& mocks) { + mocks.pop + ->interpose([&](mongoc_client_pool_t* ptr) -> mongoc_client_t* { + CHECK(ptr == mocks.pool_id); + return client_id; + }) + .forever(); + + client_destroy + ->interpose([&](mongoc_client_t* ptr) { + CHECK(ptr == nullptr); // Always released back into mongoc_client_pool_t. + }) + .forever(); + + push->interpose([&](mongoc_client_pool_t* ptr, mongoc_client_t* client) -> void { + CHECK(ptr == mocks.pool_id); + CHECK(client == client_id); + + ++push_count; + }); + } +}; + +} // namespace + +TEST_CASE("acquire", "[mongocxx][v1][pool]") { + pool_mocks_type mocks; + + auto pool = mocks.make(); + + identity_type client_identity; + auto const client_id = reinterpret_cast(&client_identity); + + auto client_destroy = libmongoc::client_destroy.create_instance(); + auto push = libmongoc::client_pool_push.create_instance(); + + mocks.pop->interpose([&](mongoc_client_pool_t* ptr) -> mongoc_client_t* { + CHECK(ptr == mocks.pool_id); + return client_id; + }); + client_destroy + ->interpose([&](mongoc_client_t* ptr) { + CHECK(ptr == nullptr); // Always released back into mongoc_client_pool_t. + }) + .forever(); + + int push_count = 0; + push->interpose([&](mongoc_client_pool_t* ptr, mongoc_client_t* client) -> void { + CHECK(ptr == mocks.pool_id); + CHECK(client == client_id); + + ++push_count; + }); + + { + auto entry = pool.acquire(); + + REQUIRE(entry); + REQUIRE(*entry); + CHECK(v1::client::internal::as_mongoc(*entry) == client_id); + CHECK(v1::client::internal::as_mongoc(*entry.operator->()) == client_id); + + CHECK(push_count == 0); + } + + CHECK(push_count == 1); +} + +TEST_CASE("try_acquire", "[mongocxx][v1][pool]") { + pool_mocks_type mocks; + + auto pool = mocks.make(); + + identity_type client_identity; + auto const client_id = reinterpret_cast(&client_identity); + + auto client_destroy = libmongoc::client_destroy.create_instance(); + auto push = libmongoc::client_pool_push.create_instance(); + + client_destroy + ->interpose([&](mongoc_client_t* ptr) { + CHECK(ptr == nullptr); // Always released back into mongoc_client_pool_t. + }) + .forever(); + + int push_count = 0; + push->interpose([&](mongoc_client_pool_t* ptr, mongoc_client_t* client) -> void { + CHECK(ptr == mocks.pool_id); + CHECK(client == client_id); + + ++push_count; + }); + + SECTION("none") { + mocks.try_pop->interpose([&](mongoc_client_pool_t* ptr) -> mongoc_client_t* { + CHECK(ptr == mocks.pool_id); + return nullptr; + }); + + { + auto entry_opt = pool.try_acquire(); + + CHECK_FALSE(entry_opt.has_value()); + } + + CHECK(push_count == 0); + } + + SECTION("value") { + mocks.try_pop->interpose([&](mongoc_client_pool_t* ptr) -> mongoc_client_t* { + CHECK(ptr == mocks.pool_id); + return client_id; + }); + + { + auto entry_opt = pool.try_acquire(); + + REQUIRE(entry_opt.has_value()); + + auto& entry = *entry_opt; + + REQUIRE(entry); + REQUIRE(*entry); + CHECK(v1::client::internal::as_mongoc(*entry) == client_id); + CHECK(v1::client::internal::as_mongoc(*entry.operator->()) == client_id); + + CHECK(push_count == 0); + } + + CHECK(push_count == 1); + } +} + +TEST_CASE("release", "[mongocxx][v1][pool][entry]") { + pool_mocks_type mocks; + entry_mocks_type entry_mocks{mocks}; + + auto pool = mocks.make(); + + { + auto entry = pool.acquire(); + + REQUIRE(entry); + REQUIRE(*entry); + CHECK(v1::client::internal::as_mongoc(*entry) == entry_mocks.client_id); + CHECK(v1::client::internal::as_mongoc(*entry.operator->()) == entry_mocks.client_id); + + CHECK(entry_mocks.push_count == 0); + + entry = nullptr; + + CHECK(entry_mocks.push_count == 1); + CHECK_FALSE(entry); + + entry = nullptr; + + CHECK(entry_mocks.push_count == 1); + CHECK_FALSE(entry); + } + + CHECK(entry_mocks.push_count == 1); +} + +TEST_CASE("database", "[mongocxx][v1][pool][entry]") { + pool_mocks_type mocks; + entry_mocks_type entry_mocks{mocks}; + + auto database_destroy = libmongoc::database_destroy.create_instance(); + auto get_database = libmongoc::client_get_database.create_instance(); + + identity_type database_identity; + auto const database_id = reinterpret_cast(&database_identity); + + auto const input = GENERATE("a", "b", "c"); + + database_destroy->interpose([&](mongoc_database_t* ptr) -> void { + if (ptr) { + CHECK(ptr == database_id); + } + }); + + get_database->interpose([&](mongoc_client_t* ptr, char const* name) -> mongoc_database_t* { + CHECK(ptr == entry_mocks.client_id); + CHECK_THAT(name, Catch::Matchers::Equals(input)); + return database_id; + }); + + auto pool = mocks.make(); + auto entry = pool.acquire(); + REQUIRE(entry); + REQUIRE(*entry); + auto const db = entry[input]; + + CHECK(v1::database::internal::as_mongoc(db) == database_id); +} + +} // namespace v1 +} // namespace mongocxx From 6759315d0d8f05463117d6010393cc33a1820097 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Tue, 27 Jan 2026 16:16:57 -0600 Subject: [PATCH 09/13] Fix test case tag --- src/mongocxx/test/v1/pool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mongocxx/test/v1/pool.cpp b/src/mongocxx/test/v1/pool.cpp index 0ea4f6eb7a..ea3beb7e32 100644 --- a/src/mongocxx/test/v1/pool.cpp +++ b/src/mongocxx/test/v1/pool.cpp @@ -51,7 +51,7 @@ namespace v1 { using code = mongocxx::v1::pool::errc; -TEST_CASE("error code", "[bsoncxx][v1][pool][error]") { +TEST_CASE("error code", "[mongocxx][v1][pool][error]") { using mongocxx::v1::source_errc; using mongocxx::v1::type_errc; From 65caddc661a671fedec0d5103b9deb808409ca78 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Tue, 27 Jan 2026 16:16:57 -0600 Subject: [PATCH 10/13] Fix comment typos --- src/mongocxx/test/v1/pool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mongocxx/test/v1/pool.cpp b/src/mongocxx/test/v1/pool.cpp index ea3beb7e32..e886b7a816 100644 --- a/src/mongocxx/test/v1/pool.cpp +++ b/src/mongocxx/test/v1/pool.cpp @@ -108,7 +108,7 @@ namespace { struct identity_type {}; -// Represent a successfully-constructed mocked `v1::client` object. +// Represent a successfully-constructed mocked `v1::pool` object. struct pool_mocks_type { using uri_destroy_type = decltype(libmongoc::uri_destroy.create_instance()); using uri_copy_type = decltype(libmongoc::uri_copy.create_instance()); From 40754deeda49890b13636f0fb4a44b835b3ba77e Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Tue, 27 Jan 2026 16:16:57 -0600 Subject: [PATCH 11/13] Fix missing branches on tls_opts_set --- src/mongocxx/test/v1/client.cpp | 4 +++- src/mongocxx/test/v1/pool.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mongocxx/test/v1/client.cpp b/src/mongocxx/test/v1/client.cpp index c8a8104826..4229059a70 100644 --- a/src/mongocxx/test/v1/client.cpp +++ b/src/mongocxx/test/v1/client.cpp @@ -272,7 +272,9 @@ TEST_CASE("exceptions", "[mongocxx][v1][client]") { }); client::options opts; - CHECK_NOTHROW(opts.tls_opts(v1::tls{})); + CHECKED_IF(tls_opts_set) { + CHECK_NOTHROW(opts.tls_opts(v1::tls{})); + } CHECK_THROWS_WITH_CODE(mocks.make(std::move(opts)), code::tls_not_supported); } } diff --git a/src/mongocxx/test/v1/pool.cpp b/src/mongocxx/test/v1/pool.cpp index e886b7a816..8d623288ea 100644 --- a/src/mongocxx/test/v1/pool.cpp +++ b/src/mongocxx/test/v1/pool.cpp @@ -236,7 +236,9 @@ TEST_CASE("exceptions", "[mongocxx][v1][pool]") { }); client::options opts; - CHECK_NOTHROW(opts.tls_opts(v1::tls{})); + CHECKED_IF(tls_opts_set) { + CHECK_NOTHROW(opts.tls_opts(v1::tls{})); + } CHECK_THROWS_WITH_CODE(mocks.make(pool::options{std::move(opts)}), v1::client::errc::tls_not_supported); } } From 8e26e16a8b5aaca2416429a7fc172446bc7346c9 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Tue, 27 Jan 2026 16:16:57 -0600 Subject: [PATCH 12/13] Fix reference to tls_not_supported error code --- src/mongocxx/test/v1/pool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mongocxx/test/v1/pool.cpp b/src/mongocxx/test/v1/pool.cpp index 8d623288ea..414be19bd9 100644 --- a/src/mongocxx/test/v1/pool.cpp +++ b/src/mongocxx/test/v1/pool.cpp @@ -718,7 +718,7 @@ TEST_CASE("tls_opts", "[mongocxx][v1][pool]") { CHECK(set_counter == 1); CHECK(get_counter == 1); #else - SKIP(std::error_code{code::tls_not_supported}.message()); + SKIP(std::error_code{v1::client::errc::tls_not_supported}.message()); #endif } From 50cbfe3f4eba954e0ae8e3e2afb8a86b457db050 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Tue, 27 Jan 2026 16:16:57 -0600 Subject: [PATCH 13/13] Fix incorrect macro guard of server_api::internal::to_mongoc --- src/mongocxx/lib/mongocxx/v1/server_api.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mongocxx/lib/mongocxx/v1/server_api.cpp b/src/mongocxx/lib/mongocxx/v1/server_api.cpp index 440998bd63..efae2b7c0b 100644 --- a/src/mongocxx/lib/mongocxx/v1/server_api.cpp +++ b/src/mongocxx/lib/mongocxx/v1/server_api.cpp @@ -30,7 +30,6 @@ #include #include -#include #include namespace mongocxx { @@ -208,7 +207,6 @@ std::error_category const& server_api::error_category() { return instance.value(); } -#if MONGOCXX_SSL_IS_ENABLED() std::unique_ptr server_api::internal::to_mongoc( v1::server_api const& api) { mongoc_server_api_version_t version = {}; @@ -233,7 +231,6 @@ std::unique_ptr