File indexing completed on 2024-04-28 15:58:50
0001 /* This file is part of the KDE project 0002 Copyright (C) 2004 Martin Ellis <martin.ellis@kdemail.net> 0003 Copyright (C) 2004-2016 Jarosław Staniek <staniek@kde.org> 0004 0005 This program is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Library General Public 0007 License as published by the Free Software Foundation; either 0008 version 2 of the License, or (at your option) any later version. 0009 0010 This program is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Library General Public License for more details. 0014 0015 You should have received a copy of the GNU Library General Public License 0016 along with this program; see the file COPYING. If not, write to 0017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 * Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "MysqlConnection_p.h" 0022 #include "mysql_debug.h" 0023 0024 #include "KDbConnectionData.h" 0025 0026 #include <QByteArray> 0027 #include <QStringList> 0028 #include <QFile> 0029 0030 static inline QString escapeIdentifier(const QString& str) 0031 { 0032 return QString(str).replace(QLatin1Char('`'), QLatin1Char('\'')); 0033 } 0034 0035 MysqlConnectionInternal::MysqlConnectionInternal(KDbConnection* connection) 0036 : KDbConnectionInternal(connection) 0037 , mysql(nullptr) 0038 , mysql_owned(true) 0039 , res(0) 0040 , lowerCaseTableNames(false) 0041 , serverVersion(0) 0042 { 0043 } 0044 0045 MysqlConnectionInternal::~MysqlConnectionInternal() 0046 { 0047 if (mysql_owned && mysql) { 0048 db_disconnect(); 0049 } 0050 } 0051 0052 bool MysqlConnectionInternal::db_connect(const KDbConnectionData& data) 0053 { 0054 if (!(mysql = mysql_init(mysql))) 0055 return false; 0056 0057 mysqlDebug(); 0058 QByteArray localSocket; 0059 QByteArray hostName = QFile::encodeName(data.hostName()); 0060 if (hostName.isEmpty() || 0 == qstricmp(hostName.constData(), "localhost")) { 0061 if (data.useLocalSocketFile()) { 0062 if (data.localSocketFileName().isEmpty()) { 0063 //! @todo move the list of default sockets to a generic method 0064 QStringList sockets; 0065 #ifndef Q_OS_WIN 0066 sockets 0067 << QLatin1String("/var/lib/mysql/mysql.sock") 0068 << QLatin1String("/var/run/mysqld/mysqld.sock") 0069 << QLatin1String("/var/run/mysql/mysql.sock") 0070 << QLatin1String("/tmp/mysql.sock"); 0071 0072 foreach(const QString& socket, sockets) { 0073 if (QFile(socket).exists()) { 0074 localSocket = socket.toLatin1(); 0075 break; 0076 } 0077 } 0078 #endif 0079 } else 0080 localSocket = QFile::encodeName(data.localSocketFileName()); 0081 } else { 0082 //we're not using local socket 0083 hostName = "127.0.0.1"; //this will force mysql to connect to localhost 0084 } 0085 } 0086 0087 /*! @todo is latin1() encoding here valid? what about using UTF for passwords? */ 0088 const QByteArray userName(data.userName().toLatin1()); 0089 const QByteArray password(data.password().toLatin1()); 0090 int client_flag = 0; //!< @todo support client_flag? 0091 if (mysql_real_connect(mysql, hostName.isEmpty() ? nullptr : hostName.constData(), 0092 data.userName().isEmpty() ? nullptr : userName.constData(), 0093 data.password().isNull() ? nullptr : password.constData(), 0094 nullptr, 0095 data.port(), localSocket.isEmpty() ? nullptr : localSocket.constData(), 0096 client_flag)) 0097 { 0098 serverVersion = mysql_get_server_version(mysql); 0099 return true; 0100 } 0101 return false; 0102 } 0103 0104 bool MysqlConnectionInternal::db_disconnect() 0105 { 0106 mysql_close(mysql); 0107 mysql = nullptr; 0108 serverVersion = 0; 0109 mysqlDebug(); 0110 return true; 0111 } 0112 0113 bool MysqlConnectionInternal::useDatabase(const QString &dbName) 0114 { 0115 //! @todo is here escaping needed? 0116 if (!executeSql(KDbEscapedString("USE ") + escapeIdentifier(dbName))) { 0117 return false; 0118 } 0119 if (!executeSql(KDbEscapedString("SET SESSION sql_mode='TRADITIONAL'"))) { 0120 // needed to turn warnings about trimming string values into SQL errors 0121 return false; 0122 } 0123 return true; 0124 } 0125 0126 bool MysqlConnectionInternal::executeSql(const KDbEscapedString& sql) 0127 { 0128 return 0 == mysql_real_query(mysql, sql.constData(), sql.length()); 0129 } 0130 0131 //static 0132 QString MysqlConnectionInternal::serverResultName(MYSQL *mysql) 0133 { 0134 //! @todo use mysql_stmt_sqlstate() for prepared statements 0135 return QString::fromLatin1(mysql_sqlstate(mysql)); 0136 } 0137 0138 void MysqlConnectionInternal::storeResult(KDbResult *result) 0139 { 0140 result->setServerMessage(QString::fromLatin1(mysql_error(mysql))); 0141 result->setServerErrorCode(mysql_errno(mysql)); 0142 } 0143 0144 //-------------------------------------- 0145 0146 MysqlCursorData::MysqlCursorData(KDbConnection* connection) 0147 : MysqlConnectionInternal(connection) 0148 , mysqlres(nullptr) 0149 , mysqlrow(nullptr) 0150 , lengths(nullptr) 0151 , numRows(0) 0152 { 0153 mysql_owned = false; 0154 mysql = static_cast<MysqlConnection*>(connection)->d->mysql; 0155 } 0156 0157 MysqlCursorData::~MysqlCursorData() 0158 { 0159 } 0160 0161 //-------------------------------------- 0162 0163 static inline KDbSqlString mysqlTypeName(MysqlSqlResult *result) 0164 { 0165 KDbSqlString name; 0166 if (result && result->fieldsCount() >= 2) { 0167 QSharedPointer<KDbSqlRecord> record = result->fetchRecord(); 0168 if (record) { 0169 name = static_cast<MysqlSqlRecord*>(record.data())->cstringValue(1); 0170 } 0171 } 0172 return name; 0173 } 0174 0175 //! @todo From Kexi MysqlMigrate, unused for now because enum type isn't supported by KDb 0176 #if 0 0177 //! Get the strings that identify values in an enum field 0178 /*! Parse the type of a MySQL enum field as returned by the server in a 0179 'DESCRIBE table' or 'SHOW COLUMNS FROM table' statement. The string 0180 returned by the server is in the form 'enum('option1','option2'). 0181 In this example, the result should be a string list containing two 0182 strings, "option1", "option2". 0183 \return list of possible values the field can take 0184 */ 0185 QStringList examineEnumField(const QString& table, const KDbSqlField* field) 0186 { 0187 QString vals; 0188 const KDbEscapedString query 0189 = KDbEscapedString("SHOW COLUMNS FROM `") + conn->escapeIdentifier(table) + 0190 "` LIKE '" + conn->escapeIdentifier(fld->name) + '\''; 0191 0192 if (!conn->executeSql(query)) 0193 // Huh? MySQL wont tell us what values it can take. 0194 return QStringList(); 0195 0196 MYSQL_RES *res = mysql_store_result(d->mysql); 0197 0198 if (!res) { 0199 //qWarning() << "null result"; 0200 } 0201 else { 0202 MYSQL_ROW row; 0203 if ((row = mysql_fetch_row(res))) 0204 vals = QString(row[1]); 0205 mysql_free_result(res); 0206 } 0207 0208 qDebug() << "considering" << vals; 0209 0210 // Crash and burn if we get confused... 0211 if (!vals.startsWith("enum(")) { 0212 // Huh? We're supposed to be parsing an enum! 0213 qWarning() << "1 not an enum!"; 0214 return QStringList(); 0215 } 0216 if (!vals.endsWith(')')) { 0217 qWarning() << "2 not an enum!"; 0218 return QStringList(); 0219 } 0220 0221 // It'd be nice to use QString.section or QStringList.split, but we need 0222 // to be careful as enum values can have commas and quote marks in them 0223 // e.g. CREATE TABLE t(f enum('option,''') gives one option: "option,'" 0224 vals.remove(0, 5); 0225 QRegularExpression rx = QRegularExpression("^'((?:[^,']|,|'')*)'"); 0226 QStringList values = QStringList(); 0227 int index = 0; 0228 0229 while ((index = rx.indexIn(vals, index, QRegularExpression::CaretAtOffset)) != -1) { 0230 int len = rx.matchedLength(); 0231 if (len != -1) { 0232 //qDebug() << "3 " << rx.cap(1); 0233 values << rx.cap(1); 0234 } else { 0235 qDebug() << "4 lost"; 0236 } 0237 0238 QChar next = vals[index + len]; 0239 if (next != QChar(',') && next != QChar(')')) { 0240 qDebug() << "5 " << next; 0241 } 0242 index += len + 1; 0243 } 0244 0245 return values; 0246 } 0247 #endif // examineEnumField 0248 0249 KDbField::Type MysqlSqlResult::blobType(const QString& tableName, MysqlSqlField *field) 0250 { 0251 KDbField::Type kdbType = KDbField::LongText; 0252 const KDbEscapedString sql = KDbEscapedString("SHOW COLUMNS FROM %1 LIKE '%2'") 0253 .arg(escapeIdentifier(tableName)).arg(field->name()); 0254 //! @todo this conflicts with active query 0255 QSharedPointer<KDbSqlResult> result = conn->prepareSql(sql); 0256 if (result) { 0257 MysqlSqlResult* mysqlResult = static_cast<MysqlSqlResult*>(result.data()); 0258 const KDbSqlString typeName(mysqlTypeName(mysqlResult)); 0259 if (typeName.rawDataToByteArray().toLower().contains("blob")) { 0260 // Doesn't matter how big it is, it's binary 0261 kdbType = KDbField::BLOB; 0262 } else if (field->length() < 200) { 0263 kdbType = KDbField::Text; 0264 } 0265 } 0266 return kdbType; 0267 } 0268 0269 KDbField::Type MysqlSqlResult::type(const QString& tableName, MysqlSqlField *field) 0270 { 0271 // Field type 0272 KDbField::Type kdbType = KDbField::InvalidType; 0273 0274 switch (field->type()) { 0275 // These are in the same order as mysql_com.h. 0276 // MySQL names given on the right 0277 case FIELD_TYPE_DECIMAL: // DECIMAL or NUMERIC 0278 break; 0279 case FIELD_TYPE_TINY: // TINYINT (-2^7..2^7-1 or 2^8) 0280 kdbType = KDbField::Byte; 0281 break; 0282 case FIELD_TYPE_SHORT: // SMALLINT (-2^15..2^15-1 or 2^16) 0283 kdbType = KDbField::ShortInteger; 0284 break; 0285 case FIELD_TYPE_LONG: // INTEGER (-2^31..2^31-1 or 2^32) 0286 kdbType = KDbField::Integer; 0287 break; 0288 case FIELD_TYPE_FLOAT: // FLOAT 0289 kdbType = KDbField::Float; 0290 break; 0291 case FIELD_TYPE_DOUBLE: // DOUBLE or REAL (8 byte) 0292 kdbType = KDbField::Double; 0293 break; 0294 case FIELD_TYPE_NULL: // WTF? 0295 break; 0296 case FIELD_TYPE_TIMESTAMP: // TIMESTAMP (promote?) 0297 kdbType = KDbField::DateTime; 0298 break; 0299 case FIELD_TYPE_LONGLONG: // BIGINT (-2^63..2^63-1 or 2^64) 0300 case FIELD_TYPE_INT24: // MEDIUMINT (-2^23..2^23-1 or 2^24) (promote) 0301 kdbType = KDbField::BigInteger; 0302 break; 0303 case FIELD_TYPE_DATE: // DATE 0304 kdbType = KDbField::Date; 0305 break; 0306 case FIELD_TYPE_TIME: // TIME 0307 kdbType = KDbField::Time; 0308 break; 0309 case FIELD_TYPE_DATETIME: // DATETIME 0310 kdbType = KDbField::DateTime; 0311 break; 0312 case FIELD_TYPE_YEAR: // YEAR (promote) 0313 kdbType = KDbField::ShortInteger; 0314 break; 0315 case FIELD_TYPE_NEWDATE: // WTF? 0316 case FIELD_TYPE_ENUM: // ENUM 0317 // If MySQL did what it's documentation said it did, we would come here 0318 // for enum fields ... 0319 kdbType = KDbField::Enum; 0320 break; 0321 case FIELD_TYPE_SET: // SET 0322 //! @todo: Support set column type 0323 break; 0324 case FIELD_TYPE_TINY_BLOB: 0325 case FIELD_TYPE_MEDIUM_BLOB: 0326 case FIELD_TYPE_LONG_BLOB: 0327 case FIELD_TYPE_BLOB: // BLOB or TEXT 0328 case FIELD_TYPE_VAR_STRING: // VARCHAR 0329 case FIELD_TYPE_STRING: // CHAR 0330 if (field->data->flags & ENUM_FLAG) { 0331 // ... instead we come here, using the ENUM_FLAG which is supposed to 0332 // be deprecated! Duh. 0333 kdbType = KDbField::Enum; 0334 } else { 0335 kdbType = blobType(tableName, field); 0336 } 0337 break; 0338 default: 0339 break; 0340 } 0341 return kdbType; 0342 } 0343 0344 inline void copyConstraints(int mysqlFieldFlags, KDbField* field) 0345 { 0346 field->setPrimaryKey(mysqlFieldFlags & PRI_KEY_FLAG); 0347 field->setAutoIncrement(mysqlFieldFlags & AUTO_INCREMENT_FLAG); 0348 field->setNotNull(mysqlFieldFlags & NOT_NULL_FLAG); 0349 field->setUniqueKey(mysqlFieldFlags & UNIQUE_KEY_FLAG); 0350 //! @todo: support keys 0351 } 0352 0353 static inline void copyOptions(int mysqlFieldFlags, KDbField* field) 0354 { 0355 field->setUnsigned(mysqlFieldFlags & UNSIGNED_FLAG); 0356 } 0357 0358 KDbField* MysqlSqlResult::createField(const QString &tableName, int index) 0359 { 0360 QScopedPointer<MysqlSqlField> f(static_cast<MysqlSqlField*>(field(index))); 0361 if (!f) { 0362 return nullptr; 0363 } 0364 const QString caption(f->name()); 0365 QString realFieldName(KDb::stringToIdentifier(caption.toLower())); 0366 KDbField *kdbField = new KDbField(realFieldName, type(tableName, f.data())); 0367 kdbField->setCaption(caption); 0368 const int flags = f->data->flags; 0369 copyConstraints(flags, kdbField); 0370 copyOptions(flags, kdbField); 0371 return kdbField; 0372 }