diff options
Diffstat (limited to 'src/plugins/sqldrivers/mysql')
-rw-r--r-- | src/plugins/sqldrivers/mysql/CMakeLists.txt | 7 | ||||
-rw-r--r-- | src/plugins/sqldrivers/mysql/main.cpp | 45 | ||||
-rw-r--r-- | src/plugins/sqldrivers/mysql/qsql_mysql.cpp | 695 | ||||
-rw-r--r-- | src/plugins/sqldrivers/mysql/qsql_mysql_p.h | 40 |
4 files changed, 408 insertions, 379 deletions
diff --git a/src/plugins/sqldrivers/mysql/CMakeLists.txt b/src/plugins/sqldrivers/mysql/CMakeLists.txt index a05fc513f1..2e3d028584 100644 --- a/src/plugins/sqldrivers/mysql/CMakeLists.txt +++ b/src/plugins/sqldrivers/mysql/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from mysql.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## QMYSQLDriverPlugin Plugin: @@ -13,6 +14,7 @@ qt_internal_add_plugin(QMYSQLDriverPlugin DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII + QT_NO_CONTEXTLESS_CONNECT LIBRARIES MySQL::MySQL Qt::Core @@ -20,7 +22,4 @@ qt_internal_add_plugin(QMYSQLDriverPlugin Qt::SqlPrivate ) -#### Keys ignored in scope 1:.:.:mysql.pro:<TRUE>: -# OTHER_FILES = "mysql.json" - qt_internal_force_macos_intel_arch(QMYSQLDriverPlugin) diff --git a/src/plugins/sqldrivers/mysql/main.cpp b/src/plugins/sqldrivers/mysql/main.cpp index f7d56dd1c9..2c10c4d348 100644 --- a/src/plugins/sqldrivers/mysql/main.cpp +++ b/src/plugins/sqldrivers/mysql/main.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <qsqldriverplugin.h> #include <qstringlist.h> @@ -43,6 +7,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + class QMYSQLDriverPlugin : public QSqlDriverPlugin { Q_OBJECT @@ -61,8 +27,7 @@ QMYSQLDriverPlugin::QMYSQLDriverPlugin() QSqlDriver* QMYSQLDriverPlugin::create(const QString &name) { - if (name == QLatin1String("QMYSQL") || - name == QLatin1String("QMARIADB")) { + if (name == "QMYSQL"_L1 || name == "QMARIADB"_L1) { QMYSQLDriver* driver = new QMYSQLDriver(); return driver; } diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp index 96bba79da1..71b406c1a2 100644 --- a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp +++ b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp @@ -1,56 +1,23 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtSql module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qsql_mysql_p.h" #include <qcoreapplication.h> #include <qvariant.h> +#include <qvarlengtharray.h> #include <qdatetime.h> #include <qdebug.h> #include <qfile.h> #include <qlist.h> +#include <qloggingcategory.h> #include <qsqlerror.h> #include <qsqlfield.h> #include <qsqlindex.h> #include <qsqlquery.h> #include <qsqlrecord.h> #include <qstringlist.h> +#include <qtimezone.h> #include <QtSql/private/qsqldriver_p.h> #include <QtSql/private/qsqlresult_p.h> @@ -64,12 +31,32 @@ Q_DECLARE_METATYPE(MYSQL_RES*) Q_DECLARE_METATYPE(MYSQL*) Q_DECLARE_METATYPE(MYSQL_STMT*) +// MYSQL_TYPE_JSON was introduced with MySQL 5.7.9 +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID < 50709 +#define MYSQL_TYPE_JSON 245 +#endif + // MySQL above version 8 removed my_bool typedef while MariaDB kept it, // by redefining it we can regain source compatibility. using my_bool = decltype(mysql_stmt_bind_result(nullptr, nullptr)); +// this is a copy of the old MYSQL_TIME before an additional integer was added in +// 8.0.27.0. This kills the sanity check during retrieving this struct from mysql +// when another libmysql version is used during runtime than during compile time +struct QT_MYSQL_TIME +{ + unsigned int year, month, day, hour, minute, second; + unsigned long second_part; /**< microseconds */ + my_bool neg; + enum enum_mysql_timestamp_type time_type; +}; + QT_BEGIN_NAMESPACE +Q_STATIC_LOGGING_CATEGORY(lcMysql, "qt.sql.mysql") + +using namespace Qt::StringLiterals; + class QMYSQLDriverPrivate : public QSqlDriverPrivate { Q_DECLARE_PUBLIC(QMYSQLDriver) @@ -78,6 +65,7 @@ public: QMYSQLDriverPrivate() : QSqlDriverPrivate(QSqlDriver::MySqlServer) {} MYSQL *mysql = nullptr; + QString dbName; bool preparedQuerysEnabled = false; }; @@ -113,10 +101,15 @@ static inline QVariant qDateTimeFromString(QString &val) #else if (val.isEmpty()) return QVariant(QDateTime()); - if (val.length() == 14) - // TIMESTAMPS have the format yyyyMMddhhmmss - val.insert(4, QLatin1Char('-')).insert(7, QLatin1Char('-')).insert(10, - QLatin1Char('T')).insert(13, QLatin1Char(':')).insert(16, QLatin1Char(':')); + + // TIMESTAMPS have either the format "yyyyMMddhhmmss" or "yyyy-MM-dd + // hh:mm:ss". QDateTime::fromString() can convert the latter, but not the + // former, so adapt it if necessary. + if (val.size() == 14) + val.insert(4, u'-').insert(7, u'-').insert(10, u'T').insert(13, u':').insert(16, u':'); + + if (!val.endsWith(u'Z')) + val.append(u'Z'); // make UTC return QVariant(QDateTime::fromString(val, Qt::ISODate)); #endif } @@ -135,6 +128,18 @@ static inline bool checkPreparedQueries(MYSQL *mysql) return mysql_stmt_param_count(stmt.get()) == 2; } +// used with prepared queries and bound arguments +static inline void setUtcTimeZone(MYSQL *mysql) +{ + std::unique_ptr<MYSQL_STMT, decltype(&mysql_stmt_close)> stmt(mysql_stmt_init(mysql), &mysql_stmt_close); + if (!stmt) + return; + + static const char query[] = "SET time_zone = '+00:00'"; + if (mysql_stmt_prepare(stmt.get(), query, sizeof(query) - 1)) + mysql_stmt_execute(stmt.get()); +} + class QMYSQLResultPrivate; class QMYSQLResult : public QSqlResult @@ -205,68 +210,70 @@ public: bool preparedQuery = false; }; -static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, - const QMYSQLDriverPrivate* p) +static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type, + const QMYSQLDriverPrivate *p) { - const char *cerr = p->mysql ? mysql_error(p->mysql) : 0; - return QSqlError(QLatin1String("QMYSQL: ") + err, + const char *cerr = p->mysql ? mysql_error(p->mysql) : nullptr; + return QSqlError("QMYSQL: "_L1 + err, QString::fromUtf8(cerr), type, QString::number(mysql_errno(p->mysql))); } -static QMetaType qDecodeMYSQLType(int mysqltype, uint flags) +static QMetaType qDecodeMYSQLType(enum_field_types mysqltype, uint flags) { QMetaType::Type type; switch (mysqltype) { - case FIELD_TYPE_TINY : + case MYSQL_TYPE_TINY: type = (flags & UNSIGNED_FLAG) ? QMetaType::UChar : QMetaType::Char; break; - case FIELD_TYPE_SHORT : + case MYSQL_TYPE_SHORT: type = (flags & UNSIGNED_FLAG) ? QMetaType::UShort : QMetaType::Short; break; - case FIELD_TYPE_LONG : - case FIELD_TYPE_INT24 : + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_INT24: type = (flags & UNSIGNED_FLAG) ? QMetaType::UInt : QMetaType::Int; break; - case FIELD_TYPE_YEAR : + case MYSQL_TYPE_YEAR: type = QMetaType::Int; break; - case FIELD_TYPE_LONGLONG : + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_LONGLONG: type = (flags & UNSIGNED_FLAG) ? QMetaType::ULongLong : QMetaType::LongLong; break; - case FIELD_TYPE_FLOAT : - case FIELD_TYPE_DOUBLE : - case FIELD_TYPE_DECIMAL : -#if defined(FIELD_TYPE_NEWDECIMAL) - case FIELD_TYPE_NEWDECIMAL: -#endif + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: type = QMetaType::Double; break; - case FIELD_TYPE_DATE : + case MYSQL_TYPE_DATE: type = QMetaType::QDate; break; - case FIELD_TYPE_TIME : + case MYSQL_TYPE_TIME: // A time field can be within the range '-838:59:59' to '838:59:59' so // use QString instead of QTime since QTime is limited to 24 hour clock type = QMetaType::QString; break; - case FIELD_TYPE_DATETIME : - case FIELD_TYPE_TIMESTAMP : + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: type = QMetaType::QDateTime; break; - case FIELD_TYPE_STRING : - case FIELD_TYPE_VAR_STRING : - case FIELD_TYPE_BLOB : - case FIELD_TYPE_TINY_BLOB : - case FIELD_TYPE_MEDIUM_BLOB : - case FIELD_TYPE_LONG_BLOB : - case FIELD_TYPE_GEOMETRY : + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_JSON: type = (flags & BINARY_FLAG) ? QMetaType::QByteArray : QMetaType::QString; break; - default: - case FIELD_TYPE_ENUM : - case FIELD_TYPE_SET : + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + type = QMetaType::QString; + break; + default: // needed because there are more enum values which are not available in all headers type = QMetaType::QString; break; } @@ -276,34 +283,34 @@ static QMetaType qDecodeMYSQLType(int mysqltype, uint flags) static QSqlField qToField(MYSQL_FIELD *field) { QSqlField f(QString::fromUtf8(field->name), - qDecodeMYSQLType(int(field->type), field->flags), + qDecodeMYSQLType(field->type, field->flags), QString::fromUtf8(field->table)); f.setRequired(IS_NOT_NULL(field->flags)); f.setLength(field->length); f.setPrecision(field->decimals); - f.setSqlType(field->type); f.setAutoValue(field->flags & AUTO_INCREMENT_FLAG); return f; } -static QSqlError qMakeStmtError(const QString& err, QSqlError::ErrorType type, - MYSQL_STMT* stmt) +static QSqlError qMakeStmtError(const QString &err, QSqlError::ErrorType type, + MYSQL_STMT *stmt) { const char *cerr = mysql_stmt_error(stmt); - return QSqlError(QLatin1String("QMYSQL: ") + err, + return QSqlError("QMYSQL: "_L1 + err, QString::fromLatin1(cerr), type, QString::number(mysql_stmt_errno(stmt))); } -static bool qIsBlob(int t) +static bool qIsBlob(enum_field_types t) { return t == MYSQL_TYPE_TINY_BLOB || t == MYSQL_TYPE_BLOB || t == MYSQL_TYPE_MEDIUM_BLOB - || t == MYSQL_TYPE_LONG_BLOB; + || t == MYSQL_TYPE_LONG_BLOB + || t == MYSQL_TYPE_JSON; } -static bool qIsTimeOrDate(int t) +static bool qIsTimeOrDate(enum_field_types t) { // *not* MYSQL_TYPE_TIME because its range is bigger than QTime // (see above) @@ -318,9 +325,14 @@ static bool qIsInteger(int t) || t == QMetaType::LongLong || t == QMetaType::ULongLong; } +static inline bool qIsBitfield(enum_field_types type) +{ + return type == MYSQL_TYPE_BIT; +} + void QMYSQLResultPrivate::bindBlobs() { - for (int i = 0; i < fields.count(); ++i) { + for (int i = 0; i < fields.size(); ++i) { const MYSQL_FIELD *fieldInfo = fields.at(i).myField; if (qIsBlob(inBinds[i].buffer_type) && meta && fieldInfo) { MYSQL_BIND *bind = &inBinds[i]; @@ -362,7 +374,7 @@ bool QMYSQLResultPrivate::bindInValues() bind->buffer_length = f.bufLength = 0; hasBlobs = true; } else if (qIsTimeOrDate(fieldInfo->type)) { - bind->buffer_length = f.bufLength = sizeof(MYSQL_TIME); + bind->buffer_length = f.bufLength = sizeof(QT_MYSQL_TIME); } else if (qIsInteger(f.type.id())) { bind->buffer_length = f.bufLength = 8; } else { @@ -375,8 +387,6 @@ bool QMYSQLResultPrivate::bindInValues() char *field = bind->buffer_length ? new char[bind->buffer_length + 1]{} : nullptr; bind->buffer = f.outField = field; - if (qIsTimeOrDate(fieldInfo->type)) - new (field) MYSQL_TIME; ++i; } @@ -408,7 +418,7 @@ void QMYSQLResult::cleanup() if (d->result) mysql_free_result(d->result); -// must iterate trough leftover result sets from multi-selects or stored procedures +// must iterate through leftover result sets from multi-selects or stored procedures // if this isn't done subsequent queries will fail with "Commands out of sync" while (driver() && d->drv_d_func()->mysql && mysql_next_result(d->drv_d_func()->mysql) == 0) { MYSQL_RES *res = mysql_store_result(d->drv_d_func()->mysql); @@ -418,7 +428,7 @@ void QMYSQLResult::cleanup() if (d->stmt) { if (mysql_stmt_close(d->stmt)) - qWarning("QMYSQLResult::cleanup: unable to free statement handle"); + qCWarning(lcMysql, "QMYSQLResult::cleanup: unable to free statement handle"); d->stmt = 0; } @@ -427,11 +437,8 @@ void QMYSQLResult::cleanup() d->meta = 0; } - for (const auto &field : qAsConst(d->fields)) { - if (qIsTimeOrDate(field.myField->type)) - reinterpret_cast<MYSQL_TIME *>(field.outField)->~MYSQL_TIME(); - delete[] field.outField; - } + for (const QMYSQLResultPrivate::QMyField &f : std::as_const(d->fields)) + delete[] f.outField; if (d->outBinds) { delete[] d->outBinds; @@ -521,12 +528,7 @@ bool QMYSQLResult::fetchLast() return success; } - my_ulonglong numRows; - if (d->preparedQuery) { - numRows = mysql_stmt_num_rows(d->stmt); - } else { - numRows = mysql_num_rows(d->result); - } + my_ulonglong numRows = d->preparedQuery ? mysql_stmt_num_rows(d->stmt) : mysql_num_rows(d->result); if (at() == int(numRows)) return true; if (!numRows) @@ -544,11 +546,25 @@ bool QMYSQLResult::fetchFirst() return fetch(0); } +static inline uint64_t +qDecodeBitfield(const QMYSQLResultPrivate::QMyField &f, const char *outField) +{ + // byte-aligned length + const auto numBytes = (f.myField->length + 7) / 8; + uint64_t val = 0; + for (unsigned long i = 0; i < numBytes && outField; ++i) { + uint64_t tmp = static_cast<uint8_t>(outField[i]); + val <<= 8; + val |= tmp; + } + return val; +} + QVariant QMYSQLResult::data(int field) { Q_D(QMYSQLResult); - if (!isSelect() || field >= d->fields.count()) { - qWarning("QMYSQLResult::data: column %d out of range", field); + if (!isSelect() || field >= d->fields.size()) { + qCWarning(lcMysql, "QMYSQLResult::data: column %d out of range", field); return QVariant(); } @@ -561,8 +577,9 @@ QVariant QMYSQLResult::data(int field) if (d->preparedQuery) { if (f.nullIndicator) return QVariant(f.type); - - if (qIsInteger(f.type.id())) { + if (qIsBitfield(f.myField->type)) { + return QVariant::fromValue(qDecodeBitfield(f, f.outField)); + } else if (qIsInteger(f.type.id())) { QVariant variant(f.type, f.outField); // we never want to return char variants here, see QTBUG-53397 if (f.type.id() == QMetaType::UChar) @@ -570,8 +587,8 @@ QVariant QMYSQLResult::data(int field) else if (f.type.id() == QMetaType::Char) return variant.toInt(); return variant; - } else if (qIsTimeOrDate(f.myField->type) && f.bufLength == sizeof(MYSQL_TIME)) { - auto t = reinterpret_cast<const MYSQL_TIME *>(f.outField); + } else if (qIsTimeOrDate(f.myField->type) && f.bufLength >= sizeof(QT_MYSQL_TIME)) { + auto t = reinterpret_cast<const QT_MYSQL_TIME *>(f.outField); QDate date; QTime time; if (f.type.id() != QMetaType::QTime) @@ -579,7 +596,7 @@ QVariant QMYSQLResult::data(int field) if (f.type.id() != QMetaType::QDate) time = QTime(t->hour, t->minute, t->second, t->second_part / 1000); if (f.type.id() == QMetaType::QDateTime) - return QDateTime(date, time); + return QDateTime(date, time, QTimeZone::UTC); else if (f.type.id() == QMetaType::QDate) return date; else @@ -594,6 +611,9 @@ QVariant QMYSQLResult::data(int field) return QVariant(f.type); } + if (qIsBitfield(f.myField->type)) + return QVariant::fromValue(qDecodeBitfield(f, d->row[field])); + fieldLength = mysql_fetch_lengths(d->result)[field]; if (f.type.id() != QMetaType::QByteArray) @@ -663,7 +683,7 @@ QVariant QMYSQLResult::data(int field) bool QMYSQLResult::isNull(int field) { Q_D(const QMYSQLResult); - if (field < 0 || field >= d->fields.count()) + if (field < 0 || field >= d->fields.size()) return true; if (d->preparedQuery) return d->fields.at(field).nullIndicator; @@ -682,7 +702,7 @@ bool QMYSQLResult::reset (const QString& query) cleanup(); const QByteArray encQuery = query.toUtf8(); - if (mysql_real_query(d->drv_d_func()->mysql, encQuery.data(), encQuery.length())) { + if (mysql_real_query(d->drv_d_func()->mysql, encQuery.data(), encQuery.size())) { setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute query"), QSqlError::StatementError, d->drv_d_func())); return false; @@ -702,6 +722,7 @@ bool QMYSQLResult::reset (const QString& query) for(int i = 0; i < numFields; i++) { MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i); d->fields[i].type = qDecodeMYSQLType(field->type, field->flags); + d->fields[i].myField = field; } setAt(QSql::BeforeFirstRow); } @@ -767,7 +788,7 @@ QSqlRecord QMYSQLResult::record() const if (!mysql_errno(d->drv_d_func()->mysql)) { mysql_field_seek(res, 0); MYSQL_FIELD* field = mysql_fetch_field(res); - while(field) { + while (field) { info.append(qToField(field)); field = mysql_fetch_field(res); } @@ -790,8 +811,8 @@ bool QMYSQLResult::nextResult() d->result = 0; setSelect(false); - for (int i = 0; i < d->fields.count(); ++i) - delete[] d->fields[i].outField; + for (const QMYSQLResultPrivate::QMyField &f : std::as_const(d->fields)) + delete[] f.outField; d->fields.clear(); int status = mysql_next_result(d->drv_d_func()->mysql); @@ -804,7 +825,7 @@ bool QMYSQLResult::nextResult() } d->result = mysql_store_result(d->drv_d_func()->mysql); - int numFields = mysql_field_count(d->drv_d_func()->mysql); + unsigned int numFields = mysql_field_count(d->drv_d_func()->mysql); if (!d->result && numFields > 0) { setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store next result"), QSqlError::StatementError, d->drv_d_func())); @@ -816,9 +837,10 @@ bool QMYSQLResult::nextResult() d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql); if (isSelect()) { - for (int i = 0; i < numFields; i++) { - MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i); + for (unsigned int i = 0; i < numFields; i++) { + MYSQL_FIELD *field = mysql_fetch_field_direct(d->result, i); d->fields[i].type = qDecodeMYSQLType(field->type, field->flags); + d->fields[i].myField = field; } } @@ -831,29 +853,6 @@ void QMYSQLResult::virtual_hook(int id, void *data) QSqlResult::virtual_hook(id, data); } -static MYSQL_TIME *toMySqlDate(QDate date, QTime time, int type) -{ - Q_ASSERT(type == QMetaType::QTime || type == QMetaType::QDate - || type == QMetaType::QDateTime); - - MYSQL_TIME *myTime = new MYSQL_TIME; - memset(myTime, 0, sizeof(MYSQL_TIME)); - - if (type == QMetaType::QTime || type == QMetaType::QDateTime) { - myTime->hour = time.hour(); - myTime->minute = time.minute(); - myTime->second = time.second(); - myTime->second_part = time.msec() * 1000; - } - if (type == QMetaType::QDate || type == QMetaType::QDateTime) { - myTime->year = date.year(); - myTime->month = date.month(); - myTime->day = date.day(); - } - - return myTime; -} - bool QMYSQLResult::prepare(const QString& query) { Q_D(QMYSQLResult); @@ -878,7 +877,7 @@ bool QMYSQLResult::prepare(const QString& query) } const QByteArray encQuery = query.toUtf8(); - r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.length()); + r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.size()); if (r != 0) { setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", "Unable to prepare statement"), QSqlError::StatementError, d->stmt)); @@ -886,9 +885,9 @@ bool QMYSQLResult::prepare(const QString& query) return false; } - if (mysql_stmt_param_count(d->stmt) > 0) {// allocate memory for outvalues - d->outBinds = new MYSQL_BIND[mysql_stmt_param_count(d->stmt)]; - } + const auto paramCount = mysql_stmt_param_count(d->stmt); + if (paramCount > 0) // allocate memory for outvalues + d->outBinds = new MYSQL_BIND[paramCount](); setSelect(d->bindInValues()); d->preparedQuery = true; @@ -906,7 +905,7 @@ bool QMYSQLResult::exec() return false; int r = 0; - QList<MYSQL_TIME *> timeVector; + QList<QT_MYSQL_TIME *> timeVector; QList<QByteArray> stringVector; QList<my_bool> nullVector; @@ -919,17 +918,16 @@ bool QMYSQLResult::exec() return false; } - if (mysql_stmt_param_count(d->stmt) > 0 && - mysql_stmt_param_count(d->stmt) == (uint)values.count()) { - - nullVector.resize(values.count()); - for (int i = 0; i < values.count(); ++i) { + const unsigned long paramCount = mysql_stmt_param_count(d->stmt); + if (paramCount > 0 && paramCount == static_cast<size_t>(values.size())) { + nullVector.resize(values.size()); + for (qsizetype i = 0; i < values.size(); ++i) { const QVariant &val = boundValues().at(i); void *data = const_cast<void *>(val.constData()); MYSQL_BIND* currBind = &d->outBinds[i]; - nullVector[i] = static_cast<my_bool>(val.isNull()); + nullVector[i] = static_cast<my_bool>(QSqlResultPrivate::isVariantNull(val)); currBind->is_null = &nullVector[i]; currBind->length = 0; currBind->is_unsigned = 0; @@ -944,27 +942,41 @@ bool QMYSQLResult::exec() case QMetaType::QTime: case QMetaType::QDate: case QMetaType::QDateTime: { - MYSQL_TIME *myTime = toMySqlDate(val.toDate(), val.toTime(), val.userType()); + auto myTime = new QT_MYSQL_TIME{}; timeVector.append(myTime); - currBind->buffer = myTime; - switch (val.userType()) { - case QMetaType::QTime: + + QDate date; + QTime time; + int type = val.userType(); + if (type == QMetaType::QTime) { + time = val.toTime(); currBind->buffer_type = MYSQL_TYPE_TIME; myTime->time_type = MYSQL_TIMESTAMP_TIME; - break; - case QMetaType::QDate: + } else if (type == QMetaType::QDate) { + date = val.toDate(); currBind->buffer_type = MYSQL_TYPE_DATE; myTime->time_type = MYSQL_TIMESTAMP_DATE; - break; - case QMetaType::QDateTime: + } else { + QDateTime dt = val.toDateTime().toUTC(); + date = dt.date(); + time = dt.time(); currBind->buffer_type = MYSQL_TYPE_DATETIME; myTime->time_type = MYSQL_TIMESTAMP_DATETIME; - break; - default: - break; } - currBind->buffer_length = sizeof(MYSQL_TIME); + + if (type == QMetaType::QTime || type == QMetaType::QDateTime) { + myTime->hour = time.hour(); + myTime->minute = time.minute(); + myTime->second = time.second(); + myTime->second_part = time.msec() * 1000; + } + if (type == QMetaType::QDate || type == QMetaType::QDateTime) { + myTime->year = date.year(); + myTime->month = date.month(); + myTime->day = date.day(); + } + currBind->buffer_length = sizeof(QT_MYSQL_TIME); currBind->length = 0; break; } case QMetaType::UInt: @@ -998,12 +1010,16 @@ bool QMYSQLResult::exec() stringVector.append(ba); currBind->buffer_type = MYSQL_TYPE_STRING; currBind->buffer = const_cast<char *>(ba.constData()); - currBind->buffer_length = ba.length(); + currBind->buffer_length = ba.size(); break; } } } +#if defined(MARIADB_VERSION_ID) || MYSQL_VERSION_ID < 80300 r = mysql_stmt_bind_param(d->stmt, d->outBinds); +#else + r = mysql_stmt_bind_named_param(d->stmt, d->outBinds, paramCount, nullptr); +#endif if (r != 0) { setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", "Unable to bind value"), QSqlError::StatementError, d->stmt)); @@ -1074,7 +1090,7 @@ static void qLibraryInit() return; if (mysql_library_init(0, 0, 0)) { - qWarning("QMYSQLDriver::qServerInit: unable to start server."); + qCWarning(lcMysql, "QMYSQLDriver::qServerInit: unable to start server."); } #endif // Q_NO_MYSQL_EMBEDDED @@ -1166,100 +1182,182 @@ bool QMYSQLDriver::hasFeature(DriverFeature f) const return false; } -static void setOptionFlag(uint &optionFlags, const QString &opt) +static void setOptionFlag(uint &optionFlags, QStringView opt) { - if (opt == QLatin1String("CLIENT_COMPRESS")) + if (opt == "CLIENT_COMPRESS"_L1) optionFlags |= CLIENT_COMPRESS; - else if (opt == QLatin1String("CLIENT_FOUND_ROWS")) + else if (opt == "CLIENT_FOUND_ROWS"_L1) optionFlags |= CLIENT_FOUND_ROWS; - else if (opt == QLatin1String("CLIENT_IGNORE_SPACE")) + else if (opt == "CLIENT_IGNORE_SPACE"_L1) optionFlags |= CLIENT_IGNORE_SPACE; - else if (opt == QLatin1String("CLIENT_INTERACTIVE")) + else if (opt == "CLIENT_INTERACTIVE"_L1) optionFlags |= CLIENT_INTERACTIVE; - else if (opt == QLatin1String("CLIENT_NO_SCHEMA")) + else if (opt == "CLIENT_NO_SCHEMA"_L1) optionFlags |= CLIENT_NO_SCHEMA; - else if (opt == QLatin1String("CLIENT_ODBC")) + else if (opt == "CLIENT_ODBC"_L1) optionFlags |= CLIENT_ODBC; - else if (opt == QLatin1String("CLIENT_SSL")) - qWarning("QMYSQLDriver: SSL_KEY, SSL_CERT and SSL_CA should be used instead of CLIENT_SSL."); + else if (opt == "CLIENT_SSL"_L1) + qCWarning(lcMysql, "QMYSQLDriver: MYSQL_OPT_SSL_KEY, MYSQL_OPT_SSL_CERT " + "and MYSQL_OPT_SSL_CA should be used instead of CLIENT_SSL."); else - qWarning("QMYSQLDriver::open: Unknown connect option '%s'", opt.toLocal8Bit().constData()); + qCWarning(lcMysql, "QMYSQLDriver::open: Unknown connect option '%ls'", + qUtf16Printable(QString(opt))); +} + +static bool setOptionString(MYSQL *mysql, mysql_option option, QStringView v) +{ + return mysql_options(mysql, option, v.toUtf8().constData()) == 0; +} + +static bool setOptionInt(MYSQL *mysql, mysql_option option, QStringView v) +{ + bool bOk; + const auto val = v.toInt(&bOk); + return bOk ? mysql_options(mysql, option, &val) == 0 : false; +} + +static bool setOptionBool(MYSQL *mysql, mysql_option option, QStringView v) +{ + bool val = (v.isEmpty() || v == "TRUE"_L1 || v == "1"_L1); + return mysql_options(mysql, option, &val) == 0; } -bool QMYSQLDriver::open(const QString& db, - const QString& user, - const QString& password, - const QString& host, - int port, - const QString& connOpts) +// MYSQL_OPT_SSL_MODE was introduced with MySQL 5.7.11 +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50711 && !defined(MARIADB_VERSION_ID) +static bool setOptionSslMode(MYSQL *mysql, mysql_option option, QStringView v) +{ + mysql_ssl_mode sslMode = SSL_MODE_DISABLED; + if (v == "DISABLED"_L1 || v == "SSL_MODE_DISABLED"_L1) + sslMode = SSL_MODE_DISABLED; + else if (v == "PREFERRED"_L1 || v == "SSL_MODE_PREFERRED"_L1) + sslMode = SSL_MODE_PREFERRED; + else if (v == "REQUIRED"_L1 || v == "SSL_MODE_REQUIRED"_L1) + sslMode = SSL_MODE_REQUIRED; + else if (v == "VERIFY_CA"_L1 || v == "SSL_MODE_VERIFY_CA"_L1) + sslMode = SSL_MODE_VERIFY_CA; + else if (v == "VERIFY_IDENTITY"_L1 || v == "SSL_MODE_VERIFY_IDENTITY"_L1) + sslMode = SSL_MODE_VERIFY_IDENTITY; + else + qCWarning(lcMysql, "Unknown ssl mode '%ls' - using SSL_MODE_DISABLED", + qUtf16Printable(QString(v))); + return mysql_options(mysql, option, &sslMode) == 0; +} +#endif + +static bool setOptionProtocol(MYSQL *mysql, mysql_option option, QStringView v) +{ + mysql_protocol_type proto = MYSQL_PROTOCOL_DEFAULT; + if (v == "TCP"_L1 || v == "MYSQL_PROTOCOL_TCP"_L1) + proto = MYSQL_PROTOCOL_TCP; + else if (v == "SOCKET"_L1 || v == "MYSQL_PROTOCOL_SOCKET"_L1) + proto = MYSQL_PROTOCOL_SOCKET; + else if (v == "PIPE"_L1 || v == "MYSQL_PROTOCOL_PIPE"_L1) + proto = MYSQL_PROTOCOL_PIPE; + else if (v == "MEMORY"_L1 || v == "MYSQL_PROTOCOL_MEMORY"_L1) + proto = MYSQL_PROTOCOL_MEMORY; + else if (v == "DEFAULT"_L1 || v == "MYSQL_PROTOCOL_DEFAULT"_L1) + proto = MYSQL_PROTOCOL_DEFAULT; + else + qCWarning(lcMysql, "Unknown protocol '%ls' - using MYSQL_PROTOCOL_DEFAULT", + qUtf16Printable(QString(v))); + return mysql_options(mysql, option, &proto) == 0; +} + +bool QMYSQLDriver::open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port, + const QString &connOpts) { Q_D(QMYSQLDriver); if (isOpen()) close(); + if (!(d->mysql = mysql_init(nullptr))) { + setLastError(qMakeError(tr("Unable to allocate a MYSQL object"), + QSqlError::ConnectionError, d)); + setOpenError(true); + return false; + } + + typedef bool (*SetOptionFunc)(MYSQL*, mysql_option, QStringView); + struct mysqloptions { + QLatin1StringView key; + mysql_option option; + SetOptionFunc func; + }; + const mysqloptions options[] = { + {"SSL_KEY"_L1, MYSQL_OPT_SSL_KEY, setOptionString}, + {"SSL_CERT"_L1, MYSQL_OPT_SSL_CERT, setOptionString}, + {"SSL_CA"_L1, MYSQL_OPT_SSL_CA, setOptionString}, + {"SSL_CAPATH"_L1, MYSQL_OPT_SSL_CAPATH, setOptionString}, + {"SSL_CIPHER"_L1, MYSQL_OPT_SSL_CIPHER, setOptionString}, + {"MYSQL_OPT_SSL_KEY"_L1, MYSQL_OPT_SSL_KEY, setOptionString}, + {"MYSQL_OPT_SSL_CERT"_L1, MYSQL_OPT_SSL_CERT, setOptionString}, + {"MYSQL_OPT_SSL_CA"_L1, MYSQL_OPT_SSL_CA, setOptionString}, + {"MYSQL_OPT_SSL_CAPATH"_L1, MYSQL_OPT_SSL_CAPATH, setOptionString}, + {"MYSQL_OPT_SSL_CIPHER"_L1, MYSQL_OPT_SSL_CIPHER, setOptionString}, + {"MYSQL_OPT_SSL_CRL"_L1, MYSQL_OPT_SSL_CRL, setOptionString}, + {"MYSQL_OPT_SSL_CRLPATH"_L1, MYSQL_OPT_SSL_CRLPATH, setOptionString}, +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50710 + {"MYSQL_OPT_TLS_VERSION"_L1, MYSQL_OPT_TLS_VERSION, setOptionString}, +#endif +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50711 && !defined(MARIADB_VERSION_ID) + {"MYSQL_OPT_SSL_MODE"_L1, MYSQL_OPT_SSL_MODE, setOptionSslMode}, +#endif + {"MYSQL_OPT_CONNECT_TIMEOUT"_L1, MYSQL_OPT_CONNECT_TIMEOUT, setOptionInt}, + {"MYSQL_OPT_READ_TIMEOUT"_L1, MYSQL_OPT_READ_TIMEOUT, setOptionInt}, + {"MYSQL_OPT_WRITE_TIMEOUT"_L1, MYSQL_OPT_WRITE_TIMEOUT, setOptionInt}, + {"MYSQL_OPT_RECONNECT"_L1, MYSQL_OPT_RECONNECT, setOptionBool}, + {"MYSQL_OPT_LOCAL_INFILE"_L1, MYSQL_OPT_LOCAL_INFILE, setOptionInt}, + {"MYSQL_OPT_PROTOCOL"_L1, MYSQL_OPT_PROTOCOL, setOptionProtocol}, + {"MYSQL_SHARED_MEMORY_BASE_NAME"_L1, MYSQL_SHARED_MEMORY_BASE_NAME, setOptionString}, + }; + auto trySetOption = [&](const QStringView &key, const QStringView &value) -> bool { + for (const mysqloptions &opt : options) { + if (key == opt.key) { + if (!opt.func(d->mysql, opt.option, value)) { + qCWarning(lcMysql, "QMYSQLDriver::open: Could not set connect option value " + "'%ls' to '%ls'", + qUtf16Printable(QString(key)), qUtf16Printable(QString(value))); + } + return true; + } + } + return false; + }; + /* This is a hack to get MySQL's stored procedure support working. Since a stored procedure _may_ return multiple result sets, we have to enable CLIEN_MULTI_STATEMENTS here, otherwise _any_ stored procedure call will fail. */ unsigned int optionFlags = CLIENT_MULTI_STATEMENTS; - const QStringList opts(connOpts.split(QLatin1Char(';'), Qt::SkipEmptyParts)); + const QList<QStringView> opts(QStringView(connOpts).split(u';', Qt::SkipEmptyParts)); QString unixSocket; - QString sslCert; - QString sslCA; - QString sslKey; - QString sslCAPath; - QString sslCipher; - my_bool reconnect=false; - uint connectTimeout = 0; - uint readTimeout = 0; - uint writeTimeout = 0; // extract the real options from the string - for (int i = 0; i < opts.count(); ++i) { - QString tmp(opts.at(i).simplified()); - int idx; - if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) { - QString val = tmp.mid(idx + 1).simplified(); - QString opt = tmp.left(idx).simplified(); - if (opt == QLatin1String("UNIX_SOCKET")) - unixSocket = val; - else if (opt == QLatin1String("MYSQL_OPT_RECONNECT")) { - if (val == QLatin1String("TRUE") || val == QLatin1String("1") || val.isEmpty()) - reconnect = true; - } else if (opt == QLatin1String("MYSQL_OPT_CONNECT_TIMEOUT")) - connectTimeout = val.toInt(); - else if (opt == QLatin1String("MYSQL_OPT_READ_TIMEOUT")) - readTimeout = val.toInt(); - else if (opt == QLatin1String("MYSQL_OPT_WRITE_TIMEOUT")) - writeTimeout = val.toInt(); - else if (opt == QLatin1String("SSL_KEY")) - sslKey = val; - else if (opt == QLatin1String("SSL_CERT")) - sslCert = val; - else if (opt == QLatin1String("SSL_CA")) - sslCA = val; - else if (opt == QLatin1String("SSL_CAPATH")) - sslCAPath = val; - else if (opt == QLatin1String("SSL_CIPHER")) - sslCipher = val; - else if (val == QLatin1String("TRUE") || val == QLatin1String("1")) - setOptionFlag(optionFlags, tmp.left(idx).simplified()); + for (const auto &option : opts) { + const QStringView sv = QStringView(option).trimmed(); + qsizetype idx; + if ((idx = sv.indexOf(u'=')) != -1) { + const QStringView key = sv.left(idx).trimmed(); + const QStringView val = sv.mid(idx + 1).trimmed(); + if (trySetOption(key, val)) + continue; + else if (key == "UNIX_SOCKET"_L1) + unixSocket = val.toString(); + else if (val == "TRUE"_L1 || val == "1"_L1) + setOptionFlag(optionFlags, key); else - qWarning("QMYSQLDriver::open: Illegal connect option value '%s'", - tmp.toLocal8Bit().constData()); + qCWarning(lcMysql, "QMYSQLDriver::open: Illegal connect option value '%ls'", + qUtf16Printable(QString(sv))); } else { - setOptionFlag(optionFlags, tmp); + setOptionFlag(optionFlags, sv); } } - if (!(d->mysql = mysql_init(nullptr))) { - setLastError(qMakeError(tr("Unable to allocate a MYSQL object"), - QSqlError::ConnectionError, d)); - setOpenError(true); - return false; - } - // try utf8 with non BMP first, utf8 (BMP only) if that fails static const char wanted_charsets[][8] = { "utf8mb4", "utf8" }; #ifdef MARIADB_VERSION_ID @@ -1278,23 +1376,6 @@ bool QMYSQLDriver::open(const QString& db, } *cs = nullptr; #endif - if (!sslKey.isNull() || !sslCert.isNull() || !sslCA.isNull() || - !sslCAPath.isNull() || !sslCipher.isNull()) { - mysql_ssl_set(d->mysql, - sslKey.isNull() ? nullptr : sslKey.toUtf8().constData(), - sslCert.isNull() ? nullptr : sslCert.toUtf8().constData(), - sslCA.isNull() ? nullptr : sslCA.toUtf8().constData(), - sslCAPath.isNull() ? nullptr : sslCAPath.toUtf8().constData(), - sslCipher.isNull() ? nullptr : sslCipher.toUtf8().constData()); - } - - if (connectTimeout != 0) - mysql_options(d->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &connectTimeout); - if (readTimeout != 0) - mysql_options(d->mysql, MYSQL_OPT_READ_TIMEOUT, &readTimeout); - if (writeTimeout != 0) - mysql_options(d->mysql, MYSQL_OPT_WRITE_TIMEOUT, &writeTimeout); - MYSQL *mysql = mysql_real_connect(d->mysql, host.isNull() ? nullptr : host.toUtf8().constData(), user.isNull() ? nullptr : user.toUtf8().constData(), @@ -1304,40 +1385,43 @@ bool QMYSQLDriver::open(const QString& db, unixSocket.isNull() ? nullptr : unixSocket.toUtf8().constData(), optionFlags); + if (mysql != d->mysql) { + setLastError(qMakeError(tr("Unable to connect"), + QSqlError::ConnectionError, d)); + mysql_close(d->mysql); + d->mysql = nullptr; + setOpenError(true); + return false; + } + // now ask the server to match the charset we selected - if (!cs || mysql_set_character_set(d->mysql, cs->csname)) { + if (!cs || mysql_set_character_set(d->mysql, cs->csname) != 0) { bool ok = false; for (const char *p : wanted_charsets) { - if (mysql_set_character_set(d->mysql, p)) { + if (mysql_set_character_set(d->mysql, p) == 0) { ok = true; break; } } if (!ok) - qWarning("MySQL: Unable to set the client character set to utf8 (\"%s\"). Using '%s' instead.", - mysql_error(d->mysql), - mysql_character_set_name(d->mysql)); + qCWarning(lcMysql, "MySQL: Unable to set the client character set to utf8 (\"%s\"). " + "Using '%s' instead.", + mysql_error(d->mysql), + mysql_character_set_name(d->mysql)); } - if (mysql == d->mysql) { - if (!db.isEmpty() && mysql_select_db(d->mysql, db.toUtf8().constData())) { - setLastError(qMakeError(tr("Unable to open database '%1'").arg(db), QSqlError::ConnectionError, d)); - mysql_close(d->mysql); - setOpenError(true); - return false; - } - if (reconnect) - mysql_options(d->mysql, MYSQL_OPT_RECONNECT, &reconnect); - } else { - setLastError(qMakeError(tr("Unable to connect"), - QSqlError::ConnectionError, d)); + if (!db.isEmpty() && mysql_select_db(d->mysql, db.toUtf8().constData())) { + setLastError(qMakeError(tr("Unable to open database '%1'").arg(db), QSqlError::ConnectionError, d)); mysql_close(d->mysql); - d->mysql = nullptr; setOpenError(true); return false; } d->preparedQuerysEnabled = checkPreparedQueries(d->mysql); + d->dbName = db; + + if (d->preparedQuerysEnabled) + setUtcTimeZone(d->mysql); #if QT_CONFIG(thread) mysql_thread_init(); @@ -1357,6 +1441,7 @@ void QMYSQLDriver::close() #endif mysql_close(d->mysql); d->mysql = nullptr; + d->dbName.clear(); setOpen(false); setOpenError(false); } @@ -1373,14 +1458,14 @@ QStringList QMYSQLDriver::tables(QSql::TableType type) const QStringList tl; QSqlQuery q(createResult()); if (type & QSql::Tables) { - QString sql = QLatin1String("select table_name from information_schema.tables where table_schema = '") + QLatin1String(d->mysql->db) + QLatin1String("' and table_type = 'BASE TABLE'"); + QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + d->dbName + "' and table_type = 'BASE TABLE'"_L1; q.exec(sql); while (q.next()) tl.append(q.value(0).toString()); } if (type & QSql::Views) { - QString sql = QLatin1String("select table_name from information_schema.tables where table_schema = '") + QLatin1String(d->mysql->db) + QLatin1String("' and table_type = 'VIEW'"); + QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + d->dbName + "' and table_type = 'VIEW'"_L1; q.exec(sql); while (q.next()) @@ -1389,18 +1474,18 @@ QStringList QMYSQLDriver::tables(QSql::TableType type) const return tl; } -QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const +QSqlIndex QMYSQLDriver::primaryIndex(const QString &tablename) const { QSqlIndex idx; if (!isOpen()) return idx; QSqlQuery i(createResult()); - QString stmt(QLatin1String("show index from %1;")); + QString stmt("show index from %1;"_L1); QSqlRecord fil = record(tablename); i.exec(stmt.arg(escapeIdentifier(tablename, QSqlDriver::TableName))); while (i.isActive() && i.next()) { - if (i.value(2).toString() == QLatin1String("PRIMARY")) { + if (i.value(2).toString() == "PRIMARY"_L1) { idx.append(fil.field(i.value(4).toString())); idx.setCursorName(i.value(0).toString()); idx.setName(i.value(2).toString()); @@ -1410,26 +1495,41 @@ QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const return idx; } -QSqlRecord QMYSQLDriver::record(const QString& tablename) const +QSqlRecord QMYSQLDriver::record(const QString &tablename) const { Q_D(const QMYSQLDriver); - QString table=tablename; - if (isIdentifierEscaped(table, QSqlDriver::TableName)) - table = stripDelimiters(table, QSqlDriver::TableName); - - QSqlRecord info; if (!isOpen()) - return info; - MYSQL_RES* r = mysql_list_fields(d->mysql, table.toUtf8().constData(), 0); - if (!r) { - return info; + return {}; + QSqlQuery i(createResult()); + QString stmt("SELECT * FROM %1 LIMIT 0"_L1); + i.exec(stmt.arg(escapeIdentifier(tablename, QSqlDriver::TableName))); + auto r = i.record(); + if (r.isEmpty()) + return r; + // no binding of WHERE possible with MySQL + // escaping on WHERE clause does not work, so use mysql_real_escape_string() + stmt = "SELECT column_name, column_default FROM information_schema.columns WHERE table_name = '%1'"_L1; + const auto baTableName = tablename.toUtf8(); + QVarLengthArray<char> tableNameQuoted(baTableName.size() * 2 + 1); +#if defined(MARIADB_VERSION_ID) + const auto len = mysql_real_escape_string(d->mysql, tableNameQuoted.data(), + baTableName.data(), baTableName.size()); +#else + const auto len = mysql_real_escape_string_quote(d->mysql, tableNameQuoted.data(), + baTableName.data(), baTableName.size(), '\''); +#endif + if (i.exec(stmt.arg(QString::fromUtf8(tableNameQuoted.data(), len)))) { + while (i.next()) { + const auto colName = i.value(0).toString(); + const auto recordIdx = r.indexOf(colName); + if (recordIdx >= 0) { + auto field = r.field(recordIdx); + field.setDefaultValue(i.value(1)); + r.replace(recordIdx, field); + } + } } - MYSQL_FIELD* field; - - while ((field = mysql_fetch_field(r))) - info.append(qToField(field)); - mysql_free_result(r); - return info; + return r; } QVariant QMYSQLDriver::handle() const @@ -1442,7 +1542,7 @@ bool QMYSQLDriver::beginTransaction() { Q_D(QMYSQLDriver); if (!isOpen()) { - qWarning("QMYSQLDriver::beginTransaction: Database not open"); + qCWarning(lcMysql, "QMYSQLDriver::beginTransaction: Database not open"); return false; } if (mysql_query(d->mysql, "BEGIN WORK")) { @@ -1457,7 +1557,7 @@ bool QMYSQLDriver::commitTransaction() { Q_D(QMYSQLDriver); if (!isOpen()) { - qWarning("QMYSQLDriver::commitTransaction: Database not open"); + qCWarning(lcMysql, "QMYSQLDriver::commitTransaction: Database not open"); return false; } if (mysql_query(d->mysql, "COMMIT")) { @@ -1472,7 +1572,7 @@ bool QMYSQLDriver::rollbackTransaction() { Q_D(QMYSQLDriver); if (!isOpen()) { - qWarning("QMYSQLDriver::rollbackTransaction: Database not open"); + qCWarning(lcMysql, "QMYSQLDriver::rollbackTransaction: Database not open"); return false; } if (mysql_query(d->mysql, "ROLLBACK")) { @@ -1497,7 +1597,7 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons case QMetaType::QString: // Escape '\' characters r = QSqlDriver::formatValue(field, trimStrings); - r.replace(QLatin1String("\\"), QLatin1String("\\\\")); + r.replace("\\"_L1, "\\\\"_L1); break; case QMetaType::QByteArray: if (isOpen()) { @@ -1506,10 +1606,10 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons QVarLengthArray<char, 512> buffer(ba.size() * 2 + 1); auto escapedSize = mysql_real_escape_string(d->mysql, buffer.data(), ba.data(), ba.size()); r.reserve(escapedSize + 3); - r = QLatin1Char('\'') + QString::fromUtf8(buffer) + QLatin1Char('\''); + r = u'\'' + QString::fromUtf8(buffer.data(), escapedSize) + u'\''; break; } else { - qWarning("QMYSQLDriver::formatValue: Database not open"); + qCWarning(lcMysql, "QMYSQLDriver::formatValue: Database not open"); } Q_FALLTHROUGH(); case QMetaType::QDateTime: @@ -1518,12 +1618,11 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons // "+00:00" starting in version 8.0.19. However, if we got here, // it's because the MySQL server is too old for prepared queries // in the first place, so it won't understand timezones either. - // Besides, MYSQL_TIME does not support timezones, so match it. - r = QLatin1Char('\'') + + r = u'\'' + dt.date().toString(Qt::ISODate) + - QLatin1Char('T') + + u'T' + dt.time().toString(Qt::ISODate) + - QLatin1Char('\''); + u'\''; } break; default: @@ -1536,9 +1635,9 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons QString QMYSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const { QString res = identifier; - if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('`')) && !identifier.endsWith(QLatin1Char('`')) ) { - res.prepend(QLatin1Char('`')).append(QLatin1Char('`')); - res.replace(QLatin1Char('.'), QLatin1String("`.`")); + if (!identifier.isEmpty() && !identifier.startsWith(u'`') && !identifier.endsWith(u'`')) { + res.replace(u'.', "`.`"_L1); + res = u'`' + res + u'`'; } return res; } @@ -1547,8 +1646,10 @@ bool QMYSQLDriver::isIdentifierEscaped(const QString &identifier, IdentifierType { Q_UNUSED(type); return identifier.size() > 2 - && identifier.startsWith(QLatin1Char('`')) //left delimited - && identifier.endsWith(QLatin1Char('`')); //right delimited + && identifier.startsWith(u'`') //left delimited + && identifier.endsWith(u'`'); //right delimited } QT_END_NAMESPACE + +#include "moc_qsql_mysql_p.cpp" diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql_p.h b/src/plugins/sqldrivers/mysql/qsql_mysql_p.h index 9ccc8f4e4f..ae3334d35e 100644 --- a/src/plugins/sqldrivers/mysql/qsql_mysql_p.h +++ b/src/plugins/sqldrivers/mysql/qsql_mysql_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtSql module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QSQL_MYSQL_H #define QSQL_MYSQL_H |