Skip to content

Commit

Permalink
Add BSON auto insertion of _id
Browse files Browse the repository at this point in the history
    Mogo requires all objects to have ObjectID as a unique Key.
    The user should not need to use this on the client side if this is not the primary key.
    But to track insertions we allow the BSON printer to auto insert a "_id" field with
    this object ObjectID if requested (default no).

    So if you request this field it will be added and the inserted ID will be returned.
  • Loading branch information
Loki-Astari committed Jul 17, 2024
1 parent 366e612 commit 8cd229d
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 31 deletions.
46 changes: 41 additions & 5 deletions src/Serialize/BsonPrinter.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "BsonPrinter.h"
#include "MongoUtility.h"
#include "ThorsIOUtil/Utility.h"
#include "ThorsLogging/ThorsLogging.h"
#include <iomanip>
Expand All @@ -7,21 +8,43 @@
using namespace ThorsAnvil::Serialize;

THORS_SERIALIZER_HEADER_ONLY_INCLUDE
BsonPrinter::BsonPrinter(std::ostream& output, PrinterConfig config)
BsonPrinter::BsonPrinter(std::ostream& output, BsonPrinterConfig config)
: PrinterInterface(output, config)
, idStore(config.idStore)
{}

THORS_SERIALIZER_HEADER_ONLY_INCLUDE
void BsonPrinter::pushLevel(bool isMap)
{
currentContainer.emplace_back(isMap ? BsonContainer::Map : BsonContainer::Array);
}
THORS_SERIALIZER_HEADER_ONLY_INCLUDE
void BsonPrinter::popLevel()
{
currentContainer.pop_back();
}

// MAP
THORS_SERIALIZER_HEADER_ONLY_INCLUDE
std::size_t BsonPrinter::getSizeMap(std::size_t count)
{
/*
* A map is a document:
* <size 4bytes> <Element-List> <Terminator 1byte>
* <size 4 bytes> <Element-List> <Terminator 1 byte>
* Each element int the <Element-List> consists of:
* <type 1byte> <e-name (size accounted for) + '\0' 1byte> <object (size accounted for)>
* <type 1 byte> <e-name (size accounted for) + '\0' 1 byte> <object (size accounted for)>
* If AddId is true then we will insert and _id member into the map.
* <type 1byte(0x07) <e_name _id\x00> <Object ID 12-byte>
*/
return 4 + (count * (1 + 1)) + 1;
std::size_t addIdSize = 0;
if (idStore.has_value() && currentContainer.size() == 1 && currentContainer[0] == BsonContainer::Array) {
addIdSize = 17;
}

return 4 // Size
+ (count * (1 + 1)) // Type plus '\0' for each item
+ addIdSize // Size from extra ID field being inserted.
+ 1; // Terminator
}

// ARRAY
Expand Down Expand Up @@ -53,7 +76,10 @@ std::size_t BsonPrinter::getSizeArray(std::size_t count)
}
indexTotalStringLen += (count - accountedFor) * numberOfDigitsThisLevel;

return getSizeMap(count) + indexTotalStringLen;
return 4 // Size
+ (count * (1 + 1)) // Type plus '\0' for each item
+ indexTotalStringLen // The string lengths.
+ 1; // Terminator
}

// Add a new Key
Expand Down Expand Up @@ -122,7 +148,17 @@ void BsonPrinter::openMap(std::size_t size)
{
writeKey('\x03', -1);
writeSize<4, std::int32_t>(static_cast<std::int32_t>(size));

bool shouldAddID = idStore.has_value() && currentContainer.size() == 1 && currentContainer[0] == BsonContainer::Array;
currentContainer.emplace_back(BsonContainer::Map);

if (shouldAddID)
{
addKey("_id");
writeKey('\x07', 12);
idStore->get().emplace_back();
(*this) << idStore->get().back();
}
}

THORS_SERIALIZER_HEADER_ONLY_INCLUDE
Expand Down
27 changes: 22 additions & 5 deletions src/Serialize/BsonPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <boost/endian/conversion.hpp>
#include <vector>
#include <iostream>
#include <functional>

