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

20
template <typename T>
21
struct Traits<T, typename std::enable_if<std::is_integral<T>::value>::type>
22
23
{
    static constexpr
24
    int (*Bind)(sqlite3_stmt *, int, int) = &sqlite3_bind_int;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
25
26

    static constexpr
27
    int (*Load)(sqlite3_stmt *, int) = &sqlite3_column_int;
28
29
30
31
32
33
34
35
};

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
36
37
38
        return sqlite3_bind_text( stmt, pos, tmp, -1, free );
    }

39
    static std::string Load( sqlite3_stmt* stmt, int pos )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
40
    {
41
42
43
44
        auto tmp = (const char*)sqlite3_column_text( stmt, pos );
        if ( tmp != nullptr )
            return std::string( tmp );
        return std::string();
45
46
47
    }
};

48
49
template <typename T>
struct Traits<T, typename std::enable_if<std::is_floating_point<T>::value>::type>
50
51
52
53
54
55
56
57
{
        static constexpr int
        (*Bind)(sqlite3_stmt *, int, double) = &sqlite3_bind_double;

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

58
59
60
61
62
63
64
65
66
template <>
struct Traits<std::nullptr_t>
{
    static int Bind(sqlite3_stmt* stmt, int idx, std::nullptr_t)
    {
        return sqlite3_bind_null( stmt, idx );
    }
};

67
class Tools
68
{
69
70
    private:
        typedef std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt*)> StmtPtr;
71
    public:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
72
73
74
75
76
77
78
        /**
         * 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.
         */
79
        template <typename IMPL, typename INTF, typename... Args>
80
        static std::vector<std::shared_ptr<INTF> > fetchAll( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
81
        {
82
83
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
84
                throw std::runtime_error("Invalid SQlite connection");
85

86
            std::vector<std::shared_ptr<INTF>> results;
87
            auto stmt = prepareRequest( dbConnection, req, args...);
88
            if ( stmt == nullptr )
89
                throw std::runtime_error("Failed to execute SQlite request");
90
            int res = sqlite3_step( stmt.get() );
91
92
            while ( res == SQLITE_ROW )
            {
93
                auto row = IMPL::load( dbConnection, stmt.get() );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
94
                results.push_back( row );
95
                res = sqlite3_step( stmt.get() );
96
            }
97
            return results;
98
99
        }

100
        template <typename T, typename... Args>
101
        static std::shared_ptr<T> fetchOne( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
102
        {
103
104
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
105
                return nullptr;
106

107
            std::shared_ptr<T> result;
108
            auto stmt = prepareRequest( dbConnection, req, args... );
109
            if ( stmt == nullptr )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
110
                return nullptr;
111
            if ( sqlite3_step( stmt.get() ) != SQLITE_ROW )
112
                return result;
113
            result = T::load( dbConnection, stmt.get() );
114
115
116
            return result;
        }

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
117
        template <typename... Args>
118
        static bool executeDelete( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
119
        {
120
121
122
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
                return false;
123
            if ( executeRequest( dbConnection, req, args... ) == false )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
124
                return false;
125
            return sqlite3_changes( dbConnection.get() ) > 0;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
126
127
        }

128
        template <typename... Args>
129
        static bool executeUpdate( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
130
        {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
131
            // The code would be exactly the same, do not freak out because it calls delete :)
132
            return executeDelete( dbConnectionWeak, req, args... );
133
134
        }

135
        template <typename... Args>
136
        static bool executeRequest( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
137
        {
138
139
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
140
                return false;
141
            return executeRequest( dbConnection, req, args... );
142
143
        }

144
145
146
147
148
        /**
         * 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>
149
        static unsigned int insert( DBConnection dbConnectionWeak, const std::string& req, const Args&... args )
150
        {
151
152
153
154
            auto dbConnection = dbConnectionWeak.lock();
            if ( dbConnection == nullptr )
                return false;

155
156
            if ( executeRequest( dbConnection, req, args... ) == false )
                return 0;
157
            return sqlite3_last_insert_rowid( dbConnection.get() );
158
159
        }

160
    private:
161
        template <typename... Args>
162
        static StmtPtr prepareRequest( std::shared_ptr<sqlite3> dbConnectionPtr, const std::string& req, const Args&... args )
163
        {
164
            return _prepareRequest<1>( dbConnectionPtr, req, args... );
165
166
        }

167
        template <unsigned int>
168
        static StmtPtr _prepareRequest( std::shared_ptr<sqlite3> dbConnection, const std::string& req )
169
        {
170
            assert( dbConnection != nullptr );
171
            sqlite3_stmt* stmt = nullptr;
172
            int res = sqlite3_prepare_v2( dbConnection.get(), req.c_str(), -1, &stmt, NULL );
173
            if ( res != SQLITE_OK )
174
175
            {
                std::cerr << "Failed to execute request: " << req << std::endl;
176
                std::cerr << sqlite3_errmsg( dbConnection.get() ) << std::endl;
177
            }
178
            return StmtPtr( stmt, &sqlite3_finalize );
179
180
        }

181
        template <unsigned int COLIDX, typename T, typename... Args>
182
        static StmtPtr _prepareRequest( std::shared_ptr<sqlite3> dbConnectionPtr, const std::string& req, const T& arg, const Args&... args )
183
        {
184
            auto stmt = _prepareRequest<COLIDX + 1>( dbConnectionPtr, req, args... );
185
            Traits<T>::Bind( stmt.get(), COLIDX, arg );
186
187
            return stmt;
        }
188
189
190
191
192
193
194
195
196
197
198
199
200
201

        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 )
            {
202
                std::cerr << "Failed to execute <" << req << '>' << std::endl;
203
204
205
206
207
208
209
210
211
212
213
                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;
        }
214
215
};

216
217
}

218
#endif // SQLITETOOLS_H