src/sql/drivers/psql/qsql_psql.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the QtSql module of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "qsql_psql.h"
       
    43 
       
    44 #include <qcoreapplication.h>
       
    45 #include <qvariant.h>
       
    46 #include <qdatetime.h>
       
    47 #include <qregexp.h>
       
    48 #include <qsqlerror.h>
       
    49 #include <qsqlfield.h>
       
    50 #include <qsqlindex.h>
       
    51 #include <qsqlrecord.h>
       
    52 #include <qsqlquery.h>
       
    53 #include <qsocketnotifier.h>
       
    54 #include <qstringlist.h>
       
    55 #include <qmutex.h>
       
    56 
       
    57 
       
    58 #include <libpq-fe.h>
       
    59 #include <pg_config.h>
       
    60 
       
    61 #include <stdlib.h>
       
    62 #include <math.h>
       
    63 // below code taken from an example at http://www.gnu.org/software/hello/manual/autoconf/Function-Portability.html
       
    64 #ifndef isnan
       
    65     # define isnan(x) \
       
    66         (sizeof (x) == sizeof (long double) ? isnan_ld (x) \
       
    67         : sizeof (x) == sizeof (double) ? isnan_d (x) \
       
    68         : isnan_f (x))
       
    69     static inline int isnan_f  (float       x) { return x != x; }
       
    70     static inline int isnan_d  (double      x) { return x != x; }
       
    71     static inline int isnan_ld (long double x) { return x != x; }
       
    72 #endif
       
    73 
       
    74 #ifndef isinf
       
    75     # define isinf(x) \
       
    76         (sizeof (x) == sizeof (long double) ? isinf_ld (x) \
       
    77         : sizeof (x) == sizeof (double) ? isinf_d (x) \
       
    78         : isinf_f (x))
       
    79     static inline int isinf_f  (float       x) { return isnan (x - x); }
       
    80     static inline int isinf_d  (double      x) { return isnan (x - x); }
       
    81     static inline int isinf_ld (long double x) { return isnan (x - x); }
       
    82 #endif
       
    83 
       
    84 
       
    85 // workaround for postgres defining their OIDs in a private header file
       
    86 #define QBOOLOID 16
       
    87 #define QINT8OID 20
       
    88 #define QINT2OID 21
       
    89 #define QINT4OID 23
       
    90 #define QNUMERICOID 1700
       
    91 #define QFLOAT4OID 700
       
    92 #define QFLOAT8OID 701
       
    93 #define QABSTIMEOID 702
       
    94 #define QRELTIMEOID 703
       
    95 #define QDATEOID 1082
       
    96 #define QTIMEOID 1083
       
    97 #define QTIMETZOID 1266
       
    98 #define QTIMESTAMPOID 1114
       
    99 #define QTIMESTAMPTZOID 1184
       
   100 #define QOIDOID 2278
       
   101 #define QBYTEAOID 17
       
   102 #define QREGPROCOID 24
       
   103 #define QXIDOID 28
       
   104 #define QCIDOID 29
       
   105 
       
   106 /* This is a compile time switch - if PQfreemem is declared, the compiler will use that one,
       
   107    otherwise it'll run in this template */
       
   108 template <typename T>
       
   109 inline void PQfreemem(T *t, int = 0) { free(t); }
       
   110 
       
   111 Q_DECLARE_METATYPE(PGconn*)
       
   112 Q_DECLARE_METATYPE(PGresult*)
       
   113 
       
   114 QT_BEGIN_NAMESPACE
       
   115 
       
   116 inline void qPQfreemem(void *buffer)
       
   117 {
       
   118     PQfreemem(buffer);
       
   119 }
       
   120 
       
   121 class QPSQLDriverPrivate
       
   122 {
       
   123 public:
       
   124     QPSQLDriverPrivate() : connection(0), isUtf8(false), pro(QPSQLDriver::Version6), sn(0) {}
       
   125     PGconn *connection;
       
   126     bool isUtf8;
       
   127     QPSQLDriver::Protocol pro;
       
   128     QSocketNotifier *sn;
       
   129     QStringList seid;
       
   130 
       
   131     void appendTables(QStringList &tl, QSqlQuery &t, QChar type);
       
   132 };
       
   133 
       
   134 void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type)
       
   135 {
       
   136     QString query;
       
   137     if (pro >= QPSQLDriver::Version73) {
       
   138         query = QString::fromLatin1("select pg_class.relname, pg_namespace.nspname from pg_class "
       
   139                   "left join pg_namespace on (pg_class.relnamespace = pg_namespace.oid) "
       
   140                   "where (pg_class.relkind = '%1') and (pg_class.relname !~ '^Inv') "
       
   141                   "and (pg_class.relname !~ '^pg_') "
       
   142                   "and (pg_namespace.nspname != 'information_schema') ").arg(type);
       
   143     } else {
       
   144         query = QString::fromLatin1("select relname, null from pg_class where (relkind = '%1') "
       
   145                   "and (relname !~ '^Inv') "
       
   146                   "and (relname !~ '^pg_') ").arg(type);
       
   147     }
       
   148     t.exec(query);
       
   149     while (t.next()) {
       
   150         QString schema = t.value(1).toString();
       
   151         if (schema.isEmpty() || schema == QLatin1String("public"))
       
   152             tl.append(t.value(0).toString());
       
   153         else
       
   154             tl.append(t.value(0).toString().prepend(QLatin1Char('.')).prepend(schema));
       
   155     }
       
   156 }
       
   157 
       
   158 class QPSQLResultPrivate
       
   159 {
       
   160 public:
       
   161     QPSQLResultPrivate(QPSQLResult *qq): q(qq), driver(0), result(0), currentSize(-1), preparedQueriesEnabled(false) {}
       
   162 
       
   163     QPSQLResult *q;
       
   164     const QPSQLDriverPrivate *driver;
       
   165     PGresult *result;
       
   166     int currentSize;
       
   167     bool preparedQueriesEnabled;
       
   168     QString preparedStmtId;
       
   169 
       
   170     bool processResults();
       
   171 };
       
   172 
       
   173 static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
       
   174                             const QPSQLDriverPrivate *p)
       
   175 {
       
   176     const char *s = PQerrorMessage(p->connection);
       
   177     QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
       
   178     return QSqlError(QLatin1String("QPSQL: ") + err, msg, type);
       
   179 }
       
   180 
       
   181 bool QPSQLResultPrivate::processResults()
       
   182 {
       
   183     if (!result)
       
   184         return false;
       
   185 
       
   186     int status = PQresultStatus(result);
       
   187     if (status == PGRES_TUPLES_OK) {
       
   188         q->setSelect(true);
       
   189         q->setActive(true);
       
   190         currentSize = PQntuples(result);
       
   191         return true;
       
   192     } else if (status == PGRES_COMMAND_OK) {
       
   193         q->setSelect(false);
       
   194         q->setActive(true);
       
   195         currentSize = -1;
       
   196         return true;
       
   197     }
       
   198     q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
       
   199                     "Unable to create query"), QSqlError::StatementError, driver));
       
   200     return false;
       
   201 }
       
   202 
       
   203 static QVariant::Type qDecodePSQLType(int t)
       
   204 {
       
   205     QVariant::Type type = QVariant::Invalid;
       
   206     switch (t) {
       
   207     case QBOOLOID:
       
   208         type = QVariant::Bool;
       
   209         break;
       
   210     case QINT8OID:
       
   211         type = QVariant::LongLong;
       
   212         break;
       
   213     case QINT2OID:
       
   214     case QINT4OID:
       
   215     case QOIDOID:
       
   216     case QREGPROCOID:
       
   217     case QXIDOID:
       
   218     case QCIDOID:
       
   219         type = QVariant::Int;
       
   220         break;
       
   221     case QNUMERICOID:
       
   222     case QFLOAT4OID:
       
   223     case QFLOAT8OID:
       
   224         type = QVariant::Double;
       
   225         break;
       
   226     case QABSTIMEOID:
       
   227     case QRELTIMEOID:
       
   228     case QDATEOID:
       
   229         type = QVariant::Date;
       
   230         break;
       
   231     case QTIMEOID:
       
   232     case QTIMETZOID:
       
   233         type = QVariant::Time;
       
   234         break;
       
   235     case QTIMESTAMPOID:
       
   236     case QTIMESTAMPTZOID:
       
   237         type = QVariant::DateTime;
       
   238         break;
       
   239     case QBYTEAOID:
       
   240         type = QVariant::ByteArray;
       
   241         break;
       
   242     default:
       
   243         type = QVariant::String;
       
   244         break;
       
   245     }
       
   246     return type;
       
   247 }
       
   248 
       
   249 static void qDeallocatePreparedStmt(QPSQLResultPrivate *d)
       
   250 {
       
   251     const QString stmt = QLatin1String("DEALLOCATE ") + d->preparedStmtId;
       
   252     PGresult *result = PQexec(d->driver->connection,
       
   253                               d->driver->isUtf8 ? stmt.toUtf8().constData()
       
   254                                                 : stmt.toLocal8Bit().constData());
       
   255 
       
   256     if (PQresultStatus(result) != PGRES_COMMAND_OK)
       
   257         qWarning("Unable to free statement: %s", PQerrorMessage(d->driver->connection));
       
   258     PQclear(result);
       
   259     d->preparedStmtId.clear();
       
   260 }
       
   261 
       
   262 QPSQLResult::QPSQLResult(const QPSQLDriver* db, const QPSQLDriverPrivate* p)
       
   263     : QSqlResult(db)
       
   264 {
       
   265     d = new QPSQLResultPrivate(this);
       
   266     d->driver = p;
       
   267     d->preparedQueriesEnabled = db->hasFeature(QSqlDriver::PreparedQueries);
       
   268 }
       
   269 
       
   270 QPSQLResult::~QPSQLResult()
       
   271 {
       
   272     cleanup();
       
   273 
       
   274     if (d->preparedQueriesEnabled && !d->preparedStmtId.isNull())
       
   275         qDeallocatePreparedStmt(d);
       
   276 
       
   277     delete d;
       
   278 }
       
   279 
       
   280 QVariant QPSQLResult::handle() const
       
   281 {
       
   282     return qVariantFromValue(d->result);
       
   283 }
       
   284 
       
   285 void QPSQLResult::cleanup()
       
   286 {
       
   287     if (d->result)
       
   288         PQclear(d->result);
       
   289     d->result = 0;
       
   290     setAt(QSql::BeforeFirstRow);
       
   291     d->currentSize = -1;
       
   292     setActive(false);
       
   293 }
       
   294 
       
   295 bool QPSQLResult::fetch(int i)
       
   296 {
       
   297     if (!isActive())
       
   298         return false;
       
   299     if (i < 0)
       
   300         return false;
       
   301     if (i >= d->currentSize)
       
   302         return false;
       
   303     if (at() == i)
       
   304         return true;
       
   305     setAt(i);
       
   306     return true;
       
   307 }
       
   308 
       
   309 bool QPSQLResult::fetchFirst()
       
   310 {
       
   311     return fetch(0);
       
   312 }
       
   313 
       
   314 bool QPSQLResult::fetchLast()
       
   315 {
       
   316     return fetch(PQntuples(d->result) - 1);
       
   317 }
       
   318 
       
   319 QVariant QPSQLResult::data(int i)
       
   320 {
       
   321     if (i >= PQnfields(d->result)) {
       
   322         qWarning("QPSQLResult::data: column %d out of range", i);
       
   323         return QVariant();
       
   324     }
       
   325     int ptype = PQftype(d->result, i);
       
   326     QVariant::Type type = qDecodePSQLType(ptype);
       
   327     const char *val = PQgetvalue(d->result, at(), i);
       
   328     if (PQgetisnull(d->result, at(), i))
       
   329         return QVariant(type);
       
   330     switch (type) {
       
   331     case QVariant::Bool:
       
   332         return QVariant((bool)(val[0] == 't'));
       
   333     case QVariant::String:
       
   334         return d->driver->isUtf8 ? QString::fromUtf8(val) : QString::fromAscii(val);
       
   335     case QVariant::LongLong:
       
   336         if (val[0] == '-')
       
   337             return QString::fromLatin1(val).toLongLong();
       
   338         else
       
   339             return QString::fromLatin1(val).toULongLong();
       
   340     case QVariant::Int:
       
   341         return atoi(val);
       
   342     case QVariant::Double:
       
   343         if (ptype == QNUMERICOID) {
       
   344             if (numericalPrecisionPolicy() != QSql::HighPrecision) {
       
   345                 QVariant retval;
       
   346                 bool convert;
       
   347                 double dbl=QString::fromAscii(val).toDouble(&convert);
       
   348                 if (numericalPrecisionPolicy() == QSql::LowPrecisionInt64)
       
   349                     retval = (qlonglong)dbl;
       
   350                 else if (numericalPrecisionPolicy() == QSql::LowPrecisionInt32)
       
   351                     retval = (int)dbl;
       
   352                 else if (numericalPrecisionPolicy() == QSql::LowPrecisionDouble)
       
   353                     retval = dbl;
       
   354                 if (!convert)
       
   355                     return QVariant();
       
   356                 return retval;
       
   357             }
       
   358             return QString::fromAscii(val);
       
   359         }
       
   360         return strtod(val, 0);
       
   361     case QVariant::Date:
       
   362         if (val[0] == '\0') {
       
   363             return QVariant(QDate());
       
   364         } else {
       
   365 #ifndef QT_NO_DATESTRING
       
   366             return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate));
       
   367 #else
       
   368             return QVariant(QString::fromLatin1(val));
       
   369 #endif
       
   370         }
       
   371     case QVariant::Time: {
       
   372         const QString str = QString::fromLatin1(val);
       
   373 #ifndef QT_NO_DATESTRING
       
   374         if (str.isEmpty())
       
   375             return QVariant(QTime());
       
   376         if (str.at(str.length() - 3) == QLatin1Char('+') || str.at(str.length() - 3) == QLatin1Char('-'))
       
   377             // strip the timezone
       
   378             // TODO: fix this when timestamp support comes into QDateTime
       
   379             return QVariant(QTime::fromString(str.left(str.length() - 3), Qt::ISODate));
       
   380         return QVariant(QTime::fromString(str, Qt::ISODate));
       
   381 #else
       
   382         return QVariant(str);
       
   383 #endif
       
   384     }
       
   385     case QVariant::DateTime: {
       
   386         QString dtval = QString::fromLatin1(val);
       
   387 #ifndef QT_NO_DATESTRING
       
   388         if (dtval.length() < 10)
       
   389             return QVariant(QDateTime());
       
   390         // remove the timezone
       
   391         // TODO: fix this when timestamp support comes into QDateTime
       
   392         if (dtval.at(dtval.length() - 3) == QLatin1Char('+') || dtval.at(dtval.length() - 3) == QLatin1Char('-'))
       
   393             dtval.chop(3);
       
   394         // milliseconds are sometimes returned with 2 digits only
       
   395         if (dtval.at(dtval.length() - 3).isPunct())
       
   396             dtval += QLatin1Char('0');
       
   397         if (dtval.isEmpty())
       
   398             return QVariant(QDateTime());
       
   399         else
       
   400             return QVariant(QDateTime::fromString(dtval, Qt::ISODate));
       
   401 #else
       
   402         return QVariant(dtval);
       
   403 #endif
       
   404     }
       
   405     case QVariant::ByteArray: {
       
   406         size_t len;
       
   407         unsigned char *data = PQunescapeBytea((unsigned char*)val, &len);
       
   408         QByteArray ba((const char*)data, len);
       
   409         qPQfreemem(data);
       
   410         return QVariant(ba);
       
   411     }
       
   412     default:
       
   413     case QVariant::Invalid:
       
   414         qWarning("QPSQLResult::data: unknown data type");
       
   415     }
       
   416     return QVariant();
       
   417 }
       
   418 
       
   419 bool QPSQLResult::isNull(int field)
       
   420 {
       
   421     PQgetvalue(d->result, at(), field);
       
   422     return PQgetisnull(d->result, at(), field);
       
   423 }
       
   424 
       
   425 bool QPSQLResult::reset (const QString& query)
       
   426 {
       
   427     cleanup();
       
   428     if (!driver())
       
   429         return false;
       
   430     if (!driver()->isOpen() || driver()->isOpenError())
       
   431         return false;
       
   432     d->result = PQexec(d->driver->connection,
       
   433                        d->driver->isUtf8 ? query.toUtf8().constData()
       
   434                                          : query.toLocal8Bit().constData());
       
   435     return d->processResults();
       
   436 }
       
   437 
       
   438 int QPSQLResult::size()
       
   439 {
       
   440     return d->currentSize;
       
   441 }
       
   442 
       
   443 int QPSQLResult::numRowsAffected()
       
   444 {
       
   445     return QString::fromLatin1(PQcmdTuples(d->result)).toInt();
       
   446 }
       
   447 
       
   448 QVariant QPSQLResult::lastInsertId() const
       
   449 {
       
   450     if (isActive()) {
       
   451         Oid id = PQoidValue(d->result);
       
   452         if (id != InvalidOid)
       
   453             return QVariant(id);
       
   454     }
       
   455     return QVariant();
       
   456 }
       
   457 
       
   458 QSqlRecord QPSQLResult::record() const
       
   459 {
       
   460     QSqlRecord info;
       
   461     if (!isActive() || !isSelect())
       
   462         return info;
       
   463 
       
   464     int count = PQnfields(d->result);
       
   465     for (int i = 0; i < count; ++i) {
       
   466         QSqlField f;
       
   467         if (d->driver->isUtf8)
       
   468             f.setName(QString::fromUtf8(PQfname(d->result, i)));
       
   469         else
       
   470             f.setName(QString::fromLocal8Bit(PQfname(d->result, i)));
       
   471         f.setType(qDecodePSQLType(PQftype(d->result, i)));
       
   472         int len = PQfsize(d->result, i);
       
   473         int precision = PQfmod(d->result, i);
       
   474         // swap length and precision if length == -1
       
   475         if (len == -1 && precision > -1) {
       
   476             len = precision - 4;
       
   477             precision = -1;
       
   478         }
       
   479         f.setLength(len);
       
   480         f.setPrecision(precision);
       
   481         f.setSqlType(PQftype(d->result, i));
       
   482         info.append(f);
       
   483     }
       
   484     return info;
       
   485 }
       
   486 
       
   487 void QPSQLResult::virtual_hook(int id, void *data)
       
   488 {
       
   489     Q_ASSERT(data);
       
   490 
       
   491     switch (id) {
       
   492     default:
       
   493         QSqlResult::virtual_hook(id, data);
       
   494     }
       
   495 }
       
   496 
       
   497 static QString qReplacePlaceholderMarkers(const QString &query)
       
   498 {
       
   499     const int originalLength = query.length();
       
   500     bool inQuote = false;
       
   501     int markerIdx = 0;
       
   502     QString result;
       
   503     result.reserve(originalLength + 23);
       
   504     for (int i = 0; i < originalLength; ++i) {
       
   505         const QChar ch = query.at(i);
       
   506         if (ch == QLatin1Char('?') && !inQuote) {
       
   507             result += QLatin1Char('$');
       
   508             result += QString::number(++markerIdx);
       
   509         } else {
       
   510             if (ch == QLatin1Char('\''))
       
   511                 inQuote = !inQuote;
       
   512             result += ch;
       
   513         }
       
   514     }
       
   515 
       
   516     result.squeeze();
       
   517     return result;
       
   518 }
       
   519 
       
   520 static QString qCreateParamString(const QVector<QVariant> boundValues, const QSqlDriver *driver)
       
   521 {
       
   522     if (boundValues.isEmpty())
       
   523         return QString();
       
   524 
       
   525     QString params;
       
   526     QSqlField f;
       
   527     for (int i = 0; i < boundValues.count(); ++i) {
       
   528         const QVariant &val = boundValues.at(i);
       
   529 
       
   530         f.setType(val.type());
       
   531         if (val.isNull())
       
   532             f.clear();
       
   533         else
       
   534             f.setValue(val);
       
   535         if(!params.isNull())
       
   536             params.append(QLatin1String(", "));
       
   537         params.append(driver->formatValue(f));
       
   538     }
       
   539     return params;
       
   540 }
       
   541 
       
   542 Q_GLOBAL_STATIC(QMutex, qMutex)
       
   543 QString qMakePreparedStmtId()
       
   544 {
       
   545     qMutex()->lock();
       
   546     static unsigned int qPreparedStmtCount = 0;
       
   547     QString id = QLatin1String("qpsqlpstmt_") + QString::number(++qPreparedStmtCount, 16);
       
   548     qMutex()->unlock();
       
   549     return id;
       
   550 }
       
   551 
       
   552 bool QPSQLResult::prepare(const QString &query)
       
   553 {
       
   554     if (!d->preparedQueriesEnabled)
       
   555         return QSqlResult::prepare(query);
       
   556 
       
   557     cleanup();
       
   558 
       
   559     if (!d->preparedStmtId.isEmpty())
       
   560         qDeallocatePreparedStmt(d);
       
   561 
       
   562     const QString stmtId = qMakePreparedStmtId();
       
   563     const QString stmt = QString::fromLatin1("PREPARE %1 AS ").arg(stmtId).append(qReplacePlaceholderMarkers(query));
       
   564 
       
   565     PGresult *result = PQexec(d->driver->connection,
       
   566                               d->driver->isUtf8 ? stmt.toUtf8().constData()
       
   567                                                 : stmt.toLocal8Bit().constData());
       
   568 
       
   569     if (PQresultStatus(result) != PGRES_COMMAND_OK) {
       
   570         setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
       
   571                                 "Unable to prepare statement"), QSqlError::StatementError, d->driver));
       
   572         PQclear(result);
       
   573         d->preparedStmtId.clear();
       
   574         return false;
       
   575     }
       
   576 
       
   577     PQclear(result);
       
   578     d->preparedStmtId = stmtId;
       
   579     return true;
       
   580 }
       
   581 
       
   582 bool QPSQLResult::exec()
       
   583 {
       
   584     if (!d->preparedQueriesEnabled)
       
   585         return QSqlResult::exec();
       
   586 
       
   587     cleanup();
       
   588 
       
   589     QString stmt;
       
   590     const QString params = qCreateParamString(boundValues(), d->q->driver());
       
   591     if (params.isEmpty())
       
   592         stmt = QString::fromLatin1("EXECUTE %1").arg(d->preparedStmtId);
       
   593     else
       
   594         stmt = QString::fromLatin1("EXECUTE %1 (%2)").arg(d->preparedStmtId).arg(params);
       
   595 
       
   596     d->result = PQexec(d->driver->connection,
       
   597                        d->driver->isUtf8 ? stmt.toUtf8().constData()
       
   598                                          : stmt.toLocal8Bit().constData());
       
   599 
       
   600     return d->processResults();
       
   601 }
       
   602 
       
   603 ///////////////////////////////////////////////////////////////////
       
   604 
       
   605 static bool setEncodingUtf8(PGconn* connection)
       
   606 {
       
   607     PGresult* result = PQexec(connection, "SET CLIENT_ENCODING TO 'UNICODE'");
       
   608     int status = PQresultStatus(result);
       
   609     PQclear(result);
       
   610     return status == PGRES_COMMAND_OK;
       
   611 }
       
   612 
       
   613 static void setDatestyle(PGconn* connection)
       
   614 {
       
   615     PGresult* result = PQexec(connection, "SET DATESTYLE TO 'ISO'");
       
   616     int status =  PQresultStatus(result);
       
   617     if (status != PGRES_COMMAND_OK)
       
   618         qWarning("%s", PQerrorMessage(connection));
       
   619     PQclear(result);
       
   620 }
       
   621 
       
   622 static QPSQLDriver::Protocol getPSQLVersion(PGconn* connection)
       
   623 {
       
   624     QPSQLDriver::Protocol serverVersion = QPSQLDriver::Version6;
       
   625     PGresult* result = PQexec(connection, "select version()");
       
   626     int status = PQresultStatus(result);
       
   627     if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) {
       
   628         QString val = QString::fromAscii(PQgetvalue(result, 0, 0));
       
   629         QRegExp rx(QLatin1String("(\\d+)\\.(\\d+)"));
       
   630         rx.setMinimal(true); // enforce non-greedy RegExp
       
   631         if (rx.indexIn(val) != -1) {
       
   632             int vMaj = rx.cap(1).toInt();
       
   633             int vMin = rx.cap(2).toInt();
       
   634 
       
   635             switch (vMaj) {
       
   636             case 7:
       
   637                 switch (vMin) {
       
   638                 case 0:
       
   639                     serverVersion = QPSQLDriver::Version7;
       
   640                     break;
       
   641                 case 1:
       
   642                 case 2:
       
   643                     serverVersion = QPSQLDriver::Version71;
       
   644                     break;
       
   645                 default:
       
   646                     serverVersion = QPSQLDriver::Version73;
       
   647                     break;
       
   648                 }
       
   649                 break;
       
   650             case 8:
       
   651                 switch (vMin) {
       
   652                 case 0:
       
   653                     serverVersion = QPSQLDriver::Version8;
       
   654                     break;
       
   655                 case 1:
       
   656                     serverVersion = QPSQLDriver::Version81;
       
   657                     break;
       
   658                 case 2:
       
   659                 default:
       
   660                     serverVersion = QPSQLDriver::Version82;
       
   661                     break;
       
   662                 }
       
   663                 break;
       
   664             default:
       
   665                 break;
       
   666             }
       
   667         }
       
   668     }
       
   669     PQclear(result);
       
   670 
       
   671     if (serverVersion < QPSQLDriver::Version71)
       
   672         qWarning("This version of PostgreSQL is not supported and may not work.");
       
   673 
       
   674     return serverVersion;
       
   675 }
       
   676 
       
   677 QPSQLDriver::QPSQLDriver(QObject *parent)
       
   678     : QSqlDriver(parent)
       
   679 {
       
   680     init();
       
   681 }
       
   682 
       
   683 QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent)
       
   684     : QSqlDriver(parent)
       
   685 {
       
   686     init();
       
   687     d->connection = conn;
       
   688     if (conn) {
       
   689         d->pro = getPSQLVersion(d->connection);
       
   690         setOpen(true);
       
   691         setOpenError(false);
       
   692     }
       
   693 }
       
   694 
       
   695 void QPSQLDriver::init()
       
   696 {
       
   697     d = new QPSQLDriverPrivate();
       
   698 }
       
   699 
       
   700 QPSQLDriver::~QPSQLDriver()
       
   701 {
       
   702     if (d->connection)
       
   703         PQfinish(d->connection);
       
   704     delete d;
       
   705 }
       
   706 
       
   707 QVariant QPSQLDriver::handle() const
       
   708 {
       
   709     return qVariantFromValue(d->connection);
       
   710 }
       
   711 
       
   712 bool QPSQLDriver::hasFeature(DriverFeature f) const
       
   713 {
       
   714     switch (f) {
       
   715     case Transactions:
       
   716     case QuerySize:
       
   717     case LastInsertId:
       
   718     case LowPrecisionNumbers:
       
   719     case EventNotifications:
       
   720         return true;
       
   721     case PreparedQueries:
       
   722     case PositionalPlaceholders:
       
   723         return d->pro >= QPSQLDriver::Version82;
       
   724     case BatchOperations:
       
   725     case NamedPlaceholders:
       
   726     case SimpleLocking:
       
   727     case FinishQuery:
       
   728     case MultipleResultSets:
       
   729         return false;
       
   730     case BLOB:
       
   731         return d->pro >= QPSQLDriver::Version71;
       
   732     case Unicode:
       
   733         return d->isUtf8;
       
   734     }
       
   735     return false;
       
   736 }
       
   737 
       
   738 /*
       
   739    Quote a string for inclusion into the connection string
       
   740    \ -> \\
       
   741    ' -> \'
       
   742    surround string by single quotes
       
   743  */
       
   744 static QString qQuote(QString s)
       
   745 {
       
   746     s.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
       
   747     s.replace(QLatin1Char('\''), QLatin1String("\\'"));
       
   748     s.append(QLatin1Char('\'')).prepend(QLatin1Char('\''));
       
   749     return s;
       
   750 }
       
   751 
       
   752 bool QPSQLDriver::open(const QString & db,
       
   753                         const QString & user,
       
   754                         const QString & password,
       
   755                         const QString & host,
       
   756                         int port,
       
   757                         const QString& connOpts)
       
   758 {
       
   759     if (isOpen())
       
   760         close();
       
   761     QString connectString;
       
   762     if (!host.isEmpty())
       
   763         connectString.append(QLatin1String("host=")).append(qQuote(host));
       
   764     if (!db.isEmpty())
       
   765         connectString.append(QLatin1String(" dbname=")).append(qQuote(db));
       
   766     if (!user.isEmpty())
       
   767         connectString.append(QLatin1String(" user=")).append(qQuote(user));
       
   768     if (!password.isEmpty())
       
   769         connectString.append(QLatin1String(" password=")).append(qQuote(password));
       
   770     if (port != -1)
       
   771         connectString.append(QLatin1String(" port=")).append(qQuote(QString::number(port)));
       
   772 
       
   773     // add any connect options - the server will handle error detection
       
   774     if (!connOpts.isEmpty()) {
       
   775         QString opt = connOpts;
       
   776         opt.replace(QLatin1Char(';'), QLatin1Char(' '), Qt::CaseInsensitive);
       
   777         connectString.append(QLatin1Char(' ')).append(opt);
       
   778     }
       
   779 
       
   780     d->connection = PQconnectdb(connectString.toLocal8Bit().constData());
       
   781     if (PQstatus(d->connection) == CONNECTION_BAD) {
       
   782         setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
       
   783         setOpenError(true);
       
   784         PQfinish(d->connection);
       
   785         d->connection = 0;
       
   786         return false;
       
   787     }
       
   788 
       
   789     d->pro = getPSQLVersion(d->connection);
       
   790     d->isUtf8 = setEncodingUtf8(d->connection);
       
   791     setDatestyle(d->connection);
       
   792 
       
   793     setOpen(true);
       
   794     setOpenError(false);
       
   795     return true;
       
   796 }
       
   797 
       
   798 void QPSQLDriver::close()
       
   799 {
       
   800     if (isOpen()) {
       
   801 
       
   802         d->seid.clear();
       
   803         if (d->sn) {
       
   804             disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
       
   805             delete d->sn;
       
   806             d->sn = 0;
       
   807         }
       
   808 
       
   809         if (d->connection)
       
   810             PQfinish(d->connection);
       
   811         d->connection = 0;
       
   812         setOpen(false);
       
   813         setOpenError(false);
       
   814     }
       
   815 }
       
   816 
       
   817 QSqlResult *QPSQLDriver::createResult() const
       
   818 {
       
   819     return new QPSQLResult(this, d);
       
   820 }
       
   821 
       
   822 bool QPSQLDriver::beginTransaction()
       
   823 {
       
   824     if (!isOpen()) {
       
   825         qWarning("QPSQLDriver::beginTransaction: Database not open");
       
   826         return false;
       
   827     }
       
   828     PGresult* res = PQexec(d->connection, "BEGIN");
       
   829     if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
       
   830         PQclear(res);
       
   831         setLastError(qMakeError(tr("Could not begin transaction"),
       
   832                                 QSqlError::TransactionError, d));
       
   833         return false;
       
   834     }
       
   835     PQclear(res);
       
   836     return true;
       
   837 }
       
   838 
       
   839 bool QPSQLDriver::commitTransaction()
       
   840 {
       
   841     if (!isOpen()) {
       
   842         qWarning("QPSQLDriver::commitTransaction: Database not open");
       
   843         return false;
       
   844     }
       
   845     PGresult* res = PQexec(d->connection, "COMMIT");
       
   846 
       
   847     bool transaction_failed = false;
       
   848 
       
   849     // XXX
       
   850     // This hack is used to tell if the transaction has succeeded for the protocol versions of
       
   851     // PostgreSQL below. For 7.x and other protocol versions we are left in the dark.
       
   852     // This hack can dissapear once there is an API to query this sort of information.
       
   853     if (d->pro == QPSQLDriver::Version8 ||
       
   854         d->pro == QPSQLDriver::Version81 ||
       
   855         d->pro == QPSQLDriver::Version82) {
       
   856         transaction_failed = qstrcmp(PQcmdStatus(res), "ROLLBACK") == 0;
       
   857     }
       
   858 
       
   859     if (!res || PQresultStatus(res) != PGRES_COMMAND_OK || transaction_failed) {
       
   860         PQclear(res);
       
   861         setLastError(qMakeError(tr("Could not commit transaction"),
       
   862                                 QSqlError::TransactionError, d));
       
   863         return false;
       
   864     }
       
   865     PQclear(res);
       
   866     return true;
       
   867 }
       
   868 
       
   869 bool QPSQLDriver::rollbackTransaction()
       
   870 {
       
   871     if (!isOpen()) {
       
   872         qWarning("QPSQLDriver::rollbackTransaction: Database not open");
       
   873         return false;
       
   874     }
       
   875     PGresult* res = PQexec(d->connection, "ROLLBACK");
       
   876     if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
       
   877         setLastError(qMakeError(tr("Could not rollback transaction"),
       
   878                                 QSqlError::TransactionError, d));
       
   879         PQclear(res);
       
   880         return false;
       
   881     }
       
   882     PQclear(res);
       
   883     return true;
       
   884 }
       
   885 
       
   886 QStringList QPSQLDriver::tables(QSql::TableType type) const
       
   887 {
       
   888     QStringList tl;
       
   889     if (!isOpen())
       
   890         return tl;
       
   891     QSqlQuery t(createResult());
       
   892     t.setForwardOnly(true);
       
   893 
       
   894     if (type & QSql::Tables)
       
   895         d->appendTables(tl, t, QLatin1Char('r'));
       
   896     if (type & QSql::Views)
       
   897         d->appendTables(tl, t, QLatin1Char('v'));
       
   898     if (type & QSql::SystemTables) {
       
   899         t.exec(QLatin1String("select relname from pg_class where (relkind = 'r') "
       
   900                 "and (relname like 'pg_%') "));
       
   901         while (t.next())
       
   902             tl.append(t.value(0).toString());
       
   903     }
       
   904 
       
   905     return tl;
       
   906 }
       
   907 
       
   908 static void qSplitTableName(QString &tablename, QString &schema)
       
   909 {
       
   910     int dot = tablename.indexOf(QLatin1Char('.'));
       
   911     if (dot == -1)
       
   912         return;
       
   913     schema = tablename.left(dot);
       
   914     tablename = tablename.mid(dot + 1);
       
   915 }
       
   916 
       
   917 QSqlIndex QPSQLDriver::primaryIndex(const QString& tablename) const
       
   918 {
       
   919     QSqlIndex idx(tablename);
       
   920     if (!isOpen())
       
   921         return idx;
       
   922     QSqlQuery i(createResult());
       
   923     QString stmt;
       
   924 
       
   925     QString tbl = tablename;
       
   926     QString schema;
       
   927     qSplitTableName(tbl, schema);
       
   928 
       
   929     if (isIdentifierEscaped(tbl, QSqlDriver::TableName))
       
   930         tbl = stripDelimiters(tbl, QSqlDriver::TableName);
       
   931     else
       
   932         tbl = tbl.toLower();
       
   933 
       
   934     if (isIdentifierEscaped(schema, QSqlDriver::TableName))
       
   935         schema = stripDelimiters(schema, QSqlDriver::TableName);
       
   936     else
       
   937         schema = schema.toLower();
       
   938 
       
   939     switch(d->pro) {
       
   940     case QPSQLDriver::Version6:
       
   941         stmt = QLatin1String("select pg_att1.attname, int(pg_att1.atttypid), pg_cl.relname "
       
   942                 "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind "
       
   943                 "where pg_cl.relname = '%1_pkey' "
       
   944                 "and pg_cl.oid = pg_ind.indexrelid "
       
   945                 "and pg_att2.attrelid = pg_ind.indexrelid "
       
   946                 "and pg_att1.attrelid = pg_ind.indrelid "
       
   947                 "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] "
       
   948                 "order by pg_att2.attnum");
       
   949         break;
       
   950     case QPSQLDriver::Version7:
       
   951     case QPSQLDriver::Version71:
       
   952         stmt = QLatin1String("select pg_att1.attname, pg_att1.atttypid::int, pg_cl.relname "
       
   953                 "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind "
       
   954                 "where pg_cl.relname = '%1_pkey' "
       
   955                 "and pg_cl.oid = pg_ind.indexrelid "
       
   956                 "and pg_att2.attrelid = pg_ind.indexrelid "
       
   957                 "and pg_att1.attrelid = pg_ind.indrelid "
       
   958                 "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] "
       
   959                 "order by pg_att2.attnum");
       
   960         break;
       
   961     case QPSQLDriver::Version73:
       
   962     case QPSQLDriver::Version74:
       
   963     case QPSQLDriver::Version8:
       
   964     case QPSQLDriver::Version81:
       
   965     case QPSQLDriver::Version82:
       
   966         stmt = QLatin1String("SELECT pg_attribute.attname, pg_attribute.atttypid::int, "
       
   967                 "pg_class.relname "
       
   968                 "FROM pg_attribute, pg_class "
       
   969                 "WHERE %1 pg_class.oid IN "
       
   970                 "(SELECT indexrelid FROM pg_index WHERE indisprimary = true AND indrelid IN "
       
   971                 " (SELECT oid FROM pg_class WHERE relname = '%2')) "
       
   972                 "AND pg_attribute.attrelid = pg_class.oid "
       
   973                 "AND pg_attribute.attisdropped = false "
       
   974                 "ORDER BY pg_attribute.attnum");
       
   975         if (schema.isEmpty())
       
   976             stmt = stmt.arg(QLatin1String("pg_table_is_visible(pg_class.oid) AND"));
       
   977         else
       
   978             stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from "
       
   979                    "pg_namespace where pg_namespace.nspname = '%1') AND ").arg(schema));
       
   980         break;
       
   981     }
       
   982 
       
   983     i.exec(stmt.arg(tbl));
       
   984     while (i.isActive() && i.next()) {
       
   985         QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt()));
       
   986         idx.append(f);
       
   987         idx.setName(i.value(2).toString());
       
   988     }
       
   989     return idx;
       
   990 }
       
   991 
       
   992 QSqlRecord QPSQLDriver::record(const QString& tablename) const
       
   993 {
       
   994     QSqlRecord info;
       
   995     if (!isOpen())
       
   996         return info;
       
   997 
       
   998     QString tbl = tablename;
       
   999     QString schema;
       
  1000     qSplitTableName(tbl, schema);
       
  1001 
       
  1002     if (isIdentifierEscaped(tbl, QSqlDriver::TableName))
       
  1003         tbl = stripDelimiters(tbl, QSqlDriver::TableName);
       
  1004     else
       
  1005         tbl = tbl.toLower();
       
  1006 
       
  1007     if (isIdentifierEscaped(schema, QSqlDriver::TableName))
       
  1008         schema = stripDelimiters(schema, QSqlDriver::TableName);
       
  1009     else
       
  1010         schema = schema.toLower();
       
  1011 
       
  1012     QString stmt;
       
  1013     switch(d->pro) {
       
  1014     case QPSQLDriver::Version6:
       
  1015         stmt = QLatin1String("select pg_attribute.attname, int(pg_attribute.atttypid), "
       
  1016                 "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
       
  1017                 "int(pg_attribute.attrelid), pg_attribute.attnum "
       
  1018                 "from pg_class, pg_attribute "
       
  1019                 "where pg_class.relname = '%1' "
       
  1020                 "and pg_attribute.attnum > 0 "
       
  1021                 "and pg_attribute.attrelid = pg_class.oid ");
       
  1022         break;
       
  1023     case QPSQLDriver::Version7:
       
  1024         stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, "
       
  1025                 "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
       
  1026                 "pg_attribute.attrelid::int, pg_attribute.attnum "
       
  1027                 "from pg_class, pg_attribute "
       
  1028                 "where pg_class.relname = '%1' "
       
  1029                 "and pg_attribute.attnum > 0 "
       
  1030                 "and pg_attribute.attrelid = pg_class.oid ");
       
  1031         break;
       
  1032     case QPSQLDriver::Version71:
       
  1033         stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, "
       
  1034                 "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
       
  1035                 "pg_attrdef.adsrc "
       
  1036                 "from pg_class, pg_attribute "
       
  1037                 "left join pg_attrdef on (pg_attrdef.adrelid = "
       
  1038                 "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) "
       
  1039                 "where pg_class.relname = '%1' "
       
  1040                 "and pg_attribute.attnum > 0 "
       
  1041                 "and pg_attribute.attrelid = pg_class.oid "
       
  1042                 "order by pg_attribute.attnum ");
       
  1043         break;
       
  1044     case QPSQLDriver::Version73:
       
  1045     case QPSQLDriver::Version74:
       
  1046     case QPSQLDriver::Version8:
       
  1047     case QPSQLDriver::Version81:
       
  1048     case QPSQLDriver::Version82:
       
  1049         stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, "
       
  1050                 "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
       
  1051                 "pg_attrdef.adsrc "
       
  1052                 "from pg_class, pg_attribute "
       
  1053                 "left join pg_attrdef on (pg_attrdef.adrelid = "
       
  1054                 "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) "
       
  1055                 "where %1 "
       
  1056                 "and pg_class.relname = '%2' "
       
  1057                 "and pg_attribute.attnum > 0 "
       
  1058                 "and pg_attribute.attrelid = pg_class.oid "
       
  1059                 "and pg_attribute.attisdropped = false "
       
  1060                 "order by pg_attribute.attnum ");
       
  1061         if (schema.isEmpty())
       
  1062             stmt = stmt.arg(QLatin1String("pg_table_is_visible(pg_class.oid)"));
       
  1063         else
       
  1064             stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from "
       
  1065                    "pg_namespace where pg_namespace.nspname = '%1')").arg(schema));
       
  1066         break;
       
  1067     }
       
  1068 
       
  1069     QSqlQuery query(createResult());
       
  1070     query.exec(stmt.arg(tbl));
       
  1071     if (d->pro >= QPSQLDriver::Version71) {
       
  1072         while (query.next()) {
       
  1073             int len = query.value(3).toInt();
       
  1074             int precision = query.value(4).toInt();
       
  1075             // swap length and precision if length == -1
       
  1076             if (len == -1 && precision > -1) {
       
  1077                 len = precision - 4;
       
  1078                 precision = -1;
       
  1079             }
       
  1080             QString defVal = query.value(5).toString();
       
  1081             if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\''))
       
  1082                 defVal = defVal.mid(1, defVal.length() - 2);
       
  1083             QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()));
       
  1084             f.setRequired(query.value(2).toBool());
       
  1085             f.setLength(len);
       
  1086             f.setPrecision(precision);
       
  1087             f.setDefaultValue(defVal);
       
  1088             f.setSqlType(query.value(1).toInt());
       
  1089             info.append(f);
       
  1090         }
       
  1091     } else {
       
  1092         // Postgres < 7.1 cannot handle outer joins
       
  1093         while (query.next()) {
       
  1094             QString defVal;
       
  1095             QString stmt2 = QLatin1String("select pg_attrdef.adsrc from pg_attrdef where "
       
  1096                             "pg_attrdef.adrelid = %1 and pg_attrdef.adnum = %2 ");
       
  1097             QSqlQuery query2(createResult());
       
  1098             query2.exec(stmt2.arg(query.value(5).toInt()).arg(query.value(6).toInt()));
       
  1099             if (query2.isActive() && query2.next())
       
  1100                 defVal = query2.value(0).toString();
       
  1101             if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\''))
       
  1102                 defVal = defVal.mid(1, defVal.length() - 2);
       
  1103             int len = query.value(3).toInt();
       
  1104             int precision = query.value(4).toInt();
       
  1105             // swap length and precision if length == -1
       
  1106             if (len == -1 && precision > -1) {
       
  1107                 len = precision - 4;
       
  1108                 precision = -1;
       
  1109             }
       
  1110             QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()));
       
  1111             f.setRequired(query.value(2).toBool());
       
  1112             f.setLength(len);
       
  1113             f.setPrecision(precision);
       
  1114             f.setDefaultValue(defVal);
       
  1115             f.setSqlType(query.value(1).toInt());
       
  1116             info.append(f);
       
  1117         }
       
  1118     }
       
  1119 
       
  1120     return info;
       
  1121 }
       
  1122 
       
  1123 QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
       
  1124 {
       
  1125     QString r;
       
  1126     if (field.isNull()) {
       
  1127         r = QLatin1String("NULL");
       
  1128     } else {
       
  1129         switch (field.type()) {
       
  1130         case QVariant::DateTime:
       
  1131 #ifndef QT_NO_DATESTRING
       
  1132             if (field.value().toDateTime().isValid()) {
       
  1133                 QDate dt = field.value().toDateTime().date();
       
  1134                 QTime tm = field.value().toDateTime().time();
       
  1135                 // msecs need to be right aligned otherwise psql
       
  1136                 // interpretes them wrong
       
  1137                 r = QLatin1Char('\'') + QString::number(dt.year()) + QLatin1Char('-')
       
  1138                           + QString::number(dt.month()) + QLatin1Char('-')
       
  1139                           + QString::number(dt.day()) + QLatin1Char(' ')
       
  1140                           + tm.toString() + QLatin1Char('.')
       
  1141                           + QString::number(tm.msec()).rightJustified(3, QLatin1Char('0'))
       
  1142                           + QLatin1Char('\'');
       
  1143             } else {
       
  1144                 r = QLatin1String("NULL");
       
  1145             }
       
  1146 #else
       
  1147             r = QLatin1String("NULL");
       
  1148 #endif // QT_NO_DATESTRING
       
  1149             break;
       
  1150         case QVariant::Time:
       
  1151 #ifndef QT_NO_DATESTRING
       
  1152             if (field.value().toTime().isValid()) {
       
  1153                 r = QLatin1Char('\'') + field.value().toTime().toString(Qt::ISODate) + QLatin1Char('\'');
       
  1154             } else
       
  1155 #endif
       
  1156             {
       
  1157                 r = QLatin1String("NULL");
       
  1158             }
       
  1159             break;
       
  1160         case QVariant::String:
       
  1161         {
       
  1162             // Escape '\' characters
       
  1163             r = QSqlDriver::formatValue(field, trimStrings);
       
  1164             r.replace(QLatin1String("\\"), QLatin1String("\\\\"));
       
  1165             break;
       
  1166         }
       
  1167         case QVariant::Bool:
       
  1168             if (field.value().toBool())
       
  1169                 r = QLatin1String("TRUE");
       
  1170             else
       
  1171                 r = QLatin1String("FALSE");
       
  1172             break;
       
  1173         case QVariant::ByteArray: {
       
  1174             QByteArray ba(field.value().toByteArray());
       
  1175             size_t len;
       
  1176 #if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 80200
       
  1177             unsigned char *data = PQescapeByteaConn(d->connection, (unsigned char*)ba.constData(), ba.size(), &len);
       
  1178 #else
       
  1179             unsigned char *data = PQescapeBytea((unsigned char*)ba.constData(), ba.size(), &len);
       
  1180 #endif
       
  1181             r += QLatin1Char('\'');
       
  1182             r += QLatin1String((const char*)data);
       
  1183             r += QLatin1Char('\'');
       
  1184             qPQfreemem(data);
       
  1185             break;
       
  1186         }
       
  1187         case QVariant::Double: {
       
  1188             double val = field.value().toDouble();
       
  1189             if (isnan(val))
       
  1190                 r = QLatin1String("'NaN'");
       
  1191             else {
       
  1192                 int res = isinf(val);
       
  1193                 if (res == 1)
       
  1194                     r = QLatin1String("'Infinity'");
       
  1195                 else if (res == -1)
       
  1196                     r = QLatin1String("'-Infinity'");
       
  1197                 else
       
  1198                     r = QSqlDriver::formatValue(field, trimStrings);
       
  1199             }
       
  1200             break;
       
  1201         }
       
  1202         default:
       
  1203             r = QSqlDriver::formatValue(field, trimStrings);
       
  1204             break;
       
  1205         }
       
  1206     }
       
  1207     return r;
       
  1208 }
       
  1209 
       
  1210 QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
       
  1211 {
       
  1212     QString res = identifier;
       
  1213     if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) {
       
  1214         res.replace(QLatin1Char('"'), QLatin1String("\"\""));
       
  1215         res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
       
  1216         res.replace(QLatin1Char('.'), QLatin1String("\".\""));
       
  1217     }
       
  1218     return res;
       
  1219 }
       
  1220 
       
  1221 bool QPSQLDriver::isOpen() const
       
  1222 {
       
  1223     return PQstatus(d->connection) == CONNECTION_OK;
       
  1224 }
       
  1225 
       
  1226 QPSQLDriver::Protocol QPSQLDriver::protocol() const
       
  1227 {
       
  1228     return d->pro;
       
  1229 }
       
  1230 
       
  1231 bool QPSQLDriver::subscribeToNotificationImplementation(const QString &name)
       
  1232 {
       
  1233     if (!isOpen()) {
       
  1234         qWarning("QPSQLDriver::subscribeToNotificationImplementation: database not open.");
       
  1235         return false;
       
  1236     }
       
  1237 
       
  1238     if (d->seid.contains(name)) {
       
  1239         qWarning("QPSQLDriver::subscribeToNotificationImplementation: already subscribing to '%s'.",
       
  1240             qPrintable(name));
       
  1241         return false;
       
  1242     }
       
  1243 
       
  1244     int socket = PQsocket(d->connection);
       
  1245     if (socket) {
       
  1246         QString query = QLatin1String("LISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
       
  1247         if (PQresultStatus(PQexec(d->connection,
       
  1248                                   d->isUtf8 ? query.toUtf8().constData()
       
  1249                                             : query.toLocal8Bit().constData())
       
  1250                           ) != PGRES_COMMAND_OK) {
       
  1251             setLastError(qMakeError(tr("Unable to subscribe"), QSqlError::StatementError, d));
       
  1252             return false;
       
  1253         }
       
  1254 
       
  1255         if (!d->sn) {
       
  1256             d->sn = new QSocketNotifier(socket, QSocketNotifier::Read);
       
  1257             connect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
       
  1258         }
       
  1259     }
       
  1260 
       
  1261     d->seid << name;
       
  1262     return true;
       
  1263 }
       
  1264 
       
  1265 bool QPSQLDriver::unsubscribeFromNotificationImplementation(const QString &name)
       
  1266 {
       
  1267     if (!isOpen()) {
       
  1268         qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: database not open.");
       
  1269         return false;
       
  1270     }
       
  1271 
       
  1272     if (!d->seid.contains(name)) {
       
  1273         qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: not subscribed to '%s'.",
       
  1274             qPrintable(name));
       
  1275         return false;
       
  1276     }
       
  1277 
       
  1278     QString query = QLatin1String("UNLISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
       
  1279     if (PQresultStatus(PQexec(d->connection,
       
  1280                               d->isUtf8 ? query.toUtf8().constData()
       
  1281                                         : query.toLocal8Bit().constData())
       
  1282                       ) != PGRES_COMMAND_OK) {
       
  1283         setLastError(qMakeError(tr("Unable to unsubscribe"), QSqlError::StatementError, d));
       
  1284         return false;
       
  1285     }
       
  1286 
       
  1287     d->seid.removeAll(name);
       
  1288 
       
  1289     if (d->seid.isEmpty()) {
       
  1290         disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
       
  1291         delete d->sn;
       
  1292         d->sn = 0;
       
  1293     }
       
  1294 
       
  1295     return true;
       
  1296 }
       
  1297 
       
  1298 QStringList QPSQLDriver::subscribedToNotificationsImplementation() const
       
  1299 {
       
  1300     return d->seid;
       
  1301 }
       
  1302 
       
  1303 void QPSQLDriver::_q_handleNotification(int)
       
  1304 {
       
  1305     PQconsumeInput(d->connection);
       
  1306 
       
  1307     PGnotify *notify = 0;
       
  1308     while((notify = PQnotifies(d->connection)) != 0) {
       
  1309         QString name(QLatin1String(notify->relname));
       
  1310         if (d->seid.contains(name))
       
  1311             emit notification(name);
       
  1312         else
       
  1313             qWarning("QPSQLDriver: received notification for '%s' which isn't subscribed to.",
       
  1314                     qPrintable(name));
       
  1315 
       
  1316         qPQfreemem(notify);
       
  1317     }
       
  1318 }
       
  1319 
       
  1320 QT_END_NAMESPACE