Files
qt5-legatus-legacy/Lts/Tools/DBUpdate/db_update.cpp
T
2026-08-13 18:33:10 +02:00

920 lines
34 KiB
C++
Executable File

/**
* @RDE_HEADER@
* DBUpdate
* $Id$
* $HeadURL$
* $Author$
* Created by Iuliana Ojog, 2021-02-17
*/
#include "db_update.h"
#include "ui_db_update.h"
#include "show_history.h"
#include <ngldb/dbconnection.h>
#include <ngldb/dbtransaction.h>
#include <ngldb/nsqlquery.h>
#include <nglsettings/nglsettings_settings.h>
#include <ngltools/ngltools_filesystem.h>
#include <QMessageBox>
#include <QDateTime>
#include <QDir>
#include <QFileDialog>
#include <QSqlError>
namespace DBUpdate
{
QString DBUpdate::m_defaultPath = QDir::homePath() + QStringLiteral("/bin/updates/");
DBUpdate::DBUpdate(QWidget *parent)
: QMainWindow(parent)
, m_ui(new Ui::DBUpdate)
, m_showHistory(new ShowHistory)
, m_logger(getLogger("DBUpdate"))
{
m_ui->setupUi(this);
setWindowTitle(QLatin1String("Database Update"));
m_globalUpdatesModel = new GlobalUpdatesModel(this);
m_globalUpdatesProxyModel = new GlobalUpdatesProxyModel(this);
m_globalUpdatesProxyModel->setSourceModel(m_globalUpdatesModel);
m_ui->tvGlobalUpdates->setModel(m_globalUpdatesProxyModel);
connect(m_ui->tvGlobalUpdates, &QTableView::doubleClicked, this, &DBUpdate::onShowGlobalHistory);
m_exercisesModel = new ExercisesModel(this);
m_exercisesProxyModel = new ExercisesProxyModel(this);
m_exercisesProxyModel->setSourceModel(m_exercisesModel);
m_ui->tvExerciseUpdates->setModel(m_exercisesProxyModel);
connect(m_ui->tvExerciseUpdates, &QTableView::doubleClicked, this, &DBUpdate::onShowExerciseHistory);
connect(m_ui->tbClose, &QToolButton::clicked, this, &DBUpdate::onClosedClicked);
connect(m_ui->tbLoadUpdate, &QToolButton::clicked, this, &DBUpdate::onUpdateLoad);
connect(m_ui->tbRunUpdate, &QToolButton::clicked, this, &DBUpdate::onUpdateRun);
connect(m_ui->tbShowLog, &QToolButton::clicked, this, &DBUpdate::onShowLogFile);
connect(m_ui->tbClearLog, &QToolButton::clicked, this, &DBUpdate::onClearLogFile);
connect(m_ui->tbLoadSQL, &QToolButton::clicked, this, &DBUpdate::onLoadSQL);
connect(m_ui->tbBackup, &QToolButton::clicked, this, &DBUpdate::onBackupDB);
m_ui->tvExerciseUpdates->hideColumn(ExercisesModel::Columns::Id);
m_ui->tvExerciseUpdates->hideColumn(ExercisesModel::Columns::StartTime);
m_ui->tvExerciseUpdates->hideColumn(ExercisesModel::Columns::EndTime);
//m_ui->tvExerciseUpdates->hideColumn(ExercisesModel::Columns::StateId);
enaDisa();
}
DBUpdate::~DBUpdate()
{
delete m_showHistory;
delete m_globalUpdatesModel;
delete m_globalUpdatesProxyModel;
delete m_exercisesModel;
delete m_exercisesProxyModel;
delete m_ui;
}
bool DBUpdate::init()
{
QString str;
NGL::Settings s(str);
// open postgres db, read table structure, read static data
const QString dbUser(QStringLiteral("postgres"));
const QString dbPasswd(QStringLiteral("postgres"));
m_db = DBConnection::open(m_logger,
dbUser,
dbPasswd,
s.databaseDriver(),
s.databaseHostname(),
s.databaseName());
if (!m_db.isOpen())
return false;
m_ui->lblDbConnection->setText(m_db.hostName() + QStringLiteral(" / ") + m_db.databaseName());
enaDisa();
return true;
}
void DBUpdate::onClosedClicked()
{
close();
}
void DBUpdate::onUpdateLoad()
{
DEBUG_LOG(m_logger) << "onUpdateLoad";
// open dialog
const QStringList filter(
{
tr("Update Files (*.upd)"),
#ifdef Q_OS_WIN
tr("All (*.*)")
#else
tr("All (*)")
#endif
});
QString fileName = QFileDialog::getOpenFileName(this,
tr("Load database update file"),
m_defaultPath,
filter.join(QStringLiteral(";;")));
if (fileName.isEmpty())
return;
QFileInfo fileInfo(fileName);
if (!fileInfo.isReadable())
{
QMessageBox::warning(this, tr("Error!"), tr("An error occurred while loading the file.\n"
"The loaded file is not readable."));
return;
}
m_defaultPath = fileInfo.path() + QLatin1Char('/');
m_update.filename = fileName;
if (parseUpdateFile(fileName))
{
m_globalUpdatesModel->initModel(m_db, m_update.version, m_update.global_schemas.split(QStringLiteral(",")));
m_exercisesModel->initModel(m_db, m_update.version, m_update.exercise_schemas.split(QStringLiteral(",")));
m_ui->tbRunUpdate->setEnabled(true);
}
else
ERROR_LOG(m_logger) << "Not a valid update file " << fileName;
}
void DBUpdate::onUpdateRun()
{
QStringList gSchemas = m_globalUpdatesModel->getSchemasToUpdate();
QStringList eSchemas = m_exercisesModel->getSchemasToUpdate();
// generate SQL
m_sql.clear();
if (m_update.global_sql.size() > 0 && gSchemas.size() > 0)
{
for (auto &gs : gSchemas)
{
m_sql += QStringLiteral("--BEGIN;\n");
if (gs != QStringLiteral("PUBLIC"))
{
QString sUser = QStringLiteral("ets_") + gs;
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + gs + QStringLiteral(" GRANT SELECT,INSERT,UPDATE,DELETE,TRUNCATE ON TABLES TO ") + sUser + QStringLiteral(";\n");
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + gs + QStringLiteral(" GRANT SELECT, REFERENCES ON TABLES TO ") + sUser + QStringLiteral(";\n");
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + gs + QStringLiteral(" GRANT EXECUTE ON FUNCTIONS TO ") + sUser + QStringLiteral(";\n");
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + gs + QStringLiteral(" GRANT ALL ON SEQUENCES TO ") + sUser + QStringLiteral(";\n");
}
m_sql += QStringLiteral("SET SEARCH_PATH TO %1;\n").arg(gs);
m_sql += m_update.global_sql;
m_sql += QStringLiteral("\n");
m_sql += QStringLiteral("INSERT INTO db_version (description, version) VALUES ('%1', '%2');\n\n").arg(m_update.info, m_update.version);
m_sql += QStringLiteral("\n--COMMIT;\n\n");
}
}
if (m_update.exercise_sql.size() > 0 && eSchemas.size() > 0)
{
for (auto &es : eSchemas)
{
m_sql += QStringLiteral("--BEGIN;\n");
QString sUser = QStringLiteral("ets_ex_00"); // + es;
m_sql += QStringLiteral("GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA %1 TO %2;\n").arg(es).arg(sUser);
m_sql += QStringLiteral("GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA %1 TO %2;\n").arg(es).arg(sUser);
m_sql += QStringLiteral("GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA %1 TO %2;\n").arg(es).arg(sUser);
// backwards compatibility
QString sOldUser = QStringLiteral("ets_") + es;
m_sql += QStringLiteral("SELECT public.revoke_user_if_exists('%1','%2');\n").arg(sOldUser).arg(es);
/*
m_sql += QStringLiteral("SELECT create_user_if_not_exists('%1');\n").arg(sOldUser);
m_sql += QStringLiteral("REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA %1 FROM %2 CASCADE;\n").arg(es).arg(sOldUser);
m_sql += QStringLiteral("REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA %1 FROM %2 CASCADE;\n").arg(es).arg(sOldUser);
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA %1 REVOKE ALL ON TABLES FROM %2;\n").arg(es).arg(sOldUser);
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE ets_exm IN SCHEMA %1 REVOKE ALL ON TABLES FROM %2;\n").arg(es).arg(sOldUser);
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA %1 REVOKE ALL ON FUNCTIONS FROM %2;\n").arg(es).arg(sOldUser);
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE ets_exm IN SCHEMA %1 REVOKE ALL ON FUNCTIONS FROM %2;\n").arg(es).arg(sOldUser);
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA %1 REVOKE ALL ON SEQUENCES FROM %2;\n").arg(es).arg(sOldUser);
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE ets_exm IN SCHEMA %1 REVOKE ALL ON SEQUENCES FROM %2;\n").arg(es).arg(sOldUser);
m_sql += QStringLiteral("DROP USER %1;\n").arg(sOldUser);
*/
// backwards compatibility
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + es + QStringLiteral(" GRANT SELECT,INSERT,UPDATE,DELETE,TRUNCATE ON TABLES TO ") + sUser + QStringLiteral(";\n");
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + es + QStringLiteral(" GRANT SELECT, REFERENCES ON TABLES TO ") + sUser + QStringLiteral(";\n");
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + es + QStringLiteral(" GRANT EXECUTE ON FUNCTIONS TO ") + sUser + QStringLiteral(";\n");
m_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + es + QStringLiteral(" GRANT ALL ON SEQUENCES TO ") + sUser + QStringLiteral(";\n");
m_sql += QStringLiteral("SET SEARCH_PATH TO %1;\n").arg(es);
m_sql += m_update.exercise_sql;
m_sql += QStringLiteral("\n");
m_sql += QStringLiteral("INSERT INTO db_version (description, version) VALUES ('%1', '%2');\n\n").arg(m_update.info, m_update.version);
m_sql += QStringLiteral("\n--COMMIT;\n\n");
}
}
if (m_sql.size() > 0)
{
saveUpdateSql();
DBTransaction transaction(m_db);
NSqlQuery q(m_db);
transaction.begin();
if (!q.exec(m_sql))
{
DEBUG_LOG(m_logger) << "Failed sql: " << m_sql;
ERROR_LOG(m_logger) << "Error running sql: " << q.lastError().databaseText() << " " << q.lastError().driverText();
transaction.rollback();
if (m_showUi)
QMessageBox::warning(this, tr("Error!"), tr("An error occurred executing sql. See log file for details."));
}
else
{
transaction.commit();
if (transaction.state())
{
DEBUG_LOG(m_logger) << "SUCCESS running update: " << m_update.filename;
if (m_showUi)
QMessageBox::information(this, tr("Success"), tr("Update successful!"));
}
else
{
transaction.rollback();
if (m_showUi)
QMessageBox::warning(this, tr("Error!"), tr("An error occurred executing sql. See log file for details."));
}
}
}
else
if (m_showUi)
QMessageBox::warning(this, tr("Warning"), tr("Nothing to update."));
if (m_showUi)
enaDisa();
}
void DBUpdate::saveUpdateSql()
{
QString sqlFile = DBUpdate::m_defaultPath + m_update.version;
if (m_update.project.size() > 0)
sqlFile += QStringLiteral("_") + m_update.project;
sqlFile += QStringLiteral("_run.sql");
QFile file(sqlFile);
if (!file.open(QIODevice::WriteOnly | QFile::Truncate))
{
ERROR_LOG(m_logger) << "Error opening file '" << file.fileName() << "' for writing: "
<< file.errorString();
return ;
}
file.write(m_sql.toUtf8());
file.write("\n");
file.close();
INFO_LOG(m_logger) << "Saved GENERATED UPDATE FILE: " << sqlFile;
return;
}
void DBUpdate::onBackupDB()
{
INFO_LOG(m_logger) << "Creating DB Backup";
const QString& gVers = m_globalUpdatesModel->getLastUpdate();
const QString& eVers = m_exercisesModel->getLastUpdate();
QString currVersion = ( gVers >= eVers ? gVers : eVers );
QString date = QDate::currentDate().toString(QLatin1String("yyyyMMdd"));
QString dumpFile = DBUpdate::m_defaultPath + m_db.hostName() + QLatin1Char('_') +
m_db.databaseName()+ QLatin1Char('_') + date + QLatin1Char('_') + currVersion + QStringLiteral(".dump");
DBUpdateRun *dbRun = new DBUpdateRun(QString(), false, true);
bool backupOK = dbRun->runPgDump(dumpFile, m_db.hostName(), m_db.databaseName());
if (!backupOK)
{
ERROR_LOG(m_logger) << "Backup failed";
QMessageBox::critical(this, tr("Backup"), tr("Backup failed!"), QMessageBox::Ok);
}
else
{
INFO_LOG(m_logger) << "Created DB Backup successfully: " << dumpFile;
QMessageBox::information(this, tr("Backup"), tr("Successfully created DB backup: %1").arg(dumpFile), QMessageBox::Ok);
}
delete dbRun;
}
void DBUpdate::enaDisa()
{
if (!m_ui)
return;
// reset UI
m_globalUpdatesModel->initModel(m_db);
m_exercisesModel->initModel(m_db);
m_ui->tbRunUpdate->setEnabled(false);
m_ui->lblSelectedUpdate->setText(tr("(no update loaded)"));
m_ui->teHeader->clear();
m_ui->lblSQL->clear();
m_ui->pteSQL->clear();
m_ui->tvExerciseUpdates->resizeColumnsToContents();
m_ui->tvGlobalUpdates->resizeColumnsToContents();
const QString& gVers = m_globalUpdatesModel->getLastUpdate();
const QString& eVers = m_exercisesModel->getLastUpdate();
int upperGVersion = gVers.split(QLatin1Char('.')).at(0).toInt();
int gVersion = gVers.section(QLatin1Char('.'), 1).toInt();
DEBUG_LOG(m_logger) << "gVersion: " << gVersion;
DEBUG_LOG(m_logger) << "upperGVersion: " << upperGVersion;
int upperEVersion = eVers.split(QLatin1Char('.')).at(0).toInt();
int eVersion = eVers.section(QLatin1Char('.'), 1).toInt();
DEBUG_LOG(m_logger) << "eVersion: " << eVersion;
DEBUG_LOG(m_logger) << "upperEVersion: " << upperEVersion;
QString currVersion = eVers;
if ( upperGVersion > upperEVersion )
currVersion = gVers;
else if ( upperGVersion == upperEVersion )
currVersion = (gVersion >= eVersion ? gVers : eVers );
m_ui->lblVersion->setText(currVersion);
}
void DBUpdate::onShowLogFile()
{
QString logFile = NGL::Settings::logPath() + QStringLiteral("/") + QString::fromLatin1(qgetenv("USER")) + QStringLiteral("_dbu.log");
DEBUG_LOG(m_logger) << "onShowLogFile(): " << logFile;
QFile file(logFile);
if (!file.open(QIODevice::ReadOnly))
{
ERROR_LOG(m_logger) << "Cannot open file " << logFile;
return ;
}
QByteArray output = file.readAll();
m_ui->pteLogFile->clear();
m_ui->pteLogFile->setPlainText(QString::fromLocal8Bit(output));
file.close();
return;
}
void DBUpdate::onClearLogFile()
{
QString logFile = NGL::Settings::logPath() + QStringLiteral("/") + QString::fromLatin1(qgetenv("USER")) + QStringLiteral("_dbu.log");
DEBUG_LOG(m_logger) << "onClearLogFile(): " << logFile;
QFile file(logFile);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QFile::Truncate))
{
ERROR_LOG(m_logger) << "Cannot clear file " << logFile;
return ;
}
file.close();
m_ui->pteLogFile->clear();
return;
}
bool DBUpdate::parseUpdateFile(const QString &fileName, bool showUi)
{
DEBUG_LOG(m_logger) << "parseUpdateFile: " << fileName;
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
{
ERROR_LOG(m_logger) << "Cannot read file " << fileName;
return false;
}
QByteArray fileContent = file.readAll();
QString updateFile = QString::fromLocal8Bit(fileContent);
QStringList strUpdate;
bool fileOk = true;
static const QRegularExpression re_info(QStringLiteral("INFO: (.*?)$"), QRegularExpression::MultilineOption);
static const QRegularExpression re_date(QStringLiteral("DATE: (.*?)$"), QRegularExpression::MultilineOption);
static const QRegularExpression re_vers(QStringLiteral("VERSION: (.*?)$"), QRegularExpression::MultilineOption);
static const QRegularExpression re_proj(QStringLiteral("PROJECT: (.*?)$"), QRegularExpression::MultilineOption);
static const QRegularExpression re_glob(QStringLiteral("GLOBAL: (.+?);.(.+?COMMIT;)"), QRegularExpression::DotMatchesEverythingOption);
static const QRegularExpression re_exer(QStringLiteral("EXERCISE: (.+?);.(.+?COMMIT;)"), QRegularExpression::DotMatchesEverythingOption);
QRegularExpressionMatch match_info = re_info.match(updateFile);
QRegularExpressionMatch match_proj = re_proj.match(updateFile);
QRegularExpressionMatch match_vers = re_vers.match(updateFile);
QRegularExpressionMatch match_date = re_date.match(updateFile);
QRegularExpressionMatch match_glob = re_glob.match(updateFile);
QRegularExpressionMatch match_exer = re_exer.match(updateFile);
if (match_info.hasMatch() && match_vers.hasMatch() && match_glob.hasMatch() && match_exer.hasMatch())
// these are mandatory for a valid update file
{
strUpdate += match_info.captured(0);
strUpdate += match_vers.captured(0);
if (match_date.hasMatch())
{
strUpdate += match_date.captured(0);
m_update.date = match_date.captured(1);
}
if (match_proj.hasMatch())
{
strUpdate += match_proj.captured(0);
m_update.project = match_proj.captured(1).toLower();
}
strUpdate += QStringLiteral("GLOBAL: ") + match_glob.captured(1);
strUpdate += QStringLiteral("EXERCISE: ") + match_exer.captured(1);
m_update.info = match_info.captured(1);
m_update.version = match_vers.captured(1);
ERROR_LOG(m_logger) << "Update version: " << m_update.version;
m_update.global_schemas = match_glob.captured(1);
m_update.global_sql = match_glob.captured(2);
m_update.exercise_schemas = match_exer.captured(1);
m_update.exercise_sql = match_exer.captured(2);
m_update.global_sql.remove(QStringLiteral("BEGIN;"));
m_update.global_sql.remove(QStringLiteral("COMMIT;"));
m_update.exercise_sql.remove(QStringLiteral("BEGIN;"));
m_update.exercise_sql.remove(QStringLiteral("COMMIT;"));
}
else
{
fileOk = false;
}
if (fileOk && showUi)
{
m_ui->lblSelectedUpdate->setText(fileName);
m_ui->teHeader->clear();
m_ui->teHeader->setText(strUpdate.join(QStringLiteral("\n")));
DEBUG_LOG(m_logger) << " teHeader strUpdate: " << strUpdate.join(QStringLiteral("\n"));
}
file.close();
return fileOk;
}
void DBUpdate::onShowGlobalHistory(const QModelIndex &idx)
{
DEBUG_LOG(m_logger) << "onShowHistory";
QModelIndex realIdx = m_globalUpdatesProxyModel->mapToSource(idx);
const QString &schema = m_globalUpdatesModel->getRowData(realIdx.row()).schema;
DEBUG_LOG(m_logger) << "Schema = " << schema;
if (m_showHistory->init(m_db, schema))
m_showHistory->show();
}
void DBUpdate::onShowExerciseHistory(const QModelIndex &idx)
{
DEBUG_LOG(m_logger) << "onShowHistory";
QModelIndex realIdx = m_exercisesProxyModel->mapToSource(idx);
const QString &schema = m_exercisesModel->getRowData(realIdx.row()).schema;
DEBUG_LOG(m_logger) << "Schema = " << schema;
if (m_showHistory->init(m_db, schema))
m_showHistory->show();
}
void DBUpdate::onLoadSQL()
{
DEBUG_LOG(m_logger) << "onLoadSQL";
// open dialog
const QStringList filter(
{
tr("Update Files (*.sql)"),
#ifdef Q_OS_WIN
tr("All (*.*)")
#else
tr("All (*)")
#endif
});
QString fileName = QFileDialog::getOpenFileName(this,
tr("Load sql file"),
m_defaultPath,
filter.join(QStringLiteral(";;")));
if (fileName.isEmpty())
return;
QFileInfo fileInfo(fileName);
if (!fileInfo.isReadable())
{
QMessageBox::warning(this, tr("Error!"), tr("An error occurred while loading the file.\n"
"The loaded file is not readable."));
return;
}
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
{
ERROR_LOG(m_logger) << "Cannot open file " << fileName;
return ;
}
QByteArray output = file.readAll();
m_ui->lblSQL->setText(fileName);
m_ui->pteSQL->clear();
m_ui->pteSQL->setPlainText(QString::fromLocal8Bit(output));
file.close();
}
//===============================================================================================
DBUpdateRun::DBUpdateRun(const QString &updateFile, bool generateOnly, bool withBackup, QObject *parent)
: QObject(parent)
, m_generateOnly(generateOnly)
, m_withBackup(withBackup)
, m_globalUpdatesModel(new GlobalUpdatesModel(this))
, m_exercisesModel(new ExercisesModel(this))
, m_proc(nullptr)
, m_logger(getLogger("DBUpdate"))
{
m_update.filename = updateFile;
}
DBUpdateRun::~DBUpdateRun()
{
delete m_globalUpdatesModel;
delete m_exercisesModel;
}
bool DBUpdateRun::init()
{
QString str;
NGL::Settings s(str);
// open postgres db, read table structure, read static data
const QString dbUser(QStringLiteral("postgres"));
const QString dbPasswd(QStringLiteral("postgres"));
m_db = DBConnection::open(m_logger,
dbUser,
dbPasswd,
s.databaseDriver(),
s.databaseHostname(),
s.databaseName());
if (!m_db.isOpen())
return false;
DEBUG_LOG(m_logger) << "DB Open: " << s.databaseHostname() << " / " << s.databaseName();
m_globalUpdatesModel->initModel(m_db);
m_exercisesModel->initModel(m_db);
return true;
}
bool DBUpdateRun::runUpdate()
{
DEBUG_LOG(m_logger) << "runUpdate: " << m_update.filename;
bool bRet = false;
if (m_withBackup)
{
DEBUG_LOG(m_logger) << "Creating DB Backup ...";
const QString& gVers = m_globalUpdatesModel->getLastUpdate();
const QString& eVers = m_exercisesModel->getLastUpdate();
QString currVersion = ( gVers >= eVers ? gVers : eVers );
QString dumpFile = DBUpdate::m_defaultPath + m_db.hostName() + QLatin1Char('_') +
m_db.databaseName()+ QLatin1Char('_') + currVersion + QStringLiteral(".dump");
if (!runPgDump(dumpFile, m_db.hostName(), m_db.databaseName()))
ERROR_LOG(m_logger) << "DB Backup failed!!!";
}
if (parseUpdateFile())
{
DEBUG_LOG(m_logger) << "succesfully parsed update file:" << m_update.filename;
m_globalUpdatesModel->initModel(m_db, m_update.version, m_update.global_schemas.split(QStringLiteral(",")));
m_exercisesModel->initModel(m_db, m_update.version, m_update.exercise_schemas.split(QStringLiteral(",")));
generateSql();
if (m_update.generated_file.size() > 0)
bRet = true;
if (!m_generateOnly && bRet)
{
DEBUG_LOG(m_logger) << "Running sql file: " << m_update.generated_file;
bRet = runPsql();
}
}
return bRet;
}
bool DBUpdateRun::runPsql()
{
const QStringList args({QStringLiteral("-U"),
QStringLiteral("postgres"),
QStringLiteral("-f"),
m_update.generated_file,
QStringLiteral("-h"),
m_db.hostName(),
m_db.databaseName()});
handleProcInit(executablePath(QStringLiteral("psql")), args);
connect(m_proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &DBUpdateRun::onPsqlFinished);
m_proc->start();
return m_proc->waitForFinished();
}
bool DBUpdateRun::runPgDump(const QString& dumpFile, const QString& dbHost, const QString& dbName)
{
INFO_LOG(m_logger) << "Backup file = " << dumpFile;
const QStringList args(
{ QStringLiteral("--column-inserts"),
QStringLiteral("-U"), QStringLiteral("postgres"),
QStringLiteral("-f"), dumpFile,
QStringLiteral("-h"), dbHost, dbName});
handleProcInit(executablePath(QStringLiteral("pg_dump")), args);
connect(m_proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &DBUpdateRun::onPgDumpFinished);
m_proc->start();
return m_proc->waitForFinished();
}
QString DBUpdateRun::executablePath(const QString &program)
{
QString errStr;
const QString ret = NGL::postgresExecutablePath(program, errStr);
if (!errStr.isEmpty())
WARNING_LOG(m_logger) << errStr;
return ret;
}
void DBUpdateRun::handleProcInit(const QString &program, const QStringList &args)
{
if (m_proc)
{
m_proc->disconnect(this);
m_proc->deleteLater();
}
m_proc = new QProcess(this);
NOTICE_LOG(m_logger) << "Starting process '" << program << "' with args '" << args.join(QLatin1Char(' ')) << "'";
m_proc->setProgram(program);
m_proc->setArguments(args);
connect(m_proc, &QProcess::errorOccurred,
this, &DBUpdateRun::onErrorOccured);
}
void DBUpdateRun::handleProcFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
if (!m_proc)
return;
if (exitCode != 0 || exitStatus != 0)
{
QByteArray out = m_proc->readAllStandardError();
if (!out.isEmpty())
out += '\n';
out += m_proc->readAllStandardOutput();
ERROR_LOG(m_logger) << "Process finished - exitCode: " << exitCode << ", exitStatus = " << exitStatus << ", " << out;
}
m_proc->disconnect(this);
m_proc->deleteLater();
m_proc = nullptr;
}
void DBUpdateRun::onPsqlFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
NOTICE_LOG(m_logger) << "onPsqlFinished: exitCode: " << exitCode << ", exitStatus: " << exitStatus;
if (exitCode > 0)
{
ERROR_LOG(m_logger) << "Psql failed";
emit runSqlFailed();
return;
}
emit runSqlFinished();
}
void DBUpdateRun::onPgDumpFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
NOTICE_LOG(m_logger) << "onPgDumpFinished: exitCode: " << exitCode << ", exitStatus: " << exitStatus;
if (exitCode > 0)
{
ERROR_LOG(m_logger) << "Backup (pg_dump) failed";
emit runPgDumpFailed();
return;
}
ERROR_LOG(m_logger) << "Backup successfully finished!";
emit runPgDumpFinished();
}
void DBUpdateRun::onErrorOccured(QProcess::ProcessError error)
{
ERROR_LOG(m_logger) << "error: " << error << ", " << m_proc->readAllStandardOutput() << ", " << m_proc->readAllStandardError();
}
bool DBUpdateRun::parseUpdateFile()
{
DEBUG_LOG(m_logger) << "parseUpdateFile: " << m_update.filename;
QFile file(m_update.filename);
if (!file.open(QIODevice::ReadOnly))
{
ERROR_LOG(m_logger) << "Cannot read file " << m_update.filename;
return false;
}
QByteArray fileContent = file.readAll();
QString updateFile = QString::fromLocal8Bit(fileContent);
QStringList strUpdate;
bool fileOk = true;
static const QRegularExpression re_info(QStringLiteral("INFO: (.*?)$"), QRegularExpression::MultilineOption);
static const QRegularExpression re_date(QStringLiteral("DATE: (.*?)$"), QRegularExpression::MultilineOption);
static const QRegularExpression re_vers(QStringLiteral("VERSION: (.*?)$"), QRegularExpression::MultilineOption);
static const QRegularExpression re_proj(QStringLiteral("PROJECT: (.*?)$"), QRegularExpression::MultilineOption);
static const QRegularExpression re_glob(QStringLiteral("GLOBAL: (.+?);.(.+?COMMIT;)"), QRegularExpression::DotMatchesEverythingOption);
static const QRegularExpression re_exer(QStringLiteral("EXERCISE: (.+?);.(.+?COMMIT;)"), QRegularExpression::DotMatchesEverythingOption);
QRegularExpressionMatch match_info = re_info.match(updateFile);
QRegularExpressionMatch match_vers = re_vers.match(updateFile);
QRegularExpressionMatch match_date = re_date.match(updateFile);
QRegularExpressionMatch match_glob = re_glob.match(updateFile);
QRegularExpressionMatch match_exer = re_exer.match(updateFile);
QRegularExpressionMatch match_proj = re_proj.match(updateFile);
if (match_info.hasMatch() && match_vers.hasMatch() && match_glob.hasMatch() && match_exer.hasMatch())
// these are mandatory for a valid update file
{
strUpdate += match_info.captured(0);
strUpdate += match_vers.captured(0);
if (match_date.hasMatch())
{
strUpdate += match_date.captured(0);
m_update.date = match_date.captured(1);
}
if (match_proj.hasMatch())
{
strUpdate += match_proj.captured(0);
m_update.project = match_proj.captured(1).toLower();
}
strUpdate += QStringLiteral("GLOBAL: ") + match_glob.captured(1);
strUpdate += QStringLiteral("EXERCISE: ") + match_exer.captured(1);
m_update.info = match_info.captured(1);
m_update.version = match_vers.captured(1);
m_update.global_schemas = match_glob.captured(1);
m_update.global_sql = match_glob.captured(2);
m_update.exercise_schemas = match_exer.captured(1);
m_update.exercise_sql = match_exer.captured(2);
m_update.global_sql.remove(QStringLiteral("BEGIN;"));
m_update.global_sql.remove(QStringLiteral("COMMIT;"));
m_update.exercise_sql.remove(QStringLiteral("BEGIN;"));
m_update.exercise_sql.remove(QStringLiteral("COMMIT;"));
}
else
{
fileOk = false;
}
file.close();
return fileOk;
}
void DBUpdateRun::generateSql()
{
DEBUG_LOG(m_logger) << "generateSql:";
QStringList gSchemas = m_globalUpdatesModel->getSchemasToUpdate();
QStringList eSchemas = m_exercisesModel->getSchemasToUpdate();
DEBUG_LOG(m_logger) << "Global schemas: " << gSchemas.join(QStringLiteral(","));
DEBUG_LOG(m_logger) << "Exercise schemas: " << gSchemas.join(QStringLiteral(","));
DEBUG_LOG(m_logger) << "Global sql: " << m_update.global_sql;
DEBUG_LOG(m_logger) << "Exercise sql: " << m_update.exercise_sql;
// generate SQL
m_update.generated_sql.clear();
if (m_update.global_sql.size() > 0 && gSchemas.size() > 0)
{
ERROR_LOG(m_logger) << "Global sql: " << m_update.global_sql;
for (auto &gs : gSchemas)
{
m_update.generated_sql += QStringLiteral("--BEGIN;\n");
if (gs != QStringLiteral("PUBLIC"))
{
QString sUser = QStringLiteral("ets_") + gs;
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + gs + QStringLiteral(" GRANT SELECT,INSERT,UPDATE,DELETE,TRUNCATE ON TABLES TO ") + sUser + QStringLiteral(";\n");
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + gs + QStringLiteral(" GRANT SELECT, REFERENCES ON TABLES TO ") + sUser + QStringLiteral(";\n");
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + gs + QStringLiteral(" GRANT EXECUTE ON FUNCTIONS TO ") + sUser + QStringLiteral(";\n");
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + gs + QStringLiteral(" GRANT ALL ON SEQUENCES TO ") + sUser + QStringLiteral(";\n");
}
}
m_update.generated_sql += QStringLiteral("SET SEARCH_PATH TO %1;\n").arg(gSchemas.join(QStringLiteral(",")));
m_update.generated_sql += m_update.global_sql;
m_update.generated_sql += QStringLiteral("\n");
m_update.generated_sql += QStringLiteral("INSERT INTO db_version (description, version) VALUES ('%1', '%2');\n\n").arg(m_update.info, m_update.version);
m_update.generated_sql += QStringLiteral("\n--COMMIT;\n\n");
}
if (m_update.exercise_sql.size() > 0 && eSchemas.size() > 0)
{
ERROR_LOG(m_logger) << "Exercise sql: " << m_update.exercise_sql;
for (auto &es : eSchemas)
{
m_update.generated_sql += QStringLiteral("--BEGIN;\n");
QString sUser = QStringLiteral("ets_ex_00"); // + es;
m_update.generated_sql += QStringLiteral("GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA %1 TO %2;\n").arg(es).arg(sUser);
m_update.generated_sql += QStringLiteral("GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA %1 TO %2;\n").arg(es).arg(sUser);
m_update.generated_sql += QStringLiteral("GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA %1 TO %2;\n").arg(es).arg(sUser);
//backwards compatibility
QString sOldUser = QStringLiteral("ets_") + es;
m_update.generated_sql += QStringLiteral("SELECT public.revoke_user_if_exists('%1','%2');\n").arg(sOldUser).arg(es);
/*
m_update.generated_sql += QStringLiteral("SELECT create_user_if_not_exists('%1');\n").arg(sOldUser);
m_update.generated_sql += QStringLiteral("REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA %1 FROM %2 CASCADE;\n").arg(es).arg(sOldUser);
m_update.generated_sql += QStringLiteral("REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA %1 FROM %2 CASCADE;\n").arg(es).arg(sOldUser);
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA %1 REVOKE ALL ON TABLES FROM %2;\n").arg(es).arg(sOldUser);
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE ets_exm IN SCHEMA %1 REVOKE ALL ON TABLES FROM %2;\n").arg(es).arg(sOldUser);
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA %1 REVOKE ALL ON FUNCTIONS FROM %2;\n").arg(es).arg(sOldUser);
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE ets_exm IN SCHEMA %1 REVOKE ALL ON FUNCTIONS FROM %2;\n").arg(es).arg(sOldUser);
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA %1 REVOKE ALL ON SEQUENCES FROM %2;\n").arg(es).arg(sOldUser);
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES FOR ROLE ets_exm IN SCHEMA %1 REVOKE ALL ON SEQUENCES FROM %2;\n").arg(es).arg(sOldUser);
m_update.generated_sql += QStringLiteral("DROP USER %1;\n").arg(sOldUser);
*/
//backwards compatibility
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + es + QStringLiteral(" GRANT SELECT,INSERT,UPDATE,DELETE,TRUNCATE ON TABLES TO ") + sUser + QStringLiteral(";\n");
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + es + QStringLiteral(" GRANT SELECT, REFERENCES ON TABLES TO ") + sUser + QStringLiteral(";\n");
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + es + QStringLiteral(" GRANT EXECUTE ON FUNCTIONS TO ") + sUser + QStringLiteral(";\n");
m_update.generated_sql += QStringLiteral("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + es + QStringLiteral(" GRANT ALL ON SEQUENCES TO ") + sUser + QStringLiteral(";\n");
m_update.generated_sql += QStringLiteral("SET SEARCH_PATH TO %1;\n").arg(es);
m_update.generated_sql += m_update.exercise_sql;
m_update.generated_sql += QStringLiteral("\n");
m_update.generated_sql += QStringLiteral("INSERT INTO db_version (description, version) VALUES ('%1', '%2');\n\n").arg(m_update.info, m_update.version);
m_update.generated_sql += QStringLiteral("\n--COMMIT;\n\n");
}
}
DEBUG_LOG(m_logger) << "Generated sql size: " << m_update.generated_sql.size();
if (m_update.generated_sql.size() > 0)
{
QString sqlFile = DBUpdate::m_defaultPath + m_update.version;
if (m_update.project.size() > 0)
sqlFile += QStringLiteral("_") + m_update.project;
sqlFile += QStringLiteral("_run.sql");
QFile file(sqlFile);
if (file.open(QIODevice::WriteOnly | QFile::Truncate))
{
file.write(m_update.generated_sql.toUtf8());
file.write("\n");
file.close();
INFO_LOG(m_logger) << "Saved GENERATED UPDATE FILE: " << sqlFile;
m_update.generated_file = sqlFile;
}
else
{
ERROR_LOG(m_logger) << "Error opening file '" << file.fileName() << "' for writing: "
<< file.errorString();
m_update.generated_file = QString();
}
}
}
} // namespace DBUpdate