QSqlQuery Memory issues. QSqlQuery::exec() and QSqlDatabase::open()/close(); - c++

I'm checking the memory usage of an application I've made. It makes numerous calls to read and write values to and from a database (SQLite 3). I've observed the following:
QSqlQuery::exec() uses some KB of RAM to execute a given query, but does not release the memory after it goes out of scope.
QSqlDatabase:: open() & close() do not help free resources as the documentation suggest. If anything, close() causes resources (at least memory) to remain 'trapped' on the heap/stack.
For example, here is a typical segment of code I've been using to access my database.
QStringList values;
db.open();
QString strQuery = "SELECT DISTINCT " + field + " FROM " + table + str;
QSqlQuery query(db);
query.prepare(strQuery);
if(query.exec() == true)
{
while(query.next())
{
values.push_back(query.value(0).toString());
}
}
db.close();
Having experimented with I find the code below 'traps' less memory:
QStringList values;
QString strQuery = "SELECT DISTINCT " + field + " FROM " + table + str;
QSqlQuery query(strQuery, db);
while(query.next())
{
values.push_back(query.value(0).toString());
}
However, a small amount of memory is still not released. Has anyone else experienced anything like this?
Can I some how release this memory?
P.s. Same happens here, some memory is never released:
db.open();
QSqlQuery query(db);
query.exec("DELETE FROM table1");
query.exec("DELETE FROM table2");
query.exec("DELETE FROM table3");
query.exec("DELETE FROM table4");
...
db.close();

It seems that in order to release this memory you must create the QSqlQuery variable as a pointer, and delete this pointer before you close the database:
QStringList values;
db.open();
QString strQuery = "SELECT DISTINCT " + field + " FROM " + table + str;
QSqlQuery *query = new QSqlQuery(db);
query->prepare(strQuery);
if(query->exec() == true)
{
while(query->next())
{
values.push_back(query->value(0).toString());
}
}
delete query;
db.close();
The memory is then released after the database closes.

You have to use QSqlQuery.finish () or QSqlQuery.clear before you close the database. Otherwise residual memory is left out in the Query object. It is mentioned in the document that Query object can be used for multiple query. You will noticed the "memory leak".. when you query for 10,000 records. The memory usage goes up drastically.

From the documentation of QSqlDatabase::addDatabase and QSqlDatabase::database() one can deduce that there is a global variable that manages the database connections. If you look into qsqldatabase.cpp you will find a QConnectionDict.
BTW: Do not construct your SQL queries by concatenating strings, always use prepare and bindValue (SQL injecttion!), if there is any chance that parts of the query come from user input.

Related

QSqlQuery 'Parameter Count Mismatch'

I am trying to take data from six fields on a form and pass the data the user enters into a database (A local SQLite3 database). The ID is an integer and the primary key and the rest are varchars of enough length. After reading a lot of related questions, I still cannot figure out where my code is going wrong. I have confirmation that the program is connected to the database, but I keep getting 'parameter count mismatch'. Where am I going wrong? I'm using Qt 5.15 and C++. The table TOOL exists, as I have it open in SQLite Manager with a test row already inserted.
Below is the function that is supposed to submit the data:
//passes data to database upon clicking submit button
void Add_item::on_submitButton_clicked() {
QString name, location, safety, summary, uses, idNumber;
idNumber = ui->testIdBox->text();
name = ui->nameField->text();
location = ui->locationField->text();
safety = ui->safetyField->text();
summary = ui->summaryField->text();
uses = ui->useField->text();
QSqlQuery qry;
qry.prepare("INSERT INTO TOOLS (TOOL_ID, TOOL_NAME, TOOL_SUMMARY, TOOL_LOCATION, TOOL_USE, TOOL_SAFETY) "
"VALUES (:idNumber, :name, :summary, :location, :uses, :safety)");
//binding all values to prevent sql injection attacks
qry.bindValue(":idNumber", idNumber);
qry.bindValue(":name", name);
qry.bindValue(":summary", summary);
qry.bindValue(":location", location);
qry.bindValue(":uses", uses);
qry.bindValue(":safety", safety);
if(qry.exec()){
QMessageBox::critical(this,tr("Confirmation Message"),tr("Success!"));
}
else {
QMessageBox::critical(this,tr("Confirmation Message"),tr("Error, data was not saved."), qry.lastError().text());
}
connClose();
}
Here is where I connect to the database:
//connecting to database
bool Add_item::connOpen() {
QSqlDatabase mydb = QSqlDatabase::addDatabase("QSQLITE");
mydb.setDatabaseName("C:/Users/laesc/OneDrive/Documents/ToolBuddy/test.db");
if (mydb.open()) {
ui->statusLabel->setText("Connected!");
qDebug()<<("Connected");
return true;
}
else {
ui->statusLabel->setText("Connection Not Successful...");
qDebug()<<("Not Connected");
return false;
}
}
For future viewers, I ended up reducing the number of parameters to 2 and the query worked. For one reason or another the query didn't work if I tried to bind more than 2 variables. I'm just doing multiple queries.

