SqliteTools.h 7.79 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 T, typename Enable = void>
struct Traits;

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

    static constexpr
33
    int (*Load)(sqlite3_stmt *, int) = &sqlite3_column_int;
34
35
};

36
37
38
39
40
41
42
43
44
45
46
template <>
struct Traits<ForeignKey>
{
    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 );
    }
};

47
48
49
50
51
52
template <>
struct Traits<std::string>
{
    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
53
54
55
        return sqlite3_bind_text( stmt, pos, tmp, -1, free );
    }

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

65
66
template <typename T>
struct Traits<T, typename std::enable_if<std::is_floating_point<T>::value>::type>
67
68
69
70
71
72
73
74
{
        static constexpr int
        (*Bind)(sqlite3_stmt *, int, double) = &sqlite3_bind_double;

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

75
76
77
78
79
80
81
82
83
template <>
struct Traits<std::nullptr_t>
{
    static int Bind(sqlite3_stmt* stmt, int idx, std::nullptr_t)
    {
        return sqlite3_bind_null( stmt, idx );
    }
};

84
class Tools
85
{
86
87
    private:
        typedef std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt*)> StmtPtr;
88
    public:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
89
90
91
92
93
94
95
        /**
         * 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.
         */
96
        template <typename IMPL, typename INTF, typename... Args>
97
        static std::vector<std::shared_ptr<INTF> > fetchAll( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
98
        {
99
100
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
101
                throw std::runtime_error("Invalid SQlite connection");
102

103
            std::vector<std::shared_ptr<INTF>> results;
104
            auto stmt = prepareRequest( dbConnection, req, args...);
105
            if ( stmt == nullptr )
106
                throw std::runtime_error("Failed to execute SQlite request");
107
            int res = sqlite3_step( stmt.get() );
108
109
            while ( res == SQLITE_ROW )
            {
110
                auto row = IMPL::load( dbConnection, stmt.get() );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
111
                results.push_back( row );
112
                res = sqlite3_step( stmt.get() );
113
            }
114
            return results;
115
116
        }

117
        template <typename T, typename... Args>
118
        static std::shared_ptr<T> fetchOne( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
119
        {
120
121
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
122
                return nullptr;
123

124
            std::shared_ptr<T> result;
125
            auto stmt = prepareRequest( dbConnection, req, args... );
126
            if ( stmt == nullptr )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
127
                return nullptr;
128
            if ( sqlite3_step( stmt.get() ) != SQLITE_ROW )
129
                return result;
130
            result = T::load( dbConnection, stmt.get() );
131
132
133
            return result;
        }

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
134
        template <typename... Args>
135
        static bool executeDelete( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
136
        {
137
138
139
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
                return false;
140
            if ( executeRequest( dbConnection, req, args... ) == false )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
141
                return false;
142
            return sqlite3_changes( dbConnection.get() ) > 0;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
143
144
        }

145
        template <typename... Args>
146
        static bool executeUpdate( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
147
        {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
148
            // The code would be exactly the same, do not freak out because it calls delete :)
149
            return executeDelete( dbConnectionWeak, req, args... );
150
151
        }

152
        template <typename... Args>
153
        static bool executeRequest( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
154
        {
155
156
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
157
                return false;
158
            return executeRequest( dbConnection, req, args... );
159
160
        }

161
162
163
164
165
        /**
         * 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>
166
        static unsigned int insert( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
167
        {
168
169
170
171
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
                return false;

172
173
            if ( executeRequest( dbConnection, req, args... ) == false )
                return 0;
174
            return sqlite3_last_insert_rowid( dbConnection.get() );
175
176
        }

177
    private:
178
        template <typename... Args>
179
        static StmtPtr prepareRequest( std::shared_ptr<sqlite3> dbConnectionPtr, const std::string& req, const Args&... args )
180
        {
181
            return _prepareRequest<1>( dbConnectionPtr, req, args... );
182
183
        }

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

198
        template <unsigned int COLIDX, typename T, typename... Args>
199
        static StmtPtr _prepareRequest( std::shared_ptr<sqlite3> dbConnectionPtr, const std::string& req, const T& arg, const Args&... args )
200
        {
201
            auto stmt = _prepareRequest<COLIDX + 1>( dbConnectionPtr, req, args... );
202
            Traits<T>::Bind( stmt.get(), COLIDX, arg );
203
204
            return stmt;
        }
205
206
207
208
209
210
211
212
213
214
215
216
217
218

        template <typename... Args>
        static bool executeRequest( std::shared_ptr<sqlite3> dbConnection, const std::string& req, const Args&... args )
        {
            auto stmt = prepareRequest( dbConnection, req, args... );
            if ( stmt == nullptr )
                return false;
            int res;
            do
            {
                res = sqlite3_step( stmt.get() );
            } while ( res == SQLITE_ROW );
            if ( res != SQLITE_DONE )
            {
219
                std::cerr << "Failed to execute <" << req << '>' << std::endl;
220
221
222
223
224
225
226
227
228
229
230
                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;
        }
231
232
};

233
234
}

235
#endif // SQLITETOOLS_H