mirror of
https://github.com/arcan1s/queued.git
synced 2025-04-24 23:47:19 +00:00
447 lines
13 KiB
C++
447 lines
13 KiB
C++
/*
|
|
* Copyright (c) 2017 Evgeniy Alekseev
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
*
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*/
|
|
/**
|
|
* @file QueuedDatabase.cpp
|
|
* Source code of queued library
|
|
* @author Evgeniy Alekseev
|
|
* @copyright GPLv3
|
|
* @bug https://github.com/arcan1s/queued/issues
|
|
*/
|
|
|
|
|
|
#include <queued/Queued.h>
|
|
|
|
#include <QSqlError>
|
|
#include <QSqlQuery>
|
|
#include <QSqlRecord>
|
|
|
|
#include <queued/QueuedDatabaseSchema.h>
|
|
|
|
|
|
/**
|
|
* @fn QueuedDatabase
|
|
*/
|
|
QueuedDatabase::QueuedDatabase(QObject *parent, const QString path,
|
|
const QString driver)
|
|
: QObject(parent)
|
|
, m_path(path)
|
|
{
|
|
qCDebug(LOG_LIB) << __PRETTY_FUNCTION__;
|
|
|
|
m_database = QSqlDatabase::addDatabase(driver);
|
|
m_database.setDatabaseName(path);
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn ~QueuedDatabase
|
|
*/
|
|
QueuedDatabase::~QueuedDatabase()
|
|
{
|
|
qCDebug(LOG_LIB) << __PRETTY_FUNCTION__;
|
|
|
|
m_database.close();
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn checkDatabase
|
|
*/
|
|
void QueuedDatabase::checkDatabase()
|
|
{
|
|
QStringList tables = m_database.tables();
|
|
QStringList schemaTables = QueuedDB::DBSchema.keys();
|
|
for (auto &table : schemaTables) {
|
|
// create table if does not exist
|
|
if (!tables.contains(table))
|
|
createTable(table);
|
|
// update schema
|
|
createSchema(table);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn createAdministrator
|
|
*/
|
|
void QueuedDatabase::createAdministrator(const QString &_user,
|
|
const QString &_password)
|
|
{
|
|
qCDebug(LOG_LIB) << "Check for user" << _user;
|
|
|
|
QSqlQuery query
|
|
= m_database.exec(QString("SELECT * FROM '%1' WHERE name='%2'")
|
|
.arg(QueuedDB::USERS_TABLE)
|
|
.arg(_user));
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid())
|
|
qCWarning(LOG_LIB) << "Could not get record" << _user << "from"
|
|
<< QueuedDB::USERS_TABLE << "message"
|
|
<< error.text();
|
|
else if (query.next())
|
|
return;
|
|
|
|
qCInfo(LOG_LIB) << "Create administrator user" << _user;
|
|
QVariantHash payload = {
|
|
{"name", _user},
|
|
{"password", _password},
|
|
{"permissions", static_cast<int>(QueuedEnums::Permission::SuperAdmin)}};
|
|
|
|
if (!add(QueuedDB::USERS_TABLE, payload))
|
|
qCCritical(LOG_LIB) << "Could not create administrator";
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn get
|
|
*/
|
|
QList<QVariantHash> QueuedDatabase::get(const QString &_table,
|
|
const QString &_condition)
|
|
{
|
|
qCDebug(LOG_LIB) << "Get records in table" << _table << "with condition"
|
|
<< _condition;
|
|
|
|
QList<QVariantHash> output;
|
|
QSqlQuery query
|
|
= m_database.exec(QString("SELECT * FROM '%1' %2 ORDER BY _id ASC")
|
|
.arg(_table)
|
|
.arg(_condition));
|
|
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid()) {
|
|
qCWarning(LOG_LIB) << "Could not get records from" << _table
|
|
<< "message" << error.text();
|
|
return output;
|
|
}
|
|
QSqlRecord record = query.record();
|
|
|
|
QStringList columns = QueuedDB::DBSchema[_table].keys();
|
|
auto dbColumns = getColumnsInRecord(columns, record);
|
|
while (query.next()) {
|
|
QVariantHash entry;
|
|
for (auto &column : columns)
|
|
entry[column] = query.value(dbColumns[column]);
|
|
output.append(entry);
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn get
|
|
*/
|
|
QVariantHash QueuedDatabase::get(const QString &_table, const long long _id)
|
|
{
|
|
qCDebug(LOG_LIB) << "Get record" << _id << "in table" << _table;
|
|
|
|
auto output = get(_table, QString("WHERE _id=%1").arg(_id));
|
|
if (output.count() == 0) {
|
|
qCWarning(LOG_LIB) << "Could not find records for" << _id;
|
|
return QVariantHash();
|
|
} else if (output.count() == 1) {
|
|
return output.at(0);
|
|
} else {
|
|
qCWarning(LOG_LIB) << "Too many records found for" << _id;
|
|
return output.at(0);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn open
|
|
*/
|
|
bool QueuedDatabase::open(const QString &_hostname, const int _port,
|
|
const QString &_username, const QString &_password)
|
|
{
|
|
qCDebug(LOG_LIB) << "Open database at" << _hostname << _port << "as user"
|
|
<< _username;
|
|
|
|
if (!_hostname.isEmpty())
|
|
m_database.setHostName(_hostname);
|
|
if (_port > 0)
|
|
m_database.setPort(_port);
|
|
bool status = _username.isEmpty() ? m_database.open()
|
|
: m_database.open(_username, _password);
|
|
|
|
qCDebug(LOG_LIB) << "Open database status" << status;
|
|
if (status)
|
|
checkDatabase();
|
|
return status;
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn path
|
|
*/
|
|
QString QueuedDatabase::path() const
|
|
{
|
|
return m_path;
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn add
|
|
*/
|
|
long long QueuedDatabase::add(const QString &_table, const QVariantHash &_value)
|
|
{
|
|
qCDebug(LOG_LIB) << "Add record" << _value << "to table" << _table;
|
|
|
|
auto payload = getQueryPayload(_table, _value);
|
|
// build query
|
|
QSqlQuery query
|
|
= m_database.exec(QString("INSERT INTO %1 (%2) VALUES (%3)")
|
|
.arg(_table)
|
|
.arg(payload.first.join(QChar(',')))
|
|
.arg(payload.second.join(QChar(','))));
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid()) {
|
|
qCCritical(LOG_LIB) << "Could not add record" << _value << "to table"
|
|
<< _table << "message" << error.text();
|
|
return -1;
|
|
}
|
|
|
|
return lastInsertionId(_table);
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn modify
|
|
*/
|
|
bool QueuedDatabase::modify(const QString &_table, const long long _id,
|
|
const QVariantHash &_value)
|
|
{
|
|
qCDebug(LOG_LIB) << "Modify record" << _id << "in table" << _table
|
|
<< "with value" << _value;
|
|
|
|
auto payload = getQueryPayload(_table, _value);
|
|
QStringList stringPayload;
|
|
for (int i = 0; i < payload.first.count(); i++)
|
|
stringPayload.append(QString("%1=%2")
|
|
.arg(payload.first.at(i))
|
|
.arg(payload.second.at(i)));
|
|
// build query
|
|
QSqlQuery query = m_database.exec(QString("UPDATE %1 SET %2 WHERE _id=%3")
|
|
.arg(_table)
|
|
.arg(stringPayload.join(QChar(',')))
|
|
.arg(_id));
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid()) {
|
|
qCCritical(LOG_LIB) << "Could not modify record" << _value << "in table"
|
|
<< _table << "message" << error.text();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn remove
|
|
*/
|
|
bool QueuedDatabase::remove(const QString &_table, const long long _id)
|
|
{
|
|
qCDebug(LOG_LIB) << "Remove row" << _id << "from" << _table;
|
|
|
|
QSqlQuery query = m_database.exec(
|
|
QString("DELETE FROM %1 WHERE _id=%2").arg(_table).arg(_id));
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid()) {
|
|
qCCritical(LOG_LIB) << "Could not remove record" << _id << "in table"
|
|
<< _table << "message" << error.text();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn removeTasks
|
|
*/
|
|
void QueuedDatabase::removeTasks(const QDateTime &_endTime)
|
|
{
|
|
qCDebug(LOG_LIB) << "Remove all tasks which are older than" << _endTime;
|
|
|
|
QSqlQuery query = m_database.exec(
|
|
QString("DELETE FROM %1 WHERE datetime(endTime) < datetime('%2')")
|
|
.arg(QueuedDB::TASKS_TABLE)
|
|
.arg(_endTime.toString(Qt::ISODateWithMs)));
|
|
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid())
|
|
qCCritical(LOG_LIB) << "Could not remove tasks in table"
|
|
<< "message" << error.text();
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn removeTokens
|
|
*/
|
|
void QueuedDatabase::removeTokens()
|
|
{
|
|
QString now = QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs);
|
|
QSqlQuery query = m_database.exec(
|
|
QString("DELETE FROM %1 WHERE datetime(validUntil) > datetime('%2')")
|
|
.arg(QueuedDB::TOKENS_TABLE)
|
|
.arg(now));
|
|
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid())
|
|
qCCritical(LOG_LIB) << "Could not remove tokens in table"
|
|
<< "message" << error.text();
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn removeUsers
|
|
*/
|
|
void QueuedDatabase::removeUsers(const QDateTime &_lastLogin)
|
|
{
|
|
qCDebug(LOG_LIB) << "Remove all users which logged older than"
|
|
<< _lastLogin;
|
|
|
|
QSqlQuery query = m_database.exec(
|
|
QString("DELETE FROM %1 WHERE datetime(lastLogin) < datetime('%2')")
|
|
.arg(QueuedDB::USERS_TABLE)
|
|
.arg(_lastLogin.toString(Qt::ISODateWithMs)));
|
|
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid())
|
|
qCCritical(LOG_LIB) << "Could not remove users in table"
|
|
<< "message" << error.text();
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn createSchema
|
|
*/
|
|
void QueuedDatabase::createSchema(const QString &_table)
|
|
{
|
|
qCDebug(LOG_LIB) << "Create schema for" << _table;
|
|
|
|
QSqlRecord record = m_database.record(_table);
|
|
// get column names
|
|
QStringList columns;
|
|
for (int i = 0; i < record.count(); i++)
|
|
columns.append(record.fieldName(i));
|
|
|
|
// check and append if any
|
|
QStringList schemaColumns = QueuedDB::DBSchema[_table].keys();
|
|
for (auto &column : schemaColumns) {
|
|
if (columns.contains(column))
|
|
continue;
|
|
QueuedDB::QueuedDBField field = QueuedDB::DBSchema[_table][column];
|
|
QSqlQuery query
|
|
= m_database.exec(QString("ALTER TABLE '%1' ADD `%2` %3")
|
|
.arg(_table)
|
|
.arg(column)
|
|
.arg(field.sqlDescription));
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid())
|
|
qCCritical(LOG_LIB) << "Could not insert column" << column
|
|
<< "to table" << _table
|
|
<< "error:" << error.text();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn createTable
|
|
*/
|
|
void QueuedDatabase::createTable(const QString &_table)
|
|
{
|
|
qCDebug(LOG_LIB) << "Create table" << _table;
|
|
|
|
QSqlQuery query = m_database.exec(
|
|
QString("CREATE TABLE '%1' (`_id` INTEGER PRIMARY KEY AUTOINCREMENT)")
|
|
.arg(_table));
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid())
|
|
qCCritical(LOG_LIB) << "Could not create table" << _table
|
|
<< "error:" << error.text();
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn getColumnsInRecord
|
|
*/
|
|
QHash<QString, int>
|
|
QueuedDatabase::getColumnsInRecord(const QStringList &_columns,
|
|
const QSqlRecord &_record) const
|
|
{
|
|
qCDebug(LOG_LIB) << "Search for columns" << _columns;
|
|
|
|
return std::accumulate(
|
|
_columns.begin(), _columns.end(), QHash<QString, int>(),
|
|
[&_record](QHash<QString, int> &map, const QString &column) {
|
|
map[column] = _record.indexOf(column);
|
|
return map;
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn lastInsertionId
|
|
*/
|
|
long long QueuedDatabase::lastInsertionId(const QString &_table) const
|
|
{
|
|
qCDebug(LOG_LIB) << "Get last row ID from" << _table;
|
|
|
|
QSqlQuery query
|
|
= m_database.exec(QString("SELECT max(_id) FROM '%1'").arg(_table));
|
|
QSqlError error = query.lastError();
|
|
if (error.isValid()) {
|
|
qCCritical(LOG_LIB) << "Could not get last insertion ID";
|
|
return -1;
|
|
}
|
|
|
|
long long id;
|
|
while (query.next())
|
|
id = query.value(0).toLongLong();
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
/**
|
|
* @fn getQueryPayload
|
|
*/
|
|
QPair<QStringList, QStringList>
|
|
QueuedDatabase::getQueryPayload(const QString &_table,
|
|
const QVariantHash &_value) const
|
|
{
|
|
qCDebug(LOG_LIB) << "Add record" << _value << "to table" << _table;
|
|
|
|
QStringList keys;
|
|
QStringList values;
|
|
QStringList schemaColumns = QueuedDB::DBSchema[_table].keys();
|
|
for (auto &key : _value.keys()) {
|
|
if (!schemaColumns.contains(key)) {
|
|
qCWarning(LOG_LIB) << "No key" << key << "found in schema of"
|
|
<< _table;
|
|
continue;
|
|
}
|
|
if (key == QString("_id")) {
|
|
qCWarning(LOG_LIB) << "Modifying record ID is not allowed";
|
|
continue;
|
|
}
|
|
keys.append(key);
|
|
values.append(QString("'%1'").arg(_value[key].toString()));
|
|
}
|
|
|
|
return {keys, values};
|
|
}
|