QSqlQuery insert full struct instead of individual fields? - c++

Following QSqlQuery Class documentation one can implemented an insert operation in a MySQL database, i.e. like this:
QSqlQuery query;
query.prepare("INSERT INTO person (id, forename, surname) "
"VALUES (:id, :forename, :surname)");
query.bindValue(":id", 1001);
query.bindValue(":forename", "Bart");
query.bindValue(":surname", "Simpson");
query.exec();
It works. But it's fragile, isn't? It will stop working (because names are hard coded) when a column name mismatch occurs, if the database programmer changes a column name in the MySQL script, i.e. surname to lastname.
What came to my mind was to change the whole mechanism so that instead of inserting individual fields one big object (perhaps a struct composed by the individual fields) should be inserted. And then, in the db side, the fields should be split back into a set of fields, i.e. using a view or a trigger.
Am I going in the right direction?
I'll appreciate general comments on the issue.

Well, there is a way, but you must be sure that structure of the table and column meaning wouldn't change and your query has data of each column to insert.
Firstly, you must select all information about your table:
SELECT c.column_name FROM information_schema.columns c
WHERE c.table_name = 'some_table_name'
ORDER BY c.ordinal_position ASC
Secondly, your code can be change like this. I am using universal method that meaning you have already known data:
QList<QVariant> valuesList { QVariant(1), QVariant("Bart"), QVariant("Simpson") };
QList<QPair<QString, QVariant>> varList;
QSqlQuery query;
query.exec("SELECT #rownum := #rownum + 1 AS row_num, c.column_name
FROM information_schema.columns c, (SELECT #rownum := -1) r
WHERE c.table_name = 'some_table_name'
ORDER BY c.ordinal_position ASC");
while(query.next())
colList << QPair<QString, QString>(query.value(1).toString(),
valuesList.at(query.value(0).toInt()));
QString queryString = "INSERT INTO person (%1) "
"VALUES (%2)";
QString insertColsString;
QString bindColsString;
for(int i = 0; i < colList.size(); i++) {
insertColsString += colList.at(i).first + ", ";
bindColsString += ":" colList.at(i).first + ", ";
}
if(!insertColsString.isEmpty()) {
insertColsString.chop(2);
bindColsString.chop(2);
}
query.prepare(queryString.arg(insertColsString, bindColsString));
for(int i = 0; i < colList.size(); i++)
query.bindValue(":" + colList.at(i).first, colList.at(i).second);
query.exec();

Related

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.

QtSql Dynamic Select query with multiple WHERE filters

I need to execute any select query using the same method.
So, the query my has to filter the selected data using one value or
more.
Filters are stored in a map that has the column name as the key and
the filtering value as its value.
So my Question is : how to add filters dynamically into Select statement?
What I tried:
1. QSqlQueryModel Class:
I could create a QSqlQueryModel object and set a query to
retrieve the whole table data but, I
could not find any functionality in this class to filter this data :
QSqlQueryModel *model = new QSqlQueryModel;
model->setQuery("SELECT * FROM employee");
2. QSqlTableModel: This class is used to view table data in a qtableView,I can use this class to read table data and then filter this data like this (I have not tried this yet):
QSqlTableModel *model = new QSqlTableModel
model->setTable("employee");
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select();
model->setFilter("colum5 > 10");
// I can use after that data() method to retrieve filtered data.
3. For Loop I thought about using for loop to add filters directly but, I would prefer a better way because I believe that QT offers a such service.
The method shall looks like this:
/**
* #brief getData executes sql select query.
* #param query [out] QSqlQuery query object after executing the query.
* #param queryFilters [in] map of query filters (column-name, filter-
value).
* #param table [in] table name.
* #return
*/
bool getData(QSqlQuery &query, std::map<std::string,QVariant> &queryFilters,
std::string &table){
bool status = false;
std::string queryText = "SELECT * from " + table + " WHERE ";
// I should apply filters from queryFilters map here.
return status;
}
There are several ways you could do this.
Using a for-loop with std::map.
Use a for loop to iterate through your key-pair values.
bool getData(QSqlQuery &query, const std::map<std::string,QVariant> &queryFilters,
std::string &table)
{
// ...
std::string queryText = "SELECT * from " + table + " WHERE";
for (auto it = queryFilters.begin(); it != queryFilters.end(); )
{
queryText += " " + it->first + "='" + it->second.toString().toStdString() + "'";
it++;
// check the iterator isn't the last iterator
if (it != queryFilters.end())
queryText += " AND"; // separate your "filters" using an AND
}
// ...
}
Using a for-loop with QMap.
But heck this is Qt so why not take advantage of the QMap, QStringList, and QString QTL types.
bool getData(QSqlQuery &query, const QMap<QString, QVariant> &queryFilters,
const QString &table)
{
// ...
QString queryText = "SELECT * from " + table + " WHERE ";
QStringList filters;
foreach (const QString &filterKey, queryFilters.keys())
filters << filterKey + "='" + queryFilters.value(filterKey).toString() + "'";
queryText += filters.join(" AND ");
// ...
}
Note that foreach is a Qt-defined macro. See the foreach keyword.
For other QTL types you might want to be aware of, see containers.
QSqlQueryModel???
I can't tell from your question and comments whether you actually have an sql table model/view/widget in the background or whether you're using something else entirely.
I thought about using loop for this matter.But, I thought that there is a better way using some qt classes like : QSqlQueryModel
For sure, just browsing through the documentation, QSqlQueryModel doesn't have a filter feature.
But... QSqlTableModel does have this feature. The plus side is, that if you already have a QSqlQueryModel sitting somewhere, you could upgrade it to a QSqlTableModel since the latter inherits the former. But again, I don't have enough information to make judgements so I'm just leading you around in the dark here.
Hopefully, this answer sheds some light on your predicament along with the reminder of how you could ask a better question to obtain more accurate responses.

QSqlDatabase::record(const QString &tablename) returns empty record

I have already read this question. I want to get field names of a table using method QSqlDatabase::record(const QString &tablename). But it always returns an empty record. I can query database tables by QSQLQuery properly. My database is a SQL Server database.
Actually you should get.
As you are saying SQL server, try with "#yourtablename". I mean to say prepend "#" before your tablename in your QString.
if no luck,
Check your table name. (spaces or additional chars etc..)
Type cast to QString (safe side). Or create a QString object with table name and pass it.
Trim the QString, before passing it.
Still for some reason if you are not getting the field names, try below steps.
Your QSqlRecord QSqlDatabase::record(const QString &tablename) const will return a QSqlRecord object
First get the number fields in record
int QSqlRecord::count() const
Loop the count (ex: using for) and get the field name for each index using below function
QString QSqlRecord::fieldName(int index) const
Some pseudo code below: (assuming you have successful connection established, Not compiled and not tested.)
QSqlRecord rec = QSqlDatabase::record("Your table name");
int count = rec.count();
QStringList fieldNames;
For (int i =0; i<count; i++)
{
fieldNames.push_back(rec.fieldName(i);
}
By Select Query:
QSqlQuery select;
select.prepare("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'YourTableName'");
if (select.exec() && select.next()) {
QSqlRecord record = select.record();
}

QSqlQuery.record() is always empty

I'm editing the database in this manner:
QSqlQuery query;
query.prepare("UPDATE student "
"SET name = ? "
"WHERE id = ?");
QString name = "t";
int id = 3;
query.addBindValue(name);
query.addBindValue(id);
query.exec(); // query exec returns true
QSqlRecord record = query.record(); // but the record is empty!
mTableModel->beforeInsert(record);
The retrieved record is always empty, but the QSqlTableModel still changes! I need the record to be valid because I'm trying to synchronize an sql db with a std::vector.
I'm connecting to the database like this:
mDatabase = QSqlDatabase::addDatabase("QSQLITE");
mDatabase.setDatabaseName("database.db");
mDatabase.open();
I tried calling QSqlQuery::clear(), QSqlQuery::finish() but it didn't help. I also tried to open and close the DB, but it also didn't help. What can I do? :\
Qt is not a pain indeed.
All your code is good. The only wrong assumption is that an update request will automatically give you back the updated record. You have to make a new select request on this id to get the updates data in a QSqlRecord.
//[untested]
QSqlQuery select;
select.prepare("SELECT * from student where id = ?");
select.addBindValue(id);
if (select.exec() && select.next()) {
QSqlRecord record = select.record();
}

Opening a different window if a certain value in the database if found

I'm having troubles figuring out how to check if the information in my database has a certain value to change log in screens. I wanted to open a different window when the user logs in as a teacher or a student. I did assign each person a student or teacher value in database as well. The code for else student works fine by the way.
edit: if statement is where I'm suppose to put the code.
if(count == 1)
{
ui->statusLabel->setText("Username password correct. Username: "
+ uname);
//dbConnection->connClose();
if(query.value(""))
{
}
else
{
// hide login window
this->hide();
// open admin window
StudentDialog myStudent;
myStudent.setModal(true);
myStudent.exec();
}
}
http://qt-project.org/doc/qt-4.8/sql-sqlstatements.html
It shows this example:
while (query.next()) {
QString name = query.value(0).toString();
int salary = query.value(1).toInt();
qDebug() << name << salary;
}
And this one:
QSqlDatabase::database().transaction();
QSqlQuery query;
query.exec("SELECT id FROM employee WHERE name = 'Torild Halvorsen'");
if (query.next()) {
int employeeId = query.value(0).toInt();
query.exec("INSERT INTO project (id, name, ownerid) "
"VALUES (201, 'Manhattan Project', "
+ QString::number(employeeId) + ')');
}
QSqlDatabase::database().commit();
Basically the query stores the results in a 2d array. To navigate across the columns, use the value index. To cast it, use the QVariant::toXXX() options, such as toInt() or toString(), etc. To get the next row of values that comes out of the executed query, call query.next().
http://qt-project.org/doc/qt-4.8/qsqlquery.html#value
http://qt-project.org/doc/qt-4.8/qsqlquery.html#next
http://qt-project.org/doc/qt-4.8/qvariant.html#details
So in your case, you will probably just call:
bool is_teacher = query.value(0).toBool();
if(is_teacher)
{
// ...
}
// etc
If you are still having trouble with some of your statements going through, try:
qDebug() << qPrintable(query.lastError().text());
Hope that helps.