ROSE  0.11.83.2
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://github.com/matzke1/sawyer.
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
23 std::string sqlite3_errstr(int status) {
24  return "sqlite3 error " + boost::lexical_cast<std::string>(status);
25 }
26 #endif
27 
28 namespace Sawyer {
29 namespace Database {
30 
32 class Sqlite: public Connection {
33 public:
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
50 namespace Detail {
51 
52 class SqliteStatement;
53 
55 // SQLite3 connection details
57 
58 class SqliteConnection: public ConnectionBase {
59  friend class ::Sawyer::Database::Sqlite;
60  friend class ::Sawyer::Database::Detail::SqliteStatement;
61 
62  sqlite3 *connection = nullptr;
63 
64 public:
65  ~SqliteConnection() {
66  close();
67  }
68 
69 private:
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 
108 class SqliteStatement: public StatementBase {
109  friend class ::Sawyer::Database::Detail::SqliteConnection;
110 
111  sqlite3_stmt *stmt = nullptr; // underlying SQLite3 statement
112 
113 private:
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 
132 public:
133  ~SqliteStatement() {
134  if (stmt) {
135  sqlite3_finalize(stmt);
136  stmt = nullptr;
137  }
138  }
139 
140 private:
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 
254 inline Statement
255 SqliteConnection::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 
266 inline Sqlite&
267 Sqlite::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
Name space for the entire library.
Definition: FeasiblePath.h:785