我正在做插入:
QSqlQuery myQuery(db);
myQuery.prepare("INSERT INTO mytable VALUES (:val1, :val2)");
myQuery.bindValue(":val1", 1);
myQuery.bindValue(":val2", 2);
myQuery.exec();
然后我需要获取执行的 SQL 查询以用于日志记录。
myQuery.executedQuery()
返回"INSERT INTO mytable VALUES (?, ?)"
。
如何使用所使用的实际绑定值来执行查询?
更好的功能(受 Qt 源代码启发:http://qt.gitorious.org/qt/qt/blobs/4.7/src/sql/kernel/qsqlresult.cpp#line644)。
此函数应该处理几乎所有情况:使用名称绑定时,此代码不适用于 Oracle DB(这是本机支持名称绑定的唯一数据库 =>execedQuery() 不返回带有“?”的查询,而是返回原始查询...)
为了能够支持数据库的原生支持名称绑定,绑定值的键必须按长度排序,然后循环遍历排序后的映射...
QString getLastExecutedQuery(const QSqlQuery& query)
{
QString sql = query.executedQuery();
const int nbBindValues = query.boundValues().size();
for(int i = 0, j = 0; j < nbBindValues; ++j)
{
i = sql.indexOf(QLatin1Char('?'), i);
if (i <= 0)
{
break;
}
const QVariant &var = query.boundValue(j);
QSqlField field(QLatin1String(""), var.type());
if (var.isNull())
{
field.clear();
}
else
{
field.setValue(var);
}
QString formatV = query.driver()->formatValue(field);
sql.replace(i, 1, formatV);
i += formatV.length();
}
return sql;
}
编辑:我在上一个函数中发现了一个错误,如果是“?”存在于带引号的字符串内,即 '?'被下一个可用值替换。该错误已经存在于 Qt 源代码中。 这个功能应该可以解决这个问题(可以改进很多,但想法就在那里)
QString getLastExecutedQuery(const QSqlQuery& query)
{
QString sql = query.executedQuery();
int nbBindValues = query.boundValues().size();
for(int i = 0, j = 0; j < nbBindValues;)
{
int s = sql.indexOf(QLatin1Char('\''), i);
i = sql.indexOf(QLatin1Char('?'), i);
if (i < 1)
{
break;
}
if(s < i && s > 0)
{
i = sql.indexOf(QLatin1Char('\''), s + 1) + 1;
if(i < 2)
{
break;
}
}
else
{
const QVariant &var = query.boundValue(j);
QSqlField field(QLatin1String(""), var.type());
if (var.isNull())
{
field.clear();
}
else
{
field.setValue(var);
}
QString formatV = query.driver()->formatValue(field);
sql.replace(i, 1, formatV);
i += formatV.length();
++j;
}
}
return sql;
}
lightstep建议的替代方案是准备查询字符串,然后调用一个函数,该函数首先将查询写入日志,然后才调用真正的execute()。我个人使用 QString::arg() 和 "%number" 作为参数来创建查询字符串而不是 bindValue()。
让我们总结一下:
解决方案#1(lightstep)
我想出了这个解决方法:
QString getLastExecutedQuery(const QSqlQuery& query) { QString str = query.lastQuery(); QMapIterator<QString, QVariant> it(query.boundValues()); while (it.hasNext()) { it.next(); str.replace(it.key(),it.value().toString()); } return str; }
解决方案#2(我):
// my helper function
#define SQLDB_SHOW_QUERIES
#define SQLDB_LOG_QUERIES
#define SQLDB_LOG_FILENAME "sqlite.db.log"
bool executeQuery(QSqlQuery& queryObject, const QString& query)
{
bool result = true;;
#ifdef SQLDB_SHOW_QUERIES
std::cout<<query.toStdString()<<std::endl;
#endif
#ifdef SQLDB_LOG_QUERIES
std::fstream fs_log;
fs_log.open(SQLDB_LOG_FILENAME,std::ios::out|std::ios::app);
if (fs_log.is_open())
{
fs_log<<query.toUtf8().data()<<std::endl;
}
#endif
result &= queryObject.exec(query);
#ifdef SQLDB_SHOW_QUERIES
if (!result) std::cout<<queryObject.lastError().text().toStdString()<<std::endl;
std::cout<<std::endl;
#endif
#ifdef SQLDB_LOG_QUERIES
if (fs_log.is_open())
{
if (!result) fs_log<<queryObject.lastError().text().toUtf8().data()<<std::endl;
fs_log<<std::endl;
fs_log.close();
}
#endif
return result;
}
// your sample code
QSqlQuery myQuery(db);
QString query = QString("INSERT INTO mytable VALUES (%1,%2)")
.arg(1).arg(2);
executeQuery(myQuery,query);
您必须以相反的顺序迭代元素才能获得正确的结果。
Example:
Query: " :a :aa "
query.bindValue(":a",1);
query.bindValue(":aa",1);
getLastExecutedQuery will return: "1 1a"
固定解决方案#1(轻步)
QString getLastExecutedQuery(const QSqlQuery& query)
{
QString str = query.lastQuery();
QMapIterator<QString, QVariant> it(query.boundValues());
it.toBack();
while (it.hasPrevious())
{
it.previous();
str.replace(it.key(),it.value().toString());
}
return str;
}
如果数据库用户具有“SUPER”权限,则可以在运行时设置日志记录。我在这篇文章中找到了这个答案的一些灵感:如何显示 MySQL 上执行的最后一个查询?
在prepare语句前添加以下代码:
QSqlQuery query("SET GLOBAL log_output = 'TABLE'");
query.exec("SET GLOBAL general_log = 'ON'");
在prepare、bindValue和exec语句后添加以下代码:
query.exec("SET GLOBAL general_log = 0");
执行的查询存储在数据库“mysql”的表“general_log”中。 “general_log”表将显示未准备的变量以及已填充变量的查询。我没有尝试过,但可能可以设置 MySQL 会话变量“sql_log_off”,并且用户不需要“SUPER”权限。请参阅MySQL 文档。
它仅适用于 MySQL >= 5.1.12。
理想的解决方案应该是修补 Qt(仍然不在 Qt 5 或 Qt 6 中):
方法:
QString QSqlQuery::executedQuery() const
应该调用(在 SQLite 的实现中)函数:
char *sqlite3_expanded_sql(sqlite3_stmt *pStmt);
或者,
QSqlResult::BindingSyntax QSqlResult::bindingSyntax() const
应该是[public],所以我们可以自己选择使用哪个函数来替换绑定。目前,当调用以下之一时,它会更改为“位置”:
void addBindValue(const QVariant &val, QSql::ParamType paramType = QSql::In)
void bindValue(int pos, const QVariant &val, QSql::ParamType paramType = QSql::In)
在 Qt 改变之前,我们所能做的就是手动调用其中一个函数:
QString formatNamedSqlQuery(const QSqlQuery *query)
{
QMap<QString, QVariant> vals = query->boundValues();
QMap<QString, QVariant>::const_iterator i;
for(i = vals.constBegin() ; i != vals.constEnd() ; ++i) {
const QVariant &var = i.value();
QSqlField field(QLatin1String(""), var.type());
if (var.isNull()) {
field.clear();
} else {
field.setValue(var);
}
QString formatV = query->driver()->formatValue(field);
if (formatV.size() > 100)
formatV = formatV.left(100);
qstr.replace(i.key(), formatV);
}
return qstr;
}
QString formatPositionedSqlQuery(const QSqlQuery *query)
{
QString qstr = query->lastQuery();
QList<QVariant> list = query->boundValues().values();
QString val;
int i = 0;
int idx = 0;
for (idx = 0; idx < list.count(); ++idx) {
i = qstr.indexOf(QLatin1Char('?'), i);
if (i == -1)
continue;
QVariant var = list.value(idx);
QSqlField f(QLatin1String(""), QVariant::Type(var.userType()));
if (var.isNull())
f.clear();
else
f.setValue(var);
val = query->driver()->formatValue(f);
qstr = qstr.replace(i, 1, val);
i += val.length();
}
return qstr;
}
PS。我们还可以子类化 QSqlQuery 并跟踪内部的“bool located”,覆盖“execulatedQuery()”并根据此 bool 选择适当的。