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