summaryrefslogtreecommitdiffstats
path: root/src/plugins/sqldrivers
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/sqldrivers')
-rw-r--r--src/plugins/sqldrivers/.cmake.conf2
-rw-r--r--src/plugins/sqldrivers/CMakeLists.txt16
-rw-r--r--src/plugins/sqldrivers/configure.cmake7
-rw-r--r--src/plugins/sqldrivers/db2/CMakeLists.txt5
-rw-r--r--src/plugins/sqldrivers/db2/qsql_db2.cpp87
-rw-r--r--src/plugins/sqldrivers/ibase/CMakeLists.txt1
-rw-r--r--src/plugins/sqldrivers/ibase/qsql_ibase.cpp307
-rw-r--r--src/plugins/sqldrivers/mimer/CMakeLists.txt24
-rw-r--r--src/plugins/sqldrivers/mimer/README6
-rw-r--r--src/plugins/sqldrivers/mimer/main.cpp33
-rw-r--r--src/plugins/sqldrivers/mimer/mimer.json5
-rw-r--r--src/plugins/sqldrivers/mimer/qsql_mimer.cpp1610
-rw-r--r--src/plugins/sqldrivers/mimer/qsql_mimer.h50
-rw-r--r--src/plugins/sqldrivers/mysql/CMakeLists.txt6
-rw-r--r--src/plugins/sqldrivers/mysql/qsql_mysql.cpp297
-rw-r--r--src/plugins/sqldrivers/oci/CMakeLists.txt5
-rw-r--r--src/plugins/sqldrivers/oci/qsql_oci.cpp110
-rw-r--r--src/plugins/sqldrivers/oci/qsql_oci_p.h1
-rw-r--r--src/plugins/sqldrivers/odbc/CMakeLists.txt8
-rw-r--r--src/plugins/sqldrivers/odbc/qsql_odbc.cpp974
-rw-r--r--src/plugins/sqldrivers/psql/CMakeLists.txt8
-rw-r--r--src/plugins/sqldrivers/psql/qsql_psql.cpp169
-rw-r--r--src/plugins/sqldrivers/qt_cmdline.cmake4
-rw-r--r--src/plugins/sqldrivers/sqlite/CMakeLists.txt15
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp247
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h7
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp258
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h21
-rw-r--r--src/plugins/sqldrivers/sqlite/smain.cpp2
29 files changed, 3267 insertions, 1018 deletions
diff --git a/src/plugins/sqldrivers/.cmake.conf b/src/plugins/sqldrivers/.cmake.conf
index 146dd2bd5a..10bc1fd407 100644
--- a/src/plugins/sqldrivers/.cmake.conf
+++ b/src/plugins/sqldrivers/.cmake.conf
@@ -1 +1 @@
-set(QT_REPO_MODULE_VERSION "6.6.0")
+set(QT_REPO_MODULE_VERSION "6.8.0")
diff --git a/src/plugins/sqldrivers/CMakeLists.txt b/src/plugins/sqldrivers/CMakeLists.txt
index 932e501c48..43abb00ad1 100644
--- a/src/plugins/sqldrivers/CMakeLists.txt
+++ b/src/plugins/sqldrivers/CMakeLists.txt
@@ -1,12 +1,17 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from sqldrivers.pro.
-
-# special case begin
cmake_minimum_required(VERSION 3.16)
if (NOT CMAKE_PROJECT_NAME STREQUAL "QtBase" AND NOT CMAKE_PROJECT_NAME STREQUAL "Qt")
include(.cmake.conf)
+ # Store initial build type (if any is specified) to be read by
+ # qt_internal_set_cmake_build_type().
+ # See qt_internal_set_cmake_build_type() for details.
+ if(DEFINED CACHE{CMAKE_BUILD_TYPE})
+ set(__qt_internal_standalone_project_cmake_build_type_before_project_call
+ "${CMAKE_BUILD_TYPE}")
+ endif()
+
project(QSQLiteDriverPlugins
VERSION "${QT_REPO_MODULE_VERSION}"
DESCRIPTION "Qt6 SQL driver plugins"
@@ -67,7 +72,10 @@ if(QT_FEATURE_sql_ibase)
add_subdirectory(ibase)
endif()
+if(QT_FEATURE_sql_mimer)
+ add_subdirectory(mimer)
+endif()
+
if(NOT CMAKE_PROJECT_NAME STREQUAL "QtBase" AND NOT CMAKE_PROJECT_NAME STREQUAL "Qt")
qt_print_feature_summary()
endif()
-# special case end
diff --git a/src/plugins/sqldrivers/configure.cmake b/src/plugins/sqldrivers/configure.cmake
index 4132196e6c..534ac020d8 100644
--- a/src/plugins/sqldrivers/configure.cmake
+++ b/src/plugins/sqldrivers/configure.cmake
@@ -20,6 +20,7 @@ qt_find_package(Oracle PROVIDED_TARGETS Oracle::OCI MODULE_NAME sqldrivers QMAKE
qt_find_package(ODBC PROVIDED_TARGETS ODBC::ODBC MODULE_NAME sqldrivers QMAKE_LIB odbc)
qt_find_package(SQLite3 PROVIDED_TARGETS SQLite::SQLite3 MODULE_NAME sqldrivers QMAKE_LIB sqlite3)
qt_find_package(Interbase PROVIDED_TARGETS Interbase::Interbase MODULE_NAME sqldrivers QMAKE_LIB ibase) # special case
+qt_find_package(Mimer PROVIDED_TARGETS MimerSQL::MimerSQL MODULE_NAME sqldrivers QMAKE_LIB mimer)
if(NOT WIN32 AND QT_FEATURE_system_zlib)
qt_add_qmake_lib_dependency(sqlite3 zlib)
endif()
@@ -64,6 +65,11 @@ qt_feature("system-sqlite" PRIVATE
AUTODETECT OFF
CONDITION QT_FEATURE_sql_sqlite AND SQLite3_FOUND
)
+qt_feature("sql-mimer" PRIVATE
+ LABEL "Mimer"
+ CONDITION Mimer_FOUND
+)
+
qt_configure_add_summary_section(NAME "Qt Sql Drivers")
qt_configure_add_summary_entry(ARGS "sql-db2")
qt_configure_add_summary_entry(ARGS "sql-ibase")
@@ -73,6 +79,7 @@ qt_configure_add_summary_entry(ARGS "sql-odbc")
qt_configure_add_summary_entry(ARGS "sql-psql")
qt_configure_add_summary_entry(ARGS "sql-sqlite")
qt_configure_add_summary_entry(ARGS "system-sqlite")
+qt_configure_add_summary_entry(ARGS "sql-mimer")
qt_configure_end_summary_section() # end of "Qt Sql Drivers" section
qt_configure_add_report_entry(
TYPE WARNING
diff --git a/src/plugins/sqldrivers/db2/CMakeLists.txt b/src/plugins/sqldrivers/db2/CMakeLists.txt
index 4f5680799f..119a5b53c9 100644
--- a/src/plugins/sqldrivers/db2/CMakeLists.txt
+++ b/src/plugins/sqldrivers/db2/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from db2.pro.
-
#####################################################################
## QDB2DriverPlugin Plugin:
#####################################################################
@@ -23,9 +21,6 @@ qt_internal_add_plugin(QDB2DriverPlugin
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:db2.pro:<TRUE>:
-# OTHER_FILES = "db2.json"
-
## Scopes:
#####################################################################
diff --git a/src/plugins/sqldrivers/db2/qsql_db2.cpp b/src/plugins/sqldrivers/db2/qsql_db2.cpp
index df2b35ecd1..9b6a06c378 100644
--- a/src/plugins/sqldrivers/db2/qsql_db2.cpp
+++ b/src/plugins/sqldrivers/db2/qsql_db2.cpp
@@ -14,6 +14,7 @@
#include <QDebug>
#include <QtSql/private/qsqldriver_p.h>
#include <QtSql/private/qsqlresult_p.h>
+#include "private/qtools_p.h"
#if defined(Q_CC_BOR)
// DB2's sqlsystm.h (included through sqlcli1.h) defines the SQL_BIGINT_TYPE
@@ -43,10 +44,13 @@ class QDB2DriverPrivate : public QSqlDriverPrivate
Q_DECLARE_PUBLIC(QDB2Driver)
public:
- QDB2DriverPrivate() : QSqlDriverPrivate(), hEnv(0), hDbc(0) { dbmsType = QSqlDriver::DB2; }
- SQLHANDLE hEnv;
- SQLHANDLE hDbc;
+ QDB2DriverPrivate() : QSqlDriverPrivate(QSqlDriver::DB2) {}
+ SQLHANDLE hEnv = 0;
+ SQLHANDLE hDbc = 0;
QString user;
+
+ void qSplitTableQualifier(const QString &qualifier, QString &catalog,
+ QString &schema, QString &table) const;
};
class QDB2ResultPrivate;
@@ -301,7 +305,6 @@ static QSqlField qMakeFieldInfo(const QDB2ResultPrivate* d, int i)
// else required is unknown
f.setLength(colSize == 0 ? -1 : int(colSize));
f.setPrecision(colScale == 0 ? -1 : int(colScale));
- f.setSqlType(int(colType));
SQLTCHAR tableName[TABLENAMESIZE];
SQLSMALLINT tableNameLen;
r = SQLColAttribute(d->hStmt, i + 1, SQL_DESC_BASE_TABLE_NAME, tableName,
@@ -461,34 +464,27 @@ static QByteArray qGetBinaryData(SQLHANDLE hStmt, int column, SQLLEN& lengthIndi
return fieldVal;
}
-static void qSplitTableQualifier(const QString & qualifier, QString * catalog,
- QString * schema, QString * table)
+void QDB2DriverPrivate::qSplitTableQualifier(const QString &qualifier, QString &catalog,
+ QString &schema, QString &table) const
{
- if (!catalog || !schema || !table)
- return;
- QStringList l = qualifier.split(u'.');
- if (l.count() > 3)
- return; // can't possibly be a valid table qualifier
- int i = 0, n = l.count();
- if (n == 1) {
- *table = qualifier;
- } else {
- for (QStringList::Iterator it = l.begin(); it != l.end(); ++it) {
- if (n == 3) {
- if (i == 0)
- *catalog = *it;
- else if (i == 1)
- *schema = *it;
- else if (i == 2)
- *table = *it;
- } else if (n == 2) {
- if (i == 0)
- *schema = *it;
- else if (i == 1)
- *table = *it;
- }
- i++;
- }
+ const QList<QStringView> l = QStringView(qualifier).split(u'.');
+ switch (l.count()) {
+ case 1:
+ table = qualifier;
+ break;
+ case 2:
+ schema = l.at(0).toString();
+ table = l.at(1).toString();
+ break;
+ case 3:
+ catalog = l.at(0).toString();
+ schema = l.at(1).toString();
+ table = l.at(2).toString();
+ break;
+ default:
+ qSqlWarning(QString::fromLatin1("QODBCDriver::splitTableQualifier: Unable to split table qualifier '%1'")
+ .arg(qualifier), this);
+ break;
}
}
@@ -508,7 +504,6 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt)
// else we don't know.
f.setLength(qGetIntData(hStmt, 6, isNull)); // column size
f.setPrecision(qGetIntData(hStmt, 8, isNull)); // precision
- f.setSqlType(type);
return f;
}
@@ -1353,7 +1348,7 @@ QSqlRecord QDB2Driver::record(const QString& tableName) const
SQLHANDLE hStmt;
QString catalog, schema, table;
- qSplitTableQualifier(tableName, &catalog, &schema, &table);
+ d->qSplitTableQualifier(tableName, catalog, schema, table);
if (schema.isEmpty())
schema = d->user;
@@ -1509,7 +1504,7 @@ QSqlIndex QDB2Driver::primaryIndex(const QString& tablename) const
return index;
}
QString catalog, schema, table;
- qSplitTableQualifier(tablename, &catalog, &schema, &table);
+ d->qSplitTableQualifier(tablename, catalog, schema, table);
if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
catalog = stripDelimiters(catalog, QSqlDriver::TableName);
@@ -1674,17 +1669,17 @@ QString QDB2Driver::formatValue(const QSqlField &field, bool trimStrings) const
}
}
case QMetaType::QByteArray: {
- QByteArray ba = field.value().toByteArray();
- QString res;
- res += "BLOB(X'"_L1;
- static const char hexchars[] = "0123456789abcdef";
- for (int i = 0; i < ba.size(); ++i) {
- uchar s = (uchar) ba[i];
- res += QLatin1Char(hexchars[s >> 4]);
- res += QLatin1Char(hexchars[s & 0x0f]);
+ const QByteArray ba = field.value().toByteArray();
+ QString r;
+ r.reserve(ba.size() * 2 + 9);
+ r += "BLOB(X'"_L1;
+ for (const char c : ba) {
+ const uchar s = uchar(c);
+ r += QLatin1Char(QtMiscUtils::toHexLower(s >> 4));
+ r += QLatin1Char(QtMiscUtils::toHexLower(s & 0x0f));
}
- res += "')"_L1;
- return res;
+ r += "')"_L1;
+ return r;
}
default:
return QSqlDriver::formatValue(field, trimStrings);
@@ -1702,10 +1697,12 @@ QString QDB2Driver::escapeIdentifier(const QString &identifier, IdentifierType)
QString res = identifier;
if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) {
res.replace(u'"', "\"\""_L1);
- res.prepend(u'"').append(u'"');
res.replace(u'.', "\".\""_L1);
+ res = u'"' + res + u'"';
}
return res;
}
QT_END_NAMESPACE
+
+#include "moc_qsql_db2_p.cpp"
diff --git a/src/plugins/sqldrivers/ibase/CMakeLists.txt b/src/plugins/sqldrivers/ibase/CMakeLists.txt
index a21be56b07..b8f2d2561f 100644
--- a/src/plugins/sqldrivers/ibase/CMakeLists.txt
+++ b/src/plugins/sqldrivers/ibase/CMakeLists.txt
@@ -10,6 +10,7 @@ qt_internal_add_plugin(QIBaseDriverPlugin
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
LIBRARIES
Interbase::Interbase
Qt::Core
diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
index d228628c08..28361e250d 100644
--- a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
+++ b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
@@ -4,9 +4,11 @@
#include "qsql_ibase_p.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdatetime.h>
+#include <QtCore/qtimezone.h>
#include <QtCore/qdeadlinetimer.h>
#include <QtCore/qdebug.h>
#include <QtCore/qlist.h>
+#include <QtCore/qloggingcategory.h>
#include <QtCore/qmap.h>
#include <QtCore/qmutex.h>
#include <QtCore/qvariant.h>
@@ -20,9 +22,12 @@
#include <stdlib.h>
#include <limits.h>
#include <math.h>
+#include <mutex>
QT_BEGIN_NAMESPACE
+static Q_LOGGING_CATEGORY(lcIbase, "qt.sql.ibase")
+
using namespace Qt::StringLiterals;
#define FBVERSION SQL_DIALECT_V6
@@ -36,7 +41,15 @@ using namespace Qt::StringLiterals;
#define blr_boolean_dtype blr_bool
#endif
-enum { QIBaseChunkSize = SHRT_MAX / 2 };
+constexpr qsizetype QIBaseChunkSize = SHRT_MAX / 2;
+
+#if (FB_API_VER >= 40)
+typedef QMap<quint16, QByteArray> QFbTzIdToIanaIdMap;
+typedef QMap<QByteArray, quint16> QIanaIdToFbTzIdMap;
+Q_GLOBAL_STATIC(QFbTzIdToIanaIdMap, qFbTzIdToIanaIdMap)
+Q_GLOBAL_STATIC(QIanaIdToFbTzIdMap, qIanaIdToFbTzIdMap)
+std::once_flag initTZMappingFlag;
+#endif
static bool getIBaseError(QString& msg, const ISC_STATUS* status, ISC_LONG &sqlcode)
{
@@ -85,6 +98,9 @@ static void initDA(XSQLDA *sqlda)
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_TIMESTAMP:
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+#endif
case SQL_TYPE_TIME:
case SQL_TYPE_DATE:
case SQL_TEXT:
@@ -122,7 +138,7 @@ static void delDA(XSQLDA *&sqlda)
delete [] sqlda->sqlvar[i].sqldata;
}
free(sqlda);
- sqlda = 0;
+ sqlda = nullptr;
}
static QMetaType::Type qIBaseTypeName(int iType, bool hasScale)
@@ -139,6 +155,9 @@ static QMetaType::Type qIBaseTypeName(int iType, bool hasScale)
case blr_sql_date:
return QMetaType::QDate;
case blr_timestamp:
+#if (FB_API_VER >= 40)
+ case blr_timestamp_tz:
+#endif
return QMetaType::QDateTime;
case blr_blob:
return QMetaType::QByteArray;
@@ -155,7 +174,7 @@ static QMetaType::Type qIBaseTypeName(int iType, bool hasScale)
case blr_boolean_dtype:
return QMetaType::Bool;
}
- qWarning("qIBaseTypeName: unknown datatype: %d", iType);
+ qCWarning(lcIbase, "qIBaseTypeName: unknown datatype: %d", iType);
return QMetaType::UnknownType;
}
@@ -174,6 +193,9 @@ static QMetaType::Type qIBaseTypeName2(int iType, bool hasScale)
case SQL_DOUBLE:
return QMetaType::Double;
case SQL_TIMESTAMP:
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+#endif
return QMetaType::QDateTime;
case SQL_TYPE_TIME:
return QMetaType::QTime;
@@ -208,12 +230,45 @@ static QDateTime fromTimeStamp(char *buffer)
// have to demangle the structure ourselves because isc_decode_time
// strips the msecs
- t = t.addMSecs(int(((ISC_TIMESTAMP*)buffer)->timestamp_time / 10));
- d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date));
-
+ auto timebuf = reinterpret_cast<ISC_TIMESTAMP*>(buffer);
+ t = t.addMSecs(static_cast<int>(timebuf->timestamp_time / 10));
+ d = bd.addDays(timebuf->timestamp_date);
return QDateTime(d, t);
}
+#if (FB_API_VER >= 40)
+QDateTime fromTimeStampTz(char *buffer)
+{
+ static const QDate bd(1858, 11, 17);
+ QTime t(0, 0);
+ QDate d;
+
+ // have to demangle the structure ourselves because isc_decode_time
+ // strips the msecs
+ auto timebuf = reinterpret_cast<ISC_TIMESTAMP_TZ*>(buffer);
+ t = t.addMSecs(static_cast<int>(timebuf->utc_timestamp.timestamp_time / 10));
+ d = bd.addDays(timebuf->utc_timestamp.timestamp_date);
+ quint16 fpTzID = timebuf->time_zone;
+
+ QByteArray timeZoneName = qFbTzIdToIanaIdMap()->value(fpTzID);
+ if (!timeZoneName.isEmpty())
+ return QDateTime(d, t, QTimeZone(timeZoneName));
+ else
+ return {};
+}
+
+ISC_TIMESTAMP_TZ toTimeStampTz(const QDateTime &dt)
+{
+ static const QTime midnight(0, 0, 0, 0);
+ static const QDate basedate(1858, 11, 17);
+ ISC_TIMESTAMP_TZ ts;
+ ts.utc_timestamp.timestamp_time = midnight.msecsTo(dt.time()) * 10;
+ ts.utc_timestamp.timestamp_date = basedate.daysTo(dt.date());
+ ts.time_zone = qIanaIdToFbTzIdMap()->value(dt.timeZone().id().simplified(), 0);
+ return ts;
+}
+#endif
+
static ISC_TIME toTime(QTime t)
{
static const QTime midnight(0, 0, 0, 0);
@@ -282,6 +337,34 @@ public:
return true;
}
+#if (FB_API_VER >= 40)
+ void initTZMappingCache()
+ {
+ Q_Q(QIBaseDriver);
+ QSqlQuery qry(q->createResult());
+ qry.setForwardOnly(true);
+ qry.exec(QString("select * from RDB$TIME_ZONES"_L1));
+ if (qry.lastError().type()) {
+ q->setLastError(QSqlError(
+ QCoreApplication::translate("QIBaseDriver",
+ "failed to query time zone mapping from system table"),
+ qry.lastError().databaseText(),
+ QSqlError::StatementError,
+ qry.lastError().nativeErrorCode()));
+
+ return;
+ }
+
+ while (qry.next()) {
+ auto record = qry.record();
+ quint16 fbTzId = record.value(0).value<quint16>();
+ QByteArray ianaId = record.value(1).toByteArray().simplified();
+ qFbTzIdToIanaIdMap()->insert(fbTzId, ianaId);
+ qIanaIdToFbTzIdMap()->insert(ianaId, fbTzId);
+ }
+ }
+#endif
+
public:
isc_db_handle ibase;
isc_tr_handle trans;
@@ -352,9 +435,9 @@ public:
bool isSelect();
QVariant fetchBlob(ISC_QUAD *bId);
- bool writeBlob(int i, const QByteArray &ba);
+ bool writeBlob(qsizetype iPos, const QByteArray &ba);
QVariant fetchArray(int pos, ISC_QUAD *arr);
- bool writeArray(int i, const QList<QVariant> &list);
+ bool writeArray(qsizetype i, const QList<QVariant> &list);
public:
ISC_STATUS status[20];
isc_tr_handle trans;
@@ -374,8 +457,8 @@ QIBaseResultPrivate::QIBaseResultPrivate(QIBaseResult *q, const QIBaseDriver *dr
localTransaction(!drv_d_func()->ibase),
stmt(0),
ibase(drv_d_func()->ibase),
- sqlda(0),
- inda(0),
+ sqlda(nullptr),
+ inda(nullptr),
queryType(-1)
{
}
@@ -399,20 +482,20 @@ void QIBaseResultPrivate::cleanup()
q->cleanup();
}
-bool QIBaseResultPrivate::writeBlob(int i, const QByteArray &ba)
+bool QIBaseResultPrivate::writeBlob(qsizetype iPos, const QByteArray &ba)
{
isc_blob_handle handle = 0;
- ISC_QUAD *bId = (ISC_QUAD*)inda->sqlvar[i].sqldata;
+ ISC_QUAD *bId = (ISC_QUAD*)inda->sqlvar[iPos].sqldata;
isc_create_blob2(status, &ibase, &trans, &handle, bId, 0, 0);
if (!isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to create BLOB"),
QSqlError::StatementError)) {
- int i = 0;
+ qsizetype i = 0;
while (i < ba.size()) {
- isc_put_segment(status, &handle, qMin(ba.size() - i, int(QIBaseChunkSize)),
- const_cast<char*>(ba.data()) + i);
+ isc_put_segment(status, &handle, qMin(ba.size() - i, QIBaseChunkSize),
+ ba.data() + i);
if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to write BLOB")))
return false;
- i += qMin(ba.size() - i, int(QIBaseChunkSize));
+ i += qMin(ba.size() - i, QIBaseChunkSize);
}
}
isc_close_blob(status, &handle);
@@ -432,7 +515,7 @@ QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId)
QByteArray ba;
int chunkSize = QIBaseChunkSize;
ba.resize(chunkSize);
- int read = 0;
+ qsizetype read = 0;
while (isc_get_segment(status, &handle, &len, chunkSize, ba.data() + read) == 0 || status[1] == isc_segment) {
read += len;
ba.resize(read + chunkSize);
@@ -454,7 +537,7 @@ QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId)
}
template<typename T>
-static QList<QVariant> toList(char** buf, int count, T* = nullptr)
+static QList<QVariant> toList(char** buf, int count)
{
QList<QVariant> res;
for (int i = 0; i < count; ++i) {
@@ -493,7 +576,7 @@ static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim,
}
break; }
case blr_long:
- valList = toList<int>(&buffer, numElements[dim], static_cast<int *>(0));
+ valList = toList<int>(&buffer, numElements[dim]);
break;
case blr_short:
valList = toList<short>(&buffer, numElements[dim]);
@@ -513,8 +596,16 @@ static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim,
buffer += sizeof(ISC_TIMESTAMP);
}
break;
+#if (FB_API_VER >= 40)
+ case blr_timestamp_tz:
+ for (int i = 0; i < numElements[dim]; ++i) {
+ valList.append(fromTimeStampTz(buffer));
+ buffer += sizeof(ISC_TIMESTAMP_TZ);
+ }
+ break;
+#endif
case blr_sql_time:
- for(int i = 0; i < numElements[dim]; ++i) {
+ for (int i = 0; i < numElements[dim]; ++i) {
valList.append(fromTime(buffer));
buffer += sizeof(ISC_TIME);
}
@@ -593,9 +684,8 @@ QVariant QIBaseResultPrivate::fetchArray(int pos, ISC_QUAD *arr)
template<typename T>
static char* fillList(char *buffer, const QList<QVariant> &list, T* = nullptr)
{
- for (int i = 0; i < list.size(); ++i) {
- T val;
- val = qvariant_cast<T>(list.at(i));
+ for (const auto &elem : list) {
+ T val = qvariant_cast<T>(elem);
memcpy(buffer, &val, sizeof(T));
buffer += sizeof(T);
}
@@ -605,11 +695,9 @@ static char* fillList(char *buffer, const QList<QVariant> &list, T* = nullptr)
template<>
char* fillList<float>(char *buffer, const QList<QVariant> &list, float*)
{
- for (int i = 0; i < list.size(); ++i) {
- double val;
- float val2 = 0;
- val = qvariant_cast<double>(list.at(i));
- val2 = (float)val;
+ for (const auto &elem : list) {
+ double val = qvariant_cast<double>(elem);
+ float val2 = (float)val;
memcpy(buffer, &val2, sizeof(float));
buffer += sizeof(float);
}
@@ -644,7 +732,6 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list,
QMetaType::Type type, short curDim, ISC_ARRAY_DESC *arrayDesc,
QString& error)
{
- int i;
ISC_ARRAY_BOUND *bounds = arrayDesc->array_desc_bounds;
short dim = arrayDesc->array_desc_dimensions - 1;
@@ -659,14 +746,14 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list,
}
if (curDim != dim) {
- for(i = 0; i < list.size(); ++i) {
+ for (const auto &elem : list) {
- if (list.at(i).typeId() != QMetaType::QVariantList) { // dimensions mismatch
+ if (elem.typeId() != QMetaType::QVariantList) { // dimensions mismatch
error = "Array dimensons mismatch. Fieldname: %1"_L1;
return 0;
}
- buffer = createArrayBuffer(buffer, list.at(i).toList(), type, curDim + 1,
+ buffer = createArrayBuffer(buffer, elem.toList(), type, curDim + 1,
arrayDesc, error);
if (!buffer)
return 0;
@@ -693,29 +780,40 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list,
buffer = fillList<quint64>(buffer, list);
break;
case QMetaType::QString:
- for (i = 0; i < list.size(); ++i)
- buffer = qFillBufferWithString(buffer, list.at(i).toString(),
+ for (const auto &elem : list)
+ buffer = qFillBufferWithString(buffer, elem.toString(),
arrayDesc->array_desc_length,
arrayDesc->array_desc_dtype == blr_varying,
true);
break;
case QMetaType::QDate:
- for (i = 0; i < list.size(); ++i) {
- *((ISC_DATE*)buffer) = toDate(list.at(i).toDate());
+ for (const auto &elem : list) {
+ *((ISC_DATE*)buffer) = toDate(elem.toDate());
buffer += sizeof(ISC_DATE);
}
break;
case QMetaType::QTime:
- for (i = 0; i < list.size(); ++i) {
- *((ISC_TIME*)buffer) = toTime(list.at(i).toTime());
+ for (const auto &elem : list) {
+ *((ISC_TIME*)buffer) = toTime(elem.toTime());
buffer += sizeof(ISC_TIME);
}
break;
-
case QMetaType::QDateTime:
- for (i = 0; i < list.size(); ++i) {
- *((ISC_TIMESTAMP*)buffer) = toTimeStamp(list.at(i).toDateTime());
- buffer += sizeof(ISC_TIMESTAMP);
+ for (const auto &elem : list) {
+ switch (arrayDesc->array_desc_dtype) {
+ case blr_timestamp:
+ *((ISC_TIMESTAMP*)buffer) = toTimeStamp(elem.toDateTime());
+ buffer += sizeof(ISC_TIMESTAMP);
+ break;
+#if (FB_API_VER >= 40)
+ case blr_timestamp_tz:
+ *((ISC_TIMESTAMP_TZ*)buffer) = toTimeStampTz(elem.toDateTime());
+ buffer += sizeof(ISC_TIMESTAMP_TZ);
+ break;
+#endif
+ default:
+ break;
+ }
}
break;
case QMetaType::Bool:
@@ -728,7 +826,7 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list,
return buffer;
}
-bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list)
+bool QIBaseResultPrivate::writeArray(qsizetype column, const QList<QVariant> &list)
{
Q_Q(QIBaseResult);
QString error;
@@ -745,7 +843,6 @@ bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list)
short arraySize = 1;
ISC_LONG bufLen;
- QList<QVariant> subList = list;
short dimensions = desc.array_desc_dimensions;
for(int i = 0; i < dimensions; ++i) {
@@ -847,7 +944,7 @@ QIBaseResult::QIBaseResult(const QIBaseDriver *db)
bool QIBaseResult::prepare(const QString& query)
{
Q_D(QIBaseResult);
-// qDebug("prepare: %s", qPrintable(query));
+// qDebug("prepare: %ls", qUtf16Printable(query));
if (!driver() || !driver()->isOpen() || driver()->isOpenError())
return false;
d->cleanup();
@@ -856,13 +953,13 @@ bool QIBaseResult::prepare(const QString& query)
createDA(d->sqlda);
if (d->sqlda == (XSQLDA*)0) {
- qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory";
+ qCWarning(lcIbase) << "QIOBaseResult: createDA(): failed to allocate memory";
return false;
}
createDA(d->inda);
if (d->inda == (XSQLDA*)0){
- qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory";
+ qCWarning(lcIbase) << "QIOBaseResult: createDA(): failed to allocate memory";
return false;
}
@@ -886,7 +983,7 @@ bool QIBaseResult::prepare(const QString& query)
if (d->inda->sqld > d->inda->sqln) {
enlargeDA(d->inda, d->inda->sqld);
if (d->inda == (XSQLDA*)0) {
- qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory";
+ qCWarning(lcIbase) << "QIOBaseResult: enlargeDA(): failed to allocate memory";
return false;
}
@@ -900,7 +997,7 @@ bool QIBaseResult::prepare(const QString& query)
// need more field descriptors
enlargeDA(d->sqlda, d->sqlda->sqld);
if (d->sqlda == (XSQLDA*)0) {
- qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory";
+ qCWarning(lcIbase) << "QIOBaseResult: enlargeDA(): failed to allocate memory";
return false;
}
@@ -920,7 +1017,6 @@ bool QIBaseResult::prepare(const QString& query)
return true;
}
-
bool QIBaseResult::exec()
{
Q_D(QIBaseResult);
@@ -936,20 +1032,17 @@ bool QIBaseResult::exec()
if (d->inda) {
const QList<QVariant> &values = boundValues();
- int i;
if (values.count() > d->inda->sqld) {
- qWarning() << "QIBaseResult::exec: Parameter mismatch, expected"_L1 <<
- d->inda->sqld << ", got"_L1 << values.count() <<
- "parameters"_L1;
+ qCWarning(lcIbase) << "QIBaseResult::exec: Parameter mismatch, expected"_L1
+ << d->inda->sqld << ", got"_L1 << values.count()
+ << "parameters"_L1;
return false;
}
- int para = 0;
- for (i = 0; i < values.count(); ++i) {
- para = i;
+ for (qsizetype para = 0; para < values.count(); ++para) {
if (!d->inda->sqlvar[para].sqldata)
// skip unknown datatypes
continue;
- const QVariant val(values[i]);
+ const QVariant &val = values[para];
if (d->inda->sqlvar[para].sqltype & 1) {
if (QSqlResultPrivate::isVariantNull(val)) {
// set null indicator
@@ -961,6 +1054,12 @@ bool QIBaseResult::exec()
}
// a value of 0 means non-null.
*(d->inda->sqlvar[para].sqlind) = 0;
+ } else {
+ if (QSqlResultPrivate::isVariantNull(val)) {
+ qCWarning(lcIbase) << "QIBaseResult::exec: Null value replaced by default (zero)"_L1
+ << "value for type of column"_L1 << d->inda->sqlvar[para].ownname
+ << ", which is not nullable."_L1;
+ }
}
switch(d->inda->sqlvar[para].sqltype & ~1) {
case SQL_INT64:
@@ -997,6 +1096,11 @@ bool QIBaseResult::exec()
case SQL_TIMESTAMP:
*((ISC_TIMESTAMP*)d->inda->sqlvar[para].sqldata) = toTimeStamp(val.toDateTime());
break;
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+ *((ISC_TIMESTAMP_TZ*)d->inda->sqlvar[para].sqldata) = toTimeStampTz(val.toDateTime());
+ break;
+#endif
case SQL_TYPE_TIME:
*((ISC_TIME*)d->inda->sqlvar[para].sqldata) = toTime(val.toTime());
break;
@@ -1019,8 +1123,8 @@ bool QIBaseResult::exec()
*((bool*)d->inda->sqlvar[para].sqldata) = val.toBool();
break;
default:
- qWarning("QIBaseResult::exec: Unknown datatype %d",
- d->inda->sqlvar[para].sqltype & ~1);
+ qCWarning(lcIbase, "QIBaseResult::exec: Unknown datatype %d",
+ d->inda->sqlvar[para].sqltype & ~1);
break;
}
}
@@ -1180,6 +1284,11 @@ bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx)
case SQL_BOOLEAN:
row[idx] = QVariant(bool((*(bool*)buf)));
break;
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+ row[idx] = fromTimeStampTz(buf);
+ break;
+#endif
default:
// unknown type - don't even try to fetch
row[idx] = QVariant();
@@ -1286,7 +1395,7 @@ int QIBaseResult::numRowsAffected()
bIsProcedure = true; // will sum all changes
break;
default:
- qWarning() << "numRowsAffected: Unknown statement type (" << d->queryType << ")";
+ qCWarning(lcIbase) << "numRowsAffected: Unknown statement type (" << d->queryType << ")";
return -1;
}
@@ -1352,7 +1461,6 @@ QSqlRecord QIBaseResult::record() const
f.setRequiredStatus(q.value(3).toBool() ? QSqlField::Required : QSqlField::Optional);
}
}
- f.setSqlType(v.sqltype);
rec.append(f);
}
return rec;
@@ -1408,26 +1516,26 @@ bool QIBaseDriver::hasFeature(DriverFeature f) const
return false;
}
-bool QIBaseDriver::open(const QString & db,
- const QString & user,
- const QString & password,
- const QString & host,
- int port,
- const QString & connOpts)
+bool QIBaseDriver::open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port,
+ const QString &connOpts)
{
Q_D(QIBaseDriver);
if (isOpen())
close();
- const QStringList opts(connOpts.split(u';', Qt::SkipEmptyParts));
+ const auto opts(QStringView(connOpts).split(u';', Qt::SkipEmptyParts));
QByteArray role;
- for (int i = 0; i < opts.count(); ++i) {
- QString tmp(opts.at(i).simplified());
+ for (const auto &opt : opts) {
+ const auto tmp(opt.trimmed());
qsizetype idx;
if ((idx = tmp.indexOf(u'=')) != -1) {
- QString val = tmp.mid(idx + 1).simplified();
- QString opt = tmp.left(idx).simplified();
+ const auto val = tmp.mid(idx + 1).trimmed();
+ const auto opt = tmp.left(idx).trimmed().toString();
if (opt.toUpper() == "ISC_DPB_SQL_ROLE_NAME"_L1) {
role = val.toLocal8Bit();
role.truncate(255);
@@ -1478,6 +1586,14 @@ bool QIBaseDriver::open(const QString & db,
setOpen(true);
setOpenError(false);
+#if (FB_API_VER >= 40)
+ std::call_once(initTZMappingFlag, [d](){ d->initTZMappingCache(); });
+ if (lastError().isValid())
+ {
+ setOpen(true);
+ return false;
+ }
+#endif
return true;
}
@@ -1496,13 +1612,6 @@ void QIBaseDriver::close()
qFreeEventBuffer(eBuffer);
}
d->eventBuffers.clear();
-
-#if defined(FB_API_VER)
- // TODO check whether this workaround for Firebird crash is still needed
- QDeadlineTimer timer(500);
- while (!timer.hasExpired())
- QCoreApplication::processEvents();
-#endif
}
isc_detach_database(d->status, &d->ibase);
@@ -1587,8 +1696,8 @@ QStringList QIBaseDriver::tables(QSql::TableType type) const
q.setForwardOnly(true);
if (!q.exec("select rdb$relation_name from rdb$relations "_L1 + typeFilter))
return res;
- while(q.next())
- res << q.value(0).toString().simplified();
+ while (q.next())
+ res << q.value(0).toString().simplified();
return res;
}
@@ -1599,13 +1708,9 @@ QSqlRecord QIBaseDriver::record(const QString& tablename) const
if (!isOpen())
return rec;
+ const QString table = stripDelimiters(tablename, QSqlDriver::TableName);
QSqlQuery q(createResult());
q.setForwardOnly(true);
- QString table = tablename;
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
- else
- table = table.toUpper();
q.exec("SELECT a.RDB$FIELD_NAME, b.RDB$FIELD_TYPE, b.RDB$FIELD_LENGTH, "
"b.RDB$FIELD_SCALE, b.RDB$FIELD_PRECISION, a.RDB$NULL_FLAG "
"FROM RDB$RELATION_FIELDS a, RDB$FIELDS b "
@@ -1625,7 +1730,6 @@ QSqlRecord QIBaseDriver::record(const QString& tablename) const
f.setPrecision(0);
}
f.setRequired(q.value(5).toInt() > 0);
- f.setSqlType(type);
rec.append(f);
}
@@ -1638,12 +1742,7 @@ QSqlIndex QIBaseDriver::primaryIndex(const QString &table) const
if (!isOpen())
return index;
- QString tablename = table;
- if (isIdentifierEscaped(tablename, QSqlDriver::TableName))
- tablename = stripDelimiters(tablename, QSqlDriver::TableName);
- else
- tablename = tablename.toUpper();
-
+ const QString tablename = stripDelimiters(table, QSqlDriver::TableName);
QSqlQuery q(createResult());
q.setForwardOnly(true);
q.exec("SELECT a.RDB$INDEX_NAME, b.RDB$FIELD_NAME, d.RDB$FIELD_TYPE, d.RDB$FIELD_SCALE "
@@ -1738,13 +1837,13 @@ bool QIBaseDriver::subscribeToNotification(const QString &name)
{
Q_D(QIBaseDriver);
if (!isOpen()) {
- qWarning("QIBaseDriver::subscribeFromNotificationImplementation: database not open.");
+ qCWarning(lcIbase, "QIBaseDriver::subscribeFromNotificationImplementation: database not open.");
return false;
}
if (d->eventBuffers.contains(name)) {
- qWarning("QIBaseDriver::subscribeToNotificationImplementation: already subscribing to '%s'.",
- qPrintable(name));
+ qCWarning(lcIbase, "QIBaseDriver::subscribeToNotificationImplementation: already subscribing to '%ls'.",
+ qUtf16Printable(name));
return false;
}
@@ -1785,13 +1884,13 @@ bool QIBaseDriver::unsubscribeFromNotification(const QString &name)
{
Q_D(QIBaseDriver);
if (!isOpen()) {
- qWarning("QIBaseDriver::unsubscribeFromNotificationImplementation: database not open.");
+ qCWarning(lcIbase, "QIBaseDriver::unsubscribeFromNotificationImplementation: database not open.");
return false;
}
if (!d->eventBuffers.contains(name)) {
- qWarning("QIBaseDriver::QIBaseSubscriptionState not subscribed to '%s'.",
- qPrintable(name));
+ qCWarning(lcIbase, "QIBaseDriver::QIBaseSubscriptionState not subscribed to '%ls'.",
+ qUtf16Printable(name));
return false;
}
@@ -1846,8 +1945,8 @@ void QIBaseDriver::qHandleEventNotification(void *updatedResultBuffer)
(&qEventCallback)),
eBuffer->resultBuffer);
if (Q_UNLIKELY(status[0] == 1 && status[1])) {
- qCritical("QIBaseDriver::qHandleEventNotification: could not resubscribe to '%s'",
- qPrintable(i.key()));
+ qCritical("QIBaseDriver::qHandleEventNotification: could not resubscribe to '%ls'",
+ qUtf16Printable(i.key()));
}
return;
@@ -1860,8 +1959,8 @@ QString QIBaseDriver::escapeIdentifier(const QString &identifier, IdentifierType
QString res = identifier;
if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) {
res.replace(u'"', "\"\""_L1);
- res.prepend(u'"').append(u'"');
res.replace(u'.', "\".\""_L1);
+ res = u'"' + res + u'"';
}
return res;
}
@@ -1873,3 +1972,5 @@ int QIBaseDriver::maximumIdentifierLength(IdentifierType type) const
}
QT_END_NAMESPACE
+
+#include "moc_qsql_ibase_p.cpp"
diff --git a/src/plugins/sqldrivers/mimer/CMakeLists.txt b/src/plugins/sqldrivers/mimer/CMakeLists.txt
new file mode 100644
index 0000000000..303af7120c
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Generated from mimer.pro.
+
+#####################################################################
+## MIMERSQLDriverPlugin Plugin:
+#####################################################################
+
+qt_internal_add_plugin(QMimerSQLDriverPlugin
+ OUTPUT_NAME qsqlmimer
+ PLUGIN_TYPE sqldrivers
+ SOURCES
+ main.cpp
+ qsql_mimer.cpp qsql_mimer.h
+ DEFINES
+ QT_NO_CAST_FROM_ASCII
+ QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
+ LIBRARIES
+ MimerSQL::MimerSQL
+ Qt::Core
+ Qt::SqlPrivate
+)
+
+#### Keys ignored in scope 1:.:.:mimer.pro:<TRUE>:
+# OTHER_FILES = "mimer.json"
diff --git a/src/plugins/sqldrivers/mimer/README b/src/plugins/sqldrivers/mimer/README
new file mode 100644
index 0000000000..02e4c3e162
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/README
@@ -0,0 +1,6 @@
+You will need the Mimer SQL development headers and libraries installed before
+compiling this plugin. qsql_mimer.h contains an include to mimerapi.h that is
+needed for the driver to compile.
+
+See the Qt SQL documentation for more information on compiling Qt SQL driver
+plugins.
diff --git a/src/plugins/sqldrivers/mimer/main.cpp b/src/plugins/sqldrivers/mimer/main.cpp
new file mode 100644
index 0000000000..560b7da7c7
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/main.cpp
@@ -0,0 +1,33 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2022 Mimer Information Technology
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qsql_mimer.h"
+
+#include <qsqldriverplugin.h>
+#include <qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+class QMimerSQLDriverPlugin : public QSqlDriverPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QSqlDriverFactoryInterface" FILE "mimer.json")
+public:
+ QMimerSQLDriverPlugin();
+ QSqlDriver *create(const QString &) override;
+};
+
+QMimerSQLDriverPlugin::QMimerSQLDriverPlugin() : QSqlDriverPlugin() { }
+
+QSqlDriver *QMimerSQLDriverPlugin::create(const QString &name)
+{
+ if (name == "QMIMER"_L1)
+ return new QMimerSQLDriver;
+ return nullptr;
+}
+
+QT_END_NAMESPACE
+
+#include "main.moc"
diff --git a/src/plugins/sqldrivers/mimer/mimer.json b/src/plugins/sqldrivers/mimer/mimer.json
new file mode 100644
index 0000000000..fba96b765d
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/mimer.json
@@ -0,0 +1,5 @@
+{
+ "Keys": [
+ "QMIMER"
+ ]
+}
diff --git a/src/plugins/sqldrivers/mimer/qsql_mimer.cpp b/src/plugins/sqldrivers/mimer/qsql_mimer.cpp
new file mode 100644
index 0000000000..7f89e0a0d5
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/qsql_mimer.cpp
@@ -0,0 +1,1610 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2022 Mimer Information Technology
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include <qcoreapplication.h>
+#include <qvariant.h>
+#include <qmetatype.h>
+#include <qdatetime.h>
+#include <qloggingcategory.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlrecord.h>
+#include <qsqlquery.h>
+#include <qsocketnotifier.h>
+#include <qstringlist.h>
+#include <qlocale.h>
+#if defined(Q_OS_WIN32)
+# include <QtCore/qt_windows.h>
+#endif
+#include <QtSql/private/qsqlresult_p.h>
+#include <QtSql/private/qsqldriver_p.h>
+#include "qsql_mimer.h"
+
+#define MIMER_DEFAULT_DATATYPE 1000
+
+Q_DECLARE_OPAQUE_POINTER(MimerSession)
+Q_DECLARE_METATYPE(MimerSession)
+
+Q_DECLARE_OPAQUE_POINTER(MimerStatement)
+Q_DECLARE_METATYPE(MimerStatement)
+
+QT_BEGIN_NAMESPACE
+
+static Q_LOGGING_CATEGORY(lcMimer, "qt.sql.mimer")
+
+enum class MimerColumnTypes {
+ Binary,
+ Clob,
+ Blob,
+ String,
+ Int,
+ Numeric,
+ Long,
+ Float,
+ Double,
+ Boolean,
+ Uuid,
+ Date,
+ Time,
+ Timestamp,
+ Unknown
+};
+
+using namespace Qt::StringLiterals;
+
+class QMimerSQLResultPrivate;
+
+class QMimerSQLResult final : public QSqlResult
+{
+ Q_DECLARE_PRIVATE(QMimerSQLResult)
+public:
+ QMimerSQLResult(const QMimerSQLDriver *db);
+ virtual ~QMimerSQLResult() override;
+ QVariant handle() const override;
+ static constexpr int genericError = -1;
+ static constexpr int lobChunkMaxSizeSet = 1048500;
+ static constexpr int lobChunkMaxSizeFetch = 65536;
+ static constexpr int maxStackStringSize = 200;
+ static constexpr int maxTimeStringSize = 18;
+ static constexpr int maxDateStringSize = 10;
+ static constexpr int maxTimestampStringSize = 29;
+
+private:
+ void cleanup();
+ bool fetch(int i) override;
+ bool fetchFirst() override;
+ bool fetchLast() override;
+ bool fetchNext() override;
+ QVariant data(int i) override;
+ bool isNull(int index) override;
+ bool reset(const QString &query) override;
+ int size() override;
+ int numRowsAffected() override;
+ QSqlRecord record() const override;
+ bool prepare(const QString &query) override;
+ bool execBatch(bool arrayBind = false) override;
+ bool exec() override;
+ qint64 currentRow();
+ QVariant lastInsertId() const override;
+};
+
+class QMimerSQLDriverPrivate final : public QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QMimerSQLDriver)
+public:
+ QMimerSQLDriverPrivate() : QSqlDriverPrivate(QSqlDriver::MimerSQL), sessionhandle(nullptr) { }
+ MimerSession sessionhandle;
+ QString dbName;
+ QString dbUser;
+ void splitTableQualifier(const QString &qualifier, QString *schema, QString *table) const;
+};
+
+class QMimerSQLResultPrivate : public QSqlResultPrivate
+{
+ Q_DECLARE_PUBLIC(QMimerSQLResult)
+public:
+ Q_DECLARE_SQLDRIVER_PRIVATE(QMimerSQLDriver)
+ QMimerSQLResultPrivate(QMimerSQLResult *q, const QMimerSQLDriver *drv)
+ : QSqlResultPrivate(q, drv),
+ statementhandle(nullptr),
+ lobhandle(nullptr),
+ rowsAffected(0),
+ preparedQuery(false),
+ openCursor(false),
+ openStatement(false),
+ executedStatement(false),
+ callWithOut(false),
+ execBatch(false),
+ currentRow(QSql::BeforeFirstRow)
+ {
+ }
+ MimerStatement statementhandle;
+ MimerLob lobhandle;
+ int rowsAffected;
+ bool preparedQuery;
+ bool openCursor;
+ bool openStatement;
+ bool executedStatement;
+ bool callWithOut;
+ bool execBatch;
+ qint64 currentSize = -1;
+ qint64 currentRow; // Only used when forwardOnly()
+ QVector<QVariant> batch_vector;
+};
+
+static QSqlError qMakeError(const QString &err, const int errCode, QSqlError::ErrorType type,
+ const QMimerSQLDriverPrivate *p)
+{
+ QString msg;
+ if (p) {
+ size_t str_len;
+ int e_code;
+ int rc;
+ str_len = (rc = MimerGetError(p->sessionhandle, &e_code, NULL, 0)) + 1;
+ if (!MIMER_SUCCEEDED(rc)) {
+ msg = QCoreApplication::translate("QMimerSQL", "No Mimer SQL error for code %1")
+ .arg(errCode);
+ } else {
+ QVarLengthArray<wchar_t> tmp_buff((qsizetype)str_len);
+ if (!MIMER_SUCCEEDED(
+ rc = MimerGetError(p->sessionhandle, &e_code, tmp_buff.data(), str_len)))
+ msg = QCoreApplication::translate("QMimerSQL", "No Mimer SQL error for code %1")
+ .arg(errCode);
+ else
+ msg = QString::fromWCharArray(tmp_buff.data());
+ }
+ } else {
+ msg = QCoreApplication::translate("QMimerSQL", "Generic Mimer SQL error");
+ }
+
+ return QSqlError("QMIMER: "_L1 + err, msg, type, QString::number(errCode));
+}
+
+static QString msgCouldNotGet(const char *type, int column)
+{
+ //: Data type, column
+ return QCoreApplication::translate("QMimerSQLResult",
+ "Could not get %1, column %2").arg(QLatin1StringView(type)).arg(column);
+}
+
+static QString msgCouldNotSet(const char *type, int column)
+{
+ //: Data type, parameter
+ return QCoreApplication::translate("QMimerSQLResult",
+ "Could not set %1, parameter %2").arg(QLatin1StringView(type)).arg(column);
+}
+
+QMimerSQLDriver::QMimerSQLDriver(QObject *parent) : QSqlDriver(*new QMimerSQLDriverPrivate, parent)
+{
+}
+
+QMimerSQLDriver::QMimerSQLDriver(MimerSession *conn, QObject *parent)
+ : QSqlDriver(*new QMimerSQLDriverPrivate, parent)
+{
+ Q_D(QMimerSQLDriver);
+ if (conn)
+ d->sessionhandle = *conn;
+}
+
+QMimerSQLDriver::~QMimerSQLDriver()
+{
+ close();
+}
+
+QMimerSQLResult::QMimerSQLResult(const QMimerSQLDriver *db)
+ : QSqlResult(*new QMimerSQLResultPrivate(this, db))
+{
+ Q_D(QMimerSQLResult);
+ d->preparedQuery = db->hasFeature(QSqlDriver::PreparedQueries);
+}
+
+QMimerSQLResult::~QMimerSQLResult()
+{
+ cleanup();
+}
+
+static MimerColumnTypes mimerMapColumnTypes(int32_t t)
+{
+ switch (t) {
+ case MIMER_BINARY:
+ case MIMER_BINARY_VARYING:
+ return MimerColumnTypes::Binary;
+ case MIMER_BLOB:
+ case MIMER_NATIVE_BLOB:
+ return MimerColumnTypes::Blob;
+ case MIMER_CLOB:
+ case MIMER_NCLOB:
+ case MIMER_NATIVE_CLOB:
+ case MIMER_NATIVE_NCLOB:
+ return MimerColumnTypes::Clob;
+ case MIMER_DATE:
+ return MimerColumnTypes::Date;
+ case MIMER_TIME:
+ return MimerColumnTypes::Time;
+ case MIMER_TIMESTAMP:
+ return MimerColumnTypes::Timestamp;
+ case MIMER_INTERVAL_DAY:
+ case MIMER_INTERVAL_DAY_TO_HOUR:
+ case MIMER_INTERVAL_DAY_TO_MINUTE:
+ case MIMER_INTERVAL_DAY_TO_SECOND:
+ case MIMER_INTERVAL_HOUR:
+ case MIMER_INTERVAL_HOUR_TO_MINUTE:
+ case MIMER_INTERVAL_HOUR_TO_SECOND:
+ case MIMER_INTERVAL_MINUTE:
+ case MIMER_INTERVAL_MINUTE_TO_SECOND:
+ case MIMER_INTERVAL_MONTH:
+ case MIMER_INTERVAL_SECOND:
+ case MIMER_INTERVAL_YEAR:
+ case MIMER_INTERVAL_YEAR_TO_MONTH:
+ case MIMER_NCHAR:
+ case MIMER_CHARACTER:
+ case MIMER_CHARACTER_VARYING:
+ case MIMER_NCHAR_VARYING:
+ case MIMER_UTF8:
+ case MIMER_DEFAULT_DATATYPE:
+ return MimerColumnTypes::String;
+ case MIMER_INTEGER:
+ case MIMER_DECIMAL:
+ case MIMER_FLOAT:
+ return MimerColumnTypes::Numeric;
+ case MIMER_BOOLEAN:
+ return MimerColumnTypes::Boolean;
+ case MIMER_T_BIGINT:
+ case MIMER_T_UNSIGNED_BIGINT:
+ case MIMER_NATIVE_BIGINT_NULLABLE:
+ case MIMER_NATIVE_BIGINT:
+ return MimerColumnTypes::Long;
+ case MIMER_NATIVE_REAL_NULLABLE:
+ case MIMER_NATIVE_REAL:
+ case MIMER_T_REAL:
+ return MimerColumnTypes::Float;
+ case MIMER_T_FLOAT:
+ case MIMER_NATIVE_DOUBLE_NULLABLE:
+ case MIMER_NATIVE_DOUBLE:
+ case MIMER_T_DOUBLE:
+ return MimerColumnTypes::Double;
+ case MIMER_NATIVE_INTEGER:
+ case MIMER_NATIVE_INTEGER_NULLABLE:
+ case MIMER_NATIVE_SMALLINT_NULLABLE:
+ case MIMER_NATIVE_SMALLINT:
+ case MIMER_T_INTEGER:
+ case MIMER_T_SMALLINT:
+ return MimerColumnTypes::Int;
+ case MIMER_UUID:
+ return MimerColumnTypes::Uuid;
+ default:
+ qCWarning(lcMimer) << "QMimerSQLDriver::mimerMapColumnTypes: Unknown data type:" << t;
+ }
+ return MimerColumnTypes::Unknown;
+}
+
+static QMetaType::Type qDecodeMSQLType(int32_t t)
+{
+ switch (t) {
+ case MIMER_BINARY:
+ case MIMER_BINARY_VARYING:
+ case MIMER_BLOB:
+ case MIMER_NATIVE_BLOB:
+ return QMetaType::QByteArray;
+ case MIMER_CLOB:
+ case MIMER_NCLOB:
+ case MIMER_NATIVE_CLOB:
+ case MIMER_NATIVE_NCLOB:
+ case MIMER_INTERVAL_DAY:
+ case MIMER_DECIMAL:
+ case MIMER_INTERVAL_DAY_TO_HOUR:
+ case MIMER_INTERVAL_DAY_TO_MINUTE:
+ case MIMER_INTERVAL_DAY_TO_SECOND:
+ case MIMER_INTERVAL_HOUR:
+ case MIMER_INTERVAL_HOUR_TO_MINUTE:
+ case MIMER_INTERVAL_HOUR_TO_SECOND:
+ case MIMER_INTERVAL_MINUTE:
+ case MIMER_INTERVAL_MINUTE_TO_SECOND:
+ case MIMER_INTERVAL_MONTH:
+ case MIMER_INTERVAL_SECOND:
+ case MIMER_INTERVAL_YEAR:
+ case MIMER_INTERVAL_YEAR_TO_MONTH:
+ case MIMER_NCHAR:
+ case MIMER_CHARACTER:
+ case MIMER_CHARACTER_VARYING:
+ case MIMER_NCHAR_VARYING:
+ case MIMER_UTF8:
+ case MIMER_DEFAULT_DATATYPE:
+ case MIMER_INTEGER:
+ case MIMER_FLOAT:
+ return QMetaType::QString;
+ case MIMER_BOOLEAN:
+ return QMetaType::Bool;
+ case MIMER_T_BIGINT:
+ case MIMER_T_UNSIGNED_BIGINT:
+ case MIMER_NATIVE_BIGINT_NULLABLE:
+ case MIMER_NATIVE_BIGINT:
+ return QMetaType::LongLong;
+ case MIMER_NATIVE_REAL_NULLABLE:
+ case MIMER_NATIVE_REAL:
+ case MIMER_T_REAL:
+ return QMetaType::Float;
+ case MIMER_T_FLOAT:
+ case MIMER_NATIVE_DOUBLE_NULLABLE:
+ case MIMER_NATIVE_DOUBLE:
+ case MIMER_T_DOUBLE:
+ return QMetaType::Double;
+ case MIMER_NATIVE_INTEGER_NULLABLE:
+ case MIMER_T_INTEGER:
+ case MIMER_NATIVE_INTEGER:
+ return QMetaType::Int;
+ case MIMER_NATIVE_SMALLINT_NULLABLE:
+ case MIMER_T_SMALLINT:
+ return QMetaType::Int;
+ case MIMER_DATE:
+ return QMetaType::QDate;
+ case MIMER_TIME:
+ return QMetaType::QTime;
+ break;
+ case MIMER_TIMESTAMP:
+ return QMetaType::QDateTime;
+ case MIMER_UUID:
+ return QMetaType::QUuid;
+ default:
+ qCWarning(lcMimer) << "QMimerSQLDriver::qDecodeMSQLType: Unknown data type:" << t;
+ return QMetaType::UnknownType;
+ }
+}
+
+static int32_t qLookupMimDataType(QStringView s)
+{
+ if (s == u"BINARY")
+ return MIMER_BINARY;
+ if (s == u"BINARY VARYING")
+ return MIMER_BINARY_VARYING;
+ if (s == u"BINARY LARGE OBJECT")
+ return MIMER_BLOB;
+ if (s == u"CHARACTER LARGE OBJECT")
+ return MIMER_CLOB;
+ if (s == u"NATIONAL CHAR LARGE OBJECT")
+ return MIMER_NCLOB;
+ if (s == u"INTERVAL DAY")
+ return MIMER_INTERVAL_DAY;
+ if (s == u"DECIMAL")
+ return MIMER_DECIMAL;
+ if (s == u"INTERVAL DAY TO HOUR")
+ return MIMER_INTERVAL_DAY_TO_HOUR;
+ if (s == u"INTERVAL DAY TO MINUTE")
+ return MIMER_INTERVAL_DAY_TO_MINUTE;
+ if (s == u"INTERVAL DAY TO SECOND")
+ return MIMER_INTERVAL_DAY_TO_SECOND;
+ if (s == u"INTERVAL HOUR")
+ return MIMER_INTERVAL_HOUR;
+ if (s == u"INTERVAL HOUR TO MINUTE")
+ return MIMER_INTERVAL_HOUR_TO_MINUTE;
+ if (s == u"INTERVAL HOUR TO SECOND")
+ return MIMER_INTERVAL_HOUR_TO_SECOND;
+ if (s == u"INTERVAL MINUTE")
+ return MIMER_INTERVAL_MINUTE;
+ if (s == u"INTERVAL MINUTE TO SECOND")
+ return MIMER_INTERVAL_MINUTE_TO_SECOND;
+ if (s == u"INTERVAL MONTH")
+ return MIMER_INTERVAL_MONTH;
+ if (s == u"INTERVAL SECOND")
+ return MIMER_INTERVAL_SECOND;
+ if (s == u"INTERVAL YEAR")
+ return MIMER_INTERVAL_YEAR;
+ if (s == u"INTERVAL YEAR TO MONTH")
+ return MIMER_INTERVAL_YEAR_TO_MONTH;
+ if (s == u"NATIONAL CHARACTER")
+ return MIMER_NCHAR;
+ if (s == u"CHARACTER")
+ return MIMER_CHARACTER;
+ if (s == u"CHARACTER VARYING")
+ return MIMER_CHARACTER_VARYING;
+ if (s == u"NATIONAL CHARACTER VARYING")
+ return MIMER_NCHAR_VARYING;
+ if (s == u"UTF-8")
+ return MIMER_UTF8;
+ if (s == u"BOOLEAN")
+ return MIMER_BOOLEAN;
+ if (s == u"BIGINT")
+ return MIMER_T_BIGINT;
+ if (s == u"REAL")
+ return MIMER_T_REAL;
+ if (s == u"FLOAT")
+ return MIMER_T_FLOAT;
+ if (s == u"DOUBLE PRECISION")
+ return MIMER_T_DOUBLE;
+ if (s == u"INTEGER")
+ return MIMER_T_INTEGER;
+ if (s == u"SMALLINT")
+ return MIMER_T_SMALLINT;
+ if (s == u"DATE")
+ return MIMER_DATE;
+ if (s == u"TIME")
+ return MIMER_TIME;
+ if (s == u"TIMESTAMP")
+ return MIMER_TIMESTAMP;
+ if (s == u"BUILTIN.UUID")
+ return MIMER_UUID;
+ if (s == u"USER-DEFINED")
+ return MIMER_DEFAULT_DATATYPE;
+ qCWarning(lcMimer) << "QMimerSQLDriver::qLookupMimDataType: Unhandled data type:" << s;
+ return MIMER_DEFAULT_DATATYPE;
+}
+
+QVariant QMimerSQLResult::handle() const
+{
+ Q_D(const QMimerSQLResult);
+ return QVariant::fromValue(d->statementhandle);
+}
+
+void QMimerSQLResult::cleanup()
+{
+ Q_D(QMimerSQLResult);
+ if (!driver() || !driver()->isOpen()) {
+ d->openCursor = false;
+ d->openStatement = false;
+ return;
+ }
+ if (d->openCursor) {
+ const int32_t err = MimerCloseCursor(d->statementhandle);
+ if (!MIMER_SUCCEEDED(err))
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Could not close cursor"), err,
+ QSqlError::StatementError, d->drv_d_func()));
+ d->openCursor = false;
+ }
+ if (d->openStatement) {
+ const int32_t err = MimerEndStatement(&d->statementhandle);
+ if (!MIMER_SUCCEEDED(err))
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Could not close statement"),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ d->openStatement = false;
+ }
+ d->currentSize = -1;
+}
+
+qint64 QMimerSQLResult::currentRow()
+{
+ Q_D(const QMimerSQLResult);
+ return d->currentRow;
+}
+
+bool QMimerSQLResult::fetch(int i)
+{
+ Q_D(const QMimerSQLResult);
+ int32_t err = 0;
+ if (!isActive() || !isSelect())
+ return false;
+ if (i == at())
+ return true;
+ if (i < 0)
+ return false;
+
+ if (isForwardOnly() && i < at())
+ return false;
+
+ if (isForwardOnly()) {
+ bool rc;
+ do {
+ rc = fetchNext();
+ } while (rc && currentRow() < i);
+ return rc;
+ } else {
+ err = MimerFetchScroll(d->statementhandle, MIMER_ABSOLUTE, i + 1);
+ if (err == MIMER_NO_DATA)
+ return false;
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(QCoreApplication::translate("QMimerSQLResult", "Fetch did not succeed"),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ setAt(MimerCurrentRow(d->statementhandle) - 1);
+ return true;
+}
+
+bool QMimerSQLResult::fetchFirst()
+{
+ Q_D(const QMimerSQLResult);
+ int32_t err = 0;
+ if (!isActive() || !isSelect())
+ return false;
+ if (isForwardOnly()) {
+ if (currentRow() < 0)
+ return fetchNext();
+ else if (currentRow() == 0)
+ setAt(0);
+ else
+ return false;
+ } else {
+ err = MimerFetchScroll(d->statementhandle, MIMER_FIRST, 0);
+ if (MIMER_SUCCEEDED(err) && err != MIMER_NO_DATA)
+ setAt(0);
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Fetch first did not succeed"), err,
+ QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ if (err == MIMER_NO_DATA)
+ return false;
+ return true;
+}
+
+bool QMimerSQLResult::fetchLast()
+{
+ Q_D(const QMimerSQLResult);
+ int32_t err = 0;
+ int row = 0;
+ if (!isActive() || !isSelect())
+ return false;
+ if (isForwardOnly()) {
+ bool rc;
+ do {
+ rc = fetchNext();
+ } while (rc);
+
+ return currentRow() >= 0;
+ } else {
+ err = MimerFetchScroll(d->statementhandle, static_cast<std::int32_t>(MIMER_LAST), 0);
+ if (err == MIMER_NO_DATA)
+ return false;
+ if (MIMER_SUCCEEDED(err)) {
+ row = MimerCurrentRow(d->statementhandle) - 1;
+ } else {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult:", "Fetch last did not succeed"),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ }
+
+ if (row < 0) {
+ setAt(QSql::BeforeFirstRow);
+ return false;
+ } else {
+ setAt(row);
+ return true;
+ }
+}
+
+bool QMimerSQLResult::fetchNext()
+{
+ Q_D(QMimerSQLResult);
+ int32_t err = 0;
+ if (!isActive() || !isSelect())
+ return false;
+ if (isForwardOnly())
+ err = MimerFetch(d->statementhandle);
+ else
+ err = MimerFetchScroll(d->statementhandle, MIMER_NEXT, 0);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Could not fetch next row"), err,
+ QSqlError::StatementError, d->drv_d_func()));
+ if (isForwardOnly())
+ d->currentRow = QSql::BeforeFirstRow;
+ return false;
+ }
+ if (err == MIMER_NO_DATA)
+ return false;
+ if (isForwardOnly())
+ setAt(++d->currentRow);
+ else
+ setAt(MimerCurrentRow(d->statementhandle) - 1);
+ return true;
+}
+
+QVariant QMimerSQLResult::data(int i)
+{
+ Q_D(QMimerSQLResult);
+ int32_t err;
+ int32_t mType;
+ if (d->callWithOut) {
+ if (i >= MimerParameterCount(d->statementhandle)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult:", "Column %1 out of range")
+ .arg(i),
+ genericError, QSqlError::StatementError, nullptr));
+ return QVariant();
+ }
+ mType = MimerParameterType(d->statementhandle, static_cast<std::int16_t>(i + 1));
+ } else {
+ if (i >= MimerColumnCount(d->statementhandle)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult:", "Column %1 out of range")
+ .arg(i),
+ genericError, QSqlError::StatementError, nullptr));
+ return QVariant();
+ }
+ mType = MimerColumnType(d->statementhandle, static_cast<std::int16_t>(i + 1));
+ }
+ const QMetaType::Type type = qDecodeMSQLType(mType);
+ const MimerColumnTypes mimDataType = mimerMapColumnTypes(mType);
+ err = MimerIsNull(d->statementhandle, static_cast<std::int16_t>(i + 1));
+ if (err > 0) {
+ return QVariant(QMetaType(type), nullptr);
+ } else {
+ switch (mimDataType) {
+ case MimerColumnTypes::Date: {
+ wchar_t dateString_w[maxDateStringSize + 1];
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), dateString_w,
+ sizeof(dateString_w) / sizeof(dateString_w[0]));
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("date", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return QDate::fromString(QString::fromWCharArray(dateString_w), "yyyy-MM-dd"_L1);
+ }
+ case MimerColumnTypes::Time: {
+ wchar_t timeString_w[maxTimeStringSize + 1];
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), timeString_w,
+ sizeof(timeString_w) / sizeof(timeString_w[0]));
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("time", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ QString timeString = QString::fromWCharArray(timeString_w);
+ QString timeFormatString = "HH:mm:ss"_L1;
+ if (timeString.size() > 8) {
+ timeFormatString.append(".zzz"_L1);
+ timeString = timeString.left(12);
+ }
+ return QTime::fromString(timeString, timeFormatString);
+ }
+ case MimerColumnTypes::Timestamp: {
+ wchar_t dateTimeString_w[maxTimestampStringSize + 1];
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1),
+ dateTimeString_w,
+ sizeof(dateTimeString_w) / sizeof(dateTimeString_w[0]));
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotGet("date time", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ QString dateTimeString = QString::fromWCharArray(dateTimeString_w);
+ QString dateTimeFormatString = "yyyy-MM-dd HH:mm:ss"_L1;
+ if (dateTimeString.size() > 19) {
+ dateTimeFormatString.append(".zzz"_L1);
+ dateTimeString = dateTimeString.left(23);
+ }
+ return QDateTime::fromString(dateTimeString, dateTimeFormatString);
+ }
+ case MimerColumnTypes::Int: {
+ int resInt;
+ err = MimerGetInt32(d->statementhandle, static_cast<std::int16_t>(i + 1), &resInt);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("int32", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return resInt;
+ }
+ case MimerColumnTypes::Long: {
+ int64_t resLongLong;
+ err = MimerGetInt64(d->statementhandle, static_cast<std::int16_t>(i + 1), &resLongLong);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("int64", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return (qlonglong)resLongLong;
+ }
+ case MimerColumnTypes::Boolean: {
+ err = MimerGetBoolean(d->statementhandle, static_cast<std::int16_t>(i + 1));
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotGet("boolean", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return err == 1;
+ }
+ case MimerColumnTypes::Float: {
+ float resFloat;
+ err = MimerGetFloat(d->statementhandle, static_cast<std::int16_t>(i + 1), &resFloat);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("float", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return resFloat;
+ }
+ case MimerColumnTypes::Double: {
+ double resDouble;
+ err = MimerGetDouble(d->statementhandle, static_cast<std::int16_t>(i + 1), &resDouble);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotGet("double", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ switch (numericalPrecisionPolicy()) {
+ case QSql::LowPrecisionInt32:
+ return static_cast<std::int32_t>(resDouble);
+ case QSql::LowPrecisionInt64:
+ return static_cast<qint64>(resDouble);
+ case QSql::LowPrecisionDouble:
+ return static_cast<qreal>(resDouble);
+ case QSql::HighPrecision:
+ return QString::number(resDouble, 'g', 17);
+ }
+ return QVariant(QMetaType(type), nullptr);
+ }
+ case MimerColumnTypes::Binary: {
+ QByteArray byteArray;
+ // Get size
+ err = MimerGetBinary(d->statementhandle, static_cast<std::int16_t>(i + 1), NULL, 0);
+ if (MIMER_SUCCEEDED(err)) {
+ byteArray.resize(err);
+ err = MimerGetBinary(d->statementhandle, static_cast<std::int16_t>(i + 1),
+ byteArray.data(), err);
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotGet("binary", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return byteArray;
+ }
+ case MimerColumnTypes::Blob: {
+ QByteArray byteArray;
+ size_t size;
+ err = MimerGetLob(d->statementhandle, static_cast<std::int16_t>(i + 1), &size,
+ &d->lobhandle);
+ if (MIMER_SUCCEEDED(err)) {
+ constexpr size_t maxSize = lobChunkMaxSizeFetch;
+ QVarLengthArray<char> blobchar(lobChunkMaxSizeFetch);
+ byteArray.reserve(size);
+ size_t left_to_return = size;
+ while (left_to_return > 0) {
+ const size_t bytesToReceive =
+ left_to_return <= maxSize ? left_to_return : maxSize;
+ err = MimerGetBlobData(&d->lobhandle, blobchar.data(), bytesToReceive);
+ byteArray.append(QByteArray::fromRawData(blobchar.data(), bytesToReceive));
+ left_to_return -= bytesToReceive;
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("BLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ }
+ } else {
+ setLastError(qMakeError(msgCouldNotGet("BLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return byteArray;
+ }
+ case MimerColumnTypes::Numeric:
+ case MimerColumnTypes::String: {
+ wchar_t resString_w[maxStackStringSize + 1];
+ // Get size
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), resString_w,
+ 0);
+ if (MIMER_SUCCEEDED(err)) {
+ int size = err;
+ if (err <= maxStackStringSize) { // For smaller strings, use a small buffer for
+ // efficiency
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1),
+ resString_w, maxStackStringSize + 1);
+ if (MIMER_SUCCEEDED(err))
+ return QString::fromWCharArray(resString_w);
+ } else { // For larger strings, dynamically allocate memory
+ QVarLengthArray<wchar_t> largeResString_w(size + 1);
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1),
+ largeResString_w.data(), size + 1);
+ if (MIMER_SUCCEEDED(err))
+ return QString::fromWCharArray(largeResString_w.data());
+ }
+ }
+ setLastError(qMakeError(msgCouldNotGet(
+ mimDataType == MimerColumnTypes::Numeric ? "numeric" : "string", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ case MimerColumnTypes::Clob: {
+ size_t size;
+ err = MimerGetLob(d->statementhandle, static_cast<std::int16_t>(i + 1), &size,
+ &d->lobhandle);
+ if (MIMER_SUCCEEDED(err)) {
+ constexpr size_t maxSize = lobChunkMaxSizeFetch;
+ QVarLengthArray<wchar_t> clobstring_w(lobChunkMaxSizeFetch + 1);
+
+ size_t left_to_return = size;
+ QString returnString;
+ while (left_to_return > 0) {
+ const size_t bytesToReceive =
+ left_to_return <= maxSize ? left_to_return : maxSize;
+ err = MimerGetNclobData(&d->lobhandle, clobstring_w.data(), bytesToReceive + 1);
+ returnString.append(QString::fromWCharArray(clobstring_w.data()));
+ left_to_return -= bytesToReceive;
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("CLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ }
+ return returnString;
+ }
+ setLastError(qMakeError(msgCouldNotGet("CLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ case MimerColumnTypes::Uuid: {
+ unsigned char uuidChar[16];
+ err = MimerGetUUID(d->statementhandle, static_cast<std::int16_t>(i + 1), uuidChar);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("UUID", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ const QByteArray uuidByteArray = QByteArray(reinterpret_cast<char *>(uuidChar), 16);
+ return QUuid::fromRfc4122(uuidByteArray);
+ }
+ case MimerColumnTypes::Unknown:
+ default:
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Unknown data type %1").arg(i),
+ genericError, QSqlError::StatementError, nullptr));
+ }
+ return QVariant(QMetaType(type), nullptr);
+ }
+}
+
+bool QMimerSQLResult::isNull(int index)
+{
+ Q_D(const QMimerSQLResult);
+ const int32_t rc = MimerIsNull(d->statementhandle, static_cast<std::int16_t>(index + 1));
+ if (!MIMER_SUCCEEDED(rc)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Could not check null, column %1")
+ .arg(index),
+ rc, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ return rc != 0;
+}
+
+bool QMimerSQLResult::reset(const QString &query)
+{
+ if (!prepare(query))
+ return false;
+ return exec();
+}
+
+int QMimerSQLResult::size()
+{
+ Q_D(QMimerSQLResult);
+ if (!isActive() || !isSelect() || isForwardOnly())
+ return -1;
+
+ if (d->currentSize != -1)
+ return d->currentSize;
+
+ const int currentRow = MimerCurrentRow(d->statementhandle);
+ MimerFetchScroll(d->statementhandle, static_cast<std::int32_t>(MIMER_LAST), 0);
+ int size = MimerCurrentRow(d->statementhandle);
+ if (!MIMER_SUCCEEDED(size))
+ size = -1;
+ MimerFetchScroll(d->statementhandle, MIMER_ABSOLUTE, currentRow);
+ d->currentSize = size;
+ return size;
+}
+
+int QMimerSQLResult::numRowsAffected()
+{
+ Q_D(const QMimerSQLResult);
+ return d->rowsAffected;
+}
+
+QSqlRecord QMimerSQLResult::record() const
+{
+ Q_D(const QMimerSQLResult);
+ QSqlRecord rec;
+ if (!isActive() || !isSelect() || !driver())
+ return rec;
+ QSqlField field;
+ const int colSize = MimerColumnCount(d->statementhandle);
+ for (int i = 0; i < colSize; i++) {
+ wchar_t colName_w[100];
+ MimerColumnName(d->statementhandle, static_cast<std::int16_t>(i + 1), colName_w,
+ sizeof(colName_w) / sizeof(colName_w[0]));
+ field.setName(QString::fromWCharArray(colName_w));
+ const int32_t mType = MimerColumnType(d->statementhandle, static_cast<std::int16_t>(i + 1));
+ const QMetaType::Type type = qDecodeMSQLType(mType);
+ field.setMetaType(QMetaType(type));
+ field.setValue(QVariant(field.metaType()));
+ // field.setPrecision(); Should be implemented once the Mimer API can give this
+ // information.
+ // field.setLength(); Should be implemented once the Mimer API can give
+ // this information.
+ rec.insert(i, field);
+ }
+ return rec;
+}
+
+bool QMimerSQLResult::prepare(const QString &query)
+{
+ Q_D(QMimerSQLResult);
+ int32_t err;
+ if (!driver())
+ return false;
+ if (!d->preparedQuery)
+ return QSqlResult::prepare(query);
+ if (query.isEmpty())
+ return false;
+ cleanup();
+ const int option = isForwardOnly() ? MIMER_FORWARD_ONLY : MIMER_SCROLLABLE;
+ err = MimerBeginStatement8(d->drv_d_func()->sessionhandle, query.toUtf8().constData(), option,
+ &d->statementhandle);
+ if (err == MIMER_STATEMENT_CANNOT_BE_PREPARED) {
+ err = MimerExecuteStatement8(d->drv_d_func()->sessionhandle, query.toUtf8().constData());
+ if (MIMER_SUCCEEDED(err)) {
+ d->executedStatement = true;
+ d->openCursor = false;
+ d->openStatement = false;
+ return true;
+ }
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(QCoreApplication::translate("QMimerSQLResult",
+ "Could not prepare/execute statement"),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ d->openStatement = true;
+ return true;
+}
+
+bool QMimerSQLResult::exec()
+{
+ Q_D(QMimerSQLResult);
+ int32_t err;
+ if (!driver())
+ return false;
+ if (!d->preparedQuery)
+ return QSqlResult::exec();
+ if (d->executedStatement) {
+ d->executedStatement = false;
+ return true;
+ }
+ if (d->openCursor) {
+ setAt(QSql::BeforeFirstRow);
+ err = MimerCloseCursor(d->statementhandle);
+ d->openCursor = false;
+ d->currentSize = -1;
+ }
+ QVector<QVariant> &values = boundValues();
+ if (d->execBatch)
+ values = d->batch_vector;
+ int mimParamCount = MimerParameterCount(d->statementhandle);
+ if (!MIMER_SUCCEEDED(mimParamCount))
+ mimParamCount = 0;
+ if (mimParamCount != values.size()) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Wrong number of parameters"),
+ genericError, QSqlError::StatementError, nullptr));
+ return false;
+ }
+ for (int i = 0; i < mimParamCount; i++) {
+ if (bindValueType(i) == QSql::Out) {
+ d->callWithOut = true;
+ continue;
+ }
+ const QVariant &val = values.at(i);
+ if (QSqlResultPrivate::isVariantNull(val) || val.isNull() || val.toString().isNull()) {
+ err = MimerSetNull(d->statementhandle, i + 1);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("null", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ continue;
+ }
+
+ const int mimParamType = MimerParameterType(d->statementhandle, i + 1);
+ const MimerColumnTypes mimDataType = mimerMapColumnTypes(mimParamType);
+ switch (mimDataType) {
+ case MimerColumnTypes::Int: {
+ bool convertOk;
+ err = MimerSetInt32(d->statementhandle, i + 1, val.toInt(&convertOk));
+ if (!convertOk || !MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("int32", i),
+ convertOk ? err : genericError, QSqlError::StatementError,
+ convertOk ? d->drv_d_func() : nullptr));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Long: {
+ bool convertOk;
+ err = MimerSetInt64(d->statementhandle, i + 1, val.toLongLong(&convertOk));
+ if (!convertOk || !MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("int64", i),
+ convertOk ? err : genericError, QSqlError::StatementError,
+ convertOk ? d->drv_d_func() : nullptr));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Float: {
+ bool convertOk;
+ err = MimerSetFloat(d->statementhandle, i + 1, val.toFloat(&convertOk));
+ if (!convertOk || !MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("float", i),
+ convertOk ? err : genericError, QSqlError::StatementError,
+ convertOk ? d->drv_d_func() : nullptr));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Double: {
+ bool convertOk;
+ err = MimerSetDouble(d->statementhandle, i + 1, val.toDouble(&convertOk));
+ if (!convertOk || !MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("double", i),
+ convertOk ? err : genericError, QSqlError::StatementError,
+ convertOk ? d->drv_d_func() : nullptr));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Binary: {
+ const QByteArray binArr = val.toByteArray();
+ size_t size = static_cast<std::size_t>(binArr.size());
+ err = MimerSetBinary(d->statementhandle, i + 1, binArr.data(), size);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("binary", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Boolean: {
+ err = MimerSetBoolean(d->statementhandle, i + 1, val.toBool() == true ? 1 : 0);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("boolean", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Uuid: {
+ const QByteArray uuidArray =
+ QByteArray::fromHex(val.toUuid().toString(QUuid::WithoutBraces).toLatin1());
+ const unsigned char *uuid =
+ reinterpret_cast<const unsigned char *>(uuidArray.constData());
+ err = MimerSetUUID(d->statementhandle, i + 1, uuid);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("UUID", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Numeric:
+ case MimerColumnTypes::String: {
+ QByteArray string_b = val.toString().trimmed().toUtf8();
+ const char *string_u = string_b.constData();
+ err = MimerSetString8(d->statementhandle, i + 1, string_u);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet(
+ mimDataType == MimerColumnTypes::Numeric ? "numeric" : "string", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Date: {
+ err = MimerSetString8(d->statementhandle, i + 1, val.toString().toUtf8().constData());
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("date", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Time: {
+ QString timeFormatString = "hh:mm:ss"_L1;
+ const QTime timeVal = val.toTime();
+ if (timeVal.msec() > 0)
+ timeFormatString.append(".zzz"_L1);
+ err = MimerSetString8(d->statementhandle, i + 1,
+ timeVal.toString(timeFormatString).toUtf8().constData());
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("time", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Timestamp: {
+ QString dateTimeFormatString = "yyyy-MM-dd hh:mm:ss"_L1;
+ const QDateTime dateTimeVal = val.toDateTime();
+ if (dateTimeVal.time().msec() > 0)
+ dateTimeFormatString.append(".zzz"_L1);
+ err = MimerSetString8(
+ d->statementhandle, i + 1,
+ val.toDateTime().toString(dateTimeFormatString).toUtf8().constData());
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotSet("datetime", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Blob: {
+ QByteArray blobArr = val.toByteArray();
+ const char *blobData = blobArr.constData();
+ qsizetype size = blobArr.size();
+ err = MimerSetLob(d->statementhandle, i + 1, size, &d->lobhandle);
+ if (MIMER_SUCCEEDED(err)) {
+ qsizetype maxSize = lobChunkMaxSizeSet;
+ if (size > maxSize) {
+ qsizetype left_to_send = size;
+ for (qsizetype k = 0; left_to_send > 0; k++) {
+ if (left_to_send <= maxSize) {
+ err = MimerSetBlobData(&d->lobhandle, &blobData[k * maxSize],
+ left_to_send);
+ left_to_send = 0;
+ } else {
+ err = MimerSetBlobData(&d->lobhandle, &blobData[k * maxSize], maxSize);
+ left_to_send = left_to_send - maxSize;
+ }
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("BLOB byte array", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ } else {
+ err = MimerSetBlobData(&d->lobhandle, blobArr, size);
+ }
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotSet("BLOB byte array", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Clob: {
+ QByteArray string_b = val.toString().trimmed().toUtf8();
+ const char *string_u = string_b.constData();
+ size_t size_c = 1;
+ size_t size = 0;
+ while (string_u[size++])
+ if ((string_u[size] & 0xc0) != 0x80)
+ size_c++;
+ err = MimerSetLob(d->statementhandle, i + 1, size_c, &d->lobhandle);
+ if (MIMER_SUCCEEDED(err)) {
+ constexpr size_t maxSize = lobChunkMaxSizeSet;
+ if (size > maxSize) {
+ size_t left_to_send = size;
+ size_t pos = 0;
+ uint step_back = 0;
+ while (left_to_send > 0 && step_back < maxSize) {
+ step_back = 0;
+ if (left_to_send <= maxSize) {
+ err = MimerSetNclobData8(&d->lobhandle, &string_u[pos], left_to_send);
+ left_to_send = 0;
+ } else {
+ // Check that we don't split a multi-byte utf-8 characters
+ while (pos + maxSize - step_back > 0
+ && (string_u[pos + maxSize - step_back] & 0xc0) == 0x80)
+ step_back++;
+ err = MimerSetNclobData8(&d->lobhandle, &string_u[pos],
+ maxSize - step_back);
+ left_to_send = left_to_send - maxSize + step_back;
+ pos += maxSize - step_back;
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotSet("CLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ }
+ } else {
+ err = MimerSetNclobData8(&d->lobhandle, string_u, size);
+ }
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("CLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Unknown:
+ default:
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Unknown datatype, parameter %1")
+ .arg(i),
+ genericError, QSqlError::StatementError, nullptr));
+ return false;
+ }
+ }
+ if (d->execBatch)
+ return true;
+ err = MimerExecute(d->statementhandle);
+ if (MIMER_SUCCEEDED(err)) {
+ d->rowsAffected = err;
+ int k = 0;
+ for (qsizetype i = 0; i < values.size(); i++) {
+ if (bindValueType(i) == QSql::Out || bindValueType(i) == QSql::InOut) {
+ bindValue(i, data(k), QSql::In);
+ k++;
+ }
+ }
+ d->callWithOut = false;
+ }
+ setSelect(false);
+ if (MIMER_SEQUENCE_ERROR == err) {
+ err = MimerOpenCursor(d->statementhandle);
+ d->rowsAffected = err;
+ d->openCursor = true;
+ d->currentRow = QSql::BeforeFirstRow;
+ setSelect(true);
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(QCoreApplication::translate("QMimerSQLResult",
+ "Could not execute statement/open cursor"),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ setActive(true);
+ return true;
+}
+
+bool QMimerSQLResult::execBatch(bool arrayBind)
+{
+ Q_D(QMimerSQLResult);
+ Q_UNUSED(arrayBind);
+ int32_t err;
+ const QVector<QVariant> values = boundValues();
+
+ // Check that we only have input parameters. Currently
+ // we can only handle batch operations without output parameters.
+ for (qsizetype i = 0; i < values.first().toList().size(); i++)
+ if (bindValueType(i) == QSql::Out || bindValueType(i) == QSql::InOut) {
+ setLastError(qMakeError(QCoreApplication::translate(
+ "QMimerSQLResult",
+ "Only input parameters can be used in batch operations"),
+ genericError, QSqlError::StatementError, nullptr));
+ d->execBatch = false;
+ return false;
+ }
+ d->execBatch = true;
+ for (qsizetype i = 0; i < values.first().toList().size(); i++) {
+ for (qsizetype j = 0; j < values.size(); j++)
+ d->batch_vector.append(values.at(j).toList().at(i));
+ exec();
+ if (i != (values.at(0).toList().size() - 1)) {
+ err = MimerAddBatch(d->statementhandle);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ //: %1 is the batch number
+ QCoreApplication::translate("QMimerSQLResult", "Could not add batch %1")
+ .arg(i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ d->execBatch = false;
+ return false;
+ }
+ }
+ d->batch_vector.clear();
+ }
+ d->execBatch = false;
+ err = MimerExecute(d->statementhandle);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Could not execute batch"), err,
+ QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ return true;
+}
+
+QVariant QMimerSQLResult::lastInsertId() const
+{
+ Q_D(const QMimerSQLResult);
+ int64_t lastSequence;
+ const int32_t err = MimerGetSequenceInt64(d->statementhandle, &lastSequence);
+ if (!MIMER_SUCCEEDED(err))
+ return QVariant(QMetaType(QMetaType::LongLong), nullptr);
+ return QVariant(qint64(lastSequence));
+}
+
+bool QMimerSQLDriver::hasFeature(DriverFeature f) const
+{
+ switch (f) {
+ case NamedPlaceholders: // Is true in reality but Qt parses Sql statement...
+ case EventNotifications:
+ case LowPrecisionNumbers:
+ case MultipleResultSets:
+ case SimpleLocking:
+ case CancelQuery:
+ return false;
+ case FinishQuery:
+ case LastInsertId:
+ case Transactions:
+ case QuerySize:
+ case BLOB:
+ case Unicode:
+ case PreparedQueries:
+ case PositionalPlaceholders:
+ case BatchOperations:
+ return true;
+ }
+ return true;
+}
+
+bool QMimerSQLDriver::open(const QString &db, const QString &user, const QString &password,
+ const QString &host, int port, const QString &connOpts)
+{
+ Q_D(QMimerSQLDriver);
+ Q_UNUSED(host);
+ Q_UNUSED(port);
+ Q_UNUSED(connOpts);
+ if (isOpen())
+ close();
+ const int32_t err = MimerBeginSession8(db.toUtf8().constData(), user.toUtf8().constData(),
+ password.toUtf8().constData(), &d->sessionhandle);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLDriver", "Could not connect to database")
+ + " "_L1 + db,
+ err, QSqlError::ConnectionError, nullptr));
+ setOpenError(true);
+ return false;
+ }
+ d->dbUser = user;
+ d->dbName = db;
+ setOpen(true);
+ setOpenError(false);
+ return true;
+}
+
+void QMimerSQLDriver::close()
+{
+ Q_D(QMimerSQLDriver);
+ if (isOpen()) {
+ const int end_err = MimerEndSession(&d->sessionhandle);
+ if (MIMER_SUCCEEDED(end_err)) {
+ setOpen(false);
+ setOpenError(false);
+ }
+ }
+}
+
+QSqlResult *QMimerSQLDriver::createResult() const
+{
+ return new QMimerSQLResult(this);
+}
+
+QStringList QMimerSQLDriver::tables(QSql::TableType type) const
+{
+ QStringList tl;
+ if (!isOpen())
+ return tl;
+ QSqlQuery t(createResult());
+ QString sql;
+ switch (type) {
+ case QSql::Tables: {
+ sql = "select table_name from information_schema.tables where "
+ "table_type=\'BASE TABLE\' AND table_schema = CURRENT_USER"_L1;
+ break;
+ }
+ case QSql::SystemTables: {
+ sql = "select table_name from information_schema.tables where "
+ "table_type=\'BASE TABLE\' AND table_schema = \'SYSTEM\'"_L1;
+ break;
+ }
+ case QSql::Views: {
+ sql = "select table_name from information_schema.tables where "
+ "table_type=\'VIEW\' AND table_schema = CURRENT_USER"_L1;
+ break;
+ }
+ case QSql::AllTables: {
+ sql = "select table_name from information_schema.tables where "
+ "(table_type=\'VIEW\' or table_type=\'BASE TABLE\')"
+ " AND (table_schema = CURRENT_USER OR table_schema =\'SYSTEM\')"_L1;
+ break;
+ }
+ default:
+ break;
+ }
+ if (sql.length() > 0) {
+ t.exec(sql);
+ while (t.next())
+ tl.append(t.value(0).toString());
+ }
+ return tl;
+}
+
+QSqlIndex QMimerSQLDriver::primaryIndex(const QString &tablename) const
+{
+ Q_D(const QMimerSQLDriver);
+ if (!isOpen())
+ return QSqlIndex();
+ QString table = tablename;
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+ QSqlIndex index(tablename);
+ QSqlQuery t(createResult());
+ QString schema;
+ QString qualifiedName = table;
+ d->splitTableQualifier(qualifiedName, &schema, &table);
+ QString sql =
+ "select information_schema.ext_access_paths.column_name,"
+ "case when data_type = 'INTERVAL' then 'INTERVAL '|| interval_type "
+ "when data_type = 'INTEGER' and numeric_precision > 10 then 'BIGINT' "
+ "when data_type = 'INTEGER' and numeric_precision <= 10 AND NUMERIC_PRECISION > 5 "
+ "then 'INTEGER' when data_type = 'INTEGER' and numeric_precision <= 5 then 'SMALLINT' "
+ "else upper(data_type) end as data_type "
+ "from information_schema.ext_access_paths full outer join "
+ "information_schema.columns on information_schema.ext_access_paths.column_name = "
+ "information_schema.columns.column_name and "
+ "information_schema.ext_access_paths.table_name = "
+ "information_schema.columns.table_name where "
+ "information_schema.ext_access_paths.table_name = \'"_L1;
+ sql.append(table)
+ .append("\' and index_type = \'PRIMARY KEY\'"_L1);
+ if (schema.length() == 0)
+ sql.append(" and table_schema = CURRENT_USER"_L1);
+ else
+ sql.append(" and table_schema = \'"_L1).append(schema).append("\'"_L1);
+
+ if (!t.exec(sql))
+ return QSqlIndex();
+ int i = 0;
+ while (t.next()) {
+ QSqlField field(t.value(0).toString(),
+ QMetaType(qDecodeMSQLType(qLookupMimDataType(t.value(1).toString()))),
+ tablename);
+ index.insert(i, field);
+ index.setName(t.value(0).toString());
+ i++;
+ }
+ return index;
+}
+
+QSqlRecord QMimerSQLDriver::record(const QString &tablename) const
+{
+ Q_D(const QMimerSQLDriver);
+ if (!isOpen())
+ return QSqlRecord();
+ QSqlRecord rec;
+ QSqlQuery t(createResult());
+ QString qualifiedName = tablename;
+ if (isIdentifierEscaped(qualifiedName, QSqlDriver::TableName))
+ qualifiedName = stripDelimiters(qualifiedName, QSqlDriver::TableName);
+ QString schema, table;
+ d->splitTableQualifier(qualifiedName, &schema, &table);
+
+ QString sql =
+ "select column_name, case when data_type = 'INTERVAL' then 'INTERVAL '|| interval_type "
+ "when data_type = 'INTEGER' and numeric_precision > 10 then 'BIGINT' "
+ "when data_type = 'INTEGER' and numeric_precision <= 10 AND numeric_precision > 5 "
+ "then 'INTEGER' when data_type = 'INTEGER' and numeric_precision <= 5 then 'SMALLINT' "
+ "else UPPER(data_type) end as data_type, case when is_nullable = 'YES' then false else "
+ "true end as required, "
+ "coalesce(numeric_precision, coalesce(datetime_precision,coalesce(interval_precision, "
+ "-1))) as prec from information_schema.columns where table_name = \'"_L1;
+ if (schema.length() == 0)
+ sql.append(table).append("\' and table_schema = CURRENT_USER"_L1);
+ else
+ sql.append(table).append("\' and table_schema = \'"_L1).append(schema).append("\'"_L1);
+ sql.append(" order by ordinal_position"_L1);
+ if (!t.exec(sql))
+ return QSqlRecord();
+
+ while (t.next()) {
+ QSqlField field(t.value(0).toString(),
+ QMetaType(qDecodeMSQLType(qLookupMimDataType(t.value(1).toString()))),
+ tablename);
+ field.setRequired(t.value(3).toBool());
+ if (t.value(3).toInt() != -1)
+ field.setPrecision(t.value(3).toInt());
+ rec.append(field);
+ }
+
+ return rec;
+}
+
+QVariant QMimerSQLDriver::handle() const
+{
+ Q_D(const QMimerSQLDriver);
+ return QVariant::fromValue(d->sessionhandle);
+}
+
+QString QMimerSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
+{
+ Q_UNUSED(type);
+ QString res = identifier;
+ if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"')) {
+ res.replace(u'"', "\"\""_L1);
+ res = u'"' + res + u'"';
+ res.replace(u'.', "\".\""_L1);
+ }
+ return res;
+}
+
+bool QMimerSQLDriver::beginTransaction()
+{
+ Q_D(const QMimerSQLDriver);
+ const int32_t err = MimerBeginTransaction(d->sessionhandle, MIMER_TRANS_READWRITE);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLDriver", "Could not start transaction"), err,
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QMimerSQLDriver::commitTransaction()
+{
+ Q_D(const QMimerSQLDriver);
+ const int32_t err = MimerEndTransaction(d->sessionhandle, MIMER_COMMIT);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLDriver", "Could not commit transaction"), err,
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QMimerSQLDriver::rollbackTransaction()
+{
+ Q_D(const QMimerSQLDriver);
+ const int32_t err = MimerEndTransaction(d->sessionhandle, MIMER_ROLLBACK);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLDriver", "Could not roll back transaction"),
+ err, QSqlError::TransactionError, d));
+ return false;
+ }
+ return true;
+}
+
+void QMimerSQLDriverPrivate::splitTableQualifier(const QString &qualifiedName, QString *schema,
+ QString *table) const
+{
+ const QList<QStringView> l = QStringView(qualifiedName).split(u'.');
+ int n = l.count();
+ if (n > 2) {
+ return; // can't possibly be a valid table qualifier
+ } else if (n == 1) {
+ *schema = QString();
+ *table = l.at(0).toString();
+ } else {
+ *schema = l.at(0).toString();
+ *table = l.at(1).toString();
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qsql_mimer.cpp"
diff --git a/src/plugins/sqldrivers/mimer/qsql_mimer.h b/src/plugins/sqldrivers/mimer/qsql_mimer.h
new file mode 100644
index 0000000000..03ad8f21f2
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/qsql_mimer.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2022 Mimer Information Technology
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QSQL_MIMER_H
+#define QSQL_MIMER_H
+
+#include <QtSql/qsqldriver.h>
+#include <QUuid>
+#include <mimerapi.h>
+
+#ifdef QT_PLUGIN
+# define Q_EXPORT_SQLDRIVER_MIMER
+#else
+# define Q_EXPORT_SQLDRIVER_MIMER Q_SQL_EXPORT
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QMimerSQLDriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_MIMER QMimerSQLDriver : public QSqlDriver
+{
+ friend class QMimerSQLResultPrivate;
+ Q_DECLARE_PRIVATE(QMimerSQLDriver)
+ Q_OBJECT
+public:
+ explicit QMimerSQLDriver(QObject *parent = nullptr);
+ explicit QMimerSQLDriver(MimerSession *conn, QObject *parent = nullptr);
+ ~QMimerSQLDriver() override;
+ bool hasFeature(DriverFeature f) const override;
+ bool open(const QString &db, const QString &user, const QString &password, const QString &host,
+ int port, const QString &connOpts) override;
+ void close() override;
+ QSqlResult *createResult() const override;
+ QStringList tables(QSql::TableType type) const override;
+ QSqlIndex primaryIndex(const QString &tablename) const override;
+ QSqlRecord record(const QString &tablename) const override;
+ QVariant handle() const override;
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const override;
+protected:
+ bool beginTransaction() override;
+ bool commitTransaction() override;
+ bool rollbackTransaction() override;
+
+private:
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_MIMER
diff --git a/src/plugins/sqldrivers/mysql/CMakeLists.txt b/src/plugins/sqldrivers/mysql/CMakeLists.txt
index 4a65847be7..2e3d028584 100644
--- a/src/plugins/sqldrivers/mysql/CMakeLists.txt
+++ b/src/plugins/sqldrivers/mysql/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from mysql.pro.
-
#####################################################################
## QMYSQLDriverPlugin Plugin:
#####################################################################
@@ -16,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
@@ -23,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/qsql_mysql.cpp b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
index 717692b772..cfd4931b46 100644
--- a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
+++ b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
@@ -10,12 +10,14 @@
#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>
@@ -29,12 +31,30 @@ 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
+static Q_LOGGING_CATEGORY(lcMysql, "qt.sql.mysql")
+
using namespace Qt::StringLiterals;
class QMYSQLDriverPrivate : public QSqlDriverPrivate
@@ -45,6 +65,7 @@ public:
QMYSQLDriverPrivate() : QSqlDriverPrivate(QSqlDriver::MySqlServer)
{}
MYSQL *mysql = nullptr;
+ QString dbName;
bool preparedQuerysEnabled = false;
};
@@ -80,9 +101,15 @@ static inline QVariant qDateTimeFromString(QString &val)
#else
if (val.isEmpty())
return QVariant(QDateTime());
+
+ // 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)
- // TIMESTAMPS have the format yyyyMMddhhmmss
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
}
@@ -101,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
@@ -198,6 +237,7 @@ static QMetaType qDecodeMYSQLType(enum_field_types mysqltype, uint flags)
case MYSQL_TYPE_YEAR:
type = QMetaType::Int;
break;
+ case MYSQL_TYPE_BIT:
case MYSQL_TYPE_LONGLONG:
type = (flags & UNSIGNED_FLAG) ? QMetaType::ULongLong : QMetaType::LongLong;
break;
@@ -248,7 +288,6 @@ static QSqlField qToField(MYSQL_FIELD *field)
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;
}
@@ -286,6 +325,11 @@ 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.size(); ++i) {
@@ -330,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 {
@@ -384,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;
}
@@ -502,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.size()) {
- qWarning("QMYSQLResult::data: column %d out of range", field);
+ qCWarning(lcMysql, "QMYSQLResult::data: column %d out of range", field);
return QVariant();
}
@@ -519,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)
@@ -528,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)
@@ -537,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
@@ -552,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)
@@ -660,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);
}
@@ -777,6 +840,7 @@ bool QMYSQLResult::nextResult()
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;
}
}
@@ -789,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);
@@ -844,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;
@@ -864,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;
@@ -877,9 +918,8 @@ bool QMYSQLResult::exec()
return false;
}
- if (mysql_stmt_param_count(d->stmt) > 0 &&
- mysql_stmt_param_count(d->stmt) == (uint)values.size()) {
-
+ 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);
@@ -902,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:
@@ -961,7 +1015,11 @@ bool QMYSQLResult::exec()
}
}
+#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));
@@ -1032,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
@@ -1139,9 +1197,11 @@ static void setOptionFlag(uint &optionFlags, QStringView opt)
else if (opt == "CLIENT_ODBC"_L1)
optionFlags |= CLIENT_ODBC;
else if (opt == "CLIENT_SSL"_L1)
- qWarning("QMYSQLDriver: MYSQL_OPT_SSL_KEY, MYSQL_OPT_SSL_CERT and MYSQL_OPT_SSL_CA should be used instead of CLIENT_SSL.");
+ 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)
@@ -1162,6 +1222,47 @@ static bool setOptionBool(MYSQL *mysql, mysql_option option, QStringView v)
return mysql_options(mysql, option, &val) == 0;
}
+// 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,
@@ -1199,18 +1300,27 @@ bool QMYSQLDriver::open(const QString &db,
{"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)) {
- qWarning("QMYSQLDriver::open: Could not set connect option value '%s' to '%s'",
- key.toLocal8Bit().constData(), value.toLocal8Bit().constData());
+ qCWarning(lcMysql, "QMYSQLDriver::open: Could not set connect option value "
+ "'%ls' to '%ls'",
+ qUtf16Printable(QString(key)), qUtf16Printable(QString(value)));
}
return true;
}
@@ -1241,8 +1351,8 @@ bool QMYSQLDriver::open(const QString &db,
else if (val == "TRUE"_L1 || val == "1"_L1)
setOptionFlag(optionFlags, key);
else
- qWarning("QMYSQLDriver::open: Illegal connect option value '%s'",
- sv.toLocal8Bit().constData());
+ qCWarning(lcMysql, "QMYSQLDriver::open: Illegal connect option value '%ls'",
+ qUtf16Printable(QString(sv)));
} else {
setOptionFlag(optionFlags, sv);
}
@@ -1294,9 +1404,10 @@ bool QMYSQLDriver::open(const QString &db,
}
}
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 (!db.isEmpty() && mysql_select_db(d->mysql, db.toUtf8().constData())) {
@@ -1307,6 +1418,10 @@ bool QMYSQLDriver::open(const QString &db,
}
d->preparedQuerysEnabled = checkPreparedQueries(d->mysql);
+ d->dbName = db;
+
+ if (d->preparedQuerysEnabled)
+ setUtcTimeZone(d->mysql);
#if QT_CONFIG(thread)
mysql_thread_init();
@@ -1326,6 +1441,7 @@ void QMYSQLDriver::close()
#endif
mysql_close(d->mysql);
d->mysql = nullptr;
+ d->dbName.clear();
setOpen(false);
setOpenError(false);
}
@@ -1342,14 +1458,14 @@ QStringList QMYSQLDriver::tables(QSql::TableType type) const
QStringList tl;
QSqlQuery q(createResult());
if (type & QSql::Tables) {
- QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + QLatin1StringView(d->mysql->db) + "' and table_type = 'BASE TABLE'"_L1;
+ 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 = "select table_name from information_schema.tables where table_schema = '"_L1 + QLatin1StringView(d->mysql->db) + "' and table_type = 'VIEW'"_L1;
+ 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())
@@ -1382,20 +1498,38 @@ QSqlIndex QMYSQLDriver::primaryIndex(const QString &tablename) const
QSqlRecord QMYSQLDriver::record(const QString &tablename) const
{
Q_D(const QMYSQLDriver);
- const QString table = stripDelimiters(tablename, QSqlDriver::TableName);
-
- QSqlRecord info;
if (!isOpen())
- return info;
- MYSQL_RES *r = mysql_list_fields(d->mysql, table.toUtf8().constData(), nullptr);
- if (!r)
- return info;
-
- MYSQL_FIELD *field;
- while ((field = mysql_fetch_field(r)))
- info.append(qToField(field));
- mysql_free_result(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);
+ }
+ }
+ }
+ return r;
}
QVariant QMYSQLDriver::handle() const
@@ -1408,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")) {
@@ -1423,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")) {
@@ -1438,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")) {
@@ -1475,7 +1609,7 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons
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:
@@ -1484,7 +1618,6 @@ 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 = u'\'' +
dt.date().toString(Qt::ISODate) +
u'T' +
diff --git a/src/plugins/sqldrivers/oci/CMakeLists.txt b/src/plugins/sqldrivers/oci/CMakeLists.txt
index eb0597e434..66c4219905 100644
--- a/src/plugins/sqldrivers/oci/CMakeLists.txt
+++ b/src/plugins/sqldrivers/oci/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from oci.pro.
-
#####################################################################
## QOCIDriverPlugin Plugin:
#####################################################################
@@ -23,9 +21,6 @@ qt_internal_add_plugin(QOCIDriverPlugin
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:oci.pro:<TRUE>:
-# OTHER_FILES = "oci.json"
-
## Scopes:
#####################################################################
diff --git a/src/plugins/sqldrivers/oci/qsql_oci.cpp b/src/plugins/sqldrivers/oci/qsql_oci.cpp
index b8202a64cc..68e303490d 100644
--- a/src/plugins/sqldrivers/oci/qsql_oci.cpp
+++ b/src/plugins/sqldrivers/oci/qsql_oci.cpp
@@ -7,6 +7,7 @@
#include <qdatetime.h>
#include <qdebug.h>
#include <qlist.h>
+#include <qloggingcategory.h>
#include <qmetatype.h>
#if QT_CONFIG(regularexpression)
#include <qregularexpression.h>
@@ -30,14 +31,7 @@
#define _int64 __int64
#endif
-
#include <oci.h>
-#ifdef max
-#undef max
-#endif
-#ifdef min
-#undef min
-#endif
#include <stdlib.h>
@@ -58,6 +52,8 @@ Q_DECLARE_METATYPE(OCIStmt*)
QT_BEGIN_NAMESPACE
+static Q_LOGGING_CATEGORY(lcOci, "qt.sql.oci")
+
using namespace Qt::StringLiterals;
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
@@ -140,8 +136,8 @@ QOCIDateTime::QOCIDateTime(OCIEnv *env, OCIError *err, const QDateTime &dt)
if (dt.isValid()) {
const QDate date = dt.date();
const QTime time = dt.time();
- // Zone in +hh:mm format (stripping UTC prefix from OffsetName)
- QString timeZone = dt.timeZone().displayName(dt, QTimeZone::OffsetName).mid(3);
+ // Zone in +hh:mm format
+ const QString timeZone = dt.toString("ttt"_L1);
const OraText *tz = reinterpret_cast<const OraText *>(timeZone.utf16());
OCIDateTimeConstruct(env, err, dateTime, date.year(), date.month(), date.day(), time.hour(),
time.minute(), time.second(), time.msec() * 1000000,
@@ -280,7 +276,7 @@ public:
0);
#ifdef QOCI_DEBUG
if (r != 0)
- qWarning("QOCIResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_FORM.");
+ qCWarning(lcOci, "QOCIResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_FORM.");
#endif
#endif
@@ -440,7 +436,7 @@ int QOCIResultPrivate::bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, in
-1,
SQLT_RDD, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
} else {
- qWarning("Unknown bind variable");
+ qCWarning(lcOci, "Unknown bind variable");
r = OCI_ERROR;
}
} else {
@@ -556,7 +552,7 @@ void QOCIDriverPrivate::allocErrorHandle()
OCI_HTYPE_ERROR,
0, nullptr);
if (r != OCI_SUCCESS)
- qWarning("QOCIDriver: unable to allocate error handle");
+ qCWarning(lcOci, "QOCIDriver: unable to allocate error handle");
}
struct OraFieldInfo
@@ -592,12 +588,7 @@ QString qOraWarn(OCIError *err, int *errorCode)
void qOraWarning(const char* msg, OCIError *err)
{
-#ifdef QOCI_DEBUG
- qWarning("%s %s", msg, qPrintable(qOraWarn(err)));
-#else
- Q_UNUSED(msg);
- Q_UNUSED(err);
-#endif
+ qCWarning(lcOci, "%s %ls", msg, qUtf16Printable(qOraWarn(err)));
}
static int qOraErrorNumber(OCIError *err)
@@ -660,7 +651,7 @@ QMetaType qDecodeOCIType(const QString& ocitype, QSql::NumericalPrecisionPolicy
else if (ocitype == "UNDEFINED"_L1)
type = QMetaType::UnknownType;
if (type == QMetaType::UnknownType)
- qWarning("qDecodeOCIType: unknown type: %s", ocitype.toLocal8Bit().constData());
+ qCWarning(lcOci, "qDecodeOCIType: unknown type: %ls", qUtf16Printable(ocitype));
return QMetaType(type);
}
@@ -728,7 +719,7 @@ QMetaType qDecodeOCIType(int ocitype, QSql::NumericalPrecisionPolicy precisionPo
type = QMetaType::QDateTime;
break;
default:
- qWarning("qDecodeOCIType: unknown OCI datatype: %d", ocitype);
+ qCWarning(lcOci, "qDecodeOCIType: unknown OCI datatype: %d", ocitype);
break;
}
return QMetaType(type);
@@ -745,7 +736,6 @@ static QSqlField qFromOraInf(const OraFieldInfo &ofi)
f.setLength(ofi.oraPrecision == 0 ? 38 : int(ofi.oraPrecision));
f.setPrecision(ofi.oraScale);
- f.setSqlType(int(ofi.oraType));
return f;
}
@@ -844,7 +834,7 @@ QOCICols::OraFieldInf::~OraFieldInf()
if (lob) {
int r = OCIDescriptorFree(lob, OCI_DTYPE_LOB);
if (r != 0)
- qWarning("QOCICols: Cannot free LOB descriptor");
+ qCWarning(lcOci, "QOCICols: Cannot free LOB descriptor");
}
if (dataPtr) {
switch (typ.id()) {
@@ -853,7 +843,7 @@ QOCICols::OraFieldInf::~OraFieldInf()
case QMetaType::QDateTime: {
int r = OCIDescriptorFree(dataPtr, OCI_DTYPE_TIMESTAMP_TZ);
if (r != OCI_SUCCESS)
- qWarning("QOCICols: Cannot free OCIDateTime descriptor");
+ qCWarning(lcOci, "QOCICols: Cannot free OCIDateTime descriptor");
break;
}
default:
@@ -908,7 +898,7 @@ QOCICols::QOCICols(int size, QOCIResultPrivate* dp)
case QMetaType::QDateTime:
r = OCIDescriptorAlloc(d->env, (void **)&fieldInf[idx].dataPtr, OCI_DTYPE_TIMESTAMP_TZ, 0, 0);
if (r != OCI_SUCCESS) {
- qWarning("QOCICols: Unable to allocate the OCIDateTime descriptor");
+ qCWarning(lcOci, "QOCICols: Unable to allocate the OCIDateTime descriptor");
break;
}
r = OCIDefineByPos(d->sql,
@@ -1079,7 +1069,7 @@ OCILobLocator **QOCICols::createLobLocator(int position, OCIEnv* env)
0,
0);
if (r != 0) {
- qWarning("QOCICols: Cannot create LOB locator");
+ qCWarning(lcOci, "QOCICols: Cannot create LOB locator");
lob = 0;
}
return &lob;
@@ -1317,7 +1307,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a
return false;
#ifdef QOCI_DEBUG
- qDebug() << "columnCount:" << columnCount << boundValues;
+ qCDebug(lcOci) << "columnCount:" << columnCount << boundValues;
#endif
int i;
@@ -1436,7 +1426,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a
// we may now populate column with data
for (uint row = 0; row < col.recordCount; ++row) {
- const QVariant &val = boundValues.at(i).toList().at(row);
+ const QVariant val = boundValues.at(i).toList().at(row);
if (QSqlResultPrivate::isVariantNull(val) && !d->isOutValue(i)) {
columns[i].indicators[row] = -1;
@@ -1514,13 +1504,13 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a
QOCIBatchColumn &bindColumn = columns[i];
#ifdef QOCI_DEBUG
- qDebug("OCIBindByPos(%p, %p, %p, %d, %p, %d, %d, %p, %p, 0, %d, %p, OCI_DEFAULT)",
+ qCDebug(lcOci, "OCIBindByPos(%p, %p, %p, %d, %p, %d, %d, %p, %p, 0, %d, %p, OCI_DEFAULT)",
d->sql, &bindColumn.bindh, d->err, i + 1, bindColumn.data,
bindColumn.maxLen, bindColumn.bindAs, bindColumn.indicators, bindColumn.lengths,
arrayBind ? bindColumn.maxarr_len : 0, arrayBind ? &bindColumn.curelep : 0);
for (int ii = 0; ii < (int)bindColumn.recordCount; ++ii) {
- qDebug(" record %d: indicator %d, length %d", ii, bindColumn.indicators[ii],
+ qCDebug(lcOci, " record %d: indicator %d, length %d", ii, bindColumn.indicators[ii],
bindColumn.lengths[ii]);
}
#endif
@@ -1540,7 +1530,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a
OCI_DEFAULT);
#ifdef QOCI_DEBUG
- qDebug("After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh);
+ qCDebug(lcOci, "After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh);
#endif
if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) {
@@ -1800,7 +1790,7 @@ void QOCICols::getValues(QVariantList &v, int index)
v[index + i] = QVariant(QMetaType(QMetaType::QByteArray));
break;
default:
- qWarning("QOCICols::value: unknown data type");
+ qCWarning(lcOci, "QOCICols::value: unknown data type");
break;
}
}
@@ -1821,7 +1811,7 @@ QOCIResultPrivate::QOCIResultPrivate(QOCIResult *q, const QOCIDriver *drv)
OCI_HTYPE_ERROR,
0, nullptr);
if (r != OCI_SUCCESS)
- qWarning("QOCIResult: unable to alloc error handle");
+ qCWarning(lcOci, "QOCIResult: unable to alloc error handle");
}
QOCIResultPrivate::~QOCIResultPrivate()
@@ -1829,10 +1819,10 @@ QOCIResultPrivate::~QOCIResultPrivate()
delete cols;
if (sql && OCIHandleFree(sql, OCI_HTYPE_STMT) != OCI_SUCCESS)
- qWarning("~QOCIResult: unable to free statement handle");
+ qCWarning(lcOci, "~QOCIResult: unable to free statement handle");
if (OCIHandleFree(err, OCI_HTYPE_ERROR) != OCI_SUCCESS)
- qWarning("~QOCIResult: unable to free error report handle");
+ qCWarning(lcOci, "~QOCIResult: unable to free error report handle");
}
@@ -1889,7 +1879,7 @@ bool QOCIResult::gotoNext(QSqlCachedResult::ValueCache &values, int index)
break;
case OCI_ERROR:
if (qOraErrorNumber(d->err) == 1406) {
- qWarning("QOCI Warning: data truncated for %s", lastQuery().toLocal8Bit().constData());
+ qCWarning(lcOci, "QOCI Warning: data truncated for %ls", qUtf16Printable(lastQuery()));
r = OCI_SUCCESS; /* ignore it */
break;
}
@@ -2003,7 +1993,7 @@ bool QOCIResult::exec()
setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
"Unable to get statement type"), QSqlError::StatementError, d->err));
#ifdef QOCI_DEBUG
- qDebug() << "lastQuery()" << lastQuery();
+ qCDebug(lcOci) << "lastQuery()" << lastQuery();
#endif
return false;
}
@@ -2018,7 +2008,7 @@ bool QOCIResult::exec()
setLastError(qMakeError(QCoreApplication::translate("QOCIResult", "Unable to bind value"),
QSqlError::StatementError, d->err));
#ifdef QOCI_DEBUG
- qDebug() << "lastQuery()" << lastQuery();
+ qCDebug(lcOci) << "lastQuery()" << lastQuery();
#endif
return false;
}
@@ -2037,7 +2027,7 @@ bool QOCIResult::exec()
setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
"Unable to execute statement"), QSqlError::StatementError, d->err));
#ifdef QOCI_DEBUG
- qDebug() << "lastQuery()" << lastQuery();
+ qCDebug(lcOci) << "lastQuery()" << lastQuery();
#endif
return false;
}
@@ -2129,7 +2119,7 @@ QOCIDriver::QOCIDriver(QObject* parent)
0,
NULL);
if (r != 0) {
- qWarning("QOCIDriver: unable to create environment");
+ qCWarning(lcOci, "QOCIDriver: unable to create environment");
setLastError(qMakeError(tr("Unable to initialize", "QOCIDriver"),
QSqlError::ConnectionError, d->err));
return;
@@ -2160,10 +2150,10 @@ QOCIDriver::~QOCIDriver()
close();
int r = OCIHandleFree(d->err, OCI_HTYPE_ERROR);
if (r != OCI_SUCCESS)
- qWarning("Unable to free Error handle: %d", r);
+ qCWarning(lcOci, "Unable to free Error handle: %d", r);
r = OCIHandleFree(d->env, OCI_HTYPE_ENV);
if (r != OCI_SUCCESS)
- qWarning("Unable to free Environment handle: %d", r);
+ qCWarning(lcOci, "Unable to free Environment handle: %d", r);
}
bool QOCIDriver::hasFeature(DriverFeature f) const
@@ -2198,8 +2188,8 @@ static void qParseOpts(const QString &options, QOCIDriverPrivate *d)
for (const auto tmp : opts) {
qsizetype idx;
if ((idx = tmp.indexOf(u'=')) == -1) {
- qWarning("QOCIDriver::parseArgs: Invalid parameter: '%s'",
- tmp.toLocal8Bit().constData());
+ qCWarning(lcOci, "QOCIDriver::parseArgs: Invalid parameter: '%ls'",
+ qUtf16Printable(tmp.toString()));
continue;
}
const QStringView opt = tmp.left(idx);
@@ -2219,12 +2209,12 @@ static void qParseOpts(const QString &options, QOCIDriverPrivate *d)
} else if (val == "OCI_SYSOPER"_L1) {
d->authMode = OCI_SYSOPER;
} else if (val != "OCI_DEFAULT"_L1) {
- qWarning("QOCIDriver::parseArgs: Unsupported value for OCI_AUTH_MODE: '%s'",
- val.toLocal8Bit().constData());
+ qCWarning(lcOci, "QOCIDriver::parseArgs: Unsupported value for OCI_AUTH_MODE: '%ls'",
+ qUtf16Printable(val.toString()));
}
} else {
- qWarning("QOCIDriver::parseArgs: Invalid parameter: '%s'",
- opt.toLocal8Bit().constData());
+ qCWarning(lcOci, "QOCIDriver::parseArgs: Invalid parameter: '%ls'",
+ qUtf16Printable(opt.toString()));
}
}
}
@@ -2321,7 +2311,7 @@ bool QOCIDriver::open(const QString & db,
sizeof(vertxt),
OCI_HTYPE_SVCCTX);
if (r != 0) {
- qWarning("QOCIDriver::open: could not get Oracle server version.");
+ qCWarning(lcOci, "QOCIDriver::open: could not get Oracle server version.");
} else {
QString versionStr;
versionStr = QString(reinterpret_cast<const QChar *>(vertxt));
@@ -2370,7 +2360,7 @@ bool QOCIDriver::beginTransaction()
{
Q_D(QOCIDriver);
if (!isOpen()) {
- qWarning("QOCIDriver::beginTransaction: Database not open");
+ qCWarning(lcOci, "QOCIDriver::beginTransaction: Database not open");
return false;
}
int r = OCITransStart(d->svc,
@@ -2391,7 +2381,7 @@ bool QOCIDriver::commitTransaction()
{
Q_D(QOCIDriver);
if (!isOpen()) {
- qWarning("QOCIDriver::commitTransaction: Database not open");
+ qCWarning(lcOci, "QOCIDriver::commitTransaction: Database not open");
return false;
}
int r = OCITransCommit(d->svc,
@@ -2411,7 +2401,7 @@ bool QOCIDriver::rollbackTransaction()
{
Q_D(QOCIDriver);
if (!isOpen()) {
- qWarning("QOCIDriver::rollbackTransaction: Database not open");
+ qCWarning(lcOci, "QOCIDriver::rollbackTransaction: Database not open");
return false;
}
int r = OCITransRollback(d->svc,
@@ -2563,8 +2553,7 @@ QSqlRecord QOCIDriver::record(const QString& tablename) const
// eg. a sub-query on the sys.synonyms table
QString stmt("select column_name, data_type, data_length, "
"data_precision, data_scale, nullable, data_default%1"
- "from all_tab_columns a "
- "where a.table_name=%2"_L1);
+ "from all_tab_columns a "_L1);
if (d->serverVersion >= 9)
stmt = stmt.arg(", char_length "_L1);
else
@@ -2578,7 +2567,7 @@ QSqlRecord QOCIDriver::record(const QString& tablename) const
else
table = table.toUpper();
- tmpStmt = stmt.arg(u'\'' + table + u'\'');
+ tmpStmt = stmt + "where a.table_name='"_L1 + table + u'\'';
if (owner.isEmpty()) {
owner = d->user;
}
@@ -2692,7 +2681,7 @@ QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const
QString QOCIDriver::formatValue(const QSqlField &field, bool trimStrings) const
{
- switch (field.typeID()) {
+ switch (field.metaType().id()) {
case QMetaType::QDateTime: {
QDateTime datetime = field.value().toDateTime();
QString datestring;
@@ -2754,10 +2743,19 @@ QString QOCIDriver::escapeIdentifier(const QString &identifier, IdentifierType t
QString res = identifier;
if (!identifier.isEmpty() && !isIdentifierEscaped(identifier, type)) {
res.replace(u'"', "\"\""_L1);
- res.prepend(u'"').append(u'"');
res.replace(u'.', "\".\""_L1);
+ res = u'"' + res + u'"';
}
return res;
}
+int QOCIDriver::maximumIdentifierLength(IdentifierType type) const
+{
+ Q_D(const QOCIDriver);
+ Q_UNUSED(type);
+ return d->serverVersion > 12 ? 128 : 30;
+}
+
QT_END_NAMESPACE
+
+#include "moc_qsql_oci_p.cpp"
diff --git a/src/plugins/sqldrivers/oci/qsql_oci_p.h b/src/plugins/sqldrivers/oci/qsql_oci_p.h
index b2aca6d563..91fef9de7b 100644
--- a/src/plugins/sqldrivers/oci/qsql_oci_p.h
+++ b/src/plugins/sqldrivers/oci/qsql_oci_p.h
@@ -58,6 +58,7 @@ public:
bool trimStrings) const override;
QVariant handle() const override;
QString escapeIdentifier(const QString &identifier, IdentifierType) const override;
+ int maximumIdentifierLength(IdentifierType type) const override;
protected:
bool beginTransaction() override;
diff --git a/src/plugins/sqldrivers/odbc/CMakeLists.txt b/src/plugins/sqldrivers/odbc/CMakeLists.txt
index 2b56a4fca6..7812aced2c 100644
--- a/src/plugins/sqldrivers/odbc/CMakeLists.txt
+++ b/src/plugins/sqldrivers/odbc/CMakeLists.txt
@@ -1,13 +1,11 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from odbc.pro.
-
#####################################################################
## QODBCDriverPlugin Plugin:
#####################################################################
-qt_find_package(ODBC) # special case
+qt_find_package(ODBC)
qt_internal_add_plugin(QODBCDriverPlugin
OUTPUT_NAME qsqlodbc
@@ -18,6 +16,7 @@ qt_internal_add_plugin(QODBCDriverPlugin
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
LIBRARIES
ODBC::ODBC
Qt::Core
@@ -25,9 +24,6 @@ qt_internal_add_plugin(QODBCDriverPlugin
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:odbc.pro:<TRUE>:
-# OTHER_FILES = "odbc.json"
-
## Scopes:
#####################################################################
diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp
index 4b7ad455f1..976911d458 100644
--- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp
+++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp
@@ -10,6 +10,7 @@
#include <qcoreapplication.h>
#include <qdatetime.h>
#include <qlist.h>
+#include <qloggingcategory.h>
#include <qmath.h>
#include <qsqlerror.h>
#include <qsqlfield.h>
@@ -26,38 +27,62 @@
QT_BEGIN_NAMESPACE
+static Q_LOGGING_CATEGORY(lcOdbc, "qt.sql.odbc")
+
using namespace Qt::StringLiterals;
+// non-standard ODBC SQL data type from SQL Server sometimes used instead of SQL_TIME
+#ifndef SQL_SS_TIME2
+#define SQL_SS_TIME2 (-154)
+#endif
+
// undefine this to prevent initial check of the ODBC driver
#define ODBC_CHECK_DRIVER
-static const int COLNAMESIZE = 256;
-static const SQLSMALLINT TABLENAMESIZE = 128;
+static constexpr int COLNAMESIZE = 256;
+static constexpr SQLSMALLINT TABLENAMESIZE = 128;
//Map Qt parameter types to ODBC types
-static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };
+static constexpr SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };
-inline static QString fromSQLTCHAR(const QVarLengthArray<SQLTCHAR>& input, qsizetype size=-1)
+class SqlStmtHandle
{
- QString result;
+public:
+ SqlStmtHandle(SQLHANDLE hDbc)
+ {
+ SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &stmtHandle);
+ }
+ ~SqlStmtHandle()
+ {
+ if (stmtHandle != SQL_NULL_HSTMT)
+ SQLFreeHandle(SQL_HANDLE_STMT, stmtHandle);
+ }
+ SQLHANDLE handle() const
+ {
+ return stmtHandle;
+ }
+ bool isValid() const
+ {
+ return stmtHandle != SQL_NULL_HSTMT;
+ }
+ SQLHANDLE stmtHandle = SQL_NULL_HSTMT;
+};
+template<typename C, int SIZE = sizeof(SQLTCHAR)>
+inline static QString fromSQLTCHAR(const C &input, qsizetype size = -1)
+{
// Remove any trailing \0 as some drivers misguidedly append one
- int realsize = qMin(size, input.size());
- if (realsize > 0 && input[realsize-1] == 0)
+ qsizetype realsize = qMin(size, input.size());
+ if (realsize > 0 && input[realsize - 1] == 0)
realsize--;
- switch(sizeof(SQLTCHAR)) {
- case 1:
- result=QString::fromUtf8((const char *)input.constData(), realsize);
- break;
- case 2:
- result = QString::fromUtf16(reinterpret_cast<const char16_t *>(input.constData()), realsize);
- break;
- case 4:
- result = QString::fromUcs4(reinterpret_cast<const char32_t *>(input.constData()), realsize);
- break;
- default:
- qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR)));
- }
- return result;
+ if constexpr (SIZE == 1)
+ return QString::fromUtf8(reinterpret_cast<const char *>(input.constData()), realsize);
+ else if constexpr (SIZE == 2)
+ return QString::fromUtf16(reinterpret_cast<const char16_t *>(input.constData()), realsize);
+ else if constexpr (SIZE == 4)
+ return QString::fromUcs4(reinterpret_cast<const char32_t *>(input.constData()), realsize);
+ else
+ static_assert(QtPrivate::value_dependent_false<SIZE>(),
+ "Don't know how to handle sizeof(SQLTCHAR) != 1/2/4");
}
template<int SIZE = sizeof(SQLTCHAR)>
@@ -74,14 +99,13 @@ QStringConverter::Encoding encodingForSqlTChar()
"Don't know how to handle sizeof(SQLTCHAR) != 1/2/4");
}
-inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(const QString &input)
+inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(QStringView input)
{
QVarLengthArray<SQLTCHAR> result;
QStringEncoder enc(encodingForSqlTChar());
result.resize(enc.requiredSpace(input.size()));
const auto end = enc.appendToBuffer(reinterpret_cast<char *>(result.data()), input);
result.resize((end - reinterpret_cast<char *>(result.data())) / sizeof(SQLTCHAR));
- result.append(0); // make sure it's null terminated, doesn't matter if it already is, it does if it isn't.
return result;
}
@@ -90,7 +114,7 @@ class QODBCDriverPrivate : public QSqlDriverPrivate
Q_DECLARE_PUBLIC(QODBCDriver)
public:
- enum DefaultCase {Lower, Mixed, Upper, Sensitive};
+ enum class DefaultCase {Lower, Mixed, Upper, Sensitive};
using QSqlDriverPrivate::QSqlDriverPrivate;
SQLHANDLE hEnv = nullptr;
@@ -111,15 +135,18 @@ public:
void checkHasMultiResults();
void checkSchemaUsage();
void checkDateTimePrecision();
+ void checkDefaultCase();
bool setConnectionOptions(const QString& connOpts);
void splitTableQualifier(const QString &qualifier, QString &catalog,
QString &schema, QString &table) const;
- DefaultCase defaultCase() const;
QString adjustCase(const QString&) const;
QChar quoteChar();
+ SQLRETURN sqlFetchNext(const SqlStmtHandle &hStmt) const;
+ SQLRETURN sqlFetchNext(SQLHANDLE hStmt) const;
private:
bool isQuoteInitialized = false;
QChar quote = u'"';
+ DefaultCase m_defaultCase = DefaultCase::Mixed;
};
class QODBCResultPrivate;
@@ -199,118 +226,121 @@ void QODBCResultPrivate::updateStmtHandleState()
disconnectCount = drv_d_func() ? drv_d_func()->disconnectCount : 0;
}
-static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode = nullptr)
+struct DiagRecord
+{
+ QString description;
+ QString sqlState;
+ QString errorCode;
+};
+static QList<DiagRecord> qWarnODBCHandle(int handleType, SQLHANDLE handle)
{
- SQLINTEGER nativeCode_ = 0;
+ SQLINTEGER nativeCode = 0;
SQLSMALLINT msgLen = 0;
+ SQLSMALLINT i = 1;
SQLRETURN r = SQL_NO_DATA;
- SQLTCHAR state_[SQL_SQLSTATE_SIZE+1];
- QVarLengthArray<SQLTCHAR> description_(SQL_MAX_MESSAGE_LENGTH);
- QString result;
- int i = 1;
+ QVarLengthArray<SQLTCHAR, SQL_SQLSTATE_SIZE + 1> state(SQL_SQLSTATE_SIZE + 1);
+ QVarLengthArray<SQLTCHAR, SQL_MAX_MESSAGE_LENGTH + 1> description(SQL_MAX_MESSAGE_LENGTH + 1);
+ QList<DiagRecord> result;
- description_[0] = 0;
+ if (!handle)
+ return result;
do {
r = SQLGetDiagRec(handleType,
handle,
i,
- state_,
- &nativeCode_,
- 0,
- 0,
+ state.data(),
+ &nativeCode,
+ description.data(),
+ description.size(),
&msgLen);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && msgLen > 0)
- description_.resize(msgLen+1);
- r = SQLGetDiagRec(handleType,
- handle,
- i,
- state_,
- &nativeCode_,
- description_.data(),
- description_.size(),
- &msgLen);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
- if (nativeCode)
- *nativeCode = nativeCode_;
- const QString tmpstore = fromSQLTCHAR(description_, msgLen);
- if (result != tmpstore) {
- if (!result.isEmpty())
- result += u' ';
- result += tmpstore;
- }
+ if (msgLen >= description.size()) {
+ description.resize(msgLen + 1); // incl. \0 termination
+ continue;
+ }
+ if (SQL_SUCCEEDED(r)) {
+ result.push_back({fromSQLTCHAR(description, msgLen),
+ fromSQLTCHAR(state),
+ QString::number(nativeCode)});
} else if (r == SQL_ERROR || r == SQL_INVALID_HANDLE) {
- return result;
+ break;
}
++i;
} while (r != SQL_NO_DATA);
return result;
}
-static QString qODBCWarn(const SQLHANDLE hStmt, const SQLHANDLE envHandle = 0,
- const SQLHANDLE pDbC = 0, int *nativeCode = nullptr)
+static QList<DiagRecord> qODBCWarn(const SQLHANDLE hStmt,
+ const SQLHANDLE envHandle = nullptr,
+ const SQLHANDLE pDbC = nullptr)
{
- QString result;
- if (envHandle)
- result += qWarnODBCHandle(SQL_HANDLE_ENV, envHandle, nativeCode);
- if (pDbC) {
- const QString dMessage = qWarnODBCHandle(SQL_HANDLE_DBC, pDbC, nativeCode);
- if (!dMessage.isEmpty()) {
- if (!result.isEmpty())
- result += u' ';
- result += dMessage;
- }
- }
- if (hStmt) {
- const QString hMessage = qWarnODBCHandle(SQL_HANDLE_STMT, hStmt, nativeCode);
- if (!hMessage.isEmpty()) {
- if (!result.isEmpty())
- result += u' ';
- result += hMessage;
- }
- }
+ QList<DiagRecord> result;
+ result.append(qWarnODBCHandle(SQL_HANDLE_ENV, envHandle));
+ result.append(qWarnODBCHandle(SQL_HANDLE_DBC, pDbC));
+ result.append(qWarnODBCHandle(SQL_HANDLE_STMT, hStmt));
return result;
}
-static QString qODBCWarn(const QODBCResultPrivate* odbc, int *nativeCode = nullptr)
+static QList<DiagRecord> qODBCWarn(const QODBCResultPrivate *odbc)
+{
+ return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc());
+}
+
+static QList<DiagRecord> qODBCWarn(const QODBCDriverPrivate *odbc)
{
- return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc(), nativeCode);
+ return qODBCWarn(nullptr, odbc->hEnv, odbc->hDbc);
}
-static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = nullptr)
+static DiagRecord combineRecords(const QList<DiagRecord> &records)
{
- return qODBCWarn(0, odbc->hEnv, odbc->hDbc, nativeCode);
+ const auto add = [](const DiagRecord &a, const DiagRecord &b) {
+ return DiagRecord{a.description + u' ' + b.description,
+ a.sqlState + u';' + b.sqlState,
+ a.errorCode + u';' + b.errorCode};
+ };
+ if (records.isEmpty())
+ return {};
+ return std::accumulate(std::next(records.begin()), records.end(), records.front(), add);
}
-static void qSqlWarning(const QString& message, const QODBCResultPrivate* odbc)
+static QSqlError errorFromDiagRecords(const QString &err,
+ QSqlError::ErrorType type,
+ const QList<DiagRecord> &records)
{
- qWarning() << message << "\tError:" << qODBCWarn(odbc);
+ if (records.empty())
+ return QSqlError("QODBC: unknown error"_L1, {}, type, {});
+ const auto combined = combineRecords(records);
+ return QSqlError("QODBC: "_L1 + err, combined.description + ", "_L1 + combined.sqlState, type,
+ combined.errorCode);
}
-static void qSqlWarning(const QString &message, const QODBCDriverPrivate *odbc)
+static QString errorStringFromDiagRecords(const QList<DiagRecord>& records)
{
- qWarning() << message << "\tError:" << qODBCWarn(odbc);
+ const auto combined = combineRecords(records);
+ return combined.description;
}
-static void qSqlWarning(const QString &message, const SQLHANDLE hStmt)
+template<class T>
+static void qSqlWarning(const QString &message, T &&val)
{
- qWarning() << message << "\tError:" << qODBCWarn(hStmt);
+ const auto addMsg = errorStringFromDiagRecords(qODBCWarn(val));
+ if (addMsg.isEmpty())
+ qCWarning(lcOdbc) << message;
+ else
+ qCWarning(lcOdbc) << message << "\tError:" << addMsg;
}
-static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QODBCResultPrivate* p)
+static QSqlError qMakeError(const QString &err,
+ QSqlError::ErrorType type,
+ const QODBCResultPrivate *p)
{
- int nativeCode = -1;
- QString message = qODBCWarn(p, &nativeCode);
- return QSqlError("QODBC: "_L1 + err, message, type,
- nativeCode != -1 ? QString::number(nativeCode) : QString());
+ return errorFromDiagRecords(err, type, qODBCWarn(p));
}
-static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
- const QODBCDriverPrivate* p)
+static QSqlError qMakeError(const QString &err,
+ QSqlError::ErrorType type,
+ const QODBCDriverPrivate *p)
{
- int nativeCode = -1;
- QString message = qODBCWarn(p, &nativeCode);
- return QSqlError("QODBC: "_L1 + err, message, type,
- nativeCode != -1 ? QString::number(nativeCode) : QString());
+ return errorFromDiagRecords(err, type, qODBCWarn(p));
}
static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true)
@@ -348,6 +378,7 @@ static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true)
case SQL_TYPE_DATE:
type = QMetaType::QDate;
break;
+ case SQL_SS_TIME2:
case SQL_TIME:
case SQL_TYPE_TIME:
type = QMetaType::QTime;
@@ -376,115 +407,69 @@ static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true)
return QMetaType(type);
}
-static QVariant qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode)
+template <typename CT>
+static QVariant getStringDataImpl(SQLHANDLE hStmt, SQLUSMALLINT column, qsizetype colSize, SQLSMALLINT targetType)
{
QString fieldVal;
SQLRETURN r = SQL_ERROR;
SQLLEN lengthIndicator = 0;
-
- // NB! colSize must be a multiple of 2 for unicode enabled DBs
- if (colSize <= 0) {
- colSize = 256;
- } else if (colSize > 65536) { // limit buffer size to 64 KB
- colSize = 65536;
- } else {
- colSize++; // make sure there is room for more than the 0 termination
- }
- if (unicode) {
+ QVarLengthArray<CT> buf(colSize);
+ while (true) {
r = SQLGetData(hStmt,
- column+1,
- SQL_C_TCHAR,
- NULL,
- 0,
- &lengthIndicator);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
- colSize = int(lengthIndicator / sizeof(SQLTCHAR) + 1);
- QVarLengthArray<SQLTCHAR> buf(colSize);
- memset(buf.data(), 0, colSize*sizeof(SQLTCHAR));
- while (true) {
- r = SQLGetData(hStmt,
- column+1,
- SQL_C_TCHAR,
- (SQLPOINTER)buf.data(),
- colSize*sizeof(SQLTCHAR),
- &lengthIndicator);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
- if (lengthIndicator == SQL_NULL_DATA) {
- return {};
- }
- // starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned
- // instead of the length (which sometimes was wrong in older versions)
- // see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx
- // if length indicator equals SQL_NO_TOTAL, indicating that
- // more data can be fetched, but size not known, collect data
- // and fetch next block
- if (lengthIndicator == SQL_NO_TOTAL) {
- fieldVal += fromSQLTCHAR(buf, colSize);
- continue;
- }
- // if SQL_SUCCESS_WITH_INFO is returned, indicating that
- // more data can be fetched, the length indicator does NOT
- // contain the number of bytes returned - it contains the
- // total number of bytes that CAN be fetched
- int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : int(lengthIndicator / sizeof(SQLTCHAR));
- fieldVal += fromSQLTCHAR(buf, rSize);
- if (lengthIndicator < SQLLEN(colSize*sizeof(SQLTCHAR))) {
- // workaround for Drivermanagers that don't return SQL_NO_DATA
- break;
- }
- } else if (r == SQL_NO_DATA) {
- break;
- } else {
- qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
+ column + 1,
+ targetType,
+ SQLPOINTER(buf.data()), SQLINTEGER(buf.size() * sizeof(CT)),
+ &lengthIndicator);
+ if (SQL_SUCCEEDED(r)) {
+ if (lengthIndicator == SQL_NULL_DATA) {
return {};
}
- }
- } else {
- r = SQLGetData(hStmt,
- column+1,
- SQL_C_CHAR,
- NULL,
- 0,
- &lengthIndicator);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
- colSize = lengthIndicator + 1;
- QVarLengthArray<SQLCHAR> buf(colSize);
- while (true) {
- r = SQLGetData(hStmt,
- column+1,
- SQL_C_CHAR,
- (SQLPOINTER)buf.data(),
- colSize,
- &lengthIndicator);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
- if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
- return {};
- }
- // if SQL_SUCCESS_WITH_INFO is returned, indicating that
- // more data can be fetched, the length indicator does NOT
- // contain the number of bytes returned - it contains the
- // total number of bytes that CAN be fetched
- qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator;
- // Remove any trailing \0 as some drivers misguidedly append one
- int realsize = qMin(rSize, buf.size());
- if (realsize > 0 && buf[realsize - 1] == 0)
- realsize--;
- fieldVal += QString::fromUtf8(reinterpret_cast<const char *>(buf.constData()), realsize);
- if (lengthIndicator < SQLLEN(colSize)) {
- // workaround for Drivermanagers that don't return SQL_NO_DATA
- break;
- }
- } else if (r == SQL_NO_DATA) {
+ // starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned
+ // instead of the length (which sometimes was wrong in older versions)
+ // see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx
+ // if length indicator equals SQL_NO_TOTAL, indicating that
+ // more data can be fetched, but size not known, collect data
+ // and fetch next block
+ if (lengthIndicator == SQL_NO_TOTAL) {
+ fieldVal += fromSQLTCHAR<QVarLengthArray<CT>, sizeof(CT)>(buf, buf.size());
+ continue;
+ }
+ // if SQL_SUCCESS_WITH_INFO is returned, indicating that
+ // more data can be fetched, the length indicator does NOT
+ // contain the number of bytes returned - it contains the
+ // total number of bytes that CAN be fetched
+ const qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO)
+ ? buf.size()
+ : qsizetype(lengthIndicator / sizeof(CT));
+ fieldVal += fromSQLTCHAR<QVarLengthArray<CT>, sizeof(CT)>(buf, rSize);
+ // lengthIndicator does not contain the termination character
+ if (lengthIndicator < SQLLEN((buf.size() - 1) * sizeof(CT))) {
+ // workaround for Drivermanagers that don't return SQL_NO_DATA
break;
- } else {
- qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
- return {};
}
+ } else if (r == SQL_NO_DATA) {
+ break;
+ } else {
+ qSqlWarning("QODBC::getStringData: Error while fetching data"_L1, hStmt);
+ return {};
}
}
return fieldVal;
}
+static QVariant qGetStringData(SQLHANDLE hStmt, SQLUSMALLINT column, int colSize, bool unicode)
+{
+ if (colSize <= 0) {
+ colSize = 256; // default Prealloc size of QVarLengthArray
+ } else if (colSize > 65536) { // limit buffer size to 64 KB
+ colSize = 65536;
+ } else {
+ colSize++; // make sure there is room for more than the 0 termination
+ }
+ return unicode ? getStringDataImpl<SQLTCHAR>(hStmt, column, colSize, SQL_C_TCHAR)
+ : getStringDataImpl<SQLCHAR>(hStmt, column, colSize, SQL_C_CHAR);
+}
+
static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)
{
QByteArray fieldVal;
@@ -496,19 +481,19 @@ static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)
SQLLEN lengthIndicator = 0;
SQLRETURN r = SQL_ERROR;
- QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE);
+ QVarLengthArray<SQLTCHAR, COLNAMESIZE> colName(COLNAMESIZE);
r = SQLDescribeCol(hStmt,
column + 1,
- colName.data(),
- COLNAMESIZE,
+ colName.data(), SQLSMALLINT(colName.size()),
&colNameLen,
&colType,
&colSize,
&colScale,
&nullable);
if (r != SQL_SUCCESS)
- qWarning() << "qGetBinaryData: Unable to describe column" << column;
+ qSqlWarning(("QODBC::qGetBinaryData: Unable to describe column %1"_L1)
+ .arg(QString::number(column)), hStmt);
// SQLDescribeCol may return 0 if size cannot be determined
if (!colSize)
colSize = 255;
@@ -523,7 +508,7 @@ static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)
const_cast<char *>(fieldVal.constData() + read),
colSize,
&lengthIndicator);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+ if (!SQL_SUCCEEDED(r))
break;
if (lengthIndicator == SQL_NULL_DATA)
return QVariant(QMetaType(QMetaType::QByteArray));
@@ -552,7 +537,7 @@ static QVariant qGetIntData(SQLHANDLE hStmt, int column, bool isSigned = true)
(SQLPOINTER)&intbuf,
sizeof(intbuf),
&lengthIndicator);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+ if (!SQL_SUCCEEDED(r))
return QVariant();
if (lengthIndicator == SQL_NULL_DATA)
return QVariant(QMetaType::fromType<int>());
@@ -572,7 +557,7 @@ static QVariant qGetDoubleData(SQLHANDLE hStmt, int column)
(SQLPOINTER) &dblbuf,
0,
&lengthIndicator);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
return QVariant();
}
if (lengthIndicator == SQL_NULL_DATA)
@@ -592,7 +577,7 @@ static QVariant qGetBigIntData(SQLHANDLE hStmt, int column, bool isSigned = true
(SQLPOINTER) &lngbuf,
sizeof(lngbuf),
&lengthIndicator);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+ if (!SQL_SUCCEEDED(r))
return QVariant();
if (lengthIndicator == SQL_NULL_DATA)
return QVariant(QMetaType::fromType<qlonglong>());
@@ -608,16 +593,14 @@ static bool isAutoValue(const SQLHANDLE hStmt, int column)
SQLLEN nNumericAttribute = 0; // Check for auto-increment
const SQLRETURN r = ::SQLColAttribute(hStmt, column + 1, SQL_DESC_AUTO_UNIQUE_VALUE,
0, 0, 0, &nNumericAttribute);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
- qSqlWarning(QStringLiteral("qMakeField: Unable to get autovalue attribute for column ")
- + QString::number(column), hStmt);
+ if (!SQL_SUCCEEDED(r)) {
+ qSqlWarning(("QODBC::isAutoValue: Unable to get autovalue attribute for column %1"_L1)
+ .arg(QString::number(column)), hStmt);
return false;
}
return nNumericAttribute != SQL_FALSE;
}
-static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMessage);
-
// creates a QSqlField from a valid hStmt generated
// by SQLColumns. The hStmt has to point to a valid position.
static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate* p)
@@ -629,7 +612,6 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate*
f.setLength(var.isNull() ? -1 : var.toInt()); // column size
var = qGetIntData(hStmt, 8).toInt();
f.setPrecision(var.isNull() ? -1 : var.toInt()); // precision
- f.setSqlType(type);
int required = qGetIntData(hStmt, 10).toInt(); // nullable-flag
// required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
if (required == SQL_NO_NULLS)
@@ -640,16 +622,7 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate*
return f;
}
-static QSqlField qMakeFieldInfo(const QODBCResultPrivate* p, int i )
-{
- QString errorMessage;
- const QSqlField result = qMakeFieldInfo(p->hStmt, i, &errorMessage);
- if (!errorMessage.isEmpty())
- qSqlWarning(errorMessage, p);
- return result;
-}
-
-static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMessage)
+static QSqlField qMakeFieldInfo(const QODBCResultPrivate *p, int i)
{
SQLSMALLINT colNameLen;
SQLSMALLINT colType;
@@ -657,12 +630,10 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess
SQLSMALLINT colScale;
SQLSMALLINT nullable;
SQLRETURN r = SQL_ERROR;
- QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE);
- errorMessage->clear();
- r = SQLDescribeCol(hStmt,
+ QVarLengthArray<SQLTCHAR, COLNAMESIZE> colName(COLNAMESIZE);
+ r = SQLDescribeCol(p->hStmt,
i+1,
- colName.data(),
- (SQLSMALLINT)COLNAMESIZE,
+ colName.data(), SQLSMALLINT(colName.size()),
&colNameLen,
&colType,
&colSize,
@@ -670,12 +641,13 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess
&nullable);
if (r != SQL_SUCCESS) {
- *errorMessage = QStringLiteral("qMakeField: Unable to describe column ") + QString::number(i);
+ qSqlWarning(("QODBC::qMakeFieldInfo: Unable to describe column %1"_L1)
+ .arg(QString::number(i)), p);
return QSqlField();
}
SQLLEN unsignedFlag = SQL_FALSE;
- r = SQLColAttribute (hStmt,
+ r = SQLColAttribute (p->hStmt,
i + 1,
SQL_DESC_UNSIGNED,
0,
@@ -683,15 +655,14 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess
0,
&unsignedFlag);
if (r != SQL_SUCCESS) {
- qSqlWarning(QStringLiteral("qMakeField: Unable to get column attributes for column ")
- + QString::number(i), hStmt);
+ qSqlWarning(("QODBC::qMakeFieldInfo: Unable to get column attributes for column %1"_L1)
+ .arg(QString::number(i)), p);
}
const QString qColName(fromSQLTCHAR(colName, colNameLen));
// nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
QMetaType type = qDecodeODBCType(colType, unsignedFlag == SQL_FALSE);
QSqlField f(qColName, type);
- f.setSqlType(colType);
f.setLength(colSize == 0 ? -1 : int(colSize));
f.setPrecision(colScale == 0 ? -1 : int(colScale));
if (nullable == SQL_NO_NULLS)
@@ -699,10 +670,10 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess
else if (nullable == SQL_NULLABLE)
f.setRequired(false);
// else we don't know
- f.setAutoValue(isAutoValue(hStmt, i));
- QVarLengthArray<SQLTCHAR> tableName(TABLENAMESIZE);
+ f.setAutoValue(isAutoValue(p->hStmt, i));
+ QVarLengthArray<SQLTCHAR, TABLENAMESIZE> tableName(TABLENAMESIZE);
SQLSMALLINT tableNameLen;
- r = SQLColAttribute(hStmt,
+ r = SQLColAttribute(p->hStmt,
i + 1,
SQL_DESC_BASE_TABLE_NAME,
tableName.data(),
@@ -731,7 +702,7 @@ QChar QODBCDriverPrivate::quoteChar()
&driverResponse,
sizeof(driverResponse),
&length);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ if (SQL_SUCCEEDED(r))
quote = QChar(driverResponse[0]);
else
quote = u'"';
@@ -740,7 +711,19 @@ QChar QODBCDriverPrivate::quoteChar()
return quote;
}
-static SQLRETURN qt_string_SQLSetConnectAttr(SQLHDBC handle, SQLINTEGER attr, const QString &val)
+SQLRETURN QODBCDriverPrivate::sqlFetchNext(const SqlStmtHandle &hStmt) const
+{
+ return sqlFetchNext(hStmt.handle());
+}
+
+SQLRETURN QODBCDriverPrivate::sqlFetchNext(SQLHANDLE hStmt) const
+{
+ if (hasSQLFetchScroll)
+ return SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0);
+ return SQLFetch(hStmt);
+}
+
+static SQLRETURN qt_string_SQLSetConnectAttr(SQLHDBC handle, SQLINTEGER attr, QStringView val)
{
auto encoded = toSQLTCHAR(val);
return SQLSetConnectAttr(handle, attr,
@@ -749,101 +732,106 @@ static SQLRETURN qt_string_SQLSetConnectAttr(SQLHDBC handle, SQLINTEGER attr, co
}
-bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts)
+bool QODBCDriverPrivate::setConnectionOptions(const QString &connOpts)
{
// Set any connection attributes
- const QStringList opts(connOpts.split(u';', Qt::SkipEmptyParts));
SQLRETURN r = SQL_SUCCESS;
- for (int i = 0; i < opts.count(); ++i) {
- const QString tmp(opts.at(i));
+ for (const auto connOpt : QStringTokenizer{connOpts, u';'}) {
int idx;
- if ((idx = tmp.indexOf(u'=')) == -1) {
- qWarning() << "QODBCDriver::open: Illegal connect option value '" << tmp << '\'';
+ if ((idx = connOpt.indexOf(u'=')) == -1) {
+ qSqlWarning(("QODBCDriver::open: Illegal connect option value '%1'"_L1)
+ .arg(connOpt), this);
continue;
}
- const QString opt(tmp.left(idx));
- const QString val(tmp.mid(idx + 1).simplified());
+ const auto opt(connOpt.left(idx));
+ const auto val(connOpt.mid(idx + 1).trimmed());
SQLUINTEGER v = 0;
r = SQL_SUCCESS;
- if (opt.toUpper() == "SQL_ATTR_ACCESS_MODE"_L1) {
- if (val.toUpper() == "SQL_MODE_READ_ONLY"_L1) {
+ if (opt == "SQL_ATTR_ACCESS_MODE"_L1) {
+ if (val == "SQL_MODE_READ_ONLY"_L1) {
v = SQL_MODE_READ_ONLY;
- } else if (val.toUpper() == "SQL_MODE_READ_WRITE"_L1) {
+ } else if (val == "SQL_MODE_READ_WRITE"_L1) {
v = SQL_MODE_READ_WRITE;
} else {
- qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
+ .arg(val), this);
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_CONNECTION_TIMEOUT"_L1) {
+ } else if (opt == "SQL_ATTR_CONNECTION_TIMEOUT"_L1) {
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_LOGIN_TIMEOUT"_L1) {
+ } else if (opt == "SQL_ATTR_LOGIN_TIMEOUT"_L1) {
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_CURRENT_CATALOG"_L1) {
+ } else if (opt == "SQL_ATTR_CURRENT_CATALOG"_L1) {
r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, val);
- } else if (opt.toUpper() == "SQL_ATTR_METADATA_ID"_L1) {
- if (val.toUpper() == "SQL_TRUE"_L1) {
+ } else if (opt == "SQL_ATTR_METADATA_ID"_L1) {
+ if (val == "SQL_TRUE"_L1) {
v = SQL_TRUE;
- } else if (val.toUpper() == "SQL_FALSE"_L1) {
+ } else if (val == "SQL_FALSE"_L1) {
v = SQL_FALSE;
} else {
- qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
+ .arg(val), this);
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_METADATA_ID, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_PACKET_SIZE"_L1) {
+ } else if (opt == "SQL_ATTR_PACKET_SIZE"_L1) {
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_TRACEFILE"_L1) {
+ } else if (opt == "SQL_ATTR_TRACEFILE"_L1) {
r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, val);
- } else if (opt.toUpper() == "SQL_ATTR_TRACE"_L1) {
- if (val.toUpper() == "SQL_OPT_TRACE_OFF"_L1) {
+ } else if (opt == "SQL_ATTR_TRACE"_L1) {
+ if (val == "SQL_OPT_TRACE_OFF"_L1) {
v = SQL_OPT_TRACE_OFF;
- } else if (val.toUpper() == "SQL_OPT_TRACE_ON"_L1) {
+ } else if (val == "SQL_OPT_TRACE_ON"_L1) {
v = SQL_OPT_TRACE_ON;
} else {
- qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
+ .arg(val), this);
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACE, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_CONNECTION_POOLING"_L1) {
+ } else if (opt == "SQL_ATTR_CONNECTION_POOLING"_L1) {
if (val == "SQL_CP_OFF"_L1)
v = SQL_CP_OFF;
- else if (val.toUpper() == "SQL_CP_ONE_PER_DRIVER"_L1)
+ else if (val == "SQL_CP_ONE_PER_DRIVER"_L1)
v = SQL_CP_ONE_PER_DRIVER;
- else if (val.toUpper() == "SQL_CP_ONE_PER_HENV"_L1)
+ else if (val == "SQL_CP_ONE_PER_HENV"_L1)
v = SQL_CP_ONE_PER_HENV;
- else if (val.toUpper() == "SQL_CP_DEFAULT"_L1)
+ else if (val == "SQL_CP_DEFAULT"_L1)
v = SQL_CP_DEFAULT;
else {
- qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
+ .arg(val), this);
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_CP_MATCH"_L1) {
- if (val.toUpper() == "SQL_CP_STRICT_MATCH"_L1)
+ } else if (opt == "SQL_ATTR_CP_MATCH"_L1) {
+ if (val == "SQL_CP_STRICT_MATCH"_L1)
v = SQL_CP_STRICT_MATCH;
- else if (val.toUpper() == "SQL_CP_RELAXED_MATCH"_L1)
+ else if (val == "SQL_CP_RELAXED_MATCH"_L1)
v = SQL_CP_RELAXED_MATCH;
- else if (val.toUpper() == "SQL_CP_MATCH_DEFAULT"_L1)
+ else if (val == "SQL_CP_MATCH_DEFAULT"_L1)
v = SQL_CP_MATCH_DEFAULT;
else {
- qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
+ .arg(val), this);
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CP_MATCH, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_ODBC_VERSION"_L1) {
+ } else if (opt == "SQL_ATTR_ODBC_VERSION"_L1) {
// Already handled in QODBCDriver::open()
continue;
} else {
- qWarning() << "QODBCDriver::open: Unknown connection attribute '" << opt << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown connection attribute '%1'"_L1)
+ .arg(opt), this);
}
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
- qSqlWarning(QString::fromLatin1("QODBCDriver::open: Unable to set connection attribute'%1'").arg(
- opt), this);
+ if (!SQL_SUCCEEDED(r))
+ qSqlWarning(("QODBCDriver::open: Unable to set connection attribute '%1'"_L1)
+ .arg(opt), this);
}
return true;
}
@@ -851,60 +839,65 @@ bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts)
void QODBCDriverPrivate::splitTableQualifier(const QString &qualifier, QString &catalog,
QString &schema, QString &table) const
{
+ Q_Q(const QODBCDriver);
+ const auto adjustName = [&](const QString &name) {
+ if (q->isIdentifierEscaped(name, QSqlDriver::TableName))
+ return q->stripDelimiters(name, QSqlDriver::TableName);
+ return adjustCase(name);
+ };
+ catalog.clear();
+ schema.clear();
+ table.clear();
if (!useSchema) {
- table = qualifier;
+ table = adjustName(qualifier);
return;
}
const QList<QStringView> l = QStringView(qualifier).split(u'.');
switch (l.count()) {
case 1:
- table = qualifier;
+ table = adjustName(qualifier);
break;
case 2:
- schema = l.at(0).toString();
- table = l.at(1).toString();
+ schema = adjustName(l.at(0).toString());
+ table = adjustName(l.at(1).toString());
break;
case 3:
- catalog = l.at(0).toString();
- schema = l.at(1).toString();
- table = l.at(2).toString();
+ catalog = adjustName(l.at(0).toString());
+ schema = adjustName(l.at(1).toString());
+ table = adjustName(l.at(2).toString());
break;
default:
- qSqlWarning(QString::fromLatin1("QODBCDriver::splitTableQualifier: Unable to split table qualifier '%1'")
+ qSqlWarning(("QODBCDriver::splitTableQualifier: Unable to split table qualifier '%1'"_L1)
.arg(qualifier), this);
break;
}
}
-QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const
+void QODBCDriverPrivate::checkDefaultCase()
{
- DefaultCase ret;
+ m_defaultCase = DefaultCase::Mixed; //arbitrary case if driver cannot be queried
SQLUSMALLINT casing;
- int r = SQLGetInfo(hDbc,
- SQL_IDENTIFIER_CASE,
- &casing,
- sizeof(casing),
- NULL);
- if ( r != SQL_SUCCESS)
- ret = Mixed;//arbitrary case if driver cannot be queried
- else {
+ SQLRETURN r = SQLGetInfo(hDbc,
+ SQL_IDENTIFIER_CASE,
+ &casing,
+ sizeof(casing),
+ NULL);
+ if (r == SQL_SUCCESS) {
switch (casing) {
- case (SQL_IC_UPPER):
- ret = Upper;
- break;
- case (SQL_IC_LOWER):
- ret = Lower;
- break;
- case (SQL_IC_SENSITIVE):
- ret = Sensitive;
- break;
- case (SQL_IC_MIXED):
- default:
- ret = Mixed;
- break;
+ case SQL_IC_UPPER:
+ m_defaultCase = DefaultCase::Upper;
+ break;
+ case SQL_IC_LOWER:
+ m_defaultCase = DefaultCase::Lower;
+ break;
+ case SQL_IC_SENSITIVE:
+ m_defaultCase = DefaultCase::Sensitive;
+ break;
+ case SQL_IC_MIXED:
+ m_defaultCase = DefaultCase::Mixed;
+ break;
}
}
- return ret;
}
/*
@@ -913,20 +906,16 @@ QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const
*/
QString QODBCDriverPrivate::adjustCase(const QString &identifier) const
{
- QString ret = identifier;
- switch(defaultCase()) {
- case (Lower):
- ret = identifier.toLower();
- break;
- case (Upper):
- ret = identifier.toUpper();
- break;
- case(Mixed):
- case(Sensitive):
- default:
- ret = identifier;
+ switch (m_defaultCase) {
+ case DefaultCase::Lower:
+ return identifier.toLower();
+ case DefaultCase::Upper:
+ return identifier.toUpper();
+ case DefaultCase::Mixed:
+ case DefaultCase::Sensitive:
+ break;
}
- return ret;
+ return identifier;
}
////////////////////////////////////////////////////////////////////////////
@@ -942,8 +931,7 @@ QODBCResult::~QODBCResult()
if (d->hStmt && d->isStmtHandleValid() && driver() && driver()->isOpen()) {
SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
if (r != SQL_SUCCESS)
- qSqlWarning("QODBCDriver: Unable to free statement handle "_L1
- + QString::number(r), d);
+ qSqlWarning(("QODBCResult: Unable to free statement handle "_L1), d);
}
}
@@ -987,7 +975,7 @@ bool QODBCResult::reset (const QString& query)
(SQLPOINTER)SQL_CURSOR_STATIC,
SQL_IS_UINTEGER);
}
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
"Please check your ODBC driver configuration"), QSqlError::StatementError, d));
@@ -1000,7 +988,7 @@ bool QODBCResult::reset (const QString& query)
encoded.data(),
SQLINTEGER(encoded.size()));
}
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r!= SQL_NO_DATA) {
+ if (!SQL_SUCCEEDED(r) && r!= SQL_NO_DATA) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to execute statement"), QSqlError::StatementError, d));
return false;
@@ -1008,7 +996,7 @@ bool QODBCResult::reset (const QString& query)
SQLULEN isScrollable = 0;
r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ if (SQL_SUCCEEDED(r))
setForwardOnly(isScrollable == SQL_NONSCROLLABLE);
SQLSMALLINT count = 0;
@@ -1077,7 +1065,7 @@ bool QODBCResult::fetchNext()
else
r = SQLFetch(d->hStmt);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
if (r != SQL_NO_DATA)
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to fetch next"), QSqlError::ConnectionError, d));
@@ -1174,7 +1162,8 @@ QVariant QODBCResult::data(int field)
{
Q_D(QODBCResult);
if (field >= d->rInf.count() || field < 0) {
- qWarning() << "QODBCResult::data: column" << field << "out of range";
+ qSqlWarning(("QODBCResult::data: column %1 out of range"_L1)
+ .arg(QString::number(field)), d);
return QVariant();
}
if (field < d->fieldCacheIdx)
@@ -1210,7 +1199,7 @@ QVariant QODBCResult::data(int field)
(SQLPOINTER)&dbuf,
0,
&lengthIndicator);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
+ if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA))
d->fieldCache[i] = QVariant(QDate(dbuf.year, dbuf.month, dbuf.day));
else
d->fieldCache[i] = QVariant(QMetaType::fromType<QDate>());
@@ -1223,7 +1212,7 @@ QVariant QODBCResult::data(int field)
(SQLPOINTER)&tbuf,
0,
&lengthIndicator);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
+ if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA))
d->fieldCache[i] = QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second));
else
d->fieldCache[i] = QVariant(QMetaType::fromType<QTime>());
@@ -1236,7 +1225,7 @@ QVariant QODBCResult::data(int field)
(SQLPOINTER)&dtbuf,
0,
&lengthIndicator);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
+ if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA))
d->fieldCache[i] = QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day),
QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000)));
else
@@ -1300,8 +1289,7 @@ int QODBCResult::numRowsAffected()
SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount);
if (r == SQL_SUCCESS)
return affectedRowCount;
- else
- qSqlWarning("QODBCResult::numRowsAffected: Unable to count affected rows"_L1, d);
+ qSqlWarning("QODBCResult::numRowsAffected: Unable to count affected rows"_L1, d);
return -1;
}
@@ -1341,7 +1329,7 @@ bool QODBCResult::prepare(const QString& query)
(SQLPOINTER)SQL_CURSOR_STATIC,
SQL_IS_UINTEGER);
}
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
"Please check your ODBC driver configuration"), QSqlError::StatementError, d));
@@ -1382,8 +1370,7 @@ bool QODBCResult::exec()
QVariantList &values = boundValues();
QByteArrayList tmpStorage(values.count(), QByteArray()); // targets for SQLBindParameter()
- QVarLengthArray<SQLLEN, 32> indicators(values.count());
- memset(indicators.data(), 0, indicators.size() * sizeof(SQLLEN));
+ QVarLengthArray<SQLLEN, 32> indicators(values.count(), 0);
// bind parameters - only positional binding allowed
SQLRETURN r;
@@ -1667,15 +1654,15 @@ bool QODBCResult::exec()
break; }
}
if (r != SQL_SUCCESS) {
- qWarning() << "QODBCResult::exec: unable to bind variable:" << qODBCWarn(d);
+ qSqlWarning("QODBCResult::exec: unable to bind variable:"_L1, d);
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to bind variable"), QSqlError::StatementError, d));
return false;
}
}
r = SQLExecute(d->hStmt);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {
- qWarning() << "QODBCResult::exec: Unable to execute statement:" << qODBCWarn(d);
+ if (!SQL_SUCCEEDED(r) && r != SQL_NO_DATA) {
+ qSqlWarning("QODBCResult::exec: Unable to execute statement:"_L1, d);
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to execute statement"), QSqlError::StatementError, d));
return false;
@@ -1683,7 +1670,7 @@ bool QODBCResult::exec()
SQLULEN isScrollable = 0;
r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ if (SQL_SUCCEEDED(r))
setForwardOnly(isScrollable == SQL_NONSCROLLABLE);
SQLSMALLINT count = 0;
@@ -1815,9 +1802,7 @@ bool QODBCResult::nextResult()
SQLRETURN r = SQLMoreResults(d->hStmt);
if (r != SQL_SUCCESS) {
if (r == SQL_SUCCESS_WITH_INFO) {
- int nativeCode = -1;
- QString message = qODBCWarn(d, &nativeCode);
- qWarning() << "QODBCResult::nextResult():" << message;
+ qSqlWarning("QODBCResult::nextResult:"_L1, d);
} else {
if (r != SQL_NO_DATA)
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
@@ -1936,6 +1921,18 @@ bool QODBCDriver::open(const QString & db,
int,
const QString& connOpts)
{
+ const auto ensureEscaped = [](QString arg) -> QString {
+ QChar quoteChar;
+ if (arg.startsWith(u'"'))
+ quoteChar = u'\'';
+ else if (arg.startsWith(u'\''))
+ quoteChar = u'"';
+ else if (arg.contains(u';'))
+ quoteChar = u'"';
+ else
+ return arg;
+ return quoteChar + arg + quoteChar;
+ };
Q_D(QODBCDriver);
if (isOpen())
close();
@@ -1943,7 +1940,7 @@ bool QODBCDriver::open(const QString & db,
r = SQLAllocHandle(SQL_HANDLE_ENV,
SQL_NULL_HANDLE,
&d->hEnv);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
qSqlWarning("QODBCDriver::open: Unable to allocate environment"_L1, d);
setOpenError(true);
return false;
@@ -1955,7 +1952,7 @@ bool QODBCDriver::open(const QString & db,
r = SQLAllocHandle(SQL_HANDLE_DBC,
d->hEnv,
&d->hDbc);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
qSqlWarning("QODBCDriver::open: Unable to allocate connection"_L1, d);
setOpenError(true);
cleanup();
@@ -1971,33 +1968,31 @@ bool QODBCDriver::open(const QString & db,
QString connQStr;
// support the "DRIVER={SQL SERVER};SERVER=blah" syntax
if (db.contains(".dsn"_L1, Qt::CaseInsensitive))
- connQStr = "FILEDSN="_L1 + db;
+ connQStr = "FILEDSN="_L1 + ensureEscaped(db);
else if (db.contains("DRIVER="_L1, Qt::CaseInsensitive)
|| db.contains("SERVER="_L1, Qt::CaseInsensitive))
connQStr = db;
else
- connQStr = "DSN="_L1 + db;
+ connQStr = "DSN="_L1 + ensureEscaped(db);
if (!user.isEmpty())
- connQStr += ";UID="_L1 + user;
+ connQStr += ";UID="_L1 + ensureEscaped(user);
if (!password.isEmpty())
- connQStr += ";PWD="_L1 + password;
+ connQStr += ";PWD="_L1 + ensureEscaped(password);
SQLSMALLINT cb;
- QVarLengthArray<SQLTCHAR> connOut(1024);
- memset(connOut.data(), 0, connOut.size() * sizeof(SQLTCHAR));
+ QVarLengthArray<SQLTCHAR, 1024> connOut(1024);
{
auto encoded = toSQLTCHAR(connQStr);
r = SQLDriverConnect(d->hDbc,
nullptr,
encoded.data(), SQLSMALLINT(encoded.size()),
- connOut.data(),
- 1024,
+ connOut.data(), SQLSMALLINT(connOut.size()),
&cb,
/*SQL_DRIVER_NOPROMPT*/0);
}
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
setOpenError(true);
cleanup();
@@ -2018,6 +2013,7 @@ bool QODBCDriver::open(const QString & db,
d->checkHasSQLFetchScroll();
d->checkHasMultiResults();
d->checkDateTimePrecision();
+ d->checkDefaultCase();
setOpen(true);
setOpenError(false);
if (d->dbmsType == MSSqlServer) {
@@ -2076,7 +2072,7 @@ void QODBCDriverPrivate::checkUnicode()
(SQLPOINTER)&fFunc,
sizeof(fFunc),
NULL);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WCHAR)) {
+ if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WCHAR)) {
unicode = true;
return;
}
@@ -2086,7 +2082,7 @@ void QODBCDriverPrivate::checkUnicode()
(SQLPOINTER)&fFunc,
sizeof(fFunc),
NULL);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WVARCHAR)) {
+ if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WVARCHAR)) {
unicode = true;
return;
}
@@ -2096,27 +2092,36 @@ void QODBCDriverPrivate::checkUnicode()
(SQLPOINTER)&fFunc,
sizeof(fFunc),
NULL);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WLONGVARCHAR)) {
+ if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WLONGVARCHAR)) {
unicode = true;
return;
}
- SQLHANDLE hStmt;
- r = SQLAllocHandle(SQL_HANDLE_STMT,
- hDbc,
- &hStmt);
- r = SQLExecDirect(hStmt, toSQLTCHAR("select 'test'"_L1).data(), SQL_NTS);
+ SqlStmtHandle hStmt(hDbc);
+ // for databases which do not return something useful in SQLGetInfo and are picky about a
+ // 'SELECT' statement without 'FROM' but support VALUE(foo) statement like e.g. DB2 or Oracle
+ const std::array<QStringView, 3> statements = {
+ u"select 'test'",
+ u"values('test')",
+ u"select 'test' from dual",
+ };
+ for (const auto &statement : statements) {
+ auto encoded = toSQLTCHAR(statement);
+ r = SQLExecDirect(hStmt.handle(), encoded.data(), SQLINTEGER(encoded.size()));
+ if (r == SQL_SUCCESS)
+ break;
+ }
if (r == SQL_SUCCESS) {
- r = SQLFetch(hStmt);
+ r = SQLFetch(hStmt.handle());
if (r == SQL_SUCCESS) {
- QVarLengthArray<SQLWCHAR> buffer(10);
- r = SQLGetData(hStmt, 1, SQL_C_WCHAR, buffer.data(), buffer.size() * sizeof(SQLWCHAR), NULL);
+ QVarLengthArray<SQLWCHAR, 10> buffer(10);
+ r = SQLGetData(hStmt.handle(), 1, SQL_C_WCHAR, buffer.data(),
+ buffer.size() * sizeof(SQLWCHAR), NULL);
if (r == SQL_SUCCESS && fromSQLTCHAR(buffer) == "test"_L1) {
unicode = true;
}
}
}
- r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
bool QODBCDriverPrivate::checkDriver() const
@@ -2146,9 +2151,10 @@ bool QODBCDriverPrivate::checkDriver() const
return false;
}
if (sup == SQL_FALSE) {
- qWarning () << "QODBCDriver::open: Warning - Driver doesn't support all needed functionality ("
- << func
- << ").\nPlease look at the Qt SQL Module Driver documentation for more information.";
+ qSqlWarning(("QODBCDriver::checkDriver: Driver doesn't support all needed "
+ "functionality (func id %1).\nPlease look at the Qt SQL Module "
+ "Driver documentation for more information."_L1)
+ .arg(QString::number(func)), this);
return false;
}
}
@@ -2163,8 +2169,9 @@ bool QODBCDriverPrivate::checkDriver() const
return false;
}
if (sup == SQL_FALSE) {
- qWarning() << "QODBCDriver::checkDriver: Warning - Driver doesn't support some non-critical functions ("
- << func << ')';
+ qSqlWarning(("QODBCDriver::checkDriver: Driver doesn't support some "
+ "non-critical functions (func id %1)."_L1)
+ .arg(QString::number(func)), this);
return true;
}
}
@@ -2183,23 +2190,22 @@ void QODBCDriverPrivate::checkSchemaUsage()
(SQLPOINTER) &val,
sizeof(val),
NULL);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ if (SQL_SUCCEEDED(r))
useSchema = (val != 0);
}
void QODBCDriverPrivate::checkDBMS()
{
SQLRETURN r;
- QVarLengthArray<SQLTCHAR> serverString(200);
+ QVarLengthArray<SQLTCHAR, 200> serverString(200);
SQLSMALLINT t;
- memset(serverString.data(), 0, serverString.size() * sizeof(SQLTCHAR));
r = SQLGetInfo(hDbc,
SQL_DBMS_NAME,
serverString.data(),
SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
&t);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ if (SQL_SUCCEEDED(r)) {
const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
if (serverType.contains("PostgreSQL"_L1, Qt::CaseInsensitive))
dbmsType = QSqlDriver::PostgreSQL;
@@ -2217,7 +2223,7 @@ void QODBCDriverPrivate::checkDBMS()
serverString.data(),
SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
&t);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ if (SQL_SUCCEEDED(r)) {
const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
isFreeTDSDriver = serverType.contains("tdsodbc"_L1, Qt::CaseInsensitive);
unicode = unicode && !isFreeTDSDriver;
@@ -2228,46 +2234,42 @@ void QODBCDriverPrivate::checkHasSQLFetchScroll()
{
SQLUSMALLINT sup;
SQLRETURN r = SQLGetFunctions(hDbc, SQL_API_SQLFETCHSCROLL, &sup);
- if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || sup != SQL_TRUE) {
+ if ((!SQL_SUCCEEDED(r)) || sup != SQL_TRUE) {
hasSQLFetchScroll = false;
- qWarning("QODBCDriver::checkHasSQLFetchScroll: Warning - Driver doesn't support scrollable result sets, use forward only mode for queries");
+ qSqlWarning("QODBCDriver::checkHasSQLFetchScroll: Driver doesn't support "
+ "scrollable result sets, use forward only mode for queries"_L1, this);
}
}
void QODBCDriverPrivate::checkHasMultiResults()
{
- QVarLengthArray<SQLTCHAR> driverResponse(2);
+ QVarLengthArray<SQLTCHAR, 2> driverResponse(2);
SQLSMALLINT length;
SQLRETURN r = SQLGetInfo(hDbc,
SQL_MULT_RESULT_SETS,
driverResponse.data(),
SQLSMALLINT(driverResponse.size() * sizeof(SQLTCHAR)),
&length);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
- hasMultiResultSets = fromSQLTCHAR(driverResponse, length/sizeof(SQLTCHAR)).startsWith(u'Y');
+ if (SQL_SUCCEEDED(r))
+ hasMultiResultSets = fromSQLTCHAR(driverResponse, length / sizeof(SQLTCHAR)).startsWith(u'Y');
}
void QODBCDriverPrivate::checkDateTimePrecision()
{
SQLINTEGER columnSize;
- SQLHANDLE hStmt;
+ SqlStmtHandle hStmt(hDbc);
- SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
- if (r != SQL_SUCCESS) {
+ if (!hStmt.isValid())
return;
- }
- r = SQLGetTypeInfo(hStmt, SQL_TIMESTAMP);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
- r = SQLFetch(hStmt);
- if ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO )
- {
- if (SQLGetData(hStmt, 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS) {
+ SQLRETURN r = SQLGetTypeInfo(hStmt.handle(), SQL_TIMESTAMP);
+ if (SQL_SUCCEEDED(r)) {
+ r = SQLFetch(hStmt.handle());
+ if (SQL_SUCCEEDED(r)) {
+ if (SQLGetData(hStmt.handle(), 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS)
datetimePrecision = (int)columnSize;
- }
}
}
- SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
QSqlResult *QODBCDriver::createResult() const
@@ -2279,7 +2281,7 @@ bool QODBCDriver::beginTransaction()
{
Q_D(QODBCDriver);
if (!isOpen()) {
- qWarning("QODBCDriver::beginTransaction: Database not open");
+ qSqlWarning("QODBCDriver::beginTransaction: Database not open"_L1, d);
return false;
}
SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF);
@@ -2299,7 +2301,7 @@ bool QODBCDriver::commitTransaction()
{
Q_D(QODBCDriver);
if (!isOpen()) {
- qWarning("QODBCDriver::commitTransaction: Database not open");
+ qSqlWarning("QODBCDriver::commitTransaction: Database not open"_L1, d);
return false;
}
SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
@@ -2317,7 +2319,7 @@ bool QODBCDriver::rollbackTransaction()
{
Q_D(QODBCDriver);
if (!isOpen()) {
- qWarning("QODBCDriver::rollbackTransaction: Database not open");
+ qSqlWarning("QODBCDriver::rollbackTransaction: Database not open"_L1, d);
return false;
}
SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
@@ -2352,19 +2354,16 @@ QStringList QODBCDriver::tables(QSql::TableType type) const
QStringList tl;
if (!isOpen())
return tl;
- SQLHANDLE hStmt;
- SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
- d->hDbc,
- &hStmt);
- if (r != SQL_SUCCESS) {
+ SqlStmtHandle hStmt(d->hDbc);
+ if (!hStmt.isValid()) {
qSqlWarning("QODBCDriver::tables: Unable to allocate handle"_L1, d);
return tl;
}
- r = SQLSetStmtAttr(hStmt,
- SQL_ATTR_CURSOR_TYPE,
- (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
- SQL_IS_UINTEGER);
+ SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
QStringList tableType;
if (type & QSql::Tables)
tableType += "TABLE"_L1;
@@ -2378,7 +2377,7 @@ QStringList QODBCDriver::tables(QSql::TableType type) const
{
auto joinedTableTypeString = toSQLTCHAR(tableType.join(u','));
- r = SQLTables(hStmt,
+ r = SQLTables(hStmt.handle(),
nullptr, 0,
nullptr, 0,
nullptr, 0,
@@ -2386,34 +2385,21 @@ QStringList QODBCDriver::tables(QSql::TableType type) const
}
if (r != SQL_SUCCESS)
- qSqlWarning("QODBCDriver::tables Unable to execute table list"_L1, d);
+ qSqlWarning("QODBCDriver::tables Unable to execute table list"_L1,
+ hStmt.handle());
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
-
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {
- qWarning() << "QODBCDriver::tables failed to retrieve table/view list: (" << r << "," << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ")";
+ r = d->sqlFetchNext(hStmt);
+ if (!SQL_SUCCEEDED(r) && r != SQL_NO_DATA) {
+ qSqlWarning("QODBCDriver::tables failed to retrieve table/view list"_L1,
+ hStmt.handle());
return QStringList();
}
while (r == SQL_SUCCESS) {
- tl.append(qGetStringData(hStmt, 2, -1, d->unicode).toString());
-
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
+ tl.append(qGetStringData(hStmt.handle(), 2, -1, d->unicode).toString());
+ r = d->sqlFetchNext(hStmt);
}
- r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
- if (r!= SQL_SUCCESS)
- qSqlWarning("QODBCDriver: Unable to free statement handle"_L1 + QString::number(r), d);
return tl;
}
@@ -2426,41 +2412,23 @@ QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const
bool usingSpecialColumns = false;
QSqlRecord rec = record(tablename);
- SQLHANDLE hStmt;
- SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
- d->hDbc,
- &hStmt);
- if (r != SQL_SUCCESS) {
- qSqlWarning("QODBCDriver::primaryIndex: Unable to list primary key"_L1, d);
+ SqlStmtHandle hStmt(d->hDbc);
+ if (!hStmt.isValid()) {
+ qSqlWarning("QODBCDriver::primaryIndex: Unable to allocate handle"_L1, d);
return index;
}
QString catalog, schema, table;
d->splitTableQualifier(tablename, catalog, schema, table);
- if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
- catalog = stripDelimiters(catalog, QSqlDriver::TableName);
- else
- catalog = d->adjustCase(catalog);
-
- if (isIdentifierEscaped(schema, QSqlDriver::TableName))
- schema = stripDelimiters(schema, QSqlDriver::TableName);
- else
- schema = d->adjustCase(schema);
-
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
- else
- table = d->adjustCase(table);
-
- r = SQLSetStmtAttr(hStmt,
- SQL_ATTR_CURSOR_TYPE,
- (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
- SQL_IS_UINTEGER);
+ SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
{
auto c = toSQLTCHAR(catalog);
auto s = toSQLTCHAR(schema);
auto t = toSQLTCHAR(table);
- r = SQLPrimaryKeys(hStmt,
+ r = SQLPrimaryKeys(hStmt.handle(),
catalog.isEmpty() ? nullptr : c.data(), c.size(),
schema.isEmpty() ? nullptr : s.data(), s.size(),
t.data(), t.size());
@@ -2473,7 +2441,7 @@ QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const
auto c = toSQLTCHAR(catalog);
auto s = toSQLTCHAR(schema);
auto t = toSQLTCHAR(table);
- r = SQLSpecialColumns(hStmt,
+ r = SQLSpecialColumns(hStmt.handle(),
SQL_BEST_ROWID,
catalog.isEmpty() ? nullptr : c.data(), c.size(),
schema.isEmpty() ? nullptr : s.data(), s.size(),
@@ -2482,44 +2450,31 @@ QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const
SQL_NULLABLE);
if (r != SQL_SUCCESS) {
- qSqlWarning("QODBCDriver::primaryIndex: Unable to execute primary key list"_L1, d);
+ qSqlWarning("QODBCDriver::primaryIndex: Unable to execute primary key list"_L1,
+ hStmt.handle());
} else {
usingSpecialColumns = true;
}
}
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
+ r = d->sqlFetchNext(hStmt);
int fakeId = 0;
QString cName, idxName;
// Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
while (r == SQL_SUCCESS) {
if (usingSpecialColumns) {
- cName = qGetStringData(hStmt, 1, -1, d->unicode).toString(); // column name
+ cName = qGetStringData(hStmt.handle(), 1, -1, d->unicode).toString(); // column name
idxName = QString::number(fakeId++); // invent a fake index name
} else {
- cName = qGetStringData(hStmt, 3, -1, d->unicode).toString(); // column name
- idxName = qGetStringData(hStmt, 5, -1, d->unicode).toString(); // pk index name
+ cName = qGetStringData(hStmt.handle(), 3, -1, d->unicode).toString(); // column name
+ idxName = qGetStringData(hStmt.handle(), 5, -1, d->unicode).toString(); // pk index name
}
index.append(rec.field(cName));
index.setName(idxName);
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
-
+ r = d->sqlFetchNext(hStmt);
}
- r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
- if (r!= SQL_SUCCESS)
- qSqlWarning("QODBCDriver: Unable to free statement handle"_L1 + QString::number(r), d);
return index;
}
@@ -2530,41 +2485,24 @@ QSqlRecord QODBCDriver::record(const QString& tablename) const
if (!isOpen())
return fil;
- SQLHANDLE hStmt;
- QString catalog, schema, table;
- d->splitTableQualifier(tablename, catalog, schema, table);
-
- if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
- catalog = stripDelimiters(catalog, QSqlDriver::TableName);
- else
- catalog = d->adjustCase(catalog);
-
- if (isIdentifierEscaped(schema, QSqlDriver::TableName))
- schema = stripDelimiters(schema, QSqlDriver::TableName);
- else
- schema = d->adjustCase(schema);
-
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
- else
- table = d->adjustCase(table);
-
- SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
- d->hDbc,
- &hStmt);
- if (r != SQL_SUCCESS) {
+ SqlStmtHandle hStmt(d->hDbc);
+ if (!hStmt.isValid()) {
qSqlWarning("QODBCDriver::record: Unable to allocate handle"_L1, d);
return fil;
}
- r = SQLSetStmtAttr(hStmt,
- SQL_ATTR_CURSOR_TYPE,
- (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
- SQL_IS_UINTEGER);
+
+ QString catalog, schema, table;
+ d->splitTableQualifier(tablename, catalog, schema, table);
+
+ SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
{
auto c = toSQLTCHAR(catalog);
auto s = toSQLTCHAR(schema);
auto t = toSQLTCHAR(table);
- r = SQLColumns(hStmt,
+ r = SQLColumns(hStmt.handle(),
catalog.isEmpty() ? nullptr : c.data(), c.size(),
schema.isEmpty() ? nullptr : s.data(), s.size(),
t.data(), t.size(),
@@ -2572,32 +2510,14 @@ QSqlRecord QODBCDriver::record(const QString& tablename) const
0);
}
if (r != SQL_SUCCESS)
- qSqlWarning("QODBCDriver::record: Unable to execute column list"_L1, d);
-
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
+ qSqlWarning("QODBCDriver::record: Unable to execute column list"_L1, hStmt.handle());
+ r = d->sqlFetchNext(hStmt);
// Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
while (r == SQL_SUCCESS) {
-
- fil.append(qMakeFieldInfo(hStmt, d));
-
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
+ fil.append(qMakeFieldInfo(hStmt.handle(), d));
+ r = d->sqlFetchNext(hStmt);
}
-
- r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
- if (r!= SQL_SUCCESS)
- qSqlWarning("QODBCDriver: Unable to free statement handle "_L1 + QString::number(r), d);
-
return fil;
}
@@ -2609,9 +2529,10 @@ QString QODBCDriver::formatValue(const QSqlField &field,
r = "NULL"_L1;
} else if (field.metaType().id() == QMetaType::QDateTime) {
// Use an escape sequence for the datetime fields
- if (field.value().toDateTime().isValid()){
- QDate dt = field.value().toDateTime().date();
- QTime tm = field.value().toDateTime().time();
+ const QDateTime dateTime = field.value().toDateTime();
+ if (dateTime.isValid()) {
+ const QDate dt = dateTime.date();
+ const QTime tm = dateTime.time();
// Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10
r = "{ ts '"_L1 +
QString::number(dt.year()) + u'-' +
@@ -2650,9 +2571,10 @@ QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType)
QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar();
QString res = identifier;
if (!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) {
- res.replace(quote, QString(quote)+QString(quote));
- res.prepend(quote).append(quote);
- res.replace(u'.', QString(quote) + u'.' +QString(quote));
+ const QString quoteStr(quote);
+ res.replace(quote, quoteStr + quoteStr);
+ res.replace(u'.', quoteStr + u'.' + quoteStr);
+ res = quote + res + quote;
}
return res;
}
diff --git a/src/plugins/sqldrivers/psql/CMakeLists.txt b/src/plugins/sqldrivers/psql/CMakeLists.txt
index 269fe3367d..2f55ab4950 100644
--- a/src/plugins/sqldrivers/psql/CMakeLists.txt
+++ b/src/plugins/sqldrivers/psql/CMakeLists.txt
@@ -1,9 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from psql.pro.
-
-qt_find_package(PostgreSQL) # special case
+qt_find_package(PostgreSQL)
#####################################################################
## QPSQLDriverPlugin Plugin:
@@ -18,6 +16,7 @@ qt_internal_add_plugin(QPSQLDriverPlugin
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
LIBRARIES
PostgreSQL::PostgreSQL
Qt::Core
@@ -25,9 +24,6 @@ qt_internal_add_plugin(QPSQLDriverPlugin
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:psql.pro:<TRUE>:
-# OTHER_FILES = "psql.json"
-
# PostgreSQL delivers header files that are not a part of PostgreSQL itself. When precompiled
# headers are processed, MinGW uses 'pthread.h' from the PostgreSQL installation directory.
# As result, we disable precompile headers for the plugin.
diff --git a/src/plugins/sqldrivers/psql/qsql_psql.cpp b/src/plugins/sqldrivers/psql/qsql_psql.cpp
index 06a15bb14a..7b9521fec6 100644
--- a/src/plugins/sqldrivers/psql/qsql_psql.cpp
+++ b/src/plugins/sqldrivers/psql/qsql_psql.cpp
@@ -6,6 +6,7 @@
#include <qcoreapplication.h>
#include <qvariant.h>
#include <qdatetime.h>
+#include <qloggingcategory.h>
#include <qregularexpression.h>
#include <qsqlerror.h>
#include <qsqlfield.h>
@@ -65,6 +66,8 @@ Q_DECLARE_METATYPE(PGresult*)
QT_BEGIN_NAMESPACE
+static Q_LOGGING_CATEGORY(lcPsql, "qt.sql.postgresql")
+
using namespace Qt::StringLiterals;
inline void qPQfreemem(void *buffer)
@@ -74,11 +77,11 @@ inline void qPQfreemem(void *buffer)
/* Missing declaration of PGRES_SINGLE_TUPLE for PSQL below 9.2 */
#if !defined PG_VERSION_NUM || PG_VERSION_NUM-0 < 90200
-static const int PGRES_SINGLE_TUPLE = 9;
+static constexpr int PGRES_SINGLE_TUPLE = 9;
#endif
typedef int StatementId;
-static const StatementId InvalidStatementId = 0;
+static constexpr StatementId InvalidStatementId = 0;
class QPSQLResultPrivate;
@@ -122,10 +125,9 @@ public:
QSocketNotifier *sn = nullptr;
QPSQLDriver::Protocol pro = QPSQLDriver::Version6;
StatementId currentStmtId = InvalidStatementId;
- int stmtCount = 0;
+ StatementId stmtCount = InvalidStatementId;
mutable bool pendingNotifyCheck = false;
bool hasBackslashEscape = false;
- bool isUtf8 = false;
void appendTables(QStringList &tl, QSqlQuery &t, QChar type);
PGresult *exec(const char *stmt);
@@ -141,6 +143,7 @@ public:
bool setEncodingUtf8();
void setDatestyle();
void setByteaOutput();
+ void setUtcTimeZone();
void detectBackslashEscape();
mutable QHash<int, QString> oidToTable;
};
@@ -175,7 +178,7 @@ PGresult *QPSQLDriverPrivate::exec(const char *stmt)
PGresult *QPSQLDriverPrivate::exec(const QString &stmt)
{
- return exec((isUtf8 ? stmt.toUtf8() : stmt.toLocal8Bit()).constData());
+ return exec(stmt.toUtf8().constData());
}
StatementId QPSQLDriverPrivate::sendQuery(const QString &stmt)
@@ -183,8 +186,7 @@ StatementId QPSQLDriverPrivate::sendQuery(const QString &stmt)
// Discard any prior query results that the application didn't eat.
// This is required for PQsendQuery()
discardResults();
- const int result = PQsendQuery(connection,
- (isUtf8 ? stmt.toUtf8() : stmt.toLocal8Bit()).constData());
+ const int result = PQsendQuery(connection, stmt.toUtf8().constData());
currentStmtId = result ? generateStatementId() : InvalidStatementId;
return currentStmtId;
}
@@ -209,8 +211,8 @@ PGresult *QPSQLDriverPrivate::getResult(StatementId stmtId) const
if (stmtId != currentStmtId) {
// If you change the following warning, remember to update it
// on sql-driver.html page too.
- qWarning("QPSQLDriver::getResult: Query results lost - "
- "probably discarded on executing another SQL query.");
+ qCWarning(lcPsql, "QPSQLDriver::getResult: Query results lost - "
+ "probably discarded on executing another SQL query.");
return nullptr;
}
PGresult *result = PQgetResult(connection);
@@ -234,7 +236,7 @@ void QPSQLDriverPrivate::discardResults() const
StatementId QPSQLDriverPrivate::generateStatementId()
{
- int stmtId = ++stmtCount;
+ StatementId stmtId = ++stmtCount;
if (stmtId <= 0)
stmtId = stmtCount = 1;
return stmtId;
@@ -245,18 +247,18 @@ void QPSQLDriverPrivate::checkPendingNotifications() const
Q_Q(const QPSQLDriver);
if (seid.size() && !pendingNotifyCheck) {
pendingNotifyCheck = true;
- QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), "_q_handleNotification", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), &QPSQLDriver::_q_handleNotification, Qt::QueuedConnection);
}
}
-class QPSQLResultPrivate : public QSqlResultPrivate
+class QPSQLResultPrivate final : public QSqlResultPrivate
{
Q_DECLARE_PUBLIC(QPSQLResult)
public:
Q_DECLARE_SQLDRIVER_PRIVATE(QPSQLDriver)
using QSqlResultPrivate::QSqlResultPrivate;
- QString fieldSerial(qsizetype i) const override { return u'$' + QString::number(i + 1); }
+ QString fieldSerial(qsizetype i) const override { return QString("$%1"_L1).arg(i + 1); }
void deallocatePreparedStmt();
std::queue<PGresult*> nextResultSets;
@@ -274,7 +276,7 @@ static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type,
const QPSQLDriverPrivate *p, PGresult *result = nullptr)
{
const char *s = PQerrorMessage(p->connection);
- QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
+ QString msg = QString::fromUtf8(s);
QString errorCode;
if (result) {
errorCode = QString::fromLatin1(PQresultErrorField(result, PG_DIAG_SQLSTATE));
@@ -382,8 +384,10 @@ void QPSQLResultPrivate::deallocatePreparedStmt()
const QString stmt = QStringLiteral("DEALLOCATE ") + preparedStmtId;
PGresult *result = drv_d_func()->exec(stmt);
- if (PQresultStatus(result) != PGRES_COMMAND_OK)
- qWarning("Unable to free statement: %s", PQerrorMessage(drv_d_func()->connection));
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
+ const QString msg = QString::fromUtf8(PQerrorMessage(drv_d_func()->connection));
+ qCWarning(lcPsql, "Unable to free statement: %ls.", qUtf16Printable(msg));
+ }
PQclear(result);
}
preparedStmtId.clear();
@@ -593,7 +597,7 @@ QVariant QPSQLResult::data(int i)
{
Q_D(const QPSQLResult);
if (i >= PQnfields(d->result)) {
- qWarning("QPSQLResult::data: column %d out of range", i);
+ qCWarning(lcPsql, "QPSQLResult::data: column %d out of range.", i);
return QVariant();
}
const int currentRow = isForwardOnly() ? 0 : at();
@@ -606,7 +610,7 @@ QVariant QPSQLResult::data(int i)
case QMetaType::Bool:
return QVariant((bool)(val[0] == 't'));
case QMetaType::QString:
- return d->drv_d_func()->isUtf8 ? QString::fromUtf8(val) : QString::fromLatin1(val);
+ return QString::fromUtf8(val);
case QMetaType::LongLong:
if (val[0] == '-')
return QByteArray::fromRawData(val, qstrlen(val)).toLongLong();
@@ -641,23 +645,21 @@ QVariant QPSQLResult::data(int i)
}
return dbl;
}
- case QMetaType::QDate:
#if QT_CONFIG(datestring)
+ case QMetaType::QDate:
return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate));
-#else
- return QVariant(QString::fromLatin1(val));
-#endif
case QMetaType::QTime:
-#if QT_CONFIG(datestring)
return QVariant(QTime::fromString(QString::fromLatin1(val), Qt::ISODate));
+ case QMetaType::QDateTime: {
+ QString tzString(QString::fromLatin1(val));
+ if (!tzString.endsWith(u'Z'))
+ tzString.append(u'Z'); // make UTC
+ return QVariant(QDateTime::fromString(tzString, Qt::ISODate));
+ }
#else
- return QVariant(QString::fromLatin1(val));
-#endif
+ case QMetaType::QDate:
+ case QMetaType::QTime:
case QMetaType::QDateTime:
-#if QT_CONFIG(datestring)
- return QVariant(QDateTime::fromString(QString::fromLatin1(val),
- Qt::ISODate).toLocalTime());
-#else
return QVariant(QString::fromLatin1(val));
#endif
case QMetaType::QByteArray: {
@@ -668,7 +670,7 @@ QVariant QPSQLResult::data(int i)
return QVariant(ba);
}
default:
- qWarning("QPSQLResult::data: unknown data type");
+ qCWarning(lcPsql, "QPSQLResult::data: unhandled data type %d.", type.id());
}
return QVariant();
}
@@ -747,10 +749,7 @@ QSqlRecord QPSQLResult::record() const
int count = PQnfields(d->result);
QSqlField f;
for (int i = 0; i < count; ++i) {
- if (d->drv_d_func()->isUtf8)
- f.setName(QString::fromUtf8(PQfname(d->result, i)));
- else
- f.setName(QString::fromLocal8Bit(PQfname(d->result, i)));
+ f.setName(QString::fromUtf8(PQfname(d->result, i)));
const int tableOid = PQftable(d->result, i);
// WARNING: We cannot execute any other SQL queries on
// the same db connection while forward-only mode is active
@@ -801,7 +800,6 @@ QSqlRecord QPSQLResult::record() const
f.setLength(len);
f.setPrecision(precision);
- f.setSqlType(ptype);
info.append(f);
}
return info;
@@ -918,7 +916,7 @@ void QPSQLDriverPrivate::setDatestyle()
PGresult *result = exec("SET DATESTYLE TO 'ISO'");
int status = PQresultStatus(result);
if (status != PGRES_COMMAND_OK)
- qWarning("%s", PQerrorMessage(connection));
+ qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
PQclear(result);
}
@@ -931,11 +929,20 @@ void QPSQLDriverPrivate::setByteaOutput()
PGresult *result = exec("SET bytea_output TO escape");
int status = PQresultStatus(result);
if (status != PGRES_COMMAND_OK)
- qWarning("%s", PQerrorMessage(connection));
+ qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
PQclear(result);
}
}
+void QPSQLDriverPrivate::setUtcTimeZone()
+{
+ PGresult *result = exec("SET TIME ZONE 'UTC'");
+ int status = PQresultStatus(result);
+ if (status != PGRES_COMMAND_OK)
+ qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
+ PQclear(result);
+}
+
void QPSQLDriverPrivate::detectBackslashEscape()
{
// standard_conforming_strings option introduced in 8.2
@@ -1069,16 +1076,16 @@ QPSQLDriver::Protocol QPSQLDriverPrivate::getPSQLVersion()
if (serverVersion == QPSQLDriver::VersionUnknown) {
serverVersion = clientVersion;
if (serverVersion != QPSQLDriver::VersionUnknown)
- qWarning("The server version of this PostgreSQL is unknown, falling back to the client version.");
+ qCWarning(lcPsql, "The server version of this PostgreSQL is unknown, "
+ "falling back to the client version.");
}
// Keep the old behavior unchanged
if (serverVersion == QPSQLDriver::VersionUnknown)
serverVersion = QPSQLDriver::Version6;
- if (serverVersion < QPSQLDriver::Version7_3) {
- qWarning("This version of PostgreSQL is not supported and may not work.");
- }
+ if (serverVersion < QPSQLDriver::Version7_3)
+ qCWarning(lcPsql, "This version of PostgreSQL is not supported and may not work.");
return serverVersion;
}
@@ -1104,8 +1111,7 @@ QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent)
QPSQLDriver::~QPSQLDriver()
{
Q_D(QPSQLDriver);
- if (d->connection)
- PQfinish(d->connection);
+ PQfinish(d->connection);
}
QVariant QPSQLDriver::handle() const
@@ -1125,6 +1131,7 @@ bool QPSQLDriver::hasFeature(DriverFeature f) const
case EventNotifications:
case MultipleResultSets:
case BLOB:
+ case Unicode:
return true;
case PreparedQueries:
case PositionalPlaceholders:
@@ -1135,8 +1142,6 @@ bool QPSQLDriver::hasFeature(DriverFeature f) const
case FinishQuery:
case CancelQuery:
return false;
- case Unicode:
- return d->isUtf8;
}
return false;
}
@@ -1194,9 +1199,16 @@ bool QPSQLDriver::open(const QString &db,
d->pro = d->getPSQLVersion();
d->detectBackslashEscape();
- d->isUtf8 = d->setEncodingUtf8();
+ if (!d->setEncodingUtf8()) {
+ setLastError(qMakeError(tr("Unable to set client encoding to 'UNICODE'"), QSqlError::ConnectionError, d));
+ setOpenError(true);
+ PQfinish(d->connection);
+ d->connection = nullptr;
+ return false;
+ }
d->setDatestyle();
d->setByteaOutput();
+ d->setUtcTimeZone();
setOpen(true);
setOpenError(false);
@@ -1209,13 +1221,12 @@ void QPSQLDriver::close()
d->seid.clear();
if (d->sn) {
- disconnect(d->sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(_q_handleNotification()));
+ disconnect(d->sn, &QSocketNotifier::activated, this, &QPSQLDriver::_q_handleNotification);
delete d->sn;
d->sn = nullptr;
}
- if (d->connection)
- PQfinish(d->connection);
+ PQfinish(d->connection);
d->connection = nullptr;
setOpen(false);
setOpenError(false);
@@ -1230,7 +1241,7 @@ bool QPSQLDriver::beginTransaction()
{
Q_D(QPSQLDriver);
if (!isOpen()) {
- qWarning("QPSQLDriver::beginTransaction: Database not open");
+ qCWarning(lcPsql, "QPSQLDriver::beginTransaction: Database not open.");
return false;
}
PGresult *res = d->exec("BEGIN");
@@ -1248,7 +1259,7 @@ bool QPSQLDriver::commitTransaction()
{
Q_D(QPSQLDriver);
if (!isOpen()) {
- qWarning("QPSQLDriver::commitTransaction: Database not open");
+ qCWarning(lcPsql, "QPSQLDriver::commitTransaction: Database not open.");
return false;
}
PGresult *res = d->exec("COMMIT");
@@ -1277,7 +1288,7 @@ bool QPSQLDriver::rollbackTransaction()
{
Q_D(QPSQLDriver);
if (!isOpen()) {
- qWarning("QPSQLDriver::rollbackTransaction: Database not open");
+ qCWarning(lcPsql, "QPSQLDriver::rollbackTransaction: Database not open.");
return false;
}
PGresult *res = d->exec("ROLLBACK");
@@ -1414,7 +1425,6 @@ QSqlRecord QPSQLDriver::record(const QString &tablename) const
f.setLength(len);
f.setPrecision(precision);
f.setDefaultValue(defVal);
- f.setSqlType(query.value(1).toInt());
info.append(f);
}
@@ -1439,32 +1449,28 @@ QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
r = nullStr();
} else {
switch (field.metaType().id()) {
- case QMetaType::QDateTime:
-#if QT_CONFIG(datestring)
- if (field.value().toDateTime().isValid()) {
+ case QMetaType::QDateTime: {
+ const auto dt = field.value().toDateTime();
+ if (dt.isValid()) {
// we force the value to be considered with a timezone information, and we force it to be UTC
// this is safe since postgresql stores only the UTC value and not the timezone offset (only used
// while parsing), so we have correct behavior in both case of with timezone and without tz
r = QStringLiteral("TIMESTAMP WITH TIME ZONE ") + u'\'' +
- QLocale::c().toString(field.value().toDateTime().toUTC(), u"yyyy-MM-ddThh:mm:ss.zzz") +
+ QLocale::c().toString(dt.toUTC(), u"yyyy-MM-ddThh:mm:ss.zzz") +
u'Z' + u'\'';
} else {
r = nullStr();
}
-#else
- r = nullStr();
-#endif // datestring
break;
- case QMetaType::QTime:
-#if QT_CONFIG(datestring)
- if (field.value().toTime().isValid()) {
- r = u'\'' + field.value().toTime().toString(u"hh:mm:ss.zzz") + u'\'';
- } else
-#endif
- {
+ }
+ case QMetaType::QTime: {
+ const auto t = field.value().toTime();
+ if (t.isValid())
+ r = u'\'' + QLocale::c().toString(t, u"hh:mm:ss.zzz") + u'\'';
+ else
r = nullStr();
- }
break;
+ }
case QMetaType::QString:
r = QSqlDriver::formatValue(field, trimStrings);
if (d->hasBackslashEscape)
@@ -1516,8 +1522,8 @@ QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType)
QString res = identifier;
if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) {
res.replace(u'"', "\"\""_L1);
- res.prepend(u'"').append(u'"');
res.replace(u'.', "\".\""_L1);
+ res = u'"' + res + u'"';
}
return res;
}
@@ -1538,7 +1544,7 @@ bool QPSQLDriver::subscribeToNotification(const QString &name)
{
Q_D(QPSQLDriver);
if (!isOpen()) {
- qWarning("QPSQLDriver::subscribeToNotificationImplementation: database not open.");
+ qCWarning(lcPsql, "QPSQLDriver::subscribeToNotification: Database not open.");
return false;
}
@@ -1563,11 +1569,12 @@ bool QPSQLDriver::subscribeToNotification(const QString &name)
PQclear(result);
if (!d->sn) {
- d->sn = new QSocketNotifier(socket, QSocketNotifier::Read);
- connect(d->sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(_q_handleNotification()));
+ d->sn = new QSocketNotifier(socket, QSocketNotifier::Read, this);
+ connect(d->sn, &QSocketNotifier::activated, this, &QPSQLDriver::_q_handleNotification);
}
} else {
- qWarning("QPSQLDriver::subscribeToNotificationImplementation: PQsocket didn't return a valid socket to listen on");
+ qCWarning(lcPsql, "QPSQLDriver::subscribeToNotificationImplementation: "
+ "PQsocket didn't return a valid socket to listen on.");
return false;
}
@@ -1578,13 +1585,13 @@ bool QPSQLDriver::unsubscribeFromNotification(const QString &name)
{
Q_D(QPSQLDriver);
if (!isOpen()) {
- qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: database not open.");
+ qCWarning(lcPsql, "QPSQLDriver::unsubscribeFromNotification: Database not open.");
return false;
}
if (!d->seid.contains(name)) {
- qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: not subscribed to '%s'.",
- qPrintable(name));
+ qCWarning(lcPsql, "QPSQLDriver::unsubscribeFromNotification: not subscribed to '%ls'.",
+ qUtf16Printable(name));
return false;
}
@@ -1600,7 +1607,7 @@ bool QPSQLDriver::unsubscribeFromNotification(const QString &name)
d->seid.removeAll(name);
if (d->seid.isEmpty()) {
- disconnect(d->sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(_q_handleNotification()));
+ disconnect(d->sn, &QSocketNotifier::activated, this, &QPSQLDriver::_q_handleNotification);
delete d->sn;
d->sn = nullptr;
}
@@ -1627,17 +1634,19 @@ void QPSQLDriver::_q_handleNotification()
QString payload;
#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 70400
if (notify->extra)
- payload = d->isUtf8 ? QString::fromUtf8(notify->extra) : QString::fromLatin1(notify->extra);
+ payload = QString::fromUtf8(notify->extra);
#endif
QSqlDriver::NotificationSource source = (notify->be_pid == PQbackendPID(d->connection)) ? QSqlDriver::SelfSource : QSqlDriver::OtherSource;
emit notification(name, source, payload);
}
else
- qWarning("QPSQLDriver: received notification for '%s' which isn't subscribed to.",
- qPrintable(name));
+ qCWarning(lcPsql, "QPSQLDriver: received notification for '%ls' which isn't subscribed to.",
+ qUtf16Printable(name));
qPQfreemem(notify);
}
}
QT_END_NAMESPACE
+
+#include "moc_qsql_psql_p.cpp"
diff --git a/src/plugins/sqldrivers/qt_cmdline.cmake b/src/plugins/sqldrivers/qt_cmdline.cmake
index 8116a5cdae..2bb1fc64eb 100644
--- a/src/plugins/sqldrivers/qt_cmdline.cmake
+++ b/src/plugins/sqldrivers/qt_cmdline.cmake
@@ -3,7 +3,7 @@
qt_commandline_option(mysql_config TYPE string)
qt_commandline_option(psql_config TYPE string)
-qt_commandline_option(sqlite TYPE enum NAME system-sqlite MAPPING qt no system yes)
+qt_commandline_option(sqlite CONTROLS_FEATURE TYPE enum NAME system-sqlite MAPPING qt no system yes)
qt_commandline_option(sql-db2 TYPE boolean)
qt_commandline_option(sql-ibase TYPE boolean)
qt_commandline_option(sql-mysql TYPE boolean)
@@ -11,6 +11,7 @@ qt_commandline_option(sql-oci TYPE boolean)
qt_commandline_option(sql-odbc TYPE boolean)
qt_commandline_option(sql-psql TYPE boolean)
qt_commandline_option(sql-sqlite TYPE boolean)
+qt_commandline_option(sql-mimer TYPE boolean)
qt_commandline_option(plugin-sql-db2 TYPE void NAME sql-db2)
qt_commandline_option(plugin-sql-ibase TYPE void NAME sql-ibase)
qt_commandline_option(plugin-sql-mysql TYPE void NAME sql-mysql)
@@ -18,3 +19,4 @@ qt_commandline_option(plugin-sql-oci TYPE void NAME sql-oci)
qt_commandline_option(plugin-sql-odbc TYPE void NAME sql-odbc)
qt_commandline_option(plugin-sql-psql TYPE void NAME sql-psql)
qt_commandline_option(plugin-sql-sqlite TYPE void NAME sql-sqlite)
+qt_commandline_option(plugin-sql-mimer TYPE void NAME sql-mimer)
diff --git a/src/plugins/sqldrivers/sqlite/CMakeLists.txt b/src/plugins/sqldrivers/sqlite/CMakeLists.txt
index a183ecc965..4203a5c437 100644
--- a/src/plugins/sqldrivers/sqlite/CMakeLists.txt
+++ b/src/plugins/sqldrivers/sqlite/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from sqlite.pro.
-
#####################################################################
## QSQLiteDriverPlugin Plugin:
#####################################################################
@@ -12,20 +10,18 @@ qt_internal_add_plugin(QSQLiteDriverPlugin
PLUGIN_TYPE sqldrivers
SOURCES
qsql_sqlite.cpp qsql_sqlite_p.h
+ qsql_sqlite_vfs.cpp qsql_sqlite_vfs_p.h
smain.cpp
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
LIBRARIES
Qt::Core
Qt::CorePrivate
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:sqlite.pro:<TRUE>:
-# OTHER_FILES = "sqlite.json"
-# QT_FOR_CONFIG = "sqldrivers-private"
-
## Scopes:
#####################################################################
@@ -34,7 +30,6 @@ qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_system_sqlite
SQLite::SQLite3
)
-# special case begin
if (NOT QT_FEATURE_system_sqlite)
# On newer compilers compiling sqlite.c produces warnings
qt_disable_warnings(QSQLiteDriverPlugin)
@@ -43,7 +38,11 @@ endif()
if(QT_FEATURE_system_sqlite)
qt_internal_force_macos_intel_arch(QSQLiteDriverPlugin)
endif()
-# special case end
+
+qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sqlite AND VXWORKS
+ DEFINES
+ SQLITE_OS_UNIX=1
+)
qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sqlite
SOURCES
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
index 6812addae1..c574772fd7 100644
--- a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
@@ -7,6 +7,7 @@
#include <qdatetime.h>
#include <qdebug.h>
#include <qlist.h>
+#include <qloggingcategory.h>
#include <qsqlerror.h>
#include <qsqlfield.h>
#include <qsqlindex.h>
@@ -38,23 +39,9 @@ Q_DECLARE_METATYPE(sqlite3_stmt*)
QT_BEGIN_NAMESPACE
-using namespace Qt::StringLiterals;
+static Q_LOGGING_CATEGORY(lcSqlite, "qt.sql.sqlite")
-static QString _q_escapeIdentifier(const QString &identifier, QSqlDriver::IdentifierType type)
-{
- QString res = identifier;
- // If it contains [ and ] then we assume it to be escaped properly already as this indicates
- // the syntax is exactly how it should be
- if (identifier.contains(u'[') && identifier.contains(u']'))
- return res;
- if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"')) {
- res.replace(u'"', "\"\""_L1);
- res.prepend(u'"').append(u'"');
- if (type == QSqlDriver::TableName)
- res.replace(u'.', "\".\""_L1);
- }
- return res;
-}
+using namespace Qt::StringLiterals;
static int qGetColumnType(const QString &tpName)
{
@@ -114,11 +101,64 @@ class QSQLiteDriverPrivate : public QSqlDriverPrivate
public:
inline QSQLiteDriverPrivate() : QSqlDriverPrivate(QSqlDriver::SQLite) {}
+ bool isIdentifierEscaped(QStringView identifier) const;
+ QSqlIndex getTableInfo(QSqlQuery &query, const QString &tableName,
+ bool onlyPIndex = false) const;
+
sqlite3 *access = nullptr;
QList<QSQLiteResult *> results;
QStringList notificationid;
};
+bool QSQLiteDriverPrivate::isIdentifierEscaped(QStringView identifier) const
+{
+ return identifier.size() > 2
+ && ((identifier.startsWith(u'"') && identifier.endsWith(u'"'))
+ || (identifier.startsWith(u'`') && identifier.endsWith(u'`'))
+ || (identifier.startsWith(u'[') && identifier.endsWith(u']')));
+}
+
+QSqlIndex QSQLiteDriverPrivate::getTableInfo(QSqlQuery &query, const QString &tableName,
+ bool onlyPIndex) const
+{
+ Q_Q(const QSQLiteDriver);
+ QString schema;
+ QString table = q->escapeIdentifier(tableName, QSqlDriver::TableName);
+ const auto indexOfSeparator = table.indexOf(u'.');
+ if (indexOfSeparator > -1) {
+ auto leftName = QStringView{table}.first(indexOfSeparator);
+ auto rightName = QStringView{table}.sliced(indexOfSeparator + 1);
+ if (isIdentifierEscaped(leftName) && isIdentifierEscaped(rightName)) {
+ schema = leftName.toString() + u'.';
+ table = rightName.toString();
+ }
+ }
+
+ query.exec("PRAGMA "_L1 + schema + "table_info ("_L1 + table + u')');
+ QSqlIndex ind;
+ while (query.next()) {
+ bool isPk = query.value(5).toInt();
+ if (onlyPIndex && !isPk)
+ continue;
+ QString typeName = query.value(2).toString().toLower();
+ QString defVal = query.value(4).toString();
+ if (!defVal.isEmpty() && defVal.at(0) == u'\'') {
+ const int end = defVal.lastIndexOf(u'\'');
+ if (end > 0)
+ defVal = defVal.mid(1, end - 1);
+ }
+
+ QSqlField fld(query.value(1).toString(), QMetaType(qGetColumnType(typeName)), tableName);
+ if (isPk && (typeName == "integer"_L1))
+ // INTEGER PRIMARY KEY fields are auto-generated in sqlite
+ // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY!
+ fld.setAutoValue(true);
+ fld.setRequired(query.value(3).toInt() != 0);
+ fld.setDefaultValue(defVal);
+ ind.append(fld);
+ }
+ return ind;
+}
class QSQLiteResultPrivate : public QSqlCachedResultPrivate
{
@@ -210,7 +250,6 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset)
}
QSqlField fld(colName, QMetaType(fieldType), tableName);
- fld.setSqlType(stp);
rInf.append(fld);
}
}
@@ -625,6 +664,30 @@ static void _q_regexp_cleanup(void *cache)
}
#endif
+static void _q_lower(sqlite3_context* context, int argc, sqlite3_value** argv)
+{
+ if (Q_UNLIKELY(argc != 1)) {
+ sqlite3_result_text(context, nullptr, 0, nullptr);
+ return;
+ }
+ const QString lower = QString::fromUtf8(
+ reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))).toLower();
+ const QByteArray ba = lower.toUtf8();
+ sqlite3_result_text(context, ba.data(), ba.size(), SQLITE_TRANSIENT);
+}
+
+static void _q_upper(sqlite3_context* context, int argc, sqlite3_value** argv)
+{
+ if (Q_UNLIKELY(argc != 1)) {
+ sqlite3_result_text(context, nullptr, 0, nullptr);
+ return;
+ }
+ const QString upper = QString::fromUtf8(
+ reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))).toUpper();
+ const QByteArray ba = upper.toUtf8();
+ sqlite3_result_text(context, ba.data(), ba.size(), SQLITE_TRANSIENT);
+}
+
QSQLiteDriver::QSQLiteDriver(QObject * parent)
: QSqlDriver(*new QSQLiteDriverPrivate, parent)
{
@@ -691,13 +754,16 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
bool openReadOnlyOption = false;
bool openUriOption = false;
bool useExtendedResultCodes = true;
+ bool useQtVfs = false;
+ bool useQtCaseFolding = false;
+ bool openNoFollow = false;
#if QT_CONFIG(regularexpression)
static const auto regexpConnectOption = "QSQLITE_ENABLE_REGEXP"_L1;
bool defineRegexp = false;
int regexpCacheSize = 25;
#endif
- const auto opts = QStringView{conOpts}.split(u';');
+ const auto opts = QStringView{conOpts}.split(u';', Qt::SkipEmptyParts);
for (auto option : opts) {
option = option.trimmed();
if (option.startsWith("QSQLITE_BUSY_TIMEOUT"_L1)) {
@@ -708,6 +774,8 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
if (ok)
timeOut = nt;
}
+ } else if (option == "QSQLITE_USE_QT_VFS"_L1) {
+ useQtVfs = true;
} else if (option == "QSQLITE_OPEN_READONLY"_L1) {
openReadOnlyOption = true;
} else if (option == "QSQLITE_OPEN_URI"_L1) {
@@ -716,6 +784,10 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
sharedCache = true;
} else if (option == "QSQLITE_NO_USE_EXTENDED_RESULT_CODES"_L1) {
useExtendedResultCodes = false;
+ } else if (option == "QSQLITE_ENABLE_NON_ASCII_CASE_FOLDING"_L1) {
+ useQtCaseFolding = true;
+ } else if (option == "QSQLITE_OPEN_NOFOLLOW"_L1) {
+ openNoFollow = true;
}
#if QT_CONFIG(regularexpression)
else if (option.startsWith(regexpConnectOption)) {
@@ -733,16 +805,25 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
}
}
#endif
+ else
+ qCWarning(lcSqlite, "Unsupported option '%ls'", qUtf16Printable(option.toString()));
}
int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE));
openMode |= (sharedCache ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE);
if (openUriOption)
openMode |= SQLITE_OPEN_URI;
+ if (openNoFollow) {
+#if defined(SQLITE_OPEN_NOFOLLOW)
+ openMode |= SQLITE_OPEN_NOFOLLOW;
+#else
+ qCWarning(lcSqlite, "SQLITE_OPEN_NOFOLLOW not supported with the SQLite version %s", sqlite3_libversion());
+#endif
+ }
openMode |= SQLITE_OPEN_NOMUTEX;
- const int res = sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, nullptr);
+ const int res = sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, useQtVfs ? "QtVFS" : nullptr);
if (res == SQLITE_OK) {
sqlite3_busy_timeout(d->access, timeOut);
@@ -757,6 +838,12 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
nullptr, &_q_regexp_cleanup);
}
#endif
+ if (useQtCaseFolding) {
+ sqlite3_create_function_v2(d->access, "lower", 1, SQLITE_UTF8, nullptr,
+ &_q_lower, nullptr, nullptr, nullptr);
+ sqlite3_create_function_v2(d->access, "upper", 1, SQLITE_UTF8, nullptr,
+ &_q_upper, nullptr, nullptr, nullptr);
+ }
return true;
} else {
setLastError(qMakeError(d->access, tr("Error opening database"),
@@ -877,79 +964,26 @@ QStringList QSQLiteDriver::tables(QSql::TableType type) const
return res;
}
-static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false)
-{
- QString schema;
- QString table(tableName);
- const qsizetype indexOfSeparator = tableName.indexOf(u'.');
- if (indexOfSeparator > -1) {
- const qsizetype indexOfCloseBracket = tableName.indexOf(u']');
- if (indexOfCloseBracket != tableName.size() - 1) {
- // Handles a case like databaseName.tableName
- schema = tableName.left(indexOfSeparator + 1);
- table = tableName.mid(indexOfSeparator + 1);
- } else {
- const qsizetype indexOfOpenBracket = tableName.lastIndexOf(u'[', indexOfCloseBracket);
- if (indexOfOpenBracket > 0) {
- // Handles a case like databaseName.[tableName]
- schema = tableName.left(indexOfOpenBracket);
- table = tableName.mid(indexOfOpenBracket);
- }
- }
- }
- q.exec("PRAGMA "_L1 + schema + "table_info ("_L1 +
- _q_escapeIdentifier(table, QSqlDriver::TableName) + u')');
- QSqlIndex ind;
- while (q.next()) {
- bool isPk = q.value(5).toInt();
- if (onlyPIndex && !isPk)
- continue;
- QString typeName = q.value(2).toString().toLower();
- QString defVal = q.value(4).toString();
- if (!defVal.isEmpty() && defVal.at(0) == u'\'') {
- const int end = defVal.lastIndexOf(u'\'');
- if (end > 0)
- defVal = defVal.mid(1, end - 1);
- }
-
- QSqlField fld(q.value(1).toString(), QMetaType(qGetColumnType(typeName)), tableName);
- if (isPk && (typeName == "integer"_L1))
- // INTEGER PRIMARY KEY fields are auto-generated in sqlite
- // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY!
- fld.setAutoValue(true);
- fld.setRequired(q.value(3).toInt() != 0);
- fld.setDefaultValue(defVal);
- ind.append(fld);
- }
- return ind;
-}
-
-QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const
+QSqlIndex QSQLiteDriver::primaryIndex(const QString &tablename) const
{
+ Q_D(const QSQLiteDriver);
if (!isOpen())
return QSqlIndex();
- QString table = tblname;
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
-
QSqlQuery q(createResult());
q.setForwardOnly(true);
- return qGetTableInfo(q, table, true);
+ return d->getTableInfo(q, tablename, true);
}
-QSqlRecord QSQLiteDriver::record(const QString &tbl) const
+QSqlRecord QSQLiteDriver::record(const QString &tablename) const
{
+ Q_D(const QSQLiteDriver);
if (!isOpen())
return QSqlRecord();
- QString table = tbl;
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
-
QSqlQuery q(createResult());
q.setForwardOnly(true);
- return qGetTableInfo(q, table);
+ return d->getTableInfo(q, tablename);
}
QVariant QSQLiteDriver::handle() const
@@ -960,7 +994,52 @@ QVariant QSQLiteDriver::handle() const
QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
{
- return _q_escapeIdentifier(identifier, type);
+ Q_D(const QSQLiteDriver);
+ if (identifier.isEmpty() || isIdentifierEscaped(identifier, type))
+ return identifier;
+
+ const auto indexOfSeparator = identifier.indexOf(u'.');
+ if (indexOfSeparator > -1) {
+ auto leftName = QStringView{identifier}.first(indexOfSeparator);
+ auto rightName = QStringView{identifier}.sliced(indexOfSeparator + 1);
+ const QStringView leftEnclose = d->isIdentifierEscaped(leftName) ? u"" : u"\"";
+ const QStringView rightEnclose = d->isIdentifierEscaped(rightName) ? u"" : u"\"";
+ if (leftEnclose.isEmpty() || rightEnclose.isEmpty())
+ return (leftEnclose + leftName + leftEnclose + u'.' + rightEnclose + rightName
+ + rightEnclose);
+ }
+ return u'"' + identifier + u'"';
+}
+
+bool QSQLiteDriver::isIdentifierEscaped(const QString &identifier, IdentifierType type) const
+{
+ Q_D(const QSQLiteDriver);
+ Q_UNUSED(type);
+ return d->isIdentifierEscaped(QStringView{identifier});
+}
+
+QString QSQLiteDriver::stripDelimiters(const QString &identifier, IdentifierType type) const
+{
+ Q_D(const QSQLiteDriver);
+ const auto indexOfSeparator = identifier.indexOf(u'.');
+ if (indexOfSeparator > -1) {
+ auto leftName = QStringView{identifier}.first(indexOfSeparator);
+ auto rightName = QStringView{identifier}.sliced(indexOfSeparator + 1);
+ const auto leftEscaped = d->isIdentifierEscaped(leftName);
+ const auto rightEscaped = d->isIdentifierEscaped(rightName);
+ if (leftEscaped || rightEscaped) {
+ if (leftEscaped)
+ leftName = leftName.sliced(1).chopped(1);
+ if (rightEscaped)
+ rightName = rightName.sliced(1).chopped(1);
+ return leftName + u'.' + rightName;
+ }
+ }
+
+ if (isIdentifierEscaped(identifier, type))
+ return identifier.mid(1, identifier.size() - 2);
+
+ return identifier;
}
static void handle_sqlite_callback(void *qobj,int aoperation, char const *adbname, char const *atablename,
@@ -979,12 +1058,13 @@ bool QSQLiteDriver::subscribeToNotification(const QString &name)
{
Q_D(QSQLiteDriver);
if (!isOpen()) {
- qWarning("Database not open.");
+ qCWarning(lcSqlite, "QSQLiteDriver::subscribeToNotification: Database not open.");
return false;
}
if (d->notificationid.contains(name)) {
- qWarning("Already subscribing to '%s'.", qPrintable(name));
+ qCWarning(lcSqlite, "QSQLiteDriver::subscribeToNotification: Already subscribing to '%ls'.",
+ qUtf16Printable(name));
return false;
}
@@ -1000,12 +1080,13 @@ bool QSQLiteDriver::unsubscribeFromNotification(const QString &name)
{
Q_D(QSQLiteDriver);
if (!isOpen()) {
- qWarning("Database not open.");
+ qCWarning(lcSqlite, "QSQLiteDriver::unsubscribeFromNotification: Database not open.");
return false;
}
if (!d->notificationid.contains(name)) {
- qWarning("Not subscribed to '%s'.", qPrintable(name));
+ qCWarning(lcSqlite, "QSQLiteDriver::unsubscribeFromNotification: Not subscribed to '%ls'.",
+ qUtf16Printable(name));
return false;
}
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h
index 53ffb45f96..db6b76cb69 100644
--- a/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h
@@ -54,9 +54,12 @@ public:
QStringList tables(QSql::TableType) const override;
QSqlRecord record(const QString& tablename) const override;
- QSqlIndex primaryIndex(const QString &table) const override;
+ QSqlIndex primaryIndex(const QString &tablename) const override;
QVariant handle() const override;
- QString escapeIdentifier(const QString &identifier, IdentifierType) const override;
+
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const override;
+ bool isIdentifierEscaped(const QString &identifier, IdentifierType type) const override;
+ QString stripDelimiters(const QString &identifier, IdentifierType type) const override;
bool subscribeToNotification(const QString &name) override;
bool unsubscribeFromNotification(const QString &name) override;
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp
new file mode 100644
index 0000000000..bbba3cd14f
--- /dev/null
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp
@@ -0,0 +1,258 @@
+// Copyright (C) 2023 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_sqlite_vfs_p.h"
+
+#include <QFile>
+
+#include <limits.h> // defines PATH_MAX on unix
+#include <sqlite3.h>
+#include <stdio.h> // defines FILENAME_MAX everywhere
+
+#ifndef PATH_MAX
+# define PATH_MAX FILENAME_MAX
+#endif
+#if SQLITE_VERSION_NUMBER < 3040000
+typedef const char *sqlite3_filename;
+#endif
+
+namespace {
+struct Vfs : sqlite3_vfs {
+ sqlite3_vfs *pVfs;
+ sqlite3_io_methods ioMethods;
+};
+
+struct File : sqlite3_file {
+ class QtFile : public QFile {
+ public:
+ QtFile(const QString &name, bool removeOnClose)
+ : QFile(name)
+ , removeOnClose(removeOnClose)
+ {}
+
+ ~QtFile() override
+ {
+ if (removeOnClose)
+ remove();
+ }
+ private:
+ bool removeOnClose;
+ };
+ QtFile *pFile;
+};
+
+
+int xClose(sqlite3_file *sfile)
+{
+ auto file = static_cast<File *>(sfile);
+ delete file->pFile;
+ file->pFile = nullptr;
+ return SQLITE_OK;
+}
+
+int xRead(sqlite3_file *sfile, void *ptr, int iAmt, sqlite3_int64 iOfst)
+{
+ auto file = static_cast<File *>(sfile);
+ if (!file->pFile->seek(iOfst))
+ return SQLITE_IOERR_READ;
+
+ auto sz = file->pFile->read(static_cast<char *>(ptr), iAmt);
+ if (sz < iAmt) {
+ memset(static_cast<char *>(ptr) + sz, 0, size_t(iAmt - sz));
+ return SQLITE_IOERR_SHORT_READ;
+ }
+ return SQLITE_OK;
+}
+
+int xWrite(sqlite3_file *sfile, const void *data, int iAmt, sqlite3_int64 iOfst)
+{
+ auto file = static_cast<File *>(sfile);
+ if (!file->pFile->seek(iOfst))
+ return SQLITE_IOERR_SEEK;
+ return file->pFile->write(reinterpret_cast<const char*>(data), iAmt) == iAmt ? SQLITE_OK : SQLITE_IOERR_WRITE;
+}
+
+int xTruncate(sqlite3_file *sfile, sqlite3_int64 size)
+{
+ auto file = static_cast<File *>(sfile);
+ return file->pFile->resize(size) ? SQLITE_OK : SQLITE_IOERR_TRUNCATE;
+}
+
+int xSync(sqlite3_file *sfile, int /*flags*/)
+{
+ static_cast<File *>(sfile)->pFile->flush();
+ return SQLITE_OK;
+}
+
+int xFileSize(sqlite3_file *sfile, sqlite3_int64 *pSize)
+{
+ auto file = static_cast<File *>(sfile);
+ *pSize = file->pFile->size();
+ return SQLITE_OK;
+}
+
+// No lock/unlock for QFile, QLockFile doesn't work for me
+
+int xLock(sqlite3_file *, int) { return SQLITE_OK; }
+
+int xUnlock(sqlite3_file *, int) { return SQLITE_OK; }
+
+int xCheckReservedLock(sqlite3_file *, int *pResOut)
+{
+ *pResOut = 0;
+ return SQLITE_OK;
+}
+
+int xFileControl(sqlite3_file *, int, void *) { return SQLITE_NOTFOUND; }
+
+int xSectorSize(sqlite3_file *)
+{
+ return 4096;
+}
+
+int xDeviceCharacteristics(sqlite3_file *)
+{
+ return 0; // no SQLITE_IOCAP_XXX
+}
+
+int xOpen(sqlite3_vfs *svfs, sqlite3_filename zName, sqlite3_file *sfile,
+ int flags, int *pOutFlags)
+{
+ auto vfs = static_cast<Vfs *>(svfs);
+ auto file = static_cast<File *>(sfile);
+ memset(file, 0, sizeof(File));
+ QIODeviceBase::OpenMode mode = QIODeviceBase::NotOpen;
+ if (!zName || (flags & SQLITE_OPEN_MEMORY))
+ return SQLITE_PERM;
+ if ((flags & SQLITE_OPEN_READONLY) &&
+ !(flags & SQLITE_OPEN_READWRITE) &&
+ !(flags & SQLITE_OPEN_CREATE) &&
+ !(flags & SQLITE_OPEN_DELETEONCLOSE)) {
+ mode |= QIODeviceBase::OpenModeFlag::ReadOnly;
+ } else {
+ /*
+ ** ^The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction
+ ** with the [SQLITE_OPEN_CREATE] flag, which are both directly
+ ** analogous to the O_EXCL and O_CREAT flags of the POSIX open()
+ ** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the
+ ** SQLITE_OPEN_CREATE, is used to indicate that file should always
+ ** be created, and that it is an error if it already exists.
+ ** It is <i>not</i> used to indicate the file should be opened
+ ** for exclusive access.
+ */
+ if ((flags & SQLITE_OPEN_CREATE) && (flags & SQLITE_OPEN_EXCLUSIVE))
+ mode |= QIODeviceBase::OpenModeFlag::NewOnly;
+
+ if (flags & SQLITE_OPEN_READWRITE)
+ mode |= QIODeviceBase::OpenModeFlag::ReadWrite;
+ }
+
+ file->pMethods = &vfs->ioMethods;
+ file->pFile = new File::QtFile(QString::fromUtf8(zName), bool(flags & SQLITE_OPEN_DELETEONCLOSE));
+ if (!file->pFile->open(mode))
+ return SQLITE_CANTOPEN;
+ if (pOutFlags)
+ *pOutFlags = flags;
+
+ return SQLITE_OK;
+}
+
+int xDelete(sqlite3_vfs *, const char *zName, int)
+{
+ return QFile::remove(QString::fromUtf8(zName)) ? SQLITE_OK : SQLITE_ERROR;
+}
+
+int xAccess(sqlite3_vfs */*svfs*/, const char *zName, int flags, int *pResOut)
+{
+ *pResOut = 0;
+ switch (flags) {
+ case SQLITE_ACCESS_EXISTS:
+ case SQLITE_ACCESS_READ:
+ *pResOut = QFile::exists(QString::fromUtf8(zName));
+ break;
+ default:
+ break;
+ }
+ return SQLITE_OK;
+}
+
+int xFullPathname(sqlite3_vfs *, const char *zName, int nOut, char *zOut)
+{
+ if (!zName)
+ return SQLITE_ERROR;
+
+ int i = 0;
+ for (;zName[i] && i < nOut; ++i)
+ zOut[i] = zName[i];
+
+ if (i >= nOut)
+ return SQLITE_ERROR;
+
+ zOut[i] = '\0';
+ return SQLITE_OK;
+}
+
+int xRandomness(sqlite3_vfs *svfs, int nByte, char *zOut)
+{
+ auto vfs = static_cast<Vfs *>(svfs)->pVfs;
+ return vfs->xRandomness(vfs, nByte, zOut);
+}
+
+int xSleep(sqlite3_vfs *svfs, int microseconds)
+{
+ auto vfs = static_cast<Vfs *>(svfs)->pVfs;
+ return vfs->xSleep(vfs, microseconds);
+}
+
+int xCurrentTime(sqlite3_vfs *svfs, double *zOut)
+{
+ auto vfs = static_cast<Vfs *>(svfs)->pVfs;
+ return vfs->xCurrentTime(vfs, zOut);
+}
+
+int xGetLastError(sqlite3_vfs *, int, char *)
+{
+ return 0;
+}
+
+int xCurrentTimeInt64(sqlite3_vfs *svfs, sqlite3_int64 *zOut)
+{
+ auto vfs = static_cast<Vfs *>(svfs)->pVfs;
+ return vfs->xCurrentTimeInt64(vfs, zOut);
+}
+} // namespace {
+
+void register_qt_vfs()
+{
+ static Vfs vfs;
+ memset(&vfs, 0, sizeof(Vfs));
+ vfs.iVersion = 1;
+ vfs.szOsFile = sizeof(File);
+ vfs.mxPathname = PATH_MAX;
+ vfs.zName = "QtVFS";
+ vfs.xOpen = &xOpen;
+ vfs.xDelete = &xDelete;
+ vfs.xAccess = &xAccess;
+ vfs.xFullPathname = &xFullPathname;
+ vfs.xRandomness = &xRandomness;
+ vfs.xSleep = &xSleep;
+ vfs.xCurrentTime = &xCurrentTime;
+ vfs.xGetLastError = &xGetLastError;
+ vfs.xCurrentTimeInt64 = &xCurrentTimeInt64;
+ vfs.pVfs = sqlite3_vfs_find(nullptr);
+ vfs.ioMethods.iVersion = 1;
+ vfs.ioMethods.xClose = &xClose;
+ vfs.ioMethods.xRead = &xRead;
+ vfs.ioMethods.xWrite = &xWrite;
+ vfs.ioMethods.xTruncate = &xTruncate;
+ vfs.ioMethods.xSync = &xSync;
+ vfs.ioMethods.xFileSize = &xFileSize;
+ vfs.ioMethods.xLock = &xLock;
+ vfs.ioMethods.xUnlock = &xUnlock;
+ vfs.ioMethods.xCheckReservedLock = &xCheckReservedLock;
+ vfs.ioMethods.xFileControl = &xFileControl;
+ vfs.ioMethods.xSectorSize = &xSectorSize;
+ vfs.ioMethods.xDeviceCharacteristics = &xDeviceCharacteristics;
+
+ sqlite3_vfs_register(&vfs, 0);
+}
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h
new file mode 100644
index 0000000000..56024b3ecb
--- /dev/null
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 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_SQLITE_VFS_H
+#define QSQL_SQLITE_VFS_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+void register_qt_vfs();
+
+
+#endif // QSQL_SQLITE_VFS_H
diff --git a/src/plugins/sqldrivers/sqlite/smain.cpp b/src/plugins/sqldrivers/sqlite/smain.cpp
index f84a256bc8..0d201c38d3 100644
--- a/src/plugins/sqldrivers/sqlite/smain.cpp
+++ b/src/plugins/sqldrivers/sqlite/smain.cpp
@@ -4,6 +4,7 @@
#include <qsqldriverplugin.h>
#include <qstringlist.h>
#include "qsql_sqlite_p.h"
+#include "qsql_sqlite_vfs_p.h"
QT_BEGIN_NAMESPACE
@@ -23,6 +24,7 @@ public:
QSQLiteDriverPlugin::QSQLiteDriverPlugin()
: QSqlDriverPlugin()
{
+ register_qt_vfs();
}
QSqlDriver* QSQLiteDriverPlugin::create(const QString &name)