Sqlite: Add locking for sqlite transactions

If you use the database in a multi-threaded environment, you must always
your statements in transactions.

For read-only statements, you use DeferredTransaction and write statements
you use ImmediateTransaction. If you mix read and write statements you have
to use ImmediateTransaction. Don't use DeferredTransaction because it leads
to undefined behavior.

Change-Id: Ida298a20f33423c8da09e768a7b658dd8e918f46
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Marco Bubke
2017-09-19 11:48:47 +02:00
committed by Tim Jenssen
parent ee5efe06e6
commit 623135592c
8 changed files with 201 additions and 11 deletions

View File

@@ -31,6 +31,7 @@
#include <utils/smallstring.h> #include <utils/smallstring.h>
#include <mutex>
#include <vector> #include <vector>
namespace Sqlite { namespace Sqlite {
@@ -38,11 +39,13 @@ namespace Sqlite {
class SQLITE_EXPORT Database class SQLITE_EXPORT Database
{ {
template <typename Database> template <typename Database>
friend class SqliteAbstractTransaction; friend class AbstractTransaction;
friend class SqliteStatement; friend class Statement;
friend class SqliteBackend; friend class Backend;
public: public:
using MutexType = std::mutex;
Database(); Database();
Database(Utils::PathString &&databaseFilePath); Database(Utils::PathString &&databaseFilePath);
@@ -79,12 +82,13 @@ public:
private: private:
void initializeTables(); void initializeTables();
std::mutex &databaseMutex() { return m_databaseMutex; }
private: private:
Utils::PathString m_databaseFilePath;
DatabaseBackend m_databaseBackend; DatabaseBackend m_databaseBackend;
std::vector<Table> m_sqliteTables; std::vector<Table> m_sqliteTables;
Utils::PathString m_databaseFilePath; std::mutex m_databaseMutex;
JournalMode m_journalMode = JournalMode::Wal; JournalMode m_journalMode = JournalMode::Wal;
OpenMode m_openMode = OpenMode::ReadWrite; OpenMode m_openMode = OpenMode::ReadWrite;
bool m_isOpen = false; bool m_isOpen = false;

View File

@@ -27,6 +27,8 @@
#include "sqliteglobal.h" #include "sqliteglobal.h"
#include <mutex>
namespace Sqlite { namespace Sqlite {
class DatabaseBackend; class DatabaseBackend;
@@ -50,11 +52,13 @@ public:
protected: protected:
AbstractTransaction(Database &database) AbstractTransaction(Database &database)
: m_database(database) : m_databaseLock(database.databaseMutex()),
m_database(database)
{ {
} }
private: private:
std::lock_guard<typename Database::MutexType> m_databaseLock;
Database &m_database; Database &m_database;
bool m_isAlreadyCommited = false; bool m_isAlreadyCommited = false;
}; };

View File

@@ -0,0 +1,37 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "googletest.h"
class MockMutex
{
public:
using MutexType = MockMutex;
MOCK_METHOD0(lock, void ());
MOCK_METHOD0(unlock, void ());
};

View File

@@ -27,6 +27,8 @@
#include "googletest.h" #include "googletest.h"
#include "mockmutex.h"
#include <sqlitetable.h> #include <sqlitetable.h>
#include <utils/smallstringview.h> #include <utils/smallstringview.h>
@@ -34,8 +36,19 @@
class MockSqliteDatabase class MockSqliteDatabase
{ {
public: public:
using MutexType = MockMutex;
MockSqliteDatabase() = default;
MockSqliteDatabase(const MockMutex &mockMutex)
{
ON_CALL(*this, databaseMutex())
.WillByDefault(ReturnRef(const_cast<MockMutex &>(mockMutex)));
}
MOCK_METHOD1(execute, MOCK_METHOD1(execute,
void (Utils::SmallStringView sqlStatement)); void (Utils::SmallStringView sqlStatement));
MOCK_METHOD0(databaseMutex,
MockMutex &());
}; };

View File

@@ -0,0 +1,115 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "googletest.h"
#include <sqlitetransaction.h>
#include <mocksqlitedatabase.h>
namespace {
using DeferredTransaction = Sqlite::DeferredTransaction<MockSqliteDatabase>;
using ImmediateTransaction = Sqlite::ImmediateTransaction<MockSqliteDatabase>;
using ExclusiveTransaction = Sqlite::ExclusiveTransaction<MockSqliteDatabase>;
class SqliteTransaction : public testing::Test
{
protected:
MockMutex mockMutex;
MockSqliteDatabase mockDatabase{mockMutex};
};
TEST_F(SqliteTransaction, DeferredTransactionCommit)
{
EXPECT_CALL(mockDatabase, databaseMutex());
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN")));
EXPECT_CALL(mockDatabase, execute(Eq("COMMIT")));
EXPECT_CALL(mockMutex, unlock());
DeferredTransaction transaction{mockDatabase};
transaction.commit();
}
TEST_F(SqliteTransaction, DeferredTransactionRollBack)
{
EXPECT_CALL(mockDatabase, databaseMutex());
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN")));
EXPECT_CALL(mockDatabase, execute(Eq("ROLLBACK")));
EXPECT_CALL(mockMutex, unlock());
DeferredTransaction transaction{mockDatabase};
}
TEST_F(SqliteTransaction, ImmediateTransactionCommit)
{
EXPECT_CALL(mockDatabase, databaseMutex());
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE")));
EXPECT_CALL(mockDatabase, execute(Eq("COMMIT")));
EXPECT_CALL(mockMutex, unlock());
ImmediateTransaction transaction{mockDatabase};
transaction.commit();
}
TEST_F(SqliteTransaction, ImmediateTransactionRollBack)
{
EXPECT_CALL(mockDatabase, databaseMutex());
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE")));
EXPECT_CALL(mockDatabase, execute(Eq("ROLLBACK")));
EXPECT_CALL(mockMutex, unlock());
ImmediateTransaction transaction{mockDatabase};
}
TEST_F(SqliteTransaction, ExclusiveTransactionCommit)
{
EXPECT_CALL(mockDatabase, databaseMutex());
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN EXCLUSIVE")));
EXPECT_CALL(mockDatabase, execute(Eq("COMMIT")));
EXPECT_CALL(mockMutex, unlock());
ExclusiveTransaction transaction{mockDatabase};
transaction.commit();
}
TEST_F(SqliteTransaction, ExclusiveTransactionRollBack)
{
EXPECT_CALL(mockDatabase, databaseMutex());
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN EXCLUSIVE")));
EXPECT_CALL(mockDatabase, execute(Eq("ROLLBACK")));
EXPECT_CALL(mockMutex, unlock());
ExclusiveTransaction transaction{mockDatabase};
}
}

View File

@@ -42,7 +42,8 @@ using Sqlite::Table;
class StorageSqliteStatementFactory : public testing::Test class StorageSqliteStatementFactory : public testing::Test
{ {
protected: protected:
NiceMock<MockSqliteDatabase> mockDatabase; NiceMock<MockMutex> mockMutex;
NiceMock<MockSqliteDatabase> mockDatabase{mockMutex};
StatementFactory factory{mockDatabase}; StatementFactory factory{mockDatabase};
}; };
@@ -50,10 +51,12 @@ TEST_F(StorageSqliteStatementFactory, AddSymbolsTable)
{ {
InSequence s; InSequence s;
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE"))); EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE IF NOT EXISTS symbols(symbolId INTEGER PRIMARY KEY, usr TEXT, symbolName TEXT)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE IF NOT EXISTS symbols(symbolId INTEGER PRIMARY KEY, usr TEXT, symbolName TEXT)")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_symbols_usr ON symbols(usr)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_symbols_usr ON symbols(usr)")));
EXPECT_CALL(mockDatabase, execute(Eq("COMMIT"))); EXPECT_CALL(mockDatabase, execute(Eq("COMMIT")));
EXPECT_CALL(mockMutex, unlock());
factory.createSymbolsTable(); factory.createSymbolsTable();
} }
@@ -62,10 +65,12 @@ TEST_F(StorageSqliteStatementFactory, AddLocationsTable)
{ {
InSequence s; InSequence s;
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE"))); EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE IF NOT EXISTS locations(symbolId INTEGER, line INTEGER, column INTEGER, sourceId INTEGER)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE IF NOT EXISTS locations(symbolId INTEGER, line INTEGER, column INTEGER, sourceId INTEGER)")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_locations_sourceId ON locations(sourceId)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_locations_sourceId ON locations(sourceId)")));
EXPECT_CALL(mockDatabase, execute(Eq("COMMIT"))); EXPECT_CALL(mockDatabase, execute(Eq("COMMIT")));
EXPECT_CALL(mockMutex, unlock());
factory.createLocationsTable(); factory.createLocationsTable();
} }
@@ -74,9 +79,11 @@ TEST_F(StorageSqliteStatementFactory, AddSourcesTable)
{ {
InSequence s; InSequence s;
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE"))); EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE IF NOT EXISTS sources(sourceId INTEGER PRIMARY KEY, sourcePath TEXT)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE IF NOT EXISTS sources(sourceId INTEGER PRIMARY KEY, sourcePath TEXT)")));
EXPECT_CALL(mockDatabase, execute(Eq("COMMIT"))); EXPECT_CALL(mockDatabase, execute(Eq("COMMIT")));
EXPECT_CALL(mockMutex, unlock());
factory.createSourcesTable(); factory.createSourcesTable();
} }
@@ -85,11 +92,13 @@ TEST_F(StorageSqliteStatementFactory, AddNewSymbolsTable)
{ {
InSequence s; InSequence s;
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE"))); EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE TEMPORARY TABLE newSymbols(temporarySymbolId INTEGER PRIMARY KEY, symbolId INTEGER, usr TEXT, symbolName TEXT)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE TEMPORARY TABLE newSymbols(temporarySymbolId INTEGER PRIMARY KEY, symbolId INTEGER, usr TEXT, symbolName TEXT)")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_newSymbols_usr_symbolName ON newSymbols(usr, symbolName)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_newSymbols_usr_symbolName ON newSymbols(usr, symbolName)")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_newSymbols_symbolId ON newSymbols(symbolId)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_newSymbols_symbolId ON newSymbols(symbolId)")));
EXPECT_CALL(mockDatabase, execute(Eq("COMMIT"))); EXPECT_CALL(mockDatabase, execute(Eq("COMMIT")));
EXPECT_CALL(mockMutex, unlock());
factory.createNewSymbolsTable(); factory.createNewSymbolsTable();
} }
@@ -99,10 +108,12 @@ TEST_F(StorageSqliteStatementFactory, AddNewLocationsTable)
{ {
InSequence s; InSequence s;
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE"))); EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE TEMPORARY TABLE newLocations(temporarySymbolId INTEGER, symbolId INTEGER, line INTEGER, column INTEGER, sourceId INTEGER)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE TEMPORARY TABLE newLocations(temporarySymbolId INTEGER, symbolId INTEGER, line INTEGER, column INTEGER, sourceId INTEGER)")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_newLocations_sourceId ON newLocations(sourceId)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_newLocations_sourceId ON newLocations(sourceId)")));
EXPECT_CALL(mockDatabase, execute(Eq("COMMIT"))); EXPECT_CALL(mockDatabase, execute(Eq("COMMIT")));
EXPECT_CALL(mockMutex, unlock());
factory.createNewLocationsTable(); factory.createNewLocationsTable();
} }
@@ -111,6 +122,8 @@ TEST_F(StorageSqliteStatementFactory, AddTablesInConstructor)
{ {
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE"))).Times(5); EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE"))).Times(5);
EXPECT_CALL(mockDatabase, execute(Eq("COMMIT"))).Times(5); EXPECT_CALL(mockDatabase, execute(Eq("COMMIT"))).Times(5);
EXPECT_CALL(mockMutex, lock()).Times(5);
EXPECT_CALL(mockMutex, unlock()).Times(5);
EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE IF NOT EXISTS symbols(symbolId INTEGER PRIMARY KEY, usr TEXT, symbolName TEXT)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE IF NOT EXISTS symbols(symbolId INTEGER PRIMARY KEY, usr TEXT, symbolName TEXT)")));
EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_symbols_usr ON symbols(usr)"))); EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_symbols_usr ON symbols(usr)")));

