File indexing completed on 2024-04-28 15:58:51

0001 /* This file is part of the KDE project
0002    Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
0003    Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
0004    Copyright (C) 2003 Joseph Wenninger<jowenn@kde.org>
0005    Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org>
0006 
0007    This program is free software; you can redistribute it and/or
0008    modify it under the terms of the GNU Library General Public
0009    License as published by the Free Software Foundation; either
0010    version 2 of the License, or (at your option) any later version.
0011 
0012    This program is distributed in the hope that it will be useful,
0013    but WITHOUT ANY WARRANTY; without even the implied warranty of
0014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015    Library General Public License for more details.
0016 
0017    You should have received a copy of the GNU Library General Public License
0018    along with this program; see the file COPYING.  If not, write to
0019    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020  * Boston, MA 02110-1301, USA.
0021 */
0022 
0023 #include "MysqlDriver.h"
0024 #include "KDbDriverBehavior.h"
0025 #include "KDbExpression.h"
0026 #include "KDbPreparedStatement.h"
0027 #include "MysqlConnection.h"
0028 
0029 #include <KPluginFactory>
0030 
0031 #include <mysql.h>
0032 
0033 KDB_DRIVER_PLUGIN_FACTORY(MysqlDriver, "kdb_mysqldriver.json")
0034 
0035 /*! @todo Implement buffered/unbuffered cursor, rather than buffer everything.
0036    Each MYSQL connection can only handle at most one unbuffered cursor,
0037    so MysqlConnection should keep count?
0038  */
0039 
0040 MysqlDriver::MysqlDriver(QObject *parent, const QVariantList &args)
0041     : KDbDriver(parent, args)
0042     , m_longTextPrimaryKeyType(QLatin1String("VARCHAR(255)")) // fair enough for PK
0043 {
0044     KDbDriverBehavior *beh = behavior();
0045     beh->features = IgnoreTransactions | CursorForward;
0046 
0047     beh->ROW_ID_FIELD_NAME = QLatin1String("LAST_INSERT_ID()");
0048     beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE = true;
0049     beh->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY = false;
0050     beh->USING_DATABASE_REQUIRED_TO_CONNECT = false;
0051     beh->OPENING_QUOTATION_MARK_BEGIN_FOR_IDENTIFIER = '`';
0052     beh->CLOSING_QUOTATION_MARK_BEGIN_FOR_IDENTIFIER = '`';
0053     //! @todo add configuration option
0054     beh->TEXT_TYPE_MAX_LENGTH = 255;
0055     beh->RANDOM_FUNCTION = QLatin1String("RAND");
0056     beh->GET_TABLE_NAMES_SQL = KDbEscapedString("SHOW TABLES");
0057 
0058     initDriverSpecificKeywords(keywords);
0059 
0060     //predefined properties
0061 #if MYSQL_VERSION_ID < 40000
0062     beh->properties["client_library_version"] = MYSQL_SERVER_VERSION; //nothing better
0063     beh->properties["default_server_encoding"] = MYSQL_CHARSET; //nothing better
0064 #else
0065     // https://dev.mysql.com/doc/refman/5.7/en/mysql-get-client-version.html
0066     beh->properties.insert("client_library_version", int(mysql_get_client_version()));
0067 #endif
0068 
0069     beh->typeNames[KDbField::Byte] = QLatin1String("TINYINT");
0070     beh->typeNames[KDbField::ShortInteger] = QLatin1String("SMALLINT");
0071     beh->typeNames[KDbField::Integer] = QLatin1String("INT");
0072     beh->typeNames[KDbField::BigInteger] = QLatin1String("BIGINT");
0073     // Can use BOOLEAN here, but BOOL has been in MySQL longer
0074     beh->typeNames[KDbField::Boolean] = QLatin1String("BOOL");
0075     beh->typeNames[KDbField::Date] = QLatin1String("DATE");
0076     beh->typeNames[KDbField::DateTime] = QLatin1String("DATETIME");
0077     beh->typeNames[KDbField::Time] = QLatin1String("TIME");
0078     beh->typeNames[KDbField::Float] = QLatin1String("FLOAT");
0079     beh->typeNames[KDbField::Double] = QLatin1String("DOUBLE");
0080     beh->typeNames[KDbField::Text] = QLatin1String("VARCHAR");
0081     beh->typeNames[KDbField::LongText] = QLatin1String("LONGTEXT");
0082     beh->typeNames[KDbField::BLOB] = QLatin1String("BLOB");
0083 }
0084 
0085 MysqlDriver::~MysqlDriver()
0086 {
0087 }
0088 
0089 KDbConnection* MysqlDriver::drv_createConnection(const KDbConnectionData& connData,
0090                                                  const KDbConnectionOptions &options)
0091 {
0092     return new MysqlConnection(this, connData, options);
0093 }
0094 
0095 bool MysqlDriver::isSystemObjectName(const QString& name) const
0096 {
0097     Q_UNUSED(name);
0098     return false;
0099 }
0100 
0101 bool MysqlDriver::isSystemDatabaseName(const QString &name) const
0102 {
0103     return    0 == name.compare(QLatin1String("mysql"), Qt::CaseInsensitive)
0104            || 0 == name.compare(QLatin1String("information_schema"), Qt::CaseInsensitive)
0105            || 0 == name.compare(QLatin1String("performance_schema"), Qt::CaseInsensitive);
0106 }
0107 
0108 bool MysqlDriver::drv_isSystemFieldName(const QString& name) const
0109 {
0110     Q_UNUSED(name);
0111     return false;
0112 }
0113 
0114 bool MysqlDriver::supportsDefaultValue(const KDbField &field) const
0115 {
0116     switch(field.type()) {
0117     case KDbField::LongText:
0118     case KDbField::BLOB:
0119         return false;
0120     default:
0121         return true;
0122     }
0123 }
0124 
0125 KDbEscapedString MysqlDriver::escapeString(const QString& str) const
0126 {
0127     //escape as in https://dev.mysql.com/doc/refman/5.0/en/string-syntax.html
0128 //! @todo support more characters, like %, _
0129 
0130     const int old_length = str.length();
0131     int i;
0132     for (i = 0; i < old_length; i++) {   //anything to escape?
0133         const unsigned int ch = str[i].unicode();
0134         if (ch == '\\' || ch == '\'' || ch == '"' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\b' || ch == '\0')
0135             break;
0136     }
0137     if (i >= old_length) { //no characters to escape
0138         return KDbEscapedString("'") + KDbEscapedString(str) + '\'';
0139     }
0140 
0141     QChar *new_string = new QChar[ old_length * 3 + 1 ]; // a worst case approximation
0142 //! @todo move new_string to KDbDriver::m_new_string or so...
0143     int new_length = 0;
0144     new_string[new_length++] = QLatin1Char('\''); //prepend '
0145     for (i = 0; i < old_length; i++, new_length++) {
0146         const unsigned int ch = str[i].unicode();
0147         if (ch == '\\') {
0148             new_string[new_length++] = QLatin1Char('\\');
0149             new_string[new_length] = QLatin1Char('\\');
0150         } else if (ch <= '\'') {//check for speedup
0151             if (ch == '\'') {
0152                 new_string[new_length++] = QLatin1Char('\\');
0153                 new_string[new_length] = QLatin1Char('\'');
0154             } else if (ch == '"') {
0155                 new_string[new_length++] = QLatin1Char('\\');
0156                 new_string[new_length] = QLatin1Char('"');
0157             } else if (ch == '\n') {
0158                 new_string[new_length++] = QLatin1Char('\\');
0159                 new_string[new_length] = QLatin1Char('n');
0160             } else if (ch == '\r') {
0161                 new_string[new_length++] = QLatin1Char('\\');
0162                 new_string[new_length] = QLatin1Char('r');
0163             } else if (ch == '\t') {
0164                 new_string[new_length++] = QLatin1Char('\\');
0165                 new_string[new_length] = QLatin1Char('t');
0166             } else if (ch == '\b') {
0167                 new_string[new_length++] = QLatin1Char('\\');
0168                 new_string[new_length] = QLatin1Char('b');
0169             } else if (ch == '\0') {
0170                 new_string[new_length++] = QLatin1Char('\\');
0171                 new_string[new_length] = QLatin1Char('0');
0172             } else
0173                 new_string[new_length] = str[i];
0174         } else
0175             new_string[new_length] = str[i];
0176     }
0177 
0178     new_string[new_length++] = QLatin1Char('\''); //append '
0179     KDbEscapedString result(QString(new_string, new_length));
0180     delete [] new_string;
0181     return result;
0182 }
0183 
0184 KDbEscapedString MysqlDriver::escapeBLOB(const QByteArray& array) const
0185 {
0186     return KDbEscapedString(KDb::escapeBLOB(array, KDb::BLOBEscapingType::ZeroXHex));
0187 }
0188 
0189 KDbEscapedString MysqlDriver::escapeString(const QByteArray& str) const
0190 {
0191 //! @todo optimize using mysql_real_escape_string()?
0192 //! see https://dev.mysql.com/doc/refman/5.0/en/string-syntax.html
0193 
0194     return KDbEscapedString("'") + KDbEscapedString(str)
0195            .replace('\\', "\\\\")
0196            .replace('\'', "\\''")
0197            .replace('"', "\\\"")
0198            + '\'';
0199 }
0200 
0201 /*! Add back-ticks to an identifier, and replace any back-ticks within
0202  * the name with single quotes.
0203  */
0204 QString MysqlDriver::drv_escapeIdentifier(const QString& str) const
0205 {
0206     return QString(str).replace(QLatin1Char('"'), QLatin1String("\"\""));
0207 }
0208 
0209 QByteArray MysqlDriver::drv_escapeIdentifier(const QByteArray& str) const
0210 {
0211     return QByteArray(str).replace('`', '\'');
0212 }
0213 
0214 //! Overrides the default implementation
0215 QString MysqlDriver::sqlTypeName(KDbField::Type type, const KDbField &field) const
0216 {
0217     if (field.isPrimaryKey() && type == KDbField::LongText) {
0218         return m_longTextPrimaryKeyType;
0219     }
0220     return KDbDriver::sqlTypeName(type, field);
0221 }
0222 
0223 KDbEscapedString MysqlDriver::lengthFunctionToString(const KDbNArgExpression &args,
0224                                                      KDbQuerySchemaParameterValueListIterator* params,
0225                                                      KDb::ExpressionCallStack* callStack) const
0226 {
0227     return KDbFunctionExpression::toString(
0228                 QLatin1String("CHAR_LENGTH"), this, args, params, callStack);
0229 }
0230 
0231 KDbEscapedString MysqlDriver::greatestOrLeastFunctionToString(const QString &name,
0232                                                      const KDbNArgExpression &args,
0233                                                      KDbQuerySchemaParameterValueListIterator* params,
0234                                                      KDb::ExpressionCallStack* callStack) const
0235 {
0236     return KDbFunctionExpression::greatestOrLeastFunctionUsingCaseToString(
0237                 name, this, args, params, callStack);
0238 }
0239 
0240 KDbEscapedString MysqlDriver::unicodeFunctionToString(
0241                                             const KDbNArgExpression &args,
0242                                             KDbQuerySchemaParameterValueListIterator* params,
0243                                             KDb::ExpressionCallStack* callStack) const
0244 {
0245     Q_ASSERT(args.argCount() == 1);
0246     return KDbEscapedString("ORD(CONVERT(%1 USING UTF16))")
0247                             .arg(args.arg(0).toString(this, params, callStack));
0248 }
0249 
0250 KDbEscapedString MysqlDriver::concatenateFunctionToString(const KDbBinaryExpression &args,
0251                                                           KDbQuerySchemaParameterValueListIterator* params,
0252                                                           KDb::ExpressionCallStack* callStack) const
0253 {
0254     return KDbEscapedString("CONCAT(%1, %2)").arg(args.left().toString(this, params, callStack))
0255                                              .arg(args.right().toString(this, params, callStack));
0256 }
0257 
0258 #include "MysqlDriver.moc"