You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
keximdb/keximdb/src/keximdb/mdbmigrate.cpp

485 lines
14 KiB

/* This file is part of the KDE project
Copyright (C) 2005,2006 Martin Ellis <martin.ellis@kdemail.net>
Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
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., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#include "mdbmigrate.h"
#include <ntqstring.h>
#include <ntqcstring.h>
#include <ntqregexp.h>
#include <ntqfile.h>
#include <ntqvariant.h>
#include <ntqdatetime.h>
#include <ntqvaluelist.h>
#include <kdebug.h>
#include <kexiutils/identifier.h>
using namespace KexiMigration;
/* This is the implementation for the MDB file import routines. */
KEXIMIGRATE_DRIVER_INFO( MDBMigrate, mdb );
static TQCString isNonUnicodePropId( "source_database_has_nonunicode_encoding" );
static TQCString nonUnicodePropId( "source_database_nonunicode_encoding" );
/* ************************************************************************** */
MDBMigrate::MDBMigrate(TQObject *parent, const char *name,
const TQStringList &args) :
KexiMigrate(parent, name, args)
{
/*! @todo invert the sense of values, then remove "Non-" from these strings */
m_properties[ isNonUnicodePropId ] = TQVariant( true );
m_propertyCaptions[ isNonUnicodePropId ] =
i18n("Source Database Has Non-Unicode Encoding");
m_properties[ nonUnicodePropId ] = TQVariant("");
m_propertyCaptions[ nonUnicodePropId ]
= i18n("Source Database Non-Unicode Encoding");
initBackend();
}
/* ************************************************************************** */
//! Destructor
MDBMigrate::~MDBMigrate() {
releaseBackend();
}
/* ************************************************************************** */
void MDBMigrate::initBackend() {
mdb_init();
// Date format associated with TQt::ISODate: YYYY-MM-DDTHH:MM:SS
// (where T is a literal). The following is equivalent to %FT%T, but
// backards compatible with old/Windows C libraries.
// See strftime documentation for more info.
mdb_set_date_fmt("%Y-%m-%dT%H:%M%:%S");
}
void MDBMigrate::releaseBackend() {
mdb_exit();
}
/* ************************************************************************** */
/*! Properties */
TQVariant MDBMigrate::propertyValue( const TQCString& propName )
{
if ( propName == isNonUnicodePropId ) {
m_properties[ isNonUnicodePropId ] = TQVariant(false);
// Costly, but we need this to get this property from file...
drv_connect();
drv_disconnect();
}
return KexiMigrate::propertyValue( propName );
}
/* ************************************************************************** */
/*! Connect to the db backend */
bool MDBMigrate::drv_connect() {
kdDebug() << "mdb_open:" << endl;
KexiDB::ConnectionData *data = m_migrateData->source;
// mdb_open takes a char*, not const char*, hence this nonsense.
char *filename = tqstrdup(TQFile::encodeName(data->fileName()));
m_mdb = mdb_open (filename, MDB_NOFLAGS);
delete [] filename;
if (!m_mdb) {
kdDebug() << "mdb_open failed." << endl;
return false;
}
// Setting source encoding
if ( !m_properties[ nonUnicodePropId ].toCString().isEmpty() ) {
TQCString encoding = m_properties[ nonUnicodePropId ].toCString();
mdb_set_encoding( m_mdb, (const char*) encoding );
kdDebug() << "non-unicode encoding set to \""
<< encoding
<< "\"" << endl;
}
// Supports setting source encoding
m_properties[ isNonUnicodePropId ] = TQVariant( IS_JET3(m_mdb) );
return true;
}
/*! Disconnect from the db backend */
bool MDBMigrate::drv_disconnect()
{
mdb_close( m_mdb );
return true;
}
//! Get the table definition for a given table name
/*! Look up the table definition for the given table. This only returns a ptr
to the MdbTableDef - it doesn't load e.g. the column data.
Remember to mdb_free_tabledef the table definition when it's finished
with.
\return the table definition, or null if no matching table was found
*/
MdbTableDef* MDBMigrate::getTableDef(const TQString& tableName)
{
MdbTableDef *tableDef = 0;
// Look through each entry in the catalogue ...
for (unsigned int i = 0; i < m_mdb->num_catalog; i++) {
MdbCatalogEntry* dbObject =
(MdbCatalogEntry*)(g_ptr_array_index (m_mdb->catalog, i));
// ... for a table with the given name
if (dbObject->object_type == MDB_TABLE) {
TQString dbObjectName = TQString::fromUtf8(dbObject->object_name);
if (dbObjectName.lower() == tableName.lower()) {
tableDef = mdb_read_table(dbObject);
break;
}
}
}
return tableDef;
}
/* ************************************************************************** */
/*! Get the types and properties for each column. */
bool MDBMigrate::drv_readTableSchema( const TQString& originalName,
KexiDB::TableSchema& tableSchema )
{
//! Get the column meta-data
MdbTableDef *tableDef = getTableDef(originalName);
if(!tableDef) {
kdDebug() << "MDBMigrate::drv_getTableDef: couldn't find table "
<< originalName << endl;
return false;
}
mdb_read_columns(tableDef);
kdDebug() << "MDBMigrate::drv_readTableSchema: #cols = "
<< tableDef->num_cols << endl;
/*! Convert column data to Kexi TableSchema
Nice mix of terminology here, MDBTools has columns, Kexi has fields. */
MdbColumn *col;
for (unsigned int i = 0; i < tableDef->num_cols; i++) {
col = (MdbColumn*) g_ptr_array_index(tableDef->columns, i);
// Field name
TQString fldName = TQString::fromUtf8(col->name);
kdDebug() << "MDBMigrate::drv_readTableSchema: got column "
<< fldName << "\"" << col->name << endl;
TQString fldID( KexiUtils::string2Identifier(fldName) );
// Field type
KexiDB::Field *fld =
new KexiDB::Field(fldID, type(col->col_type));
kdDebug() << "MDBMigrate::drv_readTableSchema: size "
<< col->col_size << " type " << type(col->col_type) <<endl;
fld->setCaption(fldName);
tableSchema.addField(fld);
}
getPrimaryKey(&tableSchema, tableDef);
//! Free the column meta-data - as soon as it doesn't seg fault.
//mdb_free_tabledef(tableDef);
return true;
}
/*! Get a list of tables and put into the supplied string list */
bool MDBMigrate::drv_tableNames(TQStringList& tableNames)
{
// Try to read the catalogue of database objects
if (!mdb_read_catalog (m_mdb, MDB_ANY)) {
kdDebug() << "MDBMigrate::drv_tableNames: couldn't read catalogue" << endl;
return false;
}
// Add non-system tables to the list
for (unsigned int i = 0; i < m_mdb->num_catalog; i++) {
MdbCatalogEntry* dbObject =
(MdbCatalogEntry*)(g_ptr_array_index (m_mdb->catalog, i));
if (dbObject->object_type == MDB_TABLE) {
TQString dbObjectName = TQString::fromUtf8(dbObject->object_name);
if (!dbObjectName.startsWith("MSys")) {
kdDebug() << "MDBMigrate::drv_tableNames: " << dbObjectName << endl;
tableNames << dbObjectName;
}
}
}
return true;
}
TQVariant MDBMigrate::toTQVariant(const char* data, unsigned int len, int type) {
if(len == 0) {
// Null ptr => null value
return TQVariant();
} else {
switch (type) {
case MDB_TEXT:
case MDB_MEMO:
return TQVariant( TQString::fromUtf8(data, len) );
case MDB_BOOL: //! @todo use &bool!
case MDB_BYTE:
return TQString::fromUtf8(data, len).toShort();
case MDB_SDATETIME:
return TQDateTime::fromString(data, TQt::ISODate);
case MDB_INT:
case MDB_LONGINT:
return TQString::fromUtf8(data, len).toLongLong();
case MDB_FLOAT:
return TQString::fromUtf8(data, len).toFloat();
case MDB_DOUBLE:
case MDB_MONEY: //! @todo
case MDB_NUMERIC: //! @todo
return TQString::fromUtf8(data, len).toDouble();
case MDB_OLE:
case MDB_REPID:
default:
return TQVariant(TQString::fromUtf8(data, len));
}
}
}
/*! Copy MDB table to KexiDB database */
bool MDBMigrate::drv_copyTable( const TQString& srcTable,
KexiDB::Connection *destConn,
KexiDB::TableSchema* dstTable)
{
TQString kdLoc = "MDBMigrate::drv_copyTable: ";
MdbTableDef *tableDef = getTableDef(srcTable);
if(!tableDef) {
kdDebug() << kdLoc << srcTable << endl;
return false;
}
char *columnData[256];
int columnDataLength[256];
//! Bind + allocate the DB columns to columnData and columnDataLength arrays
mdb_read_columns(tableDef); // mdb_bind_column dies without this
for (unsigned int i = 0; i < tableDef->num_cols; i++) {
columnData[i] = (char*) g_malloc(MDB_BIND_SIZE);
// Columns are numbered from 1
// and why aren't these unsigned ints?
mdb_bind_column(tableDef, i + 1, columnData[i], &columnDataLength[i]);
}
//! Copy each row into vals
mdb_rewind_table(tableDef);
kdDebug() << kdLoc << "Fetching " << tableDef->num_rows << " rows" << endl;
#ifdef KEXI_MIGRATION_MAX_ROWS_TO_IMPORT
TQ_ULLONG rows=0;
#endif
bool ok = true;
while(mdb_fetch_row(tableDef)) {
TQValueList<TQVariant> vals = TQValueList<TQVariant>();
// kdDebug() << kdLoc << "Copying " << tableDef->num_cols << " cols" << endl;
for (unsigned int i = 0; i < tableDef->num_cols; i++) {
MdbColumn *col = (MdbColumn*) g_ptr_array_index(tableDef->columns, i);
if (col->col_type == MDB_OLE && col->cur_value_len) {
mdb_ole_read(m_mdb, col, columnData[i], MDB_BIND_SIZE);
}
//! @todo: How to import binary data?
TQVariant var = toTQVariant(columnData[i], columnDataLength[i],
col->col_type);
vals << var;
}
if ( !destConn->insertRecord( *dstTable, vals ) ) {
ok = false;
break;
}
updateProgress();
#ifdef KEXI_MIGRATION_MAX_ROWS_TO_IMPORT
//! @todo this is risky when there are references between tables
if (++rows == KEXI_MIGRATION_MAX_ROWS_TO_IMPORT)
break;
#endif
}
//! Deallocate (unbind) the DB columns arrays and column meta-data
for (unsigned int i = 0; i < tableDef->num_cols; i++) {
g_free(columnData[i]);
}
// When memory leaks are better than seg. faults...
//mdb_free_tabledef(tableDef);
return ok;
}
//! Convert an MDB type to a KexiDB type, prompting user if necessary.
KexiDB::Field::Type MDBMigrate::type(int type)
{
// Field type
KexiDB::Field::Type kexiType = KexiDB::Field::InvalidType;
switch(type)
{
case MDB_BOOL:
kexiType = KexiDB::Field::Boolean;
break;
case MDB_BYTE:
kexiType = KexiDB::Field::Byte;
break;
case MDB_INT:
kexiType = KexiDB::Field::Integer;
break;
case MDB_LONGINT:
kexiType = KexiDB::Field::BigInteger;
break;
case MDB_MONEY:
//! @todo temporary simplification
kexiType = KexiDB::Field::Double;
break;
case MDB_FLOAT:
kexiType = KexiDB::Field::Float;
break;
case MDB_DOUBLE:
kexiType = KexiDB::Field::Double;
break;
case MDB_SDATETIME:
kexiType = KexiDB::Field::DateTime;
break;
case MDB_TEXT:
kexiType = KexiDB::Field::LongText;
break;
case MDB_OLE:
kexiType = KexiDB::Field::BLOB;
break;
case MDB_MEMO:
kexiType = KexiDB::Field::LongText;
break;
//! @todo temporary simplification
case MDB_NUMERIC:
kexiType = KexiDB::Field::Double;
break;
case MDB_REPID:
// ?
default:
kexiType = KexiDB::Field::InvalidType;
}
// If we don't know what it is, hope it's text. :o)
if (kexiType == KexiDB::Field::InvalidType) {
return KexiDB::Field::LongText;
}
return kexiType;
}
bool MDBMigrate::getPrimaryKey( KexiDB::TableSchema* table,
MdbTableDef* tableDef ) {
TQString kdLoc = "MDBMigrate::getPrimaryKey: ";
MdbIndex *idx;
if (!tableDef) {
return false;
}
mdb_read_columns(tableDef);
mdb_read_indices(tableDef);
//! Find the PK index in the MDB file
bool foundIdx = false;
for (unsigned int i = 0; i < tableDef->num_idxs; i++) {
idx = (MdbIndex*) g_ptr_array_index (tableDef->indices, i);
TQString fldName = TQString::fromUtf8(idx->name);
if (!strcmp(idx->name, "PrimaryKey")) {
idx = (MdbIndex*) g_ptr_array_index (tableDef->indices, i);
foundIdx = true;
break;
}
}
if(!foundIdx) {
mdb_free_indices(tableDef->indices);
return false;
}
//! @todo: MDB index order (asc, desc)
kdDebug() << kdLoc << "num_keys " << idx->num_keys << endl;
//! Create the KexiDB IndexSchema ...
TQByteArray key_col_num(idx->num_keys);
// MDBTools counts columns from 1 - subtract 1 where necessary
KexiDB::IndexSchema* p_idx = new KexiDB::IndexSchema(table);
for (unsigned int i = 0; i < idx->num_keys; i++) {
key_col_num[i] = idx->key_col_num[i];
kdDebug() << kdLoc << "key " << i + 1
<< " col " << key_col_num[i]
<< table->field(idx->key_col_num[i] - 1)->name()
<< endl;
p_idx->addField(table->field(idx->key_col_num[i] - 1));
}
kdDebug() << kdLoc << p_idx->debugString() << endl;
//! ... and add it to the table definition
// but only if the PK has only one field, so far :o(
KexiDB::Field *f;
if(idx->num_keys == 1 && (f = table->field(idx->key_col_num[0] - 1))) {
f->setPrimaryKey(true);
} else {
//! @todo: How to add a composite PK to a TableSchema?
//m_table->setPrimaryKey(p_idx);
}
mdb_free_indices(tableDef->indices);
return true;
}
bool MDBMigrate::drv_getTableSize(const TQString& table, TQ_ULLONG& size) {
//! Get the column meta-data, which contains the table size
MdbTableDef *tableDef = getTableDef(table);
if(!tableDef) {
kdDebug() << "MDBMigrate::drv_getTableDef: couldn't find table "
<< table << endl;
return false;
}
size = (TQ_ULLONG)(tableDef->num_rows);
mdb_free_tabledef(tableDef);
return true;
}
#include "mdbmigrate.moc"