Как получить последний подготовленный и выполненный запрос с помощью QsqlQuery?

Я делаю вставку:

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 (?, ?)".

Как мне получить выполненный запрос с фактическими привязанными значениями, которые были использованы?

Ответы

Ответ 1

Альтернативой тому, что предлагается lightstep, является подготовка строк запроса, а затем вызов функции, которая сначала записывает запрос в журнал и только затем вызывает реальный запуск(). Я лично использую 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);

Ответ 2

Лучшая функция (вдохновленная исходным кодом Qt: http://qt.gitorious.org/qt/qt/blobs/4.7/src/sql/kernel/qsqlresult.cpp#line644).

Эта функция должна обрабатывать почти все случаи: этот код не работает с Oracle DB при использовании связывания имен (это единственная СУБД, которая изначально поддерживает имя Binding = > executeQuery(), не возвращает запрос с помощью??, но исходный запрос...)

Чтобы иметь возможность поддерживать встроенную поддержку. Имя Binding для DB, ключи связанных значений должны сортироваться по длине, а затем перебирать отсортированную карту...

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;
}

Ответ 3

Вам нужно перебирать элементы в обратном порядке, чтобы получить правильный результат.

Example:
Query: " :a :aa " 
query.bindValue(":a",1);
query.bindValue(":aa",1);
getLastExecutedQuery will return: "1 1a"

Исправлено решение # 1 (lightstep)

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;
}

Ответ 4

Если пользователь базы данных имеет права "SUPER", журнал может быть установлен во время выполнения. Я нашел вдохновение для этого ответа в этом сообщении: Как показать последние запросы, выполненные в MySQL?

Добавьте следующий код перед оператором подготовки:

QSqlQuery query("SET GLOBAL log_output = 'TABLE'");
query.exec("SET GLOBAL general_log = 'ON'");

Добавьте следующий код после инструкций prepare, bindValue и exec:

query.exec("SET GLOBAL general_log = 0");

Выполненные запросы сохраняются в таблице "general_log" базы данных "mysql". В таблице "general_log" будут показаны подготовленные без переменных, а также запросы с заполненными переменными. Я не пробовал, но можно установить переменную сеанса MySQL "sql_log_off", а пользователю не нужны права "SUPER". См. Документация по MySQL.

Он работает только с MySQL >= 5.1.12.