Data models that represents the payload to be submitted to the service for creating/deleting documents.
//
// Created by Rakesh on 22/12/2024.
//
#pragma once
#include "create.hpp"
#include "update.hpp"
#include "delete.hpp"
namespace spt::mongoservice::api::model::request
{
struct TransactionBuilder
{
TransactionBuilder( std::string_view database, std::string_view collection ) : database{ database }, collection{ collection } {}
~TransactionBuilder() = default;
TransactionBuilder( const TransactionBuilder& ) = delete;
TransactionBuilder& operator=( const TransactionBuilder& ) = delete;
TransactionBuilder( TransactionBuilder&& ) = delete;
TransactionBuilder& operator=( TransactionBuilder&& ) = delete;
template <util::Visitable Document, util::Visitable Metadata>
requires std::is_same_v<decltype(Document::id), bsoncxx::oid>
void addCreate( const Create<Document, Metadata>& request )
{
items << util::marshall( request );
}
template <util::Visitable Document, util::Visitable Metadata>
requires std::is_same_v<decltype(Document::id), bsoncxx::oid>
void addUpdate( const MergeForId<Document, Metadata>& request )
{
items << util::marshall( request );
}
template <util::Visitable Document, util::Visitable Metadata, util::Visitable Filter, template<typename, typename, typename> typename Model>
requires std::is_same_v<Model<Document, Metadata, Filter>, Replace<Document, Metadata, Filter>> ||
std::is_same_v<Model<Document, Metadata, Filter>, Update<Document, Metadata, Filter>>
void addUpdate( const Model<Document, Metadata, Filter>& request )
{
items << util::marshall( request );
}
template <util::Visitable Document, util::Visitable Metadata>
requires std::constructible_from<Metadata, bsoncxx::document::view>
void addRemove( const Delete<Document, Metadata>& request )
{
items << util::marshall( request );
}
[[nodiscard]] bsoncxx::document::value build();
private:
std::string database;
std::string collection;
bsoncxx::builder::stream::array items;
};
}
Data models that represents the payloads the service responds with when creating/deleting document(s).
//
// Created by Rakesh on 19/12/2024.
//
#pragma once
#if defined __has_include
#if __has_include("../../../common/visit_struct/visit_struct_intrusive.hpp")
#include "../../../common/visit_struct/visit_struct_intrusive.hpp"
#include "../../../common/util/serialise.hpp"
#else
#include <mongo-service/common/visit_struct/visit_struct_intrusive.hpp>
#include <mongo-service/common/util/serialise.hpp>
#endif
#endif
#include <cstdint>
#include <string>
#include <vector>
#include <bsoncxx/oid.hpp>
namespace spt::mongoservice::api::model::response
{
struct Transaction
{
struct History
{
History() = default;
~History() = default;
History(History&&) = default;
History& operator=(History&&) = default;
bool operator==(const History& rhs) const = default;
History(const History&) = delete;
History& operator=(const History&) = delete;
BEGIN_VISITABLES(History);
VISITABLE(std::vector<bsoncxx::oid>, created);
VISITABLE(std::vector<bsoncxx::oid>, updated);
VISITABLE(std::vector<bsoncxx::oid>, deleted);
VISITABLE(std::string, database);
VISITABLE(std::string, collection);
END_VISITABLES;
};
explicit Transaction( bsoncxx::document::view document ) { util::unmarshall( *this, document ); }
Transaction() = default;
~Transaction() = default;
Transaction(Transaction&&) = default;
Transaction& operator=(Transaction&&) = default;
Transaction(const Transaction&) = delete;
Transaction& operator=(const Transaction&) = delete;
BEGIN_VISITABLES(Transaction);
VISITABLE(History, history);
VISITABLE(int32_t, created);
VISITABLE(int32_t, updated);
VISITABLE(int32_t, deleted);
END_VISITABLES;
};
}
Sample code illustrating the transaction action.
#include <mongo-service/api/repository/repository.hpp>
namespace example
{
struct Document
{
explicit Document( bsoncxx::document::view bson ) { spt::util::unmarshall( *this, bson ); }
Document() = default;
~Document() = default;
Document(Document&&) = default;
Document& operator=(Document&&) = default;
bool operator==(const Document&) const = default;
Document(const Document&) = delete;
Document& operator=(const Document&) = delete;
BEGIN_VISITABLES(Document);
VISITABLE(bsoncxx::oid, id);
VISITABLE(std::string, str);
VISITABLE_DIRECT_INIT(spt::util::DateTimeMs, created, {std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() )});
VISITABLE_DIRECT_INIT(int64_t, integer, {5});
VISITABLE_DIRECT_INIT(double, floating, {10.345});
VISITABLE_DIRECT_INIT(bool, boolean, {true});
END_VISITABLES;
};
struct Metadata
{
explicit Metadata( bsoncxx::document::view bson ) { spt::util::unmarshall( *this, bson ); }
Metadata() = default;
~Metadata() = default;
Metadata(Metadata&&) = default;
Metadata& operator=(Metadata&&) = default;
bool operator==(const Metadata&) const = default;
Metadata(const Metadata&) = delete;
Metadata& operator=(const Metadata&) = delete;
BEGIN_VISITABLES(Metadata);
VISITABLE(std::string, project);
VISITABLE(std::string, product);
END_VISITABLES;
};
}
int main()
{
using namespace spt::mongoservice::api;
using bsoncxx::builder::stream::open_array;
using bsoncxx::builder::stream::close_array;
using bsoncxx::builder::stream::open_document;
using bsoncxx::builder::stream::close_document;
auto builder = model::request::TransactionBuilder{ "unit", "test" };
auto insert = model::request::Create<pmodel::Document, pmodel::Metadata>{};
insert.database = "test";
insert.collection = "test";
insert.application = "unitTest";
insert.document.str = "value";
insert.metadata = pmodel::Metadata{};
insert.metadata->project = "serialisation";
insert.metadata->product = "mongo-service";
insert.options = options::Insert{};
insert.options->writeConcern = options::WriteConcern{};
insert.options->writeConcern->tag = "test";
insert.options->writeConcern->majority = std::chrono::milliseconds{ 100 };
insert.options->writeConcern->timeout = std::chrono::milliseconds{ 250 };
insert.options->writeConcern->nodes = 2;
insert.options->writeConcern->acknowledgeLevel = options::WriteConcern::Level::Majority;
insert.options->writeConcern->journal = true;
insert.options->bypassValidation = true;
insert.options->ordered = true;
builder.addCreate( insert );
auto update = model::request::Update<bsoncxx::document::value, pmodel::Metadata, model::request::IdFilter>{};
update.document.filter = model::request::IdFilter{};
update.document.filter->id = bsoncxx::oid{};
update.document.update = document{} <<
"$unset" << open_document << "obsoleteProperty" << 1 << close_document <<
"$set" <<
open_document <<
"modified" << bsoncxx::types::b_date{ std::chrono::system_clock::now() } <<
"user._id" << bsoncxx::oid{} <<
close_document <<
finalize;
update.database = "unit";
update.collection = "test";
update.application = "unitTest";
update.options = options::Update{};
update.options->collation = options::Collation{};
update.options->collation->locale = "en";
update.options->collation->strength = 1;
update.options->writeConcern = options::WriteConcern{};
update.options->writeConcern->tag = "test";
update.options->writeConcern->majority = std::chrono::milliseconds{ 100 };
update.options->writeConcern->timeout = std::chrono::milliseconds{ 250 };
update.options->writeConcern->nodes = 2;
update.options->writeConcern->acknowledgeLevel = options::WriteConcern::Level::Majority;
update.options->writeConcern->journal = true;
update.options->bypassValidation = false;
update.options->upsert = false;
builder.addUpdate( update );
auto remove = model::request::Delete<bsoncxx::document::value, pmodel::Metadata>{ document{} << finalize };
remove.database = "unit";
remove.collection = "test";
remove.application = "unitTest";
remove.options = options::Delete{};
remove.options->collation = options::Collation{};
remove.options->collation->locale = "en";
remove.options->collation->strength = 1;
remove.options->hint = document{} << "name" << 1 << finalize;
remove.options->writeConcern = options::WriteConcern{};
remove.options->writeConcern->tag = "test";
remove.options->writeConcern->majority = std::chrono::milliseconds{ 100 };
remove.options->writeConcern->timeout = std::chrono::milliseconds{ 250 };
remove.options->writeConcern->nodes = 2;
remove.options->writeConcern->acknowledgeLevel = options::WriteConcern::Level::Majority;
remove.options->writeConcern->journal = true;
remove.options->let = document{} <<
"vars" <<
open_document <<
"total" <<
open_document <<
"$add" << open_array << "$price" << "$tax" << close_array <<
close_document <<
"discounted" <<
open_document <<
"$cond" <<
open_document <<
"if" << "$applyDiscount" << "then" << 0.9 << "else" << 1 <<
close_document <<
close_document <<
close_document <<
"in" <<
open_document <<
"$multiply" << open_array << "$$total" << "$$discounted" << close_array <<
close_document <<
finalize;
builder.addRemove( remove );
const auto bson = builder.build();
auto result = repository::transaction( bson );
if ( !result.has_value() )
{
LOG_WARN << "Error executing transaction. " << magic_enum::enum_name( result.error().cause ) << ". " << result.error().message;
}
}