Page MenuHomePhabricator

No OneTemporary

diff --git a/restruct.pro b/restruct.pro
--- a/restruct.pro
+++ b/restruct.pro
@@ -1,48 +1,48 @@
######################################################################
# Project started 11.01.2008 at 13:10
######################################################################
-QT += core gui xml sql
+QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets concurrent
TEMPLATE = app
TARGET = restruct
DESTDIR = .
BASEDIR = $$dirname(PWD)
INCLUDEPATH += . ./src $$BASEDIR/include
PRECOMPILED_HEADER = ./src/stable.h
DEFINES += KTOOLS_STATIC
RESOURCES += resource/project.qrc
SOURCES += src/restruct.cpp \
src/mainwindow.cpp \
src/updater.cpp \
src/sqlerrordlg.cpp
HEADERS += src/mainwindow.h \
src/updater.h \
src/psettings.h \
src/sqlerrordlg.h
LIBS += -L$$BASEDIR/lib -lktools -lpq
win32 {
RC_FILE = resource/project.rc
LIBS += -luuid -loleaut32 -lgdi32 -luser32
}
win32-msvc* {
CONFIG(debug, debug|release) {
QMAKE_LFLAGS *= /NODEFAULTLIB:msvcrt
LIBS = $$replace(LIBS,-lktools,-lktoolsd)
}
DEFINES += _CRT_SECURE_NO_WARNINGS
}
#unix {
# SOURCES += src/
# HEADERS += src/
#}
diff --git a/src/stable.h b/src/stable.h
--- a/src/stable.h
+++ b/src/stable.h
@@ -1,26 +1,25 @@
#include <QApplication>
#include <QCheckBox>
#include <QCloseEvent>
#include <QtConcurrentRun>
#include <QDesktopWidget>
#include <QDialog>
-#include <QDomDocument>
-#include <QDomElement>
#include <QFile>
#include <QFileDialog>
#include <QGroupBox>
#include <QHash>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QMessageBox>
#include <QProgressBar>
#include <QPushButton>
#include <QShortcut>
#include <QtDebug>
#include <QTemporaryFile>
#include <QTextCodec>
#include <QTextEdit>
#include <QTime>
#include <QTranslator>
#include <QVBoxLayout>
+#include <QXmlStreamReader>
diff --git a/src/updater.cpp b/src/updater.cpp
--- a/src/updater.cpp
+++ b/src/updater.cpp
@@ -1,882 +1,875 @@
#include "stable.h"
#include "updater.h"
#include "psettings.h"
-#include <QByteArrayMatcher>
#include <ksds.h>
#include <ksettings.h>
#include <sqlproc.h>
+#include <QXmlStreamReader>
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( QString::fromUtf8( "Не задан файл конфигурации." ) );
return false;
}
//если не задано полное имя файла конфигурации, то искать его в текущем каталоге
QString filename = _pset->controlFile;
if ( !QDir::isAbsolutePath( filename ) )
filename = QString( "%1/%2" ).arg( QDir::currentPath() ).arg( _pset->controlFile );
//проверить наличие файла
if ( !QFile::exists( filename ) )
{
emit error( QString::fromUtf8( "Не найден файл конфигурации\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( QString::fromUtf8( "Сбой при загрузке файла конфигурации:\n"
"Невозможно открыть для чтения файл %1" ).arg( QDir::toNativeSeparators(
_pset->controlFile ) ) );
return false;
}
- //парсим входной файл в DOM
- int domErrorLine;
- int domErrorColumn;
- QString domErrorMsg;
- QDomDocument dom;
- if ( !dom.setContent( &controlFile, false, &domErrorMsg, &domErrorLine, &domErrorColumn ) )
- {
- emit error( QString::fromUtf8( "Сбой при загрузке файла конфигурации:\n"
- "Ошибка: %1, строка %2, позиция %3").
- arg( domErrorMsg ).arg( domErrorLine ).arg( domErrorColumn ) );
- controlFile.close();
- return false;
- }
-
- controlFile.close();
+ //парсим входной файл
+ QXmlStreamReader xml;
+ xml.setDevice( &controlFile );
emit message( QString::fromUtf8( "Загрузка конфигурации из файла\n%1" ).
arg( QDir::toNativeSeparators( _pset->controlFile ) ) );
- //идём по дереву узлов документа
- QDomElement document = dom.documentElement();
- QDomNode node = document.firstChild();
- while ( !node.isNull() )
+ //идём по элементам документа
+ while ( !xml.atEnd() )
{
- if ( node.isElement() && (node.nodeName() == "package") )
- {
- if ( !readPackage( node ) )
+ xml.readNext();
+ if ( xml.isStartElement() && xml.name() == "package" )
+ if ( !readPackage( xml ) )
return false;
- }
- node = node.nextSibling();
}
- //найти файл с расширением pkginfo и именем как у входного файла, если такого нет,
+ if ( xml.hasError() )
+ {
+ emit error( QString::fromUtf8( "Сбой при загрузке файла конфигурации:\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( QString::fromUtf8( "Загрузка конфигурации из файла\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( QString::fromUtf8( "Не задано имя базы данных" ) );
return false;
}
return true;
}
//---------------------------------------------------------------------------
-bool DatabaseUpdater::readPackage( const QDomNode& a_node )
+bool DatabaseUpdater::readPackage( QXmlStreamReader& a_xml )
{
- QDomElement elt = a_node.toElement();
- QString packageId = elt.attribute( "id" );
- QString uriAttr = elt.attribute( "uri" );
+ 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 );
//собираем информацию о скриптах данного пакета
- QString SCRIPT_NODE( "script" );
- QString REVISION_ATTR( "revision" );
- QString TRANSACTION_ATTR( "transaction" );
- QString COMMENT_ATTR( "comment" );
- QString SCRIPT_ATTR( "file" );
- QString URI_DATA_ATTR( "udata" );
- QString NO_REQUIRED_ATTR = QString::fromUtf8( "Неправильное содержание файла конфигурации:\n"
+ 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 = QString::fromUtf8( "Неправильное содержание файла конфигурации:\n"
"Не указан обязательный атрибут «%1» пакета «%2»" );
- QDomNode node = elt.firstChild();
- while ( !node.isNull() )
+ while ( !a_xml.atEnd() )
{
- if ( node.isElement() && (node.nodeName() == SCRIPT_NODE) )
+ a_xml.readNext();
+ if ( a_xml.isStartElement() && a_xml.name() == SCRIPT_NODE )
{
- elt = node.toElement();
+ const QXmlStreamAttributes& attrs = a_xml.attributes();
//проверка наличия обязательных атрибутов
- if ( !elt.hasAttribute( REVISION_ATTR ) )
+ if ( !attrs.hasAttribute( REVISION_ATTR ) )
{
emit error( NO_REQUIRED_ATTR.arg( REVISION_ATTR ).arg( packageId ) );
return false;
}
- if ( !elt.hasAttribute( SCRIPT_ATTR ) )
+ if ( !attrs.hasAttribute( SCRIPT_ATTR ) )
{
emit error( NO_REQUIRED_ATTR.arg( SCRIPT_ATTR ).arg( packageId ) );
return false;
}
//проверка правильности указания атрибутов
bool ok;
- int revision = elt.attribute( REVISION_ATTR ).toInt( &ok );
+ int revision = attrs.value( REVISION_ATTR ).toString().toInt( &ok );
if ( !ok )
{
emit error( QString::fromUtf8(
"Атрибут «revision» пакета «%1» должен быть числом" ).
arg( packageId ) );
return false;
}
//заносим информацию о скрипте в список скриптов пакета
- QString file = elt.attribute( SCRIPT_ATTR );
- QString uriScript = elt.attribute( URI_DATA_ATTR );
- QString comment = elt.attribute( COMMENT_ATTR );
- QString strans = elt.attribute( TRANSACTION_ATTR );
+ 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 );
}
- node = node.nextSibling();
}
//упорядочить скрипты по возрастанию номеров ревизий
package->sortScripts();
return true;
}
//---------------------------------------------------------------------------
bool DatabaseUpdater::runScripts()
{
//определение пакета, скрипты которого будут выполняться
Package* package = 0;
QListIterator<Package*> it( _packages );
while ( it.hasNext() )
{
package = it.next();
if ( package->id() == _pset->packageId )
break;
else
package = 0;
}
if ( !package )
{
emit error( QString::fromUtf8( "Для пакета «%1» не задано ни одного "
"сценария создания базы данных" ).arg( _pset->packageId ) );
return false;
}
//проверка наличия файлов со скриптами на диске
QStringList paths;
QFileInfo fi( _pset->controlFile );
paths << fi.canonicalPath() << fi.canonicalPath() + "/script" << QDir::currentPath();
DatabaseScript* script = 0;
QListIterator<DatabaseScript*> it2( package->scripts() );
while ( it2.hasNext() )
{
script = it2.next();
if ( !script->findScript( paths ) )
{
emit error( QString::fromUtf8( "Не найден файл сценария создания "
"базы данных %1" ).arg( script->script() ) );
return false;
}
if ( package->uri() && !script->findUriScript( paths ) )
{
emit error( QString::fromUtf8( "Не найден файл условно-реальной "
"информации %1" ).arg( script->uriScript() ) );
return false;
}
}
QString curDate = QDate::currentDate().toString( Qt::ISODate );
SqlProcessor proc;
SqlProcessor uriProc;
emit message( QString::fromUtf8( "Подключение к серверу баз данных" ) );
//пока идут служебные подключения к БД и запросы, подключаем только сигнал 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&)) );
qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
//---- 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( QString::fromUtf8( "Удаление базы данных <b>%1</b>" ).arg( _pset->database ) );
qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
if ( !dropDatabase( proc, _pset ) )
return false;
dbExists = false;
}
//создание БД если она не существует
if ( !dbExists )
{
emit message( QString::fromUtf8( "Создание базы данных <b>%1</b>" ).arg( _pset->database ) );
qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
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( QString::fromUtf8( "Удаление учебной базы данных <b>%1</b>" ).arg( uriDatabase ) );
qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
if ( !dropDatabase( proc, _pset, uriDatabase ) )
return false;
uriDbExists = false;
}
//создание учебной БД если она не существует
if ( !uriDbExists )
{
emit message( QString::fromUtf8( "Создание учебной базы данных <b>%1</b>" ).arg( uriDatabase ) );
qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
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'" );
KSimpleDataSet data = proc.result();
//если пустой результат запроса, значит таблицы dm_version нет в БД
bool createDmVersion = data.size() == 0;
//если таблица есть, то сравнить число её атрибутов с эталоном
data.first();
if ( data.isValid() && data.value( 0 ).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( QString::fromUtf8( "Создание таблицы изменений БД" ) );
proc.execSQL( "begin" );
if ( !proc.execute( _createDmVersionScript ) )
{
emit error( QString::fromUtf8( "Сбой при создании таблицы изменений БД" ) );
return false;
}
if ( legacyDatabase )
{ //запись информации о версии №1 в unversioned БД
if ( !proc.execSQL( QString::fromUtf8( "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( QString("select max(revision), count(*) from dm_version "
"where package_id='%1'" ).arg( package->id() ) );
data = proc.result();
data.first();
if ( data.isValid() && (data.value( 1 ) > 0) )
currentDatabaseRevision = data.value( 0 ).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<DatabaseScript*> iter( package->scripts() );
while ( iter.hasNext() )
{
script = iter.next();
int revision = script->revision();
bool transaction = script->transaction();
//если была найдена изначальная БД, то всё уже сделано для 1-й версии
if ( (revision == 1) && legacyDatabase )
continue;
//пропускаем уже установленные версии БД
if ( revision <= currentDatabaseRevision )
continue;
if ( revision == 1 )
emit message( QString::fromUtf8( "Создание базы данных версии %1" ).arg( revision ) );
else
emit message( QString::fromUtf8( "Обновление базы данных до версии %1" ).arg( revision ) );
//подготовка скрипта к выполнению
QList<QByteArray> mainScript;
QList<QByteArray> 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( QString::fromUtf8( "Создание учебной базы данных версии %1" ).arg( revision ) );
else
emit message( QString::fromUtf8( "Обновление учебной базы данных до версии %1" ).arg( revision ) );
if ( !uriProc.execute( mainScript ) )
{
if ( transaction )
proc.execSQL( "rollback" );
return false;
}
if ( !script->uriScript().isEmpty() )
{
emit message( QString::fromUtf8( "Загрузка условной информации в учебную базу данных" ) );
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( QString::fromUtf8( "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( QString( "select 1 from pg_database where datname='%1\'" ).arg(a_dbname) );
return a_proc.result().size() == 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, QString( "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, QString( "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;
KSimpleDataSet data = a_proc.result();
data.first();
while ( data.isValid() )
{
result.append( data.value( 0 ) );
data.next();
}
return result;
}
//---------------------------------------------------------------------------
bool DatabaseUpdater::loadScriptsFromResource()
{
QString errmess = QString::fromUtf8( "Сбой при чтении ресурсов программы" );
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<QByteArray>& oa_mainScript,
QList<QByteArray>& oa_globalsScript )
{
//пропустим файл через парсер, на выходе список команд
bool helperInjected = false;
SqlProcessor proc;
QList<QByteArray> result = proc.parse( a_filename );
//обход списка и анализ команд
QListIterator<QByteArray> it( result );
while ( it.hasNext() )
{
const QByteArray originalLine = it.next();
QByteArray line = originalLine.toLower();
int i = 0;
int end = line.size();
//пропускаем первое слово, перед ним пробелов нет, парсер их убирает
while ( i != end && !QChar(line.at( i )).isSpace() )
++i;
//анализируем первое слово
QByteArray command1 = line.left( i );
if ( command1 == "\\encoding" )
{
oa_mainScript.append( originalLine );
oa_globalsScript.append( originalLine );
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 );
if ( command1 == "set" )
{
oa_mainScript.append( originalLine );
if ( command2 == "names" || command2 == "client_encoding" )
oa_globalsScript.append( originalLine );
}
else if ( command1 == "create" )
{
if ( command2 == "group" || command2 == "user" ||
command2 == "role" || command2 == "tablespace" )
{ //добавляем в выходной скрипт globals вызов безопасной функции-обёртки
if ( !helperInjected )
{ //но сначала вставляем команды создания функций-обёрток
helperInjected = true;
QListIterator<QByteArray> helperIter( _createHelperFunctionsScript );
while ( helperIter.hasNext() )
oa_globalsScript.append( helperIter.next() );
}
//переход на начало имени объекта
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;
//т.к. имя без двойных кавычек, то перевести его в нижний регистр,
//как это делает PostgreSQL
name = line.mid( p, i - p ).toLower();
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 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( originalLine );
}
}
else
{ //все остальные команды в основной скрипт
oa_mainScript.append( originalLine );
}
} //цикл по командам
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 ( a_proc.result().size() == 1 )
a_proc.execSQL( "update pg_class set relmaclabel=null" );
}
//---------------------------------------------------------------------------
bool DatabaseUpdater::createLanguagePlpgsql( SqlProcessor& a_proc,
SqlProcessor& a_uriProc,
bool a_uri )
{
static const char* CREATE_SQL = "create language plpgsql";
static const char* CHECK_SQL = "select 1 from pg_language where lanname='plpgsql'";
a_proc.execSQL( CHECK_SQL );
if ( a_proc.result().size() == 0 )
{
if ( !a_proc.execSQL( CREATE_SQL ) )
return false;
}
if ( a_uri )
{
a_uriProc.execSQL( CHECK_SQL );
if ( a_uriProc.result().size() == 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;
}
diff --git a/src/updater.h b/src/updater.h
--- a/src/updater.h
+++ b/src/updater.h
@@ -1,116 +1,115 @@
#ifndef UPDATER_H
#define UPDATER_H
-#include <QDomNode>
#include <QList>
#include <QObject>
-#include <QString>
class DatabaseScript
{
friend class DatabaseUpdater;
public:
DatabaseScript( int a_revision, bool a_transaction, const QString& a_script,
const QString& a_uriScript, const QString& a_comment )
: _revision(a_revision), _transaction(a_transaction)
,_script(a_script), _uriScript(a_uriScript), _comment(a_comment) {}
int revision() const { return _revision; }
bool transaction() const { return _transaction; }
QString script() const { return _script; }
QString uriScript() const { return _uriScript; }
QString comment() const { return _comment; }
bool findScript( const QStringList& a_paths );
bool findUriScript( const QStringList& a_paths );
private:
int _revision;
bool _transaction;
QString _script;
QString _uriScript;
QString _comment;
};
class Package
{
public:
Package( const QString& a_id, bool a_uri = true )
: _id(a_id), _uri(a_uri) {}
void addScript( DatabaseScript* a_script ) { _scripts.append( a_script ); }
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; }
QString id() const { return _id; }
QList<DatabaseScript*> scripts() const { return _scripts; }
private:
QList<DatabaseScript*> _scripts;
QString _id;
bool _uri;
};
class SqlProcessor;
class ProgramSettings;
class QByteArray;
class QByteArrayMatcher;
+class QXmlStreamReader;
class DatabaseUpdater : public QObject
{
Q_OBJECT
public:
DatabaseUpdater();
virtual ~DatabaseUpdater();
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 };
ProgramSettings* _pset;
QString _logText;
QList<Package*> _packages;
QList<QByteArray> _createDmVersionScript;
QList<QByteArray> _createHelperFunctionsScript;
QList<QByteArray> _dropHelperFunctionsScript;
QList<QByteArray> _setSearchPathScript;
int _revisionBefore;
int _revisionAfter;
QStringList databaseTableList( SqlProcessor& a_proc );
bool checkArguments();
bool readConfig();
- bool readPackage( const QDomNode& a_node );
+ bool readPackage( QXmlStreamReader& a_xml );
bool loadScriptsFromResource();
bool runScripts();
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 createLanguagePlpgsql( SqlProcessor& a_proc, SqlProcessor& a_uriProc,
bool a_uri );
void prepareScripts( const QString& a_filename, QList<QByteArray>& oa_mainScript,
QList<QByteArray>& oa_globalsScript );
void clearMaclabels( SqlProcessor& a_proc );
};
//!главный класс приложения
#endif //UPDATER_H

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jun 11, 8:11 PM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
126669

Event Timeline