Stroika Library 3.0d18
 
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__ < 20)
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__ < 20)
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::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", 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 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;
299 if (const Document::FilterElements::Value* rhsValue = get_if<Document::FilterElements::Value> (&eqOp->fRHS)) {
300 // move to server side
301 filterDoc.append (kvp (useFieldName.AsUTF8<string> (), VV2BSONV_ (*rhsValue)));
302 transfered = true;
303 anyTransfers = true;
304 }
305 }
306 if (not transfered) {
307 clientSideOps += op; // keep for client side
308 }
309 }
310 if (anyTransfers) {
311 // if we moved any to server side, then return the filterDoc and the client side ops
312 return make_tuple (filterDoc.extract (), clientSideOps.empty () ? optional<Filter>{} : make_optional (Filter{clientSideOps}));
313 }
314 // else no change
315 return make_tuple (nullopt, filter);
316 }
317 return make_tuple (nullopt, nullopt);
318 }
319}
320
321namespace {
322 /**
323 * Break the given Stroika filter into parts that can be remoted to MongoDB, and parts that must be handled locally
324 *
325 * @param filter
326 * @return tuple<optional<bsoncxx::document::value>, optional<Projection>>
327 *
328 * SEE https://stackoverflow.com/questions/62704615/mongodb-projection-on-c
329 */
330 tuple<optional<bsoncxx::document::value>, optional<Projection>> Partition_ (const optional<Projection>& p)
331 {
332 if (p) {
333 /*
334 * support mongoProjection - e.g. {{a: 1, b:0}} etc...
335 */
336 tuple<Document::Projection::Flag, Set<String>> fields = p->GetFields ();
337 Require (get<1> (fields).size () >= 1); // cannot (usefully) project to null-space
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_;
343 }
344 projectionDoc.append (kvp (mongoFieldName.AsUTF8<string> (), get<0> (fields) == Document::Projection::Flag::eInclude ? 1 : 0));
345 }
346 return make_tuple (projectionDoc.extract (), nullopt);
347 }
348 return make_tuple (nullopt, nullopt);
349 }
350}
351
352namespace {
353 struct AdminRep_ final : Stroika::Foundation::Database::Document::MongoDBClient::AdminConnection::IRep {
354 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex_;
355 variant<mongocxx::client, mongocxx::pool::entry> fClientStorage_;
356 mongocxx::client* fClientPtr_;
357
358 AdminRep_ (const AdminRep_&) = delete;
359 AdminRep_ (const AdminConnection::Options& options)
360 {
361 TraceContextBumper ctx{"MongoDBClient:::AdminConnection::Rep_::CTOR"};
362 if (auto os = get_if<String> (&options.fConnectionTarget)) {
363 fClientStorage_ = mongocxx::client{ConnectionString2MongoURI_ (*os)};
364 fClientPtr_ = get_if<mongocxx::client> (&fClientStorage_);
365 }
366 else if (auto op = get_if<ConnectionPool> (&options.fConnectionTarget)) {
367 fClientStorage_ = op->PeekPool ().acquire ();
368 fClientPtr_ = get<mongocxx::pool::entry> (fClientStorage_).operator->();
369 }
370 EnsureNotNull (fClientPtr_);
371 }
372 ~AdminRep_ () = default;
373
374 // MongoDBClient::Connection::IRep overrides
375 public:
376 virtual mongocxx::client& GetClientRef () override
377 {
378 return *fClientPtr_;
379 }
380 virtual Document::Document run_command (const Document::Document& v) override
381 {
382#if USE_NOISY_TRACE_IN_THIS_MODULE_
383 TraceContextBumper ctx{"MongoDBClient::AdminRep_::run_command"};
384#endif
385 try {
386 mongocxx::database adminDB_;
387 return FromBSON_ (fClientPtr_->database ("admin").run_command (ToBSON_ (v)));
388 }
389 catch (...) {
390 DoReThrow_ ();
391 }
392 }
393 virtual Set<String> GetDatabases () override
394 {
395 try {
396 vector<string> n = fClientPtr_->list_database_names ();
397 return Iterable<string>{n}.Map<Set<String>> ([] (string i) { return String::FromUTF8 (i); });
398 }
399 catch (...) {
400 DoReThrow_ ();
401 }
402 }
403 virtual void DropDatabase (const String& dbName) override
404 {
405 try {
406 mongocxx::database{fClientPtr_->database (dbName.AsUTF8<string> ())}.drop ();
407 }
408 catch (...) {
409 DoReThrow_ ();
410 }
411 }
412 virtual void CreateDatabase (const String& dbName) override
413 {
414 try {
415 // doesn't appear to be anything todo to create the database except maybe writing to it
416 mongocxx::database d{fClientPtr_->database (dbName.AsUTF8<string> ())};
417 d.create_collection ("_junk_");
418 d.collection ("_junk_").drop ();
419 }
420 catch (...) {
421 DoReThrow_ ();
422 }
423 }
424 };
425}
426
427namespace {
428 struct ConnectionRep_ final : Stroika::Foundation::Database::Document::MongoDBClient::Connection::IRep {
430 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex_; // since shares unsyncrhonized connection, share its context
431 shared_ptr<ConnectionRep_> fConnectionRep_; // save to bump reference count
432 mongocxx::collection fCollection_;
433
434 CollectionRep_ (const shared_ptr<ConnectionRep_>& connectionRep, const String& collectionName)
435 : fConnectionRep_{connectionRep}
436 , fCollection_{connectionRep->fDatabase_.collection (collectionName.AsUTF8<string> ())}
437 {
438#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
439 fAssertExternallySynchronizedMutex_.SetAssertExternallySynchronizedMutexContext (
440 connectionRep->fAssertExternallySynchronizedMutex_.GetSharedContext ());
441#endif
442 }
443 virtual IDType Add (const Document::Document& v) override
444 {
445#if USE_NOISY_TRACE_IN_THIS_MODULE_
446 TraceContextBumper ctx{"MongoDBClient::CollectionRep_::Add()"};
447#endif
448 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
449 try {
450 // auto insert_one_result = fCollection_.insert_one(make_document(kvp("i", 0)));
451 if (auto insert_one_result = fCollection_.insert_one (ToBSON_ (v))) {
452 return ID_2_string_ (insert_one_result->inserted_id ());
453 }
454 }
455 catch (...) {
456 DoReThrow_ ();
457 }
458 Throw (RuntimeErrorException{"failed to add doc"});
459 }
460 virtual optional<Document::Document> GetOne (const IDType& id, const optional<Projection>& projection) override
461 {
462#if USE_NOISY_TRACE_IN_THIS_MODULE_
463 TraceContextBumper ctx{"MongoDBClient::CollectionRep_::GetOne()"};
464#endif
465 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
466 try {
467 bsoncxx::builder::basic::document filterDoc;
468 filterDoc.append (kvp ("_id", bsoncxx::oid{id.AsUTF8<string> ()})); //kMongoID
469 auto [mongoProjection, myProjection] = Partition_ (projection);
470 mongocxx::options::find o;
471 if (mongoProjection) {
472 o.projection (mongoProjection->view ());
473 }
474 auto result = fCollection_.find_one (filterDoc.view (), o);
475 if (result) {
476 auto rr = FromBSON_ (bsoncxx::document::view_or_value{*result});
477 if (myProjection) {
478 rr = myProjection->Apply (rr);
479 }
480 return rr;
481 }
482 return nullopt;
483 }
484 catch (...) {
485 DoReThrow_ ();
486 }
487 }
488 virtual Sequence<Document::Document> GetAll (const optional<Filter>& filter, const optional<Projection>& projection) override
489 {
490#if USE_NOISY_TRACE_IN_THIS_MODULE_
491 TraceContextBumper ctx{"MongoDBClient::CollectionRep_::GetAll()"};
492#endif
493 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
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 ());
500 }
501 try {
502 auto cursor = fCollection_.find (mongoFilter ? mongoFilter->view () : bsoncxx::builder::basic::document{}.view (), o);
503 for (auto&& doc : cursor) {
504 auto rr = FromBSON_ (doc);
505 if (myProjection) {
506 rr = myProjection->Apply (rr);
507 }
508 if (not filter or filter->Matches (rr)) {
509 result += rr;
510 }
511 }
512 return result;
513 }
514 catch (...) {
515 DoReThrow_ ();
516 }
517 }
518 virtual void Update (const IDType& id, const Document::Document& newV, const optional<Set<String>>& onlyTheseFields) override
519 {
520#if USE_NOISY_TRACE_IN_THIS_MODULE_
521 TraceContextBumper ctx{"MongoDBClient::CollectionRep_::Update()"};
522#endif
523 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
524 // incomplete... - not sure how to handle partial update vs full update - must read mongo docs more carefully
525 Document::Document uploadDoc = newV;
526 if (onlyTheseFields) {
527 uploadDoc.RetainAll (*onlyTheseFields);
528 }
529 uploadDoc.RemoveIf ("id");
530 try {
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) {
536 Throw (RuntimeErrorException{"failed to update doc - not modified"});
537 }
538 }
539 else {
540 Throw (RuntimeErrorException{"failed to update doc"});
541 }
542 }
543 else {
544 if (auto o = fCollection_.replace_one (make_document (kvp ("_id", bsoncxx::oid{id.AsUTF8<string> ()})), bsonDoc.view ())) {
545 if (o->modified_count () == 0) {
546 Throw (RuntimeErrorException{"failed to replace doc - not modified"});
547 }
548 }
549 else {
550 Throw (RuntimeErrorException{"failed to replace doc"});
551 }
552 }
553 }
554 catch (...) {
555 DoReThrow_ ();
556 }
557 }
558 virtual void Remove (const IDType& id) override
559 {
560#if USE_NOISY_TRACE_IN_THIS_MODULE_
561 TraceContextBumper ctx{"MongoDBClient::CollectionRep_::Remove()"};
562#endif
563 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
564 try {
565 bsoncxx::builder::basic::document filterDoc;
566 filterDoc.append (kvp ("_id", bsoncxx::oid{id.AsUTF8<string> ()})); // kMongoID_
567 auto result = fCollection_.delete_one (filterDoc.view ());
568 if (result && result->deleted_count () == 0) {
569 Throw (RuntimeErrorException{"failed to delete doc"});
570 }
571 }
572 catch (...) {
573 DoReThrow_ ();
574 }
575 }
576 };
577
578 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex_;
579 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
580 mongocxx::database fDatabase_;
581
582 ConnectionRep_ (const Connection::Options& options)
583 {
584 TraceContextBumper ctx{"MongoDBClient::ConnectionRep_::CTOR"};
585 try {
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> ());
589 }
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> ());
593 }
594 Ensure (fDatabase_); // properly constructed
595 }
596 catch (...) {
597 DoReThrow_ ();
598 }
599 }
600 ~ConnectionRep_ () = default;
601
602 // Document::Connection::IRep overrides
603 public:
604 virtual shared_ptr<const EngineProperties> GetEngineProperties () const override
605 {
606 struct MyEngineProperties_ final : EngineProperties {
607 virtual String GetEngineName () const override
608 {
609 return "mongo-cxx-driver"sv; // must indirect to connection to get more info (from dns at least? not clear)
610 }
611 };
612 return make_shared<const MyEngineProperties_> (); // dynamic info based on connection/dsn
613 }
614 virtual Set<String> GetCollections () override
615 {
616 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
617 try {
618 vector<string> n = fDatabase_.list_collection_names ();
619 return Iterable<string>{n}.Map<Set<String>> ([] (string i) { return String{i}; });
620 }
621 catch (const mongocxx::v_noabi::operation_exception& e) {
622 DbgTrace ("e={}"_f, e);
623 if (e.raw_server_error ()) {
624 DbgTrace ("e.raw={}"_f, FromBSON_ (e.raw_server_error ()->view ()));
625 }
626 // a specific error here - check for that - and throw others... no such database gets mapped to empty collection list
627 return {};
628 }
629 catch (...) {
630 DoReThrow_ ();
631 }
632 }
633 virtual void CreateCollection (const String& name) override
634 {
635#if USE_NOISY_TRACE_IN_THIS_MODULE_
636 TraceContextBumper ctx{"mongocxx::CreateCollection()"};
637#endif
638 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
639 try {
640 fDatabase_.create_collection (name.AsUTF8<string> ());
641 }
642 catch (...) {
643 DoReThrow_ ();
644 }
645 }
646 virtual void DropCollection (const String& name) override
647 {
648#if USE_NOISY_TRACE_IN_THIS_MODULE_
649 TraceContextBumper ctx{"mongocxx::DropCollection()"};
650#endif
651 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
652 try {
653 fDatabase_.collection (name.AsUTF8<string> ()).drop ();
654 }
655 catch (...) {
656 DoReThrow_ ();
657 }
658 }
659 virtual Document::Collection::Ptr GetCollection (const String& name) override
660 {
661 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
662 try {
664 make_shared<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
665 }
666 catch (...) {
667 DoReThrow_ ();
668 }
669 }
670 virtual Document::Transaction mkTransaction () override
671 {
672 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
673 try {
674 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
675 return Transaction{conn};
676 }
677 catch (...) {
678 DoReThrow_ ();
679 }
680 }
681
682 // MongoDBClient::Connection::IRep overrides
683 public:
684 virtual mongocxx::client& GetClientRef () override
685 {
686 if (auto oc = get_if<mongocxx::client> (&fClientStorage_)) {
687 return *oc;
688 }
689 else if (auto oe = get_if<mongocxx::pool::entry> (&fClientStorage_)) {
690 return *oe->operator->();
691 }
692 else {
694 static mongocxx::client x;
695 return x;
696 }
697 }
698 };
699}
700
701/*
702 ********************************************************************************
703 ********************* Document::MongoDBClient::Activator ***********************
704 ********************************************************************************
705 */
706unique_ptr<mongocxx::instance> Document::MongoDBClient::Activator::sMongoInstance_;
707
708Document::MongoDBClient::Activator::Activator ()
709 : fAllowReactivation_{false}
710{
711 Require (Debug::AppearsDuringMainLifetime ());
712 if (sActivatorLiveCnt_.fetch_add (1) == 0) {
713 if (not sMongoInstance_) {
714 sMongoInstance_ = make_unique<mongocxx::instance> ();
715 }
716 }
717}
718Document::MongoDBClient::Activator::Activator (AllowReactivateFlag)
719 : Activator{}
720{
721 fAllowReactivation_ = true;
722}
723
724Document::MongoDBClient::Activator::~Activator ()
725{
726 Require (Debug::AppearsDuringMainLifetime ());
727 Require (sActivatorLiveCnt_ > 0);
728 if (sActivatorLiveCnt_.fetch_sub (1) == 0 and not fAllowReactivation_) {
729 sMongoInstance_.reset ();
730 }
731}
732
733/*
734 ********************************************************************************
735 ******************* Document::MongoDBClient::ConnectionPool ********************
736 ********************************************************************************
737 */
738ConnectionPool::ConnectionPool (shared_ptr<mongocxx::pool>&& poolRep)
739 : fPool_{move (poolRep)}
740{
741}
742ConnectionPool::ConnectionPool (const String& connectionString)
743 : ConnectionPool{make_shared<mongocxx::pool> (ConnectionString2MongoURI_ (connectionString))}
744{
745}
746
747mongocxx::pool& ConnectionPool::PeekPool () const
748{
749 AssertNotNull (fPool_);
750 return *fPool_;
751}
752
753/*
754 ********************************************************************************
755 ****************** Document::MongoDBClient::Connection::Ptr ********************
756 ********************************************************************************
757 */
758Document::MongoDBClient::Connection::Ptr::Ptr (const shared_ptr<IRep>& src)
759 : inherited{src}
760{
761}
762
763/*
764 ********************************************************************************
765 **************** Document::MongoDBClient::AdminConnection::New *****************
766 ********************************************************************************
767 */
768auto Document::MongoDBClient::AdminConnection::New (const AdminConnection::Options& options) -> Ptr
769{
770#if qStroika_Foundation_Debug_AssertionsChecked
771 Require (sActivatorLiveCnt_ > 0);
772#endif
773 return Ptr{make_shared<AdminRep_> (options)};
774}
775
776/*
777 ********************************************************************************
778 ******************* Document::MongoDBClient::Connection::New *******************
779 ********************************************************************************
780 */
781auto Document::MongoDBClient::Connection::New (const Connection::Options& options) -> Ptr
782{
783#if qStroika_Foundation_Debug_AssertionsChecked
784 Require (sActivatorLiveCnt_ > 0);
785#endif
786 return Ptr{make_shared<ConnectionRep_> (options)};
787}
788
789/*
790 ********************************************************************************
791 ******************************* SQLite::Transaction ****************************
792 ********************************************************************************
793 */
794struct Transaction::MyRep_ : public MongoDBClient::Transaction::IRep {
795 MyRep_ (const Connection::Ptr& db)
796 : fConnectionPtr_{db}
797 {
799 }
800 virtual void Commit () override
801 {
802 Require (not fCompleted_);
803 fCompleted_ = true;
804 // fConnectionPtr_->Exec ("COMMIT;"sv);
805 }
806 virtual void Rollback () override
807 {
808 Require (not fCompleted_);
809 fCompleted_ = true;
810 // fConnectionPtr_->Exec ("ROLLBACK;"sv);
811 }
812 virtual Disposition GetDisposition () const override
813 {
814 // @todo record more info so we can report finer grained status ; try/catch in rollback/commit and dbgtraces
815 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
816 }
817 Connection::Ptr fConnectionPtr_;
818 bool fCompleted_{false};
819};
820Transaction::Transaction (const Connection::Ptr& db)
821 : inherited{make_unique<MyRep_> (db)}
822{
823}
824#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