MongoDB.live, free & fully virtual. June 9th - 10th. Register Now

MongoCxx: how to insert blobs?

I’m currently trying to write C++ code with which I can store and retrieve blobs (binary large objects) into my database.

So far, I successfully wrote regular arrays, but I have doubts about whether they are stored in a dense form. Or am I mistaken here and I could simply write an array with the data being encoded in some integer type and be done with it?
I took a look at bsoncxx::types::b_binary, but the documentation is a little bit short.
So far, I managed to find no example or tutorial that deals with blobs.

In my case in particular, amongst other things, I need to store the data from PNG images in the database. What is the best practice for that?

If possible, I’d like some basic example code that goes from having the binary data in some reasonable form (like vector<uint8_t>) and declaring some appropriate builder to a finished bsoncxx::document::value.
I’d also be interested in the best practice of retrieving the value, especially how to have the least copy operations in order to arrive at the binary data being in the original form as above.
But in any case, any sort of help would be appreciated.

(Note that I just began using mongocxx and MongoDB in general, and not completely familiar with the full terminology.)

My current attempt is to work with arrays. The code I use so far is for writing data:

bsoncxx::types::b_oid write_blob()
{
    mongocxx::uri uri("mongodb://localhost:27017");
    mongocxx::client client(uri);
    mongocxx::database database = client["test_database"];
    mongocxx::collection collection = database["test_collection"];

    std::vector<uint8_t> elements = {7, 8, 9};
    auto doc = bsoncxx::builder::basic::document{};
    doc.append(bsoncxx::builder::basic::kvp("data", [&elements](bsoncxx::builder::basic::sub_array child) {
	for (const auto& element : elements) {
		child.append(element);
	}
    }));

    bsoncxx::document::value value = doc.extract();

    bsoncxx::stdx::optional<mongocxx::result::insert_one> result =
        collection.insert_one(value.view());
		
    std::cout << "id is: " << result->inserted_id().get_oid().value.to_string() << std::endl;

    return result->inserted_id().get_oid();
}

and for receiving data:

void read_blob(bsoncxx::types::b_oid hash)
{
    mongocxx::uri uri("mongodb://localhost:27017");
    mongocxx::client client(uri);
    mongocxx::database database = client["test_database"];
    mongocxx::collection collection = database["test_collection"];

    bsoncxx::stdx::optional<bsoncxx::document::value> value = collection.find_one(bsoncxx::builder::stream::document{} << "_id" << hash << bsoncxx::builder::stream::finalize);
    
    for(size_t i=0; i<value->view()["data"].length(); i++)
    {
        if(value->view()["data"][i].type() == bsoncxx::type::k_int32)
	{
		std::cout << value->view()["data"][i].get_int32() << std::endl;
	}
    }
}

Now this works to a certain degree so far, but I have several issues with this:

  1. I have doubts whether the data written here is stored and accessed efficiently, that is as binary block.
  2. I am not sure if my code is best practice, especially if it avoids unnecessary copying.
  3. The type of the array is integer 32, while I had hoped to enode bytes. Is there a way to encode something of byte size, like uint8_t? I hope to avoid the approach of putting several one byte together, especially as their amount might not be divisible by four.
  4. The iteration goes out of bounds. length() returns the byte size, which in this case is 54 for some reason, and I have no idea why. Is this the length of it being encoded in ASCII? Why is it not divisible by eight then? In any case, is there a proper way to get the actual array size, three in this case?

Ideally, I’d like to see somebody to rewrite my functions.