Add a very ugly login page to test database connection
I want to perform all SQL queries in a thread, to avoid freezing the UI,
that sometimes might happen when there is a lot of data to fetch; should
not happen very often, though.
Neither libpq nor Qt SQL allow queries on the same connection from
differents threads, and, in Qt SQL, all queries must be performed from
the same thread where the connection was established. In Qt5 i had to
either create a connection per thread, or use a QThread-derived object
to hold the connection and use signals and slots to pass query and
response data between the UI and database threads; it was usable but not
pretty.
With Qt6 and Concurrent’s QThreadPool now i can use QFutures instead,
that are not as cumbersome as with Qt5, because i no longer need
QFutureWatcher. I still have the problem that all queries must be done
from within the same thread, and QThreadPool uses an arbitrary thread.
The solution is to create a “pool” with a single, non-expirable thread,
and call all Concurrent::run onto that pool.
I have to test it properly, and first need to open the database to test
whether that, at least, works. I added a simple “login page” for that,
and to make a first attempt to error messages; i use a control that is
like Kirigami’s InlineMessage for now, but i am not sure.
I also do not know how i will configure database’s connection details. I
usually make use of pg_service.conf, because then the application only
need to know its service name, but i am not sure whether other people
would find it as comfortable as i do.
2024-12-16 11:59:19 +00:00
|
|
|
#include "database.h"
|
|
|
|
#include <QSqlDatabase>
|
|
|
|
#include <QSqlError>
|
2024-12-23 19:54:04 +00:00
|
|
|
#include <QSqlQuery>
|
Add a very ugly login page to test database connection
I want to perform all SQL queries in a thread, to avoid freezing the UI,
that sometimes might happen when there is a lot of data to fetch; should
not happen very often, though.
Neither libpq nor Qt SQL allow queries on the same connection from
differents threads, and, in Qt SQL, all queries must be performed from
the same thread where the connection was established. In Qt5 i had to
either create a connection per thread, or use a QThread-derived object
to hold the connection and use signals and slots to pass query and
response data between the UI and database threads; it was usable but not
pretty.
With Qt6 and Concurrent’s QThreadPool now i can use QFutures instead,
that are not as cumbersome as with Qt5, because i no longer need
QFutureWatcher. I still have the problem that all queries must be done
from within the same thread, and QThreadPool uses an arbitrary thread.
The solution is to create a “pool” with a single, non-expirable thread,
and call all Concurrent::run onto that pool.
I have to test it properly, and first need to open the database to test
whether that, at least, works. I added a simple “login page” for that,
and to make a first attempt to error messages; i use a control that is
like Kirigami’s InlineMessage for now, but i am not sure.
I also do not know how i will configure database’s connection details. I
usually make use of pg_service.conf, because then the application only
need to know its service name, but i am not sure whether other people
would find it as comfortable as i do.
2024-12-16 11:59:19 +00:00
|
|
|
|
|
|
|
Database::Database(QObject *parent)
|
|
|
|
: QObject{parent}
|
|
|
|
, m_pool{}
|
|
|
|
{
|
|
|
|
m_pool.setMaxThreadCount(1);
|
|
|
|
m_pool.setExpiryTimeout(-1);
|
|
|
|
}
|
|
|
|
|
2024-12-24 02:46:20 +00:00
|
|
|
Database &Database::getInstance()
|
|
|
|
{
|
|
|
|
static Database instance;
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
Database *Database::create(QQmlEngine *qmlEngine, QJSEngine *)
|
|
|
|
{
|
|
|
|
Database &instance = getInstance();
|
|
|
|
Q_ASSERT(qmlEngine->thread() == instance.thread());
|
|
|
|
|
|
|
|
static QQmlEngine *engine = nullptr;
|
|
|
|
if (engine) {
|
|
|
|
Q_ASSERT(qmlEngine == engine);
|
|
|
|
} else {
|
|
|
|
engine = qmlEngine;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJSEngine::setObjectOwnership(&instance, QJSEngine::CppOwnership);
|
|
|
|
|
|
|
|
return &instance;
|
|
|
|
}
|
|
|
|
|
2024-12-23 19:54:04 +00:00
|
|
|
QFuture<void> Database::open(const QString &user,
|
|
|
|
const QString &password,
|
|
|
|
const QString &hostName,
|
|
|
|
bool usePort,
|
|
|
|
int portNumber,
|
|
|
|
const QString &databaseName,
|
|
|
|
const QString &connectOptions)
|
Add a very ugly login page to test database connection
I want to perform all SQL queries in a thread, to avoid freezing the UI,
that sometimes might happen when there is a lot of data to fetch; should
not happen very often, though.
Neither libpq nor Qt SQL allow queries on the same connection from
differents threads, and, in Qt SQL, all queries must be performed from
the same thread where the connection was established. In Qt5 i had to
either create a connection per thread, or use a QThread-derived object
to hold the connection and use signals and slots to pass query and
response data between the UI and database threads; it was usable but not
pretty.
With Qt6 and Concurrent’s QThreadPool now i can use QFutures instead,
that are not as cumbersome as with Qt5, because i no longer need
QFutureWatcher. I still have the problem that all queries must be done
from within the same thread, and QThreadPool uses an arbitrary thread.
The solution is to create a “pool” with a single, non-expirable thread,
and call all Concurrent::run onto that pool.
I have to test it properly, and first need to open the database to test
whether that, at least, works. I added a simple “login page” for that,
and to make a first attempt to error messages; i use a control that is
like Kirigami’s InlineMessage for now, but i am not sure.
I also do not know how i will configure database’s connection details. I
usually make use of pg_service.conf, because then the application only
need to know its service name, but i am not sure whether other people
would find it as comfortable as i do.
2024-12-16 11:59:19 +00:00
|
|
|
{
|
2024-12-23 19:54:04 +00:00
|
|
|
return QtConcurrent::run(
|
|
|
|
&m_pool,
|
|
|
|
[this, user, password, hostName, usePort, portNumber, databaseName, connectOptions]() {
|
|
|
|
QString errorMessage;
|
|
|
|
QString connectionName;
|
|
|
|
{
|
|
|
|
QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL");
|
|
|
|
db.setHostName(hostName);
|
|
|
|
if (usePort) {
|
|
|
|
db.setPort(portNumber);
|
|
|
|
}
|
|
|
|
db.setDatabaseName(databaseName);
|
|
|
|
db.setConnectOptions(connectOptions);
|
|
|
|
if (db.open(user, password)) {
|
|
|
|
QSqlQuery q(db);
|
|
|
|
if (q.exec("SET search_path TO camper, public")) {
|
|
|
|
emit opened();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
errorMessage = q.lastError().text();
|
|
|
|
} else {
|
|
|
|
errorMessage = db.lastError().text();
|
|
|
|
}
|
|
|
|
connectionName = db.connectionName();
|
2024-12-20 20:29:46 +00:00
|
|
|
}
|
2024-12-23 19:54:04 +00:00
|
|
|
QSqlDatabase::removeDatabase(connectionName);
|
|
|
|
emit errorOcurred(errorMessage);
|
|
|
|
});
|
Add a very ugly login page to test database connection
I want to perform all SQL queries in a thread, to avoid freezing the UI,
that sometimes might happen when there is a lot of data to fetch; should
not happen very often, though.
Neither libpq nor Qt SQL allow queries on the same connection from
differents threads, and, in Qt SQL, all queries must be performed from
the same thread where the connection was established. In Qt5 i had to
either create a connection per thread, or use a QThread-derived object
to hold the connection and use signals and slots to pass query and
response data between the UI and database threads; it was usable but not
pretty.
With Qt6 and Concurrent’s QThreadPool now i can use QFutures instead,
that are not as cumbersome as with Qt5, because i no longer need
QFutureWatcher. I still have the problem that all queries must be done
from within the same thread, and QThreadPool uses an arbitrary thread.
The solution is to create a “pool” with a single, non-expirable thread,
and call all Concurrent::run onto that pool.
I have to test it properly, and first need to open the database to test
whether that, at least, works. I added a simple “login page” for that,
and to make a first attempt to error messages; i use a control that is
like Kirigami’s InlineMessage for now, but i am not sure.
I also do not know how i will configure database’s connection details. I
usually make use of pg_service.conf, because then the application only
need to know its service name, but i am not sure whether other people
would find it as comfortable as i do.
2024-12-16 11:59:19 +00:00
|
|
|
}
|
2024-12-16 18:22:39 +00:00
|
|
|
|
2024-12-19 00:53:12 +00:00
|
|
|
QFuture<void> Database::close()
|
|
|
|
{
|
|
|
|
return QtConcurrent::run(&m_pool, [this]() {
|
2024-12-20 20:29:46 +00:00
|
|
|
QString connectionName;
|
|
|
|
{
|
|
|
|
QSqlDatabase db = QSqlDatabase::database();
|
|
|
|
if (!db.isValid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
connectionName = db.connectionName();
|
|
|
|
db.close();
|
2024-12-19 00:53:12 +00:00
|
|
|
}
|
2024-12-20 20:29:46 +00:00
|
|
|
QSqlDatabase::removeDatabase(connectionName);
|
2024-12-19 00:53:12 +00:00
|
|
|
emit closed();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
Check QSqlQuery errors when doing a query
This is more for me than the end user, because if there is an error with
a query, there is—almost aways—nothing a user can do, since it is
probably an error in the static SQL string or the database. However, it
is better to show an error, than to do nothing at all when there is a
failure.
According to Qt’s documentation[0], QFuture relies on exceptions for the
error handling. At first i assumed that i had to attach an onFailed
handler to QFuture in order to receive that exception and skip the then
handler.
However, i can not attach the onFailure inside Database::query, before
returning the QFuture, because the subsequent then is only executed when
there _is_ an error, never in the normal, non-exceptional, case. I
would have to add the onFailure after then.
Nonetheless, i found out that there is no need for onFailure: since i do
not call result(), i do not get the exception, and the actual work is
performed in the then handler when no exception is raised.
[0]: https://doc.qt.io/qt-6/qfuture.html
2025-01-15 12:01:40 +00:00
|
|
|
void Database::checkError(const QSqlQuery &query)
|
|
|
|
{
|
|
|
|
QSqlError lastError = query.lastError();
|
|
|
|
if (!lastError.isValid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QString errorMessage = lastError.text();
|
|
|
|
emit getInstance().errorOcurred(errorMessage);
|
|
|
|
throw std::runtime_error(errorMessage.toStdString());
|
|
|
|
}
|
|
|
|
|
2024-12-16 18:22:39 +00:00
|
|
|
#include "moc_database.cpp"
|