src/sql/drivers/psql/qsql_psql.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/sql/drivers/psql/qsql_psql.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,1320 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_psql.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 <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
+
+/* 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_METATYPE(PGconn*)
+Q_DECLARE_METATYPE(PGresult*)
+
+QT_BEGIN_NAMESPACE
+
+inline void qPQfreemem(void *buffer)
+{
+    PQfreemem(buffer);
+}
+
+class QPSQLDriverPrivate
+{
+public:
+    QPSQLDriverPrivate() : connection(0), isUtf8(false), pro(QPSQLDriver::Version6), sn(0) {}
+    PGconn *connection;
+    bool isUtf8;
+    QPSQLDriver::Protocol pro;
+    QSocketNotifier *sn;
+    QStringList seid;
+
+    void appendTables(QStringList &tl, QSqlQuery &t, QChar type);
+};
+
+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));
+    }
+}
+
+class QPSQLResultPrivate
+{
+public:
+    QPSQLResultPrivate(QPSQLResult *qq): q(qq), driver(0), result(0), currentSize(-1), preparedQueriesEnabled(false) {}
+
+    QPSQLResult *q;
+    const QPSQLDriverPrivate *driver;
+    PGresult *result;
+    int currentSize;
+    bool preparedQueriesEnabled;
+    QString preparedStmtId;
+
+    bool processResults();
+};
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
+                            const QPSQLDriverPrivate *p)
+{
+    const char *s = PQerrorMessage(p->connection);
+    QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
+    return QSqlError(QLatin1String("QPSQL: ") + err, msg, type);
+}
+
+bool QPSQLResultPrivate::processResults()
+{
+    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, driver));
+    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;
+}
+
+static void qDeallocatePreparedStmt(QPSQLResultPrivate *d)
+{
+    const QString stmt = QLatin1String("DEALLOCATE ") + d->preparedStmtId;
+    PGresult *result = PQexec(d->driver->connection,
+                              d->driver->isUtf8 ? stmt.toUtf8().constData()
+                                                : stmt.toLocal8Bit().constData());
+
+    if (PQresultStatus(result) != PGRES_COMMAND_OK)
+        qWarning("Unable to free statement: %s", PQerrorMessage(d->driver->connection));
+    PQclear(result);
+    d->preparedStmtId.clear();
+}
+
+QPSQLResult::QPSQLResult(const QPSQLDriver* db, const QPSQLDriverPrivate* p)
+    : QSqlResult(db)
+{
+    d = new QPSQLResultPrivate(this);
+    d->driver = p;
+    d->preparedQueriesEnabled = db->hasFeature(QSqlDriver::PreparedQueries);
+}
+
+QPSQLResult::~QPSQLResult()
+{
+    cleanup();
+
+    if (d->preparedQueriesEnabled && !d->preparedStmtId.isNull())
+        qDeallocatePreparedStmt(d);
+
+    delete d;
+}
+
+QVariant QPSQLResult::handle() const
+{
+    return qVariantFromValue(d->result);
+}
+
+void QPSQLResult::cleanup()
+{
+    if (d->result)
+        PQclear(d->result);
+    d->result = 0;
+    setAt(QSql::BeforeFirstRow);
+    d->currentSize = -1;
+    setActive(false);
+}
+
+bool QPSQLResult::fetch(int i)
+{
+    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()
+{
+    return fetch(PQntuples(d->result) - 1);
+}
+
+QVariant QPSQLResult::data(int i)
+{
+    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->driver->isUtf8 ? QString::fromUtf8(val) : QString::fromAscii(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::fromAscii(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::fromAscii(val);
+        }
+        return strtod(val, 0);
+    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());
+        if (str.at(str.length() - 3) == QLatin1Char('+') || str.at(str.length() - 3) == QLatin1Char('-'))
+            // strip the timezone
+            // TODO: fix this when timestamp support comes into QDateTime
+            return QVariant(QTime::fromString(str.left(str.length() - 3), Qt::ISODate));
+        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());
+        // remove the timezone
+        // TODO: fix this when timestamp support comes into QDateTime
+        if (dtval.at(dtval.length() - 3) == QLatin1Char('+') || dtval.at(dtval.length() - 3) == QLatin1Char('-'))
+            dtval.chop(3);
+        // milliseconds are sometimes returned with 2 digits only
+        if (dtval.at(dtval.length() - 3).isPunct())
+            dtval += QLatin1Char('0');
+        if (dtval.isEmpty())
+            return QVariant(QDateTime());
+        else
+            return QVariant(QDateTime::fromString(dtval, Qt::ISODate));
+#else
+        return QVariant(dtval);
+#endif
+    }
+    case QVariant::ByteArray: {
+        size_t len;
+        unsigned char *data = PQunescapeBytea((unsigned char*)val, &len);
+        QByteArray ba((const char*)data, len);
+        qPQfreemem(data);
+        return QVariant(ba);
+    }
+    default:
+    case QVariant::Invalid:
+        qWarning("QPSQLResult::data: unknown data type");
+    }
+    return QVariant();
+}
+
+bool QPSQLResult::isNull(int field)
+{
+    PQgetvalue(d->result, at(), field);
+    return PQgetisnull(d->result, at(), field);
+}
+
+bool QPSQLResult::reset (const QString& query)
+{
+    cleanup();
+    if (!driver())
+        return false;
+    if (!driver()->isOpen() || driver()->isOpenError())
+        return false;
+    d->result = PQexec(d->driver->connection,
+                       d->driver->isUtf8 ? query.toUtf8().constData()
+                                         : query.toLocal8Bit().constData());
+    return d->processResults();
+}
+
+int QPSQLResult::size()
+{
+    return d->currentSize;
+}
+
+int QPSQLResult::numRowsAffected()
+{
+    return QString::fromLatin1(PQcmdTuples(d->result)).toInt();
+}
+
+QVariant QPSQLResult::lastInsertId() const
+{
+    if (isActive()) {
+        Oid id = PQoidValue(d->result);
+        if (id != InvalidOid)
+            return QVariant(id);
+    }
+    return QVariant();
+}
+
+QSqlRecord QPSQLResult::record() const
+{
+    QSqlRecord info;
+    if (!isActive() || !isSelect())
+        return info;
+
+    int count = PQnfields(d->result);
+    for (int i = 0; i < count; ++i) {
+        QSqlField f;
+        if (d->driver->isUtf8)
+            f.setName(QString::fromUtf8(PQfname(d->result, i)));
+        else
+            f.setName(QString::fromLocal8Bit(PQfname(d->result, i)));
+        f.setType(qDecodePSQLType(PQftype(d->result, i)));
+        int len = PQfsize(d->result, i);
+        int precision = PQfmod(d->result, i);
+        // swap length and precision if length == -1
+        if (len == -1 && precision > -1) {
+            len = precision - 4;
+            precision = -1;
+        }
+        f.setLength(len);
+        f.setPrecision(precision);
+        f.setSqlType(PQftype(d->result, i));
+        info.append(f);
+    }
+    return info;
+}
+
+void QPSQLResult::virtual_hook(int id, void *data)
+{
+    Q_ASSERT(data);
+
+    switch (id) {
+    default:
+        QSqlResult::virtual_hook(id, data);
+    }
+}
+
+static QString qReplacePlaceholderMarkers(const QString &query)
+{
+    const int originalLength = query.length();
+    bool inQuote = false;
+    int markerIdx = 0;
+    QString result;
+    result.reserve(originalLength + 23);
+    for (int i = 0; i < originalLength; ++i) {
+        const QChar ch = query.at(i);
+        if (ch == QLatin1Char('?') && !inQuote) {
+            result += QLatin1Char('$');
+            result += QString::number(++markerIdx);
+        } else {
+            if (ch == QLatin1Char('\''))
+                inQuote = !inQuote;
+            result += ch;
+        }
+    }
+
+    result.squeeze();
+    return result;
+}
+
+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)
+{
+    if (!d->preparedQueriesEnabled)
+        return QSqlResult::prepare(query);
+
+    cleanup();
+
+    if (!d->preparedStmtId.isEmpty())
+        qDeallocatePreparedStmt(d);
+
+    const QString stmtId = qMakePreparedStmtId();
+    const QString stmt = QString::fromLatin1("PREPARE %1 AS ").arg(stmtId).append(qReplacePlaceholderMarkers(query));
+
+    PGresult *result = PQexec(d->driver->connection,
+                              d->driver->isUtf8 ? stmt.toUtf8().constData()
+                                                : stmt.toLocal8Bit().constData());
+
+    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
+        setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
+                                "Unable to prepare statement"), QSqlError::StatementError, d->driver));
+        PQclear(result);
+        d->preparedStmtId.clear();
+        return false;
+    }
+
+    PQclear(result);
+    d->preparedStmtId = stmtId;
+    return true;
+}
+
+bool QPSQLResult::exec()
+{
+    if (!d->preparedQueriesEnabled)
+        return QSqlResult::exec();
+
+    cleanup();
+
+    QString stmt;
+    const QString params = qCreateParamString(boundValues(), d->q->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 = PQexec(d->driver->connection,
+                       d->driver->isUtf8 ? stmt.toUtf8().constData()
+                                         : stmt.toLocal8Bit().constData());
+
+    return d->processResults();
+}
+
+///////////////////////////////////////////////////////////////////
+
+static bool setEncodingUtf8(PGconn* connection)
+{
+    PGresult* result = PQexec(connection, "SET CLIENT_ENCODING TO 'UNICODE'");
+    int status = PQresultStatus(result);
+    PQclear(result);
+    return status == PGRES_COMMAND_OK;
+}
+
+static void setDatestyle(PGconn* connection)
+{
+    PGresult* result = PQexec(connection, "SET DATESTYLE TO 'ISO'");
+    int status =  PQresultStatus(result);
+    if (status != PGRES_COMMAND_OK)
+        qWarning("%s", PQerrorMessage(connection));
+    PQclear(result);
+}
+
+static QPSQLDriver::Protocol getPSQLVersion(PGconn* connection)
+{
+    QPSQLDriver::Protocol serverVersion = QPSQLDriver::Version6;
+    PGresult* result = PQexec(connection, "select version()");
+    int status = PQresultStatus(result);
+    if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) {
+        QString val = QString::fromAscii(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();
+
+            switch (vMaj) {
+            case 7:
+                switch (vMin) {
+                case 0:
+                    serverVersion = QPSQLDriver::Version7;
+                    break;
+                case 1:
+                case 2:
+                    serverVersion = QPSQLDriver::Version71;
+                    break;
+                default:
+                    serverVersion = QPSQLDriver::Version73;
+                    break;
+                }
+                break;
+            case 8:
+                switch (vMin) {
+                case 0:
+                    serverVersion = QPSQLDriver::Version8;
+                    break;
+                case 1:
+                    serverVersion = QPSQLDriver::Version81;
+                    break;
+                case 2:
+                default:
+                    serverVersion = QPSQLDriver::Version82;
+                    break;
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+    PQclear(result);
+
+    if (serverVersion < QPSQLDriver::Version71)
+        qWarning("This version of PostgreSQL is not supported and may not work.");
+
+    return serverVersion;
+}
+
+QPSQLDriver::QPSQLDriver(QObject *parent)
+    : QSqlDriver(parent)
+{
+    init();
+}
+
+QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent)
+    : QSqlDriver(parent)
+{
+    init();
+    d->connection = conn;
+    if (conn) {
+        d->pro = getPSQLVersion(d->connection);
+        setOpen(true);
+        setOpenError(false);
+    }
+}
+
+void QPSQLDriver::init()
+{
+    d = new QPSQLDriverPrivate();
+}
+
+QPSQLDriver::~QPSQLDriver()
+{
+    if (d->connection)
+        PQfinish(d->connection);
+    delete d;
+}
+
+QVariant QPSQLDriver::handle() const
+{
+    return qVariantFromValue(d->connection);
+}
+
+bool QPSQLDriver::hasFeature(DriverFeature f) const
+{
+    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:
+        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)
+{
+    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 = getPSQLVersion(d->connection);
+    d->isUtf8 = setEncodingUtf8(d->connection);
+    setDatestyle(d->connection);
+
+    setOpen(true);
+    setOpenError(false);
+    return true;
+}
+
+void QPSQLDriver::close()
+{
+    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, d);
+}
+
+bool QPSQLDriver::beginTransaction()
+{
+    if (!isOpen()) {
+        qWarning("QPSQLDriver::beginTransaction: Database not open");
+        return false;
+    }
+    PGresult* res = PQexec(d->connection, "BEGIN");
+    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
+        PQclear(res);
+        setLastError(qMakeError(tr("Could not begin transaction"),
+                                QSqlError::TransactionError, d));
+        return false;
+    }
+    PQclear(res);
+    return true;
+}
+
+bool QPSQLDriver::commitTransaction()
+{
+    if (!isOpen()) {
+        qWarning("QPSQLDriver::commitTransaction: Database not open");
+        return false;
+    }
+    PGresult* res = PQexec(d->connection, "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) {
+        transaction_failed = qstrcmp(PQcmdStatus(res), "ROLLBACK") == 0;
+    }
+
+    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK || transaction_failed) {
+        PQclear(res);
+        setLastError(qMakeError(tr("Could not commit transaction"),
+                                QSqlError::TransactionError, d));
+        return false;
+    }
+    PQclear(res);
+    return true;
+}
+
+bool QPSQLDriver::rollbackTransaction()
+{
+    if (!isOpen()) {
+        qWarning("QPSQLDriver::rollbackTransaction: Database not open");
+        return false;
+    }
+    PGresult* res = PQexec(d->connection, "ROLLBACK");
+    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
+        setLastError(qMakeError(tr("Could not rollback transaction"),
+                                QSqlError::TransactionError, d));
+        PQclear(res);
+        return false;
+    }
+    PQclear(res);
+    return true;
+}
+
+QStringList QPSQLDriver::tables(QSql::TableType type) const
+{
+    QStringList tl;
+    if (!isOpen())
+        return tl;
+    QSqlQuery t(createResult());
+    t.setForwardOnly(true);
+
+    if (type & QSql::Tables)
+        d->appendTables(tl, t, QLatin1Char('r'));
+    if (type & QSql::Views)
+        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
+{
+    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:
+        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;
+    }
+
+    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
+{
+    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:
+        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;
+    }
+
+    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;
+}
+
+QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
+{
+    QString r;
+    if (field.isNull()) {
+        r = QLatin1String("NULL");
+    } else {
+        switch (field.type()) {
+        case QVariant::DateTime:
+#ifndef QT_NO_DATESTRING
+            if (field.value().toDateTime().isValid()) {
+                QDate dt = field.value().toDateTime().date();
+                QTime tm = field.value().toDateTime().time();
+                // msecs need to be right aligned otherwise psql
+                // interpretes them wrong
+                r = QLatin1Char('\'') + QString::number(dt.year()) + QLatin1Char('-')
+                          + QString::number(dt.month()) + QLatin1Char('-')
+                          + QString::number(dt.day()) + QLatin1Char(' ')
+                          + tm.toString() + QLatin1Char('.')
+                          + QString::number(tm.msec()).rightJustified(3, QLatin1Char('0'))
+                          + 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(Qt::ISODate) + QLatin1Char('\'');
+            } else
+#endif
+            {
+                r = QLatin1String("NULL");
+            }
+            break;
+        case QVariant::String:
+        {
+            // Escape '\' characters
+            r = QSqlDriver::formatValue(field, trimStrings);
+            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, (unsigned char*)ba.constData(), ba.size(), &len);
+#else
+            unsigned char *data = PQescapeBytea((unsigned char*)ba.constData(), ba.size(), &len);
+#endif
+            r += QLatin1Char('\'');
+            r += QLatin1String((const char*)data);
+            r += QLatin1Char('\'');
+            qPQfreemem(data);
+            break;
+        }
+        case QVariant::Double: {
+            double val = field.value().toDouble();
+            if (isnan(val))
+                r = QLatin1String("'NaN'");
+            else {
+                int res = isinf(val);
+                if (res == 1)
+                    r = QLatin1String("'Infinity'");
+                else if (res == -1)
+                    r = QLatin1String("'-Infinity'");
+                else
+                    r = QSqlDriver::formatValue(field, trimStrings);
+            }
+            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
+{
+    return PQstatus(d->connection) == CONNECTION_OK;
+}
+
+QPSQLDriver::Protocol QPSQLDriver::protocol() const
+{
+    return d->pro;
+}
+
+bool QPSQLDriver::subscribeToNotificationImplementation(const QString &name)
+{
+    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) {
+        QString query = QLatin1String("LISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
+        if (PQresultStatus(PQexec(d->connection,
+                                  d->isUtf8 ? query.toUtf8().constData()
+                                            : query.toLocal8Bit().constData())
+                          ) != PGRES_COMMAND_OK) {
+            setLastError(qMakeError(tr("Unable to subscribe"), QSqlError::StatementError, d));
+            return false;
+        }
+
+        if (!d->sn) {
+            d->sn = new QSocketNotifier(socket, QSocketNotifier::Read);
+            connect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
+        }
+    }
+
+    d->seid << name;
+    return true;
+}
+
+bool QPSQLDriver::unsubscribeFromNotificationImplementation(const QString &name)
+{
+    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);
+    if (PQresultStatus(PQexec(d->connection,
+                              d->isUtf8 ? query.toUtf8().constData()
+                                        : query.toLocal8Bit().constData())
+                      ) != PGRES_COMMAND_OK) {
+        setLastError(qMakeError(tr("Unable to unsubscribe"), QSqlError::StatementError, d));
+        return false;
+    }
+
+    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::subscribedToNotificationsImplementation() const
+{
+    return d->seid;
+}
+
+void QPSQLDriver::_q_handleNotification(int)
+{
+    PQconsumeInput(d->connection);
+
+    PGnotify *notify = 0;
+    while((notify = PQnotifies(d->connection)) != 0) {
+        QString name(QLatin1String(notify->relname));
+        if (d->seid.contains(name))
+            emit notification(name);
+        else
+            qWarning("QPSQLDriver: received notification for '%s' which isn't subscribed to.",
+                    qPrintable(name));
+
+        qPQfreemem(notify);
+    }
+}
+
+QT_END_NAMESPACE