- 
                Notifications
    You must be signed in to change notification settings 
- Fork 547
CXX-3236 add mongocxx v1 declarations #1482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Posting initial comments.
- Should
std::unique_ptr<impl> _implbe madevoid* _implinstead (even when aclass implis used) to leave open the possibility of replacingimplwith a mongoc equivalent or vise versa as an ABI compatible change? Or is this "premature optimization" and usingstd::unique_ptr<T>is sufficient?
I am slightly in favor of switching to void *, but also OK with the current proposal. I expect saving one pointer dereference may not make an observable performance difference in most cases (especially when much of mongocxx API includes network calls), but this may help to future-proof the ABI.
- Regardless of the question above, should classes which can currently be implemented entirely in terms of a
mongoc_*_ttype usevoid* _impl; // mongoc_*_t, e.g. to avoid double-indirection? (Initial changes propose that only the view-likev1::events::*classes do this.)
Similar: Slightly in favor, but OK with current proposal.
- Is providing
char const*+std::string+string_viewset of overloads premature optimization? Should they be simplified to just a singlestring_viewoverload with an internalstd::stringallocation?
IIUC this only appears to impact four methods: read_concern::acknowledge_string(), server_error::has_error_label(), uri::uri(), and write_concern::tag.
Some libbson API was added accept a length argument (e.g. bson_iter_init_find_w_len). If libmongoc adds functions accepting a string length (suggested in comment), that may avoid a need for the char const* / std::string overloads. Filed CDRIVER-6128 to propose this API in the C driver.
IMO: I am slightly in favor of reducing the overloads since this could be eventually improved by libmongoc additions.
- Is removing the deprecated
v1::read_preference::hedge()premature? (CXX-3241)
I am in favor of adding to v1. Hedged reads was only deprecated in server 8.0. And only deprecated in the C++ driver v4.1.
I am in favor of keepint these out. CXX-1939 deprecated 5 years ago, and CXX-1978 deprecated 8 years ago, I expect those have low usage. If that assumption is wrong, they can be added to v1 on request.
- Should
mongoc_cursor_clone()be implemented by mongocxx by making cursors copyable? Or shouldmongoc_cursor_clone()be deprecated given its confusing behavior (re-execute the underlying query)?
I also think mongoc_cursor_clone behavior is surprising (I would expect "clone" means to clone the current state). mongoc_cursor_clone documents the behavior. And this appears to be the requested behavior in CDRIVER-204 for a language binding. I would rather not continue the pattern in C++ driver unless there is a known need. But if there are users wanting the C API, I think it is fine as-is.
- Should
v1::change_stream::batch_size()supportstd::uint32_tfor consistency withmongoc_cursor_set_batch_size()? Or doesmongoc_cursor_set_batch_size()need to be updated to supportstd::int64_tinstead?
The spec suggests both should be std::int32_t. I expect mongoc_cursor_set_batch_size should instead be int32_t to match the spec.
        
          
                src/mongocxx/include/mongocxx/v1/events/server_heartbeat_failed.hpp
              
                Outdated
          
            Show resolved
            Hide resolved
        
              
          
                src/mongocxx/include/mongocxx/v1/events/server_heartbeat_started.hpp
              
                Outdated
          
            Show resolved
            Hide resolved
        
              
          
                src/mongocxx/include/mongocxx/v1/events/server_heartbeat_succeeded.hpp
              
                Outdated
          
            Show resolved
            Hide resolved
        
      There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am slightly in favor of reducing the [NTBS] overloads since this could be eventually improved by libmongoc additions.
Done. This means we are accepting a performance penalty (due to unconditional std::string allocations) until mongoc improves support for length-based string API.
I am in favor of adding [
.hedge()] to v1. Hedged reads was only deprecated in server 8.0.
Done. v1::read_preference also has deprecated .hedge() accessors.
I would rather not continue the [
mongoc_cursor_clone()] pattern in C++ driver unless there is a known need.
Will leave v1::cursor as-is then. Both v1::cursor and v_noabi::cursor will remain non-copyable.
I am slightly in favor of switching to
void *... this may help to future-proof the ABI.
Done. All _impl data members are now annotated with the underlying type (class impl; is assumed and implied by the presence of the declaration).
The following classes are defined entirely in terms of the mongoc API:
- v1::events::*(all event types are fully backed by mongoc equivalents)
- v1::client_encryption->- mongoc_client_encryption_t
- v1::client_session::options->- mongoc_session_opt_t
- v1::read_concern->- mongoc_read_concern_t
- v1::read_preference->- mongoc_read_prefs_t
- v1::transaction->- mongoc_transaction_opt_t
- v1::uri->- mongoc_uri_t
- v1::write_concern->- mongoc_write_concern_t
The following classes are defined entirely in terms of other mongocxx API:
- v1::change_stream::iterator->- v1::change_stream(shared state iterators)
- v1::cursor::iterator->- v1::cursor(shared state iterators)
The following classes still use class impl; despite an apparent mongoc equivalent being available:
- v1::apm(- mongoc_apm_callbacks_t)- Needs to support std::function<T>getters.
 
