Cache.h 7.94 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*****************************************************************************
 * Media Library
 *****************************************************************************
 * Copyright (C) 2015 Hugo Beauzée-Luyssen, Videolabs
 *
 * Authors: Hugo Beauzée-Luyssen<hugo@beauzee.fr>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
23
24
25
#ifndef CACHE_H
#define CACHE_H

26
#include <cassert>
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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();
        }
42
43
44
45
        static unsigned int key( const TYPE* self )
        {
            return self->id();
        }
46
        static unsigned int key( sqlite::Row& row )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
47
        {
48
            return row.load<unsigned int>( 0 );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
49
50
51
52
53
54
        }
};

/**
 * This utility class eases up the implementation of caching.
 *
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
55
56
57
58
59
 * 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.
60
 *   One that takes a sqlite::Row and one that takes an instance of the type being cached
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
61
62
63
64
65
 *
 * 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
66
67
68
69
70
 * - 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
71
 * - Inherit this class and specify the template parameter & policies accordingly
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
72
73
 * - Make this class a friend class of the class you inherit from
 */
74
template <typename IMPL, typename INTF, typename TABLEPOLICY, typename CACHEPOLICY = PrimaryKeyCacheKeyPolicy<IMPL> >
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
75
76
77
class Cache
{
    public:
78
        static std::shared_ptr<IMPL> fetch( DBConnection dbConnection, const typename CACHEPOLICY::KeyType& key )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
79
        {
80
            Lock lock( Mutex );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
81
82
83
            auto it = Store.find( key );
            if ( it != Store.end() )
                return it->second;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
84
            static const std::string req = "SELECT * FROM " + TABLEPOLICY::Name +
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
85
                            " WHERE " + TABLEPOLICY::CacheColumn + " = ?";
86
            auto res = sqlite::Tools::fetchOne<IMPL>( dbConnection, req, key );
87
88
            if ( res == nullptr )
                return nullptr;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
89
90
91
92
            Store[key] = res;
            return res;
        }

93
94
95
96
97
98
        template <typename... Args>
        static std::shared_ptr<IMPL> fetchOne( DBConnection dbConnection, const std::string& req, Args&&... args )
        {
            return sqlite::Tools::fetchOne<IMPL>( dbConnection, req, std::forward<Args>( args )... );
        }

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
99
100
101
102
        /*
         * Will fetch all elements from the database & cache them.
         *
         */
103
        static std::vector<std::shared_ptr<INTF>> fetchAll( DBConnection dbConnection )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
104
        {
105
            static const std::string req = "SELECT * FROM " + TABLEPOLICY::Name;
106
107
108
            // Lock the cache mutex before attempting to acquire a context, otherwise
            // we could have a thread locking cache then DB, and a thread locking DB then cache
            Lock lock( Mutex );
109
            return sqlite::Tools::fetchAll<IMPL, INTF>( dbConnection, req );
110
111
112
113
114
        }

        template <typename... Args>
        static std::vector<std::shared_ptr<INTF>> fetchAll( DBConnection dbConnection, const std::string &req, Args&&... args )
        {
115
            Lock lock( Mutex );
116
            return sqlite::Tools::fetchAll<IMPL, INTF>( dbConnection, req, std::forward<Args>( args )... );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
117
118
        }

119
        static std::shared_ptr<IMPL> load( DBConnection dbConnection, sqlite::Row& row )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
120
        {
121
            auto cacheKey = CACHEPOLICY::key( row );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
122

123
            Lock lock( Mutex );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
124
125
126
            auto it = Store.find( cacheKey );
            if ( it != Store.end() )
                return it->second;
127
            auto inst = std::make_shared<IMPL>( dbConnection, row );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
128
129
130
131
            Store[cacheKey] = inst;
            return inst;
        }

132
        static bool destroy( DBConnection dbConnection, const typename CACHEPOLICY::KeyType& key )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
133
        {
134
            Lock lock( Mutex );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
135
136
137
138
139
            auto it = Store.find( key );
            if ( it != Store.end() )
                Store.erase( it );
            static const std::string req = "DELETE FROM " + TABLEPOLICY::Name + " WHERE " +
                    TABLEPOLICY::CacheColumn + " = ?";
140
            return sqlite::Tools::executeDelete( dbConnection, req, key );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
141
142
        }

143
        static bool destroy( DBConnection dbConnection, const std::shared_ptr<IMPL>& self )
144
145
        {
            const auto& key = CACHEPOLICY::key( self );
146
            return destroy( dbConnection, key );
147
148
        }

149
        static bool destroy( DBConnection dbConnection, const IMPL* self )
150
151
        {
            const auto& key = CACHEPOLICY::key( self );
152
            return destroy( dbConnection, key );
153
154
        }

155
156
        static void clear()
        {
157
            Lock lock( Mutex );
158
159
160
            Store.clear();
        }

161
162
163
164
165
166
        /**
         * @brief discard Discard a record from the cache
         * @param key The key used for cache
         * @return
         */
        static bool discard( const std::shared_ptr<IMPL>& record )
167
        {
168
            auto key = CACHEPOLICY::key( record );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
169
            Lock lock( Mutex );
170
            auto it = Store.find( key );
171
            if ( it == Store.end() )
172
173
174
175
176
                return false;
            Store.erase( it );
            return true;
        }

177
178
179
180
181
    protected:
        /*
         * Create a new instance of the cache class.
         */
        template <typename... Args>
182
        static bool insert( DBConnection dbConnection, std::shared_ptr<IMPL> self, const std::string& req, Args&&... args )
183
        {
184
185
            Lock lock( Mutex );

186
            unsigned int pKey = sqlite::Tools::insert( dbConnection, req, std::forward<Args>( args )... );
187
            if ( pKey == 0 )
188
189
                return false;
            (self.get())->*TABLEPOLICY::PrimaryKey = pKey;
190
191
192
193
194
195
            auto cacheKey = CACHEPOLICY::key( self );

            // 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;
196
            return true;
197
198
        }

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
199
    private:
200
        static std::unordered_map<typename CACHEPOLICY::KeyType, std::shared_ptr<IMPL> > Store;
201
202
        static std::recursive_mutex Mutex;
        typedef std::lock_guard<std::recursive_mutex> Lock;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
203
204
205
};

template <typename IMPL, typename INTF, typename TABLEPOLICY, typename CACHEPOLICY>
206
std::unordered_map<typename CACHEPOLICY::KeyType, std::shared_ptr<IMPL> >
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
207
208
209
Cache<IMPL, INTF, TABLEPOLICY, CACHEPOLICY>::Store;

template <typename IMPL, typename INTF, typename TABLEPOLICY, typename CACHEPOLICY>
210
std::recursive_mutex Cache<IMPL, INTF, TABLEPOLICY, CACHEPOLICY>::Mutex;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
211
212

#endif // CACHE_H