View File

@@ -59,9 +59,9 @@ protected:
protected: protected:
FilePathCache<std::mutex> filePathCache; FilePathCache<std::mutex> filePathCache;
NiceMock<MockSqliteDatabase> mockDatabase; NiceMock<MockMutex> mockMutex;
NiceMock<MockSqliteDatabase> mockDatabase{mockMutex};
StatementFactory statementFactory{mockDatabase}; StatementFactory statementFactory{mockDatabase};
MockSqliteWriteStatement &insertSymbolsToNewSymbolsStatement = statementFactory.insertSymbolsToNewSymbolsStatement; MockSqliteWriteStatement &insertSymbolsToNewSymbolsStatement = statementFactory.insertSymbolsToNewSymbolsStatement;
MockSqliteWriteStatement &insertLocationsToNewLocationsStatement = statementFactory.insertLocationsToNewLocationsStatement; MockSqliteWriteStatement &insertLocationsToNewLocationsStatement = statementFactory.insertLocationsToNewLocationsStatement;
MockSqliteWriteStatement &insertSourcesStatement = statementFactory.insertSourcesStatement; MockSqliteWriteStatement &insertSourcesStatement = statementFactory.insertSourcesStatement;
@@ -172,6 +172,7 @@ TEST_F(SymbolStorage, AddSymbolsAndSourceLocationsCallsWrite)
{ {
InSequence sequence; InSequence sequence;
EXPECT_CALL(mockMutex, lock());
EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE"))); EXPECT_CALL(mockDatabase, execute(Eq("BEGIN IMMEDIATE")));
EXPECT_CALL(insertSymbolsToNewSymbolsStatement, write(_, _, _)).Times(2); EXPECT_CALL(insertSymbolsToNewSymbolsStatement, write(_, _, _)).Times(2);
EXPECT_CALL(insertLocationsToNewLocationsStatement, write(1, 42, 23, 3)); EXPECT_CALL(insertLocationsToNewLocationsStatement, write(1, 42, 23, 3));
@@ -188,6 +189,7 @@ TEST_F(SymbolStorage, AddSymbolsAndSourceLocationsCallsWrite)
EXPECT_CALL(deleteNewSymbolsTableStatement, execute()); EXPECT_CALL(deleteNewSymbolsTableStatement, execute());
EXPECT_CALL(deleteNewLocationsTableStatement, execute()); EXPECT_CALL(deleteNewLocationsTableStatement, execute());
EXPECT_CALL(mockDatabase, execute(Eq("COMMIT"))); EXPECT_CALL(mockDatabase, execute(Eq("COMMIT")));
EXPECT_CALL(mockMutex, unlock());
storage.addSymbolsAndSourceLocations(symbolEntries, sourceLocations); storage.addSymbolsAndSourceLocations(symbolEntries, sourceLocations);
} }

View File

@@ -74,7 +74,8 @@ SOURCES += \
symbolquery-test.cpp \ symbolquery-test.cpp \
storagesqlitestatementfactory-test.cpp \ storagesqlitestatementfactory-test.cpp \
querysqlitestatementfactory-test.cpp \ querysqlitestatementfactory-test.cpp \
sqliteindex-test.cpp sqliteindex-test.cpp \
sqlitetransaction-test.cpp
!isEmpty(LIBCLANG_LIBS) { !isEmpty(LIBCLANG_LIBS) {
SOURCES += \ SOURCES += \
@@ -201,7 +202,8 @@ HEADERS += \
mocksqlitereadstatement.h \ mocksqlitereadstatement.h \
google-using-declarations.h \ google-using-declarations.h \
mocksymbolindexing.h \ mocksymbolindexing.h \
sqliteteststatement.h sqliteteststatement.h \
mockmutex.h
!isEmpty(LIBCLANG_LIBS) { !isEmpty(LIBCLANG_LIBS) {
HEADERS += \ HEADERS += \