Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qsql_mysql.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qsql_mysql_p.h"
5
6#include <qcoreapplication.h>
7#include <qvariant.h>
8#include <qvarlengtharray.h>
9#include <qdatetime.h>
10#include <qdebug.h>
11#include <qfile.h>
12#include <qlist.h>
13#include <qloggingcategory.h>
14#include <qsqlerror.h>
15#include <qsqlfield.h>
16#include <qsqlindex.h>
17#include <qsqlquery.h>
18#include <qsqlrecord.h>
19#include <qstringlist.h>
20#include <qtimezone.h>
21#include <QtSql/private/qsqldriver_p.h>
22#include <QtSql/private/qsqlresult_p.h>
23
24#ifdef Q_OS_WIN32
25// comment the next line out if you want to use MySQL/embedded on Win32 systems.
26// note that it will crash if you don't statically link to the mysql/e library!
27# define Q_NO_MYSQL_EMBEDDED
28#endif
29
30Q_DECLARE_METATYPE(MYSQL_RES*)
32Q_DECLARE_METATYPE(MYSQL_STMT*)
33
34// MYSQL_TYPE_JSON was introduced with MySQL 5.7.9
35#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID < 50709
36#define MYSQL_TYPE_JSON 245
37#endif
38
39// MySQL above version 8 removed my_bool typedef while MariaDB kept it,
40// by redefining it we can regain source compatibility.
41using my_bool = decltype(mysql_stmt_bind_result(nullptr, nullptr));
42
43// this is a copy of the old MYSQL_TIME before an additional integer was added in
44// 8.0.27.0. This kills the sanity check during retrieving this struct from mysql
45// when another libmysql version is used during runtime than during compile time
47{
48 unsigned int year, month, day, hour, minute, second;
49 unsigned long second_part;
51 enum enum_mysql_timestamp_type time_type;
52};
53
55
56static Q_LOGGING_CATEGORY(lcMysql, "qt.sql.mysql")
57
58using namespace Qt::StringLiterals;
59
61{
62 Q_DECLARE_PUBLIC(QMYSQLDriver)
63
64public:
67 MYSQL *mysql = nullptr;
69 bool preparedQuerysEnabled = false;
70};
71
72static inline QVariant qDateFromString(const QString &val)
73{
74#if !QT_CONFIG(datestring)
76 return QVariant(val);
77#else
78 if (val.isEmpty())
79 return QVariant(QDate());
80 return QVariant(QDate::fromString(val, Qt::ISODate));
81#endif
82}
83
84static inline QVariant qTimeFromString(const QString &val)
85{
86#if !QT_CONFIG(datestring)
88 return QVariant(val);
89#else
90 if (val.isEmpty())
91 return QVariant(QTime());
92 return QVariant(QTime::fromString(val, Qt::ISODate));
93#endif
94}
95
97{
98#if !QT_CONFIG(datestring)
100 return QVariant(val);
101#else
102 if (val.isEmpty())
103 return QVariant(QDateTime());
104
105 // TIMESTAMPS have either the format "yyyyMMddhhmmss" or "yyyy-MM-dd
106 // hh:mm:ss". QDateTime::fromString() can convert the latter, but not the
107 // former, so adapt it if necessary.
108 if (val.size() == 14)
109 val.insert(4, u'-').insert(7, u'-').insert(10, u'T').insert(13, u':').insert(16, u':');
110
111 if (!val.endsWith(u'Z'))
112 val.append(u'Z'); // make UTC
113 return QVariant(QDateTime::fromString(val, Qt::ISODate));
114#endif
115}
116
117// check if this client and server version of MySQL/MariaDB support prepared statements
118static inline bool checkPreparedQueries(MYSQL *mysql)
119{
120 std::unique_ptr<MYSQL_STMT, decltype(&mysql_stmt_close)> stmt(mysql_stmt_init(mysql), &mysql_stmt_close);
121 if (!stmt)
122 return false;
123
124 static const char dummyQuery[] = "SELECT ? + ?";
125 if (mysql_stmt_prepare(stmt.get(), dummyQuery, sizeof(dummyQuery) - 1))
126 return false;
127
128 return mysql_stmt_param_count(stmt.get()) == 2;
129}
130
131// used with prepared queries and bound arguments
132static inline void setUtcTimeZone(MYSQL *mysql)
133{
134 std::unique_ptr<MYSQL_STMT, decltype(&mysql_stmt_close)> stmt(mysql_stmt_init(mysql), &mysql_stmt_close);
135 if (!stmt)
136 return;
137
138 static const char query[] = "SET time_zone = '+00:00'";
139 if (mysql_stmt_prepare(stmt.get(), query, sizeof(query) - 1))
140 mysql_stmt_execute(stmt.get());
141}
142
144
146{
147 Q_DECLARE_PRIVATE(QMYSQLResult)
148 friend class QMYSQLDriver;
149
150public:
152 ~QMYSQLResult();
153
154 QVariant handle() const override;
155protected:
156 void cleanup();
157 bool fetch(int i) override;
158 bool fetchNext() override;
159 bool fetchLast() override;
160 bool fetchFirst() override;
161 QVariant data(int field) override;
162 bool isNull(int field) override;
163 bool reset (const QString& query) override;
164 int size() override;
165 int numRowsAffected() override;
166 QVariant lastInsertId() const override;
167 QSqlRecord record() const override;
168 void virtual_hook(int id, void *data) override;
169 bool nextResult() override;
170 void detachFromResultSet() override;
171
172 bool prepare(const QString &stmt) override;
173 bool exec() override;
174};
175
177{
178 Q_DECLARE_PUBLIC(QMYSQLResult)
179
180public:
182
184
185 bool bindInValues();
186 void bindBlobs();
187
188 MYSQL_RES *result = nullptr;
189 MYSQL_ROW row;
190
191 struct QMyField
192 {
193 char *outField = nullptr;
194 const MYSQL_FIELD *myField = nullptr;
196 my_bool nullIndicator = false;
197 ulong bufLength = 0ul;
198 };
199
200 QList<QMyField> fields;
201
202 MYSQL_STMT *stmt = nullptr;
203 MYSQL_RES *meta = nullptr;
204
205 MYSQL_BIND *inBinds = nullptr;
206 MYSQL_BIND *outBinds = nullptr;
207
208 int rowsAffected = 0;
209 bool hasBlobs = false;
210 bool preparedQuery = false;
211};
212
214 const QMYSQLDriverPrivate *p)
215{
216 const char *cerr = p->mysql ? mysql_error(p->mysql) : nullptr;
217 return QSqlError("QMYSQL: "_L1 + err,
218 QString::fromUtf8(cerr),
219 type, QString::number(mysql_errno(p->mysql)));
220}
221
222
223static QMetaType qDecodeMYSQLType(enum_field_types mysqltype, uint flags)
224{
226 switch (mysqltype) {
227 case MYSQL_TYPE_TINY:
228 type = (flags & UNSIGNED_FLAG) ? QMetaType::UChar : QMetaType::Char;
229 break;
230 case MYSQL_TYPE_SHORT:
231 type = (flags & UNSIGNED_FLAG) ? QMetaType::UShort : QMetaType::Short;
232 break;
233 case MYSQL_TYPE_LONG:
234 case MYSQL_TYPE_INT24:
235 type = (flags & UNSIGNED_FLAG) ? QMetaType::UInt : QMetaType::Int;
236 break;
237 case MYSQL_TYPE_YEAR:
238 type = QMetaType::Int;
239 break;
240 case MYSQL_TYPE_BIT:
241 case MYSQL_TYPE_LONGLONG:
242 type = (flags & UNSIGNED_FLAG) ? QMetaType::ULongLong : QMetaType::LongLong;
243 break;
244 case MYSQL_TYPE_FLOAT:
245 case MYSQL_TYPE_DOUBLE:
246 case MYSQL_TYPE_DECIMAL:
247 case MYSQL_TYPE_NEWDECIMAL:
248 type = QMetaType::Double;
249 break;
250 case MYSQL_TYPE_DATE:
251 type = QMetaType::QDate;
252 break;
253 case MYSQL_TYPE_TIME:
254 // A time field can be within the range '-838:59:59' to '838:59:59' so
255 // use QString instead of QTime since QTime is limited to 24 hour clock
256 type = QMetaType::QString;
257 break;
258 case MYSQL_TYPE_DATETIME:
259 case MYSQL_TYPE_TIMESTAMP:
260 type = QMetaType::QDateTime;
261 break;
262 case MYSQL_TYPE_STRING:
263 case MYSQL_TYPE_VAR_STRING:
264 case MYSQL_TYPE_BLOB:
265 case MYSQL_TYPE_TINY_BLOB:
266 case MYSQL_TYPE_MEDIUM_BLOB:
267 case MYSQL_TYPE_LONG_BLOB:
268 case MYSQL_TYPE_GEOMETRY:
269 case MYSQL_TYPE_JSON:
270 type = (flags & BINARY_FLAG) ? QMetaType::QByteArray : QMetaType::QString;
271 break;
272 case MYSQL_TYPE_ENUM:
273 case MYSQL_TYPE_SET:
274 type = QMetaType::QString;
275 break;
276 default: // needed because there are more enum values which are not available in all headers
277 type = QMetaType::QString;
278 break;
279 }
280 return QMetaType(type);
281}
282
283static QSqlField qToField(MYSQL_FIELD *field)
284{
285 QSqlField f(QString::fromUtf8(field->name),
286 qDecodeMYSQLType(field->type, field->flags),
287 QString::fromUtf8(field->table));
288 f.setRequired(IS_NOT_NULL(field->flags));
289 f.setLength(field->length);
290 f.setPrecision(field->decimals);
291 f.setAutoValue(field->flags & AUTO_INCREMENT_FLAG);
292 return f;
293}
294
296 MYSQL_STMT *stmt)
297{
298 const char *cerr = mysql_stmt_error(stmt);
299 return QSqlError("QMYSQL: "_L1 + err,
301 type, QString::number(mysql_stmt_errno(stmt)));
302}
303
304static bool qIsBlob(enum_field_types t)
305{
306 return t == MYSQL_TYPE_TINY_BLOB
307 || t == MYSQL_TYPE_BLOB
308 || t == MYSQL_TYPE_MEDIUM_BLOB
309 || t == MYSQL_TYPE_LONG_BLOB
310 || t == MYSQL_TYPE_JSON;
311}
312
313static bool qIsTimeOrDate(enum_field_types t)
314{
315 // *not* MYSQL_TYPE_TIME because its range is bigger than QTime
316 // (see above)
317 return t == MYSQL_TYPE_DATE || t == MYSQL_TYPE_DATETIME || t == MYSQL_TYPE_TIMESTAMP;
318}
319
320static bool qIsInteger(int t)
321{
322 return t == QMetaType::Char || t == QMetaType::UChar
323 || t == QMetaType::Short || t == QMetaType::UShort
324 || t == QMetaType::Int || t == QMetaType::UInt
325 || t == QMetaType::LongLong || t == QMetaType::ULongLong;
326}
327
328static inline bool qIsBitfield(enum_field_types type)
329{
330 return type == MYSQL_TYPE_BIT;
331}
332
334{
335 for (int i = 0; i < fields.size(); ++i) {
336 const MYSQL_FIELD *fieldInfo = fields.at(i).myField;
337 if (qIsBlob(inBinds[i].buffer_type) && meta && fieldInfo) {
338 MYSQL_BIND *bind = &inBinds[i];
339 bind->buffer_length = fieldInfo->max_length;
340 delete[] static_cast<char*>(bind->buffer);
341 bind->buffer = new char[fieldInfo->max_length];
342 fields[i].outField = static_cast<char*>(bind->buffer);
343 }
344 }
345}
346
348{
349 if (!meta)
350 meta = mysql_stmt_result_metadata(stmt);
351 if (!meta)
352 return false;
353
354 fields.resize(mysql_num_fields(meta));
355
356 inBinds = new MYSQL_BIND[fields.size()];
357 memset(inBinds, 0, fields.size() * sizeof(MYSQL_BIND));
358
359 const MYSQL_FIELD *fieldInfo;
360
361 int i = 0;
362 while((fieldInfo = mysql_fetch_field(meta))) {
363 MYSQL_BIND *bind = &inBinds[i];
364
365 QMyField &f = fields[i];
366 f.myField = fieldInfo;
367 bind->buffer_length = f.bufLength = fieldInfo->length + 1;
368 bind->buffer_type = fieldInfo->type;
369 f.type = qDecodeMYSQLType(fieldInfo->type, fieldInfo->flags);
370 if (qIsBlob(fieldInfo->type)) {
371 // the size of a blob-field is available as soon as we call
372 // mysql_stmt_store_result()
373 // after mysql_stmt_exec() in QMYSQLResult::exec()
374 bind->buffer_length = f.bufLength = 0;
375 hasBlobs = true;
376 } else if (qIsTimeOrDate(fieldInfo->type)) {
377 bind->buffer_length = f.bufLength = sizeof(QT_MYSQL_TIME);
378 } else if (qIsInteger(f.type.id())) {
379 bind->buffer_length = f.bufLength = 8;
380 } else {
381 bind->buffer_type = MYSQL_TYPE_STRING;
382 }
383
384 bind->is_null = &f.nullIndicator;
385 bind->length = &f.bufLength;
386 bind->is_unsigned = fieldInfo->flags & UNSIGNED_FLAG ? 1 : 0;
387
388 char *field = bind->buffer_length ? new char[bind->buffer_length + 1]{} : nullptr;
389 bind->buffer = f.outField = field;
390
391 ++i;
392 }
393 return true;
394}
395
400
405
407{
408 Q_D(const QMYSQLResult);
409 if (d->preparedQuery)
410 return d->meta ? QVariant::fromValue(d->meta) : QVariant::fromValue(d->stmt);
411 else
412 return QVariant::fromValue(d->result);
413}
414
416{
417 Q_D(QMYSQLResult);
418 if (d->result)
419 mysql_free_result(d->result);
420
421// must iterate through leftover result sets from multi-selects or stored procedures
422// if this isn't done subsequent queries will fail with "Commands out of sync"
423 while (driver() && d->drv_d_func()->mysql && mysql_next_result(d->drv_d_func()->mysql) == 0) {
424 MYSQL_RES *res = mysql_store_result(d->drv_d_func()->mysql);
425 if (res)
426 mysql_free_result(res);
427 }
428
429 if (d->stmt) {
430 if (mysql_stmt_close(d->stmt))
431 qCWarning(lcMysql, "QMYSQLResult::cleanup: unable to free statement handle");
432 d->stmt = 0;
433 }
434
435 if (d->meta) {
436 mysql_free_result(d->meta);
437 d->meta = 0;
438 }
439
440 for (const QMYSQLResultPrivate::QMyField &f : std::as_const(d->fields))
441 delete[] f.outField;
442
443 if (d->outBinds) {
444 delete[] d->outBinds;
445 d->outBinds = 0;
446 }
447
448 if (d->inBinds) {
449 delete[] d->inBinds;
450 d->inBinds = 0;
451 }
452
453 d->hasBlobs = false;
454 d->fields.clear();
455 d->result = nullptr;
456 d->row = nullptr;
457 setAt(-1);
458 setActive(false);
459}
460
462{
463 Q_D(QMYSQLResult);
464 if (!driver())
465 return false;
466 if (isForwardOnly()) { // fake a forward seek
467 if (at() < i) {
468 int x = i - at();
469 while (--x && fetchNext()) {};
470 return fetchNext();
471 } else {
472 return false;
473 }
474 }
475 if (at() == i)
476 return true;
477 if (d->preparedQuery) {
478 mysql_stmt_data_seek(d->stmt, i);
479
480 int nRC = mysql_stmt_fetch(d->stmt);
481 if (nRC) {
482 if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED)
484 "Unable to fetch data"), QSqlError::StatementError, d->stmt));
485 return false;
486 }
487 } else {
488 mysql_data_seek(d->result, i);
489 d->row = mysql_fetch_row(d->result);
490 if (!d->row)
491 return false;
492 }
493
494 setAt(i);
495 return true;
496}
497
499{
500 Q_D(QMYSQLResult);
501 if (!driver())
502 return false;
503 if (d->preparedQuery) {
504 int nRC = mysql_stmt_fetch(d->stmt);
505 if (nRC) {
506 if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED)
508 "Unable to fetch data"), QSqlError::StatementError, d->stmt));
509 return false;
510 }
511 } else {
512 d->row = mysql_fetch_row(d->result);
513 if (!d->row)
514 return false;
515 }
516 setAt(at() + 1);
517 return true;
518}
519
521{
522 Q_D(QMYSQLResult);
523 if (!driver())
524 return false;
525 if (isForwardOnly()) { // fake this since MySQL can't seek on forward only queries
526 bool success = fetchNext(); // did we move at all?
527 while (fetchNext()) {};
528 return success;
529 }
530
531 my_ulonglong numRows = d->preparedQuery ? mysql_stmt_num_rows(d->stmt) : mysql_num_rows(d->result);
532 if (at() == int(numRows))
533 return true;
534 if (!numRows)
535 return false;
536 return fetch(numRows - 1);
537}
538
540{
541 if (at() == 0)
542 return true;
543
544 if (isForwardOnly())
545 return (at() == QSql::BeforeFirstRow) ? fetchNext() : false;
546 return fetch(0);
547}
548
549static inline uint64_t
551{
552 // byte-aligned length
553 const auto numBytes = (f.myField->length + 7) / 8;
554 uint64_t val = 0;
555 for (unsigned long i = 0; i < numBytes && outField; ++i) {
556 uint64_t tmp = static_cast<uint8_t>(outField[i]);
557 val <<= 8;
558 val |= tmp;
559 }
560 return val;
561}
562
564{
565 Q_D(QMYSQLResult);
566 if (!isSelect() || field >= d->fields.size()) {
567 qCWarning(lcMysql, "QMYSQLResult::data: column %d out of range", field);
568 return QVariant();
569 }
570
571 if (!driver())
572 return QVariant();
573
574 my_ulonglong fieldLength = 0;
575 const QMYSQLResultPrivate::QMyField &f = d->fields.at(field);
576 QString val;
577 if (d->preparedQuery) {
578 if (f.nullIndicator)
579 return QVariant(f.type);
580 if (qIsBitfield(f.myField->type)) {
581 return QVariant::fromValue(qDecodeBitfield(f, f.outField));
582 } else if (qIsInteger(f.type.id())) {
583 QVariant variant(f.type, f.outField);
584 // we never want to return char variants here, see QTBUG-53397
585 if (f.type.id() == QMetaType::UChar)
586 return variant.toUInt();
587 else if (f.type.id() == QMetaType::Char)
588 return variant.toInt();
589 return variant;
590 } else if (qIsTimeOrDate(f.myField->type) && f.bufLength >= sizeof(QT_MYSQL_TIME)) {
591 auto t = reinterpret_cast<const QT_MYSQL_TIME *>(f.outField);
592 QDate date;
593 QTime time;
594 if (f.type.id() != QMetaType::QTime)
595 date = QDate(t->year, t->month, t->day);
596 if (f.type.id() != QMetaType::QDate)
597 time = QTime(t->hour, t->minute, t->second, t->second_part / 1000);
598 if (f.type.id() == QMetaType::QDateTime)
600 else if (f.type.id() == QMetaType::QDate)
601 return date;
602 else
603 return time;
604 }
605
606 if (f.type.id() != QMetaType::QByteArray)
607 val = QString::fromUtf8(f.outField, f.bufLength);
608 } else {
609 if (d->row[field] == nullptr) {
610 // NULL value
611 return QVariant(f.type);
612 }
613
614 if (qIsBitfield(f.myField->type))
615 return QVariant::fromValue(qDecodeBitfield(f, d->row[field]));
616
617 fieldLength = mysql_fetch_lengths(d->result)[field];
618
619 if (f.type.id() != QMetaType::QByteArray)
620 val = QString::fromUtf8(d->row[field], fieldLength);
621 }
622
623 switch (f.type.id()) {
624 case QMetaType::LongLong:
625 return QVariant(val.toLongLong());
626 case QMetaType::ULongLong:
627 return QVariant(val.toULongLong());
628 case QMetaType::Char:
629 case QMetaType::Short:
630 case QMetaType::Int:
631 return QVariant(val.toInt());
632 case QMetaType::UChar:
633 case QMetaType::UShort:
634 case QMetaType::UInt:
635 return QVariant(val.toUInt());
636 case QMetaType::Double: {
637 QVariant v;
638 bool ok=false;
639 double dbl = val.toDouble(&ok);
640 switch(numericalPrecisionPolicy()) {
642 v=QVariant(dbl).toInt();
643 break;
645 v = QVariant(dbl).toLongLong();
646 break;
648 v = QVariant(dbl);
649 break;
651 default:
652 v = val;
653 ok = true;
654 break;
655 }
656 if (ok)
657 return v;
658 return QVariant();
659 }
660 case QMetaType::QDate:
661 return qDateFromString(val);
662 case QMetaType::QTime:
663 return qTimeFromString(val);
664 case QMetaType::QDateTime:
665 return qDateTimeFromString(val);
666 case QMetaType::QByteArray: {
667
669 if (d->preparedQuery) {
670 ba = QByteArray(f.outField, f.bufLength);
671 } else {
672 ba = QByteArray(d->row[field], fieldLength);
673 }
674 return QVariant(ba);
675 }
676 case QMetaType::QString:
677 default:
678 return QVariant(val);
679 }
680 Q_UNREACHABLE();
681}
682
683bool QMYSQLResult::isNull(int field)
684{
685 Q_D(const QMYSQLResult);
686 if (field < 0 || field >= d->fields.size())
687 return true;
688 if (d->preparedQuery)
689 return d->fields.at(field).nullIndicator;
690 else
691 return d->row[field] == nullptr;
692}
693
695{
696 Q_D(QMYSQLResult);
697 if (!driver() || !driver()->isOpen() || driver()->isOpenError())
698 return false;
699
700 d->preparedQuery = false;
701
702 cleanup();
703
704 const QByteArray encQuery = query.toUtf8();
705 if (mysql_real_query(d->drv_d_func()->mysql, encQuery.data(), encQuery.size())) {
706 setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute query"),
707 QSqlError::StatementError, d->drv_d_func()));
708 return false;
709 }
710 d->result = mysql_store_result(d->drv_d_func()->mysql);
711 if (!d->result && mysql_field_count(d->drv_d_func()->mysql) > 0) {
712 setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store result"),
713 QSqlError::StatementError, d->drv_d_func()));
714 return false;
715 }
716 int numFields = mysql_field_count(d->drv_d_func()->mysql);
717 setSelect(numFields != 0);
718 d->fields.resize(numFields);
719 d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql);
720
721 if (isSelect()) {
722 for(int i = 0; i < numFields; i++) {
723 MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i);
724 d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
725 d->fields[i].myField = field;
726 }
728 }
729 setActive(true);
730 return isActive();
731}
732
734{
735 Q_D(const QMYSQLResult);
736 if (driver() && isSelect())
737 if (d->preparedQuery)
738 return mysql_stmt_num_rows(d->stmt);
739 else
740 return int(mysql_num_rows(d->result));
741 else
742 return -1;
743}
744
746{
747 Q_D(const QMYSQLResult);
748 return d->rowsAffected;
749}
750
752{
753 Q_D(QMYSQLResult);
754
755 if (d->preparedQuery) {
756 mysql_stmt_free_result(d->stmt);
757 }
758}
759
761{
762 Q_D(const QMYSQLResult);
763 if (!isActive() || !driver())
764 return QVariant();
765
766 if (d->preparedQuery) {
767 quint64 id = mysql_stmt_insert_id(d->stmt);
768 if (id)
769 return QVariant(id);
770 } else {
771 quint64 id = mysql_insert_id(d->drv_d_func()->mysql);
772 if (id)
773 return QVariant(id);
774 }
775 return QVariant();
776}
777
779{
780 Q_D(const QMYSQLResult);
782 MYSQL_RES *res;
783 if (!isActive() || !isSelect() || !driver())
784 return info;
785
786 res = d->preparedQuery ? d->meta : d->result;
787
788 if (!mysql_errno(d->drv_d_func()->mysql)) {
789 mysql_field_seek(res, 0);
790 MYSQL_FIELD* field = mysql_fetch_field(res);
791 while (field) {
792 info.append(qToField(field));
793 field = mysql_fetch_field(res);
794 }
795 }
796 mysql_field_seek(res, 0);
797 return info;
798}
799
801{
802 Q_D(QMYSQLResult);
803 if (!driver())
804 return false;
805
806 setAt(-1);
807 setActive(false);
808
809 if (d->result && isSelect())
810 mysql_free_result(d->result);
811 d->result = 0;
812 setSelect(false);
813
814 for (const QMYSQLResultPrivate::QMyField &f : std::as_const(d->fields))
815 delete[] f.outField;
816 d->fields.clear();
817
818 int status = mysql_next_result(d->drv_d_func()->mysql);
819 if (status > 0) {
820 setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute next query"),
821 QSqlError::StatementError, d->drv_d_func()));
822 return false;
823 } else if (status == -1) {
824 return false; // No more result sets
825 }
826
827 d->result = mysql_store_result(d->drv_d_func()->mysql);
828 unsigned int numFields = mysql_field_count(d->drv_d_func()->mysql);
829 if (!d->result && numFields > 0) {
830 setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store next result"),
831 QSqlError::StatementError, d->drv_d_func()));
832 return false;
833 }
834
835 setSelect(numFields > 0);
836 d->fields.resize(numFields);
837 d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql);
838
839 if (isSelect()) {
840 for (unsigned int i = 0; i < numFields; i++) {
841 MYSQL_FIELD *field = mysql_fetch_field_direct(d->result, i);
842 d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
843 d->fields[i].myField = field;
844 }
845 }
846
847 setActive(true);
848 return true;
849}
850
852{
854}
855
857{
858 Q_D(QMYSQLResult);
859 if (!driver())
860 return false;
861
862 cleanup();
863 if (!d->drv_d_func()->preparedQuerysEnabled)
865
866 int r;
867
868 if (query.isEmpty())
869 return false;
870
871 if (!d->stmt)
872 d->stmt = mysql_stmt_init(d->drv_d_func()->mysql);
873 if (!d->stmt) {
874 setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to prepare statement"),
875 QSqlError::StatementError, d->drv_d_func()));
876 return false;
877 }
878
879 const QByteArray encQuery = query.toUtf8();
880 r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.size());
881 if (r != 0) {
883 "Unable to prepare statement"), QSqlError::StatementError, d->stmt));
884 cleanup();
885 return false;
886 }
887
888 const auto paramCount = mysql_stmt_param_count(d->stmt);
889 if (paramCount > 0) // allocate memory for outvalues
890 d->outBinds = new MYSQL_BIND[paramCount]();
891
892 setSelect(d->bindInValues());
893 d->preparedQuery = true;
894 return true;
895}
896
898{
899 Q_D(QMYSQLResult);
900 if (!driver())
901 return false;
902 if (!d->preparedQuery)
903 return QSqlResult::exec();
904 if (!d->stmt)
905 return false;
906
907 int r = 0;
908 QList<QT_MYSQL_TIME *> timeVector;
909 QList<QByteArray> stringVector;
910 QList<my_bool> nullVector;
911
912 const QList<QVariant> values = boundValues();
913
914 r = mysql_stmt_reset(d->stmt);
915 if (r != 0) {
917 "Unable to reset statement"), QSqlError::StatementError, d->stmt));
918 return false;
919 }
920
921 const unsigned long paramCount = mysql_stmt_param_count(d->stmt);
922 if (paramCount > 0 && paramCount == static_cast<size_t>(values.size())) {
923 nullVector.resize(values.size());
924 for (qsizetype i = 0; i < values.size(); ++i) {
925 const QVariant &val = boundValues().at(i);
926 void *data = const_cast<void *>(val.constData());
927
928 MYSQL_BIND* currBind = &d->outBinds[i];
929
930 nullVector[i] = static_cast<my_bool>(QSqlResultPrivate::isVariantNull(val));
931 currBind->is_null = &nullVector[i];
932 currBind->length = 0;
933 currBind->is_unsigned = 0;
934
935 switch (val.userType()) {
936 case QMetaType::QByteArray:
937 currBind->buffer_type = MYSQL_TYPE_BLOB;
938 currBind->buffer = const_cast<char *>(val.toByteArray().constData());
939 currBind->buffer_length = val.toByteArray().size();
940 break;
941
942 case QMetaType::QTime:
943 case QMetaType::QDate:
944 case QMetaType::QDateTime: {
945 auto myTime = new QT_MYSQL_TIME{};
946 timeVector.append(myTime);
947 currBind->buffer = myTime;
948
949 QDate date;
950 QTime time;
951 int type = val.userType();
952 if (type == QMetaType::QTime) {
953 time = val.toTime();
954 currBind->buffer_type = MYSQL_TYPE_TIME;
955 myTime->time_type = MYSQL_TIMESTAMP_TIME;
956 } else if (type == QMetaType::QDate) {
957 date = val.toDate();
958 currBind->buffer_type = MYSQL_TYPE_DATE;
959 myTime->time_type = MYSQL_TIMESTAMP_DATE;
960 } else {
961 QDateTime dt = val.toDateTime().toUTC();
962 date = dt.date();
963 time = dt.time();
964 currBind->buffer_type = MYSQL_TYPE_DATETIME;
965 myTime->time_type = MYSQL_TIMESTAMP_DATETIME;
966 }
967
968 if (type == QMetaType::QTime || type == QMetaType::QDateTime) {
969 myTime->hour = time.hour();
970 myTime->minute = time.minute();
971 myTime->second = time.second();
972 myTime->second_part = time.msec() * 1000;
973 }
974 if (type == QMetaType::QDate || type == QMetaType::QDateTime) {
975 myTime->year = date.year();
976 myTime->month = date.month();
977 myTime->day = date.day();
978 }
979 currBind->buffer_length = sizeof(QT_MYSQL_TIME);
980 currBind->length = 0;
981 break; }
982 case QMetaType::UInt:
983 case QMetaType::Int:
984 currBind->buffer_type = MYSQL_TYPE_LONG;
985 currBind->buffer = data;
986 currBind->buffer_length = sizeof(int);
987 currBind->is_unsigned = (val.userType() != QMetaType::Int);
988 break;
989 case QMetaType::Bool:
990 currBind->buffer_type = MYSQL_TYPE_TINY;
991 currBind->buffer = data;
992 currBind->buffer_length = sizeof(bool);
993 currBind->is_unsigned = false;
994 break;
995 case QMetaType::Double:
996 currBind->buffer_type = MYSQL_TYPE_DOUBLE;
997 currBind->buffer = data;
998 currBind->buffer_length = sizeof(double);
999 break;
1000 case QMetaType::LongLong:
1001 case QMetaType::ULongLong:
1002 currBind->buffer_type = MYSQL_TYPE_LONGLONG;
1003 currBind->buffer = data;
1004 currBind->buffer_length = sizeof(qint64);
1005 currBind->is_unsigned = (val.userType() == QMetaType::ULongLong);
1006 break;
1007 case QMetaType::QString:
1008 default: {
1009 QByteArray ba = val.toString().toUtf8();
1010 stringVector.append(ba);
1011 currBind->buffer_type = MYSQL_TYPE_STRING;
1012 currBind->buffer = const_cast<char *>(ba.constData());
1013 currBind->buffer_length = ba.size();
1014 break; }
1015 }
1016 }
1017
1018#if defined(MARIADB_VERSION_ID) || MYSQL_VERSION_ID < 80300
1019 r = mysql_stmt_bind_param(d->stmt, d->outBinds);
1020#else
1021 r = mysql_stmt_bind_named_param(d->stmt, d->outBinds, paramCount, nullptr);
1022#endif
1023 if (r != 0) {
1025 "Unable to bind value"), QSqlError::StatementError, d->stmt));
1026 qDeleteAll(timeVector);
1027 return false;
1028 }
1029 }
1030 r = mysql_stmt_execute(d->stmt);
1031
1032 qDeleteAll(timeVector);
1033
1034 if (r != 0) {
1036 "Unable to execute statement"), QSqlError::StatementError, d->stmt));
1037 return false;
1038 }
1039 //if there is meta-data there is also data
1040 setSelect(d->meta);
1041
1042 d->rowsAffected = mysql_stmt_affected_rows(d->stmt);
1043
1044 if (isSelect()) {
1045 my_bool update_max_length = true;
1046
1047 r = mysql_stmt_bind_result(d->stmt, d->inBinds);
1048 if (r != 0) {
1050 "Unable to bind outvalues"), QSqlError::StatementError, d->stmt));
1051 return false;
1052 }
1053 if (d->hasBlobs)
1054 mysql_stmt_attr_set(d->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &update_max_length);
1055
1056 r = mysql_stmt_store_result(d->stmt);
1057 if (r != 0) {
1059 "Unable to store statement results"), QSqlError::StatementError, d->stmt));
1060 return false;
1061 }
1062
1063 if (d->hasBlobs) {
1064 // mysql_stmt_store_result() with STMT_ATTR_UPDATE_MAX_LENGTH set to true crashes
1065 // when called without a preceding call to mysql_stmt_bind_result()
1066 // in versions < 4.1.8
1067 d->bindBlobs();
1068 r = mysql_stmt_bind_result(d->stmt, d->inBinds);
1069 if (r != 0) {
1071 "Unable to bind outvalues"), QSqlError::StatementError, d->stmt));
1072 return false;
1073 }
1074 }
1076 }
1077 setActive(true);
1078 return true;
1079}
1080
1082
1084static bool qMySqlInitHandledByUser = false;
1085
1086static void qLibraryInit()
1087{
1088#ifndef Q_NO_MYSQL_EMBEDDED
1090 return;
1091
1092 if (mysql_library_init(0, 0, 0)) {
1093 qCWarning(lcMysql, "QMYSQLDriver::qServerInit: unable to start server.");
1094 }
1095#endif // Q_NO_MYSQL_EMBEDDED
1096
1097#if defined(MARIADB_BASE_VERSION) || defined(MARIADB_VERSION_ID)
1098 qAddPostRoutine([]() { mysql_server_end(); });
1099#endif
1100}
1101
1102static void qLibraryEnd()
1103{
1104#if !defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID)
1105# if !defined(Q_NO_MYSQL_EMBEDDED)
1106 mysql_library_end();
1107# endif
1108#endif
1109}
1110
1112 : QSqlDriver(*new QMYSQLDriverPrivate, parent)
1113{
1114 init();
1115 qLibraryInit();
1116}
1117
1124 : QSqlDriver(*new QMYSQLDriverPrivate, parent)
1125{
1126 Q_D(QMYSQLDriver);
1127 init();
1128 if (con) {
1129 d->mysql = con;
1130 setOpen(true);
1131 setOpenError(false);
1132 if (qMySqlConnectionCount == 1)
1134 } else {
1135 qLibraryInit();
1136 }
1137}
1138
1139void QMYSQLDriver::init()
1140{
1141 Q_D(QMYSQLDriver);
1142 d->mysql = 0;
1144}
1145
1152
1154{
1155 Q_D(const QMYSQLDriver);
1156 switch (f) {
1157 case Transactions:
1158 if (d->mysql) {
1159 if ((d->mysql->server_capabilities & CLIENT_TRANSACTIONS) == CLIENT_TRANSACTIONS)
1160 return true;
1161 }
1162 return false;
1163 case NamedPlaceholders:
1164 case BatchOperations:
1165 case SimpleLocking:
1166 case EventNotifications:
1167 case FinishQuery:
1168 case CancelQuery:
1169 return false;
1170 case QuerySize:
1171 case BLOB:
1172 case LastInsertId:
1173 case Unicode:
1175 return true;
1176 case PreparedQueries:
1178 return d->preparedQuerysEnabled;
1179 case MultipleResultSets:
1180 return true;
1181 }
1182 return false;
1183}
1184
1185static void setOptionFlag(uint &optionFlags, QStringView opt)
1186{
1187 if (opt == "CLIENT_COMPRESS"_L1)
1188 optionFlags |= CLIENT_COMPRESS;
1189 else if (opt == "CLIENT_FOUND_ROWS"_L1)
1190 optionFlags |= CLIENT_FOUND_ROWS;
1191 else if (opt == "CLIENT_IGNORE_SPACE"_L1)
1192 optionFlags |= CLIENT_IGNORE_SPACE;
1193 else if (opt == "CLIENT_INTERACTIVE"_L1)
1194 optionFlags |= CLIENT_INTERACTIVE;
1195 else if (opt == "CLIENT_NO_SCHEMA"_L1)
1196 optionFlags |= CLIENT_NO_SCHEMA;
1197 else if (opt == "CLIENT_ODBC"_L1)
1198 optionFlags |= CLIENT_ODBC;
1199 else if (opt == "CLIENT_SSL"_L1)
1200 qCWarning(lcMysql, "QMYSQLDriver: MYSQL_OPT_SSL_KEY, MYSQL_OPT_SSL_CERT "
1201 "and MYSQL_OPT_SSL_CA should be used instead of CLIENT_SSL.");
1202 else
1203 qCWarning(lcMysql, "QMYSQLDriver::open: Unknown connect option '%ls'",
1205}
1206
1207static bool setOptionString(MYSQL *mysql, mysql_option option, QStringView v)
1208{
1209 return mysql_options(mysql, option, v.toUtf8().constData()) == 0;
1210}
1211
1212static bool setOptionInt(MYSQL *mysql, mysql_option option, QStringView v)
1213{
1214 bool bOk;
1215 const auto val = v.toInt(&bOk);
1216 return bOk ? mysql_options(mysql, option, &val) == 0 : false;
1217}
1218
1219static bool setOptionBool(MYSQL *mysql, mysql_option option, QStringView v)
1220{
1221 bool val = (v.isEmpty() || v == "TRUE"_L1 || v == "1"_L1);
1222 return mysql_options(mysql, option, &val) == 0;
1223}
1224
1225// MYSQL_OPT_SSL_MODE was introduced with MySQL 5.7.11
1226#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50711 && !defined(MARIADB_VERSION_ID)
1227static bool setOptionSslMode(MYSQL *mysql, mysql_option option, QStringView v)
1228{
1229 mysql_ssl_mode sslMode = SSL_MODE_DISABLED;
1230 if (v == "DISABLED"_L1 || v == "SSL_MODE_DISABLED"_L1)
1231 sslMode = SSL_MODE_DISABLED;
1232 else if (v == "PREFERRED"_L1 || v == "SSL_MODE_PREFERRED"_L1)
1233 sslMode = SSL_MODE_PREFERRED;
1234 else if (v == "REQUIRED"_L1 || v == "SSL_MODE_REQUIRED"_L1)
1235 sslMode = SSL_MODE_REQUIRED;
1236 else if (v == "VERIFY_CA"_L1 || v == "SSL_MODE_VERIFY_CA"_L1)
1237 sslMode = SSL_MODE_VERIFY_CA;
1238 else if (v == "VERIFY_IDENTITY"_L1 || v == "SSL_MODE_VERIFY_IDENTITY"_L1)
1239 sslMode = SSL_MODE_VERIFY_IDENTITY;
1240 else
1241 qCWarning(lcMysql, "Unknown ssl mode '%ls' - using SSL_MODE_DISABLED",
1243 return mysql_options(mysql, option, &sslMode) == 0;
1244}
1245#endif
1246
1247static bool setOptionProtocol(MYSQL *mysql, mysql_option option, QStringView v)
1248{
1249 mysql_protocol_type proto = MYSQL_PROTOCOL_DEFAULT;
1250 if (v == "TCP"_L1 || v == "MYSQL_PROTOCOL_TCP"_L1)
1251 proto = MYSQL_PROTOCOL_TCP;
1252 else if (v == "SOCKET"_L1 || v == "MYSQL_PROTOCOL_SOCKET"_L1)
1253 proto = MYSQL_PROTOCOL_SOCKET;
1254 else if (v == "PIPE"_L1 || v == "MYSQL_PROTOCOL_PIPE"_L1)
1255 proto = MYSQL_PROTOCOL_PIPE;
1256 else if (v == "MEMORY"_L1 || v == "MYSQL_PROTOCOL_MEMORY"_L1)
1257 proto = MYSQL_PROTOCOL_MEMORY;
1258 else if (v == "DEFAULT"_L1 || v == "MYSQL_PROTOCOL_DEFAULT"_L1)
1259 proto = MYSQL_PROTOCOL_DEFAULT;
1260 else
1261 qCWarning(lcMysql, "Unknown protocol '%ls' - using MYSQL_PROTOCOL_DEFAULT",
1263 return mysql_options(mysql, option, &proto) == 0;
1264}
1265
1267 const QString &user,
1268 const QString &password,
1269 const QString &host,
1270 int port,
1271 const QString &connOpts)
1272{
1273 Q_D(QMYSQLDriver);
1274 if (isOpen())
1275 close();
1276
1277 if (!(d->mysql = mysql_init(nullptr))) {
1278 setLastError(qMakeError(tr("Unable to allocate a MYSQL object"),
1280 setOpenError(true);
1281 return false;
1282 }
1283
1284 typedef bool (*SetOptionFunc)(MYSQL*, mysql_option, QStringView);
1285 struct mysqloptions {
1287 mysql_option option;
1288 SetOptionFunc func;
1289 };
1290 const mysqloptions options[] = {
1291 {"SSL_KEY"_L1, MYSQL_OPT_SSL_KEY, setOptionString},
1292 {"SSL_CERT"_L1, MYSQL_OPT_SSL_CERT, setOptionString},
1293 {"SSL_CA"_L1, MYSQL_OPT_SSL_CA, setOptionString},
1294 {"SSL_CAPATH"_L1, MYSQL_OPT_SSL_CAPATH, setOptionString},
1295 {"SSL_CIPHER"_L1, MYSQL_OPT_SSL_CIPHER, setOptionString},
1296 {"MYSQL_OPT_SSL_KEY"_L1, MYSQL_OPT_SSL_KEY, setOptionString},
1297 {"MYSQL_OPT_SSL_CERT"_L1, MYSQL_OPT_SSL_CERT, setOptionString},
1298 {"MYSQL_OPT_SSL_CA"_L1, MYSQL_OPT_SSL_CA, setOptionString},
1299 {"MYSQL_OPT_SSL_CAPATH"_L1, MYSQL_OPT_SSL_CAPATH, setOptionString},
1300 {"MYSQL_OPT_SSL_CIPHER"_L1, MYSQL_OPT_SSL_CIPHER, setOptionString},
1301 {"MYSQL_OPT_SSL_CRL"_L1, MYSQL_OPT_SSL_CRL, setOptionString},
1302 {"MYSQL_OPT_SSL_CRLPATH"_L1, MYSQL_OPT_SSL_CRLPATH, setOptionString},
1303#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50710
1304 {"MYSQL_OPT_TLS_VERSION"_L1, MYSQL_OPT_TLS_VERSION, setOptionString},
1305#endif
1306#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50711 && !defined(MARIADB_VERSION_ID)
1307 {"MYSQL_OPT_SSL_MODE"_L1, MYSQL_OPT_SSL_MODE, setOptionSslMode},
1308#endif
1309 {"MYSQL_OPT_CONNECT_TIMEOUT"_L1, MYSQL_OPT_CONNECT_TIMEOUT, setOptionInt},
1310 {"MYSQL_OPT_READ_TIMEOUT"_L1, MYSQL_OPT_READ_TIMEOUT, setOptionInt},
1311 {"MYSQL_OPT_WRITE_TIMEOUT"_L1, MYSQL_OPT_WRITE_TIMEOUT, setOptionInt},
1312 {"MYSQL_OPT_RECONNECT"_L1, MYSQL_OPT_RECONNECT, setOptionBool},
1313 {"MYSQL_OPT_LOCAL_INFILE"_L1, MYSQL_OPT_LOCAL_INFILE, setOptionInt},
1314 {"MYSQL_OPT_PROTOCOL"_L1, MYSQL_OPT_PROTOCOL, setOptionProtocol},
1315 {"MYSQL_SHARED_MEMORY_BASE_NAME"_L1, MYSQL_SHARED_MEMORY_BASE_NAME, setOptionString},
1316 };
1317 auto trySetOption = [&](const QStringView &key, const QStringView &value) -> bool {
1318 for (const mysqloptions &opt : options) {
1319 if (key == opt.key) {
1320 if (!opt.func(d->mysql, opt.option, value)) {
1321 qCWarning(lcMysql, "QMYSQLDriver::open: Could not set connect option value "
1322 "'%ls' to '%ls'",
1324 }
1325 return true;
1326 }
1327 }
1328 return false;
1329 };
1330
1331 /* This is a hack to get MySQL's stored procedure support working.
1332 Since a stored procedure _may_ return multiple result sets,
1333 we have to enable CLIEN_MULTI_STATEMENTS here, otherwise _any_
1334 stored procedure call will fail.
1335 */
1336 unsigned int optionFlags = CLIENT_MULTI_STATEMENTS;
1337 const QList<QStringView> opts(QStringView(connOpts).split(u';', Qt::SkipEmptyParts));
1338 QString unixSocket;
1339
1340 // extract the real options from the string
1341 for (const auto &option : opts) {
1342 const QStringView sv = QStringView(option).trimmed();
1343 qsizetype idx;
1344 if ((idx = sv.indexOf(u'=')) != -1) {
1345 const QStringView key = sv.left(idx).trimmed();
1346 const QStringView val = sv.mid(idx + 1).trimmed();
1347 if (trySetOption(key, val))
1348 continue;
1349 else if (key == "UNIX_SOCKET"_L1)
1350 unixSocket = val.toString();
1351 else if (val == "TRUE"_L1 || val == "1"_L1)
1352 setOptionFlag(optionFlags, key);
1353 else
1354 qCWarning(lcMysql, "QMYSQLDriver::open: Illegal connect option value '%ls'",
1356 } else {
1357 setOptionFlag(optionFlags, sv);
1358 }
1359 }
1360
1361 // try utf8 with non BMP first, utf8 (BMP only) if that fails
1362 static const char wanted_charsets[][8] = { "utf8mb4", "utf8" };
1363#ifdef MARIADB_VERSION_ID
1364 MARIADB_CHARSET_INFO *cs = nullptr;
1365 for (const char *p : wanted_charsets) {
1366 cs = mariadb_get_charset_by_name(p);
1367 if (cs) {
1368 d->mysql->charset = cs;
1369 break;
1370 }
1371 }
1372#else
1373 // dummy
1374 struct {
1375 const char *csname;
1376 } *cs = nullptr;
1377#endif
1378
1379 MYSQL *mysql = mysql_real_connect(d->mysql,
1380 host.isNull() ? nullptr : host.toUtf8().constData(),
1381 user.isNull() ? nullptr : user.toUtf8().constData(),
1382 password.isNull() ? nullptr : password.toUtf8().constData(),
1383 db.isNull() ? nullptr : db.toUtf8().constData(),
1384 (port > -1) ? port : 0,
1385 unixSocket.isNull() ? nullptr : unixSocket.toUtf8().constData(),
1386 optionFlags);
1387
1388 if (mysql != d->mysql) {
1389 setLastError(qMakeError(tr("Unable to connect"),
1391 mysql_close(d->mysql);
1392 d->mysql = nullptr;
1393 setOpenError(true);
1394 return false;
1395 }
1396
1397 // now ask the server to match the charset we selected
1398 if (!cs || mysql_set_character_set(d->mysql, cs->csname) != 0) {
1399 bool ok = false;
1400 for (const char *p : wanted_charsets) {
1401 if (mysql_set_character_set(d->mysql, p) == 0) {
1402 ok = true;
1403 break;
1404 }
1405 }
1406 if (!ok)
1407 qCWarning(lcMysql, "MySQL: Unable to set the client character set to utf8 (\"%s\"). "
1408 "Using '%s' instead.",
1409 mysql_error(d->mysql),
1410 mysql_character_set_name(d->mysql));
1411 }
1412
1413 if (!db.isEmpty() && mysql_select_db(d->mysql, db.toUtf8().constData())) {
1414 setLastError(qMakeError(tr("Unable to open database '%1'").arg(db), QSqlError::ConnectionError, d));
1415 mysql_close(d->mysql);
1416 setOpenError(true);
1417 return false;
1418 }
1419
1420 d->preparedQuerysEnabled = checkPreparedQueries(d->mysql);
1421 d->dbName = db;
1422
1423 if (d->preparedQuerysEnabled)
1424 setUtcTimeZone(d->mysql);
1425
1426#if QT_CONFIG(thread)
1427 mysql_thread_init();
1428#endif
1429
1430 setOpen(true);
1431 setOpenError(false);
1432 return true;
1433}
1434
1436{
1437 Q_D(QMYSQLDriver);
1438 if (isOpen()) {
1439#if QT_CONFIG(thread)
1440 mysql_thread_end();
1441#endif
1442 mysql_close(d->mysql);
1443 d->mysql = nullptr;
1444 d->dbName.clear();
1445 setOpen(false);
1446 setOpenError(false);
1447 }
1448}
1449
1451{
1452 return new QMYSQLResult(this);
1453}
1454
1456{
1457 Q_D(const QMYSQLDriver);
1458 QStringList tl;
1460 if (type & QSql::Tables) {
1461 QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + d->dbName + "' and table_type = 'BASE TABLE'"_L1;
1462 q.exec(sql);
1463
1464 while (q.next())
1465 tl.append(q.value(0).toString());
1466 }
1467 if (type & QSql::Views) {
1468 QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + d->dbName + "' and table_type = 'VIEW'"_L1;
1469 q.exec(sql);
1470
1471 while (q.next())
1472 tl.append(q.value(0).toString());
1473 }
1474 return tl;
1475}
1476
1478{
1479 QSqlIndex idx;
1480 if (!isOpen())
1481 return idx;
1482
1484 QString stmt("show index from %1;"_L1);
1485 QSqlRecord fil = record(tablename);
1486 i.exec(stmt.arg(escapeIdentifier(tablename, QSqlDriver::TableName)));
1487 while (i.isActive() && i.next()) {
1488 if (i.value(2).toString() == "PRIMARY"_L1) {
1489 idx.append(fil.field(i.value(4).toString()));
1490 idx.setCursorName(i.value(0).toString());
1491 idx.setName(i.value(2).toString());
1492 }
1493 }
1494
1495 return idx;
1496}
1497
1499{
1500 Q_D(const QMYSQLDriver);
1501 if (!isOpen())
1502 return {};
1504 QString stmt("SELECT * FROM %1 LIMIT 0"_L1);
1505 i.exec(stmt.arg(escapeIdentifier(tablename, QSqlDriver::TableName)));
1506 auto r = i.record();
1507 if (r.isEmpty())
1508 return r;
1509 // no binding of WHERE possible with MySQL
1510 // escaping on WHERE clause does not work, so use mysql_real_escape_string()
1511 stmt = "SELECT column_name, column_default FROM information_schema.columns WHERE table_name = '%1'"_L1;
1512 const auto baTableName = tablename.toUtf8();
1513 QVarLengthArray<char> tableNameQuoted(baTableName.size() * 2 + 1);
1514#if defined(MARIADB_VERSION_ID)
1515 const auto len = mysql_real_escape_string(d->mysql, tableNameQuoted.data(),
1516 baTableName.data(), baTableName.size());
1517#else
1518 const auto len = mysql_real_escape_string_quote(d->mysql, tableNameQuoted.data(),
1519 baTableName.data(), baTableName.size(), '\'');
1520#endif
1521 if (i.exec(stmt.arg(QString::fromUtf8(tableNameQuoted.data(), len)))) {
1522 while (i.next()) {
1523 const auto colName = i.value(0).toString();
1524 const auto recordIdx = r.indexOf(colName);
1525 if (recordIdx >= 0) {
1526 auto field = r.field(recordIdx);
1527 field.setDefaultValue(i.value(1));
1528 r.replace(recordIdx, field);
1529 }
1530 }
1531 }
1532 return r;
1533}
1534
1536{
1537 Q_D(const QMYSQLDriver);
1538 return QVariant::fromValue(d->mysql);
1539}
1540
1542{
1543 Q_D(QMYSQLDriver);
1544 if (!isOpen()) {
1545 qCWarning(lcMysql, "QMYSQLDriver::beginTransaction: Database not open");
1546 return false;
1547 }
1548 if (mysql_query(d->mysql, "BEGIN WORK")) {
1549 setLastError(qMakeError(tr("Unable to begin transaction"),
1551 return false;
1552 }
1553 return true;
1554}
1555
1557{
1558 Q_D(QMYSQLDriver);
1559 if (!isOpen()) {
1560 qCWarning(lcMysql, "QMYSQLDriver::commitTransaction: Database not open");
1561 return false;
1562 }
1563 if (mysql_query(d->mysql, "COMMIT")) {
1564 setLastError(qMakeError(tr("Unable to commit transaction"),
1566 return false;
1567 }
1568 return true;
1569}
1570
1572{
1573 Q_D(QMYSQLDriver);
1574 if (!isOpen()) {
1575 qCWarning(lcMysql, "QMYSQLDriver::rollbackTransaction: Database not open");
1576 return false;
1577 }
1578 if (mysql_query(d->mysql, "ROLLBACK")) {
1579 setLastError(qMakeError(tr("Unable to rollback transaction"),
1581 return false;
1582 }
1583 return true;
1584}
1585
1586QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
1587{
1588 Q_D(const QMYSQLDriver);
1589 QString r;
1590 if (field.isNull()) {
1591 r = QStringLiteral("NULL");
1592 } else {
1593 switch (field.metaType().id()) {
1594 case QMetaType::Double:
1595 r = QString::number(field.value().toDouble(), 'g', field.precision());
1596 break;
1597 case QMetaType::QString:
1598 // Escape '\' characters
1599 r = QSqlDriver::formatValue(field, trimStrings);
1600 r.replace("\\"_L1, "\\\\"_L1);
1601 break;
1602 case QMetaType::QByteArray:
1603 if (isOpen()) {
1604 const QByteArray ba = field.value().toByteArray();
1605 // buffer has to be at least length*2+1 bytes
1606 QVarLengthArray<char, 512> buffer(ba.size() * 2 + 1);
1607 auto escapedSize = mysql_real_escape_string(d->mysql, buffer.data(), ba.data(), ba.size());
1608 r.reserve(escapedSize + 3);
1609 r = u'\'' + QString::fromUtf8(buffer.data(), escapedSize) + u'\'';
1610 break;
1611 } else {
1612 qCWarning(lcMysql, "QMYSQLDriver::formatValue: Database not open");
1613 }
1614 Q_FALLTHROUGH();
1615 case QMetaType::QDateTime:
1616 if (QDateTime dt = field.value().toDateTime(); dt.isValid()) {
1617 // MySQL format doesn't like the "Z" at the end, but does allow
1618 // "+00:00" starting in version 8.0.19. However, if we got here,
1619 // it's because the MySQL server is too old for prepared queries
1620 // in the first place, so it won't understand timezones either.
1621 r = u'\'' +
1622 dt.date().toString(Qt::ISODate) +
1623 u'T' +
1624 dt.time().toString(Qt::ISODate) +
1625 u'\'';
1626 }
1627 break;
1628 default:
1629 r = QSqlDriver::formatValue(field, trimStrings);
1630 }
1631 }
1632 return r;
1633}
1634
1636{
1637 QString res = identifier;
1638 if (!identifier.isEmpty() && !identifier.startsWith(u'`') && !identifier.endsWith(u'`')) {
1639 res.replace(u'.', "`.`"_L1);
1640 res = u'`' + res + u'`';
1641 }
1642 return res;
1643}
1644
1646{
1647 Q_UNUSED(type);
1648 return identifier.size() > 2
1649 && identifier.startsWith(u'`') //left delimited
1650 && identifier.endsWith(u'`'); //right delimited
1651}
1652
1654
1655#include "moc_qsql_mysql_p.cpp"
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
static QString translate(const char *context, const char *key, const char *disambiguation=nullptr, int n=-1)
\threadsafe
\inmodule QtCore\reentrant
Definition qdatetime.h:283
QDateTime toUTC() const
Returns a copy of this datetime converted to UTC.
\inmodule QtCore \reentrant
Definition qdatetime.h:29
int month() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
int day() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
int year() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
bool open(const QString &db, const QString &user, const QString &password, const QString &host, int port, const QString &connOpts) override
Derived classes must reimplement this pure virtual function to open a database connection on database...
QSqlIndex primaryIndex(const QString &tablename) const override
Returns the primary index for table tableName.
void close() override
Derived classes must reimplement this pure virtual function in order to close the database connection...
QString escapeIdentifier(const QString &identifier, IdentifierType type) const override
Returns the identifier escaped according to the database rules.
QStringList tables(QSql::TableType) const override
Returns a list of the names of the tables in the database.
QString formatValue(const QSqlField &field, bool trimStrings) const override
Returns a string representation of the field value for the database.
QMYSQLDriver(QObject *parent=nullptr)
bool commitTransaction() override
This function is called to commit a transaction.
QVariant handle() const override
Returns the low-level database handle wrapped in a QVariant or an invalid variant if there is no hand...
bool beginTransaction() override
This function is called to begin a transaction.
bool rollbackTransaction() override
This function is called to rollback a transaction.
bool hasFeature(DriverFeature f) const override
Returns true if the driver supports feature feature; otherwise returns false.
QSqlResult * createResult() const override
Creates an empty SQL result on the database.
bool isIdentifierEscaped(const QString &identifier, IdentifierType type) const override
Returns whether identifier is escaped according to the database rules.
QSqlRecord record(const QString &tablename) const override
Returns a QSqlRecord populated with the names of the fields in table tableName.
QList< QMyField > fields
QMYSQLResult(const QMYSQLDriver *db)
QSqlRecord record() const override
Returns the current record if the query is active; otherwise returns an empty QSqlRecord.
bool exec() override
Executes the query, returning true if successful; otherwise returns false.
bool isNull(int field) override
Returns true if the field at position index in the current row is null; otherwise returns false.
int numRowsAffected() override
Returns the number of rows affected by the last query executed, or -1 if it cannot be determined or i...
bool fetchNext() override
Positions the result to the next available record (row) in the result.
bool fetchFirst() override
Positions the result to the first record (row 0) in the result.
QVariant lastInsertId() const override
Returns the object ID of the most recent inserted row if the database supports it.
int size() override
Returns the size of the SELECT result, or -1 if it cannot be determined or if the query is not a SELE...
bool reset(const QString &query) override
Sets the result to use the SQL statement query for subsequent data retrieval.
void virtual_hook(int id, void *data) override
QVariant data(int field) override
Returns the data for field index in the current row as a QVariant.
bool fetch(int i) override
Positions the result to an arbitrary (zero-based) row index.
QVariant handle() const override
Returns the low-level database handle for this result set wrapped in a QVariant or an invalid QVarian...
bool fetchLast() override
Positions the result to the last record (last row) in the result.
bool prepare(const QString &stmt) override
Prepares the given query for execution; the query will normally use placeholders so that it can be ex...
void detachFromResultSet() override
bool nextResult() override
\inmodule QtCore
Definition qmetatype.h:341
int id(int=0) const
Definition qmetatype.h:475
Type
\macro Q_DECLARE_OPAQUE_POINTER(PointerType)
Definition qmetatype.h:345
\inmodule QtCore
Definition qobject.h:103
The QSqlDriver class is an abstract base class for accessing specific SQL databases.
Definition qsqldriver.h:26
virtual QString formatValue(const QSqlField &field, bool trimStrings=false) const
Returns a string representation of the field value for the database.
IdentifierType
This enum contains a list of SQL identifier types.
Definition qsqldriver.h:41
DriverFeature
This enum contains a list of features a driver might support.
Definition qsqldriver.h:33
@ PositionalPlaceholders
Definition qsqldriver.h:34
@ LowPrecisionNumbers
Definition qsqldriver.h:35
@ EventNotifications
Definition qsqldriver.h:36
@ PreparedQueries
Definition qsqldriver.h:33
@ NamedPlaceholders
Definition qsqldriver.h:34
@ BatchOperations
Definition qsqldriver.h:35
@ MultipleResultSets
Definition qsqldriver.h:36
virtual void setLastError(const QSqlError &e)
This function is used to set the value of the last error, error, that occurred on the database.
virtual bool isOpen() const
Returns true if the database connection is open; otherwise returns false.
virtual void setOpenError(bool e)
This function sets the open error state of the database to error.
virtual void setOpen(bool o)
This function sets the open state of the database to open.
The QSqlError class provides SQL database error information.
Definition qsqlerror.h:17
ErrorType
This enum type describes the context in which the error occurred, e.g., a connection error,...
Definition qsqlerror.h:19
@ StatementError
Definition qsqlerror.h:22
@ ConnectionError
Definition qsqlerror.h:21
The QSqlField class manipulates the fields in SQL database tables and views.
Definition qsqlfield.h:19
QMetaType metaType
Definition qsqlfield.h:28
bool isNull() const
Returns true if the field's value is NULL; otherwise returns false.
int precision
Definition qsqlfield.h:34
QVariant value
Definition qsqlfield.h:24
The QSqlIndex class provides functions to manipulate and describe database indexes.
Definition qsqlindex.h:18
void setName(const QString &name)
Sets \l name to name.
Definition qsqlindex.cpp:98
void setCursorName(const QString &cursorName)
Sets \l cursorName to cursorName.
void append(const QSqlField &field)
Appends the field field to the list of indexed fields.
The QSqlQuery class provides a means of executing and manipulating SQL statements.
Definition qsqlquery.h:24
The QSqlRecord class encapsulates a database record.
Definition qsqlrecord.h:20
static bool isVariantNull(const QVariant &variant)
QSqlResultPrivate(QSqlResult *q, const QSqlDriver *drv)
The QSqlResult class provides an abstract interface for accessing data from specific SQL databases.
Definition qsqlresult.h:22
bool isForwardOnly() const
Returns true if you can only scroll forward through the result set; otherwise returns false.
virtual void virtual_hook(int id, void *data)
int at() const
Returns the current (zero-based) row position of the result.
virtual bool prepare(const QString &query)
Prepares the given query for execution; the query will normally use placeholders so that it can be ex...
virtual bool exec()
Executes the query, returning true if successful; otherwise returns false.
bool isSelect() const
Returns true if the current result is from a SELECT statement; otherwise returns false.
virtual void setAt(int at)
This function is provided for derived classes to set the internal (zero-based) row position to index.
virtual void setSelect(bool s)
This function is provided for derived classes to indicate whether or not the current statement is a S...
virtual void setActive(bool a)
This function is provided for derived classes to set the internal active state to active.
QVariantList & boundValues(QT6_DECL_NEW_OVERLOAD)
QSql::NumericalPrecisionPolicy numericalPrecisionPolicy() const
virtual void setLastError(const QSqlError &e)
This function is provided for derived classes to set the last error to error.
const QSqlDriver * driver() const
Returns the driver associated with the result.
bool isActive() const
Returns true if the result has records to be retrieved; otherwise returns false.
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:78
constexpr QStringView left(qsizetype n) const noexcept
constexpr QStringView mid(qsizetype pos, qsizetype n=-1) const noexcept
Returns the substring of length length starting at position start in this object.
QStringView trimmed() const noexcept
Strips leading and trailing whitespace and returns the result.
qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5455
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3824
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
bool isNull() const
Returns true if this string is null; otherwise returns false.
Definition qstring.h:994
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8870
bool endsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string ends with s; otherwise returns false.
Definition qstring.cpp:5506
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
QByteArray toUtf8() const &
Definition qstring.h:634
\inmodule QtCore \reentrant
Definition qdatetime.h:215
int hour() const
Returns the hour part (0 to 23) of the time.
int minute() const
Returns the minute part (0 to 59) of the time.
int msec() const
Returns the millisecond part (0 to 999) of the time.
int second() const
Returns the second part (0 to 59) of the time.
\inmodule QtCore
Definition qvariant.h:65
QDateTime toDateTime() const
Returns the variant as a QDateTime if the variant has userType() \l QMetaType::QDateTime,...
double toDouble(bool *ok=nullptr) const
Returns the variant as a double if the variant has userType() \l QMetaType::Double,...
qlonglong toLongLong(bool *ok=nullptr) const
Returns the variant as a long long int if the variant has userType() \l QMetaType::LongLong,...
int toInt(bool *ok=nullptr) const
Returns the variant as an int if the variant has userType() \l QMetaType::Int, \l QMetaType::Bool,...
uint toUInt(bool *ok=nullptr) const
Returns the variant as an unsigned int if the variant has userType() \l QMetaType::UInt,...
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:536
QByteArray toByteArray() const
Returns the variant as a QByteArray if the variant has userType() \l QMetaType::QByteArray or \l QMet...
#define this
Definition dialogs.cpp:9
QDate date
[1]
qDeleteAll(list.begin(), list.end())
QStyleOptionButton opt
@ BeforeFirstRow
Definition qtsqlglobal.h:21
@ Views
Definition qtsqlglobal.h:38
@ Tables
Definition qtsqlglobal.h:36
@ LowPrecisionInt32
Definition qtsqlglobal.h:44
@ LowPrecisionDouble
Definition qtsqlglobal.h:46
@ LowPrecisionInt64
Definition qtsqlglobal.h:45
@ HighPrecision
Definition qtsqlglobal.h:48
Combined button and popup list for selecting options.
Definition qcompare.h:63
@ ISODate
@ SkipEmptyParts
Definition qnamespace.h:128
#define Q_FALLTHROUGH()
void qAddPostRoutine(QtCleanUpFunction p)
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
EGLOutputPortEXT port
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define Q_DECLARE_METATYPE(TYPE)
Definition qmetatype.h:1525
GLenum GLsizei GLsizei GLint * values
[15]
GLsizei const GLfloat * v
[13]
GLuint64 GLenum void * handle
GLint GLint GLint GLint GLint x
[0]
GLuint64 key
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLboolean r
[2]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLfloat GLfloat f
GLenum GLuint buffer
GLenum type
GLbitfield flags
GLboolean reset
GLenum query
GLenum func
Definition qopenglext.h:663
GLuint res
GLuint GLfloat * val
GLdouble GLdouble t
Definition qopenglext.h:243
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
GLuint GLenum option
GLenum GLsizei len
static void split(QT_FT_Vector *b)
static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type, const QDB2DriverPrivate *p)
Definition qsql_db2.cpp:203
static bool setOptionString(MYSQL *mysql, mysql_option option, QStringView v)
static bool qMySqlInitHandledByUser
static QVariant qTimeFromString(const QString &val)
static bool qIsTimeOrDate(enum_field_types t)
static void setOptionFlag(uint &optionFlags, QStringView opt)
static bool setOptionInt(MYSQL *mysql, mysql_option option, QStringView v)
static QVariant qDateTimeFromString(QString &val)
static bool setOptionBool(MYSQL *mysql, mysql_option option, QStringView v)
static bool checkPreparedQueries(MYSQL *mysql)
static bool qIsBlob(enum_field_types t)
static uint64_t qDecodeBitfield(const QMYSQLResultPrivate::QMyField &f, const char *outField)
static void setUtcTimeZone(MYSQL *mysql)
static bool qIsInteger(int t)
static void qLibraryInit()
static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type, const QMYSQLDriverPrivate *p)
static QVariant qDateFromString(const QString &val)
static bool qIsBitfield(enum_field_types type)
static int qMySqlConnectionCount
static QSqlError qMakeStmtError(const QString &err, QSqlError::ErrorType type, MYSQL_STMT *stmt)
static void qLibraryEnd()
static QSqlField qToField(MYSQL_FIELD *field)
static bool setOptionProtocol(MYSQL *mysql, mysql_option option, QStringView v)
static QMetaType qDecodeMYSQLType(enum_field_types mysqltype, uint flags)
decltype(mysql_stmt_bind_result(nullptr, nullptr)) my_bool
#define Q_DECLARE_SQLDRIVER_PRIVATE(Class)
SSL_CTX int void * arg
#define qUtf16Printable(string)
Definition qstring.h:1543
#define QStringLiteral(str)
#define tr(X)
#define Q_UNUSED(x)
unsigned long ulong
Definition qtypes.h:35
unsigned long long quint64
Definition qtypes.h:61
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
long long qint64
Definition qtypes.h:60
#define explicit
QByteArray ba
[0]
QObject::connect nullptr
QVariant variant
[1]
QMimeDatabase db
[0]
QHostInfo info
[0]
socketLayer bind(QHostAddress::Any, 4000)
const MYSQL_FIELD * myField
unsigned int month
unsigned long second_part
unsigned int minute
unsigned int year
unsigned int hour
unsigned int second
enum enum_mysql_timestamp_type time_type
unsigned int day