Audacity 3.2.0
Connection.cpp
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: GPL-2.0-or-later
3 * SPDX-FileName: Connection.cpp
4 * SPDX-FileContributor: Dmitry Vedenko
5 */
6
7#include "Connection.h"
8
9#include <algorithm>
10#include <cassert>
11
12#include "sqlite3.h"
13
14#include "MemoryX.h"
15
16#include "SQLiteUtils.h"
17
18namespace audacity::sqlite
19{
20Result<Connection>
21Connection::Open(std::string_view path, OpenMode mode, ThreadMode threadMode)
22{
23 auto error = Initialize();
24
25 if (error.IsError())
26 return error;
27
28 int flags = 0;
29
30 switch (mode)
31 {
33 flags = SQLITE_OPEN_READWRITE;
34 break;
36 flags = SQLITE_OPEN_READONLY;
37 break;
39 flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
40 break;
42 flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MEMORY;
43 break;
44 }
45
46 switch (threadMode)
47 {
49 flags |= SQLITE_OPEN_NOMUTEX;
50 break;
52 flags |= SQLITE_OPEN_FULLMUTEX;
53 break;
54 }
55
56 sqlite3* connection = nullptr;
57
58 // If the path is not null-terminated, copy it to a temporary string.
59 std::string temp;
60 if (*(path.data() + path.size()) != '\0')
61 {
62 temp = std::string(path);
63 path = temp;
64 }
65
66 error = Error(sqlite3_open_v2(path.data(), &connection, flags, nullptr));
67
68 if (error.IsError())
69 return error;
70
71 return Connection(connection, true);
72}
73
75{
76 if (connection == nullptr)
77 return Error(SQLITE_MISUSE);
78
79 return Connection(connection, false);
80}
81
83 const Connection& connection, OpenMode mode, ThreadMode threadMode)
84{
85 if (!connection.IsOpen())
86 return Error(SQLITE_MISUSE);
87
88 auto path = connection.GetPath();
89
90 // For memory and temporary databases, the path is empty.
91 if (path.empty())
92 return Error(SQLITE_MISUSE);
93
94 return Open(path, mode, threadMode);
95}
96
98Connection::Reopen(sqlite3* connection, OpenMode mode, ThreadMode threadMode)
99{
100 auto result = Wrap(connection);
101
102 if (!result)
103 return result;
104
105 return Reopen(*result, mode, threadMode);
106}
107
108Connection::Connection(sqlite3* connection, bool owned) noexcept
109 : mConnection { connection }
110 , mIsOwned { owned }
111{
112}
113
115{
116 *this = std::move(rhs);
117}
118
120{
121 std::swap(mConnection, rhs.mConnection);
122 std::swap(mIsOwned, rhs.mIsOwned);
123 std::swap(mInDestructor, rhs.mInDestructor);
124 std::swap(mPendingTransactions, rhs.mPendingTransactions);
125
126 return *this;
127}
128
130{
131 mInDestructor = true;
132 auto error = Close(true);
133 assert(!error.IsError());
134}
135
136bool Connection::IsOpen() const noexcept
137{
138 return mConnection != nullptr;
139}
140
141bool Connection::CheckTableExists(std::string_view tableName) const
142{
143 auto stmt = CreateStatement(
144 "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?)");
145
146 if (!stmt)
147 return false;
148
149 auto result = stmt->Prepare(tableName).Run();
150
151 if (!result.HasRows())
152 return false;
153
154 for (auto row : result)
155 return row.GetOr(0, false);
156
157 return false;
158}
159
160Error Connection::Execute(std::string_view sql) noexcept
161{
162 if (mInDestructor || mConnection == nullptr)
163 return Error(SQLITE_MISUSE);
164
165 auto tx = BeginTransaction("Connection_Execute");
166
167 auto first = sql.data();
168 auto last = first + sql.size();
169
170 while (first != last)
171 {
172 const char* next = nullptr;
173 sqlite3_stmt* statement = nullptr;
174
175 int result = sqlite3_prepare_v2 (
176 mConnection, first, last - first, &statement, &next);
177
178 if (result != SQLITE_OK)
179 return Error(result);
180
181 first = next;
182
183 if (statement == nullptr)
184 continue;
185
186 auto finalizer = finally([statement] { sqlite3_finalize(statement); });
187
188 result = sqlite3_step(statement);
189
190 if (result != SQLITE_OK && result != SQLITE_DONE && result != SQLITE_ROW)
191 return Error(result);
192
193 while (result == SQLITE_ROW)
194 result = sqlite3_step(statement);
195 }
196
197 return tx.Commit();
198}
199
201{
202 return Transaction(*this, TransactionHandler, std::move(name));
203}
204
206 Connection& connection, Transaction::TransactionOperation operation,
207 Transaction& transaction)
208{
209 std::string sql;
210
211 const auto& name = transaction.mName;
212
213 switch (operation)
214 {
216 sql = std::string("SAVEPOINT ") + name;
217 break;
219 sql = std::string("RELEASE SAVEPOINT ") + name;
220 break;
222 sql = std::string("ROLLBACK TO SAVEPOINT ") + name +
223 std::string("; RELEASE SAVEPOINT ") + name;
224 break;
225 }
226
227 auto error = Error(sqlite3_exec(
228 connection.mConnection, sql.c_str(), nullptr, nullptr, nullptr));
229
231 {
232 transaction.mCommitted = true;
233
234 connection.mPendingTransactions.erase(
235 std::remove_if(
236 connection.mPendingTransactions.begin(),
237 connection.mPendingTransactions.end(),
238 [tx = &transaction](auto lhs) { return lhs == tx; }),
239 connection.mPendingTransactions.end());
240 }
241
242 if (error.IsError())
243 return error;
244
246 {
247 connection.mPendingTransactions.push_back(&transaction);
248 }
249
250 return {};
251}
252
254{
255 if (mInDestructor || mConnection == nullptr)
256 return Error(SQLITE_MISUSE);
257
258 sqlite3_stmt* statement = nullptr;
259
260 auto error = Error(sqlite3_prepare_v2(
261 mConnection, sql.data(), sql.size(), &statement, nullptr));
262
263 if (error.IsError())
264 return error;
265
266 return Result<Statement>(Statement(statement));
267}
268
270 const std::string& tableName, const std::string& columnName, int64_t rowId,
271 bool readOnly, const std::string& databaseName) const
272{
273 if (mInDestructor || mConnection == nullptr)
274 return Error(SQLITE_MISUSE);
275
276 sqlite3_blob* blob = nullptr;
277
278 auto error = Error(sqlite3_blob_open(
279 mConnection, databaseName.c_str(), tableName.c_str(), columnName.c_str(),
280 rowId, readOnly ? 0 : 1, &blob));
281
282 if (error.IsError())
283 return error;
284
285 return Blob { blob };
286}
287
288Connection::operator sqlite3*() const noexcept
289{
290 return mConnection;
291}
292
293Connection::operator bool() const noexcept
294{
295 return IsOpen();
296}
297
298Error Connection::Close(bool force) noexcept
299{
300 // If there is a transaction in progress,
301 // rollback it.
302 std::vector<Transaction*> pendingTransactions;
303
304 for (auto* transaction : pendingTransactions)
305 if (auto err = transaction->Abort(); !force && err.IsError())
306 return err;
307
308 if (mConnection != nullptr && mIsOwned)
309 if(auto err = Error(sqlite3_close(mConnection)); err.IsError())
310 return err;
311
312 mConnection = nullptr;
313
314 return {};
315}
316
317std::string_view Connection::GetPath(const char* dbName) const noexcept
318{
319 auto path = sqlite3_db_filename(mConnection, dbName);
320
321 if (path == nullptr)
322 return {};
323
324 return path;
325}
326
327} // namespace audacity::sqlite
wxString name
Definition: TagsEditor.cpp:166
A class representing a BLOB in a SQLite database.
Definition: Blob.h:18
A class representing a connection to a SQLite database.
Definition: Connection.h:48
bool CheckTableExists(std::string_view tableName) const
Returns true if the table exists in the database.
Definition: Connection.cpp:141
Error Close(bool force) noexcept
Closes the connection.
Definition: Connection.cpp:298
static Result< Connection > Reopen(const Connection &connection, OpenMode mode=OpenMode::ReadWriteCreate, ThreadMode threadMode=ThreadMode::Serialized)
Definition: Connection.cpp:82
std::vector< Transaction * > mPendingTransactions
Definition: Connection.h:143
Result< Blob > OpenBlob(const std::string &tableName, const std::string &columnName, int64_t rowId, bool readOnly, const std::string &databaseName="main") const
Opens a BLOB for reading or writing.
Definition: Connection.cpp:269
static Result< Connection > Open(std::string_view path, OpenMode mode=OpenMode::ReadWriteCreate, ThreadMode threadMode=ThreadMode::Serialized)
Opens a connection to a database.
Definition: Connection.cpp:21
Error Execute(std::string_view sql) noexcept
Executes the given SQL statement and returns the result.
Definition: Connection.cpp:160
std::string_view GetPath(const char *dbName={}) const noexcept
Returns the path to the database.
Definition: Connection.cpp:317
Connection & operator=(const Connection &)=delete
Result< Statement > CreateStatement(std::string_view sql) const
Prepares the given SQL statement for execution.
Definition: Connection.cpp:253
Transaction BeginTransaction(std::string name)
Starts a new transaction.
Definition: Connection.cpp:200
bool IsOpen() const noexcept
Returns true if the connection is open.
Definition: Connection.cpp:136
static Error TransactionHandler(Connection &connection, Transaction::TransactionOperation operation, Transaction &name)
Definition: Connection.cpp:205
static Result< Connection > Wrap(sqlite3 *connection)
Definition: Connection.cpp:74
A class representing an error in SQLite.
Definition: Error.h:17
bool IsError() const noexcept
Returns true if the object represents an error.
Definition: Error.cpp:25
A class representing a result of an operation.
Definition: Result.h:19
A class representing a compiled statement.
Definition: Statement.h:202
A class representing a transaction in SQLite.
Definition: Transaction.h:20
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:634
OpenMode
The mode in which the database should be opened.
Definition: Connection.h:26
ThreadMode
The mode in which the database should be accessed.
Definition: Connection.h:39
Error Initialize() noexcept
Definition: SQLiteUtils.cpp:92