diff options
Diffstat (limited to 'kexi/migration')
27 files changed, 4894 insertions, 0 deletions
diff --git a/kexi/migration/Makefile.am b/kexi/migration/Makefile.am new file mode 100644 index 00000000..75806ba5 --- /dev/null +++ b/kexi/migration/Makefile.am @@ -0,0 +1,57 @@ +include $(top_srcdir)/kexi/Makefile.global + +lib_LTLIBRARIES = libkeximigrate.la + +INCLUDES = \ + -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/core \ + -I$(top_srcdir)/kexi/widget \ + -I$(top_builddir)/kexi/widget \ + -I$(top_srcdir)/kexi/main/startup \ + -I$(top_builddir)/kexi/main/startup \ + $(all_includes) + +if compile_pgsql_plugin +pgsql_dir=pqxx +endif + +if compile_mysql_plugin +mysql_dir=mysql +endif + +SUBDIRS = . $(pgsql_dir) $(mysql_dir) + +libkeximigrate_la_METASOURCES = AUTO + +libkeximigrate_la_SOURCES = keximigrate.cpp importwizard.cpp migratemanager.cpp \ + keximigratedata.cpp importoptionsdlg.cpp + +libkeximigrate_la_LIBADD = \ + $(top_builddir)/kexi/core/libkexicore.la \ + $(top_builddir)/kexi/kexidb/libkexidb.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/kexi/main/libkeximain.la \ + $(LIB_QT) $(LIB_KDECORE) + +libkeximigrate_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(VER_INFO) + +noinst_HEADERS = importwizard.h migratemanager_p.h + +keximigrateincludedir=$(includedir)/kexidb +keximigrateinclude_HEADERS=keximigrate.h keximigratedata.h migratemanager.h + +kde_servicetypes_DATA = keximigration_driver.desktop + +KDE_OPTIONS=nofinal +noinst_PROGRAMS = keximigratetest + +keximigratetest_SOURCES = keximigratetest.cpp +keximigratetest_LDADD = libkeximigrate.la \ + $(top_builddir)/kexi/core/libkexicore.la \ + $(top_builddir)/kexi/kexidb/libkexidb.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/kexi/main/libkeximain.la \ + $(LIB_QT) $(LIB_KDECORE) + +keximigratetest_LDFLAGS = $(KDE_RPATH) $(all_libraries) + diff --git a/kexi/migration/configure.in.in b/kexi/migration/configure.in.in new file mode 100644 index 00000000..6744e431 --- /dev/null +++ b/kexi/migration/configure.in.in @@ -0,0 +1,7 @@ +# KexiMDB isn't built as part of Kexi right now. +#AC_ARG_ENABLE(keximdb, +# AC_HELP_STRING([--enable-keximdb], +# [build KexiMDB (MS Access) plugin [default=no]]), +# compile_keximdb_plugin=$enableval, compile_keximdb_plugin=no) +# +#AM_CONDITIONAL(compile_keximdb_plugin, test "x$compile_keximdb_plugin" != "xno") diff --git a/kexi/migration/importoptionsdlg.cpp b/kexi/migration/importoptionsdlg.cpp new file mode 100644 index 00000000..24ff3259 --- /dev/null +++ b/kexi/migration/importoptionsdlg.cpp @@ -0,0 +1,111 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "importoptionsdlg.h" +#include <widget/kexicharencodingcombobox.h> + +#include <qdir.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qtextcodec.h> +#include <qcheckbox.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kcombobox.h> +#include <klocale.h> +#include <kglobal.h> +#include <kcharsets.h> +#include <kiconloader.h> + +using namespace KexiMigration; + +OptionsDialog::OptionsDialog( const QString& databaseFile, const QString& selectedEncoding, QWidget* parent ) + : KDialogBase( + KDialogBase::Plain, + i18n( "Advanced Import Options" ), + Ok|Cancel, + Ok, + parent, + "KexiMigration::OptionsDialog", + true, + false + ) +{ + setIcon(DesktopIcon("configure")); + QGridLayout *lyr = new QGridLayout( plainPage(), 4, 3, KDialogBase::marginHint(), + KDialogBase::spacingHint()); + + m_encodingComboBox = new KexiCharacterEncodingComboBox(plainPage(), selectedEncoding); + m_encodingComboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + lyr->addWidget( m_encodingComboBox, 1, 1 ); + QLabel* lbl = new QLabel( + i18n("<h3>Text encoding for Microsoft Access database</h3>\n" + "<p>Database file \"%1\" appears to be created by a version of Microsoft Access older than 2000.</p>" + "<p>In order to properly import national characters, you may need to choose a proper text encoding " + "if the database was created on a computer with a different character set.</p>") + .arg(QDir::convertSeparators(databaseFile)), plainPage()); + lbl->setAlignment( Qt::AlignAuto | Qt::WordBreak ); + lbl->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + lyr->addMultiCellWidget( lbl, 0, 0, 0, 2 ); + + QLabel* lbl2 = new QLabel( m_encodingComboBox, i18n("Text encoding:"), plainPage()); + lyr->addWidget( lbl2, 1, 0 ); + + m_chkAlwaysUseThisEncoding = new QCheckBox( + i18n("Always use this encoding in similar situations"), plainPage()); + lyr->addMultiCellWidget( m_chkAlwaysUseThisEncoding, 2, 2, 1,2 ); + + lyr->addItem( new QSpacerItem( 20, 111, QSizePolicy::Minimum, QSizePolicy::Expanding ), 3, 1 ); + lyr->addItem( new QSpacerItem( 121, 20, QSizePolicy::Expanding, QSizePolicy::Minimum ), 1, 2 ); + + //read config + kapp->config()->setGroup("ImportExport"); + QString defaultEncodingForMSAccessFiles = kapp->config()->readEntry("DefaultEncodingForMSAccessFiles"); + if (!defaultEncodingForMSAccessFiles.isEmpty()) { + m_encodingComboBox->setSelectedEncoding(defaultEncodingForMSAccessFiles); + m_chkAlwaysUseThisEncoding->setChecked(true); + } + + adjustSize(); + m_encodingComboBox->setFocus(); +} + +OptionsDialog::~OptionsDialog() +{ +} + +KexiCharacterEncodingComboBox* OptionsDialog::encodingComboBox() const +{ + return m_encodingComboBox; +} + +void OptionsDialog::accept() +{ + kapp->config()->setGroup("ImportExport"); + if (m_chkAlwaysUseThisEncoding->isChecked()) + kapp->config()->writeEntry("defaultEncodingForMSAccessFiles", + m_encodingComboBox->selectedEncoding()); + else + kapp->config()->deleteEntry("defaultEncodingForMSAccessFiles"); + + KDialogBase::accept(); +} + +#include "importoptionsdlg.moc" diff --git a/kexi/migration/importoptionsdlg.h b/kexi/migration/importoptionsdlg.h new file mode 100644 index 00000000..736cb174 --- /dev/null +++ b/kexi/migration/importoptionsdlg.h @@ -0,0 +1,51 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIMIGRATIONOPTIONSDIALOG_H +#define KEXIMIGRATIONOPTIONSDIALOG_H + +#include <kdialogbase.h> + +class QCheckBox; +class KexiCharacterEncodingComboBox; + +namespace KexiMigration { + +//! @short Import Options dialog. +//! It is currently used for MDB driver only +//! @todo Hardcoded. Move such code to KexiMigrate drivers. +class OptionsDialog : public KDialogBase +{ + Q_OBJECT + public: + OptionsDialog( const QString& databaseFile, const QString& selectedEncoding, QWidget* parent = 0 ); + virtual ~OptionsDialog(); + + KexiCharacterEncodingComboBox* encodingComboBox() const; + + protected slots: + virtual void accept(); + + protected: + KexiCharacterEncodingComboBox *m_encodingComboBox; + QCheckBox *m_chkAlwaysUseThisEncoding; +}; +} + +#endif diff --git a/kexi/migration/importwizard.cpp b/kexi/migration/importwizard.cpp new file mode 100644 index 00000000..dda3d200 --- /dev/null +++ b/kexi/migration/importwizard.cpp @@ -0,0 +1,1031 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Adam Pigg <[email protected]> + Copyright (C) 2004-2006 Jaroslaw Staniek <[email protected]> + Copyright (C) 2005 Martin Ellis <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "importwizard.h" +#include "keximigrate.h" +#include "importoptionsdlg.h" + +#include <qlabel.h> +#include <qlayout.h> +#include <qvbuttongroup.h> +#include <qradiobutton.h> +#include <qcheckbox.h> + +#include <kcombobox.h> +#include <kmessagebox.h> +#include <kpushbutton.h> +#include <kdebug.h> +#include <klineedit.h> +#include <kiconloader.h> +#include <kbuttonbox.h> + +#include <kexidb/drivermanager.h> +#include <kexidb/driver.h> +#include <kexidb/connectiondata.h> +#include <kexidb/utils.h> +#include <core/kexidbconnectionset.h> +#include <core/kexi.h> +#include <KexiConnSelector.h> +#include <KexiProjectSelector.h> +#include <KexiOpenExistingFile.h> +#include <KexiDBTitlePage.h> +#include <kexiutils/utils.h> +#include <kexidbdrivercombobox.h> +#include <kexitextmsghandler.h> +#include <widget/kexicharencodingcombobox.h> +#include <widget/kexiprjtypeselector.h> + + +using namespace KexiMigration; + +//=========================================================== +// +ImportWizard::ImportWizard(QWidget *parent, QMap<QString,QString>* args) + : KWizard(parent) + , m_args(args) +{ + setCaption(i18n("Import Database")); + setIcon(DesktopIcon("database_import")); + m_prjSet = 0; + m_fileBasedDstWasPresented = false; + m_setupFileBasedSrcNeeded = true; + m_importExecuted = false; + m_srcTypeCombo = 0; + + setMinimumSize(400, 400); + parseArguments(); + setupIntro(); +// setupSrcType(); + setupSrcConn(); + setupSrcDB(); + setupDstType(); + setupDstTitle(); + setupDst(); + setupImportType(); + setupImporting(); + setupFinish(); + + connect(this, SIGNAL(selected(const QString &)), this, SLOT(pageSelected(const QString &))); + connect(this, SIGNAL(helpClicked()), this, SLOT(helpClicked())); + + if (m_predefinedConnectionData) { + // setup wizard for predefined server source + m_srcConn->showAdvancedConn(); + setAppropriate( m_srcConnPage, false ); + setAppropriate( m_srcDBPage, false ); + } + else if (!m_predefinedDatabaseName.isEmpty()) { + // setup wizard for predefined source + // (used when external project type was opened in Kexi, e.g. mdb file) +// MigrateManager manager; +// QString driverName = manager.driverForMimeType( m_predefinedMimeType ); +// m_srcTypeCombo->setCurrentText( driverName ); + +// showPage( m_srcConnPage ); + m_srcConn->showSimpleConn(); + m_srcConn->setSelectedFileName(m_predefinedDatabaseName); + + //disable all prev pages except "welcome" page + for (int i=0; i<indexOf(m_dstTypePage); i++) { + if (page(i)!=m_introPage) + setAppropriate( page(i), false ); + } + } + + m_sourceDBEncoding = QString::fromLatin1(KGlobal::locale()->encoding()); //default +} + +//=========================================================== +// +ImportWizard::~ImportWizard() +{ + delete m_prjSet; +} + +//=========================================================== +// +void ImportWizard::parseArguments() +{ + m_predefinedConnectionData = 0; + if (!m_args) + return; + if (!(*m_args)["databaseName"].isEmpty() && !(*m_args)["mimeType"].isEmpty()) { + m_predefinedDatabaseName = (*m_args)["databaseName"]; + m_predefinedMimeType = (*m_args)["mimeType"]; + if (m_args->contains("connectionData")) { + m_predefinedConnectionData = new KexiDB::ConnectionData(); + KexiDB::fromMap( + KexiUtils::deserializeMap((*m_args)["connectionData"]), *m_predefinedConnectionData + ); + } + } + m_args->clear(); +} + +//=========================================================== +// +void ImportWizard::setupIntro() +{ + m_introPage = new QWidget(this); + QVBoxLayout *vbox = new QVBoxLayout(m_introPage, KDialog::marginHint()); + + QLabel *lblIntro = new QLabel(m_introPage); + lblIntro->setAlignment( Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak ); + QString msg; + if (m_predefinedConnectionData) { //predefined import: server source + msg = i18n("<qt>Database Importing wizard is about to import \"%1\" database " + "<nobr>(connection %2)</nobr> into a Kexi database.</qt>") + .arg(m_predefinedDatabaseName).arg(m_predefinedConnectionData->serverInfoString()); + } + else if (!m_predefinedDatabaseName.isEmpty()) { //predefined import: file source +//! @todo this message is currently ok for files only + KMimeType::Ptr mimeTypePtr = KMimeType::mimeType(m_predefinedMimeType); + msg = i18n("<qt>Database Importing wizard is about to import <nobr>\"%1\"</nobr> file " + "of type \"%2\" into a Kexi database.</qt>") + .arg(QDir::convertSeparators(m_predefinedDatabaseName)).arg(mimeTypePtr->comment()); + } + else { + msg = i18n("Database Importing wizard allows you to import an existing database " + "into a Kexi database."); + } + lblIntro->setText(msg+"\n\n" + +i18n("Click \"Next\" button to continue or \"Cancel\" button to exit this wizard.")); + vbox->addWidget( lblIntro ); + addPage(m_introPage, i18n("Welcome to the Database Importing Wizard")); +} + +//=========================================================== +// +/* +void ImportWizard::setupSrcType() +{ + m_srcTypePage = new QWidget(this); + +//! @todo Would be good if KexiDBDriverComboBox worked for migration drivers + QVBoxLayout *vbox = new QVBoxLayout(m_srcTypePage, KDialog::marginHint()); + + QHBoxLayout *hbox = new QHBoxLayout(vbox); + QLabel *lbl = new QLabel(i18n("Source database type:")+" ", m_srcTypePage); + hbox->addWidget(lbl); + + m_srcTypeCombo = new KComboBox(m_srcTypePage); + hbox->addWidget(m_srcTypeCombo); + hbox->addStretch(1); + vbox->addStretch(1); + lbl->setBuddy(m_srcTypeCombo); + + MigrateManager manager; + QStringList names = manager.driverNames(); + + m_srcTypeCombo->insertStringList(names); + addPage(m_srcTypePage, i18n("Select Source Database Type")); +} +*/ +//=========================================================== +// +void ImportWizard::setupSrcConn() +{ + m_srcConnPage = new QWidget(this); + QVBoxLayout *vbox = new QVBoxLayout(m_srcConnPage, KDialog::marginHint()); + + m_srcConn = new KexiConnSelectorWidget(Kexi::connset(), + ":ProjectMigrationSourceDir", m_srcConnPage, "m_srcConnSelector"); + + m_srcConn->hideConnectonIcon(); + m_srcConn->showSimpleConn(); + + QStringList excludedFilters; +//! @todo remove when support for kexi files as source prj is added in migration + excludedFilters += KexiDB::Driver::defaultFileBasedDriverMimeType(); + excludedFilters += "application/x-kexiproject-shortcut"; + excludedFilters += "application/x-kexi-connectiondata"; + m_srcConn->m_fileDlg->setExcludedFilters(excludedFilters); + +// m_srcConn->hideHelpers(); + vbox->addWidget(m_srcConn); + addPage(m_srcConnPage, i18n("Select Location for Source Database")); +} + +//=========================================================== +// +void ImportWizard::setupSrcDB() +{ +// arrivesrcdbPage creates widgets on that page + m_srcDBPage = new QWidget(this); + m_srcDBName = NULL; + addPage(m_srcDBPage, i18n("Select Source Database")); +} + +//=========================================================== +// +void ImportWizard::setupDstType() +{ + m_dstTypePage = new QWidget(this); + + KexiDB::DriverManager manager; + KexiDB::Driver::InfoMap drvs = manager.driversInfo(); + + QVBoxLayout *vbox = new QVBoxLayout(m_dstTypePage, KDialog::marginHint()); + + QHBoxLayout *hbox = new QHBoxLayout(vbox); + QLabel *lbl = new QLabel(i18n("Destination database type:")+" ", m_dstTypePage); + lbl->setAlignment(Qt::AlignAuto|Qt::AlignTop); + hbox->addWidget(lbl); + + m_dstPrjTypeSelector = new KexiPrjTypeSelector(m_dstTypePage); + hbox->addWidget(m_dstPrjTypeSelector); + m_dstPrjTypeSelector->option_file->setText(i18n("Database project stored in a file")); + m_dstPrjTypeSelector->option_server->setText(i18n("Database project stored on a server")); + + QVBoxLayout *frame_server_vbox = new QVBoxLayout(m_dstPrjTypeSelector->frame_server, KDialog::spacingHint()); + m_dstServerTypeCombo = new KexiDBDriverComboBox(m_dstPrjTypeSelector->frame_server, drvs, + KexiDBDriverComboBox::ShowServerDrivers); + frame_server_vbox->addWidget(m_dstServerTypeCombo); + hbox->addStretch(1); + vbox->addStretch(1); + lbl->setBuddy(m_dstServerTypeCombo); + +//! @todo hardcoded: find a way to preselect default engine item + //m_dstTypeCombo->setCurrentText("SQLite3"); + addPage(m_dstTypePage, i18n("Select Destination Database Type")); +} + +//=========================================================== +// +void ImportWizard::setupDstTitle() +{ + m_dstTitlePage = new KexiDBTitlePage(i18n("Destination project's caption:"), + this, "KexiDBTitlePage"); + m_dstTitlePage->layout()->setMargin( KDialog::marginHint() ); + m_dstTitlePage->updateGeometry(); + m_dstNewDBNameLineEdit = m_dstTitlePage->le_caption; + addPage(m_dstTitlePage, i18n("Select Destination Database Project's Caption")); +} + +//=========================================================== +// +void ImportWizard::setupDst() +{ + m_dstPage = new QWidget(this); + QVBoxLayout *vbox = new QVBoxLayout(m_dstPage, KDialog::marginHint()); + + m_dstConn = new KexiConnSelectorWidget(Kexi::connset(), + ":ProjectMigrationDestinationDir", m_dstPage, "m_dstConnSelector"); + m_dstConn->hideHelpers(); + //me: Can't connect m_dstConn->m_fileDlg here, it doesn't exist yet + //connect(this, SLOT(next()), m_dstConn->m_fileDlg, SIGNAL(accepted())); + + vbox->addWidget( m_dstConn ); + connect(m_dstConn,SIGNAL(connectionItemExecuted(ConnectionDataLVItem*)), + this,SLOT(next())); + +// m_dstConn->hideHelpers(); + m_dstConn->showSimpleConn(); + //anyway, db files will be _saved_ + m_dstConn->m_fileDlg->setMode( KexiStartupFileDialog::SavingFileBasedDB ); +// m_dstConn->hideHelpers(); +// m_dstConn->m_file->btn_advanced->hide(); +// m_dstConn->m_file->label->hide(); +// m_dstConn->m_file->lbl->hide(); + //m_dstConn->m_file->spacer7->hide(); + + + //js dstNewDBName = new KLineEdit(dstControls); + // dstNewDBName->setText(i18n("Enter new database name here")); + addPage(m_dstPage, i18n("Select Location for Destination Database")); +} + +//=========================================================== +// +void ImportWizard::setupImportType() +{ + m_importTypePage = new QWidget(this); + QVBoxLayout *vbox = new QVBoxLayout(m_importTypePage, KDialog::marginHint()); + m_importTypeButtonGroup = new QVButtonGroup(m_importTypePage); + m_importTypeButtonGroup->setLineWidth(0); + vbox->addWidget( m_importTypeButtonGroup ); + + (void)new QRadioButton(i18n("Structure and data"), m_importTypeButtonGroup); + (void)new QRadioButton(i18n("Structure only"), m_importTypeButtonGroup); + + m_importTypeButtonGroup->setExclusive( true ); + m_importTypeButtonGroup->setButton( 0 ); + addPage(m_importTypePage, i18n("Select Type of Import")); +} + +//=========================================================== +// +void ImportWizard::setupImporting() +{ + m_importingPage = new QWidget(this); + m_importingPage->hide(); + QVBoxLayout *vbox = new QVBoxLayout(m_importingPage, KDialog::marginHint()); + m_lblImportingTxt = new QLabel(m_importingPage); + m_lblImportingTxt->setAlignment( Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak ); + + m_lblImportingErrTxt = new QLabel(m_importingPage); + m_lblImportingErrTxt->setAlignment( Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak ); + + m_progressBar = new KProgress(100, m_importingPage); + m_progressBar->hide(); + + vbox->addWidget( m_lblImportingTxt ); + vbox->addWidget( m_lblImportingErrTxt ); + vbox->addStretch(1); + + KButtonBox *optionsBox = new KButtonBox(m_importingPage); + vbox->addWidget( optionsBox ); + m_importOptionsButton = optionsBox->addButton(i18n("Advanced Options"), this, SLOT(slotOptionsButtonClicked())); + m_importOptionsButton->setIconSet(SmallIconSet("configure")); + optionsBox->addStretch(1); + + vbox->addWidget( m_progressBar ); + + vbox->addStretch(2); + + m_importingPage->show(); + + addPage(m_importingPage, i18n("Importing")); +} + +//=========================================================== +// +void ImportWizard::setupFinish() +{ + m_finishPage = new QWidget(this); + m_finishPage->hide(); + QVBoxLayout *vbox = new QVBoxLayout(m_finishPage, KDialog::marginHint()); + m_finishLbl = new QLabel(m_finishPage); + m_finishLbl->setAlignment( Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak ); + + vbox->addWidget( m_finishLbl ); + m_openImportedProjectCheckBox = new QCheckBox(i18n("Open imported project"), + m_finishPage, "openImportedProjectCheckBox"); + m_openImportedProjectCheckBox->setChecked(true); + vbox->addSpacing( KDialog::spacingHint() ); + vbox->addWidget( m_openImportedProjectCheckBox ); + vbox->addStretch(1); + + addPage(m_finishPage, i18n("Success")); +} + +//=========================================================== +// +bool ImportWizard::checkUserInput() +{ + QString finishtxt; + + if (m_dstNewDBNameLineEdit->text().isEmpty()) + { + finishtxt = finishtxt + "<br>" + i18n("No new database name was entered."); + } + + Kexi::ObjectStatus result; + KexiMigrate* sourceDriver = prepareImport(result); + if (sourceDriver && sourceDriver->isSourceAndDestinationDataSourceTheSame()) + { + finishtxt = finishtxt + "<br>" + i18n("Source database is the same as destination."); + } + + if (! finishtxt.isNull()) + { + finishtxt = "<qt>" + i18n("Following problems were found with the data you entered:") + + "<br>" + finishtxt + "<br><br>" + + i18n("Please click 'Back' button and correct these errors."); + m_lblImportingErrTxt->setText(finishtxt); + } + + return finishtxt.isNull(); +} + +void ImportWizard::arriveSrcConnPage() +{ + m_srcConnPage->hide(); + +// checkIfSrcTypeFileBased(m_srcTypeCombo->currentText()); +// if (fileBasedSrcSelected()) { +//moved m_srcConn->showSimpleConn(); + /*! @todo KexiStartupFileDialog needs "open file" and "open server" modes + in addition to just "open" */ + if (m_setupFileBasedSrcNeeded) { + m_setupFileBasedSrcNeeded = false; + QStringList additionalMimeTypes; + /* moved + if (m_srcTypeCombo->currentText().contains("Access")) { + //! @todo tmp: hardcoded! + additionalMimeTypes << "application/x-msaccess"; + }*/ + m_srcConn->m_fileDlg->setMode(KexiStartupFileDialog::Opening); + m_srcConn->m_fileDlg->setAdditionalFilters(additionalMimeTypes); +/*moved if (m_srcTypeCombo->currentText().contains("Access")) { + //! @todo tmp: hardcoded! + #ifdef Q_WS_WIN + m_srcConn->m_fileDlg->setSelectedFilter("*.mdb"); + #else + m_srcConn->m_fileDlg->setFilter("*.mdb"); + #endif + }*/ + //m_srcConn->m_file->label->hide(); + //m_srcConn->m_file->btn_advanced->hide(); + //m_srcConn->m_file->label->parentWidget()->hide(); + } +// } else { +// m_srcConn->showAdvancedConn(); +// } + /*! @todo Support different file extensions based on MigrationDriver */ + m_srcConnPage->show(); +} + +void ImportWizard::arriveSrcDBPage() +{ + if (fileBasedSrcSelected()) { + //! @todo Back button doesn't work after selecting a file to import + //moved showPage(m_dstTypePage); + } + else if (!m_srcDBName) { + m_srcDBPage->hide(); + kdDebug() << "Looks like we need a project selector widget!" << endl; + + KexiDB::ConnectionData* condata = m_srcConn->selectedConnectionData(); + if(condata) { + m_prjSet = new KexiProjectSet(*condata); + QVBoxLayout *vbox = new QVBoxLayout(m_srcDBPage, KDialog::marginHint()); + m_srcDBName = new KexiProjectSelectorWidget(m_srcDBPage, + "KexiMigrationProjectSelector", m_prjSet); + vbox->addWidget( m_srcDBName ); + m_srcDBName->label->setText(i18n("Select source database you wish to import:")); + } + m_srcDBPage->show(); + } +} + +void ImportWizard::arriveDstTitlePage() +{ + if(fileBasedSrcSelected()) { + QString suggestedDBName( QFileInfo(m_srcConn->selectedFileName()).fileName() ); + const QFileInfo fi( suggestedDBName ); + suggestedDBName = suggestedDBName.left(suggestedDBName.length() + - (fi.extension().length() ? (fi.extension().length()+1) : 0)); + m_dstNewDBNameLineEdit->setText( suggestedDBName ); + } else { + if (m_predefinedConnectionData) { + // server source db is predefined + m_dstNewDBNameLineEdit->setText( m_predefinedDatabaseName ); + } + else { + if (!m_srcDBName || !m_srcDBName->selectedProjectData()) { + back(); //todo! + return; + } + m_dstNewDBNameLineEdit->setText( m_srcDBName->selectedProjectData()->databaseName() ); + } + } +} + +void ImportWizard::arriveDstPage() +{ + m_dstPage->hide(); + +// checkIfDstTypeFileBased(m_dstTypeCombo->currentText()); + if(fileBasedDstSelected()) { + m_dstConn->showSimpleConn(); + m_dstConn->m_fileDlg->setMode( KexiStartupFileDialog::SavingFileBasedDB ); + if (!m_fileBasedDstWasPresented) { + //without extension - it will be added automatically + m_dstConn->m_fileDlg->setLocationText(m_dstNewDBNameLineEdit->text()); + } + m_fileBasedDstWasPresented = true; + } else { + m_dstConn->showAdvancedConn(); + } + m_dstPage->show(); +} + +void ImportWizard::arriveImportingPage() { +// checkIfDstTypeFileBased(m_dstTypeCombo->currentText()); +/*moved if (m_fileBasedDstWasPresented) { + if (!m_dstConn->m_fileDlg->checkFileName()) { + back(); + return; + } + }*/ + m_importingPage->hide(); + if (checkUserInput()) { + setNextEnabled(m_importingPage, true); + } + else { + setNextEnabled(m_importingPage, false); + } + + m_lblImportingTxt->setText(i18n( + "All required information has now " + "been gathered. Click \"Next\" button to start importing.\n\n" + "Depending on size of the database this may take some time." + /*"Note: You may be asked for extra " + "information such as field types if " + "the wizard could not automatically " + "determine this for you."*/)); + +//todo + + //temp. hack for MS Access driver only +//! @todo for other databases we will need KexiMigration::Conenction +//! and KexiMigration::Driver classes + bool showOptions = false; + if (fileBasedSrcSelected()) { + Kexi::ObjectStatus result; + KexiMigrate* sourceDriver = prepareImport(result); + if (sourceDriver) { + showOptions = !result.error() + && sourceDriver->propertyValue( "source_database_has_nonunicode_encoding" ).toBool(); + KexiMigration::Data *data = sourceDriver->data(); + sourceDriver->setData( 0 ); + delete data; + } + } + if (showOptions) + m_importOptionsButton->show(); + else + m_importOptionsButton->hide(); + + m_importingPage->show(); +} + +void ImportWizard::arriveFinishPage() { +// backButton()->hide(); +// cancelButton()->setEnabled(false); +// m_finishLbl->setText( m_successText.arg(m_dstNewDBNameLineEdit->text()) ); +} + +bool ImportWizard::fileBasedSrcSelected() const +{ + if (m_predefinedConnectionData) + return false; + +// kdDebug() << (m_srcConn->selectedConnectionType()==KexiConnSelectorWidget::FileBased) << endl; + return m_srcConn->selectedConnectionType()==KexiConnSelectorWidget::FileBased; +} + +bool ImportWizard::fileBasedDstSelected() const +{ +// QString dstType(m_dstServerTypeCombo->currentText()); + + return m_dstPrjTypeSelector->buttonGroup->selectedId() == 1; + +/* if ((dstType == "PostgreSQL") || (dstType == "MySQL")) { + return false; + } else { + return true; + }*/ +} + + +void ImportWizard::progressUpdated(int percent) { + m_progressBar->setProgress(percent); + KApplication::kApplication()->processEvents(); +} + +//=========================================================== +// +QString ImportWizard::driverNameForSelectedSource() +{ + if (fileBasedSrcSelected()) { + KMimeType::Ptr ptr = KMimeType::findByFileContent( m_srcConn->selectedFileName() ); + if (!ptr || ptr.data()->name()=="application/octet-stream" || ptr.data()->name()=="text/plain") { + //try by URL: + ptr = KMimeType::findByURL( m_srcConn->selectedFileName() ); + } + return ptr ? m_migrateManager.driverForMimeType( ptr.data()->name() ) : QString::null; + } + + //server-based + if (m_predefinedConnectionData) { + return m_predefinedConnectionData->driverName; + } + + return m_srcConn->selectedConnectionData() + ? m_srcConn->selectedConnectionData()->driverName : QString::null; +} + +//=========================================================== +// +void ImportWizard::accept() +{ + /*moved + backButton()->setEnabled(false); + finishButton()->setEnabled(false); +// cancelButton()->setEnabled(false); + acceptImport(); + backButton()->setEnabled(true); + finishButton()->setEnabled(true); +// cancelButton()->setEnabled(true); +*/ + if (m_args) { + if ((!fileBasedDstSelected() && !m_args->contains("destinationConnectionShortcut")) + || !m_openImportedProjectCheckBox->isChecked()) + { + //do not open dest db if used didn't want it + //for server connections, destinationConnectionShortcut must be defined + m_args->remove("destinationDatabaseName"); + } + } + KWizard::accept(); +} + +KexiMigrate* ImportWizard::prepareImport(Kexi::ObjectStatus& result) +{ + KexiUtils::WaitCursor wait; + + // Start with a driver manager + KexiDB::DriverManager manager; + + kdDebug() << "Creating destination driver..." << endl; + + // Get a driver to the destination database + KexiDB::Driver *destDriver = manager.driver( + m_dstConn->selectedConnectionData() ? m_dstConn->selectedConnectionData()->driverName //server based + : KexiDB::Driver::defaultFileBasedDriverName() + // : m_dstTypeCombo->currentText() //file based + ); + if (!destDriver || manager.error()) + { + result.setStatus(&manager); + kdDebug() << "Manager error..." << endl; + manager.debugError(); +// result.setStatus(&manager); + } + + // Set up destination connection data + KexiDB::ConnectionData *cdata; + bool cdataOwned = false; + QString dbname; + if (!result.error()) + { + if (m_dstConn->selectedConnectionData()) + { + //server-based project + kdDebug() << "Server destination..." << endl; + cdata = m_dstConn->selectedConnectionData(); + dbname = m_dstNewDBNameLineEdit->text(); + } + else // if (m_dstTypeCombo->currentText().lower() == KexiDB::Driver::defaultFileBasedDriverName()) + { + //file-based project + kdDebug() << "File Destination..." << endl; + cdata = new KexiDB::ConnectionData(); + cdataOwned = true; + cdata->caption = m_dstNewDBNameLineEdit->text(); + cdata->driverName = KexiDB::Driver::defaultFileBasedDriverName(); + dbname = m_dstConn->selectedFileName(); + cdata->setFileName( dbname ); + kdDebug() << "Current file name: " << dbname << endl; + } +/* else + { + //TODO This needs a better message + //KMessageBox::error(this, + result.setStatus(i18n("No connection data is available. You did not select a destination filename."),""); + //return false; + } */ + } + + // Find a source (migration) driver name + QString sourceDriverName; + if (!result.error()) + { + sourceDriverName = driverNameForSelectedSource(); + if (sourceDriverName.isEmpty()) + result.setStatus(i18n("No appropriate migration driver found."), + m_migrateManager.possibleProblemsInfoMsg()); + } + + // Get a source (migration) driver + KexiMigrate* sourceDriver = 0; + if (!result.error()) + { + sourceDriver = m_migrateManager.driver( sourceDriverName ); + if(!sourceDriver || m_migrateManager.error()) { + kdDebug() << "Import migrate driver error..." << endl; + result.setStatus(&m_migrateManager); + } + } + + KexiUtils::removeWaitCursor(); + + // Set up source (migration) data required for connection + if (sourceDriver && !result.error()) + { + // Setup progress feedback for the GUI + if(sourceDriver->progressSupported()) { + m_progressBar->updateGeometry(); + disconnect(sourceDriver, SIGNAL(progressPercent(int)), + this, SLOT(progressUpdated(int))); + connect(sourceDriver, SIGNAL(progressPercent(int)), + this, SLOT(progressUpdated(int))); + progressUpdated(0); + } + + bool keepData; + if (m_importTypeButtonGroup->selectedId() == 0) + { + kdDebug() << "Structure and data selected" << endl; + keepData = true; + } + else if (m_importTypeButtonGroup->selectedId() == 1) + { + kdDebug() << "structure only selected" << endl; + keepData = false; + } + else + { + kdDebug() << "Neither radio button is selected (not possible?) presume keep data" << endl; + keepData = true; + } + + KexiMigration::Data* md = new KexiMigration::Data(); + // delete md->destination; + md->destination = new KexiProjectData(*cdata, dbname); + if(fileBasedSrcSelected()) { + KexiDB::ConnectionData* conn_data = new KexiDB::ConnectionData(); + conn_data->setFileName(m_srcConn->selectedFileName()); + md->source = conn_data; + md->sourceName = ""; + } + else + { + if (m_predefinedConnectionData) + md->source = m_predefinedConnectionData; + else + md->source = m_srcConn->selectedConnectionData(); + + if (!m_predefinedDatabaseName.isEmpty()) + md->sourceName = m_predefinedDatabaseName; + else + md->sourceName = m_srcDBName->selectedProjectData()->databaseName(); + //! @todo Aah, this is so C-like. Move to performImport(). + } + md->keepData = keepData; + sourceDriver->setData(md); + return sourceDriver; + } + return 0; +} + +tristate ImportWizard::import() +{ + m_importExecuted = true; + + Kexi::ObjectStatus result; + KexiMigrate* sourceDriver = prepareImport(result); + + bool acceptingNeeded = false; + + // Perform import + if (sourceDriver && !result.error()) + { + if (!m_sourceDBEncoding.isEmpty()) { + sourceDriver->setPropertyValue( "source_database_nonunicode_encoding", + QVariant(m_sourceDBEncoding.upper().replace(' ',"")) // "CP1250", not "cp 1250" + ); + } + + if (!sourceDriver->checkIfDestinationDatabaseOverwritingNeedsAccepting(&result, acceptingNeeded)) { + kdDebug() << "Abort import cause checkIfDestinationDatabaseOverwritingNeedsAccepting returned false." << endl; + return false; + } + + kdDebug() << sourceDriver->data()->destination->databaseName() << endl; + kdDebug() << "Performing import..." << endl; + } + + if (sourceDriver && !result.error() && acceptingNeeded) { // ok, the destination-db already exists... + if (KMessageBox::Yes != KMessageBox::warningYesNo(this, + "<qt>"+i18n("Database %1 already exists." + "<p>Do you want to replace it with a new one?") + .arg(sourceDriver->data()->destination->infoString()), + 0, KGuiItem(i18n("&Replace")), KGuiItem(i18n("No")))) + { + return cancelled; + } + } + + if (sourceDriver && !result.error() && sourceDriver->progressSupported()) { + m_progressBar->show(); + } + + if (sourceDriver && !result.error() && sourceDriver->performImport(&result)) + { + if (m_args) { +// if (fileBasedDstSelected()) { + m_args->insert("destinationDatabaseName", + sourceDriver->data()->destination->databaseName()); +// } + QString destinationConnectionShortcut( + Kexi::connset().fileNameForConnectionData( m_dstConn->selectedConnectionData() ) ); + if (!destinationConnectionShortcut.isEmpty()) { + m_args->insert("destinationConnectionShortcut", destinationConnectionShortcut); + } + } + setTitle(m_finishPage, i18n("Success")); + return true; + } + + if (!sourceDriver || result.error()) + { + m_progressBar->setProgress(0); + m_progressBar->hide(); + + QString msg, details; + KexiTextMessageHandler handler(msg, details); + handler.showErrorMessage(&result); + + kdDebug() << msg << "\n" << details << endl; + setTitle(m_finishPage, i18n("Failure")); + m_finishLbl->setText( + i18n("<p>Import failed.</p>%1<p>%2</p><p>You can click \"Back\" button and try again.</p>") + .arg(msg).arg(details)); + return false; + } +// delete kexi_conn; + return true; +} + +void ImportWizard::reject() +{ + KWizard::reject(); +} + +//=========================================================== +// +void ImportWizard::next() +{ + if (currentPage() == m_srcConnPage) { + if (fileBasedSrcSelected() + && /*! @todo use KURL? */!QFileInfo(m_srcConn->selectedFileName()).isFile()) { + + KMessageBox::sorry(this,i18n("Select source database filename.")); + return; + } + + if ( (! fileBasedSrcSelected()) && (! m_srcConn->selectedConnectionData()) ) { + KMessageBox::sorry(this,i18n("Select source database.")); + return; + } + + KexiMigrate* import = m_migrateManager.driver( driverNameForSelectedSource() ); + if(!import || m_migrateManager.error()) { + QString dbname; + if (fileBasedSrcSelected()) + dbname = m_srcConn->selectedFileName(); + else + dbname = m_srcConn->selectedConnectionData() + ? m_srcConn->selectedConnectionData()->serverInfoString() : QString::null; + if (!dbname.isEmpty()) + dbname = QString(" \"%1\"").arg(dbname); + KMessageBox::error(this, i18n("Could not import database%1. This type is not supported.") + .arg(dbname)); + return; + } + } + else if (currentPage() == m_dstPage) { + if (m_fileBasedDstWasPresented) { + if (fileBasedDstSelected() && !m_dstConn->m_fileDlg->checkFileName()) + return; + } + } + else if (currentPage() == m_importingPage) { + if (!m_importExecuted) { + m_importOptionsButton->hide(); + nextButton()->setEnabled(false); + finishButton()->setEnabled(false); + backButton()->setEnabled(false); + m_lblImportingTxt->setText(i18n("Importing in progress...")); + tristate res = import(); + if (true == res) { + m_finishLbl->setText( + i18n("Database has been imported into Kexi database project \"%1\".") + .arg(m_dstNewDBNameLineEdit->text()) ); + cancelButton()->setEnabled(false); + setBackEnabled(m_finishPage, false); + setFinishEnabled(m_finishPage, true); + m_openImportedProjectCheckBox->show(); + next(); + return; + } + + m_progressBar->hide(); + cancelButton()->setEnabled(true); + setBackEnabled(m_finishPage, true); + setFinishEnabled(m_finishPage, false); + m_openImportedProjectCheckBox->hide(); + if (!res) + next(); + else if (~res) { + arriveImportingPage(); + // back(); + } + m_importExecuted = false; + return; + } + } + + setAppropriate( m_srcDBPage, !fileBasedSrcSelected() && !m_predefinedConnectionData ); //skip m_srcDBPage + KWizard::next(); +} + +void ImportWizard::back() +{ + setAppropriate( m_srcDBPage, !fileBasedSrcSelected() && !m_predefinedConnectionData ); //skip m_srcDBPage + KWizard::back(); +} + +void ImportWizard::pageSelected(const QString &) +{ + if (currentPage() == m_introPage) { + } +// else if (currentPage() == m_srcTypePage) { +// } + else if (currentPage() == m_srcConnPage) { + arriveSrcConnPage(); + } + else if (currentPage() == m_srcDBPage) { + arriveSrcDBPage(); + } + else if (currentPage() == m_dstTypePage) { + } + else if (currentPage() == m_dstTitlePage) { + arriveDstTitlePage(); + } + else if (currentPage() == m_dstPage) { + arriveDstPage(); + } + else if (currentPage() == m_importingPage) { + arriveImportingPage(); + } + else if (currentPage() == m_finishPage) { + arriveFinishPage(); + } +} + +void ImportWizard::helpClicked() +{ + if (currentPage() == m_introPage) + { + KMessageBox::information(this, i18n("No help is available for this page."), i18n("Help")); + } +/* else if (currentPage() == m_srcTypePage) + { + KMessageBox::information(this, i18n("Here you can choose the type of data to import data from."), i18n("Help")); + }*/ + else if (currentPage() == m_srcConnPage) + { + KMessageBox::information(this, i18n("Here you can choose the location to import data from."), i18n("Help")); + } + else if (currentPage() == m_srcDBPage) + { + KMessageBox::information(this, i18n("Here you can choose the actual database to import data from."), i18n("Help")); + } + else if (currentPage() == m_dstTypePage) + { + KMessageBox::information(this, i18n("Here you can choose the location to save the data."), i18n("Help")); + } + else if (currentPage() == m_dstPage) + { + KMessageBox::information(this, i18n("Here you can choose the location to save the data in and the new database name."), i18n("Help")); + } + else if (currentPage() == m_finishPage || currentPage() == m_importingPage) + { + KMessageBox::information(this, i18n("No help is available for this page."), i18n("Help")); + } +} + +void ImportWizard::slotOptionsButtonClicked() +{ + OptionsDialog dlg(m_srcConn->selectedFileName(), m_sourceDBEncoding, this); + if (QDialog::Accepted != dlg.exec()) + return; + + if (m_sourceDBEncoding != dlg.encodingComboBox()->selectedEncoding()) { + m_sourceDBEncoding = dlg.encodingComboBox()->selectedEncoding(); + } +} + +#include "importwizard.moc" diff --git a/kexi/migration/importwizard.h b/kexi/migration/importwizard.h new file mode 100644 index 00000000..7d51a934 --- /dev/null +++ b/kexi/migration/importwizard.h @@ -0,0 +1,153 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Adam Pigg <[email protected]> + Copyright (C) 2004-2006 Jaroslaw Staniek <[email protected]> + Copyright (C) 2005 Martin Ellis <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIMIGRATIONIMPORTWIZARD_H +#define KEXIMIGRATIONIMPORTWIZARD_H + +#include <kwizard.h> +#include <kprogress.h> +#include <kapplication.h> + +#include <kexiutils/tristate.h> +#include "migratemanager.h" + +class QLabel; +class QCheckBox; +class QPushButton; +class QHBoxLayout; +class QVBoxLayout; +class QVButtonGroup; +class KComboBox; +class KListView; +class KLineEdit; +class KexiConnSelectorWidget; +class KexiProjectSelectorWidget; +class KexiProjectSet; +class KexiDBTitlePage; +class KexiDBDriverComboBox; +class KexiPrjTypeSelector; + +namespace Kexi +{ + class ObjectStatus; +} + +namespace KexiDB +{ + class ConnectionData; +} + +namespace KexiMigration { + +class KexiMigrate; + +//! GUI for importing external databases (file-based and server-based) +class KEXIMIGR_EXPORT ImportWizard : public KWizard +{ +Q_OBJECT +public: + /*! Creates wizard's instance. + \a args contains arguments that can be parsed by parseArguments(). + \a *arg will be also set to imported project's filename on success + and to null value on failure or cancellation. */ + ImportWizard(QWidget *parent = 0, QMap<QString,QString>* args = 0); + virtual ~ImportWizard(); + +public slots: + void progressUpdated(int percent); + +protected slots: + virtual void next(); + virtual void back(); + void pageSelected(const QString &); + virtual void accept(); + virtual void reject(); + void helpClicked(); + void slotOptionsButtonClicked(); + +private: + void parseArguments(); + void setupIntro(); +// void setupSrcType(); + void setupSrcConn(); + void setupSrcDB(); + void setupDstType(); + void setupDstTitle(); + void setupDst(); + void setupFinish(); + void setupImportType(); + void setupImporting(); + bool checkUserInput(); + + KexiMigrate* prepareImport(Kexi::ObjectStatus& result); + + /*! Performs import. \return true/false on success/faulure + or cancelled when user cancelled importing (mainly + because didn't allow overwriting an existing database by a new one). */ + tristate import(); + + bool fileBasedSrcSelected() const; + bool fileBasedDstSelected() const; + QString driverNameForSelectedSource(); +// void checkIfSrcTypeFileBased(const QString& srcType); +// void checkIfDstTypeFileBased(const QString& dstType); + + void arriveSrcConnPage(); + void arriveSrcDBPage(); + void arriveDstTitlePage(); + void arriveDstPage(); + void arriveFinishPage(); + void arriveImportingPage(); + + QWidget *m_introPage, /* *m_srcTypePage,*/ *m_srcConnPage, *m_srcDBPage, + *m_dstTypePage, *m_dstPage, *m_importTypePage, *m_importingPage, *m_finishPage; + + QVButtonGroup *m_importTypeButtonGroup; + KexiDBTitlePage* m_dstTitlePage; + + KComboBox *m_srcTypeCombo; + KexiDBDriverComboBox *m_dstServerTypeCombo; + KexiPrjTypeSelector *m_dstPrjTypeSelector; + + KexiConnSelectorWidget *m_srcConn, *m_dstConn; + KLineEdit *m_dstNewDBNameLineEdit; + KexiProjectSelectorWidget *m_srcDBName; + + QLabel *m_lblImportingTxt, *m_lblImportingErrTxt, *m_finishLbl; + QCheckBox *m_openImportedProjectCheckBox; + bool m_fileBasedDstWasPresented, m_setupFileBasedSrcNeeded, + m_importExecuted; //!< used in import() + KexiProjectSet* m_prjSet; + KProgress *m_progressBar; + QPushButton* m_importOptionsButton; + QMap<QString,QString> *m_args; + QString m_predefinedDatabaseName, m_predefinedMimeType; + KexiDB::ConnectionData *m_predefinedConnectionData; + MigrateManager m_migrateManager; //!< object lives here, so status messages can be globally preserved + + //! Encoding for source db. Currently only used for MDB driver. +//! @todo Hardcoded. Move to KexiMigrate driver's impl. + QString m_sourceDBEncoding; +}; + +} + +#endif diff --git a/kexi/migration/keximigrate.cpp b/kexi/migration/keximigrate.cpp new file mode 100644 index 00000000..26229323 --- /dev/null +++ b/kexi/migration/keximigrate.cpp @@ -0,0 +1,616 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Adam Pigg <[email protected]> + Copyright (C) 2004-2006 Jaroslaw Staniek <[email protected]> + Copyright (C) 2005 Martin Ellis <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "keximigrate.h" + +#include <kdebug.h> +#include <kinputdialog.h> +#include <kapplication.h> + +#include <kexiutils/identifier.h> +#include <core/kexi.h> +#include <core/kexiproject.h> +#include <kexidb/drivermanager.h> + +using namespace KexiDB; +using namespace KexiMigration; + +KexiMigrate::KexiMigrate(QObject *parent, const char *name, + const QStringList&) + : QObject( parent, name ) + , m_migrateData(0) + , m_destPrj(0) +{ + m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.setAutoDelete(true); +} + +//! Used for computing progress: +//! let's assume that each table creation costs the same as inserting 20 rows +#define NUM_OF_ROWS_PER_CREATE_TABLE 20 + + +//============================================================================= +// Migration parameters +void KexiMigrate::setData(KexiMigration::Data* migrateData) +{ + m_migrateData = migrateData; +} + +//============================================================================= +// Destructor +KexiMigrate::~KexiMigrate() +{ + delete m_destPrj; +} + +bool KexiMigrate::checkIfDestinationDatabaseOverwritingNeedsAccepting(Kexi::ObjectStatus* result, + bool& acceptingNeeded) +{ + acceptingNeeded = false; + if (result) + result->clearStatus(); + + KexiDB::DriverManager drvManager; + KexiDB::Driver *destDriver = drvManager.driver( + m_migrateData->destination->connectionData()->driverName); + if (!destDriver) { + result->setStatus(&drvManager, + i18n("Could not create database \"%1\".") + .arg(m_migrateData->destination->databaseName())); + return false; + } + + // For file-based dest. projects, we've already asked about overwriting + // existing project but for server-based projects we need to ask now. + if (destDriver->isFileDriver()) + return true; //nothing to check + KexiDB::Connection *tmpConn + = destDriver->createConnection( *m_migrateData->destination->connectionData() ); + if (!tmpConn || destDriver->error() || !tmpConn->connect()) { + delete tmpConn; + return true; + } + if (tmpConn->databaseExists( m_migrateData->destination->databaseName() )) { + acceptingNeeded = true; + } + tmpConn->disconnect(); + delete tmpConn; + return true; +} + +bool KexiMigrate::isSourceAndDestinationDataSourceTheSame() const +{ + KexiDB::ConnectionData* sourcedata = m_migrateData->source; + KexiDB::ConnectionData* destinationdata = m_migrateData->destination->connectionData(); + return ( + sourcedata && destinationdata && + m_migrateData->sourceName == m_migrateData->destination->databaseName() && // same database name + sourcedata->driverName == destinationdata->driverName && // same driver + sourcedata->hostName == destinationdata->hostName && // same host + sourcedata->fileName() == destinationdata->fileName() && // same filename + sourcedata->dbPath() == destinationdata->dbPath() && // same database path + sourcedata->dbFileName() == destinationdata->dbFileName() // same database filename + ); +} + +//============================================================================= +// Perform Import operation +bool KexiMigrate::performImport(Kexi::ObjectStatus* result) +{ + if (result) + result->clearStatus(); + + KexiDB::DriverManager drvManager; + KexiDB::Driver *destDriver = drvManager.driver( + m_migrateData->destination->connectionData()->driverName); + if (!destDriver) { + result->setStatus(&drvManager, + i18n("Could not create database \"%1\".") + .arg(m_migrateData->destination->databaseName())); + return false; + } + + QStringList tables; + + // Step 1 - connect + kdDebug() << "KexiMigrate::performImport() CONNECTING..." << endl; + if (!drv_connect()) { + kdDebug() << "Couldnt connect to database server" << endl; + if (result) + result->setStatus(i18n("Could not connect to data source \"%1\".") + .arg(m_migrateData->source->serverInfoString()), ""); + return false; + } + + // Step 2 - get table names + kdDebug() << "KexiMigrate::performImport() GETTING TABLENAMES..." << endl; + if (!tableNames(tables)) { + kdDebug() << "Couldnt get list of tables" << endl; + if (result) + result->setStatus( + i18n("Could not get a list of table names for data source \"%1\".") + .arg(m_migrateData->source->serverInfoString()), ""); + return false; + } + + // Check if there are any tables + if (tables.isEmpty()) { + kdDebug() << "There were no tables to import" << endl; + if (result) + result->setStatus( + i18n("No tables to import found in data source \"%1\".") + .arg(m_migrateData->source->serverInfoString()), ""); + return false; + } + + // Step 3 - Read table schemas + tables.sort(); + m_tableSchemas.clear(); + if (!destDriver) { + result->setStatus(&drvManager); + return false; + } + const bool kexi__objects_exists = tables.find("kexi__objects")!=tables.end(); + QStringList kexiDBTables; + if (kexi__objects_exists) { + tristate res = drv_queryStringListFromSQL( + QString::fromLatin1("SELECT o_name FROM kexi__objects WHERE o_type=%1") + .arg((int)KexiDB::TableObjectType), 0, kexiDBTables, -1); + if (res == true) { + // prepend KexiDB-compatible tables to 'tables' list, so we'll copy KexiDB-compatible tables first, + // to make sure existing IDs will not be in conflict with IDs newly generated for non-KexiDB tables + kexiDBTables.sort(); + foreach(QStringList::ConstIterator, it, kexiDBTables) + tables.remove( *it ); +//kdDebug() << "KexiDB-compat tables: " << kexiDBTables << endl; +//kdDebug() << "non-KexiDB tables: " << tables << endl; + } + } + +// uint i=0; + // -- read table schemas and create them in memory (only for non-KexiDB-compat tables) + foreach (QStringList::ConstIterator, it, tables) { + if (destDriver->isSystemObjectName( *it ) //"kexi__objects", etc. + || (*it).lower().startsWith("kexi__")) //tables at KexiProject level, e.g. "kexi__blobs" + continue; + // this is a non-KexiDB table: generate schema from native data source + const QString tableName( KexiUtils::string2Identifier(*it) ); + KexiDB::TableSchema *tableSchema = new KexiDB::TableSchema(tableName); + tableSchema->setCaption( *it ); //caption is equal to the original name + + if (!drv_readTableSchema(*it, *tableSchema)) { + delete tableSchema; + if (result) + result->setStatus( + i18n("Could not import project from data source \"%1\". Error reading table \"%2\".") + .arg(m_migrateData->source->serverInfoString()).arg(tableName), ""); + return false; + } + //yeah, got a table + //Add it to list of tables which we will create if all goes well + m_tableSchemas.append(tableSchema); + } + + // Step 4 - Create a new database as we have all required info + // - create copies of KexiDB-compat tables + // - create copies of non-KexiDB tables + delete m_destPrj; + m_destPrj = new KexiProject(m_migrateData->destination, + result ? (KexiDB::MessageHandler*)*result : 0); + bool ok = true == m_destPrj->create(true /*forceOverwrite*/); + + KexiDB::Connection *destConn = 0; + + if (ok) + ok = (destConn = m_destPrj->dbConnection()); + + KexiDB::Transaction trans; + if (ok) { + trans = destConn->beginTransaction(); + if (trans.isNull()) { + ok = false; + if (result) + result->setStatus(destConn, + i18n("Could not create database \"%1\".") + .arg(m_migrateData->destination->databaseName())); + //later destConn->dropDatabase(m_migrateData->destination->databaseName()); + //don't delete prj, otherwise eror message will be deleted delete prj; + //later return m_destPrj; + } + } + + if (ok) { + if (drv_progressSupported()) + progressInitialise(); + + // Step 5 - Create the copies of KexiDB-compat tables in memory (to maintain the same IDs) + m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.clear(); + foreach (QStringList::ConstIterator, it, kexiDBTables) { + //load the schema from kexi__objects and kexi__fields + TableSchema *t = new TableSchema(); + RowData data; + bool firstRecord = true; + if (true == drv_fetchRecordFromSQL( + QString("SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects " + "WHERE o_name='%1' AND o_type=%1").arg(*it).arg((int)KexiDB::TableObjectType), + data, firstRecord) + && destConn->setupObjectSchemaData( data, *t )) + { +//! @todo to reuse Connection::setupTableSchema()'s statement somehow... + //load schema for every field and add it + firstRecord = true; + QString sql( + QString::fromLatin1("SELECT t_id, f_type, f_name, f_length, f_precision, f_constraints, " + "f_options, f_default, f_order, f_caption, f_help" + " FROM kexi__fields WHERE t_id=%1 ORDER BY f_order").arg(t->id()) ); + while (ok) { + tristate res = drv_fetchRecordFromSQL(sql, data, firstRecord); + if (res != true) { + if (false == res) + ok = false; + break; + } + KexiDB::Field* f = destConn->setupField( data ); + if (f) + t->addField(f); + else + ok = false; + } + if (ok) + ok = destConn->drv_createTable(*t); + if (ok) + m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.append(t); + } + if (!ok) + delete t; + } + } + + // Step 6 - Copy kexi__objects NOW because we'll soon create new objects with new IDs (3.)... + if (ok) { + if (kexi__objects_exists) + ok = drv_copyTable("kexi__objects", destConn, destConn->tableSchema("kexi__objects")); + } + + // Step 7 - Create the non-KexiDB-compatible tables: new IDs will be assigned to them + if (ok) { + KexiDB::TableSchema *ts; + for (QPtrListIterator<TableSchema> it (m_tableSchemas); (ts = it.current()); ++it) { + ok = destConn->createTable( ts ); + if (!ok) { + kdDebug() << "Failed to create a table " << ts->name() << endl; + destConn->debugError(); + if (result) + result->setStatus(destConn, + i18n("Could not create database \"%1\".") + .arg(m_migrateData->destination->databaseName())); + m_tableSchemas.remove(ts); + break; + } + updateProgress((Q_ULLONG)NUM_OF_ROWS_PER_CREATE_TABLE); + } + } + + if (ok) + ok = destConn->commitTransaction(trans); + + if (ok) { + //add compatible tables to the list, so data will be copied, if needed + if (m_migrateData->keepData) { + for(QPtrListIterator<TableSchema> it (m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport); + it.current(); ++it) + { + m_tableSchemas.append(it.current()); + } + } + else + m_tableSchemas.clear(); + } + + if (ok) { + if (m_destPrj->error()) { + ok = false; + if (result) + result->setStatus(m_destPrj, + i18n("Could not import project from data source \"%1\".") + .arg(m_migrateData->source->serverInfoString())); + } + } + + // Step 8 - Copy data if asked to + if (ok) { + trans = destConn->beginTransaction(); + ok = !trans.isNull(); + } + if (ok) { + if (m_migrateData->keepData) { +//! @todo check detailed "copy forms/blobs/tables" flags here when we add them + // Copy data for "kexi__objectdata" as well, if available in the source db + if (tables.find("kexi__objectdata")!=tables.end()) + m_tableSchemas.append(destConn->tableSchema("kexi__objectdata")); + // Copy data for "kexi__blobs" as well, if available in the source db + if (tables.find("kexi__blobs")!=tables.end()) + m_tableSchemas.append(destConn->tableSchema("kexi__blobs")); + // Copy data for "kexi__fields" as well, if available in the source db + if (tables.find("kexi__fields")!=tables.end()) + m_tableSchemas.append(destConn->tableSchema("kexi__fields")); + } + + for(QPtrListIterator<TableSchema> ts(m_tableSchemas); ok && ts.current() != 0 ; ++ts) + { + const QString tname( ts.current()->name().lower() ); + if (destConn->driver()->isSystemObjectName( tname ) +//! @todo what if these two tables are not compatible with tables created in destination db +//! because newer db format was used? + && tname!="kexi__objectdata" //copy this too + && tname!="kexi__blobs" //copy this too + && tname!="kexi__fields" //copy this too + ) + { + kdDebug() << "Do not copy data for system table: " << tname << endl; +//! @todo copy kexi__db contents! + continue; + } + kdDebug() << "Copying data for table: " << tname << endl; + QString originalTableName; + if (kexiDBTables.find(tname)==kexiDBTables.end()) + //caption is equal to the original name + originalTableName = ts.current()->caption().isEmpty() ? tname : ts.current()->caption(); + else + originalTableName = tname; + ok = drv_copyTable(originalTableName, destConn, ts.current()); + if (!ok) { + kdDebug() << "Failed to copy table " << tname << endl; + if (result) + result->setStatus(destConn, + i18n("Could not copy table \"%1\" to destination database.").arg(tname)); + break; + } + }//for + } + + // Done. + if (ok) + ok = destConn->commitTransaction(trans); + + if (ok) + ok = drv_disconnect(); + + m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.clear(); + + if (ok) { + if (destConn) + ok = destConn->disconnect(); + return ok; + } + + // Finally: error handling + if (result && result->error()) + result->setStatus(destConn, + i18n("Could not import data from data source \"%1\".") + .arg(m_migrateData->source->serverInfoString())); + if (destConn) { + destConn->debugError(); + destConn->rollbackTransaction(trans); + } + drv_disconnect(); + if (destConn) { + destConn->disconnect(); + destConn->dropDatabase(m_migrateData->destination->databaseName()); + } + return false; +} +//============================================================================= + +bool KexiMigrate::performExport(Kexi::ObjectStatus* result) +{ + if (result) + result->clearStatus(); + + //! @todo performExport + + return false; +} + +//============================================================================= +// Functions for getting table data +bool KexiMigrate::tableNames(QStringList & tn) +{ + //! @todo Cache list of table names + kdDebug() << "Reading list of tables..." << endl; + return drv_tableNames(tn); +} + +//============================================================================= +// Progress functions +bool KexiMigrate::progressInitialise() { + Q_ULLONG sum = 0, size; + emit progressPercent(0); + + //! @todo Don't copy table names here + QStringList tables; + if(!tableNames(tables)) + return false; + + // 1) Get the number of rows/bytes to import + int tableNumber = 1; + for(QStringList::Iterator it = tables.begin(); + it != tables.end(); ++it, tableNumber++) + { + if(drv_getTableSize(*it, size)) { + kdDebug() << "KexiMigrate::progressInitialise() - table: " << *it + << "size: " << (ulong)size << endl; + sum += size; + emit progressPercent(tableNumber * 5 /* 5% */ / tables.count()); + } else { + return false; + } + } + + kdDebug() << "KexiMigrate::progressInitialise() - job size: " << (ulong)sum << endl; + m_progressTotal = sum; + m_progressTotal += tables.count() * NUM_OF_ROWS_PER_CREATE_TABLE; + m_progressTotal = m_progressTotal * 105 / 100; //add 5 percent for above task 1) + m_progressNextReport = sum / 100; + m_progressDone = m_progressTotal * 5 / 100; //5 perecent already done in task 1) + return true; +} + + +void KexiMigrate::updateProgress(Q_ULLONG step) { + m_progressDone += step; + if (m_progressDone >= m_progressNextReport) { + int percent = (m_progressDone+1) * 100 / m_progressTotal; + m_progressNextReport = ((percent + 1) * m_progressTotal) / 100; + kdDebug() << "KexiMigrate::updateProgress(): " << (ulong)m_progressDone << "/" + << (ulong)m_progressTotal << " (" << percent << "%) next report at " + << (ulong)m_progressNextReport << endl; + emit progressPercent(percent); + } +} + +//============================================================================= +// Prompt the user to choose a field type +KexiDB::Field::Type KexiMigrate::userType(const QString& fname) +{ + KInputDialog *dlg; + QStringList types; + QString res; + + types << "Byte"; + types << "Short Integer"; + types << "Integer"; + types << "Big Integer"; + types << "Boolean"; + types << "Date"; + types << "Date Time"; + types << "Time"; + types << "Float"; + types << "Double"; + types << "Text"; + types << "Long Text"; + types << "Binary Large Object"; + + res = dlg->getItem( i18n("Field Type"), + i18n("The data type for %1 could not be determined. " + "Please select one of the following data " + "types").arg(fname), + types, 0, false); + +//! @todo use QMap<QCString, KexiDB::Field::Type> here! + if (res == *types.at(0)) + return KexiDB::Field::Byte; + else if (res == *types.at(1)) + return KexiDB::Field::ShortInteger; + else if (res == *types.at(2)) + return KexiDB::Field::Integer; + else if (res == *types.at(3)) + return KexiDB::Field::BigInteger; + else if (res == *types.at(4)) + return KexiDB::Field::Boolean; + else if (res == *types.at(5)) + return KexiDB::Field::Date; + else if (res == *types.at(6)) + return KexiDB::Field::DateTime; + else if (res == *types.at(7)) + return KexiDB::Field::Time; + else if (res == *types.at(8)) + return KexiDB::Field::Float; + else if (res == *types.at(9)) + return KexiDB::Field::Double; + else if (res == *types.at(10)) + return KexiDB::Field::Text; + else if (res == *types.at(11)) + return KexiDB::Field::LongText; + else if (res == *types.at(12)) + return KexiDB::Field::BLOB; + else + return KexiDB::Field::Text; +} + +QVariant KexiMigrate::propertyValue( const QCString& propName ) +{ + return m_properties[propName.lower()]; +} + +QString KexiMigrate::propertyCaption( const QCString& propName ) const +{ + return m_propertyCaptions[propName.lower()]; +} + +void KexiMigrate::setPropertyValue( const QCString& propName, const QVariant& value ) +{ + m_properties[propName.lower()] = value; +} + +QValueList<QCString> KexiMigrate::propertyNames() const +{ + QValueList<QCString> names = m_properties.keys(); + qHeapSort(names); + return names; +} + +bool KexiMigrate::isValid() +{ + if (KexiMigration::versionMajor() != versionMajor() + || KexiMigration::versionMinor() != versionMinor()) + { + setError(ERR_INCOMPAT_DRIVER_VERSION, + i18n("Incompatible migration driver's \"%1\" version: found version %2, expected version %3.") + .arg(name()) + .arg(QString("%1.%2").arg(versionMajor()).arg(versionMinor())) + .arg(QString("%1.%2").arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor()))); + return false; + } + return true; +} + +bool KexiMigrate::drv_queryMaxNumber(const QString& tableName, + const QString& columnName, int& result) +{ + QString string; + tristate r = drv_querySingleStringFromSQL( + QString::fromLatin1("SELECT MAX(%1) FROM %2").arg(drv_escapeIdentifier(columnName)) + .arg(drv_escapeIdentifier(tableName)), 0, string); + if (r == false) + return false; + if (~r) { + result = 0; + return true; + } + bool ok; + int tmpResult = string.toInt(&ok); + if (ok) + result = tmpResult; + return ok; +} + +tristate KexiMigrate::drv_querySingleStringFromSQL( + const QString& sqlStatement, uint columnNumber, QString& string) +{ + QStringList stringList; + const tristate res = drv_queryStringListFromSQL(sqlStatement, columnNumber, stringList, 1); + if (true == res) + string = stringList.first(); + return res; +} + +#include "keximigrate.moc" diff --git a/kexi/migration/keximigrate.h b/kexi/migration/keximigrate.h new file mode 100644 index 00000000..3d5ed65f --- /dev/null +++ b/kexi/migration/keximigrate.h @@ -0,0 +1,314 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Adam Pigg <[email protected]> + Copyright (C) 2004-2006 Jaroslaw Staniek <[email protected]> + Copyright (C) 2005 Martin Ellis <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXI_MIGRATE_H +#define KEXI_MIGRATE_H + + +#include "kexidb/tableschema.h" +#include "kexidb/connection.h" +#include "keximigratedata.h" + +#include <kgenericfactory.h> +#include <qstringlist.h> +#include <qguardedptr.h> + +class KexiProject; +namespace Kexi +{ + class ObjectStatus; +} + +/*! KexiMigration implementation version. + It is altered after every change: + - major number is increased after every major Kexi release, + - minor is increased after adding binary-incompatible change. + In external code: do not use this to get library version information: + use KexiMigration::versionMajor() and KexiMigration::versionMinor() instead to get real version. +*/ +#define KEXI_MIGRATION_VERSION_MAJOR 1 +#define KEXI_MIGRATION_VERSION_MINOR 1 + +/*! + * \namespace KexiMigration + * \brief Framework for importing databases into native KexiDB databases. + */ +namespace KexiMigration +{ + +//! \return KexiMigration version info (most significant part) +KEXIMIGR_EXPORT int versionMajor(); + +//! \return KexiMigration version info (least significant part) +KEXIMIGR_EXPORT int versionMinor(); + + +//! @short Imports non-native databases into Kexi projects. +/*! A generic API for importing schema and data from an existing +database into a new Kexi project. Can be also used for importing native Kexi databases. + +Basic idea is this: +-# User selects an existing DB and new project (file or server based) +-# User specifies whether to import structure and data or structure only. +-# Import tool connects to db +-# Checks if it is already a kexi project (not implemented yet) +-# If not, then read structure and construct new project +-# Ask user what to do if column type is not supported + +See kexi/doc/dev/kexi_import.txt for more info. +*/ +class KEXIMIGR_EXPORT KexiMigrate : public QObject, public KexiDB::Object +{ + Q_OBJECT + + public: + virtual ~KexiMigrate(); + +//! @todo Remove this! KexiMigrate should be usable for multiple concurrent migrations! + KexiMigration::Data* data() const { return m_migrateData; } + +//! @todo Remove this! KexiMigrate should be usable for multiple concurrent migrations! + //! Data Setup. Requires two connection objects, a name and a bool + void setData(KexiMigration::Data* migrateData); + + /*! Checks whether the destination database exists. + For file-based dest. projects, we've already asked about overwriting + existing project but for server-based projects it's better to ask user. + This method should be called before performImport() or performExport(). + + \return true if no connection-related errors occurred. + \a acceptingNeeded is set to true if destination database exists. + In this case you should ask about accepting database overwriting. + Used in ImportWizard::import(). */ + bool checkIfDestinationDatabaseOverwritingNeedsAccepting(Kexi::ObjectStatus* result, + bool& acceptingNeeded); + + /*! Checks if the source- and the destination databases are identical. + \return true if they are identical else false. */ + bool isSourceAndDestinationDataSourceTheSame() const; + + //! Perform an import operation + bool performImport(Kexi::ObjectStatus* result = 0); + + //! Perform an export operation + bool performExport(Kexi::ObjectStatus* result = 0); + + //! Returns true if the migration driver supports progress updates. + inline bool progressSupported() { return drv_progressSupported(); } + + virtual int versionMajor() const = 0; + virtual int versionMinor() const = 0; + +//! @todo This is copied from KexiDB::Driver. One day it will be merged with KexiDB. + //! \return property value for \a propeName available for this driver. + //! If there's no such property defined for driver, Null QVariant value is returned. + virtual QVariant propertyValue( const QCString& propName ); + +//! @todo This is copied from KexiDB::Driver. One day it will be merged with KexiDB. + void setPropertyValue( const QCString& propName, const QVariant& value ); + +//! @todo This is copied from KexiDB::Driver. One day it will be merged with KexiDB. + //! \return translated property caption for \a propeName. + //! If there's no such property defined for driver, empty string value is returned. + QString propertyCaption( const QCString& propName ) const; + +//! @todo This is copied from KexiDB::Driver. One day it will be merged with KexiDB. + //! \return a list of property names available for this driver. + QValueList<QCString> propertyNames() const; + + /*! \return true is driver is valid. Checks if KexiMigrate::versionMajor() + and KexiMigrate::versionMinor() are matching. + You can reimplement this but always call KexiMigrate::isValid() implementation. */ + virtual bool isValid(); + + signals: + void progressPercent(int percent); + + protected: + //! Used by MigrateManager. + KexiMigrate(QObject *parent, const char *name, const QStringList &args = QStringList()); + + //! Connect to source database (driver specific) + virtual bool drv_connect() = 0; + //! Disconnect from source database (driver specific) + virtual bool drv_disconnect() = 0; + + //! Get table names in source database (driver specific) + virtual bool drv_tableNames(QStringList& tablenames) = 0; + + //! Read schema for a given table (driver specific) + virtual bool drv_readTableSchema( + const QString& originalName, KexiDB::TableSchema& tableSchema) = 0; + + /*! Fetches maximum number from table \a tableName, column \a columnName + into \a result. On success true is returned. If there is no records in the table, + \a result is set to 0 and true is returned. + - Note 1: implement only if the database can already contain kexidb__* tables + (so e.g. keximdb driver doea not need this). + - Note 2: default implementation uses drv_querySingleStringFromSQL() + with "SELECT MAX(columName) FROM tableName" statement, assuming SQL-compliant + backend. + */ + virtual bool drv_queryMaxNumber(const QString& tableName, + const QString& columnName, int& result); + + /*! Fetches single string at column \a columnNumber for each record from result obtained + by running \a sqlStatement. \a numRecords can be specified to limit number of records read. + If \a numRecords is -1, all records are loaded. + On success the result is stored in \a stringList and true is returned. + \return cancelled if there are no records available. + - Note: implement only if the database can already contain kexidb__* tables + (so e.g. keximdb driver does not need this). */ +//! @todo SQL-dependent! + virtual tristate drv_queryStringListFromSQL( + const QString& sqlStatement, uint columnNumber, QStringList& stringList, + int numRecords = -1) + { Q_UNUSED(sqlStatement); Q_UNUSED(columnNumber); Q_UNUSED(stringList); + Q_UNUSED(numRecords); + return cancelled; } + + /*! Fetches single string at column \a columnNumber from result obtained + by running \a sqlStatement. + On success the result is stored in \a string and true is returned. + \return cancelled if there are no records available. + This implementation uses drv_queryStringListFromSQL() with numRecords == 1. */ +//! @todo SQL-dependent! + virtual tristate drv_querySingleStringFromSQL(const QString& sqlStatement, + uint columnNumber, QString& string); + + /*! Fetches single record from result obtained + by running \a sqlStatement. + \a firstRecord should be first initialized to true, so the method can run + the query at first call and then set it will set \a firstRecord to false, + so subsequent calls will only fetch records. + On success the result is stored in \a data and true is returned, + \a data is resized to appropriate size. cancelled is returned on EOF. */ +//! @todo SQL-dependent! + virtual tristate drv_fetchRecordFromSQL(const QString& sqlStatement, + KexiDB::RowData& data, bool &firstRecord) + { Q_UNUSED(sqlStatement); Q_UNUSED(data); Q_UNUSED(firstRecord); + return cancelled; } + + //! Copy a table from source DB to target DB (driver specific) + //! - create copies of KexiDB tables + //! - create copies of non-KexiDB tables + virtual bool drv_copyTable(const QString& srcTable, KexiDB::Connection *destConn, + KexiDB::TableSchema* dstTable) = 0; + + virtual bool drv_progressSupported() { return false; } + + /*! \return the size of a table to be imported, or 0 if not supported + Finds the size of the named table, in order to provide feedback on + migration progress. + + The units of the return type are deliberately unspecified. Migration + drivers may return the number of records in the table, or the size in + bytes, etc. Units should be chosen in order that the driver can + return the size in the fastest way possible (e.g. migration from CSV + files should use file size to avoid counting the number of rows, and + migration from MDB files should return the number of rows as this is + stored within the file). + + Obviously, the driver should use the same units when reporting + migration progress. + + \return size of the specified table + */ + virtual bool drv_getTableSize(const QString&, Q_ULLONG&) + { return false; } + + void updateProgress(Q_ULLONG step = 1ULL); + +//! @todo user should be asked ONCE using a convenient wizard's page, not a popup dialog + //! Prompt user to select a field type for unrecognized fields + KexiDB::Field::Type userType(const QString& fname); + + virtual QString drv_escapeIdentifier( const QString& str ) const { + return m_kexiDBDriver ? m_kexiDBDriver->escapeIdentifier(str) : str; } + +//! @todo Remove this! KexiMigrate should be usable for multiple concurrent migrations! + //! Migrate Options + KexiMigration::Data* m_migrateData; + +// // Temporary values used during import (set by driver specific methods) +// KexiDB::Field* m_f; + + /*! Driver properties dictionary (indexed by name), + useful for presenting properties to the user. + Set available properties here in driver implementation. */ + QMap<QCString,QVariant> m_properties; + + /*! i18n'd captions for properties. You do not need + to set predefined properties' caption in driver implementation + -it's done automatically. */ + QMap<QCString,QString> m_propertyCaptions; + + //! KexiDB driver. For instance, it is used for escaping identifiers + QGuardedPtr<KexiDB::Driver> m_kexiDBDriver; + + private: + //! Get the list of tables + bool tableNames(QStringList& tablenames); + + //! Table schemas from source DB + QPtrList<KexiDB::TableSchema> m_tableSchemas; + + QPtrList<KexiDB::TableSchema> m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport; + + /*! Estimate size of migration job + Calls drv_getTableSize for each table to be copied. + \return sum of the size of all tables to be copied. + */ + bool progressInitialise(); + + KexiProject *m_destPrj; + + //! Size of migration job + Q_ULLONG m_progressTotal; + + //! Amount of migration job complete + Q_ULLONG m_progressDone; + + //! Don't recalculate progress done until this value is reached. + Q_ULLONG m_progressNextReport; + + friend class MigrateManager; +}; + +} //namespace KexiMigration + +//! Driver's static version information (implementation), +//! with KLibFactory symbol declaration. +#define KEXIMIGRATE_DRIVER_INFO( class_name, internal_name ) \ + int class_name::versionMajor() const { return KEXI_MIGRATION_VERSION_MAJOR; } \ + int class_name::versionMinor() const { return KEXI_MIGRATION_VERSION_MINOR; } \ + K_EXPORT_COMPONENT_FACTORY(keximigrate_ ## internal_name, \ + KGenericFactory<KexiMigration::class_name>( "keximigrate_" #internal_name )) + +/*! Driver's static version information, automatically implemented for KexiDB drivers. + Put this into migration driver class declaration just like Q_OBJECT macro. */ +#define KEXIMIGRATION_DRIVER \ + public: \ + virtual int versionMajor() const; \ + virtual int versionMinor() const; + +#endif + diff --git a/kexi/migration/keximigratedata.cpp b/kexi/migration/keximigratedata.cpp new file mode 100644 index 00000000..80df12f2 --- /dev/null +++ b/kexi/migration/keximigratedata.cpp @@ -0,0 +1,34 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Adam Pigg <[email protected]> + Copyright (C) 2004 Jaroslaw Staniek <[email protected]> + Copyright (C) 2005 Martin Ellis <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "keximigratedata.h" + +using namespace KexiMigration; + +Data::Data() + : source(0) + , destination(0) +{ +} + +Data::~Data() +{ +} diff --git a/kexi/migration/keximigratedata.h b/kexi/migration/keximigratedata.h new file mode 100644 index 00000000..81e8c0f5 --- /dev/null +++ b/kexi/migration/keximigratedata.h @@ -0,0 +1,59 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Adam Pigg <[email protected]> + Copyright (C) 2004 Jaroslaw Staniek <[email protected]> + Copyright (C) 2005 Martin Ellis <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXI_MIGRATE_DATA_H +#define KEXI_MIGRATE_DATA_H + +#include "kexidb/connection.h" + +class KexiProjectData; + +namespace KexiMigration +{ + //Use this class to store all possible options that could be used by keximigrate. + //The current members are not meant to be a definite set, for example, i envisage + //adding table/field lists if we allow only importing certain tables/fields + class KEXIMIGR_EXPORT Data + { + public: + Data(); + ~Data(); + + //! Connection data for the source database + KexiDB::ConnectionData* source; + + //! Name of the source database + QString sourceName; + + //! Destination project data + KexiProjectData* destination; + +// //! Actual connection to the new database +// KexiDB::Connection* dest; + +// //! New database name +// QString destName; + + //! Flag to determine structure copy, or structure + data + bool keepData; + }; +}//namespace KexiMigration +#endif diff --git a/kexi/migration/keximigratetest.cpp b/kexi/migration/keximigratetest.cpp new file mode 100644 index 00000000..bb67d985 --- /dev/null +++ b/kexi/migration/keximigratetest.cpp @@ -0,0 +1,49 @@ +/*************************************************************************** + * Copyright (C) 2004 by Adam Pigg * + * [email protected] * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +//#include <config.h> +#endif + +#include <migration/importwizard.h> +#include <kapplication.h> + +/* +This is in no way meant to compile let alone work +This is very preliminary and is meant for example only + +This will be an example program to demonstrate how to import an existing db into +a new kexi based db +*/ + +using namespace KexiMigration; + +int main(int argc, char *argv[]) +{ + KApplication app(argc, argv, "Kexi Migrate Test"); + + ImportWizard* iw = new ImportWizard(); + iw->setGeometry(300,300,300,250); + app.setMainWidget(iw); + iw->show(); + + return app.exec(); +} diff --git a/kexi/migration/keximigration_driver.desktop b/kexi/migration/keximigration_driver.desktop new file mode 100644 index 00000000..9ccb6ff2 --- /dev/null +++ b/kexi/migration/keximigration_driver.desktop @@ -0,0 +1,59 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Kexi/MigrationDriver +Comment=Kexi Data Migration Driver +Comment[bg]=Драйвер на Kexi за мигриране на данни +Comment[ca]=Controlador de migració de dades de Kexi +Comment[cy]=Gyrrydd Mudo Data Kexi +Comment[da]=Kexi datamigrationsdriver +Comment[de]=Datenmigrationstreiber für Kexi +Comment[el]=Οδηγός μεταφοράς δεδομένων του Kexi +Comment[eo]=Kexi datum-migrada pelilo +Comment[es]=Controlador de migración de datos de Kexi +Comment[et]=Kexi andmete migreerumisdraiver +Comment[eu]=Kexi-ren datuak migratzeko kontrolatzailea +Comment[fa]=گردانندۀ جابهجایی دادۀ Kexi +Comment[fi]=Kexi tietojen yhdistämisajuri +Comment[fr]=Pilote de migration de données de Kexi +Comment[fy]=Kexi Gegevensmigraasje stjoerprogramma +Comment[gl]=Controlador de Migración de Datos de Kexi +Comment[he]=מנהל התקן Data-Migration ל־Kexi +Comment[hr]=Kexi upravljački program za migraciju podataka +Comment[hu]=Kexi adatmigrálási meghajtó +Comment[is]=Kexi gagnaflutningsrekill +Comment[it]=Driver di migrazione dei dati per Kexi +Comment[ja]=Kexi データ移行ドライバ +Comment[km]=កម្មវិធីបញ្ជាសម្រាប់ផ្លាស់ប្ដូរទិន្នន័យសម្រាប់ Kexi +Comment[lv]=Kexi datu migrācijas draiveris +Comment[ms]=Pemacu Migrasi Data Kexi +Comment[nb]=Kexi-driver for datamigrering +Comment[nds]=Datenutlagerndriever för Kexi +Comment[ne]=केक्सी डेटा माइग्रेसन ड्राइभर +Comment[nl]=Kexi Datamigratie Stuurprogramma +Comment[nn]=Kexi-programtillegg for migrering av data +Comment[pl]=Wtyczka do migracji danych programu Kexi +Comment[pt]=Controlador de Migração de Dados do Kexi +Comment[pt_BR]=Driver de Migração de Dados do Kexi +Comment[ru]=Модуль драйвера миграции Kexi +Comment[sk]=Ovládač Kexi Data Migration +Comment[sl]=Gonilnik za prenos podatkov za Kexi +Comment[sr]=Драјвер Kexi-ја за миграцију података +Comment[sr@Latn]=Drajver Kexi-ja za migraciju podataka +Comment[sv]=Kexi dataövergångsdrivrutin +Comment[uk]=Драйвер міграції даних для Kexi +Comment[uz]=Kexi uchun maʼlumotlar migratsiyasi drayveri +Comment[uz@cyrillic]=Kexi учун маълумотлар миграцияси драйвери +Comment[zh_CN]=Kexi 数据升迁驱动程序 +Comment[zh_TW]=Kexi 資料轉移驅動程式 + +[PropertyDef::X-Kexi-FileMigrationDriverMime] +Type=QString + +[PropertyDef::X-Kexi-MigrationDriverName] +Type=QString + +[PropertyDef::X-Kexi-MigrationDriverType] +Type=QString + +[PropertyDef::X-Kexi-KexiMigrationVersion] +Type=QString diff --git a/kexi/migration/migratemanager.cpp b/kexi/migration/migratemanager.cpp new file mode 100644 index 00000000..320ad718 --- /dev/null +++ b/kexi/migration/migratemanager.cpp @@ -0,0 +1,384 @@ +/* This file is part of the KDE project + Daniel Molkentin <[email protected]> + Joseph Wenninger <[email protected]> + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "migratemanager.h" +#include "migratemanager_p.h" +#include "keximigrate.h" + +#include <klibloader.h> +#include <kparts/componentfactory.h> +#include <ktrader.h> +#include <kdebug.h> +#include <klocale.h> +#include <kservice.h> + +#include <assert.h> + +#include <qapplication.h> + +//remove debug +#undef KexiDBDbg +#define KexiDBDbg if (0) kdDebug() + +using namespace KexiMigration; + +MigrateManagerInternal* MigrateManagerInternal::s_self = 0L; + +/*! @todo + Temporary, needed because MigrateManagerInternal::m_drivers is autodeleted + drivers currently own KexiMigrate::Data members so these are destroyed when + last MigrateManager instance is deleted. Remove this hack when + KexiMigrate is splitted into Driver and Connection. */ +MigrateManager __manager; + +MigrateManagerInternal::MigrateManagerInternal() /* protected */ + : QObject( 0, "KexiMigrate::MigrateManagerInternal" ) + , Object() + , m_drivers(17, false) + , m_refCount(0) + , lookupDriversNeeded(true) +{ + m_drivers.setAutoDelete(true); + m_serverResultNum=0; + +} + +MigrateManagerInternal::~MigrateManagerInternal() +{ + KexiDBDbg << "MigrateManagerInternal::~MigrateManagerInternal()" << endl; + m_drivers.clear(); + if ( s_self == this ) + s_self = 0; + KexiDBDbg << "MigrateManagerInternal::~MigrateManagerInternal() ok" << endl; +} + +void MigrateManagerInternal::slotAppQuits() +{ + if (qApp->mainWidget() && qApp->mainWidget()->isVisible()) + return; //what a hack! - we give up when app is still there + KexiDBDbg << "MigrateManagerInternal::slotAppQuits(): let's clear drivers..." << endl; + m_drivers.clear(); +} + +MigrateManagerInternal *MigrateManagerInternal::self() +{ + if (!s_self) + s_self = new MigrateManagerInternal(); + + return s_self; +} + +bool MigrateManagerInternal::lookupDrivers() +{ + if (!lookupDriversNeeded) + return true; + + if (qApp) { + connect(qApp,SIGNAL(aboutToQuit()),this,SLOT(slotAppQuits())); + } +//TODO: for QT-only version check for KInstance wrapper +// KexiDBWarn << "DriverManagerInternal::lookupDrivers(): cannot work without KInstance (KGlobal::instance()==0)!" << endl; +// setError("Driver Manager cannot work without KInstance (KGlobal::instance()==0)!"); + + lookupDriversNeeded = false; + clearError(); + KTrader::OfferList tlist = KTrader::self()->query("Kexi/MigrationDriver"); + KTrader::OfferList::ConstIterator it(tlist.constBegin()); + for(; it != tlist.constEnd(); ++it) + { + KService::Ptr ptr = (*it); + QString srv_name = ptr->property("X-Kexi-MigrationDriverName").toString(); + if (srv_name.isEmpty()) { + KexiDBWarn << "MigrateManagerInternal::lookupDrivers(): " + "X-Kexi-MigrationDriverName must be set for migration driver \"" + << ptr->property("Name").toString() << "\" service!\n -- skipped!" << endl; + continue; + } + if (m_services_lcase.contains(srv_name.lower())) { + continue; + } + +//! @todo could be merged. Copied from KexiDB::DriverManager. +//<COPIED> + QString srv_ver_str = ptr->property("X-Kexi-KexiMigrationVersion").toString(); + QStringList lst( QStringList::split(".", srv_ver_str) ); + int minor_ver, major_ver; + bool ok = (lst.count() == 2); + if (ok) + major_ver = lst[0].toUInt(&ok); + if (ok) + minor_ver = lst[1].toUInt(&ok); + if (!ok) { + KexiDBWarn << "MigrateManagerInternal::lookupDrivers(): problem with detecting '" + << srv_name.lower() << "' driver's version -- skipping it!" << endl; + possibleProblems += QString("\"%1\" migration driver has unrecognized version; " + "required driver version is \"%2.%3\"") + .arg(srv_name.lower()) + .arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor()); + continue; + } + if (major_ver != KexiMigration::versionMajor() || minor_ver != KexiMigration::versionMinor()) { + KexiDBWarn << QString("MigrateManagerInternal::lookupDrivers(): '%1' driver" + " has version '%2' but required migration driver version is '%3.%4'\n" + " -- skipping this driver!").arg(srv_name.lower()).arg(srv_ver_str) + .arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor()) << endl; + possibleProblems += QString("\"%1\" migration driver has version \"%2\" " + "but required driver version is \"%3.%4\"") + .arg(srv_name.lower()).arg(srv_ver_str) + .arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor()); + continue; + } +//</COPIED> + + QString mime = ptr->property("X-Kexi-FileDBDriverMime").toString().lower(); + QString drvType = ptr->property("X-Kexi-MigrationDriverType").toString().lower(); + if (drvType=="file") { + if (!mime.isEmpty()) { + if (!m_services_by_mimetype.contains(mime)) { + m_services_by_mimetype.insert(mime, ptr); + } + else { + KexiDBWarn << "MigrateManagerInternal::lookupDrivers(): more than one driver for '" + << mime << "' mime type!" << endl; + } + } + } + m_services.insert(srv_name, ptr); + m_services_lcase.insert(srv_name.lower(), ptr); + KexiDBDbg << "MigrateManager::lookupDrivers(): registered driver: " << ptr->name() + << "(" << ptr->library() << ")" << endl; + } + + if (tlist.isEmpty()) + { + setError(ERR_DRIVERMANAGER, i18n("Could not find any import/export database drivers.") ); + return false; + } + return true; +} + +KexiMigrate* MigrateManagerInternal::driver(const QString& name) +{ + if (!lookupDrivers()) + return 0; + + clearError(); + KexiDBDbg << "MigrationrManagerInternal::migrationDriver(): loading " << name << endl; + + KexiMigrate *drv = name.isEmpty() ? 0 : m_drivers.find(name.latin1()); + if (drv) + return drv; //cached + + if (!m_services_lcase.contains(name.lower())) { + setError(ERR_DRIVERMANAGER, i18n("Could not find import/export database driver \"%1\".").arg(name) ); + return 0; + } + + KService::Ptr ptr= *(m_services_lcase.find(name.lower())); + QString srv_name = ptr->property("X-Kexi-MigrationDriverName").toString(); + + KexiDBDbg << "MigrateManagerInternal::driver(): library: "<<ptr->library()<<endl; + drv = KParts::ComponentFactory::createInstanceFromService<KexiMigrate>(ptr, + this, srv_name.latin1(), QStringList(),&m_serverResultNum); + + if (!drv) { + setError(ERR_DRIVERMANAGER, i18n("Could not load import/export database driver \"%1\".") + .arg(name) ); + if (m_componentLoadingErrors.isEmpty()) {//fill errtable on demand + m_componentLoadingErrors[KParts::ComponentFactory::ErrNoServiceFound]="ErrNoServiceFound"; + m_componentLoadingErrors[KParts::ComponentFactory::ErrServiceProvidesNoLibrary]="ErrServiceProvidesNoLibrary"; + m_componentLoadingErrors[KParts::ComponentFactory::ErrNoLibrary]="ErrNoLibrary"; + m_componentLoadingErrors[KParts::ComponentFactory::ErrNoFactory]="ErrNoFactory"; + m_componentLoadingErrors[KParts::ComponentFactory::ErrNoComponent]="ErrNoComponent"; + } + m_serverResultName=m_componentLoadingErrors[m_serverResultNum]; + return 0; + } + KexiDBDbg << "MigrateManagerInternal::driver(): loading succeed: " << name <<endl; + KexiDBDbg << "drv="<<(long)drv <<endl; + +// drv->setName(srv_name.latin1()); +// drv->d->service = ptr; //store info +// drv->d->fileDBDriverMimeType = ptr->property("X-Kexi-FileDBDriverMime").toString(); +// drv->d->initInternalProperties(); + + if (!drv->isValid()) { + setError(drv); + delete drv; + return 0; + } + + m_drivers.insert(name.latin1(), drv); //cache it + return drv; +} + +void MigrateManagerInternal::incRefCount() +{ + m_refCount++; + KexiDBDbg << "MigrateManagerInternal::incRefCount(): " << m_refCount << endl; +} + +void MigrateManagerInternal::decRefCount() +{ + m_refCount--; + KexiDBDbg << "MigrateManagerInternal::decRefCount(): " << m_refCount << endl; +// if (m_refCount<1) { +// KexiDBDbg<<"KexiDB::DriverManagerInternal::decRefCount(): reached m_refCount<1 -->deletelater()"<<endl; +// s_self=0; +// deleteLater(); +// } +} + +// --------------------------- +// --- DriverManager impl. --- +// --------------------------- + +MigrateManager::MigrateManager() + : QObject( 0, "KexiMigrate::MigrateManager" ) + , Object() + , d_int( MigrateManagerInternal::self() ) +{ + d_int->incRefCount(); +// if ( !s_self ) +// s_self = this; +// lookupDrivers(); +} + +MigrateManager::~MigrateManager() +{ + KexiDBDbg << "MigrateManager::~MigrateManager()" << endl; +/* Connection *conn; + for ( conn = m_connections.first(); conn ; conn = m_connections.next() ) { + conn->disconnect(); + conn->m_driver = 0; //don't let the connection touch our driver now + m_connections.remove(); + delete conn; + }*/ + + d_int->decRefCount(); + if (d_int->m_refCount==0) { + //delete internal drv manager! + delete d_int; + } +// if ( s_self == this ) + //s_self = 0; + KexiDBDbg << "MigrateManager::~MigrateManager() ok" << endl; +} + + +const QStringList MigrateManager::driverNames() +{ + if (!d_int->lookupDrivers()) { + kdDebug() << "MigrateManager::driverNames() lookupDrivers failed" << endl; + return QStringList(); + } + + if (d_int->m_services.isEmpty()) { + kdDebug() << "MigrateManager::driverNames() MigrateManager::ServicesMap is empty" << endl; + return QStringList(); + } + + if (d_int->error()) { + kdDebug() << "MigrateManager::driverNames() Error: " << d_int->errorMsg() << endl; + return QStringList(); + } + + return d_int->m_services.keys(); +} + +QString MigrateManager::driverForMimeType(const QString &mimeType) +{ + if (!d_int->lookupDrivers()) { + kdDebug() << "MigrateManager::driverForMimeType() lookupDrivers() failed" << endl; + setError(d_int); + return 0; + } + + KService::Ptr ptr = d_int->m_services_by_mimetype[mimeType.lower()]; + if (!ptr) { + kdDebug() << QString("MigrateManager::driverForMimeType(%1) No such mimetype").arg(mimeType) << endl; + return QString::null; + } + + return ptr->property("X-Kexi-MigrationDriverName").toString(); +} + +KexiMigrate* MigrateManager::driver(const QString& name) +{ + KexiMigrate *drv = d_int->driver(name); + if (d_int->error()) { + kdDebug() << QString("MigrateManager::driver(%1) Error: %2").arg(name).arg(d_int->errorMsg()) << endl; + setError(d_int); + } + return drv; +} + +QString MigrateManager::serverErrorMsg() +{ + return d_int->m_serverErrMsg; +} + +int MigrateManager::serverResult() +{ + return d_int->m_serverResultNum; +} + +QString MigrateManager::serverResultName() +{ + return d_int->m_serverResultName; +} + +void MigrateManager::drv_clearServerResult() +{ + d_int->m_serverErrMsg=QString::null; + d_int->m_serverResultNum=0; + d_int->m_serverResultName=QString::null; +} + +QString MigrateManager::possibleProblemsInfoMsg() const +{ + if (d_int->possibleProblems.isEmpty()) + return QString::null; + QString str; + str.reserve(1024); + str = "<ul>"; + for (QStringList::ConstIterator it = d_int->possibleProblems.constBegin(); + it!=d_int->possibleProblems.constEnd(); ++it) + { + str += (QString::fromLatin1("<li>") + *it + QString::fromLatin1("</li>")); + } + str += "</ul>"; + return str; +} + +//------------------------ + +int KexiMigration::versionMajor() +{ + return KEXI_MIGRATION_VERSION_MAJOR; +} + +int KexiMigration::versionMinor() +{ + return KEXI_MIGRATION_VERSION_MINOR; +} + +#include "migratemanager_p.moc" diff --git a/kexi/migration/migratemanager.h b/kexi/migration/migratemanager.h new file mode 100644 index 00000000..c876ab91 --- /dev/null +++ b/kexi/migration/migratemanager.h @@ -0,0 +1,82 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXI_MIGRATION_MNGR_H +#define KEXI_MIGRATION_MNGR_H + +#include <qobject.h> +#include <qcstring.h> +#include <qmap.h> +#include <qdict.h> + +#include <klibloader.h> +#include <kservice.h> + +#include "keximigrate.h" + +namespace KexiMigration { + +class MigrateManagerInternal; + +//! @short Migration library management, for finding and loading mogration drivers. +class KEXIMIGR_EXPORT MigrateManager : public QObject, public KexiDB::Object +{ + public: + typedef QMap<QString, KService::Ptr> ServicesMap; + + MigrateManager(); + virtual ~MigrateManager(); + + /*! Tries to load db driver with named name \a name. + The name is case insensitive. + \return db driver, or 0 if error (then error message is also set) */ + KexiMigrate* driver(const QString& name); + + /*! returns list of available drivers names. + That drivers can be loaded by first use of driver() method. */ + const QStringList driverNames(); + + /*! Looks up a drivers list by MIME type of database file. + Only file-based database drivers are checked. + The lookup is case insensitive. + \return driver name or null string if no driver found. + */ + QString driverForMimeType(const QString &mimeType); + + //! server error is set if there is error at KService level (useful for debugging) + virtual QString serverErrorMsg(); + virtual int serverResult(); + virtual QString serverResultName(); + +//! @todo copied from KexiDB::DriverManager, merge it. + /*! HTML information about possible problems encountered. + It's displayed in 'details' section, if an error encountered. + Currently it contains a list of incompatible migration drivers. */ + QString possibleProblemsInfoMsg() const; + + protected: + virtual void drv_clearServerResult(); + + private: + MigrateManagerInternal *d_int; +}; + +} //namespace KexiMigrate + +#endif diff --git a/kexi/migration/migratemanager_p.h b/kexi/migration/migratemanager_p.h new file mode 100644 index 00000000..0d90acfe --- /dev/null +++ b/kexi/migration/migratemanager_p.h @@ -0,0 +1,85 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXI_MIGRATE_MNGR_P_H +#define KEXI_MIGRATE_MNGR_P_H + +#include <qobject.h> +#include <qasciidict.h> + +namespace KexiMigration { + +/*! Internal class of driver manager. +*/ +class MigrateManagerInternal : public QObject, public KexiDB::Object +{ + Q_OBJECT + public: + ~MigrateManagerInternal(); + + /*! Tries to load db driver \a name. + \return db driver, or 0 if error (then error message is also set) */ + KexiMigrate* driver(const QString& name); + + static MigrateManagerInternal *self(); + + /*! increments the refcount for the manager */ + void incRefCount(); + + /*! decrements the refcount for the manager + if the refcount reaches a value less than 1 the manager is freed */ + void decRefCount(); + + protected slots: + /*! Used to destroy all drivers on QApplication quit, so even if there are + DriverManager's static instances that are destroyed on program + "static destruction", drivers are not kept after QApplication death. + */ + void slotAppQuits(); + + protected: + /*! Used by self() */ + MigrateManagerInternal(); + + bool lookupDrivers(); + + static MigrateManagerInternal* s_self; + + MigrateManager::ServicesMap m_services; //! services map + MigrateManager::ServicesMap m_services_lcase; //! as above but service names in lowercase + MigrateManager::ServicesMap m_services_by_mimetype; + + QAsciiDict<KexiMigrate> m_drivers; + ulong m_refCount; + + QString m_serverErrMsg; + int m_serverResultNum; + QString m_serverResultName; + //! result names for KParts::ComponentFactory::ComponentLoadingError + QMap<int,QString> m_componentLoadingErrors; + + bool lookupDriversNeeded : 1; + + QStringList possibleProblems; + + friend class MigrateManager; +}; +} + +#endif diff --git a/kexi/migration/mysql/Makefile.am b/kexi/migration/mysql/Makefile.am new file mode 100644 index 00000000..635bbf1d --- /dev/null +++ b/kexi/migration/mysql/Makefile.am @@ -0,0 +1,18 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = keximigrate_mysql.la + +INCLUDES = -I$(srcdir)/../../.. -I$(top_srcdir)/kexi $(all_includes) -I$(MYSQL_INC) + +keximigrate_mysql_la_METASOURCES = AUTO + +keximigrate_mysql_la_SOURCES = mysqlmigrate.cpp + +keximigrate_mysql_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) ../libkeximigrate.la $(MYSQL_LIBS) -lmysqlclient + +keximigrate_mysql_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO) -no-undefined + +kde_services_DATA = keximigrate_mysql.desktop + +noinst_HEADERS = mysqlmigrate.h + diff --git a/kexi/migration/mysql/keximigrate_mysql.desktop b/kexi/migration/mysql/keximigrate_mysql.desktop new file mode 100644 index 00000000..529ad459 --- /dev/null +++ b/kexi/migration/mysql/keximigrate_mysql.desktop @@ -0,0 +1,54 @@ +[Desktop Entry] +Name=MySQL +Name[ne]=मेरो एसक्यूएल +Name[sk]=mySQL +Comment=MySQL Migration Driver for Kexi +Comment[bg]=Драйвер за мигриране от MySQL към Kexi +Comment[ca]=Controlador de migració de MySQL per a Kexi +Comment[cy]=Gyrrydd Mudo MySQL ar gyfer Kexi +Comment[da]=MySQL Migrationsdriver for Kexi +Comment[de]=MySQL-Migrationstreiber für Kexi +Comment[el]=Οδηγός μεταφοράς MySQL του Kexi +Comment[eo]=MySQL-migrada pelilo por Kexi +Comment[es]=Controlador de migración a MySQL para Kexi +Comment[et]=Kexi MySQL migreerumisdraiver +Comment[eu]=Kexi-ren MySQL migraziorako kontrolatzailea +Comment[fa]=گردانندۀ جابهجایی MySQL برای Kexi +Comment[fi]=MySQL yhdistäjäajuri Kexille +Comment[fr]=Pilote de migration MySQL pour Kexi +Comment[fy]=MySQL-Migraasjestjoerprogramma foar Kexi +Comment[gl]=Controlador de Migración de MySQL de Kexi +Comment[hr]=MySQL upravljački program migracije podataka za Kexi +Comment[hu]=Kexi MySQL-migrálási meghajtó +Comment[is]=MySQL gagnaflutningsrekill fyrir Kexi +Comment[it]=Driver di migrazione MySQL per Kexi +Comment[ja]=Kexi MySQL データ移行ドライバ +Comment[km]=កម្មវិធីបញ្ជាសម្រាប់ផ្លាស់ប្ដូរ MySQL សម្រាប់ Kexi +Comment[lv]=MySQL datu migrācijas draiveris priekš Kexi +Comment[ms]=Pemacu Migrasi MySQL bagi Kexi +Comment[nb]=Kexi-programmodul for migrering av MySQL-drivere +Comment[nds]=MySQL-Datenutlagerndriever för Kexi +Comment[ne]=केक्सीका लागि MySQL माइग्रेसन ड्राइभर +Comment[nl]=MySQL-migratiestuurprogramma voor Kexi +Comment[nn]=Kexi-programmodul for migrering av MySQL-drivarar +Comment[pl]=Wtyczka migracji danych z serwera MySQL dla Kexi +Comment[pt]=Controlador de Migração de MySQL do Kexi +Comment[pt_BR]=Driver de Migração do MySQL para o Kexi +Comment[ru]=Драйвер миграции MySQL для Kexi +Comment[sk]=Ovládač MySQL Migration Driver pre Kexi +Comment[sl]=Gonilnik MySQL za prenos podatkov za Kexi +Comment[sr]=Драјвер Kexi-ја за миграцију са MySQL-а +Comment[sr@Latn]=Drajver Kexi-ja za migraciju sa MySQL-a +Comment[sv]=MySQL-övergångsdrivrutin för Kexi +Comment[uk]=MySQL драйвер міграції даних для Kexi +Comment[uz]=Kexi uchun MySQL migratsiya drayveri +Comment[uz@cyrillic]=Kexi учун MySQL миграция драйвери +Comment[zh_CN]=Kexi MySQL 升迁驱动程序 +Comment[zh_TW]=Kexi 的 MySQL 轉移驅動程式 +X-KDE-Library=keximigrate_mysql +ServiceTypes=Kexi/MigrationDriver +Type=Service +InitialPreference=8 +X-Kexi-MigrationDriverName=MySQL +X-Kexi-MigrationDriverType=Network +X-Kexi-KexiMigrationVersion=1.1 diff --git a/kexi/migration/mysql/mysqlmigrate.cpp b/kexi/migration/mysql/mysqlmigrate.cpp new file mode 100644 index 00000000..a2c62dd5 --- /dev/null +++ b/kexi/migration/mysql/mysqlmigrate.cpp @@ -0,0 +1,522 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Martin Ellis <[email protected]> + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "mysqlmigrate.h" + +#include <qstring.h> +#include <qregexp.h> +#include <qfile.h> +#include <qvariant.h> +#include <qvaluelist.h> +#include <kdebug.h> + +#include <mysql_version.h> +#include <mysql.h> + +#include "migration/keximigratedata.h" +#include <kexidb/cursor.h> +#include <kexidb/field.h> +#include <kexidb/utils.h> +#include <kexidb/drivers/mySQL/mysqlconnection_p.cpp> +#include <kexidb/drivermanager.h> +#include <kexiutils/identifier.h> + +using namespace KexiMigration; + +/* This is the implementation for the MySQL specific import routines. */ + +KEXIMIGRATE_DRIVER_INFO( MySQLMigrate, mysql ) + +/* ************************************************************************** */ +//! Constructor +/*MySQLMigrate::MySQLMigrate() : + d(new MySqlConnectionInternal()) +{ +}*/ + +//! Constructor (needed for trading interface) +MySQLMigrate::MySQLMigrate(QObject *parent, const char *name, + const QStringList &args) : + KexiMigrate(parent, name, args) + ,d(new MySqlConnectionInternal(0)) + ,m_mysqlres(0) +{ + KexiDB::DriverManager manager; + m_kexiDBDriver = manager.driver("mysql"); +} + +/* ************************************************************************** */ +//! Destructor +MySQLMigrate::~MySQLMigrate() { + if (m_mysqlres) + mysql_free_result(m_mysqlres); + m_mysqlres = 0; +} + + +/* ************************************************************************** */ +/*! Connect to the db backend */ +bool MySQLMigrate::drv_connect() { + if(d->db_connect(*m_migrateData->source)) { + return d->useDatabase(m_migrateData->sourceName); + } else { + return false; + } +} + + +/*! Disconnect from the db backend */ +bool MySQLMigrate::drv_disconnect() +{ + return d->db_disconnect(); +} + + +/* ************************************************************************** */ +/*! Get the types and properties for each column. */ +bool MySQLMigrate::drv_readTableSchema( + const QString& originalName, KexiDB::TableSchema& tableSchema) +{ +// m_table = new KexiDB::TableSchema(table); + +// //TODO IDEA: ask for user input for captions +// tableSchema.setCaption(table + " table"); + + //Perform a query on the table to get some data + QString query = QString("SELECT * FROM `") + drv_escapeIdentifier(originalName) + "` LIMIT 0"; + if(d->executeSQL(query)) { + MYSQL_RES *res = mysql_store_result(d->mysql); + if (res != NULL) { + + unsigned int numFlds = mysql_num_fields(res); + MYSQL_FIELD *fields = mysql_fetch_fields(res); + + for(unsigned int i = 0; i < numFlds; i++) { + QString fldName(fields[i].name); + QString fldID( KexiUtils::string2Identifier(fldName) ); + + KexiDB::Field *fld = + new KexiDB::Field(fldID, type(originalName, &fields[i])); + + if(fld->type() == KexiDB::Field::Enum) { + QStringList values = examineEnumField(originalName, &fields[i]); + } + + fld->setCaption(fldName); + getConstraints(fields[i].flags, fld); + getOptions(fields[i].flags, fld); + tableSchema.addField(fld); + } + mysql_free_result(res); + } else { + kdDebug() << "MySQLMigrate::drv_tableNames: null result" << endl; + } + return true; + } else { + return false; + } +} + + +/*! Get a list of tables and put into the supplied string list */ +bool MySQLMigrate::drv_tableNames(QStringList& tableNames) +{ + if(d->executeSQL("SHOW TABLES")) { + MYSQL_RES *res = mysql_store_result(d->mysql); + if (res != NULL) { + MYSQL_ROW row; + while ((row = mysql_fetch_row(res)) != NULL) { + tableNames << QString::fromUtf8(row[0]); //utf8.. ok? + } + mysql_free_result(res); + } else { + kdDebug() << "MySQLMigrate::drv_tableNames: null result" << endl; + } + return true; + } else { + return false; + } +} + +/*! Fetches single string at column \a columnNumber for each record from result obtained + by running \a sqlStatement. + On success the result is stored in \a stringList and true is returned. + \return cancelled if there are no records available. */ +tristate MySQLMigrate::drv_queryStringListFromSQL( + const QString& sqlStatement, uint columnNumber, QStringList& stringList, int numRecords) +{ + stringList.clear(); + if (d->executeSQL(sqlStatement)) { + MYSQL_RES *res = mysql_use_result(d->mysql); + if (res != NULL) { + for (int i=0; numRecords == -1 || i < numRecords; i++) { + MYSQL_ROW row = mysql_fetch_row(res); + if (!row) { + tristate r; + if (mysql_errno(d->mysql)) + r = false; + else + r = (numRecords == -1) ? true : cancelled; + mysql_free_result(res); + return r; + } + uint numFields = mysql_num_fields(res); + if (columnNumber > (numFields-1)) { + kdWarning() << "MySQLMigrate::drv_querySingleStringFromSQL("<<sqlStatement + << "): columnNumber too large (" + << columnNumber << "), expected 0.." << numFields << endl; + mysql_free_result(res); + return false; + } + unsigned long *lengths = mysql_fetch_lengths(res); + if (!lengths) { + mysql_free_result(res); + return false; + } + stringList.append( QString::fromUtf8(row[columnNumber], lengths[columnNumber]) ); //ok? utf8? + } + mysql_free_result(res); + } else { + kdDebug() << "MySQLMigrate::drv_querySingleStringFromSQL(): null result" << endl; + } + return true; + } else { + return false; + } +} + +/*! Fetches single record from result obtained + by running \a sqlStatement. */ +tristate MySQLMigrate::drv_fetchRecordFromSQL(const QString& sqlStatement, + KexiDB::RowData& data, bool &firstRecord) +{ + if (firstRecord || !m_mysqlres) { + if (m_mysqlres) { + mysql_free_result(m_mysqlres); + m_mysqlres = 0; + } + if (!d->executeSQL(sqlStatement) || !(m_mysqlres = mysql_use_result(d->mysql))) + return false; + firstRecord = false; + } + + MYSQL_ROW row = mysql_fetch_row(m_mysqlres); + if (!row) { + tristate r = cancelled; + if (mysql_errno(d->mysql)) + r = false; + mysql_free_result(m_mysqlres); + m_mysqlres = 0; + return r; + } + const int numFields = mysql_num_fields(m_mysqlres); + unsigned long *lengths = mysql_fetch_lengths(m_mysqlres); + if (!lengths) { + mysql_free_result(m_mysqlres); + m_mysqlres = 0; + return false; + } + data.resize(numFields); + for (int i=0; i < numFields; i++) + data[i] = QString::fromUtf8(row[i], lengths[i] ); //ok? utf8? + return true; +} + +/*! Copy MySQL table to KexiDB database */ +bool MySQLMigrate::drv_copyTable(const QString& srcTable, KexiDB::Connection *destConn, + KexiDB::TableSchema* dstTable) +{ + if(d->executeSQL("SELECT * FROM `" + drv_escapeIdentifier(srcTable)) + "`") { + MYSQL_RES *res = mysql_use_result(d->mysql); + if (res != NULL) { + MYSQL_ROW row; + const KexiDB::QueryColumnInfo::Vector fieldsExpanded( dstTable->query()->fieldsExpanded() ); + while ((row = mysql_fetch_row(res)) != NULL) { + const int numFields = QMIN((int)fieldsExpanded.count(), (int)mysql_num_fields(res)); + QValueList<QVariant> vals; + unsigned long *lengths = mysql_fetch_lengths(res); + if (!lengths) { + mysql_free_result(res); + return false; + } + for(int i = 0; i < numFields; i++) + vals.append( KexiDB::cstringToVariant(row[i], fieldsExpanded.at(i)->field, (int)lengths[i]) ); + if (!destConn->insertRecord(*dstTable, vals)) { + mysql_free_result(res); + return false; + } + updateProgress(); + } + if (!row && mysql_errno(d->mysql)) { + mysql_free_result(res); + return false; + } + /*! @todo Check that wasn't an error, rather than end of result set */ + mysql_free_result(res); + } else { + kdDebug() << "MySQLMigrate::drv_copyTable: null result" << endl; + } + return true; + } else { + return false; + } +} + + +bool MySQLMigrate::drv_getTableSize(const QString& table, Q_ULLONG& size) { + if(d->executeSQL("SELECT COUNT(*) FROM `" + drv_escapeIdentifier(table)) + "`") { + MYSQL_RES *res = mysql_store_result(d->mysql); + if (res != NULL) { + MYSQL_ROW row; + while ((row = mysql_fetch_row(res)) != NULL) { + //! @todo check result valid + size = QString(row[0]).toULongLong(); + } + mysql_free_result(res); + } else { + kdDebug() << "MySQLMigrate::drv_getTableSize: null result" << endl; + } + return true; + } else { + return false; + } +} + +//! Convert a MySQL type to a KexiDB type, prompting user if necessary. +KexiDB::Field::Type MySQLMigrate::type(const QString& table, + const MYSQL_FIELD *fld) +{ + // Field type + KexiDB::Field::Type kexiType = KexiDB::Field::InvalidType; + + switch(fld->type) + { + // These are in the same order as mysql_com.h. + // MySQL names given on the right + case FIELD_TYPE_DECIMAL: // DECIMAL or NUMERIC + break; + case FIELD_TYPE_TINY: // TINYINT (-2^7..2^7-1 or 2^8) + kexiType = KexiDB::Field::Byte; + break; + case FIELD_TYPE_SHORT: // SMALLINT (-2^15..2^15-1 or 2^16) + kexiType = KexiDB::Field::ShortInteger; + break; + case FIELD_TYPE_LONG: // INTEGER (-2^31..2^31-1 or 2^32) + kexiType = KexiDB::Field::Integer; + break; + case FIELD_TYPE_FLOAT: // FLOAT + kexiType = KexiDB::Field::Float; + break; + case FIELD_TYPE_DOUBLE: // DOUBLE or REAL (8 byte) + kexiType = KexiDB::Field::Double; + break; + case FIELD_TYPE_NULL: // WTF? + break; + case FIELD_TYPE_TIMESTAMP: // TIMESTAMP (promote?) + kexiType = KexiDB::Field::DateTime; + break; + case FIELD_TYPE_LONGLONG: // BIGINT (-2^63..2^63-1 or 2^64) + case FIELD_TYPE_INT24: // MEDIUMINT (-2^23..2^23-1 or 2^24) (promote) + kexiType = KexiDB::Field::BigInteger; + break; + case FIELD_TYPE_DATE: // DATE + kexiType = KexiDB::Field::Date; + break; + case FIELD_TYPE_TIME: // TIME + kexiType = KexiDB::Field::Time; + break; + case FIELD_TYPE_DATETIME: // DATETIME + kexiType = KexiDB::Field::DateTime; + break; + case FIELD_TYPE_YEAR: // YEAR (promote) + kexiType = KexiDB::Field::ShortInteger; + break; + case FIELD_TYPE_NEWDATE: // WTF? + case FIELD_TYPE_ENUM: // ENUM + // If MySQL did what it's documentation said it did, we would come here + // for enum fields ... + kexiType = KexiDB::Field::Enum; + break; + case FIELD_TYPE_SET: // SET + //! @todo: Support set column type + break; + case FIELD_TYPE_TINY_BLOB: + case FIELD_TYPE_MEDIUM_BLOB: + case FIELD_TYPE_LONG_BLOB: + case FIELD_TYPE_BLOB: // BLOB or TEXT + case FIELD_TYPE_VAR_STRING: // VARCHAR + case FIELD_TYPE_STRING: // CHAR + + if (fld->flags & ENUM_FLAG) { + // ... instead we come here, using the ENUM_FLAG which is supposed to + // be deprecated! Duh. + kexiType = KexiDB::Field::Enum; + break; + } + kexiType = examineBlobField(table, fld); + break; + default: + kexiType = KexiDB::Field::InvalidType; + } + + if (kexiType == KexiDB::Field::InvalidType) { + return userType(table); + } + return kexiType; +} + + +//! Distinguish between a BLOB and a TEXT field +/*! MySQL uses the same field type to identify BLOB and TEXT fields. + This method queries the server to find out if a field is a binary + field or a text field. It also considers the length of CHAR and VARCHAR + fields to see whether Text or LongText is the appropriate Kexi field type. + Assumes fld is a CHAR, VARCHAR, one of the BLOBs or TEXTs. + \return KexiDB::Field::Text, KexiDB::Field::LongText or KexiDB::Field::BLOB +*/ +KexiDB::Field::Type MySQLMigrate::examineBlobField(const QString& table, + const MYSQL_FIELD* fld) { + QString mysqlType; + KexiDB::Field::Type kexiType; + QString query = "SHOW COLUMNS FROM `" + drv_escapeIdentifier(table) + + "` LIKE '" + QString::fromLatin1(fld->name) + "'"; + + if(d->executeSQL(query)) { + MYSQL_RES *res = mysql_store_result(d->mysql); + + if (res != NULL) { + MYSQL_ROW row; + while ((row = mysql_fetch_row(res)) != NULL) { + mysqlType = QString(row[1]); + } + mysql_free_result(res); + } else { + kdDebug() << "MySQLMigrate::examineBlobField: null result" << endl; + } + } else { + // Huh? MySQL wont tell us what kind of field it is! Lets guess. + return KexiDB::Field::LongText; + } + + kdDebug() << "MySQLMigrate::examineBlobField: considering " + << mysqlType << endl; + if(mysqlType.contains("blob", false) != 0) { + // Doesn't matter how big it is, it's binary + kexiType = KexiDB::Field::BLOB; + } else if(mysqlType.contains("text", false) != 0) { + // All the TEXT types are too big for Kexi text. + kexiType = KexiDB::Field::BLOB; + } else if(fld->length < 200) { + kexiType = KexiDB::Field::Text; + } else { + kexiType = KexiDB::Field::LongText; + } + return kexiType; +} + + +//! Get the strings that identify values in an enum field +/*! Parse the type of a MySQL enum field as returned by the server in a + 'DESCRIBE table' or 'SHOW COLUMNS FROM table' statement. The string + returned by the server is in the form 'enum('option1','option2'). + In this example, the result should be a string list containing two + strings, "option1", "option2". + \return list of possible values the field can take + */ +QStringList MySQLMigrate::examineEnumField(const QString& table, + const MYSQL_FIELD* fld) { + QString vals; + QString query = "SHOW COLUMNS FROM `" + drv_escapeIdentifier(table) + + "` LIKE '" + QString::fromLatin1(fld->name) + "'"; + + if(d->executeSQL(query)) { + MYSQL_RES *res = mysql_store_result(d->mysql); + + if (res != NULL) { + MYSQL_ROW row; + while ((row = mysql_fetch_row(res)) != NULL) { + vals = QString(row[1]); + } + mysql_free_result(res); + } else { + kdDebug() << "MySQLMigrate::examineEnumField: null result" << endl; + } + } else { + // Huh? MySQL wont tell us what values it can take. + return QStringList(); + } + + kdDebug() << "MySQLMigrate::examineEnumField: considering " + << vals << endl; + + // Crash and burn if we get confused... + if(!vals.startsWith("enum(")) { + // Huh? We're supposed to be parsing an enum! + kdDebug() << "MySQLMigrate::examineEnumField:1 not an enum!" << endl; + return QStringList(); + } + if(!vals.endsWith(")")) { + kdDebug() << "MySQLMigrate::examineEnumField:2 not an enum!" << endl; + return QStringList(); + } + + // It'd be nice to use QString.section or QStringList.split, but we need + // to be careful as enum values can have commas and quote marks in them + // e.g. CREATE TABLE t(f enum('option,''') gives one option: "option,'" + vals = vals.remove(0,5); + QRegExp rx = QRegExp("^'((?:[^,']|,|'')*)'"); + QStringList values = QStringList(); + int index = 0; + + while ((index = rx.search(vals, index, QRegExp::CaretAtOffset)) != -1) { + int len = rx.matchedLength(); + if (len != -1) { + kdDebug() << "MySQLMigrate::examineEnumField:3 " << rx.cap(1) << endl; + values << rx.cap(1); + } else { + kdDebug() << "MySQLMigrate::examineEnumField:4 lost" << endl; + } + + QChar next = vals[index + len]; + if (next != QChar(',') && next != QChar(')')) { + kdDebug() << "MySQLMigrate::examineEnumField:5 " << (char)next << endl; + } + index += len + 1; + } + + return values; +} + + +void MySQLMigrate::getConstraints(int flags, KexiDB::Field* fld) { + fld->setPrimaryKey(flags & PRI_KEY_FLAG); + fld->setAutoIncrement(flags & AUTO_INCREMENT_FLAG); + fld->setNotNull(flags & NOT_NULL_FLAG); + fld->setUniqueKey(flags & UNIQUE_KEY_FLAG); + //! @todo: Keys and uniqueness +} + + +void MySQLMigrate::getOptions(int flags, KexiDB::Field* fld) { + fld->setUnsigned(flags & UNSIGNED_FLAG); +} + + +#include "mysqlmigrate.moc" diff --git a/kexi/migration/mysql/mysqlmigrate.h b/kexi/migration/mysql/mysqlmigrate.h new file mode 100644 index 00000000..9f32bd2f --- /dev/null +++ b/kexi/migration/mysql/mysqlmigrate.h @@ -0,0 +1,85 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Martin Ellis <[email protected]> + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef MYSQLMIGRATE_H +#define MYSQLMIGRATE_H + +#include "migration/keximigrate.h" +#include "kexidb/drivers/mySQL/mysqlconnection_p.h" + +namespace KexiMigration +{ + + class MySQLMigrate : public KexiMigrate + { + Q_OBJECT + KEXIMIGRATION_DRIVER + + private: + MySqlConnectionInternal *d; + MYSQL_RES *m_mysqlres; + + protected: + //Driver specific function to return table names + virtual bool drv_tableNames(QStringList& tablenames); + + //Driver specific implementation to read a table schema + virtual bool drv_readTableSchema( + const QString& originalName, KexiDB::TableSchema& tableSchema); + //Driver specific connection implementation + virtual bool drv_connect(); + virtual bool drv_disconnect(); + + virtual tristate drv_queryStringListFromSQL( + const QString& sqlStatement, uint columnNumber, + QStringList& stringList, int numRecords = -1); + + virtual tristate drv_fetchRecordFromSQL(const QString& sqlStatement, + KexiDB::RowData& data, bool &firstRecord); + + virtual bool drv_copyTable(const QString& srcTable, + KexiDB::Connection *destConn, KexiDB::TableSchema* dstTable); + + virtual bool drv_progressSupported() { return true; } + virtual bool drv_getTableSize(const QString& table, Q_ULLONG& size); + +//TODO: move this somewhere to low level class (MIGRATION?) +// virtual bool drv_getTablesList( QStringList &list ); +//TODO: move this somewhere to low level class (MIGRATION?) +// virtual bool drv_containsTable( const QString &tableName ); + + public: +// MySQLMigrate(); + MySQLMigrate(QObject *parent, const char *name, const QStringList& args = QStringList()); + ~MySQLMigrate(); + + KexiDB::Field::Type type(const QString& table, const MYSQL_FIELD* t); + + KexiDB::Field::Type examineBlobField(const QString& table, + const MYSQL_FIELD* fld); + + QStringList examineEnumField(const QString& table, + const MYSQL_FIELD* fld); + void getConstraints(int mysqlConstraints, KexiDB::Field* fld); + void getOptions(int flags, KexiDB::Field* fld); + }; +} + +#endif diff --git a/kexi/migration/pqxx/Makefile.am b/kexi/migration/pqxx/Makefile.am new file mode 100644 index 00000000..d49027f1 --- /dev/null +++ b/kexi/migration/pqxx/Makefile.am @@ -0,0 +1,20 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = keximigrate_pqxx.la + +INCLUDES = -I$(srcdir)/../../.. -I$(top_srcdir)/kexi $(all_includes) -I$(PG_INCDIR) -I$(PQXX_INCDIR) + +keximigrate_pqxx_la_METASOURCES = AUTO + +keximigrate_pqxx_la_SOURCES = pqxxmigrate.cpp + +#TODO share -libs with pqxx kexidb drv! +#keximigrate_pqxx_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) -lcom_err -lkrb5 -lssl -lcrypto -lcrypt -lpqxx ../libkeximigrate.la +keximigrate_pqxx_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) -lpqxx ../libkeximigrate.la + +keximigrate_pqxx_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -L$(PQXX_LIBDIR) -L$(PG_LIBDIR) $(VER_INFO) -no-undefined + +kde_services_DATA = keximigrate_pqxx.desktop + +noinst_HEADERS = pqxxmigrate.h pg_type.h + diff --git a/kexi/migration/pqxx/keximigrate_pqxx.desktop b/kexi/migration/pqxx/keximigrate_pqxx.desktop new file mode 100644 index 00000000..3f88cbef --- /dev/null +++ b/kexi/migration/pqxx/keximigrate_pqxx.desktop @@ -0,0 +1,53 @@ +[Desktop Entry] +Name=PostgreSQL +Name[hi]=पोस्टग्रे-एसक्यूएल +Name[ne]=पोस्ट ग्रे एसक्यूएल +Comment=PostgreSQL Migration Driver for Kexi +Comment[bg]=Драйвер за мигриране от PostgreSQL към Kexi +Comment[ca]=Controlador de migració de PostgreSQL per a Kexi +Comment[cy]=Gyrrydd Mudo PostgreSQL ar gyfer Kexi +Comment[da]=PostgreSQL Migrationsdriver for Kexi +Comment[de]=PostgreSQL-Migrationstreiber für Kexi +Comment[el]=Οδηγός μεταφοράς PostgreSQL του Kexi +Comment[eo]=PostgreSQL-migrada pelilo por Kexi +Comment[es]=Controlador de migración a PostgreSQL para Kexi +Comment[et]=Kexi PostgreSQL migreerumisdraiver +Comment[eu]=Kexi-ren PostgreSQL migraziorako kontrolatzailea +Comment[fa]=گردانندۀ جابهجایی PostgreSQL برای Kexi +Comment[fi]=PostgreSQL yhdistämisajuri Kexille +Comment[fr]=Pilote de migration PostgreSQL pour Kexi +Comment[fy]=PostgreSQL-Migraasjestjoerprogramma foar Kexi +Comment[gl]=Controlador de Migración de PostgreSQL de Kexi +Comment[hr]=PostgreSQL upravljački program migracije podataka za Kexi +Comment[hu]=Kexi PostgreSQL-migrálási meghajtó +Comment[is]=PostgreSQL gagnaflutngingsrekill fyrir Kexi +Comment[it]=Driver di migrazione PostgreSQL per Kexi +Comment[ja]=Kexi PostgreSQL データ移行ドライバ +Comment[km]=កម្មវិធីបញ្ជាសម្រាប់ផ្លាស់ប្ដូរ PostgreSQL សម្រាប់ Kexi +Comment[lv]=PostgreSQL datu migrācijas draiveris priekš Kexi +Comment[nb]=Kexi-programmodul for migrering av MySQL-drivere +Comment[nds]=PostgreSQL-Datenutlagerndriever för Kexi +Comment[ne]=केक्सीका लागि पोस्टग्रेसSQL मापग्रेसन ड्राइभर +Comment[nl]=PostgreSQL-migratiestuurprogramma voor Kexi +Comment[nn]=Kexi-programmodul for migrering av MySQL-drivarar +Comment[pl]=Wtyczka migracji danych z serwera PostgreSQL dla Kexi +Comment[pt]=Controlador de Migração de PostgreSQL do Kexi +Comment[pt_BR]=Driver de Migração do PostgreSQL para o Kexi +Comment[ru]=Драйвер миграции PostrgeSQL для Kexi +Comment[sk]=Ovládač PostgreSQL Migration pre Kexi +Comment[sl]=Gonilnik PostgreSQL za prenos podatkov za Kexi +Comment[sr]=Драјвер Kexi-ја за миграцију са PostgreSQL-а +Comment[sr@Latn]=Drajver Kexi-ja za migraciju sa PostgreSQL-a +Comment[sv]=PostgreSQL-övergångsdrivrutin för Kexi +Comment[uk]=PostgreSQL драйвер міграції даних для Kexi +Comment[uz]=Kexi uchun PostgreSQL migratsiya drayveri +Comment[uz@cyrillic]=Kexi учун PostgreSQL миграция драйвери +Comment[zh_CN]=Kexi PostgreSQL 升迁驱动程序 +Comment[zh_TW]=Kexi 的 PostgreSQL 轉移驅動程式 +X-KDE-Library=keximigrate_pqxx +ServiceTypes=Kexi/MigrationDriver +Type=Service +InitialPreference=8 +X-Kexi-MigrationDriverName=PostgreSQL +X-Kexi-MigrationDriverType=Network +X-Kexi-KexiMigrationVersion=1.1 diff --git a/kexi/migration/pqxx/pg_type.h b/kexi/migration/pqxx/pg_type.h new file mode 100644 index 00000000..e0ead91a --- /dev/null +++ b/kexi/migration/pqxx/pg_type.h @@ -0,0 +1,192 @@ +// +// +// C++ Interface: pg_type +// +// Description: +// +// +// Author: Adam Pigg <[email protected]>, (C) 2003 +// +// Copyright: See COPYING file that comes with this distribution +// +// +//boolean, 'true'/'false' +#define BOOLOID 16 + +//Unknown Type +#define UNKNOWNOID 705 + +//Numberic Types +//============== + +//~18 digit integer, 8-byte storage +#define INT8OID 20 + +//-32 thousand to 32 thousand, 2-byte storage +#define INT2OID 21 + +//array of INDEX_MAX_KEYS int2 integers, used in system tables +#define INT2VECTOROID 22 + +//-2 billion to 2 billion integer, 4-byte storage +#define INT4OID 23 + +//single-precision floating point number, 4-byte storage +#define FLOAT4OID 700 + +//double-precision floating point number, 8-byte storage +#define FLOAT8OID 701 + +//monetary amounts, $d,ddd.cc +#define CASHOID 790 + +//numeric(precision, decimal), arbitrary precision number +#define NUMERICOID 1700 + +//================================== + +//Text Types +//========== +//variable-length string, binary values escaped +#define BYTEAOID 17 + +//single character +#define CHAROID 18 + +//variable-length string, no limit specified +#define TEXTOID 25 + +//char(length), blank-padded string, fixed storage length +#define BPCHAROID 1042 + +//varchar(length), non-blank-padded string, variable storage length +#define VARCHAROID 1043 + +//fixed-length bit string +#define BITOID 1560 + +//variable-length bit string +#define VARBITOID 1562 + +//================================== + +//Date Time Types +//=============== +//absolute, limited-range date and time (Unix system time) +#define ABSTIMEOID 702 + +//relative, limited-range time interval (Unix delta time) +#define RELTIMEOID 703 + +//(abstime,abstime), time interval +#define TINTERVALOID 704 + +//ANSI SQL date +#define DATEOID 1082 + +//hh:mm:ss, ANSI SQL time +#define TIMEOID 1083 + +//date and time +#define TIMESTAMPOID 1114 + +//date and time with time zone +#define TIMESTAMPTZOID 1184 + +//@ <number> <units>, time interval +#define INTERVALOID 1186 + +//hh:mm:ss, ANSI SQL time +#define TIMETZOID 1266 + + +//================================== + +//Internal OID Types +//================== +//object identifier(oid), maximum 4 billion +#define OIDOID 26 + +//(Block, offset), physical location of tuple +#define TIDOID 27 + +//transaction id +#define XIDOID 28 + +//command identifier type, sequence in transaction id +#define CIDOID 29 + +//array of INDEX_MAX_KEYS oids, used in system tables +#define OIDVECTOROID 30 + + +//================================== + +//Geometric Types +//=============== +//geometric point '(x, y)' +#define POINTOID 600 + +//geometric line segment '(pt1,pt2)' +#define LSEGOID 601 + +//geometric path '(pt1,...)' +#define PATHOID 602 + +//geometric box '(lower left,upper right)' +#define BOXOID 603 + +//geometric polygon '(pt1,...)' +#define POLYGONOID 604 + +//geometric line (not implemented)' +#define LINEOID 628 + +//geometric circle '(center,radius)' +#define CIRCLEOID 718 + +//================================== + +//Network Types +//============= +//XX:XX:XX:XX:XX:XX, MAC address +#define MACADDROID 829 + +//IP address/netmask, host address, netmask optional +#define INETOID 869 + +//network IP address/netmask, network address +#define CIDROID 650 + +//access control list +#define ACLITEMOID 1033 + + +//================================== + +//Miscellaneous Types +//=================== +//63-character type for storing system identifiers +#define NAMEOID 19 + +//registered procedure +#define REGPROCOID 24 + +//reference cursor (portal name) +#define REFCURSOROID 1790 + +//registered procedure (with args) +#define REGPROCEDUREOID 2202 + +//registered operator +#define REGOPEROID 2203 + +//registered operator (with args) +#define REGOPERATOROID 2204 + +//registered class +#define REGCLASSOID 2205 + +//registered type +#define REGTYPEOID 2206 + diff --git a/kexi/migration/pqxx/pqxxmigrate.cpp b/kexi/migration/pqxx/pqxxmigrate.cpp new file mode 100644 index 00000000..15715897 --- /dev/null +++ b/kexi/migration/pqxx/pqxxmigrate.cpp @@ -0,0 +1,660 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Adam Pigg <[email protected]> + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "pqxxmigrate.h" +#include "pg_type.h" + +#include <qstring.h> +#include <kdebug.h> +#include <qstringlist.h> + +//I maybe should not use stl? +#include <string> +#include <vector> + +#include <kexidb/cursor.h> +#include <kexidb/utils.h> +#include <kexidb/drivermanager.h> +#include <kexiutils/identifier.h> +#include <kexidb/drivers/pqxx/pqxxcursor.h> //for pgsqlCStrToVariant() + +using namespace KexiDB; +using namespace KexiMigration; + +/* +This is the implementation for the pqxx specific import routines +Thi is currently pre alpha and in no way is it meant +to compile, let alone work. This is meant as an example of +what the system might be and is a work in progress +*/ + +KEXIMIGRATE_DRIVER_INFO( PqxxMigrate, pqxx ) + +//================================================================================== +//Constructor +/*PqxxMigrate::PqxxMigrate() + : KexiMigrate(parent, name, args) +{ + m_res=0; + m_trans=0; + m_conn=0; +}*/ + +PqxxMigrate::PqxxMigrate(QObject *parent, const char *name, const QStringList &args) + : KexiMigrate(parent, name, args) +{ + m_res=0; + m_trans=0; + m_conn=0; + KexiDB::DriverManager manager; + m_kexiDBDriver = manager.driver("pqxx"); +} +//================================================================================== +//Destructor +PqxxMigrate::~PqxxMigrate() +{ + clearResultInfo(); +} + +//================================================================================== +//This is probably going to be quite complex...need to get the types for all columns +//any any other attributes required by kexi +//helped by reading the 'tables' test program +bool PqxxMigrate::drv_readTableSchema( + const QString& originalName, KexiDB::TableSchema& tableSchema) +{ +// m_table = new KexiDB::TableSchema(table); + + //TODO IDEA: ask for user input for captions +//moved m_table->setCaption(table + " table"); + + //Perform a query on the table to get some data + if (query("select * from \"" + originalName + "\" limit 1")) + { + //Loop round the fields + for (uint i = 0; i < (uint)m_res->columns(); i++) + { + QString fldName(m_res->column_name(i)); + KexiDB::Field::Type fldType = type(m_res->column_type(i), fldName); + QString fldID( KexiUtils::string2Identifier(fldName) ); + const pqxx::oid toid = tableOid(originalName); + if (toid==0) + return false; + KexiDB::Field *f = new KexiDB::Field(fldID, fldType); + f->setCaption(fldName); + f->setPrimaryKey(primaryKey(toid, i)); + f->setUniqueKey(uniqueKey(toid, i)); + f->setAutoIncrement(autoInc(toid, i));//This should be safe for all field types + tableSchema.addField(f); + + // Do this for var/char types + //m_f->setLength(m_res->at(0)[i].size()); + + // Do this for numeric type + /*m_f->setScale(0); + m_f->setPrecision(0);*/ + + kdDebug() << "Added field [" << f->name() << "] type [" << f->typeName() + << "]" << endl; + } + return true; + } + else + { + return false; + } +} + +//================================================================================== +//get a list of tables and put into the supplied string list +bool PqxxMigrate::drv_tableNames(QStringList& tableNames) +{ + /* + //pg_ = standard postgresql tables, pga_ = tables added by pgaccess, sql_ = probably information schemas, kexi__ = existing kexi tables + if (query("SELECT relname FROM pg_class WHERE ((relkind = 'r') AND ((relname !~ '^pg_') AND (relname !~ '^pga_') AND (relname !~ '^sql_') AND (relname !~ '^kexi__')))")) + */ + if (query("SELECT relname FROM pg_class WHERE ((relkind = 'r') AND ((relname !~ '^pg_') AND (relname !~ '^pga_') AND (relname !~ '^sql_')))")) + { + for (pqxx::result::const_iterator c = m_res->begin(); c != m_res->end(); ++c) + { + // Copy the result into the return list + tableNames << QString::fromLatin1 (c[0].c_str()); + } + return true; + } + else + { + return false; + } +} + +//================================================================================== +//Convert a postgresql type to a kexi type +KexiDB::Field::Type PqxxMigrate::type(int t, const QString& fname) +{ + switch(t) + { + case UNKNOWNOID: + return KexiDB::Field::InvalidType; + case BOOLOID: + return KexiDB::Field::Boolean; + case INT2OID: + return KexiDB::Field::ShortInteger; + case INT4OID: + return KexiDB::Field::Integer; + case INT8OID: + return KexiDB::Field::BigInteger; + case FLOAT4OID: + return KexiDB::Field::Float; + case FLOAT8OID: + return KexiDB::Field::Double; + case NUMERICOID: + return KexiDB::Field::Double; + case DATEOID: + return KexiDB::Field::Date; + case TIMEOID: + return KexiDB::Field::Time; + case TIMESTAMPOID: + return KexiDB::Field::DateTime; + case BYTEAOID: + return KexiDB::Field::BLOB; + case BPCHAROID: + return KexiDB::Field::Text; + case VARCHAROID: + return KexiDB::Field::Text; + case TEXTOID: + return KexiDB::Field::LongText; + } + + //Ask the user what to do with this field + return userType(fname); +} + +//================================================================================== +//Connect to the db backend +bool PqxxMigrate::drv_connect() +{ + kdDebug() << "drv_connect: " << m_migrateData->sourceName << endl; + + QString conninfo; + QString socket; + + //Setup local/remote connection + if (m_migrateData->source->hostName.isEmpty()) + { + if (m_migrateData->source->fileName().isEmpty()) + { + socket="/tmp/.s.PGSQL.5432"; + } + else + { + socket=m_migrateData->source->fileName(); + } + } + else + { + conninfo = "host='" + m_migrateData->source->hostName + "'"; + } + + //Build up the connection string + if (m_migrateData->source->port == 0) + m_migrateData->source->port = 5432; + + conninfo += QString::fromLatin1(" port='%1'").arg(m_migrateData->source->port); + + conninfo += QString::fromLatin1(" dbname='%1'").arg(m_migrateData->sourceName); + + if (!m_migrateData->source->userName.isNull()) + conninfo += QString::fromLatin1(" user='%1'").arg(m_migrateData->source->userName); + + if (!m_migrateData->source->password.isNull()) + conninfo += QString::fromLatin1(" password='%1'").arg(m_migrateData->source->password); + + try + { + m_conn = new pqxx::connection( conninfo.latin1() ); + return true; + } + catch(const std::exception &e) + { + kdDebug() << "PqxxMigrate::drv_connect:exception - " << e.what() << endl; + } + catch(...) + { + kdDebug() << "PqxxMigrate::drv_connect:exception(...)??" << endl; + } + return false; +} + +//================================================================================== +//Connect to the db backend +bool PqxxMigrate::drv_disconnect() +{ + if (m_conn) + { + m_conn->disconnect(); + delete m_conn; + m_conn = 0; + } + return true; +} +//================================================================================== +//Perform a query on the database and store result in m_res +bool PqxxMigrate::query(const QString& statement) +{ + kdDebug() << "query: " << statement.latin1() << endl; + + Q_ASSERT (m_conn); + + // Clear the last result information... + clearResultInfo (); + + try + { + //Create a transaction + m_trans = new pqxx::nontransaction(*m_conn); + //Create a result opject through the transaction + m_res = new pqxx::result(m_trans->exec(statement.latin1())); + //Commit the transaction + m_trans->commit(); + //If all went well then return true, errors picked up by the catch block + return true; + } + catch (const std::exception &e) + { + //If an error ocurred then put the error description into _dbError + kdDebug() << "pqxxImport::query:exception - " << e.what() << endl; + return false; + } + catch(...) + { + kdDebug() << "PqxxMigrate::query:exception(...)??" << endl; + } + return true; +} + +//========================================================================= +//Clears the current result +void PqxxMigrate::clearResultInfo() +{ + delete m_res; + m_res = 0; + + delete m_trans; + m_trans = 0; +} + +//========================================================================= +//Return the OID for a table +pqxx::oid PqxxMigrate::tableOid(const QString& table) +{ + QString statement; + static QString otable; + static pqxx::oid toid; + + pqxx::nontransaction* tran = 0; + pqxx::result* tmpres = 0; + + //Some simple result caching + if (table == otable) + { + kdDebug() << "Returning table OID from cache..." << endl; + return toid; + } + else + { + otable = table; + } + + try + { + statement = "SELECT relfilenode FROM pg_class WHERE (relname = '"; + statement += table; + statement += "')"; + + tran = new pqxx::nontransaction(*m_conn, "find_t_oid"); + tmpres = new pqxx::result(tran->exec(statement.latin1())); + + tran->commit(); + if (tmpres->size() > 0) + { + //We have a key field for this table, lets check if its this column + tmpres->at(0).at(0).to(toid); + } + else + { + toid = 0; + } + } + catch(const std::exception &e) + { + kdDebug() << "pqxxSqlDB::tableOid:exception - " << e.what() << endl; + kdDebug() << "pqxxSqlDB::tableOid:failed statement - " << statement << endl; + toid = 0; + } + catch(...) + { + kdDebug() << "PqxxMigrate::tableOid:exception(...)??" << endl; + } + delete tmpres; + tmpres = 0; + + delete tran; + tran = 0; + + kdDebug() << "OID for table [" << table << "] is [" << toid << "]" << endl; + return toid; +} + +//========================================================================= +//Return whether or not the curent field is a primary key +//TODO: Add result caching for speed +bool PqxxMigrate::primaryKey(pqxx::oid table_uid, int col) const +{ + QString statement; + bool pkey; + int keyf; + + pqxx::nontransaction* tran = 0; + pqxx::result* tmpres = 0; + + try + { + statement = QString("SELECT indkey FROM pg_index WHERE ((indisprimary = true) AND (indrelid = %1))").arg(table_uid); + + tran = new pqxx::nontransaction(*m_conn, "find_pkey"); + tmpres = new pqxx::result(tran->exec(statement.latin1())); + + tran->commit(); + if (tmpres->size() > 0) + { + //We have a key field for this table, lets check if its this column + tmpres->at(0).at(0).to(keyf); + if (keyf-1 == col) //-1 because pg counts from 1 and we count from 0 + { + pkey = true; + kdDebug() << "Field is pkey" << endl; + } + else + { + pkey = false; + kdDebug() << "Field is NOT pkey" << endl; + } + } + else + { + pkey = false; + kdDebug() << "Field is NOT pkey" << endl; + } + } + catch(const std::exception &e) + { + kdDebug() << "pqxxSqlDB::primaryKey:exception - " << e.what() << endl; + kdDebug() << "pqxxSqlDB::primaryKey:failed statement - " << statement << endl; + pkey = false; + } + delete tmpres; + tmpres = 0; + + delete tran; + tran = 0; + + return pkey; +} + +//========================================================================= +/*! Fetches single string at column \a columnNumber from result obtained + by running \a sqlStatement. + On success the result is stored in \a string and true is returned. + \return cancelled if there are no records available. */ +tristate PqxxMigrate::drv_queryStringListFromSQL( + const QString& sqlStatement, uint columnNumber, QStringList& stringList, int numRecords) +{ + std::string result; + int i = 0; + if (query(sqlStatement)) + { + for (pqxx::result::const_iterator it = m_res->begin(); + it != m_res->end() && (numRecords == -1 || i < numRecords); ++it, i++) + { + if (it.size() > 0 && it.size() > columnNumber) { + it.at(columnNumber).to(result); + stringList.append( QString::fromUtf8(result.c_str()) ); + } + else { + clearResultInfo(); + return cancelled; + } + } + } + else + return false; + clearResultInfo(); +/* delete tmpres; + tmpres = 0; + + delete tran; + tran = 0;*/ + + if (i < numRecords) + return cancelled; + + return true; + /* + if (d->executeSQL(sqlStatement)) { + MYSQL_RES *res = mysql_use_result(d->mysql); + if (res != NULL) { + MYSQL_ROW row = mysql_fetch_row(res); + if (!row) { + tristate r = mysql_errno(d->mysql) ? false : cancelled; + mysql_free_result(res); + return r; + } + uint numFields = mysql_num_fields(res); + if (columnNumber > (numFields-1)) { + kdWarning() << "PqxxMigrate::drv_querySingleStringFromSQL("<<sqlStatement + << "): columnNumber too large (" + << columnNumber << "), expected 0.." << numFields << endl; + mysql_free_result(res); + return false; + } + unsigned long *lengths = mysql_fetch_lengths(res); + if (!lengths) { + mysql_free_result(res); + return false; + } + string = QString::fromLatin1(row[columnNumber], lengths[columnNumber]); + mysql_free_result(res); + } else { + kdDebug() << "PqxxMigrate::drv_querySingleStringFromSQL(): null result" << endl; + } + return true; + } else { + return false; + }*/ +} + +tristate PqxxMigrate::drv_fetchRecordFromSQL(const QString& sqlStatement, + KexiDB::RowData& data, bool &firstRecord) +{ + if (firstRecord || !m_res) { + if (m_res) + clearResultInfo(); + if (!query(sqlStatement)) + return false; + m_fetchRecordFromSQL_iter = m_res->begin(); + firstRecord = false; + } + else + ++m_fetchRecordFromSQL_iter; + + if (m_fetchRecordFromSQL_iter == m_res->end()) { + clearResultInfo(); + return cancelled; + } + + std::string result; + const int numFields = m_fetchRecordFromSQL_iter.size(); + data.resize(numFields); + for (int i=0; i < numFields; i++) + data[i] = KexiDB::pgsqlCStrToVariant(m_fetchRecordFromSQL_iter.at(i)); + return true; +} + +//========================================================================= +/*! Copy PostgreSQL table to KexiDB database */ +bool PqxxMigrate::drv_copyTable(const QString& srcTable, KexiDB::Connection *destConn, + KexiDB::TableSchema* dstTable) +{ + std::vector<std::string> R; + + pqxx::work T(*m_conn, "PqxxMigrate::drv_copyTable"); + + pqxx::tablereader stream(T, (srcTable.latin1())); + + //Loop round each row, reading into a vector of strings + const KexiDB::QueryColumnInfo::Vector fieldsExpanded( dstTable->query()->fieldsExpanded() ); + for (int n=0; (stream >> R); ++n) + { + QValueList<QVariant> vals; + std::vector<std::string>::const_iterator i, end( R.end() ); + int index = 0; + for ( i = R.begin(); i != end; ++i, index++) { + if (fieldsExpanded.at(index)->field->type()==KexiDB::Field::BLOB || fieldsExpanded.at(index)->field->type()==KexiDB::Field::LongText) + vals.append( KexiDB::pgsqlByteaToByteArray((*i).c_str(), (*i).size()) ); + else + vals.append( KexiDB::cstringToVariant((*i).c_str(), + fieldsExpanded.at(index)->field, (*i).size()) ); + } + if (!destConn->insertRecord(*dstTable, vals)) + return false; + updateProgress(); + R.clear(); + } + + //This does not work in <libpqxx 2.2 + //stream.complete(); + + return true; +} + +//========================================================================= +//Return whether or not the curent field is a primary key +//TODO: Add result caching for speed +bool PqxxMigrate::uniqueKey(pqxx::oid table_uid, int col) const +{ + QString statement; + bool ukey; + int keyf; + + pqxx::nontransaction* tran = 0; + pqxx::result* tmpres = 0; + + try + { + statement = QString("SELECT indkey FROM pg_index WHERE ((indisunique = true) AND (indrelid = %1))").arg(table_uid); + + tran = new pqxx::nontransaction(*m_conn, "find_ukey"); + tmpres = new pqxx::result(tran->exec(statement.latin1())); + + tran->commit(); + if (tmpres->size() > 0) + { + //We have a key field for this table, lets check if its this column + tmpres->at(0).at(0).to(keyf); + if (keyf-1 == col) //-1 because pg counts from 1 and we count from 0 + { + ukey = true; + kdDebug() << "Field is unique" << endl; + } + else + { + ukey = false; + kdDebug() << "Field is NOT unique" << endl; + } + } + else + { + ukey = false; + kdDebug() << "Field is NOT unique" << endl; + } + } + catch(const std::exception &e) + { + kdDebug() << "uniqueKey:exception - " << e.what() << endl; + kdDebug() << "uniqueKey:failed statement - " << statement << endl; + ukey = false; + } + + delete tmpres; + tmpres = 0; + + delete tran; + tran = 0; + + return ukey; +} + +//================================================================================== +//TODO::Implement +bool PqxxMigrate::autoInc(pqxx::oid /*table_uid*/, int /*col*/) const +{ + return false; +} + +//================================================================================== +//TODO::Implement +bool PqxxMigrate::notNull(pqxx::oid /*table_uid*/, int /*col*/) const +{ + return false; +} + +//================================================================================== +//TODO::Implement +bool PqxxMigrate::notEmpty(pqxx::oid /*table_uid*/, int /*col*/) const +{ + return false; +} + +//================================================================================== +//Return a list of database names +/*bool PqxxMigrate::drv_getDatabasesList( QStringList &list ) +{ + KexiDBDrvDbg << "pqxxSqlConnection::drv_getDatabaseList" << endl; + + if (executeSQL("SELECT datname FROM pg_database WHERE datallowconn = TRUE")) + { + std::string N; + for (pqxx::result::const_iterator c = m_res->begin(); c != m_res->end(); ++c) + { + // Read value of column 0 into a string N + c[0].to(N); + // Copy the result into the return list + list << QString::fromLatin1 (N.c_str()); + KexiDBDrvDbg << N.c_str() << endl; + } + return true; + } + + return false; +}*/ + + +#include "pqxxmigrate.moc" diff --git a/kexi/migration/pqxx/pqxxmigrate.h b/kexi/migration/pqxx/pqxxmigrate.h new file mode 100644 index 00000000..c09c8a7a --- /dev/null +++ b/kexi/migration/pqxx/pqxxmigrate.h @@ -0,0 +1,120 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Adam Pigg <[email protected]> + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef PQXXIMPORT_H +#define PQXXIMPORT_H + +#include <migration/keximigrate.h> + +//Kexi Includes +#include <kexidb/field.h> +#include <kexidb/connection.h> + +#include <pqxx/pqxx> + +namespace KexiMigration +{ + class PqxxMigrate : public KexiMigrate + { + Q_OBJECT + KEXIMIGRATION_DRIVER + + public: + PqxxMigrate(QObject *parent, const char *name, const QStringList &args = QStringList()); + virtual ~PqxxMigrate(); + + protected: + //Driver specific function to return table names + virtual bool drv_tableNames(QStringList& tablenames); + + //Driver specific implementation to read a table schema + virtual bool drv_readTableSchema( + const QString& originalName, KexiDB::TableSchema& tableSchema); + + //Driver specific connection implementation + virtual bool drv_connect(); + virtual bool drv_disconnect(); + + virtual tristate drv_queryStringListFromSQL( + const QString& sqlStatement, uint columnNumber, QStringList& stringList, + int numRecords = -1); + + /*! Fetches single record from result obtained + by running \a sqlStatement. + \a firstRecord should be first initialized to true, so the method can run + the query at first call and then set it will set \a firstRecord to false, + so subsequent calls will only fetch records. + On success the result is stored in \a data and true is returned, + \a data is resized to appropriate size. cancelled is returned on EOF. */ +//! @todo SQL-dependent! + virtual tristate drv_fetchRecordFromSQL(const QString& sqlStatement, + KexiDB::RowData& data, bool &firstRecord); + + virtual bool drv_copyTable(const QString& srcTable, + KexiDB::Connection *destConn, KexiDB::TableSchema* dstTable); + + private: + //lowlevel functions/objects + //database connection + pqxx::connection* m_conn; + + //transaction + pqxx::nontransaction* m_trans; + + //lowlevel result + pqxx::result* m_res; + + //! Used in drv_fetchRecordFromSQL + pqxx::result::const_iterator m_fetchRecordFromSQL_iter; + + //perform a query on the database + bool query(const QString& statement); + + //Clear the result info + void clearResultInfo (); + + pqxx::oid tableOid(const QString& tablename); + + //Convert the pqxx type to a kexi type + KexiDB::Field::Type type(int t, const QString& fname); + + //Find out the field constraints + //Return whether or not the field is a pkey + bool primaryKey(pqxx::oid table, int col) const; + + //Return whether or not the field is unique + bool uniqueKey(pqxx::oid table, int col) const; + + //Return whether or not the field is a foreign key + bool foreignKey(pqxx::oid table, int col) const; + + //Return whether or not the field is not null + bool notNull(pqxx::oid table, int col) const; + + //Return whether or not the field is not empty + bool notEmpty(pqxx::oid table, int col) const; + + //Return whether or not the field is auto incrementing + bool autoInc(pqxx::oid table, int col) const; + + }; +} + +#endif diff --git a/kexi/migration/txt/Makefile.am b/kexi/migration/txt/Makefile.am new file mode 100644 index 00000000..46964222 --- /dev/null +++ b/kexi/migration/txt/Makefile.am @@ -0,0 +1,18 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = keximigrate_txtmigrate.la + +INCLUDES = -I$(srcdir)/../../.. -I$(top_srcdir)/kexi $(all_includes) + +keximigrate_txtmigrate_la_METASOURCES = AUTO + +keximigrate_txtmigrate_la_SOURCES = txtmigrate.cpp + +keximigrate_txtmigrate_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) ../libkeximigrate.la + +keximigrate_txtmigrate_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO) -no-undefined + +kde_services_DATA = keximigrate_txtmigrate.desktop + +noinst_HEADERS = txtmigrate.h + diff --git a/kexi/migration/txt/txtmigrate.cpp b/kexi/migration/txt/txtmigrate.cpp new file mode 100644 index 00000000..8109a5b1 --- /dev/null +++ b/kexi/migration/txt/txtmigrate.cpp @@ -0,0 +1,27 @@ +// +// C++ Implementation: txtmigrate +// +// Description: +// +// +// Author: Adam Pigg <[email protected]>, (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#include "txtmigrate.h" + +namespace KexiMigration { + +TxtMigrate::TxtMigrate() + : KexiMigrate() +{ +} + + +TxtMigrate::~TxtMigrate() +{ +} + + +}; diff --git a/kexi/migration/txt/txtmigrate.h b/kexi/migration/txt/txtmigrate.h new file mode 100644 index 00000000..e4b830c0 --- /dev/null +++ b/kexi/migration/txt/txtmigrate.h @@ -0,0 +1,33 @@ +// +// C++ Interface: txtmigrate +// +// Description: +// +// +// Author: Adam Pigg <[email protected]>, (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#ifndef KEXIMIGRATIONTXTMIGRATE_H +#define KEXIMIGRATIONTXTMIGRATE_H + +#include <keximigrate.h> + +namespace KexiMigration { + +/** +@author Adam Pigg +*/ +class TxtMigrate : public KexiMigrate +{ +public: + TxtMigrate(); + + ~TxtMigrate(); + +}; + +}; + +#endif |