Columns number. QMYSQLResult::data: column out of range - c++

Have database. Have query with unknown count of columns. Need to put all answer in QList
database = QSqlDatabase::addDatabase("QMYSQL");
...
QString sql = "Select * from test";
QSqlQuery query = QSqlQuery(database);
query.exec(sql);
QList<QStringList> retList;
Use .isValid() on value.
while (query.next()) {
int count = 0;
bool flagValues = true;
QStringList row;
while(flagValues)
{
QVariant value = query.value(count);
if(value.isValid() && !(count == memCount) )
{
count++;
row.append(value.toString());
}
else
{
flagValues = false;
}
}
retList.append(row);
All is ok, but i have a messages (not error) like in every row. :
QMYSQLResult::data: column 3 out of range
I do not want to use additional query (like information_schema) to know columns number.

Use query.record().count() to obtain the number of columns. Thus:
database = QSqlDatabase::addDatabase("QMYSQL");
...
QString sql = "Select * from test";
QSqlQuery query = QSqlQuery(database);
query.exec(sql);
const int memCount = query.record().count();
// query loop goes here

Related

QSqlQuery, wrong number of parameters for prepared statement error

I am relative new to Qt/QSqlQuery, and in my current project I need to incorporate pgcrypto module, or pgp_sym_encrypt/decrypt function to be exact, into the regular SELECT/INSERT queries.
Here's the code that has been giving me problems:
bool BaseDAO::insertIntoDb(const DataObject &data)
{
QVariantMap dataMap = data.toQVariantMap();
QString rows = getFieldsStringForSql(dataMap);
QString placeholders = getPlaceholderStringForSql(dataMap);
QSqlQuery query = DatabaseDriver::getInstance().prepareQuery(
"INSERT INTO " + getTableName() + " " + rows + " VALUES " + placeholders +
R"( RETURNING ")" + getPKString() + R"(")" );
bindValuesToQuery(query, dataMap);
query.setForwardOnly(true);
query.exec(); // <-- where error occurs
...
...
}
QString BaseDAO::getPlaceholderStringForSql(const QVariantMap& data) const
{
QString fields = "(";
int valueCount = data.count();
const QList<QString> keys = data.keys();
if(valueCount > 0)
fields += ":" + keys[0];
for(int i = 1; i < valueCount; ++i)
{
if(!QString::compare(getTableName(), "patient") && !QString::compare(keys[i], "name"))
fields += ", pgp_sym_encrypt(:name, '" + m_pw + "')"; // need to encrypt name
else
fields += ", :" + keys[i];
}
fields += ")";
return fields;
}
void BaseDAO::bindValuesToQuery(QSqlQuery& query, const QVariantMap& data) const
{
const QVariantList valueList = data.values();
const QList<QString> keys = data.keys();
int valueCount = valueList.count();
for (int i = 0; i < valueCount; i++)
{
const QVariant &val = valueList[i];
switch(val.type()) {
case QVariant::Date:
query.bindValue(":" + keys[i], val.toDate().toString("yyyy-MM-dd"));
break;
case QVariant::Time:
query.bindValue(":" + keys[i], val.toTime().toString("hh:mm:ss.zzz"));
break;
case QVariant::DateTime:
query.bindValue(":" + keys[i], val.toDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"));
break;
default:
query.bindValue(":" + keys[i], val);
break;
}
}
}
I printed out the INSERT query before it got executed with query.lastQuery() and it looks like this:
INSERT INTO patient ("birthDate", "isMarked", "isProtected", "isTemporary", "modificationDate", "modificationTime", "name", "patientID", "permissionGroup", "sex")
VALUES
(:birthDate, :isMarked, :isProtected, :isTemporary, :modificationDate, :modificationTime, pgp_sym_encrypt(:name, 'password'), :patientID, :permissionGroup, :sex)
RETURNING "idx"
and this is the error message I got:
Fatal Error: wrong number of parameters for prepared statement "qpsqlpstmt_4"
DETAIL: Expected 11 parameters but got 1.
QPSQL: Unable to create query, Query: <<***>>.
The error totally got me confused. There are 10 parameters, but it somehow expects 11 and only gets 1? Any help is greatly appreciated!
I would recommend to rewrite getPlaceholderStringForSql
void BaseDAO::bindValuesToQuery(QSqlQuery& query, const QVariantMap& data) const
{
for(const QString & key : data.keys())
{
const QVariant &val = data.value(key);
// According to Qt doc: Note that the placeholder mark (e.g :) must be included
// when specifying the placeholder name.
const QString placeholder = QString(":%1").arg(key);
switch (val.type()) {
case QVariant::Date:
query.bindValue(placeholder,val.toDate().toString( "yyyy-MM-dd"));
break;
case QVariant::Time:
query.bindValue(placeholder,val.toTime().toString("hh:mm:ss.zzz"));
break;
case QVariant::DateTime:
query.bindValue(placeholder,val.toDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"));
break;
default:
query.bindValue(placeholder,val);
break;
}
}
Use raw litteral string for pgp_sym_encrypt(:name, '" + m_pw + "')"
R"(pgp_sym_encrypt(:name, ')" + m_pw + R("'))"
The main problem of this code is that you suddenly add a database dependency in an API (QSqlDatabase and your base code using QSqlDatabase) which shall remain database independant. A much better solution would be to use c++ directly to encrypt ans decrypt the name. Or if you absolutely want to use SQL, you should probably create two stored procedure (encrypt_field and decrypt_field) encapsulating pgp_sym_encrypt and pgp_sym_decrypt. Then you should be able to make a query via a QSqlQuery calling this stored procedures. The result of encrypt_field can be add in the insert/update SQL query while the result of decrypt_field wil be used after a SELECT query.

Selecting multiple tables MySQL

I'm using MySQL connector C 6.0.2, I need to select Table1 and read some values there then switch to table2 and read the values there too etc, I have more than two tables I need to switch from table to table. How can I do this ?
example code:
connect=mysql_real_connect(connect,SERVER,USER,PASSWORD,DATABASE,0,NULL,0);
if(connect)
{
MYSQL_RES *res_set;
MYSQL_ROW row;
////table1
mysql_query(connect,"SELECT * FROM `Table1` WHERE `Column2`='1234'");
unsigned int i = 0;
res_set = mysql_store_result(connect);
unsigned int numrows = mysql_num_rows(res_set);
if(numrows==0)
{
return false;
}else
{
while ((row = mysql_fetch_row(res_set)) != NULL)
{
if(strcmp(row[2], "true")==NULL)
{/////////Here I need to read or get the values from Table2
///Select table two
}else
return false;
}
}
}
Update: I think I have solved it, it was pretty simple
connect=mysql_real_connect(connect,SERVER,USER,PASSWORD,DATABASE,0,NULL,0);
if(connect)
{
MYSQL_RES *res_set;
MYSQL_ROW row;
////table1
mysql_query(connect,"SELECT * FROM `Table1` WHERE `Column2`='1234'");
res_set = mysql_store_result(connect);
unsigned int numrows = mysql_num_rows(res_set);
if(numrows==0)
{
return false;
}else
{
while ((row = mysql_fetch_row(res_set)) != NULL)
{
if(strcmp(row[2], "true")==NULL)
{/////////Here I need to read or get the values from Table2
///Select table two
MYSQL_RES *res_set2;
////table2
mysql_query(connect,"SELECT * FROM `Table2` WHERE `Column2`='1234'");
res_set2 = mysql_store_result(connect);
unsigned int numrows2 = mysql_num_rows(res_set2);
if(numrows2==0)
{
//no result
}else
{
//do something
}
}else
return false;
}
}
}
You have the res_set variable that you got from your query; you'd get another variable from your second query. You don't switch between tables, you just get the right values from the right query results.
Also note that this sounds a lot like you should be doing this inside your SQL query, and not inside your code, but that entirely depends on what you want to do.
I suggest you play with your SQL statement to return the result sets from both tables.
Research the "SELECT JOIN" statement.
The rule of thumb is to have the data perform most of the database work, including searching multiple tables.

why is my transaction not rolling back? Qt Mysql odbc driver

I'm using Qt 4.8.3 and MySQL ODBC 3.51 Driver. When my transaction fails because of a duplicate unique Id in the second table the insert in the first table is not rolled back. Can anyone spot the error?
struct Property //see OMG's Property Service
{
std::string name;
boost::any value;
Property();
Property(const std::string &inName, const boost::any &inValue):name(inName),value(inValue){}
};
Property myFunction(QSqlDatabase &db, int amount)
{
assert(db.driver()->hasFeature(QSqlDriver::Transactions) == true);
db.transaction();
QSqlQuery query(db);
Property ret("MyPropertyTag",0);
try{
query.exec("LOCK TABLES table1 WRITE, table2 WRITE");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); }
for(int i=0;i<amount;i++)
{
query.exec("INSERT INTO table1 (someUniqueValue, newestId, Created) VALUES ('"+QString::number(i)+"', '1', NOW())");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); }
query.exec("SELECT id FROM table1 WHERE someUniqueValue = '"+QString::number(i)+"'");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); }
if(query.next() == false) { throw std::exception("no result for insert id"); }
auto Id1 = query.value(0).toString();
query.exec("INSERT INTO table2 (table1_id, Changed, Created) VALUES ('"+Id1+"', NOW(), NOW())");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); }
query.exec("SELECT Id, table1_id FROM table2 ORDER BY Id DESC LIMIT 1");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); } //lets say this throws
if(query.next() == false || query.value(1).toString() != Id1) { throw std::exception("no result for inserted id"); }
auto Id2 = query.value(0).toString();
query.exec("UPDATE table1 SET newestId = '"+Id2+"' WHERE Id = '"+Id1+"'");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); }
}
db.commit();
ret.value = amount;
} catch(std::exception &e) {
query.finish();
db.rollback();
ret.value = std::string("error:") + e.what();
}
query.exec("UNLOCK TABLES");
query.finish();
return ret;
}
Your Tables problably are Myisam, changed them to Innodb to allow transactions.
To change the AutoCommit (I'm not sure if it's necessary) you can try it this way:
mysql> SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
but this will be working only with the current connection, if you need to apply to all the DB you need to change the DB variables.