- Needs to support 
- v1::auto_encryption(- mongoc_auto_encryption_opts_t)- Needs to support .key_vault_client() -> v1::client*and.key_vault_pool() -> v1::pool*.
- Missing mongoc_auto_encryption_opts_get_*()for getters.
 
- Needs to support 
- v1::change_stream(- mongoc_change_stream_t)- Needs to support shared state for iterators (i.e. the event document).
 
- v1::client_encryption::options(- mongoc_client_encryption_opts_t)- Needs to support mongocxx getters (no mongoc_client_encryption_opts_get_*()).
 
- Needs to support mongocxx getters (no 
- v1::client_session(- mongoc_client_session_t)- Needs to support .client() -> v1::client const&and.options() -> v1::client_session::options const&.
 
- Needs to support 
- v1::client(- mongoc_client_t)- Needs to extend the lifetime of v1::apm.
 
- Needs to extend the lifetime of 
- v1::collection(- mongoc_collection_t)- Needs to store a reference to the associated v1::clientobject (possibly avoidable if we usemongoc_collection_create_indexes_with_opts()inv1::indexes::create_many()?).
 
- Needs to store a reference to the associated 
- v1::cursor(- mongoc_cursor_t)- Needs to support shared state for iterators (i.e. the result document).
 
- v1::database(- mongoc_database_t)- Needs to store a reference to the associated v1::clientobject.
- Missing mongoc_database_command_simple_with_server_id().
 
- Needs to store a reference to the associated 
- v1::encrypt(- mongoc_client_encryption_encrypt_opts_t)- Missing mongoc_client_encryption_encrypt_opts_get_*()for getters.
 
- Missing 
- v1::indexes::model(- mongoc_index_model_t)- Missing mongoc_index_model_get_*()for getters.
 
- Missing 
- v1::range(- mongoc_client_encryption_encrypt_range_opts_t)- Missing mongoc_client_encryption_encrypt_range_opts_get_*()for getters.
 
- Missing 
- v1::pool(- mongoc_client_pool_t)- Needs to extend the lifetime of v1::apm.
 
- Needs to extend the lifetime of 
        
          
                src/mongocxx/include/mongocxx/v1/events/server_heartbeat_failed.hpp
              
                Outdated
          
            Show resolved
            Hide resolved
        
      | Applied the decision made in #1487 to  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work. The efforts towards API consistency and removal of view_or_value types is much appreciated. Most of comments are documentation fix-ups and questions.
| /// - `upserted_count` ("nUpserted" or "upsertedCount") | ||
| /// - `upserted_ids` ("upserted" or "upsertedIds") | ||
| /// | ||
| /// @important The raw server response is translated by mongoc into the Bulk Write API specification format even when | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider moving this note to v1::bulk_write::raw since I expect that is the method directly exposing the BSON result of mongoc_bulk_operation_execute.
Does "even when the CRUD API specification is used" mean when v1::bulk_write is used as the underlying operation for a different CRUD method (like v1::collection::insert_many?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The note is class-level because the entire class is implemented in terms of mongoc's translated Bulk Write API fields:
mongo-cxx-driver/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/result/bulk_write.cpp
Lines 23 to 41 in 8ac2a61
| std::int32_t bulk_write::inserted_count() const { | |
| return view()["nInserted"].get_int32(); | |
| } | |
| std::int32_t bulk_write::matched_count() const { | |
| return view()["nMatched"].get_int32(); | |
| } | |
| std::int32_t bulk_write::modified_count() const { | |
| return view()["nModified"].get_int32(); | |
| } | |
| std::int32_t bulk_write::deleted_count() const { | |
| return view()["nRemoved"].get_int32(); | |
| } | |
| std::int32_t bulk_write::upserted_count() const { | |
| return view()["nUpserted"].get_int32(); | |
| } | 
The v1 implementation does not make any attempt to "revert" this field translation by mongoc, hence the reference to MONGOC_WRITE_RESULT_COMPLETE in case any particulars of this behavior need to be examined.
"Even when the CRUD API specification is used" is referring to use of API that conforms to the CRUD API specification in (nearly) all respects but the fields of the server reply. e.g. collection::bulk_write() -> Optional<BulkWriteResult> suggests the server reply document should contain a field "insertedIds", but instead it contains the field "nInserted".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggests the server reply document
I was thinking it was unnecessary to document the server reply if users would not see it. This seems to be documenting more of the internal implementation than the exposed API.
On further thought: I expect users may see the server reply in the v1::server_error exception. Is that the motivation for documenting the server reply?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking it was unnecessary to document the server reply if users would not see it. This seems to be documenting more of the internal implementation than the exposed API.
You're correct. This class doesn't have a .view() or .raw() exposing the underlying document. I am fine with demoting these comments into developer-only internal comments instead of including them in the public API documentation. (This was due to the concern that MONGOC_WRITE_RESULT_COMPLETE could possibly introduce unique behavior that is observable in the value of the resulting fields, but perhaps this was paranoia.)
Is that the motivation for documenting the server reply?
No, it is not. We deliberately do not specify any details about what may be in the server error document.
| /// - Use `this->code()` to obtain the primary error code (which may equal `this->server_code()`). | ||
| /// - Use `this->server_code()` to obtain the server error code (which may equal `zero`). | ||
| /// Use @ref mongocxx::v1::source_errc to determine the origin of `this->code()`. | ||
| /// @important `this->code()` always returns the raw server error code. Use `this->client_code()` to query the | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I neglected to ask before. What motivated the separate client error code? If a server error code is always available and can be compared against error conditions (like v1::source_errc::server), I do not see when the client error code would be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to support the situation (hopefully rare, difficult to verify) where bson_error_t::reserved (category) may indicate a client-side error code, but a server error document is also provided. In such a situation, this PR proposes saving that information alongside the server error code rather than ignoring/dropping it.
In full summary, given:
if (!mongoc_generic_server_command(..., reply.out_ptr(), &error)) {
  v1::exception::internal::throw_exception(error, reply.view()); // CXX-3237
}there are four possible outcomes (pseudocode for brevity):
| reply is | server error code | client error code | 
|---|---|---|
| not empty | v1::server_error.code() == error.code(server).client_code() == {} | v1::server_error.code() == .raw()["code"](server).client_code() == error.code(client) | 
| empty | v1::exception.code() == error.code(server) | v1::exception.code() == error.code(client) | 
The separate client code is to support the top-right cell of this table.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indicate a client-side error code, but a server error document is also provided
One possible scenario: a reply might contain only error labels in some client-side error scenarios (e.g. network error). For example:
{"errorLabels" : [ "UnknownTransactionCommitResult", "RetryableWriteError" ]}Reporting the error label appears required in some specs. For example in transactions:
Drivers MUST add error labels to certain errors when commitTransaction fails.
In this scenario, there is no .raw()["code"] to use as the server error code. I am not sure how to best address. Maybe this motivates adding raw / has_error_label methods to v1::exception?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this motivates adding
raw/has_error_labelmethods tov1::exception?
Possibly. The Transactions spec quote is followed by a reference to error reporting changes:
Any error reported by the driver in response to a server error, server selection error, or network error MUST have an API for determining whether it has a given label.
Examples of such an API include exception classes such as OperationFailure and ConnectionFailure which are not defined anywhere else in the specs.
We have v1::server_error for server errors; we do not have any exception class dedicated to server selection errors or network errors, in mongocxx or in mongoc for that matter (since mongoc blobs all errors into bson_t reply parameters). It looks like this issue was known and acknowledged in the past, in reference to CXX-834: 
mongo-cxx-driver/src/mongocxx/test/spec/unified_tests/runner.cpp
Lines 962 to 972 in d29bad7
| // TODO CXX-834: client-side errors may contain error labels. However, only | |
| // mongocxx::operation_exception keeps track of the raw_sever_error (and consequently the | |
| // error label) and, as a result, is the only exception type with the required | |
| // `has_error_label` method. Until we fix CXX-834, there's no way to check the error label of a | |
| // mongocxx::exception. | |
| REQUIRE_FALSE(expect_error["errorLabelsContain"]); | |
| REQUIRE_FALSE(expect_error["errorLabelsOmit"]); | |
| REQUIRE_FALSE(/* TODO */ expect_error["errorContains"]); | |
| REQUIRE_FALSE(/* TODO */ expect_error["errorCode"]); | |
| REQUIRE_FALSE(/* TODO */ expect_error["errorCodeName"]); | 
and:
mongo-cxx-driver/src/mongocxx/test/spec/unified_tests/runner.cpp
Lines 923 to 944 in d29bad7
| /* | |
| // This has no data to act on until CXX-834 as been implemented; see notes | |
| if (auto expected_error = expect_error["errorContains"]) { | |
| // in assert_error(): | |
| // See | |
| // | |
| "https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md#expectederror": | |
| // A substring of the expected error message (e.g. "errmsg" field in a server error | |
| // document). The test runner MUST assert that the error message contains this string using | |
| // a case-insensitive match. | |
| std::string expected_error_str(expected_error.get_string().value); | |
| std::string actual_str(reinterpret_cast<const std::string::value_type*>(actual.data()), | |
| actual.length()); | |
| transform(begin(expected_error_str), | |
| end(expected_error_str), | |
| begin(expected_error_str), | |
| &toupper); | |
| REQUIRE(actual_str.substr(expected_error_str.size()) == expected_error_str); | |
| } | |
| */ | 
The current spec tests runners only support validating error labels for errors thrown as v_noabi::operation_exception, e.g. here: 
mongo-cxx-driver/src/mongocxx/test/spec/util.cpp
Lines 406 to 417 in d29bad7
| try { | |
| auto op_runner = make_op_runner(); | |
| actual_result = op_runner.run(op); | |
| } catch (operation_exception const& e) { | |
| error_msg = e.what(); | |
| server_error = e.raw_server_error(); | |
| op_exception = e; | |
| exception = e; | |
| } catch (std::exception& e) { | |
| error_msg = e.what(); | |
| exception = e; | |
| } | 
and here:
mongo-cxx-driver/src/mongocxx/test/spec/util.cpp
Lines 692 to 705 in d29bad7
| try { | |
| session->with_transaction(with_txn_test_cb); | |
| } catch (operation_exception const& e) { | |
| error_msg = e.what(); | |
| server_error = e.raw_server_error(); | |
| exception = e; | |
| ec = e.code(); | |
| } catch (mongocxx::logic_error const& e) { | |
| // CXX-1679: some tests trigger client errors that are thrown as logic_error rather | |
| // than operation_exception (i.e. update without $ operator). | |
| error_msg = e.what(); | |
| exception.emplace(make_error_code(mongocxx::error_code(0))); | |
| ec = e.code(); | |
| } | 
Since we now have a proper mechanism to address CXX-834 following CDRIVER-5854, this may be an opportunity to add proper support for extending the error/exception API to support additional fields such as errorLabels regardless whether the source of the error is client-side or server-side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work. The efforts towards API consistency and removal of view_or_value types is much appreciated. Most of comments are documentation fix-ups and questions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Why is GitHub not allowing the submission of a comment review without a top-level comment?)
| /// - `upserted_count` ("nUpserted" or "upsertedCount") | ||
| /// - `upserted_ids` ("upserted" or "upsertedIds") | ||
| /// | ||
| /// @important The raw server response is translated by mongoc into the Bulk Write API specification format even when | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The note is class-level because the entire class is implemented in terms of mongoc's translated Bulk Write API fields:
mongo-cxx-driver/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/result/bulk_write.cpp
Lines 23 to 41 in 8ac2a61
| std::int32_t bulk_write::inserted_count() const { | |
| return view()["nInserted"].get_int32(); | |
| } | |
| std::int32_t bulk_write::matched_count() const { | |
| return view()["nMatched"].get_int32(); | |
| } | |
| std::int32_t bulk_write::modified_count() const { | |
| return view()["nModified"].get_int32(); | |
| } | |
| std::int32_t bulk_write::deleted_count() const { | |
| return view()["nRemoved"].get_int32(); | |
| } | |
| std::int32_t bulk_write::upserted_count() const { | |
| return view()["nUpserted"].get_int32(); | |
| } | 
The v1 implementation does not make any attempt to "revert" this field translation by mongoc, hence the reference to MONGOC_WRITE_RESULT_COMPLETE in case any particulars of this behavior need to be examined.
"Even when the CRUD API specification is used" is referring to use of API that conforms to the CRUD API specification in (nearly) all respects but the fields of the server reply. e.g. collection::bulk_write() -> Optional<BulkWriteResult> suggests the server reply document should contain a field "insertedIds", but instead it contains the field "nInserted".
| /// - Use `this->code()` to obtain the primary error code (which may equal `this->server_code()`). | ||
| /// - Use `this->server_code()` to obtain the server error code (which may equal `zero`). | ||
| /// Use @ref mongocxx::v1::source_errc to determine the origin of `this->code()`. | ||
| /// @important `this->code()` always returns the raw server error code. Use `this->client_code()` to query the | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to support the situation (hopefully rare, difficult to verify) where bson_error_t::reserved (category) may indicate a client-side error code, but a server error document is also provided. In such a situation, this PR proposes saving that information alongside the server error code rather than ignoring/dropping it.
In full summary, given:
if (!mongoc_generic_server_command(..., reply.out_ptr(), &error)) {
  v1::exception::internal::throw_exception(error, reply.view()); // CXX-3237
}there are four possible outcomes (pseudocode for brevity):
| reply is | server error code | client error code | 
|---|---|---|
| not empty | v1::server_error.code() == error.code(server).client_code() == {} | v1::server_error.code() == .raw()["code"](server).client_code() == error.code(client) | 
| empty | v1::exception.code() == error.code(server) | v1::exception.code() == error.code(client) | 
The separate client code is to support the top-right cell of this table.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with only very minor comments
| private: | ||
| void const* _impl; // mongoc_apm_command_succeeded_t const | ||
|  | ||
| public: | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have precedent for the placement of data members? e.g. in this case, the private imply is the first thing a user will see when they look at the class, rather than the public API functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have precedent for the placement of data members?
Per the current (and still not yet updated) coding guidelines, precedent is the traditional "for users reading the header file" layout. Instead, bsoncxx::v1 and mongocxx::v1 API follows the advice of Howard Hinnant:
But I've since come to the conclusion that this is exactly wrong. When I'm reading a class declaration, the very first things I want to know are:
- What resources does this class own?
- Can the class be default constructed?
- Can the class be copied or moved?
- How can the class be constructed (other than by default, copy or move)?
- What else can one do with the class?
Note that this is an ordered list: top priority is listed first. [...] It provides the most important characteristics of a class as soon as possible to the reader. It prevents the reader from having to search the entire class declaration for compiler-declared special members which are otherwise invisible to the reader.
Hence why bsoncxx::v1 and mongocxx::v1 instead adopt the following order of class member declarations:
class C : /* base classes */ {
  private:
    // Data members: informs what SMFs are required.
  public:
    // Dtor: clearest indication of resource ownership (or the lack/absence of it).
    // Move + Copy: informed by the data members and dtor (Ro3/Ro5/Ro0).
    // (Default) ctors: how to create this class?
    // The rest of the class API: public, then private (usual ordering).
};Furthermore, given we provide official API documentation pages (whose contents are also made immediately-visible via Doxygen-aware IDE tooling), I do not believe we should (need to) be accomodating traditional "the header file is the API documentation" practices.
If this HH-style is too confusing/disruptive, I can make the necessary changes to return to the traditional public-then-private ordering. (bsoncxx::v1 will also be updated for consistency in a separate PR.)
| /// Equivalent to @ref open_upload_stream_with_id with a file ID generated using @ref bsoncxx::v1::oid. | ||
| /// | ||
| /// @{ | ||
| v1::gridfs::uploader open_upload_stream( | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this one use the export macro as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Yes, it should be exported.
| /// @throws mongocxx::v1::exception when a client-side error is encountered. | ||
| /// @throws mongocxx::v1::server_error when a server-side error is encountered and all retry attempts have failed. | ||
| /// | ||
| MONGOCXX_ABI_EXPORT_CDECL(void) with_transaction(with_transaction_cb const& fn, v1::transaction const& opts); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Suggest swapping these two parameters only so that a potential user's lambda expression will be the final argument and formatted more nicely:
foo.with_transaction(some_options, [&](client_session& sess) {
    // do stuff
});There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ordering is for consistency with v_noabi API:
mongo-cxx-driver/src/mongocxx/include/mongocxx/v_noabi/mongocxx/client_session.hpp
Lines 211 to 212 in d29bad7
| MONGOCXX_ABI_EXPORT_CDECL(void) | |
| with_transaction(with_transaction_cb cb, options::transaction opts = {}); | 
Given this would also be a departure from the rest of the API's preference of appending option arguments to the parameter list (probably mostly motivated by the = {} pattern), I think it is better to leave the ordering as-is for now.
| /// @{ | ||
| template <typename InputIt, bsoncxx::detail::enable_if_t<is_write_iter<InputIt>::value>* = nullptr> | ||
| bsoncxx::v1::stdx::optional<v1::bulk_write::result> | ||
| bulk_write(InputIt begin, InputIt end, v1::bulk_write::options const& opts = {}) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommend deducing end type separately to support uncommon ranges with distinct sentinel types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect this is probably overkill + personally I'd rather these range-based overload templates be removed from the API in favor of a std::span<T>-based non-template API (so that the private-but-exported *_insert_many helper functions can also be avoided), but nevertheless, updated InputIt end -> Sentinel end and added a simple is_sentinel_for<Sentinel, InputIt> constraint. The constraint only tests support for weak equality comparison, not the full std::sentinel_for concept.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional questions about error reporting. Otherwise LGTM.
| /// - `upserted_count` ("nUpserted" or "upsertedCount") | ||
| /// - `upserted_ids` ("upserted" or "upsertedIds") | ||
| /// | ||
| /// @important The raw server response is translated by mongoc into the Bulk Write API specification format even when | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggests the server reply document
I was thinking it was unnecessary to document the server reply if users would not see it. This seems to be documenting more of the internal implementation than the exposed API.
On further thought: I expect users may see the server reply in the v1::server_error exception. Is that the motivation for documenting the server reply?
| /// - Use `this->code()` to obtain the primary error code (which may equal `this->server_code()`). | ||
| /// - Use `this->server_code()` to obtain the server error code (which may equal `zero`). | ||
| /// Use @ref mongocxx::v1::source_errc to determine the origin of `this->code()`. | ||
| /// @important `this->code()` always returns the raw server error code. Use `this->client_code()` to query the | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indicate a client-side error code, but a server error document is also provided
One possible scenario: a reply might contain only error labels in some client-side error scenarios (e.g. network error). For example:
{"errorLabels" : [ "UnknownTransactionCommitResult", "RetryableWriteError" ]}Reporting the error label appears required in some specs. For example in transactions:
Drivers MUST add error labels to certain errors when commitTransaction fails.
In this scenario, there is no .raw()["code"] to use as the server error code. I am not sure how to best address. Maybe this motivates adding raw / has_error_label methods to v1::exception?
| In light of the realization that the C++ Driver has not been, yet should be, supporting an API to query the error labels for a given error regardless whether it is associated with a server-side error or a client-side error, per Transactions Specification: 
 refactored the proposed API such that  
 This necessarily makes  In short, the table described here no longer excludes the ability to query  These changes motivated an additional fix/improvement, as indicated by clangd diagnostics for stateful exception classes to support nothrow copyability, per ERR60-CPP: 
 and per CppCoreGuidelines E.16: 
 A  
 This has the additional benefit of removing the need for Ro5 boilerplate and the assign-or-destroy-only moved-from state (copy semantics only). This change also highlighted the unnecessary (and incorrect) public declaration of inherited constructors for  class v1::exception : std::system_error {
  private:
    class impl;
    std::unique_ptr<impl> _impl;
  public:
    using std::system_error::system_error;
};
void example() {
  (void)(mongocxx::v1::exception{std::error_code{}}); // error: incomplete type
}Taking this into account,  | 
Summary
Resolves CXX-3236. Followup to #1462 and companion PR to #1469 and #1470. Contains cherry-pick of proposed changes in #1469 (the "cpx: cxx-abi-v1-instance" commit).
This is 5 out of an estimated 7 major PRs which in total are expected to resolve CXX-2745 and CXX-3320.
Related tickets affected by the changes in this PR (applicable only to the v1 API) include (not exhaustive, but tried my best):
document::viewinstead ofdocument::view_or_valuefrom options getters"options::client::ssl_optswithout "ssl=true" in URI"pool::entry::operator->()"bsoncxx::string::view_or_valueingridfs::bucket"#includehygiene in header files"mongocxx::v_noabi::options::index::storage_options"Relative Changelog (v_noabi -> v1)
is_open()tov1::gridfs::downloaderandv1::gridfs::uploaderto extend behavior ofoperator bool().flush()tov1::gridfs::uploader(exposingv_noabi::gridfs::uploader::flush_chunks()) + documenting the existence of underlying buffers for better control over its behavior.chunks_written()tov1::gridfs::uploader(useful information)..upserted_ids()forv1::update_many_resultfor consistency withv1::bulk_write::result(vs.v1::update_one_resultwhich uses the old.upserted_id()).k_unknowntov1::write_concernfor symmetry withv1::read_concern.v1::indexes,v1::pipeline,v1::bulk_write::single, etc.v1::bulk_write::result::raw()to allow access to the raw server response.v1::bulk_write::insert_one::valueis made public.v1::pool::entry::operator->,v1::client::database(), etc. (by removing deleted overloads and ref-qualifiers; premature pessimization).v_noabi::*::view_or_valueis replaced with eithervieworvalue(std::stringforv_noabi::string::view_or_value).value.view.char const*to allow avoiding forced allocations. (We could really use acstring_view...)view, with a few exceptions for "simple" classes wrapping the underlying value with no additional invariants (e.g.v1::bulk_write::insert_one).Optional<view_or_value>->Optional<View>(view_or_valueimplies internal ownership).T const&orOptional<T> const&: returnTorOptional<T>instead.Optional<T> const&) which severely restrict implementation freedom.v1::client_session::client(): return the associated client (must be a mongocxx reference).v1::bulk_write::single::get_*(): straightforward union-like API.constqualifier from member functions and parameters which are not logically const.v1::collection::list_indexes()usesmongoc_collection_t*.v1::databaseparameters inv1::client_encryptionusemongoc_database_t*.v1::clientdatabase operations usemongoc_client_t*.T&for setters (method-chaining) where various functions used to returnvoid.v1::server_errormongocxx::v1::exceptionwith.code() == v1::source_errc::server..code()is always the server error code,.client_code()is the optional client error code, for consistency with the.raw()invariant).v1::event::*void*are labeled with a comment indicating the underlying type.v1::events::server_descriptionis the sole exception due tov1::events::topology_description::servers(), see "Removed" entry below.server_heartbeat_failed::message()return type:string->string_view.v1::read_concern: ignore unsupported values instead of throwing an exception.v1::read_preference: treat invalid read modes ask_primaryinstead of an error (same behavior asmongoc_read_prefs_get_mode()).v1::write_concern.tag(nullptr)ask_defaultinstead of an error..acknowledge_level()errors by converting unsupported values tok_unknownand document "notk_unknown" as a precondition for.to_document()(treat as if unset).v1::gridfs::uploaderflushes its buffer on destruction (it didn't already do this?!).T*parameters are madeT&instead for input/output streams tov1::gridfs::bucket.T*instead ofOptional<T*>for.key_vault_*()inv1::auto_encryptionandv1::client_encryption.v1::search_indexes::create_index()return type to be consistent withv1::indexes::create_one()..comment()with.comment_option()inv1::find_optionsfor consistency with other option classes.v1::collection's bulk write API.Int32->Int64for bulk write API result values and somev1::pipelinefields.id_mapforv1::bulk_write::resultand related API:Size->Int64for keys ofid_map(e.g.v1::bulk_write::result) for better spec accuracy.document::elementtotypes::viewto avoid unnecessary proxy type (element key is always"_id"; useless info) + consistency with other "result" API.v1::bulk_write::*operation single-argument constructors explicit.v1::client_session::options()->.opts()to avoid conflict with::options.with_transaction_cbparameter type fromT*(unconditional not-null) toT&.v_noabi::events::server_description::is_master()(deprecated by CXX-2262, CXX-2263 #841).v_noabi::events::topology_description::server_descriptions: move ownership of underlyingmongoc_server_description_tobjects tov1::events::server_descriptionand avoid the awkward intermediate class. The array itself (not its elements) is deallocated after thestd::vector<v1::events::server_description>is fully constructed.v_noabi::gridfs::chunks_and_bytes_offset: implementation detail not used by any public API.v_noabi::result::gridfs::uploadctor is made internal forv1::gridfs::upload_result.ordered()forv1::insert_one_options(specific tov1::insert_many_options).v_noabi::options::index::base_storage_optionsand related API.v_noabi::read_preference::hedge().v1::client..ssl()API.v_noabi::options::index::haystack*()API.v_noabi::options::index::version(). (Was this meant to be "textIndexVersion"?)v1::server_error(e.g.WriteConcernError,BulkWriteError, etc.).v1::server_error::raw()should continue to suffice as a workaround for obtaining error-specific fields manually.noexcept) via template parameter constraints (e.g. likebsoncxx::v1::document::value's deleter API).v1::events::*:.duration()and.round_trip_time(): changing the return type fromstd::int64_ttostd::chrono::microseconds.v1::events::server_heartbeat_failed: support returning or throwing amongocxx::v1::exceptionfor thebson_error_t?v1::events::server_descriptiondoes not conform to specification forServerDescription.v1::tlsunderspecification and lack of support for allmongoc_ssl_opt_tfields.v1::collection: missing access to raw server response (e.g..rename()), missing support for additional command options (e.g..rename()),missing session overloads (e.g.awkward range templates, use of bulk write operations instead of dedicated database commands (e.g..estimated_document_count()),.insert_one()using bulk write "insertMany" instead of theinsertdatabase command?).indexesvs.search_indexesAPI symmetry and specification compliance (exacerbated by themodelvs. command-specific options vs. common index options situation;v1::search_indexes::modelshould probably be split intoSearchIndexModelandSearchIndexOptions).collation && !is_acknowledgedcondition for.find_and_modify()and etc.).General Notes
\paramor\returnswhen behavior is plainly obvious from the declaration (see: Slack). Only include when at least one parameter or the return value requires documentation of special values and behaviors that is not apparent from the types alone. Prefer\par Preconditionsfor partial parameter documentation.Optional<T>return type for a "field" is "obviously" empty when "unset".0being equivalent to "unset" for anIntfield is not "obvious", thus it is documented.v1::client::start_sessiondocuments: "The client session object MUST be passed as the first argument to all operations that are intended to be executed in the context of a session." -> avoid needing countless repetition of\param sessionparagraphs. The overload complexity should be revisited and addressed at a later time... (CXX-1835)\copydocis not applicable.()is omitted given\ref func_namewhen more than one overload may apply.\{ ... \}Doxygen groups when able (helped by absence of\param).mongocxx::v1::exceptionfor all other runtime errors".ex.what(); encourage programmatic inspection viav1::source_errcandv1::type_errcinstead.errc(as in bsoncxx), of which there are currently only 9 in total (all other errors are deferred to mongoc):instance(multiple object detection: CXX-3320 migrate instance and logger to mongocxx::v1 #1469)server_api(translatefalsereturned by mongoc API for invalid versions into an exception)uri(server_selection_try_once()setter throws instead of returning a boolean)pool(wait queue timeout for.acquire())collection(narrowing conversion fromstd::chrono::durationInt64 into a UInt32 for mongoc API compatibility)indexes(index name"*"is not permitted for.drop_one(), no relevant mongoc API to defer to)v1::gridfs::bucket(GridFS file metadata validation + database commands)v1::gridfs::downloader(runtime GridFS file metadata validation and underlying stream state)v1::gridfs::uploader(runtime GridFS file metadata validation and underlying stream state)v1/database.hppno longer includesv1/collection.hppand all of its transitive dependencies. 🎉void fn(T)andT fn()do not requireTto be a complete type (even whenT = Optional<U>!).collectionmember functions)...bsoncxx::v1::document::view).iterator end() consteven wheniterator begin()(not-const).T*->T&when unconditionally not-null, or by changingOptional<T*>->T*(double-null).Review Focus Items
filter).client_encryption) or the parameter (class) type (e.g.write_concern).Questions for Review
ShouldApplied: now usingstd::unique_ptr<impl> _implbe madevoid* _implinstead (even when aclass implis used) to leave open the possibility of replacingimplwith a mongoc equivalent or vise versa as an ABI compatible change? Or is this "premature optimization" and usingstd::unique_ptr<T>is sufficient?void* _impl;for all mongocxx classes.Regardless of the question above, should classes which can currently be implemented entirely in terms of aApplied: annotated with underlying type when applicable.mongoc_*_ttype usevoid* _impl; // mongoc_*_t, e.g. to avoid double-indirection? (Initial changes propose that only the view-likev1::events::*classes do this.)Is providingRemoved extra overloads: simplechar const*+std::string+string_viewset of overloads premature optimization? Should they be simplified to just a singlestring_viewoverload with an internalstd::stringallocation?string_viewonly.Is removing the deprecatedReverted: removal is premature.v1::read_preference::hedge()premature? (CXX-3241)Is removing the deprecated readConcern + writeConcern + readPreference accessors inApplied: removal is acceptable.v1::clientpremature? (CXX-1939)Is removing the deprecated geoHaystack API forApplied: removal is acceptable.v1::indexespremature? (CXX-1978)ShouldApplied: may add later upon request.mongoc_cursor_clone()be implemented by mongocxx by making cursors copyable? Or shouldmongoc_cursor_clone()be deprecated given its confusing behavior (re-execute the underlying query)?ShouldNeither: spec expectsv1::change_stream::batch_size()supportstd::uint32_tfor consistency withmongoc_cursor_set_batch_size()? Or doesmongoc_cursor_set_batch_size()need to be updated to supportstd::int64_tinstead?Int32.