8#ifndef Sawyer_DatabaseSqlite_H
9#define Sawyer_DatabaseSqlite_H
11#if __cplusplus >= 201103L
13#include <Sawyer/Database.h>
15#include <boost/filesystem.hpp>
16#include <boost/format.hpp>
17#include <boost/lexical_cast.hpp>
18#include <Sawyer/Optional.h>
22#if SQLITE_VERSION_NUMBER < 3007015
23std::string sqlite3_errstr(
int status) {
24 return "sqlite3 error " + boost::lexical_cast<std::string>(status);
32class Sqlite:
public Connection {
40 Sqlite(
const boost::filesystem::path &fileName) {
45 Sqlite& open(
const boost::filesystem::path &fileName);
58class SqliteConnection:
public ConnectionBase {
59 friend class ::Sawyer::Database::Sqlite;
60 friend class ::Sawyer::Database::Detail::SqliteStatement;
62 sqlite3 *connection =
nullptr;
70 void open(
const boost::filesystem::path &filename) {
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 );
80 void close()
override {
85 int status = sqlite3_close(connection);
87 if (SQLITE_OK != status && SQLITE_BUSY != status)
88 throw Exception(
"SqliteConnection::close(): " + std::string(sqlite3_errstr(status)));
92 std::string driverName()
const override {
96 Statement prepareStatement(
const std::string &sql)
override;
98 size_t lastInsert()
const override {
99 ASSERT_not_null(connection);
100 return boost::numeric_cast<size_t>(sqlite3_last_insert_rowid(connection));
108class SqliteStatement:
public StatementBase {
109 friend class ::Sawyer::Database::Detail::SqliteConnection;
111 sqlite3_stmt *stmt =
nullptr;
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))
126 sqlite3_finalize(stmt);
128 throw Exception(
"SqliteStatement(" + sql +
"): extraneous text after end of SQL statement");
135 sqlite3_finalize(stmt);
141 void reset(
bool doUnbind)
override {
143 throw Exception(
"SqliteStatement::reset(): connection is closed");
144 if (state() == Statement::DEAD)
145 throw Exception(
"SqliteStatement::reset(): statement is dead");
148 int status = SQLITE_OK;
149 if (Statement::EXECUTING == state() || Statement::FINISHED == state())
150 status = sqlite3_reset(stmt);
153 StatementBase::reset(doUnbind);
156 if (SQLITE_OK != status) {
157 state(Statement::DEAD);
158 throw Exception(
"SqliteStatement::reset(): " + std::string(sqlite3_errstr(status)));
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());
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());
174 void bindLow(
size_t idx,
size_t value)
override {
177 assert(
sizeof value <= 8);
178 int64_t reinterpreted = (int64_t)(uint64_t)value;
179 bindLow(idx, reinterpreted);
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());
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());
194 void bindLow(
size_t idx,
const char *value)
override {
195 value ? bindLow(idx, std::string(value)) : bindLow(idx, Nothing());
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());
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());
210 Iterator beginLow()
override {
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);
222 state(Statement::DEAD);
223 throw Exception(
"SqliteStatement::nextLow(): " + std::string(sqlite3_errstr(status)));
227 size_t nColumns()
const override {
228 return boost::numeric_cast<size_t>(sqlite3_column_count(stmt));
232 if (SQLITE_NULL == sqlite3_column_type(stmt, idx))
234 size_t nBytes = sqlite3_column_bytes(stmt, idx);
235 const unsigned char *s = sqlite3_column_text(stmt, idx);
237 return std::string(s, s+nBytes);
241 if (SQLITE_NULL == sqlite3_column_type(stmt, idx))
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);
255SqliteConnection::prepareStatement(
const std::string &sql) {
256 auto detail = std::shared_ptr<SqliteStatement>(
new SqliteStatement(shared_from_this(), sql));
257 return makeStatement(detail);
267Sqlite::open(
const boost::filesystem::path &fileName) {
268 auto pimpl = std::shared_ptr<Detail::SqliteConnection>(
new Detail::SqliteConnection);
269 pimpl->open(fileName);
Holds a value or nothing.