Archives mensuelles : mars 2024

Interroger un cluster MongoDB Atlas avec C++

J’ai décidé de me replonger dans le langage C++ le temps d’un exercice destiné aux étudiants suivant ma formation sur MongoDB en école d’ingénieur. Tout d’abord, nous avons effectué toutes les requêtes de l’exercice sur les salles (PDF) à partir de l’interpréteur JavaScript du shell MongoDB, en direction de nos clusters créés sur Atlas. Ensuite, nous avons traduit ces requêtes dans le langage de programmation préféré de chaque étudiant. N’ayant pas pratiqué le C++ depuis l’époque où la monnaie nationale était encore le franc, j’ai quant à moi pris le pari de revenir à mes premiers amours et d’utiliser le langage de Stroustrup comme base…ce fut un plaisir !

Tout d’abord, il m’a fallu installer le pilote (driver) dédié à ce langage : mongocxx.

Installation et compilation du pilote mongocxx

Rien de plus simple pour y parvenir :

    cd mongo-cxx-driver
    mkdir build && cd build
    cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local ..
    make -j$(nproc)
    sudo make install

Le programme principal

Une fois le driver installé, j’ai pu commencer à coder le petit programme pour accéder à mon cluster Atlas et y exécuter toutes les requêtes préparées dans mon header exercices.hpp . Le voici :

#include <iostream>
#include <bsoncxx/json.hpp>
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>
#include <mongocxx/exception/exception.hpp>
#include "exercices.hpp" 

using bsoncxx::to_json;

