QSqlTableModel derived class crash on setFilter/select/everything
-
Hello, I have a following problem:
I need to create a DB of students and courses with some editable info like grades etc.So I created a class deriving from QSqlTableModel and implemented some of the required functions (data, setData, roleNames)
The class implementation looks like this: (please read the comments)#include "sqldb.h" #include <QDebug> #include <QSqlRecord> #include <QSqlError> #include <QSqlTableModel> #include <QSqlQuery> static bool createTable() { if (QSqlDatabase::database().tables().contains(QStringLiteral("Students"))) { // We want to clear it at the moment QSqlQuery query; if (!query.exec("DROP TABLE 'Students'")) qFatal("Failed to drop table 'Students' %s", qPrintable(query.lastError().text())); } QSqlQuery query; if (!query.exec( "CREATE TABLE IF NOT EXISTS 'Students' (" " 'id' INTEGER AUTO_INCREMENT, " " 'name' TEXT NOT NULL," " 'department' TEXT NOT NULL, " " PRIMARY KEY(id)" ")")) { qFatal("Failed to query database: %s", qPrintable(query.lastError().text())); return false; } return true; } SqlDb::SqlDb(QObject *parent) : QSqlTableModel(parent) { if (createTable()) { insertStudent("Albert Einstein", "PPT"); insertStudent("Ernest Hemingway", "PPT"); } setEditStrategy(QSqlTableModel::OnFieldChange); setTable("Students"); if(!select()) qFatal("Students SELECT query failed %s", qPrintable(lastError().text())); } bool SqlDb::setData(const QModelIndex &item, const QVariant &value, int role) { if (item.isValid() && role == Qt::EditRole) { QSqlTableModel::setData(item, value, role); emit dataChanged(item, item); return true; } return false; } QVariant SqlDb::data(const QModelIndex &index, int role) const { QVariant value; if (index.isValid()) { if (role < Qt::UserRole) { value = QSqlTableModel::data(index, role); } else { int columnIdx = role - Qt::UserRole - 1; QModelIndex modelIndex = this->index(index.row(), columnIdx); value = QSqlTableModel::data(modelIndex, Qt::DisplayRole); } } return value; } QHash<int, QByteArray> SqlDb::roleNames() const { QHash<int, QByteArray> roles; for (int i = 0; i < this->record().count(); i++) { roles.insert(Qt::UserRole + i + 1, record().fieldName(i).toUtf8()); } return roles; } QString SqlDb::name() { return n_name; } void SqlDb::setName(const QString &newName) { qDebug() << "I'mt the last printed line"; if(newName == n_name) return; n_name = newName; const QString filterString = QString::fromLatin1("name = '%1'").arg(n_name); setFilter(filterString); // The program crashes here (Segmentation Fault) // after launching valgrind I got info "pure virtual function called" // but I have no idea which one that could be. // What's more if I commented out this `setFilter` then it // would crash on the `select()` qDebug() << "I'm not ever printed"; if (!select()) qDebug() << "Couldn't select with filter: %s" << lastError().text(); emit nameChanged(); } void SqlDb::insertStudent(const QString &name, const QString &department) { // The program doesn't also work when reusing the same DB and not using // this function at all qDebug() << "Inserting new student" << name << ", " << department; QString queryString = "INSERT INTO Students (name, department)" "VALUES (?, ?)"; QSqlQuery query(queryString); query.addBindValue(name); query.addBindValue(department); if (!query.exec()) qDebug() << "Couldn't add new student" << query.lastError().text(); } void SqlDb::updateStudentDetails(const QString &name, const QString &department) { // This function is not ever called now but it still doesn't work QSqlQuery query; query.prepare("UPDATE 'Students'" "SET name = :name, department = :department " "WHERE name == '" + n_name + "'"); query.bindValue(":name", name); query.bindValue(":department", department); if(!query.exec()) qDebug() << "Couldn't add new student" << query.lastError().text(); } void SqlDb::refresh() { // This one also doesn't work - when called - SEGFAULT // also `clear()` crashes QSqlQuery query; query.exec("SELECT * FROM Students"); setQuery(query); }
Well I guess that there is a fundamental bug somewhere in my code but I don't really know where.
The header of the class:#ifndef SQLDB_H #define SQLDB_H #include <QSqlTableModel> class SqlDb: public QSqlTableModel { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) public: SqlDb(QObject *parent = 0); QVariant data(const QModelIndex &item, int role) const Q_DECL_OVERRIDE; QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; bool setData(const QModelIndex &item, const QVariant &value, int role); QString name(); Q_INVOKABLE void insertStudent(const QString &name, const QString &department); Q_INVOKABLE void updateStudentDetails(const QString &name, const QString &department); public slots: void refresh(); void setName(const QString &newName); signals: void nameChanged(); private: QString n_name; }; #endif // SQLDB_H
-
Well I don't have the stack trace, that's one of the problems. QtCreator doesn't even tell me that is SEGFAULT, to get that message I needed to run valgrind.<- I have the trace now, see below.And also I'm not really sure if I need to reimplement the whole model but I was learning from Qt Quick Chat Tutorial in which creating models was implemented this way.
I believe I could just invoke some method on QSqlTableModel itself but would it have everything I need?
And what I need is:- listing every row (surely ok)
- setting filters to select single row by given name (student details) (probably ok, there is the setFilters function)
- resetting filters to get the whole list back again (when user closes the details page)
I'm a newbie and I'm really overhelmed by the amount of ways stuff can be done in Qt.
Since I'm already here, I'd like to ask a question that is pretty close to the subject:
if I have a model (let's say that it's QSqlTableModel) then is it better to instantiate it in every QML or create one object in main.cpp and pass it's reference to the view and then use it in all of the views?EDIT
I found the stack-trace:1 ?? 0x2659650 2 ?? 3 ?? 4 QQuickItemView::minYExtent() const 5 ?? 6 QQuickItemView::maxYExtent() const 7 ?? 8 QQuickFlickable::setContentHeight(double) 9 ?? 10 ?? 11 ?? 12 QQmlBinding::update(QFlags<QQmlPropertyData::WriteFlag>) 13 QQmlBinding::refresh() 14 QQmlNotifier::emitNotify(QQmlNotifierEndpoint *, void * *) 15 QQmlData::signalEmitted(QAbstractDeclarativeData *, QObject *, int, void * *) 16 QMetaObject::activate(QObject *, int, int, void * *) 17 QMetaObject::activate(QObject *, int, int, void * *) 18 QQuickItemPrivate::removeChild(QQuickItem *) 19 QQuickItem::setParentItem(QQuickItem *) 20 QQuickItemView::destroyingItem(QObject *) 21 ?? 22 QMetaObject::activate(QObject *, int, int, void * *) 23 QQmlInstanceModel::destroyingItem(QObject *) 24 ?? 25 QQmlDelegateModel::release(QObject *) 26 ?? 27 ?? 28 ?? 29 ?? 30 ?? 31 QQuickItemView::modelUpdated(QQmlChangeSet const&, bool) 32 ?? 33 QMetaObject::activate(QObject *, int, int, void * *) 34 QQmlInstanceModel::modelUpdated(QQmlChangeSet const&, bool) 35 ?? 36 ?? 37 ?? 38 QQmlDelegateModel::_q_modelReset() 39 ?? 40 QQmlDelegateModel::qt_metacall(QMetaObject::Call, int, void * *) 41 QMetaObject::activate(QObject *, int, int, void * *) 42 QAbstractItemModel::modelReset(QAbstractItemModel::QPrivateSignal) 43 QAbstractItemModel::endResetModel() 44 QSqlTableModel::select() 45 SqlDb::setName sqldb.cpp 110 0x407261 46 SqlDb::qt_static_metacall moc_sqldb.cpp 104 0x4081d8 47 SqlDb::qt_metacall moc_sqldb.cpp 168 0x40842b
(i truncated the bottom since it works and also removed the addresses)
45 is my call of setName, exactly the line withsetFilter
.
Above that on the stack is theselect
function which also is broken, so that's why thesetFilter
doesn't work.Btw the funny thing is that in the constructor I also call
select
but it works in there. -
Okay, I solved the problem.
It was not a bug in the model I presented but in the QML (I found that out after reading the top of the stack - there are some functions that have more common with QML than SQL). SO I created a new QML, much simpler that would run some filters on the DB and yield the results.
And it works.
Blah, whole week wasted.Now I need to find out why my QML causes the app to crash but that's a thing for another thread.