diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,457 +1,457 @@ #include "mainwindow.h" #include "psettings.h" #include "sqlerrordlg.h" #include "updater.h" #include #include #include #ifdef Q_OS_WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- MainWindow::MainWindow(QWidget* parent, Qt::WindowFlags flags) : QDialog(parent, flags) , _exitCode(0) , _actionEventType(QEvent::User) , _errorCount(0) , _updater(new DatabaseUpdater) { setWindowTitle(QStringLiteral("Создание/обновление базы данных")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setSizeGripEnabled(true); QHBoxLayout* mainLayout = new QHBoxLayout; QVBoxLayout* leftLayout = new QVBoxLayout; QVBoxLayout* rightLayout = new QVBoxLayout; QHBoxLayout* bottomRightLayout = new QHBoxLayout; leftLayout->setContentsMargins(0, 2 * style()->pixelMetric(QStyle::PM_LayoutTopMargin), style()->pixelMetric(QStyle::PM_LayoutRightMargin), 0); QLabel* picture = new QLabel; picture->setPixmap(QPixmap(":/picture.png")); //кнопки, скрытые до завершения процесса _btnSave = new QPushButton(QStringLiteral("Сохранить")); _btnClose = new QPushButton(QStringLiteral("Закрыть")); _btnSave->setVisible(false); _btnClose->setVisible(false); _btnClose->setDefault(true); leftLayout->addWidget(picture); leftLayout->addStretch(); leftLayout->addWidget(_btnSave); leftLayout->addWidget(_btnClose); QLabel* label = new QLabel(QStringLiteral("Протокол работы")); rightLayout->addWidget(label); _protocol = new QTextEdit(); _protocol->setReadOnly(true); label->setBuddy(_protocol); rightLayout->addWidget(_protocol); //индикаторы выполнения _progress = new QProgressBar; _progress->setTextVisible(false); _ai = new KActivityIndicator(KActivityIndicator::Gray); _ai->setVisible(false); bottomRightLayout->addWidget(_progress); bottomRightLayout->addWidget(_ai); rightLayout->addLayout(bottomRightLayout); mainLayout->addLayout(leftLayout); mainLayout->addLayout(rightLayout); setLayout(mainLayout); //нажатие Esc не соединяется со слотом, чтобы окно не закрывалось new QShortcut(QKeySequence(Qt::Key_Escape), this); //соединить сигналы со слотами connect(_btnSave, SIGNAL(clicked()), this, SLOT(saveProtocol())); connect(_btnClose, SIGNAL(clicked()), this, SLOT(closeDialog())); //получить незадействованный тип события _actionEventType = QEvent::registerEventType(); } MainWindow::~MainWindow() { delete _updater; } //--------------------------------------------------------------------------- void MainWindow::showEvent(QShowEvent* a_event) { QDialog::showEvent(a_event); qApp->postEvent(this, new QEvent(QEvent::Type(_actionEventType))); } //--------------------------------------------------------------------------- bool MainWindow::event(QEvent* a_event) { if (a_event->type() == _actionEventType) { startExecution(); a_event->accept(); return true; } return QWidget::event(a_event); } //--------------------------------------------------------------------------- void MainWindow::closeEvent(QCloseEvent* a_event) { if (_futureWatcher.isRunning()) a_event->ignore(); else a_event->accept(); } //--------------------------------------------------------------------------- void MainWindow::startExecution() { if (_futureWatcher.isRunning()) return; ProgramSettings pset; processArguments(pset); logon(pset); connect(_updater, SIGNAL(progress(int)), _progress, SLOT(setValue(int))); connect(_updater, SIGNAL(error(const QString&)), this, SLOT(error(const QString&))); connect(_updater, SIGNAL(message(const QString&)), this, SLOT(message(const QString&))); connect(_updater, SIGNAL(sqlError(const QString&, const QString&, const QString&)), this, SLOT(sqlError(const QString&, const QString&, const QString&))); connect(_updater, SIGNAL(logConnectionParameters(const QString&, const QString&, const QString&)), this, SLOT(logConnectionParameters(const QString&, const QString&, const QString&))); connect(&_futureWatcher, SIGNAL(finished()), this, SLOT(finishExecution())); _ai->setVisible(true); _ai->start(); //int rc = _updater.run( pset ); QFuture rc = QtConcurrent::run(_updater, &DatabaseUpdater::run, pset); _futureWatcher.setFuture(rc); } //--------------------------------------------------------------------------- void MainWindow::finishExecution() { _ai->stop(); _ai->setVisible(false); //проверяем число ошибок, т.к. при выполнении без транзакции run() всегда возвращает 0 if (_errorCount == 0) { int before = _updater->revisionBefore(); int after = _updater->revisionAfter(); if (before == after) message(QStringLiteral("База данных в актуальном состоянии.")); else if (before == 0) message(QStringLiteral("База данных создана и обновлена до версии %1.").arg(after)); else message(QStringLiteral("База данных версии %1 обновлена до версии %2.").arg(before).arg(after)); _exitCode = (before & 0xFFFF) | ((after & 0xFFFF) << 16); closeDialog(); //при успешном завершении, то окно закрывается автоматически } else { _btnClose->show(); _btnSave->show(); _btnClose->setFocus(); message(QStringLiteral("Выполнение прервано в результате ошибки." "
База данных не изменилась.")); _exitCode = -1; } } //--------------------------------------------------------------------------- int MainWindow::executeAction() { ProgramSettings pset; processArguments(pset); logon(pset); _ai->start(); DatabaseUpdater updater; connect(&updater, SIGNAL(progress(int)), _progress, SLOT(setValue(int))); connect(&updater, SIGNAL(error(const QString&)), this, SLOT(error(const QString&))); connect(&updater, SIGNAL(message(const QString&)), this, SLOT(message(const QString&))); connect(&updater, SIGNAL(sqlError(const QString&, const QString&, const QString&)), this, SLOT(sqlError(const QString&, const QString&, const QString&))); connect(&updater, SIGNAL(logConnectionParameters(const QString&, const QString&, const QString&)), this, SLOT(logConnectionParameters(const QString&, const QString&, const QString&))); int rc = updater.run(pset); //проверяем число ошибок, т.к. при выполнении без транзакции run() всегда возвращает 0 if (_errorCount == 0) { int before = updater.revisionBefore(); int after = updater.revisionAfter(); if (before == after) message(QStringLiteral("База данных в актуальном состоянии.")); else if (before == 0) message(QStringLiteral("База данных создана и обновлена до версии %1.").arg(after)); else message(QStringLiteral("База данных версии %1 обновлена до версии %2.").arg(before).arg(after)); rc = (before & 0xFFFF) | ((after & 0xFFFF) << 16); } else { _btnClose->show(); _btnSave->show(); _btnClose->setFocus(); message(QStringLiteral("Выполнение прервано в результате ошибки." "
База данных не изменилась.")); rc = -1; } return rc; } void MainWindow::processArguments(ProgramSettings& a_pset) { QString USERNAME_PARAM("username"); QString PASSWORD_PARAM("password"); QString DATABASE_PARAM("database"); QString DROPDB_PARAM("dropdb"); QString HOST_PARAM("host"); QString PORT_PARAM("port"); QString FILE_PARAM("file"); //заполнить параметры default значениями QHash params; params.insert(USERNAME_PARAM, QString()); params.insert(PASSWORD_PARAM, QString()); params.insert(DATABASE_PARAM, QString()); params.insert(DROPDB_PARAM, QString()); params.insert(HOST_PARAM, QString()); params.insert(PORT_PARAM, QString()); params.insert(FILE_PARAM, QString()); //чтение аргументов QString key; - QString curArg; - QStringListIterator args(QApplication::arguments()); - args.next(); //пропускаем первый параметр - имя программы - while (args.hasNext()) + QStringList arguments = QApplication::arguments(); + auto end = arguments.cend(); + auto begin = arguments.cbegin(); + for (auto it = begin + 1; it != end; ++it) //пропускаем первый параметр - имя программы { - curArg = args.next(); + QString curArg = *it; if (curArg.startsWith("--")) { //обработка длинного параметра curArg.remove(0, 2); QMutableHashIterator option(params); while (option.hasNext()) { option.next(); key = option.key(); if (curArg.startsWith(key) && curArg.at(key.size()) == '=') option.setValue(curArg.right(curArg.size() - key.size() - 1)); } key.clear(); } else if (curArg.at(0) == QChar('-')) { //обработка короткого параметра key.clear(); switch (curArg.at(1).toLatin1()) { case 'U': key = USERNAME_PARAM; break; case 'd': key = DATABASE_PARAM; break; case 'f': key = FILE_PARAM; break; case 'h': key = HOST_PARAM; break; case 'p': key = PORT_PARAM; break; case 'W': key = PASSWORD_PARAM; break; case '0': params[ DROPDB_PARAM ] = '1'; break; } } else { //обработка одиночного параметра if (key.isEmpty()) key = FILE_PARAM; params[ key ] = curArg; key.clear(); } } //записать прочитанные значения в структуру ProgramSettings a_pset.username = params.value(USERNAME_PARAM); a_pset.password = params.value(PASSWORD_PARAM); a_pset.database = params.value(DATABASE_PARAM); a_pset.host = params.value(HOST_PARAM); a_pset.port = params.value(PORT_PARAM); a_pset.controlFile = params.value(FILE_PARAM); a_pset.dropdb = !params.value(DROPDB_PARAM).isEmpty(); } //--------------------------------------------------------------------------- void MainWindow::logon(ProgramSettings& a_pset) { if (!a_pset.host.isEmpty() || !a_pset.username.isEmpty()) return; #if defined(Q_OS_WIN) KLogon* logon = KLogon::create(); if (!logon) return; QString defaultDSN; HKEY k; DWORD cb = 0; QString valuename("System Directory"); if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\irs\\b03"), 0, KEY_QUERY_VALUE, &k) == ERROR_SUCCESS && RegQueryValueEx(k, LPCTSTR(valuename.constData()), 0, 0, 0, &cb) == ERROR_SUCCESS && cb > 0) { QString systemdir; systemdir.resize((cb / sizeof(QChar)) - 1); RegQueryValueEx(k, LPCTSTR(valuename.constData()), 0, 0, LPBYTE(systemdir.data()), &cb); RegCloseKey(k); KSettings systemini(systemdir + "/system.ini"); systemini.beginGroup("Database"); defaultDSN = systemini.value("DefaultDSN").toString(); } QString username = QString::fromLocal8Bit(qgetenv("USERNAME")); logon->setParent(winId()); logon->setUsername(username); if (!defaultDSN.isNull()) logon->setDSN(defaultDSN); if (!logon->execute()) emit message(QStringLiteral("Отказ от ввода имени и пароля пользователя")); else { a_pset.host = logon->host(); a_pset.username = logon->username(); a_pset.password = logon->password(); } delete logon; #else QString username = QString::fromLocal8Bit(getenv("USER")); #endif } //--------------------------------------------------------------------------- void MainWindow::logConnectionParameters(const QString& a_host, const QString& a_database, const QString& a_username) { message(QStringLiteral("Подключение к базе данных
" "  Сервер: %1
  База данных: %2
