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 }