forked from qt-creator/qt-creator
Sqlite: Add update hook and use it to get the last changed id
Sqlite has a function to get the last inserted rowid but very often you want to get the updated rowid too. Change-Id: Ie276a5039682813ad16597433996a2959f54d9ba Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
71
src/libs/sqlite/lastchangedrowid.h
Normal file
71
src/libs/sqlite/lastchangedrowid.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2020 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 "sqlitedatabaseinterface.h"
|
||||||
|
|
||||||
|
#include <utils/smallstringio.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace Sqlite {
|
||||||
|
|
||||||
|
class LastChangedRowId
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LastChangedRowId(DatabaseInterface &database,
|
||||||
|
Utils::SmallStringView databaseName,
|
||||||
|
Utils::SmallStringView tableName)
|
||||||
|
: database(database)
|
||||||
|
, databaseName(databaseName)
|
||||||
|
, tableName(tableName)
|
||||||
|
{
|
||||||
|
callback = [this](ChangeType, char const *database, char const *table, long long rowId) {
|
||||||
|
if (this->databaseName == database && this->tableName == table)
|
||||||
|
lastRowId = rowId;
|
||||||
|
};
|
||||||
|
|
||||||
|
database.setUpdateHook(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
~LastChangedRowId() { database.resetUpdateHook(); }
|
||||||
|
|
||||||
|
long long takeLastRowId()
|
||||||
|
{
|
||||||
|
long long rowId = lastRowId;
|
||||||
|
lastRowId = -1;
|
||||||
|
return rowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
DatabaseInterface &database;
|
||||||
|
DatabaseInterface::UpdateCallback callback;
|
||||||
|
Utils::SmallStringView databaseName;
|
||||||
|
Utils::SmallStringView tableName;
|
||||||
|
long long lastRowId = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Sqlite
|
||||||
@@ -25,6 +25,7 @@ SOURCES += \
|
|||||||
$$PWD/sqlitebasestatement.cpp
|
$$PWD/sqlitebasestatement.cpp
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
$$PWD/createtablesqlstatementbuilder.h \
|
$$PWD/createtablesqlstatementbuilder.h \
|
||||||
|
$$PWD/lastchangedrowid.h \
|
||||||
$$PWD/sqlitedatabasebackend.h \
|
$$PWD/sqlitedatabasebackend.h \
|
||||||
$$PWD/sqlitedatabaseinterface.h \
|
$$PWD/sqlitedatabaseinterface.h \
|
||||||
$$PWD/sqliteexception.h \
|
$$PWD/sqliteexception.h \
|
||||||
|
|||||||
@@ -114,6 +114,13 @@ public:
|
|||||||
m_databaseBackend.walCheckpointFull();
|
m_databaseBackend.walCheckpointFull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setUpdateHook(DatabaseBackend::UpdateCallback &callback)
|
||||||
|
{
|
||||||
|
m_databaseBackend.setUpdateHook(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetUpdateHook() { m_databaseBackend.resetUpdateHook(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void deferredBegin() override;
|
void deferredBegin() override;
|
||||||
void immediateBegin() override;
|
void immediateBegin() override;
|
||||||
|
|||||||
@@ -344,13 +344,11 @@ int indexOfPragma(Utils::SmallStringView pragma, const Utils::SmallStringView (&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr const Utils::SmallStringView journalModeStrings[] = {
|
const Utils::SmallStringView journalModeStrings[] = {"delete",
|
||||||
"delete",
|
"truncate",
|
||||||
"truncate",
|
"persist",
|
||||||
"persist",
|
"memory",
|
||||||
"memory",
|
"wal"};
|
||||||
"wal"
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils::SmallStringView DatabaseBackend::journalModeToPragma(JournalMode journalMode)
|
Utils::SmallStringView DatabaseBackend::journalModeToPragma(JournalMode journalMode)
|
||||||
{
|
{
|
||||||
@@ -367,11 +365,7 @@ JournalMode DatabaseBackend::pragmaToJournalMode(Utils::SmallStringView pragma)
|
|||||||
return static_cast<JournalMode>(index);
|
return static_cast<JournalMode>(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr const Utils::SmallStringView textEncodingStrings[] = {
|
const Utils::SmallStringView textEncodingStrings[] = {"UTF-8", "UTF-16le", "UTF-16be"};
|
||||||
"UTF-8",
|
|
||||||
"UTF-16le",
|
|
||||||
"UTF-16be"
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils::SmallStringView DatabaseBackend::textEncodingToPragma(TextEncoding textEncoding)
|
Utils::SmallStringView DatabaseBackend::textEncodingToPragma(TextEncoding textEncoding)
|
||||||
{
|
{
|
||||||
@@ -426,6 +420,29 @@ void DatabaseBackend::walCheckpointFull()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void updateCallback(
|
||||||
|
void *callback, int type, char const *database, char const *table, sqlite3_int64 row)
|
||||||
|
{
|
||||||
|
auto &function = *reinterpret_cast<DatabaseBackend::UpdateCallback *>(callback);
|
||||||
|
|
||||||
|
function(static_cast<ChangeType>(type), database, table, row);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void DatabaseBackend::setUpdateHook(UpdateCallback &callback)
|
||||||
|
{
|
||||||
|
if (callback)
|
||||||
|
sqlite3_update_hook(m_databaseHandle, updateCallback, &callback);
|
||||||
|
else
|
||||||
|
sqlite3_update_hook(m_databaseHandle, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseBackend::resetUpdateHook()
|
||||||
|
{
|
||||||
|
sqlite3_update_hook(m_databaseHandle, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
void DatabaseBackend::throwExceptionStatic(const char *whatHasHappens)
|
void DatabaseBackend::throwExceptionStatic(const char *whatHasHappens)
|
||||||
{
|
{
|
||||||
throw Exception(whatHasHappens);
|
throw Exception(whatHasHappens);
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ class Database;
|
|||||||
class SQLITE_EXPORT DatabaseBackend
|
class SQLITE_EXPORT DatabaseBackend
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using UpdateCallback
|
||||||
|
= std::function<void(ChangeType type, char const *, char const *, long long)>;
|
||||||
|
|
||||||
DatabaseBackend(Database &database);
|
DatabaseBackend(Database &database);
|
||||||
~DatabaseBackend();
|
~DatabaseBackend();
|
||||||
|
|
||||||
@@ -87,6 +90,9 @@ public:
|
|||||||
|
|
||||||
void walCheckpointFull();
|
void walCheckpointFull();
|
||||||
|
|
||||||
|
void setUpdateHook(UpdateCallback &callback);
|
||||||
|
void resetUpdateHook();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool databaseIsOpen() const;
|
bool databaseIsOpen() const;
|
||||||
|
|
||||||
@@ -128,7 +134,6 @@ private:
|
|||||||
Database &m_database;
|
Database &m_database;
|
||||||
sqlite3 *m_databaseHandle;
|
sqlite3 *m_databaseHandle;
|
||||||
TextEncoding m_cachedTextEncoding;
|
TextEncoding m_cachedTextEncoding;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Sqlite
|
} // namespace Sqlite
|
||||||
|
|||||||
@@ -25,11 +25,23 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <utils/smallstringview.h>
|
||||||
|
|
||||||
|
#include "sqliteglobal.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace Sqlite {
|
namespace Sqlite {
|
||||||
class DatabaseInterface
|
class DatabaseInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using UpdateCallback
|
||||||
|
= std::function<void(ChangeType type, char const *, char const *, long long)>;
|
||||||
|
|
||||||
virtual void walCheckpointFull() = 0;
|
virtual void walCheckpointFull() = 0;
|
||||||
|
virtual void execute(Utils::SmallStringView sqlStatement) = 0;
|
||||||
|
virtual void setUpdateHook(UpdateCallback &callback) = 0;
|
||||||
|
virtual void resetUpdateHook() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
~DatabaseInterface() = default;
|
~DatabaseInterface() = default;
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
|
|
||||||
#include <utils/smallstring.h>
|
#include <utils/smallstring.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace Sqlite {
|
namespace Sqlite {
|
||||||
|
|
||||||
class SQLITE_EXPORT Exception
|
class SQLITE_EXPORT Exception
|
||||||
|
|||||||
@@ -84,4 +84,6 @@ enum TextEncoding : char
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ChangeType : int { Delete = 9, Insert = 18, Update = 23 };
|
||||||
|
|
||||||
} // namespace Sqlite
|
} // namespace Sqlite
|
||||||
|
|||||||
108
tests/unit/unittest/lastchangedrowid-test.cpp
Normal file
108
tests/unit/unittest/lastchangedrowid-test.cpp
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2020 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 "mocksqlitedatabase.h"
|
||||||
|
|
||||||
|
#include <lastchangedrowid.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class LastChangedRowId : public testing::Test
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
NiceMock<MockSqliteDatabase> mockSqliteDatabase;
|
||||||
|
Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main", "foo"};
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(LastChangedRowId, SetUpdateHookInContructor)
|
||||||
|
{
|
||||||
|
EXPECT_CALL(mockSqliteDatabase, setUpdateHook(_));
|
||||||
|
|
||||||
|
Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main", "foo"};
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LastChangedRowId, ResetUpdateHookInDestructor)
|
||||||
|
{
|
||||||
|
EXPECT_CALL(mockSqliteDatabase, resetUpdateHook());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LastChangedRowId, GetMinusOneAsRowIdIfNoCallbackWasCalled)
|
||||||
|
{
|
||||||
|
ASSERT_THAT(lastRowId.lastRowId, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LastChangedRowId, CallbackSetsLastRowId)
|
||||||
|
{
|
||||||
|
lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42);
|
||||||
|
|
||||||
|
ASSERT_THAT(lastRowId.lastRowId, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LastChangedRowId, CallbackChecksDatabaseName)
|
||||||
|
{
|
||||||
|
lastRowId.callback(Sqlite::ChangeType::Update, "temp", "foo", 42);
|
||||||
|
|
||||||
|
ASSERT_THAT(lastRowId.lastRowId, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LastChangedRowId, CallbackChecksTableName)
|
||||||
|
{
|
||||||
|
lastRowId.callback(Sqlite::ChangeType::Update, "main", "bar", 42);
|
||||||
|
|
||||||
|
ASSERT_THAT(lastRowId.lastRowId, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LastChangedRowId, LastCallSetsRowId)
|
||||||
|
{
|
||||||
|
lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42);
|
||||||
|
lastRowId.callback(Sqlite::ChangeType::Insert, "main", "foo", 33);
|
||||||
|
|
||||||
|
lastRowId.callback(Sqlite::ChangeType::Delete, "main", "foo", 66);
|
||||||
|
|
||||||
|
ASSERT_THAT(lastRowId.lastRowId, 66);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LastChangedRowId, TakeLastRowId)
|
||||||
|
{
|
||||||
|
lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42);
|
||||||
|
|
||||||
|
auto id = lastRowId.takeLastRowId();
|
||||||
|
|
||||||
|
ASSERT_THAT(id, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LastChangedRowId, TakeLastRowIdResetsRowIdToMinusOne)
|
||||||
|
{
|
||||||
|
lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42);
|
||||||
|
lastRowId.takeLastRowId();
|
||||||
|
|
||||||
|
auto id = lastRowId.takeLastRowId();
|
||||||
|
|
||||||
|
ASSERT_THAT(id, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
@@ -59,5 +59,9 @@ public:
|
|||||||
void (bool));
|
void (bool));
|
||||||
|
|
||||||
MOCK_METHOD0(walCheckpointFull, void());
|
MOCK_METHOD0(walCheckpointFull, void());
|
||||||
|
|
||||||
|
MOCK_METHOD1(setUpdateHook, void(Sqlite::DatabaseInterface::UpdateCallback &));
|
||||||
|
|
||||||
|
MOCK_METHOD0(resetUpdateHook, void());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ protected:
|
|||||||
QString databaseFilePath{":memory:"};
|
QString databaseFilePath{":memory:"};
|
||||||
Sqlite::Database database;
|
Sqlite::Database database;
|
||||||
Sqlite::TransactionInterface &transactionInterface = database;
|
Sqlite::TransactionInterface &transactionInterface = database;
|
||||||
|
MockFunction<void(Sqlite::ChangeType tupe, char const *, char const *, long long)> callbackMock;
|
||||||
|
Sqlite::Database::UpdateCallback callback = callbackMock.AsStdFunction();
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(SqliteDatabase, SetDatabaseFilePath)
|
TEST_F(SqliteDatabase, SetDatabaseFilePath)
|
||||||
@@ -220,4 +222,89 @@ TEST_F(SqliteDatabase, Rollback)
|
|||||||
ASSERT_NO_THROW(transactionInterface.rollback());
|
ASSERT_NO_THROW(transactionInterface.rollback());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SqliteDatabase, SetUpdateHookSet)
|
||||||
|
{
|
||||||
|
database.setUpdateHook(callback);
|
||||||
|
|
||||||
|
EXPECT_CALL(callbackMock, Call(_, _, _, _));
|
||||||
|
Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SqliteDatabase, SetNullUpdateHook)
|
||||||
|
{
|
||||||
|
database.setUpdateHook(callback);
|
||||||
|
Sqlite::Database::UpdateCallback newCallback;
|
||||||
|
|
||||||
|
database.setUpdateHook(newCallback);
|
||||||
|
|
||||||
|
EXPECT_CALL(callbackMock, Call(_, _, _, _)).Times(0);
|
||||||
|
Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SqliteDatabase, ResetUpdateHook)
|
||||||
|
{
|
||||||
|
database.setUpdateHook(callback);
|
||||||
|
Sqlite::Database::UpdateCallback newCallback;
|
||||||
|
|
||||||
|
database.resetUpdateHook();
|
||||||
|
|
||||||
|
EXPECT_CALL(callbackMock, Call(_, _, _, _)).Times(0);
|
||||||
|
Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SqliteDatabase, DeleteUpdateHookCall)
|
||||||
|
{
|
||||||
|
Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42);
|
||||||
|
database.setUpdateHook(callback);
|
||||||
|
|
||||||
|
EXPECT_CALL(callbackMock, Call(Eq(Sqlite::ChangeType::Delete), _, _, _));
|
||||||
|
|
||||||
|
Sqlite::WriteStatement("DELETE FROM test WHERE name = 42", database).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SqliteDatabase, InsertUpdateHookCall)
|
||||||
|
{
|
||||||
|
database.setUpdateHook(callback);
|
||||||
|
|
||||||
|
EXPECT_CALL(callbackMock, Call(Eq(Sqlite::ChangeType::Insert), _, _, _));
|
||||||
|
|
||||||
|
Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SqliteDatabase, UpdateUpdateHookCall)
|
||||||
|
{
|
||||||
|
database.setUpdateHook(callback);
|
||||||
|
|
||||||
|
EXPECT_CALL(callbackMock, Call(Eq(Sqlite::ChangeType::Insert), _, _, _));
|
||||||
|
|
||||||
|
Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SqliteDatabase, RowIdUpdateHookCall)
|
||||||
|
{
|
||||||
|
database.setUpdateHook(callback);
|
||||||
|
|
||||||
|
EXPECT_CALL(callbackMock, Call(_, _, _, Eq(42)));
|
||||||
|
|
||||||
|
Sqlite::WriteStatement("INSERT INTO test(rowid, name) VALUES (?,?)", database).write(42, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SqliteDatabase, DatabaseUpdateHookCall)
|
||||||
|
{
|
||||||
|
database.setUpdateHook(callback);
|
||||||
|
|
||||||
|
EXPECT_CALL(callbackMock, Call(_, StrEq("main"), _, _));
|
||||||
|
|
||||||
|
Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SqliteDatabase, TableUpdateHookCall)
|
||||||
|
{
|
||||||
|
database.setUpdateHook(callback);
|
||||||
|
|
||||||
|
EXPECT_CALL(callbackMock, Call(_, _, StrEq("test"), _));
|
||||||
|
|
||||||
|
Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ SOURCES += \
|
|||||||
filepathview-test.cpp \
|
filepathview-test.cpp \
|
||||||
gtest-creator-printing.cpp \
|
gtest-creator-printing.cpp \
|
||||||
gtest-qt-printing.cpp \
|
gtest-qt-printing.cpp \
|
||||||
|
lastchangedrowid-test.cpp \
|
||||||
lineprefixer-test.cpp \
|
lineprefixer-test.cpp \
|
||||||
locatorfilter-test.cpp \
|
locatorfilter-test.cpp \
|
||||||
matchingtext-test.cpp \
|
matchingtext-test.cpp \
|
||||||
|
|||||||
Reference in New Issue
Block a user