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