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"