SqliteTools.h 8.29 KB
Newer Older
1
2
3
#ifndef SQLITETOOLS_H
#define SQLITETOOLS_H

4
#include <cassert>
5
#include <cstring>
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
6
#include <memory>
7
8
#include <sqlite3.h>
#include <string>
9
#include <vector>
10
#include <iostream>
11

12
13
#include "Types.h"

14
15
16
namespace sqlite
{

17
18
19
20
21
22
struct ForeignKey
{
    constexpr ForeignKey(unsigned int v) : value(v) {}
    unsigned int value;
};

23
24
25
template <typename ToCheck, typename T>
using IsSameDecay = std::is_same<typename std::decay<ToCheck>::type, T>;

26
27
28
template <typename T, typename Enable = void>
struct Traits;

29
template <typename T>
30
31
32
struct Traits<T, typename std::enable_if<std::is_integral<
        typename std::decay<T>::type
    >::value>::type>
33
34
{
    static constexpr
35
    int (*Bind)(sqlite3_stmt *, int, int) = &sqlite3_bind_int;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
36
37

    static constexpr
38
    int (*Load)(sqlite3_stmt *, int) = &sqlite3_column_int;
39
40
};

41
42
template <typename T>
struct Traits<T, typename std::enable_if<IsSameDecay<T, ForeignKey>::value>::type>
43
44
45
46
47
48
49
50
51
{
    static int Bind( sqlite3_stmt *stmt, int pos, ForeignKey fk)
    {
        if ( fk.value != 0 )
            return Traits<unsigned int>::Bind( stmt, pos, fk.value );
        return sqlite3_bind_null( stmt, pos );
    }
};

52
53
template <typename T>
struct Traits<T, typename std::enable_if<IsSameDecay<T, std::string>::value>::type>
54
55
56
57
{
    static int Bind(sqlite3_stmt* stmt, int pos, const std::string& value )
    {
        char* tmp = strdup( value.c_str() );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
58
59
60
        return sqlite3_bind_text( stmt, pos, tmp, -1, free );
    }

61
    static std::string Load( sqlite3_stmt* stmt, int pos )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
62
    {
63
64
65
66
        auto tmp = (const char*)sqlite3_column_text( stmt, pos );
        if ( tmp != nullptr )
            return std::string( tmp );
        return std::string();
67
68
69
    }
};

70
template <typename T>
71
72
73
struct Traits<T, typename std::enable_if<std::is_floating_point<
        typename std::decay<T>::type
    >::value>::type>
74
75
76
77
78
79
80
81
{
        static constexpr int
        (*Bind)(sqlite3_stmt *, int, double) = &sqlite3_bind_double;

        static constexpr double
        (*Load)(sqlite3_stmt *, int) = &sqlite3_column_double;
};

82
83
84
85
86
87
88
89
90
template <>
struct Traits<std::nullptr_t>
{
    static int Bind(sqlite3_stmt* stmt, int idx, std::nullptr_t)
    {
        return sqlite3_bind_null( stmt, idx );
    }
};

91
class Tools
92
{
93
94
    private:
        typedef std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt*)> StmtPtr;
95
    public:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
96
97
98
99
100
101
102
        /**
         * Will fetch all records of type IMPL and return them as a shared_ptr to INTF
         * This WILL add all fetched records to the cache
         *
         * @param results   A reference to the result vector. All existing elements will
         *                  be discarded.
         */
103
        template <typename IMPL, typename INTF, typename... Args>
104
        static std::vector<std::shared_ptr<INTF> > fetchAll( DBConnection dbConnectionWeak, const std::string& req, Args&&... args )
105
        {
106
107
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
108
                throw std::runtime_error("Invalid SQlite connection");
109

110
            std::vector<std::shared_ptr<INTF>> results;
111
            auto stmt = prepareRequest( dbConnection, req, std::forward<Args>( args )...);
112
            if ( stmt == nullptr )
113
                throw std::runtime_error("Failed to execute SQlite request");
114
            int res = sqlite3_step( stmt.get() );
115
116
            while ( res == SQLITE_ROW )
            {
117
                auto row = IMPL::load( dbConnection, stmt.get() );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
118
                results.push_back( row );
119
                res = sqlite3_step( stmt.get() );
120
            }
121
            return results;
122
123
        }

124
        template <typename T, typename... Args>
125
        static std::shared_ptr<T> fetchOne( DBConnection dbConnectionWeak, const std::string& req, Args&&... args )
126
        {
127
128
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
129
                return nullptr;
130

131
            std::shared_ptr<T> result;
132
            auto stmt = prepareRequest( dbConnection, req, std::forward<Args>( args )... );
133
            if ( stmt == nullptr )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
134
                return nullptr;
135
            if ( sqlite3_step( stmt.get() ) != SQLITE_ROW )
136
                return result;
137
            result = T::load( dbConnection, stmt.get() );
138
139
140
            return result;
        }

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
141
        template <typename... Args>
142
        static bool executeDelete( DBConnection dbConnectionWeak, const std::string& req, Args&&... args )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
143
        {
144
145
146
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
                return false;
147
            if ( executeRequest( dbConnection, req, std::forward<Args>( args )... ) == false )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
148
                return false;
149
            return sqlite3_changes( dbConnection.get() ) > 0;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
150
151
        }

152
        template <typename... Args>
153
        static bool executeUpdate( DBConnection dbConnectionWeak, const std::string& req, Args&&... args )
154
        {
155
156
            // The code would be exactly the same, do not freak out because it calls executeDelete :)
            return executeDelete( dbConnectionWeak, req, std::forward<Args>( args )... );
157
158
        }

159
        template <typename... Args>
160
        static bool executeRequest( DBConnection dbConnectionWeak, const std::string& req, Args&&... args )
161
        {
162
163
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
164
                return false;
165
            return executeRequest( dbConnection, req, std::forward<Args>( args )... );
166
167
        }

168
169
170
171
172
        /**
         * Inserts a record to the DB and return the newly created primary key.
         * Returns 0 (which is an invalid sqlite primary key) when insertion fails.
         */
        template <typename... Args>
173
        static unsigned int insert( DBConnection dbConnectionWeak, const std::string& req, Args&&... args )
174
        {
175
176
177
178
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
                return false;

179
            if ( executeRequest( dbConnection, req, std::forward<Args>( args )... ) == false )
180
                return 0;
181
            return sqlite3_last_insert_rowid( dbConnection.get() );
182
183
        }

184
    private:
185
        template <typename... Args>
186
        static StmtPtr prepareRequest( std::shared_ptr<sqlite3> dbConnectionPtr, const std::string& req, Args&&... args )
187
        {
188
            return _prepareRequest<1>( dbConnectionPtr, req, std::forward<Args>( args )... );
189
190
        }

191
        template <unsigned int>
192
        static StmtPtr _prepareRequest( std::shared_ptr<sqlite3> dbConnection, const std::string& req )
193
        {
194
            assert( dbConnection != nullptr );
195
            sqlite3_stmt* stmt = nullptr;
196
            int res = sqlite3_prepare_v2( dbConnection.get(), req.c_str(), -1, &stmt, NULL );
197
            if ( res != SQLITE_OK )
198
199
            {
                std::cerr << "Failed to execute request: " << req << std::endl;
200
                std::cerr << sqlite3_errmsg( dbConnection.get() ) << std::endl;
201
            }
202
            return StmtPtr( stmt, &sqlite3_finalize );
203
204
        }

205
        template <unsigned int COLIDX, typename T, typename... Args>
206
        static StmtPtr _prepareRequest( std::shared_ptr<sqlite3> dbConnectionPtr, const std::string& req, T&& arg, Args&&... args )
207
        {
208
209
            auto stmt = _prepareRequest<COLIDX + 1>( dbConnectionPtr, req, std::forward<Args>( args )... );
            Traits<T>::Bind( stmt.get(), COLIDX, std::forward<T>( arg ) );
210
211
            return stmt;
        }
212
213

        template <typename... Args>
214
        static bool executeRequest( std::shared_ptr<sqlite3> dbConnection, const std::string& req, Args&&... args )
215
        {
216
            auto stmt = prepareRequest( dbConnection, req, std::forward<Args>( args )... );
217
218
219
220
221
222
223
224
225
            if ( stmt == nullptr )
                return false;
            int res;
            do
            {
                res = sqlite3_step( stmt.get() );
            } while ( res == SQLITE_ROW );
            if ( res != SQLITE_DONE )
            {
226
                std::cerr << "Failed to execute <" << req << '>' << std::endl;
227
228
229
230
231
232
233
234
235
236
237
                std::cerr << "Invalid result: " <<
#if SQLITE_VERSION_NUMBER >= 3007015
                            sqlite3_errstr( res )
#else
                             res
#endif
                          << ": " << sqlite3_errmsg( dbConnection.get() ) << std::endl;
                return false;
            }
            return true;
        }
238
239
};

240
241
}

242
#endif // SQLITETOOLS_H