Files
qt-creator/tests/unit/unittest/sqlitedatabase-test.cpp
Marco Bubke 8330d4463a Sqlite: Fix that prepare is not checking that the database is locked
The database has to be locked because otherwise it is not thread save.
Because Sqlite connections are already called in different threads this
has to be enforced. The easiest way to lock a database is to put it in a
transaction.

It should be ok without locking so long the connection is only called
from one thread but I prefer to enforce it because I have already seen
code was preparing a statement not on initialization stage where it is
highly unlikely that there is more than one thread pre connection but in
the middle of the code. Because preparing is much more expensive than a
lock I prefer to enfore a lock here.

Change-Id: Id0b47f8d615a6697bb807392cafbe976bdd37233
Reviewed-by: Vikas Pachdha <vikas.pachdha@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
2023-05-30 11:20:00 +00:00

390 lines
11 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 "googletest.h"
#include "spydummy.h"
#include <sqlitedatabase.h>
#include <sqliteprogresshandler.h>
#include <sqlitereadstatement.h>
#include <sqlitetable.h>
#include <sqlitewritestatement.h>
#include <utils/temporarydirectory.h>
#include <QSignalSpy>
#include <QTemporaryFile>
#include <QVariant>
#include <functional>
namespace {
using testing::Contains;
using Sqlite::ColumnType;
using Sqlite::JournalMode;
using Sqlite::OpenMode;
using Sqlite::Table;
class SqliteDatabase : public ::testing::Test
{
protected:
SqliteDatabase()
{
table.setName("test");
table.addColumn("id", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}});
table.addColumn("name");
table.initialize(database);
}
~SqliteDatabase()
{
if (database.isOpen())
database.close();
}
std::vector<Utils::SmallString> names() const
{
return Sqlite::ReadStatement<1>("SELECT name FROM test", database).values<Utils::SmallString>(8);
}
static void updateHookCallback(
void *object, int type, char const *database, char const *table, long long rowId)
{
static_cast<SqliteDatabase *>(object)->callback(static_cast<Sqlite::ChangeType>(type),
database,
table,
rowId);
}
protected:
SpyDummy spyDummy;
Table table;
mutable Sqlite::Database database{":memory:", JournalMode::Memory};
Sqlite::TransactionInterface &transactionInterface = database;
MockFunction<void(Sqlite::ChangeType tupe, char const *, char const *, long long)> callbackMock;
std::function<void(Sqlite::ChangeType tupe, char const *, char const *, long long)>
callback = callbackMock.AsStdFunction();
std::unique_lock<Sqlite::Database> lock{database};
};
TEST_F(SqliteDatabase, SetDatabaseFilePath)
{
ASSERT_THAT(database.databaseFilePath(), ":memory:");
}
TEST_F(SqliteDatabase, SetJournalMode)
{
database.setJournalMode(JournalMode::Memory);
ASSERT_THAT(database.journalMode(), JournalMode::Memory);
}
TEST_F(SqliteDatabase, LockingModeIsByDefaultExlusive)
{
ASSERT_THAT(database.lockingMode(), Sqlite::LockingMode::Exclusive);
}
TEST_F(SqliteDatabase, CreateDatabaseWithLockingModeNormal)
{
Utils::PathString path{Utils::TemporaryDirectory::masterDirectoryPath()
+ "/database_exclusive_locked.db"};
Sqlite::Database database{path, JournalMode::Wal, Sqlite::LockingMode::Normal};
ASSERT_THAT(Sqlite::withImmediateTransaction(database, [&] { return database.lockingMode(); }),
Sqlite::LockingMode::Normal);
}
TEST_F(SqliteDatabase, ExclusivelyLockedDatabaseIsLockedForSecondConnection)
{
using namespace std::chrono_literals;
Utils::PathString path{Utils::TemporaryDirectory::masterDirectoryPath()
+ "/database_exclusive_locked.db"};
Sqlite::Database database{path};
ASSERT_THROW(Sqlite::Database database2(path, 1ms), Sqlite::StatementIsBusy);
}
TEST_F(SqliteDatabase, NormalLockedDatabaseCanBeReopened)
{
Utils::PathString path{Utils::TemporaryDirectory::masterDirectoryPath()
+ "/database_exclusive_locked.db"};
Sqlite::Database database{path, JournalMode::Wal, Sqlite::LockingMode::Normal};
ASSERT_NO_THROW((Sqlite::Database{path, JournalMode::Wal, Sqlite::LockingMode::Normal}));
}
TEST_F(SqliteDatabase, SetOpenlMode)
{
database.setOpenMode(OpenMode::ReadOnly);
ASSERT_THAT(database.openMode(), OpenMode::ReadOnly);
}
TEST_F(SqliteDatabase, OpenDatabase)
{
database.close();
database.open();
ASSERT_TRUE(database.isOpen());
}
TEST_F(SqliteDatabase, CloseDatabase)
{
database.close();
ASSERT_FALSE(database.isOpen());
}
TEST_F(SqliteDatabase, DatabaseIsNotInitializedAfterOpening)
{
ASSERT_FALSE(database.isInitialized());
}
TEST_F(SqliteDatabase, DatabaseIsIntializedAfterSettingItBeforeOpening)
{
database.setIsInitialized(true);
ASSERT_TRUE(database.isInitialized());
}
TEST_F(SqliteDatabase, DatabaseIsInitializedIfDatabasePathExistsAtOpening)
{
Sqlite::Database database{TESTDATA_DIR "/sqlite_database.db"};
ASSERT_TRUE(database.isInitialized());
}
TEST_F(SqliteDatabase, DatabaseIsNotInitializedIfDatabasePathDoesNotExistAtOpening)
{
Sqlite::Database database{Utils::PathString{Utils::TemporaryDirectory::masterDirectoryPath()
+ "/database_does_not_exist.db"}};
ASSERT_FALSE(database.isInitialized());
}
TEST_F(SqliteDatabase, GetChangesCount)
{
Sqlite::WriteStatement<1> statement("INSERT INTO test(name) VALUES (?)", database);
statement.write(42);
ASSERT_THAT(database.changesCount(), 1);
}
TEST_F(SqliteDatabase, GetTotalChangesCount)
{
Sqlite::WriteStatement<1> statement("INSERT INTO test(name) VALUES (?)", database);
statement.write(42);
ASSERT_THAT(database.lastInsertedRowId(), 1);
}
TEST_F(SqliteDatabase, GetLastInsertedRowId)
{
Sqlite::WriteStatement<1> statement("INSERT INTO test(name) VALUES (?)", database);
statement.write(42);
ASSERT_THAT(database.lastInsertedRowId(), 1);
}
TEST_F(SqliteDatabase, LastRowId)
{
database.setLastInsertedRowId(42);
ASSERT_THAT(database.lastInsertedRowId(), 42);
}
TEST_F(SqliteDatabase, DeferredBegin)
{
ASSERT_NO_THROW(transactionInterface.deferredBegin());
transactionInterface.commit();
}
TEST_F(SqliteDatabase, ImmediateBegin)
{
ASSERT_NO_THROW(transactionInterface.immediateBegin());
transactionInterface.commit();
}
TEST_F(SqliteDatabase, ExclusiveBegin)
{
ASSERT_NO_THROW(transactionInterface.exclusiveBegin());
transactionInterface.commit();
}
TEST_F(SqliteDatabase, Commit)
{
transactionInterface.deferredBegin();
ASSERT_NO_THROW(transactionInterface.commit());
}
TEST_F(SqliteDatabase, Rollback)
{
transactionInterface.deferredBegin();
ASSERT_NO_THROW(transactionInterface.rollback());
}
TEST_F(SqliteDatabase, SetUpdateHookSet)
{
database.setUpdateHook(this, updateHookCallback);
EXPECT_CALL(callbackMock, Call(_, _, _, _));
Sqlite::WriteStatement<1>("INSERT INTO test(name) VALUES (?)", database).write(42);
}
TEST_F(SqliteDatabase, SetNullUpdateHook)
{
database.setUpdateHook(this, updateHookCallback);
database.setUpdateHook(nullptr, nullptr);
EXPECT_CALL(callbackMock, Call(_, _, _, _)).Times(0);
Sqlite::WriteStatement<1>("INSERT INTO test(name) VALUES (?)", database).write(42);
}
TEST_F(SqliteDatabase, ResetUpdateHook)
{
database.setUpdateHook(this, updateHookCallback);
database.resetUpdateHook();
EXPECT_CALL(callbackMock, Call(_, _, _, _)).Times(0);
Sqlite::WriteStatement<1>("INSERT INTO test(name) VALUES (?)", database).write(42);
}
TEST_F(SqliteDatabase, DeleteUpdateHookCall)
{
Sqlite::WriteStatement<1>("INSERT INTO test(name) VALUES (?)", database).write(42);
database.setUpdateHook(this, updateHookCallback);
EXPECT_CALL(callbackMock, Call(Eq(Sqlite::ChangeType::Delete), _, _, _));
Sqlite::WriteStatement("DELETE FROM test WHERE name = 42", database).execute();
}
TEST_F(SqliteDatabase, InsertUpdateHookCall)
{
database.setUpdateHook(this, updateHookCallback);
EXPECT_CALL(callbackMock, Call(Eq(Sqlite::ChangeType::Insert), _, _, _));
Sqlite::WriteStatement<1>("INSERT INTO test(name) VALUES (?)", database).write(42);
}
TEST_F(SqliteDatabase, UpdateUpdateHookCall)
{
database.setUpdateHook(this, updateHookCallback);
EXPECT_CALL(callbackMock, Call(Eq(Sqlite::ChangeType::Insert), _, _, _));
Sqlite::WriteStatement<1>("INSERT INTO test(name) VALUES (?)", database).write(42);
}
TEST_F(SqliteDatabase, RowIdUpdateHookCall)
{
database.setUpdateHook(this, updateHookCallback);
EXPECT_CALL(callbackMock, Call(_, _, _, Eq(42)));
Sqlite::WriteStatement<2>("INSERT INTO test(rowid, name) VALUES (?,?)", database).write(42, "foo");
}
TEST_F(SqliteDatabase, DatabaseUpdateHookCall)
{
database.setUpdateHook(this, updateHookCallback);
EXPECT_CALL(callbackMock, Call(_, StrEq("main"), _, _));
Sqlite::WriteStatement<1>("INSERT INTO test(name) VALUES (?)", database).write(42);
}
TEST_F(SqliteDatabase, TableUpdateHookCall)
{
database.setUpdateHook(this, updateHookCallback);
EXPECT_CALL(callbackMock, Call(_, _, StrEq("test"), _));
Sqlite::WriteStatement<1>("INSERT INTO test(name) VALUES (?)", database).write(42);
}
TEST_F(SqliteDatabase, SessionsCommit)
{
database.setAttachedTables({"test"});
Sqlite::WriteStatement<2>("INSERT INTO test(id, name) VALUES (?,?)", database).write(1, "foo");
database.unlock();
Sqlite::ImmediateSessionTransaction transaction{database};
Sqlite::WriteStatement<2>("INSERT INTO test(id, name) VALUES (?,?)", database).write(2, "bar");
transaction.commit();
database.lock();
Sqlite::WriteStatement<2>("INSERT OR REPLACE INTO test(id, name) VALUES (?,?)", database)
.write(2, "hoo");
database.applyAndUpdateSessions();
ASSERT_THAT(names(), UnorderedElementsAre("foo", "bar"));
}
TEST_F(SqliteDatabase, SessionsRollback)
{
database.setAttachedTables({"test"});
Sqlite::WriteStatement<2>("INSERT INTO test(id, name) VALUES (?,?)", database).write(1, "foo");
database.unlock();
{
Sqlite::ImmediateSessionTransaction transaction{database};
Sqlite::WriteStatement<2>("INSERT INTO test(id, name) VALUES (?,?)", database).write(2, "bar");
}
database.lock();
Sqlite::WriteStatement<2>("INSERT OR REPLACE INTO test(id, name) VALUES (?,?)", database)
.write(2, "hoo");
database.applyAndUpdateSessions();
ASSERT_THAT(names(), UnorderedElementsAre("foo", "hoo"));
}
TEST_F(SqliteDatabase, ProgressHandlerInterrupts)
{
Sqlite::WriteStatement<1> statement("INSERT INTO test(name) VALUES (?)", database);
lock.unlock();
Sqlite::ProgressHandler handler{[] { return Sqlite::Progress::Interrupt; }, 1, database};
lock.lock();
ASSERT_THROW(statement.write(42), Sqlite::ExecutionInterrupted);
lock.unlock();
}
TEST_F(SqliteDatabase, ProgressHandlerContinues)
{
Sqlite::WriteStatement<1> statement("INSERT INTO test(name) VALUES (?)", database);
lock.unlock();
Sqlite::ProgressHandler handler{[] { return Sqlite::Progress::Continue; }, 1, database};
lock.lock();
ASSERT_NO_THROW(statement.write(42));
lock.unlock();
}
TEST_F(SqliteDatabase, ProgressHandlerResetsAfterLeavingScope)
{
lock.unlock();
{
Sqlite::ProgressHandler handler{[] { return Sqlite::Progress::Interrupt; }, 1, database};
}
lock.lock();
Sqlite::WriteStatement<1> statement("INSERT INTO test(name) VALUES (?)", database);
ASSERT_NO_THROW(statement.write(42));
}
} // namespace