Stroika Library 3.0d20
 
Loading...
Searching...
No Matches
MongoDBClient.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#if qStroika_HasComponent_mongocxxdriver
7DISABLE_COMPILER_MSC_WARNING_START (4166) // avoid warning of buggy usage cdecl on CTOR/DTOR- only shows up on x86 MSVC compilers
8#if defined(__clang_major__) and (16 < __clang_major__ && __clang_major__ < 21)
9DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdeprecated-literal-operator\""); // ...bsoncxx/v_noabi/bsoncxx/json.hpp:85:54: warning: identifier '_bson' preceded by whitespace in a literal operator declaration is deprecated [-Wdeprecated-literal-operator]
10#endif
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\"");
28#endif
29DISABLE_COMPILER_MSC_WARNING_END (4166)
30#endif
31
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"
41
42#include "MongoDBClient.h"
43
44using namespace Stroika::Foundation;
47using namespace Stroika::Foundation::Database;
48using namespace Stroika::Foundation::Database::Document::MongoDBClient;
50using namespace Stroika::Foundation::Debug;
51using namespace Stroika::Foundation::Execution;
52using namespace Stroika::Foundation::Memory;
53
56
61
62// Comment this in to turn on aggressive noisy DbgTrace in this module
63// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
64
65/**
66 * Character set Docx
67 * Haven't found clear docs about characterset (apis all take 'string') - but it appears (search through docs for
68 * characterset comes up with nothing but UTF-8) that they use UTF-8.
69 */
70
71#if qStroika_HasComponent_mongocxxdriver
72
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;
78
79namespace {
80 const String kMongoID_ = "_id"sv;
81}
82
83namespace {
84 atomic<unsigned int> sActivatorLiveCnt_{0};
85}
86
87namespace {
88 auto ConnectionString2MongoURI_ (const String& connectionString)
89 {
90 return mongocxx::uri{connectionString.AsUTF8<string> ()};
91 }
92}
93
94namespace {
95 [[noreturn]] void DoReThrow_ ()
96 {
97 // @todo ALSO - should check if current_exception() already a Stroika exception - and only bother wrapping native mongo ones, but this caputres
98 // most such cases...
99 if (AnyCurrentActivities ()) {
100 // capture current activities in message
101 Throw (NestedException{current_exception ()});
102 }
103 else {
104 ReThrow ();
105 }
106 }
107 String ID_2_string_ (const bsoncxx::types::bson_value::view& value)
108 {
109 switch (value.type ()) {
110 case bsoncxx::type::k_oid:
111 return String{value.get_oid ().value.to_string ()};
112 default:
114 return String{};
115 }
116 }
117 template <Common::IAnyOf<bsoncxx::types::bson_value::view, bsoncxx::document::element, bsoncxx::document::value, bsoncxx::array::element> T>
118 VariantValue BSON2VV_ (const T& value)
119 {
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: {
126 Mapping_HashTable<String, VariantValue>::DEFAULT_HASHTABLE<> vvResult; // performance tweak, add in STL, avoiding virtual calls for each add, and then move to Stroika mapping
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));
130 }
132 }
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));
139 }
140 return VariantValue{Sequence_stdvector<VariantValue>{move (vvResult)}};
141 }
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:
145 return VariantValue{nullptr}; // Stroika VariantValue doesn't distinguish between null and 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:
151 // Note - STROIKA doesn't usually (ever) generate these, because of problems documented in VV2BSONV_
152 return Time::DateTime{chrono::time_point<chrono::system_clock>{value.get_date ().value}}; // UTC datetime.
153 case bsoncxx::type::k_null:
154 return VariantValue{nullptr};
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:
158 WeakAssertNotReached (); ///< DBPointer. @deprecated
159 return VariantValue{};
160 case bsoncxx::type::k_code:
162 return VariantValue{};
163 case bsoncxx::type::k_symbol:
164 WeakAssertNotReached (); ///< Symbol. @deprecated
165 return VariantValue{};
166 case bsoncxx::type::k_codewscope:
168 return VariantValue{};
169 case bsoncxx::type::k_int32:
170 return value.get_int32 ().value;
171 case bsoncxx::type::k_timestamp:
172 WeakAssertNotReached (); // not sure how to translate/interpret
173 return VariantValue{};
174 case bsoncxx::type::k_int64:
175 return value.get_int64 ().value;
176 case bsoncxx::type::k_decimal128:
177 WeakAssertNotReached (); // ///< 128-bit decimal floating point. == not sure what todo
178 return VariantValue{};
179 case bsoncxx::type::k_maxkey:
180 case bsoncxx::type::k_minkey:
181 WeakAssertNotReached (); // not sure what todo
182 return VariantValue{};
183 default:
184 AssertNotReached (); // all cases covered
185 return VariantValue{};
186 }
187 }
188 bsoncxx::types::bson_value::value VV2BSONV_ (const VariantValue& vv)
189 {
190 using namespace std::chrono;
191 // @todo adequate first draft, but not 100% right conversions --LGP 2025-03-24
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> ())}; // @todo tweak - not quite right
203 case VariantValue::Type::eFloat:
204 return bsoncxx::types::bson_value::value{vv.As<double> ()};
205 case VariantValue::Type::eDate:
206 // MongoDB doesn't support dates before 1970, so store as string
207 // Also, because mongocxx api uses system_clock, which is neither a steady_clock nor monotonic_clock, its really not suitable for roundtripping.
208 // So just store dates as strings in the database (and our read code will convert them back to DateTime as appropriate - really VariantValue)
209 // if (Date dt = vv.As<Date> (); dt > Date{1970y / January / 1d}) {
210 // return bsoncxx::types::bson_value::value{bsoncxx::types::b_date{dt.As<std::chrono::system_clock::time_point> ()}};
211 // }
212 return vv.As<Date> ().Format (Date::kISO8601Format).AsUTF8<string> ();
213 case VariantValue::Type::eDateTime:
214 // MongoDB doesn't support dates before 1970, so store as string
215 // only makes sense to store UTC dates in database (so convert to UTC before ISO8601)
216 // Also, because mongocxx api uses system_clock, which is neither a steady_clock nor monotonic_clock, its really not suitable for roundtripping.
217 // So just store dates as strings in the database (and our read code will convert them back to DateTime as appropriate - really VariantValue)
218 // if (DateTime dt = vv.As<DateTime> ().AsUTC (); dt.GetDate () > Date{1970y / January / 1d}) {
219 // return bsoncxx::types::bson_value::value{bsoncxx::types::b_date{dt.As<std::chrono::system_clock::time_point> ()}};
220 // }
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;
226 for (const auto& ai : vv.As<Sequence<VariantValue>> ()) {
227 bsonArr.append (VV2BSONV_ (ai));
228 }
229 return bsoncxx::types::bson_value::value{bsonArr};
230 }
231 case VariantValue::Type::eMap: {
232 bsoncxx::builder::basic::document bsonDoc;
233 for (const KeyValuePair<String, VariantValue>& ai : vv.As<Mapping<String, VariantValue>> ()) {
234 bsonDoc.append (kvp (ai.fKey.AsUTF8<string> (), VV2BSONV_ (ai.fValue)));
235 }
236 return bsoncxx::types::bson_value::value{bsonDoc};
237 }
238 default:
239 AssertNotReached (); // all cases covered
240 return bsoncxx::types::bson_value::value{nullptr};
241 }
242 }
243 Document::Document FromBSON_ (const bsoncxx::document::view_or_value& b)
244 {
246 for (const bsoncxx::document::element& di : b.view ()) {
247 result.Add (String::FromUTF8 (span{di.key ()}), BSON2VV_ (di));
248 }
249 if (result.ContainsKey (kMongoID_)) {
250 // patch '_id':oid => 'id':string
251 VariantValue idValue = result[kMongoID_]; // {id: {$oid -> 67da17b30c4265ac0302f483}}
252 result.Remove (kMongoID_);
253 result.Add (Database::Document::kID, idValue);
254 }
255 return result;
256 }
257 bsoncxx::document::value ToBSON_ (const Document::Document& vv)
258 {
259 // more complex, but more performant version of
260 // bsoncxx::from_json (Variant::JSON::Writer{}.WriteAsString (VariantValue{vvv}).AsUTF8<string> ());
261 Document::Document newDoc = vv;
262 if (vv.ContainsKey (Database::Document::kID)) {
263 // patch 'id':string => '_id':oid
264 auto idValue = vv[Database::Document::kID];
265 newDoc.Remove (Database::Document::kID);
266 newDoc.Add (kMongoID_, VariantValue{Mapping<String, VariantValue>{{"$oid"sv, idValue}}});
267 }
268 bsoncxx::builder::basic::document bsonDoc;
269 for (const KeyValuePair<String, VariantValue>& ai : newDoc) {
270 bsonDoc.append (kvp (ai.fKey.AsUTF8<string> (), VV2BSONV_ (ai.fValue)));
271 }
272 return bsonDoc.extract ();
273 }
274}
275
276namespace {
277 /**
278 * Break the given Stroika filter into parts that can be remoted to MongoDB, and parts that must be handled locally
279 *
280 * @param filter
281 * @return tuple<bsoncxx::document,Filter>
282 */
283 tuple<optional<bsoncxx::document::value>, optional<Filter>> Partition_ (const optional<Filter>& filter)
284 {
285 if (filter) {
286 /*
287 * For now just look for FIELD EQUALS VALUE expressions in the top level conjunction. These can be done
288 * server or client side transparently, and moving them server side is more efficient.
289 *
290 * Much more could be done, but this is a good cost/benefit start.
291 */
293 bsoncxx::builder::basic::document filterDoc;
294 bool anyTransfers = false;
295 for (Document::FilterElements::Operation op : filter->GetConjunctionOperations ()) {
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;
299 if (const Document::FilterElements::Value* rhsValue = get_if<Document::FilterElements::Value> (&eqOp->fRHS)) {
300 // move to server side
301 if (useFieldName == kMongoID_) {
302 // @todo find cleaner way todo this cuz other thinks could be of this type?
303 filterDoc.append (kvp ("_id", bsoncxx::oid{rhsValue->As<String> ().AsUTF8<string> ()})); //kMongoID
304 }
305 else {
306 filterDoc.append (kvp (useFieldName.AsUTF8<string> (), VV2BSONV_ (*rhsValue)));
307 }
308 transferred = true;
309 anyTransfers = true;
310 }
311 }
312 if (not transferred) {
313 clientSideOps += op; // keep for client side
314 }
315 }
316 if (anyTransfers) {
317 // if we moved any to server side, then return the filterDoc and the client side ops
318 return make_tuple (filterDoc.extract (), clientSideOps.empty () ? optional<Filter>{} : make_optional (Filter{clientSideOps}));
319 }
320 // else no change
321 return make_tuple (nullopt, filter);
322 }
323 return make_tuple (nullopt, nullopt);
324 }
325}
326
327namespace {
328 /**
329 * Break the given Stroika filter into parts that can be remoted to MongoDB, and parts that must be handled locally
330 *
331 * @param filter
332 * @return tuple<optional<bsoncxx::document::value>, optional<Projection>>
333 *
334 * SEE https://stackoverflow.com/questions/62704615/mongodb-projection-on-c
335 */
336 tuple<optional<bsoncxx::document::value>, optional<Projection>> Partition_ (const optional<Projection>& p)
337 {
338 if (p) {
339 /*
340 * support mongoProjection - e.g. {{a: 1, b:0}} etc...
341 */
342 tuple<Document::Projection::Flag, Set<String>> fields = p->GetFields ();
343 Require (get<1> (fields).size () >= 1); // cannot (usefully) project to null-space
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_;
349 }
350 projectionDoc.append (kvp (mongoFieldName.AsUTF8<string> (), get<0> (fields) == Document::Projection::Flag::eInclude ? 1 : 0));
351 }
352 return make_tuple (projectionDoc.extract (), nullopt);
353 }
354 return make_tuple (nullopt, nullopt);
355 }
356}
357
358namespace {
359 struct AdminRep_ final : Stroika::Foundation::Database::Document::MongoDBClient::AdminConnection::IRep {
360 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex_;
361 variant<mongocxx::client, mongocxx::pool::entry> fClientStorage_;
362 mongocxx::client* fClientPtr_;
363
364 AdminRep_ (const AdminRep_&) = delete;
365 AdminRep_ (const AdminConnection::Options& options)
366 {
367 TraceContextBumper ctx{"MongoDBClient:::AdminConnection::Rep_::CTOR"};
368 if (auto os = get_if<String> (&options.fConnectionTarget)) {
369 fClientStorage_ = mongocxx::client{ConnectionString2MongoURI_ (*os)};
370 fClientPtr_ = get_if<mongocxx::client> (&fClientStorage_);
371 }
372 else if (auto op = get_if<ConnectionPool> (&options.fConnectionTarget)) {
373 fClientStorage_ = op->PeekPool ().acquire ();
374 fClientPtr_ = get<mongocxx::pool::entry> (fClientStorage_).operator->();
375 }
376 EnsureNotNull (fClientPtr_);
377 }
378 ~AdminRep_ () = default;
379
380 // MongoDBClient::Connection::IRep overrides
381 public:
382 virtual mongocxx::client& GetClientRef () override
383 {
384 return *fClientPtr_;
385 }
386 virtual Document::Document run_command (const Document::Document& v) override
387 {
388#if USE_NOISY_TRACE_IN_THIS_MODULE_
389 TraceContextBumper ctx{"MongoDBClient::AdminRep_::run_command"};
390#endif
391 try {
392 mongocxx::database adminDB_;
393 return FromBSON_ (fClientPtr_->database ("admin").run_command (ToBSON_ (v)));
394 }
395 catch (...) {
396 DoReThrow_ ();
397 }
398 }
399 virtual Set<String> GetDatabases () override
400 {
401 try {
402 vector<string> n = fClientPtr_->list_database_names ();
403 return Iterable<string>{n}.Map<Set<String>> ([] (string i) { return String::FromUTF8 (i); });
404 }
405 catch (...) {
406 DoReThrow_ ();
407 }
408 }
409 virtual void DropDatabase (const String& dbName) override
410 {
411 try {
412 mongocxx::database{fClientPtr_->database (dbName.AsUTF8<string> ())}.drop ();
413 }
414 catch (...) {
415 DoReThrow_ ();
416 }
417 }
418 virtual void CreateDatabase (const String& dbName) override
419 {
420 try {
421 // doesn't appear to be anything todo to create the database except maybe writing to it
422 mongocxx::database d{fClientPtr_->database (dbName.AsUTF8<string> ())};
423 d.create_collection ("_junk_");
424 d.collection ("_junk_").drop ();
425 }
426 catch (...) {
427 DoReThrow_ ();
428 }
429 }
430 };
431}
432
433namespace {
434 struct ConnectionRep_ final : Stroika::Foundation::Database::Document::MongoDBClient::Connection::IRep {
436 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex_; // since shares unsyncrhonized connection, share its context
437 shared_ptr<ConnectionRep_> fConnectionRep_; // save to bump reference count
438 mongocxx::collection fCollection_;
439
440 CollectionRep_ (const shared_ptr<ConnectionRep_>& connectionRep, const String& collectionName)
441 : fConnectionRep_{connectionRep}
442 , fCollection_{connectionRep->fDatabase_.collection (collectionName.AsUTF8<string> ())}
443 {
444#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
445 fAssertExternallySynchronizedMutex_.SetAssertExternallySynchronizedMutexContext (
446 connectionRep->fAssertExternallySynchronizedMutex_.GetSharedContext ());
447#endif
448 }
449 virtual IDType Add (const Document::Document& v) override
450 {
451#if USE_NOISY_TRACE_IN_THIS_MODULE_
452 TraceContextBumper ctx{"MongoDBClient::CollectionRep_::Add()"};
453#endif
454 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
455 try {
456 // auto insert_one_result = fCollection_.insert_one(make_document(kvp("i", 0)));
457 if (auto insert_one_result = fCollection_.insert_one (ToBSON_ (v))) {
458 return ID_2_string_ (insert_one_result->inserted_id ());
459 }
460 }
461 catch (...) {
462 DoReThrow_ ();
463 }
464 Throw (RuntimeErrorException{"failed to add doc"});
465 }
466 virtual optional<Document::Document> GetOne (const IDType& id, const optional<Projection>& projection) override
467 {
468#if USE_NOISY_TRACE_IN_THIS_MODULE_
469 TraceContextBumper ctx{"MongoDBClient::CollectionRep_::GetOne()"};
470#endif
471 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
472 try {
473 bsoncxx::builder::basic::document filterDoc;
474 filterDoc.append (kvp ("_id", bsoncxx::oid{id.AsUTF8<string> ()})); //kMongoID
475 auto [mongoProjection, myProjection] = Partition_ (projection);
476 mongocxx::options::find o;
477 if (mongoProjection) {
478 o.projection (mongoProjection->view ());
479 }
480 auto result = fCollection_.find_one (filterDoc.view (), o);
481 if (result) {
482 auto rr = FromBSON_ (bsoncxx::document::view_or_value{*result});
483 if (myProjection) {
484 rr = myProjection->Apply (rr);
485 }
486 return rr;
487 }
488 return nullopt;
489 }
490 catch (...) {
491 DoReThrow_ ();
492 }
493 }
494 virtual Sequence<Document::Document> GetAll (const optional<Filter>& filter, const optional<Projection>& projection) override
495 {
496#if USE_NOISY_TRACE_IN_THIS_MODULE_
497 TraceContextBumper ctx{"MongoDBClient::CollectionRep_::GetAll()", "filter={}, projection={}"_f, filter, projection};
498#endif
499 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
500 auto [mongoFilter, myFilter] = Partition_ (filter);
501 // must be careful if myFilter != nullptr, may not be able to do projection server-side!
502 // cuz data needed client side to do the filtering...
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);
506 if (mongoFilter) {
507 // DbgTrace ("mongoFilter={}"_f, BSON2VV_ (*mongoFilter));
508 }
509#endif
511 mongocxx::options::find o;
512 if (mongoProjection) {
513 o.projection (mongoProjection->view ());
514 }
515 try {
516 auto cursor = fCollection_.find (mongoFilter ? mongoFilter->view () : bsoncxx::builder::basic::document{}.view (), o);
517 for (auto&& doc : cursor) {
518 auto rr = FromBSON_ (doc);
519 if (myProjection) {
520 rr = myProjection->Apply (rr);
521 }
522 if (not myFilter or myFilter->Matches (rr)) {
523 result += rr;
524 }
525 }
526 return result;
527 }
528 catch (...) {
529 DoReThrow_ ();
530 }
531 }
532 virtual void Update (const IDType& id, const Document::Document& newV, const optional<Set<String>>& onlyTheseFields) override
533 {
534#if USE_NOISY_TRACE_IN_THIS_MODULE_
535 TraceContextBumper ctx{"MongoDBClient::CollectionRep_::Update()"};
536#endif
537 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
538 // incomplete... - not sure how to handle partial update vs full update - must read mongo docs more carefully
539 Document::Document uploadDoc = newV;
540 if (onlyTheseFields) {
541 uploadDoc.RetainAll (*onlyTheseFields);
542 }
543 uploadDoc.RemoveIf ("id");
544 try {
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) {
550 Throw (RuntimeErrorException{"failed to update doc - not modified"});
551 }
552 }
553 else {
554 Throw (RuntimeErrorException{"failed to update doc"});
555 }
556 }
557 else {
558 if (auto o = fCollection_.replace_one (make_document (kvp ("_id", bsoncxx::oid{id.AsUTF8<string> ()})), bsonDoc.view ())) {
559 if (o->modified_count () == 0) {
560 Throw (RuntimeErrorException{"failed to replace doc - not modified"sv});
561 }
562 }
563 else {
564 Throw (RuntimeErrorException{"failed to replace doc"sv});
565 }
566 }
567 }
568 catch (...) {
569 DoReThrow_ ();
570 }
571 }
572 virtual void Remove (const IDType& id) override
573 {
574#if USE_NOISY_TRACE_IN_THIS_MODULE_
575 TraceContextBumper ctx{"MongoDBClient::CollectionRep_::Remove()"};
576#endif
577 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
578 try {
579 bsoncxx::builder::basic::document filterDoc;
580 filterDoc.append (kvp ("_id", bsoncxx::oid{id.AsUTF8<string> ()})); // kMongoID_
581 auto result = fCollection_.delete_one (filterDoc.view ());
582 if (result && result->deleted_count () == 0) {
583 Throw (RuntimeErrorException{"failed to delete doc"sv});
584 }
585 }
586 catch (...) {
587 DoReThrow_ ();
588 }
589 }
590 };
591
592 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex_;
593 variant<mongocxx::client, mongocxx::pool::entry> fClientStorage_; // not directly used, but needed to free the resource when this connection obj goes away - and implicitly stored in database
594 mongocxx::database fDatabase_;
595
596 ConnectionRep_ (const Connection::Options& options)
597 {
598 TraceContextBumper ctx{"MongoDBClient::ConnectionRep_::CTOR"};
599 try {
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> ());
603 }
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> ());
607 }
608 Ensure (fDatabase_); // properly constructed
609 }
610 catch (...) {
611 DoReThrow_ ();
612 }
613 }
614 ~ConnectionRep_ () = default;
615
616 // Document::Connection::IRep overrides
617 public:
618 virtual shared_ptr<const EngineProperties> GetEngineProperties () const override
619 {
620 struct MyEngineProperties_ final : EngineProperties {
621 virtual String GetEngineName () const override
622 {
623 return "mongo-cxx-driver"sv; // must indirect to connection to get more info (from dns at least? not clear)
624 }
625 };
626 return make_shared<const MyEngineProperties_> (); // dynamic info based on connection/dsn
627 }
628 virtual Set<String> GetCollections () override
629 {
630 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
631 try {
632 vector<string> n = fDatabase_.list_collection_names ();
633 return Iterable<string>{n}.Map<Set<String>> ([] (string i) { return String{i}; });
634 }
635 catch (const mongocxx::v_noabi::operation_exception& e) {
636 DbgTrace ("e={}"_f, e);
637 if (e.raw_server_error ()) {
638 DbgTrace ("e.raw={}"_f, FromBSON_ (e.raw_server_error ()->view ()));
639 }
640 // a specific error here - check for that - and throw others... no such database gets mapped to empty collection list
641 return {};
642 }
643 catch (...) {
644 DoReThrow_ ();
645 }
646 }
647 virtual void CreateCollection (const String& name) override
648 {
649#if USE_NOISY_TRACE_IN_THIS_MODULE_
650 TraceContextBumper ctx{"mongocxx::CreateCollection()"};
651#endif
652 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
653 try {
654 fDatabase_.create_collection (name.AsUTF8<string> ());
655 }
656 catch (...) {
657 DoReThrow_ ();
658 }
659 }
660 virtual void DropCollection (const String& name) override
661 {
662#if USE_NOISY_TRACE_IN_THIS_MODULE_
663 TraceContextBumper ctx{"mongocxx::DropCollection()"};
664#endif
665 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
666 try {
667 fDatabase_.collection (name.AsUTF8<string> ()).drop ();
668 }
669 catch (...) {
670 DoReThrow_ ();
671 }
672 }
673 virtual Document::Collection::Ptr GetCollection (const String& name) override
674 {
675 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
676 try {
678 make_shared<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
679 }
680 catch (...) {
681 DoReThrow_ ();
682 }
683 }
684 virtual Document::Transaction mkTransaction () override
685 {
686 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
687 try {
688 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
689 return Transaction{conn};
690 }
691 catch (...) {
692 DoReThrow_ ();
693 }
694 }
695
696 // MongoDBClient::Connection::IRep overrides
697 public:
698 virtual mongocxx::client& GetClientRef () override
699 {
700 if (auto oc = get_if<mongocxx::client> (&fClientStorage_)) {
701 return *oc;
702 }
703 else if (auto oe = get_if<mongocxx::pool::entry> (&fClientStorage_)) {
704 return *oe->operator->();
705 }
706 else {
708 static mongocxx::client x;
709 return x;
710 }
711 }
712 };
713}
714
715/*
716 ********************************************************************************
717 ********************* Document::MongoDBClient::Activator ***********************
718 ********************************************************************************
719 */
720unique_ptr<mongocxx::instance> Document::MongoDBClient::Activator::sMongoInstance_;
721
722Document::MongoDBClient::Activator::Activator ()
723 : fAllowReactivation_{false}
724{
725 Require (Debug::AppearsDuringMainLifetime ());
726 if (sActivatorLiveCnt_.fetch_add (1) == 0) {
727 if (not sMongoInstance_) {
728 sMongoInstance_ = make_unique<mongocxx::instance> ();
729 }
730 }
731}
732
733Document::MongoDBClient::Activator::Activator (AllowReactivateFlag)
734 : Activator{}
735{
736 fAllowReactivation_ = true;
737}
738
739Document::MongoDBClient::Activator::~Activator ()
740{
741 Require (Debug::AppearsDuringMainLifetime ());
742 Require (sActivatorLiveCnt_ > 0);
743 if (sActivatorLiveCnt_.fetch_sub (1) == 0 and not fAllowReactivation_) {
744 sMongoInstance_.reset ();
745 }
746}
747
748/*
749 ********************************************************************************
750 ******************* Document::MongoDBClient::ConnectionPool ********************
751 ********************************************************************************
752 */
753ConnectionPool::ConnectionPool (shared_ptr<mongocxx::pool>&& poolRep)
754 : fPool_{move (poolRep)}
755{
756}
757ConnectionPool::ConnectionPool (const String& connectionString)
758 : ConnectionPool{make_shared<mongocxx::pool> (ConnectionString2MongoURI_ (connectionString))}
759{
760}
761
762mongocxx::pool& ConnectionPool::PeekPool () const
763{
764 AssertNotNull (fPool_);
765 return *fPool_;
766}
767
768/*
769 ********************************************************************************
770 ****************** Document::MongoDBClient::Connection::Ptr ********************
771 ********************************************************************************
772 */
773Document::MongoDBClient::Connection::Ptr::Ptr (const shared_ptr<IRep>& src)
774 : inherited{src}
775{
776}
777
778/*
779 ********************************************************************************
780 **************** Document::MongoDBClient::AdminConnection::New *****************
781 ********************************************************************************
782 */
783auto Document::MongoDBClient::AdminConnection::New (const AdminConnection::Options& options) -> Ptr
784{
785#if qStroika_Foundation_Debug_AssertionsChecked
786 Require (sActivatorLiveCnt_ > 0);
787#endif
788 return Ptr{make_shared<AdminRep_> (options)};
789}
790
791/*
792 ********************************************************************************
793 ******************* Document::MongoDBClient::Connection::New *******************
794 ********************************************************************************
795 */
796auto Document::MongoDBClient::Connection::New (const Connection::Options& options) -> Ptr
797{
798#if qStroika_Foundation_Debug_AssertionsChecked
799 Require (sActivatorLiveCnt_ > 0);
800#endif
801 return Ptr{make_shared<ConnectionRep_> (options)};
802}
803
804/*
805 ********************************************************************************
806 ******************************* SQLite::Transaction ****************************
807 ********************************************************************************
808 */
809struct Transaction::MyRep_ : public MongoDBClient::Transaction::IRep {
810 MyRep_ (const Connection::Ptr& db)
811 : fConnectionPtr_{db}
812 {
814 }
815 virtual void Commit () override
816 {
817 Require (not fCompleted_);
818 fCompleted_ = true;
819 // fConnectionPtr_->Exec ("COMMIT;"sv);
820 }
821 virtual void Rollback () override
822 {
823 Require (not fCompleted_);
824 fCompleted_ = true;
825 // fConnectionPtr_->Exec ("ROLLBACK;"sv);
826 }
827 virtual Disposition GetDisposition () const override
828 {
829 // @todo record more info so we can report finer grained status ; try/catch in rollback/commit and dbgtraces
830 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
831 }
832 Connection::Ptr fConnectionPtr_;
833 bool fCompleted_{false};
834};
835Transaction::Transaction (const Connection::Ptr& db)
836 : inherited{make_unique<MyRep_> (db)}
837{
838}
839#endif
#define AssertNotNull(p)
Definition Assertions.h:333
#define EnsureNotNull(p)
Definition Assertions.h:340
#define WeakAssertNotReached()
Definition Assertions.h:467
#define AssertNotImplemented()
Definition Assertions.h:401
#define AssertNotReached()
Definition Assertions.h:355
#define DbgTrace
Definition Trace.h:309
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
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)
Definition Mapping.inl:188
nonvirtual bool ContainsKey(ArgByValueType< key_type > key) const
Definition Mapping.inl:177
nonvirtual bool RemoveIf(ArgByValueType< key_type > key)
Remove the given item, if it exists. Return true if found and removed.
Definition Mapping.inl:235
nonvirtual void Remove(ArgByValueType< key_type > key)
Remove the given item (which must exist).
Definition Mapping.inl:223
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 ...
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.
Definition Projection.h:32
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...
Definition Exceptions.h:212
static constexpr string_view kISO8601Format
Y-M-D format - locale independent, and ISO-8601 date format standard.
Definition Date.h:467
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
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.
Definition Iterable.inl:308
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
bool AnyCurrentActivities()
Checks if CaptureCurrentActivities() would produce a non-empty stack (but faster)
Definition Activity.cpp:39