diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 86055fb6ea..23219b2aa5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -131,7 +131,7 @@ foreach(source ${mongocxx_examples_sources}) set(extra_libs "") set(add_to_run_examples ON) - if (${source} MATCHES "pool") + if (${source} MATCHES "pool|change_streams_examples") list(APPEND extra_libs Threads::Threads) endif() diff --git a/examples/mongocxx/causal_consistency.cpp b/examples/mongocxx/causal_consistency.cpp index 1cda6a3e4b..be3fd3fa17 100644 --- a/examples/mongocxx/causal_consistency.cpp +++ b/examples/mongocxx/causal_consistency.cpp @@ -51,7 +51,7 @@ int EXAMPLES_CDECL main() { using namespace mongocxx; instance inst{}; - client client{mongocxx::uri{"mongodb://localhost/?replicaSet=repl0"}}; + client client{mongocxx::uri{}}; // Start Causal Consistency Example 1 diff --git a/examples/mongocxx/client_session.cpp b/examples/mongocxx/client_session.cpp index 0017029d54..90225eeba1 100644 --- a/examples/mongocxx/client_session.cpp +++ b/examples/mongocxx/client_session.cpp @@ -45,7 +45,7 @@ int EXAMPLES_CDECL main() { // must remain alive for as long as the driver is in use. mongocxx::instance inst{}; - mongocxx::client conn{mongocxx::uri{"mongodb://localhost/?replicaSet=repl0"}}; + mongocxx::client conn{mongocxx::uri{}}; // By default, a session is causally consistent. Pass options::client_session to override // causal consistency. diff --git a/examples/mongocxx/mongodb.com/change_streams_examples.cpp b/examples/mongocxx/mongodb.com/change_streams_examples.cpp new file mode 100644 index 0000000000..b8d420350c --- /dev/null +++ b/examples/mongocxx/mongodb.com/change_streams_examples.cpp @@ -0,0 +1,220 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +using bsoncxx::builder::basic::kvp; +using bsoncxx::builder::basic::make_array; +using bsoncxx::builder::basic::make_document; + +using namespace mongocxx; + +mongocxx::collection +make_test_coll(mongocxx::client& client, bsoncxx::stdx::string_view db_name, bsoncxx::stdx::string_view coll_name) { + write_concern wc_majority; + wc_majority.acknowledge_level(write_concern::level::k_majority); + + read_concern rc_majority; + rc_majority.acknowledge_level(read_concern::level::k_majority); + + auto db = client[db_name]; + auto coll = db[coll_name]; + + coll.drop(); + coll = db.create_collection(coll_name); + + coll.write_concern(wc_majority); + coll.read_concern(rc_majority); + + return coll; +} + +void change_streams_example_1(mongocxx::collection inventory) { + // Start Changestream Example 1 + change_stream stream = inventory.watch(); + auto it = stream.begin(); + while (it == stream.end()) { + // Server returned no new notifications. Restart iteration to poll server. + it = stream.begin(); + } + bsoncxx::document::view next = *it; + // End Changestream Example 1 + + if (next["operationType"].get_string().value != "insert") { + throw std::logic_error{"expected operationType to be 'insert'"}; + } +} + +void change_streams_example_2(mongocxx::collection inventory) { + // Start Changestream Example 2 + options::change_stream options; + options.full_document(bsoncxx::string::view_or_value{"updateLookup"}); + change_stream stream = inventory.watch(options); + auto it = stream.begin(); + while (it == stream.end()) { + // Server returned no new notifications. Restart iteration to poll server. + it = stream.begin(); + } + bsoncxx::document::view next = *it; + // End Changestream Example 2 + + if (next["operationType"].get_string().value != "insert") { + throw std::logic_error{"expected operationType to be 'insert'"}; + } +} + +void change_streams_example_3(mongocxx::collection inventory) { + bsoncxx::stdx::optional next; + + // Get one notification to set `next`. + { + change_stream stream = inventory.watch(); + auto it = stream.begin(); + while (it == stream.end()) { + // Server returned no new notifications. Restart iteration to poll server. + it = stream.begin(); + } + next = bsoncxx::document::value(*it); + } + + // Start Changestream Example 3 + auto resume_token = (*next)["_id"].get_document().value; + options::change_stream options; + options.resume_after(resume_token); + change_stream stream = inventory.watch(options); + auto it = stream.begin(); + while (it == stream.end()) { + // Server returned no new notifications. Restart iteration to poll server. + it = stream.begin(); + } + // End Changestream Example 3 + + if ((*it)["operationType"].get_string().value != "insert") { + throw std::logic_error{"expected operationType to be 'insert'"}; + } +} + +void change_streams_example_4(mongocxx::collection inventory) { + // Create a pipeline with + // [{"$match": {"$or": [{"fullDocument.username": "alice"}, {"operationType": "delete"}]}}] + + // Start Changestream Example 4 + mongocxx::pipeline cs_pipeline; + cs_pipeline.match(make_document( + kvp("$or", + make_array( + make_document(kvp("fullDocument.username", "alice")), make_document(kvp("operationType", "delete")))))); + + change_stream stream = inventory.watch(cs_pipeline); + auto it = stream.begin(); + while (it == stream.end()) { + // Server returned no new notifications. Restart iteration to poll server. + it = stream.begin(); + } + // End Changestream Example 4 + + if ((*it)["operationType"].get_string().value != "insert") { + throw std::logic_error{"expected operationType to be 'insert'"}; + } +} + +} // namespace + +int EXAMPLES_CDECL main() { + if (char const* const topology_env = std::getenv("MONGOCXX_TEST_TOPOLOGY")) { + auto const topology = std::string(topology_env); + if (topology != "replica") { + std::cerr << "Skipping: change_streams example requires a replica set" << std::endl; + return 0; + } + } + + // The mongocxx::instance constructor and destructor initialize and shut down the driver, + // respectively. Therefore, a mongocxx::instance must be created before using the driver and + // must remain alive for as long as the driver is in use. + mongocxx::instance const inst{}; + + std::function const examples[] = { + change_streams_example_1, + change_streams_example_2, + change_streams_example_3, + change_streams_example_4, + }; + + mongocxx::pool pool{mongocxx::uri{}}; + + for (auto const& example : examples) { + auto client = pool.acquire(); + + collection inventory = make_test_coll(*client, "streams", "events"); + + std::atomic_bool insert_thread_done; + insert_thread_done.store(false); + + // Start a thread to repeatedly insert documents to generate notifications. + auto insert_thread = std::thread{[&pool, &insert_thread_done] { + auto client = pool.acquire(); + auto inventory = (*client)["streams"]["events"]; + while (true) { + auto doc = make_document(kvp("username", "alice")); + inventory.insert_one(doc.view()); + if (insert_thread_done) { + return; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + }}; + + try { + example(std::move(inventory)); + insert_thread_done = true; + insert_thread.join(); + } catch (std::logic_error const& e) { + std::cerr << e.what() << std::endl; + insert_thread_done = true; + insert_thread.detach(); + return EXIT_FAILURE; + } catch (...) { + insert_thread_done = true; + insert_thread.detach(); + throw; + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/mongocxx/mongodb.com/transactions_examples.cpp b/examples/mongocxx/mongodb.com/transactions_examples.cpp new file mode 100644 index 0000000000..a3f03a621c --- /dev/null +++ b/examples/mongocxx/mongodb.com/transactions_examples.cpp @@ -0,0 +1,290 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +using bsoncxx::builder::basic::kvp; +using bsoncxx::builder::basic::make_document; + +using namespace mongocxx; + +void transactions_intro_example_1(mongocxx::client& client) { + // Start Transactions Intro Example 1 + auto update_employee_info = [](client_session& session) { + auto& client = session.client(); + auto employees = client["hr"]["employees"]; + auto events = client["reporting"]["events"]; + + options::transaction txn_opts; + read_concern rc; + rc.acknowledge_level(read_concern::level::k_snapshot); + txn_opts.read_concern(rc); + write_concern wc; + wc.acknowledge_level(write_concern::level::k_majority); + txn_opts.write_concern(wc); + session.start_transaction(txn_opts); + + try { + employees.update_one( + make_document(kvp("employee", 3)), + make_document(kvp("$set", make_document(kvp("status", "Inactive"))))); + events.insert_one(make_document( + kvp("employee", 3), kvp("status", make_document(kvp("new", "Inactive"), kvp("old", "Active"))))); + } catch (operation_exception const& oe) { + std::cout << "Caught exception during transaction, aborting." << std::endl; + session.abort_transaction(); + throw oe; + } + + while (true) { + try { + session.commit_transaction(); // Uses write concern set at transaction start. + std::cout << "Transaction committed." << std::endl; + break; + } catch (operation_exception const& oe) { + // Can retry commit. + if (oe.has_error_label("UnknownTransactionCommitResult")) { + std::cout << "UnknownTransactionCommitResult, retrying commit operation ..." << std::endl; + continue; + } else { + std::cout << "Error during commit ..." << std::endl; + throw oe; + } + } + } + }; + // End Transactions Intro Example 1 + + auto session = client.start_session(); + update_employee_info(session); +} + +void transactions_retry_example_1(mongocxx::client& client) { + // Start Transactions Retry Example 1 + using transaction_func = std::function; + auto run_transaction_with_retry = [](transaction_func txn_func, client_session& session) { + while (true) { + try { + txn_func(session); // performs transaction. + break; + } catch (operation_exception const& oe) { + std::cout << "Transaction aborted. Caught exception during transaction." << std::endl; + // If transient error, retry the whole transaction. + if (oe.has_error_label("TransientTransactionError")) { + std::cout << "TransientTransactionError, retrying transaction ..." << std::endl; + continue; + } else { + throw oe; + } + } + } + }; + // End Transactions Retry Example 1 + + auto session = client.start_session(); + run_transaction_with_retry( + [&client](client_session& session) { + session.start_transaction(); + auto coll = client["test"]["coll"]; + coll.insert_one(make_document(kvp("x", 1))); + session.commit_transaction(); + }, + session); +} + +void transactions_retry_example_2(mongocxx::client& client) { + // Start Transactions Retry Example 2 + auto commit_with_retry = [](client_session& session) { + while (true) { + try { + session.commit_transaction(); // Uses write concern set at transaction start. + std::cout << "Transaction committed." << std::endl; + break; + } catch (operation_exception const& oe) { + // Can retry commit + if (oe.has_error_label("UnknownTransactionCommitResult")) { + std::cout << "UnknownTransactionCommitResult, retrying commit operation ..." << std::endl; + continue; + } else { + std::cout << "Error during commit ..." << std::endl; + throw oe; + } + } + } + }; + // End Transactions Retry Example 2 + + auto session = client.start_session(); + session.start_transaction(); + auto coll = client["test"]["coll"]; + coll.insert_one(make_document(kvp("x", 1))); + commit_with_retry(session); +} + +void transactions_retry_example_3(mongocxx::client& client) { + // Start Transactions Retry Example 3 + using transaction_func = std::function; + auto run_transaction_with_retry = [](transaction_func txn_func, client_session& session) { + while (true) { + try { + txn_func(session); // performs transaction. + break; + } catch (operation_exception const& oe) { + std::cout << "Transaction aborted. Caught exception during transaction." << std::endl; + // If transient error, retry the whole transaction. + if (oe.has_error_label("TransientTransactionError")) { + std::cout << "TransientTransactionError, retrying transaction ..." << std::endl; + continue; + } else { + throw oe; + } + } + } + }; + + auto commit_with_retry = [](client_session& session) { + while (true) { + try { + session.commit_transaction(); // Uses write concern set at transaction start. + std::cout << "Transaction committed." << std::endl; + break; + } catch (operation_exception const& oe) { + // Can retry commit + if (oe.has_error_label("UnknownTransactionCommitResult")) { + std::cout << "UnknownTransactionCommitResult, retrying commit operation ..." << std::endl; + continue; + } else { + std::cout << "Error during commit ..." << std::endl; + throw oe; + } + } + } + }; + + // Updates two collections in a transaction + auto update_employee_info = [&](client_session& session) { + auto& client = session.client(); + auto employees = client["hr"]["employees"]; + auto events = client["reporting"]["events"]; + + options::transaction txn_opts; + read_concern rc; + rc.acknowledge_level(read_concern::level::k_snapshot); + txn_opts.read_concern(rc); + write_concern wc; + wc.acknowledge_level(write_concern::level::k_majority); + txn_opts.write_concern(wc); + + session.start_transaction(txn_opts); + + try { + employees.update_one( + make_document(kvp("employee", 3)), + make_document(kvp("$set", make_document(kvp("status", "Inactive"))))); + events.insert_one(make_document( + kvp("employee", 3), kvp("status", make_document(kvp("new", "Inactive"), kvp("old", "Active"))))); + } catch (operation_exception const& oe) { + std::cout << "Caught exception during transaction, aborting." << std::endl; + session.abort_transaction(); + throw oe; + } + + commit_with_retry(session); + }; + + auto session = client.start_session(); + try { + run_transaction_with_retry(update_employee_info, session); + } catch (operation_exception const& oe) { + // Do something with error. + throw oe; + } + // End Transactions Retry Example 3 +} + +} // namespace + +int EXAMPLES_CDECL main() { + if (char const* const topology_env = std::getenv("MONGOCXX_TEST_TOPOLOGY")) { + auto const topology = std::string(topology_env); + if (topology != "replica") { + std::cerr << "Skipping: transactions example requires a replica set" << std::endl; + return 0; + } + } + + // The mongocxx::instance constructor and destructor initialize and shut down the driver, + // respectively. Therefore, a mongocxx::instance must be created before using the driver and + // must remain alive for as long as the driver is in use. + mongocxx::instance const inst{}; + + std::function const examples[] = { + transactions_intro_example_1, + transactions_retry_example_1, + transactions_retry_example_2, + transactions_retry_example_3, + }; + + client client{uri{}}; + + { + auto const reply = client["admin"].run_command(make_document(kvp("isMaster", 1))); + auto const max_wire_version = reply.view()["maxWireVersion"]; + if (!max_wire_version) { + std::cerr << "Skipping: could not determine max wire version" << std::endl; + return EXIT_SUCCESS; + } + if (max_wire_version.type() != bsoncxx::type::k_int32) { + throw std::logic_error{"max wire version is not int32"}; + } + if (max_wire_version.get_int32().value < 7) { + std::cerr << "Skipping: transactions example requires max wire version is >= 7" << std::endl; + return EXIT_SUCCESS; + } + } + + for (auto const& example : examples) { + /* Create necessary collections. */ + client["hr"]["employees"].drop(); + client["hr"]["employees"].insert_one(make_document(kvp("employee", 3), kvp("status", "Active"))); + client["reporting"]["events"].drop(); + client["reporting"]["events"].insert_one(make_document(kvp("employee", 3), kvp("status", "Active"))); + + try { + example(client); + } catch (std::logic_error const& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/src/mongocxx/test/v_noabi/versioned_api.cpp b/examples/mongocxx/mongodb.com/versioned_api_examples.cpp similarity index 59% rename from src/mongocxx/test/v_noabi/versioned_api.cpp rename to examples/mongocxx/mongodb.com/versioned_api_examples.cpp index 2cda74640e..3773662423 100644 --- a/src/mongocxx/test/v_noabi/versioned_api.cpp +++ b/examples/mongocxx/mongodb.com/versioned_api_examples.cpp @@ -12,8 +12,15 @@ // 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 @@ -23,46 +30,104 @@ #include #include -#include +#include -#include +namespace { + +using bsoncxx::builder::basic::kvp; +using bsoncxx::builder::basic::make_document; using namespace mongocxx; -static bool has_api_version_1() { - // API Version 1 was introduced in 5.0. - return test_util::get_max_wire_version() >= 13; +// API Version 1 was introduced in 5.0. +bool has_api_version_1() { + mongocxx::client client{mongocxx::uri{}}; + + auto const reply = client["admin"].run_command(make_document(kvp("isMaster", 1))); + auto const max_wire_version = reply.view()["maxWireVersion"]; + + if (!max_wire_version) { + return false; + } + + if (max_wire_version.type() != bsoncxx::type::k_int32) { + throw std::logic_error{"max wire version is not int32"}; + } + + if (max_wire_version.get_int32().value < 13) { + return false; + } + + return true; } -static bool has_api_version_1_with_count() { +bool has_api_version_1_with_count() { if (!has_api_version_1()) { return false; } - auto const version = test_util::get_server_version(); + mongocxx::client client{mongocxx::uri{}}; + + auto const status = client["admin"].run_command(make_document(kvp("serverStatus", 1))); + auto const version = bsoncxx::string::to_string(status.view()["version"].get_string().value); + + // mongocxx::test_util::compare_versions + auto compare_versions = [](std::string version1, std::string version2) -> std::int32_t { + // mongocxx::test_util::parse_version + auto const parse_version = [](std::string version) -> std::vector { + std::vector elements; + std::stringstream ss{version}; + std::string element; + + while (std::getline(ss, element, '.')) { + elements.push_back(std::stoi(element)); + } + + return elements; + }; + + std::vector v1 = parse_version(version1); + std::vector v2 = parse_version(version2); + + for (std::size_t i = 0; i < std::min(v1.size(), v2.size()); ++i) { + std::int32_t difference = v1[i] - v2[i]; + + if (difference != 0) { + return difference; + } + } + + return 0; + }; // BACKPORT-12171: count command was backported to 5.0.9. - if (test_util::compare_versions(version, "5.0") == 0 && test_util::compare_versions(version, "5.0.9") >= 0) { + if (compare_versions(version, "5.0") == 0 && compare_versions(version, "5.0.9") >= 0) { return true; } // BACKPORT-12170: count command was backported to 5.3.2. - if (test_util::compare_versions(version, "5.3") == 0 && test_util::compare_versions(version, "5.3.2") >= 0) { + if (compare_versions(version, "5.3") == 0 && compare_versions(version, "5.3.2") >= 0) { return true; } + // mongocxx::test_util::get_server_version + auto const get_server_version = [&status]() { + return bsoncxx::string::to_string(status.view()["version"].get_string().value); + }; + // SERVER-63850: count command was added in 6.0. - return test_util::newer_than("6.0"); + return (compare_versions(get_server_version(), version) >= 0); } // We'll format many of these examples by hand // clang-format off -TEST_CASE("Versioned API, non-strict") { - instance::current(); +void versioned_api_example_1() { if (!has_api_version_1()) { + std::cerr << "Skipping: Versioned API Example 1 requires MongoDB 5.0 or newer" << std::endl; return; } + // Start Versioned API Example 1 using namespace mongocxx; uri client_uri{"mongodb://localhost"}; @@ -78,11 +143,12 @@ TEST_CASE("Versioned API, non-strict") { // End Versioned API Example 1 } -TEST_CASE("Versioned API, strict") { - instance::current(); +void versioned_api_example_2() { if (!has_api_version_1()) { + std::cerr << "Skipping: Versioned API Example 2 requires MongoDB 5.0 or newer" << std::endl; return; } + // Start Versioned API Example 2 using namespace mongocxx; uri client_uri{"mongodb://localhost"}; @@ -99,11 +165,12 @@ TEST_CASE("Versioned API, strict") { // End Versioned API Example 2 } -TEST_CASE("Versioned API, non-strict, for commands/features outside versioned API") { - instance::current(); +void versioned_api_example_3() { if (!has_api_version_1()) { + std::cerr << "Skipping: Versioned API Example 3 requires MongoDB 5.0 or newer" << std::endl; return; } + // Start Versioned API Example 3 using namespace mongocxx; uri client_uri{"mongodb://localhost"}; @@ -120,11 +187,12 @@ TEST_CASE("Versioned API, non-strict, for commands/features outside versioned AP // End Versioned API Example 3 } -TEST_CASE("Versioned API, non-strict with deprecation errors") { - instance::current(); +void versioned_api_example_4() { if (!has_api_version_1()) { + std::cerr << "Skipping: Versioned API Example 4 requires MongoDB 5.0 or newer" << std::endl; return; } + // Start Versioned API Example 4 using namespace mongocxx; uri client_uri{"mongodb://localhost"}; @@ -144,19 +212,19 @@ TEST_CASE("Versioned API, non-strict with deprecation errors") { /// Not actually a version function. Just used to appear in the documentation examples "as if" we /// were creating a date from a timestamp string -static bsoncxx::types::b_date iso_string_to_bson_datetime(std::string const&) { +bsoncxx::types::b_date iso_string_to_bson_datetime(std::string const&) { return bsoncxx::types::b_date(std::chrono::milliseconds{0}); } -TEST_CASE("Versioned API, with insert-many for 'count' migration") { - instance::current(); - +void versioned_api_example_5() { if (!has_api_version_1()) { + std::cerr << "Skipping: Versioned API Example 5 requires MongoDB 5.0 or newer" << std::endl; return; } // Do *not* run this test if count command is included in API Version 1. if (has_api_version_1_with_count()) { + std::cerr << "Skipping: Versioned API Example 5 requires the 'count' command is NOT supported" << std::endl; return; } @@ -199,13 +267,20 @@ TEST_CASE("Versioned API, with insert-many for 'count' migration") { try { db.run_command(make_document(kvp("count", "sales"))); - FAIL_CHECK("Did not throw for apiStrict:true usage of old command"); + throw std::logic_error{"Did not throw for apiStrict:true usage of old command"}; } catch (mongocxx::operation_exception const& error) { - INFO(error.what()); - CHECK(error.code().value() == 323); - CHECK_THAT( - error.what(), - Catch::Matchers::StartsWith("Provided apiStrict:true, but the command count is not in API Version 1.")); + auto const what = std::string(error.what()); + auto const value = error.code().value(); + + std::cout << what << std::endl; + + if (value != 323) { + throw std::logic_error{"expected error code to be 323, got: " + std::to_string(value)}; + } + + if (what.find("Provided apiStrict:true, but the command count is not in API Version 1.") != 0) { + throw std::logic_error{"expected error message to start with 'Provided apiStrict:true'"}; + } } #if 0 @@ -225,7 +300,32 @@ TEST_CASE("Versioned API, with insert-many for 'count' migration") { // Start Versioned API Example 7 std::int64_t num_documents = sales.count_documents(make_document()); // End Versioned API Example 7 + // Start Versioned API Example 8 - CHECK(num_documents == 8); + if (num_documents != 8) { + throw std::logic_error{"expected the number of documents to equal 8"}; + } // End Versioned API Example 8 } + +} // namespace + +int EXAMPLES_CDECL main() { + // The mongocxx::instance constructor and destructor initialize and shut down the driver, + // respectively. Therefore, a mongocxx::instance must be created before using the driver and + // must remain alive for as long as the driver is in use. + mongocxx::instance const inst{}; + + try { + versioned_api_example_1(); + versioned_api_example_2(); + versioned_api_example_3(); + versioned_api_example_4(); + versioned_api_example_5(); + } catch (std::logic_error const& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/examples/mongocxx/with_transaction.cpp b/examples/mongocxx/with_transaction.cpp index e07779bf1f..0ab4acfb4d 100644 --- a/examples/mongocxx/with_transaction.cpp +++ b/examples/mongocxx/with_transaction.cpp @@ -64,7 +64,7 @@ int EXAMPLES_CDECL main() { // 'mongodb://mongodb0.example.com:27017,mongodb1.example.com:27017/?replicaSet=myRepl' // For a sharded cluster, connect to the mongos instances; e.g. // uriString = 'mongodb://mongos0.example.com:27017,mongos1.example.com:27017/' - mongocxx::client client{mongocxx::uri{"mongodb://localhost/?replicaSet=repl0"}}; + mongocxx::client client{mongocxx::uri{}}; // Prepare to set majority write explicitly. Note: on Atlas deployments this won't always be // needed. The suggested Atlas connection string includes majority write concern by default. diff --git a/src/mongocxx/test/CMakeLists.txt b/src/mongocxx/test/CMakeLists.txt index 27dd599062..86e9d6f325 100644 --- a/src/mongocxx/test/CMakeLists.txt +++ b/src/mongocxx/test/CMakeLists.txt @@ -117,7 +117,6 @@ set(mongocxx_test_sources_extra v_noabi/client_helpers.cpp v_noabi/instance.cpp v_noabi/logging.cpp - v_noabi/versioned_api.cpp ) file(GLOB_RECURSE mongocxx_test_headers @@ -168,9 +167,6 @@ target_link_libraries(test_mongohouse_specs PRIVATE spec_test_common client_help add_executable(test_unified_format_specs spec/unified_tests/operations.cpp spec/unified_tests/runner.cpp) target_link_libraries(test_unified_format_specs PRIVATE spec_test_common client_helpers) -add_executable(test_versioned_api v_noabi/versioned_api.cpp) -target_link_libraries(test_versioned_api PRIVATE spec_test_common client_helpers) - # Common target properties for test executables. add_library(mongocxx_test_properties INTERFACE) @@ -213,7 +209,6 @@ set_property( test_retryable_reads_specs test_transactions_specs test_unified_format_specs - test_versioned_api APPEND PROPERTY LINK_LIBRARIES mongocxx::test_properties_with_main ) @@ -243,7 +238,6 @@ foreach(test_name retryable_reads_specs transactions_specs unified_format_specs - versioned_api ) add_test(NAME ${test_name} COMMAND test_${test_name} --reporter compact --allow-running-no-tests) endforeach() diff --git a/src/mongocxx/test/v_noabi/change_streams.cpp b/src/mongocxx/test/v_noabi/change_streams.cpp index aa7e0cf840..25dd342e77 100644 --- a/src/mongocxx/test/v_noabi/change_streams.cpp +++ b/src/mongocxx/test/v_noabi/change_streams.cpp @@ -430,111 +430,6 @@ TEST_CASE("Give an invalid pipeline", "[change_stream]") { } } -TEST_CASE("Documentation Examples", "[change_stream]") { - instance::current(); - mongocxx::pool pool{uri{}, options::pool(test_util::add_test_server_api())}; - auto client = pool.acquire(); - if (!test_util::is_replica_set()) { - SKIP("change streams require replica set"); - } - - collection inventory = make_test_coll(*client, "streams", "events"); - - std::atomic_bool insert_thread_done; - insert_thread_done.store(false); - // Start a thread to repeatedly insert documents to generate notifications. - auto insert_thread = std::thread{[&pool, &insert_thread_done] { - auto client = pool.acquire(); - auto inventory = (*client)["streams"]["events"]; - while (true) { - auto doc = make_document(kvp("username", "alice")); - inventory.insert_one(doc.view()); - if (insert_thread_done) { - return; - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - }}; - - SECTION("Example 1") { - // Start Changestream Example 1 - change_stream stream = inventory.watch(); - auto it = stream.begin(); - while (it == stream.end()) { - // Server returned no new notifications. Restart iteration to poll server. - it = stream.begin(); - } - bsoncxx::document::view next = *it; - // End Changestream Example 1 - REQUIRE(next["operationType"].get_string().value == "insert"); - } - - SECTION("Example 2") { - // Start Changestream Example 2 - options::change_stream options; - options.full_document(bsoncxx::string::view_or_value{"updateLookup"}); - change_stream stream = inventory.watch(options); - auto it = stream.begin(); - while (it == stream.end()) { - // Server returned no new notifications. Restart iteration to poll server. - it = stream.begin(); - } - bsoncxx::document::view next = *it; - // End Changestream Example 2 - REQUIRE(next["operationType"].get_string().value == "insert"); - } - - SECTION("Example 3") { - bsoncxx::stdx::optional next; - // Get one notification to set `next`. - { - change_stream stream = inventory.watch(); - auto it = stream.begin(); - while (it == stream.end()) { - // Server returned no new notifications. Restart iteration to poll server. - it = stream.begin(); - } - next = bsoncxx::document::value(*it); - } - // Start Changestream Example 3 - auto resume_token = (*next)["_id"].get_document().value; - options::change_stream options; - options.resume_after(resume_token); - change_stream stream = inventory.watch(options); - auto it = stream.begin(); - while (it == stream.end()) { - // Server returned no new notifications. Restart iteration to poll server. - it = stream.begin(); - } - // End Changestream Example 3 - REQUIRE((*it)["operationType"].get_string().value == "insert"); - } - - SECTION("Example 4") { - // Create a pipeline with - // [{"$match": {"$or": [{"fullDocument.username": "alice"}, {"operationType": "delete"}]}}] - - // Start Changestream Example 4 - mongocxx::pipeline cs_pipeline; - cs_pipeline.match(make_document(kvp( - "$or", - make_array( - make_document(kvp("fullDocument.username", "alice")), make_document(kvp("operationType", "delete")))))); - - change_stream stream = inventory.watch(cs_pipeline); - auto it = stream.begin(); - while (it == stream.end()) { - // Server returned no new notifications. Restart iteration to poll server. - it = stream.begin(); - } - // End Changestream Example 4 - REQUIRE((*it)["operationType"].get_string().value == "insert"); - } - - insert_thread_done = true; - insert_thread.join(); -} - TEST_CASE("Watch 2 collections", "[change_stream]") { instance::current(); client client{uri{}, test_util::add_test_server_api()}; diff --git a/src/mongocxx/test/v_noabi/transactions.cpp b/src/mongocxx/test/v_noabi/transactions.cpp index 7c5292568f..24e3cd850f 100644 --- a/src/mongocxx/test/v_noabi/transactions.cpp +++ b/src/mongocxx/test/v_noabi/transactions.cpp @@ -214,214 +214,6 @@ TEST_CASE("Transaction tests", "[transactions]") { } } -TEST_CASE("Transactions Documentation Examples", "[transactions]") { - instance::current(); - client client{uri{}, test_util::add_test_server_api()}; - - if (!test_util::is_replica_set()) { - SKIP("transactions tests require replica set"); - } else if (test_util::get_max_wire_version() < 7) { - SKIP("transactions tests require max wire version is >= 7"); - } - - /* Create necessary collections. */ - client["hr"]["employees"].drop(); - client["hr"]["employees"].insert_one(make_document(kvp("employee", 3), kvp("status", "Active"))); - client["reporting"]["events"].drop(); - client["reporting"]["events"].insert_one(make_document(kvp("employee", 3), kvp("status", "Active"))); - - SECTION("Intro Example 1") { - // Start Transactions Intro Example 1 - auto update_employee_info = [](client_session& session) { - auto& client = session.client(); - auto employees = client["hr"]["employees"]; - auto events = client["reporting"]["events"]; - - options::transaction txn_opts; - read_concern rc; - rc.acknowledge_level(read_concern::level::k_snapshot); - txn_opts.read_concern(rc); - write_concern wc; - wc.acknowledge_level(write_concern::level::k_majority); - txn_opts.write_concern(wc); - session.start_transaction(txn_opts); - - try { - employees.update_one( - make_document(kvp("employee", 3)), - make_document(kvp("$set", make_document(kvp("status", "Inactive"))))); - events.insert_one(make_document( - kvp("employee", 3), kvp("status", make_document(kvp("new", "Inactive"), kvp("old", "Active"))))); - } catch (operation_exception const& oe) { - std::cout << "Caught exception during transaction, aborting." << std::endl; - session.abort_transaction(); - throw oe; - } - - while (true) { - try { - session.commit_transaction(); // Uses write concern set at transaction start. - std::cout << "Transaction committed." << std::endl; - break; - } catch (operation_exception const& oe) { - // Can retry commit. - if (oe.has_error_label("UnknownTransactionCommitResult")) { - std::cout << "UnknownTransactionCommitResult, retrying commit operation ..." << std::endl; - continue; - } else { - std::cout << "Error during commit ..." << std::endl; - throw oe; - } - } - } - }; - // End Transactions Intro Example 1 - auto session = client.start_session(); - update_employee_info(session); - } - - SECTION("Retry Example 1") { - // Start Transactions Retry Example 1 - using transaction_func = std::function; - auto run_transaction_with_retry = [](transaction_func txn_func, client_session& session) { - while (true) { - try { - txn_func(session); // performs transaction. - break; - } catch (operation_exception const& oe) { - std::cout << "Transaction aborted. Caught exception during transaction." << std::endl; - // If transient error, retry the whole transaction. - if (oe.has_error_label("TransientTransactionError")) { - std::cout << "TransientTransactionError, retrying transaction ..." << std::endl; - continue; - } else { - throw oe; - } - } - } - }; - // End Transactions Retry Example 1 - auto session = client.start_session(); - run_transaction_with_retry( - [&client](client_session& session) { - session.start_transaction(); - auto coll = client["test"]["coll"]; - coll.insert_one(make_document(kvp("x", 1))); - session.commit_transaction(); - }, - session); - } - - SECTION("Retry Example 2") { - // Start Transactions Retry Example 2 - auto commit_with_retry = [](client_session& session) { - while (true) { - try { - session.commit_transaction(); // Uses write concern set at transaction start. - std::cout << "Transaction committed." << std::endl; - break; - } catch (operation_exception const& oe) { - // Can retry commit - if (oe.has_error_label("UnknownTransactionCommitResult")) { - std::cout << "UnknownTransactionCommitResult, retrying commit operation ..." << std::endl; - continue; - } else { - std::cout << "Error during commit ..." << std::endl; - throw oe; - } - } - } - }; - // End Transactions Retry Example 2 - auto session = client.start_session(); - session.start_transaction(); - auto coll = client["test"]["coll"]; - coll.insert_one(make_document(kvp("x", 1))); - commit_with_retry(session); - } - - SECTION("Retry Example 3") { - // Start Transactions Retry Example 3 - using transaction_func = std::function; - auto run_transaction_with_retry = [](transaction_func txn_func, client_session& session) { - while (true) { - try { - txn_func(session); // performs transaction. - break; - } catch (operation_exception const& oe) { - std::cout << "Transaction aborted. Caught exception during transaction." << std::endl; - // If transient error, retry the whole transaction. - if (oe.has_error_label("TransientTransactionError")) { - std::cout << "TransientTransactionError, retrying transaction ..." << std::endl; - continue; - } else { - throw oe; - } - } - } - }; - - auto commit_with_retry = [](client_session& session) { - while (true) { - try { - session.commit_transaction(); // Uses write concern set at transaction start. - std::cout << "Transaction committed." << std::endl; - break; - } catch (operation_exception const& oe) { - // Can retry commit - if (oe.has_error_label("UnknownTransactionCommitResult")) { - std::cout << "UnknownTransactionCommitResult, retrying commit operation ..." << std::endl; - continue; - } else { - std::cout << "Error during commit ..." << std::endl; - throw oe; - } - } - } - }; - - // Updates two collections in a transaction - auto update_employee_info = [&](client_session& session) { - auto& client = session.client(); - auto employees = client["hr"]["employees"]; - auto events = client["reporting"]["events"]; - - options::transaction txn_opts; - read_concern rc; - rc.acknowledge_level(read_concern::level::k_snapshot); - txn_opts.read_concern(rc); - write_concern wc; - wc.acknowledge_level(write_concern::level::k_majority); - txn_opts.write_concern(wc); - - session.start_transaction(txn_opts); - - try { - employees.update_one( - make_document(kvp("employee", 3)), - make_document(kvp("$set", make_document(kvp("status", "Inactive"))))); - events.insert_one(make_document( - kvp("employee", 3), kvp("status", make_document(kvp("new", "Inactive"), kvp("old", "Active"))))); - } catch (operation_exception const& oe) { - std::cout << "Caught exception during transaction, aborting." << std::endl; - session.abort_transaction(); - throw oe; - } - - commit_with_retry(session); - }; - - auto session = client.start_session(); - try { - run_transaction_with_retry(update_employee_info, session); - } catch (operation_exception const& oe) { - // Do something with error. - throw oe; - } - // End Transactions Retry Example 3 - } -} - TEST_CASE("Transactions Mongos Pinning Prose Tests", "[transactions]") { instance::current();