diff --git a/src/mongocxx/include/mongocxx/v1/client.hpp b/src/mongocxx/include/mongocxx/v1/client.hpp index bdd5f7dc48..883943d118 100644 --- a/src/mongocxx/include/mongocxx/v1/client.hpp +++ b/src/mongocxx/include/mongocxx/v1/client.hpp @@ -38,6 +38,8 @@ #include #include +#include +#include #include namespace mongocxx { @@ -88,12 +90,12 @@ class client { /// /// This class is not copyable. /// - MONGOCXX_ABI_EXPORT_CDECL() client(client const& other); + client(client const& other) = delete; /// /// This class is not copyable. /// - MONGOCXX_ABI_EXPORT_CDECL(client&) operator=(client const& other); + client& operator=(client const& other) = delete; /// /// Initialize with the given URI. @@ -105,7 +107,7 @@ class client { /// @throws mongocxx::v1::exception when a client-side error is encountered. /// /// @{ - MONGOCXX_ABI_EXPORT_CDECL() client(v1::uri uri, options const& opts); + MONGOCXX_ABI_EXPORT_CDECL() client(v1::uri uri, options opts); /* explicit(false) */ MONGOCXX_ABI_EXPORT_CDECL() client(v1::uri uri); /// @} @@ -209,13 +211,13 @@ class client { /// - [Change Streams (MongoDB Manual)](https://www.mongodb.com/docs/manual/changeStreams/) /// /// @{ - MONGOCXX_ABI_EXPORT_CDECL(v1::change_stream) watch(v1::change_stream::options opts); + MONGOCXX_ABI_EXPORT_CDECL(v1::change_stream) watch(v1::change_stream::options const& opts); MONGOCXX_ABI_EXPORT_CDECL(v1::change_stream) watch(); MONGOCXX_ABI_EXPORT_CDECL(v1::change_stream) watch( v1::client_session const& session, - v1::change_stream::options opts); + v1::change_stream::options const& opts); MONGOCXX_ABI_EXPORT_CDECL(v1::change_stream) watch(v1::client_session const& session); /// @} @@ -244,6 +246,8 @@ class client { /// /// Invalidate this client object without invaliding existing cursors or sessions. /// + /// @warning Do not call this member function on a client obtained from a @ref v1::pool. + /// /// This function must be invoked by a (forked) child process to prevent its destruction within the child process /// from invalidating the state of the client object within the parent process. /// @@ -254,6 +258,38 @@ class client { /// - [`mongoc_client_reset`](https://mongoc.org/libmongoc/current/mongoc_client_reset.html) /// MONGOCXX_ABI_EXPORT_CDECL(void) reset(); + + /// + /// Errors codes which may be returned by @ref mongocxx::v1::client. + /// + /// @attention This feature is experimental! It is not ready for use! + /// + enum class errc { + zero, ///< Zero. + tls_not_enabled, ///< TLS is not enabled by URI options. + tls_not_supported, ///< TLS is not supported by the mongoc library. + }; + + /// + /// The error category for @ref mongocxx::v1::client::errc. + /// + /// @attention This feature is experimental! It is not ready for use! + /// + static MONGOCXX_ABI_EXPORT_CDECL(std::error_category const&) error_category(); + + /// + /// Support implicit conversion to `std::error_code`. + /// + /// @attention This feature is experimental! It is not ready for use! + /// + friend std::error_code make_error_code(errc v) { + return {static_cast(v), error_category()}; + } + + class internal; + + private: + /* explicit(false) */ client(void* impl); }; /// @@ -353,11 +389,20 @@ class client::options { /// Return the current "server_api_opts" field. /// MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v1::stdx::optional) server_api_opts() const; + + class internal; }; } // 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/client-fwd.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client-fwd.hpp index 69fa9395f8..bbcc9a476d 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client-fwd.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client-fwd.hpp @@ -14,6 +14,8 @@ #pragma once +#include // IWYU pragma: export + #include namespace mongocxx { @@ -26,7 +28,7 @@ class client; namespace mongocxx { -using ::mongocxx::v_noabi::client; +using v_noabi::client; } // namespace mongocxx @@ -36,3 +38,6 @@ using ::mongocxx::v_noabi::client; /// @file /// Declares @ref mongocxx::v_noabi::client. /// +/// @par Includes +/// - @ref mongocxx/v1/client-fwd.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client.hpp index 75e48090fb..449fe5f7b4 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client.hpp @@ -14,21 +14,36 @@ #pragma once -#include +#include // IWYU pragma: export + +// + +#include // IWYU pragma: export + +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include // IWYU pragma: keep: backward compatibility, to be removed. #include +#include +#include -#include // IWYU pragma: export +#include #include -#include +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include #include -#include -#include -#include +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include +#include // IWYU pragma: keep: backward compatibility, to be removed. + +#include +#include #include #include +#include #include -#include +#include // IWYU pragma: keep: backward compatibility, to be removed. #include #include #include @@ -52,58 +67,68 @@ namespace v_noabi { /// /// @par Example /// ```cpp -/// mongocxx::v_noabi::client mongo_client{mongocxx::v_noabi::uri{}}; -/// mongocxx::v_noabi::client mongo_client{mongocxx::v_noabi::uri{"mongodb://localhost:27017"}}; +/// v_noabi::client mongo_client{v_noabi::uri{}}; +/// v_noabi::client mongo_client{v_noabi::uri{"mongodb://localhost:27017"}}; /// ``` /// /// Note that client is not thread-safe. See /// https://www.mongodb.com/docs/languages/cpp/cpp-driver/current/thread-safety/ for more details. class client { + private: + v1::client _client; + public: /// /// Default constructs a new client. The client is not connected and is equivalent to the /// state of a moved-from client. The only valid actions to take with a default constructed /// 'client' are to assign to it, or destroy it. /// - MONGOCXX_ABI_EXPORT_CDECL() client() noexcept; + client() noexcept {} /// - /// Creates a new client connection to MongoDB. + /// Construct with the @ref mongocxx::v1 equivalent. /// - /// @param mongodb_uri - /// A MongoDB URI representing the connection parameters - /// @param options - /// Additional options that cannot be specified via the mongodb_uri + /* explicit(false) */ client(v1::client client) : _client{std::move(client)} {} + /// - /// @throws mongocxx::v_noabi::exception if invalid options are provided - /// (whether from the URI or provided client options). + /// Convert to the @ref mongocxx::v1 equivalent. /// - MONGOCXX_ABI_EXPORT_CDECL() - client(mongocxx::v_noabi::uri const& mongodb_uri, options::client const& options = options::client()); - + /// @par Postconditions: + /// - `*this` is in an assign-or-destroy-only state. /// - /// Move constructs a client. + /// @warning Invalidates all associated objects. /// - MONGOCXX_ABI_EXPORT_CDECL() client(client&&) noexcept; + explicit operator v1::client() && { + return std::move(_client); + } /// - /// Move assigns a client. + /// This class is not copyable. /// - MONGOCXX_ABI_EXPORT_CDECL(client&) operator=(client&&) noexcept; + explicit operator v1::client() const& = delete; /// - /// Destroys a client. + /// Creates a new client to MongoDB. /// - MONGOCXX_ABI_EXPORT_CDECL() ~client(); - - client(client const&) = delete; - client& operator=(client const&) = delete; + /// @param mongodb_uri + /// A MongoDB URI. + /// @param options + /// Additional URI options. + /// + /// @throws mongocxx::v_noabi::exception if invalid options are provided + /// (whether from the URI or provided client options). + /// + MONGOCXX_ABI_EXPORT_CDECL() client( + v_noabi::uri const& mongodb_uri, + options::client const& options = options::client()); /// /// Returns true if the client is valid, meaning it was not default constructed /// or moved from. /// - explicit MONGOCXX_ABI_EXPORT_CDECL() operator bool() const noexcept; + explicit operator bool() const noexcept { + return _client.operator bool(); + } /// /// Sets the read concern for this client. @@ -122,16 +147,16 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/reference/read-concern/ /// - MONGOCXX_DEPRECATED MONGOCXX_ABI_EXPORT_CDECL(void) read_concern(mongocxx::v_noabi::read_concern rc); + MONGOCXX_DEPRECATED MONGOCXX_ABI_EXPORT_CDECL(void) read_concern(v_noabi::read_concern rc); - MONGOCXX_ABI_EXPORT_CDECL(void) read_concern_deprecated(mongocxx::v_noabi::read_concern rc); + MONGOCXX_ABI_EXPORT_CDECL(void) read_concern_deprecated(v_noabi::read_concern rc); /// /// Returns the current read concern for this client. /// /// @return The current @c read_concern /// - MONGOCXX_ABI_EXPORT_CDECL(mongocxx::v_noabi::read_concern) read_concern() const; + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::read_concern) read_concern() const; /// /// Sets the read preference for this client. @@ -150,10 +175,10 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/core/read-preference/ /// - MONGOCXX_DEPRECATED MONGOCXX_ABI_EXPORT_CDECL(void) read_preference(mongocxx::v_noabi::read_preference rp); + MONGOCXX_DEPRECATED MONGOCXX_ABI_EXPORT_CDECL(void) read_preference(v_noabi::read_preference rp); MONGOCXX_ABI_EXPORT_CDECL(void) - read_preference_deprecated(mongocxx::v_noabi::read_preference rp); + read_preference_deprecated(v_noabi::read_preference rp); /// /// Returns the current read preference for this client. @@ -163,14 +188,14 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/core/read-preference/ /// - MONGOCXX_ABI_EXPORT_CDECL(mongocxx::v_noabi::read_preference) read_preference() const; + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::read_preference) read_preference() const; /// /// Returns the current uri for this client. /// /// @return The @c uri that this client was created with. /// - MONGOCXX_ABI_EXPORT_CDECL(mongocxx::v_noabi::uri) uri() const; + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::uri) uri() const; /// /// Sets the write concern for this client. @@ -186,15 +211,15 @@ class client { /// @param wc /// The new write concern /// - MONGOCXX_DEPRECATED MONGOCXX_ABI_EXPORT_CDECL(void) write_concern(mongocxx::v_noabi::write_concern wc); + MONGOCXX_DEPRECATED MONGOCXX_ABI_EXPORT_CDECL(void) write_concern(v_noabi::write_concern wc); - MONGOCXX_ABI_EXPORT_CDECL(void) write_concern_deprecated(mongocxx::v_noabi::write_concern wc); + MONGOCXX_ABI_EXPORT_CDECL(void) write_concern_deprecated(v_noabi::write_concern wc); /// /// Returns the current write concern for this client. /// /// @return the current @c write_concern - MONGOCXX_ABI_EXPORT_CDECL(mongocxx::v_noabi::write_concern) write_concern() const; + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::write_concern) write_concern() const; /// /// Obtains a database that represents a logical grouping of collections on a MongoDB server. @@ -206,10 +231,10 @@ class client { /// /// @return The database /// - MONGOCXX_ABI_EXPORT_CDECL(mongocxx::v_noabi::database) + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::database) database(bsoncxx::v_noabi::string::view_or_value name) const&; - mongocxx::v_noabi::database database(bsoncxx::v_noabi::string::view_or_value name) const&& = delete; + v_noabi::database database(bsoncxx::v_noabi::string::view_or_value name) const&& = delete; /// /// Allows the syntax @c client["db_name"] as a convenient shorthand for the client::database() @@ -222,11 +247,12 @@ class client { /// /// @return Client side representation of a server side database /// - mongocxx::v_noabi::database operator[](bsoncxx::v_noabi::string::view_or_value name) const& { + v_noabi::database operator[](bsoncxx::v_noabi::string::view_or_value name) const& { return database(name); } - mongocxx::v_noabi::database operator[](bsoncxx::v_noabi::string::view_or_value name) const&& = delete; + v_noabi::database operator[](bsoncxx::v_noabi::string::view_or_value name) const&& = delete; + /// /// Enumerates the databases in the client. /// @@ -242,7 +268,7 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/reference/command/listDatabases /// - MONGOCXX_ABI_EXPORT_CDECL(cursor) list_databases() const; + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::cursor) list_databases() const; /// /// Enumerates the databases in the client. @@ -262,7 +288,7 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/reference/command/listDatabases /// - MONGOCXX_ABI_EXPORT_CDECL(cursor) list_databases(client_session const& session) const; + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::cursor) list_databases(v_noabi::client_session const& session) const; /// /// Enumerates the databases in the client. @@ -282,7 +308,7 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/reference/command/listDatabases /// - MONGOCXX_ABI_EXPORT_CDECL(cursor) + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::cursor) list_databases(bsoncxx::v_noabi::document::view_or_value const opts) const; /// @@ -306,8 +332,8 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/reference/command/listDatabases /// - MONGOCXX_ABI_EXPORT_CDECL(cursor) - list_databases(client_session const& session, bsoncxx::v_noabi::document::view_or_value const opts) const; + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::cursor) + list_databases(v_noabi::client_session const& session, bsoncxx::v_noabi::document::view_or_value const opts) const; /// /// Queries the MongoDB server for a list of known databases. @@ -344,21 +370,22 @@ class client { /// - https://www.mongodb.com/docs/manual/reference/command/listDatabases /// MONGOCXX_ABI_EXPORT_CDECL(std::vector) - list_database_names(client_session const& session, bsoncxx::v_noabi::document::view_or_value const filter = {}) - const; + list_database_names( + v_noabi::client_session const& session, + bsoncxx::v_noabi::document::view_or_value const filter = {}) const; /// /// Create a client session for a sequence of operations. /// - /// @return A client_session object. See `mongocxx::v_noabi::client_session` for more + /// @return A client_session object. See `v_noabi::client_session` for more /// information. /// /// @throws mongocxx::v_noabi::operation_exception if the driver is not built with crypto /// support, if options is misconfigured, or if the session is configured with options that the /// server does not support. /// - MONGOCXX_ABI_EXPORT_CDECL(client_session) - start_session(options::client_session const& options = {}); + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::client_session) + start_session(v_noabi::options::client_session const& options = {}); /// /// Get a change stream on this client with an empty pipeline. @@ -374,7 +401,7 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/changeStreams/ /// - MONGOCXX_ABI_EXPORT_CDECL(change_stream) watch(options::change_stream const& options = {}); + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::change_stream) watch(v_noabi::options::change_stream const& options = {}); /// /// Get a change stream on this client with an empty pipeline. @@ -382,7 +409,7 @@ class client { /// Change streams are only supported with a "majority" read concern or no read concern. /// /// @param session - /// The mongocxx::v_noabi::client_session with which to perform the watch operation. + /// The v_noabi::client_session with which to perform the watch operation. /// @param options /// The options to use when creating the change stream. /// @@ -392,8 +419,8 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/changeStreams/ /// - MONGOCXX_ABI_EXPORT_CDECL(change_stream) - watch(client_session const& session, options::change_stream const& options = {}); + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::change_stream) + watch(v_noabi::client_session const& session, v_noabi::options::change_stream const& options = {}); /// /// Get a change stream on this client. @@ -413,8 +440,8 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/changeStreams/ /// - MONGOCXX_ABI_EXPORT_CDECL(change_stream) - watch(pipeline const& pipe, options::change_stream const& options = {}); + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::change_stream) + watch(v_noabi::pipeline const& pipe, v_noabi::options::change_stream const& options = {}); /// /// Get a change stream on this client. @@ -422,7 +449,7 @@ class client { /// Change streams are only supported with a "majority" read concern or no read concern. /// /// @param session - /// The mongocxx::v_noabi::client_session with which to perform the watch operation. + /// The v_noabi::client_session with which to perform the watch operation. /// @param pipe /// The aggregation pipeline to be used on the change notifications. /// @param options @@ -434,8 +461,11 @@ class client { /// @see /// - https://www.mongodb.com/docs/manual/changeStreams/ /// - MONGOCXX_ABI_EXPORT_CDECL(change_stream) - watch(client_session const& session, pipeline const& pipe, options::change_stream const& options = {}); + MONGOCXX_ABI_EXPORT_CDECL(v_noabi::change_stream) + watch( + v_noabi::client_session const& session, + v_noabi::pipeline const& pipe, + v_noabi::options::change_stream const& options = {}); /// /// Prevents resource cleanup in the child process from interfering @@ -454,28 +484,34 @@ class client { /// MONGOCXX_ABI_EXPORT_CDECL(void) reset(); - private: - friend ::mongocxx::v_noabi::client_session; - friend ::mongocxx::v_noabi::collection; - friend ::mongocxx::v_noabi::database; - friend ::mongocxx::v_noabi::options::auto_encryption; - friend ::mongocxx::v_noabi::options::client_encryption; - friend ::mongocxx::v_noabi::pool; - - explicit client(void* implementation); + class internal; - change_stream _watch(client_session const* session, pipeline const& pipe, options::change_stream const& options); + private: + v_noabi::change_stream _watch( + v_noabi::client_session const* session, + v_noabi::pipeline const& pipe, + v_noabi::options::change_stream const& options); +}; - class impl; +} // namespace v_noabi +} // namespace mongocxx - template - static auto _get_impl(Self& self) -> decltype(*self._impl); +namespace mongocxx { +namespace v_noabi { - impl& _get_impl(); - impl const& _get_impl() const; +/// +/// Convert to the @ref mongocxx::v_noabi equivalent of `v`. +/// +inline v_noabi::client from_v1(v1::client v) { + return {std::move(v)}; +} - std::unique_ptr _impl; -}; +/// +/// Convert to the @ref mongocxx::v1 equivalent of `v`. +/// +inline v1::client to_v1(v_noabi::client v) { + return v1::client{std::move(v)}; +} } // namespace v_noabi } // namespace mongocxx @@ -486,3 +522,6 @@ class client { /// @file /// Provides @ref mongocxx::v_noabi::client. /// +/// @par Includes +/// - @ref mongocxx/v1/client.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client_session.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client_session.hpp index 0ad65e91fa..da9e943e14 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client_session.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client_session.hpp @@ -227,7 +227,7 @@ class client_session { class impl; - client_session(mongocxx::v_noabi::client const* client, options::client_session const& options); + client_session(mongocxx::v_noabi::client* client, options::client_session const& options); impl const& _get_impl() const { return *_impl; diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/database.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/database.hpp index e4156119f2..6aacc9a7dc 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/database.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/database.hpp @@ -537,7 +537,7 @@ class database { friend ::mongocxx::v_noabi::client; friend ::mongocxx::v_noabi::collection; - database(mongocxx::v_noabi::client const& client, bsoncxx::v_noabi::string::view_or_value name); + database(void* client, bsoncxx::v_noabi::string::view_or_value name); cursor _aggregate(client_session const* session, pipeline const& pipeline, options::aggregate const& options); diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/client-fwd.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/client-fwd.hpp index ccb728fa83..8daa5fa095 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/client-fwd.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/client-fwd.hpp @@ -14,6 +14,8 @@ #pragma once +#include // IWYU pragma: export + #include namespace mongocxx { @@ -29,7 +31,7 @@ class client; namespace mongocxx { namespace options { -using ::mongocxx::v_noabi::options::client; +using v_noabi::options::client; } // namespace options } // namespace mongocxx @@ -40,3 +42,6 @@ using ::mongocxx::v_noabi::options::client; /// @file /// Declares @ref mongocxx::v_noabi::options::client. /// +/// @par Includes +/// - @ref mongocxx/v1/client-fwd.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/client.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/client.hpp index 4224b15513..21575c59ad 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/client.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/client.hpp @@ -14,10 +14,15 @@ #pragma once -#include // IWYU pragma: keep: backward compatibility, to be removed. - #include // IWYU pragma: export +// + +#include // IWYU pragma: export + +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include + #include #include @@ -36,6 +41,11 @@ namespace options { /// class client { public: + /// + /// Default initialization. + /// + client() = default; + /// /// Sets the SSL-related options. /// @@ -49,7 +59,9 @@ class client { /// @deprecated /// Please use tls_opts instead. /// - MONGOCXX_DEPRECATED MONGOCXX_ABI_EXPORT_CDECL(client&) ssl_opts(tls ssl_opts); + MONGOCXX_DEPRECATED client& ssl_opts(tls ssl_opts) { + return this->tls_opts(std::move(ssl_opts)); + } /// /// Sets the TLS-related options. @@ -61,7 +73,10 @@ class client { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(client&) tls_opts(tls tls_opts); + client& tls_opts(tls tls_opts) { + _tls_opts = std::move(tls_opts); + return *this; + } /// /// The current SSL-related options. @@ -70,14 +85,18 @@ class client { /// /// @deprecated Please use tls_opts instead. /// - MONGOCXX_DEPRECATED MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional const&) ssl_opts() const; + MONGOCXX_DEPRECATED bsoncxx::v_noabi::stdx::optional const& ssl_opts() const { + return this->tls_opts(); + } /// /// The current TLS-related options. /// /// @return The TLS-related options. /// - MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional const&) tls_opts() const; + bsoncxx::v_noabi::stdx::optional const& tls_opts() const { + return _tls_opts; + } /// /// Sets the automatic encryption options. @@ -89,7 +108,10 @@ class client { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(client&) auto_encryption_opts(auto_encryption auto_encryption_opts); + client& auto_encryption_opts(auto_encryption auto_encryption_opts) { + _auto_encrypt_opts = std::move(auto_encryption_opts); + return *this; + } /// /// Gets the current automatic encryption options. @@ -97,8 +119,9 @@ class client { /// @return /// The automatic encryption opts. /// - MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional const&) - auto_encryption_opts() const; + bsoncxx::v_noabi::stdx::optional const& auto_encryption_opts() const { + return _auto_encrypt_opts; + } /// /// Sets the APM-related options. @@ -110,14 +133,19 @@ class client { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(client&) apm_opts(apm apm_opts); + client& apm_opts(apm apm_opts) { + _apm_opts = std::move(apm_opts); + return *this; + } /// /// The current APM-related options. /// /// @return The APM-related options. /// - MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional const&) apm_opts() const; + bsoncxx::v_noabi::stdx::optional const& apm_opts() const { + return _apm_opts; + } /// /// Sets the server API options. @@ -129,7 +157,10 @@ class client { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(client&) server_api_opts(server_api server_api_opts); + client& server_api_opts(server_api server_api_opts) { + _server_api_opts = std::move(server_api_opts); + return *this; + } /// /// Gets the current server API options or returns a disengaged optional if there are no server @@ -138,23 +169,73 @@ class client { /// @return /// The server API options. /// - MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional const&) - server_api_opts() const; + bsoncxx::v_noabi::stdx::optional const& server_api_opts() const { + return _server_api_opts; + } + + class internal; private: bsoncxx::v_noabi::stdx::optional _tls_opts; bsoncxx::v_noabi::stdx::optional _apm_opts; bsoncxx::v_noabi::stdx::optional _auto_encrypt_opts; bsoncxx::v_noabi::stdx::optional _server_api_opts; + + /* explicit(false) */ client(v1::client::options opts); + + explicit operator v1::client::options() const; }; } // namespace options } // namespace v_noabi } // namespace mongocxx +namespace mongocxx { +namespace v_noabi { + +/// +/// Convert to the @ref mongocxx::v_noabi equivalent of `v`. +/// +/// @important The `auto_encryption_opts` field in the resulting object is unset when not explicitly provided as an +/// argument to this conversion function. +/// +/// @{ +MONGOCXX_ABI_EXPORT_CDECL(v_noabi::options::client) from_v1(v1::client::options v); + +inline v_noabi::options::client from_v1(v1::client::options v, v_noabi::options::auto_encryption opts) { + auto ret = from_v1(std::move(v)); + ret.auto_encryption_opts(std::move(opts)); + return ret; +} +/// @} +/// + +/// +/// Convert to the @ref mongocxx::v1 equivalent of `v`. +/// +/// @important The `auto_encryption_opts` field in the resulting object is unset when not explicitly provided as an +/// argument to this conversion function. +/// +/// @{ +MONGOCXX_ABI_EXPORT_CDECL(v1::client::options) to_v1(v_noabi::options::client v); + +inline v1::client::options to_v1(v_noabi::options::client v, v1::auto_encryption_options opts) { + auto ret = to_v1(std::move(v)); + ret.auto_encryption_opts(std::move(opts)); + return ret; +} +/// @} +/// + +} // namespace v_noabi +} // namespace mongocxx + #include /// /// @file /// Provides @ref mongocxx::v_noabi::options::client. /// +/// @par Includes +/// - @ref mongocxx/v1/client.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api.hpp index 58603670d4..17d141105e 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api.hpp @@ -158,6 +158,8 @@ class server_api { return _version; } + class internal; + private: version _version; bsoncxx::v_noabi::stdx::optional _strict; diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/tls.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/tls.hpp index 695b7cf85e..7d96a7a3fc 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/tls.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/tls.hpp @@ -204,6 +204,8 @@ class tls { MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional const&) allow_invalid_certificates() const; + class internal; + private: bsoncxx::v_noabi::stdx::optional _pem_file; bsoncxx::v_noabi::stdx::optional _pem_password; diff --git a/src/mongocxx/lib/mongocxx/private/mongoc.hh b/src/mongocxx/lib/mongocxx/private/mongoc.hh index 2437dc09bc..e10be77175 100644 --- a/src/mongocxx/lib/mongocxx/private/mongoc.hh +++ b/src/mongocxx/lib/mongocxx/private/mongoc.hh @@ -215,6 +215,7 @@ BSONCXX_PRIVATE_WARNINGS_POP(); X(client_get_uri) \ X(client_get_write_concern) \ X(client_new_from_uri) \ + X(client_new_from_uri_with_error) \ X(client_pool_destroy) \ X(client_pool_enable_auto_encryption) \ X(client_pool_new_with_error) \ diff --git a/src/mongocxx/lib/mongocxx/v1/apm.cpp b/src/mongocxx/lib/mongocxx/v1/apm.cpp index 801b968843..1ab33f8c89 100644 --- a/src/mongocxx/lib/mongocxx/v1/apm.cpp +++ b/src/mongocxx/lib/mongocxx/v1/apm.cpp @@ -224,6 +224,57 @@ std::function_server_heartbeat_succeeded; } +auto apm::internal::command_started(apm const& self) -> fn_type const& { + return impl::with(self)._command_started; +} + +auto apm::internal::command_failed(apm const& self) -> fn_type const& { + return impl::with(self)._command_failed; +} + +auto apm::internal::command_succeeded(apm const& self) -> fn_type const& { + return impl::with(self)._command_succeeded; +} + +auto apm::internal::server_closed(apm const& self) -> fn_type const& { + return impl::with(self)._server_closed; +} + +auto apm::internal::server_description_changed(apm const& self) + -> fn_type const& { + return impl::with(self)._server_description_changed; +} + +auto apm::internal::server_opening(apm const& self) -> fn_type const& { + return impl::with(self)._server_opening; +} + +auto apm::internal::topology_closed(apm const& self) -> fn_type const& { + return impl::with(self)._topology_closed; +} + +auto apm::internal::topology_description_changed(apm const& self) + -> fn_type const& { + return impl::with(self)._topology_description_changed; +} + +auto apm::internal::topology_opening(apm const& self) -> fn_type const& { + return impl::with(self)._topology_opening; +} + +auto apm::internal::server_heartbeat_started(apm const& self) -> fn_type const& { + return impl::with(self)._server_heartbeat_started; +} + +auto apm::internal::server_heartbeat_failed(apm const& self) -> fn_type const& { + return impl::with(self)._server_heartbeat_failed; +} + +auto apm::internal::server_heartbeat_succeeded(apm const& self) + -> fn_type const& { + return impl::with(self)._server_heartbeat_succeeded; +} + auto apm::internal::command_started(apm& self) -> fn_type& { return impl::with(self)._command_started; } diff --git a/src/mongocxx/lib/mongocxx/v1/apm.hh b/src/mongocxx/lib/mongocxx/v1/apm.hh index 0b84fa5215..d775ca08bc 100644 --- a/src/mongocxx/lib/mongocxx/v1/apm.hh +++ b/src/mongocxx/lib/mongocxx/v1/apm.hh @@ -43,6 +43,19 @@ class apm::internal { template using fn_type = std::function; + static fn_type const& command_started(apm const& self); + static fn_type const& command_failed(apm const& self); + static fn_type const& command_succeeded(apm const& self); + static fn_type const& server_closed(apm const& self); + static fn_type const& server_description_changed(apm const& self); + static fn_type const& server_opening(apm const& self); + static fn_type const& topology_closed(apm const& self); + static fn_type const& topology_description_changed(apm const& self); + static fn_type const& topology_opening(apm const& self); + static fn_type const& server_heartbeat_started(apm const& self); + static fn_type const& server_heartbeat_failed(apm const& self); + static fn_type const& server_heartbeat_succeeded(apm const& self); + static fn_type& command_started(apm& self); static fn_type& command_failed(apm& self); static fn_type& command_succeeded(apm& self); diff --git a/src/mongocxx/lib/mongocxx/v1/change_stream.cpp b/src/mongocxx/lib/mongocxx/v1/change_stream.cpp index f0d57a4cfa..156a455069 100644 --- a/src/mongocxx/lib/mongocxx/v1/change_stream.cpp +++ b/src/mongocxx/lib/mongocxx/v1/change_stream.cpp @@ -178,6 +178,10 @@ void change_stream::internal::advance_iterator(change_stream& self) { } } +mongoc_change_stream_t const* change_stream::internal::as_mongoc(change_stream const& self) { + return impl::with(self)._stream; +} + class change_stream::options::impl { public: bsoncxx::v1::stdx::optional _batch_size; @@ -340,6 +344,35 @@ bool operator==(change_stream::iterator const& lhs, change_stream::iterator cons return false; } +bsoncxx::v1::stdx::optional const& change_stream::options::internal::collation( + options const& self) { + return impl::with(self)._collation; +} + +bsoncxx::v1::stdx::optional const& change_stream::options::internal::comment( + options const& self) { + return impl::with(self)._comment; +} + +bsoncxx::v1::stdx::optional const& change_stream::options::internal::full_document(options const& self) { + return impl::with(self)._full_document; +} + +bsoncxx::v1::stdx::optional const& change_stream::options::internal::full_document_before_change( + options const& self) { + return impl::with(self)._full_document_before_change; +} + +bsoncxx::v1::stdx::optional const& change_stream::options::internal::resume_after( + options const& self) { + return impl::with(self)._resume_after; +} + +bsoncxx::v1::stdx::optional const& change_stream::options::internal::start_after( + options const& self) { + return impl::with(self)._start_after; +} + bsoncxx::v1::stdx::optional& change_stream::options::internal::collation(options& self) { return impl::with(self)._collation; } diff --git a/src/mongocxx/lib/mongocxx/v1/change_stream.hh b/src/mongocxx/lib/mongocxx/v1/change_stream.hh index c552dc7ca9..1d2fd4aa7d 100644 --- a/src/mongocxx/lib/mongocxx/v1/change_stream.hh +++ b/src/mongocxx/lib/mongocxx/v1/change_stream.hh @@ -44,10 +44,19 @@ class change_stream::internal { static MONGOCXX_ABI_EXPORT_CDECL_TESTING(bool) is_dead(change_stream const& self); static void advance_iterator(change_stream& self); + + static MONGOCXX_ABI_EXPORT_CDECL_TESTING(mongoc_change_stream_t const*) as_mongoc(change_stream const& self); }; class change_stream::options::internal { public: + static bsoncxx::v1::stdx::optional const& collation(options const& self); + static bsoncxx::v1::stdx::optional const& comment(options const& self); + static bsoncxx::v1::stdx::optional const& full_document(options const& self); + static bsoncxx::v1::stdx::optional const& full_document_before_change(options const& self); + static bsoncxx::v1::stdx::optional const& resume_after(options const& self); + static bsoncxx::v1::stdx::optional const& start_after(options const& self); + static bsoncxx::v1::stdx::optional& collation(options& self); static bsoncxx::v1::stdx::optional& comment(options& self); static bsoncxx::v1::stdx::optional& full_document(options& self); diff --git a/src/mongocxx/lib/mongocxx/v1/client.cpp b/src/mongocxx/lib/mongocxx/v1/client.cpp index aa94c84363..1eb4a7646d 100644 --- a/src/mongocxx/lib/mongocxx/v1/client.cpp +++ b/src/mongocxx/lib/mongocxx/v1/client.cpp @@ -1,6 +1,6 @@ -// Copyright 2009-present MongoDB, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); +// Copyright 2009-present MongoDB, Inc. // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // @@ -12,4 +12,859 @@ // 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace mongocxx { +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; + v1::apm _apm; + + ~impl() { + libmongoc::client_destroy(_client); + } + + impl(impl&& other) noexcept = delete; + impl& operator=(impl&& other) noexcept = delete; + impl(impl const& other) noexcept = delete; + impl& operator=(impl const& other) noexcept = delete; + + explicit impl(mongoc_uri_t const* uri) + : _client{[&] { + bson_error_t error = {}; + + if (auto const ret = libmongoc::client_new_from_uri_with_error(uri, &error)) { + return ret; + } + + v1::throw_exception(error); + }()} {} + + explicit impl(mongoc_client_t* client) : _client{client} {} + + static impl const& with(client const& self) { + return *static_cast(self._impl); + } + + static impl const* with(client const* self) { + return static_cast(self->_impl); + } + + static impl& with(client& self) { + return *static_cast(self._impl); + } + + static impl* with(client* self) { + return static_cast(self->_impl); + } + + static impl* with(void* ptr) { + return static_cast(ptr); + } +}; + +// NOLINTBEGIN(cppcoreguidelines-owning-memory): owning void* for ABI stability. + +client::~client() { + delete impl::with(this); +} + +client::client(client&& other) noexcept : _impl{exchange(other._impl, nullptr)} {} + +client& client::operator=(client&& other) noexcept { + if (this != &other) { + delete impl::with(exchange(_impl, exchange(other._impl, nullptr))); + } + + return *this; +} + +// NOLINTEND(cppcoreguidelines-owning-memory) + +client::client(v1::uri uri, options opts) : client{new impl{v1::uri::internal::as_mongoc(uri)}} { + auto const& _client = impl::with(this)->_client; + + if (auto& opt = options::internal::apm_opts(opts)) { + internal::set_apm(*this, std::move(*opt)); + } + + 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)) { + v1::throw_exception(error); + } + } + + 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)) { + v1::throw_exception(error); + } + } + + { + auto const tls_enabled = uri.tls(); + +#if MONGOCXX_SSL_IS_ENABLED() + if (auto const& opt = options::internal::tls_opts(opts)) { + if (!tls_enabled) { + throw v1::exception::internal::make(errc::tls_not_enabled); + } + + auto const v = to_mongoc(*opt); + libmongoc::client_set_ssl_opts(_client, &v); + } +#else + if (tls_enabled || options::internal::tls_opts(opts)) { + throw v1::exception::internal::make(errc::tls_not_supported); + } +#endif + } +} + +client::client(v1::uri uri) : client{std::move(uri), {}} {} + +client::client() : _impl{nullptr} {} + +client::operator bool() const { + return _impl != nullptr; +} + +v1::uri client::uri() const { + return v1::uri::internal::make(libmongoc::uri_copy(libmongoc::client_get_uri(impl::with(this)->_client))); +} + +v1::database client::database(std::string name) { + // TODO: v1::database (CXX-3237) + (void)name; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +v1::database client::operator[](std::string name) { + // TODO: v1::database (CXX-3237) + (void)name; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +namespace { + +v1::cursor list_databases_impl(mongoc_client_t* client, bson_t const* opts) { + return v1::cursor::internal::make(libmongoc::client_find_databases_with_opts(client, opts)); +} + +} // namespace + +v1::cursor client::list_databases() { + return list_databases_impl(impl::with(this)->_client, nullptr); +} + +v1::cursor client::list_databases(v1::client_session const& session) { + // TODO: v1::client_session (CXX-3237) + (void)session; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +v1::cursor client::list_databases(bsoncxx::v1::document::view opts) { + return list_databases_impl(impl::with(this)->_client, scoped_bson_view{opts}.bson()); +} + +v1::cursor client::list_databases(v1::client_session const& session, bsoncxx::v1::document::view opts) { + // TODO: v1::client_session (CXX-3237) + (void)session; + (void)opts; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +namespace { + +std::vector list_database_names_impl(mongoc_client_t* client, bson_t const* opts) { + struct names_deleter { + void operator()(char** ptr) const noexcept { + bson_strfreev(ptr); + } + }; + + using names_type = std::unique_ptr; + + bson_error_t error = {}; + + if (auto const names = names_type{libmongoc::client_get_database_names_with_opts(client, opts, &error)}) { + std::vector ret; + + for (char const* const* iter = names.get(); *iter != nullptr; ++iter) { + ret.emplace_back(*iter); + } + + return ret; + } + + v1::throw_exception(error); +} + +} // namespace + +std::vector client::list_database_names(bsoncxx::v1::document::view filter) { + scoped_bson const opts{BCON_NEW("filter", BCON_DOCUMENT(scoped_bson_view{filter}.bson()))}; + return list_database_names_impl(impl::with(this)->_client, opts.bson()); +} + +std::vector client::list_database_names( + v1::client_session const& session, + bsoncxx::v1::document::view filter) { + // TODO: v1::client_session (CXX-3237) + (void)session; + (void)filter; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +std::vector client::list_database_names() { + return list_database_names_impl(impl::with(this)->_client, nullptr); +} + +std::vector client::list_database_names(v1::client_session const& session) { + // TODO: v1::client_session (CXX-3237) + (void)session; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +v1::client_session client::start_session(v1::client_session::options opts) { + // TODO: v1::client_session (CXX-3237) + (void)opts; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +v1::client_session client::start_session() { + // TODO: v1::client_session (CXX-3237) + MONGOCXX_PRIVATE_UNREACHABLE; +} + +namespace { + +v1::change_stream watch_impl(mongoc_client_t* client, bsoncxx::v1::array::view pipeline, bson_t const* opts) { + return v1::change_stream::internal::make(libmongoc::client_watch(client, scoped_bson_view{pipeline}.bson(), opts)); +} + +bsoncxx::v1::document::value to_document(v1::change_stream::options const& opts) { + scoped_bson bson; + + if (auto const& opt = v1::change_stream::options::internal::full_document(opts)) { + bson += scoped_bson{BCON_NEW("fullDocument", BCON_UTF8(opt->c_str()))}; + } + + if (auto const& opt = v1::change_stream::options::internal::full_document_before_change(opts)) { + bson += scoped_bson{BCON_NEW("fullDocumentBeforeChange", BCON_UTF8(opt->c_str()))}; + } + + if (auto const& opt = v1::change_stream::options::internal::resume_after(opts)) { + bson += scoped_bson{BCON_NEW("resumeAfter", BCON_DOCUMENT(scoped_bson_view{*opt}.bson()))}; + } + + if (auto const& opt = v1::change_stream::options::internal::start_after(opts)) { + bson += scoped_bson{BCON_NEW("startAfter", BCON_DOCUMENT(scoped_bson_view{*opt}.bson()))}; + } + + if (auto const opt = opts.batch_size()) { + bson += scoped_bson{BCON_NEW("batchSize", BCON_INT32(*opt))}; + } + + if (auto const& opt = v1::change_stream::options::internal::collation(opts)) { + bson += scoped_bson{BCON_NEW("collation", BCON_DOCUMENT(scoped_bson_view{*opt}.bson()))}; + } + + if (auto const& opt = v1::change_stream::options::internal::comment(opts)) { + scoped_bson v; + + if (!BSON_APPEND_VALUE(v.inout_ptr(), "comment", &bsoncxx::v1::types::value::internal::get_bson_value(*opt))) { + throw std::logic_error{"mongocxx::v1::client::watch: BSON_APPEND_VALUE failed"}; + } + + bson += v; + } + + if (auto const opt = opts.start_at_operation_time()) { + scoped_bson v; + + // BCON_TIMESTAMP() incorrectly uses int32_ptr instead of uint32_ptr. Use BSON_*() API instead. + if (!BSON_APPEND_TIMESTAMP(v.inout_ptr(), "startAtOperationTime", opt->timestamp, opt->increment)) { + throw std::logic_error{"mongocxx::v1::client::watch: BSON_APPEND_TIMESTAMP failed"}; + } + + bson += v; + } + + if (auto const opt = opts.max_await_time()) { + bson += scoped_bson{BCON_NEW("maxAwaitTimeMS", BCON_INT64(opt->count()))}; + } + + return std::move(bson).value(); +} + +} // namespace + +v1::change_stream client::watch(v1::change_stream::options const& opts) { + return watch_impl(impl::with(this)->_client, bsoncxx::v1::array::view{}, scoped_bson{to_document(opts)}.bson()); +} + +v1::change_stream client::watch() { + return watch_impl(impl::with(this)->_client, bsoncxx::v1::array::view{}, nullptr); +} + +v1::change_stream client::watch(v1::client_session const& session, v1::change_stream::options const& opts) { + // TODO: v1::client_session (CXX-3237) + (void)session; + (void)opts; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +v1::change_stream client::watch(v1::client_session const& session) { + // TODO: v1::client_session (CXX-3237) + (void)session; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +v1::change_stream client::watch(v1::pipeline const& pipeline, v1::change_stream::options const& opts) { + return watch_impl(impl::with(this)->_client, pipeline.view_array(), scoped_bson{to_document(opts)}.bson()); +} + +v1::change_stream +client::watch(v1::client_session const& session, v1::pipeline const& pipeline, v1::change_stream::options const& opts) { + // TODO: v1::client_session (CXX-3237) + (void)session; + (void)pipeline; + (void)opts; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +v1::change_stream client::watch(v1::pipeline const& pipeline) { + return watch_impl(impl::with(this)->_client, pipeline.view_array(), nullptr); +} + +v1::change_stream client::watch(v1::client_session const& session, v1::pipeline const& pipeline) { + // TODO: v1::client_session (CXX-3237) + (void)session; + (void)pipeline; + MONGOCXX_PRIVATE_UNREACHABLE; +} + +void client::reset() { + libmongoc::client_reset(impl::with(this)->_client); +} + +std::error_category const& client::error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "mongocxx::v1::client"; + } + + std::string message(int v) const noexcept override { + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::tls_not_enabled: + return "TLS is not enabled by the URI option"; + case code::tls_not_supported: + return "TLS is not supported by the mongoc library"; + 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::tls_not_enabled: + case code::tls_not_supported: + 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::tls_not_enabled: + case code::tls_not_supported: + return type == condition::invalid_argument; + + case code::zero: + default: + return false; + } + } + + return false; + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +client::client(void* impl) : _impl{impl} {} + +client client::internal::make(mongoc_client_t* client) { + return {new impl{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); +} + +void client::internal::disown(client& self) { + impl::with(self)._client = nullptr; +} + +mongoc_client_t const* client::internal::as_mongoc(client const& self) { + return impl::with(self)._client; +} + +mongoc_client_t* client::internal::as_mongoc(client& self) { + return impl::with(self)._client; +} + +class client::options::impl { + public: + bsoncxx::v1::stdx::optional _tls_opts; + bsoncxx::v1::stdx::optional _auto_encryption_opts; + bsoncxx::v1::stdx::optional _apm_opts; + bsoncxx::v1::stdx::optional _server_api_opts; + + static impl const& with(options const& self) { + return *static_cast(self._impl); + } + + static impl const* with(options const* self) { + return static_cast(self->_impl); + } + + static impl& with(options& self) { + return *static_cast(self._impl); + } + + static impl* with(options* self) { + return static_cast(self->_impl); + } + + static impl* with(void* ptr) { + return static_cast(ptr); + } +}; + +// NOLINTBEGIN(cppcoreguidelines-owning-memory): owning void* for ABI stability. + +client::options::~options() { + delete impl::with(this); +} + +client::options::options(options&& other) noexcept : _impl{exchange(other._impl, nullptr)} {} + +client::options& client::options::operator=(options&& other) noexcept { + if (this != &other) { + delete impl::with(exchange(_impl, exchange(other._impl, nullptr))); + } + + return *this; +} + +client::options::options(options const& other) : _impl{new impl{impl::with(other)}} {} + +client::options& client::options::operator=(options const& other) { + if (this != &other) { + delete impl::with(exchange(_impl, new impl{impl::with(other)})); + } + + return *this; +} + +client::options::options() : _impl{new impl{}} {} + +// NOLINTEND(cppcoreguidelines-owning-memory) + +client::options& client::options::tls_opts(v1::tls v) { + impl::with(this)->_tls_opts = std::move(v); + return *this; +} + +bsoncxx::v1::stdx::optional client::options::tls_opts() const { + return impl::with(this)->_tls_opts; +} + +client::options& client::options::auto_encryption_opts(v1::auto_encryption_options v) { + impl::with(this)->_auto_encryption_opts = std::move(v); + return *this; +} + +bsoncxx::v1::stdx::optional client::options::auto_encryption_opts() const { + return impl::with(this)->_auto_encryption_opts; +} + +client::options& client::options::apm_opts(v1::apm v) { + impl::with(this)->_apm_opts = std::move(v); + return *this; +} + +bsoncxx::v1::stdx::optional client::options::apm_opts() const { + return impl::with(this)->_apm_opts; +} + +client::options& client::options::server_api_opts(v1::server_api v) { + impl::with(this)->_server_api_opts = std::move(v); + return *this; +} + +bsoncxx::v1::stdx::optional client::options::server_api_opts() const { + return impl::with(this)->_server_api_opts; +} + +bsoncxx::v1::stdx::optional& client::options::internal::tls_opts(options& self) { + return impl::with(self)._tls_opts; +} + +bsoncxx::v1::stdx::optional& client::options::internal::auto_encryption_opts( + options& self) { + return impl::with(self)._auto_encryption_opts; +} + +bsoncxx::v1::stdx::optional& client::options::internal::apm_opts(options& self) { + return impl::with(self)._apm_opts; +} + +bsoncxx::v1::stdx::optional& client::options::internal::server_api_opts(options& self) { + return impl::with(self)._server_api_opts; +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/client.hh b/src/mongocxx/lib/mongocxx/v1/client.hh new file mode 100644 index 0000000000..b365baf3b1 --- /dev/null +++ b/src/mongocxx/lib/mongocxx/v1/client.hh @@ -0,0 +1,55 @@ +// 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 +#include +#include + +#include + +#include +#include + +namespace mongocxx { +namespace v1 { + +class client::internal { + public: + static MONGOCXX_ABI_EXPORT_CDECL_TESTING(client) make(mongoc_client_t* client); + + static void set_apm(client& self, v1::apm v); + + static void disown(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); +}; + +class client::options::internal { + public: + static bsoncxx::v1::stdx::optional& tls_opts(options& self); + static bsoncxx::v1::stdx::optional& auto_encryption_opts(options& self); + static bsoncxx::v1::stdx::optional& apm_opts(options& self); + static bsoncxx::v1::stdx::optional& server_api_opts(options& self); +}; + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/client_session.cpp b/src/mongocxx/lib/mongocxx/v1/client_session.cpp index 48ccc4ec29..4af5bb9c2f 100644 --- a/src/mongocxx/lib/mongocxx/v1/client_session.cpp +++ b/src/mongocxx/lib/mongocxx/v1/client_session.cpp @@ -13,3 +13,11 @@ // limitations under the License. #include + +namespace mongocxx { +namespace v1 { + +client_session::options::~options() = default; // TODO: v1::client_session (CXX-3237) + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/cursor.cpp b/src/mongocxx/lib/mongocxx/v1/cursor.cpp index 636113efac..290b1eba86 100644 --- a/src/mongocxx/lib/mongocxx/v1/cursor.cpp +++ b/src/mongocxx/lib/mongocxx/v1/cursor.cpp @@ -124,6 +124,10 @@ cursor cursor::internal::make(mongoc_cursor_t* cursor, type type) { return {new impl{cursor, type}}; } +mongoc_cursor_t const* cursor::internal::as_mongoc(cursor const& self) { + return impl::with(self)._cursor; +} + mongoc_cursor_t* cursor::internal::as_mongoc(cursor& self) { return impl::with(self)._cursor; } diff --git a/src/mongocxx/lib/mongocxx/v1/cursor.hh b/src/mongocxx/lib/mongocxx/v1/cursor.hh index 262a46b346..e19b0ed332 100644 --- a/src/mongocxx/lib/mongocxx/v1/cursor.hh +++ b/src/mongocxx/lib/mongocxx/v1/cursor.hh @@ -47,6 +47,7 @@ class cursor::internal { static void advance_iterator(cursor& self); + static MONGOCXX_ABI_EXPORT_CDECL_TESTING(mongoc_cursor_t const*) as_mongoc(cursor const& self); static mongoc_cursor_t* as_mongoc(cursor& self); }; diff --git a/src/mongocxx/lib/mongocxx/v1/tls.cpp b/src/mongocxx/lib/mongocxx/v1/tls.cpp index fb47015988..ecc40ff479 100644 --- a/src/mongocxx/lib/mongocxx/v1/tls.cpp +++ b/src/mongocxx/lib/mongocxx/v1/tls.cpp @@ -138,6 +138,26 @@ bsoncxx::v1::stdx::optional tls::allow_invalid_certificates() const { return impl::with(this)->_allow_invalid_certificates; } +bsoncxx::v1::stdx::optional const& tls::internal::pem_file(tls const& self) { + return impl::with(self)._pem_file; +} + +bsoncxx::v1::stdx::optional const& tls::internal::pem_password(tls const& self) { + return impl::with(self)._pem_password; +} + +bsoncxx::v1::stdx::optional const& tls::internal::ca_file(tls const& self) { + return impl::with(self)._ca_file; +} + +bsoncxx::v1::stdx::optional const& tls::internal::ca_dir(tls const& self) { + return impl::with(self)._ca_dir; +} + +bsoncxx::v1::stdx::optional const& tls::internal::crl_file(tls const& self) { + return impl::with(self)._crl_file; +} + bsoncxx::v1::stdx::optional& tls::internal::pem_file(tls& self) { return impl::with(self)._pem_file; } diff --git a/src/mongocxx/lib/mongocxx/v1/tls.hh b/src/mongocxx/lib/mongocxx/v1/tls.hh index 93abe6f2da..08d2238b8f 100644 --- a/src/mongocxx/lib/mongocxx/v1/tls.hh +++ b/src/mongocxx/lib/mongocxx/v1/tls.hh @@ -27,6 +27,12 @@ namespace v1 { class tls::internal { public: + static bsoncxx::v1::stdx::optional const& pem_file(tls const& self); + static bsoncxx::v1::stdx::optional const& pem_password(tls const& self); + static bsoncxx::v1::stdx::optional const& ca_file(tls const& self); + static bsoncxx::v1::stdx::optional const& ca_dir(tls const& self); + static bsoncxx::v1::stdx::optional const& crl_file(tls const& self); + static bsoncxx::v1::stdx::optional& pem_file(tls& self); static bsoncxx::v1::stdx::optional& pem_password(tls& self); static bsoncxx::v1::stdx::optional& ca_file(tls& self); diff --git a/src/mongocxx/lib/mongocxx/v1/uri.hh b/src/mongocxx/lib/mongocxx/v1/uri.hh index 6939cd7531..4ec5ccdb41 100644 --- a/src/mongocxx/lib/mongocxx/v1/uri.hh +++ b/src/mongocxx/lib/mongocxx/v1/uri.hh @@ -26,7 +26,7 @@ namespace v1 { class uri::internal { public: - static uri make(mongoc_uri_t* uri); + static MONGOCXX_ABI_EXPORT_CDECL_TESTING(uri) make(mongoc_uri_t* uri); static MONGOCXX_ABI_EXPORT_CDECL_TESTING(mongoc_uri_t const*) as_mongoc(uri const& self); }; diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp index cc0ae04a0f..a28f1a7ca5 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp @@ -12,325 +12,328 @@ // 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 +#include +#include +#include #include -#include #include #include -#include #include #include #include -#include +#include // IWYU pragma: keep: MONGOCXX_SSL_IS_ENABLED #include #include #include #include #include -#include +#include #include +#include #include namespace mongocxx { namespace v_noabi { -using bsoncxx::v_noabi::builder::basic::kvp; - namespace { -class database_names { - public: - explicit database_names(char** names) : _names{names} {} - - ~database_names() { - bson_strfreev(_names); - } - - database_names(database_names&&) = delete; - database_names& operator=(database_names&&) = delete; - database_names(database_names const&) = delete; - database_names& operator=(database_names const&) = delete; - - char const* operator[](std::size_t const i) const { - return _names[i]; - } - bool operator!() const { - return _names == nullptr; +template +Client& check_moved_from(Client& client) { + if (!client) { + throw v_noabi::logic_error{v_noabi::error_code::k_invalid_client_object}; } + return client; +} - private: - char** _names; -}; } // namespace -client::client() noexcept = default; - -client::client(mongocxx::v_noabi::uri const& uri, options::client const& options) { +client::client(v_noabi::uri const& uri, options::client const& options) { #if MONGOCXX_SSL_IS_ENABLED() if (options.tls_opts()) { if (!uri.tls()) - throw exception{error_code::k_invalid_parameter, "cannot set TLS options if 'tls=true' not in URI"}; + throw v_noabi::exception{ + v_noabi::error_code::k_invalid_parameter, "cannot set TLS options if 'tls=true' not in URI"}; } #else if (uri.tls() || options.tls_opts()) { - throw exception{error_code::k_ssl_not_supported}; + throw v_noabi::exception{v_noabi::error_code::k_ssl_not_supported}; } #endif - auto new_client = libmongoc::client_new_from_uri(v_noabi::uri::internal::as_mongoc(uri)); - if (!new_client) { - // Shouldn't happen after checks above, but future libmongoc's may change behavior. - throw exception{error_code::k_invalid_parameter, "could not construct client from URI"}; - } - _impl = bsoncxx::make_unique(std::move(new_client)); + auto const ptr = libmongoc::client_new_from_uri(v_noabi::uri::internal::as_mongoc(uri)); - if (options.apm_opts()) { - _impl->listeners = *options.apm_opts(); - auto callbacks = options::make_apm_callbacks(_impl->listeners); - // We cast the APM class to a void* so we can pass it into libmongoc's context. - // It will be cast back to an APM class in the event handlers. - auto context = static_cast(&(_impl->listeners)); - libmongoc::client_set_apm_callbacks(_get_impl().client_t, callbacks.get(), context); + if (!ptr) { + // Shouldn't happen after checks above, but future libmongoc's may change behavior. + throw v_noabi::exception{v_noabi::error_code::k_invalid_parameter, "could not construct client from URI"}; } - if (auto const& auto_encryption_opts = options.auto_encryption_opts()) { - auto const opts = v_noabi::options::auto_encryption::internal::to_mongoc(*auto_encryption_opts); + _client = v1::client::internal::make(ptr); // Ownership transfer. - bson_error_t error; - auto r = libmongoc::client_enable_auto_encryption(_get_impl().client_t, opts, &error); + if (auto& opts = options.apm_opts()) { + v1::client::internal::set_apm(_client, v_noabi::to_v1(*opts)); + } - libmongoc::auto_encryption_opts_destroy(opts); + if (auto const& opts = options.auto_encryption_opts()) { + bson_error_t error = {}; - if (!r) { - throw_exception(error); + if (!libmongoc::client_enable_auto_encryption( + ptr, v_noabi::options::auto_encryption::internal::to_mongoc(*opts).get(), &error)) { + v_noabi::throw_exception(error); } } - if (options.server_api_opts()) { - auto const& server_api_opts = *options.server_api_opts(); - auto mongoc_server_api_opts = options::make_server_api(server_api_opts); + if (auto const& opts = options.server_api_opts()) { + bson_error_t error = {}; - bson_error_t error; - auto result = libmongoc::client_set_server_api(_get_impl().client_t, mongoc_server_api_opts.get(), &error); - - if (!result) { - throw_exception(error); + if (!libmongoc::client_set_server_api( + ptr, v_noabi::options::server_api::internal::to_mongoc(*opts).get(), &error)) { + v_noabi::throw_exception(error); } } #if MONGOCXX_SSL_IS_ENABLED() - if (options.tls_opts()) { - auto mongoc_opts = options::make_tls_opts(*options.tls_opts()); - _impl->tls_options = std::move(mongoc_opts.second); - libmongoc::client_set_ssl_opts(_get_impl().client_t, &mongoc_opts.first); + if (auto const& opts = options.tls_opts()) { + auto const v = v_noabi::options::tls::internal::to_mongoc(*opts); + libmongoc::client_set_ssl_opts(ptr, &v.opt); } #endif } -client::client(void* implementation) - : _impl{bsoncxx::make_unique(static_cast<::mongoc_client_t*>(implementation))} {} - -client::client(client&&) noexcept = default; -client& client::operator=(client&&) noexcept = default; - -client::~client() = default; - -client::operator bool() const noexcept { - return static_cast(_impl); -} - -void client::read_concern_deprecated(mongocxx::v_noabi::read_concern rc) { - libmongoc::client_set_read_concern(_get_impl().client_t, v_noabi::read_concern::internal::as_mongoc(rc)); +void client::read_concern_deprecated(v_noabi::read_concern rc) { + libmongoc::client_set_read_concern( + v1::client::internal::as_mongoc(check_moved_from(_client)), v_noabi::read_concern::internal::as_mongoc(rc)); } -void client::read_concern(mongocxx::v_noabi::read_concern rc) { +void client::read_concern(v_noabi::read_concern rc) { return read_concern_deprecated(std::move(rc)); } -mongocxx::v_noabi::read_concern client::read_concern() const { +v_noabi::read_concern client::read_concern() const { return v1::read_concern::internal::make( - libmongoc::read_concern_copy(libmongoc::client_get_read_concern(_get_impl().client_t))); + libmongoc::read_concern_copy( + libmongoc::client_get_read_concern(v1::client::internal::as_mongoc(check_moved_from(_client))))); } -void client::read_preference_deprecated(mongocxx::v_noabi::read_preference rp) { - libmongoc::client_set_read_prefs(_get_impl().client_t, v_noabi::read_preference::internal::as_mongoc(rp)); +void client::read_preference_deprecated(v_noabi::read_preference rp) { + libmongoc::client_set_read_prefs( + v1::client::internal::as_mongoc(check_moved_from(_client)), v_noabi::read_preference::internal::as_mongoc(rp)); } -void client::read_preference(mongocxx::v_noabi::read_preference rp) { +void client::read_preference(v_noabi::read_preference rp) { return read_preference_deprecated(std::move(rp)); } -mongocxx::v_noabi::read_preference client::read_preference() const { +v_noabi::read_preference client::read_preference() const { return v1::read_preference::internal::make( - libmongoc::read_prefs_copy(libmongoc::client_get_read_prefs(_get_impl().client_t))); + libmongoc::read_prefs_copy( + libmongoc::client_get_read_prefs(v1::client::internal::as_mongoc(check_moved_from(_client))))); } -mongocxx::v_noabi::uri client::uri() const { - return v1::uri::internal::make(libmongoc::uri_copy(libmongoc::client_get_uri(_get_impl().client_t))); +v_noabi::uri client::uri() const { + return check_moved_from(_client).uri(); } -void client::write_concern_deprecated(mongocxx::v_noabi::write_concern wc) { - libmongoc::client_set_write_concern(_get_impl().client_t, v_noabi::write_concern::internal::as_mongoc(wc)); +void client::write_concern_deprecated(v_noabi::write_concern wc) { + libmongoc::client_set_write_concern( + v1::client::internal::as_mongoc(check_moved_from(_client)), v_noabi::write_concern::internal::as_mongoc(wc)); } -void client::write_concern(mongocxx::v_noabi::write_concern wc) { +void client::write_concern(v_noabi::write_concern wc) { return write_concern_deprecated(std::move(wc)); } -mongocxx::v_noabi::write_concern client::write_concern() const { +v_noabi::write_concern client::write_concern() const { return v1::write_concern::internal::make( - libmongoc::write_concern_copy(libmongoc::client_get_write_concern(_get_impl().client_t))); + libmongoc::write_concern_copy( + libmongoc::client_get_write_concern(v1::client::internal::as_mongoc(check_moved_from(_client))))); } -mongocxx::v_noabi::database client::database(bsoncxx::v_noabi::string::view_or_value name) const& { - return mongocxx::v_noabi::database(*this, std::move(name)); +v_noabi::database client::database(bsoncxx::v_noabi::string::view_or_value name) const& { + // Backward compatibility: `database()` is not logically const. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto& c = const_cast(check_moved_from(_client)); + + return v_noabi::database(v1::client::internal::as_mongoc(c), std::move(name)); } -cursor client::list_databases() const { - return v1::cursor::internal::make(libmongoc::client_find_databases_with_opts(_get_impl().client_t, nullptr)); +v_noabi::cursor client::list_databases() const { + // Backward compatibility: `list_databases()` is not logically const. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto& c = const_cast(check_moved_from(_client)); + + return c.list_databases(); } -cursor client::list_databases(client_session const& session) const { - bsoncxx::v_noabi::builder::basic::document options_doc; - options_doc.append(bsoncxx::v_noabi::builder::concatenate_doc{session._get_impl().to_document()}); - return v1::cursor::internal::make( - libmongoc::client_find_databases_with_opts(_get_impl().client_t, to_scoped_bson_view(options_doc))); +v_noabi::cursor client::list_databases(v_noabi::client_session const& session) const { + // Backward compatibility: `list_databases()` is not logically const. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto& c = const_cast(check_moved_from(_client)); + + return c.list_databases(to_scoped_bson_view(session._get_impl().to_document()).view()); } -cursor client::list_databases(bsoncxx::v_noabi::document::view_or_value const opts) const { - return v1::cursor::internal::make( - libmongoc::client_find_databases_with_opts(_get_impl().client_t, to_scoped_bson_view(opts))); +v_noabi::cursor client::list_databases(bsoncxx::v_noabi::document::view_or_value const opts) const { + // Backward compatibility: `list_databases()` is not logically const. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto& c = const_cast(check_moved_from(_client)); + + return c.list_databases(to_scoped_bson_view(opts).view()); } -cursor client::list_databases(client_session const& session, bsoncxx::v_noabi::document::view_or_value const opts) - const { - bsoncxx::v_noabi::builder::basic::document options_doc; - options_doc.append(bsoncxx::v_noabi::builder::concatenate_doc{session._get_impl().to_document()}); - options_doc.append(bsoncxx::v_noabi::builder::concatenate_doc{opts}); - return v1::cursor::internal::make( - libmongoc::client_find_databases_with_opts(_get_impl().client_t, to_scoped_bson_view(options_doc))); +v_noabi::cursor client::list_databases( + v_noabi::client_session const& session, + bsoncxx::v_noabi::document::view_or_value const opts) const { + // Backward compatibility: `list_databases()` is not logically const. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto& c = const_cast(check_moved_from(_client)); + + scoped_bson doc; + + doc += to_scoped_bson_view(session._get_impl().to_document()); + doc += to_scoped_bson_view(opts); + + return c.list_databases(doc.view()); } -std::vector client::list_database_names(bsoncxx::v_noabi::document::view_or_value filter) const { - bsoncxx::v_noabi::builder::basic::document options_builder; +namespace { + +std::vector list_database_names_impl(mongoc_client_t* client, bson_t const* opts) { + struct names_deleter { + void operator()(char** ptr) const noexcept { + bson_strfreev(ptr); + } + }; - options_builder.append(kvp("filter", filter)); + using names_type = std::unique_ptr; - bson_error_t error; + bson_error_t error = {}; - database_names const names( - libmongoc::client_get_database_names_with_opts( - _get_impl().client_t, to_scoped_bson_view(options_builder), &error)); + if (auto const names = names_type{libmongoc::client_get_database_names_with_opts(client, opts, &error)}) { + std::vector ret; - if (!names) { - throw_exception(error); - } + for (char const* const* iter = names.get(); *iter != nullptr; ++iter) { + ret.emplace_back(*iter); + } - std::vector _names; - for (std::size_t i = 0u; names[i]; ++i) { - _names.emplace_back(names[i]); + return ret; } - return _names; + v_noabi::throw_exception(error); } -std::vector client::list_database_names( - client_session const& session, - bsoncxx::v_noabi::document::view_or_value const filter) const { - bsoncxx::v_noabi::builder::basic::document options_builder; +} // namespace - options_builder.append(bsoncxx::v_noabi::builder::concatenate_doc{session._get_impl().to_document()}); - options_builder.append(kvp("filter", filter)); +std::vector client::list_database_names(bsoncxx::v_noabi::document::view_or_value filter) const try { + // Backward compatibility: `list_database_names()` is not logically const. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto& c = const_cast(check_moved_from(_client)); - bson_error_t error; + return c.list_database_names(to_scoped_bson_view(filter).view()); +} catch (v1::exception const& ex) { + v_noabi::throw_exception(ex); +} - database_names const names( - libmongoc::client_get_database_names_with_opts( - _get_impl().client_t, to_scoped_bson_view(options_builder), &error)); +std::vector client::list_database_names( + v_noabi::client_session const& session, + bsoncxx::v_noabi::document::view_or_value const filter) const try { + // Backward compatibility: `list_databases()` is not logically const. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto& c = const_cast(check_moved_from(_client)); - if (!names) { - throw_exception(error); - } + scoped_bson opts; - std::vector res; - for (std::size_t i = 0u; names[i]; ++i) { - res.emplace_back(names[i]); - } + opts += to_scoped_bson(session._get_impl().to_document()); + opts += scoped_bson{BCON_NEW("filter", BCON_DOCUMENT(to_scoped_bson_view(filter).bson()))}; - return res; + return list_database_names_impl(v1::client::internal::as_mongoc(c), opts.bson()); +} catch (v1::exception const& ex) { + v_noabi::throw_exception(ex); } -mongocxx::v_noabi::client_session client::start_session(mongocxx::v_noabi::options::client_session const& options) { - return client_session(this, options); +v_noabi::client_session client::start_session(v_noabi::options::client_session const& options) { + return v_noabi::client_session{this, options}; } void client::reset() { - libmongoc::client_reset(_get_impl().client_t); + check_moved_from(_client).reset(); } -change_stream client::watch(options::change_stream const& options) { - return watch(pipeline{}, options); +v_noabi::change_stream client::watch(v_noabi::options::change_stream const& options) { + return _watch(nullptr, v_noabi::pipeline{}, options); } -change_stream client::watch(client_session const& session, options::change_stream const& options) { - return _watch(&session, pipeline{}, options); +v_noabi::change_stream client::watch(client_session const& session, v_noabi::options::change_stream const& options) { + return _watch(&session, v_noabi::pipeline{}, options); } -change_stream client::watch(pipeline const& pipe, options::change_stream const& options) { +v_noabi::change_stream client::watch(v_noabi::pipeline const& pipe, v_noabi::options::change_stream const& options) { return _watch(nullptr, pipe, options); } -change_stream -client::watch(client_session const& session, pipeline const& pipe, options::change_stream const& options) { +v_noabi::change_stream client::watch( + client_session const& session, + v_noabi::pipeline const& pipe, + v_noabi::options::change_stream const& options) { return _watch(&session, pipe, options); } -change_stream -client::_watch(client_session const* session, pipeline const& pipe, options::change_stream const& options) { - bsoncxx::v_noabi::builder::basic::document container; - container.append(bsoncxx::v_noabi::builder::basic::kvp("pipeline", pipe.view_array())); +v_noabi::change_stream client::_watch( + client_session const* session, + v_noabi::pipeline const& pipe, + v_noabi::options::change_stream const& options) { + scoped_bson pipeline{BCON_NEW("pipeline", BCON_ARRAY(to_scoped_bson_view(pipe.view_array()).bson()))}; + + auto opts = to_scoped_bson(v_noabi::options::change_stream::internal::to_document(options)); - bsoncxx::v_noabi::builder::basic::document options_builder; - options_builder.append( - bsoncxx::v_noabi::builder::concatenate(v_noabi::options::change_stream::internal::to_document(options))); if (session) { - options_builder.append(bsoncxx::v_noabi::builder::concatenate_doc{session->_get_impl().to_document()}); + opts += to_scoped_bson_view(session->_get_impl().to_document()); } return v1::change_stream::internal::make( libmongoc::client_watch( - _get_impl().client_t, to_scoped_bson_view(container), to_scoped_bson_view(options_builder))); -} - -template -auto client::_get_impl(Self& self) -> decltype(*self._impl) { - if (!self._impl) { - throw logic_error{error_code::k_invalid_client_object}; - } - return *self._impl; + v1::client::internal::as_mongoc(check_moved_from(_client)), pipeline.bson(), opts.bson())); } -client::impl const& client::_get_impl() const { - return _get_impl(*this); +void client::internal::disown(client& self) { + v1::client::internal::disown(check_moved_from(self._client)); } -client::impl& client::_get_impl() { - return _get_impl(*this); +mongoc_client_t* client::internal::as_mongoc(client& self) { + return v1::client::internal::as_mongoc(check_moved_from(self._client)); } } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.hh index 9945157d01..4e52451fa7 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.hh @@ -14,33 +14,20 @@ #pragma once -#include - #include // IWYU pragma: export +// + #include -#include namespace mongocxx { namespace v_noabi { -class client::impl { +class client::internal { public: - impl(mongoc_client_t* client) : client_t(client) {} - - ~impl() { - libmongoc::client_destroy(client_t); - } - - impl(impl&&) = delete; - impl& operator=(impl&&) = delete; - - impl(impl const&) = delete; - impl& operator=(impl const&) = delete; + static void disown(client& self); - mongoc_client_t* client_t; - std::list tls_options; - options::apm listeners; + static mongoc_client_t* as_mongoc(client& self); }; } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client_session.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client_session.cpp index c917b2556d..ef009178bc 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client_session.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client_session.cpp @@ -27,7 +27,7 @@ namespace v_noabi { // Private constructors. client_session::client_session( - mongocxx::v_noabi::client const* client, + mongocxx::v_noabi::client* client, mongocxx::v_noabi::options::client_session const& options) : _impl(bsoncxx::make_unique(client, options)) {} diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client_session.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client_session.hh index 757778d0b4..cbcec9ce17 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client_session.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client_session.hh @@ -71,7 +71,7 @@ bool with_transaction_cpp_cb(mongoc_client_session_t*, void* ctx, bson_t** reply class client_session::impl { public: - impl(mongocxx::v_noabi::client const* client, options::client_session const& session_options) + impl(mongocxx::v_noabi::client* client, options::client_session const& session_options) : _client(client), _options(session_options), _session_t(nullptr, nullptr) { // Create a mongoc_session_opts_t from session_options. std::unique_ptr opt_t{ @@ -91,7 +91,7 @@ class client_session::impl { } bson_error_t error; - auto s = libmongoc::client_start_session(_client->_get_impl().client_t, opt_t.get(), &error); + auto s = libmongoc::client_start_session(v_noabi::client::internal::as_mongoc(*_client), opt_t.get(), &error); if (!s) { throw mongocxx::v_noabi::exception{error_code::k_cannot_create_session, error.message}; } @@ -231,7 +231,7 @@ class client_session::impl { } private: - mongocxx::v_noabi::client const* _client; + mongocxx::v_noabi::client* _client; options::client_session _options; using unique_session = std::unique_ptr>; diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp index 93b7d604c7..4cee4e777d 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp @@ -258,14 +258,14 @@ collection::collection(database const& database, bsoncxx::v_noabi::string::view_ bsoncxx::make_unique( libmongoc::database_get_collection(database._get_impl().database_t, collection_name.terminated().data()), database.name(), - database._get_impl().client_impl)) {} + database._get_impl().client)) {} collection::collection(database const& database, void* collection) : _impl( bsoncxx::make_unique( static_cast(collection), database.name(), - database._get_impl().client_impl)) {} + database._get_impl().client)) {} collection::collection(collection const& c) { if (c) { @@ -1234,8 +1234,7 @@ cursor collection::_distinct( bson_t const* error_document = {}; auto fake_cursor = v1::cursor::internal::make( - libmongoc::cursor_new_from_command_reply_with_opts( - _get_impl().client_impl->client_t, fake_reply.inout_ptr(), nullptr)); + libmongoc::cursor_new_from_command_reply_with_opts(_get_impl().client, fake_reply.inout_ptr(), nullptr)); if (libmongoc::cursor_error_document(v1::cursor::internal::as_mongoc(fake_cursor), &error, &error_document)) { if (error_document) { bsoncxx::v_noabi::document::value error_doc{ @@ -1382,11 +1381,11 @@ collection::_watch(client_session const* session, pipeline const& pipe, options: } index_view collection::indexes() { - return index_view{_get_impl().collection_t, _get_impl().client_impl->client_t}; + return index_view{_get_impl().collection_t, _get_impl().client}; } search_index_view collection::search_indexes() { - return search_index_view{_get_impl().collection_t, _get_impl().client_impl->client_t}; + return search_index_view{_get_impl().collection_t, _get_impl().client}; } mongocxx::v_noabi::bulk_write collection::_init_insert_many( diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.hh index abfe0b3e1f..5897c80b57 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.hh @@ -20,27 +20,24 @@ #include // IWYU pragma: export #include -#include - #include #include #include +#include + +#include + namespace mongocxx { namespace v_noabi { class collection::impl { public: - impl( - mongoc_collection_t* collection, - bsoncxx::v_noabi::stdx::string_view database_name, - mongocxx::v_noabi::client::impl const* client) - : collection_t(collection), database_name(std::move(database_name)), client_impl(client) {} + impl(mongoc_collection_t* collection, bsoncxx::v_noabi::stdx::string_view database_name, mongoc_client_t* client) + : collection_t(collection), database_name(std::move(database_name)), client(client) {} impl(impl const& i) - : collection_t{libmongoc::collection_copy(i.collection_t)}, - database_name{i.database_name}, - client_impl{i.client_impl} {} + : collection_t{libmongoc::collection_copy(i.collection_t)}, database_name{i.database_name}, client{i.client} {} impl& operator=(impl const& i) { if (this != &i) { @@ -48,7 +45,7 @@ class collection::impl { collection_t = libmongoc::collection_copy(i.collection_t); database_name = i.database_name; - client_impl = i.client_impl; + client = i.client; } return *this; @@ -60,7 +57,7 @@ class collection::impl { mongoc_collection_t* collection_t; std::string database_name; - mongocxx::v_noabi::client::impl const* client_impl; + mongoc_client_t* client; }; } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp index 16e1faf1de..72a679f099 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp @@ -91,11 +91,11 @@ database& database::operator=(database&&) noexcept = default; database::~database() = default; -database::database(mongocxx::v_noabi::client const& client, bsoncxx::v_noabi::string::view_or_value name) +database::database(void* client, bsoncxx::v_noabi::string::view_or_value name) : _impl( bsoncxx::make_unique( - libmongoc::client_get_database(client._get_impl().client_t, name.terminated().data()), - &client._get_impl(), + libmongoc::client_get_database(static_cast(client), name.terminated().data()), + static_cast(client), name.terminated().data())) {} database::database(database const& d) { @@ -258,7 +258,7 @@ bsoncxx::v_noabi::document::value database::run_command( scoped_bson reply; auto result = libmongoc::client_command_simple_with_server_id( - _get_impl().client_impl->client_t, + _get_impl().client, _get_impl().name.c_str(), to_scoped_bson_view(command.view()), libmongoc::database_get_read_prefs(_get_impl().database_t), diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.hh index 386825609a..a7420fc181 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.hh @@ -18,25 +18,25 @@ #include // IWYU pragma: export #include -#include #include +#include + namespace mongocxx { namespace v_noabi { class database::impl { public: - impl(mongoc_database_t* db, mongocxx::v_noabi::client::impl const* client, std::string name) - : database_t(db), client_impl(client), name(std::move(name)) {} + impl(mongoc_database_t* db, mongoc_client_t* client, std::string name) + : database_t(db), client(client), name(std::move(name)) {} - impl(impl const& i) - : database_t{libmongoc::database_copy(i.database_t)}, client_impl{i.client_impl}, name{i.name} {} + impl(impl const& i) : database_t{libmongoc::database_copy(i.database_t)}, client{i.client}, name{i.name} {} impl& operator=(impl const& i) { if (this != &i) { libmongoc::database_destroy(database_t); database_t = libmongoc::database_copy(i.database_t); - client_impl = i.client_impl; + client = i.client; name = i.name; } @@ -48,7 +48,7 @@ class database::impl { } mongoc_database_t* database_t; - mongocxx::v_noabi::client::impl const* client_impl; + mongoc_client_t* client; std::string name; }; 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 097d94fa6c..1d9a0c4e55 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 @@ -18,13 +18,13 @@ #include +#include #include #include #include #include -#include #include #include #include @@ -122,54 +122,56 @@ v1::auto_encryption_options auto_encryption::internal::to_v1(auto_encryption con return v1::auto_encryption_options{v}; } -mongoc_auto_encryption_opts_t* auto_encryption::internal::to_mongoc(auto_encryption const& opts) { +std::unique_ptr +auto_encryption::internal::to_mongoc(auto_encryption const& opts) { + std::unique_ptr ret{ + libmongoc::auto_encryption_opts_new()}; + + auto const ptr = ret.get(); + if (opts.key_vault_client() && opts.key_vault_pool()) { throw v_noabi::exception{ error_code::k_invalid_parameter, "cannot set both key vault client and key vault pool, please choose one"}; } - auto const ret = libmongoc::auto_encryption_opts_new(); - - if (!ret) { + if (!ptr) { throw std::logic_error{"could not get object from libmongoc"}; } if (auto const& opt = opts.key_vault_client()) { - mongoc_client_t* client_t = (*opt)->_get_impl().client_t; - libmongoc::auto_encryption_opts_set_keyvault_client(ret, client_t); + libmongoc::auto_encryption_opts_set_keyvault_client(ptr, v_noabi::client::internal::as_mongoc(**opt)); } if (auto const& opt = opts.key_vault_pool()) { - mongoc_client_pool_t* pool_t = (*opt)->_impl->client_pool_t; - libmongoc::auto_encryption_opts_set_keyvault_client_pool(ret, pool_t); + libmongoc::auto_encryption_opts_set_keyvault_client_pool(ptr, (*opt)->_impl->client_pool_t); } if (auto const& opt = opts.key_vault_namespace()) { auto const& ns = *opt; - libmongoc::auto_encryption_opts_set_keyvault_namespace(ret, ns.first.c_str(), ns.second.c_str()); + libmongoc::auto_encryption_opts_set_keyvault_namespace(ptr, ns.first.c_str(), ns.second.c_str()); } if (auto const& opt = opts.kms_providers()) { - libmongoc::auto_encryption_opts_set_kms_providers(ret, to_scoped_bson_view(*opt)); + libmongoc::auto_encryption_opts_set_kms_providers(ptr, to_scoped_bson_view(*opt)); } if (auto const& opt = opts.tls_opts()) { - libmongoc::auto_encryption_opts_set_tls_opts(ret, to_scoped_bson_view(*opt)); + libmongoc::auto_encryption_opts_set_tls_opts(ptr, to_scoped_bson_view(*opt)); } if (auto const& opt = opts.schema_map()) { - libmongoc::auto_encryption_opts_set_schema_map(ret, to_scoped_bson_view(*opt)); + libmongoc::auto_encryption_opts_set_schema_map(ptr, to_scoped_bson_view(*opt)); } if (auto const& opt = opts.encrypted_fields_map()) { - libmongoc::auto_encryption_opts_set_encrypted_fields_map(ret, to_scoped_bson_view(*opt)); + libmongoc::auto_encryption_opts_set_encrypted_fields_map(ptr, to_scoped_bson_view(*opt)); } - libmongoc::auto_encryption_opts_set_bypass_auto_encryption(ret, opts.bypass_auto_encryption()); - libmongoc::auto_encryption_opts_set_bypass_query_analysis(ret, opts.bypass_query_analysis()); + 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(ret, to_scoped_bson_view(*opt)); + libmongoc::auto_encryption_opts_set_extra(ptr, to_scoped_bson_view(*opt)); } return ret; diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/auto_encryption.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/auto_encryption.hh index 2e8f23afdc..cd642a2c45 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/auto_encryption.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/auto_encryption.hh @@ -14,6 +14,8 @@ #pragma once +#include + #include // IWYU pragma: export // @@ -29,7 +31,14 @@ class auto_encryption::internal { static auto_encryption from_v1(v1::auto_encryption_options v); static v1::auto_encryption_options to_v1(auto_encryption const& v); - static mongoc_auto_encryption_opts_t* to_mongoc(auto_encryption const& opts); + 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( + auto_encryption const& opts); }; } // namespace options diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client.cpp index 4b08ea0aa7..a5c660d20d 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client.cpp @@ -12,56 +12,72 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include + +// + +#include + +#include + +#include +#include +#include namespace mongocxx { namespace v_noabi { namespace options { -client& client::tls_opts(tls tls_opts) { - _tls_opts = std::move(tls_opts); - return *this; -} +client::client(v1::client::options opts) + : _tls_opts{std::move(v1::client::options::internal::tls_opts(opts))}, + _apm_opts{std::move(v1::client::options::internal::apm_opts(opts))}, + _auto_encrypt_opts{}, + _server_api_opts{std::move(v1::client::options::internal::server_api_opts(opts))} {} -bsoncxx::v_noabi::stdx::optional const& client::tls_opts() const { - return _tls_opts; -} +client::operator v1::client::options() const { + using mongocxx::v_noabi::from_v1; -client& client::ssl_opts(tls ssl_opts) { - return tls_opts(std::move(ssl_opts)); -} + v1::client::options ret; -bsoncxx::v_noabi::stdx::optional const& client::ssl_opts() const { - return tls_opts(); -} + if (_tls_opts) { + ret.tls_opts(v_noabi::to_v1(*_tls_opts)); + } -client& client::apm_opts(apm apm_opts) { - _apm_opts = std::move(apm_opts); - return *this; -} + if (_apm_opts) { + ret.apm_opts(v_noabi::to_v1(*_apm_opts)); + } -bsoncxx::v_noabi::stdx::optional const& client::apm_opts() const { - return _apm_opts; + // _auto_encrypt_opts + + if (_server_api_opts) { + ret.server_api_opts(v_noabi::to_v1(*_server_api_opts)); + } + + return ret; } -client& client::auto_encryption_opts(auto_encryption auto_encryption_opts) { - _auto_encrypt_opts = std::move(auto_encryption_opts); - return *this; +client client::internal::from_v1(v1::client::options v) { + return {std::move(v)}; } -bsoncxx::v_noabi::stdx::optional const& client::auto_encryption_opts() const { - return _auto_encrypt_opts; +v1::client::options client::internal::to_v1(client const& v) { + return v1::client::options{v}; } -client& client::server_api_opts(server_api server_api_opts) { - _server_api_opts = std::move(server_api_opts); - return *this; +} // namespace options +} // namespace v_noabi +} // namespace mongocxx + +namespace mongocxx { +namespace v_noabi { + +v_noabi::options::client from_v1(v1::client::options v) { + return v_noabi::options::client::internal::from_v1(std::move(v)); } -bsoncxx::v_noabi::stdx::optional const& client::server_api_opts() const { - return _server_api_opts; +v1::client::options to_v1(v_noabi::options::client v) { + return v_noabi::options::client::internal::to_v1(v); } -} // namespace options } // namespace v_noabi } // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client.hh new file mode 100644 index 0000000000..f75a7fd710 --- /dev/null +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client.hh @@ -0,0 +1,35 @@ +// 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 + +namespace mongocxx { +namespace v_noabi { +namespace options { + +class client::internal { + public: + static client from_v1(v1::client::options v); + static v1::client::options to_v1(client const& v); +}; + +} // namespace options +} // namespace v_noabi +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client_encryption.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client_encryption.cpp index 32281ba239..44dccb42a5 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client_encryption.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/client_encryption.cpp @@ -65,8 +65,8 @@ void* client_encryption::convert() const { mongoc_client_encryption_opts_t* opts_t = libmongoc::client_encryption_opts_new(); if (_key_vault_client) { - mongoc_client_t* client_t = (*_key_vault_client)->_get_impl().client_t; - libmongoc::client_encryption_opts_set_keyvault_client(opts_t, client_t); + libmongoc::client_encryption_opts_set_keyvault_client( + opts_t, v_noabi::client::internal::as_mongoc(**_key_vault_client)); } if (_key_vault_namespace) { diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.cpp index 17d319bd98..c1b20c5788 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.cpp @@ -16,6 +16,7 @@ // +#include #include #include @@ -24,6 +25,8 @@ #include #include +#include + namespace mongocxx { namespace v_noabi { namespace options { @@ -44,6 +47,35 @@ server_api::version server_api::version_from_string(bsoncxx::v_noabi::stdx::stri throw v_noabi::logic_error{v_noabi::error_code::k_invalid_parameter, "invalid server API version"}; } +std::unique_ptr server_api::internal::to_mongoc( + server_api const& opts) { + mongoc_server_api_version_t mongoc_api_version = {}; + + // Convert version enum value to std::string then to c_str to create mongoc api version. + auto result = libmongoc::server_api_version_from_string( + server_api::version_to_string(opts.get_version()).c_str(), &mongoc_api_version); + if (!result) { + throw mongocxx::v_noabi::logic_error{ + mongocxx::v_noabi::error_code::k_invalid_parameter, + "invalid server API version" + server_api::version_to_string(opts.get_version())}; + } + + std::unique_ptr ret{ + libmongoc::server_api_new(mongoc_api_version)}; + + auto const ptr = ret.get(); + + if (!ptr) { + throw mongocxx::v_noabi::logic_error{ + mongocxx::v_noabi::error_code::k_create_resource_fail, "could not create server API"}; + } + + libmongoc::server_api_strict(ptr, opts._strict.value_or(false)); + libmongoc::server_api_deprecation_errors(ptr, opts._deprecation_errors.value_or(false)); + + return ret; +} + } // namespace options } // namespace v_noabi } // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.hh index dade61da37..93cac1f786 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.hh @@ -20,44 +20,22 @@ #include -#include -#include - #include namespace mongocxx { namespace v_noabi { namespace options { -using unique_server_api = std::unique_ptr; - -inline unique_server_api make_server_api(server_api const& opts) { - mongoc_server_api_version_t mongoc_api_version = {}; - - // Convert version enum value to std::string then to c_str to create mongoc api version. - auto result = libmongoc::server_api_version_from_string( - server_api::version_to_string(opts.get_version()).c_str(), &mongoc_api_version); - if (!result) { - throw mongocxx::v_noabi::logic_error{ - mongocxx::v_noabi::error_code::k_invalid_parameter, - "invalid server API version" + server_api::version_to_string(opts.get_version())}; - } - - auto mongoc_server_api_opts = libmongoc::server_api_new(mongoc_api_version); - if (!mongoc_server_api_opts) { - throw mongocxx::v_noabi::logic_error{ - mongocxx::v_noabi::error_code::k_create_resource_fail, "could not create server API"}; - } - - if (opts.strict().value_or(false)) { - libmongoc::server_api_strict(mongoc_server_api_opts, opts.strict().value_or(false)); - } - if (opts.deprecation_errors().value_or(false)) { - libmongoc::server_api_deprecation_errors(mongoc_server_api_opts, opts.deprecation_errors().value_or(false)); - } +class server_api::internal { + public: + struct mongoc_server_api_deleter { + void operator()(mongoc_server_api_t* ptr) const noexcept { + libmongoc::server_api_destroy(ptr); + } + }; - return {mongoc_server_api_opts, libmongoc::server_api_destroy}; -} + static std::unique_ptr to_mongoc(server_api const& opts); +}; } // namespace options } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/tls.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/tls.cpp index c54c6ad303..b96d2327e4 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/tls.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/tls.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include // @@ -23,6 +23,8 @@ #include #include +#include + namespace mongocxx { namespace v_noabi { namespace options { @@ -89,6 +91,38 @@ bsoncxx::v_noabi::stdx::optional const& tls::allow_invalid_certificates() return _allow_invalid_certificates; } +#if MONGOCXX_SSL_IS_ENABLED() +tls::internal::to_mongoc_type tls::internal::to_mongoc(tls const& opts) { + to_mongoc_type ret = {}; + + auto& opt = ret.opt; + + if (opts._pem_file) { + opt.pem_file = ret.string_owner.emplace(ret.string_owner.end(), opts._pem_file->terminated())->data(); + } + + if (opts._pem_password) { + opt.pem_pwd = ret.string_owner.emplace(ret.string_owner.end(), opts._pem_password->terminated())->data(); + } + + if (opts._ca_file) { + opt.ca_file = ret.string_owner.emplace(ret.string_owner.end(), opts._ca_file->terminated())->data(); + } + + if (opts._ca_dir) { + opt.ca_dir = ret.string_owner.emplace(ret.string_owner.end(), opts._ca_dir->terminated())->data(); + } + + if (opts._crl_file) { + opt.crl_file = ret.string_owner.emplace(ret.string_owner.end(), opts._crl_file->terminated())->data(); + } + + opt.weak_cert_validation = opts._allow_invalid_certificates.value_or(false); + + return ret; +} +#endif + } // namespace options } // namespace v_noabi } // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/tls.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/tls.hh index 96ecd6d1f7..585cd4681a 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/tls.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/tls.hh @@ -18,42 +18,32 @@ // +#include + +#if MONGOCXX_SSL_IS_ENABLED() #include +#include + #include -#include +#endif namespace mongocxx { namespace v_noabi { namespace options { +class tls::internal { + public: #if MONGOCXX_SSL_IS_ENABLED() -inline std::pair<::mongoc_ssl_opt_t, std::list> make_tls_opts( - tls const& tls_opts) { - ::mongoc_ssl_opt_t out{}; - std::list values; - - if (tls_opts.pem_file()) { - out.pem_file = values.emplace(values.end(), tls_opts.pem_file()->terminated())->data(); - } - if (tls_opts.pem_password()) { - out.pem_pwd = values.emplace(values.end(), tls_opts.pem_password()->terminated())->data(); - } - if (tls_opts.ca_file()) { - out.ca_file = values.emplace(values.end(), tls_opts.ca_file()->terminated())->data(); - } - if (tls_opts.ca_dir()) { - out.ca_dir = values.emplace(values.end(), tls_opts.ca_dir()->terminated())->data(); - } - if (tls_opts.crl_file()) { - out.crl_file = values.emplace(values.end(), tls_opts.crl_file()->terminated())->data(); - } - if (tls_opts.allow_invalid_certificates()) { - out.weak_cert_validation = *(tls_opts.allow_invalid_certificates()); - } - return {out, std::move(values)}; -} + struct to_mongoc_type { + std::list string_owner; + + mongoc_ssl_opt_t opt; + }; + + static to_mongoc_type to_mongoc(tls const& opts); #endif +}; } // namespace options } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.cpp index 0da468de2f..358c8ca17f 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pool.cpp @@ -12,9 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + +// + +#include + #include -#include #include #include #include @@ -49,9 +54,9 @@ static mongoc_client_pool_t* construct_client_pool(mongoc_uri_t const* uri) { } void pool::_release(client* client) { - libmongoc::client_pool_push(_impl->client_pool_t, client->_get_impl().client_t); + 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 - client->_get_impl().client_t = nullptr; + v_noabi::client::internal::disown(*client); delete client; // NOLINT(cppcoreguidelines-owning-memory): custom deleter. } @@ -60,50 +65,43 @@ pool::~pool() = default; 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 (options.client_opts().tls_opts()) { - if (!uri.tls()) - throw exception{error_code::k_invalid_parameter, "cannot set TLS options if 'tls=true' not in URI"}; + 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 mongoc_opts = options::make_tls_opts(*options.client_opts().tls_opts()); - _impl->tls_options = std::move(mongoc_opts.second); - libmongoc::client_pool_set_ssl_opts(_impl->client_pool_t, &mongoc_opts.first); + auto const v = v_noabi::options::tls::internal::to_mongoc(*opts); + libmongoc::client_pool_set_ssl_opts(_impl->client_pool_t, &v.opt); } #else - if (uri.tls() || options.client_opts().tls_opts()) - throw exception{error_code::k_ssl_not_supported}; + 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& auto_encryption_opts = options.client_opts().auto_encryption_opts()) { - auto const opts = v_noabi::options::auto_encryption::internal::to_mongoc(*auto_encryption_opts); - - bson_error_t error; - auto r = libmongoc::client_pool_enable_auto_encryption(_impl->client_pool_t, opts, &error); - - libmongoc::auto_encryption_opts_destroy(opts); + if (auto const& opts = options.client_opts().auto_encryption_opts()) { + bson_error_t error = {}; - if (!r) { - throw_exception(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); } } - if (options.client_opts().apm_opts()) { - _impl->listeners = *options.client_opts().apm_opts(); - auto callbacks = options::make_apm_callbacks(_impl->listeners); - // We cast the APM class to a void* so we can pass it into libmongoc's context. - // It will be cast back to an APM class in the event handlers. - auto context = static_cast(&(_impl->listeners)); - libmongoc::client_pool_set_apm_callbacks(_impl->client_pool_t, callbacks.get(), context); + 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 (options.client_opts().server_api_opts()) { - auto const& server_api_opts = *options.client_opts().server_api_opts(); - auto mongoc_server_api_opts = options::make_server_api(server_api_opts); + if (auto const& opts = options.client_opts().server_api_opts()) { + bson_error_t error = {}; - bson_error_t error; - auto result = libmongoc::client_pool_set_server_api(_impl->client_pool_t, mongoc_server_api_opts.get(), &error); - - if (!result) { - throw_exception(error); + if (!libmongoc::client_pool_set_server_api( + _impl->client_pool_t, v_noabi::options::server_api::internal::to_mongoc(*opts).get(), &error)) { + v_noabi::throw_exception(error); } } } @@ -129,21 +127,27 @@ pool::entry::operator bool() const noexcept { pool::entry::entry(pool::entry::unique_client p) : _client(std::move(p)) {} pool::entry pool::acquire() { - auto cli = libmongoc::client_pool_pop(_impl->client_pool_t); - if (!cli) + 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."}; + } - return entry(entry::unique_client(new client(cli), [this](client* client) { _release(client); })); + return entry(entry::unique_client(new v_noabi::client{v1::client::internal::make(cli)}, [this](client* client) { + _release(client); + })); } bsoncxx::v_noabi::stdx::optional pool::try_acquire() { - auto cli = libmongoc::client_pool_try_pop(_impl->client_pool_t); - if (!cli) + auto const cli = libmongoc::client_pool_try_pop(_impl->client_pool_t); + if (!cli) { return bsoncxx::v_noabi::stdx::nullopt; + } - return entry(entry::unique_client(new client(cli), [this](client* client) { _release(client); })); + return entry(entry::unique_client(new v_noabi::client{v1::client::internal::make(cli)}, [this](client* client) { + _release(client); + })); } } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.cpp index d7ebbebe01..3c9ec06a28 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.cpp @@ -49,6 +49,10 @@ void uri::server_selection_try_once(bool val) try { throw v_noabi::exception{v_noabi::error_code::k_invalid_uri, "failed to set 'serverSelectionTryOnce' option"}; } +v1::uri const& uri::internal::as_v1(uri const& self) { + return self._uri; +} + mongoc_uri_t const* uri::internal::as_mongoc(uri const& self) { return v1::uri::internal::as_mongoc(self._uri); } diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.hh index 10d1220832..e62ad0ec0c 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.hh @@ -25,6 +25,8 @@ namespace v_noabi { class uri::internal { public: + static v1::uri const& as_v1(uri const& self); + static mongoc_uri_t const* as_mongoc(uri const& self); }; diff --git a/src/mongocxx/test/CMakeLists.txt b/src/mongocxx/test/CMakeLists.txt index c58f550a8f..9f5cab95b6 100644 --- a/src/mongocxx/test/CMakeLists.txt +++ b/src/mongocxx/test/CMakeLists.txt @@ -112,6 +112,7 @@ set(mongocxx_test_sources_v1 v1/auto_encryption_options.cpp v1/bsoncxx.cpp v1/bulk_write.cpp + v1/client.cpp v1/change_stream.cpp v1/count_options.cpp v1/cursor.cpp diff --git a/src/mongocxx/test/v1/client.cpp b/src/mongocxx/test/v1/client.cpp new file mode 100644 index 0000000000..c66f3c2bd1 --- /dev/null +++ b/src/mongocxx/test/v1/client.cpp @@ -0,0 +1,1657 @@ +// 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 +#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 +#include +#include +#include + +namespace mongocxx { +namespace v1 { + +using code = mongocxx::v1::client::errc; + +TEST_CASE("error code", "[bsoncxx][v1][client][error]") { + using mongocxx::v1::source_errc; + using mongocxx::v1::type_errc; + + auto const& category = mongocxx::v1::client::error_category(); + CHECK_THAT(category.name(), Catch::Matchers::Equals("mongocxx::v1::client")); + + 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::tls_not_enabled; + + 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::tls_not_enabled) == source_errc::mongocxx); + CHECK(make_error_code(code::tls_not_supported) == source_errc::mongocxx); + } + + SECTION("type") { + CHECK(make_error_code(code::tls_not_enabled) == type_errc::invalid_argument); + CHECK(make_error_code(code::tls_not_supported) == type_errc::invalid_argument); + } +} + +namespace { + +struct identity_type {}; + +// Represent a successfully-constructed mocked `v1::client` object. +struct client_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 client_destroy_type = decltype(libmongoc::client_destroy.create_instance()); + using client_new_type = decltype(libmongoc::client_new_from_uri_with_error.create_instance()); + + identity_type uri_identity; + identity_type client_identity; + + mongoc_uri_t* uri_id = reinterpret_cast(&uri_identity); + mongoc_client_t* client_id = reinterpret_cast(&client_identity); + + uri_destroy_type uri_destroy; + uri_copy_type uri_copy; + uri_get_tls_type uri_get_tls; + client_destroy_type client_destroy; + client_new_type client_new_from_uri_with_error; + + v1::uri uri; + + ~client_mocks_type() = default; + client_mocks_type(client_mocks_type&& other) noexcept = delete; + client_mocks_type& operator=(client_mocks_type&& other) noexcept = delete; + client_mocks_type(client_mocks_type const& other) = delete; + client_mocks_type& operator=(client_mocks_type const& other) = delete; + + client_mocks_type() + : uri_destroy{libmongoc::uri_destroy.create_instance()}, + uri_copy{libmongoc::uri_copy.create_instance()}, + uri_get_tls{libmongoc::uri_get_tls.create_instance()}, + client_destroy{libmongoc::client_destroy.create_instance()}, + client_new_from_uri_with_error{libmongoc::client_new_from_uri_with_error.create_instance()}, + uri{v1::uri::internal::make(uri_id)} { + 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(); + + client_destroy + ->interpose([&](mongoc_client_t* ptr) { + if (ptr) { + CHECK(ptr == client_id); + } + }) + .forever(); + + client_new_from_uri_with_error + ->interpose([&](mongoc_uri_t const* uri, bson_error_t* error) -> mongoc_client_t* { + CHECK(uri == uri_id); + CHECK(error != nullptr); + return client_id; + }) + .forever(); + } + + template + v1::client make(Args&&... args) { + return {std::move(uri), std::forward(args)...}; + } +}; + +} // namespace + +TEST_CASE("exceptions", "[mongocxx][v1][client]") { + client_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_set_ssl_opts.create_instance(); + set_ssl_opts->interpose([&](mongoc_client_t* client, mongoc_ssl_opt_t const* opts) -> void { + CHECK(client == mocks.client_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(std::move(opts)), code::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(std::move(opts)), code::tls_not_supported); + } + } +#endif + } + + SECTION("mongoc") { + auto const v = GENERATE(as(), 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, v, "%s", msg); + error->reserved = 2; // MONGOC_ERROR_CATEGORY + }; + + SECTION("new_from_uri_with_error") { + mocks.client_new_from_uri_with_error->interpose( + [&](mongoc_uri_t const* uri, bson_error_t* error) -> mongoc_client_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_enable_auto_encryption.create_instance(); + enable_auto_encryption->interpose( + [&](mongoc_client_t* client, mongoc_auto_encryption_opts_t* opts, bson_error_t* error) -> bool { + CHECK(client == mocks.client_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_set_server_api.create_instance(); + set_server_api->interpose( + [&](mongoc_client_t* client, mongoc_server_api_t const* api, bson_error_t* error) -> bool { + CHECK(client == mocks.client_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)); + } + } + + SECTION("list_database_names") { + auto client = mocks.make(); + + auto get_database_names_with_opts = libmongoc::client_get_database_names_with_opts.create_instance(); + get_database_names_with_opts->interpose( + [&](mongoc_client_t* client, bson_t const* opts, bson_error_t* error) -> char** { + CHECK(client == mocks.client_id); + CHECK(opts == nullptr); + set_error(error); + return nullptr; + }); + + try { + client.list_database_names(); + 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][client]") { + identity_type id1; + identity_type id2; + + auto const client1 = reinterpret_cast(&id1); + auto const client2 = reinterpret_cast(&id2); + + int destroy_count = 0; + + auto destroy = libmongoc::client_destroy.create_instance(); + destroy + ->interpose([&](mongoc_client_t* ptr) -> void { + if (ptr) { + if (ptr != client1 && ptr != client2) { + FAIL("unexpected mongoc_client_t"); + } + ++destroy_count; + } + }) + .forever(); + + auto source = client::internal::make(client1); + auto target = client::internal::make(client2); + + REQUIRE(client::internal::as_mongoc(source) == client1); + REQUIRE(client::internal::as_mongoc(target) == client2); + + 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(client::internal::as_mongoc(move) == client1); + CHECK(destroy_count == 0); + + target = std::move(move); + + // move is in an assign-or-move-only state. + CHECK_FALSE(move); + + CHECK(target); + CHECK(client::internal::as_mongoc(target) == client1); + CHECK(destroy_count == 1); + } + + CHECK(destroy_count == 1); + } +} + +TEST_CASE("ownership", "[mongocxx][v1][client][options]") { + client::options source; + client::options target; + + auto source_value = std::string{"source"}; + auto target_value = std::string{"target"}; + + source.tls_opts(std::move(v1::tls{}.ca_file(source_value))); + target.tls_opts(std::move(v1::tls{}.ca_file(target_value))); + + REQUIRE(source.tls_opts().value().ca_file() == "source"); + REQUIRE(target.tls_opts().value().ca_file() == "target"); + + SECTION("move") { + auto move = std::move(source); + + // source is in an assign-or-move-only state. + + CHECK(move.tls_opts().value().ca_file() == source_value); + + target = std::move(move); + + // source is in an assign-or-move-only state. + + CHECK(target.tls_opts().value().ca_file() == source_value); + } + + SECTION("copy") { + auto copy = source; + + CHECK(source.tls_opts().value().ca_file() == source_value); + CHECK(copy.tls_opts().value().ca_file() == source_value); + + target = copy; + + CHECK(copy.tls_opts().value().ca_file() == source_value); + CHECK(target.tls_opts().value().ca_file() == source_value); + } +} + +TEST_CASE("default", "[mongocxx][v1][client]") { + client const client; + + CHECK_FALSE(client); +} + +TEST_CASE("default", "[mongocxx][v1][client][options]") { + client::options const opts; + + CHECK_FALSE(opts.tls_opts().has_value()); + CHECK_FALSE(opts.auto_encryption_opts().has_value()); + CHECK_FALSE(opts.apm_opts().has_value()); + CHECK_FALSE(opts.server_api_opts().has_value()); +} + +TEST_CASE("tls_opts", "[mongocxx][v1][client]") { +#if MONGOCXX_SSL_IS_ENABLED() + client_mocks_type mocks; + + auto set_ssl_opts = libmongoc::client_set_ssl_opts.create_instance(); + set_ssl_opts + ->interpose([&](mongoc_client_t* client, mongoc_ssl_opt_t const* opts) { + CHECK(client == mocks.client_id); + CHECK(opts != nullptr); + FAIL("should not reach this point"); + }) + .forever(); + + SECTION("string") { + using data_mem_type = char const* mongoc_ssl_opt_t::*; + using setter_type = v1::tls& (v1::tls::*)(std::string); + + std::string value{"a very long string to avoid small string optimization for unique identity checks"}; + auto const value_ptr = value.c_str(); + + setter_type setter = nullptr; + data_mem_type data_mem = {}; + + std::tie(setter, data_mem) = GENERATE( + table({ + {&v1::tls::pem_file, &mongoc_ssl_opt_t::pem_file}, + {&v1::tls::pem_password, &mongoc_ssl_opt_t::pem_pwd}, + {&v1::tls::ca_file, &mongoc_ssl_opt_t::ca_file}, + {&v1::tls::ca_dir, &mongoc_ssl_opt_t::ca_dir}, + {&v1::tls::crl_file, &mongoc_ssl_opt_t::crl_file}, + })); + + int set_counter = 0; + set_ssl_opts->interpose([&](mongoc_client_t* client, mongoc_ssl_opt_t const* opts) -> void { + CHECK(client == mocks.client_id); + REQUIRE(opts != nullptr); + CHECK(static_cast(opts->*data_mem) == static_cast(value_ptr)); + ++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.*setter)(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); + } + + SECTION("bool") { + using data_mem_type = bool mongoc_ssl_opt_t::*; + using setter_type = v1::tls& (v1::tls::*)(bool); + + auto const value = GENERATE(false, true); + + setter_type setter = nullptr; + data_mem_type data_mem = {}; + + std::tie(setter, data_mem) = GENERATE( + table({ + {&v1::tls::allow_invalid_certificates, &mongoc_ssl_opt_t::weak_cert_validation}, + })); + + int set_counter = 0; + set_ssl_opts->interpose([&](mongoc_client_t* client, mongoc_ssl_opt_t const* opts) -> void { + CHECK(client == mocks.client_id); + REQUIRE(opts != nullptr); + CHECK(opts->*data_mem == 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.*setter)(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 { + +template +void test_auto_encryption_opts_bool( + client_mocks_type& mocks, + Mock& mock, + v1::auto_encryption_options& (v1::auto_encryption_options::*setter)(bool), + mongoc_auto_encryption_opts_t* opts_id) { + auto const input = GENERATE(false, true); + + int counter = 0; + + auto fn = mock.create_instance(); + fn->interpose([&](mongoc_auto_encryption_opts_t* ptr, bool v) -> void { + CHECK(ptr == opts_id); + CHECK(v == input); + ++counter; + }); + + { + v1::auto_encryption_options auto_encryption_opts; + CHECK_NOTHROW((auto_encryption_opts.*setter)(input)); + + client::options opts; + CHECK_NOTHROW(opts.auto_encryption_opts(std::move(auto_encryption_opts))); + (void)mocks.make(std::move(opts)); + } + + CHECK(counter == 1); +} + +template +void test_auto_encryption_opts_doc( + client_mocks_type& mocks, + Mock& mock, + v1::auto_encryption_options& (v1::auto_encryption_options::*setter)(bsoncxx::v1::document::value), + mongoc_auto_encryption_opts_t* opts_id) { + auto const doc = GENERATE(as(), R"({})", R"({"x": 1})"); + + int counter = 0; + + auto fn = mock.create_instance(); + fn->interpose([&](mongoc_auto_encryption_opts_t* ptr, bson_t const* bson) -> void { + CHECK(ptr == opts_id); + REQUIRE(bson != nullptr); + CHECK(scoped_bson_view{bson}.view() == doc.view()); + ++counter; + }); + + { + v1::auto_encryption_options auto_encryption_opts; + CHECK_NOTHROW((auto_encryption_opts.*setter)(doc.value())); + + client::options opts; + CHECK_NOTHROW(opts.auto_encryption_opts(std::move(auto_encryption_opts))); + (void)mocks.make(std::move(opts)); + } + + CHECK(counter == 1); +} + +} // namespace + +TEST_CASE("auto_encryption_opts", "[mongocxx][v1][client]") { + // 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 client_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_enable_auto_encryption.create_instance(); + + opts_destroy + ->interpose([&](mongoc_auto_encryption_opts_t* ptr) { + if (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_t* client, mongoc_auto_encryption_opts_t* opts, bson_error_t* error) -> bool { + CHECK(client == mocks.client_id); + CHECK(opts == opts_id); + CHECK(error != nullptr); + ++enable_count; + return true; + }); + + SECTION("key_vault_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) { + if (ptr != kv_client_id) { + FAIL_CHECK("unexpected mongoc_client_t"); + } + } + }); + + int counter = 0; + auto set_keyvault_client = libmongoc::auto_encryption_opts_set_keyvault_client.create_instance(); + set_keyvault_client->interpose([&](mongoc_auto_encryption_opts_t* ptr, mongoc_client_t* kv_ptr) -> void { + CHECK(ptr == opts_id); + CHECK(kv_ptr == kv_client_id); + ++counter; + }); + + { + v1::auto_encryption_options auto_encryption_opts; + CHECK_NOTHROW(auto_encryption_opts.key_vault_client(&kv_client)); + + client::options opts; + CHECK_NOTHROW(opts.auto_encryption_opts(std::move(auto_encryption_opts))); + mocks.client_destroy->interpose([&](mongoc_client_t* ptr) -> void { CHECK(ptr == mocks.client_id); }); + (void)mocks.make(std::move(opts)); + } + + CHECK(counter == 1); + CHECK(enable_count == 1); + } + + SECTION("key_vault_pool") { + // TODO: v1::pool (CXX-3237) + } + + SECTION("key_vault_namespace") { + auto const db = GENERATE(as(), "db1", "db2"); + auto const coll = GENERATE(as(), "coll1", "coll2"); + + auto fn = libmongoc::auto_encryption_opts_set_keyvault_namespace.create_instance(); + + int counter = 0; + fn->interpose([&](mongoc_auto_encryption_opts_t* ptr, char const* db_ptr, char const* coll_ptr) -> void { + CHECK(ptr == opts_id); + CHECK_THAT(db_ptr, Catch::Matchers::Equals(db)); + CHECK_THAT(coll_ptr, Catch::Matchers::Equals(coll)); + ++counter; + }); + + { + v1::auto_encryption_options auto_encryption_opts; + CHECK_NOTHROW(auto_encryption_opts.key_vault_namespace({db, coll})); + + 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("kms_providers") { + test_auto_encryption_opts_doc( + mocks, + libmongoc::auto_encryption_opts_set_kms_providers, + &v1::auto_encryption_options::kms_providers, + opts_id); + CHECK(enable_count == 1); + } + + SECTION("tls_opts") { + test_auto_encryption_opts_doc( + mocks, libmongoc::auto_encryption_opts_set_tls_opts, &v1::auto_encryption_options::tls_opts, opts_id); + CHECK(enable_count == 1); + } + + SECTION("schema_map") { + test_auto_encryption_opts_doc( + mocks, libmongoc::auto_encryption_opts_set_schema_map, &v1::auto_encryption_options::schema_map, opts_id); + CHECK(enable_count == 1); + } + + SECTION("encrypted_fields_map") { + test_auto_encryption_opts_doc( + mocks, + libmongoc::auto_encryption_opts_set_encrypted_fields_map, + &v1::auto_encryption_options::encrypted_fields_map, + opts_id); + CHECK(enable_count == 1); + } + + SECTION("bypass_auto_encryption") { + test_auto_encryption_opts_bool( + mocks, + libmongoc::auto_encryption_opts_set_bypass_auto_encryption, + &v1::auto_encryption_options::bypass_auto_encryption, + opts_id); + CHECK(enable_count == 1); + } + + SECTION("bypass_query_analysis") { + test_auto_encryption_opts_bool( + mocks, + libmongoc::auto_encryption_opts_set_bypass_query_analysis, + &v1::auto_encryption_options::bypass_query_analysis, + opts_id); + CHECK(enable_count == 1); + } + + SECTION("extra_options") { + test_auto_encryption_opts_doc( + mocks, libmongoc::auto_encryption_opts_set_extra, &v1::auto_encryption_options::extra_options, opts_id); + CHECK(enable_count == 1); + } +} + +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 + +TEST_CASE("apm_opts", "[mongocxx][v1][client]") { + client_mocks_type mocks; + + identity_type callbacks_identity; + auto const callbacks_id = reinterpret_cast(&callbacks_identity); + + v1::apm callbacks; + + static constexpr std::size_t N = 12; // Number of APM callback functions. + std::array counters = {}; // Number of times each APM callback (by index) is invoked. + std::array, N> setters = {{ + [&] { callbacks.on_command_started(callback_fn_type{}); }, + [&] { callbacks.on_command_failed(callback_fn_type{}); }, + [&] { callbacks.on_command_succeeded(callback_fn_type{}); }, + [&] { callbacks.on_server_closed(callback_fn_type{}); }, + [&] { callbacks.on_server_description_changed(callback_fn_type{}); }, + [&] { callbacks.on_server_opening(callback_fn_type{}); }, + [&] { callbacks.on_topology_closed(callback_fn_type{}); }, + [&] { callbacks.on_topology_description_changed(callback_fn_type{}); }, + [&] { callbacks.on_topology_opening(callback_fn_type{}); }, + [&] { callbacks.on_server_heartbeat_started(callback_fn_type{}); }, + [&] { callbacks.on_server_heartbeat_failed(callback_fn_type{}); }, + [&] { callbacks.on_server_heartbeat_succeeded(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_set_apm_callbacks.create_instance(); + + auto set_command_started = libmongoc::apm_set_command_started_cb.create_instance(); + auto set_command_failed = libmongoc::apm_set_command_failed_cb.create_instance(); + auto set_command_succeeded = libmongoc::apm_set_command_succeeded_cb.create_instance(); + auto set_server_closed = libmongoc::apm_set_server_closed_cb.create_instance(); + auto set_server_description_changed = libmongoc::apm_set_server_changed_cb.create_instance(); + auto set_server_opening = libmongoc::apm_set_server_opening_cb.create_instance(); + auto set_topology_closed = libmongoc::apm_set_topology_closed_cb.create_instance(); + auto set_topology_description_changed = libmongoc::apm_set_topology_changed_cb.create_instance(); + auto set_topology_opening = libmongoc::apm_set_topology_opening_cb.create_instance(); + auto set_server_heartbeat_started = libmongoc::apm_set_server_heartbeat_started_cb.create_instance(); + auto set_server_heartbeat_failed = libmongoc::apm_set_server_heartbeat_failed_cb.create_instance(); + auto set_server_heartbeat_succeeded = libmongoc::apm_set_server_heartbeat_succeeded_cb.create_instance(); + + callbacks_destroy->interpose([&](mongoc_apm_callbacks_t* ptr) { + if (ptr) { + REQUIRE(ptr == callbacks_id); + } + }); + + callbacks_new->interpose([&]() -> mongoc_apm_callbacks_t* { return callbacks_id; }).forever(); + + set_apm_callbacks->interpose( + [&](mongoc_client_t* client, mongoc_apm_callbacks_t* callbacks, void* context) -> bool { + CHECK(client == mocks.client_id); + CHECK(callbacks == callbacks_id); + CHECK(context != nullptr); + return true; + }); + + // Each `set_*` increments the appropriate counter in `counters` (by index) upon invocation. + { + std::size_t idx = 0; + + set_command_started->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_command_failed->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_command_succeeded->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_server_closed->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_server_description_changed->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_server_opening->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_topology_closed->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_topology_description_changed->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_topology_opening->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_server_heartbeat_started->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_server_heartbeat_failed->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + set_server_heartbeat_succeeded->interpose(callback_mock_fn_type{callbacks_id, &counters[idx++]}); + } + + auto const idx = GENERATE(range(std::size_t{0}, N)); + CAPTURE(idx); + + setters[idx](); + + { + client::options opts; + CHECK_NOTHROW(opts.apm_opts(std::move(callbacks))); + (void)mocks.make(std::move(opts)); + } + + // Only one APM callback (by index) should have been invoked. + for (std::size_t i = 0u; i < N; ++i) { + CHECKED_IF(i == idx) { + CHECK(counters[i] == 1); + } + + else { + CHECK(counters[i] == 0); + } + } +} + +TEST_CASE("server_api_opts", "[mongocxx][v1][client]") { + client_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_set_server_api.create_instance(); + + server_api_destroy + ->interpose([&](mongoc_server_api_t* ptr) { + if (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_t* ptr, mongoc_server_api_t const* api, bson_error_t* error) -> bool { + CHECK(ptr == mocks.client_id); + CHECK(api == server_api_id); + CHECK(error != nullptr); + ++set_counter; + return true; + }); + + v1::server_api api{v1::server_api::version::k_version_1}; + + SECTION("strict") { + 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); + } + + SECTION("deprecation_errors") { + auto const input = GENERATE(false, true); + + CHECK_NOTHROW(api.deprecation_errors(input)); + + int counter = 0; + + auto deprecation_errors = libmongoc::server_api_deprecation_errors.create_instance(); + deprecation_errors->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); + } +} + +TEST_CASE("uri", "[mongocxx][v1][client]") { + identity_type uri1_identity; + identity_type uri2_identity; + identity_type client_identity; + + auto const uri1_id = reinterpret_cast(&uri1_identity); + auto const uri2_id = reinterpret_cast(&uri2_identity); + auto const client_id = reinterpret_cast(&client_identity); + + auto uri_destroy = libmongoc::uri_destroy.create_instance(); + auto uri_copy = libmongoc::uri_copy.create_instance(); + auto uri_get_tls = libmongoc::uri_get_tls.create_instance(); + auto client_destroy = libmongoc::client_destroy.create_instance(); + auto client_new_from_uri_with_error = libmongoc::client_new_from_uri_with_error.create_instance(); + auto get_uri = libmongoc::client_get_uri.create_instance(); + + int uri_destroy_count = 0; + uri_destroy + ->interpose([&](mongoc_uri_t* ptr) { + if (ptr) { + if (ptr != uri1_id && ptr != uri2_id) { + FAIL_CHECK("unexpected mongoc_uri_t"); + } + + ++uri_destroy_count; + } + }) + .forever(); + + int uri_copy_count = 0; + uri_copy + ->interpose([&](mongoc_uri_t const* ptr) -> mongoc_uri_t* { + CHECK(ptr == uri1_id); + ++uri_copy_count; + return uri2_id; + }) + .forever(); + + int uri_get_tls_count = 0; + uri_get_tls + ->interpose([&](mongoc_uri_t const* ptr) -> bool { + CHECK(ptr == uri1_id); + ++uri_get_tls_count; + return false; + }) + .forever(); + + client_destroy + ->interpose([&](mongoc_client_t* ptr) { + if (ptr) { + CHECK(ptr == client_id); + } + }) + .forever(); + + int new_count = 0; + client_new_from_uri_with_error + ->interpose([&](mongoc_uri_t const* uri, bson_error_t* error) -> mongoc_client_t* { + CHECK(uri == uri1_id); + CHECK(error != nullptr); + ++new_count; + return client_id; + }) + .forever(); + + int get_uri_count = 0; + get_uri->interpose([&](mongoc_client_t const* ptr) -> mongoc_uri_t const* { + CHECK(ptr == client_id); + ++get_uri_count; + return uri1_id; + }); + + { + v1::client const client{v1::uri::internal::make(uri1_id)}; + + CHECK(uri_destroy_count == 1); + CHECK(uri_copy_count == 0); + CHECK(get_uri_count == 0); + CHECK(uri_get_tls_count == 1); + + { + auto const uri = client.uri(); + + CHECK(uri_destroy_count == 1); + CHECK(uri_copy_count == 1); + CHECK(get_uri_count == 1); + + CHECK(v1::uri::internal::as_mongoc(uri) == uri2_id); + } + + CHECK(uri_destroy_count == 2); + CHECK(uri_copy_count == 1); + CHECK(get_uri_count == 1); + CHECK(uri_get_tls_count == 1); + } + + CHECK(new_count == 1); +} + +TEST_CASE("database", "[mongocxx][v1][client]") { + // TODO: v1::database (CXX-3237) +} + +TEST_CASE("list_databases", "[mongocxx][v1][client]") { + client_mocks_type mocks; + + identity_type cursor_identity; + auto const cursor_id = reinterpret_cast(&cursor_identity); + + auto cursor_destroy = libmongoc::cursor_destroy.create_instance(); + auto find_databases_with_opts = libmongoc::client_find_databases_with_opts.create_instance(); + + cursor_destroy->interpose([&](mongoc_cursor_t* ptr) -> void { + if (ptr) { + CHECK(ptr == cursor_id); + } + }); + + auto client = mocks.make(); + + SECTION("basic") { + auto const names = GENERATE( + values>({ + {}, + {"x"}, + {"a", "b", "c"}, + })); + + SECTION("no options") { + int counter = 0; + find_databases_with_opts->interpose([&](mongoc_client_t* ptr, bson_t const* opts) -> mongoc_cursor_t* { + CHECK(ptr == mocks.client_id); + CHECK(opts == nullptr); + ++counter; + return cursor_id; + }); + + { + auto const cursor = client.list_databases(); + CHECK(v1::cursor::internal::as_mongoc(cursor) == cursor_id); + } + + CHECK(counter == 1); + } + + SECTION("with options") { + scoped_bson const owner{R"({"x": 1})"}; + + int counter = 0; + find_databases_with_opts->interpose([&](mongoc_client_t* ptr, bson_t const* opts) -> mongoc_cursor_t* { + CHECK(ptr == mocks.client_id); + REQUIRE(opts != nullptr); + CHECK(scoped_bson_view{opts}.data() == owner.data()); + ++counter; + return cursor_id; + }); + + { + auto const cursor = client.list_databases(owner.view()); + CHECK(v1::cursor::internal::as_mongoc(cursor) == cursor_id); + } + + CHECK(counter == 1); + } + } + + SECTION("session") { + // TODO: v1::client_session (CXX-3237) + } +} + +TEST_CASE("list_database_names", "[mongocxx][v1][client]") { + client_mocks_type mocks; + + identity_type cursor_identity; + auto const cursor_id = reinterpret_cast(&cursor_identity); + + auto cursor_destroy = libmongoc::cursor_destroy.create_instance(); + auto get_database_names_with_opts = libmongoc::client_get_database_names_with_opts.create_instance(); + + cursor_destroy->interpose([&](mongoc_cursor_t* ptr) -> void { + if (ptr) { + CHECK(ptr == cursor_id); + } + }); + + auto client = mocks.make(); + + SECTION("basic") { + auto const names = GENERATE( + values>({ + {}, + {"x"}, + {"a", "b", "c"}, + })); + + struct names_deleter { + void operator()(char** ptr) const noexcept { + bson_strfreev(ptr); + } + }; + + SECTION("no filter") { + int counter = 0; + get_database_names_with_opts->interpose( + [&](mongoc_client_t* ptr, bson_t const* opts, bson_error_t* error) -> char** { + CHECK(ptr == mocks.client_id); + CHECK(opts == nullptr); + CHECK(error != nullptr); + + ++counter; + + if (names.empty()) { + return static_cast(bson_malloc0(sizeof(char*))); // Null terminator. + } + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays): bson compatibility. + auto owner = std::unique_ptr( + static_cast(bson_malloc0((names.size() + 1u) * sizeof(char*)))); + + for (std::size_t idx = 0u; idx < names.size(); ++idx) { + auto const& name = names[idx]; + owner[idx] = bson_strndup(name.c_str(), name.size()); + } + + return owner.release(); + }); + + auto const result = client.list_database_names(); + + CHECK(result == names); + CHECK(counter == 1); + } + + SECTION("with filter") { + scoped_bson const filter{R"({"x": 1})"}; + scoped_bson const options{BCON_NEW("filter", BCON_DOCUMENT(filter.bson()))}; + + int counter = 0; + get_database_names_with_opts->interpose( + [&](mongoc_client_t* ptr, bson_t const* opts, bson_error_t* error) -> char** { + CHECK(ptr == mocks.client_id); + REQUIRE(opts != nullptr); + CHECK(error != nullptr); + + CHECK(scoped_bson_view{opts}.view() == options.view()); + + ++counter; + + if (names.empty()) { + return static_cast(bson_malloc0(sizeof(char*))); // Null terminator. + } + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays): bson compatibility. + auto owner = std::unique_ptr( + static_cast(bson_malloc0((names.size() + 1u) * sizeof(char*)))); + + for (std::size_t idx = 0u; idx < names.size(); ++idx) { + auto const& name = names[idx]; + owner[idx] = bson_strndup(name.c_str(), name.size()); + } + + return owner.release(); + }); + + auto const result = client.list_database_names(filter.view()); + + CHECK(result == names); + CHECK(counter == 1); + } + } + + SECTION("session") { + // TODO: v1::client_session (CXX-3237) + } +} + +TEST_CASE("start_session", "[mongocxx][v1][client]") { + // TODO: v1::client_session (CXX-3237) +} + +namespace { + +template +void test_watch_opts_string( + client_mocks_type& mocks, + Mock& mock, + v1::change_stream::options& (v1::change_stream::options::*mem_fn)(std::string), + char const* field, + mongoc_change_stream_t* stream_id) { + auto const input = GENERATE("a", "b", "c"); + + int count = 0; + mock->interpose([&](mongoc_client_t* ptr, bson_t const* pipeline, bson_t const* opts) -> mongoc_change_stream_t* { + CHECK(ptr == mocks.client_id); + CHECK(scoped_bson_view{pipeline}.view().empty()); + REQUIRE(opts != nullptr); + + ++count; + + auto const e = scoped_bson_view{opts}.view()[field]; + REQUIRE(e); + CHECK(e.type_id() == bsoncxx::v1::types::id::k_string); + CHECK(e.get_string().value == input); + + return stream_id; + }); + + { + auto client = mocks.make(); + + v1::change_stream::options stream_opts; + CHECK_NOTHROW((stream_opts.*mem_fn)(input)); + + auto const stream = client.watch(std::move(stream_opts)); + CHECK(v1::change_stream::internal::as_mongoc(stream) == stream_id); + } + + CHECK(count == 1); +} + +template +void test_watch_opts_doc( + client_mocks_type& mocks, + Mock& mock, + v1::change_stream::options& (v1::change_stream::options::*mem_fn)(bsoncxx::v1::document::value), + char const* field, + mongoc_change_stream_t* stream_id) { + auto const input = GENERATE(as(), R"({})", R"({"x": 1})"); + + int count = 0; + mock->interpose([&](mongoc_client_t* ptr, bson_t const* pipeline, bson_t const* opts) -> mongoc_change_stream_t* { + CHECK(ptr == mocks.client_id); + CHECK(scoped_bson_view{pipeline}.view().empty()); + REQUIRE(opts != nullptr); + + ++count; + + auto const e = scoped_bson_view{opts}.view()[field]; + REQUIRE(e); + CHECK(e.type_id() == bsoncxx::v1::types::id::k_document); + CHECK(e.get_document().value == input.view()); + + return stream_id; + }); + + { + auto client = mocks.make(); + + v1::change_stream::options stream_opts; + CHECK_NOTHROW((stream_opts.*mem_fn)(input.value())); + + auto const stream = client.watch(std::move(stream_opts)); + CHECK(v1::change_stream::internal::as_mongoc(stream) == stream_id); + } + + CHECK(count == 1); +} + +template +void test_watch_opts_int32( + client_mocks_type& mocks, + Mock& mock, + v1::change_stream::options& (v1::change_stream::options::*mem_fn)(std::int32_t), + char const* field, + mongoc_change_stream_t* stream_id) { + auto const input = GENERATE(values({INT32_MIN, -1, 0, 1, INT32_MAX})); + + int count = 0; + mock->interpose([&](mongoc_client_t* ptr, bson_t const* pipeline, bson_t const* opts) -> mongoc_change_stream_t* { + CHECK(ptr == mocks.client_id); + CHECK(scoped_bson_view{pipeline}.view().empty()); + REQUIRE(opts != nullptr); + + ++count; + + auto const e = scoped_bson_view{opts}.view()[field]; + REQUIRE(e); + CHECK(e.type_id() == bsoncxx::v1::types::id::k_int32); + CHECK(e.get_int32().value == input); + + return stream_id; + }); + + { + auto client = mocks.make(); + + v1::change_stream::options stream_opts; + CHECK_NOTHROW((stream_opts.*mem_fn)(input)); + + auto const stream = client.watch(std::move(stream_opts)); + CHECK(v1::change_stream::internal::as_mongoc(stream) == stream_id); + } + + CHECK(count == 1); +} + +template +void test_watch_opts_type( + client_mocks_type& mocks, + Mock& mock, + v1::change_stream::options& (v1::change_stream::options::*mem_fn)(bsoncxx::v1::types::value), + char const* field, + mongoc_change_stream_t* stream_id) { + auto const input = GENERATE(as(), bsoncxx::v1::types::b_null{}, 1, 2.0, "three"); + + int count = 0; + mock->interpose([&](mongoc_client_t* ptr, bson_t const* pipeline, bson_t const* opts) -> mongoc_change_stream_t* { + CHECK(ptr == mocks.client_id); + CHECK(scoped_bson_view{pipeline}.view().empty()); + REQUIRE(opts != nullptr); + + ++count; + + auto const e = scoped_bson_view{opts}.view()[field]; + REQUIRE(e); + CHECK(e.type_id() == input.type_id()); + CHECK(e.type_view() == input); + + return stream_id; + }); + + { + auto client = mocks.make(); + + v1::change_stream::options stream_opts; + CHECK_NOTHROW((stream_opts.*mem_fn)(bsoncxx::v1::types::value{input})); + + auto const stream = client.watch(std::move(stream_opts)); + CHECK(v1::change_stream::internal::as_mongoc(stream) == stream_id); + } + + CHECK(count == 1); +} + +template +void test_watch_opts_timestamp( + client_mocks_type& mocks, + Mock& mock, + v1::change_stream::options& (v1::change_stream::options::*mem_fn)(bsoncxx::v1::types::b_timestamp), + char const* field, + mongoc_change_stream_t* stream_id) { + auto const timestamp = GENERATE(values({0u, 1u, UINT32_MAX - 1u, 0, 1, UINT32_MAX})); + auto const increment = GENERATE(values({0u, 1u, UINT32_MAX - 1u, 0, 1, UINT32_MAX})); + auto const input = bsoncxx::v1::types::b_timestamp{timestamp, increment}; + + int count = 0; + mock->interpose([&](mongoc_client_t* ptr, bson_t const* pipeline, bson_t const* opts) -> mongoc_change_stream_t* { + CHECK(ptr == mocks.client_id); + CHECK(scoped_bson_view{pipeline}.view().empty()); + REQUIRE(opts != nullptr); + + ++count; + + auto const e = scoped_bson_view{opts}.view()[field]; + REQUIRE(e); + CHECK(e.type_id() == bsoncxx::v1::types::id::k_timestamp); + CHECK(e.get_timestamp() == input); + + return stream_id; + }); + + { + auto client = mocks.make(); + + v1::change_stream::options stream_opts; + CHECK_NOTHROW((stream_opts.*mem_fn)(input)); + + auto const stream = client.watch(std::move(stream_opts)); + CHECK(v1::change_stream::internal::as_mongoc(stream) == stream_id); + } + + CHECK(count == 1); +} + +template +void test_watch_opts_ms( + client_mocks_type& mocks, + Mock& mock, + v1::change_stream::options& (v1::change_stream::options::*mem_fn)(std::chrono::milliseconds), + char const* field, + mongoc_change_stream_t* stream_id) { + auto const value = GENERATE(values({INT64_MIN, -1, 0, 1, INT64_MAX})); + auto const input = std::chrono::milliseconds{value}; + + int count = 0; + mock->interpose([&](mongoc_client_t* ptr, bson_t const* pipeline, bson_t const* opts) -> mongoc_change_stream_t* { + CHECK(ptr == mocks.client_id); + CHECK(scoped_bson_view{pipeline}.view().empty()); + REQUIRE(opts != nullptr); + + ++count; + + auto const e = scoped_bson_view{opts}.view()[field]; + REQUIRE(e); + CHECK(e.type_id() == bsoncxx::v1::types::id::k_int64); + CHECK(e.get_int64().value == value); + + return stream_id; + }); + + { + auto client = mocks.make(); + + v1::change_stream::options stream_opts; + CHECK_NOTHROW((stream_opts.*mem_fn)(input)); + + auto const stream = client.watch(std::move(stream_opts)); + CHECK(v1::change_stream::internal::as_mongoc(stream) == stream_id); + } + + CHECK(count == 1); +} + +} // namespace + +TEST_CASE("watch", "[mongocxx][v1][client]") { + client_mocks_type mocks; + + identity_type stream_identity; + auto const stream_id = reinterpret_cast(&stream_identity); + + auto change_stream_destroy = libmongoc::change_stream_destroy.create_instance(); + auto watch = libmongoc::client_watch.create_instance(); + + change_stream_destroy + ->interpose([&](mongoc_change_stream_t* ptr) { + if (ptr) { + CHECK(ptr == stream_id); + } + }) + .forever(); + + v1::change_stream::options stream_opts; + + SECTION("no options") { + int count = 0; + watch->interpose( + [&](mongoc_client_t* ptr, bson_t const* pipeline, bson_t const* opts) -> mongoc_change_stream_t* { + CHECK(ptr == mocks.client_id); + CHECK(scoped_bson_view{pipeline}.view().empty()); + CHECK(opts == nullptr); + + ++count; + + return stream_id; + }); + + { + auto client = mocks.make(); + auto const stream = client.watch(); + CHECK(v1::change_stream::internal::as_mongoc(stream) == stream_id); + } + + CHECK(count == 1); + } + + SECTION("with options") { + SECTION("full_document") { + test_watch_opts_string(mocks, watch, &v1::change_stream::options::full_document, "fullDocument", stream_id); + } + + SECTION("full_document_before_change") { + test_watch_opts_string( + mocks, + watch, + &v1::change_stream::options::full_document_before_change, + "fullDocumentBeforeChange", + stream_id); + } + + SECTION("resume_after") { + test_watch_opts_doc(mocks, watch, &v1::change_stream::options::resume_after, "resumeAfter", stream_id); + } + + SECTION("start_after") { + test_watch_opts_doc(mocks, watch, &v1::change_stream::options::start_after, "startAfter", stream_id); + } + + SECTION("batch_size") { + test_watch_opts_int32(mocks, watch, &v1::change_stream::options::batch_size, "batchSize", stream_id); + } + + SECTION("collation") { + test_watch_opts_doc(mocks, watch, &v1::change_stream::options::collation, "collation", stream_id); + } + + SECTION("comment") { + test_watch_opts_type(mocks, watch, &v1::change_stream::options::comment, "comment", stream_id); + } + + SECTION("start_at_operation_time") { + test_watch_opts_timestamp( + mocks, watch, &v1::change_stream::options::start_at_operation_time, "startAtOperationTime", stream_id); + } + + SECTION("max_await_time") { + test_watch_opts_ms(mocks, watch, &v1::change_stream::options::max_await_time, "maxAwaitTimeMS", stream_id); + } + } + + SECTION("with pipeline") { + v1::pipeline pipeline; + pipeline.append_stage(scoped_bson{R"({"x": 1})"}.value()); + auto const data = pipeline.view_array().data(); + + int count = 0; + + SECTION("no options") { + watch->interpose( + [&](mongoc_client_t* ptr, bson_t const* pipeline, bson_t const* opts) -> mongoc_change_stream_t* { + CHECK(ptr == mocks.client_id); + CHECK(scoped_bson_view{pipeline}.data() == data); + CHECK(opts == nullptr); + + ++count; + + return stream_id; + }); + + auto client = mocks.make(); + auto const stream = client.watch(pipeline); + CHECK(v1::change_stream::internal::as_mongoc(stream) == stream_id); + } + + SECTION("with options") { + watch->interpose( + [&](mongoc_client_t* ptr, bson_t const* pipeline, bson_t const* opts) -> mongoc_change_stream_t* { + CHECK(ptr == mocks.client_id); + CHECK(scoped_bson_view{pipeline}.data() == data); + REQUIRE(opts != nullptr); + + CHECK(scoped_bson_view{opts}.view().empty()); + + ++count; + + return stream_id; + }); + + auto client = mocks.make(); + auto const stream = client.watch(pipeline, v1::change_stream::options{}); + CHECK(v1::change_stream::internal::as_mongoc(stream) == stream_id); + } + + CHECK(count == 1); + } + + SECTION("with session") { + // TODO: v1::client_session (CXX-3237) + } +} + +TEST_CASE("reset", "[mongocxx][v1][client]") { + client_mocks_type mocks; + + auto reset = libmongoc::client_reset.create_instance(); + reset->interpose([&](mongoc_client_t* ptr) -> void { CHECK(ptr == mocks.client_id); }); + + (void)mocks.make().reset(); +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/test/v_noabi/client.cpp b/src/mongocxx/test/v_noabi/client.cpp index 20bc8e860e..ca35963522 100644 --- a/src/mongocxx/test/v_noabi/client.cpp +++ b/src/mongocxx/test/v_noabi/client.cpp @@ -413,22 +413,21 @@ TEST_CASE("A client can be constructed with SSL options", "[client]") { tls_opts.crl_file(crl_file); tls_opts.allow_invalid_certificates(allow_invalid_certificates); - ::mongoc_ssl_opt_t interposed = {}; - client_set_ssl_opts->interpose([&](::mongoc_client_t*, ::mongoc_ssl_opt_t const* opts) { set_tls_opts_called = true; - interposed = *opts; + + REQUIRE(opts != nullptr); + + CHECK_THAT(opts->pem_file, Catch::Matchers::Equals(pem_file)); + CHECK_THAT(opts->pem_pwd, Catch::Matchers::Equals(pem_password)); + CHECK_THAT(opts->ca_file, Catch::Matchers::Equals(ca_file)); + CHECK_THAT(opts->ca_dir, Catch::Matchers::Equals(ca_dir)); + CHECK_THAT(opts->crl_file, Catch::Matchers::Equals(crl_file)); + CHECK(opts->weak_cert_validation == allow_invalid_certificates); }); client c{uri{"mongodb://mongodb.example.com:9999/?ssl=true"}, options::client().tls_opts(tls_opts)}; - - REQUIRE(set_tls_opts_called); - REQUIRE(interposed.pem_file == pem_file); - REQUIRE(interposed.pem_pwd == pem_password); - REQUIRE(interposed.ca_file == ca_file); - REQUIRE(interposed.ca_dir == ca_dir); - REQUIRE(interposed.crl_file == crl_file); - REQUIRE(interposed.weak_cert_validation == allow_invalid_certificates); + CHECK(set_tls_opts_called); } #endif diff --git a/src/mongocxx/test/v_noabi/pool.cpp b/src/mongocxx/test/v_noabi/pool.cpp index 97dfffc849..9fe1c427cb 100644 --- a/src/mongocxx/test/v_noabi/pool.cpp +++ b/src/mongocxx/test/v_noabi/pool.cpp @@ -86,22 +86,22 @@ TEST_CASE( tls_opts.crl_file(crl_file); tls_opts.allow_invalid_certificates(allow_invalid_certificates); - ::mongoc_ssl_opt_t interposed = {}; - - client_pool_set_ssl_opts->visit([&](::mongoc_client_pool_t*, ::mongoc_ssl_opt_t const* opts) { + client_pool_set_ssl_opts->interpose([&](::mongoc_client_pool_t*, ::mongoc_ssl_opt_t const* opts) { set_tls_opts_called = true; - interposed = *opts; + + REQUIRE(opts != nullptr); + + CHECK_THAT(opts->pem_file, Catch::Matchers::Equals(pem_file)); + CHECK_THAT(opts->pem_pwd, Catch::Matchers::Equals(pem_password)); + CHECK_THAT(opts->ca_file, Catch::Matchers::Equals(ca_file)); + CHECK_THAT(opts->ca_dir, Catch::Matchers::Equals(ca_dir)); + CHECK_THAT(opts->crl_file, Catch::Matchers::Equals(crl_file)); + CHECK(opts->weak_cert_validation == allow_invalid_certificates); }); pool p{uri{"mongodb://mongodb.example.com:9999/?tls=true"}, options::client().tls_opts(tls_opts)}; - REQUIRE(set_tls_opts_called); - REQUIRE(interposed.pem_file == pem_file); - REQUIRE(interposed.pem_pwd == pem_password); - REQUIRE(interposed.ca_file == ca_file); - REQUIRE(interposed.ca_dir == ca_dir); - REQUIRE(interposed.crl_file == crl_file); - REQUIRE(interposed.weak_cert_validation == allow_invalid_certificates); + CHECK(set_tls_opts_called); } #endif