| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the test suite of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | |
| 30 | #include <QtTest/QtTest> |
| 31 | #include <QtGui> |
| 32 | #include <QtWidgets> |
| 33 | |
| 34 | #include <qsqldriver.h> |
| 35 | #include <qsqldatabase.h> |
| 36 | #include <qsqlerror.h> |
| 37 | #include <qsqlfield.h> |
| 38 | #include <qsqlquery.h> |
| 39 | #include <qsqlrecord.h> |
| 40 | |
| 41 | #include <qsqlquerymodel.h> |
| 42 | #include <qsortfilterproxymodel.h> |
| 43 | |
| 44 | #include "../../kernel/qsqldatabase/tst_databases.h" |
| 45 | |
| 46 | Q_DECLARE_METATYPE(Qt::Orientation) |
| 47 | |
| 48 | class tst_QSqlQueryModel : public QObject |
| 49 | { |
| 50 | Q_OBJECT |
| 51 | |
| 52 | public: |
| 53 | tst_QSqlQueryModel(); |
| 54 | virtual ~tst_QSqlQueryModel(); |
| 55 | |
| 56 | public slots: |
| 57 | void initTestCase(); |
| 58 | void cleanupTestCase(); |
| 59 | void init(); |
| 60 | void cleanup(); |
| 61 | |
| 62 | private slots: |
| 63 | void insertColumn_data() { generic_data(); } |
| 64 | void insertColumn(); |
| 65 | void removeColumn_data() { generic_data(); } |
| 66 | void removeColumn(); |
| 67 | void record_data() { generic_data(); } |
| 68 | void record(); |
| 69 | void () { generic_data(); } |
| 70 | void setHeaderData(); |
| 71 | void fetchMore_data() { generic_data(); } |
| 72 | void fetchMore(); |
| 73 | |
| 74 | //problem specific tests |
| 75 | void withSortFilterProxyModel_data() { generic_data(); } |
| 76 | void withSortFilterProxyModel(); |
| 77 | void setQuerySignalEmission_data() { generic_data(); } |
| 78 | void setQuerySignalEmission(); |
| 79 | void setQueryWithNoRowsInResultSet_data() { generic_data(); } |
| 80 | void setQueryWithNoRowsInResultSet(); |
| 81 | void nestedResets_data() { generic_data(); } |
| 82 | void nestedResets(); |
| 83 | |
| 84 | void task_180617(); |
| 85 | void task_180617_data() { generic_data(); } |
| 86 | void task_QTBUG_4963_setHeaderDataWithProxyModel(); |
| 87 | |
| 88 | private: |
| 89 | void generic_data(const QString &engine=QString()); |
| 90 | void dropTestTables(QSqlDatabase db); |
| 91 | void createTestTables(QSqlDatabase db); |
| 92 | void populateTestTables(QSqlDatabase db); |
| 93 | tst_Databases dbs; |
| 94 | }; |
| 95 | |
| 96 | /* Stupid class that makes protected members public for testing */ |
| 97 | class DBTestModel: public QSqlQueryModel |
| 98 | { |
| 99 | public: |
| 100 | DBTestModel(QObject *parent = 0): QSqlQueryModel(parent) {} |
| 101 | QModelIndex indexInQuery(const QModelIndex &item) const { return QSqlQueryModel::indexInQuery(item); } |
| 102 | }; |
| 103 | |
| 104 | tst_QSqlQueryModel::tst_QSqlQueryModel() |
| 105 | { |
| 106 | } |
| 107 | |
| 108 | tst_QSqlQueryModel::~tst_QSqlQueryModel() |
| 109 | { |
| 110 | } |
| 111 | |
| 112 | void tst_QSqlQueryModel::initTestCase() |
| 113 | { |
| 114 | QVERIFY(dbs.open()); |
| 115 | for (QStringList::ConstIterator it = dbs.dbNames.begin(); it != dbs.dbNames.end(); ++it) { |
| 116 | QSqlDatabase db = QSqlDatabase::database(connectionName: (*it)); |
| 117 | CHECK_DATABASE(db); |
| 118 | dropTestTables(db); //in case of leftovers |
| 119 | createTestTables(db); |
| 120 | populateTestTables(db); |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | void tst_QSqlQueryModel::cleanupTestCase() |
| 125 | { |
| 126 | for (QStringList::ConstIterator it = dbs.dbNames.begin(); it != dbs.dbNames.end(); ++it) { |
| 127 | QSqlDatabase db = QSqlDatabase::database(connectionName: (*it)); |
| 128 | CHECK_DATABASE(db); |
| 129 | dropTestTables(db); |
| 130 | } |
| 131 | dbs.close(); |
| 132 | } |
| 133 | |
| 134 | void tst_QSqlQueryModel::dropTestTables(QSqlDatabase db) |
| 135 | { |
| 136 | QStringList tableNames; |
| 137 | tableNames << qTableName(prefix: "test" , __FILE__, db) |
| 138 | << qTableName(prefix: "test2" , __FILE__, db) |
| 139 | << qTableName(prefix: "test3" , __FILE__, db) |
| 140 | << qTableName(prefix: "many" , __FILE__, db); |
| 141 | tst_Databases::safeDropTables(db, tableNames); |
| 142 | } |
| 143 | |
| 144 | void tst_QSqlQueryModel::createTestTables(QSqlDatabase db) |
| 145 | { |
| 146 | dropTestTables(db); |
| 147 | QSqlQuery q(db); |
| 148 | QSqlDriver::DbmsType dbType = tst_Databases::getDatabaseType(db); |
| 149 | if (dbType == QSqlDriver::PostgreSQL) |
| 150 | QVERIFY_SQL( q, exec("set client_min_messages='warning'" )); |
| 151 | QVERIFY_SQL( q, exec("create table " + qTableName("test" , __FILE__, db) + "(id integer not null, name varchar(20), title integer, primary key (id))" )); |
| 152 | QVERIFY_SQL( q, exec("create table " + qTableName("test2" , __FILE__, db) + "(id integer not null, title varchar(20), primary key (id))" )); |
| 153 | QVERIFY_SQL( q, exec("create table " + qTableName("test3" , __FILE__, db) + "(id integer not null, primary key (id))" )); |
| 154 | QVERIFY_SQL( q, exec("create table " + qTableName("many" , __FILE__, db) + "(id integer not null, name varchar(20), primary key (id))" )); |
| 155 | } |
| 156 | |
| 157 | void tst_QSqlQueryModel::populateTestTables(QSqlDatabase db) |
| 158 | { |
| 159 | qWarning() << "Populating test tables, this can take quite a while... ZZZzzz..." ; |
| 160 | bool hasTransactions = db.driver()->hasFeature(f: QSqlDriver::Transactions); |
| 161 | |
| 162 | QSqlQuery q(db), q2(db); |
| 163 | |
| 164 | tst_Databases::safeDropTables(db, tableNames: QStringList() << qTableName(prefix: "manytmp" , __FILE__, db) << qTableName(prefix: "test3tmp" , __FILE__, db)); |
| 165 | QVERIFY_SQL(q, exec("create table " + qTableName("manytmp" , __FILE__, db) + "(id integer not null, name varchar(20), primary key (id))" )); |
| 166 | QVERIFY_SQL(q, exec("create table " + qTableName("test3tmp" , __FILE__, db) + "(id integer not null, primary key (id))" )); |
| 167 | |
| 168 | if (hasTransactions) QVERIFY_SQL(db, transaction()); |
| 169 | |
| 170 | QVERIFY_SQL(q, exec("insert into " + qTableName("test" , __FILE__, db) + " values(1, 'harry', 1)" )); |
| 171 | QVERIFY_SQL(q, exec("insert into " + qTableName("test" , __FILE__, db) + " values(2, 'trond', 2)" )); |
| 172 | QVERIFY_SQL(q, exec("insert into " + qTableName("test2" , __FILE__, db) + " values(1, 'herr')" )); |
| 173 | QVERIFY_SQL(q, exec("insert into " + qTableName("test2" , __FILE__, db) + " values(2, 'mister')" )); |
| 174 | |
| 175 | QVERIFY_SQL(q, exec(QString("insert into " + qTableName("test3" , __FILE__, db) + " values(0)" ))); |
| 176 | QVERIFY_SQL(q, prepare("insert into " + qTableName("test3" , __FILE__, db) + "(id) select id + ? from " + qTableName("test3tmp" , __FILE__, db))); |
| 177 | for (int i=1; i<260; i*=2) { |
| 178 | q2.exec(query: "delete from " + qTableName(prefix: "test3tmp" , __FILE__, db)); |
| 179 | QVERIFY_SQL(q2, exec("insert into " + qTableName("test3tmp" , __FILE__, db) + "(id) select id from " + qTableName("test3" , __FILE__, db))); |
| 180 | q.bindValue(pos: 0, val: i); |
| 181 | QVERIFY_SQL(q, exec()); |
| 182 | } |
| 183 | |
| 184 | QVERIFY_SQL(q, exec(QString("insert into " + qTableName("many" , __FILE__, db) + "(id, name) values (0, \'harry\')" ))); |
| 185 | QVERIFY_SQL(q, prepare("insert into " + qTableName("many" , __FILE__, db) + "(id, name) select id + ?, name from " + qTableName("manytmp" , __FILE__, db))); |
| 186 | for (int i=1; i < 2048; i*=2) { |
| 187 | q2.exec(query: "delete from " + qTableName(prefix: "manytmp" , __FILE__, db)); |
| 188 | QVERIFY_SQL(q2, exec("insert into " + qTableName("manytmp" , __FILE__, db) + "(id, name) select id, name from " + qTableName("many" , __FILE__, db))); |
| 189 | q.bindValue(pos: 0, val: i); |
| 190 | QVERIFY_SQL(q, exec()); |
| 191 | } |
| 192 | |
| 193 | if (hasTransactions) QVERIFY_SQL(db, commit()); |
| 194 | |
| 195 | tst_Databases::safeDropTables(db, tableNames: QStringList() << qTableName(prefix: "manytmp" , __FILE__, db) << qTableName(prefix: "test3tmp" , __FILE__, db)); |
| 196 | } |
| 197 | |
| 198 | void tst_QSqlQueryModel::generic_data(const QString& engine) |
| 199 | { |
| 200 | if ( dbs.fillTestTable(driverPrefix: engine) == 0 ) { |
| 201 | if(engine.isEmpty()) |
| 202 | QSKIP( "No database drivers are available in this Qt configuration" ); |
| 203 | else |
| 204 | QSKIP( (QString("No database drivers of type %1 are available in this Qt configuration" ).arg(engine)).toLocal8Bit()); |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | void tst_QSqlQueryModel::init() |
| 209 | { |
| 210 | } |
| 211 | |
| 212 | void tst_QSqlQueryModel::cleanup() |
| 213 | { |
| 214 | } |
| 215 | |
| 216 | void tst_QSqlQueryModel::removeColumn() |
| 217 | { |
| 218 | QFETCH(QString, dbName); |
| 219 | QSqlDatabase db = QSqlDatabase::database(connectionName: dbName); |
| 220 | CHECK_DATABASE(db); |
| 221 | |
| 222 | DBTestModel model; |
| 223 | model.setQuery(QSqlQuery("select * from " + qTableName(prefix: "test" , __FILE__, db), db)); |
| 224 | model.fetchMore(); |
| 225 | QSignalSpy spy(&model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int))); |
| 226 | |
| 227 | QCOMPARE(model.columnCount(), 3); |
| 228 | QVERIFY(model.removeColumn(0)); |
| 229 | QCOMPARE(spy.count(), 1); |
| 230 | QVERIFY(*(QModelIndex *)spy.at(0).at(0).constData() == QModelIndex()); |
| 231 | QCOMPARE(spy.at(0).at(1).toInt(), 0); |
| 232 | QCOMPARE(spy.at(0).at(2).toInt(), 0); |
| 233 | |
| 234 | QCOMPARE(model.columnCount(), 2); |
| 235 | QCOMPARE(model.indexInQuery(model.index(0, 0)).column(), 1); |
| 236 | QCOMPARE(model.indexInQuery(model.index(0, 1)).column(), 2); |
| 237 | QCOMPARE(model.indexInQuery(model.index(0, 2)).column(), -1); |
| 238 | QCOMPARE(model.indexInQuery(model.index(0, 3)).column(), -1); |
| 239 | |
| 240 | QVERIFY(model.insertColumn(1)); |
| 241 | QCOMPARE(model.columnCount(), 3); |
| 242 | QCOMPARE(model.indexInQuery(model.index(0, 0)).column(), 1); |
| 243 | QCOMPARE(model.indexInQuery(model.index(0, 1)).column(), -1); |
| 244 | QCOMPARE(model.indexInQuery(model.index(0, 2)).column(), 2); |
| 245 | QCOMPARE(model.indexInQuery(model.index(0, 3)).column(), -1); |
| 246 | |
| 247 | QCOMPARE(model.data(model.index(0, 0)).toString(), QString("harry" )); |
| 248 | QCOMPARE(model.data(model.index(0, 1)), QVariant()); |
| 249 | QCOMPARE(model.data(model.index(0, 2)).toInt(), 1); |
| 250 | QCOMPARE(model.data(model.index(0, 3)), QVariant()); |
| 251 | |
| 252 | QVERIFY(!model.removeColumn(42)); |
| 253 | QVERIFY(!model.removeColumn(3)); |
| 254 | QVERIFY(!model.removeColumn(1, model.index(1, 2))); |
| 255 | QCOMPARE(model.columnCount(), 3); |
| 256 | |
| 257 | QVERIFY(model.removeColumn(2)); |
| 258 | |
| 259 | QCOMPARE(spy.count(), 2); |
| 260 | QVERIFY(*(QModelIndex *)spy.at(1).at(0).constData() == QModelIndex()); |
| 261 | QCOMPARE(spy.at(1).at(1).toInt(), 2); |
| 262 | QCOMPARE(spy.at(1).at(2).toInt(), 2); |
| 263 | |
| 264 | QCOMPARE(model.columnCount(), 2); |
| 265 | QCOMPARE(model.indexInQuery(model.index(0, 0)).column(), 1); |
| 266 | QCOMPARE(model.indexInQuery(model.index(0, 1)).column(), -1); |
| 267 | QCOMPARE(model.indexInQuery(model.index(0, 2)).column(), -1); |
| 268 | QCOMPARE(model.indexInQuery(model.index(0, 3)).column(), -1); |
| 269 | |
| 270 | QVERIFY(model.removeColumn(1)); |
| 271 | |
| 272 | QCOMPARE(spy.count(), 3); |
| 273 | QVERIFY(*(QModelIndex *)spy.at(2).at(0).constData() == QModelIndex()); |
| 274 | QCOMPARE(spy.at(2).at(1).toInt(), 1); |
| 275 | QCOMPARE(spy.at(2).at(2).toInt(), 1); |
| 276 | |
| 277 | QCOMPARE(model.columnCount(), 1); |
| 278 | QCOMPARE(model.indexInQuery(model.index(0, 0)).column(), 1); |
| 279 | QCOMPARE(model.indexInQuery(model.index(0, 1)).column(), -1); |
| 280 | QCOMPARE(model.indexInQuery(model.index(0, 2)).column(), -1); |
| 281 | QCOMPARE(model.indexInQuery(model.index(0, 3)).column(), -1); |
| 282 | QCOMPARE(model.data(model.index(0, 0)).toString(), QString("harry" )); |
| 283 | |
| 284 | QVERIFY(model.removeColumn(0)); |
| 285 | |
| 286 | QCOMPARE(spy.count(), 4); |
| 287 | QVERIFY(*(QModelIndex *)spy.at(3).at(0).constData() == QModelIndex()); |
| 288 | QCOMPARE(spy.at(3).at(1).toInt(), 0); |
| 289 | QCOMPARE(spy.at(3).at(2).toInt(), 0); |
| 290 | |
| 291 | QCOMPARE(model.columnCount(), 0); |
| 292 | QCOMPARE(model.indexInQuery(model.index(0, 0)).column(), -1); |
| 293 | QCOMPARE(model.indexInQuery(model.index(0, 1)).column(), -1); |
| 294 | QCOMPARE(model.indexInQuery(model.index(0, 2)).column(), -1); |
| 295 | QCOMPARE(model.indexInQuery(model.index(0, 3)).column(), -1); |
| 296 | } |
| 297 | |
| 298 | void tst_QSqlQueryModel::insertColumn() |
| 299 | { |
| 300 | QFETCH(QString, dbName); |
| 301 | QSqlDatabase db = QSqlDatabase::database(connectionName: dbName); |
| 302 | CHECK_DATABASE(db); |
| 303 | const QSqlDriver::DbmsType dbType = tst_Databases::getDatabaseType(db); |
| 304 | |
| 305 | DBTestModel model; |
| 306 | model.setQuery(QSqlQuery("select * from " + qTableName(prefix: "test" , __FILE__, db), db)); |
| 307 | model.fetchMore(); // necessary??? |
| 308 | |
| 309 | bool isToUpper = (dbType == QSqlDriver::Interbase) || (dbType == QSqlDriver::Oracle) || (dbType == QSqlDriver::DB2); |
| 310 | const QString idColumn(isToUpper ? "ID" : "id" ); |
| 311 | const QString nameColumn(isToUpper ? "NAME" : "name" ); |
| 312 | const QString titleColumn(isToUpper ? "TITLE" : "title" ); |
| 313 | |
| 314 | QSignalSpy spy(&model, SIGNAL(columnsInserted(QModelIndex,int,int))); |
| 315 | |
| 316 | QCOMPARE(model.data(model.index(0, 0)).toInt(), 1); |
| 317 | QCOMPARE(model.data(model.index(0, 1)).toString(), QString("harry" )); |
| 318 | QCOMPARE(model.data(model.index(0, 2)).toInt(), 1); |
| 319 | QCOMPARE(model.data(model.index(0, 3)), QVariant()); |
| 320 | |
| 321 | QCOMPARE(model.headerData(0, Qt::Horizontal).toString(), idColumn); |
| 322 | QCOMPARE(model.headerData(1, Qt::Horizontal).toString(), nameColumn); |
| 323 | QCOMPARE(model.headerData(2, Qt::Horizontal).toString(), titleColumn); |
| 324 | QCOMPARE(model.headerData(3, Qt::Horizontal).toString(), QString("4" )); |
| 325 | |
| 326 | QVERIFY(model.insertColumn(1)); |
| 327 | |
| 328 | QCOMPARE(spy.count(), 1); |
| 329 | QVERIFY(*(QModelIndex *)spy.at(0).at(0).constData() == QModelIndex()); |
| 330 | QCOMPARE(spy.at(0).at(1).toInt(), 1); |
| 331 | QCOMPARE(spy.at(0).at(2).toInt(), 1); |
| 332 | |
| 333 | QCOMPARE(model.indexInQuery(model.index(0, 0)).column(), 0); |
| 334 | QCOMPARE(model.indexInQuery(model.index(0, 1)).column(), -1); |
| 335 | QCOMPARE(model.indexInQuery(model.index(0, 2)).column(), 1); |
| 336 | QCOMPARE(model.indexInQuery(model.index(0, 3)).column(), 2); |
| 337 | QCOMPARE(model.indexInQuery(model.index(0, 4)).column(), -1); |
| 338 | |
| 339 | QCOMPARE(model.data(model.index(0, 0)).toInt(), 1); |
| 340 | QCOMPARE(model.data(model.index(0, 1)), QVariant()); |
| 341 | QCOMPARE(model.data(model.index(0, 2)).toString(), QString("harry" )); |
| 342 | QCOMPARE(model.data(model.index(0, 3)).toInt(), 1); |
| 343 | QCOMPARE(model.data(model.index(0, 4)), QVariant()); |
| 344 | |
| 345 | QCOMPARE(model.headerData(0, Qt::Horizontal).toString(), idColumn); |
| 346 | QCOMPARE(model.headerData(1, Qt::Horizontal).toString(), QString("2" )); |
| 347 | QCOMPARE(model.headerData(2, Qt::Horizontal).toString(), nameColumn); |
| 348 | QCOMPARE(model.headerData(3, Qt::Horizontal).toString(), titleColumn); |
| 349 | QCOMPARE(model.headerData(4, Qt::Horizontal).toString(), QString("5" )); |
| 350 | |
| 351 | QVERIFY(!model.insertColumn(-1)); |
| 352 | QVERIFY(!model.insertColumn(100)); |
| 353 | QVERIFY(!model.insertColumn(1, model.index(1, 1))); |
| 354 | |
| 355 | QVERIFY(model.insertColumn(0)); |
| 356 | |
| 357 | QCOMPARE(spy.count(), 2); |
| 358 | QVERIFY(*(QModelIndex *)spy.at(1).at(0).constData() == QModelIndex()); |
| 359 | QCOMPARE(spy.at(1).at(1).toInt(), 0); |
| 360 | QCOMPARE(spy.at(1).at(2).toInt(), 0); |
| 361 | |
| 362 | QCOMPARE(model.indexInQuery(model.index(0, 0)).column(), -1); |
| 363 | QCOMPARE(model.indexInQuery(model.index(0, 1)).column(), 0); |
| 364 | QCOMPARE(model.indexInQuery(model.index(0, 2)).column(), -1); |
| 365 | QCOMPARE(model.indexInQuery(model.index(0, 3)).column(), 1); |
| 366 | QCOMPARE(model.indexInQuery(model.index(0, 4)).column(), 2); |
| 367 | QCOMPARE(model.indexInQuery(model.index(0, 5)).column(), -1); |
| 368 | |
| 369 | QVERIFY(!model.insertColumn(6)); |
| 370 | QVERIFY(model.insertColumn(5)); |
| 371 | |
| 372 | QCOMPARE(spy.count(), 3); |
| 373 | QVERIFY(*(QModelIndex *)spy.at(2).at(0).constData() == QModelIndex()); |
| 374 | QCOMPARE(spy.at(2).at(1).toInt(), 5); |
| 375 | QCOMPARE(spy.at(2).at(2).toInt(), 5); |
| 376 | |
| 377 | QCOMPARE(model.indexInQuery(model.index(0, 0)).column(), -1); |
| 378 | QCOMPARE(model.indexInQuery(model.index(0, 1)).column(), 0); |
| 379 | QCOMPARE(model.indexInQuery(model.index(0, 2)).column(), -1); |
| 380 | QCOMPARE(model.indexInQuery(model.index(0, 3)).column(), 1); |
| 381 | QCOMPARE(model.indexInQuery(model.index(0, 4)).column(), 2); |
| 382 | QCOMPARE(model.indexInQuery(model.index(0, 5)).column(), -1); |
| 383 | QCOMPARE(model.indexInQuery(model.index(0, 6)).column(), -1); |
| 384 | |
| 385 | QCOMPARE(model.record().field(0).name(), QString()); |
| 386 | QCOMPARE(model.record().field(1).name(), idColumn); |
| 387 | QCOMPARE(model.record().field(2).name(), QString()); |
| 388 | QCOMPARE(model.record().field(3).name(), nameColumn); |
| 389 | QCOMPARE(model.record().field(4).name(), titleColumn); |
| 390 | QCOMPARE(model.record().field(5).name(), QString()); |
| 391 | QCOMPARE(model.record().field(6).name(), QString()); |
| 392 | |
| 393 | QCOMPARE(model.headerData(0, Qt::Horizontal).toString(), QString("1" )); |
| 394 | QCOMPARE(model.headerData(1, Qt::Horizontal).toString(), idColumn); |
| 395 | QCOMPARE(model.headerData(2, Qt::Horizontal).toString(), QString("3" )); |
| 396 | QCOMPARE(model.headerData(3, Qt::Horizontal).toString(), nameColumn); |
| 397 | QCOMPARE(model.headerData(4, Qt::Horizontal).toString(), titleColumn); |
| 398 | QCOMPARE(model.headerData(5, Qt::Horizontal).toString(), QString("6" )); |
| 399 | QCOMPARE(model.headerData(6, Qt::Horizontal).toString(), QString("7" )); |
| 400 | } |
| 401 | |
| 402 | void tst_QSqlQueryModel::record() |
| 403 | { |
| 404 | QFETCH(QString, dbName); |
| 405 | QSqlDatabase db = QSqlDatabase::database(connectionName: dbName); |
| 406 | CHECK_DATABASE(db); |
| 407 | const QSqlDriver::DbmsType dbType = tst_Databases::getDatabaseType(db); |
| 408 | |
| 409 | QSqlQueryModel model; |
| 410 | model.setQuery(QSqlQuery("select * from " + qTableName(prefix: "test" , __FILE__, db), db)); |
| 411 | |
| 412 | QSqlRecord rec = model.record(); |
| 413 | |
| 414 | bool isToUpper = (dbType == QSqlDriver::Interbase) || (dbType == QSqlDriver::Oracle) || (dbType == QSqlDriver::DB2); |
| 415 | |
| 416 | QCOMPARE(rec.count(), 3); |
| 417 | QCOMPARE(rec.fieldName(0), isToUpper ? QString("ID" ) : QString("id" )); |
| 418 | QCOMPARE(rec.fieldName(1), isToUpper ? QString("NAME" ) : QString("name" )); |
| 419 | QCOMPARE(rec.fieldName(2), isToUpper ? QString("TITLE" ) : QString("title" )); |
| 420 | QCOMPARE(rec.value(0), QVariant(rec.field(0).type())); |
| 421 | QCOMPARE(rec.value(1), QVariant(rec.field(1).type())); |
| 422 | QCOMPARE(rec.value(2), QVariant(rec.field(2).type())); |
| 423 | |
| 424 | rec = model.record(row: 0); |
| 425 | QCOMPARE(rec.fieldName(0), isToUpper ? QString("ID" ) : QString("id" )); |
| 426 | QCOMPARE(rec.fieldName(1), isToUpper ? QString("NAME" ) : QString("name" )); |
| 427 | QCOMPARE(rec.fieldName(2), isToUpper ? QString("TITLE" ) : QString("title" )); |
| 428 | QCOMPARE(rec.value(0).toString(), QString("1" )); |
| 429 | QCOMPARE(rec.value(1), QVariant("harry" )); |
| 430 | QCOMPARE(rec.value(2), QVariant(1)); |
| 431 | } |
| 432 | |
| 433 | void tst_QSqlQueryModel::() |
| 434 | { |
| 435 | QFETCH(QString, dbName); |
| 436 | QSqlDatabase db = QSqlDatabase::database(connectionName: dbName); |
| 437 | CHECK_DATABASE(db); |
| 438 | const QSqlDriver::DbmsType dbType = tst_Databases::getDatabaseType(db); |
| 439 | |
| 440 | QSqlQueryModel model; |
| 441 | |
| 442 | QVERIFY(!model.setHeaderData(5, Qt::Vertical, "foo" )); |
| 443 | QVERIFY(model.headerData(5, Qt::Vertical).isValid()); |
| 444 | |
| 445 | model.setQuery(QSqlQuery("select * from " + qTableName(prefix: "test" , __FILE__, db), db)); |
| 446 | |
| 447 | qRegisterMetaType<Qt::Orientation>(typeName: "Qt::Orientation" ); |
| 448 | QSignalSpy spy(&model, SIGNAL(headerDataChanged(Qt::Orientation,int,int))); |
| 449 | QVERIFY(model.setHeaderData(2, Qt::Horizontal, "bar" )); |
| 450 | QCOMPARE(model.headerData(2, Qt::Horizontal).toString(), QString("bar" )); |
| 451 | QCOMPARE(spy.count(), 1); |
| 452 | QCOMPARE(qvariant_cast<Qt::Orientation>(spy.value(0).value(0)), Qt::Horizontal); |
| 453 | QCOMPARE(spy.value(0).value(1).toInt(), 2); |
| 454 | QCOMPARE(spy.value(0).value(2).toInt(), 2); |
| 455 | |
| 456 | QVERIFY(!model.setHeaderData(7, Qt::Horizontal, "foo" , Qt::ToolTipRole)); |
| 457 | QVERIFY(!model.headerData(7, Qt::Horizontal, Qt::ToolTipRole).isValid()); |
| 458 | |
| 459 | bool isToUpper = (dbType == QSqlDriver::Interbase) || (dbType == QSqlDriver::Oracle) || (dbType == QSqlDriver::DB2); |
| 460 | QCOMPARE(model.headerData(0, Qt::Horizontal).toString(), isToUpper ? QString("ID" ) : QString("id" )); |
| 461 | QCOMPARE(model.headerData(1, Qt::Horizontal).toString(), isToUpper ? QString("NAME" ) : QString("name" )); |
| 462 | QCOMPARE(model.headerData(2, Qt::Horizontal).toString(), QString("bar" )); |
| 463 | QVERIFY(model.headerData(3, Qt::Horizontal).isValid()); |
| 464 | } |
| 465 | |
| 466 | void tst_QSqlQueryModel::fetchMore() |
| 467 | { |
| 468 | QFETCH(QString, dbName); |
| 469 | QSqlDatabase db = QSqlDatabase::database(connectionName: dbName); |
| 470 | CHECK_DATABASE(db); |
| 471 | |
| 472 | QSqlQueryModel model; |
| 473 | QSignalSpy modelAboutToBeResetSpy(&model, SIGNAL(modelAboutToBeReset())); |
| 474 | QSignalSpy modelResetSpy(&model, SIGNAL(modelReset())); |
| 475 | |
| 476 | model.setQuery(QSqlQuery("select * from " + qTableName(prefix: "many" , __FILE__, db), db)); |
| 477 | int rowCount = model.rowCount(); |
| 478 | |
| 479 | QCOMPARE(modelAboutToBeResetSpy.count(), 1); |
| 480 | QCOMPARE(modelResetSpy.count(), 1); |
| 481 | |
| 482 | // If the driver doesn't return the query size fetchMore() causes the |
| 483 | // model to grow and new signals are emitted |
| 484 | QSignalSpy rowsInsertedSpy(&model, SIGNAL(rowsInserted(QModelIndex,int,int))); |
| 485 | if (!db.driver()->hasFeature(f: QSqlDriver::QuerySize)) { |
| 486 | model.fetchMore(); |
| 487 | int newRowCount = model.rowCount(); |
| 488 | QCOMPARE(rowsInsertedSpy.value(0).value(1).toInt(), rowCount); |
| 489 | QCOMPARE(rowsInsertedSpy.value(0).value(2).toInt(), newRowCount - 1); |
| 490 | } |
| 491 | } |
| 492 | |
| 493 | // For task 149491: When used with QSortFilterProxyModel, a view and a |
| 494 | // database that doesn't support the QuerySize feature, blank rows was |
| 495 | // appended if the query returned more than 256 rows and setQuery() |
| 496 | // was called more than once. This because an insertion of rows was |
| 497 | // triggered at the same time as the model was being cleared. |
| 498 | void tst_QSqlQueryModel::withSortFilterProxyModel() |
| 499 | { |
| 500 | QFETCH(QString, dbName); |
| 501 | QSqlDatabase db = QSqlDatabase::database(connectionName: dbName); |
| 502 | CHECK_DATABASE(db); |
| 503 | |
| 504 | if (db.driver()->hasFeature(f: QSqlDriver::QuerySize)) |
| 505 | QSKIP("Test applies only for drivers not reporting the query size." ); |
| 506 | |
| 507 | QSqlQueryModel model; |
| 508 | model.setQuery(QSqlQuery("SELECT * FROM " + qTableName(prefix: "test3" , __FILE__, db), db)); |
| 509 | QSortFilterProxyModel proxy; |
| 510 | proxy.setSourceModel(&model); |
| 511 | |
| 512 | QTableView view; |
| 513 | view.setModel(&proxy); |
| 514 | |
| 515 | QSignalSpy modelAboutToBeResetSpy(&model, SIGNAL(modelAboutToBeReset())); |
| 516 | QSignalSpy modelResetSpy(&model, SIGNAL(modelReset())); |
| 517 | QSignalSpy modelRowsInsertedSpy(&model, SIGNAL(rowsInserted(QModelIndex,int,int))); |
| 518 | model.setQuery(QSqlQuery("SELECT * FROM " + qTableName(prefix: "test3" , __FILE__, db), db)); |
| 519 | view.scrollToBottom(); |
| 520 | |
| 521 | QTestEventLoop::instance().enterLoop(secs: 1); |
| 522 | |
| 523 | QCOMPARE(proxy.rowCount(), 511); |
| 524 | |
| 525 | // setQuery() resets the model accompanied by begin and end signals |
| 526 | QCOMPARE(modelAboutToBeResetSpy.count(), 1); |
| 527 | QCOMPARE(modelResetSpy.count(), 1); |
| 528 | |
| 529 | // The call to scrollToBottom() forces the model to fetch additional rows. |
| 530 | QCOMPARE(modelRowsInsertedSpy.count(), 1); |
| 531 | QCOMPARE(modelRowsInsertedSpy.value(0).value(1).toInt(), 256); |
| 532 | QCOMPARE(modelRowsInsertedSpy.value(0).value(2).toInt(), 510); |
| 533 | } |
| 534 | |
| 535 | // For task 155402: When the model is already empty when setQuery() is called |
| 536 | // no rows have to be removed and rowsAboutToBeRemoved and rowsRemoved should |
| 537 | // not be emitted. |
| 538 | void tst_QSqlQueryModel::setQuerySignalEmission() |
| 539 | { |
| 540 | QFETCH(QString, dbName); |
| 541 | QSqlDatabase db = QSqlDatabase::database(connectionName: dbName); |
| 542 | CHECK_DATABASE(db); |
| 543 | |
| 544 | QSqlQueryModel model; |
| 545 | QSignalSpy modelAboutToBeResetSpy(&model, SIGNAL(modelAboutToBeReset())); |
| 546 | QSignalSpy modelResetSpy(&model, SIGNAL(modelReset())); |
| 547 | |
| 548 | // First select, the model was empty and no rows had to be removed, but model resets anyway. |
| 549 | model.setQuery(QSqlQuery("SELECT * FROM " + qTableName(prefix: "test" , __FILE__, db), db)); |
| 550 | QCOMPARE(modelAboutToBeResetSpy.count(), 1); |
| 551 | QCOMPARE(modelResetSpy.count(), 1); |
| 552 | |
| 553 | // Second select, the model wasn't empty and two rows had to be removed! |
| 554 | // setQuery() resets the model accompanied by begin and end signals |
| 555 | model.setQuery(QSqlQuery("SELECT * FROM " + qTableName(prefix: "test" , __FILE__, db), db)); |
| 556 | QCOMPARE(modelAboutToBeResetSpy.count(), 2); |
| 557 | QCOMPARE(modelResetSpy.count(), 2); |
| 558 | } |
| 559 | |
| 560 | // For task 170783: When the query's result set is empty no rows should be inserted, |
| 561 | // i.e. no rowsAboutToBeInserted or rowsInserted signals should be emitted. |
| 562 | void tst_QSqlQueryModel::setQueryWithNoRowsInResultSet() |
| 563 | { |
| 564 | QFETCH(QString, dbName); |
| 565 | QSqlDatabase db = QSqlDatabase::database(connectionName: dbName); |
| 566 | CHECK_DATABASE(db); |
| 567 | |
| 568 | QSqlQueryModel model; |
| 569 | QSignalSpy modelRowsAboutToBeInsertedSpy(&model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int))); |
| 570 | QSignalSpy modelRowsInsertedSpy(&model, SIGNAL(rowsInserted(QModelIndex,int,int))); |
| 571 | |
| 572 | // The query's result set will be empty so no signals should be emitted! |
| 573 | QSqlQuery query(db); |
| 574 | QVERIFY_SQL(query, exec("SELECT * FROM " + qTableName("test" , __FILE__, db) + " where 0 = 1" )); |
| 575 | model.setQuery(query); |
| 576 | QCOMPARE(modelRowsAboutToBeInsertedSpy.count(), 0); |
| 577 | QCOMPARE(modelRowsInsertedSpy.count(), 0); |
| 578 | } |
| 579 | |
| 580 | class NestedResetsTest: public QSqlQueryModel |
| 581 | { |
| 582 | Q_OBJECT |
| 583 | |
| 584 | public: |
| 585 | NestedResetsTest(QObject* parent = 0) : QSqlQueryModel(parent), gotAboutToBeReset(false), gotReset(false) |
| 586 | { |
| 587 | connect(sender: this, SIGNAL(modelAboutToBeReset()), receiver: this, SLOT(modelAboutToBeResetSlot())); |
| 588 | connect(sender: this, SIGNAL(modelReset()), receiver: this, SLOT(modelResetSlot())); |
| 589 | } |
| 590 | |
| 591 | void testNested() |
| 592 | { |
| 593 | // Only the outermost beginResetModel/endResetModel should |
| 594 | // emit signals. |
| 595 | gotAboutToBeReset = gotReset = false; |
| 596 | beginResetModel(); |
| 597 | QCOMPARE(gotAboutToBeReset, true); |
| 598 | QCOMPARE(gotReset, false); |
| 599 | |
| 600 | gotAboutToBeReset = gotReset = false; |
| 601 | beginResetModel(); |
| 602 | QCOMPARE(gotAboutToBeReset, false); |
| 603 | QCOMPARE(gotReset, false); |
| 604 | |
| 605 | gotAboutToBeReset = gotReset = false; |
| 606 | endResetModel(); |
| 607 | QCOMPARE(gotAboutToBeReset, false); |
| 608 | QCOMPARE(gotReset, false); |
| 609 | |
| 610 | gotAboutToBeReset = gotReset = false; |
| 611 | endResetModel(); |
| 612 | QCOMPARE(gotAboutToBeReset, false); |
| 613 | QCOMPARE(gotReset, true); |
| 614 | } |
| 615 | |
| 616 | void testClear() // QTBUG-49404: Basic test whether clear() emits signals. |
| 617 | { |
| 618 | gotAboutToBeReset = gotReset = false; |
| 619 | clear(); |
| 620 | QVERIFY(gotAboutToBeReset); |
| 621 | QVERIFY(gotReset); |
| 622 | } |
| 623 | |
| 624 | private slots: |
| 625 | void modelAboutToBeResetSlot() { gotAboutToBeReset = true; } |
| 626 | void modelResetSlot() { gotReset = true; } |
| 627 | |
| 628 | private: |
| 629 | bool gotAboutToBeReset; |
| 630 | bool gotReset; |
| 631 | }; |
| 632 | |
| 633 | void tst_QSqlQueryModel::nestedResets() |
| 634 | { |
| 635 | QFETCH(QString, dbName); |
| 636 | QSqlDatabase db = QSqlDatabase::database(connectionName: dbName); |
| 637 | CHECK_DATABASE(db); |
| 638 | |
| 639 | NestedResetsTest t; |
| 640 | t.testClear(); |
| 641 | t.testNested(); |
| 642 | } |
| 643 | |
| 644 | // For task 180617 |
| 645 | // According to the task, several specific duplicate SQL queries would cause |
| 646 | // multiple empty grid lines to be visible in the view |
| 647 | void tst_QSqlQueryModel::task_180617() |
| 648 | { |
| 649 | QFETCH(QString, dbName); |
| 650 | QSqlDatabase db = QSqlDatabase::database(connectionName: dbName); |
| 651 | CHECK_DATABASE(db); |
| 652 | const QString test3(qTableName(prefix: "test3" , __FILE__, db)); |
| 653 | |
| 654 | QTableView view; |
| 655 | QCOMPARE(view.columnAt(0), -1); |
| 656 | QCOMPARE(view.rowAt(0), -1); |
| 657 | |
| 658 | QSqlQueryModel model; |
| 659 | model.setQuery( query: "SELECT TOP 0 * FROM " + test3, db ); |
| 660 | view.setModel(&model); |
| 661 | |
| 662 | bool error = false; |
| 663 | // Usually a syntax error |
| 664 | if (model.lastError().isValid()) // usually a syntax error |
| 665 | error = true; |
| 666 | |
| 667 | QCOMPARE(view.columnAt(0), (error)?-1:0 ); |
| 668 | QCOMPARE(view.rowAt(0), -1); |
| 669 | |
| 670 | model.setQuery( query: "SELECT TOP 0 * FROM " + test3, db ); |
| 671 | model.setQuery( query: "SELECT TOP 0 * FROM " + test3, db ); |
| 672 | model.setQuery( query: "SELECT TOP 0 * FROM " + test3, db ); |
| 673 | model.setQuery( query: "SELECT TOP 0 * FROM " + test3, db ); |
| 674 | |
| 675 | QCOMPARE(view.columnAt(0), (error)?-1:0 ); |
| 676 | QCOMPARE(view.rowAt(0), -1); |
| 677 | } |
| 678 | |
| 679 | void tst_QSqlQueryModel::() |
| 680 | { |
| 681 | QSqlQueryModel plainModel; |
| 682 | QSortFilterProxyModel proxyModel; |
| 683 | proxyModel.setSourceModel(&plainModel); |
| 684 | QVERIFY(!plainModel.setHeaderData(0, Qt::Horizontal, QObject::tr("ID" ))); |
| 685 | // And it should not crash. |
| 686 | } |
| 687 | |
| 688 | QTEST_MAIN(tst_QSqlQueryModel) |
| 689 | #include "tst_qsqlquerymodel.moc" |
| 690 | |