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;
139 db.beginTransaction();
144 for (int32_t k = version_from + 1; k <= version_to; k++) {
145 auto result_code = db.exec(schema_migrations_.at(static_cast<size_t>(k)),
nullptr,
nullptr);
146 if (result_code != SQLITE_OK) {
147 LOG_ERROR <<
"Can't migrate DB from version " << (k - 1) <<
" to version " << k <<
": " << db.errmsg();
152 if (!dbInsertBackMigrations(db, version_to)) {
156 db.commitTransaction();
161 bool SQLStorageBase::dbMigrateBackward(
int version_from,
int version_to) {
162 if (version_to <= 0) {
163 version_to = current_schema_version_;
166 LOG_INFO <<
"Migrating DB backward from version " << version_from <<
" to version " << version_to;
169 for (
int ver = version_from; ver > version_to; --ver) {
170 std::string migration;
173 auto statement = db.prepareStatement(
"SELECT migration FROM rollback_migrations WHERE version_from=?;", ver);
174 if (statement.step() != SQLITE_ROW) {
175 LOG_ERROR <<
"Can't extract migration script: " << db.errmsg();
178 migration = *(statement.get_result_col_str(0));
181 if (db.exec(migration,
nullptr,
nullptr) != SQLITE_OK) {
182 LOG_ERROR <<
"Can't migrate db from version " << (ver) <<
" to version " << ver - 1 <<
": " << db.errmsg();
185 if (db.exec(std::string(
"DELETE FROM rollback_migrations WHERE version_from=") + std::to_string(ver) +
";",
nullptr,
186 nullptr) != SQLITE_OK) {
187 LOG_ERROR <<
"Can't clear old migration script: " << db.errmsg();
194 bool SQLStorageBase::dbMigrate() {
195 DbVersion schema_version = getVersion();
197 if (schema_version == DbVersion::kInvalid) {
198 LOG_ERROR <<
"Sqlite database file is invalid.";
202 auto schema_num_version =
static_cast<int32_t
>(schema_version);
203 if (schema_num_version == current_schema_version_) {
208 LOG_ERROR <<
"Database is opened in readonly mode and cannot be migrated to latest version";
212 if (schema_version == DbVersion::kEmpty) {
213 LOG_INFO <<
"Bootstraping DB to version " << current_schema_version_;
217 db.beginTransaction();
222 auto result_code = db.exec(current_schema_,
nullptr,
nullptr);
223 if (result_code != SQLITE_OK) {
224 LOG_ERROR <<
"Can't bootstrap DB to version " << current_schema_version_ <<
": " << db.errmsg();
228 if (!dbInsertBackMigrations(db, current_schema_version_)) {
232 db.commitTransaction();
233 }
else if (schema_num_version > current_schema_version_) {
234 dbMigrateBackward(schema_num_version);
236 dbMigrateForward(schema_num_version);
242 DbVersion SQLStorageBase::getVersion() {
246 auto statement = db.prepareStatement(
"SELECT count(*) FROM sqlite_master WHERE type='table';");
247 if (statement.step() != SQLITE_ROW) {
248 LOG_ERROR <<
"Can't get tables count: " << db.errmsg();
249 return DbVersion::kInvalid;
252 if (statement.get_result_col_int(0) == 0) {
253 return DbVersion::kEmpty;
256 statement = db.prepareStatement(
"SELECT version FROM version LIMIT 1;");
258 if (statement.step() != SQLITE_ROW) {
259 LOG_ERROR <<
"Can't get database version: " << db.errmsg();
260 return DbVersion::kInvalid;
264 return DbVersion(statement.get_result_col_int(0));
265 }
catch (
const std::exception&) {
266 return DbVersion::kInvalid;
269 return DbVersion::kInvalid;