SqliteTools.h 7.77 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
#include <thread>
12

13
#include "Types.h"
14
#include "database/SqliteConnection.h"
15
#include "logging/Logger.h"
16

17
18
19
namespace sqlite
{

20
21
22
23
24
25
struct ForeignKey
{
    constexpr ForeignKey(unsigned int v) : value(v) {}
    unsigned int value;
};

26
27
28
template <typename ToCheck, typename T>
using IsSameDecay = std::is_same<typename std::decay<ToCheck>::type, T>;

29
30
31
template <typename T, typename Enable = void>
struct Traits;

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

    static constexpr
41
    int (*Load)(sqlite3_stmt *, int) = &sqlite3_column_int;
42
43
};

44
45
template <typename T>
struct Traits<T, typename std::enable_if<IsSameDecay<T, ForeignKey>::value>::type>
46
47
48
49
50
51
52
53
54
{
    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 );
    }
};

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

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

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

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

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

93
class Tools
94
{
95
96
    private:
        typedef std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt*)> StmtPtr;
97
    public:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
98
99
100
101
102
103
104
        /**
         * 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.
         */
105
        template <typename IMPL, typename INTF, typename... Args>
106
        static std::vector<std::shared_ptr<INTF> > fetchAll( DBConnection dbConnection, const std::string& req, Args&&... args )
107
        {
108
109
            auto ctx = dbConnection->acquireContext();

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 dbConnection, const std::string& req, Args&&... args )
126
        {
127
128
            auto ctx = dbConnection->acquireContext();

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

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
138
        template <typename... Args>
139
        static bool executeRequest( DBConnection dbConnection, const std::string& req, Args&&... args )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
140
        {
141
142
            auto ctx = dbConnection->acquireContext();
            return executeRequestLocked( dbConnection, req, std::forward<Args>( args )... );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
143
144
        }

145
        template <typename... Args>
146
        static bool executeDelete( DBConnection dbConnection, const std::string& req, Args&&... args )
147
        {
148
149
            auto ctx = dbConnection->acquireContext();
            if ( executeRequestLocked( dbConnection, req, std::forward<Args>( args )... ) == false )
150
151
                return false;
            return sqlite3_changes( dbConnection->getConn() ) > 0;
152
153
        }

154
        template <typename... Args>
155
        static bool executeUpdate( DBConnection dbConnectionWeak, const std::string& req, Args&&... args )
156
        {
157
158
            // The code would be exactly the same, do not freak out because it calls executeDelete :)
            return executeDelete( dbConnectionWeak, req, std::forward<Args>( 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 dbConnection, const std::string& req, Args&&... args )
167
        {
168
169
            auto ctx = dbConnection->acquireContext();
            if ( executeRequestLocked( dbConnection, req, std::forward<Args>( args )... ) == false )
170
                return 0;
171
            return sqlite3_last_insert_rowid( dbConnection->getConn() );
172
173
        }

174
    private:
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
        template <typename... Args>
        static bool executeRequestLocked( DBConnection dbConnection, const std::string& req, Args&&... args )
        {
            auto stmt = prepareRequest( dbConnection, req, std::forward<Args>( args )... );
            if ( stmt == nullptr )
                return false;
            int res;
            do
            {
                res = sqlite3_step( stmt.get() );
            } while ( res == SQLITE_ROW );
            if ( res != SQLITE_DONE )
            {
#if SQLITE_VERSION_NUMBER >= 3007015
                auto err = sqlite3_errstr( res );
#else
                auto err = res;
#endif
                LOG_ERROR( "Failed to execute <", req, ">\nInvalid result: ", err,
                          ": ", sqlite3_errmsg( dbConnection->getConn() ) );
                return false;
            }
            return true;
        }

200
        template <typename... Args>
201
        static StmtPtr prepareRequest( DBConnection dbConnection, const std::string& req, Args&&... args )
202
        {
203
            return _prepareRequest<1>( dbConnection, req, std::forward<Args>( args )... );
204
205
        }

206
        template <unsigned int>
207
        static StmtPtr _prepareRequest( DBConnection dbConnection, const std::string& req )
208
        {
209
            sqlite3_stmt* stmt = nullptr;
210
            int res = sqlite3_prepare_v2( dbConnection->getConn(), req.c_str(), -1, &stmt, NULL );
211
            if ( res != SQLITE_OK )
212
            {
213
214
                LOG_ERROR( "Failed to execute request: ", req, ' ',
                            sqlite3_errmsg( dbConnection->getConn() ) );
215
            }
216
            return StmtPtr( stmt, &sqlite3_finalize );
217
218
        }

219
        template <unsigned int COLIDX, typename T, typename... Args>
220
        static StmtPtr _prepareRequest( DBConnection dbConnection, const std::string& req, T&& arg, Args&&... args )
221
        {
222
            auto stmt = _prepareRequest<COLIDX + 1>( dbConnection, req, std::forward<Args>( args )... );
223
            Traits<T>::Bind( stmt.get(), COLIDX, std::forward<T>( arg ) );
224
225
            return stmt;
        }
226
227

        friend SqliteConnection;
228
229
};

230
231
}

232
#endif // SQLITETOOLS_H