#include "sqlite3.hpp" #include #include #include #include #include #include #include namespace mapbox { namespace sqlite { class DatabaseImpl { public: DatabaseImpl(const char* filename, int flags) { const int error = sqlite3_open_v2(filename, &db, flags, nullptr); if (error != SQLITE_OK) { const auto message = sqlite3_errmsg(db); db = nullptr; throw Exception { error, message }; } } ~DatabaseImpl() { if (!db) return; const int error = sqlite3_close(db); if (error != SQLITE_OK) { mbgl::Log::Error(mbgl::Event::Database, "%s (Code %i)", sqlite3_errmsg(db), error); } } sqlite3* db = nullptr; }; class StatementImpl { public: StatementImpl(sqlite3* db, const char* sql) { const int error = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); if (error != SQLITE_OK) { stmt = nullptr; throw Exception { error, sqlite3_errmsg(db) }; } } ~StatementImpl() { if (!stmt) return; sqlite3_finalize(stmt); } void check(int err) { if (err != SQLITE_OK) { throw Exception { err, sqlite3_errmsg(sqlite3_db_handle(stmt)) }; } } sqlite3_stmt* stmt = nullptr; int64_t lastInsertRowId = 0; int64_t changes = 0; }; template using optional = std::experimental::optional; static const char* codeToString(const int err) { switch (err) { case SQLITE_OK: return "SQLITE_OK"; case SQLITE_ERROR: return "SQLITE_ERROR"; case SQLITE_INTERNAL: return "SQLITE_INTERNAL"; case SQLITE_PERM: return "SQLITE_PERM"; case SQLITE_ABORT: return "SQLITE_ABORT"; case SQLITE_BUSY: return "SQLITE_BUSY"; case SQLITE_LOCKED: return "SQLITE_LOCKED"; case SQLITE_NOMEM: return "SQLITE_NOMEM"; case SQLITE_READONLY: return "SQLITE_READONLY"; case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT"; case SQLITE_IOERR: return "SQLITE_IOERR"; case SQLITE_CORRUPT: return "SQLITE_CORRUPT"; case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND"; case SQLITE_FULL: return "SQLITE_FULL"; case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN"; case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL"; case SQLITE_EMPTY: return "SQLITE_EMPTY"; case SQLITE_SCHEMA: return "SQLITE_SCHEMA"; case SQLITE_TOOBIG: return "SQLITE_TOOBIG"; case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT"; case SQLITE_MISMATCH: return "SQLITE_MISMATCH"; case SQLITE_MISUSE: return "SQLITE_MISUSE"; case SQLITE_NOLFS: return "SQLITE_NOLFS"; case SQLITE_AUTH: return "SQLITE_AUTH"; case SQLITE_FORMAT: return "SQLITE_FORMAT"; case SQLITE_RANGE: return "SQLITE_RANGE"; case SQLITE_NOTADB: return "SQLITE_NOTADB"; case SQLITE_NOTICE: return "SQLITE_NOTICE"; case SQLITE_WARNING: return "SQLITE_WARNING"; case SQLITE_ROW: return "SQLITE_ROW"; case SQLITE_DONE: return "SQLITE_DONE"; default: return ""; } } static void errorLogCallback(void *, const int err, const char *msg) { auto severity = mbgl::EventSeverity::Info; switch (err) { case SQLITE_ERROR: // Generic error case SQLITE_INTERNAL: // Internal logic error in SQLite case SQLITE_PERM: // Access permission denied case SQLITE_ABORT: // Callback routine requested an abort case SQLITE_BUSY: // The database file is locked case SQLITE_LOCKED: // A table in the database is locked case SQLITE_NOMEM: // A malloc() failed case SQLITE_READONLY: // Attempt to write a readonly database case SQLITE_INTERRUPT: // Operation terminated by sqlite3_interrupt( case SQLITE_IOERR: // Some kind of disk I/O error occurred case SQLITE_CORRUPT: // The database disk image is malformed case SQLITE_NOTFOUND: // Unknown opcode in sqlite3_file_control() case SQLITE_FULL: // Insertion failed because database is full case SQLITE_CANTOPEN: // Unable to open the database file case SQLITE_PROTOCOL: // Database lock protocol error case SQLITE_EMPTY: // Internal use only case SQLITE_SCHEMA: // The database schema changed case SQLITE_TOOBIG: // String or BLOB exceeds size limit case SQLITE_CONSTRAINT: // Abort due to constraint violation case SQLITE_MISMATCH: // Data type mismatch case SQLITE_MISUSE: // Library used incorrectly case SQLITE_NOLFS: // Uses OS features not supported on host case SQLITE_AUTH: // Authorization denied case SQLITE_FORMAT: // Not used case SQLITE_RANGE: // 2nd parameter to sqlite3_bind out of range case SQLITE_NOTADB: // File opened that is not a database file severity = mbgl::EventSeverity::Error; break; case SQLITE_WARNING: // Warnings from sqlite3_log() severity = mbgl::EventSeverity::Warning; break; case SQLITE_NOTICE: // Notifications from sqlite3_log() default: break; } mbgl::Log::Record(severity, mbgl::Event::Database, "%s (%s)", msg, codeToString(err)); } const static bool sqliteVersionCheck __attribute__((unused)) = []() { if (sqlite3_libversion_number() / 1000000 != SQLITE_VERSION_NUMBER / 1000000) { char message[96]; snprintf(message, 96, "sqlite3 libversion mismatch: headers report %d, but library reports %d", SQLITE_VERSION_NUMBER, sqlite3_libversion_number()); throw std::runtime_error(message); } // Enable SQLite logging before initializing the database. sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, nullptr); return true; }(); Database::Database(const std::string &filename, int flags) : impl(std::make_unique(filename.c_str(), flags)) { } Database::Database(Database &&other) : impl(std::move(other.impl)) {} Database &Database::operator=(Database &&other) { std::swap(impl, other.impl); return *this; } Database::~Database() = default; void Database::setBusyTimeout(std::chrono::milliseconds timeout) { assert(impl); const int err = sqlite3_busy_timeout(impl->db, int(std::min(timeout.count(), std::numeric_limits::max()))); if (err != SQLITE_OK) { throw Exception { err, sqlite3_errmsg(impl->db) }; } } void Database::exec(const std::string &sql) { assert(impl); char *msg = nullptr; const int err = sqlite3_exec(impl->db, sql.c_str(), nullptr, nullptr, &msg); if (msg) { const std::string message = msg; sqlite3_free(msg); throw Exception { err, message }; } else if (err != SQLITE_OK) { throw Exception { err, sqlite3_errmsg(impl->db) }; } } Statement::Statement(Database& db, const char* sql) : impl(std::make_unique(db.impl->db, sql)) { } Statement::~Statement() { #ifndef NDEBUG // Crash if we're destructing this object while we know a Query object references this. assert(!used); #endif } Query::Query(Statement& stmt_) : stmt(stmt_) { assert(stmt.impl); #ifndef NDEBUG assert(!stmt.used); stmt.used = true; #endif } Query::~Query() { reset(); clearBindings(); #ifndef NDEBUG stmt.used = false; #endif } template <> void Query::bind(int offset, std::nullptr_t) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_null(stmt.impl->stmt, offset)); } template <> void Query::bind(int offset, int8_t value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value)); } template <> void Query::bind(int offset, int16_t value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value)); } template <> void Query::bind(int offset, int32_t value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value)); } template <> void Query::bind(int offset, int64_t value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value)); } template <> void Query::bind(int offset, uint8_t value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value)); } template <> void Query::bind(int offset, uint16_t value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value)); } template <> void Query::bind(int offset, uint32_t value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value)); } template <> void Query::bind(int offset, float value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_double(stmt.impl->stmt, offset, value)); } template <> void Query::bind(int offset, double value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_double(stmt.impl->stmt, offset, value)); } template <> void Query::bind(int offset, bool value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_int(stmt.impl->stmt, offset, value)); } template <> void Query::bind(int offset, const char *value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_text(stmt.impl->stmt, offset, value, -1, SQLITE_STATIC)); } // We currently cannot use sqlite3_bind_blob64 / sqlite3_bind_text64 because they // was introduced in SQLite 3.8.7, and we need to support earlier versions: // iOS 8.0: 3.7.13 // iOS 8.2: 3.8.5 // According to http://stackoverflow.com/questions/14288128/what-version-of-sqlite-does-ios-provide, // the first iOS version with 3.8.7+ was 9.0, with 3.8.8. void Query::bind(int offset, const char * value, std::size_t length, bool retain) { assert(stmt.impl); if (length > std::numeric_limits::max()) { throw std::range_error("value too long for sqlite3_bind_text"); } stmt.impl->check(sqlite3_bind_text(stmt.impl->stmt, offset, value, int(length), retain ? SQLITE_TRANSIENT : SQLITE_STATIC)); } void Query::bind(int offset, const std::string& value, bool retain) { bind(offset, value.data(), value.size(), retain); } void Query::bindBlob(int offset, const void * value, std::size_t length, bool retain) { assert(stmt.impl); if (length > std::numeric_limits::max()) { throw std::range_error("value too long for sqlite3_bind_text"); } stmt.impl->check(sqlite3_bind_blob(stmt.impl->stmt, offset, value, int(length), retain ? SQLITE_TRANSIENT : SQLITE_STATIC)); } void Query::bindBlob(int offset, const std::vector& value, bool retain) { bindBlob(offset, value.data(), value.size(), retain); } template <> void Query::bind( int offset, std::chrono::time_point value) { assert(stmt.impl); stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, std::chrono::system_clock::to_time_t(value))); } template <> void Query::bind(int offset, optional value) { if (!value) { bind(offset, nullptr); } else { bind(offset, *value); } } template <> void Query::bind( int offset, optional> value) { if (!value) { bind(offset, nullptr); } else { bind(offset, *value); } } bool Query::run() { assert(stmt.impl); const int err = sqlite3_step(stmt.impl->stmt); stmt.impl->lastInsertRowId = sqlite3_last_insert_rowid(sqlite3_db_handle(stmt.impl->stmt)); stmt.impl->changes = sqlite3_changes(sqlite3_db_handle(stmt.impl->stmt)); if (err == SQLITE_DONE) { return false; } else if (err == SQLITE_ROW) { return true; } else if (err != SQLITE_OK) { throw Exception { err, sqlite3_errmsg(sqlite3_db_handle(stmt.impl->stmt)) }; } else { return false; } } template <> bool Query::get(int offset) { assert(stmt.impl); return sqlite3_column_int(stmt.impl->stmt, offset); } template <> int Query::get(int offset) { assert(stmt.impl); return sqlite3_column_int(stmt.impl->stmt, offset); } template <> int64_t Query::get(int offset) { assert(stmt.impl); return sqlite3_column_int64(stmt.impl->stmt, offset); } template <> double Query::get(int offset) { assert(stmt.impl); return sqlite3_column_double(stmt.impl->stmt, offset); } template <> std::string Query::get(int offset) { assert(stmt.impl); return { reinterpret_cast(sqlite3_column_blob(stmt.impl->stmt, offset)), size_t(sqlite3_column_bytes(stmt.impl->stmt, offset)) }; } template <> std::vector Query::get(int offset) { assert(stmt.impl); const auto* begin = reinterpret_cast(sqlite3_column_blob(stmt.impl->stmt, offset)); const uint8_t* end = begin + sqlite3_column_bytes(stmt.impl->stmt, offset); return { begin, end }; } template <> std::chrono::time_point Query::get(int offset) { assert(stmt.impl); return std::chrono::time_point_cast( std::chrono::system_clock::from_time_t(sqlite3_column_int64(stmt.impl->stmt, offset))); } template <> optional Query::get(int offset) { assert(stmt.impl); if (sqlite3_column_type(stmt.impl->stmt, offset) == SQLITE_NULL) { return optional(); } else { return get(offset); } } template <> optional Query::get(int offset) { assert(stmt.impl); if (sqlite3_column_type(stmt.impl->stmt, offset) == SQLITE_NULL) { return optional(); } else { return get(offset); } } template <> optional Query::get(int offset) { assert(stmt.impl); if (sqlite3_column_type(stmt.impl->stmt, offset) == SQLITE_NULL) { return optional(); } else { return get(offset); } } template <> optional> Query::get(int offset) { assert(stmt.impl); if (sqlite3_column_type(stmt.impl->stmt, offset) == SQLITE_NULL) { return {}; } else { return get>( offset); } } void Query::reset() { assert(stmt.impl); sqlite3_reset(stmt.impl->stmt); } void Query::clearBindings() { assert(stmt.impl); sqlite3_clear_bindings(stmt.impl->stmt); } int64_t Query::lastInsertRowId() const { assert(stmt.impl); return stmt.impl->lastInsertRowId; } uint64_t Query::changes() const { assert(stmt.impl); auto changes_ = stmt.impl->changes; return (changes_ < 0 ? 0 : changes_); } Transaction::Transaction(Database& db_, Mode mode) : db(db_) { switch (mode) { case Deferred: db.exec("BEGIN DEFERRED TRANSACTION"); break; case Immediate: db.exec("BEGIN IMMEDIATE TRANSACTION"); break; case Exclusive: db.exec("BEGIN EXCLUSIVE TRANSACTION"); break; } } Transaction::~Transaction() { if (needRollback) { try { rollback(); } catch (...) { // Ignore failed rollbacks in destructor. } } } void Transaction::commit() { needRollback = false; db.exec("COMMIT TRANSACTION"); } void Transaction::rollback() { needRollback = false; db.exec("ROLLBACK TRANSACTION"); } } // namespace sqlite } // namespace mapbox