diff --git a/.mci.yml b/.mci.yml index 7f35da88d4..aba3a0285f 100644 --- a/.mci.yml +++ b/.mci.yml @@ -10,9 +10,10 @@ exec_timeout_secs: 3600 variables: # If updating mongoc_version_minimum, also update: - # - the default value of --c-driver-build-ref in etc/make_release.py # - LIBBSON_REQUIRED_VERSION and LIBMONGOC_REQUIRED_VERSION in CMakeLists.txt # - the version of pkg:github/mongodb/mongo-c-driver in etc/purls.txt + # - the default value of --c-driver-build-ref in etc/make_release.py + # Only LIBMONGOC_DOWNLOAD_VERSION needs to be updated when pinning to an unreleased commit. mongoc_version_minimum: &mongoc_version_minimum "1.28.0" integration_matrix: @@ -1292,7 +1293,7 @@ task_groups: binary: bash args: [-c, rm -rf *] - - name: test_atlas_task_group_search_indexes + - name: test_atlas_task_group_search_indexes_7.0 setup_group: - func: "setup" - func: "clone_drivers-evergreen-tools" @@ -1320,6 +1321,34 @@ task_groups: tasks: - test_search_index_helpers + - name: test_atlas_task_group_search_indexes_8.0 + setup_group: + - func: "setup" + - func: "clone_drivers-evergreen-tools" + - command: subprocess.exec + params: + working_dir: mongo-cxx-driver + binary: bash + add_expansions_to_env: true + env: + MONGODB_VERSION: '8.0' + args: [-c, "${DRIVERS_TOOLS}/.evergreen/atlas/setup-atlas-cluster.sh"] + - command: expansions.update + # Expected to set MONGODB_URI expansion. + params: + file: mongo-cxx-driver/atlas-expansion.yml + teardown_group: + - command: subprocess.exec + params: + working_dir: mongo-cxx-driver + binary: bash + add_expansions_to_env: true + args: [-c, "${DRIVERS_TOOLS}/.evergreen/atlas/teardown-atlas-cluster.sh"] + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - test_search_index_helpers + ####################################### # Buildvariants # @@ -1360,6 +1389,14 @@ buildvariants: <<: *integration_matrix_expansions_linux <<: *integration_matrix_tasks_single + - name: integration-ubuntu2004-8.0-single + display_name: "Ubuntu 20.04 Debug (MongoDB 8.0)" + run_on: ubuntu2004-large + expansions: + mongodb_version: "8.0" + <<: *integration_matrix_expansions_linux + <<: *integration_matrix_tasks_single + - name: integration-ubuntu2004-7.0-single display_name: "Ubuntu 20.04 Debug (MongoDB 7.0)" run_on: ubuntu2004-large @@ -1416,6 +1453,14 @@ buildvariants: <<: *integration_matrix_expansions_windows_vs2019 <<: *integration_matrix_tasks_single + - name: integration-vs2019-8.0-single + display_name: "Windows (VS 2019) Debug (MongoDB 8.0)" + run_on: windows-vsCurrent-large + expansions: + mongodb_version: "8.0" + <<: *integration_matrix_expansions_windows_vs2019 + <<: *integration_matrix_tasks_single + - name: integration-vs2019-7.0-single display_name: "Windows (VS 2019) Debug (MongoDB 7.0)" run_on: windows-vsCurrent-large @@ -1472,6 +1517,14 @@ buildvariants: <<: *integration_matrix_expansions_linux <<: *integration_matrix_tasks_replica + - name: integration-ubuntu2004-8.0-replica + display_name: "Ubuntu 20.04 Debug replica set (MongoDB 8.0)" + run_on: ubuntu2004-large + expansions: + mongodb_version: "8.0" + <<: *integration_matrix_expansions_linux + <<: *integration_matrix_tasks_replica + - name: integration-ubuntu2004-7.0-replica display_name: "Ubuntu 20.04 Debug replica set (MongoDB 7.0)" run_on: ubuntu2004-large @@ -1528,6 +1581,14 @@ buildvariants: <<: *integration_matrix_expansions_linux <<: *integration_matrix_tasks_sharded + - name: integration-ubuntu2004-8.0-sharded + display_name: "Ubuntu 20.04 Debug sharded cluster (MongoDB 8.0)" + run_on: ubuntu2004-large + expansions: + mongodb_version: "8.0" + <<: *integration_matrix_expansions_linux + <<: *integration_matrix_tasks_sharded + - name: integration-ubuntu2004-7.0-sharded display_name: "Ubuntu 20.04 Debug sharded cluster (MongoDB 7.0)" run_on: ubuntu2004-large @@ -1941,7 +2002,24 @@ buildvariants: tasks: - name: compile_without_tests - name: compile_macro_guard_tests - - name: test_atlas_task_group_search_indexes + + - name: atlas-search-indexes-7.0 + display_name: "Atlas Search Indexes (7.0)" + expansions: + build_type: "Debug" + run_on: + - ubuntu2004-build + tasks: + - name: test_atlas_task_group_search_indexes_7.0 + + - name: atlas-search-indexes-8.0 + display_name: "Atlas Search Indexes (8.0)" + expansions: + build_type: "Debug" + run_on: + - ubuntu2004-build + tasks: + - name: test_atlas_task_group_search_indexes_8.0 - name: ubuntu2004-debug-gcc display_name: "Ubuntu 20.04 Debug (GCC)" diff --git a/CMakeLists.txt b/CMakeLists.txt index 1617b2d2a7..cbf0bedc0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ set(LIBBSON_REQUIRED_ABI_VERSION 1.0) # Also update etc/purls.txt. set(LIBMONGOC_REQUIRED_VERSION 1.28.0) -set(LIBMONGOC_DOWNLOAD_VERSION 1.28.0) +set(LIBMONGOC_DOWNLOAD_VERSION d934cd5de55af65220816e4fd01ce3f9c0ef1cd4) # TODO: update to 1.29.0 once it is released. set(LIBMONGOC_REQUIRED_ABI_VERSION 1.0) set(NEED_DOWNLOAD_C_DRIVER false) diff --git a/docs/api/mongocxx/examples/change_streams.md b/docs/api/mongocxx/examples/change_streams.md new file mode 100644 index 0000000000..4d9e80c9f0 --- /dev/null +++ b/docs/api/mongocxx/examples/change_streams.md @@ -0,0 +1,23 @@ +# Obtain a Change Stream + +## From a Client + +@snippet api/mongocxx/examples/change_streams/from_client.cpp Example + +## From a Database + +@snippet api/mongocxx/examples/change_streams/from_database.cpp Example + +## From a Collection + +@snippet api/mongocxx/examples/change_streams/from_collection.cpp Example + +# Use a Change Stream + +## Basic Usage + +@snippet api/mongocxx/examples/change_streams/basic.cpp Example + +## With Pipeline + +@snippet api/mongocxx/examples/change_streams/with_pipeline.cpp Example diff --git a/docs/api/mongocxx/examples/client_sessions.md b/docs/api/mongocxx/examples/client_sessions.md new file mode 100644 index 0000000000..806d160335 --- /dev/null +++ b/docs/api/mongocxx/examples/client_sessions.md @@ -0,0 +1,22 @@ +# Create a Client Session + +## Basic Usage + +@snippet api/mongocxx/examples/client_sessions/create/basic.cpp Example + +## With Options + +@snippet api/mongocxx/examples/client_sessions/create/with_options.cpp Example + +# Use a Client Session + +@see +- [Causal Consistency and Read and Write Concerns (MongoDB Manual)](https://www.mongodb.com/docs/manual/core/causal-consistency-read-write-concerns/) + +## Basic Usage + +@snippet api/mongocxx/examples/client_sessions/use/basic.cpp Example + +## With Transactions + +@snippet api/mongocxx/examples/client_sessions/use/transactions.cpp Example diff --git a/docs/api/mongocxx/examples/collections.md b/docs/api/mongocxx/examples/collections.md index 16dcece429..c1c3fb4593 100644 --- a/docs/api/mongocxx/examples/collections.md +++ b/docs/api/mongocxx/examples/collections.md @@ -26,18 +26,50 @@ # Index Operations -## List Indexes +## On a Collection + +### List Indexes @snippet examples/api/mongocxx/examples/collections/list_indexes.cpp Example -## Create an Index +### Create an Index @snippet examples/api/mongocxx/examples/collections/create_index.cpp Example -## Create an Index With Options +### Create an Index With Options @snippet examples/api/mongocxx/examples/collections/create_index_with_options.cpp Example +## With an Index View + +### Obtain an Index View + +@snippet examples/api/mongocxx/examples/collections/index_views/indexes.cpp Example + +### List Indexes + +@snippet examples/api/mongocxx/examples/collections/index_views/list.cpp Example + +### Create an Index + +@snippet examples/api/mongocxx/examples/collections/index_views/create.cpp Example + +### Create an Index With Options + +@snippet examples/api/mongocxx/examples/collections/index_views/create_with_options.cpp Example + +### Create Multiple Indexes + +@snippet examples/api/mongocxx/examples/collections/index_views/create_many.cpp Example + +### Drop an Index + +@snippet examples/api/mongocxx/examples/collections/index_views/drop.cpp Example + +### Drop All Indexes + +@snippet examples/api/mongocxx/examples/collections/index_views/drop_all.cpp Example + # Document Operations ## Query the Number of Documents @@ -111,3 +143,17 @@ ## Execute an Aggregation Operation @snippet examples/api/mongocxx/examples/collections/aggregate.cpp Example + +# Error Handling + +## Invalid Collection + +@snippet examples/api/mongocxx/examples/collections/incompatible_options.cpp Example + +## Invalid Parameter + +@snippet examples/api/mongocxx/examples/collections/invalid_parameter.cpp Example + +## Incompatible Options + +@snippet examples/api/mongocxx/examples/collections/incompatible_options.cpp Example diff --git a/docs/api/mongocxx/examples/databases.md b/docs/api/mongocxx/examples/databases.md index c1574d6e09..4b4f90eb9c 100644 --- a/docs/api/mongocxx/examples/databases.md +++ b/docs/api/mongocxx/examples/databases.md @@ -41,3 +41,9 @@ ## List Collection Names in the Database @snippet examples/api/mongocxx/examples/databases/list_collection_names.cpp Example + +# Error Handling + +## Invalid Database + +@snippet examples/api/mongocxx/examples/databases/invalid.cpp Example diff --git a/examples/api/db_lock.cpp b/examples/api/db_lock.cpp index dbae316b63..4a624e016b 100644 --- a/examples/api/db_lock.cpp +++ b/examples/api/db_lock.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -32,14 +33,23 @@ std::mutex db_locks_mut; } // namespace db_lock::~db_lock() { - this->get().drop(); + this->get().drop(wc_majority()); } db_lock::db_lock(mongocxx::client& client, std::string name) : _client_ptr(&client), _name(name) { + // https://www.mongodb.com/docs/manual/reference/limits/#mongodb-limit-Length-of-Database-Names + static constexpr std::size_t db_name_size_max = 63u; + + if (_name.size() > db_name_size_max) { + // Strip prefix, which is more likely to be common across components. + // e.g. `api_mongocxx_examples_very_long_component_name` -> `g_component_name` (length: 16). + _name = std::move(_name).substr(_name.size() - db_name_size_max, db_name_size_max); + } + ((void)std::lock_guard{db_locks_mut}, _lock = std::unique_lock(db_locks[name])); - this->get().drop(); + this->get().drop(wc_majority()); } mongocxx::database db_lock::get() const& { diff --git a/examples/api/mongocxx/examples/change_streams/basic.cpp b/examples/api/mongocxx/examples/change_streams/basic.cpp new file mode 100644 index 0000000000..9ae4cc1ee7 --- /dev/null +++ b/examples/api/mongocxx/examples/change_streams/basic.cpp @@ -0,0 +1,79 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::database db) { + mongocxx::collection coll = db.create_collection("coll"); + + mongocxx::change_stream stream = coll.watch(); + + auto result_opt = coll.insert_one(bsoncxx::from_json(R"({"x": 1})")); + EXPECT(result_opt); + auto id = result_opt->inserted_id(); + + int count = 0; + auto now = [] { return std::chrono::steady_clock::now(); }; + auto start = now(); + + // periodicNoopIntervalSecs: 10 (default) + while (count < 1 && now() - start < std::chrono::seconds(10)) { + for (bsoncxx::document::view change : stream) { + ++count; + + EXPECT(change["operationType"]); + EXPECT(change["operationType"].get_string().value.compare("insert") == 0); + + EXPECT(change["ns"]); + EXPECT(change["ns"]["db"].get_string().value == db.name()); + EXPECT(change["ns"]["coll"].get_string().value == coll.name()); + + EXPECT(change["fullDocument"]); + EXPECT(change["fullDocument"]["x"]); + + EXPECT(change["documentKey"]); + EXPECT(change["documentKey"]["_id"].get_oid().value == id); + } + } + + EXPECT(count == 1); +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_REPLICA() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + example(set_rw_concern_majority(guard.get())); + } +} diff --git a/examples/api/mongocxx/examples/change_streams/from_client.cpp b/examples/api/mongocxx/examples/change_streams/from_client.cpp new file mode 100644 index 0000000000..90825a2ddb --- /dev/null +++ b/examples/api/mongocxx/examples/change_streams/from_client.cpp @@ -0,0 +1,66 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::client client) { + // Basic usage. + { + mongocxx::change_stream stream = client.watch(); + + EXPECT(stream.get_resume_token()); + } + + // With options. + { + mongocxx::options::change_stream opts; + + opts.batch_size(1); + // ... other change stream options. + + mongocxx::change_stream stream = client.watch(opts); + + EXPECT(stream.get_resume_token()); + } + + // With a pipeline. + { + mongocxx::pipeline pipeline; + + pipeline.match(bsoncxx::from_json(R"({"operationType": "insert"})")); + // ... other pipeline options. + + mongocxx::change_stream stream = client.watch(pipeline); + + EXPECT(stream.get_resume_token()); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_REPLICA() { + example(mongocxx::client{mongocxx::uri{}}); +} diff --git a/examples/api/mongocxx/examples/change_streams/from_collection.cpp b/examples/api/mongocxx/examples/change_streams/from_collection.cpp new file mode 100644 index 0000000000..1eb27f76bf --- /dev/null +++ b/examples/api/mongocxx/examples/change_streams/from_collection.cpp @@ -0,0 +1,73 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::collection coll) { + // Basic usage. + { + mongocxx::change_stream stream = coll.watch(); + + EXPECT(stream.get_resume_token()); + } + + // With options. + { + mongocxx::options::change_stream opts; + + opts.batch_size(1); + // ... other change stream options. + + mongocxx::change_stream stream = coll.watch(opts); + + EXPECT(stream.get_resume_token()); + } + + // With a pipeline. + { + mongocxx::pipeline pipeline; + + pipeline.match(bsoncxx::from_json(R"({"operationType": "insert"})")); + // ... other pipeline options. + + mongocxx::change_stream stream = coll.watch(pipeline); + + EXPECT(stream.get_resume_token()); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_REPLICA() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + example(guard.get()["coll"]); + } +} diff --git a/examples/api/mongocxx/examples/change_streams/from_database.cpp b/examples/api/mongocxx/examples/change_streams/from_database.cpp new file mode 100644 index 0000000000..656186c600 --- /dev/null +++ b/examples/api/mongocxx/examples/change_streams/from_database.cpp @@ -0,0 +1,73 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::database db) { + // Basic usage. + { + mongocxx::change_stream stream = db.watch(); + + EXPECT(stream.get_resume_token()); + } + + // With options. + { + mongocxx::options::change_stream opts; + + opts.batch_size(1); + // ... other change stream options. + + mongocxx::change_stream stream = db.watch(opts); + + EXPECT(stream.get_resume_token()); + } + + // With a pipeline. + { + mongocxx::pipeline pipeline; + + pipeline.match(bsoncxx::from_json(R"({"operationType": "insert"})")); + // ... other pipeline options. + + mongocxx::change_stream stream = db.watch(pipeline); + + EXPECT(stream.get_resume_token()); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_REPLICA() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + example(guard.get()); + } +} diff --git a/examples/api/mongocxx/examples/change_streams/with_pipeline.cpp b/examples/api/mongocxx/examples/change_streams/with_pipeline.cpp new file mode 100644 index 0000000000..8a70589753 --- /dev/null +++ b/examples/api/mongocxx/examples/change_streams/with_pipeline.cpp @@ -0,0 +1,104 @@ +// 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 { + +// [Example] +void example(mongocxx::database db) { + mongocxx::collection coll = db.create_collection("coll"); + + mongocxx::pipeline pipeline; + + pipeline.match( + bsoncxx::from_json(R"({"operationType": "insert", "fullDocument.watched": true})")); + // ... other pipeline options. + + mongocxx::change_stream stream = coll.watch(pipeline); + + // Observed. + EXPECT(coll.insert_one(bsoncxx::from_json(R"({"x": 1, "watched": true})"))); + + // Not observed (fullDocument mismatch). + EXPECT(coll.insert_one(bsoncxx::from_json(R"({"x": 2, "watched": false})"))); + + // Not observed (operationType mismatch). + EXPECT(coll.update_one(bsoncxx::from_json(R"({"x": 2})"), + bsoncxx::from_json(R"({"$set": {"watched": true}})"))); + + // Observed. + EXPECT(coll.insert_one(bsoncxx::from_json(R"({"x": 3, "watched": true})"))); + + int count = 0; + auto now = [] { return std::chrono::steady_clock::now(); }; + auto start = now(); + + // periodicNoopIntervalSecs: 10 (default) + while (count < 2 && now() - start < std::chrono::seconds(10)) { + for (bsoncxx::document::view change : stream) { + ++count; + } + } + + EXPECT(count == 2); +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_REPLICA() { + mongocxx::client client{mongocxx::uri{}}; + + try { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + auto db = set_rw_concern_majority(guard.get()); + auto coll = db["coll"]; + + example(db); + + EXPECT(coll.count_documents(bsoncxx::from_json(R"({"x": {"$exists": 1}})")) == 3); + + auto doc_opt = coll.find_one(bsoncxx::from_json(R"({"x": 2})")); + + EXPECT(doc_opt); + + auto& doc = *doc_opt; + + EXPECT(doc["watched"]); + EXPECT(doc["watched"].get_bool().value); + } catch (const mongocxx::exception& ex) { + if (std::strstr(ex.what(), "not supported") != nullptr) { + // MongoDB 4.2+ required for sharded clusters. + } else { + throw; + } + } +} diff --git a/examples/api/mongocxx/examples/client_sessions/create/basic.cpp b/examples/api/mongocxx/examples/client_sessions/create/basic.cpp new file mode 100644 index 0000000000..bf3d683b76 --- /dev/null +++ b/examples/api/mongocxx/examples/client_sessions/create/basic.cpp @@ -0,0 +1,42 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::client client) { + mongocxx::client_session session = client.start_session(); + + EXPECT(&session.client() == &client); + + EXPECT(session.id()["id"]); + EXPECT(session.id()["id"].type() == bsoncxx::type::k_binary); + EXPECT(session.id()["id"].get_binary().sub_type == bsoncxx::binary_sub_type::k_uuid); +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + example(mongocxx::client{mongocxx::uri{}}); +} diff --git a/examples/api/mongocxx/examples/client_sessions/create/with_options.cpp b/examples/api/mongocxx/examples/client_sessions/create/with_options.cpp new file mode 100644 index 0000000000..7c6cdd4488 --- /dev/null +++ b/examples/api/mongocxx/examples/client_sessions/create/with_options.cpp @@ -0,0 +1,42 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::client client) { + mongocxx::options::client_session opts; + + opts.snapshot(true); + // ... other client session options. + + mongocxx::client_session session = client.start_session(opts); + + EXPECT(session.options().snapshot() == true); +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + example(mongocxx::client{mongocxx::uri{}}); +} diff --git a/examples/api/mongocxx/examples/client_sessions/use/basic.cpp b/examples/api/mongocxx/examples/client_sessions/use/basic.cpp new file mode 100644 index 0000000000..92dd589937 --- /dev/null +++ b/examples/api/mongocxx/examples/client_sessions/use/basic.cpp @@ -0,0 +1,65 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::client_session session, mongocxx::database db) { + using bsoncxx::builder::basic::make_document; + + mongocxx::collection coll = db.create_collection(session, "coll"); + + auto x1 = bsoncxx::from_json(R"({"x": 1})"); + + EXPECT(coll.insert_one(session, x1.view())); + EXPECT(coll.update_one(session, x1.view(), bsoncxx::from_json(R"({"$inc": {"x": 1}})"))); + + auto doc_opt = coll.find_one(session, make_document()); + + EXPECT(doc_opt); + + auto& doc = *doc_opt; + + EXPECT(doc["x"]); + EXPECT(doc["x"].get_int32().value == 2); +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + example(client.start_session(), set_rw_concern_majority(guard.get())); + } +} diff --git a/examples/api/mongocxx/examples/client_sessions/use/transactions.cpp b/examples/api/mongocxx/examples/client_sessions/use/transactions.cpp new file mode 100644 index 0000000000..67ad38a987 --- /dev/null +++ b/examples/api/mongocxx/examples/client_sessions/use/transactions.cpp @@ -0,0 +1,88 @@ +// 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 { + +// [Example] +void example(mongocxx::client_session session, mongocxx::collection coll) { + using bsoncxx::builder::basic::make_document; + + using mongocxx::model::insert_one; + + auto x0 = bsoncxx::from_json(R"({"x": 0})"); + auto inc = bsoncxx::from_json(R"({"$inc": {"x": 1}})"); + + session.start_transaction(); + + { + auto result_opt = coll.create_bulk_write(session) + .append(insert_one{x0.view()}) + .append(insert_one{x0.view()}) + .append(insert_one{x0.view()}) + .execute(); + + EXPECT(result_opt); + EXPECT(result_opt->inserted_count() == 3); + } + + { + auto result_opt = + coll.update_many(session, bsoncxx::from_json(R"({"x": {"$exists": 1}})"), inc.view()); + + EXPECT(result_opt); + EXPECT(result_opt->modified_count() == 3); + } + + session.commit_transaction(); + + EXPECT(coll.count_documents(session, bsoncxx::from_json(R"({"x": 1})")) == 3); +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_REPLICA() { + mongocxx::client client{mongocxx::uri{}}; + + try { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + example(client.start_session(), + set_rw_concern_majority(guard.get()).create_collection("coll")); + } catch (const mongocxx::exception& ex) { + if (std::strstr(ex.what(), "not supported") != nullptr) { + // MongoDB 4.2+ required for sharded clusters. + } else { + throw; + } + } +} diff --git a/examples/api/mongocxx/examples/clients/create/single/options/auto_encryption.cpp b/examples/api/mongocxx/examples/clients/create/single/options/auto_encryption.cpp index 016799255a..c2409dd17b 100644 --- a/examples/api/mongocxx/examples/clients/create/single/options/auto_encryption.cpp +++ b/examples/api/mongocxx/examples/clients/create/single/options/auto_encryption.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,8 @@ void example(bsoncxx::document::view kms_providers) { auto_encryption_opts.key_vault_namespace({"keyvault", "datakeys"}); auto_encryption_opts.kms_providers(kms_providers); + auto_encryption_opts.extra_options(bsoncxx::from_json( + R"({"mongocryptdURI": "mongodb://localhost:27027", "mongocryptdSpawnArgs": ["--port", "27027"]})")); // ... other automatic encryption options. mongocxx::options::client client_opts; diff --git a/examples/api/mongocxx/examples/collections/create_index.cpp b/examples/api/mongocxx/examples/collections/create_index.cpp index a25c221e21..7fcb311ac7 100644 --- a/examples/api/mongocxx/examples/collections/create_index.cpp +++ b/examples/api/mongocxx/examples/collections/create_index.cpp @@ -55,10 +55,10 @@ RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { return std::distance(cursor.begin(), cursor.end()); }; - EXPECT(count_indexes() == 1); // _id + EXPECT(count_indexes() == 1); // _id_ example(coll); - EXPECT(count_indexes() == 2); // _id, key_1 + EXPECT(count_indexes() == 2); // _id_, key_1 } } diff --git a/examples/api/mongocxx/examples/collections/incompatible_options.cpp b/examples/api/mongocxx/examples/collections/incompatible_options.cpp new file mode 100644 index 0000000000..f503c2d6d7 --- /dev/null +++ b/examples/api/mongocxx/examples/collections/incompatible_options.cpp @@ -0,0 +1,65 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::collection coll) { + mongocxx::options::find_one_and_update opts; + + opts.collation(bsoncxx::from_json(R"({"locale": "simple"})")); + + { + mongocxx::write_concern wc; + wc.acknowledge_level(mongocxx::write_concern::level::k_unacknowledged); + opts.write_concern(wc); + } + + auto empty = bsoncxx::builder::basic::make_document(); + + try { + auto name = coll.find_one_and_update(empty.view(), empty.view(), opts); + + EXPECT(false && "should not reach this point"); + } catch (const mongocxx::exception& ex) { + EXPECT(ex.code() == mongocxx::error_code::k_invalid_parameter); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + example(set_rw_concern_majority(guard.get()).create_collection("coll")); + } +} diff --git a/examples/api/mongocxx/examples/collections/index_views/create.cpp b/examples/api/mongocxx/examples/collections/index_views/create.cpp new file mode 100644 index 0000000000..6edfcd65e0 --- /dev/null +++ b/examples/api/mongocxx/examples/collections/index_views/create.cpp @@ -0,0 +1,76 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::index_view indexes) { + // Basic usage. + { + auto result_opt = indexes.create_one(bsoncxx::from_json(R"({"x": 1})")); + + EXPECT(result_opt); + EXPECT(result_opt->compare("x_1") == 0); + } + + // Index model. + { + auto result_opt = + indexes.create_one(mongocxx::index_model{bsoncxx::from_json(R"({"y": 1})")}); + + EXPECT(result_opt); + EXPECT(result_opt->compare("y_1") == 0); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + auto coll = set_rw_concern_majority(guard.get().create_collection("coll")); + + auto count_indexes = [&coll] { + auto cursor = coll.list_indexes(); + + return std::distance(cursor.begin(), cursor.end()); + }; + + EXPECT(count_indexes() == 1); // _id_ + + example(coll.indexes()); + + EXPECT(count_indexes() == 3); // _id_, x_1, y_1 + } +} diff --git a/examples/api/mongocxx/examples/collections/index_views/create_many.cpp b/examples/api/mongocxx/examples/collections/index_views/create_many.cpp new file mode 100644 index 0000000000..3b0a6b7a7e --- /dev/null +++ b/examples/api/mongocxx/examples/collections/index_views/create_many.cpp @@ -0,0 +1,69 @@ +// 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 { + +// [Example] +void example(mongocxx::index_view indexes) { + std::vector models; + + models.emplace_back(bsoncxx::from_json(R"({"x": 1})")); + models.emplace_back(bsoncxx::from_json(R"({"y": 1})")); + + auto result = indexes.create_many(models); + + // SERVER-78611 + if (result["raw"]) { + result = result["raw"].get_document().value.begin()->get_document().value; + } + + EXPECT(result["ok"]); + EXPECT(result["ok"].get_double().value == 1.0); + + EXPECT(result["numIndexesBefore"]); + EXPECT(result["numIndexesBefore"].get_int32().value == 1); // _id_ + + EXPECT(result["numIndexesAfter"]); + EXPECT(result["numIndexesAfter"].get_int32().value == 3); // _id_, x_1, y_1 +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + example(set_rw_concern_majority(guard.get().create_collection("coll")).indexes()); + } +} diff --git a/examples/api/mongocxx/examples/collections/index_views/create_with_options.cpp b/examples/api/mongocxx/examples/collections/index_views/create_with_options.cpp new file mode 100644 index 0000000000..1d96882113 --- /dev/null +++ b/examples/api/mongocxx/examples/collections/index_views/create_with_options.cpp @@ -0,0 +1,99 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::index_view indexes) { + // Index options. + { + auto opts = bsoncxx::from_json(R"({"name": "one"})"); + // ... other index options. + + auto result_opt = indexes.create_one(bsoncxx::from_json(R"({"key_a": 1})"), opts.view()); + + EXPECT(result_opt); + EXPECT(result_opt->compare("one") == 0); + } + + // Index model. + { + auto result_opt = indexes.create_one(mongocxx::index_model{ + bsoncxx::from_json(R"({"y": 1})"), + bsoncxx::from_json(R"({"name": "two"})"), + }); + + EXPECT(result_opt); + EXPECT(result_opt->compare("two") == 0); + } + + // Create index options. + { + auto opts = bsoncxx::builder::basic::make_document(); + + mongocxx::options::index_view create_opts; + + // ... set create index options. + + auto result_opt = indexes.create_one(bsoncxx::from_json(R"({"z": 1})"), opts.view()); + + EXPECT(result_opt); + EXPECT(result_opt->compare("z_1") == 0); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + auto db = set_rw_concern_majority(guard.get()); + auto coll = db.create_collection("coll"); + + example(coll.indexes()); + + for (auto doc : coll.indexes().list()) { + EXPECT(doc["name"]); + + if (doc["name"].get_string().value.compare("custom_name") == 0) { + EXPECT(doc["unique"]); + EXPECT(doc["unique"].get_bool().value == true); + } + } + } +} diff --git a/examples/api/mongocxx/examples/collections/index_views/drop.cpp b/examples/api/mongocxx/examples/collections/index_views/drop.cpp new file mode 100644 index 0000000000..c64a6dcc00 --- /dev/null +++ b/examples/api/mongocxx/examples/collections/index_views/drop.cpp @@ -0,0 +1,80 @@ +// 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 + +namespace { + +// [Example] +// [ +// { +// "key": { "_id": 1 }, +// "name": "_id_" +// { +// "key": { "x": 1 }, +// "name": "x_1" +// }, +// { +// "key": { "y": 1 }, +// "name": "custom_name" +// } +// ] +void example(mongocxx::index_view indexes) { + auto count_indexes = [&indexes] { + auto cursor = indexes.list(); + return std::distance(cursor.begin(), cursor.end()); + }; + + EXPECT(count_indexes() == 3); // _id_, x_1, custom_name + + indexes.drop_one("custom_name"); + + EXPECT(count_indexes() == 2); // _id_, x_1 + + indexes.drop_one(bsoncxx::from_json(R"({"x": 1})")); + + EXPECT(count_indexes() == 1); // _id_ +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + auto coll = set_rw_concern_majority(guard.get().create_collection("coll")); + + (void)coll.create_index(bsoncxx::from_json(R"({"x": 1})")); + (void)coll.create_index(bsoncxx::from_json(R"({"y": 1})"), + bsoncxx::from_json(R"({"name": "custom_name"})")); + + example(coll.indexes()); + } +} diff --git a/examples/api/mongocxx/examples/collections/index_views/drop_all.cpp b/examples/api/mongocxx/examples/collections/index_views/drop_all.cpp new file mode 100644 index 0000000000..eccdea1489 --- /dev/null +++ b/examples/api/mongocxx/examples/collections/index_views/drop_all.cpp @@ -0,0 +1,76 @@ +// 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 + +namespace { + +// [Example] +// [ +// { +// "key": { "_id": 1 }, +// "name": "_id_" +// { +// "key": { "x": 1 }, +// "name": "x_1" +// }, +// { +// "key": { "y": 1 }, +// "name": "custom_name" +// } +// ] +void example(mongocxx::index_view indexes) { + auto count_indexes = [&indexes] { + auto cursor = indexes.list(); + return std::distance(cursor.begin(), cursor.end()); + }; + + EXPECT(count_indexes() == 3); // _id_, x_1, custom_name + + indexes.drop_all(); + + EXPECT(count_indexes() == 1); // _id_ +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + auto coll = set_rw_concern_majority(guard.get().create_collection("coll")); + + (void)coll.create_index(bsoncxx::from_json(R"({"x": 1})")); + (void)coll.create_index(bsoncxx::from_json(R"({"y": 1})"), + bsoncxx::from_json(R"({"name": "custom_name"})")); + + example(coll.indexes()); + } +} diff --git a/examples/api/mongocxx/examples/collections/index_views/indexes.cpp b/examples/api/mongocxx/examples/collections/index_views/indexes.cpp new file mode 100644 index 0000000000..674cc0fa35 --- /dev/null +++ b/examples/api/mongocxx/examples/collections/index_views/indexes.cpp @@ -0,0 +1,48 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::collection coll) { + mongocxx::index_view indexes = coll.indexes(); + + for (bsoncxx::document::view doc : indexes.list()) { + EXPECT(doc["key"]); + EXPECT(doc["name"]); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + example(set_rw_concern_majority(guard.get().create_collection("coll"))); + } +} diff --git a/examples/api/mongocxx/examples/collections/index_views/list.cpp b/examples/api/mongocxx/examples/collections/index_views/list.cpp new file mode 100644 index 0000000000..ab61cd4c90 --- /dev/null +++ b/examples/api/mongocxx/examples/collections/index_views/list.cpp @@ -0,0 +1,58 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::index_view indexes) { + for (bsoncxx::document::view doc : indexes.list()) { + EXPECT(doc["name"]); + EXPECT(doc["name"].type() == bsoncxx::type::k_string); + + EXPECT(doc["key"]); + EXPECT(doc["key"].type() == bsoncxx::type::k_document); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + auto coll = set_rw_concern_majority(guard.get().create_collection("coll")); + + (void)coll.create_index(bsoncxx::from_json(R"({"key": 1})")); + + example(coll.indexes()); + } +} diff --git a/examples/api/mongocxx/examples/collections/invalid.cpp b/examples/api/mongocxx/examples/collections/invalid.cpp new file mode 100644 index 0000000000..3d23a3b1be --- /dev/null +++ b/examples/api/mongocxx/examples/collections/invalid.cpp @@ -0,0 +1,44 @@ +// 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 + +namespace { + +// [Example] +void example() { + mongocxx::collection coll; + + try { + auto name = coll.name(); // DO NOT DO THIS. Throws. + + EXPECT(false && "should not reach this point"); + } catch (const mongocxx::exception& ex) { + EXPECT(ex.code() == mongocxx::error_code::k_invalid_collection_object); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_WITH_INSTANCE() { + example(); +} diff --git a/examples/api/mongocxx/examples/collections/invalid_parameter.cpp b/examples/api/mongocxx/examples/collections/invalid_parameter.cpp new file mode 100644 index 0000000000..e54a1ddccf --- /dev/null +++ b/examples/api/mongocxx/examples/collections/invalid_parameter.cpp @@ -0,0 +1,58 @@ +// 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 + +namespace { + +// [Example] +void example(mongocxx::collection coll) { + mongocxx::options::find opts; + + opts.max_await_time(std::chrono::milliseconds(-1)); + + try { + auto name = coll.find(bsoncxx::builder::basic::make_document(), opts); + + EXPECT(false && "should not reach this point"); + } catch (const mongocxx::exception& ex) { + EXPECT(ex.code() == mongocxx::error_code::k_invalid_parameter); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_FOR_SINGLE() { + mongocxx::client client{mongocxx::uri{}}; + + { + db_lock guard{client, EXAMPLES_COMPONENT_NAME_STR}; + + example(set_rw_concern_majority(guard.get()).create_collection("coll")); + } +} diff --git a/examples/api/mongocxx/examples/databases/invalid.cpp b/examples/api/mongocxx/examples/databases/invalid.cpp new file mode 100644 index 0000000000..08f7f16419 --- /dev/null +++ b/examples/api/mongocxx/examples/databases/invalid.cpp @@ -0,0 +1,44 @@ +// 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 + +namespace { + +// [Example] +void example() { + mongocxx::database db; + + try { + auto name = db.name(); // DO NOT DO THIS. Throws. + + EXPECT(false && "should not reach this point"); + } catch (const mongocxx::exception& ex) { + EXPECT(ex.code() == mongocxx::error_code::k_invalid_database_object); + } +} +// [Example] + +} // namespace + +RUNNER_REGISTER_COMPONENT_WITH_INSTANCE() { + example(); +} diff --git a/examples/api/runner.cpp b/examples/api/runner.cpp index 7360f1cd3c..4a01574ee3 100644 --- a/examples/api/runner.cpp +++ b/examples/api/runner.cpp @@ -26,13 +26,15 @@ #include #include #include +#include +#include #include #include #include #include -#include -#include +#include +#include #include #include @@ -76,6 +78,9 @@ class runner_type { std::minstd_rand::result_type seed = 0u; std::minstd_rand gen; unsigned int jobs = 0; + bool use_fork = true; + std::vector filters; + bool verbose = false; static void run_with_jobs(const std::vector& components, unsigned int jobs) { if (jobs == 1) { @@ -112,64 +117,64 @@ class runner_type { return_from_main, }; -#if !defined(_MSC_VER) action run_forking_components() { - // Forking with threads is difficult and the number of components that require forking are - // few in number. Run forking components sequentially. - for (const auto& component : forking_components) { - const auto& fn = component.fn; - const auto& name = component.name; - - const pid_t pid = ::fork(); - - // Child: do nothing more than call the registered function. - if (pid == 0) { - fn(); - return action::return_from_main; // Return from `main()`. - } + if (use_fork) { +#if !defined(_MSC_VER) + // Forking with threads is difficult and the number of components that require forking + // are few in number. Run forking components sequentially. + for (const auto& component : forking_components) { + const auto& fn = component.fn; + const auto& name = component.name; + + const pid_t pid = ::fork(); + + // Child: do nothing more than call the registered function. + if (pid == 0) { + fn(); + return action::return_from_main; // Return from `main()`. + } - // Parent: wait for child and handle returned status values. - else { - int status; + // Parent: wait for child and handle returned status values. + else { + int status; - const int ret = ::waitpid(pid, &status, 0); + const int ret = ::waitpid(pid, &status, 0); - // For non-zero exit codes, permit continuation for example coverage. - if (WIFEXITED(status) && WEXITSTATUS(status) != EXIT_SUCCESS) { - std::cout << __func__ << ": failed: " << name - << " exited with a non-zero exit code: " << WEXITSTATUS(status) - << std::endl; + // For non-zero exit codes, permit continuation for example coverage. + if (WIFEXITED(status) && WEXITSTATUS(status) != EXIT_SUCCESS) { + std::cout << __func__ << ": failed: " << name + << " exited with a non-zero exit code: " << WEXITSTATUS(status) + << std::endl; - return action::fail; - } + return action::fail; + } - // For unexpected signals, stop immediately. - else if (WIFSIGNALED(status)) { - const int signal = WTERMSIG(status); - const char* const sigstr = ::strsignal(signal); + // For unexpected signals, stop immediately. + else if (WIFSIGNALED(status)) { + const int signal = WTERMSIG(status); + const char* const sigstr = ::strsignal(signal); - std::cout << __func__ << ": failed: " << name - << " was killed by signal: " << signal << " (" - << (sigstr ? sigstr : "") << ")" << std::endl; + std::cout << __func__ << ": failed: " << name + << " was killed by signal: " << signal << " (" + << (sigstr ? sigstr : "") << ")" << std::endl; - std::exit(EXIT_FAILURE); - } + std::exit(EXIT_FAILURE); + } - // We don't expect any other failure condition. - else { - assert(ret != -1); + // We don't expect any other failure condition. + else { + assert(ret != -1); + } } } + + return action::succeed; +#endif // !defined(_MSC_VER) } - return action::succeed; - } -#else - action run_forking_components() { std::cout << "Skipping API examples that require forked processes" << std::endl; return action::succeed; } -#endif // !defined(_MSC_VER) void run_components_with_instance() { mongocxx::instance instance; @@ -179,8 +184,8 @@ class runner_type { try { mongocxx::client client{mongocxx::uri{"mongodb://localhost:27017/"}}; - const auto reply = client["admin"].run_command(bsoncxx::builder::basic::make_document( - bsoncxx::builder::basic::kvp("isMaster", 1))); + const auto reply = + client["admin"].run_command(bsoncxx::from_json(R"({"isMaster": 1})")); if (reply["msg"]) { std::cout << "Running API examples against a live sharded server" << std::endl; @@ -238,6 +243,18 @@ class runner_type { } } + void set_use_fork(bool use_fork) { + this->use_fork = use_fork; + } + + void add_filter(const char* filter) { + this->filters.emplace_back(filter); + } + + void set_verbose(bool verbose) { + this->verbose = verbose; + } + int run() { assert(jobs > 0u); @@ -245,9 +262,66 @@ class runner_type { gen.seed(seed); + std::vector* all_components[] = { + &components, + &components_with_instance, + &components_for_single, + &components_for_replica, + &components_for_sharded, + &forking_components, + }; + + // Unconditionally sort to ensure seed consistency after shuffle. + for (auto cptr : all_components) { + std::sort(cptr->begin(), cptr->end(), [](const component& lhs, const component& rhs) { + return std::strcmp(lhs.name, rhs.name) < 0; + }); + } + + // Filter components to be executed to those containing all filter substrings. + for (auto filter : filters) { + for (auto cptr : all_components) { + cptr->erase(std::remove_if(cptr->begin(), + cptr->end(), + [&filter](component c) { + if (std::strstr(c.name, filter.c_str()) != nullptr) { + return false; + } + return true; + }), + cptr->end()); + } + } + + // Print the list of components to be executed. + if (verbose) { + std::vector names; + + names.reserve(std::accumulate(std::begin(all_components), + std::end(all_components), + std::size_t{0}, + [](std::size_t n, const std::vector* cptr) { + return n + cptr->size(); + })); + + for (auto cptr : all_components) { + for (auto c : *cptr) { + names.emplace_back(c.name); + } + } + + std::sort(names.begin(), names.end()); + + std::cout << "API example components to be executed:" << std::endl; + for (auto name : names) { + std::cout << " - " << name << std::endl; + } + } + // Prevent ordering dependencies across examples. - std::shuffle(components.begin(), components.end(), gen); - std::shuffle(forking_components.begin(), forking_components.end(), gen); + for (auto cptr : all_components) { + std::shuffle(cptr->begin(), cptr->end(), gen); + } run_components(); @@ -268,6 +342,119 @@ class runner_type { runner_type runner; +bool parse_seed(int argc, char** argv, int i, bool& set_seed) { + if (strcmp(argv[i], "--seed") == 0) { + if (i + 1 >= argc) { + std::cerr << "missing argument to --seed" << std::endl; + return false; + } + + char* const seed_str = argv[i + 1]; // Next argument. + char* end = nullptr; + + const auto seed = + static_cast(std::strtoul(seed_str, &end, 10)); + + if (static_cast(end - seed_str) != std::strlen(seed_str)) { + std::cerr << "invalid seed string: " << seed_str << std::endl; + return false; + } + + runner.set_seed(seed); + set_seed = true; + } + + return true; +} + +bool parse_jobs(int argc, char** argv, int i, bool& set_jobs) { + if (strcmp(argv[i], "--jobs") == 0) { + if (i + 1 >= argc) { + std::cerr << "missing argument to --jobs" << std::endl; + return false; + } + + char* const jobs_str = argv[i + 1]; // Next argument. + char* end = nullptr; + + const auto jobs = std::strtoul(jobs_str, &end, 10); + + if (static_cast(end - jobs_str) != std::strlen(jobs_str)) { + std::cerr << "invalid jobs string: " << jobs_str << std::endl; + return false; + } + + if (jobs >= UINT_MAX) { + std::cerr << "invalid jobs string (too large): " << jobs_str << std::endl; + } + + runner.set_jobs(static_cast(jobs)); + set_jobs = true; + } + + return true; +} + +bool parse_use_fork(int argc, char** argv, int i, bool& set_use_fork) { + if (strcmp(argv[i], "--use-fork") == 0) { + if (i + 1 >= argc) { + std::cerr << "missing argument to --use-fork" << std::endl; + return false; + } + + char* const use_fork_str = argv[i + 1]; // Next argument. + char* end = nullptr; + + const auto flag = std::strtoul(use_fork_str, &end, 10); + + if (static_cast(end - use_fork_str) != std::strlen(use_fork_str)) { + std::cerr << "invalid argument: " << use_fork_str << std::endl; + return false; + } + + runner.set_use_fork(flag == 0 ? false : true); + set_use_fork = true; + } + + return true; +} + +bool parse_filter(int argc, char** argv, int i) { + if (strcmp(argv[i], "--filter") == 0) { + if (i + 1 >= argc) { + std::cerr << "missing argument to --filter" << std::endl; + return false; + } + + runner.add_filter(argv[i + 1]); + } + + return true; +} + +bool parse_verbose(int argc, char** argv, int i) { + if (strcmp(argv[i], "--verbose") == 0) { + if (i + 1 >= argc) { + std::cerr << "missing argument to --verbose" << std::endl; + return false; + } + + char* const verbose_str = argv[i + 1]; // Next argument. + char* end = nullptr; + + const auto verbose = std::strtoul(verbose_str, &end, 10); + + if (static_cast(end - verbose_str) != std::strlen(verbose_str)) { + std::cerr << "invalid verbose string: " << verbose_str << std::endl; + return false; + } + + runner.set_verbose(verbose != 0u); + } + + return true; +} + } // namespace void runner_register_component(void (*fn)(), const char* name) { @@ -297,53 +484,35 @@ void runner_register_forking_component(void (*fn)(), const char* name) { int EXAMPLES_CDECL main(int argc, char** argv) { bool set_seed = false; bool set_jobs = false; + bool set_use_fork = false; // Simple command-line argument parser. - for (int i = 1; i < argc; ++i) { + for (int i = 1; i < argc; i += 2) { // Permit using a custom seed for reproducibility. - if (strcmp(argv[i], "--seed") == 0) { - if (i + 1 >= argc) { - std::cerr << "missing argument to --seed" << std::endl; - return 1; - } - - char* const seed_str = argv[++i]; // Next argument. - char* end = nullptr; - - const auto seed = - static_cast(std::strtoul(seed_str, &end, 10)); - - if (static_cast(end - seed_str) != std::strlen(seed_str)) { - std::cerr << "invalid seed string: " << seed_str << std::endl; - return 1; - } - - runner.set_seed(seed); - set_seed = true; + if (!parse_seed(argc, argv, i, set_seed)) { + return EXIT_FAILURE; } - if (strcmp(argv[i], "--jobs") == 0) { - if (i + 1 >= argc) { - std::cerr << "missing argument to --jobs" << std::endl; - return 1; - } - - char* const jobs_str = argv[++i]; // Next argument. - char* end = nullptr; - - const auto jobs = std::strtoul(jobs_str, &end, 10); + // Allow setting job count (e.g. set to 1 for debugging). + if (!parse_jobs(argc, argv, i, set_jobs)) { + return EXIT_FAILURE; + } - if (static_cast(end - jobs_str) != std::strlen(jobs_str)) { - std::cerr << "invalid jobs string: " << jobs_str << std::endl; - return 1; - } + // Allow disabling use of fork (e.g. disable for debugging). + if (!parse_use_fork(argc, argv, i, set_use_fork)) { + return EXIT_FAILURE; + } - if (jobs >= UINT_MAX) { - std::cerr << "invalid jobs string (too large): " << jobs_str << std::endl; - } + // Allow limiting executed examples to a subset of components. + // Only components whose name contains all filters as a substring are executed. + if (!parse_filter(argc, argv, i)) { + return EXIT_FAILURE; + } - runner.set_jobs(static_cast(jobs)); - set_jobs = true; + // Allow printing the name of components being executed. + // Useful when combined with `--filter`. + if (!parse_verbose(argc, argv, i)) { + return EXIT_FAILURE; } } @@ -357,5 +526,10 @@ int EXAMPLES_CDECL main(int argc, char** argv) { runner.set_jobs(0); } + // Default: use fork when available. + if (!set_use_fork) { + runner.set_use_fork(true); + } + return runner.run(); // Return directly from forked processes. } diff --git a/src/mongocxx/include/mongocxx/doc.hpp b/src/mongocxx/include/mongocxx/doc.hpp index b37dc903dc..669ad0f0d1 100644 --- a/src/mongocxx/include/mongocxx/doc.hpp +++ b/src/mongocxx/include/mongocxx/doc.hpp @@ -101,6 +101,8 @@ /// @li @subpage topic-mongocxx-examples-clients /// @li @subpage topic-mongocxx-examples-databases /// @li @subpage topic-mongocxx-examples-collections +/// @li @subpage topic-mongocxx-examples-client-sessions +/// @li @subpage topic-mongocxx-examples-change-streams /// @li @subpage topic-mongocxx-examples-operation-exceptions /// @@ -122,8 +124,8 @@ /// @page topic-mongocxx-examples-uri URI /// @brief How to create and use URIs. /// @tableofcontents -/// @see [Connection Strings (MongoDB -/// Manual)](https://www.mongodb.com/docs/manual/reference/connection-string/) +/// @see +/// - [Connection Strings (MongoDB Manual)](https://www.mongodb.com/docs/manual/reference/connection-string/) /// @include{doc} api/mongocxx/examples/uri.md /// @@ -134,6 +136,13 @@ /// @include{doc} api/mongocxx/examples/clients.md /// +/// +/// @page topic-mongocxx-examples-client-sessions Client Sessions +/// @brief How to create and use client sessions. +/// @tableofcontents +/// @include{doc} api/mongocxx/examples/client_sessions.md +/// + /// /// @page topic-mongocxx-examples-databases Databases /// @brief How to obtain and use databases. @@ -148,6 +157,13 @@ /// @include{doc} api/mongocxx/examples/collections.md /// +/// +/// @page topic-mongocxx-examples-change-streams Change Streams +/// @brief How to obtain and use change streams. +/// @tableofcontents +/// @include{doc} api/mongocxx/examples/change_streams.md +/// + /// /// @page topic-mongocxx-examples-operation-exceptions Operation Exceptions /// @brief How to handle exceptions thrown by database and collection operations.