diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /kexi/kexidb/utils.cpp | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kexi/kexidb/utils.cpp')
-rw-r--r-- | kexi/kexidb/utils.cpp | 1262 |
1 files changed, 1262 insertions, 0 deletions
diff --git a/kexi/kexidb/utils.cpp b/kexi/kexidb/utils.cpp new file mode 100644 index 00000000..78b7e416 --- /dev/null +++ b/kexi/kexidb/utils.cpp @@ -0,0 +1,1262 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl> + + This library 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 library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "utils.h" +#include "cursor.h" +#include "drivermanager.h" +#include "lookupfieldschema.h" + +#include <qmap.h> +#include <qthread.h> +#include <qdom.h> +#include <qintdict.h> +#include <qbuffer.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kstaticdeleter.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kiconloader.h> + +#include "utils_p.h" + +using namespace KexiDB; + +//! Cache +struct TypeCache +{ + QMap< uint, TypeGroupList > tlist; + QMap< uint, QStringList > nlist; + QMap< uint, QStringList > slist; + QMap< uint, Field::Type > def_tlist; +}; + +static KStaticDeleter<TypeCache> KexiDB_typeCacheDeleter; +TypeCache *KexiDB_typeCache = 0; + +static void initList() +{ + KexiDB_typeCacheDeleter.setObject( KexiDB_typeCache, new TypeCache() ); + + for (uint t=0; t<=KexiDB::Field::LastType; t++) { + const uint tg = KexiDB::Field::typeGroup( t ); + TypeGroupList list; + QStringList name_list, str_list; + if (KexiDB_typeCache->tlist.find( tg )!=KexiDB_typeCache->tlist.end()) { + list = KexiDB_typeCache->tlist[ tg ]; + name_list = KexiDB_typeCache->nlist[ tg ]; + str_list = KexiDB_typeCache->slist[ tg ]; + } + list+= t; + name_list += KexiDB::Field::typeName( t ); + str_list += KexiDB::Field::typeString( t ); + KexiDB_typeCache->tlist[ tg ] = list; + KexiDB_typeCache->nlist[ tg ] = name_list; + KexiDB_typeCache->slist[ tg ] = str_list; + } + + KexiDB_typeCache->def_tlist[ Field::InvalidGroup ] = Field::InvalidType; + KexiDB_typeCache->def_tlist[ Field::TextGroup ] = Field::Text; + KexiDB_typeCache->def_tlist[ Field::IntegerGroup ] = Field::Integer; + KexiDB_typeCache->def_tlist[ Field::FloatGroup ] = Field::Double; + KexiDB_typeCache->def_tlist[ Field::BooleanGroup ] = Field::Boolean; + KexiDB_typeCache->def_tlist[ Field::DateTimeGroup ] = Field::Date; + KexiDB_typeCache->def_tlist[ Field::BLOBGroup ] = Field::BLOB; +} + +const TypeGroupList KexiDB::typesForGroup(KexiDB::Field::TypeGroup typeGroup) +{ + if (!KexiDB_typeCache) + initList(); + return KexiDB_typeCache->tlist[ typeGroup ]; +} + +QStringList KexiDB::typeNamesForGroup(KexiDB::Field::TypeGroup typeGroup) +{ + if (!KexiDB_typeCache) + initList(); + return KexiDB_typeCache->nlist[ typeGroup ]; +} + +QStringList KexiDB::typeStringsForGroup(KexiDB::Field::TypeGroup typeGroup) +{ + if (!KexiDB_typeCache) + initList(); + return KexiDB_typeCache->slist[ typeGroup ]; +} + +KexiDB::Field::Type KexiDB::defaultTypeForGroup(KexiDB::Field::TypeGroup typeGroup) +{ + if (!KexiDB_typeCache) + initList(); + return (typeGroup <= Field::LastTypeGroup) ? KexiDB_typeCache->def_tlist[ typeGroup ] : Field::InvalidType; +} + +void KexiDB::getHTMLErrorMesage(Object* obj, QString& msg, QString &details) +{ + Connection *conn = 0; + if (!obj || !obj->error()) { + if (dynamic_cast<Cursor*>(obj)) { + conn = dynamic_cast<Cursor*>(obj)->connection(); + obj = conn; + } + else { + return; + } + } +// if (dynamic_cast<Connection*>(obj)) { + // conn = dynamic_cast<Connection*>(obj); + //} + if (!obj || !obj->error()) + return; + //lower level message is added to the details, if there is alread message specified + if (!obj->msgTitle().isEmpty()) + msg += "<p>" + obj->msgTitle(); + + if (msg.isEmpty()) + msg = "<p>" + obj->errorMsg(); + else + details += "<p>" + obj->errorMsg(); + + if (!obj->serverErrorMsg().isEmpty()) + details += "<p><b><nobr>" +i18n("Message from server:") + "</nobr></b><br>" + obj->serverErrorMsg(); + if (!obj->recentSQLString().isEmpty()) + details += "<p><b><nobr>" +i18n("SQL statement:") + QString("</nobr></b><br><tt>%1</tt>").arg(obj->recentSQLString()); + int serverResult; + QString serverResultName; + if (obj->serverResult()!=0) { + serverResult = obj->serverResult(); + serverResultName = obj->serverResultName(); + } + else { + serverResult = obj->previousServerResult(); + serverResultName = obj->previousServerResultName(); + } + if (!serverResultName.isEmpty()) + details += (QString("<p><b><nobr>")+i18n("Server result name:")+"</nobr></b><br>"+serverResultName); + if (!details.isEmpty() + && (!obj->serverErrorMsg().isEmpty() || !obj->recentSQLString().isEmpty() || !serverResultName.isEmpty() || serverResult!=0) ) + { + details += (QString("<p><b><nobr>")+i18n("Server result number:")+"</nobr></b><br>"+QString::number(serverResult)); + } + + if (!details.isEmpty() && !details.startsWith("<qt>")) { + if (details.startsWith("<p>")) + details = QString::fromLatin1("<qt>")+details; + else + details = QString::fromLatin1("<qt><p>")+details; + } +} + +void KexiDB::getHTMLErrorMesage(Object* obj, QString& msg) +{ + getHTMLErrorMesage(obj, msg, msg); +} + +void KexiDB::getHTMLErrorMesage(Object* obj, ResultInfo *result) +{ + getHTMLErrorMesage(obj, result->msg, result->desc); +} + +int KexiDB::idForObjectName( Connection &conn, const QString& objName, int objType ) +{ + RowData data; + if (true!=conn.querySingleRecord(QString("select o_id from kexi__objects where lower(o_name)='%1' and o_type=%2") + .arg(objName.lower()).arg(objType), data)) + return 0; + bool ok; + int id = data[0].toInt(&ok); + return ok ? id : 0; +} + +//----------------------------------------- + +TableOrQuerySchema::TableOrQuerySchema(Connection *conn, const QCString& name) + : m_name(name) +{ + m_table = conn->tableSchema(QString(name)); + m_query = m_table ? 0 : conn->querySchema(QString(name)); + if (!m_table && !m_query) + KexiDBWarn << "TableOrQuery(FieldList &tableOrQuery) : " + " tableOrQuery is neither table nor query!" << endl; +} + +TableOrQuerySchema::TableOrQuerySchema(Connection *conn, const QCString& name, bool table) + : m_name(name) + , m_table(table ? conn->tableSchema(QString(name)) : 0) + , m_query(table ? 0 : conn->querySchema(QString(name))) +{ + if (table && !m_table) + KexiDBWarn << "TableOrQuery(Connection *conn, const QCString& name, bool table) : " + "no table specified!" << endl; + if (!table && !m_query) + KexiDBWarn << "TableOrQuery(Connection *conn, const QCString& name, bool table) : " + "no query specified!" << endl; +} + +TableOrQuerySchema::TableOrQuerySchema(FieldList &tableOrQuery) + : m_table(dynamic_cast<TableSchema*>(&tableOrQuery)) + , m_query(dynamic_cast<QuerySchema*>(&tableOrQuery)) +{ + if (!m_table && !m_query) + KexiDBWarn << "TableOrQuery(FieldList &tableOrQuery) : " + " tableOrQuery is nether table nor query!" << endl; +} + +TableOrQuerySchema::TableOrQuerySchema(Connection *conn, int id) +{ + m_table = conn->tableSchema(id); + m_query = m_table ? 0 : conn->querySchema(id); + if (!m_table && !m_query) + KexiDBWarn << "TableOrQuery(Connection *conn, int id) : no table or query found for id==" + << id << "!" << endl; +} + +TableOrQuerySchema::TableOrQuerySchema(TableSchema* table) + : m_table(table) + , m_query(0) +{ + if (!m_table) + KexiDBWarn << "TableOrQuery(TableSchema* table) : no table specified!" << endl; +} + +TableOrQuerySchema::TableOrQuerySchema(QuerySchema* query) + : m_table(0) + , m_query(query) +{ + if (!m_query) + KexiDBWarn << "TableOrQuery(QuerySchema* query) : no query specified!" << endl; +} + +uint TableOrQuerySchema::fieldCount() const +{ + if (m_table) + return m_table->fieldCount(); + if (m_query) + return m_query->fieldsExpanded().size(); + return 0; +} + +const QueryColumnInfo::Vector TableOrQuerySchema::columns(bool unique) +{ + if (m_table) + return m_table->query()->fieldsExpanded(unique ? QuerySchema::Unique : QuerySchema::Default); + + if (m_query) + return m_query->fieldsExpanded(unique ? QuerySchema::Unique : QuerySchema::Default); + + KexiDBWarn << "TableOrQuerySchema::column() : no query or table specified!" << endl; + return QueryColumnInfo::Vector(); +} + +QCString TableOrQuerySchema::name() const +{ + if (m_table) + return m_table->name().latin1(); + if (m_query) + return m_query->name().latin1(); + return m_name; +} + +QString TableOrQuerySchema::captionOrName() const +{ + SchemaData *sdata = m_table ? static_cast<SchemaData *>(m_table) : static_cast<SchemaData *>(m_query); + if (!sdata) + return m_name; + return sdata->caption().isEmpty() ? sdata->name() : sdata->caption(); +} + +Field* TableOrQuerySchema::field(const QString& name) +{ + if (m_table) + return m_table->field(name); + if (m_query) + return m_query->field(name); + + return 0; +} + +QueryColumnInfo* TableOrQuerySchema::columnInfo(const QString& name) +{ + if (m_table) + return m_table->query()->columnInfo(name); + + if (m_query) + return m_query->columnInfo(name); + + return 0; +} + +QString TableOrQuerySchema::debugString() +{ + if (m_table) + return m_table->debugString(); + else if (m_query) + return m_query->debugString(); + return QString::null; +} + +void TableOrQuerySchema::debug() +{ + if (m_table) + return m_table->debug(); + else if (m_query) + return m_query->debug(); +} + +Connection* TableOrQuerySchema::connection() const +{ + if (m_table) + return m_table->connection(); + else if (m_query) + return m_query->connection(); + return 0; +} + + +//------------------------------------------ + +class ConnectionTestThread : public QThread { + public: + ConnectionTestThread(ConnectionTestDialog *dlg, const KexiDB::ConnectionData& connData); + virtual void run(); + protected: + ConnectionTestDialog* m_dlg; + KexiDB::ConnectionData m_connData; +}; + +ConnectionTestThread::ConnectionTestThread(ConnectionTestDialog* dlg, const KexiDB::ConnectionData& connData) + : m_dlg(dlg), m_connData(connData) +{ +} + +void ConnectionTestThread::run() +{ + KexiDB::DriverManager manager; + KexiDB::Driver* drv = manager.driver(m_connData.driverName); +// KexiGUIMessageHandler msghdr; + if (!drv || manager.error()) { +//move msghdr.showErrorMessage(&Kexi::driverManager()); + m_dlg->error(&manager); + return; + } + KexiDB::Connection * conn = drv->createConnection(m_connData); + if (!conn || drv->error()) { +//move msghdr.showErrorMessage(drv); + delete conn; + m_dlg->error(drv); + return; + } + if (!conn->connect() || conn->error()) { +//move msghdr.showErrorMessage(conn); + m_dlg->error(conn); + delete conn; + return; + } + // SQL database backends like PostgreSQL require executing "USE database" + // if we really want to know connection to the server succeeded. + QString tmpDbName; + if (!conn->useTemporaryDatabaseIfNeeded( tmpDbName )) { + m_dlg->error(conn); + delete conn; + return; + } + delete conn; + m_dlg->error(0); +} + +ConnectionTestDialog::ConnectionTestDialog(QWidget* parent, + const KexiDB::ConnectionData& data, + KexiDB::MessageHandler& msgHandler) + : KProgressDialog(parent, "testconn_dlg", + i18n("Test Connection"), i18n("<qt>Testing connection to <b>%1</b> database server...</qt>") + .arg(data.serverInfoString(true)), true /*modal*/) + , m_thread(new ConnectionTestThread(this, data)) + , m_connData(data) + , m_msgHandler(&msgHandler) + , m_elapsedTime(0) + , m_errorObj(0) + , m_stopWaiting(false) +{ + showCancelButton(true); + progressBar()->setPercentageVisible(false); + progressBar()->setTotalSteps(0); + connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout())); + adjustSize(); + resize(250, height()); +} + +ConnectionTestDialog::~ConnectionTestDialog() +{ + m_wait.wakeAll(); + m_thread->terminate(); + delete m_thread; +} + +int ConnectionTestDialog::exec() +{ + m_timer.start(20); + m_thread->start(); + const int res = KProgressDialog::exec(); + m_thread->wait(); + m_timer.stop(); + return res; +} + +void ConnectionTestDialog::slotTimeout() +{ +// KexiDBDbg << "ConnectionTestDialog::slotTimeout() " << m_errorObj << endl; + bool notResponding = false; + if (m_elapsedTime >= 1000*5) {//5 seconds + m_stopWaiting = true; + notResponding = true; + } + if (m_stopWaiting) { + m_timer.disconnect(this); + m_timer.stop(); + slotCancel(); +// reject(); +// close(); + if (m_errorObj) { + m_msgHandler->showErrorMessage(m_errorObj); + m_errorObj = 0; + } + else if (notResponding) { + KMessageBox::sorry(0, + i18n("<qt>Test connection to <b>%1</b> database server failed. The server is not responding.</qt>") + .arg(m_connData.serverInfoString(true)), + i18n("Test Connection")); + } + else { + KMessageBox::information(0, + i18n("<qt>Test connection to <b>%1</b> database server established successfully.</qt>") + .arg(m_connData.serverInfoString(true)), + i18n("Test Connection")); + } +// slotCancel(); +// reject(); + m_wait.wakeAll(); + return; + } + m_elapsedTime += 20; + progressBar()->setProgress( m_elapsedTime ); +} + +void ConnectionTestDialog::error(KexiDB::Object *obj) +{ + KexiDBDbg << "ConnectionTestDialog::error()" << endl; + m_stopWaiting = true; + m_errorObj = obj; +/* reject(); + m_msgHandler->showErrorMessage(obj); + if (obj) { + } + else { + accept(); + }*/ + m_wait.wait(); +} + +void ConnectionTestDialog::slotCancel() +{ +// m_wait.wakeAll(); + m_thread->terminate(); + m_timer.disconnect(this); + m_timer.stop(); + KProgressDialog::slotCancel(); +} + +void KexiDB::connectionTestDialog(QWidget* parent, const KexiDB::ConnectionData& data, + KexiDB::MessageHandler& msgHandler) +{ + ConnectionTestDialog dlg(parent, data, msgHandler); + dlg.exec(); +} + +int KexiDB::rowCount(Connection &conn, const QString& sql) +{ + int count = -1; //will be changed only on success of querySingleNumber() + QString selectSql( QString::fromLatin1("SELECT COUNT() FROM (") + sql + ")" ); + conn.querySingleNumber(selectSql, count); + return count; +} + +int KexiDB::rowCount(const KexiDB::TableSchema& tableSchema) +{ +//! @todo does not work with non-SQL data sources + if (!tableSchema.connection()) { + KexiDBWarn << "KexiDB::rowsCount(const KexiDB::TableSchema&): no tableSchema.connection() !" << endl; + return -1; + } + int count = -1; //will be changed only on success of querySingleNumber() + tableSchema.connection()->querySingleNumber( + QString::fromLatin1("SELECT COUNT(*) FROM ") + + tableSchema.connection()->driver()->escapeIdentifier(tableSchema.name()), + count + ); + return count; +} + +int KexiDB::rowCount(KexiDB::QuerySchema& querySchema) +{ +//! @todo does not work with non-SQL data sources + if (!querySchema.connection()) { + KexiDBWarn << "KexiDB::rowsCount(const KexiDB::QuerySchema&): no querySchema.connection() !" << endl; + return -1; + } + int count = -1; //will be changed only on success of querySingleNumber() + querySchema.connection()->querySingleNumber( + QString::fromLatin1("SELECT COUNT(*) FROM (") + + querySchema.connection()->selectStatement(querySchema) + ")", + count + ); + return count; +} + +int KexiDB::rowCount(KexiDB::TableOrQuerySchema& tableOrQuery) +{ + if (tableOrQuery.table()) + return rowCount( *tableOrQuery.table() ); + if (tableOrQuery.query()) + return rowCount( *tableOrQuery.query() ); + return -1; +} + +int KexiDB::fieldCount(KexiDB::TableOrQuerySchema& tableOrQuery) +{ + if (tableOrQuery.table()) + return tableOrQuery.table()->fieldCount(); + if (tableOrQuery.query()) + return tableOrQuery.query()->fieldsExpanded().count(); + return -1; +} + +QMap<QString,QString> KexiDB::toMap( const ConnectionData& data ) +{ + QMap<QString,QString> m; + m["caption"] = data.caption; + m["description"] = data.description; + m["driverName"] = data.driverName; + m["hostName"] = data.hostName; + m["port"] = QString::number(data.port); + m["useLocalSocketFile"] = QString::number((int)data.useLocalSocketFile); + m["localSocketFileName"] = data.localSocketFileName; + m["password"] = data.password; + m["savePassword"] = QString::number((int)data.savePassword); + m["userName"] = data.userName; + m["fileName"] = data.fileName(); + return m; +} + +void KexiDB::fromMap( const QMap<QString,QString>& map, ConnectionData& data ) +{ + data.caption = map["caption"]; + data.description = map["description"]; + data.driverName = map["driverName"]; + data.hostName = map["hostName"]; + data.port = map["port"].toInt(); + data.useLocalSocketFile = map["useLocalSocketFile"].toInt()==1; + data.localSocketFileName = map["localSocketFileName"]; + data.password = map["password"]; + data.savePassword = map["savePassword"].toInt()==1; + data.userName = map["userName"]; + data.setFileName(map["fileName"]); +} + +bool KexiDB::splitToTableAndFieldParts(const QString& string, + QString& tableName, QString& fieldName, + SplitToTableAndFieldPartsOptions option) +{ + const int id = string.find('.'); + if (option & SetFieldNameIfNoTableName && id==-1) { + tableName = QString::null; + fieldName = string; + return !fieldName.isEmpty(); + } + if (id<=0 || id==int(string.length()-1)) + return false; + tableName = string.left(id); + fieldName = string.mid(id+1); + return !tableName.isEmpty() && !fieldName.isEmpty(); +} + +bool KexiDB::supportsVisibleDecimalPlacesProperty(Field::Type type) +{ +//! @todo add check for decimal type as well + return Field::isFPNumericType(type); +} + +QString KexiDB::formatNumberForVisibleDecimalPlaces(double value, int decimalPlaces) +{ +//! @todo round? + if (decimalPlaces < 0) { + QString s( QString::number(value, 'f', 10 /*reasonable precision*/)); + uint i = s.length()-1; + while (i>0 && s[i]=='0') + i--; + if (s[i]=='.') //remove '.' + i--; + s = s.left(i+1).replace('.', KGlobal::locale()->decimalSymbol()); + return s; + } + if (decimalPlaces == 0) + return QString::number((int)value); + return KGlobal::locale()->formatNumber(value, decimalPlaces); +} + +KexiDB::Field::Type KexiDB::intToFieldType( int type ) +{ + if (type<(int)KexiDB::Field::InvalidType || type>(int)KexiDB::Field::LastType) { + KexiDBWarn << "KexiDB::intToFieldType(): invalid type " << type << endl; + return KexiDB::Field::InvalidType; + } + return (KexiDB::Field::Type)type; +} + +static bool setIntToFieldType( Field& field, const QVariant& value ) +{ + bool ok; + const int intType = value.toInt(&ok); + if (!ok || KexiDB::Field::InvalidType == intToFieldType(intType)) {//for sanity + KexiDBWarn << "KexiDB::setFieldProperties(): invalid type" << endl; + return false; + } + field.setType((KexiDB::Field::Type)intType); + return true; +} + +//! for KexiDB::isBuiltinTableFieldProperty() +static KStaticDeleter< QAsciiDict<char> > KexiDB_builtinFieldPropertiesDeleter; +//! for KexiDB::isBuiltinTableFieldProperty() +QAsciiDict<char>* KexiDB_builtinFieldProperties = 0; + +bool KexiDB::isBuiltinTableFieldProperty( const QCString& propertyName ) +{ + if (!KexiDB_builtinFieldProperties) { + KexiDB_builtinFieldPropertiesDeleter.setObject( KexiDB_builtinFieldProperties, new QAsciiDict<char>(499) ); +#define ADD(name) KexiDB_builtinFieldProperties->insert(name, (char*)1) + ADD("type"); + ADD("primaryKey"); + ADD("indexed"); + ADD("autoIncrement"); + ADD("unique"); + ADD("notNull"); + ADD("allowEmpty"); + ADD("unsigned"); + ADD("name"); + ADD("caption"); + ADD("description"); + ADD("length"); + ADD("precision"); + ADD("defaultValue"); + ADD("width"); + ADD("visibleDecimalPlaces"); +//! @todo always update this when new builtins appear! +#undef ADD + } + return KexiDB_builtinFieldProperties->find( propertyName ); +} + +bool KexiDB::setFieldProperties( Field& field, const QMap<QCString, QVariant>& values ) +{ + QMapConstIterator<QCString, QVariant> it; + if ( (it = values.find("type")) != values.constEnd() ) { + if (!setIntToFieldType(field, *it)) + return false; + } + +#define SET_BOOLEAN_FLAG(flag, value) { \ + constraints |= KexiDB::Field::flag; \ + if (!value) \ + constraints ^= KexiDB::Field::flag; \ + } + + uint constraints = field.constraints(); + bool ok = true; + if ( (it = values.find("primaryKey")) != values.constEnd() ) + SET_BOOLEAN_FLAG(PrimaryKey, (*it).toBool()); + if ( (it = values.find("indexed")) != values.constEnd() ) + SET_BOOLEAN_FLAG(Indexed, (*it).toBool()); + if ( (it = values.find("autoIncrement")) != values.constEnd() + && KexiDB::Field::isAutoIncrementAllowed(field.type()) ) + SET_BOOLEAN_FLAG(AutoInc, (*it).toBool()); + if ( (it = values.find("unique")) != values.constEnd() ) + SET_BOOLEAN_FLAG(Unique, (*it).toBool()); + if ( (it = values.find("notNull")) != values.constEnd() ) + SET_BOOLEAN_FLAG(NotNull, (*it).toBool()); + if ( (it = values.find("allowEmpty")) != values.constEnd() ) + SET_BOOLEAN_FLAG(NotEmpty, !(*it).toBool()); + field.setConstraints( constraints ); + + uint options = 0; + if ( (it = values.find("unsigned")) != values.constEnd()) { + options |= KexiDB::Field::Unsigned; + if (!(*it).toBool()) + options ^= KexiDB::Field::Unsigned; + } + field.setOptions( options ); + + if ( (it = values.find("name")) != values.constEnd()) + field.setName( (*it).toString() ); + if ( (it = values.find("caption")) != values.constEnd()) + field.setCaption( (*it).toString() ); + if ( (it = values.find("description")) != values.constEnd()) + field.setDescription( (*it).toString() ); + if ( (it = values.find("length")) != values.constEnd()) + field.setLength( (*it).isNull() ? 0/*default*/ : (*it).toUInt(&ok) ); + if (!ok) + return false; + if ( (it = values.find("precision")) != values.constEnd()) + field.setPrecision( (*it).isNull() ? 0/*default*/ : (*it).toUInt(&ok) ); + if (!ok) + return false; + if ( (it = values.find("defaultValue")) != values.constEnd()) + field.setDefaultValue( *it ); + if ( (it = values.find("width")) != values.constEnd()) + field.setWidth( (*it).isNull() ? 0/*default*/ : (*it).toUInt(&ok) ); + if (!ok) + return false; + if ( (it = values.find("visibleDecimalPlaces")) != values.constEnd() + && KexiDB::supportsVisibleDecimalPlacesProperty(field.type()) ) + field.setVisibleDecimalPlaces( (*it).isNull() ? -1/*default*/ : (*it).toInt(&ok) ); + if (!ok) + return false; + + // set custom properties + typedef QMap<QCString, QVariant> PropertiesMap; + foreach( PropertiesMap::ConstIterator, it, values ) { + if (!isBuiltinTableFieldProperty( it.key() ) && !isExtendedTableFieldProperty( it.key() )) { + field.setCustomProperty( it.key(), it.data() ); + } + } + return true; +#undef SET_BOOLEAN_FLAG +} + +//! for KexiDB::isExtendedTableFieldProperty() +static KStaticDeleter< QAsciiDict<char> > KexiDB_extendedPropertiesDeleter; +//! for KexiDB::isExtendedTableFieldProperty() +QAsciiDict<char>* KexiDB_extendedProperties = 0; + +bool KexiDB::isExtendedTableFieldProperty( const QCString& propertyName ) +{ + if (!KexiDB_extendedProperties) { + KexiDB_extendedPropertiesDeleter.setObject( KexiDB_extendedProperties, new QAsciiDict<char>(499, false) ); +#define ADD(name) KexiDB_extendedProperties->insert(name, (char*)1) + ADD("visibleDecimalPlaces"); + ADD("rowSource"); + ADD("rowSourceType"); + ADD("rowSourceValues"); + ADD("boundColumn"); + ADD("visibleColumn"); + ADD("columnWidths"); + ADD("showColumnHeaders"); + ADD("listRows"); + ADD("limitToList"); + ADD("displayWidget"); +#undef ADD + } + return KexiDB_extendedProperties->find( propertyName ); +} + +bool KexiDB::setFieldProperty( Field& field, const QCString& propertyName, const QVariant& value ) +{ +#define SET_BOOLEAN_FLAG(flag, value) { \ + constraints |= KexiDB::Field::flag; \ + if (!value) \ + constraints ^= KexiDB::Field::flag; \ + field.setConstraints( constraints ); \ + return true; \ + } +#define GET_INT(method) { \ + const uint ival = value.toUInt(&ok); \ + if (!ok) \ + return false; \ + field.method( ival ); \ + return true; \ + } + + if (propertyName.isEmpty()) + return false; + + bool ok; + if (KexiDB::isExtendedTableFieldProperty(propertyName)) { + //a little speedup: identify extended property in O(1) + if ( "visibleDecimalPlaces" == propertyName + && KexiDB::supportsVisibleDecimalPlacesProperty(field.type()) ) + { + GET_INT( setVisibleDecimalPlaces ); + } + else { + if (!field.table()) { + KexiDBWarn << QString("KexiDB::setFieldProperty() Cannot set \"%1\" property - no table assinged for field!") + .arg(propertyName) << endl; + } + else { + LookupFieldSchema *lookup = field.table()->lookupFieldSchema(field); + const bool hasLookup = lookup != 0; + if (!hasLookup) + lookup = new LookupFieldSchema(); + if (LookupFieldSchema::setProperty( *lookup, propertyName, value )) { + if (!hasLookup && lookup) + field.table()->setLookupFieldSchema( field.name(), lookup ); + return true; + } + delete lookup; + } + } + } + else {//non-extended + if ( "type" == propertyName ) + return setIntToFieldType(field, value); + + uint constraints = field.constraints(); + if ( "primaryKey" == propertyName ) + SET_BOOLEAN_FLAG(PrimaryKey, value.toBool()); + if ( "indexed" == propertyName ) + SET_BOOLEAN_FLAG(Indexed, value.toBool()); + if ( "autoIncrement" == propertyName + && KexiDB::Field::isAutoIncrementAllowed(field.type()) ) + SET_BOOLEAN_FLAG(AutoInc, value.toBool()); + if ( "unique" == propertyName ) + SET_BOOLEAN_FLAG(Unique, value.toBool()); + if ( "notNull" == propertyName ) + SET_BOOLEAN_FLAG(NotNull, value.toBool()); + if ( "allowEmpty" == propertyName ) + SET_BOOLEAN_FLAG(NotEmpty, !value.toBool()); + + uint options = 0; + if ( "unsigned" == propertyName ) { + options |= KexiDB::Field::Unsigned; + if (!value.toBool()) + options ^= KexiDB::Field::Unsigned; + field.setOptions( options ); + return true; + } + + if ( "name" == propertyName ) { + if (value.toString().isEmpty()) + return false; + field.setName( value.toString() ); + return true; + } + if ( "caption" == propertyName ) { + field.setCaption( value.toString() ); + return true; + } + if ( "description" == propertyName ) { + field.setDescription( value.toString() ); + return true; + } + if ( "length" == propertyName ) + GET_INT( setLength ); + if ( "precision" == propertyName ) + GET_INT( setPrecision ); + if ( "defaultValue" == propertyName ) { + field.setDefaultValue( value ); + return true; + } + if ( "width" == propertyName ) + GET_INT( setWidth ); + + // last chance that never fails: custom field property + field.setCustomProperty(propertyName, value); + } + + KexiDBWarn << "KexiDB::setFieldProperty() property \"" << propertyName << "\" not found!" << endl; + return false; +#undef SET_BOOLEAN_FLAG +#undef GET_INT +} + +int KexiDB::loadIntPropertyValueFromDom( const QDomNode& node, bool* ok ) +{ + QCString valueType = node.nodeName().latin1(); + if (valueType.isEmpty() || valueType!="number") { + if (ok) + *ok = false; + return 0; + } + const QString text( QDomNode(node).toElement().text() ); + int val = text.toInt(ok); + return val; +} + +QString KexiDB::loadStringPropertyValueFromDom( const QDomNode& node, bool* ok ) +{ + QCString valueType = node.nodeName().latin1(); + if (valueType!="string") { + if (ok) + *ok = false; + return 0; + } + return QDomNode(node).toElement().text(); +} + +QVariant KexiDB::loadPropertyValueFromDom( const QDomNode& node ) +{ + QCString valueType = node.nodeName().latin1(); + if (valueType.isEmpty()) + return QVariant(); + const QString text( QDomNode(node).toElement().text() ); + bool ok; + if (valueType == "string") { + return text; + } + else if (valueType == "cstring") { + return QCString(text.latin1()); + } + else if (valueType == "number") { // integer or double + if (text.find('.')!=-1) { + double val = text.toDouble(&ok); + if (ok) + return val; + } + else { + const int val = text.toInt(&ok); + if (ok) + return val; + const Q_LLONG valLong = text.toLongLong(&ok); + if (ok) + return valLong; + } + } + else if (valueType == "bool") { + return QVariant(text.lower()=="true" || text=="1", 1); + } +//! @todo add more QVariant types + KexiDBWarn << "loadPropertyValueFromDom(): unknown type '" << valueType << "'" << endl; + return QVariant(); +} + +QDomElement KexiDB::saveNumberElementToDom(QDomDocument& doc, QDomElement& parentEl, + const QString& elementName, int value) +{ + QDomElement el( doc.createElement(elementName) ); + parentEl.appendChild( el ); + QDomElement numberEl( doc.createElement("number") ); + el.appendChild( numberEl ); + numberEl.appendChild( doc.createTextNode( QString::number(value) ) ); + return el; +} + +QDomElement KexiDB::saveBooleanElementToDom(QDomDocument& doc, QDomElement& parentEl, + const QString& elementName, bool value) +{ + QDomElement el( doc.createElement(elementName) ); + parentEl.appendChild( el ); + QDomElement numberEl( doc.createElement("bool") ); + el.appendChild( numberEl ); + numberEl.appendChild( doc.createTextNode( + value ? QString::fromLatin1("true") : QString::fromLatin1("false") ) ); + return el; +} + +//! Used in KexiDB::emptyValueForType() +static KStaticDeleter< QValueVector<QVariant> > KexiDB_emptyValueForTypeCacheDeleter; +QValueVector<QVariant> *KexiDB_emptyValueForTypeCache = 0; + +QVariant KexiDB::emptyValueForType( KexiDB::Field::Type type ) +{ + if (!KexiDB_emptyValueForTypeCache) { + KexiDB_emptyValueForTypeCacheDeleter.setObject( KexiDB_emptyValueForTypeCache, + new QValueVector<QVariant>(int(Field::LastType)+1) ); +#define ADD(t, value) (*KexiDB_emptyValueForTypeCache)[t]=value; + ADD(Field::Byte, 0); + ADD(Field::ShortInteger, 0); + ADD(Field::Integer, 0); + ADD(Field::BigInteger, 0); + ADD(Field::Boolean, QVariant(false, 0)); + ADD(Field::Float, 0.0); + ADD(Field::Double, 0.0); +//! @todo ok? we have no better defaults + ADD(Field::Text, QString(" ")); + ADD(Field::LongText, QString(" ")); + ADD(Field::BLOB, QByteArray()); +#undef ADD + } + const QVariant val( KexiDB_emptyValueForTypeCache->at( + (type<=Field::LastType) ? type : Field::InvalidType) ); + if (!val.isNull()) + return val; + else { //special cases + if (type==Field::Date) + return QDate::currentDate(); + if (type==Field::DateTime) + return QDateTime::currentDateTime(); + if (type==Field::Time) + return QTime::currentTime(); + } + KexiDBWarn << "KexiDB::emptyValueForType() no value for type " + << Field::typeName(type) << endl; + return QVariant(); +} + +//! Used in KexiDB::notEmptyValueForType() +static KStaticDeleter< QValueVector<QVariant> > KexiDB_notEmptyValueForTypeCacheDeleter; +QValueVector<QVariant> *KexiDB_notEmptyValueForTypeCache = 0; + +QVariant KexiDB::notEmptyValueForType( KexiDB::Field::Type type ) +{ + if (!KexiDB_notEmptyValueForTypeCache) { + KexiDB_notEmptyValueForTypeCacheDeleter.setObject( KexiDB_notEmptyValueForTypeCache, + new QValueVector<QVariant>(int(Field::LastType)+1) ); +#define ADD(t, value) (*KexiDB_notEmptyValueForTypeCache)[t]=value; + // copy most of the values + for (int i = int(Field::InvalidType) + 1; i<=Field::LastType; i++) { + if (i==Field::Date || i==Field::DateTime || i==Field::Time) + continue; //'current' value will be returned + if (i==Field::Text || i==Field::LongText) { + ADD(i, QVariant(QString(""))); + continue; + } + if (i==Field::BLOB) { +//! @todo blobs will contain other mime types too + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + QPixmap pm(SmallIcon("filenew")); + pm.save( &buffer, "PNG"/*! @todo default? */ ); + ADD(i, ba); + continue; + } + ADD(i, KexiDB::emptyValueForType((Field::Type)i)); + } +#undef ADD + } + const QVariant val( KexiDB_notEmptyValueForTypeCache->at( + (type<=Field::LastType) ? type : Field::InvalidType) ); + if (!val.isNull()) + return val; + else { //special cases + if (type==Field::Date) + return QDate::currentDate(); + if (type==Field::DateTime) + return QDateTime::currentDateTime(); + if (type==Field::Time) + return QTime::currentTime(); + } + KexiDBWarn << "KexiDB::notEmptyValueForType() no value for type " + << Field::typeName(type) << endl; + return QVariant(); +} + +QString KexiDB::escapeBLOB(const QByteArray& array, BLOBEscapingType type) +{ + const int size = array.size(); + if (size==0) + return QString::null; + int escaped_length = size*2; + if (type == BLOBEscape0xHex || type == BLOBEscapeOctal) + escaped_length += 2/*0x or X'*/; + else if (type == BLOBEscapeXHex) + escaped_length += 3; //X' + ' + QString str; + str.reserve(escaped_length); + if (str.capacity() < (uint)escaped_length) { + KexiDBWarn << "KexiDB::Driver::escapeBLOB(): no enough memory (cannot allocate "<< + escaped_length<<" chars)" << endl; + return QString::null; + } + if (type == BLOBEscapeXHex) + str = QString::fromLatin1("X'"); + else if (type == BLOBEscape0xHex) + str = QString::fromLatin1("0x"); + else if (type == BLOBEscapeOctal) + str = QString::fromLatin1("'"); + + int new_length = str.length(); //after X' or 0x, etc. + if (type == BLOBEscapeOctal) { + // only escape nonprintable characters as in Table 8-7: + // http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html + // i.e. escape for bytes: < 32, >= 127, 39 ('), 92(\). + for (int i = 0; i < size; i++) { + const unsigned char val = array[i]; + if (val<32 || val>=127 || val==39 || val==92) { + str[new_length++] = '\\'; + str[new_length++] = '\\'; + str[new_length++] = '0' + val/64; + str[new_length++] = '0' + (val % 64) / 8; + str[new_length++] = '0' + val % 8; + } + else { + str[new_length++] = val; + } + } + } + else { + for (int i = 0; i < size; i++) { + const unsigned char val = array[i]; + str[new_length++] = (val/16) < 10 ? ('0'+(val/16)) : ('A'+(val/16)-10); + str[new_length++] = (val%16) < 10 ? ('0'+(val%16)) : ('A'+(val%16)-10); + } + } + if (type == BLOBEscapeXHex || type == BLOBEscapeOctal) + str[new_length++] = '\''; + return str; +} + +QByteArray KexiDB::pgsqlByteaToByteArray(const char* data, int length) +{ + QByteArray array; + int output=0; + for (int pass=0; pass<2; pass++) {//2 passes to avoid allocating buffer twice: + // 0: count #of chars; 1: copy data + const char* s = data; + const char* end = s + length; + if (pass==1) { + KexiDBDbg << "processBinaryData(): real size == " << output << endl; + array.resize(output); + output=0; + } + for (int input=0; s < end; output++) { + // KexiDBDbg<<(int)s[0]<<" "<<(int)s[1]<<" "<<(int)s[2]<<" "<<(int)s[3]<<" "<<(int)s[4]<<endl; + if (s[0]=='\\' && (s+1)<end) { + //special cases as in http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html + if (s[1]=='\'') {// \' + if (pass==1) + array[output] = '\''; + s+=2; + } + else if (s[1]=='\\') { // 2 backslashes + if (pass==1) + array[output] = '\\'; + s+=2; + } + else if ((input+3)<length) {// \\xyz where xyz are 3 octal digits + if (pass==1) + array[output] = char( (int(s[1]-'0')*8+int(s[2]-'0'))*8+int(s[3]-'0') ); + s+=4; + } + else { + KexiDBDrvWarn << "processBinaryData(): no octal value after backslash" << endl; + s++; + } + } + else { + if (pass==1) + array[output] = s[0]; + s++; + } + // KexiDBDbg<<output<<": "<<(int)array[output]<<endl; + } + } + return array; +} + +QString KexiDB::variantToString( const QVariant& v ) +{ + if (v.type()==QVariant::ByteArray) + return KexiDB::escapeBLOB(v.toByteArray(), KexiDB::BLOBEscapeHex); + return v.toString(); +} + +QVariant KexiDB::stringToVariant( const QString& s, QVariant::Type type, bool &ok ) +{ + if (s.isNull()) { + ok = true; + return QVariant(); + } + if (QVariant::Invalid==type) { + ok = false; + return QVariant(); + } + if (type==QVariant::ByteArray) {//special case: hex string + const uint len = s.length(); + QByteArray ba(len/2 + len%2); + for (uint i=0; i<(len-1); i+=2) { + int c = s.mid(i,2).toInt(&ok, 16); + if (!ok) { + KexiDBWarn << "KexiDB::stringToVariant(): Error in digit " << i << endl; + return QVariant(); + } + ba[i/2] = (char)c; + } + ok = true; + return ba; + } + QVariant result(s); + if (!result.cast( type )) { + ok = false; + return QVariant(); + } + ok = true; + return result; +} + +bool KexiDB::isDefaultValueAllowed( KexiDB::Field* field ) +{ + return field && !field->isUniqueKey(); +} + +void KexiDB::getLimitsForType(Field::Type type, int &minValue, int &maxValue) +{ + switch (type) { + case Field::Byte: +//! @todo always ok? + minValue = 0; + maxValue = 255; + break; + case Field::ShortInteger: + minValue = -32768; + maxValue = 32767; + break; + case Field::Integer: + case Field::BigInteger: //cannot return anything larger + default: + minValue = (int)-0x07FFFFFFF; + maxValue = (int)(0x080000000-1); + } +} + +void KexiDB::debugRowData(const RowData& rowData) +{ + KexiDBDbg << QString("ROW DATA (%1 columns):").arg(rowData.count()) << endl; + foreach(RowData::ConstIterator, it, rowData) + KexiDBDbg << "- " << (*it) << endl; +} + +Field::Type KexiDB::maximumForIntegerTypes(Field::Type t1, Field::Type t2) +{ + if (!Field::isIntegerType(t1) || !Field::isIntegerType(t2)) + return Field::InvalidType; + if (t1==t2) + return t2; + if (t1==Field::ShortInteger && t2!=Field::Integer && t2!=Field::BigInteger) + return t1; + if (t1==Field::Integer && t2!=Field::BigInteger) + return t1; + if (t1==Field::BigInteger) + return t1; + return KexiDB::maximumForIntegerTypes(t2, t1); //swap +} + +QString KexiDB::simplifiedTypeName(const Field& field) +{ + if (field.isNumericType()) + return i18n("Number"); //simplify + else if (field.type() == Field::BLOB) +//! @todo support names of other BLOB subtypes + return i18n("Image"); //simplify + + return field.typeGroupName(); +} + +#include "utils_p.moc" |