Coverage Report

Created: 2024-04-30 09:35

src/zserio/SqliteConnection.h
Line
Count
Source
1
#ifndef ZSERIO_SQL_CONNECTION_H_INC
2
#define ZSERIO_SQL_CONNECTION_H_INC
3
4
#include <memory>
5
6
#include "zserio/SqliteException.h"
7
#include "zserio/SqliteFinalizer.h"
8
#include "zserio/StringView.h"
9
10
#include "sqlite3.h"
11
12
namespace zserio
13
{
14
15
/**
16
 * Helper class to keep sqlite3 connection and ensure its safe destruction.
17
 *
18
 * The class also provides simple interface to execute SQLite queries.
19
 */
20
class SqliteConnection
21
{
22
public:
23
    /**
24
     * Connection type.
25
     */
26
    enum ConnectionType
27
    {
28
        INTERNAL_CONNECTION, /**< Internal connection which must be released in destructor. */
29
        EXTERNAL_CONNECTION /**< External connection managed from outside. */
30
    };
31
32
    /**
33
     * Constructor.
34
     *
35
     * \param connection Pointer to the SQLite connection.
36
     * \param connectionType Type of the connection. Default is INTERNAL_CONNECTION.
37
     */
38
    explicit SqliteConnection(
39
            sqlite3* connection = nullptr, ConnectionType connectionType = INTERNAL_CONNECTION);
40
41
    /**
42
     * Destructor.
43
     */
44
    ~SqliteConnection();
45
46
    /**
47
     * Copying and moving is disallowed!
48
     * \{
49
     */
50
    SqliteConnection(const SqliteConnection&) = delete;
51
    SqliteConnection& operator=(const SqliteConnection&) = delete;
52
53
    SqliteConnection(SqliteConnection&&) = delete;
54
    SqliteConnection& operator=(SqliteConnection&&) = delete;
55
    /** \} */
56
57
    /**
58
     * Resets the connection.
59
     *
60
     * \param connection New connection to set. Default is NULL - i.e. unset.
61
     * \param connectionType Type of the new connection.
62
     */
63
    void reset(sqlite3* connection = nullptr, ConnectionType connectionType = INTERNAL_CONNECTION);
64
65
    /**
66
     * Gets the current connection type.
67
     *
68
     * When connection is NULL, the connection type is insignificant.
69
     *
70
     * \return Connection type.
71
     */
72
    ConnectionType getConnectionType() const;
73
74
    /**
75
     * Gets the current connection.
76
     *
77
     * \return SQLite connection.
78
     */
79
    sqlite3* getConnection();
80
81
    /**
82
     * Executes a single query which doesn't need to return anything - e.g. DML.
83
     *
84
     * \param sqlQuery The query string.
85
     */
86
    void executeUpdate(StringView sqlQuery);
87
88
    /**
89
     * Prepares the SQLite statement for the given query.
90
     *
91
     * Note that the user is responsible to proper statement finalization using sqlite3_finalize!
92
     *
93
     * \param sqlQuery The query string.
94
     *
95
     * \return Prepared SQLite statement.
96
     */
97
    sqlite3_stmt* prepareStatement(StringView sqlQuery);
98
99
    /**
100
     * Starts a new transaction if a transaction is not already started.
101
     *
102
     * \return True when the new transaction was started. False when a transaction is already started.
103
     */
104
    bool startTransaction();
105
106
    /**
107
     * Terminates the current transaction.
108
     *
109
     * The parameter wasTransactionStarted is used for convenience since it's then easier to write code
110
     * which uses transactions.
111
     *
112
     * \code{.cpp}
113
     * bool wasTransactionStarted = connection.startTransaction(); // transaction may be already started
114
     * // execute queries
115
     * // ...
116
     * // terminates the transaction only if it was started by the corresponding startTransaction call.
117
     * connection.endTransaction(wasTransactionStarted);
118
     * \endcode
119
     *
120
     * \param wasTransactionStarted When false, the call does actually nothing.
121
     */
122
    void endTransaction(bool wasTransactionStarted);
123
124
private:
125
    sqlite3* m_connection;
126
    ConnectionType m_connectionType;
127
};
128
129
inline SqliteConnection::SqliteConnection(sqlite3* connection, ConnectionType connectionType) :
130
        m_connection(connection),
131
        m_connectionType(connectionType)
132
20
{}
133
134
inline SqliteConnection::~SqliteConnection()
135
20
{
136
20
    reset();
137
20
}
138
139
inline void SqliteConnection::reset(sqlite3* connection, ConnectionType connectionType)
140
40
{
141
    // close connection only if it is internal
142
40
    if (m_connectionType == INTERNAL_CONNECTION)
143
35
        sqlite3_close_v2(m_connection); // sqlite3_close_v2(NULL) is a harmless no-op
144
145
40
    m_connection = connection;
146
40
    m_connectionType = connectionType;
147
40
}
148
149
inline SqliteConnection::ConnectionType SqliteConnection::getConnectionType() const
150
12
{
151
12
    return m_connectionType;
152
12
}
153
154
inline sqlite3* SqliteConnection::getConnection()
155
22
{
156
22
    return m_connection;
157
22
}
158
159
inline void SqliteConnection::executeUpdate(StringView sqlQuery)
160
17
{
161
17
    std::unique_ptr<sqlite3_stmt, SqliteFinalizer> statement(prepareStatement(sqlQuery));
162
17
    int result = sqlite3_step(statement.get());
163
17
    if (result != SQLITE_DONE)
164
1
    {
165
1
        throw SqliteException("SqliteConnection::executeUpdate(): sqlite3_step failed: ")
166
1
                << SqliteErrorCode(result);
167
1
    }
168
17
}
169
170
inline sqlite3_stmt* SqliteConnection::prepareStatement(StringView sqlQuery)
171
42
{
172
42
    sqlite3_stmt* statement = nullptr;
173
42
    const int result = sqlite3_prepare_v2(
174
42
            m_connection, sqlQuery.data(), static_cast<int>(sqlQuery.size()), &statement, nullptr);
175
42
    if (result != SQLITE_OK)
176
7
    {
177
7
        throw SqliteException("SqliteConnection::prepareStatement(): sqlite3_prepare_v2() failed: ")
178
7
                << SqliteErrorCode(result);
179
7
    }
180
181
35
    return statement;
182
42
}
183
184
inline bool SqliteConnection::startTransaction()
185
2
{
186
2
    bool wasTransactionStarted = false;
187
2
    if (sqlite3_get_autocommit(m_connection) != 0)
188
1
    {
189
1
        executeUpdate("BEGIN;");
190
1
        wasTransactionStarted = true;
191
1
    }
192
193
2
    return wasTransactionStarted;
194
2
}
195
196
inline void SqliteConnection::endTransaction(bool wasTransactionStarted)
197
2
{
198
2
    if (wasTransactionStarted)
199
1
        executeUpdate("COMMIT;");
200
2
}
201
202
} // namespace zserio
203
204
#endif // ZSERIO_SQL_CONNECTION_H_INC