Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F213318
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
51 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rRCT Программа создания/обновления структуры БД
Event Timeline
Log In to Comment