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"
41#include "Stroika/Foundation/Memory/Common.h"
43#include "MongoDBClient.h"
48using namespace Stroika::Foundation::Database;
49using namespace Stroika::Foundation::Database::Document::MongoDBClient;
51using namespace Stroika::Foundation::Debug;
53using namespace Stroika::Foundation::Memory;
72#if qStroika_HasComponent_mongocxxdriver
74using bsoncxx::builder::basic::kvp;
75using bsoncxx::builder::basic::make_array;
76using bsoncxx::builder::basic::make_document;
77using bsoncxx::builder::basic::sub_array;
78using bsoncxx::builder::basic::sub_document;
81 const String kMongoID_ =
"_id"sv;
85 atomic<unsigned int> sActivatorLiveCnt_{0};
89 auto ConnectionString2MongoURI_ (
const String& connectionString)
91 return mongocxx::uri{connectionString.
AsUTF8<
string> ()};
96 [[noreturn]]
void DoReThrow_ ()
108 String ID_2_string_ (
const bsoncxx::types::bson_value::view& value)
110 switch (value.type ()) {
111 case bsoncxx::type::k_oid:
112 return String{value.get_oid ().value.to_string ()};
118 template <Common::IAnyOf<bsoncxx::types::bson_value::view, bsoncxx::document::element, bsoncxx::document::value, bsoncxx::array::element> T>
121 switch (value.type ()) {
122 case bsoncxx::type::k_double:
123 return value.get_double ().value;
124 case bsoncxx::type::k_string:
125 return String::FromUTF8 (SpanBytesCast<span<const char8_t>> (span{value.get_string ().value}));
126 case bsoncxx::type::k_document: {
128 const bsoncxx::types::b_document& thisDoc = value.get_document ();
129 for (
auto di : thisDoc.value) {
130 vvResult.
Add (String::FromUTF8 (span{di.key ()}), BSON2VV_ (di));
134 case bsoncxx::type::k_array: {
135 vector<VariantValue> vvResult;
136 const bsoncxx::types::b_array& thisArray = value.get_array ();
137 vvResult.reserve (distance (thisArray.value.begin (), thisArray.value.end ()));
138 for (
auto ai : thisArray.value) {
139 vvResult.push_back (BSON2VV_ (ai));
143 case bsoncxx::type::k_binary:
144 return Memory::BLOB{span{value.get_binary ().bytes,
static_cast<size_t> (value.get_binary ().size)}};
145 case bsoncxx::type::k_undefined:
147 case bsoncxx::type::k_oid:
148 return String{value.get_oid ().value.to_string ()};
149 case bsoncxx::type::k_bool:
150 return static_cast<bool> (value.get_bool ());
151 case bsoncxx::type::k_date:
153 return Time::DateTime{chrono::time_point<chrono::system_clock>{value.get_date ().value}};
154 case bsoncxx::type::k_null:
156 case bsoncxx::type::k_regex:
157 return String::FromUTF8 (SpanBytesCast<span<const char8_t>> (span{value.get_string ().value}));
158 case bsoncxx::type::k_dbpointer:
161 case bsoncxx::type::k_code:
164 case bsoncxx::type::k_symbol:
167 case bsoncxx::type::k_codewscope:
170 case bsoncxx::type::k_int32:
171 return value.get_int32 ().value;
172 case bsoncxx::type::k_timestamp:
175 case bsoncxx::type::k_int64:
176 return value.get_int64 ().value;
177 case bsoncxx::type::k_decimal128:
180 case bsoncxx::type::k_maxkey:
181 case bsoncxx::type::k_minkey:
189 bsoncxx::types::bson_value::value VV2BSONV_ (
const VariantValue& vv)
191 using namespace std::chrono;
193 switch (vv.GetType ()) {
194 case VariantValue::Type::eNull:
195 return bsoncxx::types::bson_value::value{
nullptr};
196 case VariantValue::Type::eBLOB:
197 return bsoncxx::types::bson_value::value{vv.
As<
Memory::BLOB> ().As<vector<uint8_t>> ()};
198 case VariantValue::Type::eBoolean:
199 return bsoncxx::types::bson_value::value{vv.
As<
bool> ()};
200 case VariantValue::Type::eInteger:
201 return bsoncxx::types::bson_value::value{vv.
As<int64_t> ()};
202 case VariantValue::Type::eUnsignedInteger:
203 return bsoncxx::types::bson_value::value{
static_cast<int64_t
> (vv.
As<uint64_t> ())};
204 case VariantValue::Type::eFloat:
205 return bsoncxx::types::bson_value::value{vv.
As<
double> ()};
206 case VariantValue::Type::eDate:
214 case VariantValue::Type::eDateTime:
222 return vv.
As<DateTime> ().AsUTC ().Format (DateTime::kISO8601Format).AsUTF8<
string> ();
223 case VariantValue::Type::eString:
224 return bsoncxx::types::bson_value::value{vv.
As<
String> ().AsUTF8<string> ()};
225 case VariantValue::Type::eArray: {
226 bsoncxx::builder::basic::array bsonArr;
228 bsonArr.append (VV2BSONV_ (ai));
230 return bsoncxx::types::bson_value::value{bsonArr};
232 case VariantValue::Type::eMap: {
233 bsoncxx::builder::basic::document bsonDoc;
235 bsonDoc.append (kvp (ai.fKey.AsUTF8<
string> (), VV2BSONV_ (ai.fValue)));
237 return bsoncxx::types::bson_value::value{bsonDoc};
241 return bsoncxx::types::bson_value::value{
nullptr};
247 for (
const bsoncxx::document::element& di : b.view ()) {
248 result.
Add (String::FromUTF8 (span{di.key ()}), BSON2VV_ (di));
253 result.
Remove (kMongoID_);
254 result.
Add (Database::Document::kID, idValue);
265 auto idValue = vv[Database::Document::kID];
266 newDoc.
Remove (Database::Document::kID);
269 bsoncxx::builder::basic::document bsonDoc;
271 bsonDoc.append (kvp (ai.fKey.AsUTF8<
string> (), VV2BSONV_ (ai.fValue)));
273 return bsonDoc.extract ();
284 tuple<optional<bsoncxx::document::value>, optional<Filter>> Partition_ (
const optional<Filter>& filter)
294 bsoncxx::builder::basic::document filterDoc;
295 bool anyTransfers =
false;
297 bool transferred =
false;
298 if (
const Document::FilterElements::Equals* eqOp = get_if<Document::FilterElements::Equals> (&op)) {
299 String useFieldName = eqOp->fLHS == Database::Document::kID ? kMongoID_ : eqOp->fLHS;
302 if (useFieldName == kMongoID_) {
304 filterDoc.append (kvp (
"_id", bsoncxx::oid{rhsValue->As<
String> ().AsUTF8<string> ()}));
307 filterDoc.append (kvp (useFieldName.
AsUTF8<
string> (), VV2BSONV_ (*rhsValue)));
313 if (not transferred) {
319 return make_tuple (filterDoc.extract (), clientSideOps.
empty () ? optional<Filter>{} : make_optional (
Filter{clientSideOps}));
322 return make_tuple (nullopt, filter);
324 return make_tuple (nullopt, nullopt);
337 tuple<optional<bsoncxx::document::value>, optional<Projection>> Partition_ (
const optional<Projection>& p)
343 tuple<Document::Projection::Flag, Set<String>> fields = p->GetFields ();
344 Require (get<1> (fields).size () >= 1);
345 bsoncxx::builder::basic::document projectionDoc;
346 for (
String f : get<1> (fields)) {
347 String mongoFieldName = f;
348 if (mongoFieldName == Database::Document::kID) {
349 mongoFieldName = kMongoID_;
351 projectionDoc.append (kvp (mongoFieldName.
AsUTF8<
string> (), get<0> (fields) == Document::Projection::Flag::eInclude ? 1 : 0));
353 return make_tuple (projectionDoc.extract (), nullopt);
355 return make_tuple (nullopt, nullopt);
360 struct AdminRep_ final : Stroika::Foundation::Database::Document::MongoDBClient::AdminConnection::IRep {
362 variant<mongocxx::client, mongocxx::pool::entry> fClientStorage_;
363 mongocxx::client* fClientPtr_;
365 AdminRep_ (
const AdminRep_&) =
delete;
366 AdminRep_ (
const AdminConnection::Options& options)
369 if (
auto os = get_if<String> (&options.fConnectionTarget)) {
370 fClientStorage_ = mongocxx::client{ConnectionString2MongoURI_ (*os)};
371 fClientPtr_ = get_if<mongocxx::client> (&fClientStorage_);
373 else if (
auto op = get_if<ConnectionPool> (&options.fConnectionTarget)) {
374 fClientStorage_ = op->PeekPool ().acquire ();
375 fClientPtr_ = get<mongocxx::pool::entry> (fClientStorage_).operator->();
379 ~AdminRep_ () =
default;
383 virtual mongocxx::client& GetClientRef ()
override
389#if USE_NOISY_TRACE_IN_THIS_MODULE_
393 mongocxx::database adminDB_;
394 return FromBSON_ (fClientPtr_->database (
"admin").run_command (ToBSON_ (v)));
403 vector<string> n = fClientPtr_->list_database_names ();
410 virtual void DropDatabase (
const String& dbName)
override
413 mongocxx::database{fClientPtr_->database (dbName.
AsUTF8<
string> ())}.drop ();
419 virtual void CreateDatabase (
const String& dbName)
override
423 mongocxx::database d{fClientPtr_->database (dbName.
AsUTF8<
string> ())};
424 d.create_collection (
"_junk_");
425 d.collection (
"_junk_").drop ();
435 struct ConnectionRep_ final : Stroika::Foundation::Database::Document::MongoDBClient::Connection::IRep {
438 shared_ptr<ConnectionRep_> fConnectionRep_;
439 mongocxx::collection fCollection_;
441 CollectionRep_ (
const shared_ptr<ConnectionRep_>& connectionRep,
const String& collectionName)
442 : fConnectionRep_{connectionRep}
443 , fCollection_{connectionRep->fDatabase_.collection (collectionName.AsUTF8<string> ())}
445#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
446 fAssertExternallySynchronizedMutex_.SetAssertExternallySynchronizedMutexContext (
447 connectionRep->fAssertExternallySynchronizedMutex_.GetSharedContext ());
452#if USE_NOISY_TRACE_IN_THIS_MODULE_
458 if (
auto insert_one_result = fCollection_.insert_one (ToBSON_ (v))) {
459 return ID_2_string_ (insert_one_result->inserted_id ());
467 virtual optional<Document::Document> GetOne (
const IDType&
id,
const optional<Projection>& projection)
override
469#if USE_NOISY_TRACE_IN_THIS_MODULE_
474 bsoncxx::builder::basic::document filterDoc;
475 filterDoc.append (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()}));
476 auto [mongoProjection, myProjection] = Partition_ (projection);
477 mongocxx::options::find o;
478 if (mongoProjection) {
479 o.projection (mongoProjection->view ());
481 auto result = fCollection_.find_one (filterDoc.view (), o);
483 auto rr = FromBSON_ (bsoncxx::document::view_or_value{*result});
485 rr = myProjection->
Apply (rr);
497#if USE_NOISY_TRACE_IN_THIS_MODULE_
498 TraceContextBumper ctx{
"MongoDBClient::CollectionRep_::GetAll()",
"filter={}, projection={}"_f, filter, projection};
501 auto [mongoFilter, myFilter] = Partition_ (filter);
504 auto [mongoProjection, myProjection] = myFilter == nullopt ? Partition_ (projection) : make_tuple (nullopt, projection);
505#if USE_NOISY_TRACE_IN_THIS_MODULE_
506 DbgTrace (
"myFilter={}"_f, myFilter);
512 mongocxx::options::find o;
513 if (mongoProjection) {
514 o.projection (mongoProjection->view ());
517 auto cursor = fCollection_.find (mongoFilter ? mongoFilter->view () : bsoncxx::builder::basic::document{}.view (), o);
518 for (
auto&& doc : cursor) {
519 auto rr = FromBSON_ (doc);
521 rr = myProjection->Apply (rr);
523 if (not myFilter or myFilter->Matches (rr)) {
535#if USE_NOISY_TRACE_IN_THIS_MODULE_
541 if (onlyTheseFields) {
546 bsoncxx::document::value bsonDoc = ToBSON_ (uploadDoc);
547 if (onlyTheseFields) {
548 if (
auto o = fCollection_.update_one (make_document (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()})),
549 make_document (kvp (
"$set", bsonDoc.view ())))) {
550 if (o->modified_count () == 0) {
559 if (
auto o = fCollection_.replace_one (make_document (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()})), bsonDoc.view ())) {
560 if (o->modified_count () == 0) {
573 virtual void Remove (
const IDType&
id)
override
575#if USE_NOISY_TRACE_IN_THIS_MODULE_
580 bsoncxx::builder::basic::document filterDoc;
581 filterDoc.append (kvp (
"_id", bsoncxx::oid{
id.AsUTF8<
string> ()}));
582 auto result = fCollection_.delete_one (filterDoc.view ());
583 if (result && result->deleted_count () == 0) {
594 variant<mongocxx::client, mongocxx::pool::entry> fClientStorage_;
595 mongocxx::database fDatabase_;
597 ConnectionRep_ (
const Connection::Options& options)
601 if (
auto os = get_if<String> (&options.fConnectionTarget)) {
602 fClientStorage_ = mongocxx::client{ConnectionString2MongoURI_ (*os)};
603 fDatabase_ = get<mongocxx::client> (fClientStorage_).database (options.fDatabase.AsUTF8<
string> ());
605 else if (
auto op = get_if<ConnectionPool> (&options.fConnectionTarget)) {
606 fClientStorage_ = op->PeekPool ().acquire ();
607 fDatabase_ = get<mongocxx::pool::entry> (fClientStorage_)->database (options.fDatabase.AsUTF8<
string> ());
615 ~ConnectionRep_ () =
default;
619 virtual shared_ptr<const EngineProperties> GetEngineProperties ()
const override
622 virtual String GetEngineName ()
const override
624 return "mongo-cxx-driver"sv;
627 return Memory::MakeSharedPtr<const MyEngineProperties_> ();
633 vector<string> n = fDatabase_.list_collection_names ();
636 catch (
const mongocxx::v_noabi::operation_exception& e) {
638 if (e.raw_server_error ()) {
639 DbgTrace (
"e.raw={}"_f, FromBSON_ (e.raw_server_error ()->view ()));
648 virtual void CreateCollection (
const String& name)
override
650#if USE_NOISY_TRACE_IN_THIS_MODULE_
655 fDatabase_.create_collection (name.
AsUTF8<
string> ());
661 virtual void DropCollection (
const String& name)
override
663#if USE_NOISY_TRACE_IN_THIS_MODULE_
668 fDatabase_.collection (name.
AsUTF8<
string> ()).drop ();
679 Memory::MakeSharedPtr<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
699 virtual mongocxx::client& GetClientRef ()
override
701 if (
auto oc = get_if<mongocxx::client> (&fClientStorage_)) {
704 else if (
auto oe = get_if<mongocxx::pool::entry> (&fClientStorage_)) {
705 return *oe->operator->();
709 static mongocxx::client x;
721unique_ptr<mongocxx::instance> Document::MongoDBClient::Activator::sMongoInstance_;
723Document::MongoDBClient::Activator::Activator ()
724 : fAllowReactivation_{false}
726 Require (Debug::AppearsDuringMainLifetime ());
727 if (sActivatorLiveCnt_.fetch_add (1) == 0) {
728 if (not sMongoInstance_) {
729 sMongoInstance_ = make_unique<mongocxx::instance> ();
734Document::MongoDBClient::Activator::Activator (AllowReactivateFlag)
737 fAllowReactivation_ =
true;
740Document::MongoDBClient::Activator::~Activator ()
742 Require (Debug::AppearsDuringMainLifetime ());
743 Require (sActivatorLiveCnt_ > 0);
744 if (sActivatorLiveCnt_.fetch_sub (1) == 0 and not fAllowReactivation_) {
745 sMongoInstance_.reset ();
754ConnectionPool::ConnectionPool (shared_ptr<mongocxx::pool>&& poolRep)
755 : fPool_{move (poolRep)}
758ConnectionPool::ConnectionPool (
const String& connectionString)
759 : ConnectionPool{Memory::
MakeSharedPtr<mongocxx::pool> (ConnectionString2MongoURI_ (connectionString))}
763mongocxx::pool& ConnectionPool::PeekPool ()
const
774Document::MongoDBClient::Connection::Ptr::Ptr (
const shared_ptr<IRep>& src)
784auto Document::MongoDBClient::AdminConnection::New (
const AdminConnection::Options& options) -> Ptr
786#if qStroika_Foundation_Debug_AssertionsChecked
787 Require (sActivatorLiveCnt_ > 0);
789 return Ptr{Memory::MakeSharedPtr<AdminRep_> (options)};
797auto Document::MongoDBClient::Connection::New (
const Connection::Options& options) -> Ptr
799#if qStroika_Foundation_Debug_AssertionsChecked
800 Require (sActivatorLiveCnt_ > 0);
802 return Ptr{Memory::MakeSharedPtr<ConnectionRep_> (options)};
810struct Transaction::MyRep_ :
public MongoDBClient::Transaction::IRep {
812 : fConnectionPtr_{db}
816 virtual void Commit ()
override
818 Require (not fCompleted_);
822 virtual void Rollback ()
override
824 Require (not fCompleted_);
828 virtual Disposition GetDisposition ()
const override
831 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
834 bool fCompleted_{
false};
837 : inherited{make_unique<MyRep_> (db)}
#define WeakAssertNotReached()
#define AssertNotImplemented()
#define AssertNotReached()
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
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)