Ulterno
Ulterno

Reputation: 53

Multithreading using QxOrm MONGODB, using different databases for each thread

According to QxOrm manuals : 3.2 and 3.22, it seems to be possible to use

qx::QxSqlDatabase::getSingleton()->setDatabaseName("./test_qxorm.db");
qx::QxSqlDatabase::getSingleton()->setHostName("localhost");
qx::QxSqlDatabase::getSingleton()->setUserName("root");
qx::QxSqlDatabase::getSingleton()->setPassword("");

separately in every thread to use multiple databases from multiple threads.

But on actually trying to use it, that seems to not be the case for the MONGODB implementation. Also, on looking at the source for qx::dao::call_query, it looks like it hasn't been implemented the way other SQL ones have.

Main question : How do I do the same for MONGODB?

  1. Is there a different way defined for doing so when using the QXMONGODB driver or is it not implemented in any way in QxOrm?
  2. In case of the latter, how can I manage to work around it and implement my own connection pool and use it with QxOrm? Because the MONGOCXX drivers definitely seem to include the required functionality for opening multiple databases.
  3. Is there a way I can share a connection between QxOrm libraries and MONGOCXX (or maybe MONGOC) libraries without having to make major changes in either of their sources?

Here is an Example in Qt that I tried to check it out.

db_interactor.h

#include <QFile>
#include <QTextStream>

#include <QDebug>
#include <QJsonDocument>
#include <QObject>
#include <QxOrm.h>

class db_interactor : public QObject
{
    Q_OBJECT
public:
    explicit db_interactor(QObject *parent = nullptr);
    db_interactor(QString configFilename, QObject *parent = nullptr);

signals:

    void errorNoReturn (QString errString);
    void errorReturn (QString errString);
    void initComplete();
    void dataReady (QJsonDocument response);

public slots:
    void init();
    void runQuery();

private:
    bool inited = false;
    QHash<QString, QString> config;

    qx::QxSqlDatabase *db;
};

db_interactor.cpp

#include "db_interactor.h"

db_interactor::db_interactor(QString configFileName, QObject *parent) : QObject{parent}
{
    if (!configFileName.isEmpty())
    {
        //....
        config.insert(/*From file*/);
    }
    //...
}

void db_interactor::init()
{
    db = qx::QxSqlDatabase::getSingleton();
    qDebug () << "Database Pointer" << db;
    db->setDriverName("QXMONGODB");
    db->setHostName("localhost");
    if (config.contains("DB"))
    {
        qDebug () << "Setting DB : " << config["DB"];
        db->setDatabaseName(config["DB"]);
    }
    else
    {
        qDebug () << "_________CONFIG DOES NOT CONTAIN \"DB\"_________________";
        db->setDatabaseName("new_db");
    }
    qDebug () << "____________Initialising DB____________";
    qDebug () << db->getDatabase();
    inited = true;
    emit initComplete();
}

void db_interactor::runQuery()
{
    if (inited)
    {
        qx_query nq ("{\"insert\" : \"some_coll\" , \"documents\" : [{\"a\" : 213}]}");
        QSqlError err = qx::dao::call_query(nq);

        if (err.isValid())
        {
            qDebug () << "Error writing to db : " << err.driverText();

            emit errorReturn("Error writing to db : " + err.driverText());
        }
        else
        {
            emit errorNoReturn("Written to DB");

            qx_query bq ("cursor" , "{\"find\" : \"some_coll\"}");
            err = qx::dao::call_query(bq);
            if (err.isValid())
            {
                qDebug () << "Error reading from db : " << err.driverText();
                emit errorReturn("Error reading from db : " + err.driverText());
                return;
            }
            emit dataReady
            (QJsonDocument::fromJson(bq.response().toString().toUtf8()));
        }
        return;
    }
    errorReturn ("Not Initialised yet");
}

Creating 2 objects of this class in another QObject class and moving them to separate threads...

mainthingy.h

#include <QObject>
#include <QThread>
#include <QDebug>
#include <QTimer>

#include "db_interactor.h"

class mainThingy : public QObject
{
    Q_OBJECT

    QThread aFewThreads[2];
public:
    explicit mainThingy(QObject *parent = nullptr);
    // Ignore the destructor for now, as that is not the point in question
signals:
    void init1 ();
    void init2 ();

    void runQr1 ();
    void runQr2 ();

private slots:
    void dataFrom1 (QJsonDocument response);
    void dataFrom2 (QJsonDocument response);

    void showErrReturn (QString err, int thread);

private:
    db_interactor *di1, *di2;

    void f_init1();
    void f_init2();

    void f_rq1();
    void f_rq2();

    void interface();

    bool user_time = true;
    QTimer ui_timer;
};

mainthingy.cpp

#include "mainthingy.h"

#include <iostream>
#include <string>

