QSqlDatbase is closed before the query finishes - c++

I am trying to implement a database object for my application, but it became a nightmare when I started using multiple connections. Below, you can see my database C++ class code:
// here are the declarations
QString server_addr;
QString username;
QString password;
QString database_name;
QSqlDatabase connection;
QString error;
QString connectionName;
QSqlQuery m_query;
// and here are the definitions
database::database(QString connectionName) {
preferences p; p.read();
QString iConnectionName = (connectionName == "") ? default_connection_name : connectionName;
this->connectionName = iConnectionName;
if (QSqlDatabase::contains(iConnectionName))
this->connection = QSqlDatabase::database(iConnectionName);
else this->connection = QSqlDatabase::addDatabase("QMYSQL", iConnectionName);
this->connection.setHostName(p.database->server_addr);
this->connection.setUserName(p.database->username);
this->connection.setPassword(p.database->password);
this->connection.setDatabaseName(p.database->database_name);
this->connection.setPort(p.database->serverPort);
if (!connection.open())
{
this->error = this->connection.lastError().text();
}
else this->error = "";
}
QSqlQuery database::query(QString query_text) {
this->m_query = QSqlQuery(query_text, this->connection);
this->m_query.exec();
return m_query;
}
database::~database() {
if (!this->m_query.isActive()) QSqlDatabase::removeDatabase(this->connectionName);
qDebug() << "database object destroyed\n";
}
The problem occurs in a another class (that uses the database):
databaseAdaptor::databaseAdaptor() {
this->db = database();
// other construction operations
}
void databaseAdaptor::fetch() {
QSqlQuery q = db.query("SELECT * FROM `activities`");
qDebug() << "Rows count: " << q.numRowsAffected();
}
It worked some versions in the past, but for some reason, now the output from qDebug() is
Rows count: -1 // and it should be 2 for my current version of database.
In the Qt Documentation, it is said that this class' members are thread-safe. Could this be the problem? I mean, could the thread end before the queries finish their execution?
If so, how can I keep the connection open until all the queries finished their execution?
(I know it's much to read, but I am sure that other might have this problem at some point, so please take your time and read it). Thank you!

Related

InsertRecord return false but seems no error happend

I am new to qt sql, and the problem happening in QSqlRelationalModel puzzled me too much.
Model construtor:
RecordTableModel::RecordTableModel(QSharedPointer<BookTableModel> bookTableModel, QObject *parent)
: QSqlRelationalTableModel(parent), BookTable(BookTableModel)
{
setTable("record");
select();
setRelation(fieldName::genre, QSqlRelation("genres", "id", "genre"));
setRelation(fieldName::recordState, QSqlRelation("recordStates", "id", "state"));
/*test start*/
addEntry(0);
/*test end*/
}
addEntry:
int RecordTableModel::addEntry(int bookId)
{
QSqlRecord record = this->record();
QSqlRecord bookRecord = bookTable->getRecord(bookId);
//modify record
record.setValue(fieldName::bookId, bookRecord.value(BookTableModel::fieldName::bookId));
record.setValue(fieldName::bookName, bookRecord.value(BookTableModel::fieldName::bookName));
record.setValue(fieldName::author, bookRecord.value(BookTableModel::fieldName::author));
//... others same like this
if (!insertRecord(-1, record)) {
qDebug() << lastError() << "fail to insert record in addEntry function";
}
submitAll();
}
In the test the console shows the message above but lastError() contains no error message. And actually the record is inserted into table successfully. Could anyone help solving the problem?

How to replace Qthread with QtConcurrent

I have a SQL fetching class that connects to SQL and fetched the required data.
I wanted to this in another thread so I did that with QThread.
It is working as it should be.
But now I want to replace it with QTConcurrent. The problem I face with QTconcureent is that I need a connect command that initializes the database before the thread uses to do SQL query.
Here are my codes created is the public slot and qint64TotalSize is the public method of the SqlFetcher class.
Controller::Controller(QObject *parent) : QObject(parent)
{
SqlFetcher* m_Fetcher = new SqlFetcher();
qInfo() <<"Start"<< QThread::currentThread();
QFutureWatcher<void> watcher;
QFuture <void> future1 = QtConcurrent::run(m_Fetcher,&SqlFetcher::qint64TotalSize);
watcher.setFuture(future1);
//QThread* t1 = new QThread();
//m_Fetcher->moveToThread(t1);
//connect(t1, &QThread::started, m_Fetcher, &SqlFetcher::createDb);
//connect(t1, &QThread::started, m_Fetcher, &SqlFetcher::qint64TotalSize);
//t1->start();
qInfo() <<"Finish"<< QThread::currentThread();
}
void SqlFetcher::qint64TotalSize()
{
qint64 l_result= 0;
QSqlQuery l_query;
if (m_sqldb.isValid())
{
m_sqldb.open();
if ((m_sqldb.isOpen()))
{
l_query.prepare("SELECT COUNT(*) FROM table1");
l_query.exec();
if (l_query.next()) {
l_result= l_query.value(0).toInt();
}
m_sqldb.close();
}
}
qInfo() << l_result << QThread::currentThread();
}
void SqlFetcher::createDb()
{
m_sqldb = QSqlDatabase::addDatabase("QSQLITE");
m_sqldb.setDatabaseName("xyz.db");
qInfo() << "createDB" << QThread::currentThread();
}
My current output is
Start QThread(0x7feab4c0f060)
Finish QThread(0x7feab4c0f060)
0 QThread(0x7feab4d42070, name = "Thread (pooled)")
Expected Output or Output with Qthread is
Start QThread(0x7fe82140f060)
Finish QThread(0x7fe82140f060)
createDB QThread(0x7fe82155c840)
151 QThread(0x7fe82155c840)
Try executing the whole task in a run, e.g.
QtConcurrent::run([](){
SqlFetcher m_Fetcher;
m_Fetcher.createDb();
m_Fetcher.qint64TotalSize();
});
Since we're dealing with concurrency, it's safer to use named connections (otherwise the same default connection will be used every time, possibly by more than one thread). You can manage this by adding an argument to SqlFetcher::createDb:
void SqlFetcher::createDb(const QString & connectionName)
{
m_sqldb = QSqlDatabase::addDatabase("QSQLITE", connectionName);
// etc.
and to the lambda and the run function, as well:
QtConcurrent::run([](const QString & cn){
SqlFetcher m_Fetcher;
m_Fetcher.createDb(cn);
m_Fetcher.qint64TotalSize();
}, QString("TheConnectionName"));
In the other function, assign the database to the query, in construction:
void SqlFetcher::qint64TotalSize()
{
qint64 l_result= 0;
QSqlQuery l_query(m_sqldb);
//etc.

QSqlDatabasePrivate::removeDatabase: connection 'myConnectionName' is still in use, all queries will cease to work

I have a folder where i have a many databases. Some times may be deleted or added database to the folder.
So I use QTimer and read all databases.
It is a my code:
this->timer = new QTimer(this);
this->timer->setInterval(15000);
connect(this->timer, &QTimer::timeout, this, [=]() {
QString path = "C:\\Users\\User\\Desktop\\DAXI SMS SENDER\\SMSSenderAllBASE";
//QString path = qApp->applicationDirPath() + "\\SMSSenderAllBASE";
QDir recoredDir(path);
QStringList allFiles = recoredDir.entryList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
for (int i = 0; i < allFiles.size(); i++) {
QString fullPath = path + "\\" + allFiles[i];
QString connectionName = allFiles[i];
connectionName = connectionName.remove(connectionName.size() - 4, 4);
QSqlDatabase db = QSqlDatabase::addDatabase("QIBASE", connectionName);
db.setDatabaseName(fullPath);
db.setHostName("localhost");
db.setPort(3050);
db.setUserName("SYSDBA");
db.setPassword("masterkey");
thrdHelperSendSMS *help = new thrdHelperSendSMS(db, this);
connect(help, &thrdHelperSendSMS::helperFinished, this, [=](QString connectionName){
QSqlDatabase t_db = QSqlDatabase::database(connectionName);
t_db.close();
QSqlDatabase::removeDatabase(connectionName);
delete help;
});
help->run();
}
});
this->timer->start();
Yes I'm sure that the helperFinished signal will happen and this time I will not have any connection with this base.
EDIT:
If i remove
thrdHelperSendSMS *help = new thrdHelperSendSMS(db, this);
connect(help, &thrdHelperSendSMS::helperFinished, this, [=](QString connectionName){
QSqlDatabase t_db = QSqlDatabase::database(connectionName);
t_db.close();
QSqlDatabase::removeDatabase(connectionName);
delete help;
});
help->run();
example:
for (int i = 0; i < allFiles.size(); i++) {
QString fullPath = path + "\\" + allFiles[i];
QString connectionName = allFiles[i];
connectionName = connectionName.remove(connectionName.size() - 4, 4);
QSqlDatabase db = QSqlDatabase::addDatabase("QIBASE", connectionName);
db.setDatabaseName(fullPath);
db.setHostName("localhost");
db.setPort(3050);
db.setUserName("SYSDBA");
db.setPassword("masterkey");
QSqlDatabase::removeDatabase(connectionName);
}
I have the same error.
You don't use removeDatabase() correctly. The object of SqlDatabase needs to go out of scope first. See documentation.
Wrong use
QSqlDatabase db = QSqlDatabase::database("sales");
QSqlQuery query("SELECT NAME, DOB FROM EMPLOYEES", db);
QSqlDatabase::removeDatabase("sales"); // will output a warning
Correct use
{
QSqlDatabase db = QSqlDatabase::database("sales");
QSqlQuery query("SELECT NAME, DOB FROM EMPLOYEES", db);
}
// Both "db" and "query" are destroyed because they are out of scope
QSqlDatabase::removeDatabase("sales"); // correct
In the second example db will go out of scope after } and you will no longer see the error message QSqlDatabasePrivate::removeDatabase: connection 'myConnectionName' is still in use, all queries will cease to work
Please read the documentation carefully. The database stuff is sensible and every line needs to be checked carefully.
Also you have missing db.close(); - It makes sense to close the database before you remove it.
#user3606329's answer is right, but I add this possibility:
QSqlDatabase db = QSqlDatabase::database("sales");
{
QSqlQuery query("SELECT NAME, DOB FROM EMPLOYEES", db);
//use query
}
db = QSqlDatabase();
QSqlDatabase::removeDatabase("sales");
You can use std::swap to force it destruct on demand.
I basically use BOOST_SCOPE_EXIT for cases when want to call a function on scope exit including not expected exit like through an exception throw.
#include <boost/scope_exit.hpp>
{
QSqlDatabase db;
BOOST_SCOPE_EXIT(this_, &db) { // by reference, otherwise it will copy a stack object
// access object here through the this_ instead of this ...
if (db.isOpen()) {
db.close(); // closing if not yet closed before connection remove
}
std::swap(db, QSqlDatabase{}); // destruct via swap
// CAUTION:
// From this point you must not call to `QSqlDatabase::database("MYDB", ...)`, otherwise it will return another constructed object!
//
QSqlDatabase::removeDatabase("MYDB");
} BOOST_SCOPE_EXIT_END
// ui change from here ...
// accomplish last ui changes before long blocking operation
qApp->processEvents();
db = QSqlDatabase::addDatabase("...", "MYDB");
// database access from here ...
}
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "conn_name");
db.open();
if (db.open())
{
qDebug()<<"DataBase is Open";
}
else
{
qDebug()<<"DataBase is Not Open";
}
QSqlQueryModel * model = new QSqlQueryModel();
QSqlQuery query(QSqlDatabase::database("conn_name"));
query.exec("SMTHING")
if (query.exec())
{
while (query.next())
{
ui->QTableView->setModel(model);
model->setHeaderData(2, Qt::Horizontal, QObject::tr("????"));
}
}
db.close();
QSqlDatabase::removeDatabase("conn_name");
Here is my code

Commit changes to SQLite database, that can be seen between QTabs

I have a simple application, where I can log in / out users. When user logs in, application shows appropriate tab on main window (employee/admin/customer). I have a QMainWindow with QTabWidget on it. In my QMainWindow I create a database (I implemented a special class for this):
class DataBase
{
public:
DataBase();
void initDatabase();
void closeDatabase();
private:
QSqlDatabase db;
};
DataBase::DataBase()
{
}
void DataBase::initDatabase()
{
QString filename = "database.sql";
QFile file(filename);
db = QSqlDatabase::addDatabase("QSQLITE");
db.setHostName("localhost");
db.setDatabaseName(filename);
// create users table
if(this->db.open())
{
QSqlQuery usersTableQuery;
QString usersTableQueryStr = "CREATE TABLE IF NOT EXISTS USERS (ID INTEGER PRIMARY KEY NOT NULL, "
"LOGIN TEXT,"
"PASSWORD TEXT,"
"FIRSTNAME TEXT,"
"LASTNAME TEXT,"
"EMAIL TEXT,"
"ACCOUNT_TYPE INTEGER"
");";
if(usersTableQuery.exec(usersTableQueryStr))
{
qDebug() << "Create USERS table OK";
}
else
{
qDebug() << usersTableQuery.lastError().text();
}
}
else
{
qDebug() << "DB is not opened!\n";
}
// create service table
if(this->db.open())
{
QSqlQuery serviceTableQuery;
QString serviceTableQueryStr = "CREATE TABLE IF NOT EXISTS SERVICE (ID INTEGER PRIMARY KEY NOT NULL, "
"NAME TEXT,"
"PRICE REAL"
");";
if(serviceTableQuery.exec(serviceTableQueryStr))
{
qDebug() << "Create SERVICE table OK";
}
else
{
qDebug() << serviceTableQuery.lastError().text();
}
}
else
{
qDebug() << "DB is not opened!\n";
}
}
void DataBase::closeDatabase()
{
db.close();
}
My tabs for employee, admin, client look like this one:
class AdminTab : public QWidget
{
Q_OBJECT
public:
explicit AdminTab(DataBase *db, QWidget *parent = 0);
//...
Everyone (employee,client,admin) can make changes in database (for instance, admin can insert services, users can check available services, etc). However, when admin adds a service (I make an insert operation on an open database), and logs out, when the client logs in, it can't see the changes made by the admin. When I start application again, and client logs in, it can see new added service.
Adding service looks like this:
bool DataBase::insertService(QString name, double price)
{
if(!db.isOpen())
{
qDebug() << query.lastError();
return false;
}
else
{
QSqlQuery query;
query.prepare("INSERT INTO SERVICE (NAME, PRICE) "
"VALUES (:NAME, :PRICE)");
query.bindValue(":NAME", name);
query.bindValue(":PRICE", price);
if(query.exec())
{
return true;
}
else
{
qDebug() << query.lastError();
}
}
return false;
}
I guess it's the problem that the database is all the time opened, but how can I make the changes to be available just after I insert/remove something in database? I open the database when I create QMainWindow and close it in its destructor.
I thought about opening/closing the database every time I need to use it, but I can't say if it's a good solution.
Even adding :
if(query.exec())
{
query.clear();
db.commit();
return true;
}
Does not help.
Client has: QVector<Service*> availableServices; and QComboBox *servicesComboBox;, checking for all the available services, when client logs in :
void ClientTab::updateAllServices()
{
availableServices.clear();
availableServices = db->selectAllServices();
servicesComboBox->clear();
for(int i=0; i<availableServices.size(); i++)
servicesComboBox->addItem(availableServices[i]->getServiceName(), QVariant::fromValue(availableServices[i]));
servicesComboBox->setCurrentIndex(-1);
}
Service class:
#ifndef SERVICE_H
#define SERVICE_H
#include <QString>
#include <QMetaType>
#include <QVariant>
class Service : public QObject
{
Q_OBJECT
public:
Service(int id, QString name, double price);
Service(){ id = -1; name = ""; price = 0;}
QString getServiceName() const;
void setServiceName(const QString &value);
double getServicePrice() const;
void setServicePrice(double value);
int getId() const;
void setId(int value);
private:
QString name;
double price;
int id;
};
Q_DECLARE_METATYPE(Service*)
#endif // SERVICE_H
And finally, selecting all services from the database (I use this method to populate combobox on ClientTab):
QVector<Service*> DataBase::selectAllServices()
{
QVector<Service*> services;
if(!db.isOpen())
{
return services;
}
else
{
QSqlQuery query;
if(query.exec("SELECT * FROM SERVICE;"))
{
while( query.next() )
{
int id = query.value(0).toInt();
QString name = query.value(1).toString();
double price = query.value(2).toDouble();
Service *s = new Service(id, name, price);
services.push_back(s);
}
}
else
{
qDebug() << "DataBase::selectAllServices " << query.lastError();
}
}
return services;
}
Could you doublecheck that
void ClientTab::updateAllServices()
is called every time as the client logs in (not only at application start)?
SQLite database has autocommit on by default, therefore you don't need to commit anything or use any transaction for this simple thing.
Can you see the new service added in sqlite command line with a select * from service? If so, then adding a service works well, and you need to check when do you call updateAllServices().
If I understand your problem correctly, it's about how to synchronize multiple views of a database. This is indeed a difficult task.
If only one of the views is visible at any given time (like it seems in your case to be with the different tabs), just reload the data from the database and repopulate the tabs. How would you do this? Add a signal contentChanged() to your main window and let all views reload the data when they see it. (Or clear their data and reload when the user switches to the specific tab.)
In case this is too slow or you can see the same content in multiple views at the same time: You should use the models provided by Qt, e.g. QListView + QAbstractListModel instead of QListWidget. If you use those, synchronization is for free, but you should not access your SQL database directly anymore and only change it through the model.

QSqlDatabase can't be removed - Queries still active

I started with c++ QT recently.
I created class "ControllerOfDB" to hold pointer to my QSqlDatabase and few functions (to make inserts/selects).
Example select function:
QList<data1> GetData1()
{
QList<data1> output;
if(!dataBase->isOpen())
dataBase->open();
if(dataBase->isOpen())
{
QSqlQuery* query = new QSqlQuery(*dataBase);
query->prepare("SELECT * FROM table1");
if(query->exec())
while (query->next())
{
output.append( *(new data1(
query->value(0).toInt(),
query->value(1).toString(),
query->value(2).toInt(),
query->value(3).toInt(),
query->value(4).toInt(),
)) );
}
query->clear();
query->finish();
delete query;
}
return output;
}
It was all working fine, until i had to add possibility to change db or login as different user. I modified Connect and Disconnect function. After few iterrations, this is what i get:
void Connect()
{
dataBase = new QSqlDatabase(QSqlDatabase::addDatabase("QPSQL", "Main"));
dataBase->setHostName(hostName);
dataBase->setPort(port);
dataBase->setDatabaseName(dbName);
dataBase->setUserName(userName);
dataBase->setPassword(userPass);
if(!dataBase->isOpen())
dataBase->open();
}
void Disconnect()
{
if(dataBase != NULL)
if(dataBase->isOpen()){
dataBase->close();
dataBase->removeDatabase("Main");
dataBase = NULL;
}
}
hostName, port, dbName, userName and userPass are also attributes of that class.
public:
QSqlDatabase *dataBase;
QString hostName;
int port;
QString dbName;
QString userName;
QString userPass;
whenever i try to diconnect and create new connection i recieve warnings/errors about queries of my current connections - even if i only "connected" ( = used function Connect() ).
I already saw few similar topics and documentation for it, saying i have to remove queries from scope, but at this point i don't know how.
when you look at QSqlDatabase document, the method removeDatabase is static, so your method Disconnect logic should be like:
void Disconnect()
{
if(dataBase != NULL) {
if(dataBase->isOpen()){
dataBase->close();
}
delete dataBase;
dataBase = NULL;
}
QSqlDatabase::removeDatabase("Main");
}