namespace ThorsAnvil
{
Expand All @@ -20,21 +21,35 @@ namespace ThorsAnvil
class BsonPrinter;
namespace MongoUtility
{
class ObjectID;
class UTCDateTime;
BsonPrinter& operator<<(BsonPrinter& printer, MongoUtility::UTCDateTime const& data);
}

using IntTypes = std::tuple<std::int32_t, std::int64_t>;
using IntTypes = std::tuple<std::int32_t, std::int64_t>;
using IdStore = std::optional<std::reference_wrapper<std::vector<MongoUtility::ObjectID>>>;

struct BsonPrinterConfig: public PrinterInterface::PrinterConfig
{
IdStore idStore;
public:
using PrinterInterface::PrinterConfig::PrinterConfig;
BsonPrinterConfig(PrinterInterface::PrinterConfig val, IdStore idStore = {})
: PrinterInterface::PrinterConfig(std::move(val))
, idStore(idStore)
{}
};

class BsonPrinter: public PrinterInterface
{
friend BsonPrinter& MongoUtility::operator<<(BsonPrinter& printer, MongoUtility::UTCDateTime const& data);

std::string currentKey;
std::string currentKey;
std::vector<BsonContainer> currentContainer;
std::vector<std::size_t> arrayIndex;
IdStore idStore;
public:
BsonPrinter(std::ostream& output, PrinterConfig config = PrinterConfig{});
BsonPrinter(std::ostream& output, BsonPrinterConfig config = BsonPrinterConfig{});
virtual FormatType formatType() override {return FormatType::Bson;}
virtual void openDoc() override;
virtual void closeDoc() override;
Expand Down Expand Up @@ -73,8 +88,10 @@ class BsonPrinter: public PrinterInterface
protected:
// Protected to allow unit tests
virtual bool printerUsesSize() override {return true;}
virtual std::size_t getSizeMap(std::size_t /*count*/) override;
virtual std::size_t getSizeArray(std::size_t /*count*/) override;
virtual void pushLevel(bool isMap) override;
virtual void popLevel() override;
virtual std::size_t getSizeMap(std::size_t count) override;
virtual std::size_t getSizeArray(std::size_t count) override;
virtual std::size_t getSizeNull() override {return 0;}
virtual std::size_t getSizeValue(short int) override {return MaxTemplate<sizeof(short int), 4>::value;}
virtual std::size_t getSizeValue(int) override {return sizeof(int);}
Expand Down
4 changes: 2 additions & 2 deletions src/Serialize/BsonThor.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ struct Bson
// @param config.catchExceptions 'false: exceptions propogate. 'true': parsing exceptions are stopped.
// @return Object that can be passed to operator<< for serialization.
template<typename T>
Exporter<Bson, T> bsonExporter(T const& value, PrinterInterface::PrinterConfig config = PrinterInterface::PrinterConfig{})
Exporter<Bson, T, BsonPrinterConfig> bsonExporter(T const& value, BsonPrinterConfig config = PrinterInterface::PrinterConfig{})
{
config.parserInfo = static_cast<long>(BsonBaseTypeGetter<T>::value);
BsonBaseTypeGetter<T>::validate(value);

return Exporter<Bson, T>(value, config);
return Exporter<Bson, T, BsonPrinterConfig>(value, config);
}

// @function-api
Expand Down
7 changes: 3 additions & 4 deletions src/Serialize/Exporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ namespace ThorsAnvil
namespace Serialize
{

template<typename Format, typename T>
template<typename Format, typename T, typename Config = PrinterInterface::PrinterConfig>
class Exporter
{
using PrinterConfig = PrinterInterface::PrinterConfig;
T const& value;
PrinterConfig config;
Config config;
public:
Exporter(T const& value, PrinterConfig config)
Exporter(T const& value, Config config)
: value(value)
, config(config)
{}
Expand Down
12 changes: 11 additions & 1 deletion src/Serialize/SerUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,13 @@ struct MapLike
static std::size_t getPrintSize(PrinterInterface& printer, C const& object, bool)
{
std::size_t result = printer.getSizeMap(std::size(object));
printer.pushLevel(true);
for (auto const& value: object)
{
result += std::size(value.first);
result += Traits<std::remove_cv_t<T>>::getPrintSize(printer, value.second, false);
}
printer.popLevel();
return result;
}
};
Expand All @@ -90,10 +92,12 @@ struct ArrayLike
static std::size_t getPrintSize(PrinterInterface& printer, C const& object, bool)
{
std::size_t result = printer.getSizeArray(std::size(object));
printer.pushLevel(false);
for (auto const& val: object)
{
result += Traits<std::remove_cv_t<T>>::getPrintSize(printer, val, false);
}
printer.popLevel();
return result;
}
};
Expand Down Expand Up @@ -253,11 +257,15 @@ class Traits<std::pair<F, S>>
}
static std::size_t getPrintSize(PrinterInterface& printer, std::pair<F, S> const& object, bool)
{
return printer.getSizeMap(2)
std::size_t result = printer.getSizeMap(2);
printer.pushLevel(true);
result = result
+ std::strlen("first")
+ std::strlen("second")
+ Traits<std::remove_cv_t<std::decay_t<F>>>::getPrintSize(printer, object.first, false)
+ Traits<std::remove_cv_t<std::decay_t<S>>>::getPrintSize(printer, object.second, false);
printer.popLevel();
return result;
}
};

