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-2006 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 {
00042     m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.setAutoDelete(true);
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     // Check if there are any tables
00155     if (tables.isEmpty()) {
00156         kdDebug() << "There were no tables to import" << endl;
00157         if (result)
00158             result->setStatus(
00159                 i18n("No tables to import found in data source \"%1\".")
00160                     .arg(m_migrateData->source->serverInfoString()), "");
00161         return false;
00162     }
00163 
00164     // Step 3 - Read table schemas
00165     tables.sort();
00166     m_tableSchemas.clear();
00167     if (!destDriver) {
00168         result->setStatus(&drvManager);
00169         return false;
00170     }
00171     const bool kexi__objects_exists = tables.find("kexi__objects")!=tables.end();
00172     QStringList kexiDBTables;
00173     if (kexi__objects_exists) {
00174         tristate res = drv_queryStringListFromSQL(
00175             QString::fromLatin1("SELECT o_name FROM kexi__objects WHERE o_type=%1")
00176         .arg((int)KexiDB::TableObjectType), 0, kexiDBTables, -1);
00177         if (res == true) {
00178             // prepend KexiDB-compatible tables to 'tables' list, so we'll copy KexiDB-compatible tables first,
00179             // to make sure existing IDs will not be in conflict with IDs newly generated for non-KexiDB tables
00180             kexiDBTables.sort();
00181             foreach(QStringList::ConstIterator, it, kexiDBTables)
00182                 tables.remove( *it );
00183 //kdDebug() << "KexiDB-compat tables: " << kexiDBTables << endl;
00184 //kdDebug() << "non-KexiDB tables: " << tables << endl;
00185         }
00186     }
00187 
00188 //  uint i=0;
00189     // -- read table schemas and create them in memory (only for non-KexiDB-compat tables)
00190     foreach (QStringList::ConstIterator, it, tables) {
00191         if (destDriver->isSystemObjectName( *it ) //"kexi__objects", etc.
00192             || (*it).lower().startsWith("kexi__")) //tables at KexiProject level, e.g. "kexi__blobs"
00193             continue;
00194         // this is a non-KexiDB table: generate schema from native data source
00195         const QString tableName( KexiUtils::string2Identifier(*it) );
00196         KexiDB::TableSchema *tableSchema = new KexiDB::TableSchema(tableName);
00197         tableSchema->setCaption( *it ); //caption is equal to the original name
00198 
00199         if (!drv_readTableSchema(*it, *tableSchema)) {
00200             delete tableSchema;
00201             if (result)
00202                 result->setStatus(
00203                     i18n("Could not import project from data source \"%1\". Error reading table \"%2\".")
00204                     .arg(m_migrateData->source->serverInfoString()).arg(tableName), "");
00205             return false;
00206         }
00207         //yeah, got a table
00208         //Add it to list of tables which we will create if all goes well
00209         m_tableSchemas.append(tableSchema);
00210     }
00211 
00212     // Step 4 - Create a new database as we have all required info
00213     // - create copies of KexiDB-compat tables
00214     // - create copies of non-KexiDB tables
00215     delete m_destPrj;
00216     m_destPrj = new KexiProject(m_migrateData->destination, 
00217         result ? (KexiDB::MessageHandler*)*result : 0);
00218     bool ok = true == m_destPrj->create(true /*forceOverwrite*/);
00219 
00220     KexiDB::Connection *destConn = 0;
00221 
00222     if (ok)
00223         ok = (destConn = m_destPrj->dbConnection());
00224 
00225     KexiDB::Transaction trans;
00226     if (ok) {
00227         trans = destConn->beginTransaction();
00228         if (trans.isNull()) {
00229             ok = false;
00230             if (result)
00231                 result->setStatus(destConn,
00232                     i18n("Could not create database \"%1\".")
00233                     .arg(m_migrateData->destination->databaseName()));
00234             //later destConn->dropDatabase(m_migrateData->destination->databaseName());
00235             //don't delete prj, otherwise eror message will be deleted      delete prj;
00236             //later return m_destPrj;
00237         }
00238     }
00239     
00240     if (ok) {
00241         if (drv_progressSupported())
00242             progressInitialise();
00243 
00244         // Step 5 - Create the copies of KexiDB-compat tables in memory (to maintain the same IDs)
00245         m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.clear();
00246         foreach (QStringList::ConstIterator, it, kexiDBTables) {
00247             //load the schema from kexi__objects and kexi__fields
00248             TableSchema *t = new TableSchema();
00249             RowData data;
00250             bool firstRecord = true;
00251             if (true == drv_fetchRecordFromSQL(
00252                     QString("SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects "
00253                     "WHERE o_name='%1' AND o_type=%1").arg(*it).arg((int)KexiDB::TableObjectType), 
00254                     data, firstRecord)
00255                 && destConn->setupObjectSchemaData( data, *t ))
00256             {
00258                 //load schema for every field and add it
00259                 firstRecord = true;
00260                 QString sql(
00261                     QString::fromLatin1("SELECT t_id, f_type, f_name, f_length, f_precision, f_constraints, "
00262                         "f_options, f_default, f_order, f_caption, f_help"
00263                         " FROM kexi__fields WHERE t_id=%1 ORDER BY f_order").arg(t->id()) );
00264                 while (ok) {
00265                     tristate res = drv_fetchRecordFromSQL(sql, data, firstRecord);
00266                     if (res != true) {
00267                         if (false == res)
00268                             ok = false;
00269                         break;
00270                     }
00271                     KexiDB::Field* f = destConn->setupField( data );
00272                     if (f)
00273                         t->addField(f);
00274                     else
00275                         ok = false;
00276                 }
00277                 if (ok)
00278                     ok = destConn->drv_createTable(*t);
00279                 if (ok)
00280                     m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.append(t);
00281             }
00282             if (!ok)
00283                 delete t;
00284         }
00285     }
00286 
00287     // Step 6 - Copy kexi__objects NOW because we'll soon create new objects with new IDs (3.)...
00288     if (ok) {
00289         if (kexi__objects_exists)
00290             ok = drv_copyTable("kexi__objects", destConn, destConn->tableSchema("kexi__objects"));
00291     }
00292 
00293     // Step 7 - Create the non-KexiDB-compatible tables: new IDs will be assigned to them
00294     if (ok) {
00295         KexiDB::TableSchema *ts;
00296         for (QPtrListIterator<TableSchema> it (m_tableSchemas); (ts = it.current()); ++it) {
00297             ok = destConn->createTable( ts );
00298             if (!ok) {
00299                 kdDebug() << "Failed to create a table " << ts->name() << endl;
00300                 destConn->debugError();
00301                 if (result)
00302                     result->setStatus(destConn,
00303                         i18n("Could not create database \"%1\".")
00304                         .arg(m_migrateData->destination->databaseName()));
00305                 m_tableSchemas.remove(ts);
00306                 break;
00307             }
00308             updateProgress((Q_ULLONG)NUM_OF_ROWS_PER_CREATE_TABLE);
00309         }
00310     }
00311 
00312     if (ok)
00313         ok = destConn->commitTransaction(trans);
00314 
00315     if (ok) {
00316         //add compatible tables to the list, so data will be copied, if needed
00317         if (m_migrateData->keepData) {
00318             for(QPtrListIterator<TableSchema> it (m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport);
00319                 it.current(); ++it)
00320             {
00321                 m_tableSchemas.append(it.current());
00322             }
00323         }
00324         else
00325             m_tableSchemas.clear();
00326     }
00327 
00328     if (ok) {
00329         if (m_destPrj->error()) {
00330             ok = false;
00331             if (result)
00332                 result->setStatus(m_destPrj,
00333                     i18n("Could not import project from data source \"%1\".")
00334                     .arg(m_migrateData->source->serverInfoString()));
00335         }
00336     }
00337 
00338     // Step 8 - Copy data if asked to
00339     if (ok) {
00340         trans = destConn->beginTransaction();
00341         ok = !trans.isNull();
00342     }
00343     if (ok) {
00344         if (m_migrateData->keepData) {
00346             // Copy data for "kexi__objectdata" as well, if available in the source db
00347             if (tables.find("kexi__objectdata")!=tables.end())
00348                 m_tableSchemas.append(destConn->tableSchema("kexi__objectdata")); 
00349             // Copy data for "kexi__blobs" as well, if available in the source db
00350             if (tables.find("kexi__blobs")!=tables.end())
00351                 m_tableSchemas.append(destConn->tableSchema("kexi__blobs")); 
00352             // Copy data for "kexi__fields" as well, if available in the source db
00353             if (tables.find("kexi__fields")!=tables.end())
00354                 m_tableSchemas.append(destConn->tableSchema("kexi__fields")); 
00355         }
00356 
00357         for(QPtrListIterator<TableSchema> ts(m_tableSchemas); ok && ts.current() != 0 ; ++ts)
00358         {
00359             const QString tname( ts.current()->name().lower() );
00360             if (destConn->driver()->isSystemObjectName( tname )
00363                 && tname!="kexi__objectdata" //copy this too
00364                 && tname!="kexi__blobs" //copy this too
00365                 && tname!="kexi__fields" //copy this too
00366             )
00367             {
00368                 kdDebug() << "Do not copy data for system table: " << tname << endl;
00370                 continue;
00371             }
00372             kdDebug() << "Copying data for table: " << tname << endl;
00373             QString originalTableName;
00374             if (kexiDBTables.find(tname)==kexiDBTables.end())
00375                 //caption is equal to the original name
00376                 originalTableName = ts.current()->caption().isEmpty() ? tname : ts.current()->caption();
00377             else
00378                 originalTableName = tname;
00379             ok = drv_copyTable(originalTableName, destConn, ts.current());
00380             if (!ok) {
00381                 kdDebug() << "Failed to copy table " << tname << endl;
00382                 if (result)
00383                     result->setStatus(destConn,
00384                         i18n("Could not copy table \"%1\" to destination database.").arg(tname));
00385                 break;
00386             }
00387         }//for
00388     }
00389 
00390     // Done.
00391     if (ok)
00392         ok = destConn->commitTransaction(trans);
00393 
00394     if (ok)
00395         ok = drv_disconnect();
00396 
00397     m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.clear();
00398 
00399     if (ok) {
00400         if (destConn)
00401             ok = destConn->disconnect();
00402         return ok;
00403     }
00404 
00405     // Finally: error handling
00406     if (result && result->error())
00407         result->setStatus(destConn,
00408             i18n("Could not import data from data source \"%1\".")
00409                 .arg(m_migrateData->source->serverInfoString()));
00410     if (destConn) {
00411         destConn->debugError();
00412         destConn->rollbackTransaction(trans);
00413     }
00414     drv_disconnect();
00415     if (destConn) {
00416         destConn->disconnect();
00417         destConn->dropDatabase(m_migrateData->destination->databaseName());
00418     }
00419     return false;
00420 }
00421 //=============================================================================
00422 
00423 bool KexiMigrate::performExport(Kexi::ObjectStatus* result)
00424 {
00425     if (result)
00426         result->clearStatus();
00427 
00429 
00430     return false;
00431 }
00432 
00433 //=============================================================================
00434 // Functions for getting table data
00435 bool KexiMigrate::tableNames(QStringList & tn)
00436 {
00438     kdDebug() << "Reading list of tables..." << endl;
00439     return drv_tableNames(tn);
00440 }
00441 
00442 //=============================================================================
00443 // Progress functions
00444 bool KexiMigrate::progressInitialise() {
00445     Q_ULLONG sum = 0, size;
00446     emit progressPercent(0);
00447 
00449     QStringList tables;
00450     if(!tableNames(tables))
00451         return false;
00452 
00453     // 1) Get the number of rows/bytes to import
00454     int tableNumber = 1;
00455     for(QStringList::Iterator it = tables.begin();
00456         it != tables.end(); ++it, tableNumber++)
00457     {
00458         if(drv_getTableSize(*it, size)) {
00459             kdDebug() << "KexiMigrate::progressInitialise() - table: " << *it 
00460                       << "size: " << (ulong)size << endl;
00461             sum += size;
00462             emit progressPercent(tableNumber * 5 /* 5% */ / tables.count());
00463         } else {
00464             return false;
00465         }
00466     }
00467 
00468     kdDebug() << "KexiMigrate::progressInitialise() - job size: " << (ulong)sum << endl;
00469     m_progressTotal = sum;
00470     m_progressTotal += tables.count() * NUM_OF_ROWS_PER_CREATE_TABLE;
00471     m_progressTotal = m_progressTotal * 105 / 100; //add 5 percent for above task 1)
00472     m_progressNextReport = sum / 100;
00473     m_progressDone = m_progressTotal * 5 / 100; //5 perecent already done in task 1)
00474     return true;
00475 }
00476 
00477 
00478 void KexiMigrate::updateProgress(Q_ULLONG step) {
00479     m_progressDone += step;
00480     if (m_progressDone >= m_progressNextReport) {
00481         int percent = (m_progressDone+1) * 100 / m_progressTotal;
00482         m_progressNextReport = ((percent + 1) * m_progressTotal) / 100;
00483         kdDebug() << "KexiMigrate::updateProgress(): " << (ulong)m_progressDone << "/"
00484                   << (ulong)m_progressTotal << " (" << percent << "%) next report at " 
00485                   << (ulong)m_progressNextReport << endl;
00486         emit progressPercent(percent);
00487     }
00488 }
00489 
00490 //=============================================================================
00491 // Prompt the user to choose a field type
00492 KexiDB::Field::Type KexiMigrate::userType(const QString& fname)
00493 {
00494     KInputDialog *dlg;
00495     QStringList  types;
00496     QString res;
00497 
00498     types << "Byte";
00499     types << "Short Integer";
00500     types << "Integer";
00501     types << "Big Integer";
00502     types << "Boolean";
00503     types << "Date";
00504     types << "Date Time";
00505     types << "Time";
00506     types << "Float";
00507     types << "Double";
00508     types << "Text";
00509     types << "Long Text";
00510     types << "Binary Large Object";
00511 
00512     res = dlg->getItem( i18n("Field Type"),
00513                         i18n("The data type for %1 could not be determined. "
00514                  "Please select one of the following data "
00515                  "types").arg(fname),
00516                       types, 0, false);
00517 
00519     if (res == *types.at(0))
00520         return KexiDB::Field::Byte;
00521     else if (res == *types.at(1))
00522         return KexiDB::Field::ShortInteger;
00523     else if (res == *types.at(2))
00524         return KexiDB::Field::Integer;
00525     else if (res == *types.at(3))
00526         return KexiDB::Field::BigInteger;
00527     else if (res == *types.at(4))
00528         return KexiDB::Field::Boolean;
00529     else if (res == *types.at(5))
00530         return KexiDB::Field::Date;
00531     else if (res == *types.at(6))
00532         return KexiDB::Field::DateTime;
00533     else if (res == *types.at(7))
00534         return KexiDB::Field::Time;
00535     else if (res == *types.at(8))
00536         return KexiDB::Field::Float;
00537     else if (res == *types.at(9))
00538         return KexiDB::Field::Double;
00539     else if (res == *types.at(10))
00540         return KexiDB::Field::Text;
00541     else if (res == *types.at(11))
00542         return KexiDB::Field::LongText;
00543     else if (res == *types.at(12))
00544         return KexiDB::Field::BLOB;
00545     else
00546         return KexiDB::Field::Text;
00547 }
00548 
00549 QVariant KexiMigrate::propertyValue( const QCString& propName )
00550 {
00551     return m_properties[propName.lower()];
00552 }
00553 
00554 QString KexiMigrate::propertyCaption( const QCString& propName ) const
00555 {
00556     return m_propertyCaptions[propName.lower()];
00557 }
00558 
00559 void KexiMigrate::setPropertyValue( const QCString& propName, const QVariant& value )
00560 {
00561     m_properties[propName.lower()] = value;
00562 }
00563 
00564 QValueList<QCString> KexiMigrate::propertyNames() const
00565 {
00566     QValueList<QCString> names = m_properties.keys();
00567     qHeapSort(names);
00568     return names;
00569 }
00570 
00571 bool KexiMigrate::isValid()
00572 {
00573     if (KexiMigration::versionMajor() != versionMajor()
00574         || KexiMigration::versionMinor() != versionMinor())
00575     {
00576         setError(ERR_INCOMPAT_DRIVER_VERSION,
00577         i18n("Incompatible migration driver's \"%1\" version: found version %2, expected version %3.")
00578         .arg(name())
00579         .arg(QString("%1.%2").arg(versionMajor()).arg(versionMinor()))
00580         .arg(QString("%1.%2").arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor())));
00581         return false;
00582     }
00583     return true;
00584 }
00585 
00586 bool KexiMigrate::drv_queryMaxNumber(const QString& tableName, 
00587     const QString& columnName, int& result)
00588 {
00589     QString string;
00590     tristate r = drv_querySingleStringFromSQL(
00591         QString::fromLatin1("SELECT MAX(%1) FROM %2").arg(drv_escapeIdentifier(columnName))
00592         .arg(drv_escapeIdentifier(tableName)), 0, string);
00593     if (r == false)
00594         return false;
00595     if (~r) {
00596         result = 0;
00597         return true;
00598     }
00599     bool ok;
00600     int tmpResult = string.toInt(&ok);
00601     if (ok)
00602         result = tmpResult;
00603     return ok;
00604 }
00605 
00606 tristate KexiMigrate::drv_querySingleStringFromSQL(
00607     const QString& sqlStatement, uint columnNumber, QString& string)
00608 {
00609     QStringList stringList;
00610     const tristate res = drv_queryStringListFromSQL(sqlStatement, columnNumber, stringList, 1);
00611     if (true == res)
00612         string = stringList.first();
00613     return res;
00614 }
00615 
00616 #include "keximigrate.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys