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 }