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