diff options
Diffstat (limited to 'src/plugins')
129 files changed, 18974 insertions, 2019 deletions
diff --git a/src/plugins/bearer/bearer.pro b/src/plugins/bearer/bearer.pro index cc590cc545..8028e65147 100644 --- a/src/plugins/bearer/bearer.pro +++ b/src/plugins/bearer/bearer.pro @@ -7,7 +7,7 @@ TEMPLATE = subdirs #win32:SUBDIRS += nla win32:SUBDIRS += generic -win32:!wince:!winrt: SUBDIRS += nativewifi +win32:!winrt: SUBDIRS += nativewifi mac:contains(QT_CONFIG, corewlan):SUBDIRS += corewlan mac:SUBDIRS += generic android:SUBDIRS += android diff --git a/src/plugins/bearer/generic/qgenericengine.cpp b/src/plugins/bearer/generic/qgenericengine.cpp index aa0fc6b945..02ea7abf88 100644 --- a/src/plugins/bearer/generic/qgenericengine.cpp +++ b/src/plugins/bearer/generic/qgenericengine.cpp @@ -50,17 +50,10 @@ #include <QtCore/qdebug.h> #include <QtCore/private/qcoreapplication_p.h> -#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) +#if defined(Q_OS_WIN32) #include "../platformdefs_win.h" #endif -#ifdef Q_OS_WINCE -typedef ULONG NDIS_OID, *PNDIS_OID; -# ifndef QT_NO_WINCE_NUIOUSER -# include <nuiouser.h> -# endif -#endif // Q_OS_WINCE - #ifdef Q_OS_WINRT #include <qfunctions_winrt.h> @@ -92,36 +85,22 @@ QT_BEGIN_NAMESPACE #ifndef QT_NO_NETWORKINTERFACE static QNetworkConfiguration::BearerType qGetInterfaceType(const QString &interface) { -#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) +#if defined(Q_OS_WIN32) DWORD bytesWritten; NDIS_MEDIUM medium; NDIS_PHYSICAL_MEDIUM physicalMedium; -#if defined(Q_OS_WINCE) && !defined(QT_NO_WINCE_NUIOUSER) - NDISUIO_QUERY_OID nicGetOid; - HANDLE handle = CreateFile((PTCHAR)NDISUIO_DEVICE_NAME, 0, - FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); -#else unsigned long oid; HANDLE handle = CreateFile((TCHAR *)QString::fromLatin1("\\\\.\\%1").arg(interface).utf16(), 0, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); -#endif if (handle == INVALID_HANDLE_VALUE) return QNetworkConfiguration::BearerUnknown; bytesWritten = 0; -#if defined(Q_OS_WINCE) && !defined(QT_NO_WINCE_NUIOUSER) - ZeroMemory(&nicGetOid, sizeof(NDISUIO_QUERY_OID)); - nicGetOid.Oid = OID_GEN_MEDIA_SUPPORTED; - nicGetOid.ptcDeviceName = (PTCHAR)interface.utf16(); - bool result = DeviceIoControl(handle, IOCTL_NDISUIO_QUERY_OID_VALUE, &nicGetOid, sizeof(nicGetOid), - &nicGetOid, sizeof(nicGetOid), &bytesWritten, 0); -#else oid = OID_GEN_MEDIA_SUPPORTED; bool result = DeviceIoControl(handle, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid), &medium, sizeof(medium), &bytesWritten, 0); -#endif if (!result) { CloseHandle(handle); return QNetworkConfiguration::BearerUnknown; @@ -129,22 +108,9 @@ static QNetworkConfiguration::BearerType qGetInterfaceType(const QString &interf bytesWritten = 0; -#if defined(Q_OS_WINCE) && !defined(QT_NO_WINCE_NUIOUSER) - medium = NDIS_MEDIUM( *(LPDWORD)nicGetOid.Data ); - - ZeroMemory(&nicGetOid, sizeof(NDISUIO_QUERY_OID)); - nicGetOid.Oid = OID_GEN_PHYSICAL_MEDIUM; - nicGetOid.ptcDeviceName = (PTCHAR)interface.utf16(); - - result = DeviceIoControl(handle, IOCTL_NDISUIO_QUERY_OID_VALUE, &nicGetOid, sizeof(nicGetOid), - &nicGetOid, sizeof(nicGetOid), &bytesWritten, 0); - - physicalMedium = NDIS_PHYSICAL_MEDIUM( *(LPDWORD)nicGetOid.Data ); -#else oid = OID_GEN_PHYSICAL_MEDIUM; result = DeviceIoControl(handle, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid), &physicalMedium, sizeof(physicalMedium), &bytesWritten, 0); -#endif if (!result) { CloseHandle(handle); diff --git a/src/plugins/bearer/nla/nla.pro b/src/plugins/bearer/nla/nla.pro index 32ff5446e5..113d0667d2 100644 --- a/src/plugins/bearer/nla/nla.pro +++ b/src/plugins/bearer/nla/nla.pro @@ -2,11 +2,7 @@ TARGET = qnlabearer QT = core core-private network network-private -!wince* { - LIBS += -lws2_32 -} else { - LIBS += -lws2 -} +LIBS += -lws2_32 HEADERS += qnlaengine.h \ ../platformdefs_win.h \ diff --git a/src/plugins/generic/tuiotouch/qtuiohandler.cpp b/src/plugins/generic/tuiotouch/qtuiohandler.cpp index 6026e06b55..86decd312b 100644 --- a/src/plugins/generic/tuiotouch/qtuiohandler.cpp +++ b/src/plugins/generic/tuiotouch/qtuiohandler.cpp @@ -43,10 +43,12 @@ #include <QWindow> #include <QGuiApplication> #include <QTouchDevice> +#include <qmath.h> #include <qpa/qwindowsysteminterface.h> #include "qtuiocursor_p.h" +#include "qtuiotoken_p.h" #include "qtuiohandler_p.h" #include "qoscbundle_p.h" @@ -55,6 +57,12 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcTuioSource, "qt.qpa.tuio.source") Q_LOGGING_CATEGORY(lcTuioSet, "qt.qpa.tuio.set") +// With TUIO the first application takes exclusive ownership of the "device" +// we cannot attach more than one application to the same port anyway. +// Forcing delivery makes it easy to use simulators in the same machine +// and forget about headaches about unfocused TUIO windows. +static bool forceDelivery = qEnvironmentVariableIsSet("QT_TUIOTOUCH_DELIVER_WITHOUT_FOCUS"); + QTuioHandler::QTuioHandler(const QString &specification) : m_device(new QTouchDevice) // not leaked, QTouchDevice cleans up registered devices itself { @@ -157,29 +165,49 @@ void QTuioHandler::processPackets() messages.push_back(msg); } - foreach (const QOscMessage &message, messages) { - if (message.addressPattern() != "/tuio/2Dcur") { - qWarning() << "Ignoring unknown address pattern " << message.addressPattern(); - continue; - } - - QList<QVariant> arguments = message.arguments(); - if (arguments.count() == 0) { - qWarning("Ignoring TUIO message with no arguments"); - continue; - } - - QByteArray messageType = arguments.at(0).toByteArray(); - if (messageType == "source") { - process2DCurSource(message); - } else if (messageType == "alive") { - process2DCurAlive(message); - } else if (messageType == "set") { - process2DCurSet(message); - } else if (messageType == "fseq") { - process2DCurFseq(message); + for (const QOscMessage &message : messages) { + if (message.addressPattern() == "/tuio/2Dcur") { + QList<QVariant> arguments = message.arguments(); + if (arguments.count() == 0) { + qWarning("Ignoring TUIO message with no arguments"); + continue; + } + + QByteArray messageType = arguments.at(0).toByteArray(); + if (messageType == "source") { + process2DCurSource(message); + } else if (messageType == "alive") { + process2DCurAlive(message); + } else if (messageType == "set") { + process2DCurSet(message); + } else if (messageType == "fseq") { + process2DCurFseq(message); + } else { + qWarning() << "Ignoring unknown TUIO message type: " << messageType; + continue; + } + } else if (message.addressPattern() == "/tuio/2Dobj") { + QList<QVariant> arguments = message.arguments(); + if (arguments.count() == 0) { + qWarning("Ignoring TUIO message with no arguments"); + continue; + } + + QByteArray messageType = arguments.at(0).toByteArray(); + if (messageType == "source") { + process2DObjSource(message); + } else if (messageType == "alive") { + process2DObjAlive(message); + } else if (messageType == "set") { + process2DObjSet(message); + } else if (messageType == "fseq") { + process2DObjFseq(message); + } else { + qWarning() << "Ignoring unknown TUIO message type: " << messageType; + continue; + } } else { - qWarning() << "Ignoring unknown TUIO message type: " << messageType; + qWarning() << "Ignoring unknown address pattern " << message.addressPattern(); continue; } } @@ -328,11 +356,6 @@ void QTuioHandler::process2DCurFseq(const QOscMessage &message) Q_UNUSED(message); // TODO: do we need to do anything with the frame id? QWindow *win = QGuiApplication::focusWindow(); - // With TUIO the first application takes exclusive ownership of the "device" - // we cannot attach more than one application to the same port anyway. - // Forcing delivery makes it easy to use simulators in the same machine - // and forget about headaches about unfocused TUIO windows. - static bool forceDelivery = qEnvironmentVariableIsSet("QT_TUIOTOUCH_DELIVER_WITHOUT_FOCUS"); if (!win && QGuiApplication::topLevelWindows().length() > 0 && forceDelivery) win = QGuiApplication::topLevelWindows().at(0); @@ -342,12 +365,12 @@ void QTuioHandler::process2DCurFseq(const QOscMessage &message) QList<QWindowSystemInterface::TouchPoint> tpl; tpl.reserve(m_activeCursors.size() + m_deadCursors.size()); - foreach (const QTuioCursor &tc, m_activeCursors) { + for (const QTuioCursor &tc : m_activeCursors) { QWindowSystemInterface::TouchPoint tp = cursorToTouchPoint(tc, win); tpl.append(tp); } - foreach (const QTuioCursor &tc, m_deadCursors) { + for (const QTuioCursor &tc : m_deadCursors) { QWindowSystemInterface::TouchPoint tp = cursorToTouchPoint(tc, win); tp.state = Qt::TouchPointReleased; tpl.append(tp); @@ -357,5 +380,181 @@ void QTuioHandler::process2DCurFseq(const QOscMessage &message) m_deadCursors.clear(); } +void QTuioHandler::process2DObjSource(const QOscMessage &message) +{ + QList<QVariant> arguments = message.arguments(); + if (arguments.count() != 2) { + qWarning() << "Ignoring malformed TUIO source message: " << arguments.count(); + return; + } + + if (QMetaType::Type(arguments.at(1).type()) != QMetaType::QByteArray) { + qWarning("Ignoring malformed TUIO source message (bad argument type)"); + return; + } + + qCDebug(lcTuioSource) << "Got TUIO source message from: " << arguments.at(1).toByteArray(); +} + +void QTuioHandler::process2DObjAlive(const QOscMessage &message) +{ + QList<QVariant> arguments = message.arguments(); + + // delta the notified tokens that are active, against the ones we already + // know of. + // + // TBD: right now we're assuming one 2DObj alive message corresponds to a + // new data source from the input. is this correct, or do we need to store + // changes and only process the deltas on fseq? + QMap<int, QTuioToken> oldActiveTokens = m_activeTokens; + QMap<int, QTuioToken> newActiveTokens; + + for (int i = 1; i < arguments.count(); ++i) { + if (QMetaType::Type(arguments.at(i).type()) != QMetaType::Int) { + qWarning() << "Ignoring malformed TUIO alive message (bad argument on position" << i << arguments << ')'; + return; + } + + int sessionId = arguments.at(i).toInt(); + if (!oldActiveTokens.contains(sessionId)) { + // newly active + QTuioToken token(sessionId); + token.setState(Qt::TouchPointPressed); + newActiveTokens.insert(sessionId, token); + } else { + // we already know about it, remove it so it isn't marked as released + QTuioToken token = oldActiveTokens.value(sessionId); + token.setState(Qt::TouchPointStationary); // position change in SET will update if needed + newActiveTokens.insert(sessionId, token); + oldActiveTokens.remove(sessionId); + } + } + + // anything left is dead now + QMap<int, QTuioToken>::ConstIterator it = oldActiveTokens.constBegin(); + + // deadTokens should be cleared from the last FSEQ now + m_deadTokens.reserve(oldActiveTokens.size()); + + // TODO: there could be an issue of resource exhaustion here if FSEQ isn't + // sent in a timely fashion. we should probably track message counts and + // force-flush if we get too many built up. + while (it != oldActiveTokens.constEnd()) { + m_deadTokens.append(it.value()); + ++it; + } + + m_activeTokens = newActiveTokens; +} + +void QTuioHandler::process2DObjSet(const QOscMessage &message) +{ + QList<QVariant> arguments = message.arguments(); + if (arguments.count() < 7) { + qWarning() << "Ignoring malformed TUIO set message with too few arguments: " << arguments.count(); + return; + } + + if (QMetaType::Type(arguments.at(1).type()) != QMetaType::Int || + QMetaType::Type(arguments.at(2).type()) != QMetaType::Int || + QMetaType::Type(arguments.at(3).type()) != QMetaType::Float || + QMetaType::Type(arguments.at(4).type()) != QMetaType::Float || + QMetaType::Type(arguments.at(5).type()) != QMetaType::Float || + QMetaType::Type(arguments.at(6).type()) != QMetaType::Float || + QMetaType::Type(arguments.at(7).type()) != QMetaType::Float || + QMetaType::Type(arguments.at(8).type()) != QMetaType::Float || + QMetaType::Type(arguments.at(9).type()) != QMetaType::Float || + QMetaType::Type(arguments.at(10).type()) != QMetaType::Float) { + qWarning() << "Ignoring malformed TUIO set message with bad types: " << arguments; + return; + } + + int id = arguments.at(1).toInt(); + int classId = arguments.at(2).toInt(); + float x = arguments.at(3).toFloat(); + float y = arguments.at(4).toFloat(); + float angle = arguments.at(5).toFloat(); + float vx = arguments.at(6).toFloat(); + float vy = arguments.at(7).toFloat(); + float angularVelocity = arguments.at(8).toFloat(); + float acceleration = arguments.at(9).toFloat(); + float angularAcceleration = arguments.at(10).toFloat(); + + QMap<int, QTuioToken>::Iterator it = m_activeTokens.find(id); + if (it == m_activeTokens.end()) { + qWarning() << "Ignoring malformed TUIO set for nonexistent token " << classId; + return; + } + + qCDebug(lcTuioSet) << "Processing SET for token " << classId << id << " @ " << x << y << "∡" << angle << + "vel" << vx << vy << angularVelocity << "acc" << acceleration << angularAcceleration; + QTuioToken &tok = *it; + tok.setClassId(classId); + tok.setX(x); + tok.setY(y); + tok.setVX(vx); + tok.setVY(vy); + tok.setAcceleration(acceleration); + tok.setAngle(angle); + tok.setAngularVelocity(angularAcceleration); + tok.setAngularAcceleration(angularAcceleration); +} + +QWindowSystemInterface::TouchPoint QTuioHandler::tokenToTouchPoint(const QTuioToken &tc, QWindow *win) +{ + QWindowSystemInterface::TouchPoint tp; + tp.id = tc.id(); + tp.uniqueId = tc.classId(); // TODO TUIO 2.0: populate a QVariant, and register the mapping from int to arbitrary UID data + tp.flags = QTouchEvent::TouchPoint::Token; + tp.pressure = 1.0f; + + tp.normalPosition = QPointF(tc.x(), tc.y()); + + if (!m_transform.isIdentity()) + tp.normalPosition = m_transform.map(tp.normalPosition); + + tp.state = tc.state(); + tp.area = QRectF(0, 0, 1, 1); + + // We map the token position to the size of the window. + QPointF relPos = QPointF(win->size().width() * tp.normalPosition.x(), win->size().height() * tp.normalPosition.y()); + QPointF delta = relPos - relPos.toPoint(); + tp.area.moveCenter(win->mapToGlobal(relPos.toPoint()) + delta); + tp.velocity = QVector2D(win->size().width() * tc.vx(), win->size().height() * tc.vy()); + tp.rotation = tc.angle() * 180.0 / M_PI; // convert radians to degrees + return tp; +} + + +void QTuioHandler::process2DObjFseq(const QOscMessage &message) +{ + Q_UNUSED(message); // TODO: do we need to do anything with the frame id? + + QWindow *win = QGuiApplication::focusWindow(); + if (!win && QGuiApplication::topLevelWindows().length() > 0 && forceDelivery) + win = QGuiApplication::topLevelWindows().at(0); + + if (!win) + return; + + QList<QWindowSystemInterface::TouchPoint> tpl; + tpl.reserve(m_activeTokens.size() + m_deadTokens.size()); + + for (const QTuioToken & t : m_activeTokens) { + QWindowSystemInterface::TouchPoint tp = tokenToTouchPoint(t, win); + tpl.append(tp); + } + + for (const QTuioToken & t : m_deadTokens) { + QWindowSystemInterface::TouchPoint tp = tokenToTouchPoint(t, win); + tp.state = Qt::TouchPointReleased; + tp.velocity = QVector2D(); + tpl.append(tp); + } + QWindowSystemInterface::handleTouchEvent(win, m_device, tpl); + + m_deadTokens.clear(); +} + QT_END_NAMESPACE diff --git a/src/plugins/generic/tuiotouch/qtuiohandler_p.h b/src/plugins/generic/tuiotouch/qtuiohandler_p.h index 3034872aae..2e444f2a0d 100644 --- a/src/plugins/generic/tuiotouch/qtuiohandler_p.h +++ b/src/plugins/generic/tuiotouch/qtuiohandler_p.h @@ -54,6 +54,7 @@ QT_BEGIN_NAMESPACE class QTouchDevice; class QOscMessage; class QTuioCursor; +class QTuioToken; class QTuioHandler : public QObject { @@ -69,14 +70,21 @@ private slots: void process2DCurAlive(const QOscMessage &message); void process2DCurSet(const QOscMessage &message); void process2DCurFseq(const QOscMessage &message); + void process2DObjSource(const QOscMessage &message); + void process2DObjAlive(const QOscMessage &message); + void process2DObjSet(const QOscMessage &message); + void process2DObjFseq(const QOscMessage &message); private: QWindowSystemInterface::TouchPoint cursorToTouchPoint(const QTuioCursor &tc, QWindow *win); + QWindowSystemInterface::TouchPoint tokenToTouchPoint(const QTuioToken &tc, QWindow *win); QTouchDevice *m_device; QUdpSocket m_socket; QMap<int, QTuioCursor> m_activeCursors; QVector<QTuioCursor> m_deadCursors; + QMap<int, QTuioToken> m_activeTokens; + QVector<QTuioToken> m_deadTokens; QTransform m_transform; }; diff --git a/src/plugins/generic/tuiotouch/qtuiotoken_p.h b/src/plugins/generic/tuiotouch/qtuiotoken_p.h new file mode 100644 index 0000000000..5084aeed11 --- /dev/null +++ b/src/plugins/generic/tuiotouch/qtuiotoken_p.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTUIOOBJECT_P_H +#define QTUIOOBJECT_P_H + +#include <Qt> +#include <qmath.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + + A fiducial object, or token, represented by 2Dobj in TUIO 1.x and tok in TUIO 2: + a physical object whose position and rotation can be uniquely tracked + on the touchscreen surface. +*/ +class QTuioToken +{ +public: + QTuioToken(int id = -1) + : m_id(id) + , m_classId(-1) + , m_x(0) + , m_y(0) + , m_vx(0) + , m_vy(0) + , m_acceleration(0) + , m_angle(0) + , m_angularVelocity(0) + , m_angularAcceleration(0) + , m_state(Qt::TouchPointPressed) + { + } + + int id() const { return m_id; } + + int classId() const { return m_classId; } + void setClassId(int classId) { m_classId = classId; } + + void setX(float x) + { + if (state() == Qt::TouchPointStationary && + !qFuzzyCompare(m_x + 2.0, x + 2.0)) { // +2 because 1 is a valid value, and qFuzzyCompare can't cope with 0.0 + setState(Qt::TouchPointMoved); + } + m_x = x; + } + float x() const { return m_x; } + + void setY(float y) + { + if (state() == Qt::TouchPointStationary && + !qFuzzyCompare(m_y + 2.0, y + 2.0)) { // +2 because 1 is a valid value, and qFuzzyCompare can't cope with 0.0 + setState(Qt::TouchPointMoved); + } + m_y = y; + } + float y() const { return m_y; } + + void setVX(float vx) { m_vx = vx; } + float vx() const { return m_vx; } + + void setVY(float vy) { m_vy = vy; } + float vy() const { return m_vy; } + + void setAcceleration(float acceleration) { m_acceleration = acceleration; } + float acceleration() const { return m_acceleration; } + + float angle() const { return m_angle; } + void setAngle(float angle) + { + if (angle > M_PI) + angle = angle - M_PI * 2.0; // zero is pointing upwards, and is the default; but we want to have negative angles when rotating left + if (state() == Qt::TouchPointStationary && + !qFuzzyCompare(m_angle + 2.0, angle + 2.0)) { // +2 because 1 is a valid value, and qFuzzyCompare can't cope with 0.0 + setState(Qt::TouchPointMoved); + } + m_angle = angle; + } + + float angularVelocity() const { return m_angularVelocity; } + void setAngularVelocity(float angularVelocity) { m_angularVelocity = angularVelocity; } + + float angularAcceleration() const { return m_angularAcceleration; } + void setAngularAcceleration(float angularAcceleration) { m_angularAcceleration = angularAcceleration; } + + void setState(const Qt::TouchPointState &state) { m_state = state; } + Qt::TouchPointState state() const { return m_state; } + +private: + int m_id; // sessionID, temporary object ID + int m_classId; // classID (e.g. marker ID) + float m_x; + float m_y; + float m_vx; + float m_vy; + float m_acceleration; + float m_angle; + float m_angularVelocity; + float m_angularAcceleration; + Qt::TouchPointState m_state; +}; + +QT_END_NAMESPACE + +#endif // QTUIOOBJECT_P_H diff --git a/src/plugins/generic/tuiotouch/tuiotouch.pro b/src/plugins/generic/tuiotouch/tuiotouch.pro index ae2ccde058..ad6a1c6876 100644 --- a/src/plugins/generic/tuiotouch/tuiotouch.pro +++ b/src/plugins/generic/tuiotouch/tuiotouch.pro @@ -15,7 +15,8 @@ HEADERS += \ qoscbundle_p.h \ qoscmessage_p.h \ qtuiohandler_p.h \ - qtuiocursor_p.h + qtuiocursor_p.h \ + qtuiotoken_p.h OTHER_FILES += \ tuiotouch.json diff --git a/src/plugins/imageformats/gif/gif.pro b/src/plugins/imageformats/gif/gif.pro index a361bc2532..c2625be85a 100644 --- a/src/plugins/imageformats/gif/gif.pro +++ b/src/plugins/imageformats/gif/gif.pro @@ -1,9 +1,8 @@ TARGET = qgif -include(../../../gui/image/qgifhandler.pri) -INCLUDEPATH += ../../../gui/image -SOURCES += $$PWD/main.cpp -HEADERS += $$PWD/main.h +SOURCES += main.cpp qgifhandler.cpp +HEADERS += main.h qgifhandler_p.h + OTHER_FILES += gif.json PLUGIN_TYPE = imageformats diff --git a/src/plugins/imageformats/gif/qgifhandler.cpp b/src/plugins/imageformats/gif/qgifhandler.cpp new file mode 100644 index 0000000000..476b456563 --- /dev/null +++ b/src/plugins/imageformats/gif/qgifhandler.cpp @@ -0,0 +1,1218 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** WARNING: +** A separate license from Unisys may be required to use the gif +** reader. See http://www.unisys.com/about__unisys/lzw/ +** for information from Unisys +** +****************************************************************************/ + +#include "qgifhandler_p.h" + +#include <qimage.h> +#include <qiodevice.h> +#include <qvariant.h> + +QT_BEGIN_NAMESPACE + +#define Q_TRANSPARENT 0x00ffffff + +// avoid going through QImage::scanLine() which calls detach +#define FAST_SCAN_LINE(bits, bpl, y) (bits + (y) * bpl) + + +/* + Incremental image decoder for GIF image format. + + This subclass of QImageFormat decodes GIF format images, + including animated GIFs. Internally in +*/ + +class QGIFFormat { +public: + QGIFFormat(); + ~QGIFFormat(); + + int decode(QImage *image, const uchar* buffer, int length, + int *nextFrameDelay, int *loopCount); + static void scan(QIODevice *device, QVector<QSize> *imageSizes, int *loopCount); + + bool newFrame; + bool partialNewFrame; + +private: + void fillRect(QImage *image, int x, int y, int w, int h, QRgb col); + inline QRgb color(uchar index) const; + + // GIF specific stuff + QRgb* globalcmap; + QRgb* localcmap; + QImage backingstore; + unsigned char hold[16]; + bool gif89; + int count; + int ccount; + int expectcount; + enum State { + Header, + LogicalScreenDescriptor, + GlobalColorMap, + LocalColorMap, + Introducer, + ImageDescriptor, + TableImageLZWSize, + ImageDataBlockSize, + ImageDataBlock, + ExtensionLabel, + GraphicControlExtension, + ApplicationExtension, + NetscapeExtensionBlockSize, + NetscapeExtensionBlock, + SkipBlockSize, + SkipBlock, + Done, + Error + } state; + int gncols; + int lncols; + int ncols; + int lzwsize; + bool lcmap; + int swidth, sheight; + int width, height; + int left, top, right, bottom; + enum Disposal { NoDisposal, DoNotChange, RestoreBackground, RestoreImage }; + Disposal disposal; + bool disposed; + int trans_index; + bool gcmap; + int bgcol; + int interlace; + int accum; + int bitcount; + + enum { max_lzw_bits=12 }; // (poor-compiler's static const int) + + int code_size, clear_code, end_code, max_code_size, max_code; + int firstcode, oldcode, incode; + short* table[2]; + short* stack; + short *sp; + bool needfirst; + int x, y; + int frame; + bool out_of_bounds; + bool digress; + void nextY(unsigned char *bits, int bpl); + void disposePrevious(QImage *image); +}; + +/*! + Constructs a QGIFFormat. +*/ +QGIFFormat::QGIFFormat() +{ + globalcmap = 0; + localcmap = 0; + lncols = 0; + gncols = 0; + disposal = NoDisposal; + out_of_bounds = false; + disposed = true; + frame = -1; + state = Header; + count = 0; + lcmap = false; + newFrame = false; + partialNewFrame = false; + table[0] = 0; + table[1] = 0; + stack = 0; +} + +/*! + Destroys a QGIFFormat. +*/ +QGIFFormat::~QGIFFormat() +{ + if (globalcmap) delete[] globalcmap; + if (localcmap) delete[] localcmap; + delete [] stack; +} + +void QGIFFormat::disposePrevious(QImage *image) +{ + if (out_of_bounds) { + // flush anything that survived + // ### Changed: QRect(0, 0, swidth, sheight) + } + + // Handle disposal of previous image before processing next one + + if (disposed) return; + + int l = qMin(swidth-1,left); + int r = qMin(swidth-1,right); + int t = qMin(sheight-1,top); + int b = qMin(sheight-1,bottom); + + switch (disposal) { + case NoDisposal: + break; + case DoNotChange: + break; + case RestoreBackground: + if (trans_index>=0) { + // Easy: we use the transparent color + fillRect(image, l, t, r-l+1, b-t+1, Q_TRANSPARENT); + } else if (bgcol>=0) { + // Easy: we use the bgcol given + fillRect(image, l, t, r-l+1, b-t+1, color(bgcol)); + } else { + // Impossible: We don't know of a bgcol - use pixel 0 + const QRgb *bits = reinterpret_cast<const QRgb *>(image->constBits()); + fillRect(image, l, t, r-l+1, b-t+1, bits[0]); + } + // ### Changed: QRect(l, t, r-l+1, b-t+1) + break; + case RestoreImage: { + if (frame >= 0) { + for (int ln=t; ln<=b; ln++) { + memcpy(image->scanLine(ln)+l, + backingstore.constScanLine(ln-t), + (r-l+1)*sizeof(QRgb)); + } + // ### Changed: QRect(l, t, r-l+1, b-t+1) + } + } + } + disposal = NoDisposal; // Until an extension says otherwise. + + disposed = true; +} + +/*! + This function decodes some data into image changes. + + Returns the number of bytes consumed. +*/ +int QGIFFormat::decode(QImage *image, const uchar *buffer, int length, + int *nextFrameDelay, int *loopCount) +{ + // We are required to state that + // "The Graphics Interchange Format(c) is the Copyright property of + // CompuServe Incorporated. GIF(sm) is a Service Mark property of + // CompuServe Incorporated." + + if (!stack) { + stack = new short[(1 << max_lzw_bits) * 4]; + table[0] = &stack[(1 << max_lzw_bits) * 2]; + table[1] = &stack[(1 << max_lzw_bits) * 3]; + } + + image->detach(); + int bpl = image->bytesPerLine(); + unsigned char *bits = image->bits(); + +#define LM(l, m) (((m)<<8)|l) + digress = false; + const int initial = length; + while (!digress && length) { + length--; + unsigned char ch=*buffer++; + switch (state) { + case Header: + hold[count++]=ch; + if (count==6) { + // Header + gif89=(hold[3]!='8' || hold[4]!='7'); + state=LogicalScreenDescriptor; + count=0; + } + break; + case LogicalScreenDescriptor: + hold[count++]=ch; + if (count==7) { + // Logical Screen Descriptor + swidth=LM(hold[0], hold[1]); + sheight=LM(hold[2], hold[3]); + gcmap=!!(hold[4]&0x80); + //UNUSED: bpchan=(((hold[4]&0x70)>>3)+1); + //UNUSED: gcmsortflag=!!(hold[4]&0x08); + gncols=2<<(hold[4]&0x7); + bgcol=(gcmap) ? hold[5] : -1; + //aspect=hold[6] ? double(hold[6]+15)/64.0 : 1.0; + + trans_index = -1; + count=0; + ncols=gncols; + if (gcmap) { + ccount=0; + state=GlobalColorMap; + globalcmap = new QRgb[gncols+1]; // +1 for trans_index + globalcmap[gncols] = Q_TRANSPARENT; + } else { + state=Introducer; + } + } + break; + case GlobalColorMap: case LocalColorMap: + hold[count++]=ch; + if (count==3) { + QRgb rgb = qRgb(hold[0], hold[1], hold[2]); + if (state == LocalColorMap) { + if (ccount < lncols) + localcmap[ccount] = rgb; + } else { + globalcmap[ccount] = rgb; + } + if (++ccount >= ncols) { + if (state == LocalColorMap) + state=TableImageLZWSize; + else + state=Introducer; + } + count=0; + } + break; + case Introducer: + hold[count++]=ch; + switch (ch) { + case ',': + state=ImageDescriptor; + break; + case '!': + state=ExtensionLabel; + break; + case ';': + // ### Changed: QRect(0, 0, swidth, sheight) + state=Done; + break; + default: + digress=true; + // Unexpected Introducer - ignore block + state=Error; + } + break; + case ImageDescriptor: + hold[count++]=ch; + if (count==10) { + int newleft=LM(hold[1], hold[2]); + int newtop=LM(hold[3], hold[4]); + int newwidth=LM(hold[5], hold[6]); + int newheight=LM(hold[7], hold[8]); + + // disbelieve ridiculous logical screen sizes, + // unless the image frames are also large. + if (swidth/10 > qMax(newwidth,200)) + swidth = -1; + if (sheight/10 > qMax(newheight,200)) + sheight = -1; + + if (swidth <= 0) + swidth = newleft + newwidth; + if (sheight <= 0) + sheight = newtop + newheight; + + QImage::Format format = trans_index >= 0 ? QImage::Format_ARGB32 : QImage::Format_RGB32; + if (image->isNull()) { + (*image) = QImage(swidth, sheight, format); + bpl = image->bytesPerLine(); + bits = image->bits(); + memset(bits, 0, image->byteCount()); + } + + // Check if the previous attempt to create the image failed. If it + // did then the image is broken and we should give up. + if (image->isNull()) { + state = Error; + return -1; + } + + disposePrevious(image); + disposed = false; + + left = newleft; + top = newtop; + width = newwidth; + height = newheight; + + right=qMax(0, qMin(left+width, swidth)-1); + bottom=qMax(0, qMin(top+height, sheight)-1); + lcmap=!!(hold[9]&0x80); + interlace=!!(hold[9]&0x40); + //bool lcmsortflag=!!(hold[9]&0x20); + lncols=lcmap ? (2<<(hold[9]&0x7)) : 0; + if (lncols) { + if (localcmap) + delete [] localcmap; + localcmap = new QRgb[lncols+1]; + localcmap[lncols] = Q_TRANSPARENT; + ncols = lncols; + } else { + ncols = gncols; + } + frame++; + if (frame == 0) { + if (left || top || width<swidth || height<sheight) { + // Not full-size image - erase with bg or transparent + if (trans_index >= 0) { + fillRect(image, 0, 0, swidth, sheight, color(trans_index)); + // ### Changed: QRect(0, 0, swidth, sheight) + } else if (bgcol>=0) { + fillRect(image, 0, 0, swidth, sheight, color(bgcol)); + // ### Changed: QRect(0, 0, swidth, sheight) + } + } + } + + if (disposal == RestoreImage) { + int l = qMin(swidth-1,left); + int r = qMin(swidth-1,right); + int t = qMin(sheight-1,top); + int b = qMin(sheight-1,bottom); + int w = r-l+1; + int h = b-t+1; + + if (backingstore.width() < w + || backingstore.height() < h) { + // We just use the backing store as a byte array + backingstore = QImage(qMax(backingstore.width(), w), + qMax(backingstore.height(), h), + QImage::Format_RGB32); + memset(bits, 0, image->byteCount()); + } + const int dest_bpl = backingstore.bytesPerLine(); + unsigned char *dest_data = backingstore.bits(); + for (int ln=0; ln<h; ln++) { + memcpy(FAST_SCAN_LINE(dest_data, dest_bpl, ln), + FAST_SCAN_LINE(bits, bpl, t+ln) + l, w*sizeof(QRgb)); + } + } + + count=0; + if (lcmap) { + ccount=0; + state=LocalColorMap; + } else { + state=TableImageLZWSize; + } + x = left; + y = top; + accum = 0; + bitcount = 0; + sp = stack; + firstcode = oldcode = 0; + needfirst = true; + out_of_bounds = left>=swidth || y>=sheight; + } + break; + case TableImageLZWSize: { + lzwsize=ch; + if (lzwsize > max_lzw_bits) { + state=Error; + } else { + code_size=lzwsize+1; + clear_code=1<<lzwsize; + end_code=clear_code+1; + max_code_size=2*clear_code; + max_code=clear_code+2; + int i; + for (i=0; i<clear_code; i++) { + table[0][i]=0; + table[1][i]=i; + } + state=ImageDataBlockSize; + } + count=0; + break; + } case ImageDataBlockSize: + expectcount=ch; + if (expectcount) { + state=ImageDataBlock; + } else { + state=Introducer; + digress = true; + newFrame = true; + } + break; + case ImageDataBlock: + count++; + accum|=(ch<<bitcount); + bitcount+=8; + while (bitcount>=code_size && state==ImageDataBlock) { + int code=accum&((1<<code_size)-1); + bitcount-=code_size; + accum>>=code_size; + + if (code==clear_code) { + if (!needfirst) { + code_size=lzwsize+1; + max_code_size=2*clear_code; + max_code=clear_code+2; + } + needfirst=true; + } else if (code==end_code) { + bitcount = -32768; + // Left the block end arrive + } else { + if (needfirst) { + firstcode=oldcode=code; + if (!out_of_bounds && image->height() > y && ((frame == 0) || (firstcode != trans_index))) + ((QRgb*)FAST_SCAN_LINE(bits, bpl, y))[x] = color(firstcode); + x++; + if (x>=swidth) out_of_bounds = true; + needfirst=false; + if (x>=left+width) { + x=left; + out_of_bounds = left>=swidth || y>=sheight; + nextY(bits, bpl); + } + } else { + incode=code; + if (code>=max_code) { + *sp++=firstcode; + code=oldcode; + } + while (code>=clear_code+2) { + if (code >= max_code) { + state = Error; + return -1; + } + *sp++=table[1][code]; + if (code==table[0][code]) { + state=Error; + return -1; + } + if (sp-stack>=(1<<(max_lzw_bits))*2) { + state=Error; + return -1; + } + code=table[0][code]; + } + if (code < 0) { + state = Error; + return -1; + } + + *sp++=firstcode=table[1][code]; + code=max_code; + if (code<(1<<max_lzw_bits)) { + table[0][code]=oldcode; + table[1][code]=firstcode; + max_code++; + if ((max_code>=max_code_size) + && (max_code_size<(1<<max_lzw_bits))) + { + max_code_size*=2; + code_size++; + } + } + oldcode=incode; + const int h = image->height(); + QRgb *line = 0; + if (!out_of_bounds && h > y) + line = (QRgb*)FAST_SCAN_LINE(bits, bpl, y); + while (sp>stack) { + const uchar index = *(--sp); + if (!out_of_bounds && h > y && ((frame == 0) || (index != trans_index))) { + line[x] = color(index); + } + x++; + if (x>=swidth) out_of_bounds = true; + if (x>=left+width) { + x=left; + out_of_bounds = left>=swidth || y>=sheight; + nextY(bits, bpl); + if (!out_of_bounds && h > y) + line = (QRgb*)FAST_SCAN_LINE(bits, bpl, y); + } + } + } + } + } + partialNewFrame = true; + if (count==expectcount) { + count=0; + state=ImageDataBlockSize; + } + break; + case ExtensionLabel: + switch (ch) { + case 0xf9: + state=GraphicControlExtension; + break; + case 0xff: + state=ApplicationExtension; + break; +#if 0 + case 0xfe: + state=CommentExtension; + break; + case 0x01: + break; +#endif + default: + state=SkipBlockSize; + } + count=0; + break; + case ApplicationExtension: + if (count<11) hold[count]=ch; + count++; + if (count==hold[0]+1) { + if (qstrncmp((char*)(hold+1), "NETSCAPE", 8)==0) { + // Looping extension + state=NetscapeExtensionBlockSize; + } else { + state=SkipBlockSize; + } + count=0; + } + break; + case NetscapeExtensionBlockSize: + expectcount=ch; + count=0; + if (expectcount) state=NetscapeExtensionBlock; + else state=Introducer; + break; + case NetscapeExtensionBlock: + if (count<3) hold[count]=ch; + count++; + if (count==expectcount) { + *loopCount = hold[1]+hold[2]*256; + state=SkipBlockSize; // Ignore further blocks + } + break; + case GraphicControlExtension: + if (count<5) hold[count]=ch; + count++; + if (count==hold[0]+1) { + disposePrevious(image); + disposal=Disposal((hold[1]>>2)&0x7); + //UNUSED: waitforuser=!!((hold[1]>>1)&0x1); + int delay=count>3 ? LM(hold[2], hold[3]) : 1; + // IE and mozilla use a minimum delay of 10. With the minimum delay of 10 + // we are compatible to them and avoid huge loads on the app and xserver. + *nextFrameDelay = (delay < 2 ? 10 : delay) * 10; + + bool havetrans=hold[1]&0x1; + trans_index = havetrans ? hold[4] : -1; + + count=0; + state=SkipBlockSize; + } + break; + case SkipBlockSize: + expectcount=ch; + count=0; + if (expectcount) state=SkipBlock; + else state=Introducer; + break; + case SkipBlock: + count++; + if (count==expectcount) state=SkipBlockSize; + break; + case Done: + digress=true; + /* Netscape ignores the junk, so we do too. + length++; // Unget + state=Error; // More calls to this is an error + */ + break; + case Error: + return -1; // Called again after done. + } + } + return initial-length; +} + +/*! + Scans through the data stream defined by \a device and returns the image + sizes found in the stream in the \a imageSizes vector. +*/ +void QGIFFormat::scan(QIODevice *device, QVector<QSize> *imageSizes, int *loopCount) +{ + if (!device) + return; + + qint64 oldPos = device->pos(); + if (device->isSequential() || !device->seek(0)) + return; + + int colorCount = 0; + int localColorCount = 0; + int globalColorCount = 0; + int colorReadCount = 0; + bool localColormap = false; + bool globalColormap = false; + int count = 0; + int blockSize = 0; + int imageWidth = 0; + int imageHeight = 0; + bool done = false; + uchar hold[16]; + State state = Header; + + const int readBufferSize = 40960; // 40k read buffer + QByteArray readBuffer(device->read(readBufferSize)); + + if (readBuffer.isEmpty()) { + device->seek(oldPos); + return; + } + + // This is a specialized version of the state machine from decode(), + // which doesn't do any image decoding or mallocing, and has an + // optimized way of skipping SkipBlocks, ImageDataBlocks and + // Global/LocalColorMaps. + + while (!readBuffer.isEmpty()) { + int length = readBuffer.size(); + const uchar *buffer = (const uchar *) readBuffer.constData(); + while (!done && length) { + length--; + uchar ch = *buffer++; + switch (state) { + case Header: + hold[count++] = ch; + if (count == 6) { + state = LogicalScreenDescriptor; + count = 0; + } + break; + case LogicalScreenDescriptor: + hold[count++] = ch; + if (count == 7) { + imageWidth = LM(hold[0], hold[1]); + imageHeight = LM(hold[2], hold[3]); + globalColormap = !!(hold[4] & 0x80); + globalColorCount = 2 << (hold[4] & 0x7); + count = 0; + colorCount = globalColorCount; + if (globalColormap) { + int colorTableSize = 3 * globalColorCount; + if (length >= colorTableSize) { + // skip the global color table in one go + length -= colorTableSize; + buffer += colorTableSize; + state = Introducer; + } else { + colorReadCount = 0; + state = GlobalColorMap; + } + } else { + state=Introducer; + } + } + break; + case GlobalColorMap: + case LocalColorMap: + hold[count++] = ch; + if (count == 3) { + if (++colorReadCount >= colorCount) { + if (state == LocalColorMap) + state = TableImageLZWSize; + else + state = Introducer; + } + count = 0; + } + break; + case Introducer: + hold[count++] = ch; + switch (ch) { + case 0x2c: + state = ImageDescriptor; + break; + case 0x21: + state = ExtensionLabel; + break; + case 0x3b: + state = Done; + break; + default: + done = true; + state = Error; + } + break; + case ImageDescriptor: + hold[count++] = ch; + if (count == 10) { + int newLeft = LM(hold[1], hold[2]); + int newTop = LM(hold[3], hold[4]); + int newWidth = LM(hold[5], hold[6]); + int newHeight = LM(hold[7], hold[8]); + + if (imageWidth/10 > qMax(newWidth,200)) + imageWidth = -1; + if (imageHeight/10 > qMax(newHeight,200)) + imageHeight = -1; + + if (imageWidth <= 0) + imageWidth = newLeft + newWidth; + if (imageHeight <= 0) + imageHeight = newTop + newHeight; + + *imageSizes << QSize(imageWidth, imageHeight); + + localColormap = !!(hold[9] & 0x80); + localColorCount = localColormap ? (2 << (hold[9] & 0x7)) : 0; + if (localColorCount) + colorCount = localColorCount; + else + colorCount = globalColorCount; + + count = 0; + if (localColormap) { + int colorTableSize = 3 * localColorCount; + if (length >= colorTableSize) { + // skip the local color table in one go + length -= colorTableSize; + buffer += colorTableSize; + state = TableImageLZWSize; + } else { + colorReadCount = 0; + state = LocalColorMap; + } + } else { + state = TableImageLZWSize; + } + } + break; + case TableImageLZWSize: + if (ch > max_lzw_bits) + state = Error; + else + state = ImageDataBlockSize; + count = 0; + break; + case ImageDataBlockSize: + blockSize = ch; + if (blockSize) { + if (length >= blockSize) { + // we can skip the block in one go + length -= blockSize; + buffer += blockSize; + count = 0; + } else { + state = ImageDataBlock; + } + } else { + state = Introducer; + } + break; + case ImageDataBlock: + ++count; + if (count == blockSize) { + count = 0; + state = ImageDataBlockSize; + } + break; + case ExtensionLabel: + switch (ch) { + case 0xf9: + state = GraphicControlExtension; + break; + case 0xff: + state = ApplicationExtension; + break; + default: + state = SkipBlockSize; + } + count = 0; + break; + case ApplicationExtension: + if (count < 11) + hold[count] = ch; + ++count; + if (count == hold[0] + 1) { + if (qstrncmp((char*)(hold+1), "NETSCAPE", 8) == 0) + state=NetscapeExtensionBlockSize; + else + state=SkipBlockSize; + count = 0; + } + break; + case GraphicControlExtension: + if (count < 5) + hold[count] = ch; + ++count; + if (count == hold[0] + 1) { + count = 0; + state = SkipBlockSize; + } + break; + case NetscapeExtensionBlockSize: + blockSize = ch; + count = 0; + if (blockSize) + state = NetscapeExtensionBlock; + else + state = Introducer; + break; + case NetscapeExtensionBlock: + if (count < 3) + hold[count] = ch; + count++; + if (count == blockSize) { + *loopCount = LM(hold[1], hold[2]); + state = SkipBlockSize; + } + break; + case SkipBlockSize: + blockSize = ch; + count = 0; + if (blockSize) { + if (length >= blockSize) { + // we can skip the block in one go + length -= blockSize; + buffer += blockSize; + } else { + state = SkipBlock; + } + } else { + state = Introducer; + } + break; + case SkipBlock: + ++count; + if (count == blockSize) + state = SkipBlockSize; + break; + case Done: + done = true; + break; + case Error: + device->seek(oldPos); + return; + } + } + readBuffer = device->read(readBufferSize); + } + device->seek(oldPos); + return; +} + +void QGIFFormat::fillRect(QImage *image, int col, int row, int w, int h, QRgb color) +{ + if (w>0) { + for (int j=0; j<h; j++) { + QRgb *line = (QRgb*)image->scanLine(j+row); + for (int i=0; i<w; i++) + *(line+col+i) = color; + } + } +} + +void QGIFFormat::nextY(unsigned char *bits, int bpl) +{ + if (out_of_bounds) + return; + int my; + switch (interlace) { + case 0: // Non-interlaced + // if (!out_of_bounds) { + // ### Changed: QRect(left, y, right - left + 1, 1); + // } + y++; + break; + case 1: { + int i; + my = qMin(7, bottom-y); + // Don't dup with transparency + if (trans_index < 0) { + for (i=1; i<=my; i++) { + memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb), + (right-left+1)*sizeof(QRgb)); + } + } + + // if (!out_of_bounds) { + // ### Changed: QRect(left, y, right - left + 1, my + 1); + // } +// if (!out_of_bounds) +// qDebug("consumer->changed(QRect(%d, %d, %d, %d))", left, y, right-left+1, my+1); + y+=8; + if (y>bottom) { + interlace++; y=top+4; + if (y > bottom) { // for really broken GIFs with bottom < 5 + interlace=2; + y = top + 2; + if (y > bottom) { // for really broken GIF with bottom < 3 + interlace = 0; + y = top + 1; + } + } + } + } break; + case 2: { + int i; + my = qMin(3, bottom-y); + // Don't dup with transparency + if (trans_index < 0) { + for (i=1; i<=my; i++) { + memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb), + (right-left+1)*sizeof(QRgb)); + } + } + + // if (!out_of_bounds) { + // ### Changed: QRect(left, y, right - left + 1, my + 1); + // } + y+=8; + if (y>bottom) { + interlace++; y=top+2; + // handle broken GIF with bottom < 3 + if (y > bottom) { + interlace = 3; + y = top + 1; + } + } + } break; + case 3: { + int i; + my = qMin(1, bottom-y); + // Don't dup with transparency + if (trans_index < 0) { + for (i=1; i<=my; i++) { + memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb), + (right-left+1)*sizeof(QRgb)); + } + } + // if (!out_of_bounds) { + // ### Changed: QRect(left, y, right - left + 1, my + 1); + // } + y+=4; + if (y>bottom) { interlace++; y=top+1; } + } break; + case 4: + // if (!out_of_bounds) { + // ### Changed: QRect(left, y, right - left + 1, 1); + // } + y+=2; + } + + // Consume bogus extra lines + if (y >= sheight) out_of_bounds=true; //y=bottom; +} + +inline QRgb QGIFFormat::color(uchar index) const +{ + if (index > ncols) + return Q_TRANSPARENT; + + QRgb *map = lcmap ? localcmap : globalcmap; + QRgb col = map ? map[index] : 0; + return index == trans_index ? col & Q_TRANSPARENT : col; +} + +//------------------------------------------------------------------------- +//------------------------------------------------------------------------- +//------------------------------------------------------------------------- + +QGifHandler::QGifHandler() +{ + gifFormat = new QGIFFormat; + nextDelay = 100; + loopCnt = -1; + frameNumber = -1; + scanIsCached = false; +} + +QGifHandler::~QGifHandler() +{ + delete gifFormat; +} + +// Does partial decode if necessary, just to see if an image is coming + +bool QGifHandler::imageIsComing() const +{ + const int GifChunkSize = 4096; + + while (!gifFormat->partialNewFrame) { + if (buffer.isEmpty()) { + buffer += device()->read(GifChunkSize); + if (buffer.isEmpty()) + break; + } + + int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(), + &nextDelay, &loopCnt); + if (decoded == -1) + break; + buffer.remove(0, decoded); + } + return gifFormat->partialNewFrame; +} + +bool QGifHandler::canRead() const +{ + if (canRead(device()) || imageIsComing()) { + setFormat("gif"); + return true; + } + + return false; +} + +bool QGifHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("QGifHandler::canRead() called with no device"); + return false; + } + + char head[6]; + if (device->peek(head, sizeof(head)) == sizeof(head)) + return qstrncmp(head, "GIF87a", 6) == 0 + || qstrncmp(head, "GIF89a", 6) == 0; + return false; +} + +bool QGifHandler::read(QImage *image) +{ + const int GifChunkSize = 4096; + + while (!gifFormat->newFrame) { + if (buffer.isEmpty()) { + buffer += device()->read(GifChunkSize); + if (buffer.isEmpty()) + break; + } + + int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(), + &nextDelay, &loopCnt); + if (decoded == -1) + break; + buffer.remove(0, decoded); + } + if (gifFormat->newFrame || (gifFormat->partialNewFrame && device()->atEnd())) { + *image = lastImage; + ++frameNumber; + gifFormat->newFrame = false; + gifFormat->partialNewFrame = false; + return true; + } + + return false; +} + +bool QGifHandler::write(const QImage &image) +{ + Q_UNUSED(image); + return false; +} + +bool QGifHandler::supportsOption(ImageOption option) const +{ + if (!device() || device()->isSequential()) + return option == Animation; + else + return option == Size + || option == Animation; +} + +QVariant QGifHandler::option(ImageOption option) const +{ + if (option == Size) { + if (!scanIsCached) { + QGIFFormat::scan(device(), &imageSizes, &loopCnt); + scanIsCached = true; + } + // before the first frame is read, or we have an empty data stream + if (frameNumber == -1) + return (imageSizes.count() > 0) ? QVariant(imageSizes.at(0)) : QVariant(); + // after the last frame has been read, the next size is undefined + if (frameNumber >= imageSizes.count() - 1) + return QVariant(); + // and the last case: the size of the next frame + return imageSizes.at(frameNumber + 1); + } else if (option == Animation) { + return true; + } + return QVariant(); +} + +void QGifHandler::setOption(ImageOption option, const QVariant &value) +{ + Q_UNUSED(option); + Q_UNUSED(value); +} + +int QGifHandler::nextImageDelay() const +{ + return nextDelay; +} + +int QGifHandler::imageCount() const +{ + if (!scanIsCached) { + QGIFFormat::scan(device(), &imageSizes, &loopCnt); + scanIsCached = true; + } + return imageSizes.count(); +} + +int QGifHandler::loopCount() const +{ + if (!scanIsCached) { + QGIFFormat::scan(device(), &imageSizes, &loopCnt); + scanIsCached = true; + } + + if (loopCnt == 0) + return -1; + else if (loopCnt == -1) + return 0; + else + return loopCnt; +} + +int QGifHandler::currentImageNumber() const +{ + return frameNumber; +} + +QByteArray QGifHandler::name() const +{ + return "gif"; +} + +QT_END_NAMESPACE diff --git a/src/plugins/imageformats/gif/qgifhandler_p.h b/src/plugins/imageformats/gif/qgifhandler_p.h new file mode 100644 index 0000000000..bc3debe83c --- /dev/null +++ b/src/plugins/imageformats/gif/qgifhandler_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** WARNING: +** A separate license from Unisys may be required to use the gif +** reader. See http://www.unisys.com/about__unisys/lzw/ +** for information from Unisys +** +****************************************************************************/ + +#ifndef QGIFHANDLER_P_H +#define QGIFHANDLER_P_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. +// + +#include <QtGui/qimageiohandler.h> +#include <QtGui/qimage.h> +#include <QtCore/qbytearray.h> + +QT_BEGIN_NAMESPACE + +class QGIFFormat; +class QGifHandler : public QImageIOHandler +{ +public: + QGifHandler(); + ~QGifHandler(); + + bool canRead() const Q_DECL_OVERRIDE; + bool read(QImage *image) Q_DECL_OVERRIDE; + bool write(const QImage &image) Q_DECL_OVERRIDE; + + QByteArray name() const Q_DECL_OVERRIDE; + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const Q_DECL_OVERRIDE; + void setOption(ImageOption option, const QVariant &value) Q_DECL_OVERRIDE; + bool supportsOption(ImageOption option) const Q_DECL_OVERRIDE; + + int imageCount() const Q_DECL_OVERRIDE; + int loopCount() const Q_DECL_OVERRIDE; + int nextImageDelay() const Q_DECL_OVERRIDE; + int currentImageNumber() const Q_DECL_OVERRIDE; + +private: + bool imageIsComing() const; + QGIFFormat *gifFormat; + QString fileName; + mutable QByteArray buffer; + mutable QImage lastImage; + + mutable int nextDelay; + mutable int loopCnt; + int frameNumber; + mutable QVector<QSize> imageSizes; + mutable bool scanIsCached; +}; + +QT_END_NAMESPACE + +#endif // QGIFHANDLER_P_H diff --git a/src/plugins/imageformats/ico/ico.pro b/src/plugins/imageformats/ico/ico.pro index 60afdaed70..7ca1f18cb1 100644 --- a/src/plugins/imageformats/ico/ico.pro +++ b/src/plugins/imageformats/ico/ico.pro @@ -1,10 +1,7 @@ TARGET = qico -QTDIR_build:REQUIRES = "!contains(QT_CONFIG, no-ico)" - -HEADERS += qicohandler.h main.h -SOURCES += main.cpp \ - qicohandler.cpp +HEADERS += main.h qicohandler.h +SOURCES += main.cpp qicohandler.cpp OTHER_FILES += ico.json PLUGIN_TYPE = imageformats diff --git a/src/plugins/imageformats/imageformats.pro b/src/plugins/imageformats/imageformats.pro index 2b05f2feec..3de77c056d 100644 --- a/src/plugins/imageformats/imageformats.pro +++ b/src/plugins/imageformats/imageformats.pro @@ -1,5 +1,5 @@ TEMPLATE = subdirs -!contains(QT_CONFIG, no-jpeg):!contains(QT_CONFIG, jpeg):SUBDIRS += jpeg -!contains(QT_CONFIG, no-gif):!contains(QT_CONFIG, gif):SUBDIRS += gif !contains(QT_CONFIG, no-ico):SUBDIRS += ico +contains(QT_CONFIG, jpeg): SUBDIRS += jpeg +contains(QT_CONFIG, gif): SUBDIRS += gif diff --git a/src/plugins/imageformats/jpeg/jpeg.pro b/src/plugins/imageformats/jpeg/jpeg.pro index 526556179c..9181abb465 100644 --- a/src/plugins/imageformats/jpeg/jpeg.pro +++ b/src/plugins/imageformats/jpeg/jpeg.pro @@ -2,12 +2,18 @@ TARGET = qjpeg QT += core-private -QTDIR_build:REQUIRES = "!contains(QT_CONFIG, no-jpeg)" +SOURCES += main.cpp qjpeghandler.cpp +HEADERS += main.h qjpeghandler_p.h + +contains(QT_CONFIG, system-jpeg) { + msvc: \ + LIBS += libjpeg.lib + else: \ + LIBS += -ljpeg +} else { + include($$PWD/../../../3rdparty/libjpeg.pri) +} -include(../../../gui/image/qjpeghandler.pri) -INCLUDEPATH += ../../../gui/image -SOURCES += main.cpp -HEADERS += main.h OTHER_FILES += jpeg.json PLUGIN_TYPE = imageformats diff --git a/src/plugins/imageformats/jpeg/qjpeghandler.cpp b/src/plugins/imageformats/jpeg/qjpeghandler.cpp new file mode 100644 index 0000000000..52e8b39f11 --- /dev/null +++ b/src/plugins/imageformats/jpeg/qjpeghandler.cpp @@ -0,0 +1,1144 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qjpeghandler_p.h" + +#include <qimage.h> +#include <qvariant.h> +#include <qvector.h> +#include <qbuffer.h> +#include <qmath.h> +#include <private/qsimd_p.h> + +#include <stdio.h> // jpeglib needs this to be pre-included +#include <setjmp.h> + +#ifdef FAR +#undef FAR +#endif + +// including jpeglib.h seems to be a little messy +extern "C" { +// mingw includes rpcndr.h but does not define boolean +#if defined(Q_OS_WIN) && defined(Q_CC_GNU) +# if defined(__RPCNDR_H__) && !defined(boolean) + typedef unsigned char boolean; +# define HAVE_BOOLEAN +# endif +#endif + +#define XMD_H // shut JPEGlib up +#if defined(Q_OS_UNIXWARE) +# define HAVE_BOOLEAN // libjpeg under Unixware seems to need this +#endif +#include <jpeglib.h> +#ifdef const +# undef const // remove crazy C hackery in jconfig.h +#endif +} + +QT_BEGIN_NAMESPACE +QT_WARNING_DISABLE_GCC("-Wclobbered") + +Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32(quint32 *dst, const uchar *src, int len); +typedef void (QT_FASTCALL *Rgb888ToRgb32Converter)(quint32 *dst, const uchar *src, int len); + +struct my_error_mgr : public jpeg_error_mgr { + jmp_buf setjmp_buffer; +}; + +extern "C" { + +static void my_error_exit (j_common_ptr cinfo) +{ + my_error_mgr* myerr = (my_error_mgr*) cinfo->err; + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + qWarning("%s", buffer); + longjmp(myerr->setjmp_buffer, 1); +} + +static void my_output_message(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + qWarning("%s", buffer); +} + +} + + +static const int max_buf = 4096; + +struct my_jpeg_source_mgr : public jpeg_source_mgr { + // Nothing dynamic - cannot rely on destruction over longjump + QIODevice *device; + JOCTET buffer[max_buf]; + const QBuffer *memDevice; + +public: + my_jpeg_source_mgr(QIODevice *device); +}; + +extern "C" { + +static void qt_init_source(j_decompress_ptr) +{ +} + +static boolean qt_fill_input_buffer(j_decompress_ptr cinfo) +{ + my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src; + qint64 num_read = 0; + if (src->memDevice) { + src->next_input_byte = (const JOCTET *)(src->memDevice->data().constData() + src->memDevice->pos()); + num_read = src->memDevice->data().size() - src->memDevice->pos(); + src->device->seek(src->memDevice->data().size()); + } else { + src->next_input_byte = src->buffer; + num_read = src->device->read((char*)src->buffer, max_buf); + } + if (num_read <= 0) { + // Insert a fake EOI marker - as per jpeglib recommendation + src->next_input_byte = src->buffer; + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + src->bytes_in_buffer = 2; + } else { + src->bytes_in_buffer = num_read; + } + return TRUE; +} + +static void qt_skip_input_data(j_decompress_ptr cinfo, long num_bytes) +{ + my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src; + + // `dumb' implementation from jpeglib + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->bytes_in_buffer) { // Should not happen in case of memDevice + num_bytes -= (long) src->bytes_in_buffer; + (void) qt_fill_input_buffer(cinfo); + /* note we assume that qt_fill_input_buffer will never return false, + * so suspension need not be handled. + */ + } + src->next_input_byte += (size_t) num_bytes; + src->bytes_in_buffer -= (size_t) num_bytes; + } +} + +static void qt_term_source(j_decompress_ptr cinfo) +{ + my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src; + if (!src->device->isSequential()) + src->device->seek(src->device->pos() - src->bytes_in_buffer); +} + +} + +inline my_jpeg_source_mgr::my_jpeg_source_mgr(QIODevice *device) +{ + jpeg_source_mgr::init_source = qt_init_source; + jpeg_source_mgr::fill_input_buffer = qt_fill_input_buffer; + jpeg_source_mgr::skip_input_data = qt_skip_input_data; + jpeg_source_mgr::resync_to_restart = jpeg_resync_to_restart; + jpeg_source_mgr::term_source = qt_term_source; + this->device = device; + memDevice = qobject_cast<QBuffer *>(device); + bytes_in_buffer = 0; + next_input_byte = buffer; +} + + +inline static bool read_jpeg_size(int &w, int &h, j_decompress_ptr cinfo) +{ + (void) jpeg_calc_output_dimensions(cinfo); + + w = cinfo->output_width; + h = cinfo->output_height; + return true; +} + +#define HIGH_QUALITY_THRESHOLD 50 + +inline static bool read_jpeg_format(QImage::Format &format, j_decompress_ptr cinfo) +{ + + bool result = true; + switch (cinfo->output_components) { + case 1: + format = QImage::Format_Grayscale8; + break; + case 3: + case 4: + format = QImage::Format_RGB32; + break; + default: + result = false; + break; + } + cinfo->output_scanline = cinfo->output_height; + return result; +} + +static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info, + const QSize& size) +{ + QImage::Format format; + switch (info->output_components) { + case 1: + format = QImage::Format_Grayscale8; + break; + case 3: + case 4: + format = QImage::Format_RGB32; + break; + default: + return false; // unsupported format + } + + if (dest->size() != size || dest->format() != format) + *dest = QImage(size, format); + + return !dest->isNull(); +} + +static bool read_jpeg_image(QImage *outImage, + QSize scaledSize, QRect scaledClipRect, + QRect clipRect, volatile int inQuality, + Rgb888ToRgb32Converter converter, + j_decompress_ptr info, struct my_error_mgr* err ) +{ + if (!setjmp(err->setjmp_buffer)) { + // -1 means default quality. + int quality = inQuality; + if (quality < 0) + quality = 75; + + // If possible, merge the scaledClipRect into either scaledSize + // or clipRect to avoid doing a separate scaled clipping pass. + // Best results are achieved by clipping before scaling, not after. + if (!scaledClipRect.isEmpty()) { + if (scaledSize.isEmpty() && clipRect.isEmpty()) { + // No clipping or scaling before final clip. + clipRect = scaledClipRect; + scaledClipRect = QRect(); + } else if (scaledSize.isEmpty()) { + // Clipping, but no scaling: combine the clip regions. + scaledClipRect.translate(clipRect.topLeft()); + clipRect = scaledClipRect.intersected(clipRect); + scaledClipRect = QRect(); + } else if (clipRect.isEmpty()) { + // No clipping, but scaling: if we can map back to an + // integer pixel boundary, then clip before scaling. + if ((info->image_width % scaledSize.width()) == 0 && + (info->image_height % scaledSize.height()) == 0) { + int x = scaledClipRect.x() * info->image_width / + scaledSize.width(); + int y = scaledClipRect.y() * info->image_height / + scaledSize.height(); + int width = (scaledClipRect.right() + 1) * + info->image_width / scaledSize.width() - x; + int height = (scaledClipRect.bottom() + 1) * + info->image_height / scaledSize.height() - y; + clipRect = QRect(x, y, width, height); + scaledSize = scaledClipRect.size(); + scaledClipRect = QRect(); + } + } else { + // Clipping and scaling: too difficult to figure out, + // and not a likely use case, so do it the long way. + } + } + + // Determine the scale factor to pass to libjpeg for quick downscaling. + if (!scaledSize.isEmpty() && info->image_width && info->image_height) { + if (clipRect.isEmpty()) { + double f = qMin(double(info->image_width) / scaledSize.width(), + double(info->image_height) / scaledSize.height()); + + // libjpeg supports M/8 scaling with M=[1,16]. All downscaling factors + // are a speed improvement, but upscaling during decode is slower. + info->scale_num = qBound(1, qCeil(8/f), 8); + info->scale_denom = 8; + } else { + info->scale_denom = qMin(clipRect.width() / scaledSize.width(), + clipRect.height() / scaledSize.height()); + + // Only scale by powers of two when clipping so we can + // keep the exact pixel boundaries + if (info->scale_denom < 2) + info->scale_denom = 1; + else if (info->scale_denom < 4) + info->scale_denom = 2; + else if (info->scale_denom < 8) + info->scale_denom = 4; + else + info->scale_denom = 8; + info->scale_num = 1; + + // Correct the scale factor so that we clip accurately. + // It is recommended that the clip rectangle be aligned + // on an 8-pixel boundary for best performance. + while (info->scale_denom > 1 && + ((clipRect.x() % info->scale_denom) != 0 || + (clipRect.y() % info->scale_denom) != 0 || + (clipRect.width() % info->scale_denom) != 0 || + (clipRect.height() % info->scale_denom) != 0)) { + info->scale_denom /= 2; + } + } + } + + // If high quality not required, use fast decompression + if( quality < HIGH_QUALITY_THRESHOLD ) { + info->dct_method = JDCT_IFAST; + info->do_fancy_upsampling = FALSE; + } + + (void) jpeg_calc_output_dimensions(info); + + // Determine the clip region to extract. + QRect imageRect(0, 0, info->output_width, info->output_height); + QRect clip; + if (clipRect.isEmpty()) { + clip = imageRect; + } else if (info->scale_denom == info->scale_num) { + clip = clipRect.intersected(imageRect); + } else { + // The scale factor was corrected above to ensure that + // we don't miss pixels when we scale the clip rectangle. + clip = QRect(clipRect.x() / int(info->scale_denom), + clipRect.y() / int(info->scale_denom), + clipRect.width() / int(info->scale_denom), + clipRect.height() / int(info->scale_denom)); + clip = clip.intersected(imageRect); + } + + // Allocate memory for the clipped QImage. + if (!ensureValidImage(outImage, info, clip.size())) + longjmp(err->setjmp_buffer, 1); + + // Avoid memcpy() overhead if grayscale with no clipping. + bool quickGray = (info->output_components == 1 && + clip == imageRect); + if (!quickGray) { + // Ask the jpeg library to allocate a temporary row. + // The library will automatically delete it for us later. + // The libjpeg docs say we should do this before calling + // jpeg_start_decompress(). We can't use "new" here + // because we are inside the setjmp() block and an error + // in the jpeg input stream would cause a memory leak. + JSAMPARRAY rows = (info->mem->alloc_sarray) + ((j_common_ptr)info, JPOOL_IMAGE, + info->output_width * info->output_components, 1); + + (void) jpeg_start_decompress(info); + + while (info->output_scanline < info->output_height) { + int y = int(info->output_scanline) - clip.y(); + if (y >= clip.height()) + break; // We've read the entire clip region, so abort. + + (void) jpeg_read_scanlines(info, rows, 1); + + if (y < 0) + continue; // Haven't reached the starting line yet. + + if (info->output_components == 3) { + uchar *in = rows[0] + clip.x() * 3; + QRgb *out = (QRgb*)outImage->scanLine(y); + converter(out, in, clip.width()); + } else if (info->out_color_space == JCS_CMYK) { + // Convert CMYK->RGB. + uchar *in = rows[0] + clip.x() * 4; + QRgb *out = (QRgb*)outImage->scanLine(y); + for (int i = 0; i < clip.width(); ++i) { + int k = in[3]; + *out++ = qRgb(k * in[0] / 255, k * in[1] / 255, + k * in[2] / 255); + in += 4; + } + } else if (info->output_components == 1) { + // Grayscale. + memcpy(outImage->scanLine(y), + rows[0] + clip.x(), clip.width()); + } + } + } else { + // Load unclipped grayscale data directly into the QImage. + (void) jpeg_start_decompress(info); + while (info->output_scanline < info->output_height) { + uchar *row = outImage->scanLine(info->output_scanline); + (void) jpeg_read_scanlines(info, &row, 1); + } + } + + if (info->output_scanline == info->output_height) + (void) jpeg_finish_decompress(info); + + if (info->density_unit == 1) { + outImage->setDotsPerMeterX(int(100. * info->X_density / 2.54)); + outImage->setDotsPerMeterY(int(100. * info->Y_density / 2.54)); + } else if (info->density_unit == 2) { + outImage->setDotsPerMeterX(int(100. * info->X_density)); + outImage->setDotsPerMeterY(int(100. * info->Y_density)); + } + + if (scaledSize.isValid() && scaledSize != clip.size()) { + *outImage = outImage->scaled(scaledSize, Qt::IgnoreAspectRatio, quality >= HIGH_QUALITY_THRESHOLD ? Qt::SmoothTransformation : Qt::FastTransformation); + } + + if (!scaledClipRect.isEmpty()) + *outImage = outImage->copy(scaledClipRect); + return !outImage->isNull(); + } + else + return false; +} + +struct my_jpeg_destination_mgr : public jpeg_destination_mgr { + // Nothing dynamic - cannot rely on destruction over longjump + QIODevice *device; + JOCTET buffer[max_buf]; + +public: + my_jpeg_destination_mgr(QIODevice *); +}; + + +extern "C" { + +static void qt_init_destination(j_compress_ptr) +{ +} + +static boolean qt_empty_output_buffer(j_compress_ptr cinfo) +{ + my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest; + + int written = dest->device->write((char*)dest->buffer, max_buf); + if (written == -1) + (*cinfo->err->error_exit)((j_common_ptr)cinfo); + + dest->next_output_byte = dest->buffer; + dest->free_in_buffer = max_buf; + + return TRUE; +} + +static void qt_term_destination(j_compress_ptr cinfo) +{ + my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest; + qint64 n = max_buf - dest->free_in_buffer; + + qint64 written = dest->device->write((char*)dest->buffer, n); + if (written == -1) + (*cinfo->err->error_exit)((j_common_ptr)cinfo); +} + +} + +inline my_jpeg_destination_mgr::my_jpeg_destination_mgr(QIODevice *device) +{ + jpeg_destination_mgr::init_destination = qt_init_destination; + jpeg_destination_mgr::empty_output_buffer = qt_empty_output_buffer; + jpeg_destination_mgr::term_destination = qt_term_destination; + this->device = device; + next_output_byte = buffer; + free_in_buffer = max_buf; +} + + +static inline void set_text(const QImage &image, j_compress_ptr cinfo, const QString &description) +{ + QMap<QString, QString> text; + foreach (const QString &key, image.textKeys()) { + if (!key.isEmpty()) + text.insert(key, image.text(key)); + } + foreach (const QString &pair, description.split(QLatin1String("\n\n"))) { + int index = pair.indexOf(QLatin1Char(':')); + if (index >= 0 && pair.indexOf(QLatin1Char(' ')) < index) { + QString s = pair.simplified(); + if (!s.isEmpty()) + text.insert(QLatin1String("Description"), s); + } else { + QString key = pair.left(index); + if (!key.simplified().isEmpty()) + text.insert(key, pair.mid(index + 2).simplified()); + } + } + if (text.isEmpty()) + return; + + for (QMap<QString, QString>::ConstIterator it = text.constBegin(); it != text.constEnd(); ++it) { + QByteArray comment = it.key().toLatin1(); + if (!comment.isEmpty()) + comment += ": "; + comment += it.value().toLatin1(); + if (comment.length() > 65530) + comment.truncate(65530); + jpeg_write_marker(cinfo, JPEG_COM, (const JOCTET *)comment.constData(), comment.size()); + } +} + +static bool write_jpeg_image(const QImage &image, QIODevice *device, volatile int sourceQuality, const QString &description, bool optimize, bool progressive) +{ + bool success = false; + const QVector<QRgb> cmap = image.colorTable(); + + if (image.format() == QImage::Format_Invalid || image.format() == QImage::Format_Alpha8) + return false; + + struct jpeg_compress_struct cinfo; + JSAMPROW row_pointer[1]; + row_pointer[0] = 0; + + struct my_jpeg_destination_mgr *iod_dest = new my_jpeg_destination_mgr(device); + struct my_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = my_error_exit; + jerr.output_message = my_output_message; + + if (!setjmp(jerr.setjmp_buffer)) { + // WARNING: + // this if loop is inside a setjmp/longjmp branch + // do not create C++ temporaries here because the destructor may never be called + // if you allocate memory, make sure that you can free it (row_pointer[0]) + jpeg_create_compress(&cinfo); + + cinfo.dest = iod_dest; + + cinfo.image_width = image.width(); + cinfo.image_height = image.height(); + + bool gray = false; + switch (image.format()) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + case QImage::Format_Indexed8: + gray = true; + for (int i = image.colorCount(); gray && i; i--) { + gray = gray & qIsGray(cmap[i-1]); + } + cinfo.input_components = gray ? 1 : 3; + cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB; + break; + case QImage::Format_Grayscale8: + gray = true; + cinfo.input_components = 1; + cinfo.in_color_space = JCS_GRAYSCALE; + break; + default: + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + } + + jpeg_set_defaults(&cinfo); + + qreal diffInch = qAbs(image.dotsPerMeterX()*2.54/100. - qRound(image.dotsPerMeterX()*2.54/100.)) + + qAbs(image.dotsPerMeterY()*2.54/100. - qRound(image.dotsPerMeterY()*2.54/100.)); + qreal diffCm = (qAbs(image.dotsPerMeterX()/100. - qRound(image.dotsPerMeterX()/100.)) + + qAbs(image.dotsPerMeterY()/100. - qRound(image.dotsPerMeterY()/100.)))*2.54; + if (diffInch < diffCm) { + cinfo.density_unit = 1; // dots/inch + cinfo.X_density = qRound(image.dotsPerMeterX()*2.54/100.); + cinfo.Y_density = qRound(image.dotsPerMeterY()*2.54/100.); + } else { + cinfo.density_unit = 2; // dots/cm + cinfo.X_density = (image.dotsPerMeterX()+50) / 100; + cinfo.Y_density = (image.dotsPerMeterY()+50) / 100; + } + + if (optimize) + cinfo.optimize_coding = true; + + if (progressive) + jpeg_simple_progression(&cinfo); + + int quality = sourceQuality >= 0 ? qMin(int(sourceQuality),100) : 75; + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + jpeg_start_compress(&cinfo, TRUE); + + set_text(image, &cinfo, description); + + row_pointer[0] = new uchar[cinfo.image_width*cinfo.input_components]; + int w = cinfo.image_width; + while (cinfo.next_scanline < cinfo.image_height) { + uchar *row = row_pointer[0]; + switch (image.format()) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + if (gray) { + const uchar* data = image.constScanLine(cinfo.next_scanline); + if (image.format() == QImage::Format_MonoLSB) { + for (int i=0; i<w; i++) { + bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7))); + row[i] = qRed(cmap[bit]); + } + } else { + for (int i=0; i<w; i++) { + bool bit = !!(*(data + (i >> 3)) & (1 << (7 -(i & 7)))); + row[i] = qRed(cmap[bit]); + } + } + } else { + const uchar* data = image.constScanLine(cinfo.next_scanline); + if (image.format() == QImage::Format_MonoLSB) { + for (int i=0; i<w; i++) { + bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7))); + *row++ = qRed(cmap[bit]); + *row++ = qGreen(cmap[bit]); + *row++ = qBlue(cmap[bit]); + } + } else { + for (int i=0; i<w; i++) { + bool bit = !!(*(data + (i >> 3)) & (1 << (7 -(i & 7)))); + *row++ = qRed(cmap[bit]); + *row++ = qGreen(cmap[bit]); + *row++ = qBlue(cmap[bit]); + } + } + } + break; + case QImage::Format_Indexed8: + if (gray) { + const uchar* pix = image.constScanLine(cinfo.next_scanline); + for (int i=0; i<w; i++) { + *row = qRed(cmap[*pix]); + ++row; ++pix; + } + } else { + const uchar* pix = image.constScanLine(cinfo.next_scanline); + for (int i=0; i<w; i++) { + *row++ = qRed(cmap[*pix]); + *row++ = qGreen(cmap[*pix]); + *row++ = qBlue(cmap[*pix]); + ++pix; + } + } + break; + case QImage::Format_Grayscale8: + memcpy(row, image.constScanLine(cinfo.next_scanline), w); + break; + case QImage::Format_RGB888: + memcpy(row, image.constScanLine(cinfo.next_scanline), w * 3); + break; + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + { + const QRgb* rgb = (const QRgb*)image.constScanLine(cinfo.next_scanline); + for (int i=0; i<w; i++) { + *row++ = qRed(*rgb); + *row++ = qGreen(*rgb); + *row++ = qBlue(*rgb); + ++rgb; + } + } + break; + default: + { + // (Testing shows that this way is actually faster than converting to RGB888 + memcpy) + QImage rowImg = image.copy(0, cinfo.next_scanline, w, 1).convertToFormat(QImage::Format_RGB32); + const QRgb* rgb = (const QRgb*)rowImg.constScanLine(0); + for (int i=0; i<w; i++) { + *row++ = qRed(*rgb); + *row++ = qGreen(*rgb); + *row++ = qBlue(*rgb); + ++rgb; + } + } + break; + } + jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + success = true; + } else { + jpeg_destroy_compress(&cinfo); + success = false; + } + + delete iod_dest; + delete [] row_pointer[0]; + return success; +} + +class QJpegHandlerPrivate +{ +public: + enum State { + Ready, + ReadHeader, + ReadingEnd, + Error + }; + + QJpegHandlerPrivate(QJpegHandler *qq) + : quality(75), transformation(QImageIOHandler::TransformationNone), iod_src(0), + rgb888ToRgb32ConverterPtr(qt_convert_rgb888_to_rgb32), state(Ready), optimize(false), progressive(false), q(qq) + {} + + ~QJpegHandlerPrivate() + { + if(iod_src) + { + jpeg_destroy_decompress(&info); + delete iod_src; + iod_src = 0; + } + } + + bool readJpegHeader(QIODevice*); + bool read(QImage *image); + + int quality; + QImageIOHandler::Transformations transformation; + QVariant size; + QImage::Format format; + QSize scaledSize; + QRect scaledClipRect; + QRect clipRect; + QString description; + QStringList readTexts; + + struct jpeg_decompress_struct info; + struct my_jpeg_source_mgr * iod_src; + struct my_error_mgr err; + + Rgb888ToRgb32Converter rgb888ToRgb32ConverterPtr; + + State state; + + bool optimize; + bool progressive; + + QJpegHandler *q; +}; + +static bool readExifHeader(QDataStream &stream) +{ + char prefix[6]; + if (stream.readRawData(prefix, sizeof(prefix)) != sizeof(prefix)) + return false; + static const char exifMagic[6] = {'E', 'x', 'i', 'f', 0, 0}; + return memcmp(prefix, exifMagic, 6) == 0; +} + +/* + * Returns -1 on error + * Returns 0 if no Exif orientation was found + * Returns 1 orientation is horizontal (normal) + * Returns 2 mirror horizontal + * Returns 3 rotate 180 + * Returns 4 mirror vertical + * Returns 5 mirror horizontal and rotate 270 CCW + * Returns 6 rotate 90 CW + * Returns 7 mirror horizontal and rotate 90 CW + * Returns 8 rotate 270 CW + */ +static int getExifOrientation(QByteArray &exifData) +{ + QDataStream stream(&exifData, QIODevice::ReadOnly); + + if (!readExifHeader(stream)) + return -1; + + quint16 val; + quint32 offset; + const qint64 headerStart = stream.device()->pos(); + + // read byte order marker + stream >> val; + if (val == 0x4949) // 'II' == Intel + stream.setByteOrder(QDataStream::LittleEndian); + else if (val == 0x4d4d) // 'MM' == Motorola + stream.setByteOrder(QDataStream::BigEndian); + else + return -1; // unknown byte order + + // read size + stream >> val; + if (val != 0x2a) + return -1; + + stream >> offset; + + // read IFD + while (!stream.atEnd()) { + quint16 numEntries; + + // skip offset bytes to get the next IFD + const qint64 bytesToSkip = offset - (stream.device()->pos() - headerStart); + + if (stream.skipRawData(bytesToSkip) != bytesToSkip) + return -1; + + stream >> numEntries; + + for (; numEntries > 0; --numEntries) { + quint16 tag; + quint16 type; + quint32 components; + quint16 value; + quint16 dummy; + + stream >> tag >> type >> components >> value >> dummy; + if (tag == 0x0112) { // Tag Exif.Image.Orientation + if (components != 1) + return -1; + if (type != 3) // we are expecting it to be an unsigned short + return -1; + if (value < 1 || value > 8) // check for valid range + return -1; + + // It is possible to include the orientation multiple times. + // Right now the first value is returned. + return value; + } + } + + // read offset to next IFD + stream >> offset; + if (offset == 0) // this is the last IFD + break; + } + + // No Exif orientation was found + return 0; +} + +static QImageIOHandler::Transformations exif2Qt(int exifOrientation) +{ + switch (exifOrientation) { + case 1: // normal + return QImageIOHandler::TransformationNone; + case 2: // mirror horizontal + return QImageIOHandler::TransformationMirror; + case 3: // rotate 180 + return QImageIOHandler::TransformationRotate180; + case 4: // mirror vertical + return QImageIOHandler::TransformationFlip; + case 5: // mirror horizontal and rotate 270 CW + return QImageIOHandler::TransformationFlipAndRotate90; + case 6: // rotate 90 CW + return QImageIOHandler::TransformationRotate90; + case 7: // mirror horizontal and rotate 90 CW + return QImageIOHandler::TransformationMirrorAndRotate90; + case 8: // rotate 270 CW + return QImageIOHandler::TransformationRotate270; + } + qWarning("Invalid EXIF orientation"); + return QImageIOHandler::TransformationNone; +} + +/*! + \internal +*/ +bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) +{ + if(state == Ready) + { + state = Error; + iod_src = new my_jpeg_source_mgr(device); + + info.err = jpeg_std_error(&err); + err.error_exit = my_error_exit; + err.output_message = my_output_message; + + jpeg_create_decompress(&info); + info.src = iod_src; + + if (!setjmp(err.setjmp_buffer)) { + jpeg_save_markers(&info, JPEG_COM, 0xFFFF); + jpeg_save_markers(&info, JPEG_APP0 + 1, 0xFFFF); // Exif uses APP1 marker + + (void) jpeg_read_header(&info, TRUE); + + int width = 0; + int height = 0; + read_jpeg_size(width, height, &info); + size = QSize(width, height); + + format = QImage::Format_Invalid; + read_jpeg_format(format, &info); + + QByteArray exifData; + + for (jpeg_saved_marker_ptr marker = info.marker_list; marker != NULL; marker = marker->next) { + if (marker->marker == JPEG_COM) { + QString key, value; + QString s = QString::fromLatin1((const char *)marker->data, marker->data_length); + int index = s.indexOf(QLatin1String(": ")); + if (index == -1 || s.indexOf(QLatin1Char(' ')) < index) { + key = QLatin1String("Description"); + value = s; + } else { + key = s.left(index); + value = s.mid(index + 2); + } + if (!description.isEmpty()) + description += QLatin1String("\n\n"); + description += key + QLatin1String(": ") + value.simplified(); + readTexts.append(key); + readTexts.append(value); + } else if (marker->marker == JPEG_APP0 + 1) { + exifData.append((const char*)marker->data, marker->data_length); + } + } + + if (!exifData.isEmpty()) { + // Exif data present + int exifOrientation = getExifOrientation(exifData); + if (exifOrientation > 0) + transformation = exif2Qt(exifOrientation); + } + + state = ReadHeader; + return true; + } + else + { + return false; + } + } + else if(state == Error) + return false; + return true; +} + +bool QJpegHandlerPrivate::read(QImage *image) +{ + if(state == Ready) + readJpegHeader(q->device()); + + if(state == ReadHeader) + { + bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, rgb888ToRgb32ConverterPtr, &info, &err); + if (success) { + for (int i = 0; i < readTexts.size()-1; i+=2) + image->setText(readTexts.at(i), readTexts.at(i+1)); + + state = ReadingEnd; + return true; + } + + state = Error; + } + + return false; + +} + +Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, const uchar *src, int len); +Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_ssse3(quint32 *dst, const uchar *src, int len); +extern "C" void qt_convert_rgb888_to_rgb32_mips_dspr2_asm(quint32 *dst, const uchar *src, int len); + +QJpegHandler::QJpegHandler() + : d(new QJpegHandlerPrivate(this)) +{ +#if defined(__ARM_NEON__) + // from qimage_neon.cpp + if (qCpuHasFeature(NEON)) + d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_neon; +#endif + +#if defined(QT_COMPILER_SUPPORTS_SSSE3) + // from qimage_ssse3.cpps + if (qCpuHasFeature(SSSE3)) { + d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_ssse3; + } +#endif // QT_COMPILER_SUPPORTS_SSSE3 +#if defined(QT_COMPILER_SUPPORTS_MIPS_DSPR2) + if (qCpuHasFeature(DSPR2)) { + d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_mips_dspr2_asm; + } +#endif // QT_COMPILER_SUPPORTS_DSPR2 +} + +QJpegHandler::~QJpegHandler() +{ + delete d; +} + +bool QJpegHandler::canRead() const +{ + if(d->state == QJpegHandlerPrivate::Ready && !canRead(device())) + return false; + + if (d->state != QJpegHandlerPrivate::Error && d->state != QJpegHandlerPrivate::ReadingEnd) { + setFormat("jpeg"); + return true; + } + + return false; +} + +bool QJpegHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("QJpegHandler::canRead() called with no device"); + return false; + } + + char buffer[2]; + if (device->peek(buffer, 2) != 2) + return false; + return uchar(buffer[0]) == 0xff && uchar(buffer[1]) == 0xd8; +} + +bool QJpegHandler::read(QImage *image) +{ + if (!canRead()) + return false; + return d->read(image); +} + +extern void qt_imageTransform(QImage &src, QImageIOHandler::Transformations orient); + +bool QJpegHandler::write(const QImage &image) +{ + if (d->transformation != QImageIOHandler::TransformationNone) { + // We don't support writing EXIF headers so apply the transform to the data. + QImage img = image; + qt_imageTransform(img, d->transformation); + return write_jpeg_image(img, device(), d->quality, d->description, d->optimize, d->progressive); + } + return write_jpeg_image(image, device(), d->quality, d->description, d->optimize, d->progressive); +} + +bool QJpegHandler::supportsOption(ImageOption option) const +{ + return option == Quality + || option == ScaledSize + || option == ScaledClipRect + || option == ClipRect + || option == Description + || option == Size + || option == ImageFormat + || option == OptimizedWrite + || option == ProgressiveScanWrite + || option == ImageTransformation; +} + +QVariant QJpegHandler::option(ImageOption option) const +{ + switch(option) { + case Quality: + return d->quality; + case ScaledSize: + return d->scaledSize; + case ScaledClipRect: + return d->scaledClipRect; + case ClipRect: + return d->clipRect; + case Description: + d->readJpegHeader(device()); + return d->description; + case Size: + d->readJpegHeader(device()); + return d->size; + case ImageFormat: + d->readJpegHeader(device()); + return d->format; + case OptimizedWrite: + return d->optimize; + case ProgressiveScanWrite: + return d->progressive; + case ImageTransformation: + d->readJpegHeader(device()); + return int(d->transformation); + default: + break; + } + + return QVariant(); +} + +void QJpegHandler::setOption(ImageOption option, const QVariant &value) +{ + switch(option) { + case Quality: + d->quality = value.toInt(); + break; + case ScaledSize: + d->scaledSize = value.toSize(); + break; + case ScaledClipRect: + d->scaledClipRect = value.toRect(); + break; + case ClipRect: + d->clipRect = value.toRect(); + break; + case Description: + d->description = value.toString(); + break; + case OptimizedWrite: + d->optimize = value.toBool(); + break; + case ProgressiveScanWrite: + d->progressive = value.toBool(); + break; + case ImageTransformation: { + int transformation = value.toInt(); + if (transformation > 0 && transformation < 8) + d->transformation = QImageIOHandler::Transformations(transformation); + } + default: + break; + } +} + +QByteArray QJpegHandler::name() const +{ + return "jpeg"; +} + +QT_END_NAMESPACE diff --git a/src/plugins/imageformats/jpeg/qjpeghandler_p.h b/src/plugins/imageformats/jpeg/qjpeghandler_p.h new file mode 100644 index 0000000000..534ce12115 --- /dev/null +++ b/src/plugins/imageformats/jpeg/qjpeghandler_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QJPEGHANDLER_P_H +#define QJPEGHANDLER_P_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. +// + +#include <QtGui/qimageiohandler.h> +#include <QtCore/QSize> +#include <QtCore/QRect> + +QT_BEGIN_NAMESPACE + +class QJpegHandlerPrivate; +class QJpegHandler : public QImageIOHandler +{ +public: + QJpegHandler(); + ~QJpegHandler(); + + bool canRead() const Q_DECL_OVERRIDE; + bool read(QImage *image) Q_DECL_OVERRIDE; + bool write(const QImage &image) Q_DECL_OVERRIDE; + + QByteArray name() const Q_DECL_OVERRIDE; + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const Q_DECL_OVERRIDE; + void setOption(ImageOption option, const QVariant &value) Q_DECL_OVERRIDE; + bool supportsOption(ImageOption option) const Q_DECL_OVERRIDE; + +private: + QJpegHandlerPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QJPEGHANDLER_P_H diff --git a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp index 200d5789a8..994fe8386b 100644 --- a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp +++ b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp @@ -278,7 +278,7 @@ void QIBusPlatformInputContext::updatePreeditText(const QDBusVariant &text, uint QList<QInputMethodEvent::Attribute> attributes = t.attributes.imAttributes(); if (!t.text.isEmpty()) - attributes += QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursorPos, visible ? 1 : 0, QVariant()); + attributes += QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursorPos, visible ? 1 : 0); QInputMethodEvent event(t.text, attributes); QCoreApplication::sendEvent(input, &event); diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp index dc420775cf..125a03469f 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/qandroidinputcontext.cpp @@ -645,7 +645,7 @@ jboolean QAndroidInputContext::commitText(const QString &text, jint newCursorPos : localPos - text.length() + newCursorPosition; //move the cursor attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, - newLocalPos, 0, QVariant())); + newLocalPos, 0)); } } m_blockUpdateSelection = updateSelectionWasBlocked; @@ -691,7 +691,7 @@ jboolean QAndroidInputContext::finishComposingText() // Moving Qt's cursor to where the preedit cursor used to be QList<QInputMethodEvent::Attribute> attributes; - attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0, QVariant())); + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0)); QInputMethodEvent event(QString(), attributes); event.setCommitString(m_composingText); @@ -848,8 +848,7 @@ jboolean QAndroidInputContext::setComposingText(const QString &text, jint newCur QList<QInputMethodEvent::Attribute> attributes; attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, newCursorPosition, - 1, - QVariant())); + 1)); // Show compose text underlined QTextCharFormat underlined; underlined.setFontUnderline(true); @@ -921,7 +920,7 @@ jboolean QAndroidInputContext::setComposingRegion(jint start, jint end) QVariant(underlined))); // Keep the cursor position unchanged (don't move to end of preedit) - attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, currentCursor - start, 1, QVariant())); + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, currentCursor - start, 1)); QInputMethodEvent event(m_composingText, attributes); event.setCommitString(QString(), relativeStart, length); @@ -955,7 +954,7 @@ jboolean QAndroidInputContext::setSelection(jint start, jint end) // preedit cursor int localOldPos = query->value(Qt::ImCursorPosition).toInt(); int pos = localCursorPos - localOldPos; - attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, pos, 1, QVariant())); + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, pos, 1)); //but we have to tell Qt about the compose text all over again @@ -970,8 +969,7 @@ jboolean QAndroidInputContext::setSelection(jint start, jint end) // actually changing the selection attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, - end - start, - QVariant())); + end - start)); } QInputMethodEvent event(m_composingText, attributes); sendInputMethodEventThreadSafe(&event); diff --git a/src/plugins/platforms/android/qandroidplatformintegration.cpp b/src/plugins/platforms/android/qandroidplatformintegration.cpp index 80d7e31aa3..e10bd95e12 100644 --- a/src/plugins/platforms/android/qandroidplatformintegration.cpp +++ b/src/plugins/platforms/android/qandroidplatformintegration.cpp @@ -80,6 +80,8 @@ Qt::ScreenOrientation QAndroidPlatformIntegration::m_nativeOrientation = Qt::Pri Qt::ApplicationState QAndroidPlatformIntegration::m_defaultApplicationState = Qt::ApplicationActive; +bool QAndroidPlatformIntegration::m_showPasswordEnabled = false; + void *QAndroidPlatformNativeInterface::nativeResourceForIntegration(const QByteArray &resource) { if (resource=="JavaVM") @@ -191,6 +193,19 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList ¶ } QWindowSystemInterface::registerTouchDevice(m_touchDevice); } + + auto contentResolver = javaActivity.callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;"); + Q_ASSERT(contentResolver.isValid()); + QJNIObjectPrivate txtShowPassValue = QJNIObjectPrivate::callStaticObjectMethod("android/provider/Settings$System", + "getString", + "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;", + contentResolver.object(), + QJNIObjectPrivate::getStaticObjectField("android/provider/Settings$System", "TEXT_SHOW_PASSWORD", "Ljava/lang/String;").object()); + if (txtShowPassValue.isValid()) { + bool ok = false; + const int txtShowPass = txtShowPassValue.toString().toInt(&ok); + m_showPasswordEnabled = ok ? (txtShowPass == 1) : false; + } } QGuiApplicationPrivate::instance()->setApplicationState(m_defaultApplicationState); @@ -313,6 +328,9 @@ QPlatformServices *QAndroidPlatformIntegration::services() const QVariant QAndroidPlatformIntegration::styleHint(StyleHint hint) const { switch (hint) { + case PasswordMaskDelay: + // this number is from a hard-coded value in Android code (cf. PasswordTransformationMethod) + return m_showPasswordEnabled ? 1500 : 0; case ShowIsMaximized: return true; default: diff --git a/src/plugins/platforms/android/qandroidplatformintegration.h b/src/plugins/platforms/android/qandroidplatformintegration.h index 1f06c23d0b..bda0bee9ad 100644 --- a/src/plugins/platforms/android/qandroidplatformintegration.h +++ b/src/plugins/platforms/android/qandroidplatformintegration.h @@ -143,6 +143,8 @@ private: static Qt::ApplicationState m_defaultApplicationState; + static bool m_showPasswordEnabled; + QPlatformFontDatabase *m_androidFDB; QAndroidPlatformNativeInterface *m_androidPlatformNativeInterface; QAndroidPlatformServices *m_androidPlatformServices; diff --git a/src/plugins/platforms/android/qandroidplatformscreen.cpp b/src/plugins/platforms/android/qandroidplatformscreen.cpp index a73550606b..155d6bfb8d 100644 --- a/src/plugins/platforms/android/qandroidplatformscreen.cpp +++ b/src/plugins/platforms/android/qandroidplatformscreen.cpp @@ -379,9 +379,8 @@ void QAndroidPlatformScreen::doRedraw() } } - foreach (const QRect &rect, visibleRegion.rects()) { + for (const QRect &rect : visibleRegion) compositePainter.fillRect(rect, QColor(Qt::transparent)); - } ret = ANativeWindow_unlockAndPost(m_nativeSurface); if (ret >= 0) diff --git a/src/plugins/platforms/cocoa/qcocoaapplication.mm b/src/plugins/platforms/cocoa/qcocoaapplication.mm index c496134606..170f17504f 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplication.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplication.mm @@ -71,11 +71,11 @@ ** ****************************************************************************/ -#include <qcocoaapplication.h> +#include "qcocoaapplication.h" -#include <qcocoaintrospection.h> -#include <qcocoaapplicationdelegate.h> -#include <qcocoahelpers.h> +#include "qcocoaintrospection.h" +#include "qcocoaapplicationdelegate.h" +#include "qcocoahelpers.h" #include <qguiapplication.h> #include <qdebug.h> diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index fa05626d18..52a3e756b9 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -57,11 +57,8 @@ public: QPaintDevice *paintDevice() Q_DECL_OVERRIDE; void flush(QWindow *widget, const QRegion ®ion, const QPoint &offset) Q_DECL_OVERRIDE; -#ifndef QT_NO_OPENGL QImage toImage() const Q_DECL_OVERRIDE; -#else - QImage toImage() const; // No QPlatformBackingStore::toImage() for NO_OPENGL builds. -#endif + void resize (const QSize &size, const QRegion &) Q_DECL_OVERRIDE; bool scroll(const QRegion &area, int dx, int dy) Q_DECL_OVERRIDE; void beginPaint(const QRegion ®ion) Q_DECL_OVERRIDE; diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index b060d6a082..20233518b3 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -96,9 +96,8 @@ bool QCocoaBackingStore::scroll(const QRegion &area, int dx, int dy) extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset); const qreal devicePixelRatio = m_qImage.devicePixelRatio(); QPoint qpoint(dx * devicePixelRatio, dy * devicePixelRatio); - const QVector<QRect> qrects = area.rects(); - for (int i = 0; i < qrects.count(); ++i) { - const QRect &qrect = QRect(qrects.at(i).topLeft() * devicePixelRatio, qrects.at(i).size() * devicePixelRatio); + for (const QRect &rect : area) { + const QRect qrect(rect.topLeft() * devicePixelRatio, rect.size() * devicePixelRatio); qt_scrollRectInImage(m_qImage, qrect, qpoint); } return true; @@ -109,10 +108,9 @@ void QCocoaBackingStore::beginPaint(const QRegion ®ion) if (m_qImage.hasAlphaChannel()) { QPainter p(&m_qImage); p.setCompositionMode(QPainter::CompositionMode_Source); - const QVector<QRect> rects = region.rects(); const QColor blank = Qt::transparent; - for (QVector<QRect>::const_iterator it = rects.begin(), end = rects.end(); it != end; ++it) - p.fillRect(*it, blank); + for (const QRect &rect : region) + p.fillRect(rect, blank); } } diff --git a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm index 47f9539d9c..488c9b8928 100644 --- a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm @@ -132,7 +132,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); - (void)setDialogHelper:(QCocoaColorDialogHelper *)helper { mHelper = helper; - [mColorPanel setShowsAlpha:mHelper->options()->testOption(QColorDialogOptions::ShowAlphaChannel)]; + if (mHelper->options()->testOption(QColorDialogOptions::NoButtons)) { [self restoreOriginalContentView]; } else if (!mStolenContentView) { @@ -483,6 +483,14 @@ bool QCocoaColorDialogHelper::show(Qt::WindowFlags, Qt::WindowModality windowMod { if (windowModality == Qt::WindowModal) windowModality = Qt::ApplicationModal; + + // Workaround for Apple rdar://25792119: If you invoke + // -setShowsAlpha: multiple times before showing the color + // picker, its height grows irrevocably. Instead, only + // invoke it once, when we show the dialog. + [[NSColorPanel sharedColorPanel] setShowsAlpha: + options()->testOption(QColorDialogOptions::ShowAlphaChannel)]; + sharedColorPanel()->init(this); return sharedColorPanel()->show(windowModality, parent); } diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index 4eb35f5495..0375dd85f2 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -61,6 +61,8 @@ #include <qvarlengtharray.h> #include <stdlib.h> #include <qabstracteventdispatcher.h> +#include <qsysinfo.h> +#include <qglobal.h> #include <QDir> #include <qpa/qplatformnativeinterface.h> @@ -160,6 +162,11 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate); // here to make sure it gets the correct value. [mSavePanel setDelegate:self]; +#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_11) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_11) + mOpenPanel.accessoryViewDisclosed = YES; +#endif + if (mOptions->isLabelExplicitlySet(QFileDialogOptions::Accept)) [mSavePanel setPrompt:[self strip:options->labelText(QFileDialogOptions::Accept)]]; if (mOptions->isLabelExplicitlySet(QFileDialogOptions::FileName)) diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index 7480d99d19..c91c67fe79 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -202,15 +202,9 @@ NSImage *qt_mac_create_nsimage(const QIcon &icon) HIMutableShapeRef qt_mac_QRegionToHIMutableShape(const QRegion ®ion) { HIMutableShapeRef shape = HIShapeCreateMutable(); - QVector<QRect> rects = region.rects(); - if (!rects.isEmpty()) { - int n = rects.count(); - const QRect *qt_r = rects.constData(); - while (n--) { - CGRect cgRect = CGRectMake(qt_r->x(), qt_r->y(), qt_r->width(), qt_r->height()); - HIShapeUnionWithRect(shape, &cgRect); - ++qt_r; - } + for (const QRect &rect : region) { + CGRect cgRect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); + HIShapeUnionWithRect(shape, &cgRect); } return shape; } diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.h b/src/plugins/platforms/cocoa/qcocoakeymapper.h index 93ebc5b9dc..4ba615efeb 100644 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.h +++ b/src/plugins/platforms/cocoa/qcocoakeymapper.h @@ -40,7 +40,7 @@ #ifndef QCOCOAKEYMAPPER_H #define QCOCOAKEYMAPPER_H -#include <qcocoahelpers.h> +#include "qcocoahelpers.h" #include <AppKit/AppKit.h> #include <Carbon/Carbon.h> diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 3115db2b83..c9783df44b 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -431,7 +431,7 @@ static bool _q_dontOverrideCtrlLMB = false; // set the active window to zero here, the new key window's // NSWindowDidBecomeKeyNotification hander will change the active window NSWindow *keyWindow = [NSApp keyWindow]; - if (!keyWindow) { + if (!keyWindow || keyWindow == windowNotification.object) { // no new key window, go ahead and set the active window to zero if (!m_platformWindow->windowIsPopupType() && !m_isMenuView) QWindowSystemInterface::handleWindowActivated(0); @@ -524,7 +524,7 @@ QT_WARNING_POP m_backingStore = backingStore; m_backingStoreOffset = offset * m_backingStore->getBackingStoreDevicePixelRatio(); - foreach (QRect rect, region.rects()) + for (const QRect &rect : region) [self setNeedsDisplayInRect:NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height())]; } diff --git a/src/plugins/platforms/cocoa/qpaintengine_mac.mm b/src/plugins/platforms/cocoa/qpaintengine_mac.mm index 395c25c915..759c4d26a5 100644 --- a/src/plugins/platforms/cocoa/qpaintengine_mac.mm +++ b/src/plugins/platforms/cocoa/qpaintengine_mac.mm @@ -88,10 +88,7 @@ static void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransfor if (rgn.isEmpty()) { CGContextAddRect(hd, CGRectMake(0, 0, 0, 0)); } else { - QVector<QRect> rects = rgn.rects(); - const int count = rects.size(); - for (int i = 0; i < count; i++) { - const QRect &r = rects[i]; + for (const QRect &r : rgn) { CGRect mac_r = CGRectMake(r.x(), r.y(), r.width(), r.height()); CGContextAddRect(hd, mac_r); } diff --git a/src/plugins/platforms/direct2d/direct2d.pro b/src/plugins/platforms/direct2d/direct2d.pro index 005a4da6db..f4c3b5cc3b 100644 --- a/src/plugins/platforms/direct2d/direct2d.pro +++ b/src/plugins/platforms/direct2d/direct2d.pro @@ -4,7 +4,7 @@ QT *= core-private QT *= gui-private QT *= platformsupport-private -LIBS *= -ld2d1 -ld3d11 -ldwrite -lVersion -lgdi32 +LIBS += -ldwmapi -ld2d1 -ld3d11 -ldwrite -lVersion -lgdi32 include(../windows/windows.pri) diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.cpp b/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.cpp index 97eadb207b..38f2352934 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.cpp +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.cpp @@ -95,7 +95,7 @@ void QWindowsDirect2DBackingStore::beginPaint(const QRegion ®ion) painter.setCompositionMode(QPainter::CompositionMode_Source); - foreach (const QRect &r, region.rects()) + for (const QRect &r : region) painter.fillRect(r, clear); } @@ -127,10 +127,14 @@ void QWindowsDirect2DBackingStore::resize(const QSize &size, const QRegion ®i QPixmap *newPixmap = nativeWindow(window())->pixmap(); if (!old.isNull()) { - foreach (const QRect &rect, region.rects()) { + for (const QRect &rect : region) platformPixmap(newPixmap)->copy(old.handle(), rect); - } } } +QImage QWindowsDirect2DBackingStore::toImage() const +{ + return nativeWindow(window())->pixmap()->toImage(); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.h b/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.h index 9d754866cc..670c4e9840 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.h +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.h @@ -60,6 +60,8 @@ public: QPaintDevice *paintDevice() Q_DECL_OVERRIDE; void flush(QWindow *targetWindow, const QRegion ®ion, const QPoint &offset) Q_DECL_OVERRIDE; void resize(const QSize &size, const QRegion &staticContents) Q_DECL_OVERRIDE; + + QImage toImage() const override; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.cpp b/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.cpp index 4e677166b2..c750b02078 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.cpp +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.cpp @@ -122,7 +122,7 @@ void QWindowsDirect2DWindow::flush(QWindowsDirect2DBitmap *bitmap, const QRegion QRegion clipped = region; clipped &= QRect(QPoint(), size); - foreach (const QRect &rect, clipped.rects()) { + for (const QRect &rect : clipped) { QRectF rectF(rect); dc->DrawBitmap(bitmap->bitmap(), to_d2d_rect_f(rectF), diff --git a/src/plugins/platforms/directfb/qdirectfbbackingstore.cpp b/src/plugins/platforms/directfb/qdirectfbbackingstore.cpp index 0bcf93aa3d..8d5e1e50c4 100644 --- a/src/plugins/platforms/directfb/qdirectfbbackingstore.cpp +++ b/src/plugins/platforms/directfb/qdirectfbbackingstore.cpp @@ -70,9 +70,7 @@ void QDirectFbBackingStore::flush(QWindow *, const QRegion ®ion, const QPoint { m_pmdata->blittable()->unlock(); - QVector<QRect> rects = region.rects(); - for (int i = 0 ; i < rects.size(); i++) { - const QRect rect = rects.at(i); + for (const QRect &rect : region) { DFBRegion dfbReg(rect.x() + offset.x(),rect.y() + offset.y(),rect.right() + offset.x(),rect.bottom() + offset.y()); m_dfbSurface->Flip(m_dfbSurface.data(), &dfbReg, DFBSurfaceFlipFlags(DSFLIP_BLIT|DSFLIP_ONSYNC)); } @@ -108,13 +106,15 @@ bool QDirectFbBackingStore::scroll(const QRegion &area, int dx, int dy) if (area.rectCount() == 1) { scrollSurface(m_dfbSurface.data(), area.boundingRect(), dx, dy); } else { - const QVector<QRect> rects = area.rects(); - const int n = rects.size(); - for (int i=0; i<n; ++i) { - scrollSurface(m_dfbSurface.data(), rects.at(i), dx, dy); - } + for (const QRect &rect : area) + scrollSurface(m_dfbSurface.data(), rect, dx, dy); } return true; } +QImage QDirectFbBackingStore::toImage() const +{ + return m_pixmap.data()->toImage(); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/directfb/qdirectfbbackingstore.h b/src/plugins/platforms/directfb/qdirectfbbackingstore.h index 33ab1c111d..af1ce92e64 100644 --- a/src/plugins/platforms/directfb/qdirectfbbackingstore.h +++ b/src/plugins/platforms/directfb/qdirectfbbackingstore.h @@ -59,6 +59,8 @@ public: void resize (const QSize &size, const QRegion &staticContents); bool scroll(const QRegion &area, int dx, int dy); + QImage toImage() const override; + private: void lockSurfaceToImage(); diff --git a/src/plugins/platforms/ios/qioseventdispatcher.mm b/src/plugins/platforms/ios/qioseventdispatcher.mm index 8d82364cc0..f49f81912e 100644 --- a/src/plugins/platforms/ios/qioseventdispatcher.mm +++ b/src/plugins/platforms/ios/qioseventdispatcher.mm @@ -493,7 +493,7 @@ void QIOSEventDispatcher::handleRunLoopExit(CFRunLoopActivity activity) Q_UNUSED(activity); Q_ASSERT(activity == kCFRunLoopExit); - if (m_processEventLevel == 1 && !QThreadData::current()->eventLoops.top()->isRunning()) { + if (m_processEventLevel == 1 && !currentEventLoop()->isRunning()) { qEventDispatcherDebug() << "Root runloop level exited"; interruptEventLoopExec(); } diff --git a/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp b/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp index 321e124eee..f0c29130f8 100644 --- a/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp +++ b/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp @@ -412,9 +412,8 @@ QRegion QLinuxFbScreen::doRedraw() if (!mBlitter) mBlitter = new QPainter(&mFbScreenImage); - QVector<QRect> rects = touched.rects(); - for (int i = 0; i < rects.size(); i++) - mBlitter->drawImage(rects[i], *mScreenImage, rects[i]); + for (const QRect &rect : touched) + mBlitter->drawImage(rect, *mScreenImage, rect); return touched; } diff --git a/src/plugins/platforms/mirclient/qmirclientbackingstore.cpp b/src/plugins/platforms/mirclient/qmirclientbackingstore.cpp index 465ad355b9..f80d85842a 100644 --- a/src/plugins/platforms/mirclient/qmirclientbackingstore.cpp +++ b/src/plugins/platforms/mirclient/qmirclientbackingstore.cpp @@ -100,7 +100,7 @@ void QMirClientBackingStore::updateTexture() QRegion fixed; QRect imageRect = mImage.rect(); - Q_FOREACH (const QRect &rect, mDirty.rects()) { + for (const QRect &rect : mDirty) { // intersect with image rect to be sure QRect r = imageRect & rect; @@ -113,7 +113,7 @@ void QMirClientBackingStore::updateTexture() fixed |= r; } - Q_FOREACH (const QRect &rect, fixed.rects()) { + for (const QRect &rect : fixed) { // if the sub-rect is full-width we can pass the image data directly to // OpenGL instead of copying, since there is no gap between scanlines if (rect.width() == imageRect.width()) { diff --git a/src/plugins/platforms/offscreen/qoffscreencommon.cpp b/src/plugins/platforms/offscreen/qoffscreencommon.cpp index ed1a81c2b3..a63aacdbfe 100644 --- a/src/plugins/platforms/offscreen/qoffscreencommon.cpp +++ b/src/plugins/platforms/offscreen/qoffscreencommon.cpp @@ -179,9 +179,8 @@ bool QOffscreenBackingStore::scroll(const QRegion &area, int dx, int dy) if (m_image.isNull()) return false; - const QVector<QRect> rects = area.rects(); - for (int i = 0; i < rects.size(); ++i) - qt_scrollRectInImage(m_image, rects.at(i), QPoint(dx, dy)); + for (const QRect &rect : area) + qt_scrollRectInImage(m_image, rect, QPoint(dx, dy)); return true; } diff --git a/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp b/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp index a08ac2b839..90a09d3087 100644 --- a/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp +++ b/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp @@ -97,7 +97,7 @@ void QQnxButtonEventNotifier::start() m_readNotifier = new QSocketNotifier(m_fd, QSocketNotifier::Read); QObject::connect(m_readNotifier, SIGNAL(activated(int)), this, SLOT(updateButtonStates())); - qButtonDebug() << "successfully connected to Navigator. fd =" << m_fd; + qButtonDebug("successfully connected to Navigator. fd = %d", m_fd); } void QQnxButtonEventNotifier::updateButtonStates() @@ -121,7 +121,7 @@ void QQnxButtonEventNotifier::updateButtonStates() // Ensure data is null terminated buffer[bytes] = '\0'; - qButtonDebug() << "received PPS message:\n" << buffer; + qButtonDebug("received PPS message:\n%s", buffer); // Process received message QByteArray ppsData = QByteArray::fromRawData(buffer, bytes); diff --git a/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp b/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp index 79ff74b113..ce3a445d7c 100644 --- a/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp +++ b/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp @@ -721,7 +721,7 @@ void QQnxInputContext::update(Qt::InputMethodQueries queries) initEvent(&caretEvent.event, sInputSession, EVENT_CARET, CARET_POS_CHANGED, sizeof(caretEvent)); caretEvent.old_pos = lastCaret; caretEvent.new_pos = m_caretPosition; - qInputContextDebug() << "ictrl_dispatch_event caret changed" << lastCaret << m_caretPosition; + qInputContextDebug("ictrl_dispatch_event caret changed %d %d", lastCaret, m_caretPosition); p_ictrl_dispatch_event(&caretEvent.event); } } @@ -914,7 +914,7 @@ bool QQnxInputContext::handleKeyboardEvent(int flags, int sym, int mod, int scan navigation_event_t navEvent; initEvent(&navEvent.event, sInputSession, EVENT_NAVIGATION, key, sizeof(navEvent)); navEvent.magnitude = 1; - qInputContextDebug() << "ictrl_dispatch_even navigation" << key; + qInputContextDebug("ictrl_dispatch_even navigation %d", key); p_ictrl_dispatch_event(&navEvent.event); } } else { @@ -927,7 +927,7 @@ bool QQnxInputContext::handleKeyboardEvent(int flags, int sym, int mod, int scan keyEvent.sequence_id = sequenceId; p_ictrl_dispatch_event(&keyEvent.event); - qInputContextDebug() << "ictrl_dispatch_even key" << key; + qInputContextDebug("ictrl_dispatch_even key %d", key); } return true; @@ -943,7 +943,7 @@ void QQnxInputContext::updateCursorPosition() QCoreApplication::sendEvent(input, &query); m_caretPosition = query.value(Qt::ImCursorPosition).toInt(); - qInputContextDebug() << m_caretPosition; + qInputContextDebug("%d", m_caretPosition); } void QQnxInputContext::endComposition() @@ -1116,7 +1116,7 @@ int32_t QQnxInputContext::processEvent(event_t *event) int flags = KEY_SYM_VALID | KEY_CAP_VALID; if (event->event_id == IMF_KEY_DOWN) flags |= KEY_DOWN; - qInputContextDebug() << "EVENT_KEY" << flags << keySym; + qInputContextDebug("EVENT_KEY %d %d", flags, keySym); QQnxScreenEventHandler::injectKeyboardEvent(flags, keySym, modifiers, 0, keyCap); result = 0; break; @@ -1156,7 +1156,7 @@ int32_t QQnxInputContext::onCommitText(spannable_string_t *text, int32_t new_cur int32_t QQnxInputContext::onDeleteSurroundingText(int32_t left_length, int32_t right_length) { - qInputContextDebug() << "L:" << left_length << " R:" << right_length; + qInputContextDebug("L: %d R: %d", int(left_length), int(right_length)); QObject *input = qGuiApp->focusObject(); if (!input) diff --git a/src/plugins/platforms/qnx/qqnxintegration.cpp b/src/plugins/platforms/qnx/qqnxintegration.cpp index 3a0e97af6e..9d38742d6f 100644 --- a/src/plugins/platforms/qnx/qqnxintegration.cpp +++ b/src/plugins/platforms/qnx/qqnxintegration.cpp @@ -453,11 +453,11 @@ void QQnxIntegration::createDisplays() Q_SCREEN_CHECKERROR(result, "Failed to query display attachment"); if (!isAttached) { - qIntegrationDebug() << "Skipping non-attached display" << i; + qIntegrationDebug("Skipping non-attached display %d", i); continue; } - qIntegrationDebug() << "Creating screen for display" << i; + qIntegrationDebug("Creating screen for display %d", i); createDisplay(displays[i], /*isPrimary=*/false); } // of displays iteration } diff --git a/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.cpp b/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.cpp index 4955938a3a..0e16764b79 100644 --- a/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.cpp +++ b/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.cpp @@ -63,14 +63,14 @@ bool QQnxNavigatorEventHandler::handleOrientationCheck(int angle) { // reply to navigator that (any) orientation is acceptable // TODO: check if top window flags prohibit orientation change - qNavigatorEventHandlerDebug() << "angle=" << angle; + qNavigatorEventHandlerDebug("angle=%d", angle); return true; } void QQnxNavigatorEventHandler::handleOrientationChange(int angle) { // update screen geometry and reply to navigator that we're ready - qNavigatorEventHandlerDebug() << "angle=" << angle; + qNavigatorEventHandlerDebug("angle=%d", angle); emit rotationChanged(angle); } diff --git a/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp b/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp index 9ccda2d94a..1f630863b7 100644 --- a/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp +++ b/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp @@ -91,8 +91,7 @@ void QQnxNavigatorEventNotifier::start() errno = 0; m_fd = open(navigatorControlPath, O_RDWR); if (m_fd == -1) { - qNavigatorEventNotifierDebug() << "failed to open navigator pps:" - << strerror(errno); + qNavigatorEventNotifierDebug("failed to open navigator pps: %s", strerror(errno)); return; } diff --git a/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp b/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp index fd1bbc4a85..d5234ca92f 100644 --- a/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp +++ b/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp @@ -79,7 +79,7 @@ bool QQnxNavigatorPps::openPpsConnection() return false; } - qNavigatorDebug() << "successfully connected to Navigator. fd=" << m_fd; + qNavigatorDebug("successfully connected to Navigator. fd=%d", m_fd); return true; } diff --git a/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp b/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp index c9a89def41..a758bdf7f4 100644 --- a/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp +++ b/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp @@ -139,7 +139,7 @@ void QQnxRasterBackingStore::beginPaint(const QRegion ®ion) platformWindow()->adjustBufferSize(); if (window()->requestedFormat().alphaBufferSize() > 0) { - foreach (const QRect &r, region.rects()) { + for (const QRect &r : region) { // Clear transparent regions const int bg[] = { SCREEN_BLIT_COLOR, 0x00000000, diff --git a/src/plugins/platforms/qnx/qqnxrasterwindow.cpp b/src/plugins/platforms/qnx/qqnxrasterwindow.cpp index 0fe80d856d..b075690e3d 100644 --- a/src/plugins/platforms/qnx/qqnxrasterwindow.cpp +++ b/src/plugins/platforms/qnx/qqnxrasterwindow.cpp @@ -208,10 +208,9 @@ void QQnxRasterWindow::blitPreviousToCurrent(const QRegion ®ion, int dx, int QQnxBuffer &previousBuffer = m_buffers[m_previousBufferIndex]; // Break down region into non-overlapping rectangles - const QVector<QRect> rects = region.rects(); - for (int i = rects.size() - 1; i >= 0; i--) { + for (auto rit = region.rbegin(), rend = region.rend(); rit != rend; ++rit) { // Clip rectangle to bounds of target - const QRect rect = rects[i].intersected(currentBuffer.rect()); + const QRect rect = rit->intersected(currentBuffer.rect()); if (rect.isEmpty()) continue; diff --git a/src/plugins/platforms/qnx/qqnxscreen.cpp b/src/plugins/platforms/qnx/qqnxscreen.cpp index 16ddcd784b..678e83cd57 100644 --- a/src/plugins/platforms/qnx/qqnxscreen.cpp +++ b/src/plugins/platforms/qnx/qqnxscreen.cpp @@ -340,11 +340,12 @@ qreal QQnxScreen::refreshRate() const qWarning("QQnxScreen: Failed to query screen mode. Using default value of 60Hz"); return 60.0; } - qScreenDebug() << "screen mode:" << endl - << " width =" << displayMode.width << endl - << " height =" << displayMode.height << endl - << " refresh =" << displayMode.refresh << endl - << " interlaced =" << displayMode.interlaced; + qScreenDebug("screen mode:\n" + " width = %u\n" + " height = %u\n" + " refresh = %u\n" + " interlaced = %u", + uint(displayMode.width), uint(displayMode.height), uint(displayMode.refresh), uint(displayMode.interlaced)); return static_cast<qreal>(displayMode.refresh); } @@ -404,7 +405,7 @@ static bool isOrthogonal(int angle1, int angle2) void QQnxScreen::setRotation(int rotation) { - qScreenDebug() << "orientation =" << rotation; + qScreenDebug("orientation = %d", rotation); // Check if rotation changed // We only want to rotate if we are the primary screen if (m_currentRotation != rotation && isPrimaryScreen()) { diff --git a/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp b/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp index 599d43a8c8..42651732c2 100644 --- a/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp +++ b/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp @@ -147,7 +147,7 @@ bool QQnxScreenEventHandler::handleEvent(screen_event_t event, int qnxType) default: // event ignored - qScreenEventDebug() << "unknown event" << qnxType; + qScreenEventDebug("unknown event %d", qnxType); return false; } diff --git a/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.cpp b/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.cpp index 1174dc6ab3..025c03c058 100644 --- a/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.cpp +++ b/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.cpp @@ -164,7 +164,7 @@ void QQnxVirtualKeyboardPps::ppsDataReady() { ssize_t nread = qt_safe_read(m_fd, m_buffer, ms_bufferSize - 1); - qVirtualKeyboardDebug() << "keyboardMessage size: " << nread; + qVirtualKeyboardDebug("keyboardMessage size: %zd", nread); if (nread < 0){ connect(); // reconnect return; @@ -230,7 +230,7 @@ void QQnxVirtualKeyboardPps::handleKeyboardInfoMessage() } setHeight(newHeight); - qVirtualKeyboardDebug() << "size=" << newHeight; + qVirtualKeyboardDebug("size=%d", newHeight); } bool QQnxVirtualKeyboardPps::showKeyboard() diff --git a/src/plugins/platforms/windows/accessible/accessible.pri b/src/plugins/platforms/windows/accessible/accessible.pri index 0774d907f2..0e3aacc558 100644 --- a/src/plugins/platforms/windows/accessible/accessible.pri +++ b/src/plugins/platforms/windows/accessible/accessible.pri @@ -6,15 +6,13 @@ HEADERS += \ $$PWD/qwindowsaccessibility.h \ $$PWD/comutils.h -!wince: { - SOURCES += $$PWD/qwindowsmsaaaccessible.cpp - HEADERS += $$PWD/qwindowsmsaaaccessible.h +SOURCES += $$PWD/qwindowsmsaaaccessible.cpp +HEADERS += $$PWD/qwindowsmsaaaccessible.h - !mingw: { - SOURCES += $$PWD/iaccessible2.cpp - HEADERS += $$PWD/iaccessible2.h - include(../../../../3rdparty/iaccessible2/iaccessible2.pri) - } +!mingw: { + SOURCES += $$PWD/iaccessible2.cpp + HEADERS += $$PWD/iaccessible2.h + include(../../../../3rdparty/iaccessible2/iaccessible2.pri) } mingw: LIBS *= -luuid diff --git a/src/plugins/platforms/windows/accessible/comutils.cpp b/src/plugins/platforms/windows/accessible/comutils.cpp index 7655bdf622..1c072c5e2c 100644 --- a/src/plugins/platforms/windows/accessible/comutils.cpp +++ b/src/plugins/platforms/windows/accessible/comutils.cpp @@ -170,7 +170,6 @@ bool QVariant2VARIANT(const QVariant &var, VARIANT &arg, const QByteArray &typeN case QVariant::LongLong: if (out && arg.vt == (VT_CY|VT_BYREF)) { arg.pcyVal->int64 = qvar.toLongLong(); -#if !defined(Q_OS_WINCE) && defined(_MSC_VER) && _MSC_VER >= 1400 } else if (out && arg.vt == (VT_I8|VT_BYREF)) { *arg.pllVal = qvar.toLongLong(); } else { @@ -181,22 +180,11 @@ bool QVariant2VARIANT(const QVariant &var, VARIANT &arg, const QByteArray &typeN arg.vt |= VT_BYREF; } } -#else - } else { - arg.vt = VT_CY; - arg.cyVal.int64 = qvar.toLongLong(); - if (out) { - arg.pcyVal = new CY(arg.cyVal); - arg.vt |= VT_BYREF; - } - } -#endif break; case QVariant::ULongLong: if (out && arg.vt == (VT_CY|VT_BYREF)) { arg.pcyVal->int64 = qvar.toULongLong(); -#if !defined(Q_OS_WINCE) && defined(_MSC_VER) && _MSC_VER >= 1400 } else if (out && arg.vt == (VT_UI8|VT_BYREF)) { *arg.pullVal = qvar.toULongLong(); } else { @@ -207,18 +195,6 @@ bool QVariant2VARIANT(const QVariant &var, VARIANT &arg, const QByteArray &typeN arg.vt |= VT_BYREF; } } -#else - } else { - arg.vt = VT_CY; - arg.cyVal.int64 = qvar.toULongLong(); - if (out) { - arg.pcyVal = new CY(arg.cyVal); - arg.vt |= VT_BYREF; - } - } - -#endif - break; case QVariant::Bool: diff --git a/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp b/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp index 4a3f0ccb2b..08edf816b0 100644 --- a/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp +++ b/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp @@ -56,12 +56,10 @@ #include <QtGui/qguiapplication.h> #include "qwindowsaccessibility.h" -#if !defined(Q_OS_WINCE) -# ifdef Q_CC_MINGW -# include "qwindowsmsaaaccessible.h" -# else -# include "iaccessible2.h" -# endif +#ifdef Q_CC_MINGW +# include "qwindowsmsaaaccessible.h" +#else +# include "iaccessible2.h" #endif #include "comutils.h" @@ -74,11 +72,7 @@ #include <winuser.h> #if !defined(WINABLEAPI) -# if defined(Q_OS_WINCE) -# include <bldver.h> -# else -# include <winable.h> -# endif +# include <winable.h> #endif #include <servprov.h> @@ -153,10 +147,6 @@ void QWindowsAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) } } -#if defined(Q_OS_WINCE) // ### TODO: check for NotifyWinEvent in CE 6.0 - // There is no user32.lib nor NotifyWinEvent for CE - return; -#else // An event has to be associated with a window, // so find the first parent that is a widget and that has a WId QAccessibleInterface *iface = event->accessibleInterface(); @@ -179,7 +169,6 @@ void QWindowsAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) event->type() != QAccessible::ObjectDestroyed) { ::NotifyWinEvent(event->type(), hWnd, OBJID_CLIENT, QAccessible::uniqueId(iface)); } -#endif // Q_OS_WINCE } QWindow *QWindowsAccessibility::windowHelper(const QAccessibleInterface *iface) @@ -202,11 +191,6 @@ QWindow *QWindowsAccessibility::windowHelper(const QAccessibleInterface *iface) */ IAccessible *QWindowsAccessibility::wrap(QAccessibleInterface *acc) { -#if defined(Q_OS_WINCE) - Q_UNUSED(acc); - - return 0; -#else if (!acc) return 0; @@ -222,12 +206,10 @@ IAccessible *QWindowsAccessibility::wrap(QAccessibleInterface *acc) IAccessible *iacc = 0; wacc->QueryInterface(IID_IAccessible, reinterpret_cast<void **>(&iacc)); return iacc; -#endif // defined(Q_OS_WINCE) } bool QWindowsAccessibility::handleAccessibleObjectFromWindowRequest(HWND hwnd, WPARAM wParam, LPARAM lParam, LRESULT *lResult) { -#if !defined(Q_OS_WINCE) if (static_cast<long>(lParam) == static_cast<long>(UiaRootObjectId)) { /* For UI Automation */ } else if (DWORD(lParam) == DWORD(OBJID_CLIENT)) { @@ -263,12 +245,6 @@ bool QWindowsAccessibility::handleAccessibleObjectFromWindowRequest(HWND hwnd, W } } } -#else - Q_UNUSED(hwnd); - Q_UNUSED(wParam); - Q_UNUSED(lParam); - Q_UNUSED(lResult); -#endif // !defined(Q_OS_WINCE) return false; } diff --git a/src/plugins/platforms/windows/qplatformfunctions_wince.h b/src/plugins/platforms/windows/qplatformfunctions_wince.h deleted file mode 100644 index 309191537a..0000000000 --- a/src/plugins/platforms/windows/qplatformfunctions_wince.h +++ /dev/null @@ -1,371 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QPLATFORMFUNCTIONS_WCE_H -#define QPLATFORMFUNCTIONS_WCE_H -// -// W A R N I N G -// ------------- -// -// This file is part of the QPA API and is not meant to be used -// in applications. Usage of this API may make your code -// source and binary incompatible with future versions of Qt. -// - -#ifdef Q_OS_WINCE -#include <QtCore/qfunctions_wince.h> -#define UNDER_NT -#include <wingdi.h> -#include <objidl.h> - -#ifndef WM_MOUSELEAVE -# define WM_MOUSELEAVE 0x02A3 -#endif - -#ifndef WM_TOUCH -# define WM_TOUCH 0x0240 -#endif - -#ifndef WM_GETOBJECT -#define WM_GETOBJECT 0x003D -#endif - -#define GetWindowLongPtr GetWindowLong -#define SetWindowLongPtr SetWindowLong -#define GWLP_USERDATA GWL_USERDATA - -#ifndef CWP_SKIPINVISIBLE -#define CWP_SKIPINVISIBLE 0x0001 -#define CWP_SKIPTRANSPARENT 0x0004 -#endif - -#ifndef CS_OWNDC -#define CS_OWNDC 0x0020 -#endif - -#ifndef HWND_MESSAGE -#define HWND_MESSAGE 0 -#endif - -// Real Value would be 0x40000000, but if we pass this to Windows Embedded Compact -// he blits it wrongly, so lets not do any harm and define it to 0 -#ifndef CAPTUREBLT -#define CAPTUREBLT (DWORD)0x0 -#endif - -#define SW_SHOWMINIMIZED SW_MINIMIZE -#define SW_SHOWMINNOACTIVE SW_MINIMIZE - -#ifndef CF_DIBV5 -#define CF_DIBV5 17 -#endif - -#ifndef WM_MOUSEACTIVATE -#define WM_MOUSEACTIVATE 0x0021 -#endif - -#ifndef WM_CHILDACTIVATE -#define WM_CHILDACTIVATE 0x0022 -#endif - -#ifndef WM_PARENTNOTIFY -#define WM_PARENTNOTIFY 0x0210 -#endif - -#ifndef WM_ENTERIDLE -#define WM_ENTERIDLE 0x0121 -#endif - -#ifndef WM_GETMINMAXINFO -#define WM_GETMINMAXINFO 0x0024 -#endif - -#ifndef WM_WINDOWPOSCHANGING -#define WM_WINDOWPOSCHANGING 0x0046 -#endif - -#ifndef WM_NCMOUSEMOVE -#define WM_NCMOUSEMOVE 0x00A0 -#endif - -#ifndef WM_NCMBUTTONDBLCLK -#define WM_NCMBUTTONDBLCLK 0x00A -#endif - -#ifndef WM_NCCREATE -#define WM_NCCREATE 0x0081 -#endif - -#ifndef WM_NCCALCSIZE -#define WM_NCCALCSIZE 0x0083 -#endif - -#ifndef WM_NCACTIVATE -#define WM_NCACTIVATE 0x0086 -#endif - -#ifndef WM_NCMOUSELEAVE -#define WM_NCMOUSELEAVE 0x02A2 -#endif - -#ifndef WM_NCLBUTTONDOWN -#define WM_NCLBUTTONDOWN 0x00A1 -#endif - -#ifndef WM_NCLBUTTONUP -#define WM_NCLBUTTONUP 0x00A2 -#endif - -#ifndef WM_NCPAINT -#define WM_NCPAINT 0x0085 -#endif - -#ifndef WM_NCHITTEST -#define WM_NCHITTEST 0x0084 -#endif - -#ifndef WM_THEMECHANGED -#define WM_THEMECHANGED 0x031A -#endif - -#ifndef WM_DISPLAYCHANGE -#define WM_DISPLAYCHANGE 0x007E -#endif - -#ifndef VREFRESH -#define VREFRESH 116 -#endif - -#ifndef SM_SWAPBUTTON -#define SM_SWAPBUTTON 23 -#endif - -// application defines -#define SPI_SETNONCLIENTMETRICS 72 -#define SPI_SETICONTITLELOGFONT 0x0022 -#define WM_ACTIVATEAPP 0x001c -#define SW_PARENTCLOSING 1 -#define SW_OTHERMAXIMIZED 2 -#define SW_PARENTOPENING 3 -#define SW_OTHERRESTORED 4 -#define GET_XBUTTON_WPARAM(wParam) (HIWORD(wParam)) - -// drag n drop -#ifndef CFSTR_PERFORMEDDROPEFFECT -#define CFSTR_PERFORMEDDROPEFFECT TEXT("Performed DropEffect") -#endif - -// QWidget -#define SW_SHOWMINIMIZED SW_MINIMIZE - -// QRegion -#define ALTERNATE 0 -#define WINDING 1 - -// QFontEngine -typedef struct _FIXED { - WORD fract; - short value; -} FIXED; - -typedef struct tagPOINTFX { - FIXED x; - FIXED y; -} POINTFX; - -typedef struct _MAT2 { - FIXED eM11; - FIXED eM12; - FIXED eM21; - FIXED eM22; -} MAT2; - -typedef struct _GLYPHMETRICS { - UINT gmBlackBoxX; - UINT gmBlackBoxY; - POINT gmptGlyphOrigin; - short gmCellIncX; - short gmCellIncY; -} GLYPHMETRICS; - -typedef struct tagTTPOLYGONHEADER -{ - DWORD cb; - DWORD dwType; - POINTFX pfxStart; -} TTPOLYGONHEADER; - -typedef struct tagTTPOLYCURVE -{ - WORD wType; - WORD cpfx; - POINTFX apfx[1]; -} TTPOLYCURVE; - -#define GGO_NATIVE 2 -#define GGO_GLYPH_INDEX 0x0080 -#define TT_PRIM_LINE 1 -#define TT_PRIM_QSPLINE 2 -#define TT_PRIM_CSPLINE 3 -#define ANSI_VAR_FONT 12 - -#ifndef OleInitialize -#define OleInitialize(a) 0 -#endif - -#ifndef SPI_GETSNAPTODEFBUTTON -#define SPI_GETSNAPTODEFBUTTON 95 -#endif - -#ifndef WS_EX_LAYERED -#define WS_EX_LAYERED 0x00080000 -#endif - -// Clipboard -------------------------------------------------------- -#ifndef WM_CHANGECBCHAIN -#define WM_CHANGECBCHAIN 0x030D -#endif - -#ifndef WM_DRAWCLIPBOARD -#define WM_DRAWCLIPBOARD 0x0308 -#endif - -#include <QFileInfo> - -inline bool IsIconic( HWND /*hWnd*/ ) -{ - return false; -} - -inline int AddFontResourceExW( LPCWSTR name, DWORD /*fl*/, PVOID /*res*/) -{ - QString fName = QString::fromWCharArray(name); - QFileInfo fileinfo(fName); - fName = fileinfo.absoluteFilePath(); - return AddFontResource((LPCWSTR)fName.utf16()); -} - -inline bool RemoveFontResourceExW( LPCWSTR /*name*/, DWORD /*fl*/, PVOID /*pdv*/) -{ - return 0; -} - -inline void OleUninitialize() -{ -} - -inline DWORD GetGlyphOutline( HDC /*hdc*/, UINT /*uChar*/, INT /*fuFormat*/, GLYPHMETRICS * /*lpgm*/, - DWORD /*cjBuffer*/, LPVOID /*pvBuffer*/, CONST MAT2 * /*lpmat2*/ ) -{ - qFatal("GetGlyphOutline() not supported under Windows CE. Please try using freetype font-rendering, by " - "passing the command line argument -platform windows:fontengine=freetype to the application."); - return GDI_ERROR; -} - -inline HWND GetAncestor(HWND hWnd, UINT /*gaFlags*/) -{ - return GetParent(hWnd); -} - -#ifndef GA_PARENT -# define GA_PARENT 1 -#endif - -#ifndef SPI_SETFONTSMOOTHINGTYPE -# define SPI_SETFONTSMOOTHINGTYPE 0x200B -#endif -#ifndef SPI_GETFONTSMOOTHINGTYPE -# define SPI_GETFONTSMOOTHINGTYPE 0x200A -#endif -#ifndef FE_FONTSMOOTHINGCLEARTYPE -# define FE_FONTSMOOTHINGCLEARTYPE 0x0002 -#endif - -#ifndef DEVICE_FONTTYPE -#define DEVICE_FONTTYPE 0x0002 -#endif - -#ifndef RASTER_FONTTYPE -#define RASTER_FONTTYPE 0x0001 -#endif - -#ifndef WM_DISPLAYCHANGE -#define WM_DISPLAYCHANGE 0x007E -#endif - -BOOL qt_wince_ChangeClipboardChain( - HWND hWndRemove, // handle to window to remove - HWND hWndNewNext // handle to next window -); -#define ChangeClipboardChain(a,b) qt_wince_ChangeClipboardChain(a,b); - -HWND qt_wince_SetClipboardViewer( - HWND hWndNewViewer // handle to clipboard viewer window -); -#define SetClipboardViewer(a) qt_wince_SetClipboardViewer(a) - -/* Shell stock icon IDs - SHGetStockIconInfo() is not available on CE, but we're using these - constants in code that is built on CE as well */ - enum - { - SIID_INVALID = -1, - SIID_DOCNOASSOC = 0, - SIID_FOLDER = 3, - SIID_FOLDEROPEN = 4, - SIID_DRIVE35 = 6, - SIID_DRIVEFIXED = 8, - SIID_DRIVENET = 9, - SIID_DRIVECD = 11, - SIID_HELP = 23, - SIID_RECYCLER = 31, - SIID_DRIVEDVD = 59, - SIID_SHIELD = 77, - SIID_WARNING = 78, - SIID_INFO = 79, - SIID_ERROR = 80 -}; - -#ifndef SHGSI_LINKOVERLAY -// Value is wrong, but doesn't matter, not used at runtime -#define SHGSI_LINKOVERLAY 0 -#endif - -#endif // Q_OS_WINCE -#endif // QPLATFORMFUNCTIONS_WCE_H diff --git a/src/plugins/platforms/windows/qtwindowsglobal.h b/src/plugins/platforms/windows/qtwindowsglobal.h index 90008663e7..cb85856787 100644 --- a/src/plugins/platforms/windows/qtwindowsglobal.h +++ b/src/plugins/platforms/windows/qtwindowsglobal.h @@ -43,9 +43,6 @@ #include "qtwindows_additional.h" #include <QtCore/qnamespace.h> -#ifdef Q_OS_WINCE -# include "qplatformfunctions_wince.h" -#endif QT_BEGIN_NAMESPACE @@ -158,10 +155,8 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: return QtWindows::MouseWheelEvent; -#ifndef Q_OS_WINCE case WM_WINDOWPOSCHANGING: return QtWindows::GeometryChangingEvent; -#endif case WM_MOVE: return QtWindows::MoveEvent; case WM_SHOWWINDOW: @@ -172,10 +167,8 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI return QtWindows::ResizeEvent; case WM_NCCALCSIZE: return QtWindows::CalculateSize; -#ifndef Q_OS_WINCE case WM_NCHITTEST: return QtWindows::NonClientHitTest; -#endif // !Q_OS_WINCE case WM_GETMINMAXINFO: return QtWindows::QuerySizeHints; case WM_KEYDOWN: // keyboard event @@ -243,12 +236,10 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI return QtWindows::ContextMenu; #endif case WM_SYSCOMMAND: -#ifndef Q_OS_WINCE if ((wParamIn & 0xfff0) == SC_CONTEXTHELP) return QtWindows::WhatsThisEvent; -#endif break; -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) case WM_QUERYENDSESSION: return QtWindows::QueryEndSessionApplicationEvent; case WM_ENDSESSION: diff --git a/src/plugins/platforms/windows/qwindowsbackingstore.cpp b/src/plugins/platforms/windows/qwindowsbackingstore.cpp index 7123ed826d..3b7374dc92 100644 --- a/src/plugins/platforms/windows/qwindowsbackingstore.cpp +++ b/src/plugins/platforms/windows/qwindowsbackingstore.cpp @@ -87,7 +87,6 @@ void QWindowsBackingStore::flush(QWindow *window, const QRegion ®ion, QWindowsWindow *rw = QWindowsWindow::windowsWindowOf(window); Q_ASSERT(rw); -#ifndef Q_OS_WINCE const bool hasAlpha = rw->format().hasAlpha(); const Qt::WindowFlags flags = window->flags(); if ((flags & Qt::FramelessWindowHint) && QWindowsWindow::setWindowLayered(rw->handle(), flags, hasAlpha, rw->opacity()) && hasAlpha) { @@ -101,21 +100,16 @@ void QWindowsBackingStore::flush(QWindow *window, const QRegion ®ion, POINT ptDst = {r.x(), r.y()}; POINT ptSrc = {0, 0}; BLENDFUNCTION blend = {AC_SRC_OVER, 0, BYTE(qRound(255.0 * rw->opacity())), AC_SRC_ALPHA}; - if (QWindowsContext::user32dll.updateLayeredWindowIndirect) { - RECT dirty = {dirtyRect.x(), dirtyRect.y(), - dirtyRect.x() + dirtyRect.width(), dirtyRect.y() + dirtyRect.height()}; - UPDATELAYEREDWINDOWINFO info = {sizeof(info), NULL, &ptDst, &size, m_image->hdc(), &ptSrc, 0, &blend, ULW_ALPHA, &dirty}; - const BOOL result = QWindowsContext::user32dll.updateLayeredWindowIndirect(rw->handle(), &info); - if (!result) - qErrnoWarning("UpdateLayeredWindowIndirect failed for ptDst=(%d, %d)," - " size=(%dx%d), dirty=(%dx%d %d, %d)", r.x(), r.y(), - r.width(), r.height(), dirtyRect.width(), dirtyRect.height(), - dirtyRect.x(), dirtyRect.y()); - } else { - QWindowsContext::user32dll.updateLayeredWindow(rw->handle(), NULL, &ptDst, &size, m_image->hdc(), &ptSrc, 0, &blend, ULW_ALPHA); - } + RECT dirty = {dirtyRect.x(), dirtyRect.y(), + dirtyRect.x() + dirtyRect.width(), dirtyRect.y() + dirtyRect.height()}; + UPDATELAYEREDWINDOWINFO info = {sizeof(info), NULL, &ptDst, &size, m_image->hdc(), &ptSrc, 0, &blend, ULW_ALPHA, &dirty}; + const BOOL result = UpdateLayeredWindowIndirect(rw->handle(), &info); + if (!result) + qErrnoWarning("UpdateLayeredWindowIndirect failed for ptDst=(%d, %d)," + " size=(%dx%d), dirty=(%dx%d %d, %d)", r.x(), r.y(), + r.width(), r.height(), dirtyRect.width(), dirtyRect.height(), + dirtyRect.x(), dirtyRect.y()); } else { -#endif const HDC dc = rw->getDC(); if (!dc) { qErrnoWarning("%s: GetDC failed", __FUNCTION__); @@ -129,9 +123,7 @@ void QWindowsBackingStore::flush(QWindow *window, const QRegion ®ion, qErrnoWarning(int(lastError), "%s: BitBlt failed", __FUNCTION__); } rw->releaseDC(); -#ifndef Q_OS_WINCE } -#endif // Write image for debug purposes. if (QWindowsContext::verbose > 2 && lcQpaBackingStore().isDebugEnabled()) { @@ -175,7 +167,7 @@ void QWindowsBackingStore::resize(const QSize &size, const QRegion ®ion) staticRegion &= QRect(0, 0, newimg.width(), newimg.height()); QPainter painter(&newimg); painter.setCompositionMode(QPainter::CompositionMode_Source); - foreach (const QRect &rect, staticRegion.rects()) + for (const QRect &rect : staticRegion) painter.drawImage(rect, oldimg, rect); } @@ -190,10 +182,9 @@ bool QWindowsBackingStore::scroll(const QRegion &area, int dx, int dy) if (m_image.isNull() || m_image->image().isNull()) return false; - const QVector<QRect> rects = area.rects(); const QPoint offset(dx, dy); - for (int i = 0; i < rects.size(); ++i) - qt_scrollRectInImage(m_image->image(), rects.at(i), offset); + for (const QRect &rect : area) + qt_scrollRectInImage(m_image->image(), rect, offset); return true; } @@ -207,7 +198,7 @@ void QWindowsBackingStore::beginPaint(const QRegion ®ion) QPainter p(&m_image->image()); p.setCompositionMode(QPainter::CompositionMode_Source); const QColor blank = Qt::transparent; - foreach (const QRect &r, region.rects()) + for (const QRect &r : region) p.fillRect(r, blank); } } @@ -219,8 +210,6 @@ HDC QWindowsBackingStore::getDC() const return 0; } -#ifndef QT_NO_OPENGL - QImage QWindowsBackingStore::toImage() const { if (m_image.isNull()) { @@ -230,6 +219,4 @@ QImage QWindowsBackingStore::toImage() const return m_image.data()->image(); } -#endif // !QT_NO_OPENGL - QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsbackingstore.h b/src/plugins/platforms/windows/qwindowsbackingstore.h index 26c79348a9..5cd621375d 100644 --- a/src/plugins/platforms/windows/qwindowsbackingstore.h +++ b/src/plugins/platforms/windows/qwindowsbackingstore.h @@ -65,9 +65,7 @@ public: HDC getDC() const; -#ifndef QT_NO_OPENGL QImage toImage() const Q_DECL_OVERRIDE; -#endif private: QScopedPointer<QWindowsNativeImage> m_image; diff --git a/src/plugins/platforms/windows/qwindowsclipboard.cpp b/src/plugins/platforms/windows/qwindowsclipboard.cpp index d527e07308..21bc9d7377 100644 --- a/src/plugins/platforms/windows/qwindowsclipboard.cpp +++ b/src/plugins/platforms/windows/qwindowsclipboard.cpp @@ -237,8 +237,7 @@ void QWindowsClipboard::propagateClipboardMessage(UINT message, WPARAM wParam, L return; // In rare cases, a clipboard viewer can hang (application crashed, // suspended by a shell prompt 'Select' or debugger). - if (QWindowsContext::user32dll.isHungAppWindow - && QWindowsContext::user32dll.isHungAppWindow(m_nextClipboardViewer)) { + if (IsHungAppWindow(m_nextClipboardViewer)) { qWarning("Cowardly refusing to send clipboard message to hung application..."); return; } diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index efeb1f5f05..ef0962c2ff 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -51,7 +51,7 @@ #ifndef QT_NO_ACCESSIBILITY # include "accessible/qwindowsaccessibility.h" #endif -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) # include <private/qsessionmanager_p.h> # include "qwindowssessionmanager.h" #endif @@ -76,9 +76,7 @@ #include <stdlib.h> #include <stdio.h> #include <windowsx.h> -#ifndef Q_OS_WINCE -# include <comdef.h> -#endif +#include <comdef.h> QT_BEGIN_NAMESPACE @@ -99,45 +97,29 @@ int QWindowsContext::verbose = 0; # define LANG_SYRIAC 0x5a #endif -static inline bool useRTL_Extensions(QSysInfo::WinVersion ver) +static inline bool useRTL_Extensions() { - // This is SDK dependent on CE so out of scope for now - if (QSysInfo::windowsVersion() & QSysInfo::WV_CE_based) - return false; - if ((ver & QSysInfo::WV_NT_based) && (ver >= QSysInfo::WV_VISTA)) { - // Since the IsValidLanguageGroup/IsValidLocale functions always return true on - // Vista, check the Keyboard Layouts for enabling RTL. - if (const int nLayouts = GetKeyboardLayoutList(0, 0)) { - QScopedArrayPointer<HKL> lpList(new HKL[nLayouts]); - GetKeyboardLayoutList(nLayouts, lpList.data()); - for (int i = 0; i < nLayouts; ++i) { - switch (PRIMARYLANGID((quintptr)lpList[i])) { - case LANG_ARABIC: - case LANG_HEBREW: - case LANG_FARSI: - case LANG_SYRIAC: - return true; - default: - break; - } + // Since the IsValidLanguageGroup/IsValidLocale functions always return true on + // Vista, check the Keyboard Layouts for enabling RTL. + if (const int nLayouts = GetKeyboardLayoutList(0, 0)) { + QScopedArrayPointer<HKL> lpList(new HKL[nLayouts]); + GetKeyboardLayoutList(nLayouts, lpList.data()); + for (int i = 0; i < nLayouts; ++i) { + switch (PRIMARYLANGID((quintptr)lpList[i])) { + case LANG_ARABIC: + case LANG_HEBREW: + case LANG_FARSI: + case LANG_SYRIAC: + return true; + default: + break; } } - return false; - } // NT/Vista -#ifndef Q_OS_WINCE - // Pre-NT: figure out whether a RTL language is installed - return IsValidLanguageGroup(LGRPID_ARABIC, LGRPID_INSTALLED) - || IsValidLanguageGroup(LGRPID_HEBREW, LGRPID_INSTALLED) - || IsValidLocale(MAKELCID(MAKELANGID(LANG_ARABIC, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED) - || IsValidLocale(MAKELCID(MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED) - || IsValidLocale(MAKELCID(MAKELANGID(LANG_SYRIAC, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED) - || IsValidLocale(MAKELCID(MAKELANGID(LANG_FARSI, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED); -#else + } return false; -#endif } -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) static inline QWindowsSessionManager *platformSessionManager() { QGuiApplicationPrivate *guiPrivate = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(qApp)); QSessionManagerPrivate *managerPrivate = static_cast<QSessionManagerPrivate*>(QObjectPrivate::get(guiPrivate->session_manager)); @@ -160,13 +142,8 @@ static inline QWindowsSessionManager *platformSessionManager() { \internal \ingroup qt-lighthouse-win */ - -#ifndef Q_OS_WINCE - QWindowsUser32DLL::QWindowsUser32DLL() : - setLayeredWindowAttributes(0), updateLayeredWindow(0), - updateLayeredWindowIndirect(0), - isHungAppWindow(0), isTouchWindow(0), + isTouchWindow(0), registerTouchWindow(0), unregisterTouchWindow(0), getTouchInputInfo(0), closeTouchInputHandle(0), setProcessDPIAware(0), addClipboardFormatListener(0), removeClipboardFormatListener(0), @@ -177,20 +154,11 @@ QWindowsUser32DLL::QWindowsUser32DLL() : void QWindowsUser32DLL::init() { QSystemLibrary library(QStringLiteral("user32")); - // MinGW (g++ 3.4.5) accepts only C casts. - setLayeredWindowAttributes = (SetLayeredWindowAttributes)(library.resolve("SetLayeredWindowAttributes")); - updateLayeredWindow = (UpdateLayeredWindow)(library.resolve("UpdateLayeredWindow")); - if (Q_UNLIKELY(!setLayeredWindowAttributes || !updateLayeredWindow)) - qFatal("This version of Windows is not supported (User32.dll is missing the symbols 'SetLayeredWindowAttributes', 'UpdateLayeredWindow')."); - - updateLayeredWindowIndirect = (UpdateLayeredWindowIndirect)(library.resolve("UpdateLayeredWindowIndirect")); - isHungAppWindow = (IsHungAppWindow)library.resolve("IsHungAppWindow"); setProcessDPIAware = (SetProcessDPIAware)library.resolve("SetProcessDPIAware"); - if (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA) { - addClipboardFormatListener = (AddClipboardFormatListener)library.resolve("AddClipboardFormatListener"); - removeClipboardFormatListener = (RemoveClipboardFormatListener)library.resolve("RemoveClipboardFormatListener"); - } + addClipboardFormatListener = (AddClipboardFormatListener)library.resolve("AddClipboardFormatListener"); + removeClipboardFormatListener = (RemoveClipboardFormatListener)library.resolve("RemoveClipboardFormatListener"); + getDisplayAutoRotationPreferences = (GetDisplayAutoRotationPreferences)library.resolve("GetDisplayAutoRotationPreferences"); setDisplayAutoRotationPreferences = (SetDisplayAutoRotationPreferences)library.resolve("SetDisplayAutoRotationPreferences"); } @@ -208,38 +176,6 @@ bool QWindowsUser32DLL::initTouch() return isTouchWindow && registerTouchWindow && unregisterTouchWindow && getTouchInputInfo && closeTouchInputHandle; } -/*! - \class QWindowsShell32DLL - \brief Struct that contains dynamically resolved symbols of Shell32.dll. - - The stub libraries shipped with the MinGW compiler miss some of the - functions. They need to be retrieved dynamically. - - \sa QWindowsUser32DLL - - \internal - \ingroup qt-lighthouse-win -*/ - -QWindowsShell32DLL::QWindowsShell32DLL() - : sHCreateItemFromParsingName(0) - , sHGetKnownFolderIDList(0) - , sHGetStockIconInfo(0) - , sHGetImageList(0) - , sHCreateItemFromIDList(0) -{ -} - -void QWindowsShell32DLL::init() -{ - QSystemLibrary library(QStringLiteral("shell32")); - sHCreateItemFromParsingName = (SHCreateItemFromParsingName)(library.resolve("SHCreateItemFromParsingName")); - sHGetKnownFolderIDList = (SHGetKnownFolderIDList)(library.resolve("SHGetKnownFolderIDList")); - sHGetStockIconInfo = (SHGetStockIconInfo)library.resolve("SHGetStockIconInfo"); - sHGetImageList = (SHGetImageList)library.resolve("SHGetImageList"); - sHCreateItemFromIDList = (SHCreateItemFromIDList)library.resolve("SHCreateItemFromIDList"); -} - QWindowsShcoreDLL::QWindowsShcoreDLL() : getProcessDpiAwareness(0) , setProcessDpiAwareness(0) @@ -258,11 +194,8 @@ void QWindowsShcoreDLL::init() } QWindowsUser32DLL QWindowsContext::user32dll; -QWindowsShell32DLL QWindowsContext::shell32dll; QWindowsShcoreDLL QWindowsContext::shcoredll; -#endif // !Q_OS_WINCE - QWindowsContext *QWindowsContext::m_instance = 0; /*! @@ -291,7 +224,7 @@ struct QWindowsContextPrivate { QWindowsMimeConverter m_mimeConverter; QWindowsScreenManager m_screenManager; QSharedPointer<QWindowCreationContext> m_creationContext; -#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +#if !defined(QT_NO_TABLETEVENT) QScopedPointer<QWindowsTabletSupport> m_tabletSupport; #endif const HRESULT m_oleInitializeResult; @@ -306,18 +239,14 @@ QWindowsContextPrivate::QWindowsContextPrivate() , m_eventType(QByteArrayLiteral("windows_generic_MSG")) , m_lastActiveWindow(0), m_asyncExpose(0) { - const QSysInfo::WinVersion ver = QSysInfo::windowsVersion(); -#ifndef Q_OS_WINCE QWindowsContext::user32dll.init(); - QWindowsContext::shell32dll.init(); QWindowsContext::shcoredll.init(); if (m_mouseHandler.touchDevice() && QWindowsContext::user32dll.initTouch()) m_systemInfo |= QWindowsContext::SI_SupportsTouch; -#endif // !Q_OS_WINCE m_displayContext = GetDC(0); m_defaultDPI = GetDeviceCaps(m_displayContext, LOGPIXELSY); - if (useRTL_Extensions(ver)) { + if (useRTL_Extensions()) { m_systemInfo |= QWindowsContext::SI_RTL_Extensions; m_keyMapper.setUseRTLExtensions(true); } @@ -338,7 +267,7 @@ QWindowsContext::QWindowsContext() : const QByteArray bv = qgetenv("QT_QPA_VERBOSE"); if (!bv.isEmpty()) QLoggingCategory::setFilterRules(QString::fromLocal8Bit(bv)); -#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +#if !defined(QT_NO_TABLETEVENT) d->m_tabletSupport.reset(QWindowsTabletSupport::create()); qCDebug(lcQpaTablet) << "Tablet support: " << (d->m_tabletSupport.isNull() ? QStringLiteral("None") : d->m_tabletSupport->description()); #endif @@ -346,7 +275,7 @@ QWindowsContext::QWindowsContext() : QWindowsContext::~QWindowsContext() { -#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +#if !defined(QT_NO_TABLETEVENT) d->m_tabletSupport.reset(); // Destroy internal window before unregistering classes. #endif unregisterWindowClasses(); @@ -371,12 +300,10 @@ bool QWindowsContext::initTouch(unsigned integrationOptions) if (!touchDevice) return false; -#ifndef Q_OS_WINCE if (!QWindowsContext::user32dll.initTouch()) { delete touchDevice; return false; } -#endif // !Q_OS_WINCE if (!(integrationOptions & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)) touchDevice->setCapabilities(touchDevice->capabilities() | QTouchDevice::MouseEmulation); @@ -389,7 +316,7 @@ bool QWindowsContext::initTouch(unsigned integrationOptions) void QWindowsContext::setTabletAbsoluteRange(int a) { -#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +#if !defined(QT_NO_TABLETEVENT) if (!d->m_tabletSupport.isNull()) d->m_tabletSupport->setAbsoluteRange(a); #else @@ -399,19 +326,16 @@ void QWindowsContext::setTabletAbsoluteRange(int a) int QWindowsContext::processDpiAwareness() { -#ifndef Q_OS_WINCE int result; if (QWindowsContext::shcoredll.getProcessDpiAwareness && SUCCEEDED(QWindowsContext::shcoredll.getProcessDpiAwareness(NULL, &result))) { return result; } -#endif // !Q_OS_WINCE return -1; } void QWindowsContext::setProcessDpiAwareness(QtWindows::ProcessDpiAwareness dpiAwareness) { -#ifndef Q_OS_WINCE qCDebug(lcQpaWindows) << __FUNCTION__ << dpiAwareness; if (QWindowsContext::shcoredll.isValid()) { const HRESULT hr = QWindowsContext::shcoredll.setProcessDpiAwareness(dpiAwareness); @@ -426,9 +350,6 @@ void QWindowsContext::setProcessDpiAwareness(QtWindows::ProcessDpiAwareness dpiA qErrnoWarning("SetProcessDPIAware() failed"); } } -#else // !Q_OS_WINCE - Q_UNUSED(dpiAwareness) -#endif } QWindowsContext *QWindowsContext::instance() @@ -559,19 +480,14 @@ QString QWindowsContext::registerWindowClass(QString cname, if (d->m_registeredWindowClassNames.contains(cname)) // already registered in our list return cname; -#ifndef Q_OS_WINCE WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); -#else - WNDCLASS wc; -#endif wc.style = style; wc.lpfnWndProc = proc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = appInstance; wc.hCursor = 0; -#ifndef Q_OS_WINCE wc.hbrBackground = brush; if (icon) { wc.hIcon = static_cast<HICON>(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE)); @@ -587,22 +503,10 @@ QString QWindowsContext::registerWindowClass(QString cname, wc.hIcon = 0; wc.hIconSm = 0; } -#else - if (icon) { - wc.hIcon = (HICON)LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); - } else { - wc.hIcon = 0; - } -#endif wc.lpszMenuName = 0; wc.lpszClassName = reinterpret_cast<LPCWSTR>(cname.utf16()); -#ifndef Q_OS_WINCE ATOM atom = RegisterClassEx(&wc); -#else - ATOM atom = RegisterClass(&wc); -#endif - if (!atom) qErrnoWarning("QApplication::regClass: Registering window class '%s' failed.", qPrintable(cname)); @@ -720,28 +624,14 @@ static inline bool findPlatformWindowHelper(const POINT &screenPoint, unsigned c POINT point = screenPoint; ScreenToClient(*hwnd, &point); // Returns parent if inside & none matched. -#ifndef Q_OS_WINCE const HWND child = ChildWindowFromPointEx(*hwnd, point, cwexFlags); -#else -// Under Windows CE we don't use ChildWindowFromPointEx as it's not available -// and ChildWindowFromPoint does not work properly. - Q_UNUSED(cwexFlags) - const HWND child = WindowFromPoint(point); -#endif if (!child || child == *hwnd) return false; if (QWindowsWindow *window = context->findPlatformWindow(child)) { *result = window; *hwnd = child; -#ifndef Q_OS_WINCE return true; -#else -// WindowFromPoint does not return same handle in two sequential calls, which leads -// to an endless loop, but calling WindowFromPoint once is good enough. - return false; -#endif } -#ifndef Q_OS_WINCE // Does not have WS_EX_TRANSPARENT . // QTBUG-40555: despite CWP_SKIPINVISIBLE, it is possible to hit on invisible // full screen windows of other applications that have WS_EX_TRANSPARENT set // (for example created by screen sharing applications). In that case, try to @@ -757,7 +647,6 @@ static inline bool findPlatformWindowHelper(const POINT &screenPoint, unsigned c return true; } } -#endif // !Q_OS_WINCE *hwnd = child; return true; } @@ -784,7 +673,7 @@ QWindowsScreenManager &QWindowsContext::screenManager() QWindowsTabletSupport *QWindowsContext::tabletSupport() const { -#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +#if !defined(QT_NO_TABLETEVENT) return d->m_tabletSupport.data(); #else return 0; @@ -810,7 +699,6 @@ HWND QWindowsContext::createDummyWindow(const QString &classNameIn, HWND_MESSAGE, NULL, static_cast<HINSTANCE>(GetModuleHandle(0)), NULL); } -#ifndef Q_OS_WINCE // Re-engineered from the inline function _com_error::ErrorMessage(). // We cannot use it directly since it uses swprintf_s(), which is not // present in the MSVCRT.DLL found on Windows XP (QTBUG-35617). @@ -829,7 +717,6 @@ static inline QString errorMessageFromComError(const _com_error &comError) return QStringLiteral("IDispatch error #") + QString::number(wCode); return QStringLiteral("Unknown error 0x0") + QString::number(comError.Error(), 16); } -#endif // !Q_OS_WINCE /*! \brief Common COM error strings. @@ -894,12 +781,10 @@ QByteArray QWindowsContext::comErrorString(HRESULT hr) default: break; } -#ifndef Q_OS_WINCE _com_error error(hr); result += QByteArrayLiteral(" ("); result += errorMessageFromComError(error); result += ')'; -#endif // !Q_OS_WINCE return result; } @@ -935,9 +820,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, ClientToScreen(msg.hwnd, &msg.pt); } } else { -#ifndef Q_OS_WINCE GetCursorPos(&msg.pt); -#endif } // Run the native event filters. @@ -980,7 +863,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, switch (et) { case QtWindows::GestureEvent: -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result); #else return d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result); @@ -1019,11 +902,9 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, // Pass on to current creation context if (!platformWindow && !d->m_creationContext.isNull()) { switch (et) { -#ifndef Q_OS_WINCE // maybe available on some SDKs revisit WM_GETMINMAXINFO case QtWindows::QuerySizeHints: d->m_creationContext->applyToMinMaxInfo(reinterpret_cast<MINMAXINFO *>(lParam)); return true; -#endif case QtWindows::ResizeEvent: d->m_creationContext->obtainedGeometry.setSize(QSize(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); return true; @@ -1061,7 +942,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::InputMethodKeyEvent: case QtWindows::InputMethodKeyDownEvent: case QtWindows::AppCommandEvent: -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) return platformSessionManager()->isInteractionBlocked() ? true : d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result); #else return d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result); @@ -1072,7 +953,6 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::ResizeEvent: platformWindow->handleResized(static_cast<int>(wParam)); return true; -#ifndef Q_OS_WINCE // maybe available on some SDKs revisit WM_GETMINMAXINFO case QtWindows::QuerySizeHints: platformWindow->getSizeHints(reinterpret_cast<MINMAXINFO *>(lParam)); return true;// maybe available on some SDKs revisit WM_NCCALCSIZE @@ -1082,30 +962,18 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, return platformWindow->handleNonClientHitTest(QPoint(msg.pt.x, msg.pt.y), result); case QtWindows::GeometryChangingEvent: return platformWindow->QWindowsWindow::handleGeometryChanging(&msg); -#endif // !Q_OS_WINCE case QtWindows::ExposeEvent: return platformWindow->handleWmPaint(hwnd, message, wParam, lParam); case QtWindows::NonClientMouseEvent: if (platformWindow->frameStrutEventsEnabled()) -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); #else return d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); #endif break; -/* the mouse tracking on windows already handles the reset of the cursor - * and does not like somebody else handling it. - * on WINCE its necessary to handle this event to get the correct cursor - */ -#ifdef Q_OS_WINCE - case QtWindows::CursorEvent: - { - QWindowsWindow::baseWindowOf(platformWindow->window())->applyCursor(); - return true; - } -#endif case QtWindows::ScrollEvent: -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result); #else return d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result); @@ -1113,13 +981,13 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::MouseWheelEvent: case QtWindows::MouseEvent: case QtWindows::LeaveEvent: -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); #else return d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); #endif case QtWindows::TouchEvent: -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result); #else return d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result); @@ -1152,7 +1020,6 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::CompositionSettingsChanged: platformWindow->handleCompositionSettingsChanged(); return true; -#ifndef Q_OS_WINCE case QtWindows::ActivateWindowEvent: if (platformWindow->window()->flags() & Qt::WindowDoesNotAcceptFocus) { *result = LRESULT(MA_NOACTIVATE); @@ -1175,7 +1042,6 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, return true; } break; -#endif #ifndef QT_NO_CONTEXTMENU case QtWindows::ContextMenu: return handleContextMenuEvent(platformWindow->window(), msg); @@ -1186,7 +1052,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, return true; #endif } break; -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) case QtWindows::QueryEndSessionApplicationEvent: { QWindowsSessionManager *sessionManager = platformSessionManager(); if (sessionManager->isActive()) { // bogus message from windows @@ -1226,7 +1092,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, } return true; } -#endif // !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#endif // !defined(QT_NO_SESSIONMANAGER) default: break; } diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index 3559335747..32d7800ef5 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -79,38 +79,23 @@ class QPoint; class QKeyEvent; class QTouchDevice; -#ifndef Q_OS_WINCE struct QWindowsUser32DLL { QWindowsUser32DLL(); inline void init(); inline bool initTouch(); - typedef BOOL (WINAPI *IsTouchWindow)(HWND, PULONG); + typedef BOOL (WINAPI *IsTouchWindow)(HWND, PULONG); // Windows 7 typedef BOOL (WINAPI *RegisterTouchWindow)(HWND, ULONG); typedef BOOL (WINAPI *UnregisterTouchWindow)(HWND); typedef BOOL (WINAPI *GetTouchInputInfo)(HANDLE, UINT, PVOID, int); typedef BOOL (WINAPI *CloseTouchInputHandle)(HANDLE); - typedef BOOL (WINAPI *SetLayeredWindowAttributes)(HWND, COLORREF, BYTE, DWORD); - typedef BOOL (WINAPI *UpdateLayeredWindow)(HWND, HDC , const POINT *, - const SIZE *, HDC, const POINT *, COLORREF, - const BLENDFUNCTION *, DWORD); - typedef BOOL (WINAPI *UpdateLayeredWindowIndirect)(HWND, const UPDATELAYEREDWINDOWINFO *); - typedef BOOL (WINAPI *IsHungAppWindow)(HWND); typedef BOOL (WINAPI *SetProcessDPIAware)(); typedef BOOL (WINAPI *AddClipboardFormatListener)(HWND); typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND); typedef BOOL (WINAPI *GetDisplayAutoRotationPreferences)(DWORD *); typedef BOOL (WINAPI *SetDisplayAutoRotationPreferences)(DWORD); - // Functions missing in Q_CC_GNU stub libraries. - SetLayeredWindowAttributes setLayeredWindowAttributes; - UpdateLayeredWindow updateLayeredWindow; - - // Functions missing in older versions of Windows - UpdateLayeredWindowIndirect updateLayeredWindowIndirect; - IsHungAppWindow isHungAppWindow; - // Touch functions from Windows 7 onwards (also for use with Q_CC_MSVC). IsTouchWindow isTouchWindow; RegisterTouchWindow registerTouchWindow; @@ -121,7 +106,8 @@ struct QWindowsUser32DLL // Windows Vista onwards SetProcessDPIAware setProcessDPIAware; - // Clipboard listeners, Windows Vista onwards + // Clipboard listeners are present on Windows Vista onwards + // but missing in MinGW 4.9 stub libs. Can be removed in MinGW 5. AddClipboardFormatListener addClipboardFormatListener; RemoveClipboardFormatListener removeClipboardFormatListener; @@ -130,24 +116,6 @@ struct QWindowsUser32DLL SetDisplayAutoRotationPreferences setDisplayAutoRotationPreferences; }; -struct QWindowsShell32DLL -{ - QWindowsShell32DLL(); - inline void init(); - - typedef HRESULT (WINAPI *SHCreateItemFromParsingName)(PCWSTR, IBindCtx *, const GUID&, void **); - typedef HRESULT (WINAPI *SHGetKnownFolderIDList)(const GUID &, DWORD, HANDLE, PIDLIST_ABSOLUTE *); - typedef HRESULT (WINAPI *SHGetStockIconInfo)(int , int , _SHSTOCKICONINFO *); - typedef HRESULT (WINAPI *SHGetImageList)(int, REFIID , void **); - typedef HRESULT (WINAPI *SHCreateItemFromIDList)(PCIDLIST_ABSOLUTE, REFIID, void **); - - SHCreateItemFromParsingName sHCreateItemFromParsingName; - SHGetKnownFolderIDList sHGetKnownFolderIDList; - SHGetStockIconInfo sHGetStockIconInfo; - SHGetImageList sHGetImageList; - SHCreateItemFromIDList sHCreateItemFromIDList; -}; - // Shell scaling library (Windows 8.1 onwards) struct QWindowsShcoreDLL { QWindowsShcoreDLL(); @@ -163,8 +131,6 @@ struct QWindowsShcoreDLL { GetDpiForMonitor getDpiForMonitor; }; -#endif // Q_OS_WINCE - class QWindowsContext { Q_DISABLE_COPY(QWindowsContext) @@ -236,11 +202,9 @@ public: QWindowsMimeConverter &mimeConverter() const; QWindowsScreenManager &screenManager(); QWindowsTabletSupport *tabletSupport() const; -#ifndef Q_OS_WINCE + static QWindowsUser32DLL user32dll; - static QWindowsShell32DLL shell32dll; static QWindowsShcoreDLL shcoredll; -#endif static QByteArray comErrorString(HRESULT hr); bool asyncExpose() const; diff --git a/src/plugins/platforms/windows/qwindowscursor.cpp b/src/plugins/platforms/windows/qwindowscursor.cpp index d02648fade..1bebe88df7 100644 --- a/src/plugins/platforms/windows/qwindowscursor.cpp +++ b/src/plugins/platforms/windows/qwindowscursor.cpp @@ -56,7 +56,7 @@ static bool initResources() { -#if !defined (Q_OS_WINCE) && !defined (QT_NO_IMAGEFORMAT_PNG) +#if !defined (QT_NO_IMAGEFORMAT_PNG) Q_INIT_RESOURCE(cursors); #endif return true; @@ -143,7 +143,6 @@ static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits, if (hotSpot.y() < 0) hotSpot.setY(height / 2); const int n = qMax(1, width / 8); -#if !defined(Q_OS_WINCE) QScopedArrayPointer<uchar> xBits(new uchar[height * n]); QScopedArrayPointer<uchar> xMask(new uchar[height * n]); int x = 0; @@ -164,54 +163,6 @@ static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits, } return CreateCursor(GetModuleHandle(0), hotSpot.x(), hotSpot.y(), width, height, xBits.data(), xMask.data()); -#elif defined(GWES_ICONCURS) // Q_OS_WINCE - // Windows CE only supports fixed cursor size. - int sysW = GetSystemMetrics(SM_CXCURSOR); - int sysH = GetSystemMetrics(SM_CYCURSOR); - int sysN = qMax(1, sysW / 8); - uchar* xBits = new uchar[sysH * sysN]; - uchar* xMask = new uchar[sysH * sysN]; - int x = 0; - for (int i = 0; i < sysH; ++i) { - if (i >= height) { - memset(&xBits[x] , 255, sysN); - memset(&xMask[x] , 0, sysN); - x += sysN; - } else { - int fillWidth = n > sysN ? sysN : n; - const uchar *bits = bbits.constScanLine(i); - const uchar *mask = mbits.constScanLine(i); - for (int j = 0; j < fillWidth; ++j) { - uchar b = bits[j]; - uchar m = mask[j]; - if (invb) - b ^= 0xFF; - if (invm) - m ^= 0xFF; - xBits[x] = ~m; - xMask[x] = b ^ m; - ++x; - } - for (int j = fillWidth; j < sysN; ++j ) { - xBits[x] = 255; - xMask[x] = 0; - ++x; - } - } - } - - HCURSOR hcurs = CreateCursor(qWinAppInst(), hotSpot.x(), hotSpot.y(), sysW, sysH, - xBits, xMask); - delete [] xBits; - delete [] xMask; - return hcurs; -#else - Q_UNUSED(n); - Q_UNUSED(invm); - Q_UNUSED(invb); - Q_UNUSED(mbits); - return 0; -#endif } // Create a cursor from image and mask of the format QImage::Format_Mono. @@ -252,7 +203,7 @@ static QSize systemCursorSize(const QPlatformScreen *screen = Q_NULLPTR) return primaryScreenCursorSize; } -#if defined (Q_OS_WINCE) || defined (QT_NO_IMAGEFORMAT_PNG) +#if defined (QT_NO_IMAGEFORMAT_PNG) static inline QSize standardCursorSize() { return QSize(32, 32); } @@ -468,7 +419,7 @@ QWindowsCursor::PixmapCursor QWindowsCursor::customCursor(Qt::CursorShape cursor return QWindowsCursor::PixmapCursor(); } -#else // Q_OS_WINCE || QT_NO_IMAGEFORMAT_PNG +#else // QT_NO_IMAGEFORMAT_PNG struct QWindowsCustomPngCursor { Qt::CursorShape shape; int size; @@ -526,7 +477,7 @@ QWindowsCursor::PixmapCursor QWindowsCursor::customCursor(Qt::CursorShape cursor QString::fromLatin1(bestFit->fileName)); return PixmapCursor(rawImage, QPoint(bestFit->hotSpotX, bestFit->hotSpotY)); } -#endif // Q_OS_WINCE || QT_NO_IMAGEFORMAT_PNG +#endif // !QT_NO_IMAGEFORMAT_PNG struct QWindowsStandardCursorMapping { Qt::CursorShape shape; @@ -575,13 +526,8 @@ HCURSOR QWindowsCursor::createCursorFromShape(Qt::CursorShape cursorShape, const // Load available standard cursors from resources const QWindowsStandardCursorMapping *sEnd = standardCursors + sizeof(standardCursors) / sizeof(standardCursors[0]); for (const QWindowsStandardCursorMapping *s = standardCursors; s < sEnd; ++s) { - if (s->shape == cursorShape) { -#ifndef Q_OS_WINCE + if (s->shape == cursorShape) return static_cast<HCURSOR>(LoadImage(0, s->resource, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED)); -#else - return LoadCursor(0, s->resource); -#endif - } } qWarning("%s: Invalid cursor shape %d", __FUNCTION__, cursorShape); @@ -677,7 +623,6 @@ QPoint QWindowsCursor::mousePosition() QWindowsCursor::CursorState QWindowsCursor::cursorState() { -#ifndef Q_OS_WINCE enum { cursorShowing = 0x1, cursorSuppressed = 0x2 }; // Windows 8: CURSOR_SUPPRESSED CURSORINFO cursorInfo; cursorInfo.cbSize = sizeof(CURSORINFO); @@ -687,7 +632,6 @@ QWindowsCursor::CursorState QWindowsCursor::cursorState() if (cursorInfo.flags & cursorSuppressed) return CursorSuppressed; } -#endif // !Q_OS_WINCE return CursorHidden; } @@ -758,7 +702,6 @@ QPixmap QWindowsCursor::dragDefaultCursor(Qt::DropAction action) const "...............XXXX....."}; if (m_ignoreDragCursor.isNull()) { -#if !defined (Q_OS_WINCE) HCURSOR cursor = LoadCursor(NULL, IDC_NO); ICONINFO iconInfo = {0, 0, 0, 0, 0}; GetIconInfo(cursor, &iconInfo); @@ -782,9 +725,6 @@ QPixmap QWindowsCursor::dragDefaultCursor(Qt::DropAction action) const DeleteObject(iconInfo.hbmMask); DeleteObject(iconInfo.hbmColor); DestroyCursor(cursor); -#else // !Q_OS_WINCE - m_ignoreDragCursor = QPixmap(ignoreDragCursorXpmC); -#endif // !Q_OS_WINCE } return m_ignoreDragCursor; } diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp index d5e5f87e5c..a237013c87 100644 --- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp +++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp @@ -637,7 +637,6 @@ void QWindowsDialogHelperBase<BaseClass>::stopTimer() } } -#ifndef Q_OS_WINCE // Find a file dialog window created by IFileDialog by process id, window // title and class, which starts with a hash '#'. @@ -673,7 +672,6 @@ static inline HWND findDialogWindow(const QString &title) EnumWindows(findDialogEnumWindowsProc, reinterpret_cast<LPARAM>(&context)); return context.hwnd; } -#endif // !Q_OS_WINCE template <class BaseClass> void QWindowsDialogHelperBase<BaseClass>::hide() @@ -989,24 +987,19 @@ void QWindowsNativeFileDialogBase::setWindowTitle(const QString &title) IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url) { -#ifndef Q_OS_WINCE if (url.isLocalFile()) { - if (!QWindowsContext::shell32dll.sHCreateItemFromParsingName) - return Q_NULLPTR; IShellItem *result = Q_NULLPTR; const QString native = QDir::toNativeSeparators(url.toLocalFile()); const HRESULT hr = - QWindowsContext::shell32dll.sHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()), - NULL, IID_IShellItem, - reinterpret_cast<void **>(&result)); + SHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()), + NULL, IID_IShellItem, + reinterpret_cast<void **>(&result)); if (FAILED(hr)) { qErrnoWarning("%s: SHCreateItemFromParsingName(%s)) failed", __FUNCTION__, qPrintable(url.toString())); return Q_NULLPTR; } return result; } else if (url.scheme() == QLatin1String("clsid")) { - if (!QWindowsContext::shell32dll.sHGetKnownFolderIDList || !QWindowsContext::shell32dll.sHCreateItemFromIDList) - return Q_NULLPTR; // Support for virtual folders via GUID // (see https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457(v=vs.85).aspx) // specified as "clsid:<GUID>" (without '{', '}'). @@ -1017,12 +1010,12 @@ IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url) return Q_NULLPTR; } PIDLIST_ABSOLUTE idList; - HRESULT hr = QWindowsContext::shell32dll.sHGetKnownFolderIDList(uuid, 0, 0, &idList); + HRESULT hr = SHGetKnownFolderIDList(uuid, 0, 0, &idList); if (FAILED(hr)) { qErrnoWarning("%s: SHGetKnownFolderIDList(%s)) failed", __FUNCTION__, qPrintable(url.toString())); return Q_NULLPTR; } - hr = QWindowsContext::shell32dll.sHCreateItemFromIDList(idList, IID_IShellItem, reinterpret_cast<void **>(&result)); + hr = SHCreateItemFromIDList(idList, IID_IShellItem, reinterpret_cast<void **>(&result)); CoTaskMemFree(idList); if (FAILED(hr)) { qErrnoWarning("%s: SHCreateItemFromIDList(%s)) failed", __FUNCTION__, qPrintable(url.toString())); @@ -1032,9 +1025,6 @@ IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url) } else { qWarning() << __FUNCTION__ << ": Unhandled scheme: " << url.scheme(); } -#else // !Q_OS_WINCE - Q_UNUSED(url) -#endif return 0; } @@ -1050,11 +1040,9 @@ void QWindowsNativeFileDialogBase::setDirectory(const QUrl &directory) QString QWindowsNativeFileDialogBase::directory() const { -#ifndef Q_OS_WINCE IShellItem *item = 0; if (m_fileDialog && SUCCEEDED(m_fileDialog->GetFolder(&item)) && item) return QWindowsNativeFileDialogBase::itemPath(item); -#endif return QString(); } @@ -1106,7 +1094,7 @@ void QWindowsNativeFileDialogBase::setMode(QFileDialogOptions::FileMode mode, qErrnoWarning("%s: SetOptions() failed", __FUNCTION__); } -#if !defined(Q_OS_WINCE) && defined(__IShellLibrary_INTERFACE_DEFINED__) // Windows SDK 7 +#if defined(__IShellLibrary_INTERFACE_DEFINED__) // Windows SDK 7 // Helper for "Libraries": collections of folders appearing from Windows 7 // on, visible in the file dialogs. @@ -1159,7 +1147,7 @@ QString QWindowsNativeFileDialogBase::libraryItemDefaultSaveFolder(IShellItem *i return result; } -#else // !Q_OS_WINCE && __IShellLibrary_INTERFACE_DEFINED__ +#else // __IShellLibrary_INTERFACE_DEFINED__ QList<QUrl> QWindowsNativeFileDialogBase::libraryItemFolders(IShellItem *) { @@ -1171,7 +1159,7 @@ QString QWindowsNativeFileDialogBase::libraryItemDefaultSaveFolder(IShellItem *) return QString(); } -#endif // Q_OS_WINCE || !__IShellLibrary_INTERFACE_DEFINED__ +#endif // !__IShellLibrary_INTERFACE_DEFINED__ QString QWindowsNativeFileDialogBase::itemPath(IShellItem *item) { @@ -1417,14 +1405,12 @@ bool QWindowsNativeFileDialogBase::onFileOk() void QWindowsNativeFileDialogBase::close() { m_fileDialog->Close(S_OK); -#ifndef Q_OS_WINCE // IFileDialog::Close() does not work unless invoked from a callback. // Try to find the window and send it a WM_CLOSE in addition. const HWND hwnd = findDialogWindow(m_title); qCDebug(lcQpaDialogs) << __FUNCTION__ << "closing" << hwnd; if (hwnd && IsWindowVisible(hwnd)) PostMessageW(hwnd, WM_CLOSE, 0, 0); -#endif // !Q_OS_WINCE } HRESULT QWindowsNativeFileDialogEventHandler::OnFolderChanging(IFileDialog *, IShellItem *item) @@ -1725,8 +1711,6 @@ QString QWindowsFileDialogHelper::selectedNameFilter() const return m_data.selectedNameFilter(); } -#ifndef Q_OS_WINCE - /*! \class QWindowsXpNativeFileDialog \brief Native Windows directory dialog for Windows XP using SHlib-functions. @@ -2050,8 +2034,6 @@ QString QWindowsXpFileDialogHelper::selectedNameFilter() const return m_data.selectedNameFilter(); } -#endif // Q_OS_WINCE - /*! \class QWindowsNativeColorDialog \brief Native Windows color dialog. @@ -2209,17 +2191,13 @@ QPlatformDialogHelper *createHelper(QPlatformTheme::DialogType type) if (QWindowsIntegration::instance()->options() & QWindowsIntegration::NoNativeDialogs) return 0; switch (type) { - case QPlatformTheme::FileDialog: -#ifndef Q_OS_WINCE // Note: "Windows XP Professional x64 Edition has version number WV_5_2 (WV_2003). + case QPlatformTheme::FileDialog: // Note: "Windows XP Professional x64 Edition has version number WV_5_2 (WV_2003). if (QWindowsIntegration::instance()->options() & QWindowsIntegration::XpNativeDialogs || QSysInfo::windowsVersion() <= QSysInfo::WV_2003) { return new QWindowsXpFileDialogHelper(); } if (QSysInfo::windowsVersion() > QSysInfo::WV_2003) return new QWindowsFileDialogHelper(); -#else - return new QWindowsFileDialogHelper(); -#endif // Q_OS_WINCE case QPlatformTheme::ColorDialog: #ifdef USE_NATIVE_COLOR_DIALOG return new QWindowsColorDialogHelper(); diff --git a/src/plugins/platforms/windows/qwindowseglcontext.cpp b/src/plugins/platforms/windows/qwindowseglcontext.cpp index a3f9f0b44b..23a6f35576 100644 --- a/src/plugins/platforms/windows/qwindowseglcontext.cpp +++ b/src/plugins/platforms/windows/qwindowseglcontext.cpp @@ -95,13 +95,9 @@ static void *resolveFunc(HMODULE lib, const char *name) return proc; } #else -static void *resolveFunc(HMODULE lib, const char *name) +static inline void *resolveFunc(HMODULE lib, const char *name) { -# ifndef Q_OS_WINCE - return (void *) ::GetProcAddress(lib, name); -# else - return (void *) ::GetProcAddress(lib, (const wchar_t *) QString::fromLatin1(name).utf16()); -# endif // Q_OS_WINCE + return ::GetProcAddress(lib, name); } #endif // Q_CC_MINGW @@ -121,7 +117,7 @@ void *QWindowsLibEGL::resolve(const char *name) bool QWindowsLibEGL::init() { const char dllName[] = QT_STRINGIFY(LIBEGL_NAME) -#if defined(QT_DEBUG) && !defined(Q_OS_WINCE) +#if defined(QT_DEBUG) "d" #endif ""; @@ -178,7 +174,7 @@ bool QWindowsLibGLESv2::init() { const char dllName[] = QT_STRINGIFY(LIBGLESV2_NAME) -#if defined(QT_DEBUG) && !defined(Q_OS_WINCE) +#if defined(QT_DEBUG) "d" #endif ""; diff --git a/src/plugins/platforms/windows/qwindowsfontdatabase.cpp b/src/plugins/platforms/windows/qwindowsfontdatabase.cpp index 68a8fc5390..be183034de 100644 --- a/src/plugins/platforms/windows/qwindowsfontdatabase.cpp +++ b/src/plugins/platforms/windows/qwindowsfontdatabase.cpp @@ -57,10 +57,6 @@ #include <wchar.h> -#ifdef Q_OS_WINCE -# include "qplatformfunctions_wince.h" -#endif - #if !defined(QT_NO_DIRECTWRITE) # if defined(QT_USE_DIRECTWRITE2) # include <dwrite_2.h> @@ -947,17 +943,6 @@ static bool addFontToDatabase(const QString &familyName, uchar charSet, quint32 codePageRange[2] = { signature->fsCsb[0], signature->fsCsb[1] }; -#ifdef Q_OS_WINCE - if (signature->fsUsb[0] == 0) { - // If the unicode ranges bit mask is zero then - // EnumFontFamiliesEx failed to determine it properly. - // In this case we just pretend that the font supports all languages. - unicodeRange[0] = 0xbfffffff; // second most significant bit must be zero - unicodeRange[1] = 0xffffffff; - unicodeRange[2] = 0xffffffff; - unicodeRange[3] = 0xffffffff; - } -#endif writingSystems = QPlatformFontDatabase::writingSystemsFromTrueTypeBits(unicodeRange, codePageRange); // ### Hack to work around problem with Thai text on Windows 7. Segoe UI contains // the symbol for Baht, and Windows thus reports that it supports the Thai script. @@ -1117,9 +1102,8 @@ QWindowsFontEngineDataPtr sharedFontData() } #endif // QT_NO_THREAD -#ifndef Q_OS_WINCE extern Q_GUI_EXPORT bool qt_needs_a8_gamma_correction; -#endif + QWindowsFontDatabase::QWindowsFontDatabase() { // Properties accessed by QWin32PrintEngine (Qt Print Support) @@ -1133,9 +1117,7 @@ QWindowsFontDatabase::QWindowsFontDatabase() qCDebug(lcQpaFonts) << __FUNCTION__ << "Clear type: " << data->clearTypeEnabled << "gamma: " << data->fontSmoothingGamma; } -#ifndef Q_OS_WINCE qt_needs_a8_gamma_correction = true; -#endif } QWindowsFontDatabase::~QWindowsFontDatabase() @@ -1586,14 +1568,12 @@ LOGFONT QWindowsFontDatabase::fontDefToLOGFONT(const QFontDef &request) int strat = OUT_DEFAULT_PRECIS; if (request.styleStrategy & QFont::PreferBitmap) { strat = OUT_RASTER_PRECIS; -#ifndef Q_OS_WINCE } else if (request.styleStrategy & QFont::PreferDevice) { strat = OUT_DEVICE_PRECIS; } else if (request.styleStrategy & QFont::PreferOutline) { strat = OUT_OUTLINE_PRECIS; } else if (request.styleStrategy & QFont::ForceOutline) { strat = OUT_TT_ONLY_PRECIS; -#endif } lf.lfOutPrecision = strat; @@ -1602,10 +1582,8 @@ LOGFONT QWindowsFontDatabase::fontDefToLOGFONT(const QFontDef &request) if (request.styleStrategy & QFont::PreferMatch) qual = DRAFT_QUALITY; -#ifndef Q_OS_WINCE else if (request.styleStrategy & QFont::PreferQuality) qual = PROOF_QUALITY; -#endif if (request.styleStrategy & QFont::PreferAntialias) { if (QSysInfo::WindowsVersion >= QSysInfo::WV_XP && !(request.styleStrategy & QFont::NoSubpixelAntialias)) { diff --git a/src/plugins/platforms/windows/qwindowsfontdatabase_ft.cpp b/src/plugins/platforms/windows/qwindowsfontdatabase_ft.cpp index fb75e75dcd..d782519c68 100644 --- a/src/plugins/platforms/windows/qwindowsfontdatabase_ft.cpp +++ b/src/plugins/platforms/windows/qwindowsfontdatabase_ft.cpp @@ -53,10 +53,6 @@ #include <QtGui/QFontDatabase> #include <wchar.h> -#ifdef Q_OS_WINCE -#include <QtCore/QFile> -#include <QtEndian> -#endif QT_BEGIN_NAMESPACE @@ -108,8 +104,6 @@ static FontFile * createFontFile(const QString &fileName, int index) extern bool localizedName(const QString &name); extern QString getEnglishName(const QString &familyName); -#ifndef Q_OS_WINCE - namespace { struct FontKey { @@ -165,223 +159,6 @@ static const FontKey *findFontKey(const QString &name, int *indexIn = Q_NULLPTR) return Q_NULLPTR; } -#else // Q_OS_WINCE - -typedef struct { - quint16 majorVersion; - quint16 minorVersion; - quint16 numTables; - quint16 searchRange; - quint16 entrySelector; - quint16 rangeShift; -} OFFSET_TABLE; - -typedef struct { - quint32 tag; - quint32 checkSum; - quint32 offset; - quint32 length; -} TABLE_DIRECTORY; - -typedef struct { - quint16 fontSelector; - quint16 nrCount; - quint16 storageOffset; -} NAME_TABLE_HEADER; - -typedef struct { - quint16 platformID; - quint16 encodingID; - quint16 languageID; - quint16 nameID; - quint16 stringLength; - quint16 stringOffset; -} NAME_RECORD; - -typedef struct { - quint32 tag; - quint16 majorVersion; - quint16 minorVersion; - quint32 numFonts; -} TTC_TABLE_HEADER; - -static QString fontNameFromTTFile(const QString &filename, int startPos = 0) -{ - QFile f(filename); - QString retVal; - qint64 bytesRead; - qint64 bytesToRead; - - if (f.open(QIODevice::ReadOnly)) { - f.seek(startPos); - OFFSET_TABLE ttOffsetTable; - bytesToRead = sizeof(OFFSET_TABLE); - bytesRead = f.read((char*)&ttOffsetTable, bytesToRead); - if (bytesToRead != bytesRead) - return retVal; - ttOffsetTable.numTables = qFromBigEndian(ttOffsetTable.numTables); - ttOffsetTable.majorVersion = qFromBigEndian(ttOffsetTable.majorVersion); - ttOffsetTable.minorVersion = qFromBigEndian(ttOffsetTable.minorVersion); - - if (ttOffsetTable.majorVersion != 1 || ttOffsetTable.minorVersion != 0) - return retVal; - - TABLE_DIRECTORY tblDir; - bool found = false; - - for (int i = 0; i < ttOffsetTable.numTables; i++) { - bytesToRead = sizeof(TABLE_DIRECTORY); - bytesRead = f.read((char*)&tblDir, bytesToRead); - if (bytesToRead != bytesRead) - return retVal; - if (qFromBigEndian(tblDir.tag) == MAKE_TAG('n', 'a', 'm', 'e')) { - found = true; - tblDir.length = qFromBigEndian(tblDir.length); - tblDir.offset = qFromBigEndian(tblDir.offset); - break; - } - } - - if (found) { - f.seek(tblDir.offset); - NAME_TABLE_HEADER ttNTHeader; - bytesToRead = sizeof(NAME_TABLE_HEADER); - bytesRead = f.read((char*)&ttNTHeader, bytesToRead); - if (bytesToRead != bytesRead) - return retVal; - ttNTHeader.nrCount = qFromBigEndian(ttNTHeader.nrCount); - ttNTHeader.storageOffset = qFromBigEndian(ttNTHeader.storageOffset); - NAME_RECORD ttRecord; - found = false; - - for (int i = 0; i < ttNTHeader.nrCount; i++) { - bytesToRead = sizeof(NAME_RECORD); - bytesRead = f.read((char*)&ttRecord, bytesToRead); - if (bytesToRead != bytesRead) - return retVal; - ttRecord.nameID = qFromBigEndian(ttRecord.nameID); - if (ttRecord.nameID == 1) { - ttRecord.stringLength = qFromBigEndian(ttRecord.stringLength); - ttRecord.stringOffset = qFromBigEndian(ttRecord.stringOffset); - int nPos = f.pos(); - f.seek(tblDir.offset + ttRecord.stringOffset + ttNTHeader.storageOffset); - - QByteArray nameByteArray = f.read(ttRecord.stringLength); - if (!nameByteArray.isEmpty()) { - if (ttRecord.encodingID == 256 || ttRecord.encodingID == 768) { - //This is UTF-16 in big endian - int stringLength = ttRecord.stringLength / 2; - retVal.resize(stringLength); - QChar *data = retVal.data(); - const ushort *srcData = (const ushort *)nameByteArray.data(); - for (int i = 0; i < stringLength; ++i) - data[i] = qFromBigEndian(srcData[i]); - return retVal; - } else if (ttRecord.encodingID == 0) { - //This is Latin1 - retVal = QString::fromLatin1(nameByteArray); - } else { - qWarning("Could not retrieve Font name from file: %s", qPrintable(QDir::toNativeSeparators(filename))); - } - break; - } - f.seek(nPos); - } - } - } - f.close(); - } - return retVal; -} - -static QStringList fontNamesFromTTCFile(const QString &filename) -{ - QFile f(filename); - QStringList retVal; - qint64 bytesRead; - qint64 bytesToRead; - - if (f.open(QIODevice::ReadOnly)) { - TTC_TABLE_HEADER ttcTableHeader; - bytesToRead = sizeof(TTC_TABLE_HEADER); - bytesRead = f.read((char*)&ttcTableHeader, bytesToRead); - if (bytesToRead != bytesRead) - return retVal; - ttcTableHeader.majorVersion = qFromBigEndian(ttcTableHeader.majorVersion); - ttcTableHeader.minorVersion = qFromBigEndian(ttcTableHeader.minorVersion); - ttcTableHeader.numFonts = qFromBigEndian(ttcTableHeader.numFonts); - - if (ttcTableHeader.majorVersion < 1 || ttcTableHeader.majorVersion > 2) - return retVal; - QVarLengthArray<quint32> offsetTable(ttcTableHeader.numFonts); - bytesToRead = sizeof(quint32) * ttcTableHeader.numFonts; - bytesRead = f.read((char*)offsetTable.data(), bytesToRead); - if (bytesToRead != bytesRead) - return retVal; - f.close(); - for (int i = 0; i < (int)ttcTableHeader.numFonts; ++i) - retVal << fontNameFromTTFile(filename, qFromBigEndian(offsetTable[i])); - } - return retVal; -} - -static inline QString fontSettingsOrganization() { return QStringLiteral("Qt-Project"); } -static inline QString fontSettingsApplication() { return QStringLiteral("Qtbase"); } -static inline QString fontSettingsGroup() { return QStringLiteral("CEFontCache"); } - -static QString findFontFile(const QString &faceName) -{ - static QHash<QString, QString> fontCache; - - if (fontCache.isEmpty()) { - QSettings settings(QSettings::SystemScope, fontSettingsOrganization(), fontSettingsApplication()); - settings.beginGroup(fontSettingsGroup()); - foreach (const QString &fontName, settings.allKeys()) - fontCache.insert(fontName, settings.value(fontName).toString()); - settings.endGroup(); - } - - QString value = fontCache.value(faceName); - - //Fallback if we haven't cached the font yet or the font got removed/renamed iterate again over all fonts - if (value.isEmpty() || !QFile::exists(value)) { - QSettings settings(QSettings::SystemScope, fontSettingsOrganization(), fontSettingsApplication()); - settings.beginGroup(fontSettingsGroup()); - - //empty the cache first, as it seems that it is dirty - settings.remove(QString()); - - QDirIterator it(QStringLiteral("/Windows"), QStringList() << QStringLiteral("*.ttf") << QStringLiteral("*.ttc"), QDir::Files | QDir::Hidden | QDir::System); - const QLatin1Char lowerF('f'); - const QLatin1Char upperF('F'); - while (it.hasNext()) { - const QString fontFile = it.next(); - QStringList fontNames; - const QChar c = fontFile[fontFile.size() - 1]; - if (c == lowerF || c == upperF) - fontNames << fontNameFromTTFile(fontFile); - else - fontNames << fontNamesFromTTCFile(fontFile); - foreach (const QString fontName, fontNames) { - if (fontName.isEmpty()) - continue; - fontCache.insert(fontName, fontFile); - settings.setValue(fontName, fontFile); - - if (localizedName(fontName)) { - QString englishFontName = getEnglishName(fontName); - fontCache.insert(englishFontName, fontFile); - settings.setValue(englishFontName, fontFile); - } - } - } - settings.endGroup(); - value = fontCache.value(faceName); - } - return value; -} -#endif // Q_OS_WINCE - static bool addFontToDatabase(const QString &faceName, const QString &fullName, uchar charSet, @@ -453,7 +230,6 @@ static bool addFontToDatabase(const QString &faceName, } int index = 0; -#ifndef Q_OS_WINCE const FontKey *key = findFontKey(faceName, &index); if (!key) { key = findFontKey(fullName, &index); @@ -465,19 +241,11 @@ static bool addFontToDatabase(const QString &faceName, return false; } QString value = key->fileName; -#else - QString value = findFontFile(faceName); -#endif - if (value.isEmpty()) return false; if (!QDir::isAbsolutePath(value)) -#ifndef Q_OS_WINCE value.prepend(QFile::decodeName(qgetenv("windir") + "\\Fonts\\")); -#else - value.prepend(QFile::decodeName("/Windows/")); -#endif QPlatformFontDatabase::registerFont(faceName, QString(), foundryName, weight, style, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(value, index)); @@ -501,24 +269,6 @@ static bool addFontToDatabase(const QString &faceName, return true; } -#ifdef Q_OS_WINCE -static QByteArray getFntTable(HFONT hfont, uint tag) -{ - HDC hdc = GetDC(0); - HGDIOBJ oldFont = SelectObject(hdc, hfont); - quint32 t = qFromBigEndian<quint32>(tag); - QByteArray buffer; - - DWORD length = GetFontData(hdc, t, 0, NULL, 0); - if (length != GDI_ERROR) { - buffer.resize(length); - GetFontData(hdc, t, 0, reinterpret_cast<uchar *>(buffer.data()), length); - } - SelectObject(hdc, oldFont); - return buffer; -} -#endif - static int QT_WIN_CALLBACK storeFont(const LOGFONT *logFont, const TEXTMETRIC *textmetric, DWORD type, LPARAM) { @@ -576,8 +326,6 @@ struct PopulateFamiliesContext }; } // namespace -#ifndef Q_OS_WINCE - // Delayed population of font families static int QT_WIN_CALLBACK populateFontFamilies(const LOGFONT *logFont, const TEXTMETRIC *textmetric, @@ -633,74 +381,6 @@ void QWindowsFontDatabaseFT::populateFontDatabase() QPlatformFontDatabase::registerFontFamily(context.systemDefaultFont); } -#else // !Q_OS_WINCE - -// Non-delayed population of fonts (Windows CE). - -static int QT_WIN_CALLBACK populateFontCe(ENUMLOGFONTEX* f, NEWTEXTMETRICEX *textmetric, - int type, LPARAM lparam) -{ - // the "@family" fonts are just the same as "family". Ignore them. - const wchar_t *faceNameW = f->elfLogFont.lfFaceName; - if (faceNameW[0] && faceNameW[0] != L'@' && wcsncmp(faceNameW, L"WST_", 4)) { - const uchar charSet = f->elfLogFont.lfCharSet; - - FONTSIGNATURE signature; - QByteArray table; - - if (type & TRUETYPE_FONTTYPE) { - HFONT hfont = CreateFontIndirect(&f->elfLogFont); - table = getFntTable(hfont, MAKE_TAG('O', 'S', '/', '2')); - DeleteObject((HGDIOBJ)hfont); - } - - if (table.length() >= 86) { - // See also qfontdatabase_mac.cpp, offsets taken from OS/2 table in the TrueType spec - uchar *tableData = reinterpret_cast<uchar *>(table.data()); - - signature.fsUsb[0] = qFromBigEndian<quint32>(tableData + 42); - signature.fsUsb[1] = qFromBigEndian<quint32>(tableData + 46); - signature.fsUsb[2] = qFromBigEndian<quint32>(tableData + 50); - signature.fsUsb[3] = qFromBigEndian<quint32>(tableData + 54); - - signature.fsCsb[0] = qFromBigEndian<quint32>(tableData + 78); - signature.fsCsb[1] = qFromBigEndian<quint32>(tableData + 82); - } else { - memset(&signature, 0, sizeof(signature)); - } - - // NEWTEXTMETRICEX is a NEWTEXTMETRIC, which according to the documentation is - // identical to a TEXTMETRIC except for the last four members, which we don't use - // anyway - const QString faceName = QString::fromWCharArray(f->elfLogFont.lfFaceName); - if (addFontToDatabase(faceName, QString::fromWCharArray(f->elfFullName), - charSet, (TEXTMETRIC *)textmetric, &signature, type, true)) { - PopulateFamiliesContext *context = reinterpret_cast<PopulateFamiliesContext *>(lparam); - if (!context->seenSystemDefaultFont && faceName == context->systemDefaultFont) - context->seenSystemDefaultFont = true; - } - } - - // keep on enumerating - return 1; -} - -void QWindowsFontDatabaseFT::populateFontDatabase() -{ - LOGFONT lf; - lf.lfCharSet = DEFAULT_CHARSET; - HDC dummy = GetDC(0); - lf.lfFaceName[0] = 0; - lf.lfPitchAndFamily = 0; - PopulateFamiliesContext context(QWindowsFontDatabase::systemDefaultFont().family()); - EnumFontFamiliesEx(dummy, &lf, (FONTENUMPROC)populateFontCe, reinterpret_cast<LPARAM>(&context), 0); - ReleaseDC(0, dummy); - // Work around EnumFontFamiliesEx() not listing the system font, see below. - if (!context.seenSystemDefaultFont) - populateFamily(context.systemDefaultFont); -} -#endif // Q_OS_WINCE - QFontEngine * QWindowsFontDatabaseFT::fontEngine(const QFontDef &fontDef, void *handle) { QFontEngine *fe = QBasicFontDatabase::fontEngine(fontDef, handle); @@ -718,21 +398,8 @@ QFontEngine *QWindowsFontDatabaseFT::fontEngine(const QByteArray &fontData, qrea QStringList QWindowsFontDatabaseFT::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const { QStringList result; - result.append(QWindowsFontDatabase::familyForStyleHint(styleHint)); - -#ifdef Q_OS_WINCE - QSettings settings(QLatin1String("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\FontLink\\SystemLink"), QSettings::NativeFormat); - const QStringList fontList = settings.value(family).toStringList(); - foreach (const QString &fallback, fontList) { - const int sep = fallback.indexOf(QLatin1Char(',')); - if (sep > 0) - result << fallback.mid(sep + 1); - } -#endif - result.append(QWindowsFontDatabase::extraTryFontsForFamily(family)); - result.append(QBasicFontDatabase::fallbacksForFamily(family, style, styleHint, script)); qCDebug(lcQpaFonts) << __FUNCTION__ << family << style << styleHint diff --git a/src/plugins/platforms/windows/qwindowsfontengine.cpp b/src/plugins/platforms/windows/qwindowsfontengine.cpp index 806af6458b..0c213b933c 100644 --- a/src/plugins/platforms/windows/qwindowsfontengine.cpp +++ b/src/plugins/platforms/windows/qwindowsfontengine.cpp @@ -65,10 +65,6 @@ #include <limits.h> -#ifdef Q_OS_WINCE -# include "qplatformfunctions_wince.h" -#endif - #if !defined(QT_NO_DIRECTWRITE) # include <dwrite.h> #endif @@ -205,9 +201,6 @@ int QWindowsFontEngine::getGlyphIndexes(const QChar *str, int numChars, QGlyphLa { int glyph_pos = 0; { -#if defined(Q_OS_WINCE) - { -#else if (symbol) { QStringIterator it(str, str + numChars); while (it.hasNext()) { @@ -225,7 +218,6 @@ int QWindowsFontEngine::getGlyphIndexes(const QChar *str, int numChars, QGlyphLa ++glyph_pos; } } else { -#endif QStringIterator it(str, str + numChars); while (it.hasNext()) { const uint uc = it.next(); @@ -329,21 +321,16 @@ QWindowsFontEngine::~QWindowsFontEngine() glyph_t QWindowsFontEngine::glyphIndex(uint ucs4) const { - glyph_t glyph; + glyph_t glyph = 0; -#if !defined(Q_OS_WINCE) if (symbol) { glyph = getTrueTypeGlyphIndex(cmap, cmapSize, ucs4); if (glyph == 0 && ucs4 < 0x100) glyph = getTrueTypeGlyphIndex(cmap, cmapSize, ucs4 + 0xf000); } else if (ttf) { glyph = getTrueTypeGlyphIndex(cmap, cmapSize, ucs4); - } else -#endif - if (ucs4 >= tm.tmFirstChar && ucs4 <= tm.tmLastChar) { + } else if (ucs4 >= tm.tmFirstChar && ucs4 <= tm.tmLastChar) { glyph = ucs4; - } else { - glyph = 0; } return glyph; @@ -377,12 +364,8 @@ bool QWindowsFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *g inline void calculateTTFGlyphWidth(HDC hdc, UINT glyph, int &width) { -#if defined(Q_OS_WINCE) - GetCharWidth32(hdc, glyph, glyph, &width); -#else if (ptrGetCharWidthI) ptrGetCharWidthI(hdc, glyph, 1, 0, &width); -#endif } void QWindowsFontEngine::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const @@ -467,7 +450,7 @@ glyph_metrics_t QWindowsFontEngine::boundingBox(const QGlyphLayout &glyphs) return glyph_metrics_t(0, -tm.tmAscent, w - lastRightBearing(glyphs), tm.tmHeight, w, 0); } -#ifndef Q_OS_WINCE + bool QWindowsFontEngine::getOutlineMetrics(glyph_t glyph, const QTransform &t, glyph_metrics_t *metrics) const { Q_ASSERT(metrics != 0); @@ -520,11 +503,9 @@ bool QWindowsFontEngine::getOutlineMetrics(glyph_t glyph, const QTransform &t, g return false; } } -#endif glyph_metrics_t QWindowsFontEngine::boundingBox(glyph_t glyph, const QTransform &t) { -#ifndef Q_OS_WINCE HDC hdc = m_fontEngineData->hdc; SelectObject(hdc, hfont); @@ -542,34 +523,6 @@ glyph_metrics_t QWindowsFontEngine::boundingBox(glyph_t glyph, const QTransform } return glyphMetrics; -#else - HDC hdc = m_fontEngineData->hdc; - HGDIOBJ oldFont = SelectObject(hdc, hfont); - - ABC abc; - int width; - int advance; -#ifdef GWES_MGTT // true type fonts - if (GetCharABCWidths(hdc, glyph, glyph, &abc)) { - width = qAbs(abc.abcA) + abc.abcB + qAbs(abc.abcC); - advance = abc.abcA + abc.abcB + abc.abcC; - } - else -#endif -#if defined(GWES_MGRAST) || defined(GWES_MGRAST2) // raster fonts - if (GetCharWidth32(hdc, glyph, glyph, &width)) { - advance = width; - } - else -#endif - { // fallback - width = tm.tmMaxCharWidth; - advance = width; - } - - SelectObject(hdc, oldFont); - return glyph_metrics_t(0, -tm.tmAscent, width, tm.tmHeight, advance, 0).transformed(t); -#endif } QFixed QWindowsFontEngine::ascent() const @@ -636,22 +589,16 @@ void QWindowsFontEngine::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qre HDC hdc = m_fontEngineData->hdc; SelectObject(hdc, hfont); -#ifndef Q_OS_WINCE - if (ttf) -#endif - { + if (ttf) { ABC abcWidths; GetCharABCWidthsI(hdc, glyph, 1, 0, &abcWidths); if (leftBearing) *leftBearing = abcWidths.abcA; if (rightBearing) *rightBearing = abcWidths.abcC; - } -#ifndef Q_OS_WINCE - else { + } else { QFontEngine::getGlyphBearings(glyph, leftBearing, rightBearing); } -#endif } #endif // Q_CC_MINGW @@ -670,7 +617,6 @@ qreal QWindowsFontEngine::minLeftBearing() const qreal QWindowsFontEngine::minRightBearing() const { -#ifndef Q_OS_WINCE if (rbearing == SHRT_MIN) { int ml = 0; int mr = 0; @@ -726,40 +672,6 @@ qreal QWindowsFontEngine::minRightBearing() const } return rbearing; -#else // !Q_OS_WINCE - if (rbearing == SHRT_MIN) { - int ml = 0; - int mr = 0; - HDC hdc = m_fontEngineData->hdc; - SelectObject(hdc, hfont); - if (ttf) { - ABC *abc = 0; - int n = tm.tmLastChar - tm.tmFirstChar; - if (n <= max_font_count) { - abc = new ABC[n+1]; - GetCharABCWidths(hdc, tm.tmFirstChar, tm.tmLastChar, abc); - } else { - abc = new ABC[char_table_entries+1]; - for (int i = 0; i < char_table_entries; i++) - GetCharABCWidths(hdc, char_table[i], char_table[i], abc+i); - n = char_table_entries; - } - ml = abc[0].abcA; - mr = abc[0].abcC; - for (int i = 1; i < n; i++) { - if (abc[i].abcA + abc[i].abcB + abc[i].abcC != 0) { - ml = qMin(ml,abc[i].abcA); - mr = qMin(mr,abc[i].abcC); - } - } - delete [] abc; - } - lbearing = ml; - rbearing = mr; - } - - return rbearing; -#endif // Q_OS_WINCE } static inline double qt_fixed_to_double(const FIXED &p) { @@ -786,7 +698,6 @@ static bool addGlyphToPath(glyph_t glyph, const QFixedPoint &position, HDC hdc, GLYPHMETRICS gMetric; memset(&gMetric, 0, sizeof(GLYPHMETRICS)); -#ifndef Q_OS_WINCE if (metric) { // If metrics requested, retrieve first using GGO_METRICS, because the returned // values are incorrect for OpenType PS fonts if obtained at the same time as the @@ -801,7 +712,6 @@ static bool addGlyphToPath(glyph_t glyph, const QFixedPoint &position, HDC hdc, int(gMetric.gmBlackBoxX), int(gMetric.gmBlackBoxY), gMetric.gmCellIncX, gMetric.gmCellIncY); } -#endif uint glyphFormat = GGO_NATIVE; @@ -820,15 +730,6 @@ static bool addGlyphToPath(glyph_t glyph, const QFixedPoint &position, HDC hdc, return false; } -#ifdef Q_OS_WINCE - if (metric) { - // #### obey scale - *metric = glyph_metrics_t(gMetric.gmptGlyphOrigin.x, -gMetric.gmptGlyphOrigin.y, - (int)gMetric.gmBlackBoxX, (int)gMetric.gmBlackBoxY, - gMetric.gmCellIncX, gMetric.gmCellIncY); - } -#endif - DWORD offset = 0; DWORD headerOffset = 0; @@ -1052,7 +953,6 @@ QWindowsNativeImage *QWindowsFontEngine::drawGDIGlyph(HFONT font, glyph_t glyph, bool has_transformation = t.type() > QTransform::TxTranslate; -#ifndef Q_OS_WINCE unsigned int options = ttf ? ETO_GLYPH_INDEX : 0; XFORM xform; @@ -1095,13 +995,6 @@ QWindowsNativeImage *QWindowsFontEngine::drawGDIGlyph(HFONT font, glyph_t glyph, xform.eDx -= tgm.gmptGlyphOrigin.x; xform.eDy += tgm.gmptGlyphOrigin.y; } -#else // else wince - unsigned int options = 0; - if (has_transformation) { - qWarning() << "QWindowsFontEngine is unable to apply transformations other than translations for fonts on Windows CE." - << "If you need them anyway, start your application with -platform windows:fontengine=freetype."; - } -#endif // wince // The padding here needs to be kept in sync with the values in alphaMapBoundingBox. QWindowsNativeImage *ni = new QWindowsNativeImage(iw + 2 * margin, @@ -1123,14 +1016,11 @@ QWindowsNativeImage *QWindowsFontEngine::drawGDIGlyph(HFONT font, glyph_t glyph, HGDIOBJ old_font = SelectObject(hdc, font); -#ifndef Q_OS_WINCE if (has_transformation) { SetGraphicsMode(hdc, GM_ADVANCED); SetWorldTransform(hdc, &xform); ExtTextOut(hdc, 0, 0, options, 0, reinterpret_cast<LPCWSTR>(&glyph), 1, 0); - } else -#endif // !Q_OS_WINCE - { + } else { ExtTextOut(hdc, -gx + margin, -gy + margin, options, 0, reinterpret_cast<LPCWSTR>(&glyph), 1, 0); } diff --git a/src/plugins/platforms/windows/qwindowsglcontext.cpp b/src/plugins/platforms/windows/qwindowsglcontext.cpp index cc2f05b6d1..48439e9523 100644 --- a/src/plugins/platforms/windows/qwindowsglcontext.cpp +++ b/src/plugins/platforms/windows/qwindowsglcontext.cpp @@ -149,15 +149,9 @@ QT_BEGIN_NAMESPACE QWindowsOpengl32DLL QOpenGLStaticContext::opengl32; -void *QWindowsOpengl32DLL::resolve(const char *name) +FARPROC QWindowsOpengl32DLL::resolve(const char *name) { -#ifndef Q_OS_WINCE - void *proc = m_lib ? (void *) ::GetProcAddress(m_lib, name) : 0; -#else - void *proc = m_lib ? (void *) ::GetProcAddress(m_lib, (const wchar_t *) QString::fromLatin1(name).utf16()) : 0; -#endif - - return proc; + return m_lib ? ::GetProcAddress(m_lib, name) : nullptr; } bool QWindowsOpengl32DLL::init(bool softwareRendering) diff --git a/src/plugins/platforms/windows/qwindowsglcontext.h b/src/plugins/platforms/windows/qwindowsglcontext.h index e8c78860f2..3ebf5b7bc2 100644 --- a/src/plugins/platforms/windows/qwindowsglcontext.h +++ b/src/plugins/platforms/windows/qwindowsglcontext.h @@ -122,7 +122,7 @@ struct QWindowsOpengl32DLL void (APIENTRY * glGetIntegerv)(GLenum pname, GLint* params); const GLubyte * (APIENTRY * glGetString)(GLenum name); - void *resolve(const char *name); + FARPROC resolve(const char *name); private: HMODULE m_lib; bool m_nonOpengl32; diff --git a/src/plugins/platforms/windows/qwindowsinputcontext.cpp b/src/plugins/platforms/windows/qwindowsinputcontext.cpp index 501b956a68..8adbd494c4 100644 --- a/src/plugins/platforms/windows/qwindowsinputcontext.cpp +++ b/src/plugins/platforms/windows/qwindowsinputcontext.cpp @@ -190,11 +190,7 @@ bool QWindowsInputContext::hasCapability(Capability capability) const { switch (capability) { case QPlatformInputContext::HiddenTextCapability: -#ifndef Q_OS_WINCE return false; // QTBUG-40691, do not show IME on desktop for password entry fields. -#else - break; // Windows CE: Show software keyboard. -#endif default: break; } diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index b524adb2cb..8a229db812 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -64,7 +64,7 @@ #include <qpa/qplatformnativeinterface.h> #include <qpa/qwindowsysteminterface.h> -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#ifndef QT_NO_SESSIONMANAGER # include "qwindowssessionmanager.h" #endif #include <QtGui/qtouchdevice.h> @@ -102,7 +102,7 @@ QT_BEGIN_NAMESPACE It should compile with: \list - \li Microsoft Visual Studio 2008 or later (using the Microsoft Windows SDK, + \li Microsoft Visual Studio 2013 or later (using the Microsoft Windows SDK, (\c Q_CC_MSVC). \li Stock \l{http://mingw.org/}{MinGW} (\c Q_CC_MINGW). This version ships with headers that are missing a lot of WinAPI. @@ -112,7 +112,6 @@ QT_BEGIN_NAMESPACE (\c Q_CC_MINGW and \c __MINGW64_VERSION_MAJOR indicating the version). MinGW-w64 provides more complete headers (compared to stock MinGW from mingw.org), including a considerable part of the Windows SDK. - \li Visual Studio 2008 for Windows Embedded (\c Q_OS_WINCE). \endlist The file \c qtwindows_additional.h contains defines and declarations that @@ -214,9 +213,7 @@ QWindowsIntegrationPrivate::QWindowsIntegrationPrivate(const QStringList ¶mL : m_options(0) , m_fontDatabase(0) { -#ifndef Q_OS_WINCE Q_INIT_RESOURCE(openglblacklists); -#endif static bool dpiAwarenessSet = false; int tabletAbsoluteRange = -1; @@ -470,46 +467,16 @@ QWindowsStaticOpenGLContext *QWindowsIntegration::staticOpenGLContext() } #endif // !QT_NO_OPENGL -/* Workaround for QTBUG-24205: In 'Auto', pick the FreeType engine for - * QML2 applications. */ - -#ifdef Q_OS_WINCE -// It's not easy to detect if we are running a QML application -// Let's try to do so by checking if the Qt Quick module is loaded. -inline bool isQMLApplication() -{ - // check if the Qt Quick module is loaded -#ifdef _DEBUG - HMODULE handle = GetModuleHandle(L"Qt5Quick" QT_LIBINFIX L"d.dll"); -#else - HMODULE handle = GetModuleHandle(L"Qt5Quick" QT_LIBINFIX L".dll"); -#endif - return (handle != NULL); -} -#endif - QPlatformFontDatabase *QWindowsIntegration::fontDatabase() const { if (!d->m_fontDatabase) { #ifdef QT_NO_FREETYPE d->m_fontDatabase = new QWindowsFontDatabase(); #else // QT_NO_FREETYPE - if (d->m_options & QWindowsIntegration::FontDatabaseFreeType) { + if (d->m_options & QWindowsIntegration::FontDatabaseFreeType) d->m_fontDatabase = new QWindowsFontDatabaseFT; - } else if (d->m_options & QWindowsIntegration::FontDatabaseNative){ - d->m_fontDatabase = new QWindowsFontDatabase; - } else { -#ifndef Q_OS_WINCE + else d->m_fontDatabase = new QWindowsFontDatabase; -#else - if (isQMLApplication()) { - qCDebug(lcQpaFonts) << "QML application detected, using FreeType rendering"; - d->m_fontDatabase = new QWindowsFontDatabaseFT; - } - else - d->m_fontDatabase = new QWindowsFontDatabase; -#endif - } #endif // QT_NO_FREETYPE } return d->m_fontDatabase; @@ -597,7 +564,7 @@ unsigned QWindowsIntegration::options() const return d->m_options; } -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) QPlatformSessionManager *QWindowsIntegration::createPlatformSessionManager(const QString &id, const QString &key) const { return new QWindowsSessionManager(id, key); diff --git a/src/plugins/platforms/windows/qwindowsintegration.h b/src/plugins/platforms/windows/qwindowsintegration.h index 9658ef711d..437253cedc 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.h +++ b/src/plugins/platforms/windows/qwindowsintegration.h @@ -104,7 +104,7 @@ public: void beep() const Q_DECL_OVERRIDE; -#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) +#if !defined(QT_NO_SESSIONMANAGER) QPlatformSessionManager *createPlatformSessionManager(const QString &id, const QString &key) const Q_DECL_OVERRIDE; #endif diff --git a/src/plugins/platforms/windows/qwindowsinternalmimedata.cpp b/src/plugins/platforms/windows/qwindowsinternalmimedata.cpp index 2dfe7fc5f9..21ebee6262 100644 --- a/src/plugins/platforms/windows/qwindowsinternalmimedata.cpp +++ b/src/plugins/platforms/windows/qwindowsinternalmimedata.cpp @@ -39,7 +39,6 @@ #include "qwindowsinternalmimedata.h" #include "qwindowscontext.h" -#include "qplatformfunctions_wince.h" #include "qwindowsmime.h" #include <QDebug> /*! diff --git a/src/plugins/platforms/windows/qwindowskeymapper.cpp b/src/plugins/platforms/windows/qwindowskeymapper.cpp index 5b2370b69d..8d6e83298e 100644 --- a/src/plugins/platforms/windows/qwindowskeymapper.cpp +++ b/src/plugins/platforms/windows/qwindowskeymapper.cpp @@ -552,34 +552,6 @@ inline quint32 winceKeyBend(quint32 keyCode) return KeyTbl[keyCode]; } -#ifdef Q_OS_WINCE -QT_BEGIN_INCLUDE_NAMESPACE -int ToUnicode(UINT vk, int /*scancode*/, unsigned char* /*kbdBuffer*/, LPWSTR unicodeBuffer, int, int) -{ - QT_USE_NAMESPACE - QChar* buf = reinterpret_cast< QChar*>(unicodeBuffer); - if (KeyTbl[vk] == 0) { - buf[0] = vk; - return 1; - } - return 0; -} - -int ToAscii(UINT vk, int scancode, unsigned char *kbdBuffer, LPWORD unicodeBuffer, int flag) -{ - return ToUnicode(vk, scancode, kbdBuffer, (LPWSTR) unicodeBuffer, 0, flag); - -} - -bool GetKeyboardState(unsigned char* kbuffer) -{ - for (int i=0; i< 256; ++i) - kbuffer[i] = GetAsyncKeyState(i); - return true; -} -QT_END_INCLUDE_NAMESPACE -#endif // Q_OS_WINCE - // Translate a VK into a Qt key code, or unicode character static inline quint32 toKeyOrUnicode(quint32 vk, quint32 scancode, unsigned char *kbdBuffer, bool *isDeadkey = 0) { @@ -780,7 +752,6 @@ static void showSystemMenu(QWindow* w) if (!menu) return; // no menu for this window -#ifndef Q_OS_WINCE #define enabled (MF_BYCOMMAND | MF_ENABLED) #define disabled (MF_BYCOMMAND | MF_GRAYED) @@ -805,7 +776,6 @@ static void showSystemMenu(QWindow* w) #undef enabled #undef disabled -#endif // !Q_OS_WINCE const QPoint pos = QHighDpi::toNativePixels(topLevel->geometry().topLeft(), topLevel); const int ret = TrackPopupMenuEx(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD, @@ -1172,7 +1142,6 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, const MSG &ms modifiers, scancode, quint32(msg.wParam), nModifiers, text, false); result =true; bool store = true; -#ifndef Q_OS_WINCE // Alt+<alphanumerical> go to the Win32 menu system if unhandled by Qt if (msgType == WM_SYSKEYDOWN && !result && a) { HWND parent = GetParent(QWindowsWindow::handleOf(receiver)); @@ -1186,7 +1155,6 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, const MSG &ms parent = GetParent(parent); } } -#endif // !Q_OS_WINCE if (!store) key_recorder.findKey(int(msg.wParam), true); } @@ -1216,7 +1184,6 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, const MSG &ms nModifiers, (rec ? rec->text : QString()), false); result = true; -#ifndef Q_OS_WINCE // don't pass Alt to Windows unless we are embedded in a non-Qt window if (code == Qt::Key_Alt) { const QWindowsContext *context = QWindowsContext::instance(); @@ -1229,7 +1196,6 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, const MSG &ms parent = GetParent(parent); } } -#endif } } return result; diff --git a/src/plugins/platforms/windows/qwindowsmime.cpp b/src/plugins/platforms/windows/qwindowsmime.cpp index cb112446fc..d6375693d8 100644 --- a/src/plugins/platforms/windows/qwindowsmime.cpp +++ b/src/plugins/platforms/windows/qwindowsmime.cpp @@ -403,11 +403,9 @@ QDebug operator<<(QDebug d, const FORMATETC &tc) case CF_UNICODETEXT: d << "CF_UNICODETEXT"; break; -#ifndef Q_OS_WINCE case CF_ENHMETAFILE: d << "CF_ENHMETAFILE"; break; -#endif // !Q_OS_WINCE default: d << QWindowsMimeConverter::clipboardFormatName(tc.cfFormat); break; diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.cpp b/src/plugins/platforms/windows/qwindowsmousehandler.cpp index 78fff65d84..fcba0d9e9b 100644 --- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp +++ b/src/plugins/platforms/windows/qwindowsmousehandler.cpp @@ -203,7 +203,6 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; -#ifndef Q_OS_WINCE // Check for events synthesized from touch. Lower byte is touch index, 0 means pen. static const bool passSynthesizedMouseEvents = !(QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch); @@ -218,7 +217,6 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd, return false; } } -#endif // !Q_OS_WINCE const QPoint winEventPosition(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); if (et & QtWindows::NonClientEventFlag) { @@ -320,7 +318,6 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd, const bool hasCapture = platformWindow->hasMouseCapture(); const bool currentNotCapturing = hasCapture && currentWindowUnderMouse != window; -#ifndef Q_OS_WINCE // Enter new window: track to generate leave event. // If there is an active capture, only track if the current window is capturing, // so we don't get extra leave when cursor leaves the application. @@ -334,7 +331,6 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd, qWarning("TrackMouseEvent failed."); m_trackedWindow = window; } -#endif // !Q_OS_WINCE // No enter or leave events are sent as long as there is an autocapturing window. if (!hasCapture || !platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)) { @@ -487,7 +483,6 @@ bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND, QtWindows::WindowsEventType, MSG msg, LRESULT *) { -#ifndef Q_OS_WINCE typedef QWindowSystemInterface::TouchPoint QTouchPoint; typedef QList<QWindowSystemInterface::TouchPoint> QTouchPointList; @@ -563,109 +558,17 @@ bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND, QWindowSystemInterface::handleTouchEvent(window, m_touchDevice, touchPoints); -#else // !Q_OS_WINCE - Q_UNUSED(window) - Q_UNUSED(msg) -#endif return true; - } bool QWindowsMouseHandler::translateGestureEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType, MSG msg, LRESULT *) { -#ifndef Q_OS_WINCE Q_UNUSED(window) Q_UNUSED(hwnd) Q_UNUSED(msg) return false; -#else // !Q_OS_WINCE - GESTUREINFO gi; - memset(&gi, 0, sizeof(GESTUREINFO)); - gi.cbSize = sizeof(GESTUREINFO); - - if (!GetGestureInfo((HGESTUREINFO)msg.lParam, &gi)) - return false; - - const QPoint position = QPoint(gi.ptsLocation.x, gi.ptsLocation.y); - - if (gi.dwID != GID_DIRECTMANIPULATION) - return true; - static QPoint lastTouchPos; - const QScreen *screen = window->screen(); - if (!screen) - screen = QGuiApplication::primaryScreen(); - if (!screen) - return true; - const QRect screenGeometry = screen->geometry(); - QWindowSystemInterface::TouchPoint touchPoint; - static QWindowSystemInterface::TouchPoint touchPoint2; - touchPoint.id = 0;//gi.dwInstanceID; - touchPoint.pressure = 1.0; - - if (gi.dwFlags & GF_BEGIN) - touchPoint.state = Qt::TouchPointPressed; - else if (gi.dwFlags & GF_END) - touchPoint.state = Qt::TouchPointReleased; - else if (gi.dwFlags == 0) - touchPoint.state = Qt::TouchPointMoved; - else - return true; - touchPoint2.pressure = 1.0; - touchPoint2.id = 1; - const QPoint winEventPosition = position; - const int deltaX = GID_DIRECTMANIPULATION_DELTA_X(gi.ullArguments); - const int deltaY = GID_DIRECTMANIPULATION_DELTA_Y(gi.ullArguments); - //Touch points are taken from the whole screen so map the position to the screen - const QPoint globalPosition = QWindowsGeometryHint::mapToGlobal(hwnd, winEventPosition); - const QPoint globalPosition2 = QWindowsGeometryHint::mapToGlobal(hwnd, QPoint(position.x() + deltaX, position.y() + deltaY)); - - touchPoint.normalPosition = - QPointF( (qreal)globalPosition.x() / screenGeometry.width(), (qreal)globalPosition.y() / screenGeometry.height() ); - - touchPoint.area.moveCenter(globalPosition); - - QList<QWindowSystemInterface::TouchPoint> pointList; - pointList.append(touchPoint); - if (deltaX != 0 && deltaY != 0) { - touchPoint2.state = m_had2ndTouchPoint ? Qt::TouchPointMoved : Qt::TouchPointPressed; - m_had2ndTouchPoint = true; - touchPoint2.normalPosition = - QPointF( (qreal)globalPosition2.x() / screenGeometry.width(), (qreal)globalPosition2.y() / screenGeometry.height() ); - - touchPoint2.area.moveCenter(globalPosition2); - lastTouchPos = globalPosition2; - pointList.append(touchPoint2); - } else if (m_had2ndTouchPoint) { - touchPoint2.normalPosition = - QPointF( (qreal)lastTouchPos.x() / screenGeometry.width(), (qreal)lastTouchPos.y() / screenGeometry.height() ); - - touchPoint2.area.moveCenter(lastTouchPos); - touchPoint2.state = Qt::TouchPointReleased; - pointList.append(touchPoint2); - m_had2ndTouchPoint = false; - } - - if (!m_touchDevice) { - m_touchDevice = new QTouchDevice; - // TODO: Device used to be hardcoded to screen in previous code. - m_touchDevice->setType(QTouchDevice::TouchScreen); - m_touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::NormalizedPosition); - QWindowSystemInterface::registerTouchDevice(m_touchDevice); - } - - QWindowSystemInterface::handleTouchEvent(window, m_touchDevice, pointList); - // handle window focusing in/out - if (window != m_windowUnderMouse) { - if (m_windowUnderMouse) - QWindowSystemInterface::handleLeaveEvent(m_windowUnderMouse); - if (window) - QWindowSystemInterface::handleEnterEvent(window); - m_windowUnderMouse = window; - } - return true; -#endif // Q_OS_WINCE } QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.h b/src/plugins/platforms/windows/qwindowsmousehandler.h index d16c9f9e02..3eacc5f1a5 100644 --- a/src/plugins/platforms/windows/qwindowsmousehandler.h +++ b/src/plugins/platforms/windows/qwindowsmousehandler.h @@ -91,10 +91,6 @@ private: QTouchDevice *m_touchDevice; bool m_leftButtonDown; QWindow *m_previousCaptureWindow; -#ifdef Q_OS_WINCE -//This is required to send a touch up if we don't get a second touch position any more - bool m_had2ndTouchPoint; -#endif }; Qt::MouseButtons QWindowsMouseHandler::keyStateToMouseButtons(int wParam) diff --git a/src/plugins/platforms/windows/qwindowsnativeimage.cpp b/src/plugins/platforms/windows/qwindowsnativeimage.cpp index bc8bfda32b..ec9683ea8d 100644 --- a/src/plugins/platforms/windows/qwindowsnativeimage.cpp +++ b/src/plugins/platforms/windows/qwindowsnativeimage.cpp @@ -126,9 +126,7 @@ QWindowsNativeImage::QWindowsNativeImage(int width, int height, m_image = QImage(width, height, format); } -#ifndef Q_OS_WINCE GdiFlush(); -#endif } QWindowsNativeImage::~QWindowsNativeImage() diff --git a/src/plugins/platforms/windows/qwindowsopengltester.cpp b/src/plugins/platforms/windows/qwindowsopengltester.cpp index 4ca7f0e413..d5d50a69cd 100644 --- a/src/plugins/platforms/windows/qwindowsopengltester.cpp +++ b/src/plugins/platforms/windows/qwindowsopengltester.cpp @@ -54,17 +54,14 @@ #include <private/qopengl_p.h> #endif -#ifndef Q_OS_WINCE -# include <QtCore/qt_windows.h> -# include <private/qsystemlibrary_p.h> -# include <d3d9.h> -#endif +#include <QtCore/qt_windows.h> +#include <private/qsystemlibrary_p.h> +#include <d3d9.h> QT_BEGIN_NAMESPACE GpuDescription GpuDescription::detect() { -#ifndef Q_OS_WINCE typedef IDirect3D9 * (WINAPI *PtrDirect3DCreate9)(UINT); GpuDescription result; @@ -95,13 +92,6 @@ GpuDescription GpuDescription::detect() result.description = adapterIdentifier.Description; } return result; -#else // !Q_OS_WINCE - GpuDescription result; - result.vendorId = result.deviceId = result.revision =1; - result.driverVersion = QVersionNumber(1, 1, 1); - result.driverName = result.description = QByteArrayLiteral("Generic"); - return result; -#endif } #ifndef QT_NO_DEBUG_STREAM @@ -155,7 +145,6 @@ QVariant GpuDescription::toVariant() const QWindowsOpenGLTester::Renderer QWindowsOpenGLTester::requestedGlesRenderer() { -#ifndef Q_OS_WINCE const char platformVar[] = "QT_ANGLE_PLATFORM"; if (qEnvironmentVariableIsSet(platformVar)) { const QByteArray anglePlatform = qgetenv(platformVar); @@ -167,13 +156,11 @@ QWindowsOpenGLTester::Renderer QWindowsOpenGLTester::requestedGlesRenderer() return QWindowsOpenGLTester::AngleRendererD3d11Warp; qCWarning(lcQpaGl) << "Invalid value set for " << platformVar << ": " << anglePlatform; } -#endif // !Q_OS_WINCE return QWindowsOpenGLTester::InvalidRenderer; } QWindowsOpenGLTester::Renderer QWindowsOpenGLTester::requestedRenderer() { -#ifndef Q_OS_WINCE const char openGlVar[] = "QT_OPENGL"; if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES)) { const Renderer glesRenderer = QWindowsOpenGLTester::requestedGlesRenderer(); @@ -195,12 +182,9 @@ QWindowsOpenGLTester::Renderer QWindowsOpenGLTester::requestedRenderer() return QWindowsOpenGLTester::SoftwareRasterizer; qCWarning(lcQpaGl) << "Invalid value set for " << openGlVar << ": " << requested; } -#endif // !Q_OS_WINCE return QWindowsOpenGLTester::InvalidRenderer; } -#ifndef Q_OS_WINCE - static inline QString resolveBugListFile(const QString &fileName) { if (QFileInfo(fileName).isAbsolute()) @@ -216,12 +200,10 @@ static inline QString resolveBugListFile(const QString &fileName) return QStandardPaths::locate(QStandardPaths::ConfigLocation, fileName); } -# ifndef QT_NO_OPENGL +#ifndef QT_NO_OPENGL typedef QHash<QOpenGLConfig::Gpu, QWindowsOpenGLTester::Renderers> SupportedRenderersCache; Q_GLOBAL_STATIC(SupportedRenderersCache, supportedRenderersCache) -# endif - -#endif // !Q_OS_WINCE +#endif QWindowsOpenGLTester::Renderers QWindowsOpenGLTester::detectSupportedRenderers(const GpuDescription &gpu, bool glesOnly) { @@ -229,8 +211,6 @@ QWindowsOpenGLTester::Renderers QWindowsOpenGLTester::detectSupportedRenderers(c Q_UNUSED(glesOnly) #if defined(QT_NO_OPENGL) return 0; -#elif defined(Q_OS_WINCE) - return QWindowsOpenGLTester::Gles; #else QOpenGLConfig::Gpu qgpu = QOpenGLConfig::Gpu::fromDevice(gpu.vendorId, gpu.deviceId, gpu.driverVersion, gpu.description); SupportedRenderersCache *srCache = supportedRenderersCache(); @@ -280,7 +260,7 @@ QWindowsOpenGLTester::Renderers QWindowsOpenGLTester::detectSupportedRenderers(c } srCache->insert(qgpu, result); return result; -#endif // !Q_OS_WINCE && !QT_NO_OPENGL +#endif // !QT_NO_OPENGL } QWindowsOpenGLTester::Renderers QWindowsOpenGLTester::supportedGlesRenderers() @@ -301,7 +281,7 @@ QWindowsOpenGLTester::Renderers QWindowsOpenGLTester::supportedRenderers() bool QWindowsOpenGLTester::testDesktopGL() { -#if !defined(QT_NO_OPENGL) && !defined(Q_OS_WINCE) +#if !defined(QT_NO_OPENGL) HMODULE lib = 0; HWND wnd = 0; HDC dc = 0; @@ -428,7 +408,7 @@ cleanup: return result; #else return false; -#endif // !QT_NO_OPENGL && !Q_OS_WINCE +#endif // !QT_NO_OPENGL } QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp index b476c9be1d..93839ccf43 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -68,8 +68,6 @@ static inline QDpi deviceDPI(HDC hdc) return QDpi(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY)); } -#ifndef Q_OS_WINCE - static inline QDpi monitorDPI(HMONITOR hMonitor) { if (QWindowsContext::shcoredll.isValid()) { @@ -81,8 +79,6 @@ static inline QDpi monitorDPI(HMONITOR hMonitor) return QDpi(0, 0); } -#endif // !Q_OS_WINCE - typedef QList<QWindowsScreenData> WindowsScreenDataList; static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data) @@ -99,20 +95,9 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data) if (data->name == QLatin1String("WinDisc")) { data->flags |= QWindowsScreenData::LockScreen; } else { -#ifdef Q_OS_WINCE - //Windows CE, just supports one Display and expects to get only DISPLAY, - //instead of DISPLAY0 and so on, which are passed by info.szDevice - HDC hdc = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL); -#else - HDC hdc = CreateDC(info.szDevice, NULL, NULL, NULL); -#endif - if (hdc) { -#ifndef Q_OS_WINCE + if (const HDC hdc = CreateDC(info.szDevice, NULL, NULL, NULL)) { const QDpi dpi = monitorDPI(hMonitor); data->dpi = dpi.first ? dpi : deviceDPI(hdc); -#else - data->dpi = deviceDPI(hdc); -#endif data->depth = GetDeviceCaps(hdc, BITSPIXEL); data->format = data->depth == 16 ? QImage::Format_RGB16 : QImage::Format_RGB32; data->physicalSizeMM = QSizeF(GetDeviceCaps(hdc, HORZSIZE), GetDeviceCaps(hdc, VERTSIZE)); @@ -330,7 +315,6 @@ enum OrientationPreference // matching Win32 API ORIENTATION_PREFERENCE bool QWindowsScreen::setOrientationPreference(Qt::ScreenOrientation o) { bool result = false; -#ifndef Q_OS_WINCE if (QWindowsContext::user32dll.setDisplayAutoRotationPreferences) { DWORD orientationPreference = 0; switch (o) { @@ -352,14 +336,12 @@ bool QWindowsScreen::setOrientationPreference(Qt::ScreenOrientation o) } result = QWindowsContext::user32dll.setDisplayAutoRotationPreferences(orientationPreference); } -#endif // !Q_OS_WINCE return result; } Qt::ScreenOrientation QWindowsScreen::orientationPreference() { Qt::ScreenOrientation result = Qt::PrimaryOrientation; -#ifndef Q_OS_WINCE if (QWindowsContext::user32dll.getDisplayAutoRotationPreferences) { DWORD orientationPreference = 0; if (QWindowsContext::user32dll.getDisplayAutoRotationPreferences(&orientationPreference)) { @@ -379,7 +361,6 @@ Qt::ScreenOrientation QWindowsScreen::orientationPreference() } } } -#endif // !Q_OS_WINCE return result; } @@ -388,7 +369,7 @@ Qt::ScreenOrientation QWindowsScreen::orientationPreference() */ QPlatformScreen::SubpixelAntialiasingType QWindowsScreen::subpixelAntialiasingTypeHint() const { -#if defined(Q_OS_WINCE) || !defined(FT_LCD_FILTER_H) || !defined(FT_CONFIG_OPTION_SUBPIXEL_RENDERING) +#if !defined(FT_LCD_FILTER_H) || !defined(FT_CONFIG_OPTION_SUBPIXEL_RENDERING) return QPlatformScreen::Subpixel_None; #else QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint(); diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h index 2afc410e07..02a9dc3bc3 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.h +++ b/src/plugins/platforms/windows/qwindowsscreen.h @@ -41,9 +41,6 @@ #define QWINDOWSSCREEN_H #include "qtwindowsglobal.h" -#ifdef Q_OS_WINCE -# include "qplatformfunctions_wince.h" -#endif #include <QtCore/QList> #include <QtCore/QVector> diff --git a/src/plugins/platforms/windows/qwindowsservices.cpp b/src/plugins/platforms/windows/qwindowsservices.cpp index 3c99e3507e..5074db9051 100644 --- a/src/plugins/platforms/windows/qwindowsservices.cpp +++ b/src/plugins/platforms/windows/qwindowsservices.cpp @@ -46,9 +46,7 @@ #include <QtCore/QDir> #include <shlobj.h> -#ifndef Q_OS_WINCE -# include <intshcut.h> -#endif +#include <intshcut.h> QT_BEGIN_NAMESPACE @@ -56,7 +54,6 @@ enum { debug = 0 }; static inline bool shellExecute(const QUrl &url) { -#ifndef Q_OS_WINCE const QString nativeFilePath = url.isLocalFile() ? QDir::toNativeSeparators(url.toLocalFile()) : url.toString(QUrl::FullyEncoded); const quintptr result = @@ -69,10 +66,6 @@ static inline bool shellExecute(const QUrl &url) return false; } return true; -#else - Q_UNUSED(url); - return false; -#endif } // Retrieve the commandline for the default mail client. It contains a @@ -107,13 +100,9 @@ static inline QString mailCommand() } if (!command[0]) return QString(); -#ifndef Q_OS_WINCE wchar_t expandedCommand[MAX_PATH] = {0}; return ExpandEnvironmentStrings(command, expandedCommand, MAX_PATH) ? QString::fromWCharArray(expandedCommand) : QString::fromWCharArray(command); -#else - return QString(); -#endif } static inline bool launchMail(const QUrl &url) diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.h b/src/plugins/platforms/windows/qwindowstabletsupport.h index 4a24d62770..2c05dcddfc 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.h +++ b/src/plugins/platforms/windows/qwindowstabletsupport.h @@ -42,7 +42,7 @@ #include "qtwindowsglobal.h" -#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +#if !defined(QT_NO_TABLETEVENT) #include <QtCore/QVector> #include <QtCore/QPointF> @@ -143,5 +143,5 @@ private: QT_END_NAMESPACE -#endif // !QT_NO_TABLETEVENT && !Q_OS_WINCE +#endif // !QT_NO_TABLETEVENT #endif // QWINDOWSTABLETSUPPORT_H diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp index 96940f3f4c..a9307f79fb 100644 --- a/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/src/plugins/platforms/windows/qwindowstheme.cpp @@ -49,17 +49,12 @@ #include "qwindowsintegration.h" #include "qt_windows.h" #include "qwindowsfontdatabase.h" -#ifdef Q_OS_WINCE -# include "qplatformfunctions_wince.h" -# include "winuser.h" -#else -# include <commctrl.h> -# include <objbase.h> -# ifndef Q_CC_MINGW -# include <commoncontrols.h> -# endif -# include <shellapi.h> +#include <commctrl.h> +#include <objbase.h> +#ifndef Q_CC_MINGW +# include <commoncontrols.h> #endif +#include <shellapi.h> #include <QtCore/QVariant> #include <QtCore/QCoreApplication> @@ -128,7 +123,6 @@ static inline QColor getSysColor(int index) return COLORREFToQColor(GetSysColor(index)); } -#ifndef QT_NO_WINCE_SHELLSDK // QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system // models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the // behavior by running it in a thread. @@ -161,7 +155,6 @@ static bool shGetFileInfoBackground(QWindowsThreadPoolRunner &r, } return result; } -#endif // !QT_NO_WINCE_SHELLSDK // from QStyle::standardPalette static inline QPalette standardPalette() @@ -272,14 +265,9 @@ static inline QPalette menuPalette(const QPalette &systemPalette) result.setColor(QPalette::Active, QPalette::ButtonText, menuTextColor); result.setColor(QPalette::Disabled, QPalette::WindowText, disabled); result.setColor(QPalette::Disabled, QPalette::Text, disabled); -#ifndef Q_OS_WINCE const bool isFlat = booleanSystemParametersInfo(SPI_GETFLATMENU, false); result.setColor(QPalette::Disabled, QPalette::Highlight, getSysColor(isFlat ? COLOR_MENUHILIGHT : COLOR_HIGHLIGHT)); -#else - result.setColor(QPalette::Disabled, QPalette::Highlight, - getSysColor(COLOR_HIGHLIGHT)); -#endif result.setColor(QPalette::Disabled, QPalette::HighlightedText, disabled); result.setColor(QPalette::Disabled, QPalette::Button, result.color(QPalette::Active, QPalette::Button)); @@ -305,11 +293,7 @@ static inline QPalette *menuBarPalette(const QPalette &menuPalette) QPalette *result = 0; if (booleanSystemParametersInfo(SPI_GETFLATMENU, false)) { result = new QPalette(menuPalette); -#ifndef Q_OS_WINCE const QColor menubar(getSysColor(COLOR_MENUBAR)); -#else - const QColor menubar(getSysColor(COLOR_MENU)); -#endif result->setColor(QPalette::Active, QPalette::Button, menubar); result->setColor(QPalette::Disabled, QPalette::Button, menubar); result->setColor(QPalette::Inactive, QPalette::Button, menubar); @@ -379,12 +363,10 @@ QVariant QWindowsTheme::themeHint(ThemeHint hint) const return QVariant(iconThemeSearchPaths()); case StyleNames: return QVariant(styleNames()); -#ifndef Q_OS_WINCE case TextCursorWidth: return QVariant(int(dWordSystemParametersInfo(SPI_GETCARETWIDTH, 1u))); case DropShadow: return QVariant(booleanSystemParametersInfo(SPI_GETDROPSHADOW, false)); -#endif // !Q_OS_WINCE case MaximumScrollBarDragDistance: return QVariant(qRound(qreal(QWindowsContext::instance()->defaultDPI()) * 1.375)); case KeyboardScheme: @@ -438,7 +420,6 @@ void QWindowsTheme::clearFonts() void QWindowsTheme::refreshFonts() { -#ifndef Q_OS_WINCE // ALL THIS FUNCTIONALITY IS MISSING ON WINCE clearFonts(); if (!QGuiApplication::desktopSettingsAware()) return; @@ -467,7 +448,6 @@ void QWindowsTheme::refreshFonts() m_fonts[DockWidgetTitleFont] = new QFont(titleFont); m_fonts[ItemViewFont] = new QFont(iconTitleFont); m_fonts[FixedFont] = new QFont(fixedFont); -#endif // !Q_OS_WINCE } bool QWindowsTheme::usePlatformNativeDialog(DialogType type) const @@ -491,12 +471,7 @@ Q_GUI_EXPORT QPixmap qt_pixmapFromWinHICON(HICON icon); static QPixmap loadIconFromShell32(int resourceId, QSizeF size) { -#ifdef Q_OS_WINCE - HMODULE hmod = LoadLibrary(L"ceshell"); -#else - HMODULE hmod = QSystemLibrary::load(L"shell32"); -#endif - if (hmod) { + if (const HMODULE hmod = QSystemLibrary::load(L"shell32")) { HICON iconHandle = static_cast<HICON>(LoadImage(hmod, MAKEINTRESOURCE(resourceId), IMAGE_ICON, int(size.width()), int(size.height()), 0)); @@ -512,7 +487,7 @@ static QPixmap loadIconFromShell32(int resourceId, QSizeF size) QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSize) const { int resourceId = -1; - int stockId = SIID_INVALID; + SHSTOCKICONID stockId = SIID_INVALID; UINT stockFlags = 0; LPCTSTR iconName = 0; switch (sp) { @@ -575,7 +550,6 @@ QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSiz stockId = SIID_RECYCLER; resourceId = 191; break; -#ifndef Q_OS_WINCE case MessageBoxInformation: stockId = SIID_INFO; iconName = IDI_INFORMATION; @@ -595,29 +569,22 @@ QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSiz case VistaShield: stockId = SIID_SHIELD; break; -#endif default: break; } -#ifndef Q_OS_WINCE if (stockId != SIID_INVALID) { - if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA - && (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based) - && QWindowsContext::shell32dll.sHGetStockIconInfo) { - QPixmap pixmap; - SHSTOCKICONINFO iconInfo; - memset(&iconInfo, 0, sizeof(iconInfo)); - iconInfo.cbSize = sizeof(iconInfo); - stockFlags |= (pixmapSize.width() > 16 ? SHGFI_LARGEICON : SHGFI_SMALLICON); - if (QWindowsContext::shell32dll.sHGetStockIconInfo(stockId, SHGFI_ICON | stockFlags, &iconInfo) == S_OK) { - pixmap = qt_pixmapFromWinHICON(iconInfo.hIcon); - DestroyIcon(iconInfo.hIcon); - return pixmap; - } + QPixmap pixmap; + SHSTOCKICONINFO iconInfo; + memset(&iconInfo, 0, sizeof(iconInfo)); + iconInfo.cbSize = sizeof(iconInfo); + stockFlags |= (pixmapSize.width() > 16 ? SHGFI_LARGEICON : SHGFI_SMALLICON); + if (SHGetStockIconInfo(stockId, SHGFI_ICON | stockFlags, &iconInfo) == S_OK) { + pixmap = qt_pixmapFromWinHICON(iconInfo.hIcon); + DestroyIcon(iconInfo.hIcon); + return pixmap; } } -#endif if (resourceId != -1) { QPixmap pixmap = loadIconFromShell32(resourceId, pixmapSize); @@ -692,14 +659,8 @@ static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info) // For MinGW: static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}}; - if (!QWindowsContext::shell32dll.sHGetImageList) - return result; - if (iImageList == sHIL_JUMBO && QSysInfo::WindowsVersion < QSysInfo::WV_VISTA) - return result; - IImageList *imageList = 0; - HRESULT hr = QWindowsContext::shell32dll.sHGetImageList(iImageList, iID_IImageList, - reinterpret_cast<void **>(&imageList)); + HRESULT hr = SHGetImageList(iImageList, iID_IImageList, reinterpret_cast<void **>(&imageList)); if (hr != S_OK) return result; HICON hIcon; @@ -755,22 +716,13 @@ QPixmap QWindowsTheme::fileIconPixmap(const QFileInfo &fileInfo, const QSizeF &s SHFILEINFO info; const unsigned int flags = -#ifndef Q_OS_WINCE SHGFI_ICON|iconSize|SHGFI_SYSICONINDEX|SHGFI_ADDOVERLAYS|SHGFI_OVERLAYINDEX; -#else - iconSize|SHGFI_SYSICONINDEX; -#endif // Q_OS_WINCE - -#if !defined(QT_NO_WINCE_SHELLSDK) const bool val = cacheableDirIcon && useDefaultFolderIcon ? shGetFileInfoBackground(m_threadPoolRunner, L"dummy", FILE_ATTRIBUTE_DIRECTORY, &info, flags | SHGFI_USEFILEATTRIBUTES) : shGetFileInfoBackground(m_threadPoolRunner, reinterpret_cast<const wchar_t *>(filePath.utf16()), 0, &info, flags); -#else - const bool val = false; -#endif // !QT_NO_WINCE_SHELLSDK // Even if GetFileInfo returns a valid result, hIcon can be empty in some cases if (val && info.hIcon) { diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index f76b3bb2d2..4aa6eaea4a 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -62,6 +62,8 @@ #include <QtCore/QDebug> +#include <dwmapi.h> + QT_BEGIN_NAMESPACE enum { @@ -147,7 +149,6 @@ QDebug operator<<(QDebug d, const POINT &p) return d; } -# ifndef Q_OS_WINCE QDebug operator<<(QDebug d, const NCCALCSIZE_PARAMS &p) { QDebugStateSaver saver(d); @@ -178,14 +179,12 @@ QDebug operator<<(QDebug d, const WINDOWPLACEMENT &wp) << ", rcNormalPosition=" << wp.rcNormalPosition; return d; } -# endif // !Q_OS_WINCE #endif // !QT_NO_DEBUG_STREAM // QTBUG-43872, for windows that do not have WS_EX_TOOLWINDOW set, WINDOWPLACEMENT // is in workspace/available area coordinates. static QPoint windowPlacementOffset(HWND hwnd, const QPoint &point) { -#ifndef Q_OS_WINCE if (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) return QPoint(0, 0); const QWindowsScreenManager &screenManager = QWindowsContext::instance()->screenManager(); @@ -193,10 +192,6 @@ static QPoint windowPlacementOffset(HWND hwnd, const QPoint &point) ? screenManager.screens().constFirst() : screenManager.screenAtDp(point); if (screen) return screen->availableGeometry().topLeft() - screen->geometry().topLeft(); -#else - Q_UNUSED(hwnd) - Q_UNUSED(point) -#endif return QPoint(0, 0); } @@ -205,7 +200,6 @@ static QPoint windowPlacementOffset(HWND hwnd, const QPoint &point) static inline QRect frameGeometry(HWND hwnd, bool topLevel) { RECT rect = { 0, 0, 0, 0 }; -#ifndef Q_OS_WINCE if (topLevel) { WINDOWPLACEMENT windowPlacement; windowPlacement.length = sizeof(WINDOWPLACEMENT); @@ -215,7 +209,6 @@ static inline QRect frameGeometry(HWND hwnd, bool topLevel) return result.translated(windowPlacementOffset(hwnd, result.topLeft())); } } -#endif // !Q_OS_WINCE GetWindowRect(hwnd, &rect); // Screen coordinates. const HWND parent = GetParent(hwnd); if (parent && !topLevel) { @@ -236,7 +229,6 @@ static QWindow::Visibility windowVisibility_sys(HWND hwnd) { if (!IsWindowVisible(hwnd)) return QWindow::Hidden; -#ifndef Q_OS_WINCE WINDOWPLACEMENT windowPlacement; windowPlacement.length = sizeof(WINDOWPLACEMENT); if (GetWindowPlacement(hwnd, &windowPlacement)) { @@ -251,7 +243,6 @@ static QWindow::Visibility windowVisibility_sys(HWND hwnd) break; } } -#endif // !Q_OS_WINCE return QWindow::Windowed; } @@ -269,65 +260,27 @@ static inline bool windowIsOpenGL(const QWindow *w) static bool applyBlurBehindWindow(HWND hwnd) { -#ifdef Q_OS_WINCE - Q_UNUSED(hwnd); - return false; -#else - enum { dwmBbEnable = 0x1, dwmBbBlurRegion = 0x2 }; - - struct DwmBlurBehind { - DWORD dwFlags; - BOOL fEnable; - HRGN hRgnBlur; - BOOL fTransitionOnMaximized; - }; - - typedef HRESULT (WINAPI *PtrDwmEnableBlurBehindWindow)(HWND, const DwmBlurBehind*); - typedef HRESULT (WINAPI *PtrDwmIsCompositionEnabled)(BOOL *); - - // DWM API is available only from Windows Vista - if (QSysInfo::windowsVersion() < QSysInfo::WV_VISTA) - return false; - - static bool functionPointersResolved = false; - static PtrDwmEnableBlurBehindWindow dwmBlurBehind = 0; - static PtrDwmIsCompositionEnabled dwmIsCompositionEnabled = 0; - - if (Q_UNLIKELY(!functionPointersResolved)) { - QSystemLibrary library(QStringLiteral("dwmapi")); - if (library.load()) { - dwmBlurBehind = (PtrDwmEnableBlurBehindWindow)(library.resolve("DwmEnableBlurBehindWindow")); - dwmIsCompositionEnabled = (PtrDwmIsCompositionEnabled)(library.resolve("DwmIsCompositionEnabled")); - } - - functionPointersResolved = true; - } - - if (Q_UNLIKELY(!dwmBlurBehind || !dwmIsCompositionEnabled)) - return false; - BOOL compositionEnabled; - if (dwmIsCompositionEnabled(&compositionEnabled) != S_OK) + if (DwmIsCompositionEnabled(&compositionEnabled) != S_OK) return false; - DwmBlurBehind blurBehind = {0, 0, 0, 0}; + DWM_BLURBEHIND blurBehind = {0, 0, 0, 0}; if (compositionEnabled) { - blurBehind.dwFlags = dwmBbEnable | dwmBbBlurRegion; + blurBehind.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; blurBehind.fEnable = TRUE; blurBehind.hRgnBlur = CreateRectRgn(0, 0, -1, -1); } else { - blurBehind.dwFlags = dwmBbEnable; + blurBehind.dwFlags = DWM_BB_ENABLE; blurBehind.fEnable = FALSE; } - const bool result = dwmBlurBehind(hwnd, &blurBehind) == S_OK; + const bool result = DwmEnableBlurBehindWindow(hwnd, &blurBehind) == S_OK; if (blurBehind.hRgnBlur) DeleteObject(blurBehind.hRgnBlur); return result; -#endif // Q_OS_WINCE } // from qwidget_win.cpp, pass flags separately in case they have been "autofixed". @@ -346,7 +299,6 @@ static bool shouldShowMaximizeButton(const QWindow *w, Qt::WindowFlags flags) // Qt::WindowTransparentForInput (in combination with WS_EX_TRANSPARENT). bool QWindowsWindow::setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, qreal opacity) { -#ifndef Q_OS_WINCE // maybe needs revisiting WS_EX_LAYERED const LONG exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); const bool needsLayered = (flags & Qt::WindowTransparentForInput) || (hasAlpha && (flags & Qt::FramelessWindowHint)) || opacity < 1.0; @@ -359,36 +311,22 @@ bool QWindowsWindow::setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool has } } return needsLayered; -#else // !Q_OS_WINCE - Q_UNUSED(hwnd); - Q_UNUSED(flags); - Q_UNUSED(hasAlpha); - Q_UNUSED(opacity); - return false; -#endif // Q_OS_WINCE } static void setWindowOpacity(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, bool openGL, qreal level) { -#ifdef Q_OS_WINCE // WINCE does not support that feature and microsoft explicitly warns to use those calls - Q_UNUSED(hwnd); - Q_UNUSED(flags); - Q_UNUSED(hasAlpha); - Q_UNUSED(level); -#else if (QWindowsWindow::setWindowLayered(hwnd, flags, hasAlpha, level)) { const BYTE alpha = BYTE(qRound(255.0 * level)); if (hasAlpha && !openGL && (flags & Qt::FramelessWindowHint)) { // Non-GL windows with alpha: Use blend function to update. BLENDFUNCTION blend = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; - QWindowsContext::user32dll.updateLayeredWindow(hwnd, NULL, NULL, NULL, NULL, NULL, 0, &blend, ULW_ALPHA); + UpdateLayeredWindow(hwnd, NULL, NULL, NULL, NULL, NULL, 0, &blend, ULW_ALPHA); } else { - QWindowsContext::user32dll.setLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA); + SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA); } } else if (IsWindowVisible(hwnd)) { // Repaint when switching from layered. InvalidateRect(hwnd, NULL, TRUE); } -#endif // !Q_OS_WINCE } static inline void updateGLWindowSettings(const QWindow *w, HWND hwnd, Qt::WindowFlags flags, qreal opacity) @@ -604,12 +542,10 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag exStyle |= WS_EX_TOOLWINDOW; } -#ifndef Q_OS_WINCE // make mouse events fall through this window // NOTE: WS_EX_TRANSPARENT flag can make mouse inputs fall through a layered window if (flagsIn & Qt::WindowTransparentForInput) exStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT; -#endif } } @@ -650,10 +586,6 @@ QWindowsWindowData context->frameX, context->frameY, context->frameWidth, context->frameHeight, parentHandle, NULL, appinst, NULL); -#ifdef Q_OS_WINCE - if (DisableGestures(result.hwnd, TGF_GID_ALL, TGF_SCOPE_WINDOW)) - EnableGestures(result.hwnd, TGF_GID_DIRECTMANIPULATION, TGF_SCOPE_WINDOW); -#endif qCDebug(lcQpaWindows).nospace() << "CreateWindowEx: returns " << w << ' ' << result.hwnd << " obtained geometry: " << context->obtainedGeometry << ' ' << context->margins; @@ -764,9 +696,7 @@ bool QWindowsGeometryHint::validSize(const QSize &s) const QMargins QWindowsGeometryHint::frame(DWORD style, DWORD exStyle) { RECT rect = {0,0,0,0}; -#ifndef Q_OS_WINCE style &= ~(WS_OVERLAPPED); // Not permitted, see docs. -#endif if (!AdjustWindowRectEx(&rect, style, FALSE, exStyle)) qErrnoWarning("%s: AdjustWindowRectEx failed", __FUNCTION__); const QMargins result(qAbs(rect.left), qAbs(rect.top), @@ -779,7 +709,6 @@ QMargins QWindowsGeometryHint::frame(DWORD style, DWORD exStyle) bool QWindowsGeometryHint::handleCalculateSize(const QMargins &customMargins, const MSG &msg, LRESULT *result) { -#ifndef Q_OS_WINCE // NCCALCSIZE_PARAMS structure if wParam==TRUE if (!msg.wParam || customMargins.isNull()) return false; @@ -795,15 +724,8 @@ bool QWindowsGeometryHint::handleCalculateSize(const QMargins &customMargins, co << ncp->rgrc[0] << ' ' << ncp->rgrc[1] << ' ' << ncp->rgrc[2] << ' ' << ncp->lppos->cx << ',' << ncp->lppos->cy; return true; -#else - Q_UNUSED(customMargins) - Q_UNUSED(msg) - Q_UNUSED(result) - return false; -#endif } -#ifndef Q_OS_WINCE void QWindowsGeometryHint::applyToMinMaxInfo(HWND hwnd, MINMAXINFO *mmi) const { return applyToMinMaxInfo(DWORD(GetWindowLong(hwnd, GWL_STYLE)), @@ -835,7 +757,6 @@ void QWindowsGeometryHint::applyToMinMaxInfo(DWORD style, DWORD exStyle, MINMAXI << " frame=" << margins << ' ' << frameWidth << ',' << frameHeight << " out " << *mmi; } -#endif // !Q_OS_WINCE bool QWindowsGeometryHint::positionIncludesFrame(const QWindow *w) { @@ -1080,9 +1001,6 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) m_dropTarget(0), m_savedStyle(0), m_format(aWindow->requestedFormat()), -#ifdef Q_OS_WINCE - m_previouslyHidden(false), -#endif m_iconSmall(0), m_iconBig(0), m_surface(0) @@ -1116,10 +1034,8 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) QWindowsWindow::~QWindowsWindow() { setFlag(WithinDestroy); -#ifndef Q_OS_WINCE if (testFlag(TouchRegistered)) QWindowsContext::user32dll.unregisterTouchWindow(m_data.hwnd); -#endif // !Q_OS_WINCE destroyWindow(); destroyIcon(); } @@ -1163,14 +1079,6 @@ void QWindowsWindow::destroyWindow() m_surface = 0; } #endif -#ifdef Q_OS_WINCE - if ((m_windowState & Qt::WindowFullScreen) && !m_previouslyHidden) { - HWND handle = FindWindow(L"HHTaskBar", L""); - if (handle) { - ShowWindow(handle, SW_SHOW); - } - } -#endif // !Q_OS_WINCE DestroyWindow(m_data.hwnd); context->removeWindow(m_data.hwnd); m_data.hwnd = 0; @@ -1338,7 +1246,6 @@ QPoint QWindowsWindow::mapFromGlobal(const QPoint &pos) const return pos; } -#ifndef Q_OS_WINCE static inline HWND transientParentHwnd(HWND hwnd) { if (GetAncestor(hwnd, GA_PARENT) == GetDesktopWindow()) { @@ -1348,7 +1255,6 @@ static inline HWND transientParentHwnd(HWND hwnd) } return 0; } -#endif // !Q_OS_WINCE // Update the transient parent for a toplevel window. The concept does not // really exist on Windows, the relationship is set by passing a parent along with !WS_CHILD @@ -1356,7 +1262,6 @@ static inline HWND transientParentHwnd(HWND hwnd) // SetParent, which would make it a real child). void QWindowsWindow::updateTransientParent() const { -#ifndef Q_OS_WINCE if (window()->type() == Qt::Popup) return; // QTBUG-34503, // a popup stays on top, no parent, see also WindowCreationData::fromWindow(). // Update transient parent. @@ -1368,7 +1273,6 @@ void QWindowsWindow::updateTransientParent() const newTransientParent = tw->handle(); if (newTransientParent != oldTransientParent) SetWindowLongPtr(m_data.hwnd, GWL_HWNDPARENT, LONG_PTR(newTransientParent)); -#endif // !Q_OS_WINCE } static inline bool testShowWithoutActivating(const QWindow *window) @@ -1486,16 +1390,12 @@ void QWindowsWindow::handleCompositionSettingsChanged() static QRect normalFrameGeometry(HWND hwnd) { -#ifndef Q_OS_WINCE WINDOWPLACEMENT wp; wp.length = sizeof(WINDOWPLACEMENT); if (GetWindowPlacement(hwnd, &wp)) { const QRect result = qrectFromRECT(wp.rcNormalPosition); return result.translated(windowPlacementOffset(hwnd, result.topLeft())); } -#else - Q_UNUSED(hwnd) -#endif return QRect(); } @@ -1615,7 +1515,6 @@ void QWindowsBaseWindow::setGeometry_sys(const QRect &rect) const << " new frame: " << frameGeometry; bool result = false; -#ifndef Q_OS_WINCE const HWND hwnd = handle(); WINDOWPLACEMENT windowPlacement; windowPlacement.length = sizeof(WINDOWPLACEMENT); @@ -1628,9 +1527,7 @@ void QWindowsBaseWindow::setGeometry_sys(const QRect &rect) const RECTfromQRect(frameGeometry.translated(-windowPlacementOffset(hwnd, frameGeometry.topLeft()))); windowPlacement.showCmd = windowPlacement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWMINIMIZED : SW_HIDE; result = SetWindowPlacement(hwnd, &windowPlacement); - } else -#endif // !Q_OS_WINCE - { + } else { result = MoveWindow(hwnd, frameGeometry.x(), frameGeometry.y(), frameGeometry.width(), frameGeometry.height(), true); } @@ -1822,18 +1719,6 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowState newState) setFlag(FrameDirty); if ((oldState == Qt::WindowFullScreen) != (newState == Qt::WindowFullScreen)) { -#ifdef Q_OS_WINCE - HWND handle = FindWindow(L"HHTaskBar", L""); - if (handle) { - if (newState == Qt::WindowFullScreen) { - BOOL hidden = ShowWindow(handle, SW_HIDE); - if (!hidden) - m_previouslyHidden = true; - } else if (!m_previouslyHidden){ - ShowWindow(handle, SW_SHOW); - } - } -#endif if (newState == Qt::WindowFullScreen) { #ifndef Q_FLATTEN_EXPOSE UINT newStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP; @@ -1845,17 +1730,13 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowState newState) // Window state but emulated by changing geometry and style. if (!m_savedStyle) { m_savedStyle = style(); -#ifndef Q_OS_WINCE if (oldState == Qt::WindowMinimized || oldState == Qt::WindowMaximized) { const QRect nf = normalFrameGeometry(m_data.hwnd); if (nf.isValid()) m_savedFrameGeometry = nf; } else { -#endif m_savedFrameGeometry = frameGeometry_sys(); -#ifndef Q_OS_WINCE } -#endif } if (m_savedStyle & WS_SYSMENU) newStyle |= WS_SYSMENU; @@ -1968,7 +1849,6 @@ void QWindowsWindow::propagateSizeHints() bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow *qWindow, const QMargins &margins) { -#ifndef Q_OS_WINCE if (!qWindow->isTopLevel()) // Implement hasHeightForWidth(). return false; WINDOWPOS *windowPos = reinterpret_cast<WINDOWPOS *>(message->lParam); @@ -1988,10 +1868,6 @@ bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow * windowPos->cx = correctedFrameGeometry.width(); windowPos->cy = correctedFrameGeometry.height(); return true; -#else // !Q_OS_WINCE - Q_UNUSED(message) - return false; -#endif } bool QWindowsWindow::handleGeometryChanging(MSG *message) const @@ -2049,15 +1925,13 @@ static inline void addRectToWinRegion(const QRect &rect, HRGN *winRegion) static HRGN qRegionToWinRegion(const QRegion ®ion) { - const QVector<QRect> rects = region.rects(); - if (rects.isEmpty()) - return NULL; - const int rectCount = rects.size(); - if (rectCount == 1) - return createRectRegion(region.boundingRect()); - HRGN hRegion = createRectRegion(rects.front()); - for (int i = 1; i < rectCount; ++i) - addRectToWinRegion(rects.at(i), &hRegion); + auto it = region.begin(); + const auto end = region.end(); + if (it == end) + return nullptr; + HRGN hRegion = createRectRegion(*it); + while (++it != end) + addRectToWinRegion(*it, &hRegion); return hRegion; } @@ -2086,7 +1960,6 @@ void QWindowsWindow::requestActivateWindow() // 'Active' state handling is based in focus since it needs to work for // child windows as well. if (m_data.hwnd) { -#ifndef Q_OS_WINCE const DWORD currentThread = GetCurrentThreadId(); bool attached = false; DWORD foregroundThread = 0; @@ -2103,13 +1976,10 @@ void QWindowsWindow::requestActivateWindow() attached = AttachThreadInput(foregroundThread, currentThread, TRUE) == TRUE; } } -#endif // !Q_OS_WINCE SetForegroundWindow(m_data.hwnd); SetFocus(m_data.hwnd); -#ifndef Q_OS_WINCE if (attached) AttachThreadInput(foregroundThread, currentThread, FALSE); -#endif // !Q_OS_WINCE } } @@ -2191,7 +2061,6 @@ void QWindowsWindow::setFrameStrutEventsEnabled(bool enabled) } } -#ifndef Q_OS_WINCE // maybe available on some SDKs revisit WM_GETMINMAXINFO void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const { const QWindowsGeometryHint hint(window(), m_data.customMargins); @@ -2262,8 +2131,6 @@ bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *re return false; } -#endif // !Q_OS_WINCE - #ifndef QT_NO_CURSOR // Return the default cursor (Arrow) from QWindowsCursor's cache. static inline CursorHandlePtr defaultCursor(const QWindow *w) @@ -2330,7 +2197,6 @@ void QWindowsWindow::setCursor(const CursorHandlePtr &c) #endif } -#ifndef Q_OS_WINCE void QWindowsWindow::setAlertState(bool enabled) { if (isAlertState() == enabled) @@ -2369,7 +2235,6 @@ void QWindowsWindow::stopAlertWindow() info.uCount = 0; FlashWindowEx(&info); } -#endif // !Q_OS_WINCE bool QWindowsWindow::isEnabled() const { @@ -2485,7 +2350,6 @@ void QWindowsWindow::setTouchWindowTouchTypeStatic(QWindow *window, QWindowsWind void QWindowsWindow::registerTouchWindow(QWindowsWindowFunctions::TouchWindowTouchTypes touchTypes) { -#ifndef Q_OS_WINCE if ((QWindowsContext::instance()->systemInfo() & QWindowsContext::SI_SupportsTouch)) { ULONG touchFlags = 0; const bool ret = QWindowsContext::user32dll.isTouchWindow(m_data.hwnd, &touchFlags); @@ -2498,7 +2362,6 @@ void QWindowsWindow::registerTouchWindow(QWindowsWindowFunctions::TouchWindowTou else qErrnoWarning("RegisterTouchWindow() failed for window '%s'.", qPrintable(window()->objectName())); } -#endif // !Q_OS_WINCE } void QWindowsWindow::aboutToMakeCurrent() diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 999761f3c6..070add052b 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -41,9 +41,6 @@ #define QWINDOWSWINDOW_H #include "qtwindows_additional.h" -#ifdef Q_OS_WINCE -# include "qplatformfunctions_wince.h" -#endif #include "qwindowscursor.h" #include <qpa/qplatformwindow.h> @@ -60,10 +57,8 @@ struct QWindowsGeometryHint explicit QWindowsGeometryHint(const QWindow *w, const QMargins &customMargins); static QMargins frame(DWORD style, DWORD exStyle); static bool handleCalculateSize(const QMargins &customMargins, const MSG &msg, LRESULT *result); -#ifndef Q_OS_WINCE //MinMax maybe define struct if not available void applyToMinMaxInfo(DWORD style, DWORD exStyle, MINMAXINFO *mmi) const; void applyToMinMaxInfo(HWND hwnd, MINMAXINFO *mmi) const; -#endif bool validSize(const QSize &s) const; static inline QPoint mapToGlobal(HWND hwnd, const QPoint &); @@ -83,10 +78,8 @@ struct QWindowCreationContext QWindowCreationContext(const QWindow *w, const QRect &r, const QMargins &customMargins, DWORD style, DWORD exStyle); -#ifndef Q_OS_WINCE //MinMax maybe define struct if not available void applyToMinMaxInfo(MINMAXINFO *mmi) const { geometryHint.applyToMinMaxInfo(style, exStyle, mmi); } -#endif QWindowsGeometryHint geometryHint; const QWindow *window; @@ -293,10 +286,8 @@ public: HDC getDC(); void releaseDC(); -#ifndef Q_OS_WINCE // maybe available on some SDKs revisit WM_GETMINMAXINFO void getSizeHints(MINMAXINFO *mmi) const; bool handleNonClientHitTest(const QPoint &globalPos, LRESULT *result) const; -#endif // !Q_OS_WINCE #ifndef QT_NO_CURSOR CursorHandlePtr cursor() const { return m_cursor; } @@ -316,12 +307,10 @@ public: void invalidateSurface() Q_DECL_OVERRIDE; void aboutToMakeCurrent(); -#ifndef Q_OS_WINCE void setAlertState(bool enabled) Q_DECL_OVERRIDE; bool isAlertState() const Q_DECL_OVERRIDE { return testFlag(AlertState); } void alertWindow(int durationMs = 0); void stopAlertWindow(); -#endif static void setTouchWindowTouchTypeStatic(QWindow *window, QWindowsWindowFunctions::TouchWindowTouchTypes touchTypes); void registerTouchWindow(QWindowsWindowFunctions::TouchWindowTouchTypes touchTypes = QWindowsWindowFunctions::NormalTouch); @@ -355,9 +344,6 @@ private: unsigned m_savedStyle; QRect m_savedFrameGeometry; const QSurfaceFormat m_format; -#ifdef Q_OS_WINCE - bool m_previouslyHidden; -#endif HICON m_iconSmall; HICON m_iconBig; void *m_surface; @@ -366,11 +352,9 @@ private: #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug d, const RECT &r); QDebug operator<<(QDebug d, const POINT &); -# ifndef Q_OS_WINCE QDebug operator<<(QDebug d, const MINMAXINFO &i); QDebug operator<<(QDebug d, const NCCALCSIZE_PARAMS &p); QDebug operator<<(QDebug d, const WINDOWPLACEMENT &); -# endif // !Q_OS_WINCE #endif // !QT_NO_DEBUG_STREAM // ---------- QWindowsGeometryHint inline functions. @@ -434,11 +418,7 @@ inline void QWindowsWindow::destroyIcon() inline bool QWindowsWindow::isLayered() const { -#ifndef Q_OS_WINCE return GetWindowLongPtr(m_data.hwnd, GWL_EXSTYLE) & WS_EX_LAYERED; -#else - return false; -#endif } QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/windows.pri b/src/plugins/platforms/windows/windows.pri index f4dbd10a49..b8476df792 100644 --- a/src/plugins/platforms/windows/windows.pri +++ b/src/plugins/platforms/windows/windows.pri @@ -1,14 +1,11 @@ # Note: OpenGL32 must precede Gdi32 as it overwrites some functions. -LIBS *= -lole32 -!wince: LIBS *= -luser32 -lwinspool -limm32 -lwinmm -loleaut32 +LIBS += -lole32 -luser32 -lwinspool -limm32 -lwinmm -loleaut32 contains(QT_CONFIG, opengl):!contains(QT_CONFIG, opengles2):!contains(QT_CONFIG, dynamicgl): LIBS *= -lopengl32 mingw: LIBS *= -luuid # For the dialog helpers: -!wince: LIBS *= -lshlwapi -lshell32 -!wince: LIBS *= -ladvapi32 -wince: DEFINES *= QT_LIBINFIX=L"\"\\\"$${QT_LIBINFIX}\\\"\"" +LIBS += -lshlwapi -lshell32 -ladvapi32 DEFINES *= QT_NO_CAST_FROM_ASCII @@ -62,7 +59,6 @@ HEADERS += \ $$PWD/qwindowstheme.h \ $$PWD/qwindowsdialoghelpers.h \ $$PWD/qwindowsservices.h \ - $$PWD/qplatformfunctions_wince.h \ $$PWD/qwindowsnativeimage.h \ $$PWD/qwindowsnativeinterface.h \ $$PWD/qwindowsopengltester.h \ @@ -101,36 +97,30 @@ contains(QT_CONFIG,dynamicgl) { } } -!wince:!contains( DEFINES, QT_NO_TABLETEVENT ) { +!contains( DEFINES, QT_NO_TABLETEVENT ) { INCLUDEPATH += $$QT_SOURCE_TREE/src/3rdparty/wintab HEADERS += $$PWD/qwindowstabletsupport.h SOURCES += $$PWD/qwindowstabletsupport.cpp } -!wince:!contains( DEFINES, QT_NO_SESSIONMANAGER ) { +!contains( DEFINES, QT_NO_SESSIONMANAGER ) { SOURCES += $$PWD/qwindowssessionmanager.cpp HEADERS += $$PWD/qwindowssessionmanager.h } -!wince:!contains( DEFINES, QT_NO_IMAGEFORMAT_PNG ) { - RESOURCES += $$PWD/cursors.qrc -} +!contains( DEFINES, QT_NO_IMAGEFORMAT_PNG ):RESOURCES += $$PWD/cursors.qrc -!wince: RESOURCES += $$PWD/openglblacklists.qrc +RESOURCES += $$PWD/openglblacklists.qrc contains(QT_CONFIG, freetype) { - DEFINES *= QT_NO_FONTCONFIG - include($$QT_SOURCE_TREE/src/3rdparty/freetype_dependency.pri) - HEADERS += \ - $$PWD/qwindowsfontdatabase_ft.h - SOURCES += \ - $$PWD/qwindowsfontdatabase_ft.cpp -} else:contains(QT_CONFIG, system-freetype) { - include($$QT_SOURCE_TREE/src/platformsupport/fontdatabases/basic/basic.pri) - HEADERS += \ - $$PWD/qwindowsfontdatabase_ft.h - SOURCES += \ - $$PWD/qwindowsfontdatabase_ft.cpp + HEADERS += $$PWD/qwindowsfontdatabase_ft.h + SOURCES += $$PWD/qwindowsfontdatabase_ft.cpp + contains(QT_CONFIG, system-freetype) { + include($$QT_SOURCE_TREE/src/platformsupport/fontdatabases/basic/basic.pri) + } else { + DEFINES *= QT_NO_FONTCONFIG + include($$QT_SOURCE_TREE/src/3rdparty/freetype_dependency.pri) + } } contains(QT_CONFIG, accessibility):include($$PWD/accessible/accessible.pri) diff --git a/src/plugins/platforms/windows/windows.pro b/src/plugins/platforms/windows/windows.pro index 2e0f723693..adafa830d5 100644 --- a/src/plugins/platforms/windows/windows.pro +++ b/src/plugins/platforms/windows/windows.pro @@ -4,7 +4,7 @@ QT *= core-private QT *= gui-private QT *= platformsupport-private -!wince:LIBS *= -lgdi32 +LIBS += -lgdi32 -ldwmapi include(windows.pri) diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.cpp b/src/plugins/platforms/xcb/qxcbbackingstore.cpp index a999fb0aa2..130ae9be0c 100644 --- a/src/plugins/platforms/xcb/qxcbbackingstore.cpp +++ b/src/plugins/platforms/xcb/qxcbbackingstore.cpp @@ -223,14 +223,14 @@ bool QXcbShmImage::scroll(const QRegion &area, int dx, int dy) preparePaint(area); const QPoint delta(dx, dy); - foreach (const QRect &rect, area.rects()) + for (const QRect &rect : area) qt_scrollRectInImage(*image(), rect, delta); if (m_xcb_pixmap) { flushPixmap(area); ensureGC(m_xcb_pixmap); const QRect bounds(QPoint(0, 0), size()); - foreach (const QRect &src, area.rects()) { + for (const QRect &src : area) { const QRect dst = src.translated(delta).intersected(bounds); Q_XCB_CALL(xcb_copy_area(xcb_connection(), m_xcb_pixmap, @@ -531,11 +531,9 @@ void QXcbBackingStore::beginPaint(const QRegion ®ion) if (m_image->hasAlpha()) { QPainter p(paintDevice()); p.setCompositionMode(QPainter::CompositionMode_Source); - const QVector<QRect> rects = region.rects(); const QColor blank = Qt::transparent; - for (QVector<QRect>::const_iterator it = rects.begin(); it != rects.end(); ++it) { - p.fillRect(*it, blank); - } + for (const QRect &rect : region) + p.fillRect(rect, blank); } } @@ -555,22 +553,21 @@ void QXcbBackingStore::endPaint() // Slow path: the paint device was m_rgbImage. Now copy with swapping red // and blue into m_image. - const QVector<QRect> rects = region.rects(); - if (rects.isEmpty()) + auto it = region.begin(); + const auto end = region.end(); + if (it == end) return; QPainter p(m_image->image()); - for (QVector<QRect>::const_iterator it = rects.begin(); it != rects.end(); ++it) { + while (it != end) { const QRect rect = *it; p.drawImage(rect.topLeft(), m_rgbImage.copy(rect).rgbSwapped()); } } -#ifndef QT_NO_OPENGL QImage QXcbBackingStore::toImage() const { return m_image && m_image->image() ? *m_image->image() : QImage(); } -#endif QPlatformGraphicsBuffer *QXcbBackingStore::graphicsBuffer() const { diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.h b/src/plugins/platforms/xcb/qxcbbackingstore.h index 5a8f385c1b..6af679d28a 100644 --- a/src/plugins/platforms/xcb/qxcbbackingstore.h +++ b/src/plugins/platforms/xcb/qxcbbackingstore.h @@ -63,8 +63,8 @@ public: void composeAndFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, QOpenGLContext *context, bool translucentBackground) Q_DECL_OVERRIDE; - QImage toImage() const Q_DECL_OVERRIDE; #endif + QImage toImage() const Q_DECL_OVERRIDE; QPlatformGraphicsBuffer *graphicsBuffer() const Q_DECL_OVERRIDE; diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp index 2c0f1f26b0..17fafd5ef0 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -2789,9 +2789,8 @@ void QXcbWindow::setMask(const QRegion ®ion) XCB_SHAPE_SK_BOUNDING, xcb_window(), 0, 0, XCB_NONE); } else { QVector<xcb_rectangle_t> rects; - const QVector<QRect> regionRects = region.rects(); - rects.reserve(regionRects.count()); - foreach (const QRect &r, regionRects) + rects.reserve(region.rectCount()); + for (const QRect &r : region) rects.push_back(qRectToXCBRectangle(r)); xcb_shape_rectangles(connection()->xcb_connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_CLIP_ORDERING_UNSORTED, diff --git a/src/plugins/platforms/xcb/xcb_qpa_lib.pro b/src/plugins/platforms/xcb/xcb_qpa_lib.pro index f4a4e5a78a..cbb4c361f9 100644 --- a/src/plugins/platforms/xcb/xcb_qpa_lib.pro +++ b/src/plugins/platforms/xcb/xcb_qpa_lib.pro @@ -48,10 +48,10 @@ contains(QT_CONFIG, xcb-xlib) { DEFINES += XCB_USE_XINPUT2 SOURCES += qxcbconnection_xi2.cpp LIBS += -lXi - !isEmpty(QMAKE_LIBXI_VERSION_MAJOR) { - DEFINES += LIBXI_MAJOR=$$QMAKE_LIBXI_VERSION_MAJOR \ - LIBXI_MINOR=$$QMAKE_LIBXI_VERSION_MINOR \ - LIBXI_PATCH=$$QMAKE_LIBXI_VERSION_PATCH + !isEmpty(QMAKE_XINPUT2_VERSION_MAJOR) { + DEFINES += LIBXI_MAJOR=$$QMAKE_XINPUT2_VERSION_MAJOR \ + LIBXI_MINOR=$$QMAKE_XINPUT2_VERSION_MINOR \ + LIBXI_PATCH=$$QMAKE_XINPUT2_VERSION_PATCH } } } @@ -81,7 +81,7 @@ CONFIG += qpa/genericunixfontdatabase contains(QT_CONFIG, dbus-linked) { QT += dbus - LIBS += $$QT_LIBS_DBUS + LIBS += $$QMAKE_LIBS_DBUS } contains(QT_CONFIG, xcb-qt) { diff --git a/src/plugins/platformthemes/gtk3/gtk3.pro b/src/plugins/platformthemes/gtk3/gtk3.pro index cd19e73ed8..557918c5a4 100644 --- a/src/plugins/platformthemes/gtk3/gtk3.pro +++ b/src/plugins/platformthemes/gtk3/gtk3.pro @@ -8,8 +8,8 @@ load(qt_plugin) QT += core-private gui-private platformsupport-private CONFIG += X11 -QMAKE_CXXFLAGS += $$QT_CFLAGS_QGTK3 -LIBS += $$QT_LIBS_QGTK3 +QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_GTK3 +LIBS += $$QMAKE_LIBS_GTK3 HEADERS += \ qgtk3dialoghelpers.h \ diff --git a/src/plugins/sqldrivers/db2/db2.pro b/src/plugins/sqldrivers/db2/db2.pro index 2365c5bc0e..31822ef8dc 100644 --- a/src/plugins/sqldrivers/db2/db2.pro +++ b/src/plugins/sqldrivers/db2/db2.pro @@ -1,8 +1,15 @@ TARGET = qsqldb2 -SOURCES = main.cpp +HEADERS += $$PWD/qsql_db2_p.h +SOURCES += $$PWD/qsql_db2.cpp $$PWD/main.cpp + +unix { + !contains(LIBS, .*db2.*):LIBS += -ldb2 +} else { + !contains(LIBS, .*db2.*):LIBS += -ldb2cli +} + OTHER_FILES += db2.json -include(../../../sql/drivers/db2/qsql_db2.pri) PLUGIN_CLASS_NAME = QDB2DriverPlugin include(../qsqldriverbase.pri) diff --git a/src/plugins/sqldrivers/db2/main.cpp b/src/plugins/sqldrivers/db2/main.cpp index 3869d7798d..97338b8eef 100644 --- a/src/plugins/sqldrivers/db2/main.cpp +++ b/src/plugins/sqldrivers/db2/main.cpp @@ -39,7 +39,7 @@ #include <qsqldriverplugin.h> #include <qstringlist.h> -#include "../../../sql/drivers/db2/qsql_db2_p.h" +#include "qsql_db2_p.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/sqldrivers/db2/qsql_db2.cpp b/src/plugins/sqldrivers/db2/qsql_db2.cpp new file mode 100644 index 0000000000..4ccc3aca9e --- /dev/null +++ b/src/plugins/sqldrivers/db2/qsql_db2.cpp @@ -0,0 +1,1700 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_db2_p.h" +#include <qcoreapplication.h> +#include <qdatetime.h> +#include <qsqlfield.h> +#include <qsqlerror.h> +#include <qsqlindex.h> +#include <qsqlrecord.h> +#include <qstringlist.h> +#include <qvarlengtharray.h> +#include <qvector.h> +#include <QDebug> +#include <QtSql/private/qsqldriver_p.h> +#include <QtSql/private/qsqlresult_p.h> + +#if defined(Q_CC_BOR) +// DB2's sqlsystm.h (included through sqlcli1.h) defines the SQL_BIGINT_TYPE +// and SQL_BIGUINT_TYPE to wrong the types for Borland; so do the defines to +// the right type before including the header +#define SQL_BIGINT_TYPE qint64 +#define SQL_BIGUINT_TYPE quint64 +#endif + +#define UNICODE + +#include <sqlcli1.h> + +#include <string.h> + +QT_BEGIN_NAMESPACE + +static const int COLNAMESIZE = 255; +static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT }; + +class QDB2DriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QDB2Driver) + +public: + QDB2DriverPrivate() : QSqlDriverPrivate(), hEnv(0), hDbc(0) { dbmsType = QSqlDriver::DB2; } + SQLHANDLE hEnv; + SQLHANDLE hDbc; + QString user; +}; + +class QDB2ResultPrivate; + +class QDB2Result: public QSqlResult +{ + Q_DECLARE_PRIVATE(QDB2Result) + +public: + QDB2Result(const QDB2Driver *drv); + ~QDB2Result(); + bool prepare(const QString &query) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + QVariant data(int field) Q_DECL_OVERRIDE; + bool reset(const QString &query) Q_DECL_OVERRIDE; + bool fetch(int i) Q_DECL_OVERRIDE; + bool fetchNext() Q_DECL_OVERRIDE; + bool fetchFirst() Q_DECL_OVERRIDE; + bool fetchLast() Q_DECL_OVERRIDE; + bool isNull(int i) Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; + void detachFromResultSet() Q_DECL_OVERRIDE; + bool nextResult() Q_DECL_OVERRIDE; +}; + +class QDB2ResultPrivate: public QSqlResultPrivate +{ + Q_DECLARE_PUBLIC(QDB2Result) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QDB2Driver) + QDB2ResultPrivate(QDB2Result *q, const QDB2Driver *drv) + : QSqlResultPrivate(q, drv), + hStmt(0) + {} + ~QDB2ResultPrivate() + { + emptyValueCache(); + } + void clearValueCache() + { + for (int i = 0; i < valueCache.count(); ++i) { + delete valueCache[i]; + valueCache[i] = NULL; + } + } + void emptyValueCache() + { + clearValueCache(); + valueCache.clear(); + } + + SQLHANDLE hStmt; + QSqlRecord recInf; + QVector<QVariant*> valueCache; +}; + +static QString qFromTChar(SQLTCHAR* str) +{ + return QString((const QChar *)str); +} + +// dangerous!! (but fast). Don't use in functions that +// require out parameters! +static SQLTCHAR* qToTChar(const QString& str) +{ + return (SQLTCHAR*)str.utf16(); +} + +static QString qWarnDB2Handle(int handleType, SQLHANDLE handle) +{ + SQLINTEGER nativeCode; + SQLSMALLINT msgLen; + SQLRETURN r = SQL_ERROR; + SQLTCHAR state[SQL_SQLSTATE_SIZE + 1]; + SQLTCHAR description[SQL_MAX_MESSAGE_LENGTH]; + r = SQLGetDiagRec(handleType, + handle, + 1, + (SQLTCHAR*) state, + &nativeCode, + (SQLTCHAR*) description, + SQL_MAX_MESSAGE_LENGTH - 1, /* in bytes, not in characters */ + &msgLen); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) + return QString(qFromTChar(description)); + return QString(); +} + +static QString qDB2Warn(const QDB2DriverPrivate* d) +{ + return (qWarnDB2Handle(SQL_HANDLE_ENV, d->hEnv) + QLatin1Char(' ') + + qWarnDB2Handle(SQL_HANDLE_DBC, d->hDbc)); +} + +static QString qDB2Warn(const QDB2ResultPrivate* d) +{ + return (qWarnDB2Handle(SQL_HANDLE_ENV, d->drv_d_func()->hEnv) + QLatin1Char(' ') + + qWarnDB2Handle(SQL_HANDLE_DBC, d->drv_d_func()->hDbc) + + qWarnDB2Handle(SQL_HANDLE_STMT, d->hStmt)); +} + +static void qSqlWarning(const QString& message, const QDB2DriverPrivate* d) +{ + qWarning("%s\tError: %s", message.toLocal8Bit().constData(), + qDB2Warn(d).toLocal8Bit().constData()); +} + +static void qSqlWarning(const QString& message, const QDB2ResultPrivate* d) +{ + qWarning("%s\tError: %s", message.toLocal8Bit().constData(), + qDB2Warn(d).toLocal8Bit().constData()); +} + +static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, + const QDB2DriverPrivate* p) +{ + return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type); +} + +static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, + const QDB2ResultPrivate* p) +{ + return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type); +} + +static QVariant::Type qDecodeDB2Type(SQLSMALLINT sqltype) +{ + QVariant::Type type = QVariant::Invalid; + switch (sqltype) { + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_DECIMAL: + case SQL_NUMERIC: + type = QVariant::Double; + break; + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIT: + case SQL_TINYINT: + type = QVariant::Int; + break; + case SQL_BIGINT: + type = QVariant::LongLong; + break; + case SQL_BLOB: + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + case SQL_CLOB: + case SQL_DBCLOB: + type = QVariant::ByteArray; + break; + case SQL_DATE: + case SQL_TYPE_DATE: + type = QVariant::Date; + break; + case SQL_TIME: + case SQL_TYPE_TIME: + type = QVariant::Time; + break; + case SQL_TIMESTAMP: + case SQL_TYPE_TIMESTAMP: + type = QVariant::DateTime; + break; + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + type = QVariant::String; + break; + default: + type = QVariant::ByteArray; + break; + } + return type; +} + +static QSqlField qMakeFieldInfo(const QDB2ResultPrivate* d, int i) +{ + SQLSMALLINT colNameLen; + SQLSMALLINT colType; + SQLUINTEGER colSize; + SQLSMALLINT colScale; + SQLSMALLINT nullable; + SQLRETURN r = SQL_ERROR; + SQLTCHAR colName[COLNAMESIZE]; + r = SQLDescribeCol(d->hStmt, + i+1, + colName, + (SQLSMALLINT) COLNAMESIZE, + &colNameLen, + &colType, + &colSize, + &colScale, + &nullable); + + if (r != SQL_SUCCESS) { + qSqlWarning(QString::fromLatin1("qMakeFieldInfo: Unable to describe column %1").arg(i), d); + return QSqlField(); + } + QSqlField f(qFromTChar(colName), qDecodeDB2Type(colType)); + // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN + if (nullable == SQL_NO_NULLS) + f.setRequired(true); + else if (nullable == SQL_NULLABLE) + f.setRequired(false); + // else required is unknown + f.setLength(colSize == 0 ? -1 : int(colSize)); + f.setPrecision(colScale == 0 ? -1 : int(colScale)); + f.setSqlType(int(colType)); + return f; +} + +static int qGetIntData(SQLHANDLE hStmt, int column, bool& isNull) +{ + SQLINTEGER intbuf; + isNull = false; + SQLINTEGER lengthIndicator = 0; + SQLRETURN r = SQLGetData(hStmt, + column + 1, + SQL_C_SLONG, + (SQLPOINTER) &intbuf, + 0, + &lengthIndicator); + if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) { + isNull = true; + return 0; + } + return int(intbuf); +} + +static double qGetDoubleData(SQLHANDLE hStmt, int column, bool& isNull) +{ + SQLDOUBLE dblbuf; + isNull = false; + SQLINTEGER lengthIndicator = 0; + SQLRETURN r = SQLGetData(hStmt, + column+1, + SQL_C_DOUBLE, + (SQLPOINTER) &dblbuf, + 0, + &lengthIndicator); + if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) { + isNull = true; + return 0.0; + } + + return (double) dblbuf; +} + +static SQLBIGINT qGetBigIntData(SQLHANDLE hStmt, int column, bool& isNull) +{ + SQLBIGINT lngbuf = Q_INT64_C(0); + isNull = false; + SQLINTEGER lengthIndicator = 0; + SQLRETURN r = SQLGetData(hStmt, + column+1, + SQL_C_SBIGINT, + (SQLPOINTER) &lngbuf, + 0, + &lengthIndicator); + if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) + isNull = true; + + return lngbuf; +} + +static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool& isNull) +{ + QString fieldVal; + SQLRETURN r = SQL_ERROR; + SQLINTEGER lengthIndicator = 0; + + if (colSize <= 0) + colSize = 255; + 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 + SQLTCHAR* buf = new SQLTCHAR[colSize]; + + while (true) { + r = SQLGetData(hStmt, + column + 1, + SQL_C_WCHAR, + (SQLPOINTER)buf, + colSize * sizeof(SQLTCHAR), + &lengthIndicator); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { + if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) { + fieldVal.clear(); + isNull = true; + break; + } + fieldVal += qFromTChar(buf); + } else if (r == SQL_NO_DATA) { + break; + } else { + qWarning("qGetStringData: Error while fetching data (%d)", r); + fieldVal.clear(); + break; + } + } + delete[] buf; + return fieldVal; +} + +static QByteArray qGetBinaryData(SQLHANDLE hStmt, int column, SQLINTEGER& lengthIndicator, bool& isNull) +{ + QByteArray fieldVal; + SQLSMALLINT colNameLen; + SQLSMALLINT colType; + SQLUINTEGER colSize; + SQLSMALLINT colScale; + SQLSMALLINT nullable; + SQLRETURN r = SQL_ERROR; + + SQLTCHAR colName[COLNAMESIZE]; + r = SQLDescribeCol(hStmt, + column+1, + colName, + COLNAMESIZE, + &colNameLen, + &colType, + &colSize, + &colScale, + &nullable); + if (r != SQL_SUCCESS) + qWarning("qGetBinaryData: Unable to describe column %d", column); + // SQLDescribeCol may return 0 if size cannot be determined + if (!colSize) + colSize = 255; + else if (colSize > 65536) // read the field in 64 KB chunks + colSize = 65536; + char * buf = new char[colSize]; + while (true) { + r = SQLGetData(hStmt, + column+1, + colType == SQL_DBCLOB ? SQL_C_CHAR : SQL_C_BINARY, + (SQLPOINTER) buf, + colSize, + &lengthIndicator); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { + if (lengthIndicator == SQL_NULL_DATA) { + isNull = true; + break; + } else { + int rSize; + r == SQL_SUCCESS ? rSize = lengthIndicator : rSize = colSize; + if (lengthIndicator == SQL_NO_TOTAL) // size cannot be determined + rSize = colSize; + fieldVal.append(QByteArray(buf, rSize)); + if (r == SQL_SUCCESS) // the whole field was read in one chunk + break; + } + } else { + break; + } + } + delete [] buf; + return fieldVal; +} + +static void qSplitTableQualifier(const QString & qualifier, QString * catalog, + QString * schema, QString * table) +{ + if (!catalog || !schema || !table) + return; + QStringList l = qualifier.split(QLatin1Char('.')); + 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++; + } + } +} + +// 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) +{ + bool isNull; + int type = qGetIntData(hStmt, 4, isNull); + QSqlField f(qGetStringData(hStmt, 3, -1, isNull), qDecodeDB2Type(type)); + int required = qGetIntData(hStmt, 10, isNull); // nullable-flag + // required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN + if (required == SQL_NO_NULLS) + f.setRequired(true); + else if (required == SQL_NULLABLE) + f.setRequired(false); + // 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; +} + +static bool qMakeStatement(QDB2ResultPrivate* d, bool forwardOnly, bool setForwardOnly = true) +{ + SQLRETURN r; + if (!d->hStmt) { + r = SQLAllocHandle(SQL_HANDLE_STMT, + d->drv_d_func()->hDbc, + &d->hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QDB2Result::reset: Unable to allocate statement handle"), d); + return false; + } + } else { + r = SQLFreeStmt(d->hStmt, SQL_CLOSE); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QDB2Result::reset: Unable to close statement handle"), d); + return false; + } + } + + if (!setForwardOnly) + return true; + + if (forwardOnly) { + r = SQLSetStmtAttr(d->hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER) SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + } else { + r = SQLSetStmtAttr(d->hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER) SQL_CURSOR_STATIC, + SQL_IS_UINTEGER); + } + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + qSqlWarning(QString::fromLatin1("QDB2Result::reset: Unable to set %1 attribute.").arg( + forwardOnly ? QLatin1String("SQL_CURSOR_FORWARD_ONLY") + : QLatin1String("SQL_CURSOR_STATIC")), d); + return false; + } + return true; +} + +QVariant QDB2Result::handle() const +{ + Q_D(const QDB2Result); + return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt); +} + +/************************************/ + +QDB2Result::QDB2Result(const QDB2Driver *drv) + : QSqlResult(*new QDB2ResultPrivate(this, drv)) +{ +} + +QDB2Result::~QDB2Result() +{ + Q_D(const QDB2Result); + if (d->hStmt) { + SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ") + + QString::number(r), d); + } +} + +bool QDB2Result::reset (const QString& query) +{ + Q_D(QDB2Result); + setActive(false); + setAt(QSql::BeforeFirstRow); + SQLRETURN r; + + d->recInf.clear(); + d->emptyValueCache(); + + if (!qMakeStatement(d, isForwardOnly())) + return false; + + r = SQLExecDirect(d->hStmt, + qToTChar(query), + (SQLINTEGER) query.length()); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + setLastError(qMakeError(QCoreApplication::translate("QDB2Result", + "Unable to execute statement"), QSqlError::StatementError, d)); + return false; + } + SQLSMALLINT count = 0; + r = SQLNumResultCols(d->hStmt, &count); + if (count) { + setSelect(true); + for (int i = 0; i < count; ++i) { + d->recInf.append(qMakeFieldInfo(d, i)); + } + } else { + setSelect(false); + } + d->valueCache.resize(count); + d->valueCache.fill(NULL); + setActive(true); + return true; +} + +bool QDB2Result::prepare(const QString& query) +{ + Q_D(QDB2Result); + setActive(false); + setAt(QSql::BeforeFirstRow); + SQLRETURN r; + + d->recInf.clear(); + d->emptyValueCache(); + + if (!qMakeStatement(d, isForwardOnly())) + return false; + + r = SQLPrepare(d->hStmt, + qToTChar(query), + (SQLINTEGER) query.length()); + + if (r != SQL_SUCCESS) { + setLastError(qMakeError(QCoreApplication::translate("QDB2Result", + "Unable to prepare statement"), QSqlError::StatementError, d)); + return false; + } + return true; +} + +bool QDB2Result::exec() +{ + Q_D(QDB2Result); + QList<QByteArray> tmpStorage; // holds temporary ptrs + QVarLengthArray<SQLINTEGER, 32> indicators(boundValues().count()); + + memset(indicators.data(), 0, indicators.size() * sizeof(SQLINTEGER)); + setActive(false); + setAt(QSql::BeforeFirstRow); + SQLRETURN r; + + d->recInf.clear(); + d->emptyValueCache(); + + if (!qMakeStatement(d, isForwardOnly(), false)) + return false; + + + QVector<QVariant> &values = boundValues(); + int i; + for (i = 0; i < values.count(); ++i) { + // bind parameters - only positional binding allowed + SQLINTEGER *ind = &indicators[i]; + if (values.at(i).isNull()) + *ind = SQL_NULL_DATA; + if (bindValueType(i) & QSql::Out) + values[i].detach(); + + switch (values.at(i).type()) { + case QVariant::Date: { + QByteArray ba; + ba.resize(sizeof(DATE_STRUCT)); + DATE_STRUCT *dt = (DATE_STRUCT *)ba.constData(); + QDate qdt = values.at(i).toDate(); + dt->year = qdt.year(); + dt->month = qdt.month(); + dt->day = qdt.day(); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & 3], + SQL_C_DATE, + SQL_DATE, + 0, + 0, + (void *) dt, + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + tmpStorage.append(ba); + break; } + case QVariant::Time: { + QByteArray ba; + ba.resize(sizeof(TIME_STRUCT)); + TIME_STRUCT *dt = (TIME_STRUCT *)ba.constData(); + QTime qdt = values.at(i).toTime(); + dt->hour = qdt.hour(); + dt->minute = qdt.minute(); + dt->second = qdt.second(); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & 3], + SQL_C_TIME, + SQL_TIME, + 0, + 0, + (void *) dt, + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + tmpStorage.append(ba); + break; } + case QVariant::DateTime: { + QByteArray ba; + ba.resize(sizeof(TIMESTAMP_STRUCT)); + TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)ba.constData(); + QDateTime qdt = values.at(i).toDateTime(); + dt->year = qdt.date().year(); + dt->month = qdt.date().month(); + dt->day = qdt.date().day(); + dt->hour = qdt.time().hour(); + dt->minute = qdt.time().minute(); + dt->second = qdt.time().second(); + dt->fraction = qdt.time().msec() * 1000000; + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & 3], + SQL_C_TIMESTAMP, + SQL_TIMESTAMP, + 0, + 0, + (void *) dt, + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + tmpStorage.append(ba); + break; } + case QVariant::Int: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & 3], + SQL_C_SLONG, + SQL_INTEGER, + 0, + 0, + (void *)values.at(i).constData(), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::Double: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & 3], + SQL_C_DOUBLE, + SQL_DOUBLE, + 0, + 0, + (void *)values.at(i).constData(), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::ByteArray: { + int len = values.at(i).toByteArray().size(); + if (*ind != SQL_NULL_DATA) + *ind = len; + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & 3], + SQL_C_BINARY, + SQL_LONGVARBINARY, + len, + 0, + (void *)values.at(i).toByteArray().constData(), + len, + ind); + break; } + case QVariant::String: + { + QString str(values.at(i).toString()); + if (*ind != SQL_NULL_DATA) + *ind = str.length() * sizeof(QChar); + if (bindValueType(i) & QSql::Out) { + QByteArray ba((char*)str.utf16(), str.capacity() * sizeof(QChar)); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & 3], + SQL_C_WCHAR, + SQL_WVARCHAR, + str.length(), + 0, + (void *)ba.constData(), + ba.size(), + ind); + tmpStorage.append(ba); + } else { + void *data = (void*)str.utf16(); + int len = str.length(); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & 3], + SQL_C_WCHAR, + SQL_WVARCHAR, + len, + 0, + data, + len * sizeof(QChar), + ind); + } + break; + } + default: { + QByteArray ba = values.at(i).toString().toLatin1(); + int len = ba.length() + 1; + if (*ind != SQL_NULL_DATA) + *ind = ba.length(); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & 3], + SQL_C_CHAR, + SQL_VARCHAR, + len, + 0, + (void *) ba.constData(), + len, + ind); + tmpStorage.append(ba); + break; } + } + if (r != SQL_SUCCESS) { + qWarning("QDB2Result::exec: unable to bind variable: %s", + qDB2Warn(d).toLocal8Bit().constData()); + setLastError(qMakeError(QCoreApplication::translate("QDB2Result", + "Unable to bind variable"), QSqlError::StatementError, d)); + return false; + } + } + + r = SQLExecute(d->hStmt); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + qWarning("QDB2Result::exec: Unable to execute statement: %s", + qDB2Warn(d).toLocal8Bit().constData()); + setLastError(qMakeError(QCoreApplication::translate("QDB2Result", + "Unable to execute statement"), QSqlError::StatementError, d)); + return false; + } + SQLSMALLINT count = 0; + r = SQLNumResultCols(d->hStmt, &count); + if (count) { + setSelect(true); + for (int i = 0; i < count; ++i) { + d->recInf.append(qMakeFieldInfo(d, i)); + } + } else { + setSelect(false); + } + setActive(true); + d->valueCache.resize(count); + d->valueCache.fill(NULL); + + //get out parameters + if (!hasOutValues()) + return true; + + for (i = 0; i < values.count(); ++i) { + switch (values[i].type()) { + case QVariant::Date: { + DATE_STRUCT ds = *((DATE_STRUCT *)tmpStorage.takeFirst().constData()); + values[i] = QVariant(QDate(ds.year, ds.month, ds.day)); + break; } + case QVariant::Time: { + TIME_STRUCT dt = *((TIME_STRUCT *)tmpStorage.takeFirst().constData()); + values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second)); + break; } + case QVariant::DateTime: { + TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT *)tmpStorage.takeFirst().constData()); + values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day), + QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000))); + break; } + case QVariant::Int: + case QVariant::Double: + case QVariant::ByteArray: + break; + case QVariant::String: + if (bindValueType(i) & QSql::Out) + values[i] = QString((const QChar *)tmpStorage.takeFirst().constData()); + break; + default: { + values[i] = QString::fromLatin1(tmpStorage.takeFirst().constData()); + break; } + } + if (indicators[i] == SQL_NULL_DATA) + values[i] = QVariant(values[i].type()); + } + return true; +} + +bool QDB2Result::fetch(int i) +{ + Q_D(QDB2Result); + if (isForwardOnly() && i < at()) + return false; + if (i == at()) + return true; + d->clearValueCache(); + int actualIdx = i + 1; + if (actualIdx <= 0) { + setAt(QSql::BeforeFirstRow); + return false; + } + SQLRETURN r; + if (isForwardOnly()) { + bool ok = true; + while (ok && i > at()) + ok = fetchNext(); + return ok; + } else { + r = SQLFetchScroll(d->hStmt, + SQL_FETCH_ABSOLUTE, + actualIdx); + } + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) { + setLastError(qMakeError(QCoreApplication::translate("QDB2Result", + "Unable to fetch record %1").arg(i), QSqlError::StatementError, d)); + return false; + } + else if (r == SQL_NO_DATA) + return false; + setAt(i); + return true; +} + +bool QDB2Result::fetchNext() +{ + Q_D(QDB2Result); + SQLRETURN r; + d->clearValueCache(); + r = SQLFetchScroll(d->hStmt, + SQL_FETCH_NEXT, + 0); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + if (r != SQL_NO_DATA) + setLastError(qMakeError(QCoreApplication::translate("QDB2Result", + "Unable to fetch next"), QSqlError::StatementError, d)); + return false; + } + setAt(at() + 1); + return true; +} + +bool QDB2Result::fetchFirst() +{ + Q_D(QDB2Result); + if (isForwardOnly() && at() != QSql::BeforeFirstRow) + return false; + if (isForwardOnly()) + return fetchNext(); + d->clearValueCache(); + SQLRETURN r; + r = SQLFetchScroll(d->hStmt, + SQL_FETCH_FIRST, + 0); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + if(r!= SQL_NO_DATA) + setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to fetch first"), + QSqlError::StatementError, d)); + return false; + } + setAt(0); + return true; +} + +bool QDB2Result::fetchLast() +{ + Q_D(QDB2Result); + d->clearValueCache(); + + int i = at(); + if (i == QSql::AfterLastRow) { + if (isForwardOnly()) { + return false; + } else { + if (!fetch(0)) + return false; + i = at(); + } + } + + while (fetchNext()) + ++i; + + if (i == QSql::BeforeFirstRow) { + setAt(QSql::AfterLastRow); + return false; + } + + if (!isForwardOnly()) + return fetch(i); + + setAt(i); + return true; +} + + +QVariant QDB2Result::data(int field) +{ + Q_D(QDB2Result); + if (field >= d->recInf.count()) { + qWarning("QDB2Result::data: column %d out of range", field); + return QVariant(); + } + SQLRETURN r = 0; + SQLINTEGER lengthIndicator = 0; + bool isNull = false; + const QSqlField info = d->recInf.field(field); + + if (!info.isValid() || field >= d->valueCache.size()) + return QVariant(); + + if (d->valueCache[field]) + return *d->valueCache[field]; + + + QVariant* v = 0; + switch (info.type()) { + case QVariant::LongLong: + v = new QVariant((qint64) qGetBigIntData(d->hStmt, field, isNull)); + break; + case QVariant::Int: + v = new QVariant(qGetIntData(d->hStmt, field, isNull)); + break; + case QVariant::Date: { + DATE_STRUCT dbuf; + r = SQLGetData(d->hStmt, + field + 1, + SQL_C_DATE, + (SQLPOINTER) &dbuf, + 0, + &lengthIndicator); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) { + v = new QVariant(QDate(dbuf.year, dbuf.month, dbuf.day)); + } else { + v = new QVariant(QDate()); + isNull = true; + } + break; } + case QVariant::Time: { + TIME_STRUCT tbuf; + r = SQLGetData(d->hStmt, + field + 1, + SQL_C_TIME, + (SQLPOINTER) &tbuf, + 0, + &lengthIndicator); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) { + v = new QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second)); + } else { + v = new QVariant(QTime()); + isNull = true; + } + break; } + case QVariant::DateTime: { + TIMESTAMP_STRUCT dtbuf; + r = SQLGetData(d->hStmt, + field + 1, + SQL_C_TIMESTAMP, + (SQLPOINTER) &dtbuf, + 0, + &lengthIndicator); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) { + v = new QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day), + QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000))); + } else { + v = new QVariant(QDateTime()); + isNull = true; + } + break; } + case QVariant::ByteArray: + v = new QVariant(qGetBinaryData(d->hStmt, field, lengthIndicator, isNull)); + break; + case QVariant::Double: + { + switch(numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + v = new QVariant(qGetIntData(d->hStmt, field, isNull)); + break; + case QSql::LowPrecisionInt64: + v = new QVariant((qint64) qGetBigIntData(d->hStmt, field, isNull)); + break; + case QSql::LowPrecisionDouble: + v = new QVariant(qGetDoubleData(d->hStmt, field, isNull)); + break; + case QSql::HighPrecision: + default: + // length + 1 for the comma + v = new QVariant(qGetStringData(d->hStmt, field, info.length() + 1, isNull)); + break; + } + break; + } + case QVariant::String: + default: + v = new QVariant(qGetStringData(d->hStmt, field, info.length(), isNull)); + break; + } + if (isNull) + *v = QVariant(info.type()); + d->valueCache[field] = v; + return *v; +} + +bool QDB2Result::isNull(int i) +{ + Q_D(const QDB2Result); + if (i >= d->valueCache.size()) + return true; + + if (d->valueCache[i]) + return d->valueCache[i]->isNull(); + return data(i).isNull(); +} + +int QDB2Result::numRowsAffected() +{ + Q_D(const QDB2Result); + SQLINTEGER affectedRowCount = 0; + SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) + return affectedRowCount; + else + qSqlWarning(QLatin1String("QDB2Result::numRowsAffected: Unable to count affected rows"), d); + return -1; +} + +int QDB2Result::size() +{ + return -1; +} + +QSqlRecord QDB2Result::record() const +{ + Q_D(const QDB2Result); + if (isActive()) + return d->recInf; + return QSqlRecord(); +} + +bool QDB2Result::nextResult() +{ + Q_D(QDB2Result); + setActive(false); + setAt(QSql::BeforeFirstRow); + d->recInf.clear(); + d->emptyValueCache(); + setSelect(false); + + SQLRETURN r = SQLMoreResults(d->hStmt); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + if (r != SQL_NO_DATA) { + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to fetch last"), QSqlError::ConnectionError, d)); + } + return false; + } + + SQLSMALLINT fieldCount = 0; + r = SQLNumResultCols(d->hStmt, &fieldCount); + setSelect(fieldCount > 0); + for (int i = 0; i < fieldCount; ++i) + d->recInf.append(qMakeFieldInfo(d, i)); + + d->valueCache.resize(fieldCount); + d->valueCache.fill(NULL); + setActive(true); + + return true; +} + +void QDB2Result::virtual_hook(int id, void *data) +{ + QSqlResult::virtual_hook(id, data); +} + +void QDB2Result::detachFromResultSet() +{ + Q_D(QDB2Result); + if (d->hStmt) + SQLCloseCursor(d->hStmt); +} + +/************************************/ + +QDB2Driver::QDB2Driver(QObject* parent) + : QSqlDriver(*new QDB2DriverPrivate, parent) +{ +} + +QDB2Driver::QDB2Driver(Qt::HANDLE env, Qt::HANDLE con, QObject* parent) + : QSqlDriver(*new QDB2DriverPrivate, parent) +{ + Q_D(QDB2Driver); + d->hEnv = reinterpret_cast<intptr_t>(env); + d->hDbc = reinterpret_cast<intptr_t>(con); + if (env && con) { + setOpen(true); + setOpenError(false); + } +} + +QDB2Driver::~QDB2Driver() +{ + close(); +} + +bool QDB2Driver::open(const QString& db, const QString& user, const QString& password, const QString& host, int port, + const QString& connOpts) +{ + Q_D(QDB2Driver); + if (isOpen()) + close(); + SQLRETURN r; + r = SQLAllocHandle(SQL_HANDLE_ENV, + SQL_NULL_HANDLE, + &d->hEnv); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + qSqlWarning(QLatin1String("QDB2Driver::open: Unable to allocate environment"), d); + setOpenError(true); + return false; + } + + r = SQLAllocHandle(SQL_HANDLE_DBC, + d->hEnv, + &d->hDbc); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + qSqlWarning(QLatin1String("QDB2Driver::open: Unable to allocate connection"), d); + setOpenError(true); + return false; + } + + QString protocol; + // Set connection attributes + const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts)); + for (int i = 0; i < opts.count(); ++i) { + const QString tmp(opts.at(i)); + int idx; + if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) { + qWarning("QDB2Driver::open: Illegal connect option value '%s'", + tmp.toLocal8Bit().constData()); + continue; + } + + const QString opt(tmp.left(idx)); + const QString val(tmp.mid(idx + 1).simplified()); + + SQLUINTEGER v = 0; + r = SQL_SUCCESS; + if (opt == QLatin1String("SQL_ATTR_ACCESS_MODE")) { + if (val == QLatin1String("SQL_MODE_READ_ONLY")) { + v = SQL_MODE_READ_ONLY; + } else if (val == QLatin1String("SQL_MODE_READ_WRITE")) { + v = SQL_MODE_READ_WRITE; + } else { + qWarning("QDB2Driver::open: Unknown option value '%s'", + tmp.toLocal8Bit().constData()); + continue; + } + r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_ACCESS_MODE, reinterpret_cast<SQLPOINTER>(v), 0); + } else if (opt == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) { + v = val.toUInt(); + r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_LOGIN_TIMEOUT, reinterpret_cast<SQLPOINTER>(v), 0); + } else if (opt.compare(QLatin1String("PROTOCOL"), Qt::CaseInsensitive) == 0) { + protocol = tmp; + } + else { + qWarning("QDB2Driver::open: Unknown connection attribute '%s'", + tmp.toLocal8Bit().constData()); + } + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) + qSqlWarning(QString::fromLatin1("QDB2Driver::open: " + "Unable to set connection attribute '%1'").arg(opt), d); + } + + if (protocol.isEmpty()) + protocol = QLatin1String("PROTOCOL=TCPIP"); + + if (port < 0 ) + port = 50000; + + QString connQStr; + connQStr = protocol + QLatin1String(";DATABASE=") + db + QLatin1String(";HOSTNAME=") + host + + QLatin1String(";PORT=") + QString::number(port) + QLatin1String(";UID=") + user + + QLatin1String(";PWD=") + password; + + + SQLTCHAR connOut[SQL_MAX_OPTION_STRING_LENGTH]; + SQLSMALLINT cb; + + r = SQLDriverConnect(d->hDbc, + NULL, + qToTChar(connQStr), + (SQLSMALLINT) connQStr.length(), + connOut, + SQL_MAX_OPTION_STRING_LENGTH, + &cb, + SQL_DRIVER_NOPROMPT); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + setLastError(qMakeError(tr("Unable to connect"), + QSqlError::ConnectionError, d)); + setOpenError(true); + return false; + } + + d->user = user; + setOpen(true); + setOpenError(false); + return true; +} + +void QDB2Driver::close() +{ + Q_D(QDB2Driver); + SQLRETURN r; + if (d->hDbc) { + // Open statements/descriptors handles are automatically cleaned up by SQLDisconnect + if (isOpen()) { + r = SQLDisconnect(d->hDbc); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QDB2Driver::close: Unable to disconnect datasource"), d); + } + r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QDB2Driver::close: Unable to free connection handle"), d); + d->hDbc = 0; + } + + if (d->hEnv) { + r = SQLFreeHandle(SQL_HANDLE_ENV, d->hEnv); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QDB2Driver::close: Unable to free environment handle"), d); + d->hEnv = 0; + } + setOpen(false); + setOpenError(false); +} + +QSqlResult *QDB2Driver::createResult() const +{ + return new QDB2Result(this); +} + +QSqlRecord QDB2Driver::record(const QString& tableName) const +{ + Q_D(const QDB2Driver); + QSqlRecord fil; + if (!isOpen()) + return fil; + + SQLHANDLE hStmt; + QString catalog, schema, table; + qSplitTableQualifier(tableName, &catalog, &schema, &table); + if (schema.isEmpty()) + schema = d->user; + + if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) + catalog = stripDelimiters(catalog, QSqlDriver::TableName); + else + catalog = catalog.toUpper(); + + if (isIdentifierEscaped(schema, QSqlDriver::TableName)) + schema = stripDelimiters(schema, QSqlDriver::TableName); + else + schema = schema.toUpper(); + + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + else + table = table.toUpper(); + + SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, + d->hDbc, + &hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QDB2Driver::record: Unable to allocate handle"), d); + return fil; + } + + r = SQLSetStmtAttr(hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER) SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + + + //Aside: szSchemaName and szTableName parameters of SQLColumns + //are case sensitive search patterns, so no escaping is used. + r = SQLColumns(hStmt, + NULL, + 0, + qToTChar(schema), + schema.length(), + qToTChar(table), + table.length(), + NULL, + 0); + + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QDB2Driver::record: Unable to execute column list"), d); + r = SQLFetchScroll(hStmt, + SQL_FETCH_NEXT, + 0); + while (r == SQL_SUCCESS) { + fil.append(qMakeFieldInfo(hStmt)); + r = SQLFetchScroll(hStmt, + SQL_FETCH_NEXT, + 0); + } + + r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ") + + QString::number(r), d); + + return fil; +} + +QStringList QDB2Driver::tables(QSql::TableType type) const +{ + Q_D(const QDB2Driver); + QStringList tl; + if (!isOpen()) + return tl; + + SQLHANDLE hStmt; + + SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, + d->hDbc, + &hStmt); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to allocate handle"), d); + return tl; + } + r = SQLSetStmtAttr(hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + + QString tableType; + if (type & QSql::Tables) + tableType += QLatin1String("TABLE,"); + if (type & QSql::Views) + tableType += QLatin1String("VIEW,"); + if (type & QSql::SystemTables) + tableType += QLatin1String("SYSTEM TABLE,"); + if (tableType.isEmpty()) + return tl; + tableType.chop(1); + + r = SQLTables(hStmt, + NULL, + 0, + NULL, + 0, + NULL, + 0, + qToTChar(tableType), + tableType.length()); + + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) + qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to execute table list"), d); + r = SQLFetchScroll(hStmt, + SQL_FETCH_NEXT, + 0); + while (r == SQL_SUCCESS) { + bool isNull; + QString fieldVal = qGetStringData(hStmt, 2, -1, isNull); + QString userVal = qGetStringData(hStmt, 1, -1, isNull); + QString user = d->user; + if ( isIdentifierEscaped(user, QSqlDriver::TableName)) + user = stripDelimiters(user, QSqlDriver::TableName); + else + user = user.toUpper(); + + if (userVal != user) + fieldVal = userVal + QLatin1Char('.') + fieldVal; + tl.append(fieldVal); + r = SQLFetchScroll(hStmt, + SQL_FETCH_NEXT, + 0); + } + + r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to free statement handle ") + + QString::number(r), d); + return tl; +} + +QSqlIndex QDB2Driver::primaryIndex(const QString& tablename) const +{ + Q_D(const QDB2Driver); + QSqlIndex index(tablename); + if (!isOpen()) + return index; + QSqlRecord rec = record(tablename); + + SQLHANDLE hStmt; + SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, + d->hDbc, + &hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QDB2Driver::primaryIndex: Unable to list primary key"), d); + return index; + } + QString catalog, schema, table; + qSplitTableQualifier(tablename, &catalog, &schema, &table); + + if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) + catalog = stripDelimiters(catalog, QSqlDriver::TableName); + else + catalog = catalog.toUpper(); + + if (isIdentifierEscaped(schema, QSqlDriver::TableName)) + schema = stripDelimiters(schema, QSqlDriver::TableName); + else + schema = schema.toUpper(); + + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + else + table = table.toUpper(); + + r = SQLSetStmtAttr(hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + + r = SQLPrimaryKeys(hStmt, + NULL, + 0, + qToTChar(schema), + schema.length(), + qToTChar(table), + table.length()); + r = SQLFetchScroll(hStmt, + SQL_FETCH_NEXT, + 0); + + bool isNull; + QString cName, idxName; + // Store all fields in a StringList because the driver can't detail fields in this FETCH loop + while (r == SQL_SUCCESS) { + cName = qGetStringData(hStmt, 3, -1, isNull); // column name + idxName = qGetStringData(hStmt, 5, -1, isNull); // pk index name + index.append(rec.field(cName)); + index.setName(idxName); + r = SQLFetchScroll(hStmt, + SQL_FETCH_NEXT, + 0); + } + r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); + if (r!= SQL_SUCCESS) + qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ") + + QString::number(r), d); + return index; +} + +bool QDB2Driver::hasFeature(DriverFeature f) const +{ + switch (f) { + case QuerySize: + case NamedPlaceholders: + case BatchOperations: + case LastInsertId: + case SimpleLocking: + case EventNotifications: + case CancelQuery: + return false; + case BLOB: + case Transactions: + case MultipleResultSets: + case PreparedQueries: + case PositionalPlaceholders: + case LowPrecisionNumbers: + case FinishQuery: + return true; + case Unicode: + return true; + } + return false; +} + +bool QDB2Driver::beginTransaction() +{ + if (!isOpen()) { + qWarning("QDB2Driver::beginTransaction: Database not open"); + return false; + } + return setAutoCommit(false); +} + +bool QDB2Driver::commitTransaction() +{ + Q_D(QDB2Driver); + if (!isOpen()) { + qWarning("QDB2Driver::commitTransaction: Database not open"); + return false; + } + SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC, + d->hDbc, + SQL_COMMIT); + if (r != SQL_SUCCESS) { + setLastError(qMakeError(tr("Unable to commit transaction"), + QSqlError::TransactionError, d)); + return false; + } + return setAutoCommit(true); +} + +bool QDB2Driver::rollbackTransaction() +{ + Q_D(QDB2Driver); + if (!isOpen()) { + qWarning("QDB2Driver::rollbackTransaction: Database not open"); + return false; + } + SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC, + d->hDbc, + SQL_ROLLBACK); + if (r != SQL_SUCCESS) { + setLastError(qMakeError(tr("Unable to rollback transaction"), + QSqlError::TransactionError, d)); + return false; + } + return setAutoCommit(true); +} + +bool QDB2Driver::setAutoCommit(bool autoCommit) +{ + Q_D(QDB2Driver); + SQLUINTEGER ac = autoCommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF; + SQLRETURN r = SQLSetConnectAttr(d->hDbc, + SQL_ATTR_AUTOCOMMIT, + reinterpret_cast<SQLPOINTER>(ac), + sizeof(ac)); + if (r != SQL_SUCCESS) { + setLastError(qMakeError(tr("Unable to set autocommit"), + QSqlError::TransactionError, d)); + return false; + } + return true; +} + +QString QDB2Driver::formatValue(const QSqlField &field, bool trimStrings) const +{ + if (field.isNull()) + return QLatin1String("NULL"); + + switch (field.type()) { + case QVariant::DateTime: { + // 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(); + // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10 + return QLatin1Char('\'') + QString::number(dt.year()) + QLatin1Char('-') + + QString::number(dt.month()) + QLatin1Char('-') + + QString::number(dt.day()) + QLatin1Char('-') + + QString::number(tm.hour()) + QLatin1Char('.') + + QString::number(tm.minute()).rightJustified(2, QLatin1Char('0'), true) + + QLatin1Char('.') + + QString::number(tm.second()).rightJustified(2, QLatin1Char('0'), true) + + QLatin1Char('.') + + QString::number(tm.msec() * 1000).rightJustified(6, QLatin1Char('0'), true) + + QLatin1Char('\''); + } else { + return QLatin1String("NULL"); + } + } + case QVariant::ByteArray: { + QByteArray ba = field.value().toByteArray(); + QString res = QString::fromLatin1("BLOB(X'"); + 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]); + } + res += QLatin1String("')"); + return res; + } + default: + return QSqlDriver::formatValue(field, trimStrings); + } +} + +QVariant QDB2Driver::handle() const +{ + Q_D(const QDB2Driver); + return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hDbc); +} + +QString QDB2Driver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + QString res = identifier; + if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sqldrivers/db2/qsql_db2_p.h b/src/plugins/sqldrivers/db2/qsql_db2_p.h new file mode 100644 index 0000000000..fa6d739479 --- /dev/null +++ b/src/plugins/sqldrivers/db2/qsql_db2_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_DB2_H +#define QSQL_DB2_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. +// + +#include <QtCore/qglobal.h> + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_DB2 +#else +#define Q_EXPORT_SQLDRIVER_DB2 Q_SQL_EXPORT +#endif + +#include <QtSql/qsqldriver.h> + +QT_BEGIN_NAMESPACE + +class QDB2DriverPrivate; + +class Q_EXPORT_SQLDRIVER_DB2 QDB2Driver : public QSqlDriver +{ + Q_DECLARE_PRIVATE(QDB2Driver) + Q_OBJECT + friend class QDB2ResultPrivate; + +public: + explicit QDB2Driver(QObject* parent = 0); + QDB2Driver(Qt::HANDLE env, Qt::HANDLE con, QObject* parent = 0); + ~QDB2Driver(); + bool hasFeature(DriverFeature) const Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlRecord record(const QString &tableName) const Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType type) const Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString &tablename) const Q_DECL_OVERRIDE; + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; + QString formatValue(const QSqlField &field, bool trimStrings) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + bool open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port, + const QString& connOpts) Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE; + +private: + bool setAutoCommit(bool autoCommit); +}; + +QT_END_NAMESPACE + +#endif // QSQL_DB2_H diff --git a/src/plugins/sqldrivers/ibase/ibase.pro b/src/plugins/sqldrivers/ibase/ibase.pro index 1f29597a2b..8237245183 100644 --- a/src/plugins/sqldrivers/ibase/ibase.pro +++ b/src/plugins/sqldrivers/ibase/ibase.pro @@ -1,8 +1,17 @@ TARGET = qsqlibase -SOURCES = main.cpp +HEADERS += $$PWD/qsql_ibase_p.h +SOURCES += $$PWD/qsql_ibase.cpp $$PWD/main.cpp + +unix { + !contains(LIBS, .*gds.*):!contains(LIBS, .*libfb.*):LIBS += -lgds +} else { + !contains(LIBS, .*gds.*):!contains(LIBS, .*fbclient.*) { + LIBS += -lgds32_ms + } +} + OTHER_FILES += ibase.json -include(../../../sql/drivers/ibase/qsql_ibase.pri) PLUGIN_CLASS_NAME = QIBaseDriverPlugin include(../qsqldriverbase.pri) diff --git a/src/plugins/sqldrivers/ibase/main.cpp b/src/plugins/sqldrivers/ibase/main.cpp index c6f0b785de..8d462afcdc 100644 --- a/src/plugins/sqldrivers/ibase/main.cpp +++ b/src/plugins/sqldrivers/ibase/main.cpp @@ -39,7 +39,7 @@ #include <qsqldriverplugin.h> #include <qstringlist.h> -#include "../../../sql/drivers/ibase/qsql_ibase_p.h" +#include "qsql_ibase_p.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp new file mode 100644 index 0000000000..6fd91b6b76 --- /dev/null +++ b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp @@ -0,0 +1,1948 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_ibase_p.h" +#include <qcoreapplication.h> +#include <qdatetime.h> +#include <qvariant.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <QtSql/private/qsqlcachedresult_p.h> +#include <QtSql/private/qsqldriver_p.h> +#include <qlist.h> +#include <qvector.h> +#include <qtextcodec.h> +#include <qmutex.h> +#include <stdlib.h> +#include <limits.h> +#include <math.h> +#include <qdebug.h> +#include <QVarLengthArray> + +QT_BEGIN_NAMESPACE + +#define FBVERSION SQL_DIALECT_V6 + +#ifndef SQLDA_CURRENT_VERSION +#define SQLDA_CURRENT_VERSION SQLDA_VERSION1 +#endif + +enum { QIBaseChunkSize = SHRT_MAX / 2 }; + +#if defined(FB_API_VER) && FB_API_VER >= 20 +static bool getIBaseError(QString& msg, const ISC_STATUS* status, ISC_LONG &sqlcode, QTextCodec *tc) +#else +static bool getIBaseError(QString& msg, ISC_STATUS* status, ISC_LONG &sqlcode, QTextCodec *tc) +#endif +{ + if (status[0] != 1 || status[1] <= 0) + return false; + + msg.clear(); + sqlcode = isc_sqlcode(status); + char buf[512]; +#if defined(FB_API_VER) && FB_API_VER >= 20 + while(fb_interpret(buf, 512, &status)) { +#else + while(isc_interprete(buf, &status)) { +#endif + if(!msg.isEmpty()) + msg += QLatin1String(" - "); + if (tc) + msg += tc->toUnicode(buf); + else + msg += QString::fromUtf8(buf); + } + return true; +} + +static void createDA(XSQLDA *&sqlda) +{ + sqlda = (XSQLDA *) malloc(XSQLDA_LENGTH(1)); + if (sqlda == (XSQLDA*)0) return; + sqlda->sqln = 1; + sqlda->sqld = 0; + sqlda->version = SQLDA_CURRENT_VERSION; + sqlda->sqlvar[0].sqlind = 0; + sqlda->sqlvar[0].sqldata = 0; +} + +static void enlargeDA(XSQLDA *&sqlda, int n) +{ + if (sqlda != (XSQLDA*)0) + free(sqlda); + sqlda = (XSQLDA *) malloc(XSQLDA_LENGTH(n)); + if (sqlda == (XSQLDA*)0) return; + sqlda->sqln = n; + sqlda->version = SQLDA_CURRENT_VERSION; +} + +static void initDA(XSQLDA *sqlda) +{ + for (int i = 0; i < sqlda->sqld; ++i) { + switch (sqlda->sqlvar[i].sqltype & ~1) { + case SQL_INT64: + case SQL_LONG: + case SQL_SHORT: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_TIMESTAMP: + case SQL_TYPE_TIME: + case SQL_TYPE_DATE: + case SQL_TEXT: + case SQL_BLOB: + sqlda->sqlvar[i].sqldata = new char[sqlda->sqlvar[i].sqllen]; + break; + case SQL_ARRAY: + sqlda->sqlvar[i].sqldata = new char[sizeof(ISC_QUAD)]; + memset(sqlda->sqlvar[i].sqldata, 0, sizeof(ISC_QUAD)); + break; + case SQL_VARYING: + sqlda->sqlvar[i].sqldata = new char[sqlda->sqlvar[i].sqllen + sizeof(short)]; + break; + default: + // not supported - do not bind. + sqlda->sqlvar[i].sqldata = 0; + break; + } + if (sqlda->sqlvar[i].sqltype & 1) { + sqlda->sqlvar[i].sqlind = new short[1]; + *(sqlda->sqlvar[i].sqlind) = 0; + } else { + sqlda->sqlvar[i].sqlind = 0; + } + } +} + +static void delDA(XSQLDA *&sqlda) +{ + if (!sqlda) + return; + for (int i = 0; i < sqlda->sqld; ++i) { + delete [] sqlda->sqlvar[i].sqlind; + delete [] sqlda->sqlvar[i].sqldata; + } + free(sqlda); + sqlda = 0; +} + +static QVariant::Type qIBaseTypeName(int iType, bool hasScale) +{ + switch (iType) { + case blr_varying: + case blr_varying2: + case blr_text: + case blr_cstring: + case blr_cstring2: + return QVariant::String; + case blr_sql_time: + return QVariant::Time; + case blr_sql_date: + return QVariant::Date; + case blr_timestamp: + return QVariant::DateTime; + case blr_blob: + return QVariant::ByteArray; + case blr_quad: + case blr_short: + case blr_long: + return (hasScale ? QVariant::Double : QVariant::Int); + case blr_int64: + return (hasScale ? QVariant::Double : QVariant::LongLong); + case blr_float: + case blr_d_float: + case blr_double: + return QVariant::Double; + } + qWarning("qIBaseTypeName: unknown datatype: %d", iType); + return QVariant::Invalid; +} + +static QVariant::Type qIBaseTypeName2(int iType, bool hasScale) +{ + switch(iType & ~1) { + case SQL_VARYING: + case SQL_TEXT: + return QVariant::String; + case SQL_LONG: + case SQL_SHORT: + return (hasScale ? QVariant::Double : QVariant::Int); + case SQL_INT64: + return (hasScale ? QVariant::Double : QVariant::LongLong); + case SQL_FLOAT: + case SQL_DOUBLE: + return QVariant::Double; + case SQL_TIMESTAMP: + return QVariant::DateTime; + case SQL_TYPE_TIME: + return QVariant::Time; + case SQL_TYPE_DATE: + return QVariant::Date; + case SQL_ARRAY: + return QVariant::List; + case SQL_BLOB: + return QVariant::ByteArray; + default: + return QVariant::Invalid; + } +} + +static ISC_TIMESTAMP toTimeStamp(const QDateTime &dt) +{ + static const QTime midnight(0, 0, 0, 0); + static const QDate basedate(1858, 11, 17); + ISC_TIMESTAMP ts; + ts.timestamp_time = midnight.msecsTo(dt.time()) * 10; + ts.timestamp_date = basedate.daysTo(dt.date()); + return ts; +} + +static QDateTime fromTimeStamp(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 + t = t.addMSecs(int(((ISC_TIMESTAMP*)buffer)->timestamp_time / 10)); + d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date)); + + return QDateTime(d, t); +} + +static ISC_TIME toTime(const QTime &t) +{ + static const QTime midnight(0, 0, 0, 0); + return (ISC_TIME)midnight.msecsTo(t) * 10; +} + +static QTime fromTime(char *buffer) +{ + QTime t(0, 0); + // have to demangle the structure ourselves because isc_decode_time + // strips the msecs + t = t.addMSecs(int((*(ISC_TIME*)buffer) / 10)); + + return t; +} + +static ISC_DATE toDate(const QDate &t) +{ + static const QDate basedate(1858, 11, 17); + ISC_DATE date; + + date = basedate.daysTo(t); + return date; +} + +static QDate fromDate(char *buffer) +{ + static const QDate bd(1858, 11, 17); + QDate d; + + // have to demangle the structure ourselves because isc_decode_time + // strips the msecs + d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date)); + + return d; +} + +static QByteArray encodeString(QTextCodec *tc, const QString &str) +{ + if (tc) + return tc->fromUnicode(str); + return str.toUtf8(); +} + +struct QIBaseEventBuffer { +#if defined(FB_API_VER) && FB_API_VER >= 20 + ISC_UCHAR *eventBuffer; + ISC_UCHAR *resultBuffer; +#else + char *eventBuffer; + char *resultBuffer; +#endif + ISC_LONG bufferLength; + ISC_LONG eventId; + + enum QIBaseSubscriptionState { Starting, Subscribed, Finished }; + QIBaseSubscriptionState subscriptionState; +}; + +class QIBaseDriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QIBaseDriver) +public: + QIBaseDriverPrivate() : QSqlDriverPrivate(), ibase(0), trans(0), tc(0) { dbmsType = QSqlDriver::Interbase; } + + bool isError(const char *msg, QSqlError::ErrorType typ = QSqlError::UnknownError) + { + Q_Q(QIBaseDriver); + QString imsg; + ISC_LONG sqlcode; + if (!getIBaseError(imsg, status, sqlcode, tc)) + return false; + + q->setLastError(QSqlError(QCoreApplication::translate("QIBaseDriver", msg), + imsg, typ, int(sqlcode))); + return true; + } + +public: + isc_db_handle ibase; + isc_tr_handle trans; + QTextCodec *tc; + ISC_STATUS status[20]; + QMap<QString, QIBaseEventBuffer*> eventBuffers; +}; + +typedef QMap<void *, QIBaseDriver *> QIBaseBufferDriverMap; +Q_GLOBAL_STATIC(QIBaseBufferDriverMap, qBufferDriverMap) +Q_GLOBAL_STATIC(QMutex, qMutex); + +static void qFreeEventBuffer(QIBaseEventBuffer* eBuffer) +{ + qMutex()->lock(); + qBufferDriverMap()->remove(reinterpret_cast<void *>(eBuffer->resultBuffer)); + qMutex()->unlock(); + delete eBuffer; +} + +class QIBaseResultPrivate; + +class QIBaseResult : public QSqlCachedResult +{ + Q_DECLARE_PRIVATE(QIBaseResult) + +public: + explicit QIBaseResult(const QIBaseDriver* db); + + bool prepare(const QString &query) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) Q_DECL_OVERRIDE; + bool reset (const QString &query) Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; +}; + +class QIBaseResultPrivate: public QSqlCachedResultPrivate +{ + Q_DECLARE_PUBLIC(QIBaseResult) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QIBaseDriver) + + QIBaseResultPrivate(QIBaseResult *q, const QIBaseDriver *drv); + ~QIBaseResultPrivate() { cleanup(); } + + void cleanup(); + bool isError(const char *msg, QSqlError::ErrorType typ = QSqlError::UnknownError) + { + Q_Q(QIBaseResult); + QString imsg; + ISC_LONG sqlcode; + if (!getIBaseError(imsg, status, sqlcode, tc)) + return false; + + q->setLastError(QSqlError(QCoreApplication::translate("QIBaseResult", msg), + imsg, typ, int(sqlcode))); + return true; + } + + bool transaction(); + bool commit(); + + bool isSelect(); + QVariant fetchBlob(ISC_QUAD *bId); + bool writeBlob(int i, const QByteArray &ba); + QVariant fetchArray(int pos, ISC_QUAD *arr); + bool writeArray(int i, const QList<QVariant> &list); + +public: + ISC_STATUS status[20]; + isc_tr_handle trans; + //indicator whether we have a local transaction or a transaction on driver level + bool localTransaction; + isc_stmt_handle stmt; + isc_db_handle ibase; + XSQLDA *sqlda; // output sqlda + XSQLDA *inda; // input parameters + int queryType; + QTextCodec *tc; +}; + + +QIBaseResultPrivate::QIBaseResultPrivate(QIBaseResult *q, const QIBaseDriver *drv) + : QSqlCachedResultPrivate(q, drv), + trans(0), + localTransaction(!drv_d_func()->ibase), + stmt(0), + ibase(drv_d_func()->ibase), + sqlda(0), + inda(0), + queryType(-1), + tc(drv_d_func()->tc) +{ +} + +void QIBaseResultPrivate::cleanup() +{ + Q_Q(QIBaseResult); + commit(); + if (!localTransaction) + trans = 0; + + if (stmt) { + isc_dsql_free_statement(status, &stmt, DSQL_drop); + stmt = 0; + } + + delDA(sqlda); + delDA(inda); + + queryType = -1; + q->cleanup(); +} + +bool QIBaseResultPrivate::writeBlob(int i, const QByteArray &ba) +{ + isc_blob_handle handle = 0; + ISC_QUAD *bId = (ISC_QUAD*)inda->sqlvar[i].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; + while (i < ba.size()) { + isc_put_segment(status, &handle, qMin(ba.size() - i, int(QIBaseChunkSize)), + const_cast<char*>(ba.data()) + i); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to write BLOB"))) + return false; + i += qMin(ba.size() - i, int(QIBaseChunkSize)); + } + } + isc_close_blob(status, &handle); + return true; +} + +QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId) +{ + isc_blob_handle handle = 0; + + isc_open_blob2(status, &ibase, &trans, &handle, bId, 0, 0); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to open BLOB"), + QSqlError::StatementError)) + return QVariant(); + + unsigned short len = 0; + QByteArray ba; + int chunkSize = QIBaseChunkSize; + ba.resize(chunkSize); + int read = 0; + while (isc_get_segment(status, &handle, &len, chunkSize, ba.data() + read) == 0 || status[1] == isc_segment) { + read += len; + ba.resize(read + chunkSize); + } + ba.resize(read); + + bool isErr = (status[1] == isc_segstr_eof ? false : + isError(QT_TRANSLATE_NOOP("QIBaseResult", + "Unable to read BLOB"), + QSqlError::StatementError)); + + isc_close_blob(status, &handle); + + if (isErr) + return QVariant(); + + ba.resize(read); + return ba; +} + +template<typename T> +static QList<QVariant> toList(char** buf, int count, T* = 0) +{ + QList<QVariant> res; + for (int i = 0; i < count; ++i) { + res.append(*(T*)(*buf)); + *buf += sizeof(T); + } + return res; +} +/* char** ? seems like bad influence from oracle ... */ +template<> +QList<QVariant> toList<long>(char** buf, int count, long*) +{ + QList<QVariant> res; + for (int i = 0; i < count; ++i) { + if (sizeof(int) == sizeof(long)) + res.append(int((*(long*)(*buf)))); + else + res.append((qint64)(*(long*)(*buf))); + *buf += sizeof(long); + } + return res; +} + +static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim, + short* numElements, ISC_ARRAY_DESC *arrayDesc, + QTextCodec *tc) +{ + const short dim = arrayDesc->array_desc_dimensions - 1; + const unsigned char dataType = arrayDesc->array_desc_dtype; + QList<QVariant> valList; + unsigned short strLen = arrayDesc->array_desc_length; + + if (curDim != dim) { + for(int i = 0; i < numElements[curDim]; ++i) + buffer = readArrayBuffer(list, buffer, curDim + 1, numElements, + arrayDesc, tc); + } else { + switch(dataType) { + case blr_varying: + case blr_varying2: + strLen += 2; // for the two terminating null values + case blr_text: + case blr_text2: { + int o; + for (int i = 0; i < numElements[dim]; ++i) { + for(o = 0; o < strLen && buffer[o]!=0; ++o ) + ; + + if (tc) + valList.append(tc->toUnicode(buffer, o)); + else + valList.append(QString::fromUtf8(buffer, o)); + + buffer += strLen; + } + break; } + case blr_long: + valList = toList<long>(&buffer, numElements[dim], static_cast<long *>(0)); + break; + case blr_short: + valList = toList<short>(&buffer, numElements[dim]); + break; + case blr_int64: + valList = toList<qint64>(&buffer, numElements[dim]); + break; + case blr_float: + valList = toList<float>(&buffer, numElements[dim]); + break; + case blr_double: + valList = toList<double>(&buffer, numElements[dim]); + break; + case blr_timestamp: + for(int i = 0; i < numElements[dim]; ++i) { + valList.append(fromTimeStamp(buffer)); + buffer += sizeof(ISC_TIMESTAMP); + } + break; + case blr_sql_time: + for(int i = 0; i < numElements[dim]; ++i) { + valList.append(fromTime(buffer)); + buffer += sizeof(ISC_TIME); + } + break; + case blr_sql_date: + for(int i = 0; i < numElements[dim]; ++i) { + valList.append(fromDate(buffer)); + buffer += sizeof(ISC_DATE); + } + break; + } + } + if (dim > 0) + list.append(valList); + else + list += valList; + return buffer; +} + +QVariant QIBaseResultPrivate::fetchArray(int pos, ISC_QUAD *arr) +{ + QList<QVariant> list; + ISC_ARRAY_DESC desc; + + if (!arr) + return list; + + QByteArray relname(sqlda->sqlvar[pos].relname, sqlda->sqlvar[pos].relname_length); + QByteArray sqlname(sqlda->sqlvar[pos].aliasname, sqlda->sqlvar[pos].aliasname_length); + + isc_array_lookup_bounds(status, &ibase, &trans, relname.data(), sqlname.data(), &desc); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not find array"), + QSqlError::StatementError)) + return list; + + + int arraySize = 1, subArraySize; + short dimensions = desc.array_desc_dimensions; + QVarLengthArray<short> numElements(dimensions); + + for(int i = 0; i < dimensions; ++i) { + subArraySize = (desc.array_desc_bounds[i].array_bound_upper - + desc.array_desc_bounds[i].array_bound_lower + 1); + numElements[i] = subArraySize; + arraySize = subArraySize * arraySize; + } + + ISC_LONG bufLen; + QByteArray ba; + /* varying arrayelements are stored with 2 trailing null bytes + indicating the length of the string + */ + if (desc.array_desc_dtype == blr_varying + || desc.array_desc_dtype == blr_varying2) { + desc.array_desc_length += 2; + bufLen = desc.array_desc_length * arraySize * sizeof(short); + } else { + bufLen = desc.array_desc_length * arraySize; + } + + + ba.resize(int(bufLen)); + isc_array_get_slice(status, &ibase, &trans, arr, &desc, ba.data(), &bufLen); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get array data"), + QSqlError::StatementError)) + return list; + + readArrayBuffer(list, ba.data(), 0, numElements.data(), &desc, tc); + + return QVariant(list); +} + +template<typename T> +static char* fillList(char *buffer, const QList<QVariant> &list, T* = 0) +{ + for (int i = 0; i < list.size(); ++i) { + T val; + val = qvariant_cast<T>(list.at(i)); + memcpy(buffer, &val, sizeof(T)); + buffer += sizeof(T); + } + return buffer; +} + +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; + memcpy(buffer, &val2, sizeof(float)); + buffer += sizeof(float); + } + return buffer; +} + +static char* qFillBufferWithString(char *buffer, const QString& string, + short buflen, bool varying, bool array, + QTextCodec *tc) +{ + QByteArray str = encodeString(tc, string); // keep a copy of the string alive in this scope + if (varying) { + short tmpBuflen = buflen; + if (str.length() < buflen) + buflen = str.length(); + if (array) { // interbase stores varying arrayelements different than normal varying elements + memcpy(buffer, str.data(), buflen); + memset(buffer + buflen, 0, tmpBuflen - buflen); + } else { + *(short*)buffer = buflen; // first two bytes is the length + memcpy(buffer + sizeof(short), str.data(), buflen); + } + buffer += tmpBuflen; + } else { + str = str.leftJustified(buflen, ' ', true); + memcpy(buffer, str.data(), buflen); + buffer += buflen; + } + return buffer; +} + +static char* createArrayBuffer(char *buffer, const QList<QVariant> &list, + QVariant::Type type, short curDim, ISC_ARRAY_DESC *arrayDesc, + QString& error, QTextCodec *tc) +{ + int i; + ISC_ARRAY_BOUND *bounds = arrayDesc->array_desc_bounds; + short dim = arrayDesc->array_desc_dimensions - 1; + + int elements = (bounds[curDim].array_bound_upper - + bounds[curDim].array_bound_lower + 1); + + if (list.size() != elements) { // size mismatch + error = QLatin1String("Expected size: %1. Supplied size: %2"); + error = QLatin1String("Array size mismatch. Fieldname: %1 ") + + error.arg(elements).arg(list.size()); + return 0; + } + + if (curDim != dim) { + for(i = 0; i < list.size(); ++i) { + + if (list.at(i).type() != QVariant::List) { // dimensions mismatch + error = QLatin1String("Array dimensons mismatch. Fieldname: %1"); + return 0; + } + + buffer = createArrayBuffer(buffer, list.at(i).toList(), type, curDim + 1, + arrayDesc, error, tc); + if (!buffer) + return 0; + } + } else { + switch(type) { + case QVariant::Int: + case QVariant::UInt: + if (arrayDesc->array_desc_dtype == blr_short) + buffer = fillList<short>(buffer, list); + else + buffer = fillList<int>(buffer, list); + break; + case QVariant::Double: + if (arrayDesc->array_desc_dtype == blr_float) + buffer = fillList<float>(buffer, list, static_cast<float *>(0)); + else + buffer = fillList<double>(buffer, list); + break; + case QVariant::LongLong: + buffer = fillList<qint64>(buffer, list); + break; + case QVariant::ULongLong: + buffer = fillList<quint64>(buffer, list); + break; + case QVariant::String: + for (i = 0; i < list.size(); ++i) + buffer = qFillBufferWithString(buffer, list.at(i).toString(), + arrayDesc->array_desc_length, + arrayDesc->array_desc_dtype == blr_varying, + true, tc); + break; + case QVariant::Date: + for (i = 0; i < list.size(); ++i) { + *((ISC_DATE*)buffer) = toDate(list.at(i).toDate()); + buffer += sizeof(ISC_DATE); + } + break; + case QVariant::Time: + for (i = 0; i < list.size(); ++i) { + *((ISC_TIME*)buffer) = toTime(list.at(i).toTime()); + buffer += sizeof(ISC_TIME); + } + break; + + case QVariant::DateTime: + for (i = 0; i < list.size(); ++i) { + *((ISC_TIMESTAMP*)buffer) = toTimeStamp(list.at(i).toDateTime()); + buffer += sizeof(ISC_TIMESTAMP); + } + break; + default: + break; + } + } + return buffer; +} + +bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list) +{ + Q_Q(QIBaseResult); + QString error; + ISC_QUAD *arrayId = (ISC_QUAD*) inda->sqlvar[column].sqldata; + ISC_ARRAY_DESC desc; + + QByteArray relname(inda->sqlvar[column].relname, inda->sqlvar[column].relname_length); + QByteArray sqlname(inda->sqlvar[column].aliasname, inda->sqlvar[column].aliasname_length); + + isc_array_lookup_bounds(status, &ibase, &trans, relname.data(), sqlname.data(), &desc); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not find array"), + QSqlError::StatementError)) + return false; + + short arraySize = 1; + ISC_LONG bufLen; + QList<QVariant> subList = list; + + short dimensions = desc.array_desc_dimensions; + for(int i = 0; i < dimensions; ++i) { + arraySize *= (desc.array_desc_bounds[i].array_bound_upper - + desc.array_desc_bounds[i].array_bound_lower + 1); + } + + /* varying arrayelements are stored with 2 trailing null bytes + indicating the length of the string + */ + if (desc.array_desc_dtype == blr_varying || + desc.array_desc_dtype == blr_varying2) + desc.array_desc_length += 2; + + bufLen = desc.array_desc_length * arraySize; + QByteArray ba; + ba.resize(int(bufLen)); + + if (list.size() > arraySize) { + error = QLatin1String("Array size missmatch: size of %1 is %2, size of provided list is %3"); + error = error.arg(QLatin1String(sqlname)).arg(arraySize).arg(list.size()); + q->setLastError(QSqlError(error, QLatin1String(""), QSqlError::StatementError)); + return false; + } + + if (!createArrayBuffer(ba.data(), list, + qIBaseTypeName(desc.array_desc_dtype, inda->sqlvar[column].sqlscale < 0), + 0, &desc, error, tc)) { + q->setLastError(QSqlError(error.arg(QLatin1String(sqlname)), QLatin1String(""), + QSqlError::StatementError)); + return false; + } + + /* readjust the buffer size*/ + if (desc.array_desc_dtype == blr_varying + || desc.array_desc_dtype == blr_varying2) + desc.array_desc_length -= 2; + + isc_array_put_slice(status, &ibase, &trans, arrayId, &desc, ba.data(), &bufLen); + return true; +} + + +bool QIBaseResultPrivate::isSelect() +{ + char acBuffer[9]; + char qType = isc_info_sql_stmt_type; + isc_dsql_sql_info(status, &stmt, 1, &qType, sizeof(acBuffer), acBuffer); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get query info"), + QSqlError::StatementError)) + return false; + int iLength = isc_vax_integer(&acBuffer[1], 2); + queryType = isc_vax_integer(&acBuffer[3], iLength); + return (queryType == isc_info_sql_stmt_select || queryType == isc_info_sql_stmt_exec_procedure); +} + +bool QIBaseResultPrivate::transaction() +{ + if (trans) + return true; + if (drv_d_func()->trans) { + localTransaction = false; + trans = drv_d_func()->trans; + return true; + } + localTransaction = true; + + isc_start_transaction(status, &trans, 1, &ibase, 0, NULL); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not start transaction"), + QSqlError::TransactionError)) + return false; + + return true; +} + +// does nothing if the transaction is on the +// driver level +bool QIBaseResultPrivate::commit() +{ + if (!trans) + return false; + // don't commit driver's transaction, the driver will do it for us + if (!localTransaction) + return true; + + isc_commit_transaction(status, &trans); + trans = 0; + return !isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to commit transaction"), + QSqlError::TransactionError); +} + +////////// + +QIBaseResult::QIBaseResult(const QIBaseDriver *db) + : QSqlCachedResult(*new QIBaseResultPrivate(this, db)) +{ +} + +bool QIBaseResult::prepare(const QString& query) +{ + Q_D(QIBaseResult); +// qDebug("prepare: %s", qPrintable(query)); + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + d->cleanup(); + setActive(false); + setAt(QSql::BeforeFirstRow); + + createDA(d->sqlda); + if (d->sqlda == (XSQLDA*)0) { + qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory"; + return false; + } + + createDA(d->inda); + if (d->inda == (XSQLDA*)0){ + qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory"; + return false; + } + + if (!d->transaction()) + return false; + + isc_dsql_allocate_statement(d->status, &d->ibase, &d->stmt); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not allocate statement"), + QSqlError::StatementError)) + return false; + isc_dsql_prepare(d->status, &d->trans, &d->stmt, 0, + const_cast<char*>(encodeString(d->tc, query).constData()), FBVERSION, d->sqlda); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not prepare statement"), + QSqlError::StatementError)) + return false; + + isc_dsql_describe_bind(d->status, &d->stmt, FBVERSION, d->inda); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", + "Could not describe input statement"), QSqlError::StatementError)) + return false; + 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"; + return false; + } + + isc_dsql_describe_bind(d->status, &d->stmt, FBVERSION, d->inda); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", + "Could not describe input statement"), QSqlError::StatementError)) + return false; + } + initDA(d->inda); + if (d->sqlda->sqld > d->sqlda->sqln) { + // need more field descriptors + enlargeDA(d->sqlda, d->sqlda->sqld); + if (d->sqlda == (XSQLDA*)0) { + qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory"; + return false; + } + + isc_dsql_describe(d->status, &d->stmt, FBVERSION, d->sqlda); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not describe statement"), + QSqlError::StatementError)) + return false; + } + initDA(d->sqlda); + + setSelect(d->isSelect()); + if (!isSelect()) { + free(d->sqlda); + d->sqlda = 0; + } + + return true; +} + + +bool QIBaseResult::exec() +{ + Q_D(QIBaseResult); + bool ok = true; + + if (!d->trans) + d->transaction(); + + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + setActive(false); + setAt(QSql::BeforeFirstRow); + + if (d->inda) { + QVector<QVariant>& values = boundValues(); + int i; + if (values.count() > d->inda->sqld) { + qWarning("QIBaseResult::exec: Parameter mismatch, expected %d, got %d parameters", + d->inda->sqld, values.count()); + return false; + } + int para = 0; + for (i = 0; i < values.count(); ++i) { + para = i; + if (!d->inda->sqlvar[para].sqldata) + // skip unknown datatypes + continue; + const QVariant val(values[i]); + if (d->inda->sqlvar[para].sqltype & 1) { + if (val.isNull()) { + // set null indicator + *(d->inda->sqlvar[para].sqlind) = -1; + // and set the value to 0, otherwise it would count as empty string. + // it seems to be working with just setting sqlind to -1 + //*((char*)d->inda->sqlvar[para].sqldata) = 0; + continue; + } + // a value of 0 means non-null. + *(d->inda->sqlvar[para].sqlind) = 0; + } + switch(d->inda->sqlvar[para].sqltype & ~1) { + case SQL_INT64: + if (d->inda->sqlvar[para].sqlscale < 0) + *((qint64*)d->inda->sqlvar[para].sqldata) = + (qint64)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); + else + *((qint64*)d->inda->sqlvar[para].sqldata) = val.toLongLong(); + break; + case SQL_LONG: + if (d->inda->sqlvar[para].sqllen == 4) { + if (d->inda->sqlvar[para].sqlscale < 0) + *((qint32*)d->inda->sqlvar[para].sqldata) = + (qint32)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); + else + *((qint32*)d->inda->sqlvar[para].sqldata) = (qint32)val.toInt(); + } else { + *((qint64*)d->inda->sqlvar[para].sqldata) = val.toLongLong(); + } + break; + case SQL_SHORT: + if (d->inda->sqlvar[para].sqlscale < 0) + *((short*)d->inda->sqlvar[para].sqldata) = + (short)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); + else + *((short*)d->inda->sqlvar[para].sqldata) = (short)val.toInt(); + break; + case SQL_FLOAT: + *((float*)d->inda->sqlvar[para].sqldata) = (float)val.toDouble(); + break; + case SQL_DOUBLE: + *((double*)d->inda->sqlvar[para].sqldata) = val.toDouble(); + break; + case SQL_TIMESTAMP: + *((ISC_TIMESTAMP*)d->inda->sqlvar[para].sqldata) = toTimeStamp(val.toDateTime()); + break; + case SQL_TYPE_TIME: + *((ISC_TIME*)d->inda->sqlvar[para].sqldata) = toTime(val.toTime()); + break; + case SQL_TYPE_DATE: + *((ISC_DATE*)d->inda->sqlvar[para].sqldata) = toDate(val.toDate()); + break; + case SQL_VARYING: + case SQL_TEXT: + qFillBufferWithString(d->inda->sqlvar[para].sqldata, val.toString(), + d->inda->sqlvar[para].sqllen, + (d->inda->sqlvar[para].sqltype & ~1) == SQL_VARYING, false, d->tc); + break; + case SQL_BLOB: + ok &= d->writeBlob(para, val.toByteArray()); + break; + case SQL_ARRAY: + ok &= d->writeArray(para, val.toList()); + break; + default: + qWarning("QIBaseResult::exec: Unknown datatype %d", + d->inda->sqlvar[para].sqltype & ~1); + break; + } + } + } + + if (ok) { + if (colCount() && d->queryType != isc_info_sql_stmt_exec_procedure) { + isc_dsql_free_statement(d->status, &d->stmt, DSQL_close); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to close statement"))) + return false; + cleanup(); + } + if (d->queryType == isc_info_sql_stmt_exec_procedure) + isc_dsql_execute2(d->status, &d->trans, &d->stmt, FBVERSION, d->inda, d->sqlda); + else + isc_dsql_execute(d->status, &d->trans, &d->stmt, FBVERSION, d->inda); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to execute query"))) + return false; + + // Not all stored procedures necessarily return values. + if (d->queryType == isc_info_sql_stmt_exec_procedure && d->sqlda && d->sqlda->sqld == 0) + delDA(d->sqlda); + + if (d->sqlda) + init(d->sqlda->sqld); + + if (!isSelect()) + d->commit(); + + setActive(true); + return true; + } + return false; +} + +bool QIBaseResult::reset (const QString& query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) +{ + Q_D(QIBaseResult); + ISC_STATUS stat = 0; + + // Stored Procedures are special - they populate our d->sqlda when executing, + // so we don't have to call isc_dsql_fetch + if (d->queryType == isc_info_sql_stmt_exec_procedure) { + // the first "fetch" shall succeed, all consecutive ones will fail since + // we only have one row to fetch for stored procedures + if (rowIdx != 0) + stat = 100; + } else { + stat = isc_dsql_fetch(d->status, &d->stmt, FBVERSION, d->sqlda); + } + + if (stat == 100) { + // no more rows + setAt(QSql::AfterLastRow); + return false; + } + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not fetch next item"), + QSqlError::StatementError)) + return false; + if (rowIdx < 0) // not interested in actual values + return true; + + for (int i = 0; i < d->sqlda->sqld; ++i) { + int idx = rowIdx + i; + char *buf = d->sqlda->sqlvar[i].sqldata; + int size = d->sqlda->sqlvar[i].sqllen; + Q_ASSERT(buf); + + if ((d->sqlda->sqlvar[i].sqltype & 1) && *d->sqlda->sqlvar[i].sqlind) { + // null value + QVariant v; + v.convert(qIBaseTypeName2(d->sqlda->sqlvar[i].sqltype, d->sqlda->sqlvar[i].sqlscale < 0)); + if(v.type() == QVariant::Double) { + switch(numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + v.convert(QVariant::Int); + break; + case QSql::LowPrecisionInt64: + v.convert(QVariant::LongLong); + break; + case QSql::HighPrecision: + v.convert(QVariant::String); + break; + case QSql::LowPrecisionDouble: + // no conversion + break; + } + } + row[idx] = v; + continue; + } + + switch(d->sqlda->sqlvar[i].sqltype & ~1) { + case SQL_VARYING: + // pascal strings - a short with a length information followed by the data + if (d->tc) + row[idx] = d->tc->toUnicode(buf + sizeof(short), *(short*)buf); + else + row[idx] = QString::fromUtf8(buf + sizeof(short), *(short*)buf); + break; + case SQL_INT64: + if (d->sqlda->sqlvar[i].sqlscale < 0) + row[idx] = *(qint64*)buf * pow(10.0, d->sqlda->sqlvar[i].sqlscale); + else + row[idx] = QVariant(*(qint64*)buf); + break; + case SQL_LONG: + if (d->sqlda->sqlvar[i].sqllen == 4) + if (d->sqlda->sqlvar[i].sqlscale < 0) + row[idx] = QVariant(*(qint32*)buf * pow(10.0, d->sqlda->sqlvar[i].sqlscale)); + else + row[idx] = QVariant(*(qint32*)buf); + else + row[idx] = QVariant(*(qint64*)buf); + break; + case SQL_SHORT: + if (d->sqlda->sqlvar[i].sqlscale < 0) + row[idx] = QVariant(long((*(short*)buf)) * pow(10.0, d->sqlda->sqlvar[i].sqlscale)); + else + row[idx] = QVariant(int((*(short*)buf))); + break; + case SQL_FLOAT: + row[idx] = QVariant(double((*(float*)buf))); + break; + case SQL_DOUBLE: + row[idx] = QVariant(*(double*)buf); + break; + case SQL_TIMESTAMP: + row[idx] = fromTimeStamp(buf); + break; + case SQL_TYPE_TIME: + row[idx] = fromTime(buf); + break; + case SQL_TYPE_DATE: + row[idx] = fromDate(buf); + break; + case SQL_TEXT: + if (d->tc) + row[idx] = d->tc->toUnicode(buf, size); + else + row[idx] = QString::fromUtf8(buf, size); + break; + case SQL_BLOB: + row[idx] = d->fetchBlob((ISC_QUAD*)buf); + break; + case SQL_ARRAY: + row[idx] = d->fetchArray(i, (ISC_QUAD*)buf); + break; + default: + // unknown type - don't even try to fetch + row[idx] = QVariant(); + break; + } + if (d->sqlda->sqlvar[i].sqlscale < 0) { + QVariant v = row[idx]; + switch(numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + if(v.convert(QVariant::Int)) + row[idx]=v; + break; + case QSql::LowPrecisionInt64: + if(v.convert(QVariant::LongLong)) + row[idx]=v; + break; + case QSql::LowPrecisionDouble: + if(v.convert(QVariant::Double)) + row[idx]=v; + break; + case QSql::HighPrecision: + if(v.convert(QVariant::String)) + row[idx]=v; + break; + } + } + } + + return true; +} + +int QIBaseResult::size() +{ + return -1; + +#if 0 /// ### FIXME + static char sizeInfo[] = {isc_info_sql_records}; + char buf[64]; + + //qDebug() << sizeInfo; + if (!isActive() || !isSelect()) + return -1; + + char ct; + short len; + int val = 0; +// while(val == 0) { + isc_dsql_sql_info(d->status, &d->stmt, sizeof(sizeInfo), sizeInfo, sizeof(buf), buf); +// isc_database_info(d->status, &d->ibase, sizeof(sizeInfo), sizeInfo, sizeof(buf), buf); + + for(int i = 0; i < 66; ++i) + qDebug() << QString::number(buf[i]); + + for (char* c = buf + 3; *c != isc_info_end; /*nothing*/) { + ct = *(c++); + len = isc_vax_integer(c, 2); + c += 2; + val = isc_vax_integer(c, len); + c += len; + qDebug() << "size" << val; + if (ct == isc_info_req_select_count) + return val; + } + //qDebug("size -1"); + return -1; + + unsigned int i, result_size; + if (buf[0] == isc_info_sql_records) { + i = 3; + result_size = isc_vax_integer(&buf[1],2); + while (buf[i] != isc_info_end && i < result_size) { + len = (short)isc_vax_integer(&buf[i+1],2); + if (buf[i] == isc_info_req_select_count) + return (isc_vax_integer(&buf[i+3],len)); + i += len+3; + } + } +// } + return -1; +#endif +} + +int QIBaseResult::numRowsAffected() +{ + Q_D(QIBaseResult); + static char acCountInfo[] = {isc_info_sql_records}; + char cCountType; + bool bIsProcedure = false; + + switch (d->queryType) { + case isc_info_sql_stmt_select: + cCountType = isc_info_req_select_count; + break; + case isc_info_sql_stmt_update: + cCountType = isc_info_req_update_count; + break; + case isc_info_sql_stmt_delete: + cCountType = isc_info_req_delete_count; + break; + case isc_info_sql_stmt_insert: + cCountType = isc_info_req_insert_count; + break; + case isc_info_sql_stmt_exec_procedure: + bIsProcedure = true; // will sum all changes + break; + default: + qWarning() << "numRowsAffected: Unknown statement type (" << d->queryType << ")"; + return -1; + } + + char acBuffer[33]; + int iResult = -1; + isc_dsql_sql_info(d->status, &d->stmt, sizeof(acCountInfo), acCountInfo, sizeof(acBuffer), acBuffer); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get statement info"), + QSqlError::StatementError)) + return -1; + for (char *pcBuf = acBuffer + 3; *pcBuf != isc_info_end; /*nothing*/) { + char cType = *pcBuf++; + short sLength = isc_vax_integer (pcBuf, 2); + pcBuf += 2; + int iValue = isc_vax_integer (pcBuf, sLength); + pcBuf += sLength; + if (bIsProcedure) { + if (cType == isc_info_req_insert_count || cType == isc_info_req_update_count + || cType == isc_info_req_delete_count) { + if (iResult == -1) + iResult = 0; + iResult += iValue; + } + } else if (cType == cCountType) { + iResult = iValue; + break; + } + } + return iResult; +} + +QSqlRecord QIBaseResult::record() const +{ + Q_D(const QIBaseResult); + QSqlRecord rec; + if (!isActive() || !d->sqlda) + return rec; + + XSQLVAR v; + for (int i = 0; i < d->sqlda->sqld; ++i) { + v = d->sqlda->sqlvar[i]; + QSqlField f(QString::fromLatin1(v.aliasname, v.aliasname_length).simplified(), + qIBaseTypeName2(v.sqltype, v.sqlscale < 0)); + f.setLength(v.sqllen); + f.setPrecision(qAbs(v.sqlscale)); + f.setRequiredStatus((v.sqltype & 1) == 0 ? QSqlField::Required : QSqlField::Optional); + if(v.sqlscale < 0) { + QSqlQuery q(driver()->createResult()); + q.setForwardOnly(true); + q.exec(QLatin1String("select b.RDB$FIELD_PRECISION, b.RDB$FIELD_SCALE, b.RDB$FIELD_LENGTH, a.RDB$NULL_FLAG " + "FROM RDB$RELATION_FIELDS a, RDB$FIELDS b " + "WHERE b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE " + "AND a.RDB$RELATION_NAME = '") + QString::fromLatin1(v.relname, v.relname_length).toUpper() + QLatin1String("' " + "AND a.RDB$FIELD_NAME = '") + QString::fromLatin1(v.sqlname, v.sqlname_length).toUpper() + QLatin1String("' ")); + if(q.first()) { + if(v.sqlscale < 0) { + f.setLength(q.value(0).toInt()); + f.setPrecision(qAbs(q.value(1).toInt())); + } else { + f.setLength(q.value(2).toInt()); + f.setPrecision(0); + } + f.setRequiredStatus(q.value(3).toBool() ? QSqlField::Required : QSqlField::Optional); + } + } + f.setSqlType(v.sqltype); + rec.append(f); + } + return rec; +} + +QVariant QIBaseResult::handle() const +{ + Q_D(const QIBaseResult); + return QVariant(qRegisterMetaType<isc_stmt_handle>("isc_stmt_handle"), &d->stmt); +} + +/*********************************/ + +QIBaseDriver::QIBaseDriver(QObject * parent) + : QSqlDriver(*new QIBaseDriverPrivate, parent) +{ +} + +QIBaseDriver::QIBaseDriver(isc_db_handle connection, QObject *parent) + : QSqlDriver(*new QIBaseDriverPrivate, parent) +{ + Q_D(QIBaseDriver); + d->ibase = connection; + setOpen(true); + setOpenError(false); +} + +QIBaseDriver::~QIBaseDriver() +{ +} + +bool QIBaseDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case QuerySize: + case NamedPlaceholders: + case LastInsertId: + case BatchOperations: + case SimpleLocking: + case FinishQuery: + case MultipleResultSets: + case CancelQuery: + return false; + case Transactions: + case PreparedQueries: + case PositionalPlaceholders: + case Unicode: + case BLOB: + case EventNotifications: + case LowPrecisionNumbers: + return true; + } + return false; +} + +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(QLatin1Char(';'), QString::SkipEmptyParts)); + + QString encString; + QByteArray role; + for (int i = 0; i < opts.count(); ++i) { + QString tmp(opts.at(i).simplified()); + int idx; + if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) { + QString val = tmp.mid(idx + 1).simplified(); + QString opt = tmp.left(idx).simplified(); + if (opt.toUpper() == QLatin1String("ISC_DPB_LC_CTYPE")) + encString = val; + else if (opt.toUpper() == QLatin1String("ISC_DPB_SQL_ROLE_NAME")) { + role = val.toLocal8Bit(); + role.truncate(255); + } + } + } + + // Use UNICODE_FSS when no ISC_DPB_LC_CTYPE is provided + if (encString.isEmpty()) + encString = QLatin1String("UNICODE_FSS"); + else { + d->tc = QTextCodec::codecForName(encString.toLocal8Bit()); + if (!d->tc) { + qWarning("Unsupported encoding: %s. Using UNICODE_FFS for ISC_DPB_LC_CTYPE.", encString.toLocal8Bit().constData()); + encString = QLatin1String("UNICODE_FSS"); // Fallback to UNICODE_FSS + } + } + + QByteArray enc = encString.toLocal8Bit(); + QByteArray usr = user.toLocal8Bit(); + QByteArray pass = password.toLocal8Bit(); + enc.truncate(255); + usr.truncate(255); + pass.truncate(255); + + QByteArray ba; + ba.reserve(usr.length() + pass.length() + enc.length() + role.length() + 9); + ba.append(char(isc_dpb_version1)); + ba.append(char(isc_dpb_user_name)); + ba.append(char(usr.length())); + ba.append(usr.data(), usr.length()); + ba.append(char(isc_dpb_password)); + ba.append(char(pass.length())); + ba.append(pass.data(), pass.length()); + ba.append(char(isc_dpb_lc_ctype)); + ba.append(char(enc.length())); + ba.append(enc.data(), enc.length()); + + if (!role.isEmpty()) { + ba.append(char(isc_dpb_sql_role_name)); + ba.append(char(role.length())); + ba.append(role.data(), role.length()); + } + + QString portString; + if (port != -1) + portString = QStringLiteral("/%1").arg(port); + + QString ldb; + if (!host.isEmpty()) + ldb += host + portString + QLatin1Char(':'); + ldb += db; + isc_attach_database(d->status, 0, const_cast<char *>(ldb.toLocal8Bit().constData()), + &d->ibase, ba.size(), ba.data()); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Error opening database"), + QSqlError::ConnectionError)) { + setOpenError(true); + return false; + } + + setOpen(true); + setOpenError(false); + return true; +} + +void QIBaseDriver::close() +{ + Q_D(QIBaseDriver); + if (isOpen()) { + + if (d->eventBuffers.size()) { + ISC_STATUS status[20]; + QMap<QString, QIBaseEventBuffer *>::const_iterator i; + for (i = d->eventBuffers.constBegin(); i != d->eventBuffers.constEnd(); ++i) { + QIBaseEventBuffer *eBuffer = i.value(); + eBuffer->subscriptionState = QIBaseEventBuffer::Finished; + isc_cancel_events(status, &d->ibase, &eBuffer->eventId); + qFreeEventBuffer(eBuffer); + } + d->eventBuffers.clear(); + +#if defined(FB_API_VER) + // Workaround for Firebird crash + QTime timer; + timer.start(); + while (timer.elapsed() < 500) + QCoreApplication::processEvents(); +#endif + } + + isc_detach_database(d->status, &d->ibase); + d->ibase = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QIBaseDriver::createResult() const +{ + return new QIBaseResult(this); +} + +bool QIBaseDriver::beginTransaction() +{ + Q_D(QIBaseDriver); + if (!isOpen() || isOpenError()) + return false; + if (d->trans) + return false; + + isc_start_transaction(d->status, &d->trans, 1, &d->ibase, 0, NULL); + return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Could not start transaction"), + QSqlError::TransactionError); +} + +bool QIBaseDriver::commitTransaction() +{ + Q_D(QIBaseDriver); + if (!isOpen() || isOpenError()) + return false; + if (!d->trans) + return false; + + isc_commit_transaction(d->status, &d->trans); + d->trans = 0; + return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Unable to commit transaction"), + QSqlError::TransactionError); +} + +bool QIBaseDriver::rollbackTransaction() +{ + Q_D(QIBaseDriver); + if (!isOpen() || isOpenError()) + return false; + if (!d->trans) + return false; + + isc_rollback_transaction(d->status, &d->trans); + d->trans = 0; + return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Unable to rollback transaction"), + QSqlError::TransactionError); +} + +QStringList QIBaseDriver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QString typeFilter; + + if (type == QSql::SystemTables) { + typeFilter += QLatin1String("RDB$SYSTEM_FLAG != 0"); + } else if (type == (QSql::SystemTables | QSql::Views)) { + typeFilter += QLatin1String("RDB$SYSTEM_FLAG != 0 OR RDB$VIEW_BLR NOT NULL"); + } else { + if (!(type & QSql::SystemTables)) + typeFilter += QLatin1String("RDB$SYSTEM_FLAG = 0 AND "); + if (!(type & QSql::Views)) + typeFilter += QLatin1String("RDB$VIEW_BLR IS NULL AND "); + if (!(type & QSql::Tables)) + typeFilter += QLatin1String("RDB$VIEW_BLR IS NOT NULL AND "); + if (!typeFilter.isEmpty()) + typeFilter.chop(5); + } + if (!typeFilter.isEmpty()) + typeFilter.prepend(QLatin1String("where ")); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + if (!q.exec(QLatin1String("select rdb$relation_name from rdb$relations ") + typeFilter)) + return res; + while(q.next()) + res << q.value(0).toString().simplified(); + + return res; +} + +QSqlRecord QIBaseDriver::record(const QString& tablename) const +{ + QSqlRecord rec; + if (!isOpen()) + return rec; + + 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(QLatin1String("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 " + "WHERE b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE " + "AND a.RDB$RELATION_NAME = '") + table + QLatin1String("' " + "ORDER BY a.RDB$FIELD_POSITION")); + + while (q.next()) { + int type = q.value(1).toInt(); + bool hasScale = q.value(3).toInt() < 0; + QSqlField f(q.value(0).toString().simplified(), qIBaseTypeName(type, hasScale)); + if(hasScale) { + f.setLength(q.value(4).toInt()); + f.setPrecision(qAbs(q.value(3).toInt())); + } else { + f.setLength(q.value(2).toInt()); + f.setPrecision(0); + } + f.setRequired(q.value(5).toInt() > 0); + f.setSqlType(type); + + rec.append(f); + } + return rec; +} + +QSqlIndex QIBaseDriver::primaryIndex(const QString &table) const +{ + QSqlIndex index(table); + if (!isOpen()) + return index; + + QString tablename = table; + if (isIdentifierEscaped(tablename, QSqlDriver::TableName)) + tablename = stripDelimiters(tablename, QSqlDriver::TableName); + else + tablename = tablename.toUpper(); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + q.exec(QLatin1String("SELECT a.RDB$INDEX_NAME, b.RDB$FIELD_NAME, d.RDB$FIELD_TYPE, d.RDB$FIELD_SCALE " + "FROM RDB$RELATION_CONSTRAINTS a, RDB$INDEX_SEGMENTS b, RDB$RELATION_FIELDS c, RDB$FIELDS d " + "WHERE a.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' " + "AND a.RDB$RELATION_NAME = '") + tablename + + QLatin1String(" 'AND a.RDB$INDEX_NAME = b.RDB$INDEX_NAME " + "AND c.RDB$RELATION_NAME = a.RDB$RELATION_NAME " + "AND c.RDB$FIELD_NAME = b.RDB$FIELD_NAME " + "AND d.RDB$FIELD_NAME = c.RDB$FIELD_SOURCE " + "ORDER BY b.RDB$FIELD_POSITION")); + + while (q.next()) { + QSqlField field(q.value(1).toString().simplified(), qIBaseTypeName(q.value(2).toInt(), q.value(3).toInt() < 0)); + index.append(field); //TODO: asc? desc? + index.setName(q.value(0).toString()); + } + + return index; +} + +QString QIBaseDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + switch (field.type()) { + case QVariant::DateTime: { + QDateTime datetime = field.value().toDateTime(); + if (datetime.isValid()) + return QLatin1Char('\'') + QString::number(datetime.date().year()) + QLatin1Char('-') + + QString::number(datetime.date().month()) + QLatin1Char('-') + + QString::number(datetime.date().day()) + QLatin1Char(' ') + + QString::number(datetime.time().hour()) + QLatin1Char(':') + + QString::number(datetime.time().minute()) + QLatin1Char(':') + + QString::number(datetime.time().second()) + QLatin1Char('.') + + QString::number(datetime.time().msec()).rightJustified(3, QLatin1Char('0'), true) + + QLatin1Char('\''); + else + return QLatin1String("NULL"); + } + case QVariant::Time: { + QTime time = field.value().toTime(); + if (time.isValid()) + return QLatin1Char('\'') + QString::number(time.hour()) + QLatin1Char(':') + + QString::number(time.minute()) + QLatin1Char(':') + + QString::number(time.second()) + QLatin1Char('.') + + QString::number(time.msec()).rightJustified(3, QLatin1Char('0'), true) + + QLatin1Char('\''); + else + return QLatin1String("NULL"); + } + case QVariant::Date: { + QDate date = field.value().toDate(); + if (date.isValid()) + return QLatin1Char('\'') + QString::number(date.year()) + QLatin1Char('-') + + QString::number(date.month()) + QLatin1Char('-') + + QString::number(date.day()) + QLatin1Char('\''); + else + return QLatin1String("NULL"); + } + default: + return QSqlDriver::formatValue(field, trimStrings); + } +} + +QVariant QIBaseDriver::handle() const +{ + Q_D(const QIBaseDriver); + return QVariant(qRegisterMetaType<isc_db_handle>("isc_db_handle"), &d->ibase); +} + +#if defined(FB_API_VER) && FB_API_VER >= 20 +static ISC_EVENT_CALLBACK qEventCallback(char *result, ISC_USHORT length, const ISC_UCHAR *updated) +#else +static isc_callback qEventCallback(char *result, short length, char *updated) +#endif +{ + if (!updated) + return 0; + + + memcpy(result, updated, length); + qMutex()->lock(); + QIBaseDriver *driver = qBufferDriverMap()->value(result); + qMutex()->unlock(); + + // We use an asynchronous call (i.e., queued connection) because the event callback + // is executed in a different thread than the one in which the driver lives. + if (driver) + QMetaObject::invokeMethod(driver, "qHandleEventNotification", Qt::QueuedConnection, Q_ARG(void *, reinterpret_cast<void *>(result))); + + return 0; +} + +bool QIBaseDriver::subscribeToNotification(const QString &name) +{ + Q_D(QIBaseDriver); + if (!isOpen()) { + qWarning("QIBaseDriver::subscribeFromNotificationImplementation: database not open."); + return false; + } + + if (d->eventBuffers.contains(name)) { + qWarning("QIBaseDriver::subscribeToNotificationImplementation: already subscribing to '%s'.", + qPrintable(name)); + return false; + } + + QIBaseEventBuffer *eBuffer = new QIBaseEventBuffer; + eBuffer->subscriptionState = QIBaseEventBuffer::Starting; + eBuffer->bufferLength = isc_event_block(&eBuffer->eventBuffer, + &eBuffer->resultBuffer, + 1, + name.toLocal8Bit().constData()); + + qMutex()->lock(); + qBufferDriverMap()->insert(eBuffer->resultBuffer, this); + qMutex()->unlock(); + + d->eventBuffers.insert(name, eBuffer); + + ISC_STATUS status[20]; + isc_que_events(status, + &d->ibase, + &eBuffer->eventId, + eBuffer->bufferLength, + eBuffer->eventBuffer, +#if defined (FB_API_VER) && FB_API_VER >= 20 + (ISC_EVENT_CALLBACK)qEventCallback, +#else + (isc_callback)qEventCallback, +#endif + eBuffer->resultBuffer); + + if (status[0] == 1 && status[1]) { + setLastError(QSqlError(QString::fromLatin1("Could not subscribe to event notifications for %1.").arg(name))); + d->eventBuffers.remove(name); + qFreeEventBuffer(eBuffer); + return false; + } + + return true; +} + +bool QIBaseDriver::unsubscribeFromNotification(const QString &name) +{ + Q_D(QIBaseDriver); + if (!isOpen()) { + qWarning("QIBaseDriver::unsubscribeFromNotificationImplementation: database not open."); + return false; + } + + if (!d->eventBuffers.contains(name)) { + qWarning("QIBaseDriver::QIBaseSubscriptionState not subscribed to '%s'.", + qPrintable(name)); + return false; + } + + QIBaseEventBuffer *eBuffer = d->eventBuffers.value(name); + ISC_STATUS status[20]; + eBuffer->subscriptionState = QIBaseEventBuffer::Finished; + isc_cancel_events(status, &d->ibase, &eBuffer->eventId); + + if (status[0] == 1 && status[1]) { + setLastError(QSqlError(QString::fromLatin1("Could not unsubscribe from event notifications for %1.").arg(name))); + return false; + } + + d->eventBuffers.remove(name); + qFreeEventBuffer(eBuffer); + + return true; +} + +QStringList QIBaseDriver::subscribedToNotifications() const +{ + Q_D(const QIBaseDriver); + return QStringList(d->eventBuffers.keys()); +} + +void QIBaseDriver::qHandleEventNotification(void *updatedResultBuffer) +{ + Q_D(QIBaseDriver); + QMap<QString, QIBaseEventBuffer *>::const_iterator i; + for (i = d->eventBuffers.constBegin(); i != d->eventBuffers.constEnd(); ++i) { + QIBaseEventBuffer* eBuffer = i.value(); + if (reinterpret_cast<void *>(eBuffer->resultBuffer) != updatedResultBuffer) + continue; + + ISC_ULONG counts[20]; + memset(counts, 0, sizeof(counts)); + isc_event_counts(counts, eBuffer->bufferLength, eBuffer->eventBuffer, eBuffer->resultBuffer); + if (counts[0]) { + + if (eBuffer->subscriptionState == QIBaseEventBuffer::Subscribed) { + emit notification(i.key()); + emit notification(i.key(), QSqlDriver::UnknownSource, QVariant()); + } + else if (eBuffer->subscriptionState == QIBaseEventBuffer::Starting) + eBuffer->subscriptionState = QIBaseEventBuffer::Subscribed; + + ISC_STATUS status[20]; + isc_que_events(status, + &d->ibase, + &eBuffer->eventId, + eBuffer->bufferLength, + eBuffer->eventBuffer, +#if defined (FB_API_VER) && FB_API_VER >= 20 + (ISC_EVENT_CALLBACK)qEventCallback, +#else + (isc_callback)qEventCallback, +#endif + eBuffer->resultBuffer); + if (Q_UNLIKELY(status[0] == 1 && status[1])) { + qCritical("QIBaseDriver::qHandleEventNotification: could not resubscribe to '%s'", + qPrintable(i.key())); + } + + return; + } + } +} + +QString QIBaseDriver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + QString res = identifier; + if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase_p.h b/src/plugins/sqldrivers/ibase/qsql_ibase_p.h new file mode 100644 index 0000000000..c7cee41462 --- /dev/null +++ b/src/plugins/sqldrivers/ibase/qsql_ibase_p.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_IBASE_H +#define QSQL_IBASE_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. +// + +#include <QtSql/qsqldriver.h> +#include <ibase.h> + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_IBASE +#else +#define Q_EXPORT_SQLDRIVER_IBASE Q_SQL_EXPORT +#endif + +QT_BEGIN_NAMESPACE + +class QSqlResult; +class QIBaseDriverPrivate; + +class Q_EXPORT_SQLDRIVER_IBASE QIBaseDriver : public QSqlDriver +{ + friend class QIBaseResultPrivate; + Q_DECLARE_PRIVATE(QIBaseDriver) + Q_OBJECT +public: + explicit QIBaseDriver(QObject *parent = 0); + explicit QIBaseDriver(isc_db_handle connection, QObject *parent = 0); + virtual ~QIBaseDriver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + bool open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port, + const QString &connOpts) Q_DECL_OVERRIDE; + bool open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port) { return open(db, user, password, host, port, QString()); } + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + + QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString &table) const Q_DECL_OVERRIDE; + + QString formatValue(const QSqlField &field, bool trimStrings) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + + QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE; + + bool subscribeToNotification(const QString &name) Q_DECL_OVERRIDE; + bool unsubscribeFromNotification(const QString &name) Q_DECL_OVERRIDE; + QStringList subscribedToNotifications() const Q_DECL_OVERRIDE; + +private Q_SLOTS: + void qHandleEventNotification(void* updatedResultBuffer); +}; + +QT_END_NAMESPACE + +#endif // QSQL_IBASE_H diff --git a/src/plugins/sqldrivers/mysql/main.cpp b/src/plugins/sqldrivers/mysql/main.cpp index 855a6eeb9c..00d9c5bb34 100644 --- a/src/plugins/sqldrivers/mysql/main.cpp +++ b/src/plugins/sqldrivers/mysql/main.cpp @@ -39,7 +39,7 @@ #include <qsqldriverplugin.h> #include <qstringlist.h> -#include "../../../sql/drivers/mysql/qsql_mysql_p.h" +#include "qsql_mysql_p.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/sqldrivers/mysql/mysql.pro b/src/plugins/sqldrivers/mysql/mysql.pro index c917bfca48..3bd8cd0040 100644 --- a/src/plugins/sqldrivers/mysql/mysql.pro +++ b/src/plugins/sqldrivers/mysql/mysql.pro @@ -1,8 +1,23 @@ TARGET = qsqlmysql -SOURCES = main.cpp +HEADERS += $$PWD/qsql_mysql_p.h +SOURCES += $$PWD/qsql_mysql.cpp $$PWD/main.cpp + +QMAKE_CXXFLAGS *= $$QMAKE_CFLAGS_MYSQL +LIBS += $$QMAKE_LIBS_MYSQL + +unix { + isEmpty(QMAKE_LIBS_MYSQL) { + !contains(LIBS, .*mysqlclient.*):!contains(LIBS, .*mysqld.*) { + use_libmysqlclient_r:LIBS += -lmysqlclient_r + else:LIBS += -lmysqlclient + } + } +} else { + !contains(LIBS, .*mysql.*):!contains(LIBS, .*mysqld.*):LIBS += -llibmysql +} + OTHER_FILES += mysql.json -include(../../../sql/drivers/mysql/qsql_mysql.pri) PLUGIN_CLASS_NAME = QMYSQLDriverPlugin include(../qsqldriverbase.pri) diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp new file mode 100644 index 0000000000..56caf52dcb --- /dev/null +++ b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp @@ -0,0 +1,1665 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_mysql_p.h" + +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <qsqlrecord.h> +#include <qstringlist.h> +#include <qtextcodec.h> +#include <qvector.h> +#include <qfile.h> +#include <qdebug.h> +#include <QtSql/private/qsqldriver_p.h> +#include <QtSql/private/qsqlresult_p.h> + +#ifdef Q_OS_WIN32 +// comment the next line out if you want to use MySQL/embedded on Win32 systems. +// note that it will crash if you don't statically link to the mysql/e library! +# define Q_NO_MYSQL_EMBEDDED +#endif + +Q_DECLARE_METATYPE(MYSQL_RES*) +Q_DECLARE_METATYPE(MYSQL*) + +#if MYSQL_VERSION_ID >= 40108 +Q_DECLARE_METATYPE(MYSQL_STMT*) +#endif + +#if MYSQL_VERSION_ID >= 40100 +# define Q_CLIENT_MULTI_STATEMENTS CLIENT_MULTI_STATEMENTS +#else +# define Q_CLIENT_MULTI_STATEMENTS 0 +#endif + +QT_BEGIN_NAMESPACE + +class QMYSQLDriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QMYSQLDriver) + +public: + QMYSQLDriverPrivate() : QSqlDriverPrivate(), mysql(0), +#ifndef QT_NO_TEXTCODEC + tc(QTextCodec::codecForLocale()), +#else + tc(0), +#endif + preparedQuerysEnabled(false) { dbmsType = QSqlDriver::MySqlServer; } + MYSQL *mysql; + QTextCodec *tc; + + bool preparedQuerysEnabled; +}; + +static inline QString toUnicode(QTextCodec *tc, const char *str) +{ +#ifdef QT_NO_TEXTCODEC + Q_UNUSED(tc); + return QString::fromLatin1(str); +#else + return tc->toUnicode(str); +#endif +} + +static inline QString toUnicode(QTextCodec *tc, const char *str, int length) +{ +#ifdef QT_NO_TEXTCODEC + Q_UNUSED(tc); + return QString::fromLatin1(str, length); +#else + return tc->toUnicode(str, length); +#endif +} + +static inline QByteArray fromUnicode(QTextCodec *tc, const QString &str) +{ +#ifdef QT_NO_TEXTCODEC + Q_UNUSED(tc); + return str.toLatin1(); +#else + return tc->fromUnicode(str); +#endif +} + +static inline QVariant qDateFromString(const QString &val) +{ +#ifdef QT_NO_DATESTRING + Q_UNUSED(val); + return QVariant(val); +#else + if (val.isEmpty()) + return QVariant(QDate()); + return QVariant(QDate::fromString(val, Qt::ISODate)); +#endif +} + +static inline QVariant qTimeFromString(const QString &val) +{ +#ifdef QT_NO_DATESTRING + Q_UNUSED(val); + return QVariant(val); +#else + if (val.isEmpty()) + return QVariant(QTime()); + return QVariant(QTime::fromString(val, Qt::ISODate)); +#endif +} + +static inline QVariant qDateTimeFromString(QString &val) +{ +#ifdef QT_NO_DATESTRING + Q_UNUSED(val); + return QVariant(val); +#else + if (val.isEmpty()) + return QVariant(QDateTime()); + if (val.length() == 14) + // TIMESTAMPS have the format yyyyMMddhhmmss + val.insert(4, QLatin1Char('-')).insert(7, QLatin1Char('-')).insert(10, + QLatin1Char('T')).insert(13, QLatin1Char(':')).insert(16, QLatin1Char(':')); + return QVariant(QDateTime::fromString(val, Qt::ISODate)); +#endif +} + +class QMYSQLResultPrivate; + +class QMYSQLResult : public QSqlResult +{ + Q_DECLARE_PRIVATE(QMYSQLResult) + friend class QMYSQLDriver; + +public: + explicit QMYSQLResult(const QMYSQLDriver *db); + ~QMYSQLResult(); + + QVariant handle() const Q_DECL_OVERRIDE; +protected: + void cleanup(); + bool fetch(int i) Q_DECL_OVERRIDE; + bool fetchNext() Q_DECL_OVERRIDE; + bool fetchLast() Q_DECL_OVERRIDE; + bool fetchFirst() Q_DECL_OVERRIDE; + QVariant data(int field) Q_DECL_OVERRIDE; + bool isNull(int field) Q_DECL_OVERRIDE; + bool reset (const QString& query) Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QVariant lastInsertId() const Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; + bool nextResult() Q_DECL_OVERRIDE; + +#if MYSQL_VERSION_ID >= 40108 + bool prepare(const QString &stmt) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; +#endif +}; + +class QMYSQLResultPrivate: public QSqlResultPrivate +{ + Q_DECLARE_PUBLIC(QMYSQLResult) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QMYSQLDriver) + + QMYSQLResultPrivate(QMYSQLResult *q, const QMYSQLDriver *drv) + : QSqlResultPrivate(q, drv), + result(0), + rowsAffected(0), + hasBlobs(false) +#if MYSQL_VERSION_ID >= 40108 + , stmt(0), meta(0), inBinds(0), outBinds(0) +#endif + , preparedQuery(false) + { } + + MYSQL_RES *result; + MYSQL_ROW row; + + int rowsAffected; + + bool bindInValues(); + void bindBlobs(); + + bool hasBlobs; + struct QMyField + { + QMyField() + : outField(0), nullIndicator(false), bufLength(0ul), + myField(0), type(QVariant::Invalid) + {} + char *outField; + my_bool nullIndicator; + ulong bufLength; + MYSQL_FIELD *myField; + QVariant::Type type; + }; + + QVector<QMyField> fields; + +#if MYSQL_VERSION_ID >= 40108 + MYSQL_STMT* stmt; + MYSQL_RES* meta; + + MYSQL_BIND *inBinds; + MYSQL_BIND *outBinds; +#endif + + bool preparedQuery; +}; + +#ifndef QT_NO_TEXTCODEC +static QTextCodec* codec(MYSQL* mysql) +{ +#if MYSQL_VERSION_ID >= 32321 + QTextCodec* heuristicCodec = QTextCodec::codecForName(mysql_character_set_name(mysql)); + if (heuristicCodec) + return heuristicCodec; +#endif + return QTextCodec::codecForLocale(); +} +#endif // QT_NO_TEXTCODEC + +static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, + const QMYSQLDriverPrivate* p) +{ + const char *cerr = p->mysql ? mysql_error(p->mysql) : 0; + return QSqlError(QLatin1String("QMYSQL: ") + err, + p->tc ? toUnicode(p->tc, cerr) : QString::fromLatin1(cerr), + type, mysql_errno(p->mysql)); +} + + +static QVariant::Type qDecodeMYSQLType(int mysqltype, uint flags) +{ + QVariant::Type type; + switch (mysqltype) { + case FIELD_TYPE_TINY : + type = static_cast<QVariant::Type>((flags & UNSIGNED_FLAG) ? QMetaType::UChar : QMetaType::Char); + break; + case FIELD_TYPE_SHORT : + type = static_cast<QVariant::Type>((flags & UNSIGNED_FLAG) ? QMetaType::UShort : QMetaType::Short); + break; + case FIELD_TYPE_LONG : + case FIELD_TYPE_INT24 : + type = (flags & UNSIGNED_FLAG) ? QVariant::UInt : QVariant::Int; + break; + case FIELD_TYPE_YEAR : + type = QVariant::Int; + break; + case FIELD_TYPE_LONGLONG : + type = (flags & UNSIGNED_FLAG) ? QVariant::ULongLong : QVariant::LongLong; + break; + case FIELD_TYPE_FLOAT : + case FIELD_TYPE_DOUBLE : + case FIELD_TYPE_DECIMAL : +#if defined(FIELD_TYPE_NEWDECIMAL) + case FIELD_TYPE_NEWDECIMAL: +#endif + type = QVariant::Double; + break; + case FIELD_TYPE_DATE : + type = QVariant::Date; + break; + case FIELD_TYPE_TIME : + type = QVariant::Time; + break; + case FIELD_TYPE_DATETIME : + case FIELD_TYPE_TIMESTAMP : + type = QVariant::DateTime; + break; + case FIELD_TYPE_STRING : + case FIELD_TYPE_VAR_STRING : + case FIELD_TYPE_BLOB : + case FIELD_TYPE_TINY_BLOB : + case FIELD_TYPE_MEDIUM_BLOB : + case FIELD_TYPE_LONG_BLOB : + type = (flags & BINARY_FLAG) ? QVariant::ByteArray : QVariant::String; + break; + default: + case FIELD_TYPE_ENUM : + case FIELD_TYPE_SET : + type = QVariant::String; + break; + } + return type; +} + +static QSqlField qToField(MYSQL_FIELD *field, QTextCodec *tc) +{ + QSqlField f(toUnicode(tc, field->name), + qDecodeMYSQLType(int(field->type), field->flags)); + 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; +} + +#if MYSQL_VERSION_ID >= 40108 + +static QSqlError qMakeStmtError(const QString& err, QSqlError::ErrorType type, + MYSQL_STMT* stmt) +{ + const char *cerr = mysql_stmt_error(stmt); + return QSqlError(QLatin1String("QMYSQL3: ") + err, + QString::fromLatin1(cerr), + type, mysql_stmt_errno(stmt)); +} + +static bool qIsBlob(int t) +{ + return t == MYSQL_TYPE_TINY_BLOB + || t == MYSQL_TYPE_BLOB + || t == MYSQL_TYPE_MEDIUM_BLOB + || t == MYSQL_TYPE_LONG_BLOB; +} + +static bool qIsInteger(int t) +{ + return t == QMetaType::Char || t == QMetaType::UChar + || t == QMetaType::Short || t == QMetaType::UShort + || t == QMetaType::Int || t == QMetaType::UInt + || t == QMetaType::LongLong || t == QMetaType::ULongLong; +} + +void QMYSQLResultPrivate::bindBlobs() +{ + int i; + MYSQL_FIELD *fieldInfo; + MYSQL_BIND *bind; + + for(i = 0; i < fields.count(); ++i) { + fieldInfo = fields.at(i).myField; + if (qIsBlob(inBinds[i].buffer_type) && meta && fieldInfo) { + bind = &inBinds[i]; + bind->buffer_length = fieldInfo->max_length; + delete[] static_cast<char*>(bind->buffer); + bind->buffer = new char[fieldInfo->max_length]; + fields[i].outField = static_cast<char*>(bind->buffer); + } + } +} + +bool QMYSQLResultPrivate::bindInValues() +{ + MYSQL_BIND *bind; + char *field; + int i = 0; + + if (!meta) + meta = mysql_stmt_result_metadata(stmt); + if (!meta) + return false; + + fields.resize(mysql_num_fields(meta)); + + inBinds = new MYSQL_BIND[fields.size()]; + memset(inBinds, 0, fields.size() * sizeof(MYSQL_BIND)); + + MYSQL_FIELD *fieldInfo; + + while((fieldInfo = mysql_fetch_field(meta))) { + QMyField &f = fields[i]; + f.myField = fieldInfo; + + f.type = qDecodeMYSQLType(fieldInfo->type, fieldInfo->flags); + if (qIsBlob(fieldInfo->type)) { + // the size of a blob-field is available as soon as we call + // mysql_stmt_store_result() + // after mysql_stmt_exec() in QMYSQLResult::exec() + fieldInfo->length = 0; + hasBlobs = true; + } else if (qIsInteger(f.type)) { + fieldInfo->length = 8; + } else { + fieldInfo->type = MYSQL_TYPE_STRING; + } + bind = &inBinds[i]; + field = new char[fieldInfo->length + 1]; + memset(field, 0, fieldInfo->length + 1); + + bind->buffer_type = fieldInfo->type; + bind->buffer = field; + bind->buffer_length = f.bufLength = fieldInfo->length + 1; + bind->is_null = &f.nullIndicator; + bind->length = &f.bufLength; + f.outField=field; + + ++i; + } + return true; +} +#endif + +QMYSQLResult::QMYSQLResult(const QMYSQLDriver* db) + : QSqlResult(*new QMYSQLResultPrivate(this, db)) +{ +} + +QMYSQLResult::~QMYSQLResult() +{ + cleanup(); +} + +QVariant QMYSQLResult::handle() const +{ + Q_D(const QMYSQLResult); +#if MYSQL_VERSION_ID >= 40108 + if(d->preparedQuery) + return d->meta ? QVariant::fromValue(d->meta) : QVariant::fromValue(d->stmt); + else +#endif + return QVariant::fromValue(d->result); +} + +void QMYSQLResult::cleanup() +{ + Q_D(QMYSQLResult); + if (d->result) + mysql_free_result(d->result); + +// must iterate trough leftover result sets from multi-selects or stored procedures +// if this isn't done subsequent queries will fail with "Commands out of sync" +#if MYSQL_VERSION_ID >= 40100 + while (driver() && d->drv_d_func()->mysql && mysql_next_result(d->drv_d_func()->mysql) == 0) { + MYSQL_RES *res = mysql_store_result(d->drv_d_func()->mysql); + if (res) + mysql_free_result(res); + } +#endif + +#if MYSQL_VERSION_ID >= 40108 + if (d->stmt) { + if (mysql_stmt_close(d->stmt)) + qWarning("QMYSQLResult::cleanup: unable to free statement handle"); + d->stmt = 0; + } + + if (d->meta) { + mysql_free_result(d->meta); + d->meta = 0; + } + + int i; + for (i = 0; i < d->fields.count(); ++i) + delete[] d->fields[i].outField; + + if (d->outBinds) { + delete[] d->outBinds; + d->outBinds = 0; + } + + if (d->inBinds) { + delete[] d->inBinds; + d->inBinds = 0; + } +#endif + + d->hasBlobs = false; + d->fields.clear(); + d->result = NULL; + d->row = NULL; + setAt(-1); + setActive(false); +} + +bool QMYSQLResult::fetch(int i) +{ + Q_D(QMYSQLResult); + if (!driver()) + return false; + if (isForwardOnly()) { // fake a forward seek + if (at() < i) { + int x = i - at(); + while (--x && fetchNext()) {}; + return fetchNext(); + } else { + return false; + } + } + if (at() == i) + return true; + if (d->preparedQuery) { +#if MYSQL_VERSION_ID >= 40108 + mysql_stmt_data_seek(d->stmt, i); + + int nRC = mysql_stmt_fetch(d->stmt); + if (nRC) { +#ifdef MYSQL_DATA_TRUNCATED + if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED) +#else + if (nRC == 1) +#endif + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to fetch data"), QSqlError::StatementError, d->stmt)); + return false; + } +#else + return false; +#endif + } else { + mysql_data_seek(d->result, i); + d->row = mysql_fetch_row(d->result); + if (!d->row) + return false; + } + + setAt(i); + return true; +} + +bool QMYSQLResult::fetchNext() +{ + Q_D(QMYSQLResult); + if (!driver()) + return false; + if (d->preparedQuery) { +#if MYSQL_VERSION_ID >= 40108 + int nRC = mysql_stmt_fetch(d->stmt); + if (nRC) { +#ifdef MYSQL_DATA_TRUNCATED + if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED) +#else + if (nRC == 1) +#endif // MYSQL_DATA_TRUNCATED + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to fetch data"), QSqlError::StatementError, d->stmt)); + return false; + } +#else + return false; +#endif + } else { + d->row = mysql_fetch_row(d->result); + if (!d->row) + return false; + } + setAt(at() + 1); + return true; +} + +bool QMYSQLResult::fetchLast() +{ + Q_D(QMYSQLResult); + if (!driver()) + return false; + if (isForwardOnly()) { // fake this since MySQL can't seek on forward only queries + bool success = fetchNext(); // did we move at all? + while (fetchNext()) {}; + return success; + } + + my_ulonglong numRows; + if (d->preparedQuery) { +#if MYSQL_VERSION_ID >= 40108 + numRows = mysql_stmt_num_rows(d->stmt); +#else + numRows = 0; +#endif + } else { + numRows = mysql_num_rows(d->result); + } + if (at() == int(numRows)) + return true; + if (!numRows) + return false; + return fetch(numRows - 1); +} + +bool QMYSQLResult::fetchFirst() +{ + if (at() == 0) + return true; + + if (isForwardOnly()) + return (at() == QSql::BeforeFirstRow) ? fetchNext() : false; + return fetch(0); +} + +QVariant QMYSQLResult::data(int field) +{ + Q_D(QMYSQLResult); + if (!isSelect() || field >= d->fields.count()) { + qWarning("QMYSQLResult::data: column %d out of range", field); + return QVariant(); + } + + if (!driver()) + return QVariant(); + + int fieldLength = 0; + const QMYSQLResultPrivate::QMyField &f = d->fields.at(field); + QString val; + if (d->preparedQuery) { + if (f.nullIndicator) + return QVariant(f.type); + + if (qIsInteger(f.type)) + return QVariant(f.type, f.outField); + + if (f.type != QVariant::ByteArray) + val = toUnicode(d->drv_d_func()->tc, f.outField, f.bufLength); + } else { + if (d->row[field] == NULL) { + // NULL value + return QVariant(f.type); + } + + fieldLength = mysql_fetch_lengths(d->result)[field]; + + if (f.type != QVariant::ByteArray) + val = toUnicode(d->drv_d_func()->tc, d->row[field], fieldLength); + } + + switch (static_cast<int>(f.type)) { + case QVariant::LongLong: + return QVariant(val.toLongLong()); + case QVariant::ULongLong: + return QVariant(val.toULongLong()); + case QMetaType::Char: + case QMetaType::Short: + case QVariant::Int: + return QVariant(val.toInt()); + case QMetaType::UChar: + case QMetaType::UShort: + case QVariant::UInt: + return QVariant(val.toUInt()); + case QVariant::Double: { + QVariant v; + bool ok=false; + double dbl = val.toDouble(&ok); + switch(numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + v=QVariant(dbl).toInt(); + break; + case QSql::LowPrecisionInt64: + v = QVariant(dbl).toLongLong(); + break; + case QSql::LowPrecisionDouble: + v = QVariant(dbl); + break; + case QSql::HighPrecision: + default: + v = val; + ok = true; + break; + } + if(ok) + return v; + else + return QVariant(); + } + return QVariant(val.toDouble()); + case QVariant::Date: + return qDateFromString(val); + case QVariant::Time: + return qTimeFromString(val); + case QVariant::DateTime: + return qDateTimeFromString(val); + case QVariant::ByteArray: { + + QByteArray ba; + if (d->preparedQuery) { + ba = QByteArray(f.outField, f.bufLength); + } else { + ba = QByteArray(d->row[field], fieldLength); + } + return QVariant(ba); + } + default: + case QVariant::String: + return QVariant(val); + } + qWarning("QMYSQLResult::data: unknown data type"); + return QVariant(); +} + +bool QMYSQLResult::isNull(int field) +{ + Q_D(const QMYSQLResult); + if (field < 0 || field >= d->fields.count()) + return true; + if (d->preparedQuery) + return d->fields.at(field).nullIndicator; + else + return d->row[field] == NULL; +} + +bool QMYSQLResult::reset (const QString& query) +{ + Q_D(QMYSQLResult); + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + + d->preparedQuery = false; + + cleanup(); + + const QByteArray encQuery(fromUnicode(d->drv_d_func()->tc, query)); + if (mysql_real_query(d->drv_d_func()->mysql, encQuery.data(), encQuery.length())) { + setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute query"), + QSqlError::StatementError, d->drv_d_func())); + return false; + } + d->result = mysql_store_result(d->drv_d_func()->mysql); + if (!d->result && mysql_field_count(d->drv_d_func()->mysql) > 0) { + setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store result"), + QSqlError::StatementError, d->drv_d_func())); + return false; + } + int numFields = mysql_field_count(d->drv_d_func()->mysql); + setSelect(numFields != 0); + d->fields.resize(numFields); + d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql); + + if (isSelect()) { + for(int i = 0; i < numFields; i++) { + MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i); + d->fields[i].type = qDecodeMYSQLType(field->type, field->flags); + } + setAt(QSql::BeforeFirstRow); + } + setActive(true); + return isActive(); +} + +int QMYSQLResult::size() +{ + Q_D(const QMYSQLResult); + if (driver() && isSelect()) + if (d->preparedQuery) +#if MYSQL_VERSION_ID >= 40108 + return mysql_stmt_num_rows(d->stmt); +#else + return -1; +#endif + else + return int(mysql_num_rows(d->result)); + else + return -1; +} + +int QMYSQLResult::numRowsAffected() +{ + Q_D(const QMYSQLResult); + return d->rowsAffected; +} + +QVariant QMYSQLResult::lastInsertId() const +{ + Q_D(const QMYSQLResult); + if (!isActive() || !driver()) + return QVariant(); + + if (d->preparedQuery) { +#if MYSQL_VERSION_ID >= 40108 + quint64 id = mysql_stmt_insert_id(d->stmt); + if (id) + return QVariant(id); +#endif + } else { + quint64 id = mysql_insert_id(d->drv_d_func()->mysql); + if (id) + return QVariant(id); + } + return QVariant(); +} + +QSqlRecord QMYSQLResult::record() const +{ + Q_D(const QMYSQLResult); + QSqlRecord info; + MYSQL_RES *res; + if (!isActive() || !isSelect() || !driver()) + return info; + +#if MYSQL_VERSION_ID >= 40108 + res = d->preparedQuery ? d->meta : d->result; +#else + res = d->result; +#endif + + if (!mysql_errno(d->drv_d_func()->mysql)) { + mysql_field_seek(res, 0); + MYSQL_FIELD* field = mysql_fetch_field(res); + while(field) { + info.append(qToField(field, d->drv_d_func()->tc)); + field = mysql_fetch_field(res); + } + } + mysql_field_seek(res, 0); + return info; +} + +bool QMYSQLResult::nextResult() +{ + Q_D(QMYSQLResult); + if (!driver()) + return false; +#if MYSQL_VERSION_ID >= 40100 + setAt(-1); + setActive(false); + + if (d->result && isSelect()) + mysql_free_result(d->result); + d->result = 0; + setSelect(false); + + for (int i = 0; i < d->fields.count(); ++i) + delete[] d->fields[i].outField; + d->fields.clear(); + + int status = mysql_next_result(d->drv_d_func()->mysql); + if (status > 0) { + setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute next query"), + QSqlError::StatementError, d->drv_d_func())); + return false; + } else if (status == -1) { + return false; // No more result sets + } + + d->result = mysql_store_result(d->drv_d_func()->mysql); + int numFields = mysql_field_count(d->drv_d_func()->mysql); + if (!d->result && numFields > 0) { + setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store next result"), + QSqlError::StatementError, d->drv_d_func())); + return false; + } + + setSelect(numFields > 0); + d->fields.resize(numFields); + d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql); + + if (isSelect()) { + for (int i = 0; i < numFields; i++) { + MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i); + d->fields[i].type = qDecodeMYSQLType(field->type, field->flags); + } + } + + setActive(true); + return true; +#else + return false; +#endif +} + +void QMYSQLResult::virtual_hook(int id, void *data) +{ + QSqlResult::virtual_hook(id, data); +} + + +#if MYSQL_VERSION_ID >= 40108 + +static MYSQL_TIME *toMySqlDate(QDate date, QTime time, QVariant::Type type) +{ + Q_ASSERT(type == QVariant::Time || type == QVariant::Date + || type == QVariant::DateTime); + + MYSQL_TIME *myTime = new MYSQL_TIME; + memset(myTime, 0, sizeof(MYSQL_TIME)); + + if (type == QVariant::Time || type == QVariant::DateTime) { + myTime->hour = time.hour(); + myTime->minute = time.minute(); + myTime->second = time.second(); + myTime->second_part = time.msec() * 1000; + } + if (type == QVariant::Date || type == QVariant::DateTime) { + myTime->year = date.year(); + myTime->month = date.month(); + myTime->day = date.day(); + } + + return myTime; +} + +bool QMYSQLResult::prepare(const QString& query) +{ + Q_D(QMYSQLResult); + if (!driver()) + return false; +#if MYSQL_VERSION_ID >= 40108 + cleanup(); + if (!d->drv_d_func()->preparedQuerysEnabled) + return QSqlResult::prepare(query); + + int r; + + if (query.isEmpty()) + return false; + + if (!d->stmt) + d->stmt = mysql_stmt_init(d->drv_d_func()->mysql); + if (!d->stmt) { + setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to prepare statement"), + QSqlError::StatementError, d->drv_d_func())); + return false; + } + + const QByteArray encQuery(fromUnicode(d->drv_d_func()->tc, query)); + r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.length()); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to prepare statement"), QSqlError::StatementError, d->stmt)); + cleanup(); + 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)]; + } + + setSelect(d->bindInValues()); + d->preparedQuery = true; + return true; +#else + return false; +#endif +} + +bool QMYSQLResult::exec() +{ + Q_D(QMYSQLResult); + if (!driver()) + return false; + if (!d->preparedQuery) + return QSqlResult::exec(); + if (!d->stmt) + return false; + + int r = 0; + MYSQL_BIND* currBind; + QVector<MYSQL_TIME *> timeVector; + QVector<QByteArray> stringVector; + QVector<my_bool> nullVector; + + const QVector<QVariant> values = boundValues(); + + r = mysql_stmt_reset(d->stmt); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to reset statement"), QSqlError::StatementError, d->stmt)); + return false; + } + + if (mysql_stmt_param_count(d->stmt) > 0 && + mysql_stmt_param_count(d->stmt) == (uint)values.count()) { + + nullVector.resize(values.count()); + for (int i = 0; i < values.count(); ++i) { + const QVariant &val = boundValues().at(i); + void *data = const_cast<void *>(val.constData()); + + currBind = &d->outBinds[i]; + + nullVector[i] = static_cast<my_bool>(val.isNull()); + currBind->is_null = &nullVector[i]; + currBind->length = 0; + currBind->is_unsigned = 0; + + switch (val.type()) { + case QVariant::ByteArray: + currBind->buffer_type = MYSQL_TYPE_BLOB; + currBind->buffer = const_cast<char *>(val.toByteArray().constData()); + currBind->buffer_length = val.toByteArray().size(); + break; + + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime: { + MYSQL_TIME *myTime = toMySqlDate(val.toDate(), val.toTime(), val.type()); + timeVector.append(myTime); + + currBind->buffer = myTime; + switch(val.type()) { + case QVariant::Time: + currBind->buffer_type = MYSQL_TYPE_TIME; + myTime->time_type = MYSQL_TIMESTAMP_TIME; + break; + case QVariant::Date: + currBind->buffer_type = MYSQL_TYPE_DATE; + myTime->time_type = MYSQL_TIMESTAMP_DATE; + break; + case QVariant::DateTime: + currBind->buffer_type = MYSQL_TYPE_DATETIME; + myTime->time_type = MYSQL_TIMESTAMP_DATETIME; + break; + default: + break; + } + currBind->buffer_length = sizeof(MYSQL_TIME); + currBind->length = 0; + break; } + case QVariant::UInt: + case QVariant::Int: + currBind->buffer_type = MYSQL_TYPE_LONG; + currBind->buffer = data; + currBind->buffer_length = sizeof(int); + currBind->is_unsigned = (val.type() != QVariant::Int); + break; + case QVariant::Bool: + currBind->buffer_type = MYSQL_TYPE_TINY; + currBind->buffer = data; + currBind->buffer_length = sizeof(bool); + currBind->is_unsigned = false; + break; + case QVariant::Double: + currBind->buffer_type = MYSQL_TYPE_DOUBLE; + currBind->buffer = data; + currBind->buffer_length = sizeof(double); + break; + case QVariant::LongLong: + case QVariant::ULongLong: + currBind->buffer_type = MYSQL_TYPE_LONGLONG; + currBind->buffer = data; + currBind->buffer_length = sizeof(qint64); + currBind->is_unsigned = (val.type() == QVariant::ULongLong); + break; + case QVariant::String: + default: { + QByteArray ba = fromUnicode(d->drv_d_func()->tc, val.toString()); + stringVector.append(ba); + currBind->buffer_type = MYSQL_TYPE_STRING; + currBind->buffer = const_cast<char *>(ba.constData()); + currBind->buffer_length = ba.length(); + break; } + } + } + + r = mysql_stmt_bind_param(d->stmt, d->outBinds); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to bind value"), QSqlError::StatementError, d->stmt)); + qDeleteAll(timeVector); + return false; + } + } + r = mysql_stmt_execute(d->stmt); + + qDeleteAll(timeVector); + + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to execute statement"), QSqlError::StatementError, d->stmt)); + return false; + } + //if there is meta-data there is also data + setSelect(d->meta); + + d->rowsAffected = mysql_stmt_affected_rows(d->stmt); + + if (isSelect()) { + my_bool update_max_length = true; + + r = mysql_stmt_bind_result(d->stmt, d->inBinds); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to bind outvalues"), QSqlError::StatementError, d->stmt)); + return false; + } + if (d->hasBlobs) + mysql_stmt_attr_set(d->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &update_max_length); + + r = mysql_stmt_store_result(d->stmt); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to store statement results"), QSqlError::StatementError, d->stmt)); + return false; + } + + if (d->hasBlobs) { + // mysql_stmt_store_result() with STMT_ATTR_UPDATE_MAX_LENGTH set to true crashes + // when called without a preceding call to mysql_stmt_bind_result() + // in versions < 4.1.8 + d->bindBlobs(); + r = mysql_stmt_bind_result(d->stmt, d->inBinds); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to bind outvalues"), QSqlError::StatementError, d->stmt)); + return false; + } + } + setAt(QSql::BeforeFirstRow); + } + setActive(true); + return true; +} +#endif +///////////////////////////////////////////////////////// + +static int qMySqlConnectionCount = 0; +static bool qMySqlInitHandledByUser = false; + +static void qLibraryInit() +{ +#ifndef Q_NO_MYSQL_EMBEDDED +# if MYSQL_VERSION_ID >= 40000 + if (qMySqlInitHandledByUser || qMySqlConnectionCount > 1) + return; + +# if (MYSQL_VERSION_ID >= 40110 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50003 + if (mysql_library_init(0, 0, 0)) { +# else + if (mysql_server_init(0, 0, 0)) { +# endif + qWarning("QMYSQLDriver::qServerInit: unable to start server."); + } +# endif // MYSQL_VERSION_ID +#endif // Q_NO_MYSQL_EMBEDDED +} + +static void qLibraryEnd() +{ +#ifndef Q_NO_MYSQL_EMBEDDED +# if MYSQL_VERSION_ID > 40000 +# if (MYSQL_VERSION_ID >= 40110 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50003 + mysql_library_end(); +# else + mysql_server_end(); +# endif +# endif +#endif +} + +QMYSQLDriver::QMYSQLDriver(QObject * parent) + : QSqlDriver(*new QMYSQLDriverPrivate, parent) +{ + init(); + qLibraryInit(); +} + +/*! + Create a driver instance with the open connection handle, \a con. + The instance's parent (owner) is \a parent. +*/ + +QMYSQLDriver::QMYSQLDriver(MYSQL * con, QObject * parent) + : QSqlDriver(*new QMYSQLDriverPrivate, parent) +{ + Q_D(QMYSQLDriver); + init(); + if (con) { + d->mysql = (MYSQL *) con; +#ifndef QT_NO_TEXTCODEC + d->tc = codec(con); +#endif + setOpen(true); + setOpenError(false); + if (qMySqlConnectionCount == 1) + qMySqlInitHandledByUser = true; + } else { + qLibraryInit(); + } +} + +void QMYSQLDriver::init() +{ + Q_D(QMYSQLDriver); + d->mysql = 0; + qMySqlConnectionCount++; +} + +QMYSQLDriver::~QMYSQLDriver() +{ + qMySqlConnectionCount--; + if (qMySqlConnectionCount == 0 && !qMySqlInitHandledByUser) + qLibraryEnd(); +} + +bool QMYSQLDriver::hasFeature(DriverFeature f) const +{ + Q_D(const QMYSQLDriver); + switch (f) { + case Transactions: +// CLIENT_TRANSACTION should be defined in all recent mysql client libs > 3.23.34 +#ifdef CLIENT_TRANSACTIONS + if (d->mysql) { + if ((d->mysql->server_capabilities & CLIENT_TRANSACTIONS) == CLIENT_TRANSACTIONS) + return true; + } +#endif + return false; + case NamedPlaceholders: + case BatchOperations: + case SimpleLocking: + case EventNotifications: + case FinishQuery: + case CancelQuery: + return false; + case QuerySize: + case BLOB: + case LastInsertId: + case Unicode: + case LowPrecisionNumbers: + return true; + case PreparedQueries: + case PositionalPlaceholders: +#if MYSQL_VERSION_ID >= 40108 + return d->preparedQuerysEnabled; +#else + return false; +#endif + case MultipleResultSets: +#if MYSQL_VERSION_ID >= 40100 + return true; +#else + return false; +#endif + } + return false; +} + +static void setOptionFlag(uint &optionFlags, const QString &opt) +{ + if (opt == QLatin1String("CLIENT_COMPRESS")) + optionFlags |= CLIENT_COMPRESS; + else if (opt == QLatin1String("CLIENT_FOUND_ROWS")) + optionFlags |= CLIENT_FOUND_ROWS; + else if (opt == QLatin1String("CLIENT_IGNORE_SPACE")) + optionFlags |= CLIENT_IGNORE_SPACE; + else if (opt == QLatin1String("CLIENT_INTERACTIVE")) + optionFlags |= CLIENT_INTERACTIVE; + else if (opt == QLatin1String("CLIENT_NO_SCHEMA")) + optionFlags |= CLIENT_NO_SCHEMA; + else if (opt == QLatin1String("CLIENT_ODBC")) + optionFlags |= CLIENT_ODBC; + else if (opt == QLatin1String("CLIENT_SSL")) + qWarning("QMYSQLDriver: SSL_KEY, SSL_CERT and SSL_CA should be used instead of CLIENT_SSL."); + else + qWarning("QMYSQLDriver::open: Unknown connect option '%s'", opt.toLocal8Bit().constData()); +} + +bool QMYSQLDriver::open(const QString& db, + const QString& user, + const QString& password, + const QString& host, + int port, + const QString& connOpts) +{ + Q_D(QMYSQLDriver); + if (isOpen()) + close(); + + /* This is a hack to get MySQL's stored procedure support working. + Since a stored procedure _may_ return multiple result sets, + we have to enable CLIEN_MULTI_STATEMENTS here, otherwise _any_ + stored procedure call will fail. + */ + unsigned int optionFlags = Q_CLIENT_MULTI_STATEMENTS; + const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts)); + QString unixSocket; + QString sslCert; + QString sslCA; + QString sslKey; + QString sslCAPath; + QString sslCipher; +#if MYSQL_VERSION_ID >= 50000 + my_bool reconnect=false; + uint connectTimeout = 0; + uint readTimeout = 0; + uint writeTimeout = 0; +#endif + + // extract the real options from the string + for (int i = 0; i < opts.count(); ++i) { + QString tmp(opts.at(i).simplified()); + int idx; + if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) { + QString val = tmp.mid(idx + 1).simplified(); + QString opt = tmp.left(idx).simplified(); + if (opt == QLatin1String("UNIX_SOCKET")) + unixSocket = val; +#if MYSQL_VERSION_ID >= 50000 + else if (opt == QLatin1String("MYSQL_OPT_RECONNECT")) { + if (val == QLatin1String("TRUE") || val == QLatin1String("1") || val.isEmpty()) + reconnect = true; + } else if (opt == QLatin1String("MYSQL_OPT_CONNECT_TIMEOUT")) { + connectTimeout = val.toInt(); + } else if (opt == QLatin1String("MYSQL_OPT_READ_TIMEOUT")) { + readTimeout = val.toInt(); + } else if (opt == QLatin1String("MYSQL_OPT_WRITE_TIMEOUT")) { + writeTimeout = val.toInt(); + } +#endif + else if (opt == QLatin1String("SSL_KEY")) + sslKey = val; + else if (opt == QLatin1String("SSL_CERT")) + sslCert = val; + else if (opt == QLatin1String("SSL_CA")) + sslCA = val; + else if (opt == QLatin1String("SSL_CAPATH")) + sslCAPath = val; + else if (opt == QLatin1String("SSL_CIPHER")) + sslCipher = val; + else if (val == QLatin1String("TRUE") || val == QLatin1String("1")) + setOptionFlag(optionFlags, tmp.left(idx).simplified()); + else + qWarning("QMYSQLDriver::open: Illegal connect option value '%s'", + tmp.toLocal8Bit().constData()); + } else { + setOptionFlag(optionFlags, tmp); + } + } + + if (!(d->mysql = mysql_init((MYSQL*) 0))) { + setLastError(qMakeError(tr("Unable to allocate a MYSQL object"), + QSqlError::ConnectionError, d)); + setOpenError(true); + return false; + } + + if (!sslKey.isNull() || !sslCert.isNull() || !sslCA.isNull() || + !sslCAPath.isNull() || !sslCipher.isNull()) { + mysql_ssl_set(d->mysql, + sslKey.isNull() ? static_cast<const char *>(0) + : QFile::encodeName(sslKey).constData(), + sslCert.isNull() ? static_cast<const char *>(0) + : QFile::encodeName(sslCert).constData(), + sslCA.isNull() ? static_cast<const char *>(0) + : QFile::encodeName(sslCA).constData(), + sslCAPath.isNull() ? static_cast<const char *>(0) + : QFile::encodeName(sslCAPath).constData(), + sslCipher.isNull() ? static_cast<const char *>(0) + : sslCipher.toLocal8Bit().constData()); + } + +#if MYSQL_VERSION_ID >= 50000 + if (connectTimeout != 0) + mysql_options(d->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &connectTimeout); + if (readTimeout != 0) + mysql_options(d->mysql, MYSQL_OPT_READ_TIMEOUT, &readTimeout); + if (writeTimeout != 0) + mysql_options(d->mysql, MYSQL_OPT_WRITE_TIMEOUT, &writeTimeout); +#endif + MYSQL *mysql = mysql_real_connect(d->mysql, + host.isNull() ? static_cast<const char *>(0) + : host.toLocal8Bit().constData(), + user.isNull() ? static_cast<const char *>(0) + : user.toLocal8Bit().constData(), + password.isNull() ? static_cast<const char *>(0) + : password.toLocal8Bit().constData(), + db.isNull() ? static_cast<const char *>(0) + : db.toLocal8Bit().constData(), + (port > -1) ? port : 0, + unixSocket.isNull() ? static_cast<const char *>(0) + : unixSocket.toLocal8Bit().constData(), + optionFlags); + + if (mysql == d->mysql) { + if (!db.isEmpty() && mysql_select_db(d->mysql, db.toLocal8Bit().constData())) { + setLastError(qMakeError(tr("Unable to open database '%1'").arg(db), QSqlError::ConnectionError, d)); + mysql_close(d->mysql); + setOpenError(true); + return false; + } +#if MYSQL_VERSION_ID >= 50000 + if (reconnect) + mysql_options(d->mysql, MYSQL_OPT_RECONNECT, &reconnect); +#endif + } else { + setLastError(qMakeError(tr("Unable to connect"), + QSqlError::ConnectionError, d)); + mysql_close(d->mysql); + d->mysql = NULL; + setOpenError(true); + return false; + } + +#if (MYSQL_VERSION_ID >= 40113 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50007 + // force the communication to be utf8 + mysql_set_character_set(d->mysql, "utf8"); +#endif +#ifndef QT_NO_TEXTCODEC + d->tc = codec(d->mysql); +#endif + +#if MYSQL_VERSION_ID >= 40108 + d->preparedQuerysEnabled = mysql_get_client_version() >= 40108 + && mysql_get_server_version(d->mysql) >= 40100; +#else + d->preparedQuerysEnabled = false; +#endif + +#ifndef QT_NO_THREAD + mysql_thread_init(); +#endif + + + setOpen(true); + setOpenError(false); + return true; +} + +void QMYSQLDriver::close() +{ + Q_D(QMYSQLDriver); + if (isOpen()) { +#ifndef QT_NO_THREAD + mysql_thread_end(); +#endif + mysql_close(d->mysql); + d->mysql = NULL; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QMYSQLDriver::createResult() const +{ + return new QMYSQLResult(this); +} + +QStringList QMYSQLDriver::tables(QSql::TableType type) const +{ + Q_D(const QMYSQLDriver); + QStringList tl; +#if MYSQL_VERSION_ID >= 40100 + if( mysql_get_server_version(d->mysql) < 50000) + { +#endif + if (!isOpen()) + return tl; + if (!(type & QSql::Tables)) + return tl; + + MYSQL_RES* tableRes = mysql_list_tables(d->mysql, NULL); + MYSQL_ROW row; + int i = 0; + while (tableRes) { + mysql_data_seek(tableRes, i); + row = mysql_fetch_row(tableRes); + if (!row) + break; + tl.append(toUnicode(d->tc, row[0])); + i++; + } + mysql_free_result(tableRes); +#if MYSQL_VERSION_ID >= 40100 + } else { + QSqlQuery q(createResult()); + if(type & QSql::Tables) { + QString sql = QLatin1String("select table_name from information_schema.tables where table_schema = '") + QLatin1String(d->mysql->db) + QLatin1String("' and table_type = 'BASE TABLE'"); + q.exec(sql); + + while(q.next()) + tl.append(q.value(0).toString()); + } + if(type & QSql::Views) { + QString sql = QLatin1String("select table_name from information_schema.tables where table_schema = '") + QLatin1String(d->mysql->db) + QLatin1String("' and table_type = 'VIEW'"); + q.exec(sql); + + while(q.next()) + tl.append(q.value(0).toString()); + } + } +#endif + return tl; +} + +QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const +{ + QSqlIndex idx; + if (!isOpen()) + return idx; + + QSqlQuery i(createResult()); + QString stmt(QLatin1String("show index from %1;")); + QSqlRecord fil = record(tablename); + i.exec(stmt.arg(tablename)); + while (i.isActive() && i.next()) { + if (i.value(2).toString() == QLatin1String("PRIMARY")) { + idx.append(fil.field(i.value(4).toString())); + idx.setCursorName(i.value(0).toString()); + idx.setName(i.value(2).toString()); + } + } + + return idx; +} + +QSqlRecord QMYSQLDriver::record(const QString& tablename) const +{ + Q_D(const QMYSQLDriver); + QString table=tablename; + if(isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlRecord info; + if (!isOpen()) + return info; + MYSQL_RES* r = mysql_list_fields(d->mysql, table.toLocal8Bit().constData(), 0); + if (!r) { + return info; + } + MYSQL_FIELD* field; + + while ((field = mysql_fetch_field(r))) + info.append(qToField(field, d->tc)); + mysql_free_result(r); + return info; +} + +QVariant QMYSQLDriver::handle() const +{ + Q_D(const QMYSQLDriver); + return QVariant::fromValue(d->mysql); +} + +bool QMYSQLDriver::beginTransaction() +{ + Q_D(QMYSQLDriver); +#ifndef CLIENT_TRANSACTIONS + return false; +#endif + if (!isOpen()) { + qWarning("QMYSQLDriver::beginTransaction: Database not open"); + return false; + } + if (mysql_query(d->mysql, "BEGIN WORK")) { + setLastError(qMakeError(tr("Unable to begin transaction"), + QSqlError::StatementError, d)); + return false; + } + return true; +} + +bool QMYSQLDriver::commitTransaction() +{ + Q_D(QMYSQLDriver); +#ifndef CLIENT_TRANSACTIONS + return false; +#endif + if (!isOpen()) { + qWarning("QMYSQLDriver::commitTransaction: Database not open"); + return false; + } + if (mysql_query(d->mysql, "COMMIT")) { + setLastError(qMakeError(tr("Unable to commit transaction"), + QSqlError::StatementError, d)); + return false; + } + return true; +} + +bool QMYSQLDriver::rollbackTransaction() +{ + Q_D(QMYSQLDriver); +#ifndef CLIENT_TRANSACTIONS + return false; +#endif + if (!isOpen()) { + qWarning("QMYSQLDriver::rollbackTransaction: Database not open"); + return false; + } + if (mysql_query(d->mysql, "ROLLBACK")) { + setLastError(qMakeError(tr("Unable to rollback transaction"), + QSqlError::StatementError, d)); + return false; + } + return true; +} + +QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + Q_D(const QMYSQLDriver); + QString r; + if (field.isNull()) { + r = QLatin1String("NULL"); + } else { + switch(field.type()) { + case QVariant::Double: + r = QString::number(field.value().toDouble(), 'g', field.precision()); + break; + case QVariant::String: + // Escape '\' characters + r = QSqlDriver::formatValue(field, trimStrings); + r.replace(QLatin1String("\\"), QLatin1String("\\\\")); + break; + case QVariant::ByteArray: + if (isOpen()) { + const QByteArray ba = field.value().toByteArray(); + // buffer has to be at least length*2+1 bytes + char* buffer = new char[ba.size() * 2 + 1]; + int escapedSize = int(mysql_real_escape_string(d->mysql, buffer, + ba.data(), ba.size())); + r.reserve(escapedSize + 3); + r.append(QLatin1Char('\'')).append(toUnicode(d->tc, buffer)).append(QLatin1Char('\'')); + delete[] buffer; + break; + } else { + qWarning("QMYSQLDriver::formatValue: Database not open"); + } + // fall through + default: + r = QSqlDriver::formatValue(field, trimStrings); + } + } + return r; +} + +QString QMYSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + QString res = identifier; + if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('`')) && !identifier.endsWith(QLatin1Char('`')) ) { + res.prepend(QLatin1Char('`')).append(QLatin1Char('`')); + res.replace(QLatin1Char('.'), QLatin1String("`.`")); + } + return res; +} + +bool QMYSQLDriver::isIdentifierEscaped(const QString &identifier, IdentifierType type) const +{ + Q_UNUSED(type); + return identifier.size() > 2 + && identifier.startsWith(QLatin1Char('`')) //left delimited + && identifier.endsWith(QLatin1Char('`')); //right delimited +} + +QT_END_NAMESPACE diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql_p.h b/src/plugins/sqldrivers/mysql/qsql_mysql_p.h new file mode 100644 index 0000000000..7641f9aa34 --- /dev/null +++ b/src/plugins/sqldrivers/mysql/qsql_mysql_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_MYSQL_H +#define QSQL_MYSQL_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. +// + +#include <QtSql/qsqldriver.h> + +#if defined (Q_OS_WIN32) +#include <QtCore/qt_windows.h> +#endif + +#include <mysql.h> + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_MYSQL +#else +#define Q_EXPORT_SQLDRIVER_MYSQL Q_SQL_EXPORT +#endif + +QT_BEGIN_NAMESPACE + +class QMYSQLDriverPrivate; + +class Q_EXPORT_SQLDRIVER_MYSQL QMYSQLDriver : public QSqlDriver +{ + friend class QMYSQLResultPrivate; + Q_DECLARE_PRIVATE(QMYSQLDriver) + Q_OBJECT +public: + explicit QMYSQLDriver(QObject *parent=0); + explicit QMYSQLDriver(MYSQL *con, QObject * parent=0); + ~QMYSQLDriver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString& tablename) const Q_DECL_OVERRIDE; + QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE; + QString formatValue(const QSqlField &field, + bool trimStrings) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE; + + bool isIdentifierEscaped(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE; + +protected: + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; +private: + void init(); +}; + +QT_END_NAMESPACE + +#endif // QSQL_MYSQL_H diff --git a/src/plugins/sqldrivers/oci/main.cpp b/src/plugins/sqldrivers/oci/main.cpp index 980116123d..e576928d31 100644 --- a/src/plugins/sqldrivers/oci/main.cpp +++ b/src/plugins/sqldrivers/oci/main.cpp @@ -39,7 +39,7 @@ #include <qsqldriverplugin.h> #include <qstringlist.h> -#include "../../../sql/drivers/oci/qsql_oci_p.h" +#include "qsql_oci_p.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/sqldrivers/oci/oci.pro b/src/plugins/sqldrivers/oci/oci.pro index 96a0dd9ab6..a22d1181bf 100644 --- a/src/plugins/sqldrivers/oci/oci.pro +++ b/src/plugins/sqldrivers/oci/oci.pro @@ -1,8 +1,16 @@ TARGET = qsqloci -SOURCES = main.cpp +HEADERS += $$PWD/qsql_oci_p.h +SOURCES += $$PWD/qsql_oci.cpp $$PWD/main.cpp + +unix { + !contains(LIBS, .*clnts.*):LIBS += -lclntsh +} else { + LIBS *= -loci +} +darwin:QMAKE_LFLAGS += -Wl,-flat_namespace,-U,_environ + OTHER_FILES += oci.json -include(../../../sql/drivers/oci/qsql_oci.pri) PLUGIN_CLASS_NAME = QOCIDriverPlugin include(../qsqldriverbase.pri) diff --git a/src/plugins/sqldrivers/oci/qsql_oci.cpp b/src/plugins/sqldrivers/oci/qsql_oci.cpp new file mode 100644 index 0000000000..47d6db7ea4 --- /dev/null +++ b/src/plugins/sqldrivers/oci/qsql_oci.cpp @@ -0,0 +1,2725 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_oci_p.h" + +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qmetatype.h> +#include <qregexp.h> +#include <qshareddata.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <QtSql/private/qsqlcachedresult_p.h> +#include <QtSql/private/qsqldriver_p.h> +#include <qstringlist.h> +#include <qvarlengtharray.h> +#include <qvector.h> +#include <qdebug.h> + +// This is needed for oracle oci when compiling with mingw-w64 headers +#if defined(__MINGW64_VERSION_MAJOR) && defined(_WIN64) +#define _int64 __int64 +#endif + + +#include <oci.h> +#ifdef max +#undef max +#endif +#ifdef min +#undef min +#endif + +#include <stdlib.h> + +#define QOCI_DYNAMIC_CHUNK_SIZE 65535 +#define QOCI_PREFETCH_MEM 10240 + +// setting this define will allow using a query from a different +// thread than its database connection. +// warning - this is not fully tested and can lead to race conditions +#define QOCI_THREADED + +//#define QOCI_DEBUG + +Q_DECLARE_OPAQUE_POINTER(OCIEnv*); +Q_DECLARE_METATYPE(OCIEnv*) +Q_DECLARE_OPAQUE_POINTER(OCIStmt*); +Q_DECLARE_METATYPE(OCIStmt*) + +QT_BEGIN_NAMESPACE + +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +enum { QOCIEncoding = 2002 }; // AL16UTF16LE +#else +enum { QOCIEncoding = 2000 }; // AL16UTF16 +#endif + +#ifdef OCI_ATTR_CHARSET_FORM +// Always set the OCI_ATTR_CHARSET_FORM to SQLCS_NCHAR is safe +// because Oracle server will deal with the implicit Conversion +// Between CHAR and NCHAR. +// see: http://download.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89857/oci05bnd.htm#422705 +static const ub1 qOraCharsetForm = SQLCS_NCHAR; +#endif + +#if defined (OCI_UTF16ID) +static const ub2 qOraCharset = OCI_UTF16ID; +#else +static const ub2 qOraCharset = OCI_UCS2ID; +#endif + +typedef QVarLengthArray<sb2, 32> IndicatorArray; +typedef QVarLengthArray<ub2, 32> SizeArray; + +static QByteArray qMakeOraDate(const QDateTime& dt); +static QDateTime qMakeDate(const char* oraDate); + +static QByteArray qMakeOCINumber(const qlonglong &ll, OCIError *err); +static QByteArray qMakeOCINumber(const qulonglong& ull, OCIError* err); + +static qlonglong qMakeLongLong(const char* ociNumber, OCIError* err); +static qulonglong qMakeULongLong(const char* ociNumber, OCIError* err); + +static QString qOraWarn(OCIError *err, int *errorCode = 0); + +#ifndef Q_CC_SUN +static // for some reason, Sun CC can't use qOraWarning when it's declared static +#endif +void qOraWarning(const char* msg, OCIError *err); +static QSqlError qMakeError(const QString& errString, QSqlError::ErrorType type, OCIError *err); + + + +class QOCIRowId: public QSharedData +{ +public: + QOCIRowId(OCIEnv *env); + ~QOCIRowId(); + + OCIRowid *id; + +private: + QOCIRowId(const QOCIRowId &other): QSharedData(other) { Q_ASSERT(false); } +}; + +QOCIRowId::QOCIRowId(OCIEnv *env) + : id(0) +{ + OCIDescriptorAlloc (env, reinterpret_cast<dvoid **>(&id), + OCI_DTYPE_ROWID, 0, 0); +} + +QOCIRowId::~QOCIRowId() +{ + if (id) + OCIDescriptorFree(id, OCI_DTYPE_ROWID); +} + +typedef QSharedDataPointer<QOCIRowId> QOCIRowIdPointer; +QT_BEGIN_INCLUDE_NAMESPACE +Q_DECLARE_METATYPE(QOCIRowIdPointer) +QT_END_INCLUDE_NAMESPACE + +class QOCIDriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QOCIDriver) + +public: + QOCIDriverPrivate(); + + OCIEnv *env; + OCISvcCtx *svc; + OCIServer *srvhp; + OCISession *authp; + OCIError *err; + bool transaction; + int serverVersion; + int prefetchRows; + int prefetchMem; + QString user; + + void allocErrorHandle(); +}; + +class QOCICols; +class QOCIResultPrivate; + +class QOCIResult: public QSqlCachedResult +{ + Q_DECLARE_PRIVATE(QOCIResult) + friend class QOCIDriver; + friend class QOCICols; +public: + QOCIResult(const QOCIDriver *db); + ~QOCIResult(); + bool prepare(const QString &query) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + bool gotoNext(ValueCache &values, int index) Q_DECL_OVERRIDE; + bool reset(const QString &query) Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + QVariant lastInsertId() const Q_DECL_OVERRIDE; + bool execBatch(bool arrayBind = false) Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; +}; + +class QOCIResultPrivate: public QSqlCachedResultPrivate +{ +public: + Q_DECLARE_PUBLIC(QOCIResult) + Q_DECLARE_SQLDRIVER_PRIVATE(QOCIDriver) + QOCIResultPrivate(QOCIResult *q, const QOCIDriver *drv); + ~QOCIResultPrivate(); + + QOCICols *cols; + OCIEnv *env; + OCIError *err; + OCISvcCtx *&svc; + OCIStmt *sql; + bool transaction; + int serverVersion; + int prefetchRows, prefetchMem; + + void setStatementAttributes(); + int bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, int pos, + const QVariant &val, dvoid *indPtr, ub2 *tmpSize, QList<QByteArray> &tmpStorage); + int bindValues(QVector<QVariant> &values, IndicatorArray &indicators, SizeArray &tmpSizes, + QList<QByteArray> &tmpStorage); + void outValues(QVector<QVariant> &values, IndicatorArray &indicators, + QList<QByteArray> &tmpStorage); + inline bool isOutValue(int i) const + { Q_Q(const QOCIResult); return q->bindValueType(i) & QSql::Out; } + inline bool isBinaryValue(int i) const + { Q_Q(const QOCIResult); return q->bindValueType(i) & QSql::Binary; } + + void setCharset(dvoid* handle, ub4 type) const + { + int r = 0; + Q_ASSERT(handle); + +#ifdef OCI_ATTR_CHARSET_FORM + r = OCIAttrSet(handle, + type, + // this const cast is safe since OCI doesn't touch + // the charset. + const_cast<void *>(static_cast<const void *>(&qOraCharsetForm)), + 0, + OCI_ATTR_CHARSET_FORM, + //Strange Oracle bug: some Oracle servers crash the server process with non-zero error handle (mostly for 10g). + //So ignore the error message here. + 0); + #ifdef QOCI_DEBUG + if (r != 0) + qWarning("QOCIResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_FORM."); + #endif +#endif + + r = OCIAttrSet(handle, + type, + // this const cast is safe since OCI doesn't touch + // the charset. + const_cast<void *>(static_cast<const void *>(&qOraCharset)), + 0, + OCI_ATTR_CHARSET_ID, + err); + if (r != 0) + qOraWarning("QOCIResultPrivate::setCharsetI Couldn't set OCI_ATTR_CHARSET_ID: ", err); + + } +}; + +void QOCIResultPrivate::setStatementAttributes() +{ + Q_ASSERT(sql); + + int r = 0; + + if (prefetchRows >= 0) { + r = OCIAttrSet(sql, + OCI_HTYPE_STMT, + &prefetchRows, + 0, + OCI_ATTR_PREFETCH_ROWS, + err); + if (r != 0) + qOraWarning("QOCIResultPrivate::setStatementAttributes:" + " Couldn't set OCI_ATTR_PREFETCH_ROWS: ", err); + } + if (prefetchMem >= 0) { + r = OCIAttrSet(sql, + OCI_HTYPE_STMT, + &prefetchMem, + 0, + OCI_ATTR_PREFETCH_MEMORY, + err); + if (r != 0) + qOraWarning("QOCIResultPrivate::setStatementAttributes:" + " Couldn't set OCI_ATTR_PREFETCH_MEMORY: ", err); + } +} + +int QOCIResultPrivate::bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, int pos, + const QVariant &val, dvoid *indPtr, ub2 *tmpSize, QList<QByteArray> &tmpStorage) +{ + int r = OCI_SUCCESS; + void *data = const_cast<void *>(val.constData()); + + switch (val.type()) { + case QVariant::ByteArray: + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + isOutValue(pos) + ? const_cast<char *>(reinterpret_cast<QByteArray *>(data)->constData()) + : reinterpret_cast<QByteArray *>(data)->data(), + reinterpret_cast<QByteArray *>(data)->size(), + SQLT_BIN, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + break; + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime: { + QByteArray ba = qMakeOraDate(val.toDateTime()); + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_DAT, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + tmpStorage.append(ba); + break; } + case QVariant::Int: + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + // if it's an out value, the data is already detached + // so the const cast is safe. + const_cast<void *>(data), + sizeof(int), + SQLT_INT, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + break; + case QVariant::UInt: + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + // if it's an out value, the data is already detached + // so the const cast is safe. + const_cast<void *>(data), + sizeof(uint), + SQLT_UIN, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + break; + case QVariant::LongLong: + { + QByteArray ba = qMakeOCINumber(val.toLongLong(), err); + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_VNU, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + tmpStorage.append(ba); + break; + } + case QVariant::ULongLong: + { + QByteArray ba = qMakeOCINumber(val.toULongLong(), err); + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_VNU, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + tmpStorage.append(ba); + break; + } + case QVariant::Double: + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + // if it's an out value, the data is already detached + // so the const cast is safe. + const_cast<void *>(data), + sizeof(double), + SQLT_FLT, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + break; + case QVariant::UserType: + if (val.canConvert<QOCIRowIdPointer>() && !isOutValue(pos)) { + // use a const pointer to prevent a detach + const QOCIRowIdPointer rptr = qvariant_cast<QOCIRowIdPointer>(val); + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + // it's an IN value, so const_cast is ok + const_cast<OCIRowid **>(&rptr->id), + -1, + SQLT_RDD, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + } else { + qWarning("Unknown bind variable"); + r = OCI_ERROR; + } + break; + case QVariant::String: { + const QString s = val.toString(); + if (isBinaryValue(pos)) { + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + const_cast<ushort *>(s.utf16()), + s.length() * sizeof(QChar), + SQLT_LNG, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + break; + } else if (!isOutValue(pos)) { + // don't detach the string + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + // safe since oracle doesn't touch OUT values + const_cast<ushort *>(s.utf16()), + (s.length() + 1) * sizeof(QChar), + SQLT_STR, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + if (r == OCI_SUCCESS) + setCharset(*hbnd, OCI_HTYPE_BIND); + break; + } + } // fall through for OUT values + default: { + const QString s = val.toString(); + // create a deep-copy + QByteArray ba(reinterpret_cast<const char *>(s.utf16()), (s.length() + 1) * sizeof(QChar)); + if (isOutValue(pos)) { + ba.reserve((s.capacity() + 1) * sizeof(QChar)); + *tmpSize = ba.size(); + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + ba.data(), + ba.capacity(), + SQLT_STR, indPtr, tmpSize, 0, 0, 0, OCI_DEFAULT); + } else { + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_STR, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + } + if (r == OCI_SUCCESS) + setCharset(*hbnd, OCI_HTYPE_BIND); + tmpStorage.append(ba); + break; + } // default case + } // switch + if (r != OCI_SUCCESS) + qOraWarning("QOCIResultPrivate::bindValue:", err); + return r; +} + +int QOCIResultPrivate::bindValues(QVector<QVariant> &values, IndicatorArray &indicators, + SizeArray &tmpSizes, QList<QByteArray> &tmpStorage) +{ + int r = OCI_SUCCESS; + for (int i = 0; i < values.count(); ++i) { + if (isOutValue(i)) + values[i].detach(); + const QVariant &val = values.at(i); + + OCIBind * hbnd = 0; // Oracle handles these automatically + sb2 *indPtr = &indicators[i]; + *indPtr = val.isNull() ? -1 : 0; + + bindValue(sql, &hbnd, err, i, val, indPtr, &tmpSizes[i], tmpStorage); + } + return r; +} + +// will assign out value and remove its temp storage. +static void qOraOutValue(QVariant &value, QList<QByteArray> &storage, OCIError* err) +{ + switch (value.type()) { + case QVariant::Time: + value = qMakeDate(storage.takeFirst()).time(); + break; + case QVariant::Date: + value = qMakeDate(storage.takeFirst()).date(); + break; + case QVariant::DateTime: + value = qMakeDate(storage.takeFirst()); + break; + case QVariant::LongLong: + value = qMakeLongLong(storage.takeFirst(), err); + break; + case QVariant::ULongLong: + value = qMakeULongLong(storage.takeFirst(), err); + break; + case QVariant::String: + value = QString( + reinterpret_cast<const QChar *>(storage.takeFirst().constData())); + break; + default: + break; //nothing + } +} + +void QOCIResultPrivate::outValues(QVector<QVariant> &values, IndicatorArray &indicators, + QList<QByteArray> &tmpStorage) +{ + for (int i = 0; i < values.count(); ++i) { + + if (!isOutValue(i)) + continue; + + qOraOutValue(values[i], tmpStorage, err); + + QVariant::Type typ = values.at(i).type(); + if (indicators[i] == -1) // NULL + values[i] = QVariant(typ); + else + values[i] = QVariant(typ, values.at(i).constData()); + } +} + + +QOCIDriverPrivate::QOCIDriverPrivate() + : QSqlDriverPrivate(), env(0), svc(0), srvhp(0), authp(0), err(0), transaction(false), + serverVersion(-1), prefetchRows(-1), prefetchMem(QOCI_PREFETCH_MEM) +{ + dbmsType = QSqlDriver::Oracle; +} + +void QOCIDriverPrivate::allocErrorHandle() +{ + int r = OCIHandleAlloc(env, + reinterpret_cast<void **>(&err), + OCI_HTYPE_ERROR, + 0, + 0); + if (r != 0) + qWarning("QOCIDriver: unable to allocate error handle"); +} + +struct OraFieldInfo +{ + QString name; + QVariant::Type type; + ub1 oraIsNull; + ub4 oraType; + sb1 oraScale; + ub4 oraLength; // size in bytes + ub4 oraFieldLength; // amount of characters + sb2 oraPrecision; +}; + +QString qOraWarn(OCIError *err, int *errorCode) +{ + sb4 errcode; + text errbuf[1024]; + errbuf[0] = 0; + errbuf[1] = 0; + + OCIErrorGet(err, + 1, + 0, + &errcode, + errbuf, + sizeof(errbuf), + OCI_HTYPE_ERROR); + if (errorCode) + *errorCode = errcode; + return QString(reinterpret_cast<const QChar *>(errbuf)); +} + +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 +} + +static int qOraErrorNumber(OCIError *err) +{ + sb4 errcode; + OCIErrorGet(err, + 1, + 0, + &errcode, + 0, + 0, + OCI_HTYPE_ERROR); + return errcode; +} + +QSqlError qMakeError(const QString& errString, QSqlError::ErrorType type, OCIError *err) +{ + int errorCode = 0; + const QString oraErrorString = qOraWarn(err, &errorCode); + return QSqlError(errString, oraErrorString, type, errorCode); +} + +QVariant::Type qDecodeOCIType(const QString& ocitype, QSql::NumericalPrecisionPolicy precisionPolicy) +{ + QVariant::Type type = QVariant::Invalid; + if (ocitype == QLatin1String("VARCHAR2") || ocitype == QLatin1String("VARCHAR") + || ocitype.startsWith(QLatin1String("INTERVAL")) + || ocitype == QLatin1String("CHAR") || ocitype == QLatin1String("NVARCHAR2") + || ocitype == QLatin1String("NCHAR")) + type = QVariant::String; + else if (ocitype == QLatin1String("NUMBER") + || ocitype == QLatin1String("FLOAT") + || ocitype == QLatin1String("BINARY_FLOAT") + || ocitype == QLatin1String("BINARY_DOUBLE")) { + switch(precisionPolicy) { + case QSql::LowPrecisionInt32: + type = QVariant::Int; + break; + case QSql::LowPrecisionInt64: + type = QVariant::LongLong; + break; + case QSql::LowPrecisionDouble: + type = QVariant::Double; + break; + case QSql::HighPrecision: + default: + type = QVariant::String; + break; + } + } + else if (ocitype == QLatin1String("LONG") || ocitype == QLatin1String("NCLOB") + || ocitype == QLatin1String("CLOB")) + type = QVariant::ByteArray; + else if (ocitype == QLatin1String("RAW") || ocitype == QLatin1String("LONG RAW") + || ocitype == QLatin1String("ROWID") || ocitype == QLatin1String("BLOB") + || ocitype == QLatin1String("CFILE") || ocitype == QLatin1String("BFILE")) + type = QVariant::ByteArray; + else if (ocitype == QLatin1String("DATE") || ocitype.startsWith(QLatin1String("TIME"))) + type = QVariant::DateTime; + else if (ocitype == QLatin1String("UNDEFINED")) + type = QVariant::Invalid; + if (type == QVariant::Invalid) + qWarning("qDecodeOCIType: unknown type: %s", ocitype.toLocal8Bit().constData()); + return type; +} + +QVariant::Type qDecodeOCIType(int ocitype, QSql::NumericalPrecisionPolicy precisionPolicy) +{ + QVariant::Type type = QVariant::Invalid; + switch (ocitype) { + case SQLT_STR: + case SQLT_VST: + case SQLT_CHR: + case SQLT_AFC: + case SQLT_VCS: + case SQLT_AVC: + case SQLT_RDD: + case SQLT_LNG: +#ifdef SQLT_INTERVAL_YM + case SQLT_INTERVAL_YM: +#endif +#ifdef SQLT_INTERVAL_DS + case SQLT_INTERVAL_DS: +#endif + type = QVariant::String; + break; + case SQLT_INT: + type = QVariant::Int; + break; + case SQLT_FLT: + case SQLT_NUM: + case SQLT_VNU: + case SQLT_UIN: + switch(precisionPolicy) { + case QSql::LowPrecisionInt32: + type = QVariant::Int; + break; + case QSql::LowPrecisionInt64: + type = QVariant::LongLong; + break; + case QSql::LowPrecisionDouble: + type = QVariant::Double; + break; + case QSql::HighPrecision: + default: + type = QVariant::String; + break; + } + break; + case SQLT_VBI: + case SQLT_BIN: + case SQLT_LBI: + case SQLT_LVC: + case SQLT_LVB: + case SQLT_BLOB: + case SQLT_CLOB: + case SQLT_FILE: + case SQLT_NTY: + case SQLT_REF: + case SQLT_RID: + type = QVariant::ByteArray; + break; + case SQLT_DAT: + case SQLT_ODT: +#ifdef SQLT_TIMESTAMP + case SQLT_TIMESTAMP: + case SQLT_TIMESTAMP_TZ: + case SQLT_TIMESTAMP_LTZ: +#endif + type = QVariant::DateTime; + break; + default: + type = QVariant::Invalid; + qWarning("qDecodeOCIType: unknown OCI datatype: %d", ocitype); + break; + } + return type; +} + +static QSqlField qFromOraInf(const OraFieldInfo &ofi) +{ + QSqlField f(ofi.name, ofi.type); + f.setRequired(ofi.oraIsNull == 0); + + if (ofi.type == QVariant::String && ofi.oraType != SQLT_NUM && ofi.oraType != SQLT_VNU) + f.setLength(ofi.oraFieldLength); + else + f.setLength(ofi.oraPrecision == 0 ? 38 : int(ofi.oraPrecision)); + + f.setPrecision(ofi.oraScale); + f.setSqlType(int(ofi.oraType)); + return f; +} + +/*! + \internal + + Convert QDateTime to the internal Oracle DATE format NB! + It does not handle BCE dates. +*/ +QByteArray qMakeOraDate(const QDateTime& dt) +{ + QByteArray ba; + ba.resize(7); + int year = dt.date().year(); + ba[0]= (year / 100) + 100; // century + ba[1]= (year % 100) + 100; // year + ba[2]= dt.date().month(); + ba[3]= dt.date().day(); + ba[4]= dt.time().hour() + 1; + ba[5]= dt.time().minute() + 1; + ba[6]= dt.time().second() + 1; + return ba; +} + +/*! + \internal + + Convert qlonglong to the internal Oracle OCINumber format. + */ +QByteArray qMakeOCINumber(const qlonglong& ll, OCIError* err) +{ + QByteArray ba(sizeof(OCINumber), 0); + + OCINumberFromInt(err, + &ll, + sizeof(qlonglong), + OCI_NUMBER_SIGNED, + reinterpret_cast<OCINumber*>(ba.data())); + return ba; +} + +/*! + \internal + + Convert qulonglong to the internal Oracle OCINumber format. + */ +QByteArray qMakeOCINumber(const qulonglong& ull, OCIError* err) +{ + QByteArray ba(sizeof(OCINumber), 0); + + OCINumberFromInt(err, + &ull, + sizeof(qlonglong), + OCI_NUMBER_UNSIGNED, + reinterpret_cast<OCINumber*>(ba.data())); + return ba; +} + +qlonglong qMakeLongLong(const char* ociNumber, OCIError* err) +{ + qlonglong qll = 0; + OCINumberToInt(err, reinterpret_cast<const OCINumber *>(ociNumber), sizeof(qlonglong), + OCI_NUMBER_SIGNED, &qll); + return qll; +} + +qulonglong qMakeULongLong(const char* ociNumber, OCIError* err) +{ + qulonglong qull = 0; + OCINumberToInt(err, reinterpret_cast<const OCINumber *>(ociNumber), sizeof(qulonglong), + OCI_NUMBER_UNSIGNED, &qull); + return qull; +} + +QDateTime qMakeDate(const char* oraDate) +{ + int century = uchar(oraDate[0]); + if(century >= 100){ + int year = uchar(oraDate[1]); + year = ((century-100)*100) + (year-100); + int month = oraDate[2]; + int day = oraDate[3]; + int hour = oraDate[4] - 1; + int min = oraDate[5] - 1; + int sec = oraDate[6] - 1; + return QDateTime(QDate(year,month,day), QTime(hour,min,sec)); + } + return QDateTime(); +} + +class QOCICols +{ +public: + QOCICols(int size, QOCIResultPrivate* dp); + ~QOCICols(); + int readPiecewise(QVector<QVariant> &values, int index = 0); + int readLOBs(QVector<QVariant> &values, int index = 0); + int fieldFromDefine(OCIDefine* d); + void getValues(QVector<QVariant> &v, int index); + inline int size() { return fieldInf.size(); } + static bool execBatch(QOCIResultPrivate *d, QVector<QVariant> &boundValues, bool arrayBind); + + QSqlRecord rec; + +private: + char* create(int position, int size); + OCILobLocator ** createLobLocator(int position, OCIEnv* env); + OraFieldInfo qMakeOraField(const QOCIResultPrivate* p, OCIParam* param) const; + + class OraFieldInf + { + public: + OraFieldInf(): data(0), len(0), ind(0), typ(QVariant::Invalid), oraType(0), def(0), lob(0) + {} + ~OraFieldInf(); + char *data; + int len; + sb2 ind; + QVariant::Type typ; + ub4 oraType; + OCIDefine *def; + OCILobLocator *lob; + }; + + QVector<OraFieldInf> fieldInf; + const QOCIResultPrivate *const d; +}; + +QOCICols::OraFieldInf::~OraFieldInf() +{ + delete [] data; + if (lob) { + int r = OCIDescriptorFree(lob, OCI_DTYPE_LOB); + if (r != 0) + qWarning("QOCICols: Cannot free LOB descriptor"); + } +} + +QOCICols::QOCICols(int size, QOCIResultPrivate* dp) + : fieldInf(size), d(dp) +{ + ub4 dataSize = 0; + OCIDefine* dfn = 0; + int r; + + OCIParam* param = 0; + sb4 parmStatus = 0; + ub4 count = 1; + int idx = 0; + parmStatus = OCIParamGet(d->sql, + OCI_HTYPE_STMT, + d->err, + reinterpret_cast<void **>(¶m), + count); + + while (parmStatus == OCI_SUCCESS) { + OraFieldInfo ofi = qMakeOraField(d, param); + if (ofi.oraType == SQLT_RDD) + dataSize = 50; +#ifdef SQLT_INTERVAL_YM +#ifdef SQLT_INTERVAL_DS + else if (ofi.oraType == SQLT_INTERVAL_YM || ofi.oraType == SQLT_INTERVAL_DS) + // since we are binding interval datatype as string, + // we are not interested in the number of bytes but characters. + dataSize = 50; // magic number +#endif //SQLT_INTERVAL_DS +#endif //SQLT_INTERVAL_YM + else if (ofi.oraType == SQLT_NUM || ofi.oraType == SQLT_VNU){ + if (ofi.oraPrecision > 0) + dataSize = (ofi.oraPrecision + 1) * sizeof(utext); + else + dataSize = (38 + 1) * sizeof(utext); + } + else + dataSize = ofi.oraLength; + + fieldInf[idx].typ = ofi.type; + fieldInf[idx].oraType = ofi.oraType; + rec.append(qFromOraInf(ofi)); + + switch (ofi.type) { + case QVariant::DateTime: + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, dataSize+1), + dataSize+1, + SQLT_DAT, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + break; + case QVariant::Double: + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, sizeof(double) - 1), + sizeof(double), + SQLT_FLT, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + break; + case QVariant::Int: + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, sizeof(qint32) - 1), + sizeof(qint32), + SQLT_INT, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + break; + case QVariant::LongLong: + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, sizeof(OCINumber)), + sizeof(OCINumber), + SQLT_VNU, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + break; + case QVariant::ByteArray: + // RAW and LONG RAW fields can't be bound to LOB locators + if (ofi.oraType == SQLT_BIN) { +// qDebug("binding SQLT_BIN"); + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, dataSize), + dataSize, + SQLT_BIN, + &(fieldInf[idx].ind), + 0, 0, OCI_DYNAMIC_FETCH); + } else if (ofi.oraType == SQLT_LBI) { +// qDebug("binding SQLT_LBI"); + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + 0, + SB4MAXVAL, + SQLT_LBI, + &(fieldInf[idx].ind), + 0, 0, OCI_DYNAMIC_FETCH); + } else if (ofi.oraType == SQLT_CLOB) { + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + createLobLocator(idx, d->env), + -1, + SQLT_CLOB, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + } else { +// qDebug("binding SQLT_BLOB"); + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + createLobLocator(idx, d->env), + -1, + SQLT_BLOB, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + } + break; + case QVariant::String: + if (ofi.oraType == SQLT_LNG) { + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + 0, + SB4MAXVAL, + SQLT_LNG, + &(fieldInf[idx].ind), + 0, 0, OCI_DYNAMIC_FETCH); + } else { + dataSize += dataSize + sizeof(QChar); + //qDebug("OCIDefineByPosStr(%d): %d", count, dataSize); + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, dataSize), + dataSize, + SQLT_STR, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + if (r == 0) + d->setCharset(dfn, OCI_HTYPE_DEFINE); + } + break; + default: + // this should make enough space even with character encoding + dataSize = (dataSize + 1) * sizeof(utext) ; + //qDebug("OCIDefineByPosDef(%d): %d", count, dataSize); + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, dataSize), + dataSize+1, + SQLT_STR, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + break; + } + if (r != 0) + qOraWarning("QOCICols::bind:", d->err); + fieldInf[idx].def = dfn; + ++count; + ++idx; + parmStatus = OCIParamGet(d->sql, + OCI_HTYPE_STMT, + d->err, + reinterpret_cast<void **>(¶m), + count); + } +} + +QOCICols::~QOCICols() +{ +} + +char* QOCICols::create(int position, int size) +{ + char* c = new char[size+1]; + // Oracle may not fill fixed width fields + memset(c, 0, size+1); + fieldInf[position].data = c; + fieldInf[position].len = size; + return c; +} + +OCILobLocator **QOCICols::createLobLocator(int position, OCIEnv* env) +{ + OCILobLocator *& lob = fieldInf[position].lob; + int r = OCIDescriptorAlloc(env, + reinterpret_cast<void **>(&lob), + OCI_DTYPE_LOB, + 0, + 0); + if (r != 0) { + qWarning("QOCICols: Cannot create LOB locator"); + lob = 0; + } + return &lob; +} + +int QOCICols::readPiecewise(QVector<QVariant> &values, int index) +{ + OCIDefine* dfn; + ub4 typep; + ub1 in_outp; + ub4 iterp; + ub4 idxp; + ub1 piecep; + sword status; + text col [QOCI_DYNAMIC_CHUNK_SIZE+1]; + int fieldNum = -1; + int r = 0; + bool nullField; + + do { + r = OCIStmtGetPieceInfo(d->sql, d->err, reinterpret_cast<void **>(&dfn), &typep, + &in_outp, &iterp, &idxp, &piecep); + if (r != OCI_SUCCESS) + qOraWarning("OCIResultPrivate::readPiecewise: unable to get piece info:", d->err); + fieldNum = fieldFromDefine(dfn); + bool isStringField = fieldInf.at(fieldNum).oraType == SQLT_LNG; + ub4 chunkSize = QOCI_DYNAMIC_CHUNK_SIZE; + nullField = false; + r = OCIStmtSetPieceInfo(dfn, OCI_HTYPE_DEFINE, + d->err, col, + &chunkSize, piecep, NULL, NULL); + if (r != OCI_SUCCESS) + qOraWarning("OCIResultPrivate::readPiecewise: unable to set piece info:", d->err); + status = OCIStmtFetch (d->sql, d->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT); + if (status == -1) { + sb4 errcode; + OCIErrorGet(d->err, 1, 0, &errcode, 0, 0,OCI_HTYPE_ERROR); + switch (errcode) { + case 1405: /* NULL */ + nullField = true; + break; + default: + qOraWarning("OCIResultPrivate::readPiecewise: unable to fetch next:", d->err); + break; + } + } + if (status == OCI_NO_DATA) + break; + if (nullField || !chunkSize) { + fieldInf[fieldNum].ind = -1; + } else { + if (isStringField) { + QString str = values.at(fieldNum + index).toString(); + str += QString(reinterpret_cast<const QChar *>(col), chunkSize / 2); + values[fieldNum + index] = str; + fieldInf[fieldNum].ind = 0; + } else { + QByteArray ba = values.at(fieldNum + index).toByteArray(); + int sz = ba.size(); + ba.resize(sz + chunkSize); + memcpy(ba.data() + sz, reinterpret_cast<char *>(col), chunkSize); + values[fieldNum + index] = ba; + fieldInf[fieldNum].ind = 0; + } + } + } while (status == OCI_SUCCESS_WITH_INFO || status == OCI_NEED_DATA); + return r; +} + +OraFieldInfo QOCICols::qMakeOraField(const QOCIResultPrivate* p, OCIParam* param) const +{ + OraFieldInfo ofi; + ub2 colType(0); + text *colName = 0; + ub4 colNameLen(0); + sb1 colScale(0); + ub2 colLength(0); + ub2 colFieldLength(0); + sb2 colPrecision(0); + ub1 colIsNull(0); + int r(0); + QVariant::Type type(QVariant::Invalid); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colType, + 0, + OCI_ATTR_DATA_TYPE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colName, + &colNameLen, + OCI_ATTR_NAME, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colLength, + 0, + OCI_ATTR_DATA_SIZE, /* in bytes */ + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + +#ifdef OCI_ATTR_CHAR_SIZE + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colFieldLength, + 0, + OCI_ATTR_CHAR_SIZE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); +#else + // for Oracle8. + colFieldLength = colLength; +#endif + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colPrecision, + 0, + OCI_ATTR_PRECISION, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colScale, + 0, + OCI_ATTR_SCALE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colType, + 0, + OCI_ATTR_DATA_TYPE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colIsNull, + 0, + OCI_ATTR_IS_NULL, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + type = qDecodeOCIType(colType, p->q_func()->numericalPrecisionPolicy()); + + if (type == QVariant::Int) { + if (colLength == 22 && colPrecision == 0 && colScale == 0) + type = QVariant::String; + if (colScale > 0) + type = QVariant::String; + } + + // bind as double if the precision policy asks for it + if (((colType == SQLT_FLT) || (colType == SQLT_NUM)) + && (p->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionDouble)) { + type = QVariant::Double; + } + + // bind as int32 or int64 if the precision policy asks for it + if ((colType == SQLT_NUM) || (colType == SQLT_VNU) || (colType == SQLT_UIN) + || (colType == SQLT_INT)) { + if (p->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt64) + type = QVariant::LongLong; + else if (p->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt32) + type = QVariant::Int; + } + + if (colType == SQLT_BLOB) + colLength = 0; + + // colNameLen is length in bytes + ofi.name = QString(reinterpret_cast<const QChar*>(colName), colNameLen / 2); + ofi.type = type; + ofi.oraType = colType; + ofi.oraFieldLength = colFieldLength; + ofi.oraLength = colLength; + ofi.oraScale = colScale; + ofi.oraPrecision = colPrecision; + ofi.oraIsNull = colIsNull; + + return ofi; +} + +struct QOCIBatchColumn +{ + inline QOCIBatchColumn() + : bindh(0), bindAs(0), maxLen(0), recordCount(0), + data(0), lengths(0), indicators(0), maxarr_len(0), curelep(0) {} + + OCIBind* bindh; + ub2 bindAs; + ub4 maxLen; + ub4 recordCount; + char* data; + ub2* lengths; + sb2* indicators; + ub4 maxarr_len; + ub4 curelep; +}; + +struct QOCIBatchCleanupHandler +{ + inline QOCIBatchCleanupHandler(QVector<QOCIBatchColumn> &columns) + : col(columns) {} + + ~QOCIBatchCleanupHandler() + { + // deleting storage, length and indicator arrays + for ( int j = 0; j < col.count(); ++j){ + delete[] col[j].lengths; + delete[] col[j].indicators; + delete[] col[j].data; + } + } + + QVector<QOCIBatchColumn> &col; +}; + +bool QOCICols::execBatch(QOCIResultPrivate *d, QVector<QVariant> &boundValues, bool arrayBind) +{ + int columnCount = boundValues.count(); + if (boundValues.isEmpty() || columnCount == 0) + return false; + +#ifdef QOCI_DEBUG + qDebug() << "columnCount:" << columnCount << boundValues; +#endif + + int i; + sword r; + + QVarLengthArray<QVariant::Type> fieldTypes; + for (i = 0; i < columnCount; ++i) { + QVariant::Type tp = boundValues.at(i).type(); + fieldTypes.append(tp == QVariant::List ? boundValues.at(i).toList().value(0).type() + : tp); + } + + QList<QByteArray> tmpStorage; + SizeArray tmpSizes(columnCount); + QVector<QOCIBatchColumn> columns(columnCount); + QOCIBatchCleanupHandler cleaner(columns); + + // figuring out buffer sizes + for (i = 0; i < columnCount; ++i) { + + if (boundValues.at(i).type() != QVariant::List) { + + // not a list - create a deep-copy of the single value + QOCIBatchColumn &singleCol = columns[i]; + singleCol.indicators = new sb2[1]; + *singleCol.indicators = boundValues.at(i).isNull() ? -1 : 0; + + r = d->bindValue(d->sql, &singleCol.bindh, d->err, i, + boundValues.at(i), singleCol.indicators, &tmpSizes[i], tmpStorage); + + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err); + d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to bind column for batch execute"), + QSqlError::StatementError, d->err)); + return false; + } + continue; + } + + QOCIBatchColumn &col = columns[i]; + col.recordCount = boundValues.at(i).toList().count(); + + col.lengths = new ub2[col.recordCount]; + col.indicators = new sb2[col.recordCount]; + col.maxarr_len = col.recordCount; + col.curelep = col.recordCount; + + switch (fieldTypes[i]) { + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime: + col.bindAs = SQLT_DAT; + col.maxLen = 7; + break; + + case QVariant::Int: + col.bindAs = SQLT_INT; + col.maxLen = sizeof(int); + break; + + case QVariant::UInt: + col.bindAs = SQLT_UIN; + col.maxLen = sizeof(uint); + break; + + case QVariant::LongLong: + col.bindAs = SQLT_VNU; + col.maxLen = sizeof(OCINumber); + break; + + case QVariant::ULongLong: + col.bindAs = SQLT_VNU; + col.maxLen = sizeof(OCINumber); + break; + + case QVariant::Double: + col.bindAs = SQLT_FLT; + col.maxLen = sizeof(double); + break; + + case QVariant::UserType: + col.bindAs = SQLT_RDD; + col.maxLen = sizeof(OCIRowid*); + break; + + case QVariant::String: { + col.bindAs = SQLT_STR; + for (uint j = 0; j < col.recordCount; ++j) { + uint len; + if(d->isOutValue(i)) + len = boundValues.at(i).toList().at(j).toString().capacity() + 1; + else + len = boundValues.at(i).toList().at(j).toString().length() + 1; + if (len > col.maxLen) + col.maxLen = len; + } + col.maxLen *= sizeof(QChar); + break; } + + case QVariant::ByteArray: + default: { + col.bindAs = SQLT_LBI; + for (uint j = 0; j < col.recordCount; ++j) { + if(d->isOutValue(i)) + col.lengths[j] = boundValues.at(i).toList().at(j).toByteArray().capacity(); + else + col.lengths[j] = boundValues.at(i).toList().at(j).toByteArray().size(); + if (col.lengths[j] > col.maxLen) + col.maxLen = col.lengths[j]; + } + break; } + } + + col.data = new char[col.maxLen * col.recordCount]; + memset(col.data, 0, col.maxLen * col.recordCount); + + // we may now populate column with data + for (uint row = 0; row < col.recordCount; ++row) { + const QVariant &val = boundValues.at(i).toList().at(row); + + if (val.isNull()){ + columns[i].indicators[row] = -1; + columns[i].lengths[row] = 0; + } else { + columns[i].indicators[row] = 0; + char *dataPtr = columns[i].data + (columns[i].maxLen * row); + switch (fieldTypes[i]) { + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime:{ + columns[i].lengths[row] = columns[i].maxLen; + const QByteArray ba = qMakeOraDate(val.toDateTime()); + Q_ASSERT(ba.size() == int(columns[i].maxLen)); + memcpy(dataPtr, ba.constData(), columns[i].maxLen); + break; + } + case QVariant::Int: + columns[i].lengths[row] = columns[i].maxLen; + *reinterpret_cast<int*>(dataPtr) = val.toInt(); + break; + + case QVariant::UInt: + columns[i].lengths[row] = columns[i].maxLen; + *reinterpret_cast<uint*>(dataPtr) = val.toUInt(); + break; + + case QVariant::LongLong: + { + columns[i].lengths[row] = columns[i].maxLen; + const QByteArray ba = qMakeOCINumber(val.toLongLong(), d->err); + Q_ASSERT(ba.size() == int(columns[i].maxLen)); + memcpy(dataPtr, ba.constData(), columns[i].maxLen); + break; + } + case QVariant::ULongLong: + { + columns[i].lengths[row] = columns[i].maxLen; + const QByteArray ba = qMakeOCINumber(val.toULongLong(), d->err); + Q_ASSERT(ba.size() == int(columns[i].maxLen)); + memcpy(dataPtr, ba.constData(), columns[i].maxLen); + break; + } + case QVariant::Double: + columns[i].lengths[row] = columns[i].maxLen; + *reinterpret_cast<double*>(dataPtr) = val.toDouble(); + break; + + case QVariant::String: { + const QString s = val.toString(); + columns[i].lengths[row] = (s.length() + 1) * sizeof(QChar); + memcpy(dataPtr, s.utf16(), columns[i].lengths[row]); + break; + } + case QVariant::UserType: + if (val.canConvert<QOCIRowIdPointer>()) { + const QOCIRowIdPointer rptr = qvariant_cast<QOCIRowIdPointer>(val); + *reinterpret_cast<OCIRowid**>(dataPtr) = rptr->id; + columns[i].lengths[row] = 0; + break; + } + case QVariant::ByteArray: + default: { + const QByteArray ba = val.toByteArray(); + columns[i].lengths[row] = ba.size(); + memcpy(dataPtr, ba.constData(), ba.size()); + break; + } + } + } + } + + QOCIBatchColumn &bindColumn = columns[i]; + +#ifdef QOCI_DEBUG + qDebug("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], + bindColumn.lengths[ii]); + } +#endif + + + // binding the column + r = OCIBindByPos( + d->sql, &bindColumn.bindh, d->err, i + 1, + bindColumn.data, + bindColumn.maxLen, + bindColumn.bindAs, + bindColumn.indicators, + bindColumn.lengths, + 0, + arrayBind ? bindColumn.maxarr_len : 0, + arrayBind ? &bindColumn.curelep : 0, + OCI_DEFAULT); + +#ifdef QOCI_DEBUG + qDebug("After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh); +#endif + + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err); + d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to bind column for batch execute"), + QSqlError::StatementError, d->err)); + return false; + } + + r = OCIBindArrayOfStruct ( + columns[i].bindh, d->err, + columns[i].maxLen, + sizeof(columns[i].indicators[0]), + sizeof(columns[i].lengths[0]), + 0); + + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err); + d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to bind column for batch execute"), + QSqlError::StatementError, d->err)); + return false; + } + } + + //finaly we can execute + r = OCIStmtExecute(d->svc, d->sql, d->err, + arrayBind ? 1 : columns[0].recordCount, + 0, NULL, NULL, + d->transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS); + + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIPrivate::execBatch: unable to execute batch statement:", d->err); + d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to execute batch statement"), + QSqlError::StatementError, d->err)); + return false; + } + + // for out parameters we copy data back to value vector + for (i = 0; i < columnCount; ++i) { + + if (!d->isOutValue(i)) + continue; + + QVariant::Type tp = boundValues.at(i).type(); + if (tp != QVariant::List) { + qOraOutValue(boundValues[i], tmpStorage, d->err); + if (*columns[i].indicators == -1) + boundValues[i] = QVariant(tp); + continue; + } + + QVariantList *list = static_cast<QVariantList *>(const_cast<void*>(boundValues.at(i).data())); + + char* data = columns[i].data; + for (uint r = 0; r < columns[i].recordCount; ++r){ + + if (columns[i].indicators[r] == -1) { + (*list)[r] = QVariant(); + continue; + } + + switch(columns[i].bindAs) { + + case SQLT_DAT: + (*list)[r] = qMakeDate(data + r * columns[i].maxLen); + break; + + case SQLT_INT: + (*list)[r] = *reinterpret_cast<int*>(data + r * columns[i].maxLen); + break; + + case SQLT_UIN: + (*list)[r] = *reinterpret_cast<uint*>(data + r * columns[i].maxLen); + break; + + case SQLT_VNU: + { + switch (boundValues.at(i).type()) { + case QVariant::LongLong: + (*list)[r] = qMakeLongLong(data + r * columns[i].maxLen, d->err); + break; + case QVariant::ULongLong: + (*list)[r] = qMakeULongLong(data + r * columns[i].maxLen, d->err); + break; + default: + break; + } + break; + } + + case SQLT_FLT: + (*list)[r] = *reinterpret_cast<double*>(data + r * columns[i].maxLen); + break; + + case SQLT_STR: + (*list)[r] = QString(reinterpret_cast<const QChar *>(data + + r * columns[i].maxLen)); + break; + + default: + (*list)[r] = QByteArray(data + r * columns[i].maxLen, columns[i].maxLen); + break; + } + } + } + + d->q_func()->setSelect(false); + d->q_func()->setAt(QSql::BeforeFirstRow); + d->q_func()->setActive(true); + + return true; +} + +template<class T, int sz> +int qReadLob(T &buf, const QOCIResultPrivate *d, OCILobLocator *lob) +{ + ub1 csfrm; + ub4 amount; + int r; + + // Read this from the database, don't assume we know what it is set to + r = OCILobCharSetForm(d->env, d->err, lob, &csfrm); + if (r != OCI_SUCCESS) { + qOraWarning("OCIResultPrivate::readLobs: Couldn't get LOB char set form: ", d->err); + csfrm = 0; + } + + // Get the length of the LOB (this is in characters) + r = OCILobGetLength(d->svc, d->err, lob, &amount); + if (r == OCI_SUCCESS) { + if (amount == 0) { + // Short cut for null LOBs + buf.resize(0); + return OCI_SUCCESS; + } + } else { + qOraWarning("OCIResultPrivate::readLobs: Couldn't get LOB length: ", d->err); + return r; + } + + // Resize the buffer to hold the LOB contents + buf.resize(amount); + + // Read the LOB into the buffer + r = OCILobRead(d->svc, + d->err, + lob, + &amount, + 1, + buf.data(), + buf.size() * sz, // this argument is in bytes, not characters + 0, + 0, + // Extract the data from a CLOB in UTF-16 (ie. what QString uses internally) + sz == 1 ? ub2(0) : ub2(QOCIEncoding), + csfrm); + + if (r != OCI_SUCCESS) + qOraWarning("OCIResultPrivate::readLOBs: Cannot read LOB: ", d->err); + + return r; +} + +int QOCICols::readLOBs(QVector<QVariant> &values, int index) +{ + OCILobLocator *lob; + int r = OCI_SUCCESS; + + for (int i = 0; i < size(); ++i) { + const OraFieldInf &fi = fieldInf.at(i); + if (fi.ind == -1 || !(lob = fi.lob)) + continue; + + bool isClob = fi.oraType == SQLT_CLOB; + QVariant var; + + if (isClob) { + QString str; + r = qReadLob<QString, sizeof(QChar)>(str, d, lob); + var = str; + } else { + QByteArray buf; + r = qReadLob<QByteArray, sizeof(char)>(buf, d, lob); + var = buf; + } + if (r == OCI_SUCCESS) + values[index + i] = var; + else + break; + } + return r; +} + +int QOCICols::fieldFromDefine(OCIDefine* d) +{ + for (int i = 0; i < fieldInf.count(); ++i) { + if (fieldInf.at(i).def == d) + return i; + } + return -1; +} + +void QOCICols::getValues(QVector<QVariant> &v, int index) +{ + for (int i = 0; i < fieldInf.size(); ++i) { + const OraFieldInf &fld = fieldInf.at(i); + + if (fld.ind == -1) { + // got a NULL value + v[index + i] = QVariant(fld.typ); + continue; + } + + if (fld.oraType == SQLT_BIN || fld.oraType == SQLT_LBI || fld.oraType == SQLT_LNG) + continue; // already fetched piecewise + + switch (fld.typ) { + case QVariant::DateTime: + v[index + i] = QVariant(qMakeDate(fld.data)); + break; + case QVariant::Double: + case QVariant::Int: + case QVariant::LongLong: + if (d->q_func()->numericalPrecisionPolicy() != QSql::HighPrecision) { + if ((d->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionDouble) + && (fld.typ == QVariant::Double)) { + v[index + i] = *reinterpret_cast<double *>(fld.data); + break; + } else if ((d->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt64) + && (fld.typ == QVariant::LongLong)) { + qint64 qll = 0; + int r = OCINumberToInt(d->err, reinterpret_cast<OCINumber *>(fld.data), sizeof(qint64), + OCI_NUMBER_SIGNED, &qll); + if(r == OCI_SUCCESS) + v[index + i] = qll; + else + v[index + i] = QVariant(); + break; + } else if ((d->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt32) + && (fld.typ == QVariant::Int)) { + v[index + i] = *reinterpret_cast<int *>(fld.data); + break; + } + } + // else fall through + case QVariant::String: + v[index + i] = QString(reinterpret_cast<const QChar *>(fld.data)); + break; + case QVariant::ByteArray: + if (fld.len > 0) + v[index + i] = QByteArray(fld.data, fld.len); + else + v[index + i] = QVariant(QVariant::ByteArray); + break; + default: + qWarning("QOCICols::value: unknown data type"); + break; + } + } +} + +QOCIResultPrivate::QOCIResultPrivate(QOCIResult *q, const QOCIDriver *drv) + : QSqlCachedResultPrivate(q, drv), + cols(0), + env(drv_d_func()->env), + err(0), + svc(const_cast<OCISvcCtx*&>(drv_d_func()->svc)), + sql(0), + transaction(drv_d_func()->transaction), + serverVersion(drv_d_func()->serverVersion), + prefetchRows(drv_d_func()->prefetchRows), + prefetchMem(drv_d_func()->prefetchMem) +{ + int r = OCIHandleAlloc(env, + reinterpret_cast<void **>(&err), + OCI_HTYPE_ERROR, + 0, + 0); + if (r != 0) + qWarning("QOCIResult: unable to alloc error handle"); +} + +QOCIResultPrivate::~QOCIResultPrivate() +{ + delete cols; + + int r = OCIHandleFree(err, OCI_HTYPE_ERROR); + if (r != 0) + qWarning("~QOCIResult: unable to free statement handle"); +} + + +//////////////////////////////////////////////////////////////////////////// + +QOCIResult::QOCIResult(const QOCIDriver *db) + : QSqlCachedResult(*new QOCIResultPrivate(this, db)) +{ +} + +QOCIResult::~QOCIResult() +{ + Q_D(QOCIResult); + if (d->sql) { + int r = OCIHandleFree(d->sql, OCI_HTYPE_STMT); + if (r != 0) + qWarning("~QOCIResult: unable to free statement handle"); + } +} + +QVariant QOCIResult::handle() const +{ + Q_D(const QOCIResult); + return QVariant::fromValue(d->sql); +} + +bool QOCIResult::reset (const QString& query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QOCIResult::gotoNext(QSqlCachedResult::ValueCache &values, int index) +{ + Q_D(QOCIResult); + if (at() == QSql::AfterLastRow) + return false; + + bool piecewise = false; + int r = OCI_SUCCESS; + r = OCIStmtFetch(d->sql, d->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT); + + if (index < 0) //not interested in values + return r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO; + + switch (r) { + case OCI_SUCCESS: + break; + case OCI_SUCCESS_WITH_INFO: + qOraWarning("QOCIResult::gotoNext: SuccessWithInfo: ", d->err); + r = OCI_SUCCESS; //ignore it + break; + case OCI_NO_DATA: + // end of rowset + return false; + case OCI_NEED_DATA: + piecewise = true; + r = OCI_SUCCESS; + break; + case OCI_ERROR: + if (qOraErrorNumber(d->err) == 1406) { + qWarning("QOCI Warning: data truncated for %s", lastQuery().toLocal8Bit().constData()); + r = OCI_SUCCESS; /* ignore it */ + break; + } + // fall through + default: + qOraWarning("QOCIResult::gotoNext: ", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to goto next"), + QSqlError::StatementError, d->err)); + break; + } + + // need to read piecewise before assigning values + if (r == OCI_SUCCESS && piecewise) + r = d->cols->readPiecewise(values, index); + + if (r == OCI_SUCCESS) + d->cols->getValues(values, index); + if (r == OCI_SUCCESS) + r = d->cols->readLOBs(values, index); + if (r != OCI_SUCCESS) + setAt(QSql::AfterLastRow); + return r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO; +} + +int QOCIResult::size() +{ + return -1; +} + +int QOCIResult::numRowsAffected() +{ + Q_D(QOCIResult); + int rowCount; + OCIAttrGet(d->sql, + OCI_HTYPE_STMT, + &rowCount, + NULL, + OCI_ATTR_ROW_COUNT, + d->err); + return rowCount; +} + +bool QOCIResult::prepare(const QString& query) +{ + Q_D(QOCIResult); + int r = 0; + QSqlResult::prepare(query); + + delete d->cols; + d->cols = 0; + QSqlCachedResult::cleanup(); + + if (d->sql) { + r = OCIHandleFree(d->sql, OCI_HTYPE_STMT); + if (r != OCI_SUCCESS) + qOraWarning("QOCIResult::prepare: unable to free statement handle:", d->err); + } + if (query.isEmpty()) + return false; + r = OCIHandleAlloc(d->env, + reinterpret_cast<void **>(&d->sql), + OCI_HTYPE_STMT, + 0, + 0); + if (r != OCI_SUCCESS) { + qOraWarning("QOCIResult::prepare: unable to alloc statement:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to alloc statement"), QSqlError::StatementError, d->err)); + return false; + } + d->setStatementAttributes(); + const OraText *txt = reinterpret_cast<const OraText *>(query.utf16()); + const int len = query.length() * sizeof(QChar); + r = OCIStmtPrepare(d->sql, + d->err, + txt, + len, + OCI_NTV_SYNTAX, + OCI_DEFAULT); + if (r != OCI_SUCCESS) { + qOraWarning("QOCIResult::prepare: unable to prepare statement:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to prepare statement"), QSqlError::StatementError, d->err)); + return false; + } + return true; +} + +bool QOCIResult::exec() +{ + Q_D(QOCIResult); + int r = 0; + ub2 stmtType=0; + ub4 iters; + ub4 mode; + QList<QByteArray> tmpStorage; + IndicatorArray indicators(boundValueCount()); + SizeArray tmpSizes(boundValueCount()); + + r = OCIAttrGet(d->sql, + OCI_HTYPE_STMT, + &stmtType, + NULL, + OCI_ATTR_STMT_TYPE, + d->err); + + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIResult::exec: Unable to get statement type:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to get statement type"), QSqlError::StatementError, d->err)); +#ifdef QOCI_DEBUG + qDebug() << "lastQuery()" << lastQuery(); +#endif + return false; + } + + iters = stmtType == OCI_STMT_SELECT ? 0 : 1; + mode = d->transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS; + + // bind placeholders + if (boundValueCount() > 0 + && d->bindValues(boundValues(), indicators, tmpSizes, tmpStorage) != OCI_SUCCESS) { + qOraWarning("QOCIResult::exec: unable to bind value: ", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", "Unable to bind value"), + QSqlError::StatementError, d->err)); +#ifdef QOCI_DEBUG + qDebug() << "lastQuery()" << lastQuery(); +#endif + return false; + } + + // execute + r = OCIStmtExecute(d->svc, + d->sql, + d->err, + iters, + 0, + 0, + 0, + mode); + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIResult::exec: unable to execute statement:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to execute statement"), QSqlError::StatementError, d->err)); +#ifdef QOCI_DEBUG + qDebug() << "lastQuery()" << lastQuery(); +#endif + return false; + } + + if (stmtType == OCI_STMT_SELECT) { + ub4 parmCount = 0; + int r = OCIAttrGet(d->sql, OCI_HTYPE_STMT, reinterpret_cast<void **>(&parmCount), + 0, OCI_ATTR_PARAM_COUNT, d->err); + if (r == 0 && !d->cols) + d->cols = new QOCICols(parmCount, d); + setSelect(true); + QSqlCachedResult::init(parmCount); + } else { /* non-SELECT */ + setSelect(false); + } + setAt(QSql::BeforeFirstRow); + setActive(true); + + if (hasOutValues()) + d->outValues(boundValues(), indicators, tmpStorage); + + return true; +} + +QSqlRecord QOCIResult::record() const +{ + Q_D(const QOCIResult); + QSqlRecord inf; + if (!isActive() || !isSelect() || !d->cols) + return inf; + return d->cols->rec; +} + +QVariant QOCIResult::lastInsertId() const +{ + Q_D(const QOCIResult); + if (isActive()) { + QOCIRowIdPointer ptr(new QOCIRowId(d->env)); + + int r = OCIAttrGet(d->sql, OCI_HTYPE_STMT, ptr.constData()->id, + 0, OCI_ATTR_ROWID, d->err); + if (r == OCI_SUCCESS) + return QVariant::fromValue(ptr); + } + return QVariant(); +} + +bool QOCIResult::execBatch(bool arrayBind) +{ + Q_D(QOCIResult); + QOCICols::execBatch(d, boundValues(), arrayBind); + resetBindCount(); + return lastError().type() == QSqlError::NoError; +} + +void QOCIResult::virtual_hook(int id, void *data) +{ + Q_ASSERT(data); + + QSqlCachedResult::virtual_hook(id, data); +} + +//////////////////////////////////////////////////////////////////////////// + + +QOCIDriver::QOCIDriver(QObject* parent) + : QSqlDriver(*new QOCIDriverPrivate, parent) +{ + Q_D(QOCIDriver); +#ifdef QOCI_THREADED + const ub4 mode = OCI_UTF16 | OCI_OBJECT | OCI_THREADED; +#else + const ub4 mode = OCI_UTF16 | OCI_OBJECT; +#endif + int r = OCIEnvCreate(&d->env, + mode, + NULL, + NULL, + NULL, + NULL, + 0, + NULL); + if (r != 0) { + qWarning("QOCIDriver: unable to create environment"); + setLastError(qMakeError(tr("Unable to initialize", "QOCIDriver"), + QSqlError::ConnectionError, d->err)); + return; + } + + d->allocErrorHandle(); +} + +QOCIDriver::QOCIDriver(OCIEnv* env, OCISvcCtx* ctx, QObject* parent) + : QSqlDriver(*new QOCIDriverPrivate, parent) +{ + Q_D(QOCIDriver); + d->env = env; + d->svc = ctx; + + d->allocErrorHandle(); + + if (env && ctx) { + setOpen(true); + setOpenError(false); + } +} + +QOCIDriver::~QOCIDriver() +{ + Q_D(QOCIDriver); + if (isOpen()) + close(); + int r = OCIHandleFree(d->err, OCI_HTYPE_ERROR); + if (r != OCI_SUCCESS) + qWarning("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); +} + +bool QOCIDriver::hasFeature(DriverFeature f) const +{ + Q_D(const QOCIDriver); + switch (f) { + case Transactions: + case LastInsertId: + case BLOB: + case PreparedQueries: + case NamedPlaceholders: + case BatchOperations: + case LowPrecisionNumbers: + return true; + case QuerySize: + case PositionalPlaceholders: + case SimpleLocking: + case EventNotifications: + case FinishQuery: + case CancelQuery: + case MultipleResultSets: + return false; + case Unicode: + return d->serverVersion >= 9; + } + return false; +} + +static void qParseOpts(const QString &options, QOCIDriverPrivate *d) +{ + const QStringList opts(options.split(QLatin1Char(';'), QString::SkipEmptyParts)); + for (int i = 0; i < opts.count(); ++i) { + const QString tmp(opts.at(i)); + int idx; + if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) { + qWarning("QOCIDriver::parseArgs: Invalid parameter: '%s'", + tmp.toLocal8Bit().constData()); + continue; + } + const QString opt = tmp.left(idx); + const QString val = tmp.mid(idx + 1).simplified(); + bool ok; + if (opt == QLatin1String("OCI_ATTR_PREFETCH_ROWS")) { + d->prefetchRows = val.toInt(&ok); + if (!ok) + d->prefetchRows = -1; + } else if (opt == QLatin1String("OCI_ATTR_PREFETCH_MEMORY")) { + d->prefetchMem = val.toInt(&ok); + if (!ok) + d->prefetchMem = -1; + } else { + qWarning ("QOCIDriver::parseArgs: Invalid parameter: '%s'", + opt.toLocal8Bit().constData()); + } + } +} + +bool QOCIDriver::open(const QString & db, + const QString & user, + const QString & password, + const QString & hostname, + int port, + const QString &opts) +{ + Q_D(QOCIDriver); + int r; + + if (isOpen()) + close(); + + qParseOpts(opts, d); + + // Connect without tnsnames.ora if a hostname is given + QString connectionString = db; + if (!hostname.isEmpty()) + connectionString = + QString::fromLatin1("(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=%1)(Port=%2))" + "(CONNECT_DATA=(SID=%3)))").arg(hostname).arg((port > -1 ? port : 1521)).arg(db); + + r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->srvhp), OCI_HTYPE_SERVER, 0, 0); + if (r == OCI_SUCCESS) + r = OCIServerAttach(d->srvhp, d->err, reinterpret_cast<const OraText *>(connectionString.utf16()), + connectionString.length() * sizeof(QChar), OCI_DEFAULT); + if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO) + r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->svc), OCI_HTYPE_SVCCTX, 0, 0); + if (r == OCI_SUCCESS) + r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, d->srvhp, 0, OCI_ATTR_SERVER, d->err); + if (r == OCI_SUCCESS) + r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->authp), OCI_HTYPE_SESSION, 0, 0); + if (r == OCI_SUCCESS) + r = OCIAttrSet(d->authp, OCI_HTYPE_SESSION, const_cast<ushort *>(user.utf16()), + user.length() * sizeof(QChar), OCI_ATTR_USERNAME, d->err); + if (r == OCI_SUCCESS) + r = OCIAttrSet(d->authp, OCI_HTYPE_SESSION, const_cast<ushort *>(password.utf16()), + password.length() * sizeof(QChar), OCI_ATTR_PASSWORD, d->err); + + OCITrans* trans; + if (r == OCI_SUCCESS) + r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&trans), OCI_HTYPE_TRANS, 0, 0); + if (r == OCI_SUCCESS) + r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, trans, 0, OCI_ATTR_TRANS, d->err); + + if (r == OCI_SUCCESS) { + if (user.isEmpty() && password.isEmpty()) + r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_EXT, OCI_DEFAULT); + else + r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_RDBMS, OCI_DEFAULT); + } + if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO) + r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, d->authp, 0, OCI_ATTR_SESSION, d->err); + + if (r != OCI_SUCCESS) { + setLastError(qMakeError(tr("Unable to logon"), QSqlError::ConnectionError, d->err)); + setOpenError(true); + if (d->authp) + OCIHandleFree(d->authp, OCI_HTYPE_SESSION); + d->authp = 0; + if (d->srvhp) + OCIHandleFree(d->srvhp, OCI_HTYPE_SERVER); + d->srvhp = 0; + return false; + } + + // get server version + char vertxt[512]; + r = OCIServerVersion(d->svc, + d->err, + reinterpret_cast<OraText *>(vertxt), + sizeof(vertxt), + OCI_HTYPE_SVCCTX); + if (r != 0) { + qWarning("QOCIDriver::open: could not get Oracle server version."); + } else { + QString versionStr; + versionStr = QString(reinterpret_cast<const QChar *>(vertxt)); + QRegExp vers(QLatin1String("([0-9]+)\\.[0-9\\.]+[0-9]")); + if (vers.indexIn(versionStr) >= 0) + d->serverVersion = vers.cap(1).toInt(); + if (d->serverVersion == 0) + d->serverVersion = -1; + } + + setOpen(true); + setOpenError(false); + d->user = user; + + return true; +} + +void QOCIDriver::close() +{ + Q_D(QOCIDriver); + if (!isOpen()) + return; + + OCISessionEnd(d->svc, d->err, d->authp, OCI_DEFAULT); + OCIServerDetach(d->srvhp, d->err, OCI_DEFAULT); + OCIHandleFree(d->authp, OCI_HTYPE_SESSION); + d->authp = 0; + OCIHandleFree(d->srvhp, OCI_HTYPE_SERVER); + d->srvhp = 0; + OCIHandleFree(d->svc, OCI_HTYPE_SVCCTX); + d->svc = 0; + setOpen(false); + setOpenError(false); +} + +QSqlResult *QOCIDriver::createResult() const +{ + return new QOCIResult(this); +} + +bool QOCIDriver::beginTransaction() +{ + Q_D(QOCIDriver); + if (!isOpen()) { + qWarning("QOCIDriver::beginTransaction: Database not open"); + return false; + } + int r = OCITransStart(d->svc, + d->err, + 2, + OCI_TRANS_READWRITE); + if (r == OCI_ERROR) { + qOraWarning("QOCIDriver::beginTransaction: ", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIDriver", + "Unable to begin transaction"), QSqlError::TransactionError, d->err)); + return false; + } + d->transaction = true; + return true; +} + +bool QOCIDriver::commitTransaction() +{ + Q_D(QOCIDriver); + if (!isOpen()) { + qWarning("QOCIDriver::commitTransaction: Database not open"); + return false; + } + int r = OCITransCommit(d->svc, + d->err, + 0); + if (r == OCI_ERROR) { + qOraWarning("QOCIDriver::commitTransaction:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIDriver", + "Unable to commit transaction"), QSqlError::TransactionError, d->err)); + return false; + } + d->transaction = false; + return true; +} + +bool QOCIDriver::rollbackTransaction() +{ + Q_D(QOCIDriver); + if (!isOpen()) { + qWarning("QOCIDriver::rollbackTransaction: Database not open"); + return false; + } + int r = OCITransRollback(d->svc, + d->err, + 0); + if (r == OCI_ERROR) { + qOraWarning("QOCIDriver::rollbackTransaction:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIDriver", + "Unable to rollback transaction"), QSqlError::TransactionError, d->err)); + return false; + } + d->transaction = false; + return true; +} + +enum Expression { + OrExpression, + AndExpression +}; + +static QString make_where_clause(const QString &user, Expression e) +{ + static const char sysUsers[][8] = { + "MDSYS", + "LBACSYS", + "SYS", + "SYSTEM", + "WKSYS", + "CTXSYS", + "WMSYS", + }; + static const char joinC[][4] = { "or" , "and" }; + static Q_CONSTEXPR QLatin1Char bang[] = { QLatin1Char(' '), QLatin1Char('!') }; + + const QLatin1String join(joinC[e], -1); // -1: force strlen call + + QString result; + result.reserve(sizeof sysUsers / sizeof *sysUsers * + // max-sizeof(owner != <sysuser> and ) + (9 + sizeof *sysUsers + 5)); + for (const auto &sysUser : sysUsers) { + const QLatin1String l1(sysUser, -1); // -1: force strlen call + if (l1 != user) + result += QLatin1String("owner ") + bang[e] + QLatin1String("= '") + l1 + QLatin1Char(' ') + join + QLatin1Char(' '); + } + + result.chop(join.size() + 2); // remove final " <join> " + + return result; +} + +QStringList QOCIDriver::tables(QSql::TableType type) const +{ + Q_D(const QOCIDriver); + QStringList tl; + + QString user = d->user; + if ( isIdentifierEscaped(user, QSqlDriver::TableName)) + user = stripDelimiters(user, QSqlDriver::TableName); + else + user = user.toUpper(); + + if (!isOpen()) + return tl; + + QSqlQuery t(createResult()); + t.setForwardOnly(true); + if (type & QSql::Tables) { + const QLatin1String tableQuery("select owner, table_name from all_tables where "); + const QString where = make_where_clause(user, AndExpression); + t.exec(tableQuery + where); + while (t.next()) { + if (t.value(0).toString().toUpper() != user.toUpper()) + tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); + else + tl.append(t.value(1).toString()); + } + + // list all table synonyms as well + const QLatin1String synonymQuery("select owner, synonym_name from all_synonyms where "); + t.exec(synonymQuery + where); + while (t.next()) { + if (t.value(0).toString() != d->user) + tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); + else + tl.append(t.value(1).toString()); + } + } + if (type & QSql::Views) { + const QLatin1String query("select owner, view_name from all_views where "); + const QString where = make_where_clause(user, AndExpression); + t.exec(query + where); + while (t.next()) { + if (t.value(0).toString().toUpper() != d->user.toUpper()) + tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); + else + tl.append(t.value(1).toString()); + } + } + if (type & QSql::SystemTables) { + t.exec(QLatin1String("select table_name from dictionary")); + while (t.next()) { + tl.append(t.value(0).toString()); + } + const QLatin1String tableQuery("select owner, table_name from all_tables where "); + const QString where = make_where_clause(user, OrExpression); + t.exec(tableQuery + where); + while (t.next()) { + if (t.value(0).toString().toUpper() != user.toUpper()) + tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); + else + tl.append(t.value(1).toString()); + } + + // list all table synonyms as well + const QLatin1String synonymQuery("select owner, synonym_name from all_synonyms where "); + t.exec(synonymQuery + where); + while (t.next()) { + if (t.value(0).toString() != d->user) + tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); + else + tl.append(t.value(1).toString()); + } + } + return tl; +} + +void qSplitTableAndOwner(const QString & tname, QString * tbl, + QString * owner) +{ + int i = tname.indexOf(QLatin1Char('.')); // prefixed with owner? + if (i != -1) { + *tbl = tname.right(tname.length() - i - 1); + *owner = tname.left(i); + } else { + *tbl = tname; + } +} + +QSqlRecord QOCIDriver::record(const QString& tablename) const +{ + Q_D(const QOCIDriver); + QSqlRecord fil; + if (!isOpen()) + return fil; + + QSqlQuery t(createResult()); + // using two separate queries for this is A LOT faster than using + // eg. a sub-query on the sys.synonyms table + QString stmt(QLatin1String("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")); + if (d->serverVersion >= 9) + stmt = stmt.arg(QLatin1String(", char_length ")); + else + stmt = stmt.arg(QLatin1String(" ")); + bool buildRecordInfo = false; + QString table, owner, tmpStmt; + qSplitTableAndOwner(tablename, &table, &owner); + + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + else + table = table.toUpper(); + + tmpStmt = stmt.arg(QLatin1Char('\'') + table + QLatin1Char('\'')); + if (owner.isEmpty()) { + owner = d->user; + } + + if (isIdentifierEscaped(owner, QSqlDriver::TableName)) + owner = stripDelimiters(owner, QSqlDriver::TableName); + else + owner = owner.toUpper(); + + tmpStmt += QLatin1String(" and a.owner='") + owner + QLatin1Char('\''); + t.setForwardOnly(true); + t.exec(tmpStmt); + if (!t.next()) { // try and see if the tablename is a synonym + stmt = stmt + QLatin1String(" join all_synonyms b " + "on a.owner=b.table_owner and a.table_name=b.table_name " + "where b.owner='") + owner + + QLatin1String("' and b.synonym_name='") + table + + QLatin1Char('\''); + t.setForwardOnly(true); + t.exec(stmt); + if (t.next()) + buildRecordInfo = true; + } else { + buildRecordInfo = true; + } + QStringList keywords = QStringList() << QLatin1String("NUMBER") << QLatin1String("FLOAT") << QLatin1String("BINARY_FLOAT") + << QLatin1String("BINARY_DOUBLE"); + if (buildRecordInfo) { + do { + QVariant::Type ty = qDecodeOCIType(t.value(1).toString(), t.numericalPrecisionPolicy()); + QSqlField f(t.value(0).toString(), ty); + f.setRequired(t.value(5).toString() == QLatin1String("N")); + f.setPrecision(t.value(4).toInt()); + if (d->serverVersion >= 9 && (ty == QVariant::String) && !t.isNull(3) && !keywords.contains(t.value(1).toString())) { + // Oracle9: data_length == size in bytes, char_length == amount of characters + f.setLength(t.value(7).toInt()); + } else { + f.setLength(t.value(t.isNull(3) ? 2 : 3).toInt()); + } + f.setDefaultValue(t.value(6)); + fil.append(f); + } while (t.next()); + } + return fil; +} + +QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const +{ + Q_D(const QOCIDriver); + QSqlIndex idx(tablename); + if (!isOpen()) + return idx; + QSqlQuery t(createResult()); + QString stmt(QLatin1String("select b.column_name, b.index_name, a.table_name, a.owner " + "from all_constraints a, all_ind_columns b " + "where a.constraint_type='P' " + "and b.index_name = a.constraint_name " + "and b.index_owner = a.owner")); + + bool buildIndex = false; + QString table, owner, tmpStmt; + qSplitTableAndOwner(tablename, &table, &owner); + + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + else + table = table.toUpper(); + + tmpStmt = stmt + QLatin1String(" and a.table_name='") + table + QLatin1Char('\''); + if (owner.isEmpty()) { + owner = d->user; + } + + if (isIdentifierEscaped(owner, QSqlDriver::TableName)) + owner = stripDelimiters(owner, QSqlDriver::TableName); + else + owner = owner.toUpper(); + + tmpStmt += QLatin1String(" and a.owner='") + owner + QLatin1Char('\''); + t.setForwardOnly(true); + t.exec(tmpStmt); + + if (!t.next()) { + stmt += QLatin1String(" and a.table_name=(select tname from sys.synonyms " + "where sname='") + table + QLatin1String("' and creator=a.owner)"); + t.setForwardOnly(true); + t.exec(stmt); + if (t.next()) { + owner = t.value(3).toString(); + buildIndex = true; + } + } else { + buildIndex = true; + } + if (buildIndex) { + QSqlQuery tt(createResult()); + tt.setForwardOnly(true); + idx.setName(t.value(1).toString()); + do { + tt.exec(QLatin1String("select data_type from all_tab_columns where table_name='") + + t.value(2).toString() + QLatin1String("' and column_name='") + + t.value(0).toString() + QLatin1String("' and owner='") + + owner + QLatin1Char('\'')); + if (!tt.next()) { + return QSqlIndex(); + } + QSqlField f(t.value(0).toString(), qDecodeOCIType(tt.value(0).toString(), t.numericalPrecisionPolicy())); + idx.append(f); + } while (t.next()); + return idx; + } + return QSqlIndex(); +} + +QString QOCIDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + switch (field.type()) { + case QVariant::DateTime: { + QDateTime datetime = field.value().toDateTime(); + QString datestring; + if (datetime.isValid()) { + datestring = QLatin1String("TO_DATE('") + QString::number(datetime.date().year()) + + QLatin1Char('-') + + QString::number(datetime.date().month()) + QLatin1Char('-') + + QString::number(datetime.date().day()) + QLatin1Char(' ') + + QString::number(datetime.time().hour()) + QLatin1Char(':') + + QString::number(datetime.time().minute()) + QLatin1Char(':') + + QString::number(datetime.time().second()) + + QLatin1String("','YYYY-MM-DD HH24:MI:SS')"); + } else { + datestring = QLatin1String("NULL"); + } + return datestring; + } + case QVariant::Time: { + QDateTime datetime = field.value().toDateTime(); + QString datestring; + if (datetime.isValid()) { + datestring = QLatin1String("TO_DATE('") + + QString::number(datetime.time().hour()) + QLatin1Char(':') + + QString::number(datetime.time().minute()) + QLatin1Char(':') + + QString::number(datetime.time().second()) + + QLatin1String("','HH24:MI:SS')"); + } else { + datestring = QLatin1String("NULL"); + } + return datestring; + } + case QVariant::Date: { + QDate date = field.value().toDate(); + QString datestring; + if (date.isValid()) { + datestring = QLatin1String("TO_DATE('") + QString::number(date.year()) + + QLatin1Char('-') + + QString::number(date.month()) + QLatin1Char('-') + + QString::number(date.day()) + QLatin1String("','YYYY-MM-DD')"); + } else { + datestring = QLatin1String("NULL"); + } + return datestring; + } + default: + break; + } + return QSqlDriver::formatValue(field, trimStrings); +} + +QVariant QOCIDriver::handle() const +{ + Q_D(const QOCIDriver); + return QVariant::fromValue(d->env); +} + +QString QOCIDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const +{ + QString res = identifier; + if(!identifier.isEmpty() && !isIdentifierEscaped(identifier, type)) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sqldrivers/oci/qsql_oci_p.h b/src/plugins/sqldrivers/oci/qsql_oci_p.h new file mode 100644 index 0000000000..69911f4bee --- /dev/null +++ b/src/plugins/sqldrivers/oci/qsql_oci_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_OCI_H +#define QSQL_OCI_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. +// + +#include <QtSql/qsqldriver.h> + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_OCI +#else +#define Q_EXPORT_SQLDRIVER_OCI Q_SQL_EXPORT +#endif + +typedef struct OCIEnv OCIEnv; +typedef struct OCISvcCtx OCISvcCtx; + +QT_BEGIN_NAMESPACE + +class QSqlResult; +class QOCIDriverPrivate; + +class Q_EXPORT_SQLDRIVER_OCI QOCIDriver : public QSqlDriver +{ + Q_DECLARE_PRIVATE(QOCIDriver) + Q_OBJECT + friend class QOCICols; + friend class QOCIResultPrivate; + +public: + explicit QOCIDriver(QObject* parent = 0); + QOCIDriver(OCIEnv* env, OCISvcCtx* ctx, QObject* parent = 0); + ~QOCIDriver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port, + const QString &connOpts) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + QSqlRecord record(const QString &tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString& tablename) const Q_DECL_OVERRIDE; + QString formatValue(const QSqlField &field, + bool trimStrings) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE; + +protected: + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif // QSQL_OCI_H diff --git a/src/plugins/sqldrivers/odbc/main.cpp b/src/plugins/sqldrivers/odbc/main.cpp index 69ea7b1f8e..ac63941a82 100644 --- a/src/plugins/sqldrivers/odbc/main.cpp +++ b/src/plugins/sqldrivers/odbc/main.cpp @@ -39,7 +39,7 @@ #include <qsqldriverplugin.h> #include <qstringlist.h> -#include "../../../sql/drivers/odbc/qsql_odbc_p.h" +#include "qsql_odbc_p.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/sqldrivers/odbc/odbc.pro b/src/plugins/sqldrivers/odbc/odbc.pro index c0020c065f..0e49f1ac66 100644 --- a/src/plugins/sqldrivers/odbc/odbc.pro +++ b/src/plugins/sqldrivers/odbc/odbc.pro @@ -1,8 +1,19 @@ TARGET = qsqlodbc -SOURCES = main.cpp +HEADERS += $$PWD/qsql_odbc_p.h +SOURCES += $$PWD/qsql_odbc.cpp $$PWD/main.cpp + +unix { + DEFINES += UNICODE + !contains(LIBS, .*odbc.*) { + osx:LIBS += -liodbc + else:LIBS += $$QMAKE_LIBS_ODBC + } +} else { + LIBS *= -lodbc32 +} + OTHER_FILES += odbc.json -include(../../../sql/drivers/odbc/qsql_odbc.pri) PLUGIN_CLASS_NAME = QODBCDriverPlugin include(../qsqldriverbase.pri) diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp new file mode 100644 index 0000000000..59ef42d609 --- /dev/null +++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp @@ -0,0 +1,2639 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_odbc_p.h" +#include <qsqlrecord.h> + +#if defined (Q_OS_WIN32) +#include <qt_windows.h> +#endif +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qstringlist.h> +#include <qvarlengtharray.h> +#include <qvector.h> +#include <qmath.h> +#include <QDebug> +#include <QSqlQuery> +#include <QtSql/private/qsqldriver_p.h> +#include <QtSql/private/qsqlresult_p.h> + +QT_BEGIN_NAMESPACE + +// undefine this to prevent initial check of the ODBC driver +#define ODBC_CHECK_DRIVER + +static const int COLNAMESIZE = 256; +//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 }; + +inline static QString fromSQLTCHAR(const QVarLengthArray<SQLTCHAR>& input, int size=-1) +{ + QString result; + + int 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((const ushort *)input.constData(), realsize); + break; + case 4: + result=QString::fromUcs4((const uint *)input.constData(), realsize); + break; + default: + qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR))); + } + return result; +} + +inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(const QString &input) +{ + QVarLengthArray<SQLTCHAR> result; + result.resize(input.size()); + switch(sizeof(SQLTCHAR)) { + case 1: + memcpy(result.data(), input.toUtf8().data(), input.size()); + break; + case 2: + memcpy(result.data(), input.unicode(), input.size() * 2); + break; + case 4: + memcpy(result.data(), input.toUcs4().data(), input.size() * 4); + break; + default: + qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(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; +} + +class QODBCDriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QODBCDriver) + +public: + enum DefaultCase{Lower, Mixed, Upper, Sensitive}; + QODBCDriverPrivate() + : QSqlDriverPrivate(), hEnv(0), hDbc(0), unicode(false), useSchema(false), disconnectCount(0), datetime_precision(19), + isFreeTDSDriver(false), hasSQLFetchScroll(true), hasMultiResultSets(false), isQuoteInitialized(false), quote(QLatin1Char('"')) + { + } + + SQLHANDLE hEnv; + SQLHANDLE hDbc; + + bool unicode; + bool useSchema; + int disconnectCount; + int datetime_precision; + bool isFreeTDSDriver; + bool hasSQLFetchScroll; + bool hasMultiResultSets; + + bool checkDriver() const; + void checkUnicode(); + void checkDBMS(); + void checkHasSQLFetchScroll(); + void checkHasMultiResults(); + void checkSchemaUsage(); + void checkDateTimePrecision(); + bool setConnectionOptions(const QString& connOpts); + void splitTableQualifier(const QString &qualifier, QString &catalog, + QString &schema, QString &table); + DefaultCase defaultCase() const; + QString adjustCase(const QString&) const; + QChar quoteChar(); +private: + bool isQuoteInitialized; + QChar quote; +}; + +class QODBCResultPrivate; + +class QODBCResult: public QSqlResult +{ + Q_DECLARE_PRIVATE(QODBCResult) + +public: + QODBCResult(const QODBCDriver *db); + virtual ~QODBCResult(); + + bool prepare(const QString &query) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; + + QVariant lastInsertId() const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + bool fetchNext() Q_DECL_OVERRIDE; + bool fetchFirst() Q_DECL_OVERRIDE; + bool fetchLast() Q_DECL_OVERRIDE; + bool fetchPrevious() Q_DECL_OVERRIDE; + bool fetch(int i) Q_DECL_OVERRIDE; + bool reset(const QString &query) Q_DECL_OVERRIDE; + QVariant data(int field) Q_DECL_OVERRIDE; + bool isNull(int field) Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; + void detachFromResultSet() Q_DECL_OVERRIDE; + bool nextResult() Q_DECL_OVERRIDE; +}; + +class QODBCResultPrivate: public QSqlResultPrivate +{ + Q_DECLARE_PUBLIC(QODBCResult) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QODBCDriver) + QODBCResultPrivate(QODBCResult *q, const QODBCDriver *db) + : QSqlResultPrivate(q, db), + hStmt(0), + useSchema(false), + hasSQLFetchScroll(true) + { + unicode = drv_d_func()->unicode; + useSchema = drv_d_func()->useSchema; + disconnectCount = drv_d_func()->disconnectCount; + hasSQLFetchScroll = drv_d_func()->hasSQLFetchScroll; + } + + inline void clearValues() + { fieldCache.fill(QVariant()); fieldCacheIdx = 0; } + + SQLHANDLE dpEnv() const { return drv_d_func() ? drv_d_func()->hEnv : 0;} + SQLHANDLE dpDbc() const { return drv_d_func() ? drv_d_func()->hDbc : 0;} + SQLHANDLE hStmt; + + bool unicode; + bool useSchema; + + QSqlRecord rInf; + QVector<QVariant> fieldCache; + int fieldCacheIdx; + int disconnectCount; + bool hasSQLFetchScroll; + + bool isStmtHandleValid(); + void updateStmtHandleState(); +}; + +bool QODBCResultPrivate::isStmtHandleValid() +{ + return disconnectCount == drv_d_func()->disconnectCount; +} + +void QODBCResultPrivate::updateStmtHandleState() +{ + disconnectCount = drv_d_func()->disconnectCount; +} + +static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode = 0) +{ + SQLINTEGER nativeCode_ = 0; + SQLSMALLINT msgLen = 0; + SQLRETURN r = SQL_NO_DATA; + SQLTCHAR state_[SQL_SQLSTATE_SIZE+1]; + QVarLengthArray<SQLTCHAR> description_(SQL_MAX_MESSAGE_LENGTH); + QString result; + int i = 1; + + description_[0] = 0; + do { + r = SQLGetDiagRec(handleType, + handle, + i, + state_, + &nativeCode_, + 0, + 0, + &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 += QLatin1Char(' '); + result += tmpstore; + } + } else if (r == SQL_ERROR || r == SQL_INVALID_HANDLE) { + return result; + } + ++i; + } while (r != SQL_NO_DATA); + return result; +} + +static QString qODBCWarn(const SQLHANDLE hStmt, const SQLHANDLE envHandle = 0, + const SQLHANDLE pDbC = 0, int *nativeCode = 0) +{ + 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 += QLatin1Char(' '); + result += dMessage; + } + } + if (hStmt) { + const QString hMessage = qWarnODBCHandle(SQL_HANDLE_STMT, hStmt, nativeCode); + if (!hMessage.isEmpty()) { + if (!result.isEmpty()) + result += QLatin1Char(' '); + result += hMessage; + } + } + return result; +} + +static QString qODBCWarn(const QODBCResultPrivate* odbc, int *nativeCode = 0) +{ + return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc(), nativeCode); +} + +static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = 0) +{ + return qODBCWarn(0, odbc->hEnv, odbc->hDbc, nativeCode); +} + +static void qSqlWarning(const QString& message, const QODBCResultPrivate* odbc) +{ + qWarning() << message << "\tError:" << qODBCWarn(odbc); +} + +static void qSqlWarning(const QString &message, const QODBCDriverPrivate *odbc) +{ + qWarning() << message << "\tError:" << qODBCWarn(odbc); +} + +static void qSqlWarning(const QString &message, const SQLHANDLE hStmt) +{ + qWarning() << message << "\tError:" << qODBCWarn(hStmt); +} + +static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QODBCResultPrivate* p) +{ + int nativeCode = -1; + QString message = qODBCWarn(p, &nativeCode); + return QSqlError(QLatin1String("QODBC3: ") + err, message, type, nativeCode); +} + +static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, + const QODBCDriverPrivate* p) +{ + int nativeCode = -1; + QString message = qODBCWarn(p, &nativeCode); + return QSqlError(QLatin1String("QODBC3: ") + err, qODBCWarn(p), type, nativeCode); +} + +static QVariant::Type qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true) +{ + QVariant::Type type = QVariant::Invalid; + switch (sqltype) { + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: + type = QVariant::Double; + break; + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIT: + type = isSigned ? QVariant::Int : QVariant::UInt; + break; + case SQL_TINYINT: + type = QVariant::UInt; + break; + case SQL_BIGINT: + type = isSigned ? QVariant::LongLong : QVariant::ULongLong; + break; + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + type = QVariant::ByteArray; + break; + case SQL_DATE: + case SQL_TYPE_DATE: + type = QVariant::Date; + break; + case SQL_TIME: + case SQL_TYPE_TIME: + type = QVariant::Time; + break; + case SQL_TIMESTAMP: + case SQL_TYPE_TIMESTAMP: + type = QVariant::DateTime; + break; + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + type = QVariant::String; + break; + case SQL_CHAR: + case SQL_VARCHAR: +#if (ODBCVER >= 0x0350) + case SQL_GUID: +#endif + case SQL_LONGVARCHAR: + type = QVariant::String; + break; + default: + type = QVariant::ByteArray; + break; + } + return type; +} + +static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode = false) +{ + 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) { + 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) { + fieldVal.clear(); + break; + } + // 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 + // colSize-1: remove 0 termination when there is more data to fetch + 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) << ')'; + fieldVal.clear(); + break; + } + } + } 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) { + fieldVal.clear(); + break; + } + // 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 + // colSize-1: remove 0 termination when there is more data to fetch + int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator; + fieldVal += QString::fromUtf8((const char *)buf.constData(), rSize); + if (lengthIndicator < SQLLEN(colSize)) { + // 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) << ')'; + fieldVal.clear(); + break; + } + } + } + return fieldVal; +} + +static QVariant qGetBinaryData(SQLHANDLE hStmt, int column) +{ + QByteArray fieldVal; + SQLSMALLINT colNameLen; + SQLSMALLINT colType; + SQLULEN colSize; + SQLSMALLINT colScale; + SQLSMALLINT nullable; + SQLLEN lengthIndicator = 0; + SQLRETURN r = SQL_ERROR; + + QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE); + + r = SQLDescribeCol(hStmt, + column + 1, + colName.data(), + COLNAMESIZE, + &colNameLen, + &colType, + &colSize, + &colScale, + &nullable); + if (r != SQL_SUCCESS) + qWarning() << "qGetBinaryData: Unable to describe column" << column; + // SQLDescribeCol may return 0 if size cannot be determined + if (!colSize) + colSize = 255; + else if (colSize > 65536) // read the field in 64 KB chunks + colSize = 65536; + fieldVal.resize(colSize); + ulong read = 0; + while (true) { + r = SQLGetData(hStmt, + column+1, + SQL_C_BINARY, + const_cast<char *>(fieldVal.constData() + read), + colSize, + &lengthIndicator); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) + break; + if (lengthIndicator == SQL_NULL_DATA) + return QVariant(QVariant::ByteArray); + if (lengthIndicator > SQLLEN(colSize) || lengthIndicator == SQL_NO_TOTAL) { + read += colSize; + colSize = 65536; + } else { + read += lengthIndicator; + } + if (r == SQL_SUCCESS) { // the whole field was read in one chunk + fieldVal.resize(read); + break; + } + fieldVal.resize(fieldVal.size() + colSize); + } + return fieldVal; +} + +static QVariant qGetIntData(SQLHANDLE hStmt, int column, bool isSigned = true) +{ + SQLINTEGER intbuf = 0; + SQLLEN lengthIndicator = 0; + SQLRETURN r = SQLGetData(hStmt, + column+1, + isSigned ? SQL_C_SLONG : SQL_C_ULONG, + (SQLPOINTER)&intbuf, + sizeof(intbuf), + &lengthIndicator); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) + return QVariant(QVariant::Invalid); + if (lengthIndicator == SQL_NULL_DATA) + return QVariant(QVariant::Int); + if (isSigned) + return int(intbuf); + else + return uint(intbuf); +} + +static QVariant qGetDoubleData(SQLHANDLE hStmt, int column) +{ + SQLDOUBLE dblbuf; + SQLLEN lengthIndicator = 0; + SQLRETURN r = SQLGetData(hStmt, + column+1, + SQL_C_DOUBLE, + (SQLPOINTER) &dblbuf, + 0, + &lengthIndicator); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + return QVariant(QVariant::Invalid); + } + if(lengthIndicator == SQL_NULL_DATA) + return QVariant(QVariant::Double); + + return (double) dblbuf; +} + + +static QVariant qGetBigIntData(SQLHANDLE hStmt, int column, bool isSigned = true) +{ + SQLBIGINT lngbuf = 0; + SQLLEN lengthIndicator = 0; + SQLRETURN r = SQLGetData(hStmt, + column+1, + isSigned ? SQL_C_SBIGINT : SQL_C_UBIGINT, + (SQLPOINTER) &lngbuf, + sizeof(lngbuf), + &lengthIndicator); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) + return QVariant(QVariant::Invalid); + if (lengthIndicator == SQL_NULL_DATA) + return QVariant(QVariant::LongLong); + + if (isSigned) + return qint64(lngbuf); + else + return quint64(lngbuf); +} + +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); + 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) +{ + QString fname = qGetStringData(hStmt, 3, -1, p->unicode); + int type = qGetIntData(hStmt, 4).toInt(); // column type + QSqlField f(fname, qDecodeODBCType(type, p)); + QVariant var = qGetIntData(hStmt, 6); + 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) + f.setRequired(true); + else if (required == SQL_NULLABLE) + f.setRequired(false); + // else we don't know + 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) +{ + SQLSMALLINT colNameLen; + SQLSMALLINT colType; + SQLULEN colSize; + SQLSMALLINT colScale; + SQLSMALLINT nullable; + SQLRETURN r = SQL_ERROR; + QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE); + errorMessage->clear(); + r = SQLDescribeCol(hStmt, + i+1, + colName.data(), + (SQLSMALLINT)COLNAMESIZE, + &colNameLen, + &colType, + &colSize, + &colScale, + &nullable); + + if (r != SQL_SUCCESS) { + *errorMessage = QStringLiteral("qMakeField: Unable to describe column ") + QString::number(i); + return QSqlField(); + } + + SQLLEN unsignedFlag = SQL_FALSE; + r = SQLColAttribute (hStmt, + i + 1, + SQL_DESC_UNSIGNED, + 0, + 0, + 0, + &unsignedFlag); + if (r != SQL_SUCCESS) { + qSqlWarning(QStringLiteral("qMakeField: Unable to get column attributes for column ") + + QString::number(i), hStmt); + } + + const QString qColName(fromSQLTCHAR(colName, colNameLen)); + // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN + QVariant::Type 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) + f.setRequired(true); + else if (nullable == SQL_NULLABLE) + f.setRequired(false); + // else we don't know + f.setAutoValue(isAutoValue(hStmt, i)); + return f; +} + +static size_t qGetODBCVersion(const QString &connOpts) +{ + if (connOpts.contains(QLatin1String("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"), Qt::CaseInsensitive)) + return SQL_OV_ODBC3; + return SQL_OV_ODBC2; +} + +QChar QODBCDriverPrivate::quoteChar() +{ + if (!isQuoteInitialized) { + SQLTCHAR driverResponse[4]; + SQLSMALLINT length; + int r = SQLGetInfo(hDbc, + SQL_IDENTIFIER_QUOTE_CHAR, + &driverResponse, + sizeof(driverResponse), + &length); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) + quote = QChar(driverResponse[0]); + else + quote = QLatin1Char('"'); + isQuoteInitialized = true; + } + return quote; +} + + +bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts) +{ + // Set any connection attributes + const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts)); + SQLRETURN r = SQL_SUCCESS; + for (int i = 0; i < opts.count(); ++i) { + const QString tmp(opts.at(i)); + int idx; + if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) { + qWarning() << "QODBCDriver::open: Illegal connect option value '" << tmp << '\''; + continue; + } + const QString opt(tmp.left(idx)); + const QString val(tmp.mid(idx + 1).simplified()); + SQLUINTEGER v = 0; + + r = SQL_SUCCESS; + if (opt.toUpper() == QLatin1String("SQL_ATTR_ACCESS_MODE")) { + if (val.toUpper() == QLatin1String("SQL_MODE_READ_ONLY")) { + v = SQL_MODE_READ_ONLY; + } else if (val.toUpper() == QLatin1String("SQL_MODE_READ_WRITE")) { + v = SQL_MODE_READ_WRITE; + } else { + qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; + continue; + } + r = SQLSetConnectAttr(hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) size_t(v), 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_TIMEOUT")) { + v = val.toUInt(); + r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) size_t(v), 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) { + v = val.toUInt(); + r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) size_t(v), 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CURRENT_CATALOG")) { + val.utf16(); // 0 terminate + r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, + toSQLTCHAR(val).data(), + val.length()*sizeof(SQLTCHAR)); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_METADATA_ID")) { + if (val.toUpper() == QLatin1String("SQL_TRUE")) { + v = SQL_TRUE; + } else if (val.toUpper() == QLatin1String("SQL_FALSE")) { + v = SQL_FALSE; + } else { + qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; + continue; + } + r = SQLSetConnectAttr(hDbc, SQL_ATTR_METADATA_ID, (SQLPOINTER) size_t(v), 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_PACKET_SIZE")) { + v = val.toUInt(); + r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) size_t(v), 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACEFILE")) { + val.utf16(); // 0 terminate + r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, + toSQLTCHAR(val).data(), + val.length()*sizeof(SQLTCHAR)); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACE")) { + if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_OFF")) { + v = SQL_OPT_TRACE_OFF; + } else if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_ON")) { + v = SQL_OPT_TRACE_ON; + } else { + qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; + continue; + } + r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACE, (SQLPOINTER) size_t(v), 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_POOLING")) { + if (val == QLatin1String("SQL_CP_OFF")) + v = SQL_CP_OFF; + else if (val.toUpper() == QLatin1String("SQL_CP_ONE_PER_DRIVER")) + v = SQL_CP_ONE_PER_DRIVER; + else if (val.toUpper() == QLatin1String("SQL_CP_ONE_PER_HENV")) + v = SQL_CP_ONE_PER_HENV; + else if (val.toUpper() == QLatin1String("SQL_CP_DEFAULT")) + v = SQL_CP_DEFAULT; + else { + qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; + continue; + } + r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER) size_t(v), 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CP_MATCH")) { + if (val.toUpper() == QLatin1String("SQL_CP_STRICT_MATCH")) + v = SQL_CP_STRICT_MATCH; + else if (val.toUpper() == QLatin1String("SQL_CP_RELAXED_MATCH")) + v = SQL_CP_RELAXED_MATCH; + else if (val.toUpper() == QLatin1String("SQL_CP_MATCH_DEFAULT")) + v = SQL_CP_MATCH_DEFAULT; + else { + qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; + continue; + } + r = SQLSetConnectAttr(hDbc, SQL_ATTR_CP_MATCH, (SQLPOINTER) size_t(v), 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_ODBC_VERSION")) { + // Already handled in QODBCDriver::open() + continue; + } else { + qWarning() << "QODBCDriver::open: Unknown connection attribute '" << opt << '\''; + } + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) + qSqlWarning(QString::fromLatin1("QODBCDriver::open: Unable to set connection attribute'%1'").arg( + opt), this); + } + return true; +} + +void QODBCDriverPrivate::splitTableQualifier(const QString & qualifier, QString &catalog, + QString &schema, QString &table) +{ + if (!useSchema) { + table = qualifier; + return; + } + QStringList l = qualifier.split(QLatin1Char('.')); + 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++; + } + } +} + +QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const +{ + DefaultCase ret; + 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 { + 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; + } + } + return ret; +} + +/* + Adjust the casing of an identifier to match what the + database engine would have done to it. +*/ +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; + } + return ret; +} + +//////////////////////////////////////////////////////////////////////////// + +QODBCResult::QODBCResult(const QODBCDriver *db) + : QSqlResult(*new QODBCResultPrivate(this, db)) +{ +} + +QODBCResult::~QODBCResult() +{ + Q_D(QODBCResult); + if (d->hStmt && d->isStmtHandleValid() && driver()->isOpen()) { + SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") + + QString::number(r), d); + } +} + +bool QODBCResult::reset (const QString& query) +{ + Q_D(QODBCResult); + setActive(false); + setAt(QSql::BeforeFirstRow); + d->rInf.clear(); + d->fieldCache.clear(); + d->fieldCacheIdx = 0; + + // Always reallocate the statement handle - the statement attributes + // are not reset if SQLFreeStmt() is called which causes some problems. + SQLRETURN r; + if (d->hStmt && d->isStmtHandleValid()) { + r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCResult::reset: Unable to free statement handle"), d); + return false; + } + } + r = SQLAllocHandle(SQL_HANDLE_STMT, + d->dpDbc(), + &d->hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCResult::reset: Unable to allocate statement handle"), d); + return false; + } + + d->updateStmtHandleState(); + + if (isForwardOnly()) { + r = SQLSetStmtAttr(d->hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + } else { + r = SQLSetStmtAttr(d->hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_STATIC, + SQL_IS_UINTEGER); + } + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + 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)); + return false; + } + + r = SQLExecDirect(d->hStmt, + toSQLTCHAR(query).data(), + (SQLINTEGER) query.length()); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r!= SQL_NO_DATA) { + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to execute statement"), QSqlError::StatementError, d)); + return false; + } + + 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) + setForwardOnly(isScrollable == SQL_NONSCROLLABLE); + + SQLSMALLINT count = 0; + SQLNumResultCols(d->hStmt, &count); + if (count) { + setSelect(true); + for (int i = 0; i < count; ++i) { + d->rInf.append(qMakeFieldInfo(d, i)); + } + d->fieldCache.resize(count); + } else { + setSelect(false); + } + setActive(true); + + return true; +} + +bool QODBCResult::fetch(int i) +{ + Q_D(QODBCResult); + if (!driver()->isOpen()) + return false; + + if (isForwardOnly() && i < at()) + return false; + if (i == at()) + return true; + d->clearValues(); + int actualIdx = i + 1; + if (actualIdx <= 0) { + setAt(QSql::BeforeFirstRow); + return false; + } + SQLRETURN r; + if (isForwardOnly()) { + bool ok = true; + while (ok && i > at()) + ok = fetchNext(); + return ok; + } else { + r = SQLFetchScroll(d->hStmt, + SQL_FETCH_ABSOLUTE, + actualIdx); + } + if (r != SQL_SUCCESS) { + if (r != SQL_NO_DATA) + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to fetch"), QSqlError::ConnectionError, d)); + return false; + } + setAt(i); + return true; +} + +bool QODBCResult::fetchNext() +{ + Q_D(QODBCResult); + SQLRETURN r; + d->clearValues(); + + if (d->hasSQLFetchScroll) + r = SQLFetchScroll(d->hStmt, + SQL_FETCH_NEXT, + 0); + else + r = SQLFetch(d->hStmt); + + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + if (r != SQL_NO_DATA) + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to fetch next"), QSqlError::ConnectionError, d)); + return false; + } + setAt(at() + 1); + return true; +} + +bool QODBCResult::fetchFirst() +{ + Q_D(QODBCResult); + if (isForwardOnly() && at() != QSql::BeforeFirstRow) + return false; + SQLRETURN r; + d->clearValues(); + if (isForwardOnly()) { + return fetchNext(); + } + r = SQLFetchScroll(d->hStmt, + SQL_FETCH_FIRST, + 0); + if (r != SQL_SUCCESS) { + if (r != SQL_NO_DATA) + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to fetch first"), QSqlError::ConnectionError, d)); + return false; + } + setAt(0); + return true; +} + +bool QODBCResult::fetchPrevious() +{ + Q_D(QODBCResult); + if (isForwardOnly()) + return false; + SQLRETURN r; + d->clearValues(); + r = SQLFetchScroll(d->hStmt, + SQL_FETCH_PRIOR, + 0); + if (r != SQL_SUCCESS) { + if (r != SQL_NO_DATA) + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to fetch previous"), QSqlError::ConnectionError, d)); + return false; + } + setAt(at() - 1); + return true; +} + +bool QODBCResult::fetchLast() +{ + Q_D(QODBCResult); + SQLRETURN r; + d->clearValues(); + + if (isForwardOnly()) { + // cannot seek to last row in forwardOnly mode, so we have to use brute force + int i = at(); + if (i == QSql::AfterLastRow) + return false; + if (i == QSql::BeforeFirstRow) + i = 0; + while (fetchNext()) + ++i; + setAt(i); + return true; + } + + r = SQLFetchScroll(d->hStmt, + SQL_FETCH_LAST, + 0); + if (r != SQL_SUCCESS) { + if (r != SQL_NO_DATA) + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to fetch last"), QSqlError::ConnectionError, d)); + return false; + } + SQLULEN currRow = 0; + r = SQLGetStmtAttr(d->hStmt, + SQL_ROW_NUMBER, + &currRow, + SQL_IS_INTEGER, + 0); + if (r != SQL_SUCCESS) + return false; + setAt(currRow-1); + return true; +} + +QVariant QODBCResult::data(int field) +{ + Q_D(QODBCResult); + if (field >= d->rInf.count() || field < 0) { + qWarning() << "QODBCResult::data: column" << field << "out of range"; + return QVariant(); + } + if (field < d->fieldCacheIdx) + return d->fieldCache.at(field); + + SQLRETURN r(0); + SQLLEN lengthIndicator = 0; + + for (int i = d->fieldCacheIdx; i <= field; ++i) { + // some servers do not support fetching column n after we already + // fetched column n+1, so cache all previous columns here + const QSqlField info = d->rInf.field(i); + switch (info.type()) { + case QVariant::LongLong: + d->fieldCache[i] = qGetBigIntData(d->hStmt, i); + break; + case QVariant::ULongLong: + d->fieldCache[i] = qGetBigIntData(d->hStmt, i, false); + break; + case QVariant::Int: + d->fieldCache[i] = qGetIntData(d->hStmt, i); + break; + case QVariant::UInt: + d->fieldCache[i] = qGetIntData(d->hStmt, i, false); + break; + case QVariant::Date: + DATE_STRUCT dbuf; + r = SQLGetData(d->hStmt, + i + 1, + SQL_C_DATE, + (SQLPOINTER)&dbuf, + 0, + &lengthIndicator); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) + d->fieldCache[i] = QVariant(QDate(dbuf.year, dbuf.month, dbuf.day)); + else + d->fieldCache[i] = QVariant(QVariant::Date); + break; + case QVariant::Time: + TIME_STRUCT tbuf; + r = SQLGetData(d->hStmt, + i + 1, + SQL_C_TIME, + (SQLPOINTER)&tbuf, + 0, + &lengthIndicator); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) + d->fieldCache[i] = QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second)); + else + d->fieldCache[i] = QVariant(QVariant::Time); + break; + case QVariant::DateTime: + TIMESTAMP_STRUCT dtbuf; + r = SQLGetData(d->hStmt, + i + 1, + SQL_C_TIMESTAMP, + (SQLPOINTER)&dtbuf, + 0, + &lengthIndicator); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (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 + d->fieldCache[i] = QVariant(QVariant::DateTime); + break; + case QVariant::ByteArray: + d->fieldCache[i] = qGetBinaryData(d->hStmt, i); + break; + case QVariant::String: + d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), d->unicode); + break; + case QVariant::Double: + switch(numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + d->fieldCache[i] = qGetIntData(d->hStmt, i); + break; + case QSql::LowPrecisionInt64: + d->fieldCache[i] = qGetBigIntData(d->hStmt, i); + break; + case QSql::LowPrecisionDouble: + d->fieldCache[i] = qGetDoubleData(d->hStmt, i); + break; + case QSql::HighPrecision: + d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), false); + break; + } + break; + default: + d->fieldCache[i] = QVariant(qGetStringData(d->hStmt, i, info.length(), false)); + break; + } + d->fieldCacheIdx = field + 1; + } + return d->fieldCache[field]; +} + +bool QODBCResult::isNull(int field) +{ + Q_D(const QODBCResult); + if (field < 0 || field > d->fieldCache.size()) + return true; + if (field <= d->fieldCacheIdx) { + // since there is no good way to find out whether the value is NULL + // without fetching the field we'll fetch it here. + // (data() also sets the NULL flag) + data(field); + } + return d->fieldCache.at(field).isNull(); +} + +int QODBCResult::size() +{ + return -1; +} + +int QODBCResult::numRowsAffected() +{ + Q_D(QODBCResult); + SQLLEN affectedRowCount = 0; + SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount); + if (r == SQL_SUCCESS) + return affectedRowCount; + else + qSqlWarning(QLatin1String("QODBCResult::numRowsAffected: Unable to count affected rows"), d); + return -1; +} + +bool QODBCResult::prepare(const QString& query) +{ + Q_D(QODBCResult); + setActive(false); + setAt(QSql::BeforeFirstRow); + SQLRETURN r; + + d->rInf.clear(); + if (d->hStmt && d->isStmtHandleValid()) { + r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to close statement"), d); + return false; + } + } + r = SQLAllocHandle(SQL_HANDLE_STMT, + d->dpDbc(), + &d->hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to allocate statement handle"), d); + return false; + } + + d->updateStmtHandleState(); + + if (isForwardOnly()) { + r = SQLSetStmtAttr(d->hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + } else { + r = SQLSetStmtAttr(d->hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_STATIC, + SQL_IS_UINTEGER); + } + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + 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)); + return false; + } + + r = SQLPrepare(d->hStmt, + toSQLTCHAR(query).data(), + (SQLINTEGER) query.length()); + + if (r != SQL_SUCCESS) { + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to prepare statement"), QSqlError::StatementError, d)); + return false; + } + return true; +} + +bool QODBCResult::exec() +{ + Q_D(QODBCResult); + setActive(false); + setAt(QSql::BeforeFirstRow); + d->rInf.clear(); + d->fieldCache.clear(); + d->fieldCacheIdx = 0; + + if (!d->hStmt) { + qSqlWarning(QLatin1String("QODBCResult::exec: No statement handle available"), d); + return false; + } + + if (isSelect()) + SQLCloseCursor(d->hStmt); + + QVector<QVariant>& values = boundValues(); + QVector<QByteArray> tmpStorage(values.count(), QByteArray()); // holds temporary buffers + QVarLengthArray<SQLLEN, 32> indicators(values.count()); + memset(indicators.data(), 0, indicators.size() * sizeof(SQLLEN)); + + // bind parameters - only positional binding allowed + int i; + SQLRETURN r; + for (i = 0; i < values.count(); ++i) { + if (bindValueType(i) & QSql::Out) + values[i].detach(); + const QVariant &val = values.at(i); + SQLLEN *ind = &indicators[i]; + if (val.isNull()) + *ind = SQL_NULL_DATA; + switch (val.type()) { + case QVariant::Date: { + QByteArray &ba = tmpStorage[i]; + ba.resize(sizeof(DATE_STRUCT)); + DATE_STRUCT *dt = (DATE_STRUCT *)const_cast<char *>(ba.constData()); + QDate qdt = val.toDate(); + dt->year = qdt.year(); + dt->month = qdt.month(); + dt->day = qdt.day(); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_DATE, + SQL_DATE, + 0, + 0, + (void *) dt, + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; } + case QVariant::Time: { + QByteArray &ba = tmpStorage[i]; + ba.resize(sizeof(TIME_STRUCT)); + TIME_STRUCT *dt = (TIME_STRUCT *)const_cast<char *>(ba.constData()); + QTime qdt = val.toTime(); + dt->hour = qdt.hour(); + dt->minute = qdt.minute(); + dt->second = qdt.second(); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_TIME, + SQL_TIME, + 0, + 0, + (void *) dt, + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; } + case QVariant::DateTime: { + QByteArray &ba = tmpStorage[i]; + ba.resize(sizeof(TIMESTAMP_STRUCT)); + TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)const_cast<char *>(ba.constData()); + QDateTime qdt = val.toDateTime(); + dt->year = qdt.date().year(); + dt->month = qdt.date().month(); + dt->day = qdt.date().day(); + dt->hour = qdt.time().hour(); + dt->minute = qdt.time().minute(); + dt->second = qdt.time().second(); + + int precision = d->drv_d_func()->datetime_precision - 20; // (20 includes a separating period) + if (precision <= 0) { + dt->fraction = 0; + } else { + dt->fraction = qdt.time().msec() * 1000000; + + // (How many leading digits do we want to keep? With SQL Server 2005, this should be 3: 123000000) + int keep = (int)qPow(10.0, 9 - qMin(9, precision)); + dt->fraction = (dt->fraction / keep) * keep; + } + + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_TIMESTAMP, + SQL_TIMESTAMP, + d->drv_d_func()->datetime_precision, + precision, + (void *) dt, + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; } + case QVariant::Int: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_SLONG, + SQL_INTEGER, + 0, + 0, + const_cast<void *>(val.constData()), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::UInt: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_ULONG, + SQL_NUMERIC, + 15, + 0, + const_cast<void *>(val.constData()), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::Double: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_DOUBLE, + SQL_DOUBLE, + 0, + 0, + const_cast<void *>(val.constData()), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::LongLong: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_SBIGINT, + SQL_BIGINT, + 0, + 0, + const_cast<void *>(val.constData()), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::ULongLong: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_UBIGINT, + SQL_BIGINT, + 0, + 0, + const_cast<void *>(val.constData()), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::ByteArray: + if (*ind != SQL_NULL_DATA) { + *ind = val.toByteArray().size(); + } + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_BINARY, + SQL_LONGVARBINARY, + val.toByteArray().size(), + 0, + const_cast<char *>(val.toByteArray().constData()), + val.toByteArray().size(), + ind); + break; + case QVariant::Bool: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_BIT, + SQL_BIT, + 0, + 0, + const_cast<void *>(val.constData()), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::String: + if (d->unicode) { + QByteArray &ba = tmpStorage[i]; + QString str = val.toString(); + if (*ind != SQL_NULL_DATA) + *ind = str.length() * sizeof(SQLTCHAR); + int strSize = str.length() * sizeof(SQLTCHAR); + + if (bindValueType(i) & QSql::Out) { + const QVarLengthArray<SQLTCHAR> a(toSQLTCHAR(str)); + ba = QByteArray((const char *)a.constData(), a.size() * sizeof(SQLTCHAR)); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_TCHAR, + strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, + 0, // god knows... don't change this! + 0, + ba.data(), + ba.size(), + ind); + break; + } + ba = QByteArray ((const char *)toSQLTCHAR(str).constData(), str.size()*sizeof(SQLTCHAR)); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_TCHAR, + strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, + strSize, + 0, + const_cast<char *>(ba.constData()), + ba.size(), + ind); + break; + } + else + { + QByteArray &str = tmpStorage[i]; + str = val.toString().toUtf8(); + if (*ind != SQL_NULL_DATA) + *ind = str.length(); + int strSize = str.length(); + + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_CHAR, + strSize > 254 ? SQL_LONGVARCHAR : SQL_VARCHAR, + strSize, + 0, + const_cast<char *>(str.constData()), + strSize, + ind); + break; + } + // fall through + default: { + QByteArray &ba = tmpStorage[i]; + if (*ind != SQL_NULL_DATA) + *ind = ba.size(); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_BINARY, + SQL_VARBINARY, + ba.length() + 1, + 0, + const_cast<char *>(ba.constData()), + ba.length() + 1, + ind); + break; } + } + if (r != SQL_SUCCESS) { + qWarning() << "QODBCResult::exec: unable to bind variable:" << qODBCWarn(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); + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to execute statement"), QSqlError::StatementError, d)); + return false; + } + + 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) + setForwardOnly(isScrollable == SQL_NONSCROLLABLE); + + SQLSMALLINT count = 0; + SQLNumResultCols(d->hStmt, &count); + if (count) { + setSelect(true); + for (int i = 0; i < count; ++i) { + d->rInf.append(qMakeFieldInfo(d, i)); + } + d->fieldCache.resize(count); + } else { + setSelect(false); + } + setActive(true); + + + //get out parameters + if (!hasOutValues()) + return true; + + for (i = 0; i < values.count(); ++i) { + switch (values.at(i).type()) { + case QVariant::Date: { + DATE_STRUCT ds = *((DATE_STRUCT *)const_cast<char *>(tmpStorage.at(i).constData())); + values[i] = QVariant(QDate(ds.year, ds.month, ds.day)); + break; } + case QVariant::Time: { + TIME_STRUCT dt = *((TIME_STRUCT *)const_cast<char *>(tmpStorage.at(i).constData())); + values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second)); + break; } + case QVariant::DateTime: { + TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT*) + const_cast<char *>(tmpStorage.at(i).constData())); + values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day), + QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000))); + break; } + case QVariant::Bool: + case QVariant::Int: + case QVariant::UInt: + case QVariant::Double: + case QVariant::ByteArray: + case QVariant::LongLong: + case QVariant::ULongLong: + //nothing to do + break; + case QVariant::String: + if (d->unicode) { + if (bindValueType(i) & QSql::Out) { + const QByteArray &first = tmpStorage.at(i); + QVarLengthArray<SQLTCHAR> array; + array.append((const SQLTCHAR *)first.constData(), first.size()); + values[i] = fromSQLTCHAR(array, first.size()/sizeof(SQLTCHAR)); + } + break; + } + // fall through + default: { + if (bindValueType(i) & QSql::Out) + values[i] = tmpStorage.at(i); + break; } + } + if (indicators[i] == SQL_NULL_DATA) + values[i] = QVariant(values[i].type()); + } + return true; +} + +QSqlRecord QODBCResult::record() const +{ + Q_D(const QODBCResult); + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +QVariant QODBCResult::lastInsertId() const +{ + Q_D(const QODBCResult); + QString sql; + + switch (driver()->dbmsType()) { + case QSqlDriver::MSSqlServer: + case QSqlDriver::Sybase: + sql = QLatin1String("SELECT @@IDENTITY;"); + break; + case QSqlDriver::MySqlServer: + sql = QLatin1String("SELECT LAST_INSERT_ID();"); + break; + case QSqlDriver::PostgreSQL: + sql = QLatin1String("SELECT lastval();"); + break; + default: + break; + } + + if (!sql.isEmpty()) { + QSqlQuery qry(driver()->createResult()); + if (qry.exec(sql) && qry.next()) + return qry.value(0); + + qSqlWarning(QLatin1String("QODBCResult::lastInsertId: Unable to get lastInsertId"), d); + } else { + qSqlWarning(QLatin1String("QODBCResult::lastInsertId: not implemented for this DBMS"), d); + } + + return QVariant(); +} + +QVariant QODBCResult::handle() const +{ + Q_D(const QODBCResult); + return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt); +} + +bool QODBCResult::nextResult() +{ + Q_D(QODBCResult); + setActive(false); + setAt(QSql::BeforeFirstRow); + d->rInf.clear(); + d->fieldCache.clear(); + d->fieldCacheIdx = 0; + setSelect(false); + + 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; + } else { + if (r != SQL_NO_DATA) + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to fetch last"), QSqlError::ConnectionError, d)); + return false; + } + } + + SQLSMALLINT count = 0; + SQLNumResultCols(d->hStmt, &count); + if (count) { + setSelect(true); + for (int i = 0; i < count; ++i) { + d->rInf.append(qMakeFieldInfo(d, i)); + } + d->fieldCache.resize(count); + } else { + setSelect(false); + } + setActive(true); + + return true; +} + +void QODBCResult::virtual_hook(int id, void *data) +{ + QSqlResult::virtual_hook(id, data); +} + +void QODBCResult::detachFromResultSet() +{ + Q_D(QODBCResult); + if (d->hStmt) + SQLCloseCursor(d->hStmt); +} + +//////////////////////////////////////// + + +QODBCDriver::QODBCDriver(QObject *parent) + : QSqlDriver(*new QODBCDriverPrivate, parent) +{ +} + +QODBCDriver::QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject *parent) + : QSqlDriver(*new QODBCDriverPrivate, parent) +{ + Q_D(QODBCDriver); + d->hEnv = env; + d->hDbc = con; + if (env && con) { + setOpen(true); + setOpenError(false); + } +} + +QODBCDriver::~QODBCDriver() +{ + cleanup(); +} + +bool QODBCDriver::hasFeature(DriverFeature f) const +{ + Q_D(const QODBCDriver); + switch (f) { + case Transactions: { + if (!d->hDbc) + return false; + SQLUSMALLINT txn; + SQLSMALLINT t; + int r = SQLGetInfo(d->hDbc, + (SQLUSMALLINT)SQL_TXN_CAPABLE, + &txn, + sizeof(txn), + &t); + if (r != SQL_SUCCESS || txn == SQL_TC_NONE) + return false; + else + return true; + } + case Unicode: + return d->unicode; + case PreparedQueries: + case PositionalPlaceholders: + case FinishQuery: + case LowPrecisionNumbers: + return true; + case QuerySize: + case NamedPlaceholders: + case BatchOperations: + case SimpleLocking: + case EventNotifications: + case CancelQuery: + return false; + case LastInsertId: + return (d->dbmsType == MSSqlServer) + || (d->dbmsType == Sybase) + || (d->dbmsType == MySqlServer) + || (d->dbmsType == PostgreSQL); + case MultipleResultSets: + return d->hasMultiResultSets; + case BLOB: { + if (d->dbmsType == MySqlServer) + return true; + else + return false; + } + } + return false; +} + +bool QODBCDriver::open(const QString & db, + const QString & user, + const QString & password, + const QString &, + int, + const QString& connOpts) +{ + Q_D(QODBCDriver); + if (isOpen()) + close(); + SQLRETURN r; + r = SQLAllocHandle(SQL_HANDLE_ENV, + SQL_NULL_HANDLE, + &d->hEnv); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate environment"), d); + setOpenError(true); + return false; + } + r = SQLSetEnvAttr(d->hEnv, + SQL_ATTR_ODBC_VERSION, + (SQLPOINTER)qGetODBCVersion(connOpts), + SQL_IS_UINTEGER); + r = SQLAllocHandle(SQL_HANDLE_DBC, + d->hEnv, + &d->hDbc); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate connection"), d); + setOpenError(true); + cleanup(); + return false; + } + + if (!d->setConnectionOptions(connOpts)) { + cleanup(); + return false; + } + + // Create the connection string + QString connQStr; + // support the "DRIVER={SQL SERVER};SERVER=blah" syntax + if (db.contains(QLatin1String(".dsn"), Qt::CaseInsensitive)) + connQStr = QLatin1String("FILEDSN=") + db; + else if (db.contains(QLatin1String("DRIVER="), Qt::CaseInsensitive) + || db.contains(QLatin1String("SERVER="), Qt::CaseInsensitive)) + connQStr = db; + else + connQStr = QLatin1String("DSN=") + db; + + if (!user.isEmpty()) + connQStr += QLatin1String(";UID=") + user; + if (!password.isEmpty()) + connQStr += QLatin1String(";PWD=") + password; + + SQLSMALLINT cb; + QVarLengthArray<SQLTCHAR> connOut(1024); + memset(connOut.data(), 0, connOut.size() * sizeof(SQLTCHAR)); + r = SQLDriverConnect(d->hDbc, + NULL, + toSQLTCHAR(connQStr).data(), + (SQLSMALLINT)connQStr.length(), + connOut.data(), + 1024, + &cb, + /*SQL_DRIVER_NOPROMPT*/0); + + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d)); + setOpenError(true); + cleanup(); + return false; + } + + if (!d->checkDriver()) { + setLastError(qMakeError(tr("Unable to connect - Driver doesn't support all " + "functionality required"), QSqlError::ConnectionError, d)); + setOpenError(true); + cleanup(); + return false; + } + + d->checkUnicode(); + d->checkSchemaUsage(); + d->checkDBMS(); + d->checkHasSQLFetchScroll(); + d->checkHasMultiResults(); + d->checkDateTimePrecision(); + setOpen(true); + setOpenError(false); + if (d->dbmsType == MSSqlServer) { + QSqlQuery i(createResult()); + i.exec(QLatin1String("SET QUOTED_IDENTIFIER ON")); + } + return true; +} + +void QODBCDriver::close() +{ + cleanup(); + setOpen(false); + setOpenError(false); +} + +void QODBCDriver::cleanup() +{ + Q_D(QODBCDriver); + SQLRETURN r; + + if(d->hDbc) { + // Open statements/descriptors handles are automatically cleaned up by SQLDisconnect + if (isOpen()) { + r = SQLDisconnect(d->hDbc); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QODBCDriver::disconnect: Unable to disconnect datasource"), d); + else + d->disconnectCount++; + } + + r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free connection handle"), d); + d->hDbc = 0; + } + + if (d->hEnv) { + r = SQLFreeHandle(SQL_HANDLE_ENV, d->hEnv); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free environment handle"), d); + d->hEnv = 0; + } +} + +// checks whether the server can return char, varchar and longvarchar +// as two byte unicode characters +void QODBCDriverPrivate::checkUnicode() +{ + SQLRETURN r; + SQLUINTEGER fFunc; + + unicode = false; + r = SQLGetInfo(hDbc, + SQL_CONVERT_CHAR, + (SQLPOINTER)&fFunc, + sizeof(fFunc), + NULL); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WCHAR)) { + unicode = true; + return; + } + + r = SQLGetInfo(hDbc, + SQL_CONVERT_VARCHAR, + (SQLPOINTER)&fFunc, + sizeof(fFunc), + NULL); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WVARCHAR)) { + unicode = true; + return; + } + + r = SQLGetInfo(hDbc, + SQL_CONVERT_LONGVARCHAR, + (SQLPOINTER)&fFunc, + sizeof(fFunc), + NULL); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WLONGVARCHAR)) { + unicode = true; + return; + } + SQLHANDLE hStmt; + r = SQLAllocHandle(SQL_HANDLE_STMT, + hDbc, + &hStmt); + + r = SQLExecDirect(hStmt, toSQLTCHAR(QLatin1String("select 'test'")).data(), SQL_NTS); + if(r == SQL_SUCCESS) { + r = SQLFetch(hStmt); + if(r == SQL_SUCCESS) { + QVarLengthArray<SQLWCHAR> buffer(10); + r = SQLGetData(hStmt, 1, SQL_C_WCHAR, buffer.data(), buffer.size() * sizeof(SQLWCHAR), NULL); + if(r == SQL_SUCCESS && fromSQLTCHAR(buffer) == QLatin1String("test")) { + unicode = true; + } + } + } + r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); +} + +bool QODBCDriverPrivate::checkDriver() const +{ +#ifdef ODBC_CHECK_DRIVER + static const SQLUSMALLINT reqFunc[] = { + SQL_API_SQLDESCRIBECOL, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS, + SQL_API_SQLGETSTMTATTR, SQL_API_SQLGETDIAGREC, SQL_API_SQLEXECDIRECT, + SQL_API_SQLGETINFO, SQL_API_SQLTABLES, 0 + }; + + // these functions are optional + static const SQLUSMALLINT optFunc[] = { + SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT, 0 + }; + + SQLRETURN r; + SQLUSMALLINT sup; + + int i; + // check the required functions + for (i = 0; reqFunc[i] != 0; ++i) { + + r = SQLGetFunctions(hDbc, reqFunc[i], &sup); + + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCDriver::checkDriver: Cannot get list of supported functions"), this); + return false; + } + if (sup == SQL_FALSE) { + qWarning () << "QODBCDriver::open: Warning - Driver doesn't support all needed functionality (" << reqFunc[i] << + ").\nPlease look at the Qt SQL Module Driver documentation for more information."; + return false; + } + } + + // these functions are optional and just generate a warning + for (i = 0; optFunc[i] != 0; ++i) { + + r = SQLGetFunctions(hDbc, optFunc[i], &sup); + + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCDriver::checkDriver: Cannot get list of supported functions"), this); + return false; + } + if (sup == SQL_FALSE) { + qWarning() << "QODBCDriver::checkDriver: Warning - Driver doesn't support some non-critical functions (" << optFunc[i] << ')'; + return true; + } + } +#endif //ODBC_CHECK_DRIVER + + return true; +} + +void QODBCDriverPrivate::checkSchemaUsage() +{ + SQLRETURN r; + SQLUINTEGER val; + + r = SQLGetInfo(hDbc, + SQL_SCHEMA_USAGE, + (SQLPOINTER) &val, + sizeof(val), + NULL); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) + useSchema = (val != 0); +} + +void QODBCDriverPrivate::checkDBMS() +{ + SQLRETURN r; + QVarLengthArray<SQLTCHAR> 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) { + const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR)); + if (serverType.contains(QLatin1String("PostgreSQL"), Qt::CaseInsensitive)) + dbmsType = QSqlDriver::PostgreSQL; + else if (serverType.contains(QLatin1String("Oracle"), Qt::CaseInsensitive)) + dbmsType = QSqlDriver::Oracle; + else if (serverType.contains(QLatin1String("MySql"), Qt::CaseInsensitive)) + dbmsType = QSqlDriver::MySqlServer; + else if (serverType.contains(QLatin1String("Microsoft SQL Server"), Qt::CaseInsensitive)) + dbmsType = QSqlDriver::MSSqlServer; + else if (serverType.contains(QLatin1String("Sybase"), Qt::CaseInsensitive)) + dbmsType = QSqlDriver::Sybase; + } + r = SQLGetInfo(hDbc, + SQL_DRIVER_NAME, + serverString.data(), + SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)), + &t); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { + const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR)); + isFreeTDSDriver = serverType.contains(QLatin1String("tdsodbc"), Qt::CaseInsensitive); + unicode = unicode && !isFreeTDSDriver; + } +} + +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) { + hasSQLFetchScroll = false; + qWarning("QODBCDriver::checkHasSQLFetchScroll: Warning - Driver doesn't support scrollable result sets, use forward only mode for queries"); + } +} + +void QODBCDriverPrivate::checkHasMultiResults() +{ + QVarLengthArray<SQLTCHAR> 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(QLatin1Char('Y')); +} + +void QODBCDriverPrivate::checkDateTimePrecision() +{ + SQLINTEGER columnSize; + SQLHANDLE hStmt; + + SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt); + if (r != SQL_SUCCESS) { + 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) { + datetime_precision = (int)columnSize; + } + } + } + SQLFreeHandle(SQL_HANDLE_STMT, hStmt); +} + +QSqlResult *QODBCDriver::createResult() const +{ + return new QODBCResult(this); +} + +bool QODBCDriver::beginTransaction() +{ + Q_D(QODBCDriver); + if (!isOpen()) { + qWarning("QODBCDriver::beginTransaction: Database not open"); + return false; + } + SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF); + SQLRETURN r = SQLSetConnectAttr(d->hDbc, + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)size_t(ac), + sizeof(ac)); + if (r != SQL_SUCCESS) { + setLastError(qMakeError(tr("Unable to disable autocommit"), + QSqlError::TransactionError, d)); + return false; + } + return true; +} + +bool QODBCDriver::commitTransaction() +{ + Q_D(QODBCDriver); + if (!isOpen()) { + qWarning("QODBCDriver::commitTransaction: Database not open"); + return false; + } + SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC, + d->hDbc, + SQL_COMMIT); + if (r != SQL_SUCCESS) { + setLastError(qMakeError(tr("Unable to commit transaction"), + QSqlError::TransactionError, d)); + return false; + } + return endTrans(); +} + +bool QODBCDriver::rollbackTransaction() +{ + Q_D(QODBCDriver); + if (!isOpen()) { + qWarning("QODBCDriver::rollbackTransaction: Database not open"); + return false; + } + SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC, + d->hDbc, + SQL_ROLLBACK); + if (r != SQL_SUCCESS) { + setLastError(qMakeError(tr("Unable to rollback transaction"), + QSqlError::TransactionError, d)); + return false; + } + return endTrans(); +} + +bool QODBCDriver::endTrans() +{ + Q_D(QODBCDriver); + SQLUINTEGER ac(SQL_AUTOCOMMIT_ON); + SQLRETURN r = SQLSetConnectAttr(d->hDbc, + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)size_t(ac), + sizeof(ac)); + if (r != SQL_SUCCESS) { + setLastError(qMakeError(tr("Unable to enable autocommit"), QSqlError::TransactionError, d)); + return false; + } + return true; +} + +QStringList QODBCDriver::tables(QSql::TableType type) const +{ + Q_D(const QODBCDriver); + QStringList tl; + if (!isOpen()) + return tl; + SQLHANDLE hStmt; + + SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, + d->hDbc, + &hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCDriver::tables: Unable to allocate handle"), d); + return tl; + } + r = SQLSetStmtAttr(hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + QStringList tableType; + if (type & QSql::Tables) + tableType += QLatin1String("TABLE"); + if (type & QSql::Views) + tableType += QLatin1String("VIEW"); + if (type & QSql::SystemTables) + tableType += QLatin1String("SYSTEM TABLE"); + if (tableType.isEmpty()) + return tl; + + QString joinedTableTypeString = tableType.join(QLatin1Char(',')); + + r = SQLTables(hStmt, + NULL, + 0, + NULL, + 0, + NULL, + 0, + toSQLTCHAR(joinedTableTypeString).data(), + joinedTableTypeString.length() /* characters, not bytes */); + + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QODBCDriver::tables Unable to execute table list"), d); + + 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) << ")"; + return QStringList(); + } + + while (r == SQL_SUCCESS) { + QString fieldVal = qGetStringData(hStmt, 2, -1, false); + tl.append(fieldVal); + + if (d->hasSQLFetchScroll) + r = SQLFetchScroll(hStmt, + SQL_FETCH_NEXT, + 0); + else + r = SQLFetch(hStmt); + } + + r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); + if (r!= SQL_SUCCESS) + qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d); + return tl; +} + +QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const +{ + Q_D(const QODBCDriver); + QSqlIndex index(tablename); + if (!isOpen()) + return index; + bool usingSpecialColumns = false; + QSqlRecord rec = record(tablename); + + SQLHANDLE hStmt; + SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, + d->hDbc, + &hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to list primary key"), d); + return index; + } + QString catalog, schema, table; + const_cast<QODBCDriverPrivate*>(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); + r = SQLPrimaryKeys(hStmt, + catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), + catalog.length(), + schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), + schema.length(), + toSQLTCHAR(table).data(), + table.length() /* in characters, not in bytes */); + + // if the SQLPrimaryKeys() call does not succeed (e.g the driver + // does not support it) - try an alternative method to get hold of + // the primary index (e.g MS Access and FoxPro) + if (r != SQL_SUCCESS) { + r = SQLSpecialColumns(hStmt, + SQL_BEST_ROWID, + catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), + catalog.length(), + schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), + schema.length(), + toSQLTCHAR(table).data(), + table.length(), + SQL_SCOPE_CURROW, + SQL_NULLABLE); + + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to execute primary key list"), d); + } else { + usingSpecialColumns = true; + } + } + + if (d->hasSQLFetchScroll) + r = SQLFetchScroll(hStmt, + SQL_FETCH_NEXT, + 0); + else + r = SQLFetch(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); // column name + idxName = QString::number(fakeId++); // invent a fake index name + } else { + cName = qGetStringData(hStmt, 3, -1, d->unicode); // column name + idxName = qGetStringData(hStmt, 5, -1, d->unicode); // 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 = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); + if (r!= SQL_SUCCESS) + qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d); + return index; +} + +QSqlRecord QODBCDriver::record(const QString& tablename) const +{ + Q_D(const QODBCDriver); + QSqlRecord fil; + if (!isOpen()) + return fil; + + SQLHANDLE hStmt; + QString catalog, schema, table; + const_cast<QODBCDriverPrivate*>(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) { + qSqlWarning(QLatin1String("QODBCDriver::record: Unable to allocate handle"), d); + return fil; + } + r = SQLSetStmtAttr(hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + r = SQLColumns(hStmt, + catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), + catalog.length(), + schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), + schema.length(), + toSQLTCHAR(table).data(), + table.length(), + NULL, + 0); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QODBCDriver::record: Unable to execute column list"), d); + + if (d->hasSQLFetchScroll) + r = SQLFetchScroll(hStmt, + SQL_FETCH_NEXT, + 0); + else + r = SQLFetch(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); + } + + r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); + if (r!= SQL_SUCCESS) + qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") + QString::number(r), d); + + return fil; +} + +QString QODBCDriver::formatValue(const QSqlField &field, + bool trimStrings) const +{ + QString r; + if (field.isNull()) { + r = QLatin1String("NULL"); + } else if (field.type() == QVariant::DateTime) { + // 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(); + // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10 + r = QLatin1String("{ ts '") + + QString::number(dt.year()) + QLatin1Char('-') + + QString::number(dt.month()).rightJustified(2, QLatin1Char('0'), true) + + QLatin1Char('-') + + QString::number(dt.day()).rightJustified(2, QLatin1Char('0'), true) + + QLatin1Char(' ') + + tm.toString() + + QLatin1String("' }"); + } else + r = QLatin1String("NULL"); + } else if (field.type() == QVariant::ByteArray) { + QByteArray ba = field.value().toByteArray(); + QString res; + 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]); + } + r = QLatin1String("0x") + res; + } else { + r = QSqlDriver::formatValue(field, trimStrings); + } + return r; +} + +QVariant QODBCDriver::handle() const +{ + Q_D(const QODBCDriver); + return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hDbc); +} + +QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + Q_D(const QODBCDriver); + 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(QLatin1Char('.'), QString(quote)+QLatin1Char('.')+QString(quote)); + } + return res; +} + +bool QODBCDriver::isIdentifierEscaped(const QString &identifier, IdentifierType) const +{ + Q_D(const QODBCDriver); + QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar(); + return identifier.size() > 2 + && identifier.startsWith(quote) //left delimited + && identifier.endsWith(quote); //right delimited +} + +QT_END_NAMESPACE diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc_p.h b/src/plugins/sqldrivers/odbc/qsql_odbc_p.h new file mode 100644 index 0000000000..f4ce8bc243 --- /dev/null +++ b/src/plugins/sqldrivers/odbc/qsql_odbc_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_ODBC_H +#define QSQL_ODBC_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. +// + +#include <QtSql/qsqldriver.h> + +#if defined (Q_OS_WIN32) +#include <QtCore/qt_windows.h> +#endif + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_ODBC +#else +#define Q_EXPORT_SQLDRIVER_ODBC Q_SQL_EXPORT +#endif + +#ifdef Q_OS_UNIX +#define HAVE_LONG_LONG 1 // force UnixODBC NOT to fall back to a struct for BIGINTs +#endif + +#if defined(Q_CC_BOR) +// workaround for Borland to make sure that SQLBIGINT is defined +# define _MSC_VER 900 +#endif +#include <sql.h> +#if defined(Q_CC_BOR) +# undef _MSC_VER +#endif + +#include <sqlext.h> + +QT_BEGIN_NAMESPACE + +class QODBCDriverPrivate; + +class Q_EXPORT_SQLDRIVER_ODBC QODBCDriver : public QSqlDriver +{ + Q_DECLARE_PRIVATE(QODBCDriver) + Q_OBJECT + friend class QODBCResultPrivate; + +public: + explicit QODBCDriver(QObject *parent=0); + QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject * parent=0); + virtual ~QODBCDriver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + QSqlRecord record(const QString &tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString &tablename) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + QString formatValue(const QSqlField &field, + bool trimStrings) const Q_DECL_OVERRIDE; + bool open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port, + const QString &connOpts) Q_DECL_OVERRIDE; + + QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE; + + bool isIdentifierEscaped(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE; + +protected: + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; + +private: + bool endTrans(); + void cleanup(); +}; + +QT_END_NAMESPACE + +#endif // QSQL_ODBC_H diff --git a/src/plugins/sqldrivers/psql/main.cpp b/src/plugins/sqldrivers/psql/main.cpp index eae1c1c554..7657fcbfdf 100644 --- a/src/plugins/sqldrivers/psql/main.cpp +++ b/src/plugins/sqldrivers/psql/main.cpp @@ -39,7 +39,7 @@ #include <qsqldriverplugin.h> #include <qstringlist.h> -#include "../../../sql/drivers/psql/qsql_psql_p.h" +#include "qsql_psql_p.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/sqldrivers/psql/psql.pro b/src/plugins/sqldrivers/psql/psql.pro index 0fabe0e616..4a05266120 100644 --- a/src/plugins/sqldrivers/psql/psql.pro +++ b/src/plugins/sqldrivers/psql/psql.pro @@ -1,8 +1,17 @@ TARGET = qsqlpsql -SOURCES = main.cpp +HEADERS += $$PWD/qsql_psql_p.h +SOURCES += $$PWD/qsql_psql.cpp $$PWD/main.cpp + +unix|mingw { + LIBS += $$QMAKE_LIBS_PSQL + !contains(LIBS, .*pq.*):LIBS += -lpq + QMAKE_CXXFLAGS *= $$QMAKE_CFLAGS_PSQL +} else { + !contains(LIBS, .*pq.*):LIBS += -llibpq -lws2_32 -ladvapi32 +} + OTHER_FILES += psql.json -include(../../../sql/drivers/psql/qsql_psql.pri) PLUGIN_CLASS_NAME = QPSQLDriverPlugin include(../qsqldriverbase.pri) diff --git a/src/plugins/sqldrivers/psql/qsql_psql.cpp b/src/plugins/sqldrivers/psql/qsql_psql.cpp new file mode 100644 index 0000000000..fcf75af298 --- /dev/null +++ b/src/plugins/sqldrivers/psql/qsql_psql.cpp @@ -0,0 +1,1508 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_psql_p.h" + +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qregexp.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlrecord.h> +#include <qsqlquery.h> +#include <qsocketnotifier.h> +#include <qstringlist.h> +#include <qmutex.h> +#include <QtSql/private/qsqlresult_p.h> +#include <QtSql/private/qsqldriver_p.h> + +#include <libpq-fe.h> +#include <pg_config.h> + +#include <stdlib.h> +#include <math.h> +// below code taken from an example at http://www.gnu.org/software/hello/manual/autoconf/Function-Portability.html +#ifndef isnan + # define isnan(x) \ + (sizeof (x) == sizeof (long double) ? isnan_ld (x) \ + : sizeof (x) == sizeof (double) ? isnan_d (x) \ + : isnan_f (x)) + static inline int isnan_f (float x) { return x != x; } + static inline int isnan_d (double x) { return x != x; } + static inline int isnan_ld (long double x) { return x != x; } +#endif + +#ifndef isinf + # define isinf(x) \ + (sizeof (x) == sizeof (long double) ? isinf_ld (x) \ + : sizeof (x) == sizeof (double) ? isinf_d (x) \ + : isinf_f (x)) + static inline int isinf_f (float x) { return isnan (x - x); } + static inline int isinf_d (double x) { return isnan (x - x); } + static inline int isinf_ld (long double x) { return isnan (x - x); } +#endif + + +// workaround for postgres defining their OIDs in a private header file +#define QBOOLOID 16 +#define QINT8OID 20 +#define QINT2OID 21 +#define QINT4OID 23 +#define QNUMERICOID 1700 +#define QFLOAT4OID 700 +#define QFLOAT8OID 701 +#define QABSTIMEOID 702 +#define QRELTIMEOID 703 +#define QDATEOID 1082 +#define QTIMEOID 1083 +#define QTIMETZOID 1266 +#define QTIMESTAMPOID 1114 +#define QTIMESTAMPTZOID 1184 +#define QOIDOID 2278 +#define QBYTEAOID 17 +#define QREGPROCOID 24 +#define QXIDOID 28 +#define QCIDOID 29 + +#define QBITOID 1560 +#define QVARBITOID 1562 + +#define VARHDRSZ 4 + +/* This is a compile time switch - if PQfreemem is declared, the compiler will use that one, + otherwise it'll run in this template */ +template <typename T> +inline void PQfreemem(T *t, int = 0) { free(t); } + +Q_DECLARE_OPAQUE_POINTER(PGconn*) +Q_DECLARE_METATYPE(PGconn*) + +Q_DECLARE_OPAQUE_POINTER(PGresult*) +Q_DECLARE_METATYPE(PGresult*) + +QT_BEGIN_NAMESPACE + +inline void qPQfreemem(void *buffer) +{ + PQfreemem(buffer); +} + +class QPSQLResultPrivate; + +class QPSQLResult: public QSqlResult +{ + Q_DECLARE_PRIVATE(QPSQLResult) + +public: + QPSQLResult(const QPSQLDriver *db); + ~QPSQLResult(); + + QVariant handle() const Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; + +protected: + void cleanup(); + bool fetch(int i) Q_DECL_OVERRIDE; + bool fetchFirst() Q_DECL_OVERRIDE; + bool fetchLast() Q_DECL_OVERRIDE; + QVariant data(int i) Q_DECL_OVERRIDE; + bool isNull(int field) Q_DECL_OVERRIDE; + bool reset (const QString &query) Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + QVariant lastInsertId() const Q_DECL_OVERRIDE; + bool prepare(const QString &query) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; +}; + +class QPSQLDriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QPSQLDriver) +public: + QPSQLDriverPrivate() : QSqlDriverPrivate(), + connection(0), + isUtf8(false), + pro(QPSQLDriver::Version6), + sn(0), + pendingNotifyCheck(false), + hasBackslashEscape(false) + { dbmsType = QSqlDriver::PostgreSQL; } + + PGconn *connection; + bool isUtf8; + QPSQLDriver::Protocol pro; + QSocketNotifier *sn; + QStringList seid; + mutable bool pendingNotifyCheck; + bool hasBackslashEscape; + + void appendTables(QStringList &tl, QSqlQuery &t, QChar type); + PGresult * exec(const char * stmt) const; + PGresult * exec(const QString & stmt) const; + QPSQLDriver::Protocol getPSQLVersion(); + bool setEncodingUtf8(); + void setDatestyle(); + void detectBackslashEscape(); +}; + +void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type) +{ + QString query; + if (pro >= QPSQLDriver::Version73) { + query = QString::fromLatin1("select pg_class.relname, pg_namespace.nspname from pg_class " + "left join pg_namespace on (pg_class.relnamespace = pg_namespace.oid) " + "where (pg_class.relkind = '%1') and (pg_class.relname !~ '^Inv') " + "and (pg_class.relname !~ '^pg_') " + "and (pg_namespace.nspname != 'information_schema') ").arg(type); + } else { + query = QString::fromLatin1("select relname, null from pg_class where (relkind = '%1') " + "and (relname !~ '^Inv') " + "and (relname !~ '^pg_') ").arg(type); + } + t.exec(query); + while (t.next()) { + QString schema = t.value(1).toString(); + if (schema.isEmpty() || schema == QLatin1String("public")) + tl.append(t.value(0).toString()); + else + tl.append(t.value(0).toString().prepend(QLatin1Char('.')).prepend(schema)); + } +} + +PGresult * QPSQLDriverPrivate::exec(const char * stmt) const +{ + Q_Q(const QPSQLDriver); + PGresult *result = PQexec(connection, stmt); + if (seid.size() && !pendingNotifyCheck) { + pendingNotifyCheck = true; + QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), "_q_handleNotification", Qt::QueuedConnection, Q_ARG(int,0)); + } + return result; +} + +PGresult * QPSQLDriverPrivate::exec(const QString & stmt) const +{ + return exec(isUtf8 ? stmt.toUtf8().constData() : stmt.toLocal8Bit().constData()); +} + +class QPSQLResultPrivate : public QSqlResultPrivate +{ + Q_DECLARE_PUBLIC(QPSQLResult) +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QPSQLDriver); + QPSQLResultPrivate(QPSQLResult *q, const QPSQLDriver *drv) + : QSqlResultPrivate(q, drv), + result(0), + currentSize(-1), + preparedQueriesEnabled(false) + { } + + QString fieldSerial(int i) const Q_DECL_OVERRIDE { return QLatin1Char('$') + QString::number(i + 1); } + void deallocatePreparedStmt(); + + PGresult *result; + int currentSize; + bool preparedQueriesEnabled; + QString preparedStmtId; + + bool processResults(); +}; + +static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, + const QPSQLDriverPrivate *p, PGresult* result = 0) +{ + const char *s = PQerrorMessage(p->connection); + QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s); + QString errorCode; + if (result) { + errorCode = QString::fromLatin1(PQresultErrorField(result, PG_DIAG_SQLSTATE)); + msg += QString::fromLatin1("(%1)").arg(errorCode); + } + return QSqlError(QLatin1String("QPSQL: ") + err, msg, type, errorCode); +} + +bool QPSQLResultPrivate::processResults() +{ + Q_Q(QPSQLResult); + if (!result) + return false; + + int status = PQresultStatus(result); + if (status == PGRES_TUPLES_OK) { + q->setSelect(true); + q->setActive(true); + currentSize = PQntuples(result); + return true; + } else if (status == PGRES_COMMAND_OK) { + q->setSelect(false); + q->setActive(true); + currentSize = -1; + return true; + } + q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult", + "Unable to create query"), QSqlError::StatementError, drv_d_func(), result)); + return false; +} + +static QVariant::Type qDecodePSQLType(int t) +{ + QVariant::Type type = QVariant::Invalid; + switch (t) { + case QBOOLOID: + type = QVariant::Bool; + break; + case QINT8OID: + type = QVariant::LongLong; + break; + case QINT2OID: + case QINT4OID: + case QOIDOID: + case QREGPROCOID: + case QXIDOID: + case QCIDOID: + type = QVariant::Int; + break; + case QNUMERICOID: + case QFLOAT4OID: + case QFLOAT8OID: + type = QVariant::Double; + break; + case QABSTIMEOID: + case QRELTIMEOID: + case QDATEOID: + type = QVariant::Date; + break; + case QTIMEOID: + case QTIMETZOID: + type = QVariant::Time; + break; + case QTIMESTAMPOID: + case QTIMESTAMPTZOID: + type = QVariant::DateTime; + break; + case QBYTEAOID: + type = QVariant::ByteArray; + break; + default: + type = QVariant::String; + break; + } + return type; +} + +void QPSQLResultPrivate::deallocatePreparedStmt() +{ + const QString stmt = QLatin1String("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)); + PQclear(result); + preparedStmtId.clear(); +} + +QPSQLResult::QPSQLResult(const QPSQLDriver* db) + : QSqlResult(*new QPSQLResultPrivate(this, db)) +{ + Q_D(QPSQLResult); + d->preparedQueriesEnabled = db->hasFeature(QSqlDriver::PreparedQueries); +} + +QPSQLResult::~QPSQLResult() +{ + Q_D(QPSQLResult); + cleanup(); + + if (d->preparedQueriesEnabled && !d->preparedStmtId.isNull()) + d->deallocatePreparedStmt(); +} + +QVariant QPSQLResult::handle() const +{ + Q_D(const QPSQLResult); + return QVariant::fromValue(d->result); +} + +void QPSQLResult::cleanup() +{ + Q_D(QPSQLResult); + if (d->result) + PQclear(d->result); + d->result = 0; + setAt(QSql::BeforeFirstRow); + d->currentSize = -1; + setActive(false); +} + +bool QPSQLResult::fetch(int i) +{ + Q_D(const QPSQLResult); + if (!isActive()) + return false; + if (i < 0) + return false; + if (i >= d->currentSize) + return false; + if (at() == i) + return true; + setAt(i); + return true; +} + +bool QPSQLResult::fetchFirst() +{ + return fetch(0); +} + +bool QPSQLResult::fetchLast() +{ + Q_D(const QPSQLResult); + return fetch(PQntuples(d->result) - 1); +} + +QVariant QPSQLResult::data(int i) +{ + Q_D(const QPSQLResult); + if (i >= PQnfields(d->result)) { + qWarning("QPSQLResult::data: column %d out of range", i); + return QVariant(); + } + int ptype = PQftype(d->result, i); + QVariant::Type type = qDecodePSQLType(ptype); + const char *val = PQgetvalue(d->result, at(), i); + if (PQgetisnull(d->result, at(), i)) + return QVariant(type); + switch (type) { + case QVariant::Bool: + return QVariant((bool)(val[0] == 't')); + case QVariant::String: + return d->drv_d_func()->isUtf8 ? QString::fromUtf8(val) : QString::fromLatin1(val); + case QVariant::LongLong: + if (val[0] == '-') + return QString::fromLatin1(val).toLongLong(); + else + return QString::fromLatin1(val).toULongLong(); + case QVariant::Int: + return atoi(val); + case QVariant::Double: + if (ptype == QNUMERICOID) { + if (numericalPrecisionPolicy() != QSql::HighPrecision) { + QVariant retval; + bool convert; + double dbl=QString::fromLatin1(val).toDouble(&convert); + if (numericalPrecisionPolicy() == QSql::LowPrecisionInt64) + retval = (qlonglong)dbl; + else if (numericalPrecisionPolicy() == QSql::LowPrecisionInt32) + retval = (int)dbl; + else if (numericalPrecisionPolicy() == QSql::LowPrecisionDouble) + retval = dbl; + if (!convert) + return QVariant(); + return retval; + } + return QString::fromLatin1(val); + } + return QString::fromLatin1(val).toDouble(); + case QVariant::Date: + if (val[0] == '\0') { + return QVariant(QDate()); + } else { +#ifndef QT_NO_DATESTRING + return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate)); +#else + return QVariant(QString::fromLatin1(val)); +#endif + } + case QVariant::Time: { + const QString str = QString::fromLatin1(val); +#ifndef QT_NO_DATESTRING + if (str.isEmpty()) + return QVariant(QTime()); + else + return QVariant(QTime::fromString(str, Qt::ISODate)); +#else + return QVariant(str); +#endif + } + case QVariant::DateTime: { + QString dtval = QString::fromLatin1(val); +#ifndef QT_NO_DATESTRING + if (dtval.length() < 10) { + return QVariant(QDateTime()); + } else { + QChar sign = dtval[dtval.size() - 3]; + if (sign == QLatin1Char('-') || sign == QLatin1Char('+')) dtval += QLatin1String(":00"); + return QVariant(QDateTime::fromString(dtval, Qt::ISODate).toLocalTime()); + } +#else + return QVariant(dtval); +#endif + } + case QVariant::ByteArray: { + size_t len; + unsigned char *data = PQunescapeBytea((const unsigned char*)val, &len); + QByteArray ba(reinterpret_cast<const char *>(data), int(len)); + qPQfreemem(data); + return QVariant(ba); + } + default: + case QVariant::Invalid: + qWarning("QPSQLResult::data: unknown data type"); + } + return QVariant(); +} + +bool QPSQLResult::isNull(int field) +{ + Q_D(const QPSQLResult); + PQgetvalue(d->result, at(), field); + return PQgetisnull(d->result, at(), field); +} + +bool QPSQLResult::reset (const QString& query) +{ + Q_D(QPSQLResult); + cleanup(); + if (!driver()) + return false; + if (!driver()->isOpen() || driver()->isOpenError()) + return false; + d->result = d->drv_d_func()->exec(query); + return d->processResults(); +} + +int QPSQLResult::size() +{ + Q_D(const QPSQLResult); + return d->currentSize; +} + +int QPSQLResult::numRowsAffected() +{ + Q_D(const QPSQLResult); + return QString::fromLatin1(PQcmdTuples(d->result)).toInt(); +} + +QVariant QPSQLResult::lastInsertId() const +{ + Q_D(const QPSQLResult); + if (d->drv_d_func()->pro >= QPSQLDriver::Version81) { + QSqlQuery qry(driver()->createResult()); + // Most recent sequence value obtained from nextval + if (qry.exec(QLatin1String("SELECT lastval();")) && qry.next()) + return qry.value(0); + } else if (isActive()) { + Oid id = PQoidValue(d->result); + if (id != InvalidOid) + return QVariant(id); + } + return QVariant(); +} + +QSqlRecord QPSQLResult::record() const +{ + Q_D(const QPSQLResult); + QSqlRecord info; + if (!isActive() || !isSelect()) + return info; + + int count = PQnfields(d->result); + for (int i = 0; i < count; ++i) { + QSqlField f; + if (d->drv_d_func()->isUtf8) + f.setName(QString::fromUtf8(PQfname(d->result, i))); + else + f.setName(QString::fromLocal8Bit(PQfname(d->result, i))); + int ptype = PQftype(d->result, i); + f.setType(qDecodePSQLType(ptype)); + int len = PQfsize(d->result, i); + int precision = PQfmod(d->result, i); + + switch (ptype) { + case QTIMESTAMPOID: + case QTIMESTAMPTZOID: + precision = 3; + break; + + case QNUMERICOID: + if (precision != -1) { + len = (precision >> 16); + precision = ((precision - VARHDRSZ) & 0xffff); + } + break; + case QBITOID: + case QVARBITOID: + len = precision; + precision = -1; + break; + default: + if (len == -1 && precision >= VARHDRSZ) { + len = precision - VARHDRSZ; + precision = -1; + } + } + + f.setLength(len); + f.setPrecision(precision); + f.setSqlType(ptype); + info.append(f); + } + return info; +} + +void QPSQLResult::virtual_hook(int id, void *data) +{ + Q_ASSERT(data); + + QSqlResult::virtual_hook(id, data); +} + +static QString qCreateParamString(const QVector<QVariant> &boundValues, const QSqlDriver *driver) +{ + if (boundValues.isEmpty()) + return QString(); + + QString params; + QSqlField f; + for (int i = 0; i < boundValues.count(); ++i) { + const QVariant &val = boundValues.at(i); + + f.setType(val.type()); + if (val.isNull()) + f.clear(); + else + f.setValue(val); + if(!params.isNull()) + params.append(QLatin1String(", ")); + params.append(driver->formatValue(f)); + } + return params; +} + +Q_GLOBAL_STATIC(QMutex, qMutex) +QString qMakePreparedStmtId() +{ + qMutex()->lock(); + static unsigned int qPreparedStmtCount = 0; + QString id = QLatin1String("qpsqlpstmt_") + QString::number(++qPreparedStmtCount, 16); + qMutex()->unlock(); + return id; +} + +bool QPSQLResult::prepare(const QString &query) +{ + Q_D(QPSQLResult); + if (!d->preparedQueriesEnabled) + return QSqlResult::prepare(query); + + cleanup(); + + if (!d->preparedStmtId.isEmpty()) + d->deallocatePreparedStmt(); + + const QString stmtId = qMakePreparedStmtId(); + const QString stmt = QString::fromLatin1("PREPARE %1 AS ").arg(stmtId).append(d->positionalToNamedBinding(query)); + + PGresult *result = d->drv_d_func()->exec(stmt); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) { + setLastError(qMakeError(QCoreApplication::translate("QPSQLResult", + "Unable to prepare statement"), QSqlError::StatementError, d->drv_d_func(), result)); + PQclear(result); + d->preparedStmtId.clear(); + return false; + } + + PQclear(result); + d->preparedStmtId = stmtId; + return true; +} + +bool QPSQLResult::exec() +{ + Q_D(QPSQLResult); + if (!d->preparedQueriesEnabled) + return QSqlResult::exec(); + + cleanup(); + + QString stmt; + const QString params = qCreateParamString(boundValues(), driver()); + if (params.isEmpty()) + stmt = QString::fromLatin1("EXECUTE %1").arg(d->preparedStmtId); + else + stmt = QString::fromLatin1("EXECUTE %1 (%2)").arg(d->preparedStmtId).arg(params); + + d->result = d->drv_d_func()->exec(stmt); + + return d->processResults(); +} + +/////////////////////////////////////////////////////////////////// + +bool QPSQLDriverPrivate::setEncodingUtf8() +{ + PGresult* result = exec("SET CLIENT_ENCODING TO 'UNICODE'"); + int status = PQresultStatus(result); + PQclear(result); + return status == PGRES_COMMAND_OK; +} + +void QPSQLDriverPrivate::setDatestyle() +{ + PGresult* result = exec("SET DATESTYLE TO 'ISO'"); + int status = PQresultStatus(result); + if (status != PGRES_COMMAND_OK) + qWarning("%s", PQerrorMessage(connection)); + PQclear(result); +} + +void QPSQLDriverPrivate::detectBackslashEscape() +{ + // standard_conforming_strings option introduced in 8.2 + // http://www.postgresql.org/docs/8.2/static/runtime-config-compatible.html + if (pro < QPSQLDriver::Version82) { + hasBackslashEscape = true; + } else { + hasBackslashEscape = false; + PGresult* result = exec(QLatin1Literal("SELECT '\\\\' x")); + int status = PQresultStatus(result); + if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) + if (QString::fromLatin1(PQgetvalue(result, 0, 0)) == QLatin1Literal("\\")) + hasBackslashEscape = true; + PQclear(result); + } +} + +static QPSQLDriver::Protocol qMakePSQLVersion(int vMaj, int vMin) +{ + switch (vMaj) { + case 6: + return QPSQLDriver::Version6; + case 7: + { + switch (vMin) { + case 1: + return QPSQLDriver::Version71; + case 3: + return QPSQLDriver::Version73; + case 4: + return QPSQLDriver::Version74; + default: + return QPSQLDriver::Version7; + } + break; + } + case 8: + { + switch (vMin) { + case 1: + return QPSQLDriver::Version81; + case 2: + return QPSQLDriver::Version82; + case 3: + return QPSQLDriver::Version83; + case 4: + return QPSQLDriver::Version84; + default: + return QPSQLDriver::Version8; + } + break; + } + case 9: + return QPSQLDriver::Version9; + break; + default: + break; + } + return QPSQLDriver::VersionUnknown; +} + +QPSQLDriver::Protocol QPSQLDriverPrivate::getPSQLVersion() +{ + QPSQLDriver::Protocol serverVersion = QPSQLDriver::Version6; + PGresult* result = exec("select version()"); + int status = PQresultStatus(result); + if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) { + QString val = QString::fromLatin1(PQgetvalue(result, 0, 0)); + + QRegExp rx(QLatin1String("(\\d+)\\.(\\d+)")); + rx.setMinimal(true); // enforce non-greedy RegExp + + if (rx.indexIn(val) != -1) { + int vMaj = rx.cap(1).toInt(); + int vMin = rx.cap(2).toInt(); + serverVersion = qMakePSQLVersion(vMaj, vMin); +#if defined(PG_MAJORVERSION) + if (rx.indexIn(QLatin1String(PG_MAJORVERSION)) != -1) +#elif defined(PG_VERSION) + if (rx.indexIn(QLatin1String(PG_VERSION)) != -1) +#else + if (0) +#endif + { + vMaj = rx.cap(1).toInt(); + vMin = rx.cap(2).toInt(); + QPSQLDriver::Protocol clientVersion = qMakePSQLVersion(vMaj, vMin); + + if (serverVersion >= QPSQLDriver::Version9 && clientVersion < QPSQLDriver::Version9) { + //Client version before QPSQLDriver::Version9 only supports escape mode for bytea type, + //but bytea format is set to hex by default in PSQL 9 and above. So need to force the + //server use the old escape mode when connects to the new server with old client library. + PQclear(result); + result = exec("SET bytea_output=escape; "); + status = PQresultStatus(result); + } else 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."); + } + } + } + } + PQclear(result); + + //keep the old behavior unchanged + if (serverVersion == QPSQLDriver::VersionUnknown) + serverVersion = QPSQLDriver::Version6; + + if (serverVersion < QPSQLDriver::Version71) { + qWarning("This version of PostgreSQL is not supported and may not work."); + } + + return serverVersion; +} + +QPSQLDriver::QPSQLDriver(QObject *parent) + : QSqlDriver(*new QPSQLDriverPrivate, parent) +{ +} + +QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent) + : QSqlDriver(*new QPSQLDriverPrivate, parent) +{ + Q_D(QPSQLDriver); + d->connection = conn; + if (conn) { + d->pro = d->getPSQLVersion(); + d->detectBackslashEscape(); + setOpen(true); + setOpenError(false); + } +} + +QPSQLDriver::~QPSQLDriver() +{ + Q_D(QPSQLDriver); + if (d->connection) + PQfinish(d->connection); +} + +QVariant QPSQLDriver::handle() const +{ + Q_D(const QPSQLDriver); + return QVariant::fromValue(d->connection); +} + +bool QPSQLDriver::hasFeature(DriverFeature f) const +{ + Q_D(const QPSQLDriver); + switch (f) { + case Transactions: + case QuerySize: + case LastInsertId: + case LowPrecisionNumbers: + case EventNotifications: + return true; + case PreparedQueries: + case PositionalPlaceholders: + return d->pro >= QPSQLDriver::Version82; + case BatchOperations: + case NamedPlaceholders: + case SimpleLocking: + case FinishQuery: + case MultipleResultSets: + case CancelQuery: + return false; + case BLOB: + return d->pro >= QPSQLDriver::Version71; + case Unicode: + return d->isUtf8; + } + return false; +} + +/* + Quote a string for inclusion into the connection string + \ -> \\ + ' -> \' + surround string by single quotes + */ +static QString qQuote(QString s) +{ + s.replace(QLatin1Char('\\'), QLatin1String("\\\\")); + s.replace(QLatin1Char('\''), QLatin1String("\\'")); + s.append(QLatin1Char('\'')).prepend(QLatin1Char('\'')); + return s; +} + +bool QPSQLDriver::open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts) +{ + Q_D(QPSQLDriver); + if (isOpen()) + close(); + QString connectString; + if (!host.isEmpty()) + connectString.append(QLatin1String("host=")).append(qQuote(host)); + if (!db.isEmpty()) + connectString.append(QLatin1String(" dbname=")).append(qQuote(db)); + if (!user.isEmpty()) + connectString.append(QLatin1String(" user=")).append(qQuote(user)); + if (!password.isEmpty()) + connectString.append(QLatin1String(" password=")).append(qQuote(password)); + if (port != -1) + connectString.append(QLatin1String(" port=")).append(qQuote(QString::number(port))); + + // add any connect options - the server will handle error detection + if (!connOpts.isEmpty()) { + QString opt = connOpts; + opt.replace(QLatin1Char(';'), QLatin1Char(' '), Qt::CaseInsensitive); + connectString.append(QLatin1Char(' ')).append(opt); + } + + d->connection = PQconnectdb(connectString.toLocal8Bit().constData()); + if (PQstatus(d->connection) == CONNECTION_BAD) { + setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d)); + setOpenError(true); + PQfinish(d->connection); + d->connection = 0; + return false; + } + + d->pro = d->getPSQLVersion(); + d->detectBackslashEscape(); + d->isUtf8 = d->setEncodingUtf8(); + d->setDatestyle(); + + setOpen(true); + setOpenError(false); + return true; +} + +void QPSQLDriver::close() +{ + Q_D(QPSQLDriver); + if (isOpen()) { + + d->seid.clear(); + if (d->sn) { + disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int))); + delete d->sn; + d->sn = 0; + } + + if (d->connection) + PQfinish(d->connection); + d->connection = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QPSQLDriver::createResult() const +{ + return new QPSQLResult(this); +} + +bool QPSQLDriver::beginTransaction() +{ + Q_D(const QPSQLDriver); + if (!isOpen()) { + qWarning("QPSQLDriver::beginTransaction: Database not open"); + return false; + } + PGresult* res = d->exec("BEGIN"); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + setLastError(qMakeError(tr("Could not begin transaction"), + QSqlError::TransactionError, d, res)); + PQclear(res); + return false; + } + PQclear(res); + return true; +} + +bool QPSQLDriver::commitTransaction() +{ + Q_D(QPSQLDriver); + if (!isOpen()) { + qWarning("QPSQLDriver::commitTransaction: Database not open"); + return false; + } + PGresult* res = d->exec("COMMIT"); + + bool transaction_failed = false; + + // XXX + // This hack is used to tell if the transaction has succeeded for the protocol versions of + // PostgreSQL below. For 7.x and other protocol versions we are left in the dark. + // This hack can dissapear once there is an API to query this sort of information. + if (d->pro == QPSQLDriver::Version8 || + d->pro == QPSQLDriver::Version81 || + d->pro == QPSQLDriver::Version82 || + d->pro == QPSQLDriver::Version83 || + d->pro == QPSQLDriver::Version84 || + d->pro == QPSQLDriver::Version9) { + transaction_failed = qstrcmp(PQcmdStatus(res), "ROLLBACK") == 0; + } + + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK || transaction_failed) { + setLastError(qMakeError(tr("Could not commit transaction"), + QSqlError::TransactionError, d, res)); + PQclear(res); + return false; + } + PQclear(res); + return true; +} + +bool QPSQLDriver::rollbackTransaction() +{ + Q_D(QPSQLDriver); + if (!isOpen()) { + qWarning("QPSQLDriver::rollbackTransaction: Database not open"); + return false; + } + PGresult* res = d->exec("ROLLBACK"); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + setLastError(qMakeError(tr("Could not rollback transaction"), + QSqlError::TransactionError, d, res)); + PQclear(res); + return false; + } + PQclear(res); + return true; +} + +QStringList QPSQLDriver::tables(QSql::TableType type) const +{ + Q_D(const QPSQLDriver); + QStringList tl; + if (!isOpen()) + return tl; + QSqlQuery t(createResult()); + t.setForwardOnly(true); + + if (type & QSql::Tables) + const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, QLatin1Char('r')); + if (type & QSql::Views) + const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, QLatin1Char('v')); + if (type & QSql::SystemTables) { + t.exec(QLatin1String("select relname from pg_class where (relkind = 'r') " + "and (relname like 'pg_%') ")); + while (t.next()) + tl.append(t.value(0).toString()); + } + + return tl; +} + +static void qSplitTableName(QString &tablename, QString &schema) +{ + int dot = tablename.indexOf(QLatin1Char('.')); + if (dot == -1) + return; + schema = tablename.left(dot); + tablename = tablename.mid(dot + 1); +} + +QSqlIndex QPSQLDriver::primaryIndex(const QString& tablename) const +{ + Q_D(const QPSQLDriver); + QSqlIndex idx(tablename); + if (!isOpen()) + return idx; + QSqlQuery i(createResult()); + QString stmt; + + QString tbl = tablename; + QString schema; + qSplitTableName(tbl, schema); + + if (isIdentifierEscaped(tbl, QSqlDriver::TableName)) + tbl = stripDelimiters(tbl, QSqlDriver::TableName); + else + tbl = tbl.toLower(); + + if (isIdentifierEscaped(schema, QSqlDriver::TableName)) + schema = stripDelimiters(schema, QSqlDriver::TableName); + else + schema = schema.toLower(); + + switch(d->pro) { + case QPSQLDriver::Version6: + stmt = QLatin1String("select pg_att1.attname, int(pg_att1.atttypid), pg_cl.relname " + "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind " + "where pg_cl.relname = '%1_pkey' " + "and pg_cl.oid = pg_ind.indexrelid " + "and pg_att2.attrelid = pg_ind.indexrelid " + "and pg_att1.attrelid = pg_ind.indrelid " + "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] " + "order by pg_att2.attnum"); + break; + case QPSQLDriver::Version7: + case QPSQLDriver::Version71: + stmt = QLatin1String("select pg_att1.attname, pg_att1.atttypid::int, pg_cl.relname " + "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind " + "where pg_cl.relname = '%1_pkey' " + "and pg_cl.oid = pg_ind.indexrelid " + "and pg_att2.attrelid = pg_ind.indexrelid " + "and pg_att1.attrelid = pg_ind.indrelid " + "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] " + "order by pg_att2.attnum"); + break; + case QPSQLDriver::Version73: + case QPSQLDriver::Version74: + case QPSQLDriver::Version8: + case QPSQLDriver::Version81: + case QPSQLDriver::Version82: + case QPSQLDriver::Version83: + case QPSQLDriver::Version84: + case QPSQLDriver::Version9: + stmt = QLatin1String("SELECT pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_class.relname " + "FROM pg_attribute, pg_class " + "WHERE %1 pg_class.oid IN " + "(SELECT indexrelid FROM pg_index WHERE indisprimary = true AND indrelid IN " + " (SELECT oid FROM pg_class WHERE relname = '%2')) " + "AND pg_attribute.attrelid = pg_class.oid " + "AND pg_attribute.attisdropped = false " + "ORDER BY pg_attribute.attnum"); + if (schema.isEmpty()) + stmt = stmt.arg(QLatin1String("pg_table_is_visible(pg_class.oid) AND")); + else + stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from " + "pg_namespace where pg_namespace.nspname = '%1') AND ").arg(schema)); + break; + case QPSQLDriver::VersionUnknown: + qFatal("PSQL version is unknown"); + break; + } + + i.exec(stmt.arg(tbl)); + while (i.isActive() && i.next()) { + QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt())); + idx.append(f); + idx.setName(i.value(2).toString()); + } + return idx; +} + +QSqlRecord QPSQLDriver::record(const QString& tablename) const +{ + Q_D(const QPSQLDriver); + QSqlRecord info; + if (!isOpen()) + return info; + + QString tbl = tablename; + QString schema; + qSplitTableName(tbl, schema); + + if (isIdentifierEscaped(tbl, QSqlDriver::TableName)) + tbl = stripDelimiters(tbl, QSqlDriver::TableName); + else + tbl = tbl.toLower(); + + if (isIdentifierEscaped(schema, QSqlDriver::TableName)) + schema = stripDelimiters(schema, QSqlDriver::TableName); + else + schema = schema.toLower(); + + QString stmt; + switch(d->pro) { + case QPSQLDriver::Version6: + stmt = QLatin1String("select pg_attribute.attname, int(pg_attribute.atttypid), " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "int(pg_attribute.attrelid), pg_attribute.attnum " + "from pg_class, pg_attribute " + "where pg_class.relname = '%1' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid "); + break; + case QPSQLDriver::Version7: + stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "pg_attribute.attrelid::int, pg_attribute.attnum " + "from pg_class, pg_attribute " + "where pg_class.relname = '%1' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid "); + break; + case QPSQLDriver::Version71: + stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "pg_attrdef.adsrc " + "from pg_class, pg_attribute " + "left join pg_attrdef on (pg_attrdef.adrelid = " + "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) " + "where pg_class.relname = '%1' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid " + "order by pg_attribute.attnum "); + break; + case QPSQLDriver::Version73: + case QPSQLDriver::Version74: + case QPSQLDriver::Version8: + case QPSQLDriver::Version81: + case QPSQLDriver::Version82: + case QPSQLDriver::Version83: + case QPSQLDriver::Version84: + case QPSQLDriver::Version9: + stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "pg_attrdef.adsrc " + "from pg_class, pg_attribute " + "left join pg_attrdef on (pg_attrdef.adrelid = " + "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) " + "where %1 " + "and pg_class.relname = '%2' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid " + "and pg_attribute.attisdropped = false " + "order by pg_attribute.attnum "); + if (schema.isEmpty()) + stmt = stmt.arg(QLatin1String("pg_table_is_visible(pg_class.oid)")); + else + stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from " + "pg_namespace where pg_namespace.nspname = '%1')").arg(schema)); + break; + case QPSQLDriver::VersionUnknown: + qFatal("PSQL version is unknown"); + break; + } + + QSqlQuery query(createResult()); + query.exec(stmt.arg(tbl)); + if (d->pro >= QPSQLDriver::Version71) { + while (query.next()) { + int len = query.value(3).toInt(); + int precision = query.value(4).toInt(); + // swap length and precision if length == -1 + if (len == -1 && precision > -1) { + len = precision - 4; + precision = -1; + } + QString defVal = query.value(5).toString(); + if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\'')) + defVal = defVal.mid(1, defVal.length() - 2); + QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt())); + f.setRequired(query.value(2).toBool()); + f.setLength(len); + f.setPrecision(precision); + f.setDefaultValue(defVal); + f.setSqlType(query.value(1).toInt()); + info.append(f); + } + } else { + // Postgres < 7.1 cannot handle outer joins + while (query.next()) { + QString defVal; + QString stmt2 = QLatin1String("select pg_attrdef.adsrc from pg_attrdef where " + "pg_attrdef.adrelid = %1 and pg_attrdef.adnum = %2 "); + QSqlQuery query2(createResult()); + query2.exec(stmt2.arg(query.value(5).toInt()).arg(query.value(6).toInt())); + if (query2.isActive() && query2.next()) + defVal = query2.value(0).toString(); + if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\'')) + defVal = defVal.mid(1, defVal.length() - 2); + int len = query.value(3).toInt(); + int precision = query.value(4).toInt(); + // swap length and precision if length == -1 + if (len == -1 && precision > -1) { + len = precision - 4; + precision = -1; + } + QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt())); + f.setRequired(query.value(2).toBool()); + f.setLength(len); + f.setPrecision(precision); + f.setDefaultValue(defVal); + f.setSqlType(query.value(1).toInt()); + info.append(f); + } + } + + return info; +} + +template <class FloatType> +inline void assignSpecialPsqlFloatValue(FloatType val, QString *target) +{ + if (isnan(val)) { + *target = QLatin1String("'NaN'"); + } else { + switch (isinf(val)) { + case 1: + *target = QLatin1String("'Infinity'"); + break; + case -1: + *target = QLatin1String("'-Infinity'"); + break; + } + } +} + +QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + Q_D(const QPSQLDriver); + QString r; + if (field.isNull()) { + r = QLatin1String("NULL"); + } else { + switch (int(field.type())) { + case QVariant::DateTime: +#ifndef QT_NO_DATESTRING + if (field.value().toDateTime().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 = QLatin1String("TIMESTAMP WITH TIME ZONE ") + QLatin1Char('\'') + field.value().toDateTime().toUTC().toString(QLatin1String("yyyy-MM-ddThh:mm:ss.zzz")) + QLatin1Char('Z') + QLatin1Char('\''); + } else { + r = QLatin1String("NULL"); + } +#else + r = QLatin1String("NULL"); +#endif // QT_NO_DATESTRING + break; + case QVariant::Time: +#ifndef QT_NO_DATESTRING + if (field.value().toTime().isValid()) { + r = QLatin1Char('\'') + field.value().toTime().toString(QLatin1String("hh:mm:ss.zzz")) + QLatin1Char('\''); + } else +#endif + { + r = QLatin1String("NULL"); + } + break; + case QVariant::String: + r = QSqlDriver::formatValue(field, trimStrings); + if (d->hasBackslashEscape) + r.replace(QLatin1String("\\"), QLatin1String("\\\\")); + break; + case QVariant::Bool: + if (field.value().toBool()) + r = QLatin1String("TRUE"); + else + r = QLatin1String("FALSE"); + break; + case QVariant::ByteArray: { + QByteArray ba(field.value().toByteArray()); + size_t len; +#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 80200 + unsigned char *data = PQescapeByteaConn(d->connection, (const unsigned char*)ba.constData(), ba.size(), &len); +#else + unsigned char *data = PQescapeBytea((const unsigned char*)ba.constData(), ba.size(), &len); +#endif + r += QLatin1Char('\''); + r += QLatin1String((const char*)data); + r += QLatin1Char('\''); + qPQfreemem(data); + break; + } + case QMetaType::Float: + assignSpecialPsqlFloatValue(field.value().toFloat(), &r); + if (r.isEmpty()) + r = QSqlDriver::formatValue(field, trimStrings); + break; + case QVariant::Double: + assignSpecialPsqlFloatValue(field.value().toDouble(), &r); + if (r.isEmpty()) + r = QSqlDriver::formatValue(field, trimStrings); + break; + case QVariant::Uuid: + r = QLatin1Char('\'') + field.value().toString() + QLatin1Char('\''); + break; + default: + r = QSqlDriver::formatValue(field, trimStrings); + break; + } + } + return r; +} + +QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + QString res = identifier; + if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +bool QPSQLDriver::isOpen() const +{ + Q_D(const QPSQLDriver); + return PQstatus(d->connection) == CONNECTION_OK; +} + +QPSQLDriver::Protocol QPSQLDriver::protocol() const +{ + Q_D(const QPSQLDriver); + return d->pro; +} + +bool QPSQLDriver::subscribeToNotification(const QString &name) +{ + Q_D(QPSQLDriver); + if (!isOpen()) { + qWarning("QPSQLDriver::subscribeToNotificationImplementation: database not open."); + return false; + } + + if (d->seid.contains(name)) { + qWarning("QPSQLDriver::subscribeToNotificationImplementation: already subscribing to '%s'.", + qPrintable(name)); + return false; + } + + int socket = PQsocket(d->connection); + if (socket) { + // Add the name to the list of subscriptions here so that QSQLDriverPrivate::exec knows + // to check for notifications immediately after executing the LISTEN + d->seid << name; + QString query = QLatin1String("LISTEN ") + escapeIdentifier(name, QSqlDriver::TableName); + PGresult *result = d->exec(query); + if (PQresultStatus(result) != PGRES_COMMAND_OK) { + setLastError(qMakeError(tr("Unable to subscribe"), QSqlError::StatementError, d, result)); + PQclear(result); + return false; + } + PQclear(result); + + if (!d->sn) { + d->sn = new QSocketNotifier(socket, QSocketNotifier::Read); + connect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int))); + } + } else { + qWarning("QPSQLDriver::subscribeToNotificationImplementation: PQsocket didn't return a valid socket to listen on"); + return false; + } + + return true; +} + +bool QPSQLDriver::unsubscribeFromNotification(const QString &name) +{ + Q_D(QPSQLDriver); + if (!isOpen()) { + qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: database not open."); + return false; + } + + if (!d->seid.contains(name)) { + qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: not subscribed to '%s'.", + qPrintable(name)); + return false; + } + + QString query = QLatin1String("UNLISTEN ") + escapeIdentifier(name, QSqlDriver::TableName); + PGresult *result = d->exec(query); + if (PQresultStatus(result) != PGRES_COMMAND_OK) { + setLastError(qMakeError(tr("Unable to unsubscribe"), QSqlError::StatementError, d, result)); + PQclear(result); + return false; + } + PQclear(result); + + d->seid.removeAll(name); + + if (d->seid.isEmpty()) { + disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int))); + delete d->sn; + d->sn = 0; + } + + return true; +} + +QStringList QPSQLDriver::subscribedToNotifications() const +{ + Q_D(const QPSQLDriver); + return d->seid; +} + +void QPSQLDriver::_q_handleNotification(int) +{ + Q_D(QPSQLDriver); + d->pendingNotifyCheck = false; + PQconsumeInput(d->connection); + + PGnotify *notify = 0; + while((notify = PQnotifies(d->connection)) != 0) { + QString name(QLatin1String(notify->relname)); + if (d->seid.contains(name)) { + 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); +#endif + emit notification(name); + 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)); + + qPQfreemem(notify); + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/sqldrivers/psql/qsql_psql_p.h b/src/plugins/sqldrivers/psql/qsql_psql_p.h new file mode 100644 index 0000000000..8468b9af93 --- /dev/null +++ b/src/plugins/sqldrivers/psql/qsql_psql_p.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_PSQL_H +#define QSQL_PSQL_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. +// + +#include <QtSql/qsqldriver.h> + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_PSQL +#else +#define Q_EXPORT_SQLDRIVER_PSQL Q_SQL_EXPORT +#endif + +typedef struct pg_conn PGconn; +typedef struct pg_result PGresult; + +QT_BEGIN_NAMESPACE + +class QPSQLDriverPrivate; + +class Q_EXPORT_SQLDRIVER_PSQL QPSQLDriver : public QSqlDriver +{ + friend class QPSQLResultPrivate; + Q_DECLARE_PRIVATE(QPSQLDriver) + Q_OBJECT +public: + enum Protocol { + VersionUnknown = -1, + Version6 = 6, + Version7 = 7, + Version71 = 8, + Version73 = 9, + Version74 = 10, + Version8 = 11, + Version81 = 12, + Version82 = 13, + Version83 = 14, + Version84 = 15, + Version9 = 16 + }; + + explicit QPSQLDriver(QObject *parent=0); + explicit QPSQLDriver(PGconn *conn, QObject *parent=0); + ~QPSQLDriver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts) Q_DECL_OVERRIDE; + bool isOpen() const Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString& tablename) const Q_DECL_OVERRIDE; + QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE; + + Protocol protocol() const; + QVariant handle() const Q_DECL_OVERRIDE; + + QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE; + QString formatValue(const QSqlField &field, bool trimStrings) const Q_DECL_OVERRIDE; + + bool subscribeToNotification(const QString &name) Q_DECL_OVERRIDE; + bool unsubscribeFromNotification(const QString &name) Q_DECL_OVERRIDE; + QStringList subscribedToNotifications() const Q_DECL_OVERRIDE; + +protected: + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; + +private Q_SLOTS: + void _q_handleNotification(int); +}; + +QT_END_NAMESPACE + +#endif // QSQL_PSQL_H diff --git a/src/plugins/sqldrivers/sqldrivers.pro b/src/plugins/sqldrivers/sqldrivers.pro index 39c58d4f2b..37dbd2394e 100644 --- a/src/plugins/sqldrivers/sqldrivers.pro +++ b/src/plugins/sqldrivers/sqldrivers.pro @@ -1,11 +1,11 @@ TEMPLATE = subdirs -contains(sql-plugins, psql) : SUBDIRS += psql -contains(sql-plugins, mysql) : SUBDIRS += mysql -contains(sql-plugins, odbc) : SUBDIRS += odbc -contains(sql-plugins, tds) : SUBDIRS += tds -contains(sql-plugins, oci) : SUBDIRS += oci -contains(sql-plugins, db2) : SUBDIRS += db2 -contains(sql-plugins, sqlite) : SUBDIRS += sqlite -contains(sql-plugins, sqlite2) : SUBDIRS += sqlite2 -contains(sql-plugins, ibase) : SUBDIRS += ibase +contains(sql-drivers, psql) : SUBDIRS += psql +contains(sql-drivers, mysql) : SUBDIRS += mysql +contains(sql-drivers, odbc) : SUBDIRS += odbc +contains(sql-drivers, tds) : SUBDIRS += tds +contains(sql-drivers, oci) : SUBDIRS += oci +contains(sql-drivers, db2) : SUBDIRS += db2 +contains(sql-drivers, sqlite) : SUBDIRS += sqlite +contains(sql-drivers, sqlite2) : SUBDIRS += sqlite2 +contains(sql-drivers, ibase) : SUBDIRS += ibase diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp new file mode 100644 index 0000000000..ef4ef2e93c --- /dev/null +++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp @@ -0,0 +1,903 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_sqlite_p.h" + +#include <qcoreapplication.h> +#include <qdatetime.h> +#include <qvariant.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <QtSql/private/qsqlcachedresult_p.h> +#include <QtSql/private/qsqldriver_p.h> +#include <qstringlist.h> +#include <qvector.h> +#include <qdebug.h> + +#if defined Q_OS_WIN +# include <qt_windows.h> +#else +# include <unistd.h> +#endif + +#include <sqlite3.h> +#include <functional> + +Q_DECLARE_OPAQUE_POINTER(sqlite3*) +Q_DECLARE_METATYPE(sqlite3*) + +Q_DECLARE_OPAQUE_POINTER(sqlite3_stmt*) +Q_DECLARE_METATYPE(sqlite3_stmt*) + +QT_BEGIN_NAMESPACE + +static QString _q_escapeIdentifier(const QString &identifier) +{ + QString res = identifier; + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +static QVariant::Type qGetColumnType(const QString &tpName) +{ + const QString typeName = tpName.toLower(); + + if (typeName == QLatin1String("integer") + || typeName == QLatin1String("int")) + return QVariant::Int; + if (typeName == QLatin1String("double") + || typeName == QLatin1String("float") + || typeName == QLatin1String("real") + || typeName.startsWith(QLatin1String("numeric"))) + return QVariant::Double; + if (typeName == QLatin1String("blob")) + return QVariant::ByteArray; + if (typeName == QLatin1String("boolean") + || typeName == QLatin1String("bool")) + return QVariant::Bool; + return QVariant::String; +} + +static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, + int errorCode = -1) +{ + return QSqlError(descr, + QString(reinterpret_cast<const QChar *>(sqlite3_errmsg16(access))), + type, QString::number(errorCode)); +} + +class QSQLiteResultPrivate; + +class QSQLiteResult : public QSqlCachedResult +{ + Q_DECLARE_PRIVATE(QSQLiteResult) + friend class QSQLiteDriver; + +public: + explicit QSQLiteResult(const QSQLiteDriver* db); + ~QSQLiteResult(); + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache& row, int idx) Q_DECL_OVERRIDE; + bool reset(const QString &query) Q_DECL_OVERRIDE; + bool prepare(const QString &query) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QVariant lastInsertId() const Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + void detachFromResultSet() Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; +}; + +class QSQLiteDriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QSQLiteDriver) + +public: + inline QSQLiteDriverPrivate() : QSqlDriverPrivate(), access(0) { dbmsType = QSqlDriver::SQLite; } + sqlite3 *access; + QList <QSQLiteResult *> results; + QStringList notificationid; +}; + + +class QSQLiteResultPrivate: public QSqlCachedResultPrivate +{ + Q_DECLARE_PUBLIC(QSQLiteResult) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QSQLiteDriver) + QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv); + void cleanup(); + bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); + // initializes the recordInfo and the cache + void initColumns(bool emptyResultset); + void finalize(); + + sqlite3_stmt *stmt; + + bool skippedStatus; // the status of the fetchNext() that's skipped + bool skipRow; // skip the next fetchNext()? + QSqlRecord rInf; + QVector<QVariant> firstRow; +}; + +QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv) + : QSqlCachedResultPrivate(q, drv), + stmt(0), + skippedStatus(false), + skipRow(false) +{ +} + +void QSQLiteResultPrivate::cleanup() +{ + Q_Q(QSQLiteResult); + finalize(); + rInf.clear(); + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirstRow); + q->setActive(false); + q->cleanup(); +} + +void QSQLiteResultPrivate::finalize() +{ + if (!stmt) + return; + + sqlite3_finalize(stmt); + stmt = 0; +} + +void QSQLiteResultPrivate::initColumns(bool emptyResultset) +{ + Q_Q(QSQLiteResult); + int nCols = sqlite3_column_count(stmt); + if (nCols <= 0) + return; + + q->init(nCols); + + for (int i = 0; i < nCols; ++i) { + QString colName = QString(reinterpret_cast<const QChar *>( + sqlite3_column_name16(stmt, i)) + ).remove(QLatin1Char('"')); + + // must use typeName for resolving the type to match QSqliteDriver::record + QString typeName = QString(reinterpret_cast<const QChar *>( + sqlite3_column_decltype16(stmt, i))); + // sqlite3_column_type is documented to have undefined behavior if the result set is empty + int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); + + QVariant::Type fieldType; + + if (!typeName.isEmpty()) { + fieldType = qGetColumnType(typeName); + } else { + // Get the proper type for the field based on stp value + switch (stp) { + case SQLITE_INTEGER: + fieldType = QVariant::Int; + break; + case SQLITE_FLOAT: + fieldType = QVariant::Double; + break; + case SQLITE_BLOB: + fieldType = QVariant::ByteArray; + break; + case SQLITE_TEXT: + fieldType = QVariant::String; + break; + case SQLITE_NULL: + default: + fieldType = QVariant::Invalid; + break; + } + } + + QSqlField fld(colName, fieldType); + fld.setSqlType(stp); + rInf.append(fld); + } +} + +bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + Q_Q(QSQLiteResult); + int res; + int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + for(int i=0;i<firstRow.count();i++) + values[i]=firstRow[i]; + return skippedStatus; + } + skipRow = initialFetch; + + if(initialFetch) { + firstRow.clear(); + firstRow.resize(sqlite3_column_count(stmt)); + } + + if (!stmt) { + q->setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), + QCoreApplication::translate("QSQLiteResult", "No query"), QSqlError::ConnectionError)); + q->setAt(QSql::AfterLastRow); + return false; + } + res = sqlite3_step(stmt); + + switch(res) { + case SQLITE_ROW: + // check to see if should fill out columns + if (rInf.isEmpty()) + // must be first call. + initColumns(false); + if (idx < 0 && !initialFetch) + return true; + for (i = 0; i < rInf.count(); ++i) { + switch (sqlite3_column_type(stmt, i)) { + case SQLITE_BLOB: + values[i + idx] = QByteArray(static_cast<const char *>( + sqlite3_column_blob(stmt, i)), + sqlite3_column_bytes(stmt, i)); + break; + case SQLITE_INTEGER: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case SQLITE_FLOAT: + switch(q->numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + values[i + idx] = sqlite3_column_int(stmt, i); + break; + case QSql::LowPrecisionInt64: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case QSql::LowPrecisionDouble: + case QSql::HighPrecision: + default: + values[i + idx] = sqlite3_column_double(stmt, i); + break; + }; + break; + case SQLITE_NULL: + values[i + idx] = QVariant(QVariant::String); + break; + default: + values[i + idx] = QString(reinterpret_cast<const QChar *>( + sqlite3_column_text16(stmt, i)), + sqlite3_column_bytes16(stmt, i) / sizeof(QChar)); + break; + } + } + return true; + case SQLITE_DONE: + if (rInf.isEmpty()) + // must be first call. + initColumns(true); + q->setAt(QSql::AfterLastRow); + sqlite3_reset(stmt); + return false; + case SQLITE_CONSTRAINT: + case SQLITE_ERROR: + // SQLITE_ERROR is a generic error code and we must call sqlite3_reset() + // to get the specific error message. + res = sqlite3_reset(stmt); + q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + q->setAt(QSql::AfterLastRow); + return false; + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + // something wrong, don't get col info, but still return false + q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + sqlite3_reset(stmt); + q->setAt(QSql::AfterLastRow); + return false; + } + return false; +} + +QSQLiteResult::QSQLiteResult(const QSQLiteDriver* db) + : QSqlCachedResult(*new QSQLiteResultPrivate(this, db)) +{ + Q_D(QSQLiteResult); + const_cast<QSQLiteDriverPrivate*>(d->drv_d_func())->results.append(this); +} + +QSQLiteResult::~QSQLiteResult() +{ + Q_D(QSQLiteResult); + if (d->drv_d_func()) + const_cast<QSQLiteDriverPrivate*>(d->drv_d_func())->results.removeOne(this); + d->cleanup(); +} + +void QSQLiteResult::virtual_hook(int id, void *data) +{ + QSqlCachedResult::virtual_hook(id, data); +} + +bool QSQLiteResult::reset(const QString &query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QSQLiteResult::prepare(const QString &query) +{ + Q_D(QSQLiteResult); + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + + d->cleanup(); + + setSelect(false); + + const void *pzTail = NULL; + +#if (SQLITE_VERSION_NUMBER >= 3003011) + int res = sqlite3_prepare16_v2(d->drv_d_func()->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#else + int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#endif + + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } else if (pzTail && !QString(reinterpret_cast<const QChar *>(pzTail)).trimmed().isEmpty()) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute multiple statements at a time"), QSqlError::StatementError, SQLITE_MISUSE)); + d->finalize(); + return false; + } + return true; +} + +bool QSQLiteResult::exec() +{ + Q_D(QSQLiteResult); + const QVector<QVariant> values = boundValues(); + + d->skippedStatus = false; + d->skipRow = false; + d->rInf.clear(); + clearValues(); + setLastError(QSqlError()); + + int res = sqlite3_reset(d->stmt); + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to reset statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + int paramCount = sqlite3_bind_parameter_count(d->stmt); + if (paramCount == values.count()) { + for (int i = 0; i < paramCount; ++i) { + res = SQLITE_OK; + const QVariant value = values.at(i); + + if (value.isNull()) { + res = sqlite3_bind_null(d->stmt, i + 1); + } else { + switch (value.type()) { + case QVariant::ByteArray: { + const QByteArray *ba = static_cast<const QByteArray*>(value.constData()); + res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(), + ba->size(), SQLITE_STATIC); + break; } + case QVariant::Int: + case QVariant::Bool: + res = sqlite3_bind_int(d->stmt, i + 1, value.toInt()); + break; + case QVariant::Double: + res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble()); + break; + case QVariant::UInt: + case QVariant::LongLong: + res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong()); + break; + case QVariant::DateTime: { + const QDateTime dateTime = value.toDateTime(); + const QString str = dateTime.toString(QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz")); + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + str.size() * sizeof(ushort), SQLITE_TRANSIENT); + break; + } + case QVariant::Time: { + const QTime time = value.toTime(); + const QString str = time.toString(QStringLiteral("hh:mm:ss.zzz")); + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + str.size() * sizeof(ushort), SQLITE_TRANSIENT); + break; + } + case QVariant::String: { + // lifetime of string == lifetime of its qvariant + const QString *str = static_cast<const QString*>(value.constData()); + res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(), + (str->size()) * sizeof(QChar), SQLITE_STATIC); + break; } + default: { + QString str = value.toString(); + // SQLITE_TRANSIENT makes sure that sqlite buffers the data + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); + break; } + } + } + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to bind parameters"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + } + } else { + setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", + "Parameter count mismatch"), QString(), QSqlError::StatementError)); + return false; + } + d->skippedStatus = d->fetchNext(d->firstRow, 0, true); + if (lastError().isValid()) { + setSelect(false); + setActive(false); + return false; + } + setSelect(!d->rInf.isEmpty()); + setActive(true); + return true; +} + +bool QSQLiteResult::gotoNext(QSqlCachedResult::ValueCache& row, int idx) +{ + Q_D(QSQLiteResult); + return d->fetchNext(row, idx, false); +} + +int QSQLiteResult::size() +{ + return -1; +} + +int QSQLiteResult::numRowsAffected() +{ + Q_D(const QSQLiteResult); + return sqlite3_changes(d->drv_d_func()->access); +} + +QVariant QSQLiteResult::lastInsertId() const +{ + Q_D(const QSQLiteResult); + if (isActive()) { + qint64 id = sqlite3_last_insert_rowid(d->drv_d_func()->access); + if (id) + return id; + } + return QVariant(); +} + +QSqlRecord QSQLiteResult::record() const +{ + Q_D(const QSQLiteResult); + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +void QSQLiteResult::detachFromResultSet() +{ + Q_D(QSQLiteResult); + if (d->stmt) + sqlite3_reset(d->stmt); +} + +QVariant QSQLiteResult::handle() const +{ + Q_D(const QSQLiteResult); + return QVariant::fromValue(d->stmt); +} + +///////////////////////////////////////////////////////// + +QSQLiteDriver::QSQLiteDriver(QObject * parent) + : QSqlDriver(*new QSQLiteDriverPrivate, parent) +{ +} + +QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent) + : QSqlDriver(*new QSQLiteDriverPrivate, parent) +{ + Q_D(QSQLiteDriver); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLiteDriver::~QSQLiteDriver() +{ + close(); +} + +bool QSQLiteDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case BLOB: + case Transactions: + case Unicode: + case LastInsertId: + case PreparedQueries: + case PositionalPlaceholders: + case SimpleLocking: + case FinishQuery: + case LowPrecisionNumbers: + case EventNotifications: + return true; + case QuerySize: + case NamedPlaceholders: + case BatchOperations: + case MultipleResultSets: + case CancelQuery: + return false; + } + return false; +} + +/* + SQLite dbs have no user name, passwords, hosts or ports. + just file names. +*/ +bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts) +{ + Q_D(QSQLiteDriver); + if (isOpen()) + close(); + + + int timeOut = 5000; + bool sharedCache = false; + bool openReadOnlyOption = false; + bool openUriOption = false; + + const auto opts = conOpts.splitRef(QLatin1Char(';')); + for (auto option : opts) { + option = option.trimmed(); + if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT"))) { + option = option.mid(20).trimmed(); + if (option.startsWith(QLatin1Char('='))) { + bool ok; + const int nt = option.mid(1).trimmed().toInt(&ok); + if (ok) + timeOut = nt; + } + } else if (option == QLatin1String("QSQLITE_OPEN_READONLY")) { + openReadOnlyOption = true; + } else if (option == QLatin1String("QSQLITE_OPEN_URI")) { + openUriOption = true; + } else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) { + sharedCache = true; + } + } + + int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); + if (openUriOption) + openMode |= SQLITE_OPEN_URI; + + sqlite3_enable_shared_cache(sharedCache); + + if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL) == SQLITE_OK) { + sqlite3_busy_timeout(d->access, timeOut); + setOpen(true); + setOpenError(false); + return true; + } else { + if (d->access) { + sqlite3_close(d->access); + d->access = 0; + } + + setLastError(qMakeError(d->access, tr("Error opening database"), + QSqlError::ConnectionError)); + setOpenError(true); + return false; + } +} + +void QSQLiteDriver::close() +{ + Q_D(QSQLiteDriver); + if (isOpen()) { + for (QSQLiteResult *result : qAsConst(d->results)) + result->d_func()->finalize(); + + if (d->access && (d->notificationid.count() > 0)) { + d->notificationid.clear(); + sqlite3_update_hook(d->access, NULL, NULL); + } + + if (sqlite3_close(d->access) != SQLITE_OK) + setLastError(qMakeError(d->access, tr("Error closing database"), QSqlError::ConnectionError)); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QSQLiteDriver::createResult() const +{ + return new QSQLiteResult(this); +} + +bool QSQLiteDriver::beginTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("BEGIN"))) { + setLastError(QSqlError(tr("Unable to begin transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::commitTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("COMMIT"))) { + setLastError(QSqlError(tr("Unable to commit transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::rollbackTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("ROLLBACK"))) { + setLastError(QSqlError(tr("Unable to rollback transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +QStringList QSQLiteDriver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + + QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 " + "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"); + if ((type & QSql::Tables) && (type & QSql::Views)) + sql = sql.arg(QLatin1String("type='table' OR type='view'")); + else if (type & QSql::Tables) + sql = sql.arg(QLatin1String("type='table'")); + else if (type & QSql::Views) + sql = sql.arg(QLatin1String("type='view'")); + else + sql.clear(); + + if (!sql.isEmpty() && q.exec(sql)) { + while(q.next()) + res.append(q.value(0).toString()); + } + + if (type & QSql::SystemTables) { + // there are no internal tables beside this one: + res.append(QLatin1String("sqlite_master")); + } + + return res; +} + +static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) +{ + QString schema; + QString table(tableName); + int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); + if (indexOfSeparator > -1) { + schema = tableName.left(indexOfSeparator).append(QLatin1Char('.')); + table = tableName.mid(indexOfSeparator + 1); + } + q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(table) + QLatin1Char(')')); + + QSqlIndex ind; + while (q.next()) { + bool isPk = q.value(5).toInt(); + if (onlyPIndex && !isPk) + continue; + QString typeName = q.value(2).toString().toLower(); + QSqlField fld(q.value(1).toString(), qGetColumnType(typeName)); + if (isPk && (typeName == QLatin1String("integer"))) + // 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(q.value(4)); + ind.append(fld); + } + return ind; +} + +QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const +{ + 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); +} + +QSqlRecord QSQLiteDriver::record(const QString &tbl) const +{ + 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); +} + +QVariant QSQLiteDriver::handle() const +{ + Q_D(const QSQLiteDriver); + return QVariant::fromValue(d->access); +} + +QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const +{ + Q_UNUSED(type); + return _q_escapeIdentifier(identifier); +} + +static void handle_sqlite_callback(void *qobj,int aoperation, char const *adbname, char const *atablename, + sqlite3_int64 arowid) +{ + Q_UNUSED(aoperation); + Q_UNUSED(adbname); + QSQLiteDriver *driver = static_cast<QSQLiteDriver *>(qobj); + if (driver) { + QMetaObject::invokeMethod(driver, "handleNotification", Qt::QueuedConnection, + Q_ARG(QString, QString::fromUtf8(atablename)), Q_ARG(qint64, arowid)); + } +} + +bool QSQLiteDriver::subscribeToNotification(const QString &name) +{ + Q_D(QSQLiteDriver); + if (!isOpen()) { + qWarning("Database not open."); + return false; + } + + if (d->notificationid.contains(name)) { + qWarning("Already subscribing to '%s'.", qPrintable(name)); + return false; + } + + //sqlite supports only one notification callback, so only the first is registered + d->notificationid << name; + if (d->notificationid.count() == 1) + sqlite3_update_hook(d->access, &handle_sqlite_callback, reinterpret_cast<void *> (this)); + + return true; +} + +bool QSQLiteDriver::unsubscribeFromNotification(const QString &name) +{ + Q_D(QSQLiteDriver); + if (!isOpen()) { + qWarning("Database not open."); + return false; + } + + if (!d->notificationid.contains(name)) { + qWarning("Not subscribed to '%s'.", qPrintable(name)); + return false; + } + + d->notificationid.removeAll(name); + if (d->notificationid.isEmpty()) + sqlite3_update_hook(d->access, NULL, NULL); + + return true; +} + +QStringList QSQLiteDriver::subscribedToNotifications() const +{ + Q_D(const QSQLiteDriver); + return d->notificationid; +} + +void QSQLiteDriver::handleNotification(const QString &tableName, qint64 rowid) +{ + Q_D(const QSQLiteDriver); + if (d->notificationid.contains(tableName)) { + emit notification(tableName); + emit notification(tableName, QSqlDriver::UnknownSource, QVariant(rowid)); + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h new file mode 100644 index 0000000000..ca969a4b53 --- /dev/null +++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_SQLITE_H +#define QSQL_SQLITE_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. +// + +#include <QtSql/qsqldriver.h> + +struct sqlite3; + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_SQLITE +#else +#define Q_EXPORT_SQLDRIVER_SQLITE Q_SQL_EXPORT +#endif + +QT_BEGIN_NAMESPACE + +class QSqlResult; +class QSQLiteDriverPrivate; + +class Q_EXPORT_SQLDRIVER_SQLITE QSQLiteDriver : public QSqlDriver +{ + Q_DECLARE_PRIVATE(QSQLiteDriver) + Q_OBJECT + friend class QSQLiteResultPrivate; +public: + explicit QSQLiteDriver(QObject *parent = 0); + explicit QSQLiteDriver(sqlite3 *connection, QObject *parent = 0); + ~QSQLiteDriver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString & connOpts) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + + QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString &table) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE; + + bool subscribeToNotification(const QString &name) Q_DECL_OVERRIDE; + bool unsubscribeFromNotification(const QString &name) Q_DECL_OVERRIDE; + QStringList subscribedToNotifications() const Q_DECL_OVERRIDE; +private Q_SLOTS: + void handleNotification(const QString &tableName, qint64 rowid); +}; + +QT_END_NAMESPACE + +#endif // QSQL_SQLITE_H diff --git a/src/plugins/sqldrivers/sqlite/smain.cpp b/src/plugins/sqldrivers/sqlite/smain.cpp index 94b41c9878..2ad466a61e 100644 --- a/src/plugins/sqldrivers/sqlite/smain.cpp +++ b/src/plugins/sqldrivers/sqlite/smain.cpp @@ -39,7 +39,7 @@ #include <qsqldriverplugin.h> #include <qstringlist.h> -#include "../../../../src/sql/drivers/sqlite/qsql_sqlite_p.h" +#include "qsql_sqlite_p.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/sqldrivers/sqlite/sqlite.pro b/src/plugins/sqldrivers/sqlite/sqlite.pro index cc6d02cbe8..c98655f85c 100644 --- a/src/plugins/sqldrivers/sqlite/sqlite.pro +++ b/src/plugins/sqldrivers/sqlite/sqlite.pro @@ -1,8 +1,16 @@ TARGET = qsqlite -SOURCES = smain.cpp +HEADERS += $$PWD/qsql_sqlite_p.h +SOURCES += $$PWD/qsql_sqlite.cpp $$PWD/smain.cpp + +!system-sqlite:!contains(LIBS, .*sqlite3.*) { + include($$PWD/../../../3rdparty/sqlite.pri) +} else { + LIBS += $$QMAKE_LIBS_SQLITE + QMAKE_CXXFLAGS *= $$QMAKE_CFLAGS_SQLITE +} + OTHER_FILES += sqlite.json -include(../../../sql/drivers/sqlite/qsql_sqlite.pri) PLUGIN_CLASS_NAME = QSQLiteDriverPlugin include(../qsqldriverbase.pri) diff --git a/src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp b/src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp new file mode 100644 index 0000000000..67c24e4168 --- /dev/null +++ b/src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp @@ -0,0 +1,615 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_sqlite2_p.h" + +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qfile.h> +#include <qregexp.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <QtSql/private/qsqlcachedresult_p.h> +#include <QtSql/private/qsqldriver_p.h> +#include <qstringlist.h> +#include <qvector.h> + +#if !defined Q_OS_WIN +# include <unistd.h> +#endif +#include <sqlite.h> + +typedef struct sqlite_vm sqlite_vm; + +Q_DECLARE_OPAQUE_POINTER(sqlite_vm*) +Q_DECLARE_METATYPE(sqlite_vm*) + +Q_DECLARE_OPAQUE_POINTER(sqlite*) +Q_DECLARE_METATYPE(sqlite*) + +QT_BEGIN_NAMESPACE + +static QVariant::Type nameToType(const QString& typeName) +{ + QString tName = typeName.toUpper(); + if (tName.startsWith(QLatin1String("INT"))) + return QVariant::Int; + if (tName.startsWith(QLatin1String("FLOAT")) || tName.startsWith(QLatin1String("NUMERIC"))) + return QVariant::Double; + if (tName.startsWith(QLatin1String("BOOL"))) + return QVariant::Bool; + // SQLite is typeless - consider everything else as string + return QVariant::String; +} + +class QSQLite2DriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QSQLite2Driver) + +public: + QSQLite2DriverPrivate(); + sqlite *access; + bool utf8; +}; + +QSQLite2DriverPrivate::QSQLite2DriverPrivate() : QSqlDriverPrivate(), access(0) +{ + utf8 = (qstrcmp(sqlite_encoding, "UTF-8") == 0); + dbmsType = QSqlDriver::SQLite; +} + +class QSQLite2ResultPrivate; + +class QSQLite2Result : public QSqlCachedResult +{ + Q_DECLARE_PRIVATE(QSQLite2Result) + friend class QSQLite2Driver; + +public: + explicit QSQLite2Result(const QSQLite2Driver* db); + ~QSQLite2Result(); + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache &row, int idx) Q_DECL_OVERRIDE; + bool reset(const QString &query) Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + void detachFromResultSet() Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; +}; + +class QSQLite2ResultPrivate: public QSqlCachedResultPrivate +{ + Q_DECLARE_PUBLIC(QSQLite2Result) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QSQLite2Driver); + QSQLite2ResultPrivate(QSQLite2Result *q, const QSQLite2Driver *drv); + void cleanup(); + bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); + bool isSelect(); + // initializes the recordInfo and the cache + void init(const char **cnames, int numCols); + void finalize(); + + // and we have too keep our own struct for the data (sqlite works via + // callback. + const char *currentTail; + sqlite_vm *currentMachine; + + bool skippedStatus; // the status of the fetchNext() that's skipped + bool skipRow; // skip the next fetchNext()? + QSqlRecord rInf; + QVector<QVariant> firstRow; +}; + +QSQLite2ResultPrivate::QSQLite2ResultPrivate(QSQLite2Result *q, const QSQLite2Driver *drv) + : QSqlCachedResultPrivate(q, drv), + currentTail(0), + currentMachine(0), + skippedStatus(false), + skipRow(false) +{ +} + +void QSQLite2ResultPrivate::cleanup() +{ + Q_Q(QSQLite2Result); + finalize(); + rInf.clear(); + currentTail = 0; + currentMachine = 0; + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirstRow); + q->setActive(false); + q->cleanup(); +} + +void QSQLite2ResultPrivate::finalize() +{ + Q_Q(QSQLite2Result); + if (!currentMachine) + return; + + char* err = 0; + int res = sqlite_finalize(currentMachine, &err); + if (err) { + q->setLastError(QSqlError(QCoreApplication::translate("QSQLite2Result", + "Unable to fetch results"), QString::fromLatin1(err), + QSqlError::StatementError, res)); + sqlite_freemem(err); + } + currentMachine = 0; +} + +// called on first fetch +void QSQLite2ResultPrivate::init(const char **cnames, int numCols) +{ + Q_Q(QSQLite2Result); + if (!cnames) + return; + + rInf.clear(); + if (numCols <= 0) + return; + q->init(numCols); + + for (int i = 0; i < numCols; ++i) { + const char* lastDot = strrchr(cnames[i], '.'); + const char* fieldName = lastDot ? lastDot + 1 : cnames[i]; + + //remove quotations around the field name if any + QString fieldStr = QString::fromLatin1(fieldName); + QLatin1Char quote('\"'); + if ( fieldStr.length() > 2 && fieldStr.startsWith(quote) && fieldStr.endsWith(quote)) { + fieldStr = fieldStr.mid(1); + fieldStr.chop(1); + } + rInf.append(QSqlField(fieldStr, + nameToType(QString::fromLatin1(cnames[i+numCols])))); + } +} + +bool QSQLite2ResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + Q_Q(QSQLite2Result); + // may be caching. + const char **fvals; + const char **cnames; + int colNum; + int res; + int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + for(int i=0;i<firstRow.count(); i++) + values[i] = firstRow[i]; + return skippedStatus; + } + skipRow = initialFetch; + + if (!currentMachine) + return false; + + // keep trying while busy, wish I could implement this better. + while ((res = sqlite_step(currentMachine, &colNum, &fvals, &cnames)) == SQLITE_BUSY) { + // sleep instead requesting result again immidiately. +#if defined Q_OS_WIN + Sleep(1000); +#else + sleep(1); +#endif + } + + if(initialFetch) { + firstRow.clear(); + firstRow.resize(colNum); + } + + switch(res) { + case SQLITE_ROW: + // check to see if should fill out columns + if (rInf.isEmpty()) + // must be first call. + init(cnames, colNum); + if (!fvals) + return false; + if (idx < 0 && !initialFetch) + return true; + for (i = 0; i < colNum; ++i) + values[i + idx] = drv_d_func()->utf8 ? QString::fromUtf8(fvals[i]) : QString::fromLatin1(fvals[i]); + return true; + case SQLITE_DONE: + if (rInf.isEmpty()) + // must be first call. + init(cnames, colNum); + q->setAt(QSql::AfterLastRow); + return false; + case SQLITE_ERROR: + case SQLITE_MISUSE: + default: + // something wrong, don't get col info, but still return false + finalize(); // finalize to get the error message. + q->setAt(QSql::AfterLastRow); + return false; + } + return false; +} + +QSQLite2Result::QSQLite2Result(const QSQLite2Driver* db) + : QSqlCachedResult(*new QSQLite2ResultPrivate(this, db)) +{ +} + +QSQLite2Result::~QSQLite2Result() +{ + Q_D(QSQLite2Result); + d->cleanup(); +} + +void QSQLite2Result::virtual_hook(int id, void *data) +{ + QSqlCachedResult::virtual_hook(id, data); +} + +/* + Execute \a query. +*/ +bool QSQLite2Result::reset (const QString& query) +{ + Q_D(QSQLite2Result); + // this is where we build a query. + if (!driver()) + return false; + if (!driver()-> isOpen() || driver()->isOpenError()) + return false; + + d->cleanup(); + + // Um, ok. callback based so.... pass private static function for this. + setSelect(false); + char *err = 0; + int res = sqlite_compile(d->drv_d_func()->access, + d->drv_d_func()->utf8 ? query.toUtf8().constData() + : query.toLatin1().constData(), + &(d->currentTail), + &(d->currentMachine), + &err); + if (res != SQLITE_OK || err) { + setLastError(QSqlError(QCoreApplication::translate("QSQLite2Result", + "Unable to execute statement"), QString::fromLatin1(err), + QSqlError::StatementError, res)); + sqlite_freemem(err); + } + //if (*d->currentTail != '\000' then there is more sql to eval + if (!d->currentMachine) { + setActive(false); + return false; + } + // we have to fetch one row to find out about + // the structure of the result set + d->skippedStatus = d->fetchNext(d->firstRow, 0, true); + if (lastError().isValid()) { + setSelect(false); + setActive(false); + return false; + } + setSelect(!d->rInf.isEmpty()); + setActive(true); + return true; +} + +bool QSQLite2Result::gotoNext(QSqlCachedResult::ValueCache& row, int idx) +{ + Q_D(QSQLite2Result); + return d->fetchNext(row, idx, false); +} + +int QSQLite2Result::size() +{ + return -1; +} + +int QSQLite2Result::numRowsAffected() +{ + Q_D(QSQLite2Result); + return sqlite_changes(d->drv_d_func()->access); +} + +QSqlRecord QSQLite2Result::record() const +{ + Q_D(const QSQLite2Result); + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +void QSQLite2Result::detachFromResultSet() +{ + Q_D(QSQLite2Result); + d->finalize(); +} + +QVariant QSQLite2Result::handle() const +{ + Q_D(const QSQLite2Result); + return QVariant::fromValue(d->currentMachine); +} + +///////////////////////////////////////////////////////// + +QSQLite2Driver::QSQLite2Driver(QObject *parent) + : QSqlDriver(*new QSQLite2DriverPrivate, parent) +{ +} + +QSQLite2Driver::QSQLite2Driver(sqlite *connection, QObject *parent) + : QSqlDriver(*new QSQLite2DriverPrivate, parent) +{ + Q_D(QSQLite2Driver); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLite2Driver::~QSQLite2Driver() +{ +} + +bool QSQLite2Driver::hasFeature(DriverFeature f) const +{ + Q_D(const QSQLite2Driver); + switch (f) { + case Transactions: + case SimpleLocking: + return true; + case Unicode: + return d->utf8; + default: + return false; + } +} + +/* + SQLite dbs have no user name, passwords, hosts or ports. + just file names. +*/ +bool QSQLite2Driver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &) +{ + Q_D(QSQLite2Driver); + if (isOpen()) + close(); + + if (db.isEmpty()) + return false; + + char* err = 0; + d->access = sqlite_open(QFile::encodeName(db), 0, &err); + if (err) { + setLastError(QSqlError(tr("Error opening database"), QString::fromLatin1(err), + QSqlError::ConnectionError)); + sqlite_freemem(err); + err = 0; + } + + if (d->access) { + setOpen(true); + setOpenError(false); + return true; + } + setOpenError(true); + return false; +} + +void QSQLite2Driver::close() +{ + Q_D(QSQLite2Driver); + if (isOpen()) { + sqlite_close(d->access); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QSQLite2Driver::createResult() const +{ + return new QSQLite2Result(this); +} + +bool QSQLite2Driver::beginTransaction() +{ + Q_D(QSQLite2Driver); + if (!isOpen() || isOpenError()) + return false; + + char* err; + int res = sqlite_exec(d->access, "BEGIN", 0, this, &err); + + if (res == SQLITE_OK) + return true; + + setLastError(QSqlError(tr("Unable to begin transaction"), + QString::fromLatin1(err), QSqlError::TransactionError, res)); + sqlite_freemem(err); + return false; +} + +bool QSQLite2Driver::commitTransaction() +{ + Q_D(QSQLite2Driver); + if (!isOpen() || isOpenError()) + return false; + + char* err; + int res = sqlite_exec(d->access, "COMMIT", 0, this, &err); + + if (res == SQLITE_OK) + return true; + + setLastError(QSqlError(tr("Unable to commit transaction"), + QString::fromLatin1(err), QSqlError::TransactionError, res)); + sqlite_freemem(err); + return false; +} + +bool QSQLite2Driver::rollbackTransaction() +{ + Q_D(QSQLite2Driver); + if (!isOpen() || isOpenError()) + return false; + + char* err; + int res = sqlite_exec(d->access, "ROLLBACK", 0, this, &err); + + if (res == SQLITE_OK) + return true; + + setLastError(QSqlError(tr("Unable to rollback transaction"), + QString::fromLatin1(err), QSqlError::TransactionError, res)); + sqlite_freemem(err); + return false; +} + +QStringList QSQLite2Driver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + if ((type & QSql::Tables) && (type & QSql::Views)) + q.exec(QLatin1String("SELECT name FROM sqlite_master WHERE type='table' OR type='view'")); + else if (type & QSql::Tables) + q.exec(QLatin1String("SELECT name FROM sqlite_master WHERE type='table'")); + else if (type & QSql::Views) + q.exec(QLatin1String("SELECT name FROM sqlite_master WHERE type='view'")); + + if (q.isActive()) { + while(q.next()) + res.append(q.value(0).toString()); + } + + if (type & QSql::SystemTables) { + // there are no internal tables beside this one: + res.append(QLatin1String("sqlite_master")); + } + + return res; +} + +QSqlIndex QSQLite2Driver::primaryIndex(const QString &tblname) const +{ + QSqlRecord rec(record(tblname)); // expensive :( + + if (!isOpen()) + return QSqlIndex(); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + QString table = tblname; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + // finrst find a UNIQUE INDEX + q.exec(QLatin1String("PRAGMA index_list('") + table + QLatin1String("');")); + QString indexname; + while(q.next()) { + if (q.value(2).toInt()==1) { + indexname = q.value(1).toString(); + break; + } + } + if (indexname.isEmpty()) + return QSqlIndex(); + + q.exec(QLatin1String("PRAGMA index_info('") + indexname + QLatin1String("');")); + + QSqlIndex index(table, indexname); + while(q.next()) { + QString name = q.value(2).toString(); + QVariant::Type type = QVariant::Invalid; + if (rec.contains(name)) + type = rec.field(name).type(); + index.append(QSqlField(name, type)); + } + return index; +} + +QSqlRecord QSQLite2Driver::record(const QString &tbl) const +{ + if (!isOpen()) + return QSqlRecord(); + QString table = tbl; + if (isIdentifierEscaped(tbl, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + q.exec(QLatin1String("SELECT * FROM ") + tbl + QLatin1String(" LIMIT 1")); + return q.record(); +} + +QVariant QSQLite2Driver::handle() const +{ + Q_D(const QSQLite2Driver); + return QVariant::fromValue(d->access); +} + +QString QSQLite2Driver::escapeIdentifier(const QString &identifier, IdentifierType /*type*/) const +{ + QString res = identifier; + if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sqldrivers/sqlite2/qsql_sqlite2_p.h b/src/plugins/sqldrivers/sqlite2/qsql_sqlite2_p.h new file mode 100644 index 0000000000..83b248ec6a --- /dev/null +++ b/src/plugins/sqldrivers/sqlite2/qsql_sqlite2_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_SQLITE2_H +#define QSQL_SQLITE2_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. +// + +#include <QtSql/qsqldriver.h> + +#if defined (Q_OS_WIN32) +# include <QtCore/qt_windows.h> +#endif + +struct sqlite; + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_SQLITE2 +#else +#define Q_EXPORT_SQLDRIVER_SQLITE2 Q_SQL_EXPORT +#endif + +QT_BEGIN_NAMESPACE + +class QSqlResult; +class QSQLite2DriverPrivate; + +class Q_EXPORT_SQLDRIVER_SQLITE2 QSQLite2Driver : public QSqlDriver +{ + friend class QSQLite2ResultPrivate; + Q_DECLARE_PRIVATE(QSQLite2Driver) + Q_OBJECT +public: + explicit QSQLite2Driver(QObject *parent = 0); + explicit QSQLite2Driver(sqlite *connection, QObject *parent = 0); + ~QSQLite2Driver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + bool open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port, + const QString &connOpts) Q_DECL_OVERRIDE; + bool open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port) { return open(db, user, password, host, port, QString()); } + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + + QSqlRecord record(const QString &tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString &table) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif // QSQL_SQLITE2_H diff --git a/src/plugins/sqldrivers/sqlite2/smain.cpp b/src/plugins/sqldrivers/sqlite2/smain.cpp index 06b428c8ef..3a5734f8c9 100644 --- a/src/plugins/sqldrivers/sqlite2/smain.cpp +++ b/src/plugins/sqldrivers/sqlite2/smain.cpp @@ -39,7 +39,7 @@ #include <qsqldriverplugin.h> #include <qstringlist.h> -#include "../../../../src/sql/drivers/sqlite2/qsql_sqlite2_p.h" +#include "qsql_sqlite2_p.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/sqldrivers/sqlite2/sqlite2.pro b/src/plugins/sqldrivers/sqlite2/sqlite2.pro index d69afc119f..d0ab0eef41 100644 --- a/src/plugins/sqldrivers/sqlite2/sqlite2.pro +++ b/src/plugins/sqldrivers/sqlite2/sqlite2.pro @@ -1,8 +1,11 @@ TARGET = qsqlite2 -SOURCES = smain.cpp +HEADERS += $$PWD/qsql_sqlite2_p.h +SOURCES += $$PWD/qsql_sqlite2.cpp $$PWD/smain.cpp + +!contains(LIBS, .*sqlite.*):LIBS += -lsqlite + OTHER_FILES += sqlite2.json -include(../../../sql/drivers/sqlite2/qsql_sqlite2.pri) PLUGIN_CLASS_NAME = QSQLite2DriverPlugin include(../qsqldriverbase.pri) diff --git a/src/plugins/sqldrivers/tds/main.cpp b/src/plugins/sqldrivers/tds/main.cpp index ffb31ae179..4aa1444608 100644 --- a/src/plugins/sqldrivers/tds/main.cpp +++ b/src/plugins/sqldrivers/tds/main.cpp @@ -45,7 +45,7 @@ #define _WINSCARD_H_ #include <windows.h> #endif -#include "../../../sql/drivers/tds/qsql_tds_p.h" +#include "qsql_tds_p.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/sqldrivers/tds/qsql_tds.cpp b/src/plugins/sqldrivers/tds/qsql_tds.cpp new file mode 100644 index 0000000000..10d9fe7298 --- /dev/null +++ b/src/plugins/sqldrivers/tds/qsql_tds.cpp @@ -0,0 +1,881 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_tds_p.h" + +#include <qglobal.h> +#ifdef Q_OS_WIN32 // We assume that MS SQL Server is used. Set Q_USE_SYBASE to force Sybase. +// Conflicting declarations of LPCBYTE in sqlfront.h and winscard.h +#define _WINSCARD_H_ +#include <windows.h> +#else +#define Q_USE_SYBASE +#endif + +#include <qvariant.h> +#include <qdatetime.h> +#include <qhash.h> +#include <qregexp.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <QtSql/private/qsqlcachedresult_p.h> +#include <QtSql/private/qsqldriver_p.h> +#include <qstringlist.h> +#include <qvector.h> + +#include <stdlib.h> + +Q_DECLARE_OPAQUE_POINTER(LOGINREC*) +Q_DECLARE_OPAQUE_POINTER(DBPROCESS*) + +QT_BEGIN_NAMESPACE + +#ifdef DBNTWIN32 +#define QMSGHANDLE DBMSGHANDLE_PROC +#define QERRHANDLE DBERRHANDLE_PROC +#define QTDSCHAR SQLCHAR +#define QTDSDATETIME4 SQLDATETIM4 +#define QTDSDATETIME SQLDATETIME +#define QTDSDATETIME_N SQLDATETIMN +#define QTDSDECIMAL SQLDECIMAL +#define QTDSFLT4 SQLFLT4 +#define QTDSFLT8 SQLFLT8 +#define QTDSFLT8_N SQLFLTN +#define QTDSINT1 SQLINT1 +#define QTDSINT2 SQLINT2 +#define QTDSINT4 SQLINT4 +#define QTDSINT4_N SQLINTN +#define QTDSMONEY4 SQLMONEY4 +#define QTDSMONEY SQLMONEY +#define QTDSMONEY_N SQLMONEYN +#define QTDSNUMERIC SQLNUMERIC +#define QTDSTEXT SQLTEXT +#define QTDSVARCHAR SQLVARCHAR +#define QTDSBIT SQLBIT +#define QTDSBINARY SQLBINARY +#define QTDSVARBINARY SQLVARBINARY +#define QTDSIMAGE SQLIMAGE +#else +#define QMSGHANDLE MHANDLEFUNC +#define QERRHANDLE EHANDLEFUNC +#define QTDSCHAR SYBCHAR +#define QTDSDATETIME4 SYBDATETIME4 +#define QTDSDATETIME SYBDATETIME +#define QTDSDATETIME_N SYBDATETIMN +#define QTDSDECIMAL SYBDECIMAL +#define QTDSFLT8 SYBFLT8 +#define QTDSFLT8_N SYBFLTN +#define QTDSFLT4 SYBREAL +#define QTDSINT1 SYBINT1 +#define QTDSINT2 SYBINT2 +#define QTDSINT4 SYBINT4 +#define QTDSINT4_N SYBINTN +#define QTDSMONEY4 SYBMONEY4 +#define QTDSMONEY SYBMONEY +#define QTDSMONEY_N SYBMONEYN +#define QTDSNUMERIC SYBNUMERIC +#define QTDSTEXT SYBTEXT +#define QTDSVARCHAR SYBVARCHAR +#define QTDSBIT SYBBIT +#define QTDSBINARY SYBBINARY +#define QTDSVARBINARY SYBVARBINARY +#define QTDSIMAGE SYBIMAGE +// magic numbers not defined anywhere in Sybase headers +#define QTDSDECIMAL_2 55 +#define QTDSNUMERIC_2 63 +#endif //DBNTWIN32 + +#define TDS_CURSOR_SIZE 50 + +// workaround for FreeTDS +#ifndef CS_PUBLIC +#define CS_PUBLIC +#endif + +QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, int errNo = -1) +{ + return QSqlError(QLatin1String("QTDS: ") + err, QString(), type, errNo); +} + +class QTDSDriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QTDSDriver) + +public: + QTDSDriverPrivate() : QSqlDriverPrivate(), login(0), initialized(false) { dbmsType = QSqlDriver::Sybase; } + LOGINREC* login; // login information + QString hostName; + QString db; + bool initialized; +}; + +struct QTDSColumnData +{ + void *data; + DBINT nullbind; +}; +Q_DECLARE_TYPEINFO(QTDSColumnData, Q_MOVABLE_TYPE); + +class QTDSResultPrivate; + +class QTDSResult : public QSqlCachedResult +{ + Q_DECLARE_PRIVATE(QTDSResult) + +public: + explicit QTDSResult(const QTDSDriver* db); + ~QTDSResult(); + QVariant handle() const; + +protected: + void cleanup(); + bool reset(const QString &query) Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + bool gotoNext(QSqlCachedResult::ValueCache &values, int index) Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; +}; + +class QTDSResultPrivate: public QSqlCachedResultPrivate +{ + Q_DECLARE_PUBLIC(QTDSResult) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QTDSDriver) + QTDSResultPrivate(QTDSResult *q, const QTDSDriver *drv) + : QSqlCachedResultPrivate(q, drv), + login(0), + dbproc(0) {} + LOGINREC* login; // login information + DBPROCESS* dbproc; // connection from app to server + QSqlError lastError; + void addErrorMsg(QString& errMsg) { errorMsgs.append(errMsg); } + QString getErrorMsgs() { return errorMsgs.join(QLatin1String("\n")); } + void clearErrorMsgs() { errorMsgs.clear(); } + QVector<QTDSColumnData> buffer; + QSqlRecord rec; + +private: + QStringList errorMsgs; +}; + +typedef QHash<DBPROCESS *, QTDSResultPrivate *> QTDSErrorHash; +Q_GLOBAL_STATIC(QTDSErrorHash, errs) + +extern "C" { +static int CS_PUBLIC qTdsMsgHandler (DBPROCESS* dbproc, + DBINT msgno, + int msgstate, + int severity, + char* msgtext, + char* srvname, + char* /*procname*/, + int line) +{ + QTDSResultPrivate* p = errs()->value(dbproc); + + if (!p) { +// ### umm... temporary disabled since this throws a lot of warnings... +// qWarning("QTDSDriver warning (%d): [%s] from server [%s]", msgstate, msgtext, srvname); + return INT_CANCEL; + } + + if (severity > 0) { + QString errMsg = QString::fromLatin1("%1 (Msg %2, Level %3, State %4, Server %5, Line %6)") + .arg(QString::fromLatin1(msgtext)) + .arg(msgno) + .arg(severity) + .arg(msgstate) + .arg(QString::fromLatin1(srvname)) + .arg(line); + p->addErrorMsg(errMsg); + if (severity > 10) { + // Severe messages are really errors in the sense of lastError + errMsg = p->getErrorMsgs(); + p->lastError = qMakeError(errMsg, QSqlError::UnknownError, msgno); + p->clearErrorMsgs(); + } + } + + return INT_CANCEL; +} + +static int CS_PUBLIC qTdsErrHandler(DBPROCESS* dbproc, + int /*severity*/, + int dberr, + int /*oserr*/, + char* dberrstr, + char* oserrstr) +{ + QTDSResultPrivate* p = errs()->value(dbproc); + if (!p) { + qWarning("QTDSDriver error (%d): [%s] [%s]", dberr, dberrstr, oserrstr); + return INT_CANCEL; + } + /* + * If the process is dead or NULL and + * we are not in the middle of logging in... + */ + if((dbproc == NULL || DBDEAD(dbproc))) { + qWarning("QTDSDriver error (%d): [%s] [%s]", dberr, dberrstr, oserrstr); + return INT_CANCEL; + } + + + QString errMsg = QString::fromLatin1("%1 %2\n").arg(QLatin1String(dberrstr)).arg( + QLatin1String(oserrstr)); + errMsg += p->getErrorMsgs(); + p->lastError = qMakeError(errMsg, QSqlError::UnknownError, dberr); + p->clearErrorMsgs(); + + return INT_CANCEL ; +} + +} //extern "C" + + +QVariant::Type qDecodeTDSType(int type) +{ + QVariant::Type t = QVariant::Invalid; + switch (type) { + case QTDSCHAR: + case QTDSTEXT: + case QTDSVARCHAR: + t = QVariant::String; + break; + case QTDSINT1: + case QTDSINT2: + case QTDSINT4: + case QTDSINT4_N: + case QTDSBIT: + t = QVariant::Int; + break; + case QTDSFLT4: + case QTDSFLT8: + case QTDSFLT8_N: + case QTDSMONEY4: + case QTDSMONEY: + case QTDSDECIMAL: + case QTDSNUMERIC: +#ifdef QTDSNUMERIC_2 + case QTDSNUMERIC_2: +#endif +#ifdef QTDSDECIMAL_2 + case QTDSDECIMAL_2: +#endif + case QTDSMONEY_N: + t = QVariant::Double; + break; + case QTDSDATETIME4: + case QTDSDATETIME: + case QTDSDATETIME_N: + t = QVariant::DateTime; + break; + case QTDSBINARY: + case QTDSVARBINARY: + case QTDSIMAGE: + t = QVariant::ByteArray; + break; + default: + t = QVariant::Invalid; + break; + } + return t; +} + +QVariant::Type qFieldType(QTDSResultPrivate* d, int i) +{ + QVariant::Type type = qDecodeTDSType(dbcoltype(d->dbproc, i+1)); + return type; +} + + +QTDSResult::QTDSResult(const QTDSDriver* db) + : QSqlCachedResult(*new QTDSResultPrivate(this, db)) +{ + Q_D(QTDSResult); + d->login = d->drv_d_func()->login; + + d->dbproc = dbopen(d->login, const_cast<char*>(d->drv_d_func()->hostName.toLatin1().constData())); + if (!d->dbproc) + return; + if (dbuse(d->dbproc, const_cast<char*>(d->drv_d_func()->db.toLatin1().constData())) == FAIL) + return; + + // insert d in error handler dict + errs()->insert(d->dbproc, d); + dbcmd(d->dbproc, "set quoted_identifier on"); + dbsqlexec(d->dbproc); +} + +QTDSResult::~QTDSResult() +{ + Q_D(QTDSResult); + cleanup(); + if (d->dbproc) + dbclose(d->dbproc); + errs()->remove(d->dbproc); +} + +void QTDSResult::cleanup() +{ + Q_D(QTDSResult); + d->clearErrorMsgs(); + d->rec.clear(); + for (int i = 0; i < d->buffer.size(); ++i) + free(d->buffer.at(i).data); + d->buffer.clear(); + // "can" stands for "cancel"... very clever. + dbcanquery(d->dbproc); + dbfreebuf(d->dbproc); + + QSqlCachedResult::cleanup(); +} + +QVariant QTDSResult::handle() const +{ + Q_D(const QTDSResult); + return QVariant(qRegisterMetaType<DBPROCESS *>("DBPROCESS*"), &d->dbproc); +} + +static inline bool qIsNull(const QTDSColumnData &p) +{ + return p.nullbind == -1; +} + +bool QTDSResult::gotoNext(QSqlCachedResult::ValueCache &values, int index) +{ + Q_D(QTDSResult); + STATUS stat = dbnextrow(d->dbproc); + if (stat == NO_MORE_ROWS) { + setAt(QSql::AfterLastRow); + return false; + } + if ((stat == FAIL) || (stat == BUF_FULL)) { + setLastError(d->lastError); + return false; + } + + if (index < 0) + return true; + + for (int i = 0; i < d->rec.count(); ++i) { + int idx = index + i; + switch (d->rec.field(i).type()) { + case QVariant::DateTime: + if (qIsNull(d->buffer.at(i))) { + values[idx] = QVariant(QVariant::DateTime); + } else { + DBDATETIME *bdt = (DBDATETIME*) d->buffer.at(i).data; + QDate date = QDate::fromString(QLatin1String("1900-01-01"), Qt::ISODate); + QTime time = QTime::fromString(QLatin1String("00:00:00"), Qt::ISODate); + values[idx] = QDateTime(date.addDays(bdt->dtdays), time.addMSecs(int(bdt->dttime / 0.3))); + } + break; + case QVariant::Int: + if (qIsNull(d->buffer.at(i))) + values[idx] = QVariant(QVariant::Int); + else + values[idx] = *((int*)d->buffer.at(i).data); + break; + case QVariant::Double: + case QVariant::String: + if (qIsNull(d->buffer.at(i))) + values[idx] = QVariant(QVariant::String); + else + values[idx] = QString::fromLocal8Bit((const char*)d->buffer.at(i).data).trimmed(); + break; + case QVariant::ByteArray: { + if (qIsNull(d->buffer.at(i))) + values[idx] = QVariant(QVariant::ByteArray); + else + values[idx] = QByteArray((const char*)d->buffer.at(i).data); + break; + } + default: + // should never happen, and we already fired + // a warning while binding. + values[idx] = QVariant(); + break; + } + } + + return true; +} + +bool QTDSResult::reset (const QString& query) +{ + Q_D(QTDSResult); + cleanup(); + if (!driver() || !driver()-> isOpen() || driver()->isOpenError()) + return false; + setActive(false); + setAt(QSql::BeforeFirstRow); + if (dbcmd(d->dbproc, const_cast<char*>(query.toLocal8Bit().constData())) == FAIL) { + setLastError(d->lastError); + return false; + } + + if (dbsqlexec(d->dbproc) == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + if (dbresults(d->dbproc) != SUCCEED) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + + setSelect((DBCMDROW(d->dbproc) == SUCCEED)); // decide whether or not we are dealing with a SELECT query + int numCols = dbnumcols(d->dbproc); + if (numCols > 0) { + d->buffer.resize(numCols); + init(numCols); + } + for (int i = 0; i < numCols; ++i) { + int dbType = dbcoltype(d->dbproc, i+1); + QVariant::Type vType = qDecodeTDSType(dbType); + QSqlField f(QString::fromLatin1(dbcolname(d->dbproc, i+1)), vType); + f.setSqlType(dbType); + f.setLength(dbcollen(d->dbproc, i+1)); + d->rec.append(f); + + RETCODE ret = -1; + void* p = 0; + switch (vType) { + case QVariant::Int: + p = malloc(4); + ret = dbbind(d->dbproc, i+1, INTBIND, (DBINT) 4, (unsigned char *)p); + break; + case QVariant::Double: + // use string binding to prevent loss of precision + p = malloc(50); + ret = dbbind(d->dbproc, i+1, STRINGBIND, 50, (unsigned char *)p); + break; + case QVariant::String: + p = malloc(dbcollen(d->dbproc, i+1) + 1); + ret = dbbind(d->dbproc, i+1, STRINGBIND, DBINT(dbcollen(d->dbproc, i+1) + 1), (unsigned char *)p); + break; + case QVariant::DateTime: + p = malloc(8); + ret = dbbind(d->dbproc, i+1, DATETIMEBIND, (DBINT) 8, (unsigned char *)p); + break; + case QVariant::ByteArray: + p = malloc(dbcollen(d->dbproc, i+1) + 1); + ret = dbbind(d->dbproc, i+1, BINARYBIND, DBINT(dbcollen(d->dbproc, i+1) + 1), (unsigned char *)p); + break; + default: //don't bind the field since we do not support it + qWarning("QTDSResult::reset: Unsupported type for field \"%s\"", dbcolname(d->dbproc, i+1)); + break; + } + if (ret == SUCCEED) { + d->buffer[i].data = p; + ret = dbnullbind(d->dbproc, i+1, &d->buffer[i].nullbind); + } else { + d->buffer[i].data = 0; + d->buffer[i].nullbind = 0; + free(p); + } + if ((ret != SUCCEED) && (ret != -1)) { + setLastError(d->lastError); + return false; + } + } + + setActive(true); + return true; +} + +int QTDSResult::size() +{ + return -1; +} + +int QTDSResult::numRowsAffected() +{ + Q_D(const QTDSResult); +#ifdef DBNTWIN32 + if (dbiscount(d->dbproc)) { + return DBCOUNT(d->dbproc); + } + return -1; +#else + return DBCOUNT(d->dbproc); +#endif +} + +QSqlRecord QTDSResult::record() const +{ + Q_D(const QTDSResult); + return d->rec; +} + +/////////////////////////////////////////////////////////////////// + +QTDSDriver::QTDSDriver(QObject* parent) + : QSqlDriver(*new QTDSDriverPrivate, parent) +{ + init(); +} + +QTDSDriver::QTDSDriver(LOGINREC* rec, const QString& host, const QString &db, QObject* parent) + : QSqlDriver(*new QTDSDriverPrivate, parent) +{ + Q_D(QTDSDriver); + init(); + d->login = rec; + d->hostName = host; + d->db = db; + if (rec) { + setOpen(true); + setOpenError(false); + } +} + +QVariant QTDSDriver::handle() const +{ + Q_D(const QTDSDriver); + return QVariant(qRegisterMetaType<LOGINREC *>("LOGINREC*"), &d->login); +} + +void QTDSDriver::init() +{ + Q_D(QTDSDriver); + d->initialized = (dbinit() == SUCCEED); + // the following two code-lines will fail compilation on some FreeTDS versions + // just comment them out if you have FreeTDS (you won't get any errors and warnings then) + dberrhandle((QERRHANDLE)qTdsErrHandler); + dbmsghandle((QMSGHANDLE)qTdsMsgHandler); +} + +QTDSDriver::~QTDSDriver() +{ + dberrhandle(0); + dbmsghandle(0); + // dbexit also calls dbclose if necessary + dbexit(); +} + +bool QTDSDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case Transactions: + case QuerySize: + case Unicode: + case SimpleLocking: + case EventNotifications: + case MultipleResultSets: + return false; + case BLOB: + return true; + default: + return false; + } +} + +bool QTDSDriver::open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int /*port*/, + const QString& /*connOpts*/) +{ + Q_D(QTDSDriver); + if (isOpen()) + close(); + if (!d->initialized) { + setOpenError(true); + return false; + } + d->login = dblogin(); + if (!d->login) { + setOpenError(true); + return false; + } + DBSETLPWD(d->login, const_cast<char*>(password.toLocal8Bit().constData())); + DBSETLUSER(d->login, const_cast<char*>(user.toLocal8Bit().constData())); + + // Now, try to open and use the database. If this fails, return false. + DBPROCESS* dbproc; + + dbproc = dbopen(d->login, const_cast<char*>(host.toLatin1().constData())); + if (!dbproc) { + setLastError(qMakeError(tr("Unable to open connection"), QSqlError::ConnectionError, -1)); + setOpenError(true); + return false; + } + if (dbuse(dbproc, const_cast<char*>(db.toLatin1().constData())) == FAIL) { + setLastError(qMakeError(tr("Unable to use database"), QSqlError::ConnectionError, -1)); + setOpenError(true); + return false; + } + dbclose( dbproc ); + + setOpen(true); + setOpenError(false); + d->hostName = host; + d->db = db; + return true; +} + +void QTDSDriver::close() +{ + Q_D(QTDSDriver); + if (isOpen()) { +#ifdef Q_USE_SYBASE + dbloginfree(d->login); +#else + dbfreelogin(d->login); +#endif + d->login = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QTDSDriver::createResult() const +{ + return new QTDSResult(this); +} + +bool QTDSDriver::beginTransaction() +{ + return false; +/* + if (!isOpen()) { + qWarning("QTDSDriver::beginTransaction: Database not open"); + return false; + } + if (dbcmd(d->dbproc, "BEGIN TRANSACTION") == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + if (dbsqlexec(d->dbproc) == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + while(dbresults(d->dbproc) == NO_MORE_RESULTS) {} + dbfreebuf(d->dbproc); + inTransaction = true; + return true; +*/ +} + +bool QTDSDriver::commitTransaction() +{ + return false; +/* + if (!isOpen()) { + qWarning("QTDSDriver::commitTransaction: Database not open"); + return false; + } + if (dbcmd(d->dbproc, "COMMIT TRANSACTION") == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + if (dbsqlexec(d->dbproc) == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + while(dbresults(d->dbproc) == NO_MORE_RESULTS) {} + dbfreebuf(d->dbproc); + inTransaction = false; + return true; +*/ +} + +bool QTDSDriver::rollbackTransaction() +{ + return false; +/* + if (!isOpen()) { + qWarning("QTDSDriver::rollbackTransaction: Database not open"); + return false; + } + if (dbcmd(d->dbproc, "ROLLBACK TRANSACTION") == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + if (dbsqlexec(d->dbproc) == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + while(dbresults(d->dbproc) == NO_MORE_RESULTS) {} + dbfreebuf(d->dbproc); + inTransaction = false; + return true; +*/ +} + +QSqlRecord QTDSDriver::record(const QString& tablename) const +{ + QSqlRecord info; + if (!isOpen()) + return info; + QSqlQuery t(createResult()); + t.setForwardOnly(true); + + QString table = tablename; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QString stmt (QLatin1String("select name, type, length, prec from syscolumns " + "where id = (select id from sysobjects where name = '%1')")); + t.exec(stmt.arg(table)); + while (t.next()) { + QSqlField f(t.value(0).toString().simplified(), qDecodeTDSType(t.value(1).toInt())); + f.setLength(t.value(2).toInt()); + f.setPrecision(t.value(3).toInt()); + f.setSqlType(t.value(1).toInt()); + info.append(f); + } + return info; +} + +QStringList QTDSDriver::tables(QSql::TableType type) const +{ + QStringList list; + + if (!isOpen()) + return list; + + QStringList typeFilter; + + if (type & QSql::Tables) + typeFilter += QLatin1String("type='U'"); + if (type & QSql::SystemTables) + typeFilter += QLatin1String("type='S'"); + if (type & QSql::Views) + typeFilter += QLatin1String("type='V'"); + + if (typeFilter.isEmpty()) + return list; + + QSqlQuery t(createResult()); + t.setForwardOnly(true); + t.exec(QLatin1String("select name from sysobjects where ") + typeFilter.join(QLatin1String(" or "))); + while (t.next()) + list.append(t.value(0).toString().simplified()); + + return list; +} + +QString QTDSDriver::formatValue(const QSqlField &field, + bool trim) const +{ + QString r; + if (field.isNull()) + r = QLatin1String("NULL"); + else if (field.type() == QVariant::DateTime) { + if (field.value().toDateTime().isValid()){ + r = field.value().toDateTime().toString(QLatin1String("yyyyMMdd hh:mm:ss")); + r.prepend(QLatin1String("'")); + r.append(QLatin1String("'")); + } else + r = QLatin1String("NULL"); + } else if (field.type() == QVariant::ByteArray) { + QByteArray ba = field.value().toByteArray(); + QString res; + 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]); + } + r = QLatin1String("0x") + res; + } else { + r = QSqlDriver::formatValue(field, trim); + } + return r; +} + +QSqlIndex QTDSDriver::primaryIndex(const QString& tablename) const +{ + QSqlRecord rec = record(tablename); + + QString table = tablename; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlIndex idx(table); + if ((!isOpen()) || (table.isEmpty())) + return QSqlIndex(); + + QSqlQuery t(createResult()); + t.setForwardOnly(true); + t.exec(QString::fromLatin1("sp_helpindex '%1'").arg(table)); + if (t.next()) { + QStringList fNames = t.value(2).toString().simplified().split(QLatin1Char(',')); + QRegExp regx(QLatin1String("\\s*(\\S+)(?:\\s+(DESC|desc))?\\s*")); + for(QStringList::Iterator it = fNames.begin(); it != fNames.end(); ++it) { + regx.indexIn(*it); + QSqlField f(regx.cap(1), rec.field(regx.cap(1)).type()); + if (regx.cap(2).toLower() == QLatin1String("desc")) { + idx.append(f, true); + } else { + idx.append(f, false); + } + } + idx.setName(t.value(0).toString().simplified()); + } + return idx; +} + +QString QTDSDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const +{ + Q_UNUSED(type) + QString res = identifier; + if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sqldrivers/tds/qsql_tds_p.h b/src/plugins/sqldrivers/tds/qsql_tds_p.h new file mode 100644 index 0000000000..d0914455a2 --- /dev/null +++ b/src/plugins/sqldrivers/tds/qsql_tds_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_TDS_H +#define QSQL_TDS_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. +// + +#include <QtSql/qsqldriver.h> + +#ifdef Q_OS_WIN32 +#define WIN32_LEAN_AND_MEAN +#ifndef Q_USE_SYBASE +#define DBNTWIN32 // indicates 32bit windows dblib +#endif +#include <winsock2.h> +#include <QtCore/qt_windows.h> +#include <sqlfront.h> +#include <sqldb.h> +#define CS_PUBLIC +#else +#include <sybfront.h> +#include <sybdb.h> +#endif //Q_OS_WIN32 + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_TDS +#else +#define Q_EXPORT_SQLDRIVER_TDS Q_SQL_EXPORT +#endif + +QT_BEGIN_NAMESPACE + +class QSqlResult; +class QTDSDriverPrivate; + +class Q_EXPORT_SQLDRIVER_TDS QTDSDriver : public QSqlDriver +{ + Q_DECLARE_PRIVATE(QTDSDriver) + Q_OBJECT + friend class QTDSResultPrivate; +public: + explicit QTDSDriver(QObject* parent = 0); + QTDSDriver(LOGINREC* rec, const QString& host, const QString &db, QObject* parent = 0); + ~QTDSDriver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + bool open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port, + const QString &connOpts) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + QSqlRecord record(const QString &tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString &tablename) const Q_DECL_OVERRIDE; + + QString formatValue(const QSqlField &field, + bool trimStrings) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + + QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE; + +protected: + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; +private: + void init(); +}; + +QT_END_NAMESPACE + +#endif // QSQL_TDS_H diff --git a/src/plugins/sqldrivers/tds/tds.pro b/src/plugins/sqldrivers/tds/tds.pro index 88f4b7c451..b5d32ae5a8 100644 --- a/src/plugins/sqldrivers/tds/tds.pro +++ b/src/plugins/sqldrivers/tds/tds.pro @@ -1,8 +1,17 @@ TARGET = qsqltds -SOURCES = main.cpp +HEADERS += $$PWD/qsql_tds_p.h +SOURCES += $$PWD/qsql_tds.cpp $$PWD/main.cpp + +unix|mingw: { + LIBS += $$QMAKE_LIBS_TDS + !contains(LIBS, .*sybdb.*):LIBS += -lsybdb + QMAKE_CXXFLAGS *= $$QMAKE_CFLAGS_TDS +} else { + LIBS *= -lNTWDBLIB +} + OTHER_FILES += tds.json -include(../../../sql/drivers/tds/qsql_tds.pri) PLUGIN_CLASS_NAME = QTDSDriverPlugin include(../qsqldriverbase.pri) |