From a9021308617c1a8596d68ff17cdcd027963f8176 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Fri, 14 Nov 2025 14:29:13 -0600 Subject: [PATCH] v1::write_concern (CXX-3237, CXX-3238) --- .../include/mongocxx/v1/write_concern.hpp | 5 + .../v_noabi/mongocxx/write_concern-fwd.hpp | 7 +- .../v_noabi/mongocxx/write_concern.hpp | 141 ++++-- src/mongocxx/lib/mongocxx/private/mongoc.hh | 4 +- .../lib/mongocxx/v1/write_concern.cpp | 231 ++++++++- src/mongocxx/lib/mongocxx/v1/write_concern.hh | 35 ++ .../lib/mongocxx/v_noabi/mongocxx/client.cpp | 10 +- .../mongocxx/v_noabi/mongocxx/collection.cpp | 11 +- .../mongocxx/v_noabi/mongocxx/database.cpp | 11 +- .../v_noabi/mongocxx/options/transaction.hh | 21 +- .../lib/mongocxx/v_noabi/mongocxx/uri.cpp | 7 +- .../v_noabi/mongocxx/write_concern.cpp | 208 +++------ .../v_noabi/mongocxx/write_concern.hh | 18 +- src/mongocxx/test/CMakeLists.txt | 2 +- src/mongocxx/test/private/write_concern.cpp | 150 ------ src/mongocxx/test/v1/write_concern.cpp | 437 ++++++++++++++++++ src/mongocxx/test/v1/write_concern.hh | 30 ++ 17 files changed, 931 insertions(+), 397 deletions(-) create mode 100644 src/mongocxx/lib/mongocxx/v1/write_concern.hh delete mode 100644 src/mongocxx/test/private/write_concern.cpp create mode 100644 src/mongocxx/test/v1/write_concern.cpp create mode 100644 src/mongocxx/test/v1/write_concern.hh diff --git a/src/mongocxx/include/mongocxx/v1/write_concern.hpp b/src/mongocxx/include/mongocxx/v1/write_concern.hpp index 8cc990755d..db54573938 100644 --- a/src/mongocxx/include/mongocxx/v1/write_concern.hpp +++ b/src/mongocxx/include/mongocxx/v1/write_concern.hpp @@ -248,6 +248,11 @@ class write_concern { } /// @} /// + + class internal; + + private: + /* explicit(false) */ write_concern(void* impl); }; } // namespace v1 diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/write_concern-fwd.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/write_concern-fwd.hpp index 360ae0b35c..b0837867e6 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/write_concern-fwd.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/write_concern-fwd.hpp @@ -14,6 +14,8 @@ #pragma once +#include + #include namespace mongocxx { @@ -26,7 +28,7 @@ class write_concern; namespace mongocxx { -using ::mongocxx::v_noabi::write_concern; +using v_noabi::write_concern; } // namespace mongocxx @@ -36,3 +38,6 @@ using ::mongocxx::v_noabi::write_concern; /// @file /// Declares @ref mongocxx::v_noabi::write_concern. /// +/// @par Includes +/// - @ref mongocxx/v1/write_concern-fwd.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/write_concern.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/write_concern.hpp index 6d53f9bb25..a255238bc4 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/write_concern.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/write_concern.hpp @@ -14,25 +14,31 @@ #pragma once +#include // IWYU pragma: export + +// + +#include + #include #include -#include +#include // IWYU pragma: keep: backward compatibility, to be removed. #include // IWYU pragma: keep: backward compatibility, to be removed. #include +#include -#include -#include -#include -#include -#include -#include -#include // IWYU pragma: export +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include // IWYU pragma: keep: backward compatibility, to be removed. #include #include #include -#include +#include // IWYU pragma: keep: backward compatibility, to be removed. #include @@ -46,6 +52,9 @@ namespace v_noabi { /// - [Write Concern (MongoDB Manual)](https://www.mongodb.com/docs/manual/core/write-concern/) /// class write_concern { + private: + v1::write_concern _wc; + public: /// /// A class to represent the write concern level for write operations. @@ -65,32 +74,31 @@ class write_concern { /// /// Constructs a new write_concern. /// - MONGOCXX_ABI_EXPORT_CDECL() write_concern(); - - /// - /// Copy constructs a write_concern. - /// - MONGOCXX_ABI_EXPORT_CDECL() write_concern(write_concern const&); + write_concern() = default; /// - /// Copy assigns a write_concern. + /// Construct with the @ref mongocxx::v1 equivalent. /// - MONGOCXX_ABI_EXPORT_CDECL(write_concern&) operator=(write_concern const&); + /* explicit(false) */ write_concern(v1::write_concern rc) : _wc{std::move(rc)} {} /// - /// Move constructs a write_concern. + /// Convert to the @ref mongocxx::v1 equivalent. /// - MONGOCXX_ABI_EXPORT_CDECL() write_concern(write_concern&&) noexcept; - + /// @par Postconditions: + /// - `other` is in an assign-or-destroy-only state. /// - /// Move assigns a write_concern. + /// @warning Invalidates all associated iterators and views. /// - MONGOCXX_ABI_EXPORT_CDECL(write_concern&) operator=(write_concern&&) noexcept; + explicit operator v1::write_concern() && { + return std::move(_wc); + } /// - /// Destroys a write_concern. + /// Convert to the @ref mongocxx::v1 equivalent. /// - MONGOCXX_ABI_EXPORT_CDECL() ~write_concern(); + explicit operator v1::write_concern() const& { + return _wc; + } /// /// Sets the journal parameter for this write concern. @@ -100,7 +108,9 @@ class write_concern { /// before reporting a write operations was successful. This ensures that data is not lost if /// the mongod instance shuts down unexpectedly. /// - MONGOCXX_ABI_EXPORT_CDECL(void) journal(bool journal); + void journal(bool journal) { + _wc.journal(journal); + } /// /// Sets the number of nodes that are required to acknowledge the write before the operation is @@ -155,7 +165,10 @@ class write_concern { /// /// @throws mongocxx::v_noabi::logic_error for an invalid timeout value. /// - MONGOCXX_ABI_EXPORT_CDECL(void) majority(std::chrono::milliseconds timeout); + void majority(std::chrono::milliseconds timeout) { + this->timeout(timeout); + this->acknowledge_level(level::k_majority); + } /// /// Sets the name representing the server-side getLastErrorMode entry containing the list of @@ -166,7 +179,9 @@ class write_concern { /// @param tag /// The string representing on of the "getLastErrorModes" in the replica set configuration. /// - MONGOCXX_ABI_EXPORT_CDECL(void) tag(bsoncxx::v_noabi::stdx::string_view tag); + void tag(bsoncxx::v_noabi::stdx::string_view tag) { + _wc.tag(tag); + } /// /// Sets an upper bound on the time a write concern can take to be satisfied. If the write @@ -185,7 +200,9 @@ class write_concern { /// /// @return @c true if journal is required, @c false if not. /// - MONGOCXX_ABI_EXPORT_CDECL(bool) journal() const; + bool journal() const { + return _wc.journal().value_or(false); + } /// /// Gets the current number of nodes that this write_concern requires operations to reach. @@ -198,7 +215,9 @@ class write_concern { /// /// @return The number of required nodes. /// - MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional) nodes() const; + bsoncxx::v_noabi::stdx::optional nodes() const { + return _wc.nodes(); + } /// /// Gets the current acknowledgment level. @@ -208,35 +227,49 @@ class write_concern { /// /// @return The acknowledgment level. /// - MONGOCXX_ABI_EXPORT_CDECL(level) acknowledge_level() const; + level acknowledge_level() const { + return static_cast(_wc.acknowledge_level()); + } /// /// Gets the current getLastErrorMode that is required by this write_concern. /// /// @return The current getLastErrorMode. /// - MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional) tag() const; + bsoncxx::v_noabi::stdx::optional tag() const { + bsoncxx::v_noabi::stdx::optional ret; + if (auto const opt = _wc.tag()) { + ret.emplace(*opt); + } + return ret; + } /// /// Gets whether the majority of nodes is currently required by this write_concern. /// /// @return The current majority setting. /// - MONGOCXX_ABI_EXPORT_CDECL(bool) majority() const; + bool majority() const { + return _wc.acknowledge_level() == v1::write_concern::level::k_majority; + } /// /// Gets the current timeout for this write_concern. /// /// @return Current timeout in milliseconds. /// - MONGOCXX_ABI_EXPORT_CDECL(std::chrono::milliseconds) timeout() const; + std::chrono::milliseconds timeout() const { + return _wc.timeout(); + } /// /// Gets whether this write_concern requires an acknowledged write. /// /// @return Whether this write concern requires an acknowledged write. /// - MONGOCXX_ABI_EXPORT_CDECL(bool) is_acknowledged() const; + bool is_acknowledged() const { + return _wc.is_acknowledged(); + } /// /// Gets the document form of this write_concern. @@ -244,7 +277,9 @@ class write_concern { /// @return /// Document representation of this write_concern. /// - MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::document::value) to_document() const; + MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::document::value) to_document() const { + return bsoncxx::v_noabi::from_v1(_wc.to_document()); + } /// /// @relates mongocxx::v_noabi::write_concern @@ -252,25 +287,30 @@ class write_concern { /// Compares two write_concern objects for (in)-equality. /// /// @{ - friend MONGOCXX_ABI_EXPORT_CDECL(bool) operator==(write_concern const&, write_concern const&); - friend MONGOCXX_ABI_EXPORT_CDECL(bool) operator!=(write_concern const&, write_concern const&); + friend MONGOCXX_ABI_EXPORT_CDECL(bool) operator==(write_concern const& lhs, write_concern const& rhs); + + friend bool operator!=(write_concern const& lhs, write_concern const& rhs) { + return !(lhs == rhs); + } /// @} /// - private: - friend ::mongocxx::v_noabi::bulk_write; - friend ::mongocxx::v_noabi::client; - friend ::mongocxx::v_noabi::collection; - friend ::mongocxx::v_noabi::database; - friend ::mongocxx::v_noabi::options::transaction; - friend ::mongocxx::v_noabi::uri; - - class impl; + class internal; +}; - write_concern(std::unique_ptr&& implementation); +/// +/// Convert to the @ref mongocxx::v_noabi equivalent of `v`. +/// +inline v_noabi::write_concern from_v1(v1::write_concern v) { + return {std::move(v)}; +} - std::unique_ptr _impl; -}; +/// +/// Convert to the @ref mongocxx::v1 equivalent of `v`. +/// +inline v1::write_concern to_v1(v_noabi::write_concern v) { + return v1::write_concern{std::move(v)}; +} } // namespace v_noabi } // namespace mongocxx @@ -281,3 +321,6 @@ class write_concern { /// @file /// Provides @ref mongocxx::v_noabi::write_concern. /// +/// @par Includes +/// - @ref mongocxx/v1/write_concern-fwd.hpp +/// diff --git a/src/mongocxx/lib/mongocxx/private/mongoc.hh b/src/mongocxx/lib/mongocxx/private/mongoc.hh index aa5b3ec401..d0855e5bd3 100644 --- a/src/mongocxx/lib/mongocxx/private/mongoc.hh +++ b/src/mongocxx/lib/mongocxx/private/mongoc.hh @@ -396,6 +396,7 @@ BSONCXX_PRIVATE_WARNINGS_POP(); X(write_concern_get_wmajority) \ X(write_concern_get_wtag) \ X(write_concern_get_wtimeout) \ + X(write_concern_get_wtimeout_int64) \ X(write_concern_is_acknowledged) \ X(write_concern_journal_is_set) \ X(write_concern_new) \ @@ -403,7 +404,8 @@ BSONCXX_PRIVATE_WARNINGS_POP(); X(write_concern_set_w) \ X(write_concern_set_wmajority) \ X(write_concern_set_wtag) \ - X(write_concern_set_wtimeout) + X(write_concern_set_wtimeout) \ + X(write_concern_set_wtimeout_int64) namespace mongocxx { namespace libmongoc { diff --git a/src/mongocxx/lib/mongocxx/v1/write_concern.cpp b/src/mongocxx/lib/mongocxx/v1/write_concern.cpp index dc67d7aef1..1009d29ce6 100644 --- a/src/mongocxx/lib/mongocxx/v1/write_concern.cpp +++ b/src/mongocxx/lib/mongocxx/v1/write_concern.cpp @@ -12,4 +12,233 @@ // 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 + +namespace mongocxx { +namespace v1 { + +namespace { + +mongoc_write_concern_t* to_mongoc(void* ptr) { + return static_cast(ptr); +} + +} // namespace + +write_concern::~write_concern() { + libmongoc::write_concern_destroy(to_mongoc(_impl)); +} + +write_concern::write_concern(write_concern&& other) noexcept : _impl{exchange(other._impl, nullptr)} {} + +write_concern& write_concern::operator=(write_concern&& other) noexcept { + if (this != &other) { + libmongoc::write_concern_destroy(to_mongoc(exchange(_impl, exchange(other._impl, nullptr)))); + } + return *this; +} + +write_concern::write_concern(write_concern const& other) + : _impl{libmongoc::write_concern_copy(to_mongoc(other._impl))} {} + +write_concern& write_concern::operator=(write_concern const& other) { + if (this != &other) { + libmongoc::write_concern_destroy( + to_mongoc(exchange(_impl, libmongoc::write_concern_copy(to_mongoc(other._impl))))); + } + return *this; +} + +write_concern::write_concern() : _impl{libmongoc::write_concern_new()} {} + +write_concern& write_concern::acknowledge_level(level v) { + switch (v) { + case level::k_default: + libmongoc::write_concern_set_w(to_mongoc(_impl), MONGOC_WRITE_CONCERN_W_DEFAULT); + break; + case level::k_majority: + libmongoc::write_concern_set_w(to_mongoc(_impl), MONGOC_WRITE_CONCERN_W_MAJORITY); + break; + case level::k_unacknowledged: + libmongoc::write_concern_set_w(to_mongoc(_impl), MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED); + break; + case level::k_acknowledged: + libmongoc::write_concern_set_w(to_mongoc(_impl), 1); // MONGOC_WRITE_CONCERN_W_ACKNOWLEDGED + break; + + default: + case level::k_tag: + case level::k_unknown: + // Precondition violation: undocumented but well-defined behavior. + libmongoc::write_concern_set_w(to_mongoc(_impl), MONGOC_WRITE_CONCERN_W_DEFAULT); + break; + } + + return *this; +} + +write_concern::level write_concern::acknowledge_level() const { + auto const w = libmongoc::write_concern_get_w(to_mongoc(_impl)); + + switch (w) { + case MONGOC_WRITE_CONCERN_W_DEFAULT: + return level::k_default; + case MONGOC_WRITE_CONCERN_W_MAJORITY: + return level::k_majority; + case MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED: + return level::k_unacknowledged; + case MONGOC_WRITE_CONCERN_W_TAG: + return level::k_tag; + default: + return w >= 1 ? level::k_acknowledged : level::k_unknown; + } +} + +write_concern& write_concern::timeout(std::chrono::milliseconds v) { + libmongoc::write_concern_set_wtimeout_int64(to_mongoc(_impl), v.count()); + return *this; +} + +std::chrono::milliseconds write_concern::timeout() const { + return std::chrono::milliseconds{libmongoc::write_concern_get_wtimeout_int64(to_mongoc(_impl))}; +} + +write_concern& write_concern::nodes(std::int32_t v) { + // Precondition violation: undocumented but well-defined behavior. + if (v < 0) { + libmongoc::write_concern_set_w(to_mongoc(_impl), MONGOC_WRITE_CONCERN_W_DEFAULT); + } else { + libmongoc::write_concern_set_w(to_mongoc(_impl), v); + } + return *this; +} + +bsoncxx::v1::stdx::optional write_concern::nodes() const { + bsoncxx::v1::stdx::optional ret; + + auto const w = libmongoc::write_concern_get_w(to_mongoc(_impl)); + if (w >= 0) { + ret.emplace(w); + } + + return ret; +} + +write_concern& write_concern::tag(bsoncxx::v1::stdx::string_view v) { + libmongoc::write_concern_set_wtag(to_mongoc(_impl), std::string{v}.c_str()); + return *this; +} + +bsoncxx::v1::stdx::optional write_concern::tag() const { + bsoncxx::v1::stdx::optional ret; + + if (auto const str = libmongoc::write_concern_get_wtag(to_mongoc(_impl))) { + ret.emplace(str); + } + + return ret; +} + +write_concern& write_concern::journal(bool j) { + libmongoc::write_concern_set_journal(to_mongoc(_impl), j); + return *this; +} + +bsoncxx::v1::stdx::optional write_concern::journal() const { + bsoncxx::v1::stdx::optional ret; + + if (libmongoc::write_concern_journal_is_set(to_mongoc(_impl))) { + ret.emplace(libmongoc::write_concern_get_journal(to_mongoc(_impl))); + } + + return ret; +} + +bool write_concern::is_acknowledged() const { + return libmongoc::write_concern_is_acknowledged(to_mongoc(_impl)); +} + +bsoncxx::v1::document::value write_concern::to_document() const { + scoped_bson doc; + + switch (this->acknowledge_level()) { + case level::k_default: + break; + + case level::k_majority: + doc += scoped_bson{BCON_NEW("w", BCON_UTF8("majority"))}; + break; + + case level::k_tag: + doc += scoped_bson{BCON_NEW("w", BCON_UTF8(libmongoc::write_concern_get_wtag(to_mongoc(_impl))))}; + break; + + case level::k_unacknowledged: + doc += scoped_bson{BCON_NEW("w", BCON_INT32(0))}; + break; + + case level::k_acknowledged: + doc += scoped_bson{BCON_NEW("w", BCON_INT32(*this->nodes()))}; + break; + + case level::k_unknown: + // Precondition violation: undocumented but well-defined behavior. + break; + } + + if (auto const opt = this->journal()) { + doc += scoped_bson{BCON_NEW("j", BCON_BOOL(*opt))}; + } + + { + auto const t = this->timeout().count(); + + if (t > 0) { + // Backward compatibility: only use "$numberLong" when necessary. + if (t <= std::int64_t{INT32_MAX}) { + doc += scoped_bson{BCON_NEW("wtimeout", BCON_INT32(static_cast(t)))}; + } else { + doc += scoped_bson{BCON_NEW("wtimeout", BCON_INT64(t))}; + } + } + } + + return std::move(doc).value(); +} + +bool operator==(write_concern const& lhs, write_concern const& rhs) { + // Lexicographic comparison of data members. + return std::make_tuple(lhs.acknowledge_level(), lhs.timeout(), lhs.nodes(), lhs.tag(), lhs.journal()) == + std::make_tuple(rhs.acknowledge_level(), rhs.timeout(), rhs.nodes(), rhs.tag(), rhs.journal()); +} + +write_concern::write_concern(void* impl) : _impl{impl} {} + +write_concern write_concern::internal::make(mongoc_write_concern_t* rc) { + return {rc}; +} + +mongoc_write_concern_t const* write_concern::internal::as_mongoc(write_concern const& self) { + return to_mongoc(self._impl); +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/write_concern.hh b/src/mongocxx/lib/mongocxx/v1/write_concern.hh new file mode 100644 index 0000000000..93b69a820c --- /dev/null +++ b/src/mongocxx/lib/mongocxx/v1/write_concern.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 +#include + +namespace mongocxx { +namespace v1 { + +class write_concern::internal { + public: + static MONGOCXX_ABI_EXPORT_CDECL_TESTING(write_concern) make(mongoc_write_concern_t* rc); + + static mongoc_write_concern_t const* as_mongoc(write_concern const& self); +}; + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp index 992d474a16..73f44743eb 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include #include @@ -184,7 +186,7 @@ mongocxx::v_noabi::uri client::uri() const { } void client::write_concern_deprecated(mongocxx::v_noabi::write_concern wc) { - libmongoc::client_set_write_concern(_get_impl().client_t, wc._impl->write_concern_t); + libmongoc::client_set_write_concern(_get_impl().client_t, v_noabi::write_concern::internal::as_mongoc(wc)); } void client::write_concern(mongocxx::v_noabi::write_concern wc) { @@ -192,10 +194,8 @@ void client::write_concern(mongocxx::v_noabi::write_concern wc) { } mongocxx::v_noabi::write_concern client::write_concern() const { - mongocxx::v_noabi::write_concern wc( - bsoncxx::make_unique( - libmongoc::write_concern_copy(libmongoc::client_get_write_concern(_get_impl().client_t)))); - return wc; + return v1::write_concern::internal::make( + libmongoc::write_concern_copy(libmongoc::client_get_write_concern(_get_impl().client_t))); } mongocxx::v_noabi::database client::database(bsoncxx::v_noabi::string::view_or_value name) const& { diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp index 21a245d662..41525c6be4 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include #include #include @@ -42,7 +44,6 @@ #include #include #include -#include #include #include @@ -1329,14 +1330,12 @@ mongocxx::v_noabi::read_preference collection::read_preference() const { } void collection::write_concern(mongocxx::v_noabi::write_concern wc) { - libmongoc::collection_set_write_concern(_get_impl().collection_t, wc._impl->write_concern_t); + libmongoc::collection_set_write_concern(_get_impl().collection_t, v_noabi::write_concern::internal::as_mongoc(wc)); } mongocxx::v_noabi::write_concern collection::write_concern() const { - mongocxx::v_noabi::write_concern wc( - bsoncxx::make_unique( - libmongoc::write_concern_copy(libmongoc::collection_get_write_concern(_get_impl().collection_t)))); - return wc; + return v1::write_concern::internal::make( + libmongoc::write_concern_copy(libmongoc::collection_get_write_concern(_get_impl().collection_t))); } change_stream collection::watch(options::change_stream const& options) { diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp index 9500c82abd..ecf4a6d074 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include #include @@ -33,6 +35,7 @@ #include #include #include +#include #include @@ -366,14 +369,12 @@ mongocxx::v_noabi::read_preference database::read_preference() const { } void database::write_concern(mongocxx::v_noabi::write_concern wc) { - libmongoc::database_set_write_concern(_get_impl().database_t, wc._impl->write_concern_t); + libmongoc::database_set_write_concern(_get_impl().database_t, v_noabi::write_concern::internal::as_mongoc(wc)); } mongocxx::v_noabi::write_concern database::write_concern() const { - mongocxx::v_noabi::write_concern wc( - bsoncxx::make_unique( - libmongoc::write_concern_copy(libmongoc::database_get_write_concern(_get_impl().database_t)))); - return wc; + return v1::write_concern::internal::make( + libmongoc::write_concern_copy(libmongoc::database_get_write_concern(_get_impl().database_t))); } collection database::collection(bsoncxx::v_noabi::string::view_or_value name) const { diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/transaction.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/transaction.hh index 9ff2a0e567..936773a27f 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/transaction.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/transaction.hh @@ -18,15 +18,17 @@ // +#include + #include #include -#include - #include #include #include +#include + namespace mongocxx { namespace v_noabi { namespace options { @@ -72,17 +74,18 @@ class transaction::impl { } void write_concern(mongocxx::v_noabi::write_concern const& wc) { - libmongoc::transaction_opts_set_write_concern(_transaction_opt_t.get(), wc._impl->write_concern_t); + libmongoc::transaction_opts_set_write_concern( + _transaction_opt_t.get(), v_noabi::write_concern::internal::as_mongoc(wc)); } bsoncxx::v_noabi::stdx::optional write_concern() const { - auto wc = libmongoc::transaction_opts_get_write_concern(_transaction_opt_t.get()); - if (!wc) { - return bsoncxx::v_noabi::stdx::nullopt; + bsoncxx::v_noabi::stdx::optional ret; + + if (auto const wc = libmongoc::transaction_opts_get_write_concern(_transaction_opt_t.get())) { + ret.emplace(v1::write_concern::internal::make(libmongoc::write_concern_copy(wc))); } - mongocxx::v_noabi::write_concern wci( - bsoncxx::make_unique(libmongoc::write_concern_copy(wc))); - return bsoncxx::v_noabi::stdx::optional(std::move(wci)); + + return ret; } void read_preference(mongocxx::v_noabi::read_preference const& rp) { diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.cpp index cc6ed9e05c..a9b63c87c7 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/uri.cpp @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include #include @@ -127,9 +129,8 @@ std::string uri::username() const { } mongocxx::v_noabi::write_concern uri::write_concern() const { - auto wc = libmongoc::uri_get_write_concern(_impl->uri_t); - return mongocxx::v_noabi::write_concern( - bsoncxx::make_unique(libmongoc::write_concern_copy(wc))); + return v1::write_concern::internal::make( + libmongoc::write_concern_copy(libmongoc::uri_get_write_concern(_impl->uri_t))); } static bsoncxx::v_noabi::stdx::optional _string_option( diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/write_concern.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/write_concern.cpp index 32e573d8ce..90c326bd3c 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/write_concern.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/write_concern.cpp @@ -12,190 +12,94 @@ // 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 -namespace mongocxx { -namespace v_noabi { +namespace { +namespace static_assertions { +namespace level { -write_concern::write_concern() : _impl{bsoncxx::make_unique(libmongoc::write_concern_new())} {} +using lv1 = mongocxx::v1::write_concern::level; +using lv_noabi = mongocxx::v_noabi::write_concern::level; -write_concern::write_concern(std::unique_ptr&& implementation) { - _impl.reset(implementation.release()); -} +template +struct check { + static_assert( + static_cast(lhs) == static_cast(rhs), + "write_concern::level: v1 and v_noabi must have the same values"); +}; -write_concern::write_concern(write_concern&&) noexcept = default; -write_concern& write_concern::operator=(write_concern&&) noexcept = default; +template struct check; +template struct check; +template struct check; +template struct check; +template struct check; -write_concern::write_concern(write_concern const& other) - : _impl(bsoncxx::make_unique(libmongoc::write_concern_copy(other._impl->write_concern_t))) {} +} // namespace level +} // namespace static_assertions +} // namespace -write_concern& write_concern::operator=(write_concern const& other) { - _impl.reset(bsoncxx::make_unique(libmongoc::write_concern_copy(other._impl->write_concern_t)).release()); - return *this; -} +namespace mongocxx { +namespace v_noabi { -write_concern::~write_concern() = default; +namespace { -void write_concern::journal(bool journal) { - libmongoc::write_concern_set_journal(_impl->write_concern_t, journal); -} +struct static_assertions { + using lv1 = v1::write_concern::level; + using lv_noabi = v_noabi::write_concern::level; + + static_assert(static_cast(lv1::k_default) == static_cast(lv_noabi::k_default), ""); + static_assert(static_cast(lv1::k_majority) == static_cast(lv_noabi::k_majority), ""); + static_assert(static_cast(lv1::k_tag) == static_cast(lv_noabi::k_tag), ""); + static_assert(static_cast(lv1::k_unacknowledged) == static_cast(lv_noabi::k_unacknowledged), ""); + static_assert(static_cast(lv1::k_acknowledged) == static_cast(lv_noabi::k_acknowledged), ""); +}; + +} // namespace void write_concern::nodes(std::int32_t confirm_from) { if (confirm_from < 0) { throw mongocxx::v_noabi::logic_error{error_code::k_invalid_parameter}; } - libmongoc::write_concern_set_w(_impl->write_concern_t, confirm_from); + + _wc.nodes(confirm_from); } void write_concern::acknowledge_level(write_concern::level confirm_level) { - std::int32_t w = 0; - switch (confirm_level) { - case write_concern::level::k_default: - w = MONGOC_WRITE_CONCERN_W_DEFAULT; - break; - case write_concern::level::k_majority: - w = MONGOC_WRITE_CONCERN_W_MAJORITY; - break; - case write_concern::level::k_unacknowledged: - w = MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED; - break; - case write_concern::level::k_acknowledged: - w = 1; - break; - case write_concern::level::k_tag: - // no exception for setting tag if it's set - if (libmongoc::write_concern_get_w(_impl->write_concern_t) != MONGOC_WRITE_CONCERN_W_TAG) { - throw exception{error_code::k_unknown_write_concern}; - } else { - return; - } + if (confirm_level == level::k_tag) { + if (this->acknowledge_level() != level::k_tag) { + throw exception{error_code::k_unknown_write_concern}; + } else { + return; + } } - libmongoc::write_concern_set_w(_impl->write_concern_t, w); -} -void write_concern::tag(bsoncxx::v_noabi::stdx::string_view confirm_from) { - libmongoc::write_concern_set_wtag(_impl->write_concern_t, bsoncxx::v_noabi::string::to_string(confirm_from).data()); -} - -void write_concern::majority(std::chrono::milliseconds timeout) { - auto const count = timeout.count(); - if ((count < 0) || (count >= std::numeric_limits::max())) - throw logic_error{error_code::k_invalid_parameter}; - - libmongoc::write_concern_set_wmajority(_impl->write_concern_t, static_cast(count)); + _wc.acknowledge_level(static_cast(confirm_level)); } void write_concern::timeout(std::chrono::milliseconds timeout) { auto const count = timeout.count(); - if ((count < 0) || (count >= std::numeric_limits::max())) - throw logic_error{error_code::k_invalid_parameter}; - libmongoc::write_concern_set_wtimeout(_impl->write_concern_t, static_cast(count)); -} - -bool write_concern::journal() const { - return libmongoc::write_concern_get_journal(_impl->write_concern_t); -} - -bsoncxx::v_noabi::stdx::optional write_concern::nodes() const { - std::int32_t w = libmongoc::write_concern_get_w(_impl->write_concern_t); - return w >= 0 ? bsoncxx::v_noabi::stdx::optional{w} : bsoncxx::v_noabi::stdx::nullopt; -} - -write_concern::level write_concern::acknowledge_level() const { - std::int32_t w = libmongoc::write_concern_get_w(_impl->write_concern_t); - if (w >= 1) - return write_concern::level::k_acknowledged; - switch (w) { - case MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED: - return write_concern::level::k_unacknowledged; - case MONGOC_WRITE_CONCERN_W_DEFAULT: - return write_concern::level::k_default; - case MONGOC_WRITE_CONCERN_W_MAJORITY: - return write_concern::level::k_majority; - case MONGOC_WRITE_CONCERN_W_TAG: - return write_concern::level::k_tag; - default: - MONGOCXX_PRIVATE_UNREACHABLE; - } -} - -bsoncxx::v_noabi::stdx::optional write_concern::tag() const { - char const* tag_str = libmongoc::write_concern_get_wtag(_impl->write_concern_t); - return tag_str ? bsoncxx::v_noabi::stdx::make_optional(tag_str) : bsoncxx::v_noabi::stdx::nullopt; -} - -bool write_concern::majority() const { - return libmongoc::write_concern_get_wmajority(_impl->write_concern_t); -} - -std::chrono::milliseconds write_concern::timeout() const { - return std::chrono::milliseconds(libmongoc::write_concern_get_wtimeout(_impl->write_concern_t)); -} - -bool write_concern::is_acknowledged() const { - return libmongoc::write_concern_is_acknowledged(_impl->write_concern_t); -} - -bsoncxx::v_noabi::document::value write_concern::to_document() const { - using bsoncxx::v_noabi::builder::basic::kvp; - using bsoncxx::v_noabi::builder::basic::make_document; - - bsoncxx::v_noabi::builder::basic::document doc; - - if (auto ns = nodes()) { - doc.append(kvp("w", *ns)); - } else { - switch (acknowledge_level()) { - case write_concern::level::k_unacknowledged: - doc.append(kvp("w", 0)); - break; - case write_concern::level::k_default: - // "Commands supporting a write concern MUST NOT send the default write concern to - // the server." See Spec 135. - break; - case write_concern::level::k_majority: - doc.append(kvp("w", "majority")); - break; - case write_concern::level::k_tag: - if (auto t = tag()) { - doc.append(kvp("w", *t)); - } - break; - - case write_concern::level::k_acknowledged: - // `ns.has_value()` implies an acknowledged write. - break; - - default: - break; - } - } - - if (libmongoc::write_concern_journal_is_set(_impl->write_concern_t)) { - doc.append(kvp("j", journal())); - } - - std::int32_t count; - if ((count = static_cast(timeout().count())) > 0) { - doc.append(kvp("wtimeout", bsoncxx::v_noabi::types::b_int32{count})); + if ((count < 0) || (count >= std::int64_t{INT32_MAX})) { + throw logic_error{error_code::k_invalid_parameter}; } - return doc.extract(); + _wc.timeout(timeout); } bool operator==(write_concern const& lhs, write_concern const& rhs) { @@ -205,8 +109,8 @@ bool operator==(write_concern const& lhs, write_concern const& rhs) { rhs.journal(), rhs.nodes(), rhs.acknowledge_level(), rhs.tag(), rhs.majority(), rhs.timeout()); } -bool operator!=(write_concern const& lhs, write_concern const& rhs) { - return !(lhs == rhs); +mongoc_write_concern_t const* write_concern::internal::as_mongoc(write_concern const& self) { + return v1::write_concern::internal::as_mongoc(self._wc); } } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/write_concern.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/write_concern.hh index f30fc1cc03..275b97894e 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/write_concern.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/write_concern.hh @@ -16,26 +16,16 @@ #include // IWYU pragma: export +// + #include namespace mongocxx { namespace v_noabi { -class write_concern::impl { +class write_concern::internal { public: - impl(mongoc_write_concern_t* write_concern) : write_concern_t(write_concern) {} - - ~impl() { - libmongoc::write_concern_destroy(write_concern_t); - } - - impl(impl&&) = delete; - impl& operator=(impl&&) = delete; - - impl(impl const&) = delete; - impl& operator=(impl const&) = delete; - - mongoc_write_concern_t* write_concern_t; + static mongoc_write_concern_t const* as_mongoc(write_concern const& self); }; } // namespace v_noabi diff --git a/src/mongocxx/test/CMakeLists.txt b/src/mongocxx/test/CMakeLists.txt index d815badfdb..5f188e7e27 100644 --- a/src/mongocxx/test/CMakeLists.txt +++ b/src/mongocxx/test/CMakeLists.txt @@ -41,7 +41,6 @@ set(mongocxx_test_sources_private private/numeric_casting.cpp private/scoped_bson_value.cpp private/scoped_bson.cpp - private/write_concern.cpp private/mongoc_version.cpp ) @@ -104,6 +103,7 @@ set(mongocxx_test_sources_v1 v1/bsoncxx.cpp v1/exception.cpp v1/logger.cpp + v1/write_concern.cpp ) set(mongocxx_test_sources_spec diff --git a/src/mongocxx/test/private/write_concern.cpp b/src/mongocxx/test/private/write_concern.cpp deleted file mode 100644 index b6c6429df7..0000000000 --- a/src/mongocxx/test/private/write_concern.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// 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 - -namespace { -using namespace mongocxx; - -TEST_CASE( - "creation of write_concern passes universal parameters to c-driver's methods", - "[write_concern][base][c-driver]") { - SECTION("when journal is requested, mongoc_write_concern_set_journal is called with true") { - bool journal_called = false; - bool journal_value = false; - auto mock_instance = libmongoc::write_concern_set_journal.create_instance(); - mock_instance->visit([&](mongoc_write_concern_t*, bool journal) { - journal_called = true; - journal_value = journal; - }); - write_concern wc{}; - wc.journal(true); - write_concern{wc}; - REQUIRE(journal_called == true); - REQUIRE(journal_value == true); - } - - SECTION("when a timeout is set, mongoc_write_concern_set_wtimeout is called with that value") { - bool wtimeout_called = false; - int wtimeout_value = 0; - auto mock_instance = libmongoc::write_concern_set_wtimeout.create_instance(); - mock_instance->visit([&](mongoc_write_concern_t*, int wtimeout) { - wtimeout_called = true; - wtimeout_value = wtimeout; - }); - write_concern wc{}; - wc.timeout(std::chrono::seconds(1)); - write_concern{wc}; - REQUIRE(wtimeout_called == true); - REQUIRE(wtimeout_value == 1000); - } -} - -TEST_CASE("write_concern is called with w MAJORITY", "[write_concern][base][c-driver]") { - bool w_called = false, wmajority_called = false, wtag_called = false; - auto w_instance = libmongoc::write_concern_set_w.create_instance(); - auto wmajority_instance = libmongoc::write_concern_set_wmajority.create_instance(); - auto wtag_instance = libmongoc::write_concern_set_wtag.create_instance(); - w_instance->visit([&](mongoc_write_concern_t*, int) { w_called = true; }); - wmajority_instance->visit([&](mongoc_write_concern_t*, int) { wmajority_called = true; }); - wtag_instance->visit([&](mongoc_write_concern_t*, char const*) { wtag_called = true; }); - - write_concern wc{}; - wc.majority(std::chrono::milliseconds(100)); - write_concern{wc}; - - SECTION("mongoc_write_concern_set_wmajority is called") { - REQUIRE(wmajority_called == true); - } - - SECTION("mongoc_write_concern_set_w is not called") { - REQUIRE(w_called == false); - } - - SECTION("mongoc_write_concern_set_wtag is not called") { - REQUIRE(wtag_called == false); - } -} - -TEST_CASE("write_concern is called with a number of necessary confirmations", "[write_concern][base][c-driver]") { - bool w_called = false, wmajority_called = false, wtag_called = false; - int w_value = 0; - int const expected_w = 5; - auto w_instance = libmongoc::write_concern_set_w.create_instance(); - auto wmajority_instance = libmongoc::write_concern_set_wmajority.create_instance(); - auto wtag_instance = libmongoc::write_concern_set_wtag.create_instance(); - w_instance->visit([&](mongoc_write_concern_t*, int w) { - w_called = true; - w_value = w; - }); - wmajority_instance->visit([&](mongoc_write_concern_t*, int) { wmajority_called = true; }); - wtag_instance->visit([&](mongoc_write_concern_t*, char const*) { wtag_called = true; }); - - write_concern wc{}; - wc.nodes(expected_w); - write_concern{wc}; - - SECTION("mongoc_write_concern_set_w is called with that number") { - REQUIRE(w_called == true); - REQUIRE(w_value == expected_w); - } - - SECTION("mongoc_write_concern_set_wmajority is not called") { - REQUIRE(wmajority_called == false); - } - - SECTION("mongoc_write_concern_set_wtag is not called") { - REQUIRE(wtag_called == false); - } -} - -TEST_CASE("write_concern is called with a tag", "[write_concern][base][c-driver]") { - bool w_called = false, wmajority_called = false, wtag_called = false; - std::string wtag_value; - std::string const expected_wtag("MultiDataCenter"); - auto w_instance = libmongoc::write_concern_set_w.create_instance(); - auto wmajority_instance = libmongoc::write_concern_set_wmajority.create_instance(); - auto wtag_instance = libmongoc::write_concern_set_wtag.create_instance(); - w_instance->visit([&](mongoc_write_concern_t*, int) { w_called = true; }); - wmajority_instance->visit([&](mongoc_write_concern_t*, int) { wmajority_called = true; }); - wtag_instance->visit([&](mongoc_write_concern_t*, char const* wtag) { - wtag_called = true; - wtag_value = wtag; - }); - - write_concern wc{}; - wc.tag(expected_wtag); - write_concern{wc}; - - SECTION("mongoc_write_concern_set_w is not called") { - REQUIRE(w_called == false); - } - - SECTION("mongoc_write_concern_set_wmajority is not called") { - REQUIRE(wmajority_called == false); - } - - SECTION("mongoc_write_concern_set_wtag is not called") { - REQUIRE(wtag_called == true); - REQUIRE(wtag_value == expected_wtag); - } -} -} // namespace diff --git a/src/mongocxx/test/v1/write_concern.cpp b/src/mongocxx/test/v1/write_concern.cpp new file mode 100644 index 0000000000..938267cda4 --- /dev/null +++ b/src/mongocxx/test/v1/write_concern.cpp @@ -0,0 +1,437 @@ +// 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 + +namespace mongocxx { +namespace v1 { + +TEST_CASE("ownership", "[mongocxx][v1][write_concern]") { + write_concern source; + write_concern target; + + source.tag("source"); + target.tag("target"); + + SECTION("move") { + auto move = std::move(source); + + // source is in an assign-or-move-only state. + + CHECK(move.tag() == "source"); + + target = std::move(move); + + // source is in an assign-or-move-only state. + + CHECK(target.tag() == "source"); + } + + SECTION("copy") { + auto copy = source; + + CHECK(source.tag() == "source"); + CHECK(copy.tag() == "source"); + + target = copy; + + CHECK(copy.tag() == "source"); + CHECK(target.tag() == "source"); + } +} + +TEST_CASE("default", "[mongocxx][v1][write_concern]") { + using level = write_concern::level; + + write_concern const wc; + + CHECK(wc.acknowledge_level() == level::k_default); + CHECK(wc.timeout().count() == 0); + CHECK_FALSE(wc.nodes().has_value()); + CHECK_FALSE(wc.tag().has_value()); + CHECK_FALSE(wc.journal().has_value()); + CHECK(wc.is_acknowledged()); +} + +TEST_CASE("acknowledge_level", "[mongocxx][v1][write_concern]") { + using level = write_concern::level; + + write_concern wc; + + SECTION("normal") { + level input = {}; + level expected = {}; + + std::tie(input, expected) = GENERATE( + table({ + {level::k_majority, level::k_majority}, + {level::k_default, level::k_default}, + {level::k_tag, level::k_default}, // Undocumented behavior. + {level::k_unacknowledged, level::k_unacknowledged}, + {level::k_acknowledged, level::k_acknowledged}, + {level::k_unknown, level::k_default}, // Undocumented behavior. + {static_cast(-1), level::k_default}, // Undocumented behavior. + {static_cast(6), level::k_default}, // Undocumented behavior. + })); + + CAPTURE(input); + CHECK(wc.acknowledge_level(input).acknowledge_level() == expected); + } + + SECTION("nodes") { + level input = {}; + bsoncxx::v1::stdx::optional expected = {}; + + std::tie(input, expected) = GENERATE( + table>({ + {level::k_majority, {}}, + {level::k_default, {}}, + {level::k_unacknowledged, 0}, + {level::k_acknowledged, 1}, + {level::k_unknown, {}}, + })); + + CAPTURE(input); + CHECK(wc.acknowledge_level(input).nodes() == expected); + } + + { + auto const input = GENERATE(values({ + level::k_majority, + level::k_default, + level::k_unacknowledged, + level::k_acknowledged, + })); + + CAPTURE(input); + + SECTION("tag") { + CHECK_FALSE(wc.acknowledge_level(input).tag().has_value()); + } + + SECTION("journal") { + CHECK_FALSE(wc.acknowledge_level(input).journal().has_value()); + } + + SECTION("journal") { + CHECK_FALSE(wc.acknowledge_level(input).journal().has_value()); + } + } +} + +TEST_CASE("majority", "[mongocxx][v1][write_concern]") { + using level = write_concern::level; + + write_concern wc; + + SECTION("normal") { + wc.majority(); + + CHECK(wc.acknowledge_level() == level::k_majority); + CHECK(wc.timeout().count() == 0); + CHECK_FALSE(wc.nodes().has_value()); + CHECK_FALSE(wc.tag().has_value()); + CHECK_FALSE(wc.journal().has_value()); + CHECK(wc.is_acknowledged()); + } + + SECTION("timeout") { + auto const timeout = std::chrono::milliseconds{123}; + + wc.majority(timeout); + + CHECK(wc.acknowledge_level() == level::k_majority); + CHECK(wc.timeout() == timeout); + CHECK_FALSE(wc.nodes().has_value()); + CHECK_FALSE(wc.tag().has_value()); + CHECK_FALSE(wc.journal().has_value()); + CHECK(wc.is_acknowledged()); + } +} + +TEST_CASE("timeout", "[mongocxx][v1][write_concern]") { + write_concern wc; + + std::int64_t before_i64 = {}; + std::int64_t after_i64 = {}; + + std::tie(before_i64) = GENERATE(table({0, 1, INT64_MAX})); + std::tie(after_i64) = GENERATE(table({INT64_MIN, -1, 0, 1, INT64_MAX})); + + std::chrono::milliseconds const before{before_i64}; + std::chrono::milliseconds const after{after_i64}; + + CAPTURE(before); + CAPTURE(after); + + CHECK_NOTHROW(wc.timeout(before)); + CHECK(wc.timeout() == before); + + CHECK_NOTHROW(wc.timeout(after)); + + CHECKED_IF(after.count() < 0) { + CHECK(wc.timeout() == before); // Undocumented behavior. + } + + else { + CHECK(wc.timeout() == after); + } +} + +TEST_CASE("nodes", "[mongocxx][v1][write_concern]") { + using level = write_concern::level; + + write_concern wc; + + std::int32_t before = {}; + std::int32_t after = {}; + + std::tie(before) = GENERATE(table({0, 1, INT32_MAX})); + std::tie(after) = GENERATE(table({INT32_MIN, -1, 0, 1, INT32_MAX})); + + CAPTURE(before); + CAPTURE(after); + + CHECK_NOTHROW(wc.nodes(before)); + CHECK(wc.nodes() == before); + + CHECKED_IF(before == 0) { + CHECK(wc.acknowledge_level() == level::k_unacknowledged); + } + + else { + CHECK(wc.acknowledge_level() == level::k_acknowledged); + } + + CHECK_NOTHROW(wc.nodes(after)); + + CHECKED_IF(after < 0) { + CHECK_FALSE(wc.nodes().has_value()); // Undocumented behavior. + CHECK(wc.acknowledge_level() == level::k_default); + } + + else { + CHECK(wc.nodes() == after); + + CHECKED_IF(after == 0) { + CHECK(wc.acknowledge_level() == level::k_unacknowledged); + } + + else { + CHECK(wc.acknowledge_level() == level::k_acknowledged); + } + } + + CHECK_NOTHROW(wc.acknowledge_level(level::k_default)); + CHECK_FALSE(wc.nodes().has_value()); +} + +TEST_CASE("tag", "[mongocxx][v1][write_concern]") { + using level = write_concern::level; + + write_concern wc; + + bsoncxx::v1::stdx::string_view v; + + std::tie(v) = GENERATE(table({{}, "", "abc", "custom"})); + + CAPTURE(v); + + CHECK_NOTHROW(wc.tag(v)); + CHECK(wc.tag() == v); + CHECK(wc.acknowledge_level() == level::k_tag); + + CHECK_NOTHROW(wc.acknowledge_level(level::k_default)); + CHECK_FALSE(wc.tag().has_value()); +} + +TEST_CASE("journal", "[mongocxx][v1][write_concern]") { + write_concern wc; + + auto const before = static_cast(GENERATE(values({0, 1}))); + auto const after = static_cast(GENERATE(values({0, 1}))); + + CAPTURE(before); + CAPTURE(after); + + CHECK_NOTHROW(wc.journal(before)); + CHECK(wc.journal().value() == before); + + CHECK_NOTHROW(wc.journal(after)); + CHECK(wc.journal().value() == after); + + wc = {}; + + CHECK_FALSE(wc.journal().has_value()); +} + +TEST_CASE("is_acknowledged", "[mongocxx][v1][write_concern]") { + using level = write_concern::level; + + write_concern wc; + + CHECK_NOTHROW(wc.acknowledge_level(level::k_unacknowledged)); + CHECK_FALSE(wc.is_acknowledged()); + + CHECK_NOTHROW(wc.journal(true)); + CHECK(wc.is_acknowledged()); + + CHECK_NOTHROW(wc.journal(false)); + CHECK_FALSE(wc.is_acknowledged()); + + CHECK_NOTHROW(wc.acknowledge_level(level::k_acknowledged)); + CHECK(wc.is_acknowledged()); +} + +TEST_CASE("to_document", "[mongocxx][v1][write_concern]") { + using level = write_concern::level; + + write_concern wc; + + SECTION("default") { + CHECK(wc.to_document().view() == bsoncxx::v1::document::view{}); + } + + SECTION("acknowledge_level") { + level v = {}; + scoped_bson expected; + + std::tie(v, expected) = GENERATE(map( + [](std::tuple i) { + return std::make_tuple(std::get<0>(i), scoped_bson{std::get<1>(i)}); + }, + table({ + {level::k_majority, R"({"w": "majority"})"}, + {level::k_default, R"({})"}, + {level::k_unacknowledged, R"({"w": 0})"}, + {level::k_acknowledged, R"({"w": 1})"}, + }))); + + CAPTURE(v); + + CHECK_NOTHROW(wc.acknowledge_level(v)); + CHECK(wc.to_document().view() == expected.view()); + } + + SECTION("timeout") { + std::int64_t v; + scoped_bson expected; + + std::tie(v, expected) = GENERATE(map( + [](std::tuple const& i) { + return std::make_tuple(std::get<0>(i), scoped_bson{std::get<1>(i)}); + }, + table({ + {0, R"({})"}, + {1, R"({"wtimeout": 1})"}, + {INT32_MAX, R"({"wtimeout": 2147483647})"}, + {std::int64_t{INT32_MAX} + 1, R"({"wtimeout": {"$numberLong": "2147483648"}})"}, + {std::int64_t{INT64_MAX} - 1, R"({"wtimeout": {"$numberLong": "9223372036854775806"}})"}, + {std::int64_t{INT64_MAX}, R"({"wtimeout": {"$numberLong": "9223372036854775807"}})"}, + }))); + CAPTURE(v); + + auto const doc = wc.timeout(std::chrono::milliseconds{v}).to_document(); + CHECK(doc.view() == expected.view()); + } + + SECTION("nodes") { + CHECK_NOTHROW(wc.nodes(3)); + CHECK(wc.to_document().view() == scoped_bson{R"({"w": 3})"}.view()); + } + + SECTION("tag") { + CHECK_NOTHROW(wc.tag("abc")); + CHECK(wc.to_document().view() == scoped_bson{R"({"w": "abc"})"}.view()); + } + + SECTION("journal") { + auto const v = static_cast(GENERATE(values({0, 1}))); + CHECK_NOTHROW(wc.journal(v)); + CHECK(wc.to_document().view() == scoped_bson{BCON_NEW("j", BCON_BOOL(v))}.view()); + } +} + +TEST_CASE("equality", "[mongocxx][v1][write_concern]") { + using level = write_concern::level; + + write_concern lhs; + write_concern rhs; + + CHECK(lhs == lhs); + CHECK(rhs == rhs); + CHECK(lhs == rhs); + + SECTION("acknowledge_level") { + rhs.acknowledge_level(level::k_acknowledged); + + CHECK(lhs == lhs); + CHECK(rhs == rhs); + CHECK(lhs != rhs); + } + + SECTION("timeout") { + rhs.timeout(std::chrono::milliseconds{123}); + + CHECK(lhs == lhs); + CHECK(rhs == rhs); + CHECK(lhs != rhs); + } + + SECTION("nodes") { + rhs.nodes(3); + + CHECK(lhs == lhs); + CHECK(rhs == rhs); + CHECK(lhs != rhs); + } + + SECTION("tag") { + rhs.tag("abc"); + + CHECK(lhs == lhs); + CHECK(rhs == rhs); + CHECK(lhs != rhs); + } + + SECTION("journal") { + rhs.journal(false); + + CHECK(lhs == lhs); + CHECK(rhs == rhs); + CHECK(lhs != rhs); + } +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/test/v1/write_concern.hh b/src/mongocxx/test/v1/write_concern.hh new file mode 100644 index 0000000000..fc2b954b5e --- /dev/null +++ b/src/mongocxx/test/v1/write_concern.hh @@ -0,0 +1,30 @@ +// 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 + +CATCH_REGISTER_ENUM( + mongocxx::v1::write_concern::level, + mongocxx::v1::write_concern::level::k_default, + mongocxx::v1::write_concern::level::k_majority, + mongocxx::v1::write_concern::level::k_tag, + mongocxx::v1::write_concern::level::k_unacknowledged, + mongocxx::v1::write_concern::level::k_acknowledged, + mongocxx::v1::write_concern::level::k_unknown)