diff --git a/src/updater.cpp b/src/updater.cpp --- a/src/updater.cpp +++ b/src/updater.cpp @@ -1,796 +1,850 @@ #include "updater.h" #include "psettings.h" #include #include +#include #include #include #include #include #include DatabaseUpdater::DatabaseUpdater() : QObject() , _pset(0) , _revisionBefore(0) , _revisionAfter(0) , _processingUri(false) { } DatabaseUpdater::~DatabaseUpdater() { delete _pset; } int DatabaseUpdater::run(ProgramSettings& a_pset) { _pset = new ProgramSettings(a_pset); if (!checkArguments()) return -1; if (!readConfig()) return -1; if (!loadScriptsFromResource()) return -1; if (!runScripts()) return -1; return 0; } //--------------------------------------------------------------------------- bool DatabaseUpdater::checkArguments() { if (_pset->controlFile.isEmpty()) { emit error(QStringLiteral("Не задан файл конфигурации.")); return false; } //если не задано полное имя файла конфигурации, то искать его в текущем каталоге QString filename = _pset->controlFile; if (!QDir::isAbsolutePath(filename)) filename = QStringLiteral("%1/%2").arg(QDir::currentPath()).arg(_pset->controlFile); //проверить наличие файла if (!QFile::exists(filename)) { emit error(QStringLiteral("Не найден файл конфигурации\n") + QDir::toNativeSeparators(filename)); return false; } _pset->controlFile = filename; return true; } //--------------------------------------------------------------------------- bool DatabaseUpdater::readConfig() { //открываем входной файл QFile controlFile(_pset->controlFile); if (!controlFile.open(QIODevice::ReadOnly)) { emit error(QStringLiteral("Сбой при загрузке файла конфигурации:\n" "Невозможно открыть для чтения файл %1") .arg(QDir::toNativeSeparators(_pset->controlFile))); return false; } //парсим входной файл QXmlStreamReader xml; xml.setDevice(&controlFile); emit message(QStringLiteral("Загрузка конфигурации из файла\n%1").arg(QDir::toNativeSeparators(_pset->controlFile))); //идём по элементам документа while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement() && xml.name() == "package") if (!readPackage(xml)) return false; } if (xml.hasError()) { emit error(QStringLiteral("Сбой при загрузке файла конфигурации:\n" "Ошибка: %1, строка %2, позиция %3") .arg(xml.errorString()) .arg(xml.lineNumber()) .arg(xml.columnNumber())); return false; } controlFile.close(); - //найти файл с расширением pkginfo и именем как у входного файла, если такого нет, - //то использовать первый попавшийся файл с расширением pkginfo; + //найти файл с расширением upki (pkginfo) и именем как у входного файла, если такого нет, + //то использовать первый попавшийся файл с расширением upki (pkginfo); //прочитать из него идентификатор пакета и имя базы данных; //если файла нет, то и не надо + const QString UPACKAGE_INFO_EXT = QStringLiteral(".upki"); + const QString PACKAGE_INFO_EXT = QStringLiteral(".pkginfo"); + const QStringList PACKAGE_FILE_FILTER = {QStringLiteral("*.upki"), + QStringLiteral("*.pkginfo")}; QFileInfo fi(controlFile); - QStringList files = QDir(QFileInfo(controlFile).path(), "*.pkginfo", - QDir::NoSort, QDir::Files).entryList(); + QStringList files = QDir(QFileInfo(controlFile).path(), QString(), + QDir::NoSort, QDir::Files).entryList(PACKAGE_FILE_FILTER); if (!files.isEmpty()) { - QString pkgfile = QFileInfo(controlFile).completeBaseName() + ".pkginfo"; + QString completeBaseName = QFileInfo(controlFile).completeBaseName(); + QString pkgfile = completeBaseName + UPACKAGE_INFO_EXT; if (!files.contains(pkgfile, Qt::CaseInsensitive)) - pkgfile = files.at(0); + { + pkgfile = completeBaseName + PACKAGE_INFO_EXT; + + if (!files.contains(pkgfile, Qt::CaseInsensitive)) + pkgfile = files.at(0); + } pkgfile = QFileInfo(controlFile).path() + '/' + pkgfile; emit message(QStringLiteral("Загрузка конфигурации из файла\n%1").arg(QDir::toNativeSeparators(pkgfile))); - KSettings pkginfo(pkgfile); - pkginfo.beginGroup("package"); + if (pkgfile.endsWith(UPACKAGE_INFO_EXT)) + { + QFile pkginfo(pkgfile); + if (pkginfo.open(QIODevice::ReadOnly)) + { + QString content = QString::fromUtf8(pkginfo.readAll()); - QString s = pkginfo.value("dbname").toString(); - if (!s.isEmpty() && _pset->database.isEmpty()) - _pset->database = s; + QRegularExpression rxDbname(QStringLiteral(R"(^\s*package.*?\Wdbname\s*=\s*(\S+))"), + QRegularExpression::DotMatchesEverythingOption|QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch match = rxDbname.match(content); + if (match.hasMatch()) + { + QString s = match.captured(1); + if (!s.isEmpty() && _pset->database.isEmpty()) + _pset->database = s; + } - QStringList ver = pkginfo.value("version").toString().split(QChar('.')); - if (ver.size() > 1) + QRegularExpression rxVersion(QStringLiteral(R"(^\s*package.*?\Wversion\s*=\s*(\S+))"), + QRegularExpression::DotMatchesEverythingOption|QRegularExpression::CaseInsensitiveOption); + match = rxVersion.match(content); + if (match.hasMatch()) + { + QStringList ver = match.captured(1).split(QChar('.')); + if (ver.size() > 1) + { + QString s = ver.at(1); + bool ok; + int i = s.toInt(&ok); + if (ok) + _pset->dbVersion = i; + } + } + + QRegularExpression rxPackageId(QStringLiteral(R"(^\s*package.*?\Wid\s*=\s*(\S+))"), + QRegularExpression::DotMatchesEverythingOption|QRegularExpression::CaseInsensitiveOption); + match = rxPackageId.match(content); + if (match.hasMatch()) + _pset->packageId = match.captured(1); + } + } + else if (pkgfile.endsWith(PACKAGE_INFO_EXT)) { - s = ver.at(1); - bool ok; - int i = s.toInt(&ok); - if (ok) - _pset->dbVersion = i; + KSettings pkginfo(pkgfile); + pkginfo.beginGroup("package"); + + QString s = pkginfo.value("dbname").toString(); + if (!s.isEmpty() && _pset->database.isEmpty()) + _pset->database = s; + + QStringList ver = pkginfo.value("version").toString().split(QChar('.')); + if (ver.size() > 1) + { + s = ver.at(1); + bool ok; + int i = s.toInt(&ok); + if (ok) + _pset->dbVersion = i; + } + + _pset->packageId = pkginfo.value("id").toString(); } - - _pset->packageId = pkginfo.value("id").toString(); } //после загрузки конфигурации из файлов должно быть определено имя БД if (_pset->database.isEmpty()) { emit error(QStringLiteral("Не задано имя базы данных")); return false; } return true; } //--------------------------------------------------------------------------- bool DatabaseUpdater::readPackage(QXmlStreamReader& a_xml) { const QXmlStreamAttributes& packageAttrs = a_xml.attributes(); QString packageId = packageAttrs.value("id").toString(); QString uriAttr = packageAttrs.value("uri").toString(); bool uri = !((uriAttr == "no") || (uriAttr == "нет")); Package package(packageId, uri); //собираем информацию о скриптах данного пакета const QString SCRIPT_NODE("script"); const QString REVISION_ATTR("revision"); const QString TRANSACTION_ATTR("transaction"); const QString COMMENT_ATTR("comment"); const QString SCRIPT_ATTR("file"); const QString NO_REQUIRED_ATTR = QStringLiteral("Неправильное содержание файла конфигурации:\n" "Не указан обязательный атрибут «%1» пакета «%2»"); while (!a_xml.atEnd()) { a_xml.readNext(); if (a_xml.isStartElement() && a_xml.name() == SCRIPT_NODE) { const QXmlStreamAttributes& attrs = a_xml.attributes(); //проверка наличия обязательных атрибутов if (!attrs.hasAttribute(REVISION_ATTR)) { emit error(NO_REQUIRED_ATTR.arg(REVISION_ATTR).arg(packageId)); return false; } if (!attrs.hasAttribute(SCRIPT_ATTR)) { emit error(NO_REQUIRED_ATTR.arg(SCRIPT_ATTR).arg(packageId)); return false; } //проверка правильности указания атрибутов bool ok; int revision = attrs.value(REVISION_ATTR).toString().toInt(&ok); if (!ok) { emit error(QStringLiteral( "Атрибут «revision» пакета «%1» должен быть числом") .arg(packageId)); return false; } //заносим информацию о скрипте в список скриптов пакета QString file = attrs.value(SCRIPT_ATTR).toString(); QString comment = attrs.value(COMMENT_ATTR).toString(); QString strans = attrs.value(TRANSACTION_ATTR).toString(); bool transaction = !((strans == "0") || (strans.compare("no", Qt::CaseInsensitive) == 0) || (strans.compare("нет", Qt::CaseInsensitive) == 0)); package.addScript(DatabaseScript(file, transaction, revision, comment)); } } //упорядочить скрипты по возрастанию номеров ревизий package.sortScripts(); _packages.append(package); return true; } //--------------------------------------------------------------------------- bool DatabaseUpdater::runScripts() { //определение пакета, скрипты которого будут выполняться auto pp = std::find_if(_packages.cbegin(), _packages.cend(), [this](const Package& p){return p.id() == _pset->packageId;}); if (pp == _packages.cend()) { emit error(QStringLiteral("Для пакета «%1» не задано ни одного " "сценария создания базы данных") .arg(_pset->packageId)); return false; } //проверка наличия файлов со скриптами на диске const Package& package = *pp; QStringList paths; QFileInfo fi(_pset->controlFile); paths << fi.canonicalPath() << fi.canonicalPath() + "/script" << QDir::currentPath(); const auto& packageScripts = package.scripts(); for (auto it = packageScripts.begin(); it != packageScripts.end(); ++it) { const DatabaseScript& script = *it; if (!script.findScript(paths)) { emit error(QStringLiteral("Не найден файл сценария создания " "базы данных %1") .arg(script.script())); return false; } } QString curDate = QDate::currentDate().toString(Qt::ISODate); SqlProcessor proc; SqlProcessor uriProc; emit message(QStringLiteral("Подключение к серверу баз данных")); //пока идут служебные подключения к БД и запросы, подключаем только сигнал error connect(&proc, SIGNAL(error(const QString&, const QString&, const QString&)), this, SIGNAL(sqlError(const QString&, const QString&, const QString&))); connect(&uriProc, SIGNAL(error(const QString&, const QString&, const QString&)), this, SIGNAL(sqlError(const QString&, const QString&, const QString&))); //---- template1 ---- //попытка подключения к template1 и получение списка таблиц if (!proc.connectdb(_pset->host, _pset->port, "template1", _pset->username, _pset->password)) return false; QStringList template1Tables = databaseTableList(proc); //создание БД if (!createDatabase(proc, _pset, _pset->database)) return false; //создание учебной БД QString uriDatabase = _pset->database + "_u"; if (package.wantUri()) { UriGuard ug(this); if (!createDatabase(proc, _pset, uriDatabase)) return false; } connect(&proc, SIGNAL(afterConnect(QString, QString, QString, QString, QString)), this, SLOT(afterConnect(QString, QString, QString, QString, QString))); connect(&uriProc, SIGNAL(afterConnect(QString, QString, QString, QString, QString)), this, SLOT(afterConnect(QString, QString, QString, QString, QString))); //подключение к базам данных if (!proc.connectdb(_pset->host, _pset->port, _pset->database, _pset->username, _pset->password)) return false; if (package.wantUri()) { if (!uriProc.connectdb(_pset->host, _pset->port, uriDatabase, _pset->username, _pset->password)) return false; } StatusResult statusResult = databaseStatus(proc, package.id(), template1Tables); if (statusResult.failed()) return false; int databaseRevision = statusResult.databaseRevision(); int uriDatabaseRevision = 0; if (package.wantUri()) { UriGuard ug(this); statusResult = databaseStatus(uriProc, package.id(), template1Tables); if (statusResult.failed()) return false; uriDatabaseRevision = statusResult.databaseRevision(); } //создание языка plpgsql if (!createLanguagePlpgsql(proc, uriProc, package.wantUri())) return false; _revisionAfter = _revisionBefore = databaseRevision; //выполнение необходимых скриптов connect(&proc, SIGNAL(progress(int)), this, SIGNAL(progress(int))); connect(&uriProc, SIGNAL(progress(int)), this, SIGNAL(progress(int))); const auto& end = packageScripts.cend(); for (auto it = packageScripts.cbegin(); it != end; ++it) { const DatabaseScript& script = *it; int revision = script.revision(); //подготовка скрипта к выполнению QList preparedScript = prepareScript(script.script()); //выполнение скриптов в БД, пропуская уже установленные ревизии if (revision > databaseRevision) { if ( !runScript(proc, preparedScript, script, package.id()) ) return false; //сброс мандатных меток, если они есть в БД clearMaclabels(proc); } //выполнение скриптов в учебной БД, если задано её создание if (package.wantUri() && revision > uriDatabaseRevision) { UriGuard ug(this); if ( !runScript(uriProc, preparedScript, script, package.id()) ) return false; clearMaclabels(uriProc); } _revisionAfter = revision; } return true; } //--------------------------------------------------------------------------- bool DatabaseUpdater::runScript(SqlProcessor& a_proc, const QList& a_script, const DatabaseScript& a_databaseScript, const QString& a_packageId) { int revision = a_databaseScript.revision(); bool transaction = a_databaseScript.transaction(); if (revision == 1) emit message(messageString(CREATE_DB_VERSION).arg(revision)); else emit message(messageString(UPDATE_DB_VERSION).arg(revision)); //выполнение скрипта if (transaction) a_proc.execSQL("begin"); auto script = a_script; if (!a_proc.execute(script)) return false; //сброс параметра search_path, он мог быть изменён при выполнении скрипта if (!a_proc.execute(_resetSearchPathScript)) return false; //запись информации о версии в БД if (!a_proc.execSQL(QStringLiteral("insert into dm_version " "(revision,package_id,comment,gen_date) values " "(%1,'%2','%3',CURRENT_DATE)") .arg(revision) .arg(a_packageId) .arg(a_databaseScript.comment()))) return false; if (transaction) a_proc.execSQL("commit"); return true; } //--------------------------------------------------------------------------- bool DatabaseUpdater::databaseExists(SqlProcessor& a_proc, const QString& a_dbname) { a_proc.execSQL(QStringLiteral("select 1 from pg_database where datname='%1\'").arg(a_dbname)); return PQntuples(a_proc.result().pgresult) == 1; } //--------------------------------------------------------------------------- bool DatabaseUpdater::createDatabase(SqlProcessor& a_proc, ProgramSettings* a_pset, const QString& a_dbname) { //если уже подключены к указанной БД, то отключение if (a_proc.database() == a_dbname) a_proc.disconnectdb(); //если не подключены, то подключение к template1 if (a_proc.database().isNull()) { if (!a_proc.connectdb(a_pset->host, a_pset->port, "template1", a_pset->username, a_pset->password)) return false; } //удаление БД если задано параметром при запуске программы bool dbExists = databaseExists(a_proc, a_dbname); if (dbExists && _pset->dropdb) { emit message(messageString(DROP_DB).arg(a_dbname)); bool dropped = a_proc.execSQL(QString("drop database \"%1\"").arg(a_dbname)) && a_proc.commandStatus() == QString("DROP DATABASE"); if (!dropped) return false; dbExists = false; } //создание БД если она не существует if (!dbExists) { emit message(messageString(CREATE_DB).arg(a_dbname)); bool created = a_proc.execSQL(QString("create database \"%1\"").arg(a_dbname)) && a_proc.commandStatus() == QString("CREATE DATABASE"); if (!created) return false; } return true; } //--------------------------------------------------------------------------- // Получение списка таблиц в БД //--------------------------------------------------------------------------- QStringList DatabaseUpdater::databaseTableList(SqlProcessor& a_proc) { QStringList result; if (!a_proc.execSQL("select tablename from pg_tables order by tablename")) return result; SqlResult sqlresult = a_proc.result(); PGresult* pgresult = sqlresult.pgresult; QTextCodec* codec = sqlresult.codec; int numTuples = PQntuples(pgresult); for (int i = 0; i < numTuples; ++i) { if (PQgetisnull(pgresult, i, 0)) result.append(QString()); else result.append(codec->toUnicode(PQgetvalue(pgresult, i, 0))); } return result; } //--------------------------------------------------------------------------- // Получение номера текущей ревизии базы данных // // При необходимости создаётся таблица dm_version. //--------------------------------------------------------------------------- DatabaseUpdater::StatusResult DatabaseUpdater::databaseStatus(SqlProcessor& a_proc, const QString& a_packageId, const QStringList& a_template1Tables) { int currentDatabaseRevision = 0; //БД нет или пустая //считывание данных о версии БД из таблицы dm_version, //эта таблица существует только в основной БД, версия учебной должна совпадать a_proc.execSQL("select relnatts from pg_class where relname='dm_version' and relkind='r'"); PGresult* pgresult = a_proc.result().pgresult; //если пустой результат запроса, значит таблицы dm_version нет в БД bool createDmVersion = PQntuples(pgresult) == 0; //если таблица есть, то сравнить число её атрибутов с эталоном if (!createDmVersion) { const char* pgvalue = PQgetvalue(pgresult, 0, 0); if (QByteArray::fromRawData(pgvalue, qstrlen(pgvalue)).toInt() != DM_VERSION_FIELDS_COUNT) { //не та структура, удаляем таблицу if (!a_proc.execSQL("drop table dm_version")) return StatusResult(false); createDmVersion = true; } } if (createDmVersion) { emit message(messageString(CREATE_VERSION_TABLE)); a_proc.execSQL("begin"); if (!a_proc.execute(_createDmVersionScript)) { emit error(messageString(ERROR_VERSION_TABLE)); return StatusResult(false); } //если БД уже существует и её схема отличается от схемы template1, //т.е. в ней есть таблицы, то считается, что это ревизия №1 QStringList databaseTables = databaseTableList(a_proc); databaseTables.removeOne("dm_version"); if (databaseTables != a_template1Tables) { if (!a_proc.execSQL(QStringLiteral("insert into dm_version " "(revision,package_id,comment,gen_date) values " "(1,'%1','Изначальная версия',CURRENT_DATE)") .arg(a_packageId))) return StatusResult(false, currentDatabaseRevision); currentDatabaseRevision = 1; } a_proc.execSQL("commit"); } else { a_proc.execSQL(QStringLiteral("select max(revision), count(*) from dm_version " "where package_id='%1'") .arg(a_packageId)); PGresult* pgresult = a_proc.result().pgresult; if (PQntuples(pgresult)) { const char* pgvalue = PQgetvalue(pgresult, 0, 1); if (QByteArray::fromRawData(pgvalue, qstrlen(pgvalue)).toInt() > 0) { pgvalue = PQgetvalue(pgresult, 0, 0); currentDatabaseRevision = QByteArray::fromRawData(pgvalue, qstrlen(pgvalue)).toInt(); } } } return StatusResult(true, currentDatabaseRevision); } //--------------------------------------------------------------------------- bool DatabaseUpdater::loadScriptsFromResource() { QString errmess = QStringLiteral("Сбой при чтении ресурсов программы"); SqlProcessor proc; QFile resFile(":/dm_version.sql"); if (!(resFile.open(QIODevice::ReadOnly))) { emit error(errmess); return false; } _createDmVersionScript = proc.parse(resFile); resFile.close(); resFile.setFileName(":/create_helper_functions.sql"); if (!(resFile.open(QIODevice::ReadOnly))) { emit error(errmess); return false; } _createHelperFunctionsScript = proc.parse(resFile); resFile.close(); resFile.setFileName(":/drop_helper_functions.sql"); if (!(resFile.open(QIODevice::ReadOnly))) { emit error(errmess); return false; } _dropHelperFunctionsScript = proc.parse(resFile); resFile.close(); resFile.setFileName(":/set_search_path.sql"); if (!(resFile.open(QIODevice::ReadOnly))) { emit error(errmess); return false; } _resetSearchPathScript = proc.parse(resFile); return true; } //--------------------------------------------------------------------------- // Подготавливает скрипт, считанный из файла, к выполнению в БД. // Подготовка заключается в следующем: // 1. Команды создания глобальных объектов // CREATE GROUP // CREATE USER // CREATE ROLE // CREATE TABLESPACE // заменяются вызовами функций-обёрток для их безопасного выполнения. // В скрипт добавляются команды создания функций-обёрток и их удаления. // 2. Команда подключения языка plpgsql // CREATE [ TRUSTED ] [ PROCEDURAL ] LANGUAGE plpgsql // удаляется из скрипта. // Создание языка plpgsql производится отдельно, перед началом выполнения скриптов. //--------------------------------------------------------------------------- QList DatabaseUpdater::prepareScript(const QString& a_filename) { //пропустим файл через парсер, на выходе список команд SqlProcessor proc; QList result = proc.parse(a_filename); //обход списка и анализ команд bool helperRequired = false; auto end = result.end(); for (auto it = result.begin(); it != end; ++it) { QByteArray& line = *it; int i = 0; int end = line.size(); //пропускаем первое слово, перед ним пробелов нет, парсер их убирает while (i != end && !QChar(line.at(i)).isSpace()) ++i; //выделяем первое слово QByteArray command1 = line.left(i).toLower(); //переход на начало второго слова while (i != end && QChar(line.at(i)).isSpace()) ++i; int p = i; //пропускаем второе слово while (i != end && !QChar(line.at(i)).isSpace()) ++i; //выделяем второе слово QByteArray command2 = line.mid(p, i - p).toLower(); //анализируем команду if (command1 == "create") { if (command2 == "group" || command2 == "user" || command2 == "role" || command2 == "tablespace") { //добавляем в выходной скрипт вызов безопасной функции-обёртки helperRequired = true; //переход на начало имени объекта while (i != end && QChar(line.at(i)).isSpace()) ++i; QByteArray name; if (i != end) { if (line.at(i) == '"') { //если имя объекта начинается с двойной кавычки, то пропуск до следующей //двойной кавычки ++i; //пропустить открывающую двойную кавычку p = i; while (i != end && line.at(i) != '"') ++i; name = line.mid(p, i - p); if (i != end) ++i; //пропустить закрывающую двойную кавычку } else { //пропуск до конца слова p = i; while (i != end && !QChar(line.at(i)).isSpace()) ++i; name = line.mid(p, i - p); if (name.size() > 0 && name.at(name.size() - 1) == ';') name.chop(1); } } QByteArray parameters(line.right(end - i)); if (parameters.size() > 0 && parameters.at(parameters.size() - 1) == ';') parameters.chop(1); QByteArray safeCall("select public._rct_safe_create_"); safeCall.append(command2).append("('").append(name).append("','").append(parameters).append("');"); //заменяем строку в выходном массиве line = safeCall; } else if (command2 == "trusted" || command2 == "procedural" || command2 == "language") { //create language не нужен, заменяем пустой командой line = ";"; } } } //цикл по командам if (helperRequired) { for (const auto& helperScript: _createHelperFunctionsScript) result.prepend(helperScript); result.append(_dropHelperFunctionsScript); } return result; } //--------------------------------------------------------------------------- void DatabaseUpdater::clearMaclabels(SqlProcessor& a_proc) { a_proc.execSQL("select distinct 1 from pg_attribute a join pg_class c " "on (a.attrelid=c.oid) where c.relname='pg_class' and a.attname='relmaclabel'"); if (PQntuples(a_proc.result().pgresult) == 1) a_proc.execSQL("update pg_class set relmaclabel=null"); } //--------------------------------------------------------------------------- bool DatabaseUpdater::createLanguagePlpgsql(SqlProcessor& a_proc, SqlProcessor& a_uriProc, bool a_uri) { const QString CREATE_SQL = QStringLiteral("create language plpgsql"); const QString CHECK_SQL = QStringLiteral("select 1 from pg_language where lanname='plpgsql'"); a_proc.execSQL(CHECK_SQL); if (PQntuples(a_proc.result().pgresult) == 0) { if (!a_proc.execSQL(CREATE_SQL)) return false; } if (a_uri) { a_uriProc.execSQL(CHECK_SQL); if (PQntuples(a_uriProc.result().pgresult) == 0) { if (!a_uriProc.execSQL(CREATE_SQL)) return false; } } return true; } //--------------------------------------------------------------------------- void DatabaseUpdater::afterConnect(const QString& a_host, const QString& a_port, const QString& a_database, const QString& a_username, const QString& a_password) { Q_UNUSED(a_port); Q_UNUSED(a_password); emit logConnectionParameters(a_host, a_database, a_username); } //--------------------------------------------------------------------------- QString DatabaseUpdater::messageString(DatabaseUpdater::MessageId a_message) { QHash strings = { {CREATE_DB, QStringLiteral("Создание базы данных **%1**")}, {DROP_DB, QStringLiteral("Удаление базы данных **%1**")}, {CREATE_DB_VERSION, QStringLiteral("Создание базы данных версии %1")}, {UPDATE_DB_VERSION, QStringLiteral("Обновление базы данных до версии %1")}, {CREATE_VERSION_TABLE, QStringLiteral("Создание таблицы изменений базы данных")}, {ERROR_VERSION_TABLE, QStringLiteral("Сбой при создании таблицы изменений базы данных")} }; QString result = strings[a_message]; if (_processingUri) result.replace(QStringLiteral(" базы"), QStringLiteral(" учебной базы")); return result; } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- bool DatabaseScript::findScript(const QStringList& a_paths) const { _scriptFile.replace(QChar('\\'), QChar('/')); foreach (QString path, a_paths) { QDir dir(path); if (dir.exists(_scriptFile)) { _scriptFile = dir.absoluteFilePath(_scriptFile); return true; } } return false; } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- void Package::sortScripts() { std::sort(_scripts.begin(), _scripts.end(), [](DatabaseScript& s1, DatabaseScript& s2){ return s1.revision() < s2.revision(); }); }