4#include "Stroika/Foundation/StroikaPreComp.h"
6#if qStroika_HasComponent_mongocxxdriver
8#if defined(__clang_major__) and (16 < __clang_major__ && __clang_major__ < 21)
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__ < 21)
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::document::value, 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 transferred =
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 if (useFieldName == kMongoID_) {
303 filterDoc.append (kvp (
"_id", bsoncxx::oid{rhsValue->As<
String> ().AsUTF8<string> ()}));
306 filterDoc.append (kvp (useFieldName.
AsUTF8<
string> (), VV2BSONV_ (*rhsValue)));
312 if (not transferred) {
318 return make_tuple (filterDoc.extract (), clientSideOps.
empty () ? optional<Filter>{} : make_optional (
Filter{clientSideOps}));
321 return make_tuple (nullopt, filter);
323 return make_tuple (nullopt, nullopt);
336 tuple<optional<bsoncxx::document::value>, optional<Projection>> Partition_ (
const optional<Projection>& p)
342 tuple<Document::Projection::Flag, Set<String>> fields = p->GetFields ();
343 Require (get<1> (fields).size () >= 1);
344 bsoncxx::builder::basic::document projectionDoc;
345 for (
String f : get<1> (fields)) {
346 String mongoFieldName = f;
347 if (mongoFieldName == Database::Document::kID) {
348 mongoFieldName = kMongoID_;
350 projectionDoc.append (kvp (mongoFieldName.
AsUTF8<
string> (), get<0> (fields) == Document::Projection::Flag::eInclude ? 1 : 0));
352 return make_tuple (projectionDoc.extract (), nullopt);
354 return make_tuple (nullopt, nullopt);
359 struct AdminRep_ final : Stroika::Foundation::Database::Document::MongoDBClient::AdminConnection::IRep {
361 variant<mongocxx::client, mongocxx::pool::entry> fClientStorage_;
362 mongocxx::client* fClientPtr_;
364 AdminRep_ (
const AdminRep_&) =
delete;
365 AdminRep_ (
const AdminConnection::Options& options)
368 if (
auto os = get_if<String> (&options.fConnectionTarget)) {
369 fClientStorage_ = mongocxx::client{ConnectionString2MongoURI_ (*os)};
370 fClientPtr_ = get_if<mongocxx::client> (&fClientStorage_);
372 else if (
auto op = get_if<ConnectionPool> (&options.fConnectionTarget)) {
373 fClientStorage_ = op->PeekPool ().acquire ();
374 fClientPtr_ = get<mongocxx::pool::entry> (fClientStorage_).operator->();
378 ~AdminRep_ () =
default;
382 virtual mongocxx::client& GetClientRef ()
override
388#if USE_NOISY_TRACE_IN_THIS_MODULE_
392 mongocxx::database adminDB_;
393 return FromBSON_ (fClientPtr_->database (
"admin").run_command (ToBSON_ (v)));
402 vector<string> n = fClientPtr_->list_database_names ();
409 virtual void DropDatabase (
const String& dbName)
override
412 mongocxx::database{fClientPtr_->database (dbName.
AsUTF8<
string> ())}.drop ();
418 virtual void CreateDatabase (
const String& dbName)
override
422 mongocxx::database d{fClientPtr_->database (dbName.
AsUTF8<
string> ())};
423 d.create_collection (
"_junk_");
424 d.collection (
"_junk_").drop ();
434 struct ConnectionRep_ final : Stroika::Foundation::Database::Document::MongoDBClient::Connection::IRep {
437 shared_ptr<ConnectionRep_> fConnectionRep_;
438 mongocxx::collection fCollection_;
440 CollectionRep_ (
const shared_ptr<ConnectionRep_>& connectionRep,
const String& collectionName)
441 : fConnectionRep_{connectionRep}
442 , fCollection_{connectionRep->fDatabase_.collection (collectionName.AsUTF8<string> ())}
444#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
445 fAssertExternallySynchronizedMutex_.SetAssertExternallySynchronizedMutexContext (
446 connectionRep->fAssertExternallySynchronizedMutex_.GetSharedContext ());
451#if USE_NOISY_TRACE_IN_THIS_MODULE_
457 if (
auto insert_one_result = fCollection_.insert_one (ToBSON_ (v))) {
458 return ID_2_string_ (insert_one_result->inserted_id ());
466 virtual optional<Document::Document> GetOne (
const IDType&
id,
const optional<Projection>& projection)
override
468#if USE_NOISY_TRACE_IN_THIS_MODULE_
473 bsoncxx::builder::basic::document filterDoc;
474 filterDoc.append (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()}));
475 auto [mongoProjection, myProjection] = Partition_ (projection);
476 mongocxx::options::find o;
477 if (mongoProjection) {
478 o.projection (mongoProjection->view ());
480 auto result = fCollection_.find_one (filterDoc.view (), o);
482 auto rr = FromBSON_ (bsoncxx::document::view_or_value{*result});
484 rr = myProjection->
Apply (rr);
496#if USE_NOISY_TRACE_IN_THIS_MODULE_
497 TraceContextBumper ctx{
"MongoDBClient::CollectionRep_::GetAll()",
"filter={}, projection={}"_f, filter, projection};
500 auto [mongoFilter, myFilter] = Partition_ (filter);
503 auto [mongoProjection, myProjection] = myFilter == nullopt ? Partition_ (projection) : make_tuple (nullopt, projection);
504#if USE_NOISY_TRACE_IN_THIS_MODULE_
505 DbgTrace (
"myFilter={}"_f, myFilter);
511 mongocxx::options::find o;
512 if (mongoProjection) {
513 o.projection (mongoProjection->view ());
516 auto cursor = fCollection_.find (mongoFilter ? mongoFilter->view () : bsoncxx::builder::basic::document{}.view (), o);
517 for (
auto&& doc : cursor) {
518 auto rr = FromBSON_ (doc);
520 rr = myProjection->Apply (rr);
522 if (not myFilter or myFilter->Matches (rr)) {
534#if USE_NOISY_TRACE_IN_THIS_MODULE_
540 if (onlyTheseFields) {
545 bsoncxx::document::value bsonDoc = ToBSON_ (uploadDoc);
546 if (onlyTheseFields) {
547 if (
auto o = fCollection_.update_one (make_document (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()})),
548 make_document (kvp (
"$set", bsonDoc.view ())))) {
549 if (o->modified_count () == 0) {
558 if (
auto o = fCollection_.replace_one (make_document (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()})), bsonDoc.view ())) {
559 if (o->modified_count () == 0) {
572 virtual void Remove (
const IDType&
id)
override
574#if USE_NOISY_TRACE_IN_THIS_MODULE_
579 bsoncxx::builder::basic::document filterDoc;
580 filterDoc.append (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()}));
581 auto result = fCollection_.delete_one (filterDoc.view ());
582 if (result && result->deleted_count () == 0) {
593 variant<mongocxx::client, mongocxx::pool::entry> fClientStorage_;
594 mongocxx::database fDatabase_;
596 ConnectionRep_ (
const Connection::Options& options)
600 if (
auto os = get_if<String> (&options.fConnectionTarget)) {
601 fClientStorage_ = mongocxx::client{ConnectionString2MongoURI_ (*os)};
602 fDatabase_ = get<mongocxx::client> (fClientStorage_).database (options.fDatabase.AsUTF8<
string> ());
604 else if (
auto op = get_if<ConnectionPool> (&options.fConnectionTarget)) {
605 fClientStorage_ = op->PeekPool ().acquire ();
606 fDatabase_ = get<mongocxx::pool::entry> (fClientStorage_)->database (options.fDatabase.AsUTF8<
string> ());
614 ~ConnectionRep_ () =
default;
618 virtual shared_ptr<const EngineProperties> GetEngineProperties ()
const override
621 virtual String GetEngineName ()
const override
623 return "mongo-cxx-driver"sv;
626 return make_shared<const MyEngineProperties_> ();
632 vector<string> n = fDatabase_.list_collection_names ();
635 catch (
const mongocxx::v_noabi::operation_exception& e) {
637 if (e.raw_server_error ()) {
638 DbgTrace (
"e.raw={}"_f, FromBSON_ (e.raw_server_error ()->view ()));
647 virtual void CreateCollection (
const String& name)
override
649#if USE_NOISY_TRACE_IN_THIS_MODULE_
654 fDatabase_.create_collection (name.
AsUTF8<
string> ());
660 virtual void DropCollection (
const String& name)
override
662#if USE_NOISY_TRACE_IN_THIS_MODULE_
667 fDatabase_.collection (name.
AsUTF8<
string> ()).drop ();
678 make_shared<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
698 virtual mongocxx::client& GetClientRef ()
override
700 if (
auto oc = get_if<mongocxx::client> (&fClientStorage_)) {
703 else if (
auto oe = get_if<mongocxx::pool::entry> (&fClientStorage_)) {
704 return *oe->operator->();
708 static mongocxx::client x;
720unique_ptr<mongocxx::instance> Document::MongoDBClient::Activator::sMongoInstance_;
722Document::MongoDBClient::Activator::Activator ()
723 : fAllowReactivation_{false}
725 Require (Debug::AppearsDuringMainLifetime ());
726 if (sActivatorLiveCnt_.fetch_add (1) == 0) {
727 if (not sMongoInstance_) {
728 sMongoInstance_ = make_unique<mongocxx::instance> ();
733Document::MongoDBClient::Activator::Activator (AllowReactivateFlag)
736 fAllowReactivation_ =
true;
739Document::MongoDBClient::Activator::~Activator ()
741 Require (Debug::AppearsDuringMainLifetime ());
742 Require (sActivatorLiveCnt_ > 0);
743 if (sActivatorLiveCnt_.fetch_sub (1) == 0 and not fAllowReactivation_) {
744 sMongoInstance_.reset ();
753ConnectionPool::ConnectionPool (shared_ptr<mongocxx::pool>&& poolRep)
754 : fPool_{move (poolRep)}
757ConnectionPool::ConnectionPool (
const String& connectionString)
758 : ConnectionPool{make_shared<mongocxx::pool> (ConnectionString2MongoURI_ (connectionString))}
762mongocxx::pool& ConnectionPool::PeekPool ()
const
773Document::MongoDBClient::Connection::Ptr::Ptr (
const shared_ptr<IRep>& src)
783auto Document::MongoDBClient::AdminConnection::New (
const AdminConnection::Options& options) -> Ptr
785#if qStroika_Foundation_Debug_AssertionsChecked
786 Require (sActivatorLiveCnt_ > 0);
788 return Ptr{make_shared<AdminRep_> (options)};
796auto Document::MongoDBClient::Connection::New (
const Connection::Options& options) -> Ptr
798#if qStroika_Foundation_Debug_AssertionsChecked
799 Require (sActivatorLiveCnt_ > 0);
801 return Ptr{make_shared<ConnectionRep_> (options)};
809struct Transaction::MyRep_ :
public MongoDBClient::Transaction::IRep {
811 : fConnectionPtr_{db}
815 virtual void Commit ()
override
817 Require (not fCompleted_);
821 virtual void Rollback ()
override
823 Require (not fCompleted_);
827 virtual Disposition GetDisposition ()
const override
830 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
833 bool fCompleted_{
false};
836 : 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)