File indexing completed on 2024-04-21 15:29:53

0001 /* This file is part of the KDE project
0002    Copyright (C) 2008 Sharan Rao <sharanrao@gmail.com>
0003 
0004    This program is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This program is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this program; see the file COPYING.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "XbaseExport.h"
0021 
0022 #include <QHash>
0023 #include <QDir>
0024 
0025 
0026 #include "KDbField.h"
0027 #include "KDbRecordData.h"
0028 #include "KDbCursor.h"
0029 #include "KDbDriverManager.h"
0030 #include <core/kexi.h>
0031 #include <migration/keximigratedata.h>
0032 
0033 #include <cstring>
0034 
0035 #include "xbase.h"
0036 
0037 class KDbxBaseExportPrivate {
0038   public:
0039     xBaseExportPrivate() {
0040     }
0041 
0042     //! Converts KDbField types to xbase types
0043     char type(KDbField::Type fieldType);
0044 
0045     //! Appends record to xbase table
0046     bool appendRecord(const QString& sourceTableName , KDbRecordData* recordData);
0047 
0048     //! Returns max fieldlengths for xBase table
0049     int fieldLength(KDbField* f );
0050 
0051     //! converts QVariant data to a format understood by xBase
0052     QByteArray fieldData(QVariant data, char type);
0053 
0054     //! Creates xBase indexes for the table
0055     bool createIndexes(const QString& sourceTableName, KDbTableSchema* tableSchema);
0056 
0057     xbXBase xbase;
0058     QHash<QString, QString> tableNamePathMap;
0059 };
0060 
0061 char xBaseExportPrivate::type(KDbField::Type fieldType)
0062 {
0063   char xBaseType = '\0';
0064 
0065   switch( fieldType ) {
0066     case KDbField::Text:
0067     case KDbField::LongText:
0068       xBaseType = XB_CHAR_FLD;
0069       break;
0070 
0071     case KDbField::Boolean:
0072       xBaseType = XB_LOGICAL_FLD;
0073       break;
0074 
0075     case KDbField::Float:
0076     case KDbField::Double:
0077       xBaseType = XB_FLOAT_FLD;
0078 
0079     case KDbField::ShortInteger:
0080     case KDbField::Integer:
0081     case KDbField::BigInteger:
0082       xBaseType = XB_NUMERIC_FLD;
0083       break;
0084 
0085     case KDbField::DateTime:
0086     case KDbField::Date:
0087     case KDbField::Time:
0088       xBaseType = XB_DATE_FLD;
0089       break;
0090 
0091     case KDbField::BLOB:
0092       xBaseType = XB_MEMO_FLD;
0093       break;
0094 
0095     default:
0096       xBaseType = '\0';
0097   }
0098 
0099   return xBaseType;
0100 }
0101 
0102 bool xBaseExportPrivate::appendRecord( const QString& sourceTableName , KDbRecordData* recordData ) {
0103 
0104 //     xbaseDebug() << recordData->debugString();
0105   QString pathName = tableNamePathMap.value( sourceTableName );
0106   QByteArray pathNameBa = pathName.toLatin1();
0107   xbDbf* table = xbase.GetDbfPtr( pathNameBa.constData() );
0108 
0109   int returnCode;
0110   table->BlankRecord();
0111   for (int i=0;i < recordData->size();++i) {
0112   char fieldType = table->GetFieldType(i);
0113   QByteArray stringData = fieldData(recordData->value(i), fieldType);
0114 
0115   if (fieldType == XB_MEMO_FLD) {
0116   #ifdef XB_MEMO_FIELDS
0117     // we use size()+1 as size to accommodate `\0`
0118     table->UpdateMemoData(i, stringData.size()+1, stringData.constData(), F_SETLKW );
0119   #else
0120     xbaseDebug() << "XB_MEMO_FIELDS support disabled during compilation of XBase libraries";
0121   #endif
0122   } else {
0123     if ((returnCode = table->PutField( i, stringData.constData())) != XB_NO_ERROR ) {
0124       switch(returnCode) {
0125       case XB_INVALID_FIELDNO:
0126         xbaseWarning() << "Invalid field number" << i;
0127         return false;
0128       case XB_INVALID_DATA:
0129         xbaseWarning() << "Invalid data" << stringData;
0130         return false;
0131       default:
0132         xbaseWarning() << "Error number" << returnCode << "has occurred";
0133         return false;
0134     }
0135     }
0136   }
0137   }
0138 
0139   if((returnCode = table->AppendRecord()) != XB_NO_ERROR) {
0140     xbaseWarning() << "xBase Error" << returnCode << "appending data record.";
0141     return false;
0142   }
0143 
0144 //     // for debugging purposes only
0145 //     for ( int i=0; i< recordData->size(); ++i ) {
0146 //         xbaseDebug() << table->GetField(i);
0147 //     }
0148 
0149   return true;
0150 }
0151 
0152 int xBaseExportPrivate::fieldLength(KDbField* f)
0153 {
0154   const Field::Type t = f->type(); // cache: evaluating type of expressions can be expensive
0155   if (KDbField::isTextType(t)) {
0156     return f->maxLength();
0157   }
0158   // return the max possible (string)length of the types
0159   // see https://linux.techass.com/projects/xdb/xbasedocs/xbase_c3.html
0160   switch(type(t)) {
0161     case XB_CHAR_FLD:
0162       return 254;
0163     case XB_LOGICAL_FLD:
0164       return 1;
0165     case XB_FLOAT_FLD:
0166     case XB_NUMERIC_FLD:
0167       return 17;
0168     case XB_DATE_FLD:
0169       return 8;
0170     case XB_MEMO_FLD:
0171       return 10;
0172     default:
0173       return 0;
0174   }
0175 }
0176 
0177 QByteArray xBaseExportPrivate::fieldData(QVariant data, char type) {
0178 
0179   switch(type) {
0180     case XB_CHAR_FLD:
0181     case XB_FLOAT_FLD:
0182     case XB_NUMERIC_FLD:
0183       return data.toString().toUtf8();
0184 
0185     case XB_LOGICAL_FLD:
0186       if (data.toBool()) {
0187         return QString( "t" ).toLatin1();
0188       } else
0189         return QString( "f" ).toLatin1();
0190 
0191     case XB_DATE_FLD:
0192       return data.toDate().toString("yyyyMMdd").toLatin1();
0193 
0194     case XB_MEMO_FLD:
0195       return data.toByteArray();
0196     default:
0197       return QByteArray();
0198   }
0199 }
0200 
0201 bool xBaseExportPrivate::createIndexes(const QString& sourceTableName, KDbTableSchema* tableSchema) {
0202 
0203   QString pathName = tableNamePathMap.value( sourceTableName );
0204   QByteArray pathNameBa = pathName.toLatin1();
0205   xbDbf* table = xbase.GetDbfPtr( pathNameBa.constData() );
0206   int fieldCount = tableSchema->fieldCount();
0207 
0208   QString dirName = QFileInfo( pathName ).path();
0209 
0210   for (int i=0; i< fieldCount ; ++i) {
0211     KDbField* f = tableSchema->field(i);
0212 
0213     int returnCode;
0214     QString fieldName = f->name();
0215     QString indexName = dirName + QDir::separator() + sourceTableName + '_' + fieldName + ".ndx";
0216     QByteArray indexNameBa = indexName.toLatin1();
0217     QByteArray fieldNameBa = fieldName.toLatin1();
0218 
0219     xbNdx index(table);
0220     if (f->isUniqueKey() || f->isPrimaryKey()) {
0221 
0222       if ((returnCode = index.CreateIndex(indexNameBa.constData(), fieldNameBa.constData(), XB_UNIQUE, XB_OVERLAY)) != XB_NO_ERROR ) {
0223         xbaseWarning() << "Couldn't create unique index for fieldName"
0224                        << fieldName << "on table" << sourceTableName << "Error Code" << returnCode;
0225         return false;
0226       }
0227       index.CloseIndex();
0228 
0229     } else if ( f->isIndexed() ) {
0230 
0231       if ((returnCode = index.CreateIndex(indexNameBa.constData(), fieldNameBa.constData(), XB_NOT_UNIQUE, XB_OVERLAY)) != XB_NO_ERROR ) {
0232         xbaseWarning() << "Couldn't create index for fieldName" << fieldName << "on table"
0233                        << sourceTableName << "Error Code" << returnCode;
0234         return false;
0235       }
0236       index.CloseIndex();
0237 
0238     }
0239   }
0240   return true;
0241 }
0242 
0243 
0244 xBaseExport::xBaseExport()
0245 : m_migrateData( 0 ),
0246 d(new xBaseExportPrivate)
0247 {
0248 }
0249 
0250 void xBaseExport::setData(KexiMigration::Data* migrateData) {
0251   m_migrateData = migrateData;
0252 }
0253 
0254 bool xBaseExport::performExport(Kexi::ObjectStatus* result) {
0255 
0256   if (result)
0257     result->clearStatus();
0258 
0259 
0260   KDbDriverManager drvManager;
0261 
0262   if (!m_migrateData) {
0263     xbaseWarning() << "Migration Data not set yet";
0264     result->setStatus(&drvManager, tr("Data not set for migration."));
0265     return false;
0266   }
0267 
0268   KDbDriver *sourceDriver = drvManager.driver(m_migrateData->source->driverId);
0269   if (!sourceDriver) {
0270     result->setStatus(&drvManager,
0271       tr("Could not export back to destination database."));
0272     return false;
0273   }
0274 
0275   // connect to destination database
0276   if (!dest_connect()) {
0277     xbaseWarning() << "Couldn't connect to destination database";
0278     if (result)
0279       result->setStatus(tr("Could not connect to data source \"%1\".",
0280         m_migrateData->destination->connectionData()->serverInfoString()), "");
0281     return false;
0282   }
0283 
0284   KDbConnection* sourceConn = sourceDriver->createConnection(*(m_migrateData->source));
0285 
0286   if (!sourceConn || sourceDriver->error()) {
0287     xbaseWarning() << "Export failed";
0288     return false;
0289   }
0290   if (!sourceConn->connect()) {
0291     xbaseWarning() << "Export failed.Could not connect";
0292     return false;
0293   }
0294 
0295   if (!sourceConn->useDatabase(m_migrateData->sourceName)) {
0296     xbaseWarning() << "Couldn't use database "<<m_migrateData->sourceName;
0297     return false;
0298   }
0299 
0300   QStringList tables = sourceConn->tableNames();
0301 
0302   // Check if there are any tables
0303   if (tables.isEmpty()) {
0304     xbaseDebug() << "There were no tables to export";
0305     if (result)
0306       result->setStatus(
0307         tr("No tables to export found in data source \"%1\".",
0308           m_migrateData->source->serverInfoString()), "");
0309     return false;
0310   }
0311 
0312   tables.sort();
0313 
0314   // -- read table schemas and create them in memory (only for non-KDb-compat tables)
0315   foreach (const QString& tableCaption, tables) {
0316     if (dest_isSystemObjectName( tableCaption )) {
0317       return false;
0318     }
0319 
0320     KDbTableSchema *tableSchema = sourceConn->tableSchema( tableCaption );
0321 
0322     if (!dest_createTable(tableCaption, tableSchema)) {
0323       if (result)
0324         result->setStatus(tr("Could not create table in destination \"%1\". Error reading table \"%2\".",    m_migrateData->destination->connectionData()->serverInfoString(), tableCaption), "");
0325       return false;
0326     }
0327 
0328     if (m_migrateData->keepData) {
0329       if (!dest_copyTable(tableCaption, sourceConn, tableSchema)) {
0330         xbaseWarning() << "Failed to copy table " << tableCaption;
0331         if (result)
0332           result->setStatus(sourceConn,
0333               tr("Could not copy table \"%1\" to destination database.", tableCaption));
0334       }
0335     }
0336 
0337   }
0338 
0339   if (dest_disconnect()) {
0340     bool ok = false;
0341     if (sourceConn)
0342       ok = sourceConn->disconnect();
0343     return ok;
0344   }
0345 
0346   // Finally: Error.handling
0347   if (result && result->error())
0348     result->setStatus(sourceConn,
0349       tr("Could not export data to \"%1\".",
0350         m_migrateData->source->serverInfoString()));
0351   dest_disconnect();
0352   if (sourceConn) {
0353     sourceConn->disconnect();
0354   }
0355   return false;
0356 }
0357 
0358 bool xBaseExport::dest_connect() {
0359   return true;
0360 }
0361 
0362 bool xBaseExport::dest_disconnect() {
0363   QList<QString> pathNameList = d->tableNamePathMap.values();
0364   foreach(const QString& pathName, pathNameList) {
0365     QByteArray ba = pathName.toLatin1();
0366     xbDbf* tablePtr = d->xbase.GetDbfPtr(ba.constData());
0367     tablePtr->CloseDatabase();
0368     // delete tablePtr ?
0369   }
0370   return true;
0371 }
0372 
0373 bool xBaseExport::dest_createTable(const QString& originalName, KDbTableSchema* tableSchema) {
0374   // Algorithm
0375   // 1. For each fields in the table schema.
0376   // 2.   Create a xbSchema entry and add it to xbSchema array.
0377   // 3. End for
0378   // 4. Create table in overlay mode ( overwrite )
0379 
0380   int fieldCount = tableSchema->fieldCount();
0381   const int arrayLength = fieldCount + 1; // and extra space for the `null`
0382   xbSchema xBaseTableSchema[arrayLength];// = new xbSchema[fieldCount+1][4];
0383 
0384   int i = 0;
0385   for (i = 0; i < fieldCount ; ++i) {
0386     KDbField* f = tableSchema->field(i);
0387 
0388     QByteArray ba = f->name().toLatin1();
0389     //! @todo Fieldname can only be 11 characters
0390     strcpy(xBaseTableSchema[i].FieldName, ba.data());
0391     xBaseTableSchema[i].Type = d->type(f->type());
0392     xBaseTableSchema[i].FieldLen = d->fieldLength( f ); //!< @todo Check semantics
0393     xBaseTableSchema[i].NoOfDecs = ( xBaseTableSchema[i].Type != XB_CHAR_FLD )? f->scale() : 0 ;
0394 
0395   }
0396 
0397   // last member should be all 0
0398   strcpy( xBaseTableSchema[i].FieldName , "" );
0399   xBaseTableSchema[i].Type = 0;
0400   xBaseTableSchema[i].FieldLen = 0;
0401   xBaseTableSchema[i].NoOfDecs = 0;
0402 
0403   const KDbConnectionData* connData = m_migrateData->destination->connectionData();
0404   QString dirName = connData->fileName(); // this includes the forward slash after the dir name
0405 
0406   QString pathName = dirName + originalName + ".dbf";
0407   d->tableNamePathMap[originalName] = pathName;
0408 
0409   QByteArray pathNameBa = pathName.toLatin1();
0410 
0411   xbDbf* xBaseTable = new xbDbf( &d->xbase );
0412   xBaseTable->SetVersion( 4 ); // create dbase IV style files
0413   xbShort returnCode;
0414   if (( returnCode = xBaseTable->CreateDatabase( pathNameBa.constData() , xBaseTableSchema, XB_OVERLAY ))  != XB_NO_ERROR ) {
0415     xbaseWarning() << "Error creating table" << originalName << "Error Code" << returnCode;
0416     return false;
0417   }
0418 
0419   if (!d->createIndexes(originalName, tableSchema)) {
0420     return false;
0421   }
0422 
0423   return true;
0424 }
0425 
0426 bool xBaseExport::dest_copyTable(const QString& srcTableName, KDbConnection *srcConn,
0427         KDbTableSchema* /*srcTable*/) {
0428   // Algorithm
0429   // 1. pick each row
0430   // 2. Insert it into the xBase table
0431 
0432   // using the tableSchema as argument automatically appends rowid
0433   // info to the recordData which we don't want. Hence we use SQL query
0434   KDbCursor* cursor = srcConn->executeQuery(KDbEscapedString( "SELECT * FROM %1" ).arg(srcTableName));
0435 
0436   if (!cursor)
0437     return false;
0438 
0439   if (!cursor->moveFirst() && cursor->error())
0440     return false;
0441 
0442   while (!cursor->eof()) {
0443     KDbRecordData *record = cursor->storeCurrentRecord();
0444     if (!record) {
0445       return false;
0446     }
0447     if (!d->appendRecord(srcTableName, record)) {
0448       xbaseWarning() << "Couldn't append record";
0449       return false;
0450     }
0451 
0452     if (!cursor->moveNext() && cursor->error()) {
0453       return false;
0454     }
0455   }
0456   return true;
0457 }
0458 
0459 bool xBaseExport::dest_isSystemObjectName( const QString& /* objectName */ ) {
0460   return false;
0461 }