1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtSql module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qsql_mysql_p.h"
41 
42 #include <qcoreapplication.h>
43 #include <qvariant.h>
44 #include <qdatetime.h>
45 #include <qsqlerror.h>
46 #include <qsqlfield.h>
47 #include <qsqlindex.h>
48 #include <qsqlquery.h>
49 #include <qsqlrecord.h>
50 #include <qstringlist.h>
51 #if QT_CONFIG(textcodec)
52 #include <qtextcodec.h>
53 #endif
54 #include <qvector.h>
55 #include <qfile.h>
56 #include <qdebug.h>
57 #include <QtSql/private/qsqldriver_p.h>
58 #include <QtSql/private/qsqlresult_p.h>
59 
60 #ifdef Q_OS_WIN32
61 // comment the next line out if you want to use MySQL/embedded on Win32 systems.
62 // note that it will crash if you don't statically link to the mysql/e library!
63 # define Q_NO_MYSQL_EMBEDDED
64 #endif
65 
66 Q_DECLARE_METATYPE(MYSQL_RES*)
67 Q_DECLARE_METATYPE(MYSQL*)
68 Q_DECLARE_METATYPE(MYSQL_STMT*)
69 
70 // MySQL above version 8 removed my_bool typedef while MariaDB kept it,
71 // by redefining it we can regain source compatibility.
72 using my_bool = decltype(mysql_stmt_bind_result(nullptr, nullptr));
73 
74 QT_BEGIN_NAMESPACE
75 
76 class QMYSQLDriverPrivate : public QSqlDriverPrivate
77 {
78     Q_DECLARE_PUBLIC(QMYSQLDriver)
79 
80 public:
QMYSQLDriverPrivate()81     QMYSQLDriverPrivate() : QSqlDriverPrivate(QSqlDriver::MySqlServer)
82 #if QT_CONFIG(textcodec)
83         , tc(QTextCodec::codecForLocale())
84 #endif
85     {}
86     MYSQL *mysql = nullptr;
87     QTextCodec *tc = nullptr;
88     bool preparedQuerysEnabled = false;
89 };
90 
toUnicode(QTextCodec * tc,const char * str)91 static inline QString toUnicode(QTextCodec *tc, const char *str)
92 {
93 #if !QT_CONFIG(textcodec)
94     Q_UNUSED(tc);
95     return QString::fromLatin1(str);
96 #else
97     return tc->toUnicode(str);
98 #endif
99 }
100 
toUnicode(QTextCodec * tc,const char * str,int length)101 static inline QString toUnicode(QTextCodec *tc, const char *str, int length)
102 {
103 #if !QT_CONFIG(textcodec)
104     Q_UNUSED(tc);
105     return QString::fromLatin1(str, length);
106 #else
107     return tc->toUnicode(str, length);
108 #endif
109 }
110 
fromUnicode(QTextCodec * tc,const QString & str)111 static inline QByteArray fromUnicode(QTextCodec *tc, const QString &str)
112 {
113 #if !QT_CONFIG(textcodec)
114     Q_UNUSED(tc);
115     return str.toLatin1();
116 #else
117     return tc->fromUnicode(str);
118 #endif
119 }
120 
qDateFromString(const QString & val)121 static inline QVariant qDateFromString(const QString &val)
122 {
123 #if !QT_CONFIG(datestring)
124     Q_UNUSED(val);
125     return QVariant(val);
126 #else
127     if (val.isEmpty())
128         return QVariant(QDate());
129     return QVariant(QDate::fromString(val, Qt::ISODate));
130 #endif
131 }
132 
qTimeFromString(const QString & val)133 static inline QVariant qTimeFromString(const QString &val)
134 {
135 #if !QT_CONFIG(datestring)
136     Q_UNUSED(val);
137     return QVariant(val);
138 #else
139     if (val.isEmpty())
140         return QVariant(QTime());
141     return QVariant(QTime::fromString(val, Qt::ISODate));
142 #endif
143 }
144 
qDateTimeFromString(QString & val)145 static inline QVariant qDateTimeFromString(QString &val)
146 {
147 #if !QT_CONFIG(datestring)
148     Q_UNUSED(val);
149     return QVariant(val);
150 #else
151     if (val.isEmpty())
152         return QVariant(QDateTime());
153     if (val.length() == 14)
154         // TIMESTAMPS have the format yyyyMMddhhmmss
155         val.insert(4, QLatin1Char('-')).insert(7, QLatin1Char('-')).insert(10,
156                     QLatin1Char('T')).insert(13, QLatin1Char(':')).insert(16, QLatin1Char(':'));
157     return QVariant(QDateTime::fromString(val, Qt::ISODate));
158 #endif
159 }
160 
161 // check if this client and server version of MySQL/MariaDB support prepared statements
checkPreparedQueries(MYSQL * mysql)162 static inline bool checkPreparedQueries(MYSQL *mysql)
163 {
164     std::unique_ptr<MYSQL_STMT, decltype(&mysql_stmt_close)> stmt(mysql_stmt_init(mysql), &mysql_stmt_close);
165     if (!stmt)
166         return false;
167 
168     static const char dummyQuery[] = "SELECT ? + ?";
169     if (mysql_stmt_prepare(stmt.get(), dummyQuery, sizeof(dummyQuery) - 1))
170         return false;
171 
172     return mysql_stmt_param_count(stmt.get()) == 2;
173 }
174 
175 class QMYSQLResultPrivate;
176 
177 class QMYSQLResult : public QSqlResult
178 {
179     Q_DECLARE_PRIVATE(QMYSQLResult)
180     friend class QMYSQLDriver;
181 
182 public:
183     explicit QMYSQLResult(const QMYSQLDriver *db);
184     ~QMYSQLResult();
185 
186     QVariant handle() const override;
187 protected:
188     void cleanup();
189     bool fetch(int i) override;
190     bool fetchNext() override;
191     bool fetchLast() override;
192     bool fetchFirst() override;
193     QVariant data(int field) override;
194     bool isNull(int field) override;
195     bool reset (const QString& query) override;
196     int size() override;
197     int numRowsAffected() override;
198     QVariant lastInsertId() const override;
199     QSqlRecord record() const override;
200     void virtual_hook(int id, void *data) override;
201     bool nextResult() override;
202     void detachFromResultSet() override;
203 
204     bool prepare(const QString &stmt) override;
205     bool exec() override;
206 };
207 
208 class QMYSQLResultPrivate: public QSqlResultPrivate
209 {
210     Q_DECLARE_PUBLIC(QMYSQLResult)
211 
212 public:
213     Q_DECLARE_SQLDRIVER_PRIVATE(QMYSQLDriver)
214 
215     using QSqlResultPrivate::QSqlResultPrivate;
216 
217     bool bindInValues();
218     void bindBlobs();
219 
220     MYSQL_RES *result = nullptr;
221     MYSQL_ROW row;
222 
223     struct QMyField
224     {
225         char *outField = nullptr;
226         const MYSQL_FIELD *myField = nullptr;
227         QMetaType::Type type = QMetaType::UnknownType;
228         my_bool nullIndicator = false;
229         ulong bufLength = 0ul;
230     };
231 
232     QVector<QMyField> fields;
233 
234     MYSQL_STMT *stmt = nullptr;
235     MYSQL_RES *meta = nullptr;
236 
237     MYSQL_BIND *inBinds = nullptr;
238     MYSQL_BIND *outBinds = nullptr;
239 
240     int rowsAffected = 0;
241     bool hasBlobs = false;
242     bool preparedQuery = false;
243 };
244 
245 #if QT_CONFIG(textcodec)
codec(MYSQL * mysql)246 static QTextCodec* codec(MYSQL* mysql)
247 {
248     QTextCodec* heuristicCodec = QTextCodec::codecForName(mysql_character_set_name(mysql));
249     if (heuristicCodec)
250         return heuristicCodec;
251     return QTextCodec::codecForLocale();
252 }
253 #endif // textcodec
254 
qMakeError(const QString & err,QSqlError::ErrorType type,const QMYSQLDriverPrivate * p)255 static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
256                             const QMYSQLDriverPrivate* p)
257 {
258     const char *cerr = p->mysql ? mysql_error(p->mysql) : 0;
259     return QSqlError(QLatin1String("QMYSQL: ") + err,
260                      p->tc ? toUnicode(p->tc, cerr) : QString::fromLatin1(cerr),
261                      type, QString::number(mysql_errno(p->mysql)));
262 }
263 
264 
qDecodeMYSQLType(int mysqltype,uint flags)265 static QMetaType::Type qDecodeMYSQLType(int mysqltype, uint flags)
266 {
267     QMetaType::Type type;
268     switch (mysqltype) {
269     case FIELD_TYPE_TINY :
270         type = (flags & UNSIGNED_FLAG) ? QMetaType::UChar : QMetaType::Char;
271         break;
272     case FIELD_TYPE_SHORT :
273         type = (flags & UNSIGNED_FLAG) ? QMetaType::UShort : QMetaType::Short;
274         break;
275     case FIELD_TYPE_LONG :
276     case FIELD_TYPE_INT24 :
277         type = (flags & UNSIGNED_FLAG) ? QMetaType::UInt : QMetaType::Int;
278         break;
279     case FIELD_TYPE_YEAR :
280         type = QMetaType::Int;
281         break;
282     case FIELD_TYPE_LONGLONG :
283         type = (flags & UNSIGNED_FLAG) ? QMetaType::ULongLong : QMetaType::LongLong;
284         break;
285     case FIELD_TYPE_FLOAT :
286     case FIELD_TYPE_DOUBLE :
287     case FIELD_TYPE_DECIMAL :
288 #if defined(FIELD_TYPE_NEWDECIMAL)
289     case FIELD_TYPE_NEWDECIMAL:
290 #endif
291         type = QMetaType::Double;
292         break;
293     case FIELD_TYPE_DATE :
294         type = QMetaType::QDate;
295         break;
296     case FIELD_TYPE_TIME :
297         // A time field can be within the range '-838:59:59' to '838:59:59' so
298         // use QString instead of QTime since QTime is limited to 24 hour clock
299         type = QMetaType::QString;
300         break;
301     case FIELD_TYPE_DATETIME :
302     case FIELD_TYPE_TIMESTAMP :
303         type = QMetaType::QDateTime;
304         break;
305     case FIELD_TYPE_STRING :
306     case FIELD_TYPE_VAR_STRING :
307     case FIELD_TYPE_BLOB :
308     case FIELD_TYPE_TINY_BLOB :
309     case FIELD_TYPE_MEDIUM_BLOB :
310     case FIELD_TYPE_LONG_BLOB :
311     case FIELD_TYPE_GEOMETRY :
312         type = (flags & BINARY_FLAG) ? QMetaType::QByteArray : QMetaType::QString;
313         break;
314     default:
315     case FIELD_TYPE_ENUM :
316     case FIELD_TYPE_SET :
317         type = QMetaType::QString;
318         break;
319     }
320     return type;
321 }
322 
qToField(MYSQL_FIELD * field,QTextCodec * tc)323 static QSqlField qToField(MYSQL_FIELD *field, QTextCodec *tc)
324 {
325     QSqlField f(toUnicode(tc, field->name),
326                 QVariant::Type(qDecodeMYSQLType(int(field->type), field->flags)),
327                 toUnicode(tc, field->table));
328     f.setRequired(IS_NOT_NULL(field->flags));
329     f.setLength(field->length);
330     f.setPrecision(field->decimals);
331     f.setSqlType(field->type);
332     f.setAutoValue(field->flags & AUTO_INCREMENT_FLAG);
333     return f;
334 }
335 
qMakeStmtError(const QString & err,QSqlError::ErrorType type,MYSQL_STMT * stmt)336 static QSqlError qMakeStmtError(const QString& err, QSqlError::ErrorType type,
337                             MYSQL_STMT* stmt)
338 {
339     const char *cerr = mysql_stmt_error(stmt);
340     return QSqlError(QLatin1String("QMYSQL3: ") + err,
341                      QString::fromLatin1(cerr),
342                      type, QString::number(mysql_stmt_errno(stmt)));
343 }
344 
qIsBlob(int t)345 static bool qIsBlob(int t)
346 {
347     return t == MYSQL_TYPE_TINY_BLOB
348            || t == MYSQL_TYPE_BLOB
349            || t == MYSQL_TYPE_MEDIUM_BLOB
350            || t == MYSQL_TYPE_LONG_BLOB;
351 }
352 
qIsInteger(int t)353 static bool qIsInteger(int t)
354 {
355     return t == QMetaType::Char || t == QMetaType::UChar
356         || t == QMetaType::Short || t == QMetaType::UShort
357         || t == QMetaType::Int || t == QMetaType::UInt
358         || t == QMetaType::LongLong || t == QMetaType::ULongLong;
359 }
360 
bindBlobs()361 void QMYSQLResultPrivate::bindBlobs()
362 {
363     int i;
364     const MYSQL_FIELD *fieldInfo;
365     MYSQL_BIND *bind;
366 
367     for(i = 0; i < fields.count(); ++i) {
368         fieldInfo = fields.at(i).myField;
369         if (qIsBlob(inBinds[i].buffer_type) && meta && fieldInfo && fieldInfo->max_length) {
370             bind = &inBinds[i];
371             bind->buffer_length = fieldInfo->max_length;
372             delete[] static_cast<char*>(bind->buffer);
373             bind->buffer = new char[bind->buffer_length];
374             fields[i].outField = static_cast<char*>(bind->buffer);
375         }
376     }
377 }
378 
bindInValues()379 bool QMYSQLResultPrivate::bindInValues()
380 {
381     MYSQL_BIND *bind;
382     int i = 0;
383 
384     if (!meta)
385         meta = mysql_stmt_result_metadata(stmt);
386     if (!meta)
387         return false;
388 
389     fields.resize(mysql_num_fields(meta));
390 
391     inBinds = new MYSQL_BIND[fields.size()];
392     memset(inBinds, 0, fields.size() * sizeof(MYSQL_BIND));
393 
394     const MYSQL_FIELD *fieldInfo;
395 
396     while((fieldInfo = mysql_fetch_field(meta))) {
397         bind = &inBinds[i];
398 
399         QMyField &f = fields[i];
400         f.myField = fieldInfo;
401         bind->buffer_length = f.bufLength = fieldInfo->length + 1;
402         bind->buffer_type = fieldInfo->type;
403         f.type = qDecodeMYSQLType(fieldInfo->type, fieldInfo->flags);
404         if (qIsBlob(fieldInfo->type)) {
405             // the size of a blob-field is available as soon as we call
406             // mysql_stmt_store_result()
407             // after mysql_stmt_exec() in QMYSQLResult::exec()
408             bind->buffer_length = f.bufLength = 0;
409             hasBlobs = true;
410         } else if (qIsInteger(f.type)) {
411             bind->buffer_length = f.bufLength = 8;
412         } else {
413             bind->buffer_type = MYSQL_TYPE_STRING;
414         }
415 
416         bind->is_null = &f.nullIndicator;
417         bind->length = &f.bufLength;
418         bind->is_unsigned = fieldInfo->flags & UNSIGNED_FLAG ? 1 : 0;
419 
420         char *field = new char[bind->buffer_length + 1]{};
421         bind->buffer = f.outField = field;
422 
423         ++i;
424     }
425     return true;
426 }
427 
QMYSQLResult(const QMYSQLDriver * db)428 QMYSQLResult::QMYSQLResult(const QMYSQLDriver* db)
429     : QSqlResult(*new QMYSQLResultPrivate(this, db))
430 {
431 }
432 
~QMYSQLResult()433 QMYSQLResult::~QMYSQLResult()
434 {
435     cleanup();
436 }
437 
handle() const438 QVariant QMYSQLResult::handle() const
439 {
440     Q_D(const QMYSQLResult);
441     if(d->preparedQuery)
442         return d->meta ? QVariant::fromValue(d->meta) : QVariant::fromValue(d->stmt);
443     else
444         return QVariant::fromValue(d->result);
445 }
446 
cleanup()447 void QMYSQLResult::cleanup()
448 {
449     Q_D(QMYSQLResult);
450     if (d->result)
451         mysql_free_result(d->result);
452 
453 // must iterate trough leftover result sets from multi-selects or stored procedures
454 // if this isn't done subsequent queries will fail with "Commands out of sync"
455     while (driver() && d->drv_d_func()->mysql && mysql_next_result(d->drv_d_func()->mysql) == 0) {
456         MYSQL_RES *res = mysql_store_result(d->drv_d_func()->mysql);
457         if (res)
458             mysql_free_result(res);
459     }
460 
461     if (d->stmt) {
462         if (mysql_stmt_close(d->stmt))
463             qWarning("QMYSQLResult::cleanup: unable to free statement handle");
464         d->stmt = 0;
465     }
466 
467     if (d->meta) {
468         mysql_free_result(d->meta);
469         d->meta = 0;
470     }
471 
472     int i;
473     for (i = 0; i < d->fields.count(); ++i)
474         delete[] d->fields[i].outField;
475 
476     if (d->outBinds) {
477         delete[] d->outBinds;
478         d->outBinds = 0;
479     }
480 
481     if (d->inBinds) {
482         delete[] d->inBinds;
483         d->inBinds = 0;
484     }
485 
486     d->hasBlobs = false;
487     d->fields.clear();
488     d->result = NULL;
489     d->row = NULL;
490     setAt(-1);
491     setActive(false);
492 }
493 
fetch(int i)494 bool QMYSQLResult::fetch(int i)
495 {
496     Q_D(QMYSQLResult);
497     if (!driver())
498         return false;
499     if (isForwardOnly()) { // fake a forward seek
500         if (at() < i) {
501             int x = i - at();
502             while (--x && fetchNext()) {};
503             return fetchNext();
504         } else {
505             return false;
506         }
507     }
508     if (at() == i)
509         return true;
510     if (d->preparedQuery) {
511         mysql_stmt_data_seek(d->stmt, i);
512 
513         int nRC = mysql_stmt_fetch(d->stmt);
514         if (nRC) {
515 #ifdef MYSQL_DATA_TRUNCATED
516             if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED)
517 #else
518             if (nRC == 1)
519 #endif
520                 setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
521                          "Unable to fetch data"), QSqlError::StatementError, d->stmt));
522             return false;
523         }
524     } else {
525         mysql_data_seek(d->result, i);
526         d->row = mysql_fetch_row(d->result);
527         if (!d->row)
528             return false;
529     }
530 
531     setAt(i);
532     return true;
533 }
534 
fetchNext()535 bool QMYSQLResult::fetchNext()
536 {
537     Q_D(QMYSQLResult);
538     if (!driver())
539         return false;
540     if (d->preparedQuery) {
541         int nRC = mysql_stmt_fetch(d->stmt);
542         if (nRC) {
543 #ifdef MYSQL_DATA_TRUNCATED
544             if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED)
545 #else
546             if (nRC == 1)
547 #endif // MYSQL_DATA_TRUNCATED
548                 setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
549                                     "Unable to fetch data"), QSqlError::StatementError, d->stmt));
550             return false;
551         }
552     } else {
553         d->row = mysql_fetch_row(d->result);
554         if (!d->row)
555             return false;
556     }
557     setAt(at() + 1);
558     return true;
559 }
560 
fetchLast()561 bool QMYSQLResult::fetchLast()
562 {
563     Q_D(QMYSQLResult);
564     if (!driver())
565         return false;
566     if (isForwardOnly()) { // fake this since MySQL can't seek on forward only queries
567         bool success = fetchNext(); // did we move at all?
568         while (fetchNext()) {};
569         return success;
570     }
571 
572     my_ulonglong numRows;
573     if (d->preparedQuery) {
574         numRows = mysql_stmt_num_rows(d->stmt);
575     } else {
576         numRows = mysql_num_rows(d->result);
577     }
578     if (at() == int(numRows))
579         return true;
580     if (!numRows)
581         return false;
582     return fetch(numRows - 1);
583 }
584 
fetchFirst()585 bool QMYSQLResult::fetchFirst()
586 {
587     if (at() == 0)
588         return true;
589 
590     if (isForwardOnly())
591         return (at() == QSql::BeforeFirstRow) ? fetchNext() : false;
592     return fetch(0);
593 }
594 
data(int field)595 QVariant QMYSQLResult::data(int field)
596 {
597     Q_D(QMYSQLResult);
598     if (!isSelect() || field >= d->fields.count()) {
599         qWarning("QMYSQLResult::data: column %d out of range", field);
600         return QVariant();
601     }
602 
603     if (!driver())
604         return QVariant();
605 
606     int fieldLength = 0;
607     const QMYSQLResultPrivate::QMyField &f = d->fields.at(field);
608     QString val;
609     if (d->preparedQuery) {
610         if (f.nullIndicator)
611             return QVariant(QVariant::Type(f.type));
612 
613         if (qIsInteger(f.type)) {
614             QVariant variant(f.type, f.outField);
615             // we never want to return char variants here, see QTBUG-53397
616             if (static_cast<int>(f.type) == QMetaType::UChar)
617                 return variant.toUInt();
618             else if (static_cast<int>(f.type) == QMetaType::Char)
619                 return variant.toInt();
620             return variant;
621         }
622 
623         if (f.type != QMetaType::QByteArray)
624             val = toUnicode(d->drv_d_func()->tc, f.outField, f.bufLength);
625     } else {
626         if (d->row[field] == NULL) {
627             // NULL value
628             return QVariant(QVariant::Type(f.type));
629         }
630 
631         fieldLength = mysql_fetch_lengths(d->result)[field];
632 
633         if (f.type != QMetaType::QByteArray)
634             val = toUnicode(d->drv_d_func()->tc, d->row[field], fieldLength);
635     }
636 
637     switch (static_cast<int>(f.type)) {
638     case QMetaType::LongLong:
639         return QVariant(val.toLongLong());
640     case QMetaType::ULongLong:
641         return QVariant(val.toULongLong());
642     case QMetaType::Char:
643     case QMetaType::Short:
644     case QMetaType::Int:
645         return QVariant(val.toInt());
646     case QMetaType::UChar:
647     case QMetaType::UShort:
648     case QMetaType::UInt:
649         return QVariant(val.toUInt());
650     case QMetaType::Double: {
651         QVariant v;
652         bool ok=false;
653         double dbl = val.toDouble(&ok);
654         switch(numericalPrecisionPolicy()) {
655             case QSql::LowPrecisionInt32:
656                 v=QVariant(dbl).toInt();
657                 break;
658             case QSql::LowPrecisionInt64:
659                 v = QVariant(dbl).toLongLong();
660                 break;
661             case QSql::LowPrecisionDouble:
662                 v = QVariant(dbl);
663                 break;
664             case QSql::HighPrecision:
665             default:
666                 v = val;
667                 ok = true;
668                 break;
669         }
670         if(ok)
671             return v;
672         return QVariant();
673     }
674     case QMetaType::QDate:
675         return qDateFromString(val);
676     case QMetaType::QTime:
677         return qTimeFromString(val);
678     case QMetaType::QDateTime:
679         return qDateTimeFromString(val);
680     case QMetaType::QByteArray: {
681 
682         QByteArray ba;
683         if (d->preparedQuery) {
684             ba = QByteArray(f.outField, f.bufLength);
685         } else {
686             ba = QByteArray(d->row[field], fieldLength);
687         }
688         return QVariant(ba);
689     }
690     case QMetaType::QString:
691     default:
692         return QVariant(val);
693     }
694     Q_UNREACHABLE();
695 }
696 
isNull(int field)697 bool QMYSQLResult::isNull(int field)
698 {
699    Q_D(const QMYSQLResult);
700    if (field < 0 || field >= d->fields.count())
701        return true;
702    if (d->preparedQuery)
703        return d->fields.at(field).nullIndicator;
704    else
705        return d->row[field] == NULL;
706 }
707 
reset(const QString & query)708 bool QMYSQLResult::reset (const QString& query)
709 {
710     Q_D(QMYSQLResult);
711     if (!driver() || !driver()->isOpen() || driver()->isOpenError())
712         return false;
713 
714     d->preparedQuery = false;
715 
716     cleanup();
717 
718     const QByteArray encQuery(fromUnicode(d->drv_d_func()->tc, query));
719     if (mysql_real_query(d->drv_d_func()->mysql, encQuery.data(), encQuery.length())) {
720         setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute query"),
721                      QSqlError::StatementError, d->drv_d_func()));
722         return false;
723     }
724     d->result = mysql_store_result(d->drv_d_func()->mysql);
725     if (!d->result && mysql_field_count(d->drv_d_func()->mysql) > 0) {
726         setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store result"),
727                     QSqlError::StatementError, d->drv_d_func()));
728         return false;
729     }
730     int numFields = mysql_field_count(d->drv_d_func()->mysql);
731     setSelect(numFields != 0);
732     d->fields.resize(numFields);
733     d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql);
734 
735     if (isSelect()) {
736         for(int i = 0; i < numFields; i++) {
737             MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i);
738             d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
739         }
740         setAt(QSql::BeforeFirstRow);
741     }
742     setActive(true);
743     return isActive();
744 }
745 
size()746 int QMYSQLResult::size()
747 {
748     Q_D(const QMYSQLResult);
749     if (driver() && isSelect())
750         if (d->preparedQuery)
751             return mysql_stmt_num_rows(d->stmt);
752         else
753             return int(mysql_num_rows(d->result));
754     else
755         return -1;
756 }
757 
numRowsAffected()758 int QMYSQLResult::numRowsAffected()
759 {
760     Q_D(const QMYSQLResult);
761     return d->rowsAffected;
762 }
763 
detachFromResultSet()764 void QMYSQLResult::detachFromResultSet()
765 {
766     Q_D(QMYSQLResult);
767 
768     if (d->preparedQuery) {
769         mysql_stmt_free_result(d->stmt);
770     }
771 }
772 
lastInsertId() const773 QVariant QMYSQLResult::lastInsertId() const
774 {
775     Q_D(const QMYSQLResult);
776     if (!isActive() || !driver())
777         return QVariant();
778 
779     if (d->preparedQuery) {
780         quint64 id = mysql_stmt_insert_id(d->stmt);
781         if (id)
782             return QVariant(id);
783     } else {
784         quint64 id = mysql_insert_id(d->drv_d_func()->mysql);
785         if (id)
786             return QVariant(id);
787     }
788     return QVariant();
789 }
790 
record() const791 QSqlRecord QMYSQLResult::record() const
792 {
793     Q_D(const QMYSQLResult);
794     QSqlRecord info;
795     MYSQL_RES *res;
796     if (!isActive() || !isSelect() || !driver())
797         return info;
798 
799     res = d->preparedQuery ? d->meta : d->result;
800 
801     if (!mysql_errno(d->drv_d_func()->mysql)) {
802         mysql_field_seek(res, 0);
803         MYSQL_FIELD* field = mysql_fetch_field(res);
804         while(field) {
805             info.append(qToField(field, d->drv_d_func()->tc));
806             field = mysql_fetch_field(res);
807         }
808     }
809     mysql_field_seek(res, 0);
810     return info;
811 }
812 
nextResult()813 bool QMYSQLResult::nextResult()
814 {
815     Q_D(QMYSQLResult);
816     if (!driver())
817         return false;
818 
819     setAt(-1);
820     setActive(false);
821 
822     if (d->result && isSelect())
823         mysql_free_result(d->result);
824     d->result = 0;
825     setSelect(false);
826 
827     for (int i = 0; i < d->fields.count(); ++i)
828         delete[] d->fields[i].outField;
829     d->fields.clear();
830 
831     int status = mysql_next_result(d->drv_d_func()->mysql);
832     if (status > 0) {
833         setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute next query"),
834                      QSqlError::StatementError, d->drv_d_func()));
835         return false;
836     } else if (status == -1) {
837         return false;   // No more result sets
838     }
839 
840     d->result = mysql_store_result(d->drv_d_func()->mysql);
841     int numFields = mysql_field_count(d->drv_d_func()->mysql);
842     if (!d->result && numFields > 0) {
843         setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store next result"),
844                      QSqlError::StatementError, d->drv_d_func()));
845         return false;
846     }
847 
848     setSelect(numFields > 0);
849     d->fields.resize(numFields);
850     d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql);
851 
852     if (isSelect()) {
853         for (int i = 0; i < numFields; i++) {
854             MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i);
855             d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
856         }
857     }
858 
859     setActive(true);
860     return true;
861 }
862 
virtual_hook(int id,void * data)863 void QMYSQLResult::virtual_hook(int id, void *data)
864 {
865     QSqlResult::virtual_hook(id, data);
866 }
867 
toMySqlDate(QDate date,QTime time,int type)868 static MYSQL_TIME *toMySqlDate(QDate date, QTime time, int type)
869 {
870     Q_ASSERT(type == QMetaType::QTime || type == QMetaType::QDate
871              || type == QMetaType::QDateTime);
872 
873     MYSQL_TIME *myTime = new MYSQL_TIME;
874     memset(myTime, 0, sizeof(MYSQL_TIME));
875 
876     if (type == QMetaType::QTime || type == QMetaType::QDateTime) {
877         myTime->hour = time.hour();
878         myTime->minute = time.minute();
879         myTime->second = time.second();
880         myTime->second_part = time.msec() * 1000;
881     }
882     if (type == QMetaType::QDate || type == QMetaType::QDateTime) {
883         myTime->year = date.year();
884         myTime->month = date.month();
885         myTime->day = date.day();
886     }
887 
888     return myTime;
889 }
890 
prepare(const QString & query)891 bool QMYSQLResult::prepare(const QString& query)
892 {
893     Q_D(QMYSQLResult);
894     if (!driver())
895         return false;
896 
897     cleanup();
898     if (!d->drv_d_func()->preparedQuerysEnabled)
899         return QSqlResult::prepare(query);
900 
901     int r;
902 
903     if (query.isEmpty())
904         return false;
905 
906     if (!d->stmt)
907         d->stmt = mysql_stmt_init(d->drv_d_func()->mysql);
908     if (!d->stmt) {
909         setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to prepare statement"),
910                      QSqlError::StatementError, d->drv_d_func()));
911         return false;
912     }
913 
914     const QByteArray encQuery(fromUnicode(d->drv_d_func()->tc, query));
915     r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.length());
916     if (r != 0) {
917         setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
918                      "Unable to prepare statement"), QSqlError::StatementError, d->stmt));
919         cleanup();
920         return false;
921     }
922 
923     if (mysql_stmt_param_count(d->stmt) > 0) {// allocate memory for outvalues
924         d->outBinds = new MYSQL_BIND[mysql_stmt_param_count(d->stmt)];
925     }
926 
927     setSelect(d->bindInValues());
928     d->preparedQuery = true;
929     return true;
930 }
931 
exec()932 bool QMYSQLResult::exec()
933 {
934     Q_D(QMYSQLResult);
935     if (!driver())
936         return false;
937     if (!d->preparedQuery)
938         return QSqlResult::exec();
939     if (!d->stmt)
940         return false;
941 
942     int r = 0;
943     MYSQL_BIND* currBind;
944     QVector<MYSQL_TIME *> timeVector;
945     QVector<QByteArray> stringVector;
946     QVector<my_bool> nullVector;
947 
948     const QVector<QVariant> values = boundValues();
949 
950     r = mysql_stmt_reset(d->stmt);
951     if (r != 0) {
952         setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
953                      "Unable to reset statement"), QSqlError::StatementError, d->stmt));
954         return false;
955     }
956 
957     if (mysql_stmt_param_count(d->stmt) > 0 &&
958         mysql_stmt_param_count(d->stmt) == (uint)values.count()) {
959 
960         nullVector.resize(values.count());
961         for (int i = 0; i < values.count(); ++i) {
962             const QVariant &val = boundValues().at(i);
963             void *data = const_cast<void *>(val.constData());
964 
965             currBind = &d->outBinds[i];
966 
967             nullVector[i] = static_cast<my_bool>(val.isNull());
968             currBind->is_null = &nullVector[i];
969             currBind->length = 0;
970             currBind->is_unsigned = 0;
971 
972             switch (val.userType()) {
973                 case QMetaType::QByteArray:
974                     currBind->buffer_type = MYSQL_TYPE_BLOB;
975                     currBind->buffer = const_cast<char *>(val.toByteArray().constData());
976                     currBind->buffer_length = val.toByteArray().size();
977                     break;
978 
979                 case QMetaType::QTime:
980                 case QMetaType::QDate:
981                 case QMetaType::QDateTime: {
982                     MYSQL_TIME *myTime = toMySqlDate(val.toDate(), val.toTime(), val.userType());
983                     timeVector.append(myTime);
984 
985                     currBind->buffer = myTime;
986                     switch (val.userType()) {
987                     case QMetaType::QTime:
988                         currBind->buffer_type = MYSQL_TYPE_TIME;
989                         myTime->time_type = MYSQL_TIMESTAMP_TIME;
990                         break;
991                     case QMetaType::QDate:
992                         currBind->buffer_type = MYSQL_TYPE_DATE;
993                         myTime->time_type = MYSQL_TIMESTAMP_DATE;
994                         break;
995                     case QMetaType::QDateTime:
996                         currBind->buffer_type = MYSQL_TYPE_DATETIME;
997                         myTime->time_type = MYSQL_TIMESTAMP_DATETIME;
998                         break;
999                     default:
1000                         break;
1001                     }
1002                     currBind->buffer_length = sizeof(MYSQL_TIME);
1003                     currBind->length = 0;
1004                     break; }
1005                 case QMetaType::UInt:
1006                 case QMetaType::Int:
1007                     currBind->buffer_type = MYSQL_TYPE_LONG;
1008                     currBind->buffer = data;
1009                     currBind->buffer_length = sizeof(int);
1010                     currBind->is_unsigned = (val.userType() != QMetaType::Int);
1011                 break;
1012                 case QMetaType::Bool:
1013                     currBind->buffer_type = MYSQL_TYPE_TINY;
1014                     currBind->buffer = data;
1015                     currBind->buffer_length = sizeof(bool);
1016                     currBind->is_unsigned = false;
1017                     break;
1018                 case QMetaType::Double:
1019                     currBind->buffer_type = MYSQL_TYPE_DOUBLE;
1020                     currBind->buffer = data;
1021                     currBind->buffer_length = sizeof(double);
1022                     break;
1023                 case QMetaType::LongLong:
1024                 case QMetaType::ULongLong:
1025                     currBind->buffer_type = MYSQL_TYPE_LONGLONG;
1026                     currBind->buffer = data;
1027                     currBind->buffer_length = sizeof(qint64);
1028                     currBind->is_unsigned = (val.userType() == QMetaType::ULongLong);
1029                     break;
1030                 case QMetaType::QString:
1031                 default: {
1032                     QByteArray ba = fromUnicode(d->drv_d_func()->tc, val.toString());
1033                     stringVector.append(ba);
1034                     currBind->buffer_type = MYSQL_TYPE_STRING;
1035                     currBind->buffer = const_cast<char *>(ba.constData());
1036                     currBind->buffer_length = ba.length();
1037                     break; }
1038             }
1039         }
1040 
1041         r = mysql_stmt_bind_param(d->stmt, d->outBinds);
1042         if (r != 0) {
1043             setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
1044                          "Unable to bind value"), QSqlError::StatementError, d->stmt));
1045             qDeleteAll(timeVector);
1046             return false;
1047         }
1048     }
1049     r = mysql_stmt_execute(d->stmt);
1050 
1051     qDeleteAll(timeVector);
1052 
1053     if (r != 0) {
1054         setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
1055                      "Unable to execute statement"), QSqlError::StatementError, d->stmt));
1056         return false;
1057     }
1058     //if there is meta-data there is also data
1059     setSelect(d->meta);
1060 
1061     d->rowsAffected = mysql_stmt_affected_rows(d->stmt);
1062 
1063     if (isSelect()) {
1064         my_bool update_max_length = true;
1065 
1066         r = mysql_stmt_bind_result(d->stmt, d->inBinds);
1067         if (r != 0) {
1068             setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
1069                          "Unable to bind outvalues"), QSqlError::StatementError, d->stmt));
1070             return false;
1071         }
1072         if (d->hasBlobs)
1073             mysql_stmt_attr_set(d->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &update_max_length);
1074 
1075         r = mysql_stmt_store_result(d->stmt);
1076         if (r != 0) {
1077             setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
1078                          "Unable to store statement results"), QSqlError::StatementError, d->stmt));
1079             return false;
1080         }
1081 
1082         if (d->hasBlobs) {
1083             // mysql_stmt_store_result() with STMT_ATTR_UPDATE_MAX_LENGTH set to true crashes
1084             // when called without a preceding call to mysql_stmt_bind_result()
1085             // in versions < 4.1.8
1086             d->bindBlobs();
1087             r = mysql_stmt_bind_result(d->stmt, d->inBinds);
1088             if (r != 0) {
1089                 setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
1090                              "Unable to bind outvalues"), QSqlError::StatementError, d->stmt));
1091                 return false;
1092             }
1093         }
1094         setAt(QSql::BeforeFirstRow);
1095     }
1096     setActive(true);
1097     return true;
1098 }
1099 
1100 /////////////////////////////////////////////////////////
1101 
1102 static int qMySqlConnectionCount = 0;
1103 static bool qMySqlInitHandledByUser = false;
1104 
qLibraryInit()1105 static void qLibraryInit()
1106 {
1107 #ifndef Q_NO_MYSQL_EMBEDDED
1108     if (qMySqlInitHandledByUser || qMySqlConnectionCount > 1)
1109         return;
1110 
1111 # if MYSQL_VERSION_ID >= 50003
1112     if (mysql_library_init(0, 0, 0)) {
1113 # else
1114     if (mysql_server_init(0, 0, 0)) {
1115 # endif
1116         qWarning("QMYSQLDriver::qServerInit: unable to start server.");
1117     }
1118 #endif // Q_NO_MYSQL_EMBEDDED
1119 
1120 #if defined(MARIADB_BASE_VERSION) || defined(MARIADB_VERSION_ID)
1121     qAddPostRoutine([]() { mysql_server_end(); });
1122 #endif
1123 }
1124 
1125 static void qLibraryEnd()
1126 {
1127 #if !defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID)
1128 # if !defined(Q_NO_MYSQL_EMBEDDED)
1129 #  if MYSQL_VERSION_ID >= 50003
1130     mysql_library_end();
1131 #  else
1132     mysql_server_end();
1133 #  endif
1134 # endif
1135 #endif
1136 }
1137 
1138 QMYSQLDriver::QMYSQLDriver(QObject * parent)
1139     : QSqlDriver(*new QMYSQLDriverPrivate, parent)
1140 {
1141     init();
1142     qLibraryInit();
1143 }
1144 
1145 /*!
1146     Create a driver instance with the open connection handle, \a con.
1147     The instance's parent (owner) is \a parent.
1148 */
1149 
1150 QMYSQLDriver::QMYSQLDriver(MYSQL * con, QObject * parent)
1151     : QSqlDriver(*new QMYSQLDriverPrivate, parent)
1152 {
1153     Q_D(QMYSQLDriver);
1154     init();
1155     if (con) {
1156         d->mysql = (MYSQL *) con;
1157 #if QT_CONFIG(textcodec)
1158         d->tc = codec(con);
1159 #endif
1160         setOpen(true);
1161         setOpenError(false);
1162         if (qMySqlConnectionCount == 1)
1163             qMySqlInitHandledByUser = true;
1164     } else {
1165         qLibraryInit();
1166     }
1167 }
1168 
1169 void QMYSQLDriver::init()
1170 {
1171     Q_D(QMYSQLDriver);
1172     d->mysql = 0;
1173     qMySqlConnectionCount++;
1174 }
1175 
1176 QMYSQLDriver::~QMYSQLDriver()
1177 {
1178     qMySqlConnectionCount--;
1179     if (qMySqlConnectionCount == 0 && !qMySqlInitHandledByUser)
1180         qLibraryEnd();
1181 }
1182 
1183 bool QMYSQLDriver::hasFeature(DriverFeature f) const
1184 {
1185     Q_D(const QMYSQLDriver);
1186     switch (f) {
1187     case Transactions:
1188 // CLIENT_TRANSACTION should be defined in all recent mysql client libs > 3.23.34
1189 #ifdef CLIENT_TRANSACTIONS
1190         if (d->mysql) {
1191             if ((d->mysql->server_capabilities & CLIENT_TRANSACTIONS) == CLIENT_TRANSACTIONS)
1192                 return true;
1193         }
1194 #endif
1195         return false;
1196     case NamedPlaceholders:
1197     case BatchOperations:
1198     case SimpleLocking:
1199     case EventNotifications:
1200     case FinishQuery:
1201     case CancelQuery:
1202         return false;
1203     case QuerySize:
1204     case BLOB:
1205     case LastInsertId:
1206     case Unicode:
1207     case LowPrecisionNumbers:
1208         return true;
1209     case PreparedQueries:
1210     case PositionalPlaceholders:
1211         return d->preparedQuerysEnabled;
1212     case MultipleResultSets:
1213         return true;
1214     }
1215     return false;
1216 }
1217 
1218 static void setOptionFlag(uint &optionFlags, const QString &opt)
1219 {
1220     if (opt == QLatin1String("CLIENT_COMPRESS"))
1221         optionFlags |= CLIENT_COMPRESS;
1222     else if (opt == QLatin1String("CLIENT_FOUND_ROWS"))
1223         optionFlags |= CLIENT_FOUND_ROWS;
1224     else if (opt == QLatin1String("CLIENT_IGNORE_SPACE"))
1225         optionFlags |= CLIENT_IGNORE_SPACE;
1226     else if (opt == QLatin1String("CLIENT_INTERACTIVE"))
1227         optionFlags |= CLIENT_INTERACTIVE;
1228     else if (opt == QLatin1String("CLIENT_NO_SCHEMA"))
1229         optionFlags |= CLIENT_NO_SCHEMA;
1230     else if (opt == QLatin1String("CLIENT_ODBC"))
1231         optionFlags |= CLIENT_ODBC;
1232     else if (opt == QLatin1String("CLIENT_SSL"))
1233         qWarning("QMYSQLDriver: SSL_KEY, SSL_CERT and SSL_CA should be used instead of CLIENT_SSL.");
1234     else
1235         qWarning("QMYSQLDriver::open: Unknown connect option '%s'", opt.toLocal8Bit().constData());
1236 }
1237 
1238 bool QMYSQLDriver::open(const QString& db,
1239                          const QString& user,
1240                          const QString& password,
1241                          const QString& host,
1242                          int port,
1243                          const QString& connOpts)
1244 {
1245     Q_D(QMYSQLDriver);
1246     if (isOpen())
1247         close();
1248 
1249     /* This is a hack to get MySQL's stored procedure support working.
1250        Since a stored procedure _may_ return multiple result sets,
1251        we have to enable CLIEN_MULTI_STATEMENTS here, otherwise _any_
1252        stored procedure call will fail.
1253     */
1254     unsigned int optionFlags = CLIENT_MULTI_STATEMENTS;
1255     const QStringList opts(connOpts.split(QLatin1Char(';'), Qt::SkipEmptyParts));
1256     QString unixSocket;
1257     QString sslCert;
1258     QString sslCA;
1259     QString sslKey;
1260     QString sslCAPath;
1261     QString sslCipher;
1262     my_bool reconnect=false;
1263     uint connectTimeout = 0;
1264     uint readTimeout = 0;
1265     uint writeTimeout = 0;
1266 
1267     // extract the real options from the string
1268     for (int i = 0; i < opts.count(); ++i) {
1269         QString tmp(opts.at(i).simplified());
1270         int idx;
1271         if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) {
1272             QString val = tmp.mid(idx + 1).simplified();
1273             QString opt = tmp.left(idx).simplified();
1274             if (opt == QLatin1String("UNIX_SOCKET"))
1275                 unixSocket = val;
1276             else if (opt == QLatin1String("MYSQL_OPT_RECONNECT")) {
1277                 if (val == QLatin1String("TRUE") || val == QLatin1String("1") || val.isEmpty())
1278                     reconnect = true;
1279             } else if (opt == QLatin1String("MYSQL_OPT_CONNECT_TIMEOUT"))
1280                 connectTimeout = val.toInt();
1281             else if (opt == QLatin1String("MYSQL_OPT_READ_TIMEOUT"))
1282                 readTimeout = val.toInt();
1283             else if (opt == QLatin1String("MYSQL_OPT_WRITE_TIMEOUT"))
1284                 writeTimeout = val.toInt();
1285             else if (opt == QLatin1String("SSL_KEY"))
1286                 sslKey = val;
1287             else if (opt == QLatin1String("SSL_CERT"))
1288                 sslCert = val;
1289             else if (opt == QLatin1String("SSL_CA"))
1290                 sslCA = val;
1291             else if (opt == QLatin1String("SSL_CAPATH"))
1292                 sslCAPath = val;
1293             else if (opt == QLatin1String("SSL_CIPHER"))
1294                 sslCipher = val;
1295             else if (val == QLatin1String("TRUE") || val == QLatin1String("1"))
1296                 setOptionFlag(optionFlags, tmp.left(idx).simplified());
1297             else
1298                 qWarning("QMYSQLDriver::open: Illegal connect option value '%s'",
1299                          tmp.toLocal8Bit().constData());
1300         } else {
1301             setOptionFlag(optionFlags, tmp);
1302         }
1303     }
1304 
1305     if (!(d->mysql = mysql_init((MYSQL*) 0))) {
1306         setLastError(qMakeError(tr("Unable to allocate a MYSQL object"),
1307                      QSqlError::ConnectionError, d));
1308         setOpenError(true);
1309         return false;
1310     }
1311 
1312     if (!sslKey.isNull() || !sslCert.isNull() || !sslCA.isNull() ||
1313         !sslCAPath.isNull() || !sslCipher.isNull()) {
1314        mysql_ssl_set(d->mysql,
1315                         sslKey.isNull() ? static_cast<const char *>(0)
1316                                         : QFile::encodeName(sslKey).constData(),
1317                         sslCert.isNull() ? static_cast<const char *>(0)
1318                                          : QFile::encodeName(sslCert).constData(),
1319                         sslCA.isNull() ? static_cast<const char *>(0)
1320                                        : QFile::encodeName(sslCA).constData(),
1321                         sslCAPath.isNull() ? static_cast<const char *>(0)
1322                                            : QFile::encodeName(sslCAPath).constData(),
1323                         sslCipher.isNull() ? static_cast<const char *>(0)
1324                                            : sslCipher.toLocal8Bit().constData());
1325     }
1326 
1327 #if MYSQL_VERSION_ID >= 50100
1328     if (connectTimeout != 0)
1329         mysql_options(d->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &connectTimeout);
1330     if (readTimeout != 0)
1331         mysql_options(d->mysql, MYSQL_OPT_READ_TIMEOUT, &readTimeout);
1332     if (writeTimeout != 0)
1333         mysql_options(d->mysql, MYSQL_OPT_WRITE_TIMEOUT, &writeTimeout);
1334 #endif
1335     MYSQL *mysql = mysql_real_connect(d->mysql,
1336                                       host.isNull() ? static_cast<const char *>(0)
1337                                                     : host.toLocal8Bit().constData(),
1338                                       user.isNull() ? static_cast<const char *>(0)
1339                                                     : user.toLocal8Bit().constData(),
1340                                       password.isNull() ? static_cast<const char *>(0)
1341                                                         : password.toLocal8Bit().constData(),
1342                                       db.isNull() ? static_cast<const char *>(0)
1343                                                   : db.toLocal8Bit().constData(),
1344                                       (port > -1) ? port : 0,
1345                                       unixSocket.isNull() ? static_cast<const char *>(0)
1346                                                           : unixSocket.toLocal8Bit().constData(),
1347                                       optionFlags);
1348 
1349     if (mysql == d->mysql) {
1350         if (!db.isEmpty() && mysql_select_db(d->mysql, db.toLocal8Bit().constData())) {
1351             setLastError(qMakeError(tr("Unable to open database '%1'").arg(db), QSqlError::ConnectionError, d));
1352             mysql_close(d->mysql);
1353             setOpenError(true);
1354             return false;
1355         }
1356 #if MYSQL_VERSION_ID >= 50100
1357         if (reconnect)
1358             mysql_options(d->mysql, MYSQL_OPT_RECONNECT, &reconnect);
1359 #endif
1360     } else {
1361         setLastError(qMakeError(tr("Unable to connect"),
1362                      QSqlError::ConnectionError, d));
1363         mysql_close(d->mysql);
1364         d->mysql = NULL;
1365         setOpenError(true);
1366         return false;
1367     }
1368 
1369 #if MYSQL_VERSION_ID >= 50007
1370     // force the communication to be utf8mb4 (only utf8mb4 supports 4-byte characters)
1371     if (mysql_set_character_set(d->mysql, "utf8mb4")) {
1372         // this failed, try forcing it to utf (BMP only)
1373         if (mysql_set_character_set(d->mysql, "utf8"))
1374             qWarning() << "MySQL: Unable to set the client character set to utf8.";
1375 #if QT_CONFIG(textcodec)
1376         else
1377             d->tc = codec(d->mysql);
1378 #endif
1379     }
1380 #if QT_CONFIG(textcodec)
1381     else
1382         d->tc = QTextCodec::codecForName("UTF-8");
1383 #endif
1384 #endif  // MYSQL_VERSION_ID >= 50007
1385 
1386     d->preparedQuerysEnabled = checkPreparedQueries(d->mysql);
1387 
1388 #if QT_CONFIG(thread)
1389     mysql_thread_init();
1390 #endif
1391 
1392     setOpen(true);
1393     setOpenError(false);
1394     return true;
1395 }
1396 
1397 void QMYSQLDriver::close()
1398 {
1399     Q_D(QMYSQLDriver);
1400     if (isOpen()) {
1401 #if QT_CONFIG(thread)
1402         mysql_thread_end();
1403 #endif
1404         mysql_close(d->mysql);
1405         d->mysql = NULL;
1406         setOpen(false);
1407         setOpenError(false);
1408     }
1409 }
1410 
1411 QSqlResult *QMYSQLDriver::createResult() const
1412 {
1413     return new QMYSQLResult(this);
1414 }
1415 
1416 QStringList QMYSQLDriver::tables(QSql::TableType type) const
1417 {
1418     Q_D(const QMYSQLDriver);
1419     QStringList tl;
1420     QSqlQuery q(createResult());
1421     if (type & QSql::Tables) {
1422         QString sql = QLatin1String("select table_name from information_schema.tables where table_schema = '") + QLatin1String(d->mysql->db) + QLatin1String("' and table_type = 'BASE TABLE'");
1423         q.exec(sql);
1424 
1425         while (q.next())
1426             tl.append(q.value(0).toString());
1427     }
1428     if (type & QSql::Views) {
1429         QString sql = QLatin1String("select table_name from information_schema.tables where table_schema = '") + QLatin1String(d->mysql->db) + QLatin1String("' and table_type = 'VIEW'");
1430         q.exec(sql);
1431 
1432         while (q.next())
1433             tl.append(q.value(0).toString());
1434     }
1435     return tl;
1436 }
1437 
1438 QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const
1439 {
1440     QSqlIndex idx;
1441     if (!isOpen())
1442         return idx;
1443 
1444     QSqlQuery i(createResult());
1445     QString stmt(QLatin1String("show index from %1;"));
1446     QSqlRecord fil = record(tablename);
1447     i.exec(stmt.arg(escapeIdentifier(tablename, QSqlDriver::TableName)));
1448     while (i.isActive() && i.next()) {
1449         if (i.value(2).toString() == QLatin1String("PRIMARY")) {
1450             idx.append(fil.field(i.value(4).toString()));
1451             idx.setCursorName(i.value(0).toString());
1452             idx.setName(i.value(2).toString());
1453         }
1454     }
1455 
1456     return idx;
1457 }
1458 
1459 QSqlRecord QMYSQLDriver::record(const QString& tablename) const
1460 {
1461     Q_D(const QMYSQLDriver);
1462     QString table=tablename;
1463     if(isIdentifierEscaped(table, QSqlDriver::TableName))
1464         table = stripDelimiters(table, QSqlDriver::TableName);
1465 
1466     QSqlRecord info;
1467     if (!isOpen())
1468         return info;
1469     MYSQL_RES* r = mysql_list_fields(d->mysql, table.toLocal8Bit().constData(), 0);
1470     if (!r) {
1471         return info;
1472     }
1473     MYSQL_FIELD* field;
1474 
1475     while ((field = mysql_fetch_field(r)))
1476         info.append(qToField(field, d->tc));
1477     mysql_free_result(r);
1478     return info;
1479 }
1480 
1481 QVariant QMYSQLDriver::handle() const
1482 {
1483     Q_D(const QMYSQLDriver);
1484     return QVariant::fromValue(d->mysql);
1485 }
1486 
1487 bool QMYSQLDriver::beginTransaction()
1488 {
1489     Q_D(QMYSQLDriver);
1490 #ifndef CLIENT_TRANSACTIONS
1491     return false;
1492 #endif
1493     if (!isOpen()) {
1494         qWarning("QMYSQLDriver::beginTransaction: Database not open");
1495         return false;
1496     }
1497     if (mysql_query(d->mysql, "BEGIN WORK")) {
1498         setLastError(qMakeError(tr("Unable to begin transaction"),
1499                                 QSqlError::StatementError, d));
1500         return false;
1501     }
1502     return true;
1503 }
1504 
1505 bool QMYSQLDriver::commitTransaction()
1506 {
1507     Q_D(QMYSQLDriver);
1508 #ifndef CLIENT_TRANSACTIONS
1509     return false;
1510 #endif
1511     if (!isOpen()) {
1512         qWarning("QMYSQLDriver::commitTransaction: Database not open");
1513         return false;
1514     }
1515     if (mysql_query(d->mysql, "COMMIT")) {
1516         setLastError(qMakeError(tr("Unable to commit transaction"),
1517                                 QSqlError::StatementError, d));
1518         return false;
1519     }
1520     return true;
1521 }
1522 
1523 bool QMYSQLDriver::rollbackTransaction()
1524 {
1525     Q_D(QMYSQLDriver);
1526 #ifndef CLIENT_TRANSACTIONS
1527     return false;
1528 #endif
1529     if (!isOpen()) {
1530         qWarning("QMYSQLDriver::rollbackTransaction: Database not open");
1531         return false;
1532     }
1533     if (mysql_query(d->mysql, "ROLLBACK")) {
1534         setLastError(qMakeError(tr("Unable to rollback transaction"),
1535                                 QSqlError::StatementError, d));
1536         return false;
1537     }
1538     return true;
1539 }
1540 
1541 QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
1542 {
1543     Q_D(const QMYSQLDriver);
1544     QString r;
1545     if (field.isNull()) {
1546         r = QStringLiteral("NULL");
1547     } else {
1548         switch (+field.type()) {
1549         case QMetaType::Double:
1550             r = QString::number(field.value().toDouble(), 'g', field.precision());
1551             break;
1552         case QMetaType::QString:
1553             // Escape '\' characters
1554             r = QSqlDriver::formatValue(field, trimStrings);
1555             r.replace(QLatin1String("\\"), QLatin1String("\\\\"));
1556             break;
1557         case QMetaType::QByteArray:
1558             if (isOpen()) {
1559                 const QByteArray ba = field.value().toByteArray();
1560                 // buffer has to be at least length*2+1 bytes
1561                 char* buffer = new char[ba.size() * 2 + 1];
1562                 int escapedSize = int(mysql_real_escape_string(d->mysql, buffer,
1563                                       ba.data(), ba.size()));
1564                 r.reserve(escapedSize + 3);
1565                 r.append(QLatin1Char('\'')).append(toUnicode(d->tc, buffer)).append(QLatin1Char('\''));
1566                 delete[] buffer;
1567                 break;
1568             } else {
1569                 qWarning("QMYSQLDriver::formatValue: Database not open");
1570             }
1571             // fall through
1572         default:
1573             r = QSqlDriver::formatValue(field, trimStrings);
1574         }
1575     }
1576     return r;
1577 }
1578 
1579 QString QMYSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
1580 {
1581     QString res = identifier;
1582     if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('`')) && !identifier.endsWith(QLatin1Char('`')) ) {
1583         res.prepend(QLatin1Char('`')).append(QLatin1Char('`'));
1584         res.replace(QLatin1Char('.'), QLatin1String("`.`"));
1585     }
1586     return res;
1587 }
1588 
1589 bool QMYSQLDriver::isIdentifierEscaped(const QString &identifier, IdentifierType type) const
1590 {
1591     Q_UNUSED(type);
1592     return identifier.size() > 2
1593         && identifier.startsWith(QLatin1Char('`')) //left delimited
1594         && identifier.endsWith(QLatin1Char('`')); //right delimited
1595 }
1596 
1597 QT_END_NAMESPACE
1598