/** * @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 #include #include #include #include #include #include #include #include #include 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::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::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