kexi

keximigrate.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2004 Adam Pigg <adam@piggz.co.uk>
00003    Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
00004    Copyright (C) 2005 Martin Ellis <martin.ellis@kdemail.net>
00005 
00006    This program is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License as published by the Free Software Foundation; either
00009    version 2 of the License, or (at your option) any later version.
00010 
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public License
00017    along with this program; see the file COPYING.  If not, write to
00018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019  * Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include "keximigrate.h"
00023 
00024 #include <kdebug.h>
00025 #include <kinputdialog.h>
00026 #include <kapplication.h>
00027 
00028 #include <kexiutils/identifier.h>
00029 #include <core/kexi.h>
00030 #include <core/kexiproject.h>
00031 #include <kexidb/drivermanager.h>
00032 
00033 using namespace KexiDB;
00034 using namespace KexiMigration;
00035 
00036 KexiMigrate::KexiMigrate(QObject *parent, const char *name,
00037   const QStringList&) 
00038   : QObject( parent, name )
00039   , m_migrateData(0)
00040   , m_destPrj(0)
00041 //  , m_copyOfKexi__objects(0)
00042 {
00043 }
00044 
00047 #define NUM_OF_ROWS_PER_CREATE_TABLE 20
00048 
00049 
00050 //=============================================================================
00051 // Migration parameters
00052 void KexiMigrate::setData(KexiMigration::Data* migrateData)
00053 {
00054     m_migrateData = migrateData;
00055 }
00056 
00057 //=============================================================================
00058 // Destructor
00059 KexiMigrate::~KexiMigrate()
00060 {
00061     delete m_destPrj;
00062 }
00063 
00064 bool KexiMigrate::checkIfDestinationDatabaseOverwritingNeedsAccepting(Kexi::ObjectStatus* result, 
00065     bool& acceptingNeeded)
00066 {
00067     acceptingNeeded = false;
00068     if (result)
00069         result->clearStatus();
00070 
00071     KexiDB::DriverManager drvManager;
00072     KexiDB::Driver *destDriver = drvManager.driver(
00073         m_migrateData->destination->connectionData()->driverName);
00074     if (!destDriver) {
00075         result->setStatus(&drvManager,
00076             i18n("Could not create database \"%1\".")
00077             .arg(m_migrateData->destination->databaseName()));
00078         return false;
00079     }
00080 
00081     // For file-based dest. projects, we've already asked about overwriting 
00082     // existing project but for server-based projects we need to ask now.
00083     if (destDriver->isFileDriver())
00084         return true; //nothing to check
00085     KexiDB::Connection *tmpConn 
00086         = destDriver->createConnection( *m_migrateData->destination->connectionData() );
00087     if (!tmpConn || destDriver->error() || !tmpConn->connect()) {
00088         delete tmpConn;
00089         return true;
00090     }
00091     if (tmpConn->databaseExists( m_migrateData->destination->databaseName() )) {
00092         acceptingNeeded = true;
00093     }
00094     tmpConn->disconnect();
00095     delete tmpConn;
00096     return true;
00097 }
00098 
00099 bool KexiMigrate::isSourceAndDestinationDataSourceTheSame() const
00100 {
00101     KexiDB::ConnectionData* sourcedata = m_migrateData->source;
00102     KexiDB::ConnectionData* destinationdata = m_migrateData->destination->connectionData();
00103     return (
00104         sourcedata && destinationdata &&
00105         m_migrateData->sourceName == m_migrateData->destination->databaseName() && // same database name
00106         sourcedata->driverName == destinationdata->driverName && // same driver
00107         sourcedata->hostName == destinationdata->hostName && // same host
00108         sourcedata->fileName() == destinationdata->fileName() && // same filename
00109         sourcedata->dbPath() == destinationdata->dbPath() && // same database path
00110         sourcedata->dbFileName() == destinationdata->dbFileName() // same database filename
00111     );
00112 }
00113 
00114 //=============================================================================
00115 // Perform Import operation
00116 bool KexiMigrate::performImport(Kexi::ObjectStatus* result)
00117 {
00118     if (result)
00119         result->clearStatus();
00120 
00121     KexiDB::DriverManager drvManager;
00122     KexiDB::Driver *destDriver = drvManager.driver(
00123         m_migrateData->destination->connectionData()->driverName);
00124     if (!destDriver) {
00125         result->setStatus(&drvManager,
00126             i18n("Could not create database \"%1\".")
00127             .arg(m_migrateData->destination->databaseName()));
00128         return false;
00129     }
00130 
00131     QStringList tables;
00132 
00133     // Step 1 - connect
00134     kdDebug() << "KexiMigrate::performImport() CONNECTING..." << endl;
00135     if (!drv_connect()) {
00136         kdDebug() << "Couldnt connect to database server" << endl;
00137         if (result)
00138             result->setStatus(i18n("Could not connect to data source \"%1\".")
00139                 .arg(m_migrateData->source->serverInfoString()), "");
00140         return false;
00141     }
00142 
00143     // Step 2 - get table names
00144     kdDebug() << "KexiMigrate::performImport() GETTING TABLENAMES..." << endl;
00145     if (!tableNames(tables)) {
00146         kdDebug() << "Couldnt get list of tables" << endl;
00147         if (result)
00148             result->setStatus(
00149                 i18n("Could not get a list of table names for data source \"%1\".")
00150                     .arg(m_migrateData->source->serverInfoString()), "");
00151         return false;
00152     }
00153 
00154     //tmp to force error!!!
00155     //tables.prepend("$$$$$");
00156 
00157     // Check if there are any tables
00158     if (tables.isEmpty()) {
00159         kdDebug() << "There were no tables to import" << endl;
00160         if (result)
00161             result->setStatus(
00162                 i18n("No tables to import found in data source \"%1\".")
00163                     .arg(m_migrateData->source->serverInfoString()), "");
00164         return false;
00165     }
00166 
00167     // Step 3 - Read table schemas
00168     m_tableSchemas.clear();
00169     if (!destDriver) {
00170         result->setStatus(&drvManager);
00171         return false;
00172     }
00173     foreach(QStringList::ConstIterator, it, tables) {
00174         if (destDriver->isSystemObjectName( *it ) //"kexi__objects", etc.
00175             || (*it).lower().startsWith("kexi__")) //tables at KexiProject level, e.g. "kexi__blobs"
00176             continue;
00177 
00178         const QString tableName( KexiUtils::string2Identifier(*it) );
00179         KexiDB::TableSchema *tableSchema;
00180 //      if (tableName.lower().startsWith("kexi__"))
00181 //          tableSchema = new KexiDB::InternalTableSchema(tableName);
00182 //      else
00183         tableSchema = new KexiDB::TableSchema(tableName);
00184 
00185         tableSchema->setCaption( *it ); //caption is equal to the original name
00186 
00187         if (drv_readTableSchema(*it, *tableSchema)) {
00188             //yeah, got a table
00189             //Add it to list of tables which we will create if all goes well
00190             m_tableSchemas.append(tableSchema);
00191         } else {
00192             delete tableSchema;
00193             if (result)
00194                 result->setStatus(
00195                     i18n("Could not import project from data source \"%1\". Error reading table \"%2\".")
00196                     .arg(m_migrateData->source->serverInfoString()).arg(tableName), "");
00197             return false;
00198         }
00199     }
00200 
00201     // Step 4 - Create new database as we have all required info
00202     delete m_destPrj;
00203     m_destPrj = createProject(result);
00204     if (!m_destPrj || m_destPrj->error()) {
00205         if (result)
00206             result->setStatus(m_destPrj,
00207                 i18n("Could not import project from data source \"%1\".")
00208                 .arg(m_migrateData->source->serverInfoString()));
00209         return false;
00210     }
00211 
00212     // Step 5 - Copy data if asked to
00213     bool ok = true;
00214     KexiDB::Transaction trans;
00215     if (!m_migrateData->keepData)
00216         m_tableSchemas.clear();
00217 
00218     KexiDB::Connection *destConn = m_destPrj->dbConnection();
00219     ok = destConn;
00220     if (ok) {
00221         trans = destConn->beginTransaction();
00222         ok = !trans.isNull();
00223     }
00224     if (ok) {
00225         // Copy data for "kexi__objectdata" as well, if available in the source db
00226         if (tables.find("kexi__objectdata")!=tables.end())
00227             m_tableSchemas.append(destConn->tableSchema("kexi__objectdata")); 
00228     }
00229 
00230     for(QPtrListIterator<TableSchema> ts(m_tableSchemas); ok && ts.current() != 0 ; ++ts)
00231     {
00232         const QString tname( ts.current()->name().lower() );
00233         if (destConn->driver()->isSystemObjectName( tname )
00236             && tname!="kexi__objectdata" //copy this too
00237         )
00238         {
00239             kdDebug() << "Do not copy data for system table: " << tname << endl;
00241             continue;
00242         }
00243         kdDebug() << "Copying data for table: " << tname << endl;
00244         ok = drv_copyTable(
00245             ts.current()->caption().isEmpty() ? tname : ts.current()->caption(), //caption is equal to the original name
00246             destConn, 
00247             ts.current()
00248         );
00249         if (!ok) {
00250             kdDebug() << "Failed to copy table " << tname << endl;
00251             if (result)
00252                 result->setStatus(destConn,
00253                     i18n("Could not copy table \"%1\" to destination database.").arg(tname));
00254             break;
00255         }
00256     }//for
00257 
00258     // 5.1. Copy remaining "kexi__objects" contents (queries, forms, etc.) 
00259     //      if "kexi__objects" table is available in the source db
00260     if (ok && tables.find("kexi__objects")!=tables.end()) {
00261         // At 'source' side, we can only can use drv_copyTable, so let's create 
00262         // a temporary copy of "kexi__objects" and copy everything there
00263         KexiDB::TableSchema *kexi__objectsCopy = 
00264             new KexiDB::TableSchema( *destConn->tableSchema("kexi__objects") );
00265         kexi__objectsCopy->setName("kexi__objects__copy");
00266         ok = destConn->createTable( kexi__objectsCopy );
00267         if (!ok) {
00268             kdDebug() << "Failed to create a table " << kexi__objectsCopy->name() << endl;
00269             delete kexi__objectsCopy;
00270             kexi__objectsCopy = 0;
00271             destConn->debugError();
00272             if (result)
00273                 result->setStatus(destConn,
00274                     i18n("Could not create database \"%1\".")
00275                     .arg(m_migrateData->destination->databaseName()));
00276         }
00277         if (ok) {
00278             ok = drv_copyTable("kexi__objects", destConn, kexi__objectsCopy);
00279         }
00282         if (ok) {
00283             ok = destConn->executeSQL(
00284                 QString::fromLatin1("INSERT INTO kexi__objects SELECT * FROM kexi__objects__copy "
00285                     "WHERE o_type<>%1").arg((int)KexiDB::TableObjectType));
00286         }
00287         if (kexi__objectsCopy && !destConn->dropTable( kexi__objectsCopy )) {
00288             ok = false;
00289         }
00290     }
00291 
00292     // Done.
00293     if (ok) {
00294         ok = destConn->commitTransaction(trans);
00295     }
00296 
00297     if (ok)
00298         ok = drv_disconnect(); // && m_migrateData->dest->disconnect();
00299 
00300     if (!ok) {
00301         if (result && result->error())
00302             result->setStatus(destConn,
00303                 i18n("Could not import data from data source \"%1\".")
00304                     .arg(m_migrateData->source->serverInfoString()));
00305         if (destConn) {
00306             destConn->debugError();
00307             destConn->rollbackTransaction(trans);
00308         }
00309         drv_disconnect();
00310         if (destConn) {
00311             destConn->disconnect();
00312             destConn->dropDatabase(m_migrateData->destination->databaseName());
00313         }
00314         //later     delete prj;
00315         return false;
00316     }
00317     if (destConn)
00318         ok = destConn->disconnect();
00319     //later delete prj;
00320     return ok;
00321 }
00322 //=============================================================================
00323 
00324 bool KexiMigrate::performExport(Kexi::ObjectStatus* result)
00325 {
00326     if (result)
00327         result->clearStatus();
00328 
00330 
00331     return false;
00332 }
00333 
00334 //=============================================================================
00335 // Create the final database project
00336 KexiProject *KexiMigrate::createProject(Kexi::ObjectStatus* result)
00337 {
00338     kdDebug() << "Creating database [" << m_migrateData->destination->databaseName() 
00339         << "]" << endl;
00340 
00341     KexiProject *prj = new KexiProject(m_migrateData->destination,
00342         (KexiDB::MessageHandler*)*result);
00343     tristate r = prj->create(true /*forceOverwrite*/);
00344     if (r!=true) {
00345         //don't delete prj, otherwise eror message will be deleted      delete prj;
00346         return prj;
00347     }
00348 
00349     KexiDB::TransactionGuard tg(*prj->dbConnection());
00350     if (tg.transaction().isNull()) {
00351         if (result)
00352             result->setStatus(prj->dbConnection(),
00353                 i18n("Could not create database \"%1\".")
00354                 .arg(m_migrateData->destination->databaseName()));
00355         prj->dbConnection()->dropDatabase(m_migrateData->destination->databaseName());
00356         //don't delete prj, otherwise eror message will be deleted      delete prj;
00357         return prj;
00358     }
00359 
00360     if(drv_progressSupported()) {
00361         progressInitialise();
00362     }
00363 
00364     //Right, were connected..create the tables
00365     KexiDB::TableSchema *ts;
00366     for(QPtrListIterator<TableSchema> it (m_tableSchemas); (ts = it.current()) != 0;++it) {
00367         if(!prj->dbConnection()->createTable( ts )) {
00368             kdDebug() << "Failed to create a table " << ts->name() << endl;
00369             prj->dbConnection()->debugError();
00370             if (result)
00371                 result->setStatus(prj->dbConnection(),
00372                     i18n("Could not create database \"%1\".")
00373                     .arg(m_migrateData->destination->databaseName()));
00374             m_tableSchemas.remove(ts);
00375             prj->dbConnection()->dropDatabase(m_migrateData->destination->databaseName());
00376             //don't delete prj, otherwise eror message will be deleted          delete prj;
00377             return prj;
00378         }
00379         updateProgress((Q_ULLONG)NUM_OF_ROWS_PER_CREATE_TABLE);
00380     }
00381     if (!tg.commit()) {
00382         prj->dbConnection()->dropDatabase(m_migrateData->destination->databaseName());
00383         //don't delete prj, otherwise eror message will be deleted      delete prj;
00384         return prj;
00385     }
00386     return prj;
00387 }
00388 
00389 //=============================================================================
00390 // Functions for getting table data
00391 bool KexiMigrate::tableNames(QStringList & tn)
00392 {
00394     kdDebug() << "Reading list of tables..." << endl;
00395     return drv_tableNames(tn);
00396 }
00397 
00398 //=============================================================================
00399 // Progress functions
00400 bool KexiMigrate::progressInitialise() {
00401     Q_ULLONG sum = 0, size;
00402     emit progressPercent(0);
00403 
00405     QStringList tables;
00406     if(!tableNames(tables))
00407         return false;
00408 
00409     // 1) Get the number of rows/bytes to import
00410     int tableNumber = 1;
00411     for(QStringList::Iterator it = tables.begin();
00412         it != tables.end(); ++it, tableNumber++)
00413     {
00414         if(drv_getTableSize(*it, size)) {
00415             kdDebug() << "KexiMigrate::progressInitialise() - table: " << *it 
00416                       << "size: " << (ulong)size << endl;
00417             sum += size;
00418             emit progressPercent(tableNumber * 5 /* 5% */ / tables.count());
00419         } else {
00420             return false;
00421         }
00422     }
00423 
00424     kdDebug() << "KexiMigrate::progressInitialise() - job size: " << (ulong)sum << endl;
00425     m_progressTotal = sum;
00426     m_progressTotal += tables.count() * NUM_OF_ROWS_PER_CREATE_TABLE;
00427     m_progressTotal = m_progressTotal * 105 / 100; //add 5 percent for above task 1)
00428     m_progressNextReport = sum / 100;
00429     m_progressDone = m_progressTotal * 5 / 100; //5 perecent already done in task 1)
00430     return true;
00431 }
00432 
00433 
00434 void KexiMigrate::updateProgress(Q_ULLONG step) {
00435     m_progressDone += step;
00436     if (m_progressDone >= m_progressNextReport) {
00437         int percent = (m_progressDone+1) * 100 / m_progressTotal;
00438         m_progressNextReport = ((percent + 1) * m_progressTotal) / 100;
00439         kdDebug() << "KexiMigrate::updateProgress(): " << (ulong)m_progressDone << "/"
00440                   << (ulong)m_progressTotal << " (" << percent << "%) next report at " 
00441                   << (ulong)m_progressNextReport << endl;
00442         emit progressPercent(percent);
00443     }
00444 }
00445 
00446 //=============================================================================
00447 // Prompt the user to choose a field type
00448 KexiDB::Field::Type KexiMigrate::userType(const QString& fname)
00449 {
00450     KInputDialog *dlg;
00451     QStringList  types;
00452     QString res;
00453 
00454     types << "Byte";
00455     types << "Short Integer";
00456     types << "Integer";
00457     types << "Big Integer";
00458     types << "Boolean";
00459     types << "Date";
00460     types << "Date Time";
00461     types << "Time";
00462     types << "Float";
00463     types << "Double";
00464     types << "Text";
00465     types << "Long Text";
00466     types << "Binary Large Object";
00467 
00468     res = dlg->getItem( i18n("Field Type"),
00469                         i18n("The data type for %1 could not be determined. "
00470                  "Please select one of the following data "
00471                  "types").arg(fname),
00472                       types, 0, false);
00473 
00475     if (res == *types.at(0))
00476         return KexiDB::Field::Byte;
00477     else if (res == *types.at(1))
00478         return KexiDB::Field::ShortInteger;
00479     else if (res == *types.at(2))
00480         return KexiDB::Field::Integer;
00481     else if (res == *types.at(3))
00482         return KexiDB::Field::BigInteger;
00483     else if (res == *types.at(4))
00484         return KexiDB::Field::Boolean;
00485     else if (res == *types.at(5))
00486         return KexiDB::Field::Date;
00487     else if (res == *types.at(6))
00488         return KexiDB::Field::DateTime;
00489     else if (res == *types.at(7))
00490         return KexiDB::Field::Time;
00491     else if (res == *types.at(8))
00492         return KexiDB::Field::Float;
00493     else if (res == *types.at(9))
00494         return KexiDB::Field::Double;
00495     else if (res == *types.at(10))
00496         return KexiDB::Field::Text;
00497     else if (res == *types.at(11))
00498         return KexiDB::Field::LongText;
00499     else if (res == *types.at(12))
00500         return KexiDB::Field::BLOB;
00501     else
00502         return KexiDB::Field::Text;
00503 }
00504 
00505 QVariant KexiMigrate::propertyValue( const QCString& propName )
00506 {
00507     return m_properties[propName.lower()];
00508 }
00509 
00510 QString KexiMigrate::propertyCaption( const QCString& propName ) const
00511 {
00512     return m_propertyCaptions[propName.lower()];
00513 }
00514 
00515 void KexiMigrate::setPropertyValue( const QCString& propName, const QVariant& value )
00516 {
00517     m_properties[propName.lower()] = value;
00518 }
00519 
00520 QValueList<QCString> KexiMigrate::propertyNames() const
00521 {
00522     QValueList<QCString> names = m_properties.keys();
00523     qHeapSort(names);
00524     return names;
00525 }
00526 
00527 bool KexiMigrate::isValid()
00528 {
00529     if (KexiMigration::versionMajor() != versionMajor()
00530         || KexiMigration::versionMinor() != versionMinor())
00531     {
00532         setError(ERR_INCOMPAT_DRIVER_VERSION,
00533         i18n("Incompatible migration driver's \"%1\" version: found version %2, expected version %3.")
00534         .arg(name())
00535         .arg(QString("%1.%2").arg(versionMajor()).arg(versionMinor()))
00536         .arg(QString("%1.%2").arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor())));
00537         return false;
00538     }
00539     return true;
00540 }
00541 
00542 #include "keximigrate.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys