4#include "Stroika/Foundation/StroikaPreComp.h"
6#if qStroika_HasComponent_mongocxxdriver
8#if defined(__clang_major__) and (16 < __clang_major__ && __clang_major__ < 20)
9DISABLE_COMPILER_CLANG_WARNING_START (
"clang diagnostic ignored \"-Wdeprecated-literal-operator\"");
11#include <bsoncxx/builder/basic/document.hpp>
12#include <bsoncxx/document/value.hpp>
13#include <bsoncxx/json.hpp>
14#include <bsoncxx/string/to_string.hpp>
15#include <bsoncxx/types.hpp>
16#include <bsoncxx/types/bson_value/value.hpp>
17#include <bsoncxx/types/bson_value/view.hpp>
18#include <bsoncxx/view_or_value.hpp>
19#include <mongocxx/client.hpp>
20#include <mongocxx/collection.hpp>
21#include <mongocxx/exception/exception.hpp>
22#include <mongocxx/exception/operation_exception.hpp>
23#include <mongocxx/instance.hpp>
24#include <mongocxx/pool.hpp>
25#include <mongocxx/uri.hpp>
26#if defined(__clang_major__) and (16 < __clang_major__ && __clang_major__ < 20)
27DISABLE_COMPILER_CLANG_WARNING_END (
"clang diagnostic ignored \"-Wdeprecated-literal-operator\"");
29DISABLE_COMPILER_MSC_WARNING_END (4166)
34#include "Stroika/Foundation/Containers/Concrete/Mapping_HashTable.h"
38#include "Stroika/Foundation/Database/Exception.h"
39#include "Stroika/Foundation/Debug/Main.h"
40#include "Stroika/Foundation/Memory/Common.h"
42#include "MongoDBClient.h"
47using namespace Stroika::Foundation::Database;
48using namespace Stroika::Foundation::Database::Document::MongoDBClient;
50using namespace Stroika::Foundation::Debug;
52using namespace Stroika::Foundation::Memory;
71#if qStroika_HasComponent_mongocxxdriver
73using bsoncxx::builder::basic::kvp;
74using bsoncxx::builder::basic::make_array;
75using bsoncxx::builder::basic::make_document;
76using bsoncxx::builder::basic::sub_array;
77using bsoncxx::builder::basic::sub_document;
80 const String kMongoID_ =
"_id"sv;
84 atomic<unsigned int> sActivatorLiveCnt_{0};
88 auto ConnectionString2MongoURI_ (
const String& connectionString)
90 return mongocxx::uri{connectionString.
AsUTF8<
string> ()};
95 [[noreturn]]
void DoReThrow_ ()
107 String ID_2_string_ (
const bsoncxx::types::bson_value::view& value)
109 switch (value.type ()) {
110 case bsoncxx::type::k_oid:
111 return String{value.get_oid ().value.to_string ()};
117 template <Common::IAnyOf<bsoncxx::types::bson_value::view, bsoncxx::document::element, bsoncxx::array::element> T>
120 switch (value.type ()) {
121 case bsoncxx::type::k_double:
122 return value.get_double ().value;
123 case bsoncxx::type::k_string:
124 return String::FromUTF8 (SpanBytesCast<span<const char8_t>> (span{value.get_string ().value}));
125 case bsoncxx::type::k_document: {
127 const bsoncxx::types::b_document& thisDoc = value.get_document ();
128 for (
auto di : thisDoc.value) {
129 vvResult.
Add (String::FromUTF8 (span{di.key ()}), BSON2VV_ (di));
133 case bsoncxx::type::k_array: {
134 vector<VariantValue> vvResult;
135 const bsoncxx::types::b_array& thisArray = value.get_array ();
136 vvResult.reserve (distance (thisArray.value.begin (), thisArray.value.end ()));
137 for (
auto ai : thisArray.value) {
138 vvResult.push_back (BSON2VV_ (ai));
142 case bsoncxx::type::k_binary:
143 return Memory::BLOB{span{value.get_binary ().bytes,
static_cast<size_t> (value.get_binary ().size)}};
144 case bsoncxx::type::k_undefined:
146 case bsoncxx::type::k_oid:
147 return String{value.get_oid ().value.to_string ()};
148 case bsoncxx::type::k_bool:
149 return static_cast<bool> (value.get_bool ());
150 case bsoncxx::type::k_date:
152 return Time::DateTime{chrono::time_point<chrono::system_clock>{value.get_date ().value}};
153 case bsoncxx::type::k_null:
155 case bsoncxx::type::k_regex:
156 return String::FromUTF8 (SpanBytesCast<span<const char8_t>> (span{value.get_string ().value}));
157 case bsoncxx::type::k_dbpointer:
160 case bsoncxx::type::k_code:
163 case bsoncxx::type::k_symbol:
166 case bsoncxx::type::k_codewscope:
169 case bsoncxx::type::k_int32:
170 return value.get_int32 ().value;
171 case bsoncxx::type::k_timestamp:
174 case bsoncxx::type::k_int64:
175 return value.get_int64 ().value;
176 case bsoncxx::type::k_decimal128:
179 case bsoncxx::type::k_maxkey:
180 case bsoncxx::type::k_minkey:
188 bsoncxx::types::bson_value::value VV2BSONV_ (
const VariantValue& vv)
190 using namespace std::chrono;
192 switch (vv.GetType ()) {
193 case VariantValue::Type::eNull:
194 return bsoncxx::types::bson_value::value{
nullptr};
195 case VariantValue::Type::eBLOB:
196 return bsoncxx::types::bson_value::value{vv.
As<
Memory::BLOB> ().As<vector<uint8_t>> ()};
197 case VariantValue::Type::eBoolean:
198 return bsoncxx::types::bson_value::value{vv.
As<
bool> ()};
199 case VariantValue::Type::eInteger:
200 return bsoncxx::types::bson_value::value{vv.
As<int64_t> ()};
201 case VariantValue::Type::eUnsignedInteger:
202 return bsoncxx::types::bson_value::value{
static_cast<int64_t
> (vv.
As<uint64_t> ())};
203 case VariantValue::Type::eFloat:
204 return bsoncxx::types::bson_value::value{vv.
As<
double> ()};
205 case VariantValue::Type::eDate:
213 case VariantValue::Type::eDateTime:
221 return vv.
As<DateTime> ().AsUTC ().Format (DateTime::kISO8601Format).AsUTF8<
string> ();
222 case VariantValue::Type::eString:
223 return bsoncxx::types::bson_value::value{vv.
As<
String> ().AsUTF8<string> ()};
224 case VariantValue::Type::eArray: {
225 bsoncxx::builder::basic::array bsonArr;
227 bsonArr.append (VV2BSONV_ (ai));
229 return bsoncxx::types::bson_value::value{bsonArr};
231 case VariantValue::Type::eMap: {
232 bsoncxx::builder::basic::document bsonDoc;
234 bsonDoc.append (kvp (ai.fKey.AsUTF8<
string> (), VV2BSONV_ (ai.fValue)));
236 return bsoncxx::types::bson_value::value{bsonDoc};
240 return bsoncxx::types::bson_value::value{
nullptr};
246 for (
const bsoncxx::document::element& di : b.view ()) {
247 result.
Add (String::FromUTF8 (span{di.key ()}), BSON2VV_ (di));
252 result.
Remove (kMongoID_);
253 result.
Add (Database::Document::kID, idValue);
264 auto idValue = vv[Database::Document::kID];
265 newDoc.
Remove (Database::Document::kID);
268 bsoncxx::builder::basic::document bsonDoc;
270 bsonDoc.append (kvp (ai.fKey.AsUTF8<
string> (), VV2BSONV_ (ai.fValue)));
272 return bsonDoc.extract ();
283 tuple<optional<bsoncxx::document::value>, optional<Filter>> Partition_ (
const optional<Filter>& filter)
293 bsoncxx::builder::basic::document filterDoc;
294 bool anyTransfers =
false;
296 bool transfered =
false;
297 if (
const Document::FilterElements::Equals* eqOp = get_if<Document::FilterElements::Equals> (&op)) {
298 String useFieldName = eqOp->fLHS == Database::Document::kID ? kMongoID_ : eqOp->fLHS;
301 filterDoc.append (kvp (useFieldName.
AsUTF8<
string> (), VV2BSONV_ (*rhsValue)));
306 if (not transfered) {
312 return make_tuple (filterDoc.extract (), clientSideOps.
empty () ? optional<Filter>{} : make_optional (
Filter{clientSideOps}));
315 return make_tuple (nullopt, filter);
317 return make_tuple (nullopt, nullopt);
330 tuple<optional<bsoncxx::document::value>, optional<Projection>> Partition_ (
const optional<Projection>& p)
336 tuple<Document::Projection::Flag, Set<String>> fields = p->GetFields ();
337 Require (get<1> (fields).size () >= 1);
338 bsoncxx::builder::basic::document projectionDoc;
339 for (
String f : get<1> (fields)) {
340 String mongoFieldName = f;
341 if (mongoFieldName == Database::Document::kID) {
342 mongoFieldName = kMongoID_;
344 projectionDoc.append (kvp (mongoFieldName.
AsUTF8<
string> (), get<0> (fields) == Document::Projection::Flag::eInclude ? 1 : 0));
346 return make_tuple (projectionDoc.extract (), nullopt);
348 return make_tuple (nullopt, nullopt);
353 struct AdminRep_ final : Stroika::Foundation::Database::Document::MongoDBClient::AdminConnection::IRep {
355 variant<mongocxx::client, mongocxx::pool::entry> fClientStorage_;
356 mongocxx::client* fClientPtr_;
358 AdminRep_ (
const AdminRep_&) =
delete;
359 AdminRep_ (
const AdminConnection::Options& options)
362 if (
auto os = get_if<String> (&options.fConnectionTarget)) {
363 fClientStorage_ = mongocxx::client{ConnectionString2MongoURI_ (*os)};
364 fClientPtr_ = get_if<mongocxx::client> (&fClientStorage_);
366 else if (
auto op = get_if<ConnectionPool> (&options.fConnectionTarget)) {
367 fClientStorage_ = op->PeekPool ().acquire ();
368 fClientPtr_ = get<mongocxx::pool::entry> (fClientStorage_).operator->();
372 ~AdminRep_ () =
default;
376 virtual mongocxx::client& GetClientRef ()
override
382#if USE_NOISY_TRACE_IN_THIS_MODULE_
386 mongocxx::database adminDB_;
387 return FromBSON_ (fClientPtr_->database (
"admin").run_command (ToBSON_ (v)));
396 vector<string> n = fClientPtr_->list_database_names ();
403 virtual void DropDatabase (
const String& dbName)
override
406 mongocxx::database{fClientPtr_->database (dbName.
AsUTF8<
string> ())}.drop ();
412 virtual void CreateDatabase (
const String& dbName)
override
416 mongocxx::database d{fClientPtr_->database (dbName.
AsUTF8<
string> ())};
417 d.create_collection (
"_junk_");
418 d.collection (
"_junk_").drop ();
428 struct ConnectionRep_ final : Stroika::Foundation::Database::Document::MongoDBClient::Connection::IRep {
431 shared_ptr<ConnectionRep_> fConnectionRep_;
432 mongocxx::collection fCollection_;
434 CollectionRep_ (
const shared_ptr<ConnectionRep_>& connectionRep,
const String& collectionName)
435 : fConnectionRep_{connectionRep}
436 , fCollection_{connectionRep->fDatabase_.collection (collectionName.AsUTF8<string> ())}
438#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
439 fAssertExternallySynchronizedMutex_.SetAssertExternallySynchronizedMutexContext (
440 connectionRep->fAssertExternallySynchronizedMutex_.GetSharedContext ());
445#if USE_NOISY_TRACE_IN_THIS_MODULE_
451 if (
auto insert_one_result = fCollection_.insert_one (ToBSON_ (v))) {
452 return ID_2_string_ (insert_one_result->inserted_id ());
460 virtual optional<Document::Document> GetOne (
const IDType&
id,
const optional<Projection>& projection)
override
462#if USE_NOISY_TRACE_IN_THIS_MODULE_
467 bsoncxx::builder::basic::document filterDoc;
468 filterDoc.append (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()}));
469 auto [mongoProjection, myProjection] = Partition_ (projection);
470 mongocxx::options::find o;
471 if (mongoProjection) {
472 o.projection (mongoProjection->view ());
474 auto result = fCollection_.find_one (filterDoc.view (), o);
476 auto rr = FromBSON_ (bsoncxx::document::view_or_value{*result});
478 rr = myProjection->
Apply (rr);
490#if USE_NOISY_TRACE_IN_THIS_MODULE_
494 auto [mongoFilter, myFilter] = Partition_ (filter);
495 auto [mongoProjection, myProjection] = Partition_ (projection);
497 mongocxx::options::find o;
498 if (mongoProjection) {
499 o.projection (mongoProjection->view ());
502 auto cursor = fCollection_.find (mongoFilter ? mongoFilter->view () : bsoncxx::builder::basic::document{}.view (), o);
503 for (
auto&& doc : cursor) {
504 auto rr = FromBSON_ (doc);
506 rr = myProjection->Apply (rr);
508 if (not filter or filter->Matches (rr)) {
520#if USE_NOISY_TRACE_IN_THIS_MODULE_
526 if (onlyTheseFields) {
531 bsoncxx::document::value bsonDoc = ToBSON_ (uploadDoc);
532 if (onlyTheseFields) {
533 if (
auto o = fCollection_.update_one (make_document (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()})),
534 make_document (kvp (
"$set", bsonDoc.view ())))) {
535 if (o->modified_count () == 0) {
544 if (
auto o = fCollection_.replace_one (make_document (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()})), bsonDoc.view ())) {
545 if (o->modified_count () == 0) {
558 virtual void Remove (
const IDType&
id)
override
560#if USE_NOISY_TRACE_IN_THIS_MODULE_
565 bsoncxx::builder::basic::document filterDoc;
566 filterDoc.append (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()}));
567 auto result = fCollection_.delete_one (filterDoc.view ());
568 if (result && result->deleted_count () == 0) {
579 variant<mongocxx::client, mongocxx::pool::entry> fClientStorage_;
580 mongocxx::database fDatabase_;
582 ConnectionRep_ (
const Connection::Options& options)
586 if (
auto os = get_if<String> (&options.fConnectionTarget)) {
587 fClientStorage_ = mongocxx::client{ConnectionString2MongoURI_ (*os)};
588 fDatabase_ = get<mongocxx::client> (fClientStorage_).database (options.fDatabase.AsUTF8<
string> ());
590 else if (
auto op = get_if<ConnectionPool> (&options.fConnectionTarget)) {
591 fClientStorage_ = op->PeekPool ().acquire ();
592 fDatabase_ = get<mongocxx::pool::entry> (fClientStorage_)->database (options.fDatabase.AsUTF8<
string> ());
600 ~ConnectionRep_ () =
default;
604 virtual shared_ptr<const EngineProperties> GetEngineProperties ()
const override
607 virtual String GetEngineName ()
const override
609 return "mongo-cxx-driver"sv;
612 return make_shared<const MyEngineProperties_> ();
618 vector<string> n = fDatabase_.list_collection_names ();
621 catch (
const mongocxx::v_noabi::operation_exception& e) {
623 if (e.raw_server_error ()) {
624 DbgTrace (
"e.raw={}"_f, FromBSON_ (e.raw_server_error ()->view ()));
633 virtual void CreateCollection (
const String& name)
override
635#if USE_NOISY_TRACE_IN_THIS_MODULE_
640 fDatabase_.create_collection (name.
AsUTF8<
string> ());
646 virtual void DropCollection (
const String& name)
override
648#if USE_NOISY_TRACE_IN_THIS_MODULE_
653 fDatabase_.collection (name.
AsUTF8<
string> ()).drop ();
664 make_shared<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
684 virtual mongocxx::client& GetClientRef ()
override
686 if (
auto oc = get_if<mongocxx::client> (&fClientStorage_)) {
689 else if (
auto oe = get_if<mongocxx::pool::entry> (&fClientStorage_)) {
690 return *oe->operator->();
694 static mongocxx::client x;
706unique_ptr<mongocxx::instance> Document::MongoDBClient::Activator::sMongoInstance_;
708Document::MongoDBClient::Activator::Activator ()
709 : fAllowReactivation_{false}
711 Require (Debug::AppearsDuringMainLifetime ());
712 if (sActivatorLiveCnt_.fetch_add (1) == 0) {
713 if (not sMongoInstance_) {
714 sMongoInstance_ = make_unique<mongocxx::instance> ();
718Document::MongoDBClient::Activator::Activator (AllowReactivateFlag)
721 fAllowReactivation_ =
true;
724Document::MongoDBClient::Activator::~Activator ()
726 Require (Debug::AppearsDuringMainLifetime ());
727 Require (sActivatorLiveCnt_ > 0);
728 if (sActivatorLiveCnt_.fetch_sub (1) == 0 and not fAllowReactivation_) {
729 sMongoInstance_.reset ();
738ConnectionPool::ConnectionPool (shared_ptr<mongocxx::pool>&& poolRep)
739 : fPool_{move (poolRep)}
742ConnectionPool::ConnectionPool (
const String& connectionString)
743 : ConnectionPool{make_shared<mongocxx::pool> (ConnectionString2MongoURI_ (connectionString))}
747mongocxx::pool& ConnectionPool::PeekPool ()
const
758Document::MongoDBClient::Connection::Ptr::Ptr (
const shared_ptr<IRep>& src)
768auto Document::MongoDBClient::AdminConnection::New (
const AdminConnection::Options& options) -> Ptr
770#if qStroika_Foundation_Debug_AssertionsChecked
771 Require (sActivatorLiveCnt_ > 0);
773 return Ptr{make_shared<AdminRep_> (options)};
781auto Document::MongoDBClient::Connection::New (
const Connection::Options& options) -> Ptr
783#if qStroika_Foundation_Debug_AssertionsChecked
784 Require (sActivatorLiveCnt_ > 0);
786 return Ptr{make_shared<ConnectionRep_> (options)};
794struct Transaction::MyRep_ :
public MongoDBClient::Transaction::IRep {
796 : fConnectionPtr_{db}
800 virtual void Commit ()
override
802 Require (not fCompleted_);
806 virtual void Rollback ()
override
808 Require (not fCompleted_);
812 virtual Disposition GetDisposition ()
const override
815 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
818 bool fCompleted_{
false};
821 : inherited{make_unique<MyRep_> (db)}
#define WeakAssertNotReached()
#define AssertNotImplemented()
#define AssertNotReached()
variant< Equals > Operation
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual T AsUTF8() const
Mapping_HashTable<KEY_TYPE, MAPPED_VALUE_TYPE, TRAITS> is a HashTable based concrete implementation o...
Sequence_stdvector<T> is an std::vector-based concrete implementation of the Sequence<T> container pa...
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
nonvirtual bool ContainsKey(ArgByValueType< key_type > key) const
nonvirtual bool RemoveIf(ArgByValueType< key_type > key)
Remove the given item, if it exists. Return true if found and removed.
nonvirtual void Remove(ArgByValueType< key_type > key)
Remove the given item (which must exist).
nonvirtual void RetainAll(const ITERABLE_OF_KEY_TYPE &items)
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
nonvirtual RETURNTYPE As() const
EngineProperties captures the features associated with a given database engine (being talked to throu...
define a (simple) projection on a document, subsetting the fields of that document.
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...
NestedException contains a new higher level error message (typically based on argument basedOnExcepti...
static constexpr string_view kISO8601Format
Y-M-D format - locale independent, and ISO-8601 date format standard.
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement, Execution::SequencePolicy seq=Execution::SequencePolicy::eDEFAULT) const
Run the argument function (or lambda) on each element of the container.
nonvirtual RESULT_CONTAINER Map(ELEMENT_MAPPER &&elementMapper) const
functional API which iterates over all members of an Iterable, applies a map function to each element...
nonvirtual bool empty() const
Returns true iff size() == 0.
DISABLE_COMPILER_MSC_WARNING_START(4996)
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
bool AnyCurrentActivities()
Checks if CaptureCurrentActivities() would produce a non-empty stack (but faster)