From 3c1647273644a8a68100faa702a52e89a691425d Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Thu, 6 Feb 2020 23:27:33 +0200 Subject: Expose READ_ONLY_MODE_KEY property for DatabaseFileSource --- .../include/mbgl/storage/offline_database.hpp | 12 +- .../default/src/mbgl/storage/offline_database.cpp | 145 ++++++++++++++++----- 2 files changed, 116 insertions(+), 41 deletions(-) diff --git a/platform/default/include/mbgl/storage/offline_database.hpp b/platform/default/include/mbgl/storage/offline_database.hpp index 67a19fcf26..62a82f3a25 100644 --- a/platform/default/include/mbgl/storage/offline_database.hpp +++ b/platform/default/include/mbgl/storage/offline_database.hpp @@ -9,10 +9,10 @@ #include #include -#include +#include +#include #include #include -#include namespace mapbox { namespace sqlite { @@ -38,8 +38,6 @@ struct MapboxTileLimitExceededException : util::Exception { class OfflineDatabase : private util::noncopyable { public: - // Limits affect ambient caching (put) only; resources required by offline - // regions are exempt. OfflineDatabase(std::string path); ~OfflineDatabase(); @@ -96,6 +94,8 @@ public: std::exception_ptr pack(); void runPackDatabaseAutomatically(bool autopack_) { autopack = autopack_; } + void reopenDatabaseReadOnly(bool readOnly); + private: void initialize(); void handleError(const mapbox::sqlite::Exception&, const char* action); @@ -112,6 +112,7 @@ private: void cleanup(); bool disabled(); void vacuum(); + void checkFlags(); mapbox::sqlite::Statement& getStatement(const char *); @@ -139,7 +140,7 @@ private: std::string path; std::unique_ptr db; - std::unordered_map> statements; + std::map> statements; template T getPragma(const char *); @@ -151,6 +152,7 @@ private: bool evict(uint64_t neededFreeSize); bool autopack = true; + bool readOnly = false; }; } // namespace mbgl diff --git a/platform/default/src/mbgl/storage/offline_database.cpp b/platform/default/src/mbgl/storage/offline_database.cpp index 974815191b..643818fc99 100644 --- a/platform/default/src/mbgl/storage/offline_database.cpp +++ b/platform/default/src/mbgl/storage/offline_database.cpp @@ -31,6 +31,15 @@ void OfflineDatabase::initialize() { assert(!db); assert(statements.empty()); + if (readOnly) { + db = std::make_unique(mapbox::sqlite::Database::open(path, mapbox::sqlite::ReadOnly)); + + db->setBusyTimeout(Milliseconds::max()); + db->exec("PRAGMA foreign_keys = ON"); + + return; + } + db = std::make_unique( mapbox::sqlite::Database::open(path, mapbox::sqlite::ReadWriteCreate)); db->setBusyTimeout(Milliseconds::max()); @@ -153,12 +162,16 @@ void OfflineDatabase::removeExisting() { void OfflineDatabase::removeOldCacheTable() { assert(db); + checkFlags(); + db->exec("DROP TABLE IF EXISTS http_cache"); if (autopack) vacuum(); } void OfflineDatabase::createSchema() { assert(db); + checkFlags(); + vacuum(); db->exec("PRAGMA journal_mode = DELETE"); db->exec("PRAGMA synchronous = FULL"); @@ -170,6 +183,8 @@ void OfflineDatabase::createSchema() { void OfflineDatabase::migrateToVersion3() { assert(db); + checkFlags(); + vacuum(); db->exec("PRAGMA user_version = 3"); } @@ -182,6 +197,8 @@ void OfflineDatabase::migrateToVersion3() { void OfflineDatabase::migrateToVersion5() { assert(db); + checkFlags(); + db->exec("PRAGMA journal_mode = DELETE"); db->exec("PRAGMA synchronous = FULL"); db->exec("PRAGMA user_version = 5"); @@ -189,6 +206,8 @@ void OfflineDatabase::migrateToVersion5() { void OfflineDatabase::migrateToVersion6() { assert(db); + checkFlags(); + mapbox::sqlite::Transaction transaction(*db); db->exec("ALTER TABLE resources ADD COLUMN must_revalidate INTEGER NOT NULL DEFAULT 0"); db->exec("ALTER TABLE tiles ADD COLUMN must_revalidate INTEGER NOT NULL DEFAULT 0"); @@ -198,6 +217,8 @@ void OfflineDatabase::migrateToVersion6() { void OfflineDatabase::vacuum() { assert(db); + checkFlags(); + if (getPragma("PRAGMA auto_vacuum") != 2 /*INCREMENTAL*/) { db->exec("PRAGMA auto_vacuum = INCREMENTAL"); db->exec("VACUUM"); @@ -206,6 +227,12 @@ void OfflineDatabase::vacuum() { } } +void OfflineDatabase::checkFlags() { + if (readOnly) { + throw std::runtime_error("Cannot modify database in read-only mode"); + } +} + mapbox::sqlite::Statement& OfflineDatabase::getStatement(const char* sql) { if (!db) { initialize(); @@ -248,6 +275,8 @@ optional OfflineDatabase::hasInternal(const Resource& resource) { } std::pair OfflineDatabase::put(const Resource& resource, const Response& response) try { + if (readOnly) return {false, 0}; + if (!db) { initialize(); } @@ -266,6 +295,8 @@ std::pair OfflineDatabase::put(const Resource& resource, const R } std::pair OfflineDatabase::putInternal(const Resource& resource, const Response& response, bool evict_) { + checkFlags(); + if (response.error) { return { false, 0 }; } @@ -303,19 +334,20 @@ std::pair OfflineDatabase::putInternal(const Resource& resource, optional> OfflineDatabase::getResource(const Resource& resource) { // Update accessed timestamp used for LRU eviction. - try { - mapbox::sqlite::Query accessedQuery{ getStatement("UPDATE resources SET accessed = ?1 WHERE url = ?2") }; - accessedQuery.bind(1, util::now()); - accessedQuery.bind(2, resource.url); - accessedQuery.run(); - } catch (const mapbox::sqlite::Exception& ex) { - if (ex.code == mapbox::sqlite::ResultCode::NotADB || - ex.code == mapbox::sqlite::ResultCode::Corrupt) { - throw; - } + if (!readOnly) { + try { + mapbox::sqlite::Query accessedQuery{getStatement("UPDATE resources SET accessed = ?1 WHERE url = ?2")}; + accessedQuery.bind(1, util::now()); + accessedQuery.bind(2, resource.url); + accessedQuery.run(); + } catch (const mapbox::sqlite::Exception& ex) { + if (ex.code == mapbox::sqlite::ResultCode::NotADB || ex.code == mapbox::sqlite::ResultCode::Corrupt) { + throw; + } - // If we don't have any indication that the database is corrupt, continue as usual. - Log::Warning(Event::Database, static_cast(ex.code), "Can't update timestamp: %s", ex.what()); + // If we don't have any indication that the database is corrupt, continue as usual. + Log::Warning(Event::Database, static_cast(ex.code), "Can't update timestamp: %s", ex.what()); + } } // clang-format off @@ -368,6 +400,8 @@ bool OfflineDatabase::putResource(const Resource& resource, const Response& response, const std::string& data, bool compressed) { + checkFlags(); + if (response.notModified) { // clang-format off mapbox::sqlite::Query notModifiedQuery{ getStatement( @@ -451,32 +485,34 @@ bool OfflineDatabase::putResource(const Resource& resource, optional> OfflineDatabase::getTile(const Resource::TileData& tile) { // Update accessed timestamp used for LRU eviction. - try { - // clang-format off - mapbox::sqlite::Query accessedQuery{ getStatement( - "UPDATE tiles " - "SET accessed = ?1 " - "WHERE url_template = ?2 " - " AND pixel_ratio = ?3 " - " AND x = ?4 " - " AND y = ?5 " - " AND z = ?6 ") }; - // clang-format on + if (!readOnly) { + try { + // clang-format off + mapbox::sqlite::Query accessedQuery{ getStatement( + "UPDATE tiles " + "SET accessed = ?1 " + "WHERE url_template = ?2 " + " AND pixel_ratio = ?3 " + " AND x = ?4 " + " AND y = ?5 " + " AND z = ?6 ") }; + // clang-format on + + accessedQuery.bind(1, util::now()); + accessedQuery.bind(2, tile.urlTemplate); + accessedQuery.bind(3, tile.pixelRatio); + accessedQuery.bind(4, tile.x); + accessedQuery.bind(5, tile.y); + accessedQuery.bind(6, tile.z); + accessedQuery.run(); + } catch (const mapbox::sqlite::Exception& ex) { + if (ex.code == mapbox::sqlite::ResultCode::NotADB || ex.code == mapbox::sqlite::ResultCode::Corrupt) { + throw; + } - accessedQuery.bind(1, util::now()); - accessedQuery.bind(2, tile.urlTemplate); - accessedQuery.bind(3, tile.pixelRatio); - accessedQuery.bind(4, tile.x); - accessedQuery.bind(5, tile.y); - accessedQuery.bind(6, tile.z); - accessedQuery.run(); - } catch (const mapbox::sqlite::Exception& ex) { - if (ex.code == mapbox::sqlite::ResultCode::NotADB || ex.code == mapbox::sqlite::ResultCode::Corrupt) { - throw; + // If we don't have any indication that the database is corrupt, continue as usual. + Log::Warning(Event::Database, static_cast(ex.code), "Can't update timestamp: %s", ex.what()); } - - // If we don't have any indication that the database is corrupt, continue as usual. - Log::Warning(Event::Database, static_cast(ex.code), "Can't update timestamp: %s", ex.what()); } // clang-format off @@ -552,6 +588,8 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, const Response& response, const std::string& data, bool compressed) { + checkFlags(); + if (response.notModified) { // clang-format off mapbox::sqlite::Query notModifiedQuery{ getStatement( @@ -652,6 +690,8 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, } std::exception_ptr OfflineDatabase::invalidateAmbientCache() try { + checkFlags(); + // clang-format off mapbox::sqlite::Query tileQuery{ getStatement( "UPDATE tiles " @@ -683,6 +723,8 @@ std::exception_ptr OfflineDatabase::invalidateAmbientCache() try { } std::exception_ptr OfflineDatabase::clearAmbientCache() try { + checkFlags(); + // clang-format off mapbox::sqlite::Query tileQuery{ getStatement( "DELETE FROM tiles " @@ -714,6 +756,8 @@ std::exception_ptr OfflineDatabase::clearAmbientCache() try { } std::exception_ptr OfflineDatabase::invalidateRegion(int64_t regionID) try { + checkFlags(); + { // clang-format off mapbox::sqlite::Query tileQuery{ getStatement( @@ -776,6 +820,8 @@ expected OfflineDatabase::listRegions() try expected OfflineDatabase::createRegion(const OfflineRegionDefinition& definition, const OfflineRegionMetadata& metadata) try { + checkFlags(); + // clang-format off mapbox::sqlite::Query query{ getStatement( "INSERT INTO regions (definition, description) " @@ -793,6 +839,8 @@ OfflineDatabase::createRegion(const OfflineRegionDefinition& definition, expected OfflineDatabase::mergeDatabase(const std::string& sideDatabasePath) { + checkFlags(); + try { // clang-format off mapbox::sqlite::Query query{ getStatement("ATTACH DATABASE ?1 AS side") }; @@ -869,6 +917,8 @@ OfflineDatabase::mergeDatabase(const std::string& sideDatabasePath) { expected OfflineDatabase::updateMetadata(const int64_t regionID, const OfflineRegionMetadata& metadata) try { + checkFlags(); + // clang-format off mapbox::sqlite::Query query{ getStatement( "UPDATE regions SET description = ?1 " @@ -885,6 +935,8 @@ OfflineDatabase::updateMetadata(const int64_t regionID, const OfflineRegionMetad } std::exception_ptr OfflineDatabase::deleteRegion(OfflineRegion&& region) try { + checkFlags(); + { mapbox::sqlite::Query query{ getStatement("DELETE FROM regions WHERE id = ?") }; query.bind(1, region.getID()); @@ -920,6 +972,8 @@ optional OfflineDatabase::hasRegionResource(const Resource& resource) t uint64_t OfflineDatabase::putRegionResource(int64_t regionID, const Resource& resource, const Response& response) try { + checkFlags(); + if (!db) { initialize(); } @@ -935,6 +989,8 @@ uint64_t OfflineDatabase::putRegionResource(int64_t regionID, void OfflineDatabase::putRegionResources(int64_t regionID, const std::list>& resources, OfflineRegionStatus& status) try { + checkFlags(); + if (!db) { initialize(); } @@ -978,6 +1034,8 @@ void OfflineDatabase::putRegionResources(int64_t regionID, } uint64_t OfflineDatabase::putRegionResourceInternal(int64_t regionID, const Resource& resource, const Response& response) { + checkFlags(); + uint64_t size = putInternal(resource, response, false).second; bool previouslyUnused = markUsed(regionID, resource); @@ -996,6 +1054,8 @@ uint64_t OfflineDatabase::putRegionResourceInternal(int64_t regionID, const Reso } bool OfflineDatabase::markUsed(int64_t regionID, const Resource& resource) { + checkFlags(); + if (resource.kind == Resource::Kind::Tile) { // clang-format off mapbox::sqlite::Query insertQuery{ getStatement( @@ -1148,6 +1208,8 @@ T OfflineDatabase::getPragma(const char* sql) { // delete an arbitrary number of old cache entries. The free pages approach saves // us from calling VACUUM or keeping a running total, which can be costly. bool OfflineDatabase::evict(uint64_t neededFreeSize) { + checkFlags(); + uint64_t pageSize = getPragma("PRAGMA page_size"); uint64_t pageCount = getPragma("PRAGMA page_count"); @@ -1322,4 +1384,15 @@ std::exception_ptr OfflineDatabase::resetDatabase() try { return std::current_exception(); } +void OfflineDatabase::reopenDatabaseReadOnly(bool readOnly_) { + if (readOnly == readOnly_) return; + try { + cleanup(); + readOnly = readOnly_; + initialize(); + } catch (...) { + handleError("reopen database read-only"); + } +} + } // namespace mbgl -- cgit v1.2.1