int main() {

    const std::string USERNAME      = "mongosensei";
    const std::string PASSWORD      = "xxxxx";
    const std::string DATABASE      = "xxxxx";
    const std::string COLLECTION    = "salles";
    const std::string ATLAS_CLUSTER = "xxx.mongodb.net";

    mongocxx::instance inst{};

    try {
        std::string uriString = "mongodb+srv://" + USERNAME + ":" + PASSWORD + "@" + ATLAS_CLUSTER + "/" + DATABASE;
        mongocxx::uri uri(uriString);
        mongocxx::client conn(uri);

        std::vector<bsoncxx::document::value> exercices = getExercices();

        for (const auto& exercice : exercices) {
            auto numExercice = exercice["num"].get_int32();
            auto filterDocument = exercice["filtre"].get_document();
            auto projectionDocument = exercice["options"]["projection"].get_document();
            
            auto options = mongocxx::options::find{};
            options.projection(projectionDocument.view());

            auto collection = conn[DATABASE][COLLECTION];
            auto cursor = collection.find(filterDocument.view(), options);

            int nbDocuments = 0;

            for (auto&& doc : cursor) {
                nbDocuments++;
                std::cout << bsoncxx::to_json(doc) << std::endl;
            }

            std::cout << "Nb de documents pour l'exercice " << numExercice << " : " << nbDocuments << std::endl;
        }

    } catch (const mongocxx::exception& e) {
        std::cerr << "Erreur de connexion : " << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

Ce programme fait appel à un fichier qui contient la solution à chacun des exercices contenus dans le PDF dont le lien a été donné plus haut. Voici ce à quoi il ressemble :

#ifndef EXERCICES_HPP
#define EXERCICES_HPP

#include <vector>
#include <bsoncxx/builder/basic/document.hpp>

time_t parseDate(const std::string& dateStr) {
    std::tm tm = {};
    strptime(dateStr.c_str(), "%Y-%m-%d", &tm);
    return mktime(&tm);
}

std::vector<bsoncxx::document::value> getExercices() {
    using bsoncxx::builder::basic::kvp;
    using bsoncxx::builder::basic::make_document;
    using bsoncxx::builder::basic::make_array;

    std::string date_str = "2021-11-15";
    auto timestamp = std::chrono::milliseconds{parseDate(date_str) * 1000};

    std::vector<bsoncxx::document::value> exercices = {
        make_document(
            kvp("num", 1),
            kvp("filtre", make_document(
                kvp("smac", true)
            )),
            kvp("options", make_document(
                kvp("projection", make_document(
                    kvp("_id", true),
                    kvp("nom", true)
                ))
            ))
        ),
        make_document(
            kvp("num", 2),
            kvp("filtre", make_document(
                kvp("capacite", make_document(
                    kvp("$gt", bsoncxx::types::b_int64{1000})
                ))
            )),
            kvp("options", make_document(
                kvp("projection", make_document(
                    kvp("_id", false),
                    kvp("nom", true)
                ))
            ))
        ),
        make_document(
            kvp("num", 3),
            kvp("filtre", make_document(
                kvp("adresse.numero", make_document(
                    kvp("$exists", false)
                ))
            )),
            kvp("options", make_document(
                kvp("projection", make_document(
                    kvp("_id", true)
                ))
            ))
        ),
        make_document(
            kvp("num", 4),
            kvp("filtre", make_document(
                kvp("avis", make_document(
                    kvp("$size", bsoncxx::types::b_int64{1})
                ))
            )),
            kvp("options", make_document(
                kvp("projection", make_document(
                    kvp("nom", true)
                ))
            ))
        ),
        make_document(
            kvp("num", 5),
            kvp("filtre", make_document(
                kvp("styles", "blues")
            )),
            kvp("options", make_document(
                kvp("projection", make_document(
                    kvp("_id", false),
                    kvp("styles", true)
                ))
            ))
        ),
        make_document(
            kvp("num", 6),
            kvp("filtre", make_document(
                kvp("styles.0", "blues")
            )),
            kvp("options", make_document(
                kvp("projection", make_document(
                    kvp("_id", false),
                    kvp("styles", true)
                ))
            ))
        ),
        make_document(
            kvp("num", 7),
            kvp("filtre", make_document(
                kvp("$and", make_array(
                    make_document(
                        kvp("adresse.codePostal", bsoncxx::types::b_regex{"^84"}),
                        kvp("capacite", make_document(
                            kvp("$lt", 500)
                        ))
                    )
                ))
            )),
            kvp("options", make_document(
                kvp("projection", make_document(
                    kvp("_id", false),
                    kvp("adresse.ville", true)
                ))
            ))
        ),
        make_document(
            kvp("num", 8),
            kvp("filtre", make_document(
                kvp("$or", make_array(
                    make_document(
                        kvp("_id", make_document(kvp("$mod", make_array(2, 0))))
                    ),
                    make_document(
                        kvp("avis", make_document(
                            kvp("$exists", false)
                        ))
                    )
                ))
            )),
            kvp("options", make_document(
                kvp("projection", make_document(
                    kvp("_id", true)
                ))
            ))
        ),
        make_document(
            kvp("num", 9),
            kvp("filtre", make_document(
                kvp("avis", make_document(
                    kvp("$elemMatch", make_document(
                        kvp("note", make_document(
                            kvp("$gte", 8),
                            kvp("$lte", 10)
                        ))
                    ))
                ))
            )),
            kvp("options", make_document(
                kvp("projection", make_document(
                    kvp("_id", false),
                    kvp("nom", true)
                ))
            ))
        ),
        make_document(
            kvp("num", 10),
            kvp("filtre", make_document(
                kvp("avis.date", make_document(
                        kvp("$gt", bsoncxx::types::b_date{timestamp}
                    ))
                ))
            ),
            kvp("options", make_document(
                kvp("projection", make_document(
                    kvp("_id", false),
                    kvp("nom", true)
                ))
            ))
        ),
    };

    return exercices;
}

#endif // EXERCICES_HPP

La fonction parseDate est là pour nous faciliter la transformation des dates pour leur utilisation avec mes méthodes de la lib BSON, elle ne sert ici qu’une fois.

Pour construire les documents, j’ai utilisé la version basic du builder BSON, la version stream étant beaucoup moins lisible pour moi.

Compilation et exécution du code

J’ai essayé plusieurs solutions :

  • la compilation avec c++
  • la compilation avec g++
  • l’installation de l’outil pkg-config, MongoDB proposant un fichier .pc

D’abord, la version avec pkg-config :

sudo apt install pkg-config
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
c++ -o mongo_exercices --std=c++11 mongo_exercices.cpp $(pkg-config --cflags --libs libmongocxx)
./mongo_exercices

Ensuite la version avec g++, qui marche aussi avec c++ :

g++ -o mongo_exercices mongo_exercices.cpp -lmongocxx -lbsoncxx -std=c++11 \
    -I/usr/local/include/mongocxx/v_noabi \
    -I/usr/local/include/bsoncxx/v_noabi \
    -I/usr/local/include/bsoncxx/v_noabi/bsoncxx/third_party/mnmlstc \
    -L/usr/local/lib -Wl,-rpath=/usr/local/lib \
    && ./mongo_exercices

Voici la sortie produite par notre exécutable :

{ "_id" : 1, "nom" : "AJMI Jazz Club" }
{ "_id" : 2, "nom" : "Paloma" }
Nb de documents pour l'exercice 1 : 2
{ "nom" : "Paloma" }
Nb de documents pour l'exercice 2 : 1
{ "_id" : 3 }
{ "_id" : { "$oid" : "65e08cc6fa1cb047c0a5e5ea" } }
Nb de documents pour l'exercice 3 : 2
{ "_id" : 2, "nom" : "Paloma" }
Nb de documents pour l'exercice 4 : 1
{ "styles" : [ "jazz", "soul", "funk", "blues" ] }
{ "styles" : [ "blues", "rock" ] }
Nb de documents pour l'exercice 5 : 2
{ "styles" : [ "blues", "rock" ] }
Nb de documents pour l'exercice 6 : 1
{ "adresse" : { "ville" : "Avignon" } }
{ "adresse" : { "ville" : "Le Thor" } }
Nb de documents pour l'exercice 7 : 2
{ "_id" : 2 }
{ "_id" : 3 }
{ "_id" : { "$oid" : "65e08cc6fa1cb047c0a5e5ea" } }
Nb de documents pour l'exercice 8 : 3
{ "nom" : "AJMI Jazz Club" }
{ "nom" : "Paloma" }
Nb de documents pour l'exercice 9 : 2
{ "nom" : "AJMI Jazz Club" }
{ "nom" : "Paloma" }
Nb de documents pour l'exercice 10 : 2

Pour pousser plus loin votre expérience avec C++, suivez ce lien !