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