mainThingy::mainThingy(QObject *parent)
    : QObject{parent}
{
    di1 = new db_interactor(QStringLiteral(":/conf1.ini"));
    di2 = new db_interactor(QStringLiteral(":/conf2.ini"));
    di1->moveToThread(&aFewThreads[0]);
    di2->moveToThread(&aFewThreads[1]);
    aFewThreads[0].start();
    aFewThreads[1].start();

    connect(this, &mainThingy::init1, di1, &db_interactor::init);
    connect(di1, &db_interactor::initComplete, this, [this]()
    {
        qDebug () << "DB Interactor 1 Init Complete";
        user_time = true;
    });
    connect(di2, &db_interactor::initComplete, this, [this]()
    {
        qDebug () << "DB Interactor 2 Init Complete";
        user_time = true;
    });
    connect(this, &mainThingy::init2, di2, &db_interactor::init);
    connect(this, &mainThingy::runQr1, di1, &db_interactor::runQuery);
    connect(this, &mainThingy::runQr2, di2, &db_interactor::runQuery);
    connect(di1, &db_interactor::dataReady, this, &mainThingy::dataFrom1);
    connect(di2, &db_interactor::dataReady, this, &mainThingy::dataFrom2);

    connect(di1, &db_interactor::errorReturn, this, [this] (QString eS)
    {
        showErrReturn(eS, 1);
    });
    connect(di2, &db_interactor::errorReturn, this, [this] (QString eS)
    {
        showErrReturn(eS, 2);
    });

    interface();

    connect(&ui_timer, &QTimer::timeout, this, [this] ()
    {
        if (this->user_time)
        {
            interface();
        }
    });
    ui_timer.start(2000);
}

void mainThingy::dataFrom1(QJsonDocument response)
{
    qDebug () << response;
    user_time = true;
}

void mainThingy::dataFrom2(QJsonDocument response)
{
    qDebug () << response;
    user_time = true;
}

void mainThingy::showErrReturn(QString err, int thread)
{
    std::cout << "Error in " << thread << ": " << err.toStdString() << std::endl;
    qDebug () << "Err " << 1 << err;
    user_time = true;
}

void mainThingy::f_init1()
{
    qDebug () << "void mainThingy::f_init1()";
    emit init1();
}

void mainThingy::f_init2()
{
    qDebug () << "void mainThingy::f_init2()";
    emit init2();
}

void mainThingy::f_rq1()
{
    qDebug () << "void mainThingy::f_rq1()";
    emit runQr1();
}

void mainThingy::f_rq2()
{
    qDebug () << "void mainThingy::f_rq2()";
    emit runQr2();
}

void mainThingy::interface()
{
    // Made a crude little CLI to get to the real problem
    qDebug () << "void mainThingy::interface()";
    std::cout << "Ready for command" << std::endl;
    std::string usrResp;
    std::cin >> usrResp;
    QString uR = QString::fromStdString(usrResp);
    if (uR == "i1")
    {
        f_init1();
        user_time = false;
    }
    else if (uR == "i2")
    {
        f_init2();
        user_time = false;
    }
    else if (uR == "q1")
    {
        f_rq1();
        user_time = false;
    }
    else if (uR == "q2")
    {
        f_rq2();
        user_time = false;
    }
    else if (uR == "quit")
    {
        exit (0);
    }
    else
    {
        std::cout << "?" << std::endl;
        qDebug () << "??";
    }
}

Having 2 different configurations in qrc:/conf1.ini and qrc:/conf2.ini, I expected q1 and q2 to send data to 2 separate databases.

But, I found out the following:

  1. qx::QxSqlDatabase::getSingleton()->getDatabase() gave an error and a null database
  2. On initializing the di1, the query worked fine and inserted data into the correct database, but on initialising di2 in the same program instance, I found both queries "q1" and "q2" inserting data into the database initialised by di2. Essentially, the same configuration is being overwritten.

Upvotes: 0

Views: 124

Answers (1)

Ulterno
Ulterno

Reputation: 53

Using multiple databases in QxOrm has 2 ways to be used in case of SQL Databases.

  1. Make an object of QSqlDatabase and pass a pointer when calling the setXXXX() functions and use that object when calling queries.
  2. As long as there is only one database configuration for each thread in the program, pass true for bJustForCurrentThread when calling the setXXXX() functions and QxOrm will handle it according to the documentation.
void setDriverName(const QString & s, bool bJustForCurrentThread = false, QSqlDatabase * pJustForThisDatabase = NULL);
void setDatabaseName(const QString & s, bool bJustForCurrentThread = false, QSqlDatabase * pJustForThisDatabase = NULL);
void setHostName(const QString & s, bool bJustForCurrentThread = false, QSqlDatabase * pJustForThisDatabase = NULL);
void setPort(int i, bool bJustForCurrentThread = false, QSqlDatabase * pJustForThisDatabase = NULL);

In case of QXMONGODB driver, you have the option of opening multiple databases in different threads and as long as you pass true for bJustForCurrentThread when calling the setXXXX() functions, it will work as expected.

In the above question I had not passed true in the argument bJustForCurrentThread, which caused configuration in any one application thread to be set for all the other threads, giving the problems that I was having.

Conclusions:

  1. QSqlDatabase won't be usable with MONGODB driver as QSqlDatabase is for SQL Databases
  2. The way I had implemented the above example, it wouldn't work even with other drivers as expected.
  3. call_query() implementation for MONGODB seems to be different on the top, but the internal implementation still has the required support for multiple databases in different threads.

Upvotes: 0

Related Questions