QSqlQuery with back slash

I'm trying to retrieve the users from the database their names are 'DOMAIN\name'.
I've checked the query in sql console, simple select like :
select * from users where name='DOMAIN\\name'
it returns correct row if the name in the database looks like 'DOMAIN\user' (single back slash).
however QSqlQuery returns empty :
The code something like:
const QString command = QStringLiteral("select * "
"from %1 where name = '%2'")
.arg(Constants::kUsersTableName).arg(userId);
qCDebug() << "Query:" << command;
QSqlDatabase db = QSqlDatabase::database(m_connection, false);
QSqlQuery query(db);
if (!query.prepare(command) || !query.exec()) {
...
log :
Query: "select * from users where name = 'DOMAIN\\name'"
any idea why QSqlQuery returns empty at the same time when database console return valid record for the user.
dbms: MySQL
MySQL uses \ as its escape character therefore the correct query string you have to escape the slash twice, once for c++ and once for mysql:
"select * from users where name='DOMAIN\\\\name'"
The simpler solution is to use prepared statements and placeholders correctly which as a very important bonus will protect your code from SQLI.
Note that field and table names aren't allowed to be placeholders by most if not all database engines so you will still need to build that part of your string by hand:
const QString usernamePlaceholder = ":username"
const QString command = QStringLiteral("select * "
"from %1 where name = %2")
.arg(Constants::kUsersTableName).arg(usernamePlaceholder);
QSqlDatabase db = QSqlDatabase::database(m_connection, false);
QSqlQuery query(db);
if (!query.prepare(command)) {
qCDebug() << query.lastError();
return;
}
query.bindValue(usernamePlaceholder, userId);
if (!query.exec()) {
qCDebug() << query.lastError();
return;
}
bindValue will take care of whatever quoting and escaping is required for your values.

QT - empty QLineEdits not being passed as NULL when writing to SQL database

I'm relatively new to QT and C++ and am trying to make a basic front-end for a database.
I've hit a snag where QlineEdits that are left empty or blank write empty data into the database. By that I mean if I select * where 'column'is NULL, I get no results. But If I select * where 'column' = '' then I will get all empty rows.
Is there any way to change this behavior? Or have them written in as NULL? I'm worried it will cause havoc too for fields where I want the database to store QlineEdit data as integers but it will attempt to write in white space.
I could pre-set QlineEdits to 0 i.e.
ui->txt_NAF->setText();
but this will not eliminate human error.
Anyone have a solution for this problem?
Snippet of QT code used to insert into database (SQL server) below;
void log::on_pushButton_clicked() /* this is my save button */
{
MainWindow conn;
QString BANK_MNTH, BUSN_MNGR, CUST_FRST_NAME, CUST_LAST_NAME, DEAL_NUMB ,COST;
BANK_MNTH=ui->txt_BANK_MNTH->text();
BUSN_MNGR=ui->txt_BUSN_MNGR->text();
CUST_FRST_NAME=ui->txt_CUST_FRST_NAME->text();
CUST_LAST_NAME=ui->txt_CUST_LAST_NAME->text();
DEAL_NUMB=ui->txt_DEAL_NUMB->text();
COST=ui->txt_NAF->text();
if(!conn.connOpen()){
qDebug()<<"Failed to open database";
return;
}
conn.connOpen();
QSqlQuery qry;
qry.prepare("insert into LOG (BANK_MNTH, BUSN_MNGR, CUST_FRST_NAME, CUST_LAST_NAME, DEAL_NUMB ,COST) values ('"+BANK_MNTH +"','"+BUSN_MNGR+"','"+CUST_FRST_NAME+"','"+CUST_LAST_NAME+"','"+DEAL_NUMB+"','"+COST+"')");
if(qry.exec( ))
{
QMessageBox::critical(this,tr("Save"),tr("Record Saved"));
QSqlQueryModel * modal=new QSqlQueryModel();
QSqlQuery* qry2=new QSqlQuery(conn.mydb);
qry2->prepare("select DEAL_NUMB, BUSN_MNGR, CUST_FRST_NAME, CUST_LAST_NAME, COST from LOG order by DEAL_NUMB DESC");
qry2->exec();
modal->setQuery(*qry2);
ui->tableView->setModel(modal);
ui->tableView->resizeColumnsToContents();
ui->tableView->setAlternatingRowColors(true);
conn.connClose();
foreach(QLineEdit *log, this->findChildren<QLineEdit*>()) {
log->clear();
}
}
else
{
QMessageBox::critical(this,tr("Error"),qry.lastError().text());
}
}
Yes, you can write them as NULL....
Use place holders for inserting values and pass a null QVariant using below function to add NULL.
void QSqlQuery::bindValue(const QString &placeholder, const QVariant &val, QSql::ParamType paramType = QSql::In)
Your code may look like this ... (Did it for few variables for your idea...)
Write a prepare statement for insert using place holders.
query.prepare("INSERT INTO LOG (BANK_MNTH, BUSN_MNGR, CUST_FRST_NAME) "
"VALUES (:BankMonth, :BusManager, :CustFirstName)");
Now you have BankMonth,BusManager and CustFirstName as place holders of your insert statement.
Now bind the values to place holders as shown below.
Use ternary operator or what ever is convenient to you.
Check if the text is empty, if so pass QVariant(QVariant::String) for NULL else pass the text.
//Some rough code (Not compiled and tested)
BANK_MNTH=ui->txt_BANK_MNTH->text();
BUSN_MNGR=ui->txt_BUSN_MNGR->text();
CUST_FRST_NAME=ui->txt_CUST_FRST_NAME->text();
query.bindValue(":BankMonth", BANK_MNTH.trimmed().isEmpty() ? QVariant(QVariant::String): BANK_MNTH);
query.bindValue(":BusManager", BUSN_MNGR.trimmed().isEmpty() ? QVariant(QVariant::String): BUSN_MNGR);
query.bindValue(":CustFirstName", CUST_FRST_NAME.trimmed().isEmpty() ? QVariant(QVariant::String): CUST_FRST_NAME);
Refer bindValue documentation.
http://doc.qt.io/qt-5/qsqlquery.html#bindValue

Qt: Save result of SQL-query in variable, use C++ variable in SQL-query

My project is to program a simple ShopApp. One function is about checking, whether there is enough stock left, so that a customer can buy the desired amount of whatever he wants to buy. The functions looks like (where cart is a std::vector<product> and Pid stands for product id):
bool sqlfunctions::checkStock(){
QSqlQuery query;
int diff, stock;
for(iter cursor = cart.begin();cursor!=cart.end();cursor++){
query.prepare("SELECT stock FROM products WHERE id = cursor->getPid()");
query.exec();
// Need to save result of query into variable stock
stock = ??;
diff = stock - cursor->getAmount;
if(diff < 0){
return false;
}
}
return true;
}
Obviously this function is not functional, because cursor->getPid() is not executed since it is a string. So the question here is how to insert a c++-variable into the sql-query?In regular C++ i would used some swprintf_s function. So is query.prepare(swprintf_s(...)) a good idea?
The second thing is, since both query.exec() and query.prepare() are booleans, which return true or false, depeding on success, how can i store
results of the queries in a c++ variable?
Please note, that I am new to SQL and SQL with Qt. I use QT5.
I already tried to read the documentation about theQSqlQuery class and its functions, BindValue() and addBindValue() seem to be interesting. However i dont really understand how they work.
Edit
So now I've got a minimal example here that does not work yet, despite following the accepted answer. However the compiler does not give me any warnings or errors:
void MainWindow::on_ButtonSQL_clicked()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("...");
db.setDatabaseName("...");
db.setUserName("...");
db.setPassword("...");
db.setPort(3306);
db.open();
QMessageBox msgBox;
if (db.open()) {
msgBox.setText("It works :)");
msgBox.exec();
}
else {
msgBox.setText("No connection.");
msgBox.exec();
}
QSqlQuery query(db);
// This query worked!
query.exec("INSERT INTO users (id, username, balance) VALUES(25, 'someName', 10000)");
// initialize someNumber to check later, whether it was processed correctly.
int id = 2, someNumber = 20;
query.prepare("SELECT stock FROM products WHERE id = :myid");
query.bindValue(":myid", id);
query.exec();
QString idValue = query.boundValue(0).toString();
someNumber = query.value(0).toInt();
msgBox.setText("The stock is: "+QString::number(someNumber)+"\nThe placeholder has the value: "+idValue);
msgBox.exec();
}
Expected msgBox of the last msgBox is:
The stock is: 100
The placeholder value is: 2
Output actually is:
The stock is: 0
The placeholder value is: 2
If I instead try to select a string (e.g. productName), say with QString myProductName = query.value(0).toString() (and respective changes in the code), the return would be an empty string.
** SOLVED: ** See comment from Floris in the accepted answer. I missed query.next().
It is pretty straight forward actually:
QSqlQuery query;
query.prepare("Select stock from products where id = :input");
query.bindValue(":input", cursor->getPid());
query.exec();
You bind the values to the argument in the string. Arguments follow the format: :name. There is also positional binding which binds in the order it sees ?.
QSqlQuery query;
query.prepare("Select stock from products where id = ?");
// No need for an identifier
query.bindValue(cursor->getPid());
query.exec();
To iterate the records you obtained from a query you can do the following:
QSqlQuery query;
query.prepare("SELECT stock FROM employee WHERE id = ?");
query.bindValue(cursor->getPid());
query.exec();
if (query.next()) {
int stock = query.value(0).toInt();
// You could store the information you obtain here in a vector or something
}
You could also put the prepare statement outside the for loop. If you are interested in iterating multiple records (from a select statement) you can replace the if statement with a while statement.
Concerning QSqlQuery::next():
Retrieves the next record in the result, if available, and positions the query on the retrieved record. Note that the result must be in the active state and isSelect() must return true before calling this function or it will do nothing and return false.
As taken from QSqlQuery. You will need to make this call before the record will actually be accessible with .value(int).

QSqlQuery forbid non-SELECT query

I'm looking for a way to forbid non-SELECT query for a QSqlQuery object.
I know QSqlQuery::isSelect is only effective after QSqlQuery::exec (damage
already done here).
So, is there any way to do that or I have to verify the query string explicitly.
Thanks.
From my experience, the QSqlQuery::isSelect function works well even before running the QSqlQuery::exec command. But only when the object is initialized with the query. i.e:
QSqlQuery query("SELECT * FROM someTable");
qDebug() << query.isSelect(); //Outputs True
OR
QString queryString = "SELECT * FROM someTable";
QSqlQuery query(queryString);
qDebug() << query.isSelect(); //Outputs True
If you are using the prepare function and passing the query string to it, then you will have to verify the query string yourself. i.e;
QSqlQuery query;
query.prepare("SELECT * FROM someTable");
qDebug() << query.isSelect(); //Outputs False
If you really want to avoid manual verification of the query string, you will have to avoid using QSqlQuery::prepare and QSqlQuery::bindValue functions and instead create the query yourself and initialize the QSqlQuery object with the QString you create.