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 schema_migrations_(std::move(schema_migrations)),
36 schema_rollback_migrations_(std::move(schema_rollback_migrations)),
37 current_schema_(std::move(current_schema)),
38 current_schema_version_(current_schema_version) {
39 boost::filesystem::path db_parent_path = dbPath().parent_path();
40 if (!boost::filesystem::is_directory(db_parent_path)) {
41 Utils::createDirectories(db_parent_path, S_IRWXU);
44 if (stat(db_parent_path.c_str(), &st) < 0) {
45 throw StorageException(std::string(
"Could not check storage directory permissions: ") + std::strerror(errno));
47 if ((st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
50 if ((st.st_mode & (S_IRGRP | S_IROTH)) != 0) {
52 if (chmod(db_parent_path.c_str(), S_IRWXU) < 0) {
60 lock =
StorageLock(db_parent_path /
"storage.lock");
62 LOG_WARNING <<
"\033[31m"
63 <<
"Storage in " << db_parent_path
64 <<
" is already in use, running several instances concurrently may result in data corruption!"
76 if (db.get_rc() != SQLITE_OK) {
77 throw SQLException(std::string(
"Can't open database: ") + db.errmsg());
82 std::string SQLStorageBase::getTableSchemaFromDb(
const std::string& tablename) {
85 auto statement = db.prepareStatement<std::string>(
86 "SELECT sql FROM sqlite_master WHERE type='table' AND tbl_name=? LIMIT 1;", tablename);
88 if (statement.step() != SQLITE_ROW) {
89 LOG_ERROR <<
"Can't get schema of " << tablename <<
": " << db.errmsg();
93 auto schema = statement.get_result_col_str(0);
94 if (schema == boost::none) {
98 return schema.value() +
";";
101 bool SQLStorageBase::dbInsertBackMigrations(
SQLite3Guard& db,
int version_latest) {
102 if (schema_rollback_migrations_.empty()) {
103 LOG_TRACE <<
"No backward migrations defined";
107 if (schema_rollback_migrations_.size() < static_cast<size_t>(version_latest) + 1) {
108 LOG_ERROR <<
"Backward migrations from " << schema_rollback_migrations_.size() <<
" to " << version_latest
113 for (
int k = 1; k <= version_latest; k++) {
114 if (schema_rollback_migrations_.at(static_cast<size_t>(k)).empty()) {
117 auto statement = db.prepareStatement(
"INSERT OR REPLACE INTO rollback_migrations VALUES (?,?);", k,
118 schema_rollback_migrations_.at(static_cast<uint32_t>(k)));
119 if (statement.step() != SQLITE_DONE) {
120 LOG_ERROR <<
"Can't insert rollback migration script: " << db.errmsg();
128 bool SQLStorageBase::dbMigrateForward(
int version_from,
int version_to) {
129 if (version_to <= 0) {
130 version_to = current_schema_version_;
133 LOG_INFO <<
"Migrating DB from version " << version_from <<
" to version " << version_to;
137 if (!db.beginTransaction()) {
138 LOG_ERROR <<
"Can't start transaction: " << db.errmsg();
142 for (int32_t k = version_from + 1; k <= version_to; k++) {
143 auto result_code = db.exec(schema_migrations_.at(static_cast<size_t>(k)),
nullptr,
nullptr);
144 if (result_code != SQLITE_OK) {
145 LOG_ERROR <<
"Can't migrate DB from version " << (k - 1) <<
" to version " << k <<
": " << db.errmsg();
150 if (!dbInsertBackMigrations(db, version_to)) {
154 db.commitTransaction();
159 bool SQLStorageBase::dbMigrateBackward(
int version_from,
int version_to) {
160 if (version_to <= 0) {
161 version_to = current_schema_version_;
164 LOG_INFO <<
"Migrating DB backward from version " << version_from <<
" to version " << version_to;
167 for (
int ver = version_from; ver > version_to; --ver) {
168 std::string migration;
171 auto statement = db.prepareStatement(
"SELECT migration FROM rollback_migrations WHERE version_from=?;", ver);
172 if (statement.step() != SQLITE_ROW) {
173 LOG_ERROR <<
"Can't extract migration script: " << db.errmsg();
176 migration = *(statement.get_result_col_str(0));
179 if (db.exec(migration,
nullptr,
nullptr) != SQLITE_OK) {
180 LOG_ERROR <<
"Can't migrate db from version " << (ver) <<
" to version " << ver - 1 <<
": " << db.errmsg();
183 if (db.exec(std::string(
"DELETE FROM rollback_migrations WHERE version_from=") + std::to_string(ver) +
";",
nullptr,
184 nullptr) != SQLITE_OK) {
185 LOG_ERROR <<
"Can't clear old migration script: " << db.errmsg();
192 bool SQLStorageBase::dbMigrate() {
193 DbVersion schema_version = getVersion();
195 if (schema_version == DbVersion::kInvalid) {
196 LOG_ERROR <<
"Sqlite database file is invalid.";
200 auto schema_num_version = static_cast<int32_t>(schema_version);
201 if (schema_num_version == current_schema_version_) {
206 LOG_ERROR <<
"Database is opened in readonly mode and cannot be migrated to latest version";
210 if (schema_version == DbVersion::kEmpty) {
211 LOG_INFO <<
"Bootstraping DB to version " << current_schema_version_;
214 if (!db.beginTransaction()) {
215 LOG_ERROR <<
"Can't start transaction: " << db.errmsg();
219 auto result_code = db.exec(current_schema_,
nullptr,
nullptr);
220 if (result_code != SQLITE_OK) {
221 LOG_ERROR <<
"Can't bootstrap DB to version " << current_schema_version_ <<
": " << db.errmsg();
225 if (!dbInsertBackMigrations(db, current_schema_version_)) {
229 db.commitTransaction();
230 }
else if (schema_num_version > current_schema_version_) {
231 dbMigrateBackward(schema_num_version);
233 dbMigrateForward(schema_num_version);
239 DbVersion SQLStorageBase::getVersion() {
243 auto statement = db.prepareStatement(
"SELECT count(*) FROM sqlite_master WHERE type='table';");
244 if (statement.step() != SQLITE_ROW) {
245 LOG_ERROR <<
"Can't get tables count: " << db.errmsg();
246 return DbVersion::kInvalid;
249 if (statement.get_result_col_int(0) == 0) {
250 return DbVersion::kEmpty;
253 statement = db.prepareStatement(
"SELECT version FROM version LIMIT 1;");
255 if (statement.step() != SQLITE_ROW) {
256 LOG_ERROR <<
"Can't get database version: " << db.errmsg();
257 return DbVersion::kInvalid;
261 return DbVersion(statement.get_result_col_int(0));
262 }
catch (
const std::exception&) {
263 return DbVersion::kInvalid;
266 return DbVersion::kInvalid;