" "  Пользователь: %3") .arg(a_host, a_database, a_username)); } //--------------------------------------------------------------------------- void MainWindow::sqlError(const QString& a_dbError, const QString& a_commandDescription, const QString& a_command) { QString errorHtml; if (!a_commandDescription.isEmpty()) errorHtml.append(QStringLiteral("Операция:
")).append(a_commandDescription).append("
"); QString dbError = a_dbError; errorHtml.append(QStringLiteral("Сообщение об ошибке:
")).append(dbError.trimmed().replace('\n', "
")).append("
"); if (!a_command.isEmpty()) errorHtml.append(QStringLiteral("Текст команды:
")).append(a_command).append("
"); errorHtmlToLog(errorHtml); } //--------------------------------------------------------------------------- void MainWindow::error(const QString& a_error) { QString errorHtml = a_error; errorHtmlToLog(errorHtml.replace('<', "<").replace('>', ">").replace('\n', "
")); } //--------------------------------------------------------------------------- void MainWindow::errorHtmlToLog(const QString& a_errorHtml) { QString errorHtml = a_errorHtml; QTextCursor cursor = _protocol->textCursor(); cursor.insertHtml(QStringLiteral("Ошибка
")); cursor.insertHtml(errorHtml); cursor.insertHtml(QStringLiteral("
")); _protocol->moveCursor(QTextCursor::End); ++_errorCount; } //--------------------------------------------------------------------------- void MainWindow::message(const QString& a_message) { QString message = a_message; _protocol->textCursor().insertHtml(message.replace(QChar('\n'), "
").append("
")); _protocol->moveCursor(QTextCursor::End); } //--------------------------------------------------------------------------- void MainWindow::closeDialog() { QDialog::done(_exitCode); } //--------------------------------------------------------------------------- void MainWindow::saveProtocol() { QString filename = QFileDialog::getSaveFileName(this, QStringLiteral("Сохранить протокол в файл…"), QString(), QStringLiteral("Текстовые файлы (*.txt);;Все файлы (*.*)")); if (!filename.isEmpty()) { QFile outf(filename); if (outf.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QTextStream os(&outf); os.setGenerateByteOrderMark(true); os.setCodec("UTF-8"); os << _protocol->toPlainText(); } } } //!главное окно приложения diff --git a/src/updater.cpp b/src/updater.cpp --- a/src/updater.cpp +++ b/src/updater.cpp @@ -1,883 +1,875 @@ #include "updater.h" #include "psettings.h" #include #include #include #include #include #include #include DatabaseUpdater::DatabaseUpdater() : QObject() , _pset(0) , _revisionBefore(0) , _revisionAfter(0) { } 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; //прочитать из него идентификатор пакета и имя базы данных; //если файла нет, то и не надо QFileInfo fi(controlFile); QStringList files = QDir(QFileInfo(controlFile).path(), "*.pkginfo", QDir::NoSort, QDir::Files).entryList(); if (!files.isEmpty()) { QString pkgfile = QFileInfo(controlFile).completeBaseName() + ".pkginfo"; 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"); 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(); } //после загрузки конфигурации из файлов должно быть определено имя БД 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 = new Package(packageId, uri); _packages.append(package); //собираем информацию о скриптах данного пакета 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 URI_DATA_ATTR("udata"); 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 uriScript = attrs.value(URI_DATA_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)); DatabaseScript* script = new DatabaseScript(revision, transaction, file, uriScript, comment); package->addScript(script); } } //упорядочить скрипты по возрастанию номеров ревизий package->sortScripts(); return true; } //--------------------------------------------------------------------------- bool DatabaseUpdater::runScripts() { //определение пакета, скрипты которого будут выполняться - Package* package = 0; - QListIterator it(_packages); - while (it.hasNext()) - { - package = it.next(); - if (package->id() == _pset->packageId) - break; - else - package = 0; - } - - if (!package) + auto pp = std::find_if(_packages.cbegin(), _packages.cend(), + [this](Package* p){return p->id() == _pset->packageId;}); + if (pp == _packages.cend()) { emit error(QStringLiteral("Для пакета «%1» не задано ни одного " "сценария создания базы данных") .arg(_pset->packageId)); return false; } //проверка наличия файлов со скриптами на диске + Package* package = *pp; QStringList paths; QFileInfo fi(_pset->controlFile); paths << fi.canonicalPath() << fi.canonicalPath() + "/script" << QDir::currentPath(); DatabaseScript* script = 0; - QListIterator it2(package->scripts()); - while (it2.hasNext()) + auto packageScripts = package->scripts(); + auto scriptsEnd = packageScripts.cend(); + for (auto it = packageScripts.cbegin(); it != scriptsEnd; ++it) { - script = it2.next(); + script = *it; if (!script->findScript(paths)) { emit error(QStringLiteral("Не найден файл сценария создания " "базы данных %1") .arg(script->script())); return false; } if (package->uri() && !script->findUriScript(paths)) { emit error(QStringLiteral("Не найден файл условно-реальной " "информации %1") .arg(script->uriScript())); 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); int currentDatabaseRevision = 0; //БД нет или пустая //удаление БД если задано параметром при запуске программы bool dbExists = databaseExists(proc, _pset->database); if (dbExists && _pset->dropdb) { emit message(QStringLiteral("Удаление базы данных %1").arg(_pset->database)); if (!dropDatabase(proc, _pset)) return false; dbExists = false; } //создание БД если она не существует if (!dbExists) { emit message(QStringLiteral("Создание базы данных %1").arg(_pset->database)); if (!createDatabase(proc, _pset)) return false; } QString uriDatabase = _pset->database + "_u"; if (package->uri()) { //удаление учебной БД если задано параметром при запуске программы bool uriDbExists = databaseExists(proc, uriDatabase); if (uriDbExists && _pset->dropdb) { emit message(QStringLiteral("Удаление учебной базы данных %1").arg(uriDatabase)); if (!dropDatabase(proc, _pset, uriDatabase)) return false; uriDbExists = false; } //создание учебной БД если она не существует if (!uriDbExists) { emit message(QStringLiteral("Создание учебной базы данных %1").arg(uriDatabase)); if (!createDatabase(proc, _pset, uriDatabase)) return false; } } connect(&proc, SIGNAL(afterConnect(QString, QString, QString, QString, QString)), this, SLOT(afterConnect(QString, QString, QString, QString, QString))); //---- database ---- //подключение к созданной/существующей БД и получение списка таблиц if (!proc.connectdb(_pset->host, _pset->port, _pset->database, _pset->username, _pset->password)) return false; QStringList databaseTables = databaseTableList(proc); //подключение к учебной БД и получение списка таблиц QStringList uriDatabaseTables; if (package->uri()) { //---- database_u ---- if (!uriProc.connectdb(_pset->host, _pset->port, uriDatabase, _pset->username, _pset->password)) return false; uriDatabaseTables = databaseTableList(uriProc); } //считывание данных о версии БД из таблицы dm_version, //эта таблица существует только в основной БД, версия учебной должна совпадать bool legacyDatabase = false; proc.execSQL("select relnatts from pg_class " "where relname='dm_version' and relkind='r'"); PGresult* pgresult = proc.result().pgresult; //если пустой результат запроса, значит таблицы dm_version нет в БД // bool createDmVersion = data.size() == 0; 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 (!proc.execSQL("drop table dm_version")) return false; createDmVersion = true; } } if (createDmVersion) { //БД уже существовала, а таблицы dm_version в ней нет, или не та структура if (databaseTables != template1Tables) { legacyDatabase = true; currentDatabaseRevision = 1; } emit message(QStringLiteral("Создание таблицы изменений БД")); proc.execSQL("begin"); if (!proc.execute(_createDmVersionScript)) { emit error(QStringLiteral("Сбой при создании таблицы изменений БД")); return false; } if (legacyDatabase) { //запись информации о версии №1 в unversioned БД if (!proc.execSQL(QStringLiteral("insert into dm_version " "(revision,package_id,comment,gen_date) values " "(1,'%1','Изначальная версия','%2')") .arg(package->id(), curDate))) return false; } proc.execSQL("commit"); } else { proc.execSQL(QStringLiteral("select max(revision), count(*) from dm_version " "where package_id='%1'") .arg(package->id())); PGresult* pgresult = 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(); } } } //создание языка plpgsql if (!createLanguagePlpgsql(proc, uriProc, package->uri())) return false; _revisionAfter = _revisionBefore = currentDatabaseRevision; //выполнение необходимых скриптов connect(&proc, SIGNAL(progress(int)), this, SIGNAL(progress(int))); connect(&uriProc, SIGNAL(progress(int)), this, SIGNAL(progress(int))); - QListIterator iter(package->scripts()); - while (iter.hasNext()) + scriptsEnd = packageScripts.cend(); + for (auto it = packageScripts.cbegin(); it != scriptsEnd; ++it) { - script = iter.next(); + script = *it; int revision = script->revision(); bool transaction = script->transaction(); //если была найдена изначальная БД, то всё уже сделано для 1-й версии if ((revision == 1) && legacyDatabase) continue; //пропускаем уже установленные версии БД if (revision <= currentDatabaseRevision) continue; if (revision == 1) emit message(QStringLiteral("Создание базы данных версии %1").arg(revision)); else emit message(QStringLiteral("Обновление базы данных до версии %1").arg(revision)); //подготовка скрипта к выполнению QList mainScript; QList globalsScript; prepareScripts(script->script(), mainScript, globalsScript); //если в скрипте были команды создания глобальных объектов, то выполнить //их в отдельной транзакции if (globalsScript.size() > 0) { proc.execSQL("begin"); if (!proc.execute(globalsScript)) return false; proc.execSQL("commit"); } //выполнение скрипта if (transaction) proc.execSQL("begin"); if (!proc.execute(mainScript)) return false; //выполнение скриптов в учебной БД, если задано её создание if (package->uri()) { if (transaction) uriProc.execSQL("begin"); if (revision == 1) emit message(QStringLiteral("Создание учебной базы данных версии %1").arg(revision)); else emit message(QStringLiteral("Обновление учебной базы данных до версии %1").arg(revision)); if (!uriProc.execute(mainScript)) { if (transaction) proc.execSQL("rollback"); return false; } if (!script->uriScript().isEmpty()) { emit message(QStringLiteral("Загрузка условной информации в учебную базу данных")); if (!uriProc.execute(script->uriScript())) { if (transaction) proc.execSQL("rollback"); return false; } } if (!uriProc.execute(_setSearchPathScript)) return false; clearMaclabels(uriProc); if (transaction) uriProc.execSQL("commit"); } //сброс параметра search_path, он мог быть изменён при выполнении скрипта if (!proc.execute(_setSearchPathScript)) return false; //запись информации о версии в БД if (!proc.execSQL(QStringLiteral("insert into dm_version " "(revision,package_id,comment,gen_date) values " "(%1,'%2','%3','%4')") .arg(revision) .arg(package->id()) .arg(script->comment()) .arg(curDate))) return false; //сброс мандатных меток, если они есть в БД clearMaclabels(proc); if (transaction) proc.execSQL("commit"); _revisionAfter = revision; } 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_dbname.isEmpty() && a_pset->database.isEmpty()) return false; QString dbname = (a_dbname.isEmpty() ? a_pset->database : a_dbname); //если уже подключены к указанной БД, то отключение if (a_proc.database() == 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; } //если БД уже существует, то создавать не надо if (databaseExists(a_proc, dbname)) return true; return a_proc.execSQL(QString("create database \"%1\"").arg(dbname)) && a_proc.commandStatus() == QString("CREATE DATABASE"); } //--------------------------------------------------------------------------- bool DatabaseUpdater::dropDatabase(SqlProcessor& a_proc, ProgramSettings* a_pset, const QString& a_dbname) { if (a_pset->database.isEmpty()) return false; QString dbname = (a_dbname.isEmpty() ? a_pset->database : a_dbname); //если уже подключены к указанной БД, то отключение if (a_proc.database() == 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; } //если БД не существует, то удалять не надо if (!databaseExists(a_proc, dbname)) return true; return a_proc.execSQL(QString("drop database \"%1\"").arg(dbname)) && a_proc.commandStatus() == QString("DROP DATABASE"); } //--------------------------------------------------------------------------- // Получение списка таблиц в БД //--------------------------------------------------------------------------- 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; } //--------------------------------------------------------------------------- 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; } _setSearchPathScript = proc.parse(resFile); return true; } //--------------------------------------------------------------------------- // Подготавливает скрипт, считанный из файла, к выполнению в БД. // Команды анализируются и разделяются на два скрипта — основной и глобальных // объектов. Обработка заключается в следующем: // 1. Команды создания глобальных объектов // CREATE GROUP // CREATE USER // CREATE ROLE // CREATE TABLESPACE // заменяются вызовами функций-обёрток для их безопасного выполнения. // Эти вызовы вставляются в скрипт глобальных объектов. В этот же скрипт // добавляются команды создания функций-обёрток и их удаления. // 2. Команды установки кодировки клиента // SET CLIENT_ENCODING // SET NAMES // \encoding // добавляются в оба скрипта. // 3. Команда подключения языка plpgsql // CREATE [ TRUSTED ] [ PROCEDURAL ] LANGUAGE plpgsql // игнорируется и не попадает ни в один скрипт. Создание языка plpgsql // производится отдельно, перед началом выполнения скриптов. //--------------------------------------------------------------------------- void DatabaseUpdater::prepareScripts(const QString& a_filename, QList& oa_mainScript, QList& oa_globalsScript) { //пропустим файл через парсер, на выходе список команд bool helperInjected = false; SqlProcessor proc; QList result = proc.parse(a_filename); //обход списка и анализ команд - QListIterator it(result); - while (it.hasNext()) + auto end = result.cend(); + for (auto it = result.cbegin(); it != end; ++it) { - const QByteArray line = it.next(); + const 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(); if (command1 == "\\encoding") { oa_mainScript.append(line); oa_globalsScript.append(line); continue; } //переход на начало второго слова 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 == "set") { oa_mainScript.append(line); if (command2 == "names" || command2.startsWith("client_encoding")) oa_globalsScript.append(line); } else if (command1 == "create") { if (command2 == "group" || command2 == "user" || command2 == "role" || command2 == "tablespace") { //добавляем в выходной скрипт globals вызов безопасной функции-обёртки if (!helperInjected) { //но сначала вставляем команды создания функций-обёрток helperInjected = true; - QListIterator helperIter(_createHelperFunctionsScript); - while (helperIter.hasNext()) - oa_globalsScript.append(helperIter.next()); + for (const auto& helperScript: _createHelperFunctionsScript) + oa_globalsScript.append(helperScript); } //переход на начало имени объекта 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 _rct_safe_create_"); safeCall.append(command2).append("('").append(name).append("','").append(parameters).append("');"); oa_globalsScript.append(safeCall); } else if (command2 == "trusted" || command2 == "procedural" || command2 == "language") { //create language не нужен continue; } else { //create неглобального объекта, в основной скрипт oa_mainScript.append(line); } } else { //все остальные команды в основной скрипт oa_mainScript.append(line); } } //цикл по командам if (helperInjected) oa_globalsScript.append(_dropHelperFunctionsScript); } //--------------------------------------------------------------------------- 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); } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- bool DatabaseScript::findScript(const QStringList& a_paths) { _script.replace(QChar('\\'), QChar('/')); foreach (QString path, a_paths) { QDir dir(path); if (dir.exists(_script)) { _script = dir.absoluteFilePath(_script); return true; } } return false; } //--------------------------------------------------------------------------- bool DatabaseScript::findUriScript(const QStringList& a_paths) { _uriScript.replace(QChar('\\'), QChar('/')); if (_uriScript.isEmpty()) return true; foreach (QString path, a_paths) { QDir dir(path); if (dir.exists(_uriScript)) { _uriScript = dir.absoluteFilePath(_uriScript); return true; } } return false; }