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.
Related
I have a problem with printing in Qt.
I have HTML code in QString variables. In this code I want to insert data from a database.
I get an error:
E:\apprendreQt\gestionstock6\vente.cpp:117: error: invalid operands of types
'const char*' and 'const char [27]' to binary 'operator+'
How can I fix this?
Here is my code:
int num_bl = ui->numeroBLlineEdit->text().toInt() ;
QString html;
QString requette = "select num_facture,date,nom,prenom,code_fiscale,designation,qte_out, prix,(qte_out * prix ) as Montant, sum(qte_out * prix) as Total from ventes join produits_en_ventes join clients join produits on ventes.vente_id = produits_en_ventes.vente_id and ventes.client_id = clients.client_id and produits_en_ventes.produit_id = produits.produit_id where ventes.client_id = :client_id ";
if(!m_db->isOpen())
QMessageBox::critical(this,tr("Inventoria Solution"),m_db->lastError().text()) ;
else{
m_query->clear();
m_query->prepare(requette);
m_query->bindValue(":client_id ", num_bl);
if(!m_query->exec())
QMessageBox::critical(this,tr("Inventoria Solution"),m_query->lastError().text()) ;
else{
html += " <table>"
"<thead>"
"<tr>"
"<th>N°</th>"
"<th>DĂ©signation</th>"
"<th>Qte</th>"
"<th>Prix Unitaire</th>"
"<th>Montant</th>"
" </tr>"
"</thead>";
while(m_query->next())
{
int num_article = 1;
html += "<tr> <td>" + num_article + "</td> <td>"+m_query->value(5).toString()+"</td> <td>"+m_query->value(6).toInt() + "</td> <td>"+m_query->value(7).toInt() + "</td> <td>" + m_query->value(8).toInt() + "</td></tr>";
num_article++;
}
html += "<tfoot>"
"<tr>"
"<td>Total:"+ m_query->value(9).toInt()+"</td>"
"</tr>"
"</tfoot>"
"</table>";
}
print_Html(html);
}
I'm not sure about your error. However, AFAIK a Qstring cannot be concatenated with an int as such.
int myInt = 0;
QString text = "someString" + myInt; // WRONG
int myInt = 0;
QString text = "someString" + QString::number( myInt ); // CORRECT
or
int myInt = 0;
QString text = "someString" % QString::number( myInt ); // CORRECT
If you use operator+, you need to provide QString as an argument, but you use integer values instead: html += "<tr> <td>" + num_article, where num_article is declared as integer. You can replace it with, for example: QString::number(num_article). The same in this line:
"<td>Total:"+ m_query->value(9).toInt()+"</td>"
should be replaced with
"<td>Total:"+ m_query->value(9).toString()+"</td>"
in Qt5 you can use the QStringLiteral macro for each string that doesn't need to be localized to transform all the string literals from const char* (the C++ default) into QString, this will also make creation of those QStrings cheaper (on compilers that support it)
for Qt4 you can use the QString(const char*) constructor or QString::fromAscii(const char*) static function
How should go about inserting a string into a SQL argument?
Something like this:
string clas = "Computer Science";
sql = "SELECT * from STUDENTS where CLASS='clas'";
There are two ways of doing this:
This is the preferred and more secure way. You can use prepared statements like this
string clas = "Computer Science";
sql = "SELECT * FROM Students WHERE Class=?";
// Prepare the request right here
preparedStatement.setString(1, clas);
// Execute the request down here
A simpler but much less secure option (it's vulnerable to SQL-Injections)
string clas = "Computer Science";
sql = "SELECT * FROM Students WHERE Class='" + clas + "'";
Simple answer:
You can just do as follows:
string clas = "Computer Science";
sql = "SELECT * FROM Students WHERE Class='" + clas + "'";
Good answer:
But, we can do better than that. What if multiple value replacement needed, then what? See the code below, it can replace multiple strings. And also, you can write sql injection check if needed. And the best thing, you just have to call the prepare() function and you're done.
Usage Instructions:
Use ? where you need to put a string. If there are multiple string replacement needed, put all the strings in order(as parameters) when calling prepare function. Also, notice prepare function call prepare(sql, {param_1, param_2, param_3, ..., param_n}).
[Note: it'll work with c++11 and higher versions. It won't work with c++11 pre version. So, while compile it, use -std=c++11 flag with g++]
#include <iostream>
#include <string>
#include <initializer_list>
using namespace std;
// write code for sql injection if you think
// it necessary for your program
// is_safe checks for sql injection
bool is_safe(string str) {
// check if str is sql safe or not
// for sql injection
return true; // or false if not sql injection safe
}
void prepare(string &sql, initializer_list<string> list_buf) {
int idx = 0;
int list_size = (int)list_buf.size();
int i = 0;
for(string it: list_buf) {
// check for sql injection
// if you think it's necessary
if(!is_safe(it)) {
// throw error
// cause, sql injection risk
}
if(i >= list_size) {
// throw error
// cause not enough params are given in list_buf
}
idx = sql.find("?", idx);
if (idx == std::string::npos) {
if(i < list_size - 1) {
// throw error
// cause not all params given in list_buf are used
}
}
sql.replace(idx, 1, it);
idx += 1; // cause "?" is 1 char
i++;
}
}
// now test it
int main() {
string sql = "SELECT * from STUDENTS where CLASS=?";
string clas = "clas";
prepare(sql, {clas});
cout << sql << endl;
string sql2 = "select name from class where marks > ? or attendence > ?";
string marks = "80";
string attendence = "40";
prepare(sql2, {marks, attendence});
cout << sql2 << endl;
return 0;
}
[P.S.]: feel free to ask, if anything is unclear.
I cannot seem to find any resolution to this issue on my own. I use this generic function to retrieve data from a database like so:
int id = 29
ArrayList^ classes = getClassesGeneric("dep_id", "=", id.ToString());
However, this returns no results. If I query the database through MySQL Workbench or without parameters it works fine. What am I missing?
ArrayList^ Accessor::getClassesGeneric(String^ col, String^ op, String^ value)
{
ArrayList^ result = gcnew ArrayList();
this->cmd = gcnew MySqlCommand("SELECT * FROM rpos_db.classes WHERE #col #op #value;", this->con);
try
{
this->cmd->Parameters->AddWithValue("#col", col);
this->cmd->Parameters->AddWithValue("#op", op);
this->cmd->Parameters->AddWithValue("#value", value);
this->cmd->Prepare();
MySqlDataReader^ r = this->cmd->ExecuteReader();
while (r->Read())
{
Class^ c = gcnew Class();
c->id = r->GetInt32(0);
c->dep_id = r->GetInt32(1);
c->name = r->GetString(2);
c->code = r->GetString(3);
result->Add(c);
}
r->Close();
}
catch (Exception^ ex)
{
MessageBox::Show(ex->StackTrace, ex->Message);
}
return result;
}
Using the function like this produces the indented result:
classes = getClassesGeneric("1", "=", "1");
Parameters can only be used to replace literals, not object names or syntactic elements, such as the = operator. You'd either have to hardcode it. If you want to pass them dynamically, you'd have to use string manipulation:
ArrayList^ Accessor::getClassesGeneric(String^ col, String^ op, String^ value)
{
ArrayList^ result = gcnew ArrayList();
this->cmd = gcnew MySqlCommand
("SELECT * FROM rpos_db.classes WHERE " +
col + " " + op + " #value;", this->con);
try
{
this->cmd->Parameters->AddWithValue("#value", value);
this->cmd->Prepare();
MySqlDataReader^ r = this->cmd->ExecuteReader();
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
I am trying to extend dijit.form.FilteringSelect with the requirement that all instances of it should match input regardless of where the characters are in the inputted text, and should also ignore whitespace and punctuation (mainly periods and dashes).
For example if an option is "J.P. Morgan" I would want to be able to select that option after typing "JP" or "P Morgan".
Now I know that the part about matching anywhere in the string can be accomplished by passing in queryExpr: "*${0}*" when creating the instance.
What I haven't figured out is how to make it ignore whitespace, periods, and dashes. I have an example of where I'm at here - http://jsfiddle.net/mNYw2/2/. Any help would be appreciated.
the thing to master in this case is the store fetch querystrings.. It will call a function in the attached store to pull out any matching items, so if you have a value entered in the autofilling inputfield, it will eventually end up similar to this in the code:
var query = { this.searchAttr: this.get("value") }; // this is not entirely accurate
this._fetchHandle = this.store.query(query, options);
this._fetchHandle.then( showResultsFunction );
So, when you define select, override the _setStoreAttr to make changes in the store query api
dojo.declare('CustomFilteringSelect', [FilteringSelect], {
constructor: function() {
//???
},
_setStoreAttr: function(store) {
this.inherited(arguments); // allow for comboboxmixin to modify it
// above line eventually calls this._set("store", store);
// so now, 'this' has 'store' set allready
// override here
this.store.query = function(query, options) {
// note that some (Memory) stores has no 'fetch' wrapper
};
}
});
EDIT: override queryEngine function as opposed to query function
Take a look at the file SimpleQueryEngine.js under dojo/store/util. This is essentially what filters the received Array items on the given String query from the FilteringSelect. Ok, it goes like this:
var MyEngine = function(query, options) {
// create our matching query function
switch(typeof query){
default:
throw new Error("Can not query with a " + typeof query);
case "object": case "undefined":
var queryObject = query;
query = function(object){
for(var key in queryObject){
var required = queryObject[key];
if(required && required.test){
if(!required.test(object[key])){
return false;
}
}else if(required != object[key]){
return false;
}
}
return true;
};
break;
case "string":
/// HERE is most likely where you can play with the reqexp matcher.
// named query
if(!this[query]){
throw new Error("No filter function " + query + " was found in store");
}
query = this[query];
// fall through
case "function":
// fall through
}
function execute(array){
// execute the whole query, first we filter
var results = arrayUtil.filter(array, query);
// next we sort
if(options && options.sort){
results.sort(function(a, b){
for(var sort, i=0; sort = options.sort[i]; i++){
var aValue = a[sort.attribute];
var bValue = b[sort.attribute];
if (aValue != bValue) {
return !!sort.descending == aValue > bValue ? -1 : 1;
}
}
return 0;
});
}
// now we paginate
if(options && (options.start || options.count)){
var total = results.length;
results = results.slice(options.start || 0, (options.start || 0) + (options.count || Infinity));
results.total = total;
}
return results;
}
execute.matches = query;
return execute;
};
new Store( { queryEngine: MyEngine });
when execute.matches is set on bottom of this function, what happens is, that the string gets called on each item. Each item has a property - Select.searchAttr - which is tested by RegExp like so: new RegExp(query).test(item[searchAttr]); or maybe a bit simpler to understand; item[searchAttr].matches(query);
I have no testing environment, but locate the inline comment above and start using console.debug..
Example:
Stpre.data = [
{ id:'WS', name: 'Will F. Smith' },
{ id:'RD', name:'Robert O. Dinero' },
{ id:'CP', name:'Cle O. Patra' }
];
Select.searchAttr = "name";
Select.value = "Robert Din"; // keyup->autocomplete->query
Select.query will become Select.queryExp.replace("${0]", Select.value), in your simple queryExp case, 'Robert Din'.. This will get fuzzy and it would be up to you to fill in the regular expression, here's something to start with
query = query.substr(1,query.length-2); // '*' be gone
var words = query.split(" ");
var exp = "";
dojo.forEach(words, function(word, idx) {
// check if last word
var nextWord = words[idx+1] ? words[idx+1] : null;
// postfix 'match-all-but-first-letter-of-nextWord'
exp += word + (nextWord ? "[^" + nextWord[0] + "]*" : "");
});
// exp should now be "Robert[^D]*Din";
// put back '*'
query = '*' + exp + '*';