#include "utils/settings.h" #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif #define NO_SQLQUERY_LOGGING // Constants namespace { const char DATABASE_DIRECTORYNAME[] = "db"; const char DATABASE_FILENAME[] = "hyperion.db"; } //End of constants QDir DBManager::_dataDirectory; QDir DBManager::_databaseDirectory; QFileInfo DBManager::_databaseFile; QThreadStorage DBManager::_databasePool; bool DBManager::_isReadOnly {false}; DBManager::DBManager(QObject* parent) : QObject(parent) , _log(Logger::getInstance("DB")) { } void DBManager::initializeDatabase(const QDir& dataDirectory, bool isReadOnly) { _dataDirectory = dataDirectory; _databaseDirectory.setPath(_dataDirectory.absoluteFilePath(DATABASE_DIRECTORYNAME)); QDir().mkpath(_databaseDirectory.absolutePath()); _databaseFile.setFile(_databaseDirectory,DATABASE_FILENAME); _isReadOnly = isReadOnly; } void DBManager::setTable(const QString& table) { _table = table; } QSqlDatabase DBManager::getDB() const { if(_databasePool.hasLocalData()) { return _databasePool.localData(); } auto database = QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()); if (isReadOnly()) { database.setConnectOptions("QSQLITE_OPEN_READONLY"); } #ifdef SQLQUERY_LOGGING Debug(Logger::getInstance("DB"), "Database is opened in %s mode", _isReadOnly ? "read-only" : "read/write"); #endif _databasePool.setLocalData(database); database.setDatabaseName(_databaseFile.absoluteFilePath()); if(!database.open()) { Error(_log, "%s", QSTRING_CSTR(database.lastError().text())); throw std::runtime_error("Failed to open database connection!"); } return database; } bool DBManager::createRecord(const VectorPair& conditions, const QVariantMap& columns) const { if(recordExists(conditions)) { // if there is no column data, return if(columns.isEmpty()) { return true; } return updateRecord(conditions, columns); } QSqlDatabase idb = getDB(); QSqlQuery query(idb); query.setForwardOnly(true); QVariantList cValues; QStringList prep; QStringList placeh; // prep merge columns & condition QVariantMap::const_iterator columnIter = columns.constBegin(); while (columnIter != columns.constEnd()) { prep.append(columnIter.key()); cValues += columnIter.value(); placeh.append("?"); ++columnIter; } for(const auto& pair : conditions) { // remove the condition statements QString tmp = pair.first; prep << tmp.remove("AND"); cValues << pair.second; placeh.append("?"); } query.prepare(QString("INSERT INTO %1 ( %2 ) VALUES ( %3 )").arg(_table,prep.join(", "), placeh.join(", "))); // add column & condition values addBindValues(query, cValues); return executeQuery(query); } bool DBManager::recordExists(const VectorPair& conditions) const { if(conditions.isEmpty()) { return false; } QSqlDatabase idb = getDB(); QSqlQuery query(idb); query.setForwardOnly(true); QStringList prepCond; QVariantList bindVal; prepCond << "WHERE"; for(const auto& pair : conditions) { prepCond << pair.first+"= ?"; bindVal << pair.second; } query.prepare(QString("SELECT * FROM %1 %2").arg(_table,prepCond.join(" "))); addBindValues(query, bindVal); if (!executeQuery(query)) { return false; } int entry = 0; while (query.next()) { entry++; } return entry > 0; } bool DBManager::recordsNotExisting(const QVariantList& testValues,const QString& column, QStringList& nonExistingRecs, const QString& condition ) const { QSqlDatabase idb = getDB(); QSqlQuery query(idb); query.setForwardOnly(true); // prep conditions QString prepCond; if(!condition.isEmpty()) { prepCond = QString("WHERE %1").arg(condition); } QVector valueItem(testValues.size(), "(?)"); QString values = QStringList::fromVector(valueItem).join(","); query.prepare( QString("SELECT v.[column1] [%1] FROM ( VALUES %2 ) [v] WHERE %1 NOT IN ( SELECT %1 from settings %3 )") .arg(column,values, prepCond) ); addBindValues(query, testValues); if (!executeQuery(query)) { return false; } while (query.next()) { nonExistingRecs << query.value(0).toString(); } return true; } bool DBManager::updateRecord(const VectorPair& conditions, const QVariantMap& columns) const { if (isReadOnly()) { return true; } QSqlDatabase idb = getDB(); QSqlQuery query(idb); query.setForwardOnly(true); QVariantList values; QStringList prep; // prepare columns valus QVariantMap::const_iterator columnIter = columns.constBegin(); while (columnIter != columns.constEnd()) { prep += columnIter.key()+"= ?"; values += columnIter.value(); ++columnIter; } // prepare condition values QStringList prepCond; QVariantList prepBindVal; if(!conditions.isEmpty()) { prepCond << "WHERE"; } for(const auto& pair : conditions) { prepCond << pair.first+"= ?"; prepBindVal << pair.second; } query.prepare(QString("UPDATE %1 SET %2 %3").arg(_table,prep.join(", "), prepCond.join(" "))); // add column values addBindValues(query, values); // add condition values addBindValues(query, prepBindVal); return executeQuery(query); } bool DBManager::getRecord(const VectorPair& conditions, QVariantMap& results, const QStringList& tColumns, const QStringList& tOrder) const { QVector resultVector{}; bool success = getRecords(conditions, resultVector, tColumns, tOrder); if (success && !resultVector.isEmpty()) { results = resultVector.first(); } return success; } bool DBManager::getRecords(QVector& results, const QStringList& tColumns, const QStringList& tOrder) const { return getRecords({}, results, tColumns, tOrder); } bool DBManager::getRecords(const VectorPair& conditions, QVector& results, const QStringList& tColumns, const QStringList& tOrder) const { // prep conditions QStringList conditionList; QVariantList bindValues; for(const auto& pair : conditions) { conditionList << pair.first; if (pair.second.isNull()) { conditionList << "IS NULL"; } else { conditionList << "= ?"; bindValues << pair.second; } } return getRecords(conditionList.join((" ")), bindValues, results, tColumns, tOrder); } bool DBManager::getRecords(const QString& condition, const QVariantList& bindValues, QVector& results, const QStringList& tColumns, const QStringList& tOrder) const { QSqlDatabase idb = getDB(); QSqlQuery query(idb); query.setForwardOnly(true); QString sColumns("*"); if(!tColumns.isEmpty()) { sColumns = tColumns.join(", "); } QString sOrder(""); if(!tOrder.isEmpty()) { sOrder = "ORDER BY "; sOrder.append(tOrder.join(", ")); } // prep conditions QString prepCond; if(!condition.isEmpty()) { prepCond = QString("WHERE %1").arg(condition); } query.prepare(QString("SELECT %1 FROM %2 %3 %4").arg(sColumns,_table, prepCond, sOrder)); addBindValues(query, bindValues); if (!executeQuery(query)) { return false; } // iterate through all found records while(query.next()) { QVariantMap entry; QSqlRecord rec = query.record(); for(int i = 0; i= QT_VERSION_CHECK(6, 0, 0)) QVariantList boundValues = query.boundValues(); // Get bound values as a list #else QVariantMap boundValues = query.boundValues(); // Get bound values as a list #endif // Iterate through the bound values and replace placeholders for (const QVariant &value : boundValues) { // Replace the first occurrence of '?' with the actual value QString valueStr; if (value.canConvert()) { valueStr = value.toString(); } else { valueStr = "Unkown"; } executedQuery.replace(executedQuery.indexOf('?'), 1, valueStr); } } return executedQuery; } bool DBManager::executeQuery(QSqlQuery& query) const { if( !query.exec()) { QString finalQuery = constructExecutedQuery(query); QString errorText = query.lastError().text(); Debug(_log, "Database Error: '%s', SqlQuery: '%s'", QSTRING_CSTR(errorText), QSTRING_CSTR(finalQuery)); Error(_log, "Database Error: '%s'", QSTRING_CSTR(errorText)); return false; } #ifdef SQLQUERY_LOGGING QString finalQuery = constructExecutedQuery(query); Debug(_log, "SqlQuery executed: '%s'", QSTRING_CSTR(finalQuery)); #endif return true; } bool DBManager::startTransaction(QSqlDatabase& idb) const { if (!idb.transaction()) { QString errorText = QString("Could not create a database transaction. Error: %1").arg(idb.lastError().text()); Error(_log, "'%s'", QSTRING_CSTR(errorText)); return false; } return true; } bool DBManager::startTransaction(QSqlDatabase& idb, QStringList& errorList) { if (!idb.transaction()) { QString errorText = QString("Could not create a database transaction. Error: %1").arg(idb.lastError().text()); logErrorAndAppend(errorText, errorList); return false; } return true; } bool DBManager::commiTransaction(QSqlDatabase& idb) const { if (!idb.commit()) { QString errorText = QString("Could not finalize the database changes. Error: %1").arg(idb.lastError().text()); Error(_log, "'%s'", QSTRING_CSTR(errorText)); return false; } return true; } bool DBManager::commiTransaction(QSqlDatabase& idb, QStringList& errorList) { if (!idb.commit()) { QString errorText = QString("Could not finalize the database changes. Error: %1").arg(idb.lastError().text()); logErrorAndAppend(errorText, errorList); return false; } return true; } bool DBManager::rollbackTransaction(QSqlDatabase& idb) const { if (!idb.rollback()) { QString errorText = QString("Could not rollback the database transaction. Error: %1").arg(idb.lastError().text()); Error(_log, "'%s'", QSTRING_CSTR(errorText)); return false; } return true; } bool DBManager::rollbackTransaction(QSqlDatabase& idb, QStringList& errorList) { if (!idb.rollback()) { QString errorText = QString("Could not rollback the database transaction. Error: %1").arg(idb.lastError().text()); logErrorAndAppend(errorText, errorList); return false; } return true; } // Function to log error and append it to the error list void DBManager::logErrorAndAppend(const QString& errorText, QStringList& errorList) { Error(_log, "'%s'", QSTRING_CSTR(errorText)); errorList.append(errorText); }