1c6bf3e09e
This is a semantic patch using ClangTidyTransformator as in qtbase/df9d882d41b741fef7c5beeddb0abe9d904443d8, but extended to handle typedefs and accesses through pointers, too: const std::string o = "object"; auto hasTypeIgnoringPointer = [](auto type) { return anyOf(hasType(type), hasType(pointsTo(type))); }; auto derivedFromAnyOfClasses = [&](ArrayRef<StringRef> classes) { auto exprOfDeclaredType = [&](auto decl) { return expr(hasTypeIgnoringPointer(hasUnqualifiedDesugaredType(recordType(hasDeclaration(decl))))).bind(o); }; return exprOfDeclaredType(cxxRecordDecl(isSameOrDerivedFrom(hasAnyName(classes)))); }; auto renameMethod = [&] (ArrayRef<StringRef> classes, StringRef from, StringRef to) { return makeRule(cxxMemberCallExpr(on(derivedFromAnyOfClasses(classes)), callee(cxxMethodDecl(hasName(from), parameterCountIs(0)))), changeTo(cat(access(o, cat(to)), "()")), cat("use '", to, "' instead of '", from, "'")); }; renameMethod(<classes>, "count", "size"); renameMethod(<classes>, "length", "size"); except that the on() matcher has been replaced by one that doesn't ignoreParens(). a.k.a qt-port-to-std-compatible-api V5 with config Scope: 'Container'. Added two NOLINTNEXTLINEs in tst_qbitarray and tst_qcontiguouscache, to avoid porting calls that explicitly test count(). Change-Id: Icfb8808c2ff4a30187e9935a51cad26987451c22 Reviewed-by: Ivan Solovev <ivan.solovev@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
488 lines
14 KiB
C++
488 lines
14 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
|
|
#include <QTest>
|
|
|
|
|
|
#include "../qsqldatabase/tst_databases.h"
|
|
|
|
#include <QtCore>
|
|
#include <QtSql>
|
|
#include "qdebug.h"
|
|
|
|
QString qtest;
|
|
// set this define if Oracle is built with threading support
|
|
//#define QOCI_THREADED
|
|
|
|
class tst_QSqlThread : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
tst_QSqlThread();
|
|
virtual ~tst_QSqlThread();
|
|
|
|
|
|
void dropTestTables();
|
|
void createTestTables();
|
|
void recreateTestTables();
|
|
void repopulateTestTables();
|
|
|
|
void generic_data(const QString &engine=QString());
|
|
tst_Databases dbs;
|
|
|
|
public slots:
|
|
void initTestCase();
|
|
void cleanupTestCase();
|
|
void init();
|
|
void cleanup();
|
|
|
|
protected slots:
|
|
void threadFinished() {
|
|
++threadFinishedCount;
|
|
qDebug("Thread finished, total finished: %d", threadFinishedCount);
|
|
}
|
|
|
|
private slots:
|
|
void simpleThreading_data() { generic_data(); }
|
|
void simpleThreading();
|
|
void readWriteThreading_data() { generic_data(); }
|
|
void readWriteThreading();
|
|
void readFromSingleConnection_data() { generic_data(); }
|
|
void readFromSingleConnection();
|
|
void readWriteFromSingleConnection_data() { generic_data(); }
|
|
void readWriteFromSingleConnection();
|
|
void preparedReadWriteFromSingleConnection_data() { generic_data(); }
|
|
void preparedReadWriteFromSingleConnection();
|
|
void transactionsFromSingleConnection_data() { generic_data(); }
|
|
void transactionsFromSingleConnection();
|
|
|
|
private:
|
|
int threadFinishedCount;
|
|
};
|
|
|
|
static QAtomicInt counter;
|
|
|
|
class QtTestSqlThread : public QThread
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
QtTestSqlThread(const QSqlDatabase &aDb, QObject *parent = nullptr)
|
|
: QThread(parent), sourceDb(aDb) {}
|
|
|
|
void runHelper(const QString &dbName)
|
|
{
|
|
QSqlDatabase db = QSqlDatabase::cloneDatabase(sourceDb, dbName);
|
|
QVERIFY_SQL(db, open());
|
|
|
|
int sum = 0;
|
|
QSqlQuery q("select id from " + qtest, db);
|
|
QVERIFY_SQL(q, isActive());
|
|
while (q.next())
|
|
sum += q.value(0).toInt();
|
|
QCOMPARE(sum, 6);
|
|
q.clear();
|
|
}
|
|
|
|
void run() override
|
|
{
|
|
QString dbName = QString("QThreadDb%1").arg((size_t)currentThreadId());
|
|
runHelper(dbName);
|
|
|
|
QSqlDatabase::database(dbName).close();
|
|
QSqlDatabase::removeDatabase(dbName);
|
|
}
|
|
|
|
private:
|
|
QSqlDatabase sourceDb;
|
|
};
|
|
|
|
enum { ProdConIterations = 10 };
|
|
|
|
class SqlProducer: public QThread
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
SqlProducer(const QSqlDatabase &aDb, QObject *parent = nullptr)
|
|
: QThread(parent), sourceDb(aDb) {}
|
|
|
|
void runHelper(const QString &dbName)
|
|
{
|
|
QSqlDatabase db = QSqlDatabase::cloneDatabase(sourceDb, dbName);
|
|
QVERIFY_SQL(db, open());
|
|
QSqlQuery q(db);
|
|
QVERIFY_SQL(q, prepare("insert into " + qtest + " values (?, ?, ?)"));
|
|
int id = 10;
|
|
for (int i = 0; i < ProdConIterations; ++i) {
|
|
q.bindValue(0, ++id);
|
|
q.bindValue(1, "threaddy");
|
|
q.bindValue(2, 10);
|
|
QVERIFY_SQL(q, exec());
|
|
QThread::yieldCurrentThread();
|
|
}
|
|
}
|
|
|
|
void run() override
|
|
{
|
|
QString dbName = QString("Producer%1").arg((size_t)currentThreadId());
|
|
runHelper(dbName);
|
|
QSqlDatabase::database(dbName).close();
|
|
QSqlDatabase::removeDatabase(dbName);
|
|
}
|
|
private:
|
|
QSqlDatabase sourceDb;
|
|
};
|
|
|
|
class SqlConsumer: public QThread
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
SqlConsumer(const QSqlDatabase &aDb, QObject *parent = nullptr)
|
|
: QThread(parent), sourceDb(aDb) {}
|
|
|
|
void runHelper(const QString &dbName)
|
|
{
|
|
QSqlDatabase db = QSqlDatabase::cloneDatabase(sourceDb, dbName);
|
|
QVERIFY_SQL(db, open());
|
|
QSqlQuery q1(db), q2(db);
|
|
QVERIFY_SQL(q2, prepare("delete from " + qtest + " where id = :id"));
|
|
|
|
for (int i = 0; i < ProdConIterations; ++i) {
|
|
QVERIFY_SQL(q1, exec("select max(id) from " + qtest));
|
|
q1.first();
|
|
q2.bindValue(":id", q1.value(0));
|
|
q1.clear();
|
|
QVERIFY_SQL(q2, exec());
|
|
QThread::yieldCurrentThread();
|
|
}
|
|
}
|
|
|
|
void run() override
|
|
{
|
|
QString dbName = QString("Consumer%1").arg((size_t)currentThreadId());
|
|
runHelper(dbName);
|
|
QSqlDatabase::database(dbName).close();
|
|
QSqlDatabase::removeDatabase(dbName);
|
|
}
|
|
|
|
private:
|
|
QSqlDatabase sourceDb;
|
|
};
|
|
|
|
class SqlThread: public QThread
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
enum Mode { SimpleReading, PreparedReading, SimpleWriting, PreparedWriting };
|
|
|
|
SqlThread(Mode m, const QSqlDatabase &db, QObject *parent = nullptr)
|
|
: QThread(parent), sourceDb(db), mode(m) {}
|
|
|
|
void run() override
|
|
{
|
|
QSqlDatabase &db = sourceDb;
|
|
switch (mode) {
|
|
case SimpleReading: {
|
|
// Executes a Query for reading, iterates over the first 4 results
|
|
QSqlQuery q(sourceDb);
|
|
for (int j = 0; j < ProdConIterations; ++j) {
|
|
QVERIFY_SQL(q, exec("select id,name from " + qtest + " order by id"));
|
|
for (int i = 1; i < 4; ++i) {
|
|
QVERIFY_SQL(q, next());
|
|
QCOMPARE(q.value(0).toInt(), i);
|
|
}
|
|
}
|
|
break; }
|
|
case SimpleWriting: {
|
|
// Executes a query for writing (appends a new row)
|
|
QSqlQuery q(sourceDb);
|
|
for (int j = 0; j < ProdConIterations; ++j) {
|
|
QVERIFY_SQL(q, exec(QString("insert into " + qtest
|
|
+ " (id, name) values(%1, '%2')")
|
|
.arg(counter.fetchAndAddRelaxed(1)).arg("Robert")));
|
|
}
|
|
break; }
|
|
case PreparedReading: {
|
|
// Prepares a query for reading and iterates over the results
|
|
QSqlQuery q(sourceDb);
|
|
QVERIFY_SQL(q, prepare("select id, name from " + qtest + " where id = ?"));
|
|
for (int j = 0; j < ProdConIterations; ++j) {
|
|
q.addBindValue(j % 3 + 1);
|
|
QVERIFY_SQL(q, exec());
|
|
QVERIFY_SQL(q, next());
|
|
QCOMPARE(q.value(0).toInt(), j % 3 + 1);
|
|
}
|
|
break; }
|
|
case PreparedWriting: {
|
|
QSqlQuery q(sourceDb);
|
|
QVERIFY_SQL(q, prepare("insert into " + qtest + " (id, name) "
|
|
"values(?, ?)"));
|
|
for (int i = 0; i < ProdConIterations; ++i) {
|
|
q.addBindValue(counter.fetchAndAddRelaxed(1));
|
|
q.addBindValue("Robert");
|
|
QVERIFY_SQL(q, exec());
|
|
}
|
|
break; }
|
|
}
|
|
}
|
|
|
|
private:
|
|
QSqlDatabase sourceDb;
|
|
Mode mode;
|
|
};
|
|
|
|
|
|
tst_QSqlThread::tst_QSqlThread()
|
|
: threadFinishedCount(0)
|
|
{
|
|
static QSqlDatabase static_qtest_db = QSqlDatabase();
|
|
qtest = qTableName("qtest", __FILE__, static_qtest_db);
|
|
}
|
|
|
|
tst_QSqlThread::~tst_QSqlThread()
|
|
{
|
|
}
|
|
|
|
void tst_QSqlThread::generic_data(const QString& engine)
|
|
{
|
|
if ( dbs.fillTestTable(engine) == 0 ) {
|
|
if(engine.isEmpty())
|
|
QSKIP( "No database drivers are available in this Qt configuration");
|
|
else
|
|
QSKIP( (QString("No database drivers of type %1 are available in this Qt configuration").arg(engine)).toLocal8Bit());
|
|
}
|
|
}
|
|
|
|
void tst_QSqlThread::dropTestTables()
|
|
{
|
|
for (int i = 0; i < dbs.dbNames.size(); ++i) {
|
|
QSqlDatabase db = QSqlDatabase::database(dbs.dbNames.at(i));
|
|
QSqlQuery q(db);
|
|
|
|
tst_Databases::safeDropTables(db, QStringList() << qtest << qTableName("qtest2", __FILE__, db) << qTableName("emptytable", __FILE__, db));
|
|
}
|
|
}
|
|
|
|
void tst_QSqlThread::createTestTables()
|
|
{
|
|
for (int i = 0; i < dbs.dbNames.size(); ++i) {
|
|
QSqlDatabase db = QSqlDatabase::database(dbs.dbNames.at(i));
|
|
QSqlQuery q(db);
|
|
|
|
QVERIFY_SQL(q, exec("create table " + qtest
|
|
+ "(id int NOT NULL primary key, name varchar(20), title int)"));
|
|
|
|
QVERIFY_SQL(q, exec("create table " + qTableName("qtest2", __FILE__, db)
|
|
+ "(id int NOT NULL primary key, title varchar(20))"));
|
|
|
|
QVERIFY_SQL(q, exec("create table " + qTableName("emptytable", __FILE__, db)
|
|
+ "(id int NOT NULL primary key)"));
|
|
}
|
|
}
|
|
|
|
void tst_QSqlThread::repopulateTestTables()
|
|
{
|
|
for (int i = 0; i < dbs.dbNames.size(); ++i) {
|
|
QSqlDatabase db = QSqlDatabase::database(dbs.dbNames.at(i));
|
|
QSqlQuery q(db);
|
|
|
|
QVERIFY_SQL(q, exec("delete from " + qtest));
|
|
QVERIFY_SQL(q, exec("insert into " + qtest + " values(1, 'harry', 1)"));
|
|
QVERIFY_SQL(q, exec("insert into " + qtest + " values(2, 'trond', 2)"));
|
|
QVERIFY_SQL(q, exec("insert into " + qtest + " values(3, 'vohi', 3)"));
|
|
|
|
QVERIFY_SQL(q, exec("delete from " + qTableName("qtest2", __FILE__, db)));
|
|
QVERIFY_SQL(q, exec("insert into " + qTableName("qtest2", __FILE__, db) + " values(1, 'herr')"));
|
|
QVERIFY_SQL(q, exec("insert into " + qTableName("qtest2", __FILE__, db) + " values(2, 'mister')"));
|
|
}
|
|
}
|
|
|
|
void tst_QSqlThread::recreateTestTables()
|
|
{
|
|
dropTestTables();
|
|
createTestTables();
|
|
repopulateTestTables();
|
|
}
|
|
|
|
void tst_QSqlThread::initTestCase()
|
|
{
|
|
QVERIFY(dbs.open());
|
|
recreateTestTables();
|
|
}
|
|
|
|
void tst_QSqlThread::cleanupTestCase()
|
|
{
|
|
dropTestTables();
|
|
dbs.close();
|
|
}
|
|
|
|
void tst_QSqlThread::init()
|
|
{
|
|
threadFinishedCount = 0;
|
|
counter.storeRelaxed(4);
|
|
}
|
|
|
|
void tst_QSqlThread::cleanup()
|
|
{
|
|
// repopulateTestTables();
|
|
}
|
|
|
|
// This test creates two threads that clone their db connection and read
|
|
// from it
|
|
void tst_QSqlThread::simpleThreading()
|
|
{
|
|
QFETCH(QString, dbName);
|
|
QSqlDatabase db = QSqlDatabase::database(dbName);
|
|
CHECK_DATABASE(db);
|
|
|
|
if (db.databaseName() == ":memory:")
|
|
QSKIP("does not work with in-memory databases");
|
|
|
|
QtTestSqlThread t1(db);
|
|
QtTestSqlThread t2(db);
|
|
|
|
connect(&t1, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection);
|
|
connect(&t2, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection);
|
|
|
|
t1.start();
|
|
t2.start();
|
|
|
|
QTRY_VERIFY(threadFinishedCount >= 2);
|
|
}
|
|
|
|
// This test creates two threads that clone their db connection and read
|
|
// or write
|
|
void tst_QSqlThread::readWriteThreading()
|
|
{
|
|
QFETCH(QString, dbName);
|
|
QSqlDatabase db = QSqlDatabase::database(dbName);
|
|
CHECK_DATABASE(db);
|
|
|
|
if (db.databaseName() == ":memory:")
|
|
QSKIP("does not work with in-memory databases");
|
|
else if (tst_Databases::isMSAccess(db))
|
|
QSKIP("does not work with MS Access databases");
|
|
|
|
SqlProducer producer(db);
|
|
SqlConsumer consumer(db);
|
|
|
|
connect(&producer, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection);
|
|
connect(&consumer, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection);
|
|
|
|
producer.start();
|
|
consumer.start();
|
|
|
|
QTRY_VERIFY_WITH_TIMEOUT(threadFinishedCount >= 2, 10000);
|
|
}
|
|
|
|
#ifdef QOCI_THREADED
|
|
// run with n threads in parallel. Change this constant to hammer the poor DB server even more
|
|
static const int maxThreadCount = 4;
|
|
#endif
|
|
|
|
void tst_QSqlThread::readFromSingleConnection()
|
|
{
|
|
#ifdef QOCI_THREADED
|
|
QFETCH(QString, dbName);
|
|
QSqlDatabase db = QSqlDatabase::database(dbName);
|
|
CHECK_DATABASE(db);
|
|
|
|
if (db.databaseName() == ":memory:")
|
|
QSKIP("does not work with in-memory databases");
|
|
|
|
QObject cleanupHelper; // make sure the threads die when we exit the scope
|
|
for (int i = 0; i < maxThreadCount; ++i) {
|
|
SqlThread *reader = new SqlThread(SqlThread::SimpleReading, db, &cleanupHelper);
|
|
connect(reader, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection);
|
|
reader->start();
|
|
}
|
|
|
|
QTRY_VERIFY(threadFinishedCount >= maxThreadCount);
|
|
#endif
|
|
}
|
|
|
|
void tst_QSqlThread::readWriteFromSingleConnection()
|
|
{
|
|
#ifdef QOCI_THREADED
|
|
QFETCH(QString, dbName);
|
|
QSqlDatabase db = QSqlDatabase::database(dbName);
|
|
CHECK_DATABASE(db);
|
|
|
|
if (db.databaseName() == ":memory:")
|
|
QSKIP("does not work with in-memory databases");
|
|
|
|
QObject cleanupHelper;
|
|
for (int i = 0; i < maxThreadCount; ++i) {
|
|
SqlThread *reader = new SqlThread(SqlThread::SimpleReading, db, &cleanupHelper);
|
|
connect(reader, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection);
|
|
reader->start();
|
|
|
|
SqlThread *writer = new SqlThread(SqlThread::SimpleWriting, db, &cleanupHelper);
|
|
connect(writer, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection);
|
|
writer->start();
|
|
}
|
|
|
|
QTRY_VERIFY(threadFinishedCount >= maxThreadCount * 2);
|
|
#endif
|
|
}
|
|
|
|
void tst_QSqlThread::preparedReadWriteFromSingleConnection()
|
|
{
|
|
#ifdef QOCI_THREADED
|
|
QFETCH(QString, dbName);
|
|
QSqlDatabase db = QSqlDatabase::database(dbName);
|
|
CHECK_DATABASE(db);
|
|
|
|
if (db.databaseName() == ":memory:")
|
|
QSKIP("does not work with in-memory databases");
|
|
|
|
QObject cleanupHelper;
|
|
for (int i = 0; i < maxThreadCount; ++i) {
|
|
SqlThread *reader = new SqlThread(SqlThread::PreparedReading, db, &cleanupHelper);
|
|
connect(reader, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection);
|
|
reader->start();
|
|
|
|
SqlThread *writer = new SqlThread(SqlThread::PreparedWriting, db, &cleanupHelper);
|
|
connect(writer, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection);
|
|
writer->start();
|
|
}
|
|
|
|
QTRY_VERIFY(threadFinishedCount >= maxThreadCount * 2);
|
|
#endif
|
|
}
|
|
|
|
void tst_QSqlThread::transactionsFromSingleConnection()
|
|
{
|
|
#ifdef QOCI_THREADED
|
|
QFETCH(QString, dbName);
|
|
QSqlDatabase db = QSqlDatabase::database(dbName);
|
|
CHECK_DATABASE(db);
|
|
|
|
if (db.databaseName() == ":memory:")
|
|
QSKIP("does not work with in-memory databases");
|
|
|
|
// start and commit a transaction
|
|
QVERIFY_SQL(db, db.transaction());
|
|
preparedReadWriteFromSingleConnection(); // read and write from multiple threads
|
|
if (QTest::currentTestFailed())
|
|
return;
|
|
QVERIFY_SQL(db, db.commit());
|
|
|
|
// reset test environment
|
|
threadFinishedCount = 0;
|
|
|
|
// start and roll back a transaction
|
|
QVERIFY_SQL(db, db.transaction());
|
|
preparedReadWriteFromSingleConnection(); // read and write from multiple threads
|
|
if (QTest::currentTestFailed())
|
|
return;
|
|
QVERIFY_SQL(db, db.rollback());
|
|
#endif
|
|
}
|
|
|
|
QTEST_MAIN(tst_QSqlThread)
|
|
#include "tst_qsqlthread.moc"
|