QSqlQuery в Qt6: на месте и подготовленныйC++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 QSqlQuery в Qt6: на месте и подготовленный

Сообщение Anonymous »

Прежде всего, версия Qt — 6.10.0, сейчас тестируется на Arch Linux. Я думаю, что нашел ошибку в Qt, но, возможно, это только моя вина.
Моя программа запрашивает у базы данных MariaDB некоторые данные и отображает их в экземпляре QTableView через подкласс QSqlQueryModel - я только что переопределил методы columnsCount(), headerData() и data(), чтобы получить модель, доступную только для чтения:
  • Код: Выделить всё

    columnCount()
    дает постоянное число (запрос, используемый с этой моделью, всегда имеет постоянное количество столбцов в инструкции SELECT)
  • Код: Выделить всё

    headerData()
    переопределен только для того, чтобы давать удобочитаемые имена столбцов.
  • Код: Выделить всё

    data()Метод 
    используется для форматирования необработанных значений и применения некоторых стилей. Это моя реализация data():

Код: Выделить всё

QVariant EquipmentModel::data(const QModelIndex &item, int role) const {
// shortcuts
QVariant value = QSqlQueryModel::data(item, role);
int column = item.column();

// stash raw value for column 2
if (role == Qt::UserRole) {
if (column == 2)
return QSqlQueryModel::data(item, Qt::DisplayRole);
}

// data to be displayed
if (role == Qt::DisplayRole) {
// name
if (column == 1)
return value;

// type
if (column == 2) {
if (value.toString() == APP_TYPE1)
return QString(APP_TYPE1_TXT);

if (value.toString() == APP_TYPE2)
return QString(APP_TYPE2_TXT);

if (value.toString() == APP_TYPE)
return QString(APP_TYPE3_TXT);
}

// verification dates
if ((column == 3) || (column == 4)) {
if (value.isNull())
return QVariant(QMetaType::fromType());
else
return QDateTime::fromString(value.toString(), Qt::ISODate).toString("dd.MM.yyyy");
}
}

// text alignment in a cell
if (role == Qt::TextAlignmentRole) {
// default alignment
int alignment = Qt::AlignVCenter;

// type, verification date, verification valid to
if ((column == 2) || (column == 3) || (column == 4))
alignment |= Qt::AlignCenter;

// name
if (column == 1)
alignment |= Qt::AlignLeft;

return alignment;
}

// background colors
if (role == Qt::BackgroundRole) {
// verification date
if (
(column == 3) &&
(item.siblingAtColumn(2).data(Qt::UserRole).toString() == APP_TYPE1) &&
item.data(Qt::DisplayRole).isNull())
return QBrush(QColor("red"), Qt::SolidPattern);

// verification upto date
if (
(column == 4)  &&
(item.siblingAtColumn(2).data(Qt::UserRole).toString() == APP_TYPE1)) {
// time has out or empty value (no info about verifications)
if (item.data(Qt::DisplayRole).isNull() || (item.siblingAtColumn(5).data(Qt::DisplayRole).toInt() setQuery(basic_query.arg("1 = 1"));
что, в свою очередь, дает мне все записи из нужной таблицы. В этом случае я могу использовать запрос на месте, поскольку проверка вводимых пользователем данных не требуется. Однако, когда поле поиска заполнено и пользователь нажал кнопку поиска, я использую подход с подготовленным оператором в соответствии с документацией Qt:

Код: Выделить всё

QSqlQuery q;

q.prepare(basic_query.arg("e.name LIKE :search OR s.name LIKE :search OR s.description LIKE :search OR s.sn LIKE :search"));
q.bindValue(":search", QString("%%1%").arg(SearchStringFld->text()));
q.exec();

model->setQuery(std::move(q));
Проблема в том, что это работает лишь частично. Например, значения в некоторых столбцах отображаются правильно, цвет фона работает, но столбцы 3 и 4 не содержат никаких значений. Когда я пытаюсь загрузить значения элементов qDebug() в методе EquipmentModel::data(), я вижу, что этот метод вызывается несколько раз (как и ожидалось), и первые вызовы относительно Qt::DisplayRole и столбцов 3 и 4 дают правильные допустимые значения QDate. Однако более поздние вызовы приводят к недопустимым экземплярам QDate, поэтому представление не отображает никаких данных в этих столбцах. Я еще раз проверил правильность оператора SELECT, создав дамп выполненного запроса() и передав его непосредственно в MariaDB. Более того, если я попытаюсь сбросить данные модели, пройдя по строкам и столбцам сразу после назначения запроса через setQuery(), я увижу, что все ячейки данных в порядке.
Чтобы проверить, не является ли это проблемой с подготовленным запросом, я заменил назначение запроса на это:

Код: Выделить всё

model->setQuery(basic_query.arg("e.name LIKE '%%1%' OR s.name LIKE '%%1%' OR s.description LIKE '%%1%' OR s.sn LIKE '%%1%'").arg(SearchStringFld->text()));
и проблема исчезла! Вот два снимка экрана, чтобы проиллюстрировать то, что я говорю (столбцы с индексами 0 и 5 скрыты из-за их чисто технической роли):
Изображение
Изображение

Как вы можете видеть, количество строк в запросе одинаковое, столбец 1 заполняется правильно, и цвет фона также применяется, однако столбцы 3 и 4 не заполняются из-за недопустимого QDate в случае подготовленного запроса. Это похоже на ошибку в Qt, однако есть вероятность, что я делаю что-то ужасно неправильно. Я нашел тему на форуме Qt, в которой дается тот же совет: не выделяйте память в куче, используйте стек, а затем переходите к setQuery(). В этом случае я бы хотел использовать подготовленный запрос, потому что я имею дело с пользовательским вводом и хочу избежать SQL-инъекций во время моих запросов к БД.
Итак, подведем итог: я делаю что-то не так? Как правильно использовать подготовленные запросы в Qt 6 при назначении их моделям?
ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ
Я обнаружил, что проблема каким-то образом связана с типами данных, используемыми в базе данных (в моем случае MariaDB). Чтобы проверить это, я изменил тип данных для столбцов 3 и 4 с DATE на INT и отредактировал SQL-запрос, чтобы использовать прямую разницу вместо DATEDIFF - и все работает отлично! Похоже, что QSqlQueryModel каким-то образом делает недействительными значения для этих столбцов при последовательных запросах, поэтому вместо этого возвращается QDate(Invalid). Это не было проблемой, по крайней мере, в Qt 5.12. Возможно, некоторая обработка даты и времени была изменена во время цикла разработки Qt 6...
ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ 2
Существует MRE:
  • CMakeLists.txt
  • main.cpp
  • содержит только учетные данные БД, я не буду публиковать его здесь.
  • Схема базы данных
А вот скриншот финального теста:
Изображение

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ 3
Если я изменю свой базовый оператор SQL-запроса (первая его строка) с:

Код: Выделить всё

"SELECT DISTINCT e.id, e.name, e.type, v.ts, v.upto, v.d_left "
кому:

Код: Выделить всё

"SELECT DISTINCT e.id, e.name, e.type, DATE_FORMAT(v.ts, '%d.%m.%Y'), DATE_FORMAT(v.upto, '%d.%m.%Y'), v.d_left "
(помните, что я до сих пор использую простую QSqlQueryModel для MRE, поэтому никаких пользовательских преобразований данных не требуется), тогда все работает нормально!
Изображение

Итак, похоже, что QSqlQueryModel каким-то образом плохо себя ведет в связи с QTableView, когда некоторые столбцы имеют тип QDate
ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ 4
Существует пример набора данных с анонимизированными строками

Подробнее здесь: https://stackoverflow.com/questions/798 ... s-prepared
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»