#ifndef CACHE_H #define CACHE_H #include #include #include #include #include "SqliteTools.h" template class PrimaryKeyCacheKeyPolicy { public: typedef unsigned int KeyType; static unsigned int key( const std::shared_ptr& self ) { return self->id(); } static unsigned int key( sqlite3_stmt* stmt ) { return Traits::Load( stmt, 0 ); } }; /** * This utility class eases up the implementation of caching. * * It is driven by 2 policy classes: * - TABLEPOLICY describes the basics required to fetch a record. It has 2 static fields: * - Name: the table name * - CacheColumn: The column used to cache records. * - CACHEPOLICY describes which column to use for caching by providing two "key" methods. * One that takes a sqlite3_stmt and one that takes an instance of the type being cached * * The default CACHEPOLICY implementation bases itself on an unsigned int column, assumed * to be the primary key, at index 0 of a fetch statement. * * Other template parameter: * - IMPL: The actual implementation. Typically the type that inherits this class * - INTF: An interface that express the type. This is used when fetching containers, as they * are not polymorphic. * * How to use it: * - Inherit this class and specify the template parameter & policies accordingly * - Make this class a friend class of the class you inherit from */ template > class Cache { public: static std::shared_ptr fetch( sqlite3* dbConnection, const typename CACHEPOLICY::KeyType& key ) { std::lock_guard lock( Mutex ); auto it = Store.find( key ); if ( it != Store.end() ) return it->second; static const std::string req = "SELECT * FROM " + TABLEPOLICY::Name + " WHERE " + TABLEPOLICY::CacheColumn + " = ?"; auto res = SqliteTools::fetchOne( dbConnection, req.c_str(), key ); Store[key] = res; return res; } /* * Will fetch all elements from the database & cache them. * * @param res A reference to the result vector. All existing elements will * be discarded. */ static bool fetchAll( sqlite3* dbConnection, std::vector >& res ) { static const std::string req = "SELECT * FROM " + TABLEPOLICY::Name; return SqliteTools::fetchAll( dbConnection, req.c_str(), res ); } static std::shared_ptr load( sqlite3* dbConnection, sqlite3_stmt* stmt ) { auto cacheKey = CACHEPOLICY::key( stmt ); std::lock_guard lock( Mutex ); auto it = Store.find( cacheKey ); if ( it != Store.end() ) return it->second; auto inst = std::make_shared( dbConnection, stmt ); Store[cacheKey] = inst; return inst; } static bool destroy( sqlite3* dbConnection, const typename CACHEPOLICY::KeyType& key ) { std::lock_guard lock( Mutex ); auto it = Store.find( key ); if ( it != Store.end() ) Store.erase( it ); static const std::string req = "DELETE FROM " + TABLEPOLICY::Name + " WHERE " + TABLEPOLICY::CacheColumn + " = ?"; return SqliteTools::executeDelete( dbConnection, req.c_str(), key ); } static bool destroy( sqlite3* dbConnection, const std::shared_ptr& self ) { const auto& key = CACHEPOLICY::key( self ); return destroy( dbConnection, key ); } static void clear() { std::lock_guard lock( Mutex ); Store.clear(); } protected: /* * Create a new instance of the cache class. */ template static unsigned int insert( sqlite3* dbConnection, std::shared_ptr self, const char* req, const Args&... args ) { unsigned int pKey = SqliteTools::insert( dbConnection, req, args... ); if ( pKey == 0 ) return 0; auto cacheKey = CACHEPOLICY::key( self ); std::lock_guard lock( Mutex ); // We expect the cache column to be PRIMARY KEY / UNIQUE, so an insertion with // a duplicated key should have been rejected by sqlite. This indicates an invalid state assert( Store.find( cacheKey ) == Store.end() ); Store[cacheKey] = self; return pKey; } private: static std::unordered_map > Store; static std::mutex Mutex; }; template std::unordered_map > Cache::Store; template std::mutex Cache::Mutex; #endif // CACHE_H