Page MenuHomePhabricator

No OneTemporary

diff --git a/src/updater.cpp b/src/updater.cpp
--- a/src/updater.cpp
+++ b/src/updater.cpp
@@ -1,836 +1,796 @@
#include "updater.h"
#include "psettings.h"
#include <QDate>
#include <QDir>
#include <QTextCodec>
#include <QXmlStreamReader>
#include <ksettings.h>
#include <libpq-fe.h>
#include <sqlproc.h>
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;
//прочитать из него идентификатор пакета и имя базы данных;
//если файла нет, то и не надо
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);
+ 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));
- DatabaseScript* script = new DatabaseScript(revision, transaction,
- file, comment);
- package->addScript(script);
+ package.addScript(DatabaseScript(file, transaction, revision, comment));
}
}
//упорядочить скрипты по возрастанию номеров ревизий
- package->sortScripts();
+ package.sortScripts();
+ _packages.append(package);
return true;
}
//---------------------------------------------------------------------------
bool DatabaseUpdater::runScripts()
{
//определение пакета, скрипты которого будут выполняться
auto pp = std::find_if(_packages.cbegin(), _packages.cend(),
- [this](Package* p){return p->id() == _pset->packageId;});
+ [this](const Package& p){return p.id() == _pset->packageId;});
if (pp == _packages.cend())
{
emit error(QStringLiteral("Для пакета «%1» не задано ни одного "
"сценария создания базы данных")
.arg(_pset->packageId));
return false;
}
//проверка наличия файлов со скриптами на диске
- Package* package = *pp;
+ const Package& package = *pp;
QStringList paths;
QFileInfo fi(_pset->controlFile);
paths << fi.canonicalPath() << fi.canonicalPath() + "/script" << QDir::currentPath();
- DatabaseScript* script = 0;
- auto packageScripts = package->scripts();
- auto scriptsEnd = packageScripts.cend();
- for (auto it = packageScripts.cbegin(); it != scriptsEnd; ++it)
+ const auto& packageScripts = package.scripts();
+ for (auto it = packageScripts.begin(); it != packageScripts.end(); ++it)
{
- script = *it;
- if (!script->findScript(paths))
+ const DatabaseScript& script = *it;
+ if (!script.findScript(paths))
{
emit error(QStringLiteral("Не найден файл сценария создания "
"базы данных %1")
- .arg(script->script()));
+ .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);
- 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;
- }
+ //создание БД
+ if (!createDatabase(proc, _pset, _pset->database))
+ return false;
+ //создание учебной БД
QString uriDatabase = _pset->database + "_u";
- if (package->uri())
+ if (package.wantUri())
{
- //удаление учебной БД если задано параметром при запуске программы
- 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;
- }
+ 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)));
- //---- 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())
+ if (package.wantUri())
{
- //---- database_u ----
- if (!uriProc.connectdb(_pset->host, _pset->port, uriDatabase,
- _pset->username, _pset->password))
+ 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;
- }
+ StatusResult statusResult = databaseStatus(proc, package.id(), template1Tables);
+ if (statusResult.failed())
+ 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
+ int databaseRevision = statusResult.databaseRevision();
+ int uriDatabaseRevision = 0;
+ if (package.wantUri())
{
- 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();
- }
- }
+ UriGuard ug(this);
+ statusResult = databaseStatus(uriProc, package.id(), template1Tables);
+ if (statusResult.failed())
+ return false;
+ uriDatabaseRevision = statusResult.databaseRevision();
}
//создание языка plpgsql
- if (!createLanguagePlpgsql(proc, uriProc, package->uri()))
+ if (!createLanguagePlpgsql(proc, uriProc, package.wantUri()))
return false;
- _revisionAfter = _revisionBefore = currentDatabaseRevision;
+ _revisionAfter = _revisionBefore = databaseRevision;
//выполнение необходимых скриптов
connect(&proc, SIGNAL(progress(int)), this, SIGNAL(progress(int)));
connect(&uriProc, SIGNAL(progress(int)), this, SIGNAL(progress(int)));
- scriptsEnd = packageScripts.cend();
- for (auto it = packageScripts.cbegin(); it != scriptsEnd; ++it)
+ const auto& end = packageScripts.cend();
+ for (auto it = packageScripts.cbegin(); it != end; ++it)
{
- 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));
+ const DatabaseScript& script = *it;
+ int revision = script.revision();
//подготовка скрипта к выполнению
- QList<QByteArray> mainScript;
- QList<QByteArray> globalsScript;
- prepareScripts(script->script(), mainScript, globalsScript);
+ QList<QByteArray> preparedScript = prepareScript(script.script());
- //если в скрипте были команды создания глобальных объектов, то выполнить
- //их в отдельной транзакции
- if (globalsScript.size() > 0)
+ //выполнение скриптов в БД, пропуская уже установленные ревизии
+ if (revision > databaseRevision)
{
- proc.execSQL("begin");
- if (!proc.execute(globalsScript))
- return false;
- proc.execSQL("commit");
+ if ( !runScript(proc, preparedScript, script, package.id()) )
+ return false;
+ //сброс мандатных меток, если они есть в БД
+ clearMaclabels(proc);
}
- //выполнение скрипта
- if (transaction)
- proc.execSQL("begin");
-
- if (!proc.execute(mainScript))
- return false;
-
//выполнение скриптов в учебной БД, если задано её создание
- if (package->uri())
+ if (package.wantUri() && revision > uriDatabaseRevision)
{
- 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 (!uriProc.execute(_setSearchPathScript))
- return false;
+ UriGuard ug(this);
+ if ( !runScript(uriProc, preparedScript, script, package.id()) )
+ 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::runScript(SqlProcessor& a_proc,
+ const QList<QByteArray>& 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_dbname.isEmpty() && a_pset->database.isEmpty())
- return false;
-
- QString dbname = (a_dbname.isEmpty() ? a_pset->database : a_dbname);
-
//если уже подключены к указанной БД, то отключение
- if (a_proc.database() == 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;
}
- //если БД уже существует, то создавать не надо
- if (databaseExists(a_proc, dbname))
- return true;
+ //удаление БД если задано параметром при запуске программы
+ bool dbExists = databaseExists(a_proc, a_dbname);
+ if (dbExists && _pset->dropdb)
+ {
+ emit message(messageString(DROP_DB).arg(a_dbname));
- return a_proc.execSQL(QString("create database \"%1\"").arg(dbname))
- && a_proc.commandStatus() == QString("CREATE DATABASE");
-}
+ bool dropped = a_proc.execSQL(QString("drop database \"%1\"").arg(a_dbname))
+ && a_proc.commandStatus() == QString("DROP DATABASE");
+
+ if (!dropped)
+ return false;
-//---------------------------------------------------------------------------
-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);
+ dbExists = false;
+ }
- //если уже подключены к указанной БД, то отключение
- if (a_proc.database() == dbname)
- a_proc.disconnectdb();
+ //создание БД если она не существует
+ if (!dbExists)
+ {
+ emit message(messageString(CREATE_DB).arg(a_dbname));
- //если не подключены, то подключение к template1
- if (a_proc.database().isNull())
- {
- if (!a_proc.connectdb(a_pset->host, a_pset->port, "template1",
- a_pset->username, a_pset->password))
+ bool created = a_proc.execSQL(QString("create database \"%1\"").arg(a_dbname))
+ && a_proc.commandStatus() == QString("CREATE DATABASE");
+
+ if (!created)
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");
+ 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;
}
- _setSearchPathScript = proc.parse(resFile);
+ _resetSearchPathScript = proc.parse(resFile);
return true;
}
//---------------------------------------------------------------------------
// Подготавливает скрипт, считанный из файла, к выполнению в БД.
-// Команды анализируются и разделяются на два скрипта — основной и глобальных
-// объектов. Обработка заключается в следующем:
+// Подготовка заключается в следующем:
// 1. Команды создания глобальных объектов
// CREATE GROUP
// CREATE USER
// CREATE ROLE
// CREATE TABLESPACE
// заменяются вызовами функций-обёрток для их безопасного выполнения.
-// Эти вызовы вставляются в скрипт глобальных объектов. В этот же скрипт
-// добавляются команды создания функций-обёрток и их удаления.
-// 2. Команды установки кодировки клиента
-// SET CLIENT_ENCODING
-// SET NAMES
-// \encoding
-// добавляются в оба скрипта.
-// 3. Команда подключения языка plpgsql
+// В скрипт добавляются команды создания функций-обёрток и их удаления.
+// 2. Команда подключения языка plpgsql
// CREATE [ TRUSTED ] [ PROCEDURAL ] LANGUAGE plpgsql
-// игнорируется и не попадает ни в один скрипт. Создание языка plpgsql
-// производится отдельно, перед началом выполнения скриптов.
+// удаляется из скрипта.
+// Создание языка plpgsql производится отдельно, перед началом выполнения скриптов.
//---------------------------------------------------------------------------
-void DatabaseUpdater::prepareScripts(const QString& a_filename,
- QList<QByteArray>& oa_mainScript,
- QList<QByteArray>& oa_globalsScript)
+QList<QByteArray> DatabaseUpdater::prepareScript(const QString& a_filename)
{
//пропустим файл через парсер, на выходе список команд
- bool helperInjected = false;
SqlProcessor proc;
QList<QByteArray> result = proc.parse(a_filename);
//обход списка и анализ команд
- auto end = result.cend();
- for (auto it = result.cbegin(); it != end; ++it)
+ bool helperRequired = false;
+ auto end = result.end();
+ for (auto it = result.begin(); it != end; ++it)
{
- const QByteArray& line = *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();
- 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 (command1 == "create")
{
if (command2 == "group" || command2 == "user" || command2 == "role" || command2 == "tablespace")
- { //добавляем в выходной скрипт globals вызов безопасной функции-обёртки
- if (!helperInjected)
- { //но сначала вставляем команды создания функций-обёрток
- helperInjected = true;
- for (const auto& helperScript: _createHelperFunctionsScript)
- oa_globalsScript.append(helperScript);
- }
+ { //добавляем в выходной скрипт вызов безопасной функции-обёртки
+ 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 _rct_safe_create_");
safeCall.append(command2).append("('").append(name).append("','").append(parameters).append("');");
- oa_globalsScript.append(safeCall);
+ //заменяем строку в выходном массиве
+ line = safeCall;
}
else if (command2 == "trusted" || command2 == "procedural" || command2 == "language")
{ //create language не нужен
- continue;
+ line.clear();
}
- else
- { //create неглобального объекта, в основной скрипт
- oa_mainScript.append(line);
- }
- }
- else
- { //все остальные команды в основной скрипт
- oa_mainScript.append(line);
}
} //цикл по командам
- if (helperInjected)
- oa_globalsScript.append(_dropHelperFunctionsScript);
+ 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<MessageId, QString> 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)
+//---------------------------------------------------------------------------
+bool DatabaseScript::findScript(const QStringList& a_paths) const
{
- _script.replace(QChar('\\'), QChar('/'));
+ _scriptFile.replace(QChar('\\'), QChar('/'));
foreach (QString path, a_paths)
{
QDir dir(path);
- if (dir.exists(_script))
+ if (dir.exists(_scriptFile))
{
- _script = dir.absoluteFilePath(_script);
+ _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(); });
+}
diff --git a/src/updater.h b/src/updater.h
--- a/src/updater.h
+++ b/src/updater.h
@@ -1,123 +1,150 @@
#ifndef UPDATER_H
#define UPDATER_H
#include <QList>
#include <QObject>
+#include <QVector>
class DatabaseScript
{
- friend class DatabaseUpdater;
-
public:
- DatabaseScript(int a_revision, bool a_transaction, const QString& a_script,
+ DatabaseScript(const QString& a_scriptFile, bool a_transaction, int a_revision,
const QString& a_comment)
: _revision(a_revision)
, _transaction(a_transaction)
- , _script(a_script)
+ , _scriptFile(a_scriptFile)
, _comment(a_comment)
{
}
int revision() const { return _revision; }
bool transaction() const { return _transaction; }
- QString script() const { return _script; }
+ QString script() const { return _scriptFile; }
QString comment() const { return _comment; }
- bool findScript(const QStringList& a_paths);
+ bool findScript(const QStringList& a_paths) const;
private:
int _revision;
bool _transaction;
- QString _script;
+ mutable QString _scriptFile;
QString _comment;
};
class Package
{
public:
- Package(const QString& a_id, bool a_uri = true)
+ Package(const QString& a_id, bool a_wantUri = true)
: _id(a_id)
- , _uri(a_uri)
+ , _wantUri(a_wantUri)
{
}
- void addScript(DatabaseScript* a_script) { _scripts.append(a_script); }
+ void addScript(const DatabaseScript& a_script) { _scripts.append(a_script); }
+ void sortScripts();
- void sortScripts() { qSort(_scripts.begin(), _scripts.end(), scriptRevisionLessThan); }
- static bool scriptRevisionLessThan(DatabaseScript* a_script1,
- DatabaseScript* a_script2) { return a_script1->revision() < a_script2->revision(); }
-
- bool uri() const { return _uri; }
+ bool wantUri() const { return _wantUri; }
QString id() const { return _id; }
- QList<DatabaseScript*> scripts() const { return _scripts; }
+ QList<DatabaseScript> scripts() const { return _scripts; }
private:
- QList<DatabaseScript*> _scripts;
+ QList<DatabaseScript> _scripts;
QString _id;
- bool _uri;
+ bool _wantUri;
};
class SqlProcessor;
class ProgramSettings;
class QByteArray;
class QByteArrayMatcher;
class QXmlStreamReader;
class DatabaseUpdater : public QObject
{
Q_OBJECT
+ friend class UriGuard;
public:
DatabaseUpdater();
~DatabaseUpdater() override;
int run(ProgramSettings& a_pset);
int revisionBefore() const { return _revisionBefore; }
int revisionAfter() const { return _revisionAfter; }
signals:
void error(const QString& a_message);
void sqlError(const QString& a_dbError, const QString& a_commandDescription,
const QString& a_command);
void logConnectionParameters(const QString& a_host, const QString& a_database,
const QString& a_username);
void progress(int);
void message(const QString& a_message);
public slots:
void afterConnect(const QString& a_host, const QString& a_port, const QString& a_database,
const QString& a_username, const QString& a_password);
private:
enum
{
DM_VERSION_FIELDS_COUNT = 5
};
+ enum MessageId
+ {
+ CREATE_DB, DROP_DB, CREATE_DB_VERSION, UPDATE_DB_VERSION,
+ CREATE_VERSION_TABLE, ERROR_VERSION_TABLE
+ };
+
+ class StatusResult
+ {
+ public:
+ StatusResult(bool a_ok = false, int a_databaseRevision = 0)
+ : _ok(a_ok), _databaseRevision(a_databaseRevision) {}
+ bool succeeded() const { return _ok; }
+ bool failed() const { return !_ok; }
+ int databaseRevision() const { return _databaseRevision; }
+ private:
+ bool _ok;
+ int _databaseRevision;
+ };
+
ProgramSettings* _pset;
QString _logText;
- QList<Package*> _packages;
+ QList<Package> _packages;
QList<QByteArray> _createDmVersionScript;
QList<QByteArray> _createHelperFunctionsScript;
QList<QByteArray> _dropHelperFunctionsScript;
- QList<QByteArray> _setSearchPathScript;
+ QList<QByteArray> _resetSearchPathScript;
int _revisionBefore;
int _revisionAfter;
+ bool _processingUri;
QStringList databaseTableList(SqlProcessor& a_proc);
+ StatusResult databaseStatus(SqlProcessor& a_proc, const QString& a_packageId, const QStringList& a_template1Tables);
bool checkArguments();
bool readConfig();
bool readPackage(QXmlStreamReader& a_xml);
bool loadScriptsFromResource();
bool runScripts();
+ bool runScript(SqlProcessor& a_proc, const QList<QByteArray>& a_script,
+ const DatabaseScript& a_databaseScript, const QString& a_packageId);
bool databaseExists(SqlProcessor& a_proc, const QString& a_dbname);
- bool createDatabase(SqlProcessor& a_proc, ProgramSettings* a_pset,
- const QString& a_dbname = QString());
- bool dropDatabase(SqlProcessor& a_proc, ProgramSettings* a_pset,
- const QString& a_dbname = QString());
+ bool createDatabase(SqlProcessor& a_proc, ProgramSettings* a_pset, const QString& a_dbname);
bool createLanguagePlpgsql(SqlProcessor& a_proc, SqlProcessor& a_uriProc,
bool a_uri);
- void prepareScripts(const QString& a_filename, QList<QByteArray>& oa_mainScript,
- QList<QByteArray>& oa_globalsScript);
+ QList<QByteArray> prepareScript(const QString& a_filename);
void clearMaclabels(SqlProcessor& a_proc);
+ QString messageString( MessageId a_message );
+};
+
+class UriGuard
+{
+public:
+ UriGuard(DatabaseUpdater* a_updater)
+ : _updater(a_updater) { _updater->_processingUri = true; }
+ ~UriGuard() { _updater->_processingUri = false; }
+private:
+ DatabaseUpdater* _updater;
};
//!главный класс приложения
#endif //UPDATER_H

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 29, 5:38 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
139595

Event Timeline