/* This file is part of the KDE project Copyright (C) 2004-2007 Jaroslaw Staniek 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. */ #ifndef KEXIDB_UTILS_H #define KEXIDB_UTILS_H #include #include #include #include class QDomNode; class QDomElement; class QDomDocument; namespace KexiDB { //! for convenience inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, TableSchema *table, const QString &keyname, const QString &keyval) { return table!=0 && conn.executeSQL("DELETE FROM " + table->name() + " WHERE " + keyname + "=" + conn.driver()->valueToSQL( Field::Text, QVariant(keyval) )); } inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, const QString &tableName, const QString &keyname, const QString &keyval) { return conn.executeSQL("DELETE FROM " + tableName + " WHERE " + keyname + "=" + conn.driver()->valueToSQL( Field::Text, QVariant(keyval) )); } inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, TableSchema *table, const QString &keyname, int keyval) { return table!=0 && conn.executeSQL("DELETE FROM " + table->name() + " WHERE " + keyname + "=" + conn.driver()->valueToSQL( Field::Integer, QVariant(keyval) )); } inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, const QString &tableName, const QString &keyname, int keyval) { return conn.executeSQL("DELETE FROM " + tableName + " WHERE " + keyname + "=" + conn.driver()->valueToSQL( Field::Integer, QVariant(keyval) )); } /*! Delete row with two generic criterias. */ inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, const QString &tableName, const QString &keyname1, Field::Type keytype1, const QVariant& keyval1, const QString &keyname2, Field::Type keytype2, const QVariant& keyval2) { return conn.executeSQL("DELETE FROM " + tableName + " WHERE " + keyname1 + "=" + conn.driver()->valueToSQL( keytype1, keyval1 ) + " AND " + keyname2 + "=" + conn.driver()->valueToSQL( keytype2, keyval2 )); } inline KEXI_DB_EXPORT bool replaceRow(Connection &conn, TableSchema *table, const QString &keyname, const QString &keyval, const QString &valname, QVariant val, int ftype) { if (!table || !KexiDB::deleteRow(conn, table, keyname, keyval)) return false; return conn.executeSQL("INSERT INTO " + table->name() + " (" + keyname + "," + valname + ") VALUES (" + conn.driver()->valueToSQL( Field::Text, QVariant(keyval) ) + "," + conn.driver()->valueToSQL( ftype, val) + ")"); } typedef QValueList TypeGroupList; /*! \return list of types for type group \a typeGroup. */ KEXI_DB_EXPORT const TypeGroupList typesForGroup(Field::TypeGroup typeGroup); /*! \return list of i18n'd type names for type group \a typeGroup. */ KEXI_DB_EXPORT QStringList typeNamesForGroup(Field::TypeGroup typeGroup); /*! \return list of (not-i18n'd) type names for type group \a typeGroup. */ KEXI_DB_EXPORT QStringList typeStringsForGroup(Field::TypeGroup typeGroup); /*! \return default field type for type group \a typeGroup, for example, Field::Integer for Field::IntegerGroup. It is used e.g. in KexiAlterTableDialog, to properly fill 'type' property when user selects type group for a field. */ KEXI_DB_EXPORT Field::Type defaultTypeForGroup(Field::TypeGroup typeGroup); /*! \return a slightly simplified type name for \a field. For BLOB type it returns i18n'd "Image" string or other, depending on the mime type. For numbers (either floating-point or integer) it returns i18n'd "Number: string. For other types it the same string as Field::typeGroupName() is returned. */ //! @todo support names of other BLOB subtypes KEXI_DB_EXPORT QString simplifiedTypeName(const Field& field); /*! \return true if \a v represents an empty (but not null) value. Values of some types (as for strings) can be both empty and not null. */ inline bool isEmptyValue(Field *f, const QVariant &v) { if (f->hasEmptyProperty() && v.toString().isEmpty() && !v.toString().isNull()) return true; return v.isNull(); } /*! Sets \a msg to an error message retrieved from object \a obj, and \a details to details of this error (server message and result number). Does nothing if \a obj is null or no error occurred. \a msg and \a details strings are not overwritten. If \a msg is not empty, \a obj's error message is appended to \a details. */ KEXI_DB_EXPORT void getHTMLErrorMesage(Object* obj, QString& msg, QString &details); /*! This methods works like above, but appends both a message and a description to \a msg. */ KEXI_DB_EXPORT void getHTMLErrorMesage(Object* obj, QString& msg); /*! This methods works like above, but works on \a result's members instead. */ KEXI_DB_EXPORT void getHTMLErrorMesage(Object* obj, ResultInfo *result); /*! Function useful for building WHERE parts of sql statements. Constructs an sql string like "fielname = value" for specific \a drv driver, field type \a t, \a fieldName and \a value. If \a value is null, "fieldname is NULL" string is returned. */ inline KEXI_DB_EXPORT QString sqlWhere(Driver *drv, Field::Type t, const QString fieldName, const QVariant value) { if (value.isNull()) return fieldName + " is NULL"; return fieldName + "=" + drv->valueToSQL( t, value ); } /*! \return identifier for object \a objName of type \a objType or 0 if such object does not exist. */ KEXI_DB_EXPORT int idForObjectName( Connection &conn, const QString& objName, int objType ); /*! Variant class providing a pointer to table or query. */ class KEXI_DB_EXPORT TableOrQuerySchema { public: /*! Creates a new TableOrQuerySchema variant object, retrieving table or query schema using \a conn connection and \a name. If both table and query exists for \a name, table has priority over query. You should check whether a query or table has been found by testing (query() || table()) expression. */ TableOrQuerySchema(Connection *conn, const QCString& name); /*! Creates a new TableOrQuerySchema variant object, retrieving table or query schema using \a conn connection and \a name. If \a table is true, \a name is assumed to be a table name, otherwise \a name is assumed to be a query name. You should check whether a query or table has been found by testing (query() || table()) expression. */ TableOrQuerySchema(Connection *conn, const QCString& name, bool table); /*! Creates a new TableOrQuerySchema variant object. \a tableOrQuery must be of class TableSchema or QuerySchema. You should check whether a query or table has been found by testing (query() || table()) expression. */ TableOrQuerySchema(FieldList &tableOrQuery); /*! Creates a new TableOrQuerySchema variant object, retrieving table or query schema using \a conn connection and \a id. You should check whether a query or table has been found by testing (query() || table()) expression. */ TableOrQuerySchema(Connection *conn, int id); /*! Creates a new TableOrQuerySchema variant object, keeping a pointer so \a table object. */ TableOrQuerySchema(TableSchema* table); /*! Creates a new TableOrQuerySchema variant object, keeping a pointer so \a query object. */ TableOrQuerySchema(QuerySchema* query); //! \return a pointer to the query if it's provided QuerySchema* query() const { return m_query; } //! \return a pointer to the table if it's provided TableSchema* table() const { return m_table; } //! \return name of a query or table QCString name() const; //! \return caption (if present) or name of the table/query QString captionOrName() const; //! \return number of fields uint fieldCount() const; //! \return all columns for the table or the query const QueryColumnInfo::Vector columns(bool unique = false); /*! \return a field of the table or the query schema for name \a name or 0 if there is no such field. */ Field* field(const QString& name); /*! Like Field* field(const QString& name); but returns all information associated with field/column \a name. */ QueryColumnInfo* columnInfo(const QString& name); /*! \return connection object, for table or query or 0 if there's no table or query defined. */ Connection* connection() const; /*! \return String for debugging purposes. */ QString debugString(); /*! Shows debug information about table or query. */ void debug(); protected: QCString m_name; //!< the name is kept here because m_table and m_table can be 0 //! and we still want name() and acptionOrName() work. TableSchema* m_table; QuerySchema* m_query; }; //! @todo perhaps use Q_ULLONG here? /*! \return number of rows that can be retrieved after executing \a sql statement within a connection \a conn. The statement should be of type SELECT. For SQL data sources it does not fetch any records, only "COUNT(*)" SQL aggregation is used at the backed. -1 is returned if error occured. */ int rowCount(Connection &conn, const QString& sql); //! @todo perhaps use Q_ULLONG here? /*! \return number of rows that can be retrieved from \a tableSchema. The table must be created or retrieved using a Connection object, i.e. tableSchema.connection() must not return 0. For SQL data sources it does not fetch any records, only "COUNT(*)" SQL aggregation is used at the backed. -1 is returned if error occurred. */ KEXI_DB_EXPORT int rowCount(const TableSchema& tableSchema); //! @todo perhaps use Q_ULLONG here? /*! Like above but operates on a query schema. */ KEXI_DB_EXPORT int rowCount(QuerySchema& querySchema); //! @todo perhaps use Q_ULLONG here? /*! Like above but operates on a table or query schema variant. */ KEXI_DB_EXPORT int rowCount(TableOrQuerySchema& tableOrQuery); /*! \return a number of columns that can be retrieved from table or query schema. In case of query, expanded fields are counted. Can return -1 if \a tableOrQuery has neither table or query assigned. */ KEXI_DB_EXPORT int fieldCount(TableOrQuerySchema& tableOrQuery); /*! shows connection test dialog with a progress bar indicating connection testing (within a second thread). \a data is used to perform a (temporary) test connection. \a msgHandler is used to display errors. On successful connecting, a message is displayed. After testing, temporary connection is closed. */ KEXI_DB_EXPORT void connectionTestDialog(QWidget* parent, const ConnectionData& data, MessageHandler& msgHandler); /*! Saves connection data \a data into \a map. */ KEXI_DB_EXPORT QMap toMap( const ConnectionData& data ); /*! Restores connection data \a data from \a map. */ KEXI_DB_EXPORT void fromMap( const QMap& map, ConnectionData& data ); //! Used in splitToTableAndFieldParts(). enum SplitToTableAndFieldPartsOptions { FailIfNoTableOrFieldName = 0, //!< default value for splitToTableAndFieldParts() SetFieldNameIfNoTableName = 1 //!< see splitToTableAndFieldParts() }; /*! Splits \a string like "table.field" into "table" and "field" parts. On success, a table name is passed to \a tableName and a field name is passed to \a fieldName. The function fails if either: - \a string is empty, or - \a string does not contain '.' character and \a option is FailIfNoTableOrFieldName (the default), or - '.' character is the first of last character of \a string (in this case table name or field name could become empty what is not allowed). If \a option is SetFieldNameIfNoTableName and \a string does not contain '.', \a string is passed to \a fieldName and \a tableName is set to QString::null without failure. If function fails, \a tableName and \a fieldName remain unchanged. \return true on success. */ KEXI_DB_EXPORT bool splitToTableAndFieldParts(const QString& string, QString& tableName, QString& fieldName, SplitToTableAndFieldPartsOptions option = FailIfNoTableOrFieldName); /*! \return true if \a type supports "visibleDecimalPlaces" property. */ KEXI_DB_EXPORT bool supportsVisibleDecimalPlacesProperty(Field::Type type); /*! \return string constructed by converting \a value. * If \a decimalPlaces is < 0, all meaningful fractional digits are returned. * If \a automatically is 0, just integer part is returned. * If \a automatically is > 0, fractional part should take exactly N digits: if the fractional part is shorter than N, additional zeros are appended. For example, "12.345" becomes "12.345000" if N=6. No rounding is actually performed. KLocale::formatNumber() and KLocale::decimalSymbol() are used to get locale settings. @see KexiDB::Field::visibleDecimalPlaces() */ KEXI_DB_EXPORT QString formatNumberForVisibleDecimalPlaces(double value, int decimalPlaces); //! \return true if \a propertyName is a builtin field property. KEXI_DB_EXPORT bool isBuiltinTableFieldProperty( const QCString& propertyName ); //! \return true if \a propertyName is an extended field property. KEXI_DB_EXPORT bool isExtendedTableFieldProperty( const QCString& propertyName ); /*! \return type of field for integer value \a type. If \a type cannot be casted to KexiDB::Field::Type, KexiDB::Field::InvalidType is returned. This can be used when type information is deserialized from a string or QVariant. */ KEXI_DB_EXPORT Field::Type intToFieldType( int type ); /*! Sets property values for \a field. \return true if all the values are valid and allowed. On failure contents of \a field is undefined. Properties coming from extended schema are also supported. This function is used e.g. by AlterTableHandler when property information comes in form of text. */ KEXI_DB_EXPORT bool setFieldProperties( Field& field, const QMap& values ); /*! Sets property value for \a field. \return true if the property has been found and the value is valid for this property. On failure contents of \a field is undefined. Properties coming from extended schema are also supported as well as QVariant customProperty(const QString& propertyName) const; This function is used e.g. by AlterTableHandler when property information comes in form of text. */ KEXI_DB_EXPORT bool setFieldProperty(Field& field, const QCString& propertyName, const QVariant& value); /*! @return property value loaded from a DOM \a node, written in a QtDesigner-like notation: <number>int</number> or <bool>bool</bool>, etc. Supported types are "string", "cstring", "bool", "number". For invalid values null QVariant is returned. You can check the validity of the returned value using QVariant::type(). */ KEXI_DB_EXPORT QVariant loadPropertyValueFromDom( const QDomNode& node ); /*! Convenience version of loadPropertyValueFromDom(). \return int value. */ KEXI_DB_EXPORT int loadIntPropertyValueFromDom( const QDomNode& node, bool* ok ); /*! Convenience version of loadPropertyValueFromDom(). \return QString value. */ KEXI_DB_EXPORT QString loadStringPropertyValueFromDom( const QDomNode& node, bool* ok ); /*! Saves integer element for value \a value to \a doc document within parent element \a parentEl. The value will be enclosed in "number" element and "elementName" element. Example: saveNumberElementToDom(doc, parentEl, "height", 15) will create \code 15 \endcode \return the reference to element created with tag elementName. */ KEXI_DB_EXPORT QDomElement saveNumberElementToDom(QDomDocument& doc, QDomElement& parentEl, const QString& elementName, int value); /*! Saves boolean element for value \a value to \a doc document within parent element \a parentEl. Like saveNumberElementToDom() but creates "bool" tags. True/false values will be saved as "true"/"false" strings. \return the reference to element created with tag elementName. */ KEXI_DB_EXPORT QDomElement saveBooleanElementToDom(QDomDocument& doc, QDomElement& parentEl, const QString& elementName, bool value); /*! \return an empty value that can be set for a database field of type \a type having "null" property set. Empty string is returned for text type, 0 for integer or floating-point types, false for boolean type, empty null byte array for BLOB type. For date, time and date/time types current date, time, date+time is returned, respectively. Returns null QVariant for unsupported values like KexiDB::Field::InvalidType. This function is efficient (uses a cache) and is heavily used by the AlterTableHandler for filling new columns. */ KEXI_DB_EXPORT QVariant emptyValueForType( Field::Type type ); /*! \return a value that can be set for a database field of type \a type having "notEmpty" property set. It works in a similar way as @ref QVariant emptyValueForType( KexiDB::Field::Type type ) with the following differences: - " " string (a single space) is returned for Text and LongText types - a byte array with saved "filenew" PNG image (icon) for BLOB type Returns null QVariant for unsupported values like KexiDB::Field::InvalidType. This function is efficient (uses a cache) and is heavily used by the AlterTableHandler for filling new columns. */ KEXI_DB_EXPORT QVariant notEmptyValueForType( Field::Type type ); //! Escaping types used in escapeBLOB(). enum BLOBEscapingType { BLOBEscapeXHex = 1, //!< escaping like X'1FAD', used by sqlite (hex numbers) BLOBEscape0xHex, //!< escaping like 0x1FAD, used by mysql (hex numbers) BLOBEscapeHex, //!< escaping like 1FAD without quotes or prefixes BLOBEscapeOctal //!< escaping like 'zk\\000$x', used by pgsql //!< (only non-printable characters are escaped using octal numbers) //!< See http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html }; //! @todo reverse function for BLOBEscapeOctal is available: processBinaryData() in pqxxcursor.cpp - move it here /*! \return a string containing escaped, printable representation of \a array. Escaping is controlled by \a type. For empty array QString::null is returned, so if you want to use this function in an SQL statement, empty arrays should be detected and "NULL" string should be put instead. This is helper, used in Driver::escapeBLOB() and KexiDB::variantToString(). */ KEXI_DB_EXPORT QString escapeBLOB(const QByteArray& array, BLOBEscapingType type); /*! \return byte array converted from \a data of length \a length. \a data is escaped in format used by PostgreSQL's bytea datatype described at http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html This function is used by PostgreSQL KexiDB and migration drivers. */ KEXI_DB_EXPORT QByteArray pgsqlByteaToByteArray(const char* data, int length); /*! \return string value serialized from a variant value \a v. This functions works like QVariant::toString() except the case when \a v is of type ByteArray. In this case KexiDB::escapeBLOB(v.toByteArray(), KexiDB::BLOBEscapeHex) is used. This function is needed for handling values of random type, for example "defaultValue" property of table fields can contain value of any type. Note: the returned string is an unescaped string. */ KEXI_DB_EXPORT QString variantToString( const QVariant& v ); /*! \return variant value of type \a type for a string \a s that was previously serialized using \ref variantToString( const QVariant& v ) function. \a ok is set to the result of the operation. */ KEXI_DB_EXPORT QVariant stringToVariant( const QString& s, QVariant::Type type, bool &ok ); /*! \return true if setting default value for \a field field is allowed. Fields with unique (and thus primary key) flags set do not accept default values. False is returned aslo if \a field is 0. */ KEXI_DB_EXPORT bool isDefaultValueAllowed( Field* field ); /*! Gets limits for values of type \a type. The result is put into \a minValue and \a maxValue. Supported types are Byte, ShortInteger, Integer and BigInteger Results for BigInteger or non-integer types are the same as for Integer due to limitation of int type. Signed integers are assumed. */ //! @todo add support for unsigned flag KEXI_DB_EXPORT void getLimitsForType(Field::Type type, int &minValue, int &maxValue); /*! Shows debug information about \a rowData row data. */ KEXI_DB_EXPORT void debugRowData(const RowData& rowData); /*! \return type that's maximum of two integer types \a t1 and \a t2, e.g. Integer for (Byte, Integer). If one of the types is not of the integer group, Field::InvalidType is returned. */ KEXI_DB_EXPORT Field::Type maximumForIntegerTypes(Field::Type t1, Field::Type t2); /*! \return QVariant value converted from null-terminated \a data string. In case of BLOB type, \a data is not nul lterminated, so passing length is needed. */ inline QVariant cstringToVariant(const char* data, KexiDB::Field* f, int length = -1) { if (!data) return QVariant(); // from mo st to least frequently used types: if (!f || f->isTextType()) return QString::fromUtf8(data, length); if (f->isIntegerType()) { if (f->type()==KexiDB::Field::BigInteger) return QVariant( QString::fromLatin1(data, length).toLongLong() ); return QVariant( QString::fromLatin1(data, length).toInt() ); } if (f->isFPNumericType()) return QString::fromLatin1(data, length).toDouble(); if (f->type()==KexiDB::Field::BLOB) { QByteArray ba; ba.duplicate(data, length); return ba; } // the default //! @todo date/time? QVariant result(QString::fromUtf8(data, length)); if (!result.cast( KexiDB::Field::variantType(f->type()) )) return QVariant(); return result; } } #endif