/* This file is part of the KDE project Copyright (C) 2004 Martin Ellis Copyright (C) 2006 Jaroslaw Staniek 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mysqlmigrate.h" #include #include #include #include #include #include #include #include #include "migration/keximigratedata.h" #include #include #include #include #include #include using namespace KexiMigration; /* This is the implementation for the MySQL specific import routines. */ KEXIMIGRATE_DRIVER_INFO( MySQLMigrate, mysql ) /* ************************************************************************** */ //! Constructor /*MySQLMigrate::MySQLMigrate() : d(new MySqlConnectionInternal()) { }*/ //! Constructor (needed for trading interface) MySQLMigrate::MySQLMigrate(TQObject *tqparent, const char *name, const TQStringList &args) : KexiMigrate(tqparent, name, args) ,d(new MySqlConnectionInternal(0)) ,m_mysqlres(0) { KexiDB::DriverManager manager; m_kexiDBDriver = manager.driver("mysql"); } /* ************************************************************************** */ //! Destructor MySQLMigrate::~MySQLMigrate() { if (m_mysqlres) mysql_free_result(m_mysqlres); m_mysqlres = 0; } /* ************************************************************************** */ /*! Connect to the db backend */ bool MySQLMigrate::drv_connect() { if(d->db_connect(*m_migrateData->source)) { return d->useDatabase(m_migrateData->sourceName); } else { return false; } } /*! Disconnect from the db backend */ bool MySQLMigrate::drv_disconnect() { return d->db_disconnect(); } /* ************************************************************************** */ /*! Get the types and properties for each column. */ bool MySQLMigrate::drv_readTableSchema( const TQString& originalName, KexiDB::TableSchema& tableSchema) { // m_table = new KexiDB::TableSchema(table); // //TODO IDEA: ask for user input for captions // tableSchema.setCaption(table + " table"); //Perform a query on the table to get some data TQString query = TQString("SELECT * FROM `") + drv_escapeIdentifier(originalName) + "` LIMIT 0"; if(d->executeSQL(query)) { MYSQL_RES *res = mysql_store_result(d->mysql); if (res != NULL) { unsigned int numFlds = mysql_num_fields(res); MYSQL_FIELD *fields = mysql_fetch_fields(res); for(unsigned int i = 0; i < numFlds; i++) { TQString fldName(fields[i].name); TQString fldID( KexiUtils::string2Identifier(fldName) ); KexiDB::Field *fld = new KexiDB::Field(fldID, type(originalName, &fields[i])); if(fld->type() == KexiDB::Field::Enum) { TQStringList values = examineEnumField(originalName, &fields[i]); } fld->setCaption(fldName); getConstraints(fields[i].flags, fld); getOptions(fields[i].flags, fld); tableSchema.addField(fld); } mysql_free_result(res); } else { kdDebug() << "MySQLMigrate::drv_tableNames: null result" << endl; } return true; } else { return false; } } /*! Get a list of tables and put into the supplied string list */ bool MySQLMigrate::drv_tableNames(TQStringList& tableNames) { if(d->executeSQL("SHOW TABLES")) { MYSQL_RES *res = mysql_store_result(d->mysql); if (res != NULL) { MYSQL_ROW row; while ((row = mysql_fetch_row(res)) != NULL) { tableNames << TQString::fromUtf8(row[0]); //utf8.. ok? } mysql_free_result(res); } else { kdDebug() << "MySQLMigrate::drv_tableNames: null result" << endl; } return true; } else { return false; } } /*! Fetches single string at column \a columnNumber for each record from result obtained by running \a sqlStatement. On success the result is stored in \a stringList and true is returned. \return cancelled if there are no records available. */ tristate MySQLMigrate::drv_queryStringListFromSQL( const TQString& sqlStatement, uint columnNumber, TQStringList& stringList, int numRecords) { stringList.clear(); if (d->executeSQL(sqlStatement)) { MYSQL_RES *res = mysql_use_result(d->mysql); if (res != NULL) { for (int i=0; numRecords == -1 || i < numRecords; i++) { MYSQL_ROW row = mysql_fetch_row(res); if (!row) { tristate r; if (mysql_errno(d->mysql)) r = false; else r = (numRecords == -1) ? true : cancelled; mysql_free_result(res); return r; } uint numFields = mysql_num_fields(res); if (columnNumber > (numFields-1)) { kdWarning() << "MySQLMigrate::drv_querySingleStringFromSQL("<executeSQL(sqlStatement) || !(m_mysqlres = mysql_use_result(d->mysql))) return false; firstRecord = false; } MYSQL_ROW row = mysql_fetch_row(m_mysqlres); if (!row) { tristate r = cancelled; if (mysql_errno(d->mysql)) r = false; mysql_free_result(m_mysqlres); m_mysqlres = 0; return r; } const int numFields = mysql_num_fields(m_mysqlres); unsigned long *lengths = mysql_fetch_lengths(m_mysqlres); if (!lengths) { mysql_free_result(m_mysqlres); m_mysqlres = 0; return false; } data.resize(numFields); for (int i=0; i < numFields; i++) data[i] = TQString::fromUtf8(row[i], lengths[i] ); //ok? utf8? return true; } /*! Copy MySQL table to KexiDB database */ bool MySQLMigrate::drv_copyTable(const TQString& srcTable, KexiDB::Connection *destConn, KexiDB::TableSchema* dstTable) { if(d->executeSQL("SELECT * FROM `" + drv_escapeIdentifier(srcTable)) + "`") { MYSQL_RES *res = mysql_use_result(d->mysql); if (res != NULL) { MYSQL_ROW row; const KexiDB::QueryColumnInfo::Vector fieldsExpanded( dstTable->query()->fieldsExpanded() ); while ((row = mysql_fetch_row(res)) != NULL) { const int numFields = TQMIN((int)fieldsExpanded.count(), (int)mysql_num_fields(res)); TQValueList vals; unsigned long *lengths = mysql_fetch_lengths(res); if (!lengths) { mysql_free_result(res); return false; } for(int i = 0; i < numFields; i++) vals.append( KexiDB::cstringToVariant(row[i], fieldsExpanded.at(i)->field, (int)lengths[i]) ); if (!destConn->insertRecord(*dstTable, vals)) { mysql_free_result(res); return false; } updateProgress(); } if (!row && mysql_errno(d->mysql)) { mysql_free_result(res); return false; } /*! @todo Check that wasn't an error, rather than end of result set */ mysql_free_result(res); } else { kdDebug() << "MySQLMigrate::drv_copyTable: null result" << endl; } return true; } else { return false; } } bool MySQLMigrate::drv_getTableSize(const TQString& table, TQ_ULLONG& size) { if(d->executeSQL("SELECT COUNT(*) FROM `" + drv_escapeIdentifier(table)) + "`") { MYSQL_RES *res = mysql_store_result(d->mysql); if (res != NULL) { MYSQL_ROW row; while ((row = mysql_fetch_row(res)) != NULL) { //! @todo check result valid size = TQString(row[0]).toULongLong(); } mysql_free_result(res); } else { kdDebug() << "MySQLMigrate::drv_getTableSize: null result" << endl; } return true; } else { return false; } } //! Convert a MySQL type to a KexiDB type, prompting user if necessary. KexiDB::Field::Type MySQLMigrate::type(const TQString& table, const MYSQL_FIELD *fld) { // Field type KexiDB::Field::Type kexiType = KexiDB::Field::InvalidType; switch(fld->type) { // These are in the same order as mysql_com.h. // MySQL names given on the right case FIELD_TYPE_DECIMAL: // DECIMAL or NUMERIC break; case FIELD_TYPE_TINY: // TINYINT (-2^7..2^7-1 or 2^8) kexiType = KexiDB::Field::Byte; break; case FIELD_TYPE_SHORT: // SMALLINT (-2^15..2^15-1 or 2^16) kexiType = KexiDB::Field::ShortInteger; break; case FIELD_TYPE_LONG: // INTEGER (-2^31..2^31-1 or 2^32) kexiType = KexiDB::Field::Integer; break; case FIELD_TYPE_FLOAT: // FLOAT kexiType = KexiDB::Field::Float; break; case FIELD_TYPE_DOUBLE: // DOUBLE or REAL (8 byte) kexiType = KexiDB::Field::Double; break; case FIELD_TYPE_NULL: // WTF? break; case FIELD_TYPE_TIMESTAMP: // TIMESTAMP (promote?) kexiType = KexiDB::Field::DateTime; break; case FIELD_TYPE_LONGLONG: // BIGINT (-2^63..2^63-1 or 2^64) case FIELD_TYPE_INT24: // MEDIUMINT (-2^23..2^23-1 or 2^24) (promote) kexiType = KexiDB::Field::BigInteger; break; case FIELD_TYPE_DATE: // DATE kexiType = KexiDB::Field::Date; break; case FIELD_TYPE_TIME: // TIME kexiType = KexiDB::Field::Time; break; case FIELD_TYPE_DATETIME: // DATETIME kexiType = KexiDB::Field::DateTime; break; case FIELD_TYPE_YEAR: // YEAR (promote) kexiType = KexiDB::Field::ShortInteger; break; case FIELD_TYPE_NEWDATE: // WTF? case FIELD_TYPE_ENUM: // ENUM // If MySQL did what it's documentation said it did, we would come here // for enum fields ... kexiType = KexiDB::Field::Enum; break; case FIELD_TYPE_SET: // SET //! @todo: Support set column type break; case FIELD_TYPE_TINY_BLOB: case FIELD_TYPE_MEDIUM_BLOB: case FIELD_TYPE_LONG_BLOB: case FIELD_TYPE_BLOB: // BLOB or TEXT case FIELD_TYPE_VAR_STRING: // VARCHAR case FIELD_TYPE_STRING: // CHAR if (fld->flags & ENUM_FLAG) { // ... instead we come here, using the ENUM_FLAG which is supposed to // be deprecated! Duh. kexiType = KexiDB::Field::Enum; break; } kexiType = examineBlobField(table, fld); break; default: kexiType = KexiDB::Field::InvalidType; } if (kexiType == KexiDB::Field::InvalidType) { return userType(table); } return kexiType; } //! Distinguish between a BLOB and a TEXT field /*! MySQL uses the same field type to identify BLOB and TEXT fields. This method queries the server to find out if a field is a binary field or a text field. It also considers the length of CHAR and VARCHAR fields to see whether Text or LongText is the appropriate Kexi field type. Assumes fld is a CHAR, VARCHAR, one of the BLOBs or TEXTs. \return KexiDB::Field::Text, KexiDB::Field::LongText or KexiDB::Field::BLOB */ KexiDB::Field::Type MySQLMigrate::examineBlobField(const TQString& table, const MYSQL_FIELD* fld) { TQString mysqlType; KexiDB::Field::Type kexiType; TQString query = "SHOW COLUMNS FROM `" + drv_escapeIdentifier(table) + "` LIKE '" + TQString::tqfromLatin1(fld->name) + "'"; if(d->executeSQL(query)) { MYSQL_RES *res = mysql_store_result(d->mysql); if (res != NULL) { MYSQL_ROW row; while ((row = mysql_fetch_row(res)) != NULL) { mysqlType = TQString(row[1]); } mysql_free_result(res); } else { kdDebug() << "MySQLMigrate::examineBlobField: null result" << endl; } } else { // Huh? MySQL wont tell us what kind of field it is! Lets guess. return KexiDB::Field::LongText; } kdDebug() << "MySQLMigrate::examineBlobField: considering " << mysqlType << endl; if(mysqlType.tqcontains("blob", false) != 0) { // Doesn't matter how big it is, it's binary kexiType = KexiDB::Field::BLOB; } else if(mysqlType.tqcontains("text", false) != 0) { // All the TEXT types are too big for Kexi text. kexiType = KexiDB::Field::BLOB; } else if(fld->length < 200) { kexiType = KexiDB::Field::Text; } else { kexiType = KexiDB::Field::LongText; } return kexiType; } //! Get the strings that identify values in an enum field /*! Parse the type of a MySQL enum field as returned by the server in a 'DESCRIBE table' or 'SHOW COLUMNS FROM table' statement. The string returned by the server is in the form 'enum('option1','option2'). In this example, the result should be a string list containing two strings, "option1", "option2". \return list of possible values the field can take */ TQStringList MySQLMigrate::examineEnumField(const TQString& table, const MYSQL_FIELD* fld) { TQString vals; TQString query = "SHOW COLUMNS FROM `" + drv_escapeIdentifier(table) + "` LIKE '" + TQString::tqfromLatin1(fld->name) + "'"; if(d->executeSQL(query)) { MYSQL_RES *res = mysql_store_result(d->mysql); if (res != NULL) { MYSQL_ROW row; while ((row = mysql_fetch_row(res)) != NULL) { vals = TQString(row[1]); } mysql_free_result(res); } else { kdDebug() << "MySQLMigrate::examineEnumField: null result" << endl; } } else { // Huh? MySQL wont tell us what values it can take. return TQStringList(); } kdDebug() << "MySQLMigrate::examineEnumField: considering " << vals << endl; // Crash and burn if we get confused... if(!vals.startsWith("enum(")) { // Huh? We're supposed to be parsing an enum! kdDebug() << "MySQLMigrate::examineEnumField:1 not an enum!" << endl; return TQStringList(); } if(!vals.endsWith(")")) { kdDebug() << "MySQLMigrate::examineEnumField:2 not an enum!" << endl; return TQStringList(); } // It'd be nice to use TQString.section or TQStringList.split, but we need // to be careful as enum values can have commas and quote marks in them // e.g. CREATE TABLE t(f enum('option,''') gives one option: "option,'" vals = vals.remove(0,5); TQRegExp rx = TQRegExp("^'((?:[^,']|,|'')*)'"); TQStringList values = TQStringList(); int index = 0; while ((index = rx.search(vals, index, TQRegExp::CaretAtOffset)) != -1) { int len = rx.matchedLength(); if (len != -1) { kdDebug() << "MySQLMigrate::examineEnumField:3 " << rx.cap(1) << endl; values << rx.cap(1); } else { kdDebug() << "MySQLMigrate::examineEnumField:4 lost" << endl; } TQChar next = vals[index + len]; if (next != TQChar(',') && next != TQChar(')')) { kdDebug() << "MySQLMigrate::examineEnumField:5 " << (char)next << endl; } index += len + 1; } return values; } void MySQLMigrate::getConstraints(int flags, KexiDB::Field* fld) { fld->setPrimaryKey(flags & PRI_KEY_FLAG); fld->setAutoIncrement(flags & AUTO_INCREMENT_FLAG); fld->setNotNull(flags & NOT_NULL_FLAG); fld->setUniqueKey(flags & UNITQUE_KEY_FLAG); //! @todo: Keys and uniqueness } void MySQLMigrate::getOptions(int flags, KexiDB::Field* fld) { fld->setUnsigned(flags & UNSIGNED_FLAG); } #include "mysqlmigrate.moc"