#include "databaseapi.h" Q_LOGGING_CATEGORY(database, "database.api"); DatabaseAPI::DatabaseAPI(serversdk::DatabaseManager *dbMan, bool dryMode) : mDryMode(dryMode), mDbMan(dbMan), utils(std::make_unique(dbMan)) { mDbMan->setDryMode(dryMode); } bool DatabaseAPI::open(const QString& host, const QString& name, const QString& user, const QString& password) { return mDbMan->openPostgres(host, name, user, password); } bool DatabaseAPI::executeQuery(QSqlQuery &query, const QString &statement) { if (statement.isEmpty()) { query = mDbMan->executeQuery(query); } else { query = mDbMan->executeQuery(query, statement); } if (!query.isActive()) { this->mValid = false; return false; } return true; } std::shared_ptr DatabaseAPI::downloadData() { auto updatedQuery = mDbMan->createQuery(); auto deletedQuery = mDbMan->createQuery(); if (!executeQuery(updatedQuery, "SELECT * FROM colnod_token WHERE last_modified > last_update AND deleted = false")) { qWarning(database) << "ERROR during geting new/updated tokens from db."; return nullptr; } if (!executeQuery(deletedQuery, "SELECT id FROM colnod_token WHERE last_modified > last_update AND deleted = true")) { qWarning(database) << "ERROR during geting marked tokens for deletion from db."; return nullptr; } return composeData(updatedQuery, deletedQuery); } bool DatabaseAPI::processData(const std::shared_ptr &data) { bool res = true; if (!uploadTokens(data->updatedTokens)) { res = false; } if (!uploadSubjects(data->updatedSubjects)) { res = false; } if (!deleteItems(data->deletedTokens, "colnod_token")) { res = false; } if (!deleteItems(data->deletedSubjects, "colnod_subject")) { res = false; } qInfo(database) << tab << "- push db:" << tab << "tokens [" << data->updatedTokens.size() << ":" << data->deletedTokens.size() << "] subjects [" << data->updatedSubjects.size() << ":" << data->deletedSubjects.size() << "] [ added/updated : deleted ]"; if (!data->updatedTokens.isEmpty() || !data->deletedTokens.isEmpty() || !data->updatedSubjects.isEmpty() || !data->deletedSubjects.isEmpty()) { qInfo(database) << dash; } for (const auto &token : data->updatedTokens) { qInfo(database) << Message::massageTemplate("Updated", "token", "database", token->name()); } for (const auto &id : data->deletedTokens) { qInfo(database) << Message::massageTemplate("Deleted", "token", "database", id); } for (const auto &subject : data->updatedSubjects) { qInfo(database) << Message::massageTemplate("Updated", "subject", "database", subject->name()); } for (const auto &id : data->deletedSubjects) { qInfo(database) << Message::massageTemplate("Deleted", "subject", "database", id); } if (!data->updatedTokens.isEmpty() || !data->deletedTokens.isEmpty() || !data->updatedSubjects.isEmpty() || !data->deletedSubjects.isEmpty()) { qInfo(database) << dash; } return res; } // save data to structure Data to be sent to syncManager std::shared_ptr DatabaseAPI::composeData(const QSqlQuery &updatedQuery, QSqlQuery &deletedQuery) { auto data = std::make_shared(); auto list = utils->tableFromQuery(updatedQuery); for (const auto &token : list) { data->updatedTokens.insert(token->id(), token); } while (deletedQuery.next()) { data->deletedTokens.append(deletedQuery.value(0).toString()); } qInfo(database) << tab << "- src db:" << tab << "tokens [" << data->updatedTokens.size() << ":" << data->deletedTokens.size() << "] [ added/updated : deleted ]"; if (!data->updatedTokens.isEmpty() || !data->deletedTokens.isEmpty()) { qInfo(database) << dash; } for (const auto &token : data->updatedTokens) { qInfo(database) << Message::massageTemplate("Download updated", "token", "database", token->name()); } for (const auto &id : data->deletedTokens) { qInfo(database) << Message::massageTemplate("Download deleted", "token", "database", id); } if (!data->updatedTokens.isEmpty() || !data->deletedTokens.isEmpty()) { qInfo(database) << dash; } return data; } // Time of last synchronization qint64 DatabaseAPI::getLastSync() { auto query = mDbMan->createQuery(); if (!executeQuery(query, "SELECT last_sync FROM config")) { qCritical(database) << "ERROR during getting time of last sync. Exit."; return -1; } if (query.next()){ return query.value(0).toLongLong(); } return -1; } bool DatabaseAPI::setLastSync(qint64 timestamp) { auto query = mDbMan->createQuery(); query.prepare("UPDATE config SET last_sync = :last_sync"); query.bindValue(":last_sync", timestamp); if (!executeQuery(query)) { qCritical(database) << "ERROR during set time of this synchronization as last sync time"; return false; } return true; } // It update last_update time in db of deleted tokens, because in colnod is later time (time of real deletion from Colnod) void DatabaseAPI::reupdateDeleted(const QList& ids) { auto query = mDbMan->createQuery(); for (const auto &id : ids){ query.prepare("UPDATE colnod_token SET last_update = :last_update WHERE id = :id"); query.bindValue(":last_update", QDateTime::fromMSecsSinceEpoch(getNow()).toString("yyyy-MM-dd hh:mm:ss.zzz")); query.bindValue(":id", id); if (!executeQuery(query)) { if (!executeQuery(query)) { qWarning(database) << "ERROR during updating last update of deleted token:" << id << ", it will be synced from Colnod to db in next sync. Last modified will be invalid"; } } } } qint64 DatabaseAPI::getNow() { auto query = mDbMan->createQuery(); if (!executeQuery(query, "SELECT EXTRACT(EPOCH FROM NOW())*1000")) { qCritical(database) << "ERROR during getting acctual time from db"; return -1; } query.next(); return query.value(0).toLongLong(); } // All tokens which are pushed to Colnad are update back to db, because of diffrent last_update and init of new tokens bool DatabaseAPI::uploadTokens(const QHash> &tokens, const QSet &filters) { auto query = mDbMan->createQuery(); for (const auto &token : tokens) { if (exists(token->id(), "colnod_token")) { query = utils->updateTable(token.get(), "colnod_token", filters); } else { query = utils->insertIntoTable(token.get(), "colnod_token"); } if (!query.isActive()) { mValid = false; qWarning(database) << "ERROR durning inserting/updating of token:" << token->name(); return false; } } return true; } bool DatabaseAPI::uploadSubjects(const QHash> &subjects) { auto query = mDbMan->createQuery(); for (const auto &subject : subjects) { for (const auto &attribute : subject->getAttributes()) { if (exists(attribute->id(), "colnod_subject_attribute")) { query = utils->updateTable(attribute.get(), "colnod_subject_attribute"); } else { query = utils->insertIntoTable(attribute.get(), "colnod_subject_attribute"); } if (!query.isActive()) { mValid = false; qWarning(database) << "ERROR durning inserting/updating of subject attribute:" << attribute->attributeValue(); } } if (exists(subject->id(), "colnod_subject")) { query = utils->updateTable(subject.get(), "colnod_subject"); } else { query = utils->insertIntoTable(subject.get(), "colnod_subject"); } if (!query.isActive()) { mValid = false; qWarning(database) << "ERROR durning inserting/updating of subject attribute:" << subject->name(); return false; } } return true; } bool DatabaseAPI::deleteItems(const QList &ids, const QString &tableName) { auto query = mDbMan->createQuery(); QString nowString = QDateTime::fromMSecsSinceEpoch(getLastSync()).toString("yyyy-MM-dd hh:mm:ss.zzz"); if (tableName == "colnod_subject") { for (const auto &id : ids) { query.prepare("DELETE FROM colnod_subject_attribute WHERE subject_id = :id"); query.bindValue(":id", id); if (!executeQuery(query)) { qWarning(database) << "ERROR during marking as deleted:" << id; mValid = false; return false; } } } for (const auto &id : ids) { query.prepare(QString("UPDATE %1 SET deleted = true, last_update = :now, last_modified = :now WHERE id = :id").arg(tableName)); query.bindValue(":id", id); query.bindValue(":now", nowString); if (!executeQuery(query)) { qWarning(database) << "ERROR during marking as deleted:" << id; mValid = false; return false; } } return true; } std::shared_ptr DatabaseAPI::getToken(const QString &id) { auto query = mDbMan->createQuery(); query.prepare("SELECT * FROM colnod_token WHERE id = :id"); query.bindValue(":id", id); if (!executeQuery(query)) { qCritical(database) << "ERROR during accessing existing tokens. EXIT"; return nullptr; } auto list = utils->tableFromQuery(query); if (list.isEmpty()) return std::make_shared(); return list.at(0); } bool DatabaseAPI::isValid() { return mValid; } int DatabaseAPI::exists(const QString &id, const QString &tableName) { auto query = mDbMan->createQuery(); query.prepare(QString("SELECT 1 FROM %1 WHERE id = :id").arg(tableName)); query.bindValue(":id", id); if (!executeQuery(query)) { qCritical(database) << "ERROR during accessing existing tokens. EXIT(1)"; return -1; } return query.next(); } qint16 DatabaseAPI::getNumOfTokensOfSubject(const QString &id) { auto query = mDbMan->createQuery(); query.prepare("SELECT 1 FROM colnod_token WHERE subject_id = :id AND description = 'mobile'"); query.bindValue(":id", id); if (!executeQuery(query)) { qCritical(database) << "ERROR during accessing existing tokens. EXIT(1)"; return -1; } return static_cast(query.size()); } QString DatabaseAPI::getSubjectNameByTokenId(const QString &tokenId) { auto query = mDbMan->createQuery(); query.prepare("SELECT name FROM colnod_subject WHERE id = (SELECT subject_id FROM colnod_token WHERE id = :tokenId)"); query.bindValue(":tokenId", tokenId); if (!executeQuery(query)) { qCritical(database) << "ERROR during accessing existing subject. EXIT(1)"; return "error"; } if (query.next()) return query.value(0).toString(); qWarning() << "Token:" << tokenId << "is not assigned to any subject"; return "(No subject)"; }