1 #include "sqlstorage_base.h"
2 #include "storage_exception.h"
6 boost::filesystem::path SQLStorageBase::dbPath()
const {
return sqldb_path_; }
8 StorageLock::StorageLock(boost::filesystem::path path) : lock_path(std::move(path)) {
11 fs.open(lock_path.c_str(), std::fstream::in | std::fstream::out | std::fstream::app);
13 fl_ = lock_path.c_str();
14 if (!fl_.try_lock()) {
19 StorageLock::~StorageLock() {
21 if (!lock_path.empty()) {
23 std::remove(lock_path.c_str());
25 }
catch (std::exception& e) {
29 SQLStorageBase::SQLStorageBase(boost::filesystem::path sqldb_path,
bool readonly,
30 std::vector<std::string> schema_migrations,
31 std::vector<std::string> schema_rollback_migrations, std::string current_schema,
32 int current_schema_version)
33 : sqldb_path_(std::move(sqldb_path)),
35 mutex_(new std::mutex()),
36 schema_migrations_(std::move(schema_migrations)),
37 schema_rollback_migrations_(std::move(schema_rollback_migrations)),
38 current_schema_(std::move(current_schema)),
39 current_schema_version_(current_schema_version) {
40 boost::filesystem::path db_parent_path = dbPath().parent_path();
41 if (!boost::filesystem::is_directory(db_parent_path)) {
42 Utils::createDirectories(db_parent_path, S_IRWXU);
45 if (stat(db_parent_path.c_str(), &st) < 0) {
46 throw StorageException(std::string(
"Could not check storage directory permissions: ") + std::strerror(errno));
48 if ((st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
51 if ((st.st_mode & (S_IRGRP | S_IROTH)) != 0) {
53 if (chmod(db_parent_path.c_str(), S_IRWXU) < 0) {
61 lock =
StorageLock(db_parent_path /
"storage.lock");
63 LOG_WARNING <<
"\033[31m"
64 <<
"Storage in " << db_parent_path
65 <<
" is already in use, running several instances concurrently may result in data corruption!"
77 if (db.get_rc() != SQLITE_OK) {
78 throw SQLException(std::string(
"Can't open database: ") + db.errmsg());
83 std::string SQLStorageBase::getTableSchemaFromDb(
const std::string& tablename) {
86 auto statement = db.prepareStatement<std::string>(
87 "SELECT sql FROM sqlite_master WHERE type='table' AND tbl_name=? LIMIT 1;", tablename);
89 if (statement.step() != SQLITE_ROW) {
90 LOG_ERROR <<
"Can't get schema of " << tablename <<
": " << db.errmsg();
94 auto schema = statement.get_result_col_str(0);
95 if (schema == boost::none) {
99 return schema.value() +
";";
102 bool SQLStorageBase::dbInsertBackMigrations(
SQLite3Guard& db,
int version_latest) {
103 if (schema_rollback_migrations_.empty()) {
104 LOG_TRACE <<
"No backward migrations defined";
108 if (schema_rollback_migrations_.size() < static_cast<size_t>(version_latest) + 1) {
109 LOG_ERROR <<
"Backward migrations from " << schema_rollback_migrations_.size() <<
" to " << version_latest
114 for (
int k = 1; k <= version_latest; k++) {
115 if (schema_rollback_migrations_.at(static_cast<size_t>(k)).empty()) {
118 auto statement = db.prepareStatement(
"INSERT OR REPLACE INTO rollback_migrations VALUES (?,?);", k,
119 schema_rollback_migrations_.at(static_cast<uint32_t>(k)));
120 if (statement.step() != SQLITE_DONE) {
121 LOG_ERROR <<
"Can't insert rollback migration script: " << db.errmsg();
129 bool SQLStorageBase::dbMigrateForward(
int version_from,
int version_to) {
130 if (version_to <= 0) {
131 version_to = current_schema_version_;
134 LOG_INFO <<
"Migrating DB from version " << version_from <<
" to version " << version_to;
138 if (!db.beginTransaction()) {
139 LOG_ERROR <<
"Can't start transaction: " << db.errmsg();
143 for (int32_t k = version_from + 1; k <= version_to; k++) {
144 auto result_code = db.exec(schema_migrations_.at(static_cast<size_t>(k)),
nullptr,
nullptr);
145 if (result_code != SQLITE_OK) {
146 LOG_ERROR <<
"Can't migrate DB from version " << (k - 1) <<
" to version " << k <<
": " << db.errmsg();
151 if (!dbInsertBackMigrations(db, version_to)) {
155 db.commitTransaction();
160 bool SQLStorageBase::dbMigrateBackward(
int version_from,
int version_to) {
161 if (version_to <= 0) {
162 version_to = current_schema_version_;
165 LOG_INFO <<
"Migrating DB backward from version " << version_from <<
" to version " << version_to;
168 for (
int ver = version_from; ver > version_to; --ver) {
169 std::string migration;
172 auto statement = db.prepareStatement(
"SELECT migration FROM rollback_migrations WHERE version_from=?;", ver);
173 if (statement.step() != SQLITE_ROW) {
174 LOG_ERROR <<
"Can't extract migration script: " << db.errmsg();
177 migration = *(statement.get_result_col_str(0));
180 if (db.exec(migration,
nullptr,
nullptr) != SQLITE_OK) {
181 LOG_ERROR <<
"Can't migrate db from version " << (ver) <<
" to version " << ver - 1 <<
": " << db.errmsg();
184 if (db.exec(std::string(
"DELETE FROM rollback_migrations WHERE version_from=") + std::to_string(ver) +
";",
nullptr,
185 nullptr) != SQLITE_OK) {
186 LOG_ERROR <<
"Can't clear old migration script: " << db.errmsg();
193 bool SQLStorageBase::dbMigrate() {
194 DbVersion schema_version = getVersion();
196 if (schema_version == DbVersion::kInvalid) {
197 LOG_ERROR <<
"Sqlite database file is invalid.";
201 auto schema_num_version = static_cast<int32_t>(schema_version);
202 if (schema_num_version == current_schema_version_) {
207 LOG_ERROR <<
"Database is opened in readonly mode and cannot be migrated to latest version";
211 if (schema_version == DbVersion::kEmpty) {
212 LOG_INFO <<
"Bootstraping DB to version " << current_schema_version_;
215 if (!db.beginTransaction()) {
216 LOG_ERROR <<
"Can't start transaction: " << db.errmsg();
220 auto result_code = db.exec(current_schema_,
nullptr,
nullptr);
221 if (result_code != SQLITE_OK) {
222 LOG_ERROR <<
"Can't bootstrap DB to version " << current_schema_version_ <<
": " << db.errmsg();
226 if (!dbInsertBackMigrations(db, current_schema_version_)) {
230 db.commitTransaction();
231 }
else if (schema_num_version > current_schema_version_) {
232 dbMigrateBackward(schema_num_version);
234 dbMigrateForward(schema_num_version);
240 DbVersion SQLStorageBase::getVersion() {
244 auto statement = db.prepareStatement(
"SELECT count(*) FROM sqlite_master WHERE type='table';");
245 if (statement.step() != SQLITE_ROW) {
246 LOG_ERROR <<
"Can't get tables count: " << db.errmsg();
247 return DbVersion::kInvalid;
250 if (statement.get_result_col_int(0) == 0) {
251 return DbVersion::kEmpty;
254 statement = db.prepareStatement(
"SELECT version FROM version LIMIT 1;");
256 if (statement.step() != SQLITE_ROW) {
257 LOG_ERROR <<
"Can't get database version: " << db.errmsg();
258 return DbVersion::kInvalid;
262 return DbVersion(statement.get_result_col_int(0));
263 }
catch (
const std::exception&) {
264 return DbVersion::kInvalid;
267 return DbVersion::kInvalid;