Expand Down Expand Up @@ -877,7 +885,9 @@ class Traits<std::tuple<Args...>>
static std::size_t getPrintSize(PrinterInterface& printer, std::tuple<Args...> const& object, bool)
{
std::size_t result = printer.getSizeArray(sizeof...(Args));
printer.pushLevel(false);
result += getPrintSizeAllElement(printer, object, std::make_index_sequence<sizeof...(Args)>());
printer.popLevel();
return result;
}
};
Expand Down
2 changes: 2 additions & 0 deletions src/Serialize/ThorsSerializerUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ class PrinterInterface
void addValue(void const*) = delete;

virtual bool printerUsesSize() {return false;}
virtual void pushLevel(bool) {}
virtual void popLevel() {}
virtual std::size_t getSizeMap(std::size_t /*count*/) {return 0;}
virtual std::size_t getSizeArray(std::size_t /*count*/) {return 0;}
virtual std::size_t getSizeNull() {return 0;}
Expand Down
22 changes: 8 additions & 14 deletions src/Serialize/Traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,12 @@
*
* To get this to work you need to define the type SerializableType:
*
* SerializableType::write(Printer, object)
* SerializableType::read(Parser, object)
*
*
* "object" is the object you want to serialize.
* Printer/Parser will be derived from
* PrinterInterface
* ParserInterface
*
* You can write versions that use these generic types or you can
* have implementations for specific version.
* eg. BsonParser/BsonPrinter
*
* The code will link with the most appropriate version:
* struct SerializableType
* {
* static std::size_t getPrintSizeBson(BsonPrinter& printer, Type const& object);
* static void writeCustom(PrinterInterface& printer, Type const& object);
* static void readCustom(ParserInterface& parser, Type& object);
* };
*
* Sometimes you can not use the key names you want (they are reserved words or contain
* symbols that are not valid in identifiers in C++). So we provide a way to override
Expand Down Expand Up @@ -599,7 +591,9 @@ class Traits<DataType BUILDTEMPLATETYPEVALUE(THOR_TYPENAMEVALUEACTION, Count) >
\
static std::size_t getPrintSizeTotal(PrinterInterface& printer, MyType const& object, std::size_t& count, std::size_t& memberSize)\
{ \
printer.pushLevel(true); \
auto r = addSizeEachMember(printer, object, std::make_index_sequence<std::tuple_size_v<Members>>());\
printer.popLevel(); \
memberSize += r.first; \
count += r.second; \
\
Expand Down
Loading

0 comments on commit 8cd229d

Please sign in to comment.