From 6041eed20cb6ed49e846717a5918e1416347089d Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 20 Nov 2025 11:42:39 -0600 Subject: [PATCH] v1: pipeline (CXX-3237, CXX-3238) --- src/mongocxx/include/mongocxx/v1/pipeline.hpp | 2 + .../mongocxx/v_noabi/mongocxx/pipeline.hpp | 238 ++++++++++---- src/mongocxx/lib/mongocxx/v1/pipeline.cpp | 290 ++++++++++++++++- src/mongocxx/lib/mongocxx/v1/pipeline.hh | 32 ++ .../lib/mongocxx/v_noabi/mongocxx/client.cpp | 4 +- .../mongocxx/v_noabi/mongocxx/collection.cpp | 5 +- .../mongocxx/v_noabi/mongocxx/database.cpp | 4 +- .../mongocxx/v_noabi/mongocxx/pipeline.cpp | 215 +------------ .../lib/mongocxx/v_noabi/mongocxx/pipeline.hh | 26 +- src/mongocxx/test/CMakeLists.txt | 1 + src/mongocxx/test/v1/pipeline.cpp | 299 ++++++++++++++++++ 11 files changed, 810 insertions(+), 306 deletions(-) create mode 100644 src/mongocxx/lib/mongocxx/v1/pipeline.hh create mode 100644 src/mongocxx/test/v1/pipeline.cpp diff --git a/src/mongocxx/include/mongocxx/v1/pipeline.hpp b/src/mongocxx/include/mongocxx/v1/pipeline.hpp index f38f7f89e8..620b1b771d 100644 --- a/src/mongocxx/include/mongocxx/v1/pipeline.hpp +++ b/src/mongocxx/include/mongocxx/v1/pipeline.hpp @@ -252,6 +252,8 @@ class pipeline { /// Append the "$unwind" stage. /// MONGOCXX_ABI_EXPORT_CDECL(pipeline&) unwind(bsoncxx::v1::stdx::string_view v); + + class internal; }; } // namespace v1 diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pipeline.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pipeline.hpp index 49fb326e5d..48f0f74ec7 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pipeline.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/pipeline.hpp @@ -14,14 +14,20 @@ #pragma once +#include // IWYU pragma: export + +// + +#include // IWYU pragma: export + #include -#include +#include // IWYU pragma: keep: backward compatibility, to be removed. #include +#include -#include -#include -#include -#include // IWYU pragma: export +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include // IWYU pragma: keep: backward compatibility, to be removed. #include #include @@ -37,6 +43,9 @@ namespace v_noabi { /// A MongoDB aggregation pipeline. /// class pipeline { + private: + v1::pipeline _pipeline; + public: /// /// Creates a new aggregation pipeline. @@ -44,25 +53,49 @@ class pipeline { /// @see /// - https://www.mongodb.com/docs/manual/core/aggregation-pipeline/ /// - MONGOCXX_ABI_EXPORT_CDECL() pipeline(); + pipeline() = default; /// /// Move constructs a pipeline. /// - MONGOCXX_ABI_EXPORT_CDECL() pipeline(pipeline&&) noexcept; + pipeline(pipeline&& other) noexcept = default; /// /// Move assigns a pipeline. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) operator=(pipeline&&) noexcept; + pipeline& operator=(pipeline&& other) noexcept = default; /// /// Destroys a pipeline. /// - MONGOCXX_ABI_EXPORT_CDECL() ~pipeline(); + ~pipeline() = default; + + pipeline(pipeline const& other) = delete; + pipeline& operator=(pipeline const& other) = delete; - pipeline(pipeline const&) = delete; - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) operator=(pipeline const&) = delete; + /// + /// Construct with the @ref mongocxx::v1 equivalent. + /// + /* explicit(false) */ pipeline(v1::pipeline pipeline) : _pipeline{std::move(pipeline)} {} + + /// + /// Convert to the @ref mongocxx::v1 equivalent. + /// + /// @par Postconditions: + /// - `other` is in an assign-or-destroy-only state. + /// + /// @warning Invalidates all associated iterators and views. + /// + explicit operator v1::pipeline() && { + return std::move(_pipeline); + } + + /// + /// Convert to the @ref mongocxx::v1 equivalent. + /// + explicit operator v1::pipeline() const& { + return _pipeline; + } /// /// Adds new fields to documents. @@ -79,8 +112,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - add_fields(bsoncxx::v_noabi::document::view_or_value fields_to_add); + pipeline& add_fields(bsoncxx::v_noabi::document::view_or_value fields_to_add) { + _pipeline.add_fields(bsoncxx::v_noabi::to_v1(fields_to_add.view())); + return *this; + } /// /// Categorizes documents into groups, called buckets, based on a specified expression and @@ -97,8 +132,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - bucket(bsoncxx::v_noabi::document::view_or_value bucket_args); + pipeline& bucket(bsoncxx::v_noabi::document::view_or_value bucket_args) { + _pipeline.bucket(bsoncxx::v_noabi::to_v1(bucket_args.view())); + return *this; + } /// /// Categorizes documents into a specific number of groups, called buckets, based on a @@ -116,8 +153,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - bucket_auto(bsoncxx::v_noabi::document::view_or_value bucket_auto_args); + pipeline& bucket_auto(bsoncxx::v_noabi::document::view_or_value bucket_auto_args) { + _pipeline.bucket_auto(bsoncxx::v_noabi::to_v1(bucket_auto_args.view())); + return *this; + } /// /// Returns statistics regarding a collection or view. @@ -133,8 +172,11 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - coll_stats(bsoncxx::v_noabi::document::view_or_value coll_stats_args = bsoncxx::v_noabi::document::view{}); + pipeline& coll_stats( + bsoncxx::v_noabi::document::view_or_value coll_stats_args = bsoncxx::v_noabi::document::view{}) { + _pipeline.coll_stats(bsoncxx::v_noabi::to_v1(coll_stats_args.view())); + return *this; + } /// /// Returns a document containing a count of the number of documents input to the stage. @@ -149,7 +191,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) count(std::string field); + pipeline& count(std::string field) { + _pipeline.count(field); + return *this; + } /// /// Returns a stream of documents containing information on active and/or dormant @@ -167,8 +212,10 @@ class pipeline { /// @return /// A reference to the object on which this method is being called. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - current_op(bsoncxx::v_noabi::document::view_or_value current_op_args); + pipeline& current_op(bsoncxx::v_noabi::document::view_or_value current_op_args) { + _pipeline.current_op(bsoncxx::v_noabi::to_v1(current_op_args.view())); + return *this; + } /// /// Processes multiple aggregation pipelines within a single stage on the same set of input @@ -185,8 +232,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - facet(bsoncxx::v_noabi::document::view_or_value facet_args); + pipeline& facet(bsoncxx::v_noabi::document::view_or_value facet_args) { + _pipeline.facet(bsoncxx::v_noabi::to_v1(facet_args.view())); + return *this; + } /// /// Appends a stage to this pipeline object. @@ -203,8 +252,10 @@ class pipeline { /// @return /// A reference to this object on which this member function is being called. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - append_stage(bsoncxx::v_noabi::document::view_or_value stage); + pipeline& append_stage(bsoncxx::v_noabi::document::view_or_value stage) { + _pipeline.append_stage(bsoncxx::v_noabi::to_v1(stage.view())); + return *this; + } /// /// Appends stages to this pipeline object from the given bson array. @@ -221,8 +272,10 @@ class pipeline { /// @return /// A reference to the object on which this member function is being called. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - append_stages(bsoncxx::v_noabi::array::view_or_value stages); + pipeline& append_stages(bsoncxx::v_noabi::array::view_or_value stages) { + _pipeline.append_stages(bsoncxx::v_noabi::to_v1(stages.view())); + return *this; + } /// /// Outputs documents in order of nearest to farthest from a specified point. @@ -238,8 +291,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - geo_near(bsoncxx::v_noabi::document::view_or_value geo_near_args); + pipeline& geo_near(bsoncxx::v_noabi::document::view_or_value geo_near_args) { + _pipeline.geo_near(bsoncxx::v_noabi::to_v1(geo_near_args.view())); + return *this; + } /// /// Performs a recursive search on a collection. @@ -255,8 +310,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - graph_lookup(bsoncxx::v_noabi::document::view_or_value graph_lookup_args); + pipeline& graph_lookup(bsoncxx::v_noabi::document::view_or_value graph_lookup_args) { + _pipeline.graph_lookup(bsoncxx::v_noabi::to_v1(graph_lookup_args.view())); + return *this; + } /// /// Groups documents by some specified expression and outputs to the next stage a @@ -277,8 +334,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - group(bsoncxx::v_noabi::document::view_or_value group_args); + pipeline& group(bsoncxx::v_noabi::document::view_or_value group_args) { + _pipeline.group(bsoncxx::v_noabi::to_v1(group_args.view())); + return *this; + } /// /// Returns statistics regarding the use of each index for the collection. @@ -290,7 +349,10 @@ class pipeline { /// @see /// - https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexStats/ /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) index_stats(); + pipeline& index_stats() { + _pipeline.index_stats(); + return *this; + } /// /// Limits the number of documents passed to the next stage in the pipeline. @@ -305,7 +367,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) limit(std::int32_t limit); + pipeline& limit(std::int32_t limit) { + _pipeline.limit(limit); + return *this; + } /// /// Lists the sessions cached in memory by the mongod or mongos instance. @@ -321,8 +386,10 @@ class pipeline { /// @return /// A reference to the object on which this method is being called. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - list_local_sessions(bsoncxx::v_noabi::document::view_or_value list_local_sessions_args); + pipeline& list_local_sessions(bsoncxx::v_noabi::document::view_or_value list_local_sessions_args) { + _pipeline.list_local_sessions(bsoncxx::v_noabi::to_v1(list_local_sessions_args.view())); + return *this; + } /// /// Lists all sessions stored in the system.sessions collection in the config database. @@ -337,8 +404,10 @@ class pipeline { /// @return /// A reference to the object on which this method is being called. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - list_sessions(bsoncxx::v_noabi::document::view_or_value list_sessions_args); + pipeline& list_sessions(bsoncxx::v_noabi::document::view_or_value list_sessions_args) { + _pipeline.list_sessions(bsoncxx::v_noabi::to_v1(list_sessions_args.view())); + return *this; + } /// /// Performs a left outer join to an unsharded collection in the same database to filter in @@ -355,8 +424,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - lookup(bsoncxx::v_noabi::document::view_or_value lookup_args); + pipeline& lookup(bsoncxx::v_noabi::document::view_or_value lookup_args) { + _pipeline.lookup(bsoncxx::v_noabi::to_v1(lookup_args.view())); + return *this; + } /// /// Filters the documents. Only the documents that match the condition(s) specified by the @@ -372,7 +443,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) match(bsoncxx::v_noabi::document::view_or_value filter); + pipeline& match(bsoncxx::v_noabi::document::view_or_value filter) { + _pipeline.match(bsoncxx::v_noabi::to_v1(filter.view())); + return *this; + } /// /// Outputs the aggregation results to a collection. @@ -388,8 +462,10 @@ class pipeline { /// @return /// A reference to the object on which this member function is being called. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - merge(bsoncxx::v_noabi::document::view_or_value merge_args); + pipeline& merge(bsoncxx::v_noabi::document::view_or_value merge_args) { + _pipeline.merge(bsoncxx::v_noabi::to_v1(merge_args.view())); + return *this; + } /// /// Takes documents returned by the aggregation pipeline and writes them to a specified @@ -406,7 +482,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) out(std::string collection_name); + pipeline& out(std::string collection_name) { + _pipeline.out(collection_name); + return *this; + } /// /// Projects a subset of the fields in the documents to the next stage of the pipeline. @@ -421,8 +500,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - project(bsoncxx::v_noabi::document::view_or_value projection); + pipeline& project(bsoncxx::v_noabi::document::view_or_value projection) { + _pipeline.project(bsoncxx::v_noabi::to_v1(projection.view())); + return *this; + } /// /// Restricts the contents of the documents based on information stored in the documents @@ -438,8 +519,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - redact(bsoncxx::v_noabi::document::view_or_value restrictions); + pipeline& redact(bsoncxx::v_noabi::document::view_or_value restrictions) { + _pipeline.redact(bsoncxx::v_noabi::to_v1(restrictions.view())); + return *this; + } /// /// Promotes a specified document to the top level and replaces all other fields. @@ -455,8 +538,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - replace_root(bsoncxx::v_noabi::document::view_or_value replace_root_args); + pipeline& replace_root(bsoncxx::v_noabi::document::view_or_value replace_root_args) { + _pipeline.replace_root(bsoncxx::v_noabi::to_v1(replace_root_args.view())); + return *this; + } /// /// Randomly selects the specified number of documents that pass into the stage and passes the @@ -472,7 +557,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) sample(std::int32_t size); + pipeline& sample(std::int32_t size) { + _pipeline.sample(size); + return *this; + } /// /// Skips over the specified number of documents that pass into the stage and passes the @@ -488,7 +576,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) skip(std::int32_t docs_to_skip); + pipeline& skip(std::int32_t docs_to_skip) { + _pipeline.skip(docs_to_skip); + return *this; + } /// /// Sorts all input documents and returns them to the pipeline in sorted order. @@ -503,7 +594,10 @@ class pipeline { /// A reference to the object on which this member function is being called. This facilitates /// method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) sort(bsoncxx::v_noabi::document::view_or_value ordering); + pipeline& sort(bsoncxx::v_noabi::document::view_or_value ordering) { + _pipeline.sort(bsoncxx::v_noabi::to_v1(ordering.view())); + return *this; + } /// /// Groups incoming documents based on the value of a specified expression, then computes the @@ -523,8 +617,10 @@ class pipeline { /// This overload of sort_by_count() is intended to be used when the desired sort is over a /// grouping of the result of a complex expression computed from the input documents. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - sort_by_count(bsoncxx::v_noabi::document::view_or_value field_expression); + pipeline& sort_by_count(bsoncxx::v_noabi::document::view_or_value field_expression) { + _pipeline.sort_by_count(bsoncxx::v_noabi::to_v1(field_expression.view())); + return *this; + } /// /// Groups incoming documents based on the value of a specified expression, then computes the @@ -545,7 +641,10 @@ class pipeline { /// This overload of sort_by_count() is intended to be used when the desired sort is over a /// grouping of the value of a particular element in the input documents. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) sort_by_count(std::string field_expression); + pipeline& sort_by_count(std::string field_expression) { + _pipeline.sort_by_count(field_expression); + return *this; + } /// /// Deconstructs an array field from the input documents to output a document for each element. @@ -566,8 +665,10 @@ class pipeline { /// This overload of unwind() is intended to be used when additional options other than the /// field name need to be specified. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) - unwind(bsoncxx::v_noabi::document::view_or_value unwind_args); + pipeline& unwind(bsoncxx::v_noabi::document::view_or_value unwind_args) { + _pipeline.unwind(bsoncxx::v_noabi::to_v1(unwind_args.view())); + return *this; + } /// /// Deconstructs an array field from the input documents to output a document for each element. @@ -588,20 +689,19 @@ class pipeline { /// This overload of unwind() is intended to be used when no options other than the field name /// need to be specified. /// - MONGOCXX_ABI_EXPORT_CDECL(pipeline&) unwind(std::string field_name); + pipeline& unwind(std::string field_name) { + _pipeline.unwind(field_name); + return *this; + } /// /// @return A view of the underlying BSON array this pipeline represents. /// - MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::array::view) view_array() const; - - private: - friend ::mongocxx::v_noabi::client; - friend ::mongocxx::v_noabi::collection; - friend ::mongocxx::v_noabi::database; + bsoncxx::v_noabi::array::view view_array() const { + return _pipeline.view_array(); + } - class impl; - std::unique_ptr _impl; + class internal; }; } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v1/pipeline.cpp b/src/mongocxx/lib/mongocxx/v1/pipeline.cpp index a37b51f0d3..5eff59b5ff 100644 --- a/src/mongocxx/lib/mongocxx/v1/pipeline.cpp +++ b/src/mongocxx/lib/mongocxx/v1/pipeline.cpp @@ -12,4 +12,292 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include + +// + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace mongocxx { +namespace v1 { + +class pipeline::impl { + private: + static_assert(INT32_MAX == std::int32_t{2147483647}, ""); + std::array _idx = {}; // Access via `this->idx()`. + + public: + scoped_bson _doc; + std::int32_t _count = 0; + + char const* idx() { + (void)std::snprintf(_idx.data(), _idx.size(), "%" PRId32, _count); + return _idx.data(); + } + + void append(bsoncxx::v1::document::view doc) { + _doc += scoped_bson{BCON_NEW(this->idx(), BCON_DOCUMENT(scoped_bson_view{doc}.bson()))}; + ++_count; + } + + void append(char const* name, bsoncxx::v1::document::view doc) { + _doc += scoped_bson{BCON_NEW(this->idx(), "{", name, BCON_DOCUMENT(scoped_bson_view{doc}.bson()), "}")}; + ++_count; + } + + void append(char const* name, bsoncxx::v1::stdx::string_view v) { + _doc += scoped_bson{BCON_NEW(this->idx(), "{", name, BCON_UTF8(std::string{v}.c_str()), "}")}; + ++_count; + } + + void append(char const* name, std::int32_t v) { + _doc += scoped_bson{BCON_NEW(this->idx(), "{", name, BCON_INT32(v), "}")}; + ++_count; + } + + void append(char const* name, std::int64_t v) { + _doc += scoped_bson{BCON_NEW(this->idx(), "{", name, BCON_INT64(v), "}")}; + ++_count; + } + + static impl const& with(pipeline const& self) { + return *static_cast(self._impl); + } + + static impl const* with(pipeline const* self) { + return static_cast(self->_impl); + } + + static impl& with(pipeline& self) { + return *static_cast(self._impl); + } + + static impl* with(pipeline* self) { + return static_cast(self->_impl); + } + + static impl* with(void* ptr) { + return static_cast(ptr); + } +}; + +pipeline::~pipeline() { + delete impl::with(this); +} + +pipeline::pipeline(pipeline&& other) noexcept : _impl{exchange(other._impl, nullptr)} {} + +pipeline& pipeline::operator=(pipeline&& other) noexcept { + if (this != &other) { + delete impl::with(exchange(_impl, exchange(other._impl, nullptr))); + } + + return *this; +} + +pipeline::pipeline(pipeline const& other) : _impl{new impl{impl::with(other)}} {} + +pipeline& pipeline::operator=(pipeline const& other) { + if (this != &other) { + delete impl::with(exchange(_impl, new impl{impl::with(other)})); + } + + return *this; +} + +pipeline::pipeline() : _impl{new impl{}} {} + +bsoncxx::v1::array::view pipeline::view_array() const { + return impl::with(this)->_doc.array_view(); +} + +pipeline& pipeline::append_stage(bsoncxx::v1::document::view v) { + impl::with(this)->append(v); + return *this; +} + +pipeline& pipeline::append_stages(bsoncxx::v1::array::view v) { + auto& impl = impl::with(*this); + + for (auto const& e : v) { + impl.append(e.get_document().value); + } + + return *this; +} + +pipeline& pipeline::add_fields(bsoncxx::v1::document::view v) { + impl::with(this)->append("$addFields", v); + return *this; +} + +pipeline& pipeline::bucket(bsoncxx::v1::document::view v) { + impl::with(this)->append("$bucket", v); + return *this; +} + +pipeline& pipeline::bucket_auto(bsoncxx::v1::document::view v) { + impl::with(this)->append("$bucketAuto", v); + return *this; +} + +pipeline& pipeline::coll_stats(bsoncxx::v1::document::view v) { + impl::with(this)->append("$collStats", v); + return *this; +} + +pipeline& pipeline::coll_stats() { + impl::with(this)->append("$collStats", bsoncxx::v1::document::view{}); + return *this; +} + +pipeline& pipeline::count(bsoncxx::v1::stdx::string_view v) { + impl::with(this)->append("$count", v); + return *this; +} + +pipeline& pipeline::current_op(bsoncxx::v1::document::view v) { + impl::with(this)->append("$currentOp", v); + return *this; +} + +pipeline& pipeline::facet(bsoncxx::v1::document::view v) { + impl::with(this)->append("$facet", v); + return *this; +} + +pipeline& pipeline::geo_near(bsoncxx::v1::document::view v) { + impl::with(this)->append("$geoNear", v); + return *this; +} + +pipeline& pipeline::graph_lookup(bsoncxx::v1::document::view v) { + impl::with(this)->append("$graphLookup", v); + return *this; +} + +pipeline& pipeline::group(bsoncxx::v1::document::view v) { + impl::with(this)->append("$group", v); + return *this; +} + +pipeline& pipeline::index_stats() { + impl::with(this)->append("$indexStats", bsoncxx::v1::document::view{}); + return *this; +} + +pipeline& pipeline::limit(std::int32_t v) { + impl::with(this)->append("$limit", v); + return *this; +} + +pipeline& pipeline::limit(std::int64_t v) { + impl::with(this)->append("$limit", v); + return *this; +} + +pipeline& pipeline::list_local_sessions(bsoncxx::v1::document::view v) { + impl::with(this)->append("$listLocalSessions", v); + return *this; +} + +pipeline& pipeline::list_sessions(bsoncxx::v1::document::view v) { + impl::with(this)->append("$listSessions", v); + return *this; +} + +pipeline& pipeline::lookup(bsoncxx::v1::document::view v) { + impl::with(this)->append("$lookup", v); + return *this; +} + +pipeline& pipeline::match(bsoncxx::v1::document::view v) { + impl::with(this)->append("$match", v); + return *this; +} + +pipeline& pipeline::merge(bsoncxx::v1::document::view v) { + impl::with(this)->append("$merge", v); + return *this; +} + +pipeline& pipeline::out(bsoncxx::v1::stdx::string_view v) { + impl::with(this)->append("$out", v); + return *this; +} + +pipeline& pipeline::project(bsoncxx::v1::document::view v) { + impl::with(this)->append("$project", v); + return *this; +} + +pipeline& pipeline::redact(bsoncxx::v1::document::view v) { + impl::with(this)->append("$redact", v); + return *this; +} + +pipeline& pipeline::replace_root(bsoncxx::v1::document::view v) { + impl::with(this)->append("$replaceRoot", v); + return *this; +} + +pipeline& pipeline::sample(std::int32_t v) { + impl::with(this)->append("$sample", scoped_bson{BCON_NEW("size", BCON_INT32(v))}.view()); + return *this; +} + +pipeline& pipeline::skip(std::int32_t v) { + impl::with(this)->append("$skip", v); + return *this; +} + +pipeline& pipeline::skip(std::int64_t v) { + impl::with(this)->append("$skip", v); + return *this; +} + +pipeline& pipeline::sort(bsoncxx::v1::document::view v) { + impl::with(this)->append("$sort", v); + return *this; +} + +pipeline& pipeline::sort_by_count(bsoncxx::v1::document::view v) { + impl::with(this)->append("$sortByCount", v); + return *this; +} + +pipeline& pipeline::sort_by_count(bsoncxx::v1::stdx::string_view v) { + impl::with(this)->append("$sortByCount", v); + return *this; +} + +pipeline& pipeline::unwind(bsoncxx::v1::document::view v) { + impl::with(this)->append("$unwind", v); + return *this; +} + +pipeline& pipeline::unwind(bsoncxx::v1::stdx::string_view v) { + impl::with(this)->append("$unwind", v); + return *this; +} + +scoped_bson const& pipeline::internal::doc(pipeline const& self) { + return impl::with(self)._doc; +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/pipeline.hh b/src/mongocxx/lib/mongocxx/v1/pipeline.hh new file mode 100644 index 0000000000..415422c5d9 --- /dev/null +++ b/src/mongocxx/lib/mongocxx/v1/pipeline.hh @@ -0,0 +1,32 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include // IWYU pragma: export + +// + +#include + +namespace mongocxx { +namespace v1 { + +class pipeline::internal { + public: + static scoped_bson const& doc(pipeline const& self); +}; + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp index 0895375d86..5407067993 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/client.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -29,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -302,7 +302,7 @@ client::watch(client_session const& session, pipeline const& pipe, options::chan change_stream client::_watch(client_session const* session, pipeline const& pipe, options::change_stream const& options) { bsoncxx::v_noabi::builder::basic::document container; - container.append(bsoncxx::v_noabi::builder::basic::kvp("pipeline", pipe._impl->view_array())); + container.append(bsoncxx::v_noabi::builder::basic::kvp("pipeline", pipe.view_array())); bsoncxx::v_noabi::builder::basic::document options_builder; options_builder.append(bsoncxx::v_noabi::builder::concatenate(options.as_bson())); diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp index 6b2818dc22..587b386600 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/collection.cpp @@ -32,7 +32,6 @@ #include #include -#include #include #include #include @@ -468,7 +467,7 @@ collection::_aggregate(client_session const* session, pipeline const& pipeline, libmongoc::collection_aggregate( _get_impl().collection_t, static_cast<::mongoc_query_flags_t>(0), - mongocxx::to_scoped_bson_view(pipeline._impl->view_array()), + v_noabi::pipeline::internal::doc(pipeline).bson(), mongocxx::to_scoped_bson_view(b), read_prefs)); } @@ -1360,7 +1359,7 @@ collection::watch(client_session const& session, pipeline const& pipe, options:: change_stream collection::_watch(client_session const* session, pipeline const& pipe, options::change_stream const& options) { bsoncxx::v_noabi::builder::basic::document container; - container.append(kvp("pipeline", pipe._impl->view_array())); + container.append(kvp("pipeline", v_noabi::pipeline::internal::doc(pipe).array_view())); bsoncxx::v_noabi::builder::basic::document options_builder; options_builder.append(bsoncxx::v_noabi::builder::concatenate(options.as_bson())); diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp index 667fa2f17e..f6fe4215f6 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/database.cpp @@ -135,7 +135,7 @@ database::_aggregate(client_session const* session, pipeline const& pipeline, op return cursor( libmongoc::database_aggregate( _get_impl().database_t, - to_scoped_bson_view(pipeline._impl->view_array()), + v_noabi::pipeline::internal::doc(pipeline).bson(), to_scoped_bson_view(b), read_prefs)); } @@ -408,7 +408,7 @@ database::watch(client_session const& session, pipeline const& pipe, options::ch change_stream database::_watch(client_session const* session, pipeline const& pipe, options::change_stream const& options) { bsoncxx::v_noabi::builder::basic::document container; - container.append(kvp("pipeline", pipe._impl->view_array())); + container.append(kvp("pipeline", pipe.view_array())); bsoncxx::v_noabi::builder::basic::document options_builder; options_builder.append(bsoncxx::v_noabi::builder::concatenate(options.as_bson())); diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pipeline.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pipeline.cpp index 8bafaa4dd3..828c957591 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pipeline.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pipeline.cpp @@ -12,222 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include - -#include - #include -#include +// + +#include -using bsoncxx::v_noabi::builder::basic::kvp; -using bsoncxx::v_noabi::builder::basic::sub_document; +#include namespace mongocxx { namespace v_noabi { -pipeline::pipeline() : _impl(bsoncxx::make_unique()) {} - -pipeline::pipeline(pipeline&&) noexcept = default; -pipeline& pipeline::operator=(pipeline&&) noexcept = default; -pipeline::~pipeline() = default; - -pipeline& pipeline::add_fields(bsoncxx::v_noabi::document::view_or_value fields_to_add) { - _impl->sink().append([fields_to_add](sub_document sub_doc) { sub_doc.append(kvp("$addFields", fields_to_add)); }); - - return *this; -} - -pipeline& pipeline::append_stage(bsoncxx::v_noabi::document::view_or_value stage) { - _impl->sink().append(std::move(stage)); - return *this; -} - -pipeline& pipeline::append_stages(bsoncxx::v_noabi::array::view_or_value stages) { - for (auto&& stage : stages.view()) { - _impl->sink().append(stage.get_document().value); - } - return *this; -} - -pipeline& pipeline::bucket(bsoncxx::v_noabi::document::view_or_value bucket_args) { - _impl->sink().append([bucket_args](sub_document sub_doc) { sub_doc.append(kvp("$bucket", bucket_args)); }); - - return *this; -} - -pipeline& pipeline::bucket_auto(bsoncxx::v_noabi::document::view_or_value bucket_auto_args) { - _impl->sink().append( - [bucket_auto_args](sub_document sub_doc) { sub_doc.append(kvp("$bucketAuto", bucket_auto_args)); }); - - return *this; -} - -pipeline& pipeline::coll_stats(bsoncxx::v_noabi::document::view_or_value coll_stats_args) { - _impl->sink().append( - [coll_stats_args](sub_document sub_doc) { sub_doc.append(kvp("$collStats", coll_stats_args)); }); - - return *this; -} - -pipeline& pipeline::count(std::string field) { - _impl->sink().append([field](sub_document sub_doc) { sub_doc.append(kvp("$count", field)); }); - - return *this; -} - -pipeline& pipeline::current_op(bsoncxx::v_noabi::document::view_or_value current_op_args) { - _impl->sink().append( - [current_op_args](sub_document sub_doc) { sub_doc.append(kvp("$currentOp", current_op_args)); }); - - return *this; -} - -pipeline& pipeline::facet(bsoncxx::v_noabi::document::view_or_value facet_args) { - _impl->sink().append([facet_args](sub_document sub_doc) { sub_doc.append(kvp("$facet", facet_args)); }); - - return *this; -} - -pipeline& pipeline::geo_near(bsoncxx::v_noabi::document::view_or_value geo_near_args) { - _impl->sink().append([geo_near_args](sub_document sub_doc) { sub_doc.append(kvp("$geoNear", geo_near_args)); }); - - return *this; -} - -pipeline& pipeline::graph_lookup(bsoncxx::v_noabi::document::view_or_value graph_lookup_args) { - _impl->sink().append( - [graph_lookup_args](sub_document sub_doc) { sub_doc.append(kvp("$graphLookup", graph_lookup_args)); }); - - return *this; -} - -pipeline& pipeline::group(bsoncxx::v_noabi::document::view_or_value group_args) { - _impl->sink().append([group_args](sub_document sub_doc) { sub_doc.append(kvp("$group", group_args)); }); - - return *this; -} - -pipeline& pipeline::index_stats() { - _impl->sink().append( - [](sub_document sub_doc) { sub_doc.append(kvp("$indexStats", bsoncxx::v_noabi::document::view{})); }); - - return *this; -} - -pipeline& pipeline::limit(std::int32_t limit) { - _impl->sink().append([limit](sub_document sub_doc) { sub_doc.append(kvp("$limit", limit)); }); - - return *this; -} - -pipeline& pipeline::list_local_sessions(bsoncxx::v_noabi::document::view_or_value list_local_sessions_args) { - _impl->sink().append([list_local_sessions_args](sub_document sub_doc) { - sub_doc.append(kvp("$listLocalSessions", list_local_sessions_args)); - }); - - return *this; -} - -pipeline& pipeline::list_sessions(bsoncxx::v_noabi::document::view_or_value list_sessions_args) { - _impl->sink().append( - [list_sessions_args](sub_document sub_doc) { sub_doc.append(kvp("$listSessions", list_sessions_args)); }); - - return *this; -} - -pipeline& pipeline::lookup(bsoncxx::v_noabi::document::view_or_value lookup_args) { - _impl->sink().append([lookup_args](sub_document sub_doc) { sub_doc.append(kvp("$lookup", lookup_args)); }); - - return *this; -} - -pipeline& pipeline::match(bsoncxx::v_noabi::document::view_or_value filter) { - _impl->sink().append([filter](sub_document sub_doc) { sub_doc.append(kvp("$match", filter)); }); - - return *this; -} - -pipeline& pipeline::merge(bsoncxx::v_noabi::document::view_or_value merge_args) { - _impl->sink().append([merge_args](sub_document sub_doc) { sub_doc.append(kvp("$merge", merge_args)); }); - - return *this; -} - -pipeline& pipeline::out(std::string collection_name) { - _impl->sink().append([collection_name](sub_document sub_doc) { sub_doc.append(kvp("$out", collection_name)); }); - - return *this; -} - -pipeline& pipeline::project(bsoncxx::v_noabi::document::view_or_value projection) { - _impl->sink().append([projection](sub_document sub_doc) { sub_doc.append(kvp("$project", projection)); }); - - return *this; -} - -pipeline& pipeline::redact(bsoncxx::v_noabi::document::view_or_value restrictions) { - _impl->sink().append([restrictions](sub_document sub_doc) { sub_doc.append(kvp("$redact", restrictions)); }); - - return *this; -} - -pipeline& pipeline::replace_root(bsoncxx::v_noabi::document::view_or_value replace_root_args) { - _impl->sink().append( - [replace_root_args](sub_document sub_doc) { sub_doc.append(kvp("$replaceRoot", replace_root_args)); }); - - return *this; -} - -pipeline& pipeline::sample(std::int32_t size) { - _impl->sink().append([size](sub_document sub_doc1) { - sub_doc1.append(kvp("$sample", [size](sub_document sub_doc2) { sub_doc2.append(kvp("size", size)); })); - }); - - return *this; -} - -pipeline& pipeline::skip(std::int32_t docs_to_skip) { - _impl->sink().append([docs_to_skip](sub_document sub_doc) { sub_doc.append(kvp("$skip", docs_to_skip)); }); - - return *this; -} - -pipeline& pipeline::sort(bsoncxx::v_noabi::document::view_or_value ordering) { - _impl->sink().append([ordering](sub_document sub_doc) { sub_doc.append(kvp("$sort", ordering)); }); - - return *this; -} - -pipeline& pipeline::sort_by_count(bsoncxx::v_noabi::document::view_or_value field_expression) { - _impl->sink().append( - [field_expression](sub_document sub_doc) { sub_doc.append(kvp("$sortByCount", field_expression)); }); - - return *this; -} - -pipeline& pipeline::sort_by_count(std::string field_expression) { - _impl->sink().append( - [field_expression](sub_document sub_doc) { sub_doc.append(kvp("$sortByCount", field_expression)); }); - - return *this; -} - -pipeline& pipeline::unwind(bsoncxx::v_noabi::document::view_or_value unwind_args) { - _impl->sink().append([unwind_args](sub_document sub_doc) { sub_doc.append(kvp("$unwind", unwind_args)); }); - - return *this; -} - -pipeline& pipeline::unwind(std::string field_name) { - _impl->sink().append([field_name](sub_document sub_doc) { sub_doc.append(kvp("$unwind", field_name)); }); - - return *this; -} - -bsoncxx::v_noabi::array::view pipeline::view_array() const { - return _impl->view_array(); +scoped_bson const& pipeline::internal::doc(pipeline const& self) { + return v1::pipeline::internal::doc(self._pipeline); } } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pipeline.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pipeline.hh index a3cc8ae199..0cbc7ff0c3 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pipeline.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/pipeline.hh @@ -14,32 +14,18 @@ #pragma once -#include - #include // IWYU pragma: export +// + +#include + namespace mongocxx { namespace v_noabi { -class pipeline::impl { +class pipeline::internal { public: - bsoncxx::v_noabi::builder::basic::array& sink() { - return _builder; - } - - bsoncxx::v_noabi::array::view view_array() { - return _builder.view(); - } - - /// - /// view() is deprecated. Use view_array() instead. - /// - bsoncxx::v_noabi::document::view view() { - return _builder.view(); - } - - private: - bsoncxx::v_noabi::builder::basic::array _builder; + static scoped_bson const& doc(pipeline const& self); }; } // namespace v_noabi diff --git a/src/mongocxx/test/CMakeLists.txt b/src/mongocxx/test/CMakeLists.txt index 6fcacc5b8b..3751bf9aee 100644 --- a/src/mongocxx/test/CMakeLists.txt +++ b/src/mongocxx/test/CMakeLists.txt @@ -105,6 +105,7 @@ set(mongocxx_test_sources_v1 v1/data_key_options.cpp v1/exception.cpp v1/logger.cpp + v1/pipeline.cpp v1/range_options.cpp v1/read_concern.cpp v1/read_preference.cpp diff --git a/src/mongocxx/test/v1/pipeline.cpp b/src/mongocxx/test/v1/pipeline.cpp new file mode 100644 index 0000000000..427749bf26 --- /dev/null +++ b/src/mongocxx/test/v1/pipeline.cpp @@ -0,0 +1,299 @@ +// 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 mongocxx { +namespace v1 { + +TEST_CASE("ownership", "[mongocxx][v1][pipeline]") { + pipeline source; + pipeline target; + + source.append_stage(scoped_bson{R"({"$stage": "source"})"}.view()); + target.append_stage(scoped_bson{R"({"$stage": "target"})"}.view()); + + CHECK(source.view_array() == scoped_bson{R"([{"$stage": "source"}])"}.array_view()); + CHECK(target.view_array() == scoped_bson{R"([{"$stage": "target"}])"}.array_view()); + + auto const source_value = source.view_array(); + + SECTION("move") { + auto move = std::move(source); + + // source is in an assign-or-move-only state. + + CHECK(move.view_array() == source_value); + + target = std::move(move); + + // source is in an assign-or-move-only state. + + CHECK(target.view_array() == source_value); + } + + SECTION("copy") { + auto copy = source; + + CHECK(source.view_array() == source_value); + CHECK(copy.view_array() == source_value); + + target = copy; + + CHECK(copy.view_array() == source_value); + CHECK(target.view_array() == source_value); + } +} + +TEST_CASE("default") { + pipeline const v; + + CHECK(v.view_array().empty()); +} + +TEST_CASE("append_stage", "[mongocxx][v1][pipeline]") { + pipeline v; + + v.append_stage(scoped_bson{R"({"a": 1})"}.view()); + CHECK(v.view_array() == scoped_bson{R"([{"a": 1}])"}.view()); + + v.append_stage(scoped_bson{R"({"b": 2})"}.view()); + CHECK(v.view_array() == scoped_bson{R"([{"a": 1}, {"b": 2}])"}.view()); +} + +TEST_CASE("append_stages", "[mongocxx][v1][pipeline]") { + static bsoncxx::v1::array::view const empty; + + pipeline v; + + SECTION("valid") { + v.append_stages(empty); + CHECK(v.view_array() == empty); + + v.append_stages(scoped_bson{R"([{"a": 1}, {"b": 2}])"}.array_view()); + CHECK(v.view_array() == scoped_bson{R"([{"a": 1}, {"b": 2}])"}.view()); + + v.append_stages(scoped_bson{R"([{"c": 3}, {"d": 4}])"}.array_view()); + CHECK(v.view_array() == scoped_bson{R"([{"a": 1}, {"b": 2}, {"c": 3}, {"d": 4}])"}.view()); + } + + SECTION("invalid") { + SECTION("values") { + auto const input = GENERATE(values({ + scoped_bson{R"([123])"}, + scoped_bson{R"(["abc"])"}, + scoped_bson{R"([["x"]])"}, + })); + + CAPTURE(input.view()); + + CHECK_THROWS_WITH_CODE(v.append_stages(input.array_view()), bsoncxx::v1::types::view::errc::type_mismatch); + + CHECK(v.view_array() == empty); + } + + SECTION("elements") { + scoped_bson const input{R"([{"a": 1}, "throws", {"b": 2}])"}; + + CHECK_THROWS_WITH_CODE(v.append_stages(input.array_view()), bsoncxx::v1::types::view::errc::type_mismatch); + + CHECK(v.view_array() == scoped_bson{R"([{"a": 1}])"}.view()); + } + } +} + +TEST_CASE("document", "[mongocxx][v1][pipeline]") { + SECTION("common") { + using mem_fn_type = pipeline& (pipeline::*)(bsoncxx::v1::document::view); + + mem_fn_type mem_fn = {}; + char const* name = {}; + + std::tie(mem_fn, name) = GENERATE( + table({ + {&pipeline::add_fields, "$addFields"}, + {&pipeline::bucket, "$bucket"}, + {&pipeline::bucket_auto, "$bucketAuto"}, + {&pipeline::coll_stats, "$collStats"}, + {&pipeline::current_op, "$currentOp"}, + {&pipeline::facet, "$facet"}, + {&pipeline::geo_near, "$geoNear"}, + {&pipeline::graph_lookup, "$graphLookup"}, + {&pipeline::group, "$group"}, + {&pipeline::list_local_sessions, "$listLocalSessions"}, + {&pipeline::list_sessions, "$listSessions"}, + {&pipeline::lookup, "$lookup"}, + {&pipeline::match, "$match"}, + {&pipeline::merge, "$merge"}, + {&pipeline::project, "$project"}, + {&pipeline::redact, "$redact"}, + {&pipeline::replace_root, "$replaceRoot"}, + {&pipeline::sort, "$sort"}, + {&pipeline::sort_by_count, "$sortByCount"}, + {&pipeline::unwind, "$unwind"}, + })); + + CAPTURE(name); + + auto const input = GENERATE(values({ + scoped_bson{}, + scoped_bson{R"({"x": 1})"}, + scoped_bson{R"({"x": 1, "y": 2})"}, + })); + + CAPTURE(input); + + auto const v = (pipeline{}.*mem_fn)(input.view()); + auto const expected = scoped_bson{BCON_NEW("0", "{", name, BCON_DOCUMENT(input.bson()), "}")}; + + CHECK(v.view_array() == expected.view()); + } + + SECTION("coll_stats") { + auto const v = pipeline{}.coll_stats(); + auto const expected = scoped_bson{R"({"0": {"$collStats": {}}})"}; + + CHECK(v.view_array() == expected.view()); + } + + SECTION("index_stats") { + auto const v = pipeline{}.index_stats(); + auto const expected = scoped_bson{R"({"0": {"$indexStats": {}}})"}; + + CHECK(v.view_array() == expected.view()); + } +} + +TEST_CASE("string", "[mongocxx][v1][pipeline]") { + using mem_fn_type = pipeline& (pipeline::*)(bsoncxx::v1::stdx::string_view); + + mem_fn_type mem_fn = {}; + char const* name = {}; + + std::tie(mem_fn, name) = GENERATE( + table({ + {&pipeline::count, "$count"}, + {&pipeline::out, "$out"}, + {&pipeline::sort_by_count, "$sortByCount"}, + {&pipeline::unwind, "$unwind"}, + })); + + CAPTURE(name); + + auto const input = GENERATE(values({ + "", + "x", + "abc", + })); + + CAPTURE(input); + + auto const v = (pipeline{}.*mem_fn)(input); + auto const expected = scoped_bson{BCON_NEW("0", "{", name, BCON_UTF8(input), "}")}; + + CHECK(v.view_array() == expected.view()); +} + +TEST_CASE("int32", "[mongocxx][v1][pipeline]") { + auto const input = GENERATE(values({ + std::int32_t{INT32_MIN}, + std::int32_t{-1}, + std::int32_t{0}, + std::int32_t{1}, + std::int32_t{INT32_MAX}, + })); + + SECTION("common") { + using mem_fn_type = pipeline& (pipeline::*)(std::int32_t); + + mem_fn_type mem_fn = {}; + char const* name = {}; + + std::tie(mem_fn, name) = GENERATE( + table({ + {&pipeline::limit, "$limit"}, + {&pipeline::skip, "$skip"}, + })); + + CAPTURE(name); + + CAPTURE(input); + + auto const v = (pipeline{}.*mem_fn)(input); + auto const expected = scoped_bson{BCON_NEW("0", "{", name, BCON_INT32(input), "}")}; + + CHECK(v.view_array() == expected.view()); + } + + SECTION("sample") { + auto const v = pipeline{}.sample(input); + auto const expected = scoped_bson{BCON_NEW("0", "{", "$sample", "{", "size", BCON_INT32(input), "}", "}")}; + + CHECK(v.view_array() == expected.view()); + } +} + +TEST_CASE("int64", "[mongocxx][v1][pipeline]") { + using mem_fn_type = pipeline& (pipeline::*)(std::int64_t); + + mem_fn_type mem_fn = {}; + char const* name = {}; + + std::tie(mem_fn, name) = GENERATE( + table({ + {&pipeline::limit, "$limit"}, + {&pipeline::skip, "$skip"}, + })); + + CAPTURE(name); + + auto const input = GENERATE(values({ + std::int64_t{INT64_MIN}, + std::int64_t{-1}, + std::int64_t{0}, + std::int64_t{1}, + std::int64_t{INT64_MAX}, + })); + + CAPTURE(input); + + auto const v = (pipeline{}.*mem_fn)(input); + auto const expected = scoped_bson{BCON_NEW("0", "{", name, BCON_INT64(input), "}")}; + + CHECK(v.view_array() == expected.view()); +} + +} // namespace v1 +} // namespace mongocxx