1 #include "sqlstorage_base.h"
2 #include "storage_exception.h"
6 #include "utilities/utils.h"
8 boost::filesystem::path SQLStorageBase::dbPath()
const {
return sqldb_path_; }
10 StorageLock::StorageLock(boost::filesystem::path path) : lock_path(std::move(path)) {
13 fs.open(lock_path.c_str(), std::fstream::in | std::fstream::out | std::fstream::app);
15 fl_ = lock_path.c_str();
16 if (!fl_.try_lock()) {
21 StorageLock::~StorageLock() {
23 if (!lock_path.empty()) {
25 std::remove(lock_path.c_str());
27 }
catch (std::exception& e) {
31 SQLStorageBase::SQLStorageBase(boost::filesystem::path sqldb_path,
bool readonly,
32 std::vector<std::string> schema_migrations,
33 std::vector<std::string> schema_rollback_migrations, std::string current_schema,
34 int current_schema_version)
35 : sqldb_path_(std::move(sqldb_path)),
37 mutex_(new std::mutex()),
38 schema_migrations_(std::move(schema_migrations)),
39 schema_rollback_migrations_(std::move(schema_rollback_migrations)),
40 current_schema_(std::move(current_schema)),
41 current_schema_version_(current_schema_version) {
42 boost::filesystem::path db_parent_path = dbPath().parent_path();
43 if (!boost::filesystem::is_directory(db_parent_path)) {
44 Utils::createDirectories(db_parent_path, S_IRWXU);
47 if (stat(db_parent_path.c_str(), &st) < 0) {
48 throw StorageException(std::string(
"Could not check storage directory permissions: ") + std::strerror(errno));
50 if ((st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
53 if ((st.st_mode & (S_IRGRP | S_IROTH)) != 0) {
55 if (chmod(db_parent_path.c_str(), S_IRWXU) < 0) {
63 lock =
StorageLock(db_parent_path /
"storage.lock");
65 LOG_WARNING <<
"\033[31m"
66 <<
"Storage in " << db_parent_path
67 <<
" is already in use, running several instances concurrently may result in data corruption!"
79 if (db.get_rc() != SQLITE_OK) {
85 std::string SQLStorageBase::getTableSchemaFromDb(
const std::string& tablename) {
88 auto statement = db.prepareStatement<std::string>(
89 "SELECT sql FROM sqlite_master WHERE type='table' AND tbl_name=? LIMIT 1;", tablename);
91 if (statement.step() != SQLITE_ROW) {
92 LOG_ERROR <<
"Can't get schema of " << tablename <<
": " << db.errmsg();
96 auto schema = statement.get_result_col_str(0);
97 if (schema == boost::none) {
101 return schema.value() +
";";
104 bool SQLStorageBase::dbInsertBackMigrations(
SQLite3Guard& db,
int version_latest) {
105 if (schema_rollback_migrations_.empty()) {
106 LOG_TRACE <<
"No backward migrations defined";
110 if (schema_rollback_migrations_.size() <
static_cast<size_t>(version_latest) + 1) {
111 LOG_ERROR <<
"Backward migrations from " << schema_rollback_migrations_.size() <<
" to " << version_latest
116 for (
int k = 1; k <= version_latest; k++) {
117 if (schema_rollback_migrations_.at(
static_cast<size_t>(k)).empty()) {
120 auto statement = db.prepareStatement(
"INSERT OR REPLACE INTO rollback_migrations VALUES (?,?);", k,
121 schema_rollback_migrations_.at(
static_cast<uint32_t
>(k)));
122 if (statement.step() != SQLITE_DONE) {
123 LOG_ERROR <<
"Can't insert rollback migration script: " << db.errmsg();
131 bool SQLStorageBase::dbMigrateForward(
int version_from,
int version_to) {
132 if (version_to <= 0) {
133 version_to = current_schema_version_;
136 LOG_INFO <<
"Migrating DB from version " << version_from <<
" to version " << version_to;
141 db.beginTransaction();
146 for (int32_t k = version_from + 1; k <= version_to; k++) {
147 auto result_code = db.exec(schema_migrations_.at(
static_cast<size_t>(k)),
nullptr,
nullptr);
148 if (result_code != SQLITE_OK) {
149 LOG_ERROR <<
"Can't migrate DB from version " << (k - 1) <<
" to version " << k <<
": " << db.errmsg();
154 if (!dbInsertBackMigrations(db, version_to)) {
158 db.commitTransaction();
163 bool SQLStorageBase::dbMigrateBackward(
int version_from,
int version_to) {
164 if (version_to <= 0) {
165 version_to = current_schema_version_;
168 LOG_INFO <<
"Migrating DB backward from version " << version_from <<
" to version " << version_to;
171 for (
int ver = version_from; ver > version_to; --ver) {
172 std::string migration;
175 auto statement = db.prepareStatement(
"SELECT migration FROM rollback_migrations WHERE version_from=?;", ver);
176 if (statement.step() != SQLITE_ROW) {
177 LOG_ERROR <<
"Can't extract migration script: " << db.errmsg();
180 migration = *(statement.get_result_col_str(0));
183 if (db.exec(migration,
nullptr,
nullptr) != SQLITE_OK) {
184 LOG_ERROR <<
"Can't migrate db from version " << (ver) <<
" to version " << ver - 1 <<
": " << db.errmsg();
187 if (db.exec(std::string(
"DELETE FROM rollback_migrations WHERE version_from=") + std::to_string(ver) +
";",
nullptr,
188 nullptr) != SQLITE_OK) {
189 LOG_ERROR <<
"Can't clear old migration script: " << db.errmsg();
196 bool SQLStorageBase::dbMigrate() {
197 DbVersion schema_version = getVersion();
199 if (schema_version == DbVersion::kInvalid) {
200 LOG_ERROR <<
"Sqlite database file is invalid.";
204 auto schema_num_version =
static_cast<int32_t
>(schema_version);
205 if (schema_num_version == current_schema_version_) {
210 LOG_ERROR <<
"Database is opened in readonly mode and cannot be migrated to latest version";
214 if (schema_version == DbVersion::kEmpty) {
215 LOG_INFO <<
"Bootstraping DB to version " << current_schema_version_;
219 db.beginTransaction();
224 auto result_code = db.exec(current_schema_,
nullptr,
nullptr);
225 if (result_code != SQLITE_OK) {
226 LOG_ERROR <<
"Can't bootstrap DB to version " << current_schema_version_ <<
": " << db.errmsg();
230 if (!dbInsertBackMigrations(db, current_schema_version_)) {
234 db.commitTransaction();
235 }
else if (schema_num_version > current_schema_version_) {
236 dbMigrateBackward(schema_num_version);
238 dbMigrateForward(schema_num_version);
244 DbVersion SQLStorageBase::getVersion() {
248 auto statement = db.prepareStatement(
"SELECT count(*) FROM sqlite_master WHERE type='table';");
249 if (statement.step() != SQLITE_ROW) {
250 LOG_ERROR <<
"Can't get tables count: " << db.errmsg();
251 return DbVersion::kInvalid;
254 if (statement.get_result_col_int(0) == 0) {
255 return DbVersion::kEmpty;
258 statement = db.prepareStatement(
"SELECT version FROM version LIMIT 1;");
260 if (statement.step() != SQLITE_ROW) {
261 LOG_ERROR <<
"Can't get database version: " << db.errmsg();
262 return DbVersion::kInvalid;
266 return DbVersion(statement.get_result_col_int(0));
267 }
catch (
const std::exception&) {
268 return DbVersion::kInvalid;
271 return DbVersion::kInvalid;