mirror of
https://github.com/pocoproject/poco.git
synced 2025-03-25 16:13:42 +01:00
SQLite update event handling
This commit is contained in:
parent
4bcddad43e
commit
93c9e83e8d
@ -43,6 +43,7 @@
|
||||
#include "Poco/Data/SQLite/SQLite.h"
|
||||
#include "Poco/Data/MetaColumn.h"
|
||||
#include "Poco/Mutex.h"
|
||||
#include "Poco/Types.h"
|
||||
#include <map>
|
||||
|
||||
|
||||
@ -67,13 +68,9 @@ public:
|
||||
static const int THREAD_MODE_MULTI;
|
||||
static const int THREAD_MODE_SERIAL;
|
||||
|
||||
Utility();
|
||||
/// Maps SQLite column declared types to Poco::Data types through
|
||||
/// static TypeMap member.
|
||||
/// Note: SQLite is type-agnostic and it is the end-user responsibility
|
||||
/// to ensure that column declared data type corresponds to the type of
|
||||
/// data actually held in the database.
|
||||
/// Column types are case-insensitive.
|
||||
static const int OPERATION_INSERT;
|
||||
static const int OPERATION_DELETE;
|
||||
static const int OPERATION_UPDATE;
|
||||
|
||||
static std::string lastError(sqlite3* pDb);
|
||||
/// Retreives the last error code from sqlite and converts it to a string
|
||||
@ -111,10 +108,63 @@ public:
|
||||
static int getThreadMode();
|
||||
/// Returns the thread mode.
|
||||
|
||||
typedef void(*UpdateCallbackType)(void*, int, const char*, const char*, Poco::Int64);
|
||||
/// Update callback function type.
|
||||
|
||||
typedef int(*CommitCallbackType)(void*);
|
||||
/// Commit callback function type.
|
||||
|
||||
typedef void(*RollbackCallbackType)(void*);
|
||||
/// Rollback callback function type.
|
||||
|
||||
template <typename T, typename CBT>
|
||||
static bool registerUpdateHandler(sqlite3* pDB, CBT callbackFn, T* pParam)
|
||||
/// Registers the callback for (1)(insert, delete, update), (2)(commit) or
|
||||
/// or (3)(rollback) events. Only one function per group can be registered
|
||||
/// at a time. Registration is thread-safe. Storage pointed to by pParam
|
||||
/// must remain valid as long as registration is active. Registering with
|
||||
/// callbackFn set to zero disables notifications.
|
||||
///
|
||||
/// See http://www.sqlite.org/c3ref/update_hook.html and
|
||||
/// http://www.sqlite.org/c3ref/commit_hook.html for details.
|
||||
{
|
||||
typedef std::map<sqlite3*, T*> RetMap;
|
||||
|
||||
Poco::Mutex::ScopedLock l(_mutex);
|
||||
static RetMap retMap;
|
||||
T* pRet = reinterpret_cast<T*>(eventHook(pDB, callbackFn, pParam));
|
||||
|
||||
bool success = false;
|
||||
if (retMap.find(pDB) == retMap.end())
|
||||
success = (pRet == 0);
|
||||
else if (pRet != 0)
|
||||
success = (*pRet == *retMap[pDB]);
|
||||
|
||||
if (success) retMap[pDB] = pParam;
|
||||
return success;
|
||||
}
|
||||
|
||||
private:
|
||||
static TypeMap _types;
|
||||
Poco::FastMutex _mutex;
|
||||
static int _threadMode;
|
||||
Utility();
|
||||
/// Maps SQLite column declared types to Poco::Data types through
|
||||
/// static TypeMap member.
|
||||
///
|
||||
/// Note: SQLite is type-agnostic and it is the end-user responsibility
|
||||
/// to ensure that column declared data type corresponds to the type of
|
||||
/// data actually held in the database.
|
||||
///
|
||||
/// Column types are case-insensitive.
|
||||
|
||||
Utility(const Utility&);
|
||||
Utility& operator = (const Utility&);
|
||||
|
||||
static void* eventHook(sqlite3* pDB, UpdateCallbackType callbackFn, void* pParam);
|
||||
static void* eventHook(sqlite3* pDB, CommitCallbackType callbackFn, void* pParam);
|
||||
static void* eventHook(sqlite3* pDB, RollbackCallbackType callbackFn, void* pParam);
|
||||
|
||||
static TypeMap _types;
|
||||
static Poco::Mutex _mutex;
|
||||
static int _threadMode;
|
||||
};
|
||||
|
||||
|
||||
|
@ -65,14 +65,18 @@ int Utility::_threadMode =
|
||||
SQLITE_CONFIG_MULTITHREAD;
|
||||
#endif
|
||||
|
||||
const int Utility::OPERATION_INSERT = SQLITE_INSERT;
|
||||
const int Utility::OPERATION_DELETE = SQLITE_DELETE;
|
||||
const int Utility::OPERATION_UPDATE = SQLITE_UPDATE;
|
||||
|
||||
const std::string Utility::SQLITE_DATE_FORMAT = "%Y-%m-%d";
|
||||
const std::string Utility::SQLITE_TIME_FORMAT = "%H:%M:%S";
|
||||
Utility::TypeMap Utility::_types;
|
||||
|
||||
Poco::Mutex Utility::_mutex;
|
||||
|
||||
Utility::Utility()
|
||||
{
|
||||
Poco::FastMutex::ScopedLock l(_mutex);
|
||||
Poco::Mutex::ScopedLock l(_mutex);
|
||||
|
||||
if (_types.empty())
|
||||
{
|
||||
@ -299,4 +303,24 @@ bool Utility::setThreadMode(int mode)
|
||||
}
|
||||
|
||||
|
||||
void* Utility::eventHook(sqlite3* pDB, UpdateCallbackType callbackFn, void* pParam)
|
||||
{
|
||||
return sqlite3_update_hook(pDB, callbackFn, pParam);
|
||||
}
|
||||
|
||||
|
||||
void* Utility::eventHook(sqlite3* pDB, CommitCallbackType callbackFn, void* pParam)
|
||||
{
|
||||
return sqlite3_commit_hook(pDB, callbackFn, pParam);
|
||||
}
|
||||
|
||||
|
||||
void* Utility::eventHook(sqlite3* pDB, RollbackCallbackType callbackFn, void* pParam)
|
||||
{
|
||||
return sqlite3_rollback_hook(pDB, callbackFn, pParam);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} } } // namespace Poco::Data::SQLite
|
||||
|
@ -96,6 +96,7 @@ using Poco::Data::SQLite::ConstraintViolationException;
|
||||
using Poco::Data::SQLite::ParameterCountMismatchException;
|
||||
using Poco::Int32;
|
||||
using Poco::Dynamic::Var;
|
||||
using Poco::Data::SQLite::Utility;
|
||||
|
||||
|
||||
class Person
|
||||
@ -249,6 +250,11 @@ private:
|
||||
} } // namespace Poco::Data
|
||||
|
||||
|
||||
int SQLiteTest::_insertCounter;
|
||||
int SQLiteTest::_updateCounter;
|
||||
int SQLiteTest::_deleteCounter;
|
||||
|
||||
|
||||
SQLiteTest::SQLiteTest(const std::string& name): CppUnit::TestCase(name)
|
||||
{
|
||||
}
|
||||
@ -2655,6 +2661,124 @@ void SQLiteTest::testThreadModes()
|
||||
}
|
||||
|
||||
|
||||
void SQLiteTest::sqliteUpdateCallbackFn(void* pVal, int opCode, const char* pDB, const char* pTable, Poco::Int64 val)
|
||||
{
|
||||
poco_check_ptr(pVal);
|
||||
Poco::Int64* pV = reinterpret_cast<Poco::Int64*>(pVal);
|
||||
if (opCode == Utility::OPERATION_INSERT)
|
||||
{
|
||||
poco_assert (*pV == 2);
|
||||
poco_assert (val == 1);
|
||||
std::cout << "Inserted " << pDB << '.' << pTable << ", RowID=" << val << std::endl;
|
||||
++_insertCounter;
|
||||
}
|
||||
else if (opCode == Utility::OPERATION_UPDATE)
|
||||
{
|
||||
poco_assert (*pV == 3);
|
||||
poco_assert (val == 1);
|
||||
std::cout << "Updated " << pDB << '.' << pTable << ", RowID=" << val << std::endl;
|
||||
++_updateCounter;
|
||||
}
|
||||
else if (opCode == Utility::OPERATION_DELETE)
|
||||
{
|
||||
poco_assert (*pV == 4);
|
||||
poco_assert (val == 1);
|
||||
std::cout << "Deleted " << pDB << '.' << pTable << ", RowID=" << val << std::endl;
|
||||
++_deleteCounter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SQLiteTest::testUpdateCallback()
|
||||
{
|
||||
// will be updated by callback
|
||||
_insertCounter = 0;
|
||||
_updateCounter = 0;
|
||||
_deleteCounter = 0;
|
||||
|
||||
Session tmp (Poco::Data::SQLite::Connector::KEY, "dummy.db");
|
||||
assert (tmp.isConnected());
|
||||
sqlite3* pDB = AnyCast<sqlite3*>(tmp.getProperty("handle"));
|
||||
Poco::Int64 val = 1;
|
||||
assert (Utility::registerUpdateHandler(pDB, &sqliteUpdateCallbackFn, &val));
|
||||
|
||||
std::string tableName("Person");
|
||||
std::string lastName("lastname");
|
||||
std::string firstName("firstname");
|
||||
std::string address("Address");
|
||||
int age = 133132;
|
||||
int count = 0;
|
||||
std::string result;
|
||||
tmp << "DROP TABLE IF EXISTS Person", now;
|
||||
tmp << "CREATE TABLE IF NOT EXISTS Person (LastName VARCHAR(30), FirstName VARCHAR, Address VARCHAR, Age INTEGER(3))", now;
|
||||
tmp << "SELECT name FROM sqlite_master WHERE tbl_name=?", use(tableName), into(result), now;
|
||||
assert (result == tableName);
|
||||
|
||||
// insert
|
||||
val = 2;
|
||||
tmp << "INSERT INTO PERSON VALUES(:ln, :fn, :ad, :age)", use(lastName), use(firstName), use(address), use(age), now;
|
||||
tmp << "SELECT COUNT(*) FROM PERSON", into(count), now;
|
||||
assert (count == 1);
|
||||
assert (_insertCounter == 1);
|
||||
tmp << "SELECT LastName FROM PERSON", into(result), now;
|
||||
assert (lastName == result);
|
||||
tmp << "SELECT Age FROM PERSON", into(count), now;
|
||||
assert (count == age);
|
||||
|
||||
// update
|
||||
val = 3;
|
||||
tmp << "UPDATE PERSON SET Age = -1", now;
|
||||
tmp << "SELECT Age FROM PERSON", into(age), now;
|
||||
assert (-1 == age);
|
||||
assert (_updateCounter == 1);
|
||||
|
||||
// delete
|
||||
val =4;
|
||||
tmp << "DELETE FROM Person WHERE Age = -1", now;
|
||||
tmp << "SELECT COUNT(*) FROM PERSON", into(count), now;
|
||||
assert (count == 0);
|
||||
assert (_deleteCounter == 1);
|
||||
|
||||
// disarm callback and do the same drill
|
||||
assert (Utility::registerUpdateHandler(pDB, (Utility::UpdateCallbackType) 0, &val));
|
||||
|
||||
tmp << "DROP TABLE IF EXISTS Person", now;
|
||||
tmp << "CREATE TABLE IF NOT EXISTS Person (LastName VARCHAR(30), FirstName VARCHAR, Address VARCHAR, Age INTEGER(3))", now;
|
||||
tmp << "SELECT name FROM sqlite_master WHERE tbl_name=?", use(tableName), into(result), now;
|
||||
assert (result == tableName);
|
||||
|
||||
// must remain zero now
|
||||
_insertCounter = 0;
|
||||
_updateCounter = 0;
|
||||
_deleteCounter = 0;
|
||||
|
||||
// insert
|
||||
tmp << "INSERT INTO PERSON VALUES(:ln, :fn, :ad, :age)", use(lastName), use(firstName), use(address), use(age), now;
|
||||
tmp << "SELECT COUNT(*) FROM PERSON", into(count), now;
|
||||
assert (count == 1);
|
||||
assert (_insertCounter == 0);
|
||||
tmp << "SELECT LastName FROM PERSON", into(result), now;
|
||||
assert (lastName == result);
|
||||
tmp << "SELECT Age FROM PERSON", into(count), now;
|
||||
assert (count == age);
|
||||
|
||||
// update
|
||||
tmp << "UPDATE PERSON SET Age = -1", now;
|
||||
tmp << "SELECT Age FROM PERSON", into(age), now;
|
||||
assert (-1 == age);
|
||||
assert (_updateCounter == 0);
|
||||
|
||||
// delete
|
||||
tmp << "DELETE FROM Person WHERE Age = -1", now;
|
||||
tmp << "SELECT COUNT(*) FROM PERSON", into(count), now;
|
||||
assert (count == 0);
|
||||
assert (_deleteCounter == 0);
|
||||
|
||||
tmp.close();
|
||||
assert (!tmp.isConnected());
|
||||
}
|
||||
|
||||
|
||||
void SQLiteTest::setUp()
|
||||
{
|
||||
}
|
||||
@ -2747,6 +2871,7 @@ CppUnit::Test* SQLiteTest::suite()
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testReconnect);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testSystemTable);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testThreadModes);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testUpdateCallback);
|
||||
|
||||
return pSuite;
|
||||
}
|
||||
|
@ -136,12 +136,19 @@ public:
|
||||
|
||||
void testThreadModes();
|
||||
|
||||
void testUpdateCallback();
|
||||
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
static void sqliteUpdateCallbackFn(void*, int, const char*, const char*, Poco::Int64);
|
||||
|
||||
static CppUnit::Test* suite();
|
||||
|
||||
private:
|
||||
static int _insertCounter;
|
||||
static int _updateCounter;
|
||||
static int _deleteCounter;
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user