Day(s) of week selection from QT checkbox to Postgresql

I have a group of checkboxes representing the days of a week in my Qt application GUI and I select one or many days, and depending on which boxes are checked, pass a query string to PostgreSQL in order to display certain data on those days -e.g. if I checked monday and wednesday, extract (dow from timestamp) = 1 or extract(dow from timestamp) = 3 should be added to my query. I have just typed a crude solution -though haven't tested yet as I write this-, but I was wondering if there is a shorter -and more elegant- approach that I'm missing out here. The code is as below: -the queryAdditionCalltimePart and queryAdditionCallStampPart strings are later added to the relevant parts of my main query's QString before the main query is executed-
bool checkboxArray[7];
bool mult = false;
checkboxArray[0] = this->ui->checkBoxMonday->isChecked();
checkboxArray[1] = this->ui->checkBoxTuesday->isChecked();
checkboxArray[2] = this->ui->checkBoxWednesday->isChecked();
checkboxArray[3] = this->ui->checkBoxThursday->isChecked();
checkboxArray[4] = this->ui->checkBoxFriday->isChecked();
checkboxArray[5] = this->ui->checkBoxSaturday->isChecked();
checkboxArray[6] = this->ui->checkBoxSunday->isChecked();
QString queryAdditionCalltimePart = "";
QString queryAdditionCalStampPart = "";
int count = 0;
queryAdditionCalltimePart.append("(");
queryAdditionCalStampPart.append("(");
for(int i = 0; i < 7; i++)
{
if(checkboxArray[i] == true)
{
count++;
}
}
int x = 0;
for(int i = 0; i < 7; i++)
{
if(checkboxArray[i] == true)
{
queryAdditionCalltimePart.append("(SELECT EXTRACT(DOW FROM calltime) = '" +QString::number(i+1)+"')");
queryAdditionCalStampPart.append("(SELECT EXTRACT(DOW FROM cal.stamp) = '" +QString::number(i+1)+"')");
}
if(count > 1 && checkboxArray[i] == true)
{
if(x == count - 1)
{
}
else
{
queryAdditionCalltimePart.append("OR");
queryAdditionCalStampPart.append("OR");
x++;
}
}
}
queryAdditionCalltimePart.append(")");
queryAdditionCalStampPart.append(")");
You can add properties to any widget in Qt, http://qt-project.org/doc/qt-4.8/qobject.html#setProperty. The property can have any information that you want.
In your particular case, it would be cleaner to attach the SQL string as a property for each checkbox.
this->ui->checkBoxMonday->setProperty("sql",
"(SELECT EXTRACT(DOW FROM calltime) = '" +QString::number(i+1)+"') OR ";
Once you receive the user input, simply append the check box properties and remove the final OR.

How to use the MySQL count() function in QT?

I want to get the total number of rows in a table. How can I achieve this?
I wrote the code as follows:
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("localhost");
db.setDatabaseName("Dictionary");
db.setUserName("root");
db.setPassword("cinesoft");
bool ok = db.open();
int ID;
//SELECT COUNT(*) FROM icecream
QString IDnosql="SELECT * FROM Dictionary.TABLE_ENGLISH";
if(ok)
{
QSqlQuery IDquery(db);
IDquery.prepare(IDnosql);
int Id=IDquery.numRowsAffected();
IDquery.exec();
// int Id=IDquery.numRowsAffected();
QMessageBox::information(0,"sucess",QString::number(Id));
}
I use the count command. I want to get the total no of rows in my table and store to an integer variable.
You can prepare a QSqlQuery with the COUNT command:
QSqlQuery q;
q.prepare(QString("SELECT COUNT (*) FROM %1").arg(tableName));
q.exec();
int rows= 0;
if (q.next()) {
rows= q.value(0).toInt();
}
Check QSqlQuery for more details
Why don't you just use the query SELECT COUNT(*) from Dictionary.TABLE_ENGLISH - this will give you the number of rows in the table. Then get this value from the result set and store it in an integer variable.
When using a COUNT statement, you can use QSqlQuery::value( int index ) like when selecting a single value:
QString IDnosql="SELECT COUNT( * ) FROM Dictionary.TABLE_ENGLISH";
if(ok)
{
QSqlQuery IDquery(db);
IDquery.prepare(IDnosql);
if( !IDquery.exec() )
{
// TODO: perform error handling here!
}
if( !IDquery.next() )
{
// TODO: Error handling: Query did not return a result (no row, which should not be possible when using a count statement as you would always get 1 row)
}
int Id = IDquery.value( 0 ).toInt();
QMessageBox::information(0,"sucess",QString::number(Id));
}
To get the total number of rows in a table A you simply
SELECT count(*) from A;
Then you need to convert the result to an integer.