Cache.h 5.34 KB
Newer Older
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
1 2 3
#ifndef CACHE_H
#define CACHE_H

4
#include <cassert>
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#include <memory>
#include <mutex>
#include <unordered_map>

#include "SqliteTools.h"

template <typename TYPE>
class PrimaryKeyCacheKeyPolicy
{
    public:
        typedef unsigned int KeyType;
        static unsigned int key( const std::shared_ptr<TYPE>& self )
        {
            return self->id();
        }
        static unsigned int key( sqlite3_stmt* stmt )
        {
            return Traits<unsigned int>::Load( stmt, 0 );
        }
};

/**
 * This utility class eases up the implementation of caching.
 *
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
29 30 31 32 33 34 35 36 37 38 39
 * 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:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
40 41 42 43 44
 * - 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:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
45
 * - Inherit this class and specify the template parameter & policies accordingly
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
46 47
 * - Make this class a friend class of the class you inherit from
 */
48
template <typename IMPL, typename INTF, typename TABLEPOLICY, typename CACHEPOLICY = PrimaryKeyCacheKeyPolicy<IMPL> >
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
49 50 51 52 53 54 55 56 57
class Cache
{
    public:
        static std::shared_ptr<IMPL> fetch( sqlite3* dbConnection, const typename CACHEPOLICY::KeyType& key )
        {
            std::lock_guard<std::mutex> lock( Mutex );
            auto it = Store.find( key );
            if ( it != Store.end() )
                return it->second;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
58
            static const std::string req = "SELECT * FROM " + TABLEPOLICY::Name +
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
59 60 61 62 63 64 65 66 67 68 69 70
                            " WHERE " + TABLEPOLICY::CacheColumn + " = ?";
            auto res = SqliteTools::fetchOne<IMPL>( 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.
         */
71
        static bool fetchAll( sqlite3* dbConnection, std::vector<std::shared_ptr<INTF> >& res )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
72
        {
73
            static const std::string req = "SELECT * FROM " + TABLEPOLICY::Name;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
            return SqliteTools::fetchAll<IMPL, INTF>( dbConnection, req.c_str(), res );
        }

        static std::shared_ptr<IMPL> load( sqlite3* dbConnection, sqlite3_stmt* stmt )
        {
            auto cacheKey = CACHEPOLICY::key( stmt );

            std::lock_guard<std::mutex> lock( Mutex );
            auto it = Store.find( cacheKey );
            if ( it != Store.end() )
                return it->second;
            auto inst = std::make_shared<IMPL>( dbConnection, stmt );
            Store[cacheKey] = inst;
            return inst;
        }

90
        static bool destroy( sqlite3* dbConnection, const typename CACHEPOLICY::KeyType& key )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
91 92 93 94 95 96 97
        {
            std::lock_guard<std::mutex> 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 + " = ?";
98
            return SqliteTools::executeDelete( dbConnection, req.c_str(), key );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
99 100
        }

101 102 103 104 105 106
        static bool destroy( sqlite3* dbConnection, const std::shared_ptr<IMPL>& self )
        {
            const auto& key = CACHEPOLICY::key( self );
            return destroy( dbConnection, key );
        }

107 108 109 110 111 112
        static void clear()
        {
            std::lock_guard<std::mutex> lock( Mutex );
            Store.clear();
        }

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
    protected:
        /*
         * Create a new instance of the cache class.
         */
        template <typename... Args>
        static unsigned int insert( sqlite3* dbConnection, std::shared_ptr<IMPL> 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<std::mutex> 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;
        }

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
133
    private:
134
        static std::unordered_map<typename CACHEPOLICY::KeyType, std::shared_ptr<IMPL> > Store;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
135 136 137 138
        static std::mutex Mutex;
};

template <typename IMPL, typename INTF, typename TABLEPOLICY, typename CACHEPOLICY>
139
std::unordered_map<typename CACHEPOLICY::KeyType, std::shared_ptr<IMPL> >
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
140 141 142 143 144 145
Cache<IMPL, INTF, TABLEPOLICY, CACHEPOLICY>::Store;

template <typename IMPL, typename INTF, typename TABLEPOLICY, typename CACHEPOLICY>
std::mutex Cache<IMPL, INTF, TABLEPOLICY, CACHEPOLICY>::Mutex;

#endif // CACHE_H