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