ROSE 0.11.145.192
DatabaseSqlite.h
1// WARNING: Changes to this file must be contributed back to Sawyer or else they will
2// be clobbered by the next update from Sawyer. The Sawyer repository is at
3// https://gitlab.com/charger7534/sawyer.git.
4
5
6
7
8#ifndef Sawyer_DatabaseSqlite_H
9#define Sawyer_DatabaseSqlite_H
10
11#if __cplusplus >= 201103L
12
13#include <Sawyer/Database.h>
14
15#include <boost/filesystem.hpp>
16#include <boost/format.hpp>
17#include <boost/lexical_cast.hpp>
18#include <Sawyer/Optional.h>
19#include <sqlite3.h>
20#include <string>
21
22#if SQLITE_VERSION_NUMBER < 3007015
23std::string sqlite3_errstr(int status) {
24 return "sqlite3 error " + boost::lexical_cast<std::string>(status);
25}
26#endif
27
28namespace Sawyer {
29namespace Database {
30
32class Sqlite: public Connection {
33public:
35 Sqlite() {}
36
40 Sqlite(const boost::filesystem::path &fileName) {
41 open(fileName);
42 }
43
45 Sqlite& open(const boost::filesystem::path &fileName);
46};
47
48
49// Only implementation details beyond this point -- no public APIs
50namespace Detail {
51
52class SqliteStatement;
53
55// SQLite3 connection details
57
58class SqliteConnection: public ConnectionBase {
59 friend class ::Sawyer::Database::Sqlite;
60 friend class ::Sawyer::Database::Detail::SqliteStatement;
61
62 sqlite3 *connection = nullptr;
63
64public:
65 ~SqliteConnection() {
66 close();
67 }
68
69private:
70 void open(const boost::filesystem::path &filename) {
71 close();
72 if (strlen(filename.string().c_str()) != filename.string().size())
73 throw Exception("invalid database name: internal NUL character");
74 int status = sqlite3_open(filename.string().c_str(), &connection);
75 if (SQLITE_OK != status)
76 throw Exception("SqliteConnection::open(" + boost::lexical_cast<std::string>(filename) + "): " + sqlite3_errstr(status));
77 sqlite3_busy_timeout(connection, 1000 /*ms*/);
78 }
79
80 void close() override {
81 if (connection) {
82 // Will return SQLITE_BUSY (a.k.a., "database is locked") if there are outstanding prepared statements. Due to
83 // reference counting, this "close" function will only be called when none of those statements are in the EXECUTING
84 // state. Note that SQLITE_LOCKED is an entirely different error.
85 int status = sqlite3_close(connection);
86 connection = nullptr;
87 if (SQLITE_OK != status && SQLITE_BUSY != status)
88 throw Exception("SqliteConnection::close(): " + std::string(sqlite3_errstr(status)));
89 }
90 }
91
92 std::string driverName() const override {
93 return "sqlite";
94 }
95
96 Statement prepareStatement(const std::string &sql) override;
97
98 size_t lastInsert() const override {
99 ASSERT_not_null(connection);
100 return boost::numeric_cast<size_t>(sqlite3_last_insert_rowid(connection));
101 }
102};
103
105// SQLite3 statement details
107
108class SqliteStatement: public StatementBase {
109 friend class ::Sawyer::Database::Detail::SqliteConnection;
110
111 sqlite3_stmt *stmt = nullptr; // underlying SQLite3 statement
112
113private:
114 SqliteStatement(const std::shared_ptr<ConnectionBase> &db, const std::string &sql)
115 : StatementBase(db) {
116 std::string lowSql = parseParameters(sql).first;
117 const char *rest = nullptr;
118 std::shared_ptr<SqliteConnection> sqlite = std::dynamic_pointer_cast<SqliteConnection>(db);
119 ASSERT_not_null(sqlite);
120 int status = sqlite3_prepare_v2(sqlite->connection, lowSql.c_str(), lowSql.size()+1, &stmt, &rest);
121 if (SQLITE_OK != status)
122 throw Exception("SqliteStatement(" + sql + "): " + sqlite3_errstr(status));
123 while (rest && ::isspace(*rest))
124 ++rest;
125 if (rest && *rest) {
126 sqlite3_finalize(stmt); // clean up if possible; ignore error otherwise
127 stmt = nullptr;
128 throw Exception("SqliteStatement(" + sql + "): extraneous text after end of SQL statement");
129 }
130 }
131
132public:
133 ~SqliteStatement() {
134 if (stmt) {
135 sqlite3_finalize(stmt);
136 stmt = nullptr;
137 }
138 }
139
140private:
141 void reset(bool doUnbind) override {
142 if (!connection())
143 throw Exception("SqliteStatement::reset(): connection is closed");
144 if (state() == Statement::DEAD)
145 throw Exception("SqliteStatement::reset(): statement is dead");
146
147 // Reset the SQL statement with delayed error reporting
148 int status = SQLITE_OK;
149 if (Statement::EXECUTING == state() || Statement::FINISHED == state())
150 status = sqlite3_reset(stmt); // doesn't actually unbind parameters
151
152 // Do the higher-level part of the reset
153 StatementBase::reset(doUnbind);
154
155 // Finally report errors from above
156 if (SQLITE_OK != status) {
157 state(Statement::DEAD); // we no longer know the SQLite3 state
158 throw Exception("SqliteStatement::reset(): " + std::string(sqlite3_errstr(status)));
159 }
160 }
161
162 void bindLow(size_t idx, int value) override {
163 int status = sqlite3_bind_int(stmt, idx+1, value);
164 if (SQLITE_OK != status)
165 throw Exception((boost::format("SqliteStatement::bindLow(idx=%d): %s") % idx % sqlite3_errstr(status)).str());
166 }
167
168 void bindLow(size_t idx, int64_t value) override {
169 int status = sqlite3_bind_int64(stmt, idx+1, value);
170 if (SQLITE_OK != status)
171 throw Exception((boost::format("SqliteStatement::bindLow(idx=%d): %s") % idx % sqlite3_errstr(status)).str());
172 }
173
174 void bindLow(size_t idx, size_t value) override {
175 // The SQLite API doesn't have a way to bind a size_t value to a prepared statement, so we'll zero extend the size_t
176 // to 64 bits (if necessary) and then reinterpret those bits as a 2's complement signed value and bind that instead.
177 assert(sizeof value <= 8);
178 int64_t reinterpreted = (int64_t)(uint64_t)value; // C++11 implementation defined behavior
179 bindLow(idx, reinterpreted);
180 }
181
182 void bindLow(size_t idx, double value) override {
183 int status = sqlite3_bind_double(stmt, idx+1, value);
184 if (SQLITE_OK != status)
185 throw Exception((boost::format("SqliteStatement::bindLow(idx=%d): %s") % idx % sqlite3_errstr(status)).str());
186 }
187
188 void bindLow(size_t idx, const std::string &value) override {
189 int status = sqlite3_bind_text(stmt, idx+1, value.c_str(), value.size(), SQLITE_TRANSIENT);
190 if (SQLITE_OK != status)
191 throw Exception((boost::format("SqliteStatement::bindLow(idx=%d): %s") % idx % sqlite3_errstr(status)).str());
192 }
193
194 void bindLow(size_t idx, const char *value) override {
195 value ? bindLow(idx, std::string(value)) : bindLow(idx, Nothing());
196 }
197
198 void bindLow(size_t idx, const std::vector<uint8_t> &value) override {
199 int status = sqlite3_bind_blob(stmt, idx+1, value.data(), value.size(), SQLITE_TRANSIENT);
200 if (SQLITE_OK != status)
201 throw Exception((boost::format("SqliteStatement::bindLow(idx=%d): %s") % idx % sqlite3_errstr(status)).str());
202 }
203
204 void bindLow(size_t idx, Nothing) override {
205 int status = sqlite3_bind_null(stmt, idx+1);
206 if (SQLITE_OK != status)
207 throw Exception((boost::format("SqliteStatement::bindLow(idx=%d): %s") % idx % sqlite3_errstr(status)).str());
208 }
209
210 Iterator beginLow() override {
211 return nextLow();
212 }
213
214 Iterator nextLow() override {
215 int status = sqlite3_step(stmt);
216 if (SQLITE_ROW == status) {
217 return makeIterator();
218 } else if (SQLITE_DONE == status) {
219 state(Statement::FINISHED);
220 return Iterator();
221 } else {
222 state(Statement::DEAD);
223 throw Exception("SqliteStatement::nextLow(): " + std::string(sqlite3_errstr(status)));
224 }
225 }
226
227 size_t nColumns() const override {
228 return boost::numeric_cast<size_t>(sqlite3_column_count(stmt));
229 }
230
231 Sawyer::Optional<std::string> getString(size_t idx) override {
232 if (SQLITE_NULL == sqlite3_column_type(stmt, idx))
233 return Nothing();
234 size_t nBytes = sqlite3_column_bytes(stmt, idx);
235 const unsigned char *s = sqlite3_column_text(stmt, idx);
236 ASSERT_not_null(s);
237 return std::string(s, s+nBytes);
238 }
239
240 Sawyer::Optional<std::vector<uint8_t>> getBlob(size_t idx) override {
241 if (SQLITE_NULL == sqlite3_column_type(stmt, idx))
242 return Nothing();
243 size_t nBytes = sqlite3_column_bytes(stmt, idx);
244 const uint8_t *data = static_cast<const uint8_t*>(sqlite3_column_blob(stmt, idx));
245 ASSERT_not_null(data);
246 return std::vector<uint8_t>(data, data+nBytes);
247 }
248};
249
251// SqliteConnection implementations
253
254inline Statement
255SqliteConnection::prepareStatement(const std::string &sql) {
256 auto detail = std::shared_ptr<SqliteStatement>(new SqliteStatement(shared_from_this(), sql));
257 return makeStatement(detail);
258}
259
260} // namespace
261
263// Top-level Sqlite connection
265
266inline Sqlite&
267Sqlite::open(const boost::filesystem::path &fileName) {
268 auto pimpl = std::shared_ptr<Detail::SqliteConnection>(new Detail::SqliteConnection);
269 pimpl->open(fileName);
270 this->pimpl(pimpl);
271 return *this;
272}
273
274} // namespace
275} // namespace
276
277#endif
278#endif
Holds a value or nothing.
Definition Optional.h:56
Sawyer support library.