~oif-team/ubuntu/natty/qt4-x11/xi2.1

« back to all changes in this revision

Viewing changes to src/sql/drivers/psql/qsql_psql.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-08-24 04:09:09 UTC
  • Revision ID: james.westby@ubuntu.com-20050824040909-xmxe9jfr4a0w5671
Tags: upstream-4.0.0
ImportĀ upstreamĀ versionĀ 4.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/****************************************************************************
 
2
**
 
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
 
4
**
 
5
** This file is part of the sql module of the Qt Toolkit.
 
6
**
 
7
** This file may be distributed under the terms of the Q Public License
 
8
** as defined by Trolltech AS of Norway and appearing in the file
 
9
** LICENSE.QPL included in the packaging of this file.
 
10
**
 
11
** This file may be distributed and/or modified under the terms of the
 
12
** GNU General Public License version 2 as published by the Free Software
 
13
** Foundation and appearing in the file LICENSE.GPL included in the
 
14
** packaging of this file.
 
15
**
 
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
 
17
**   information about Qt Commercial License Agreements.
 
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
 
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
 
20
**
 
21
** Contact info@trolltech.com if any conditions of this licensing are
 
22
** not clear to you.
 
23
**
 
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
26
**
 
27
****************************************************************************/
 
28
 
 
29
#include "qsql_psql.h"
 
30
 
 
31
#include <math.h>
 
32
 
 
33
#include <qcoreapplication.h>
 
34
#include <qvariant.h>
 
35
#include <qdatetime.h>
 
36
#include <qregexp.h>
 
37
#include <qsqlerror.h>
 
38
#include <qsqlfield.h>
 
39
#include <qsqlindex.h>
 
40
#include <qsqlrecord.h>
 
41
#include <qsqlquery.h>
 
42
#include <qstringlist.h>
 
43
 
 
44
#include <libpq-fe.h>
 
45
 
 
46
#include <stdlib.h>
 
47
 
 
48
// workaround for postgres defining their OIDs in a private header file
 
49
#define QBOOLOID 16
 
50
#define QINT8OID 20
 
51
#define QINT2OID 21
 
52
#define QINT4OID 23
 
53
#define QNUMERICOID 1700
 
54
#define QFLOAT4OID 700
 
55
#define QFLOAT8OID 701
 
56
#define QABSTIMEOID 702
 
57
#define QRELTIMEOID 703
 
58
#define QDATEOID 1082
 
59
#define QTIMEOID 1083
 
60
#define QTIMETZOID 1266
 
61
#define QTIMESTAMPOID 1114
 
62
#define QTIMESTAMPTZOID 1184
 
63
#define QOIDOID 2278
 
64
#define QBYTEAOID 17
 
65
#define QREGPROCOID 24
 
66
#define QXIDOID 28
 
67
#define QCIDOID 29
 
68
 
 
69
Q_DECLARE_METATYPE(PGconn*)
 
70
Q_DECLARE_METATYPE(PGresult*)
 
71
 
 
72
class QPSQLDriverPrivate
 
73
{
 
74
public:
 
75
    QPSQLDriverPrivate(): connection(0), isUtf8(false), pro(QPSQLDriver::Version6) {}
 
76
    PGconn *connection;
 
77
    bool isUtf8;
 
78
    QPSQLDriver::Protocol pro;
 
79
 
 
80
    void appendTables(QStringList &tl, QSqlQuery &t, QChar type);
 
81
};
 
82
 
 
83
void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type)
 
84
{
 
85
    QString query;
 
86
    if (pro >= QPSQLDriver::Version73) {
 
87
        query = QString::fromLatin1("select pg_class.relname, pg_namespace.nspname from pg_class "
 
88
                  "left join pg_namespace on (pg_class.relnamespace = pg_namespace.oid) "
 
89
                  "where (pg_class.relkind = '%1') and (pg_class.relname !~ '^Inv') "
 
90
                  "and (pg_class.relname !~ '^pg_') "
 
91
                  "and (pg_namespace.nspname != 'information_schema') "
 
92
                  "and pg_table_is_visible(pg_class.oid)").arg(type);
 
93
    } else {
 
94
        query = QString::fromLatin1("select relname, null from pg_class where (relkind = 'r') "
 
95
                  "and (relname !~ '^Inv') "
 
96
                  "and (relname !~ '^pg_') ");
 
97
    }
 
98
    t.exec(query);
 
99
    while (t.next()) {
 
100
        QString schema = t.value(1).toString();
 
101
        if (schema.isEmpty() || schema == QLatin1String("public"))
 
102
            tl.append(t.value(0).toString());
 
103
        else
 
104
            tl.append(t.value(0).toString().prepend(QLatin1Char('.')).prepend(schema));
 
105
    }
 
106
}
 
107
 
 
108
class QPSQLResultPrivate
 
109
{
 
110
public:
 
111
    QPSQLResultPrivate(QPSQLResult *qq): q(qq), driver(0), result(0), currentSize(-1) {}
 
112
 
 
113
    QPSQLResult *q;
 
114
    const QPSQLDriverPrivate *driver;
 
115
    PGresult *result;
 
116
    int currentSize;
 
117
 
 
118
    bool processResults();
 
119
};
 
120
 
 
121
static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
 
122
                            const QPSQLDriverPrivate *p)
 
123
{
 
124
    const char *s = PQerrorMessage(p->connection);
 
125
    QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
 
126
    return QSqlError(QLatin1String("QPSQL: ") + err, msg, type);
 
127
}
 
128
 
 
129
bool QPSQLResultPrivate::processResults()
 
130
{
 
131
    if (!result)
 
132
        return false;
 
133
 
 
134
    int status = PQresultStatus(result);
 
135
    if (status == PGRES_TUPLES_OK) {
 
136
        q->setSelect(true);
 
137
        q->setActive(true);
 
138
        currentSize = PQntuples(result);
 
139
        return true;
 
140
    } else if (status == PGRES_COMMAND_OK) {
 
141
        q->setSelect(false);
 
142
        q->setActive(true);
 
143
        currentSize = -1;
 
144
        return true;
 
145
    }
 
146
    q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
 
147
                    "Unable to create query"), QSqlError::StatementError, driver));
 
148
    return false;
 
149
}
 
150
 
 
151
static QVariant::Type qDecodePSQLType(int t)
 
152
{
 
153
    QVariant::Type type = QVariant::Invalid;
 
154
    switch (t) {
 
155
    case QBOOLOID:
 
156
        type = QVariant::Bool;
 
157
        break;
 
158
    case QINT8OID:
 
159
        type = QVariant::LongLong;
 
160
        break;
 
161
    case QINT2OID:
 
162
    case QINT4OID:
 
163
    case QOIDOID:
 
164
    case QREGPROCOID:
 
165
    case QXIDOID:
 
166
    case QCIDOID:
 
167
        type = QVariant::Int;
 
168
        break;
 
169
    case QNUMERICOID:
 
170
    case QFLOAT4OID:
 
171
    case QFLOAT8OID:
 
172
        type = QVariant::Double;
 
173
        break;
 
174
    case QABSTIMEOID:
 
175
    case QRELTIMEOID:
 
176
    case QDATEOID:
 
177
        type = QVariant::Date;
 
178
        break;
 
179
    case QTIMEOID:
 
180
    case QTIMETZOID:
 
181
        type = QVariant::Time;
 
182
        break;
 
183
    case QTIMESTAMPOID:
 
184
    case QTIMESTAMPTZOID:
 
185
        type = QVariant::DateTime;
 
186
        break;
 
187
    case QBYTEAOID:
 
188
        type = QVariant::ByteArray;
 
189
        break;
 
190
    default:
 
191
        type = QVariant::String;
 
192
        break;
 
193
    }
 
194
    return type;
 
195
}
 
196
 
 
197
QPSQLResult::QPSQLResult(const QPSQLDriver* db, const QPSQLDriverPrivate* p)
 
198
    : QSqlResult(db)
 
199
{
 
200
    d = new QPSQLResultPrivate(this);
 
201
    d->driver = p;
 
202
}
 
203
 
 
204
QPSQLResult::~QPSQLResult()
 
205
{
 
206
    cleanup();
 
207
    delete d;
 
208
}
 
209
 
 
210
QVariant QPSQLResult::handle() const
 
211
{
 
212
    return qVariantFromValue(d->result);
 
213
}
 
214
 
 
215
void QPSQLResult::cleanup()
 
216
{
 
217
    if (d->result)
 
218
        PQclear(d->result);
 
219
    d->result = 0;
 
220
    setAt(QSql::BeforeFirstRow);
 
221
    d->currentSize = -1;
 
222
    setActive(false);
 
223
}
 
224
 
 
225
bool QPSQLResult::fetch(int i)
 
226
{
 
227
    if (!isActive())
 
228
        return false;
 
229
    if (i < 0)
 
230
        return false;
 
231
    if (i >= d->currentSize)
 
232
        return false;
 
233
    if (at() == i)
 
234
        return true;
 
235
    setAt(i);
 
236
    return true;
 
237
}
 
238
 
 
239
bool QPSQLResult::fetchFirst()
 
240
{
 
241
    return fetch(0);
 
242
}
 
243
 
 
244
bool QPSQLResult::fetchLast()
 
245
{
 
246
    return fetch(PQntuples(d->result) - 1);
 
247
}
 
248
 
 
249
QVariant QPSQLResult::data(int i)
 
250
{
 
251
    if (i >= PQnfields(d->result)) {
 
252
        qWarning("QPSQLResult::data: column %d out of range", i);
 
253
        return QVariant();
 
254
    }
 
255
    int ptype = PQftype(d->result, i);
 
256
    QVariant::Type type = qDecodePSQLType(ptype);
 
257
    const char *val = PQgetvalue(d->result, at(), i);
 
258
    if (PQgetisnull(d->result, at(), i))
 
259
        return QVariant(type);
 
260
    switch (type) {
 
261
    case QVariant::Bool:
 
262
        return QVariant((bool)(val[0] == 't'));
 
263
    case QVariant::String:
 
264
        return d->driver->isUtf8 ? QString::fromUtf8(val) : QString::fromAscii(val);
 
265
    case QVariant::LongLong:
 
266
        if (val[0] == '-')
 
267
            return QString::fromLatin1(val).toLongLong();
 
268
        else
 
269
            return QString::fromLatin1(val).toULongLong();
 
270
    case QVariant::Int:
 
271
        return atoi(val);
 
272
    case QVariant::Double:
 
273
        if (ptype == QNUMERICOID)
 
274
            return QString::fromAscii(val);
 
275
        return strtod(val, 0);
 
276
    case QVariant::Date:
 
277
        if (val[0] == '\0') {
 
278
            return QVariant(QDate());
 
279
        } else {
 
280
            return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate));
 
281
        }
 
282
    case QVariant::Time: {
 
283
        const QString str = QString::fromLatin1(val);
 
284
        if (str.isEmpty())
 
285
            return QVariant(QTime());
 
286
        if (str.at(str.length() - 3) == QLatin1Char('+'))
 
287
            // strip the timezone
 
288
            return QVariant(QTime::fromString(str.left(str.length() - 3), Qt::ISODate));
 
289
        return QVariant(QTime::fromString(str, Qt::ISODate));
 
290
    }
 
291
    case QVariant::DateTime: {
 
292
        QString dtval = QString::fromLatin1(val);
 
293
        if (dtval.length() < 10)
 
294
            return QVariant(QDateTime());
 
295
        // remove the timezone
 
296
        if (dtval.at(dtval.length() - 3) == QLatin1Char('+'))
 
297
            dtval.chop(3);
 
298
        // milliseconds are sometimes returned with 2 digits only
 
299
        if (dtval.at(dtval.length() - 3).isPunct())
 
300
            dtval += QLatin1Char('0');
 
301
        if (dtval.isEmpty())
 
302
            return QVariant(QDateTime());
 
303
        else
 
304
            return QVariant(QDateTime::fromString(dtval, Qt::ISODate));
 
305
    }
 
306
    case QVariant::ByteArray: {
 
307
        int i = 0;
 
308
        QByteArray ba(val);
 
309
        while (i < ba.length()) {
 
310
            if (ba.at(i) == '\\') {
 
311
                if (QChar::fromLatin1(ba.at(i + 1)).isDigit()) {
 
312
                    char *v = ba.data() + i + 3;
 
313
                    ba[i] = static_cast<char>(strtol(v - 2, &v, 8));
 
314
                    ba.remove(i + 1, 3);
 
315
                } else {
 
316
                    ba[i] = ba.at(i + 1);
 
317
                    ba.remove(i + 1, 1);
 
318
                }
 
319
            }
 
320
            ++i;
 
321
        }
 
322
        return QVariant(ba);
 
323
    }
 
324
    default:
 
325
    case QVariant::Invalid:
 
326
        qWarning("QPSQLResult::data: unknown data type");
 
327
    }
 
328
    return QVariant();
 
329
}
 
330
 
 
331
bool QPSQLResult::isNull(int field)
 
332
{
 
333
    PQgetvalue(d->result, at(), field);
 
334
    return PQgetisnull(d->result, at(), field);
 
335
}
 
336
 
 
337
bool QPSQLResult::reset (const QString& query)
 
338
{
 
339
    cleanup();
 
340
    if (!driver())
 
341
        return false;
 
342
    if (!driver()->isOpen() || driver()->isOpenError())
 
343
        return false;
 
344
    d->result = PQexec(d->driver->connection,
 
345
                       d->driver->isUtf8 ? query.toUtf8().constData()
 
346
                                         : query.toLocal8Bit().constData());
 
347
    return d->processResults();
 
348
}
 
349
 
 
350
int QPSQLResult::size()
 
351
{
 
352
    return d->currentSize;
 
353
}
 
354
 
 
355
int QPSQLResult::numRowsAffected()
 
356
{
 
357
    return QString::fromLatin1(PQcmdTuples(d->result)).toInt();
 
358
}
 
359
 
 
360
QVariant QPSQLResult::lastInsertId() const
 
361
{
 
362
    if (isActive()) {
 
363
        Oid id = PQoidValue(d->result);
 
364
        if (id != InvalidOid)
 
365
            return QVariant(id);
 
366
    }
 
367
    return QVariant();
 
368
}
 
369
 
 
370
QSqlRecord QPSQLResult::record() const
 
371
{
 
372
    QSqlRecord info;
 
373
    if (!isActive() || !isSelect())
 
374
        return info;
 
375
 
 
376
    int count = PQnfields(d->result);
 
377
    for (int i = 0; i < count; ++i) {
 
378
        QSqlField f;
 
379
        if (d->driver->isUtf8)
 
380
            f.setName(QString::fromUtf8(PQfname(d->result, i)));
 
381
        else
 
382
            f.setName(QString::fromLocal8Bit(PQfname(d->result, i)));
 
383
        f.setType(qDecodePSQLType(PQftype(d->result, i)));
 
384
        int len = PQfsize(d->result, i);
 
385
        int precision = PQfmod(d->result, i);
 
386
        // swap length and precision if length == -1
 
387
        if (len == -1 && precision > -1) {
 
388
            len = precision - 4;
 
389
            precision = -1;
 
390
        }
 
391
        f.setLength(len);
 
392
        f.setPrecision(precision);
 
393
        f.setSqlType(PQftype(d->result, i));
 
394
        info.append(f);
 
395
    }
 
396
    return info;
 
397
}
 
398
 
 
399
///////////////////////////////////////////////////////////////////
 
400
 
 
401
static bool setEncodingUtf8(PGconn* connection)
 
402
{
 
403
    PGresult* result = PQexec(connection, "SET CLIENT_ENCODING TO 'UNICODE'");
 
404
    int status = PQresultStatus(result);
 
405
    PQclear(result);
 
406
    return status == PGRES_COMMAND_OK;
 
407
}
 
408
 
 
409
static void setDatestyle(PGconn* connection)
 
410
{
 
411
    PGresult* result = PQexec(connection, "SET DATESTYLE TO 'ISO'");
 
412
    int status =  PQresultStatus(result);
 
413
    if (status != PGRES_COMMAND_OK)
 
414
        qWarning("%s", PQerrorMessage(connection));
 
415
    PQclear(result);
 
416
}
 
417
 
 
418
static QPSQLDriver::Protocol getPSQLVersion(PGconn* connection)
 
419
{
 
420
    PGresult* result = PQexec(connection, "select version()");
 
421
    int status =  PQresultStatus(result);
 
422
    if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) {
 
423
        QString val = QString::fromAscii(PQgetvalue(result, 0, 0));
 
424
        PQclear(result);
 
425
        QRegExp rx(QLatin1String("(\\d+)\\.(\\d+)"));
 
426
        rx.setMinimal(true); // enforce non-greedy RegExp
 
427
        if (rx.indexIn(val) != -1) {
 
428
            int vMaj = rx.cap(1).toInt();
 
429
            int vMin = rx.cap(2).toInt();
 
430
            if (vMaj < 6) {
 
431
                qWarning("This version of PostgreSQL is not supported and may not work.");
 
432
                return QPSQLDriver::Version6;
 
433
            }
 
434
            if (vMaj == 6) {
 
435
                return QPSQLDriver::Version6;
 
436
            } else if (vMaj == 7) {
 
437
                if (vMin < 1)
 
438
                    return QPSQLDriver::Version7;
 
439
                else if (vMin < 3)
 
440
                    return QPSQLDriver::Version71;
 
441
            }
 
442
            return QPSQLDriver::Version73;
 
443
        }
 
444
    } else {
 
445
        qWarning("This version of PostgreSQL is not supported and may not work.");
 
446
    }
 
447
 
 
448
    return QPSQLDriver::Version6;
 
449
}
 
450
 
 
451
QPSQLDriver::QPSQLDriver(QObject *parent)
 
452
    : QSqlDriver(parent)
 
453
{
 
454
    init();
 
455
}
 
456
 
 
457
QPSQLDriver::QPSQLDriver(PGconn * conn, QObject * parent)
 
458
    : QSqlDriver(parent)
 
459
{
 
460
    init();
 
461
    d->connection = conn;
 
462
    if (conn) {
 
463
        d->pro = getPSQLVersion(d->connection);
 
464
        setOpen(true);
 
465
        setOpenError(false);
 
466
    }
 
467
}
 
468
 
 
469
void QPSQLDriver::init()
 
470
{
 
471
    d = new QPSQLDriverPrivate();
 
472
}
 
473
 
 
474
QPSQLDriver::~QPSQLDriver()
 
475
{
 
476
    if (d->connection)
 
477
        PQfinish(d->connection);
 
478
    delete d;
 
479
}
 
480
 
 
481
QVariant QPSQLDriver::handle() const
 
482
{
 
483
    return qVariantFromValue(d->connection);
 
484
}
 
485
 
 
486
bool QPSQLDriver::hasFeature(DriverFeature f) const
 
487
{
 
488
    switch (f) {
 
489
    case Transactions:
 
490
    case QuerySize:
 
491
    case LastInsertId:
 
492
        return true;
 
493
    case BLOB:
 
494
        return d->pro >= QPSQLDriver::Version71;
 
495
    case Unicode:
 
496
        return d->isUtf8;
 
497
    default:
 
498
        return false;
 
499
    }
 
500
}
 
501
 
 
502
/*
 
503
   Quote a string for inclusion into the connection string
 
504
   \ -> \\
 
505
   ' -> \'
 
506
   surround string by single quotes
 
507
 */
 
508
static QString qQuote(QString s)
 
509
{
 
510
    s.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
 
511
    s.replace(QLatin1Char('\''), QLatin1String("\\'"));
 
512
    s.append(QLatin1Char('\'')).prepend(QLatin1Char('\''));
 
513
    return s;
 
514
}
 
515
 
 
516
bool QPSQLDriver::open(const QString & db,
 
517
                        const QString & user,
 
518
                        const QString & password,
 
519
                        const QString & host,
 
520
                        int port,
 
521
                        const QString& connOpts)
 
522
{
 
523
    if (isOpen())
 
524
        close();
 
525
    QString connectString;
 
526
    if (!host.isEmpty())
 
527
        connectString.append(QLatin1String("host=")).append(qQuote(host));
 
528
    if (!db.isEmpty())
 
529
        connectString.append(QLatin1String(" dbname=")).append(qQuote(db));
 
530
    if (!user.isEmpty())
 
531
        connectString.append(QLatin1String(" user=")).append(qQuote(user));
 
532
    if (!password.isEmpty())
 
533
        connectString.append(QLatin1String(" password=")).append(qQuote(password));
 
534
    if (port != -1)
 
535
        connectString.append(QLatin1String(" port=")).append(qQuote(QString::number(port)));
 
536
 
 
537
    // add any connect options - the server will handle error detection
 
538
    if (!connOpts.isEmpty()) {
 
539
        QString opt = connOpts;
 
540
        opt.replace(QLatin1Char(';'), QLatin1Char(' '), Qt::CaseInsensitive);
 
541
        connectString.append(QLatin1Char(' ')).append(opt);
 
542
    }
 
543
 
 
544
    d->connection = PQconnectdb(connectString.toLocal8Bit().constData());
 
545
    if (PQstatus(d->connection) == CONNECTION_BAD) {
 
546
        setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
 
547
        setOpenError(true);
 
548
        return false;
 
549
    }
 
550
 
 
551
    d->pro = getPSQLVersion(d->connection);
 
552
    d->isUtf8 = setEncodingUtf8(d->connection);
 
553
    setDatestyle(d->connection);
 
554
 
 
555
    setOpen(true);
 
556
    setOpenError(false);
 
557
    return true;
 
558
}
 
559
 
 
560
void QPSQLDriver::close()
 
561
{
 
562
    if (isOpen()) {
 
563
        if (d->connection)
 
564
            PQfinish(d->connection);
 
565
        d->connection = 0;
 
566
        setOpen(false);
 
567
        setOpenError(false);
 
568
    }
 
569
}
 
570
 
 
571
QSqlResult *QPSQLDriver::createResult() const
 
572
{
 
573
    return new QPSQLResult(this, d);
 
574
}
 
575
 
 
576
bool QPSQLDriver::beginTransaction()
 
577
{
 
578
    if (!isOpen()) {
 
579
        qWarning("QPSQLDriver::beginTransaction: Database not open");
 
580
        return false;
 
581
    }
 
582
    PGresult* res = PQexec(d->connection, "BEGIN");
 
583
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
 
584
        PQclear(res);
 
585
        setLastError(qMakeError(tr("Could not begin transaction"),
 
586
                                QSqlError::TransactionError, d));
 
587
        return false;
 
588
    }
 
589
    PQclear(res);
 
590
    return true;
 
591
}
 
592
 
 
593
bool QPSQLDriver::commitTransaction()
 
594
{
 
595
    if (!isOpen()) {
 
596
        qWarning("QPSQLDriver::commitTransaction: Database not open");
 
597
        return false;
 
598
    }
 
599
    PGresult* res = PQexec(d->connection, "COMMIT");
 
600
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
 
601
        PQclear(res);
 
602
        setLastError(qMakeError(tr("Could not commit transaction"),
 
603
                                QSqlError::TransactionError, d));
 
604
        return false;
 
605
    }
 
606
    PQclear(res);
 
607
    return true;
 
608
}
 
609
 
 
610
bool QPSQLDriver::rollbackTransaction()
 
611
{
 
612
    if (!isOpen()) {
 
613
        qWarning("QPSQLDriver::rollbackTransaction: Database not open");
 
614
        return false;
 
615
    }
 
616
    PGresult* res = PQexec(d->connection, "ROLLBACK");
 
617
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
 
618
        setLastError(qMakeError(tr("Could not rollback transaction"),
 
619
                                QSqlError::TransactionError, d));
 
620
        PQclear(res);
 
621
        return false;
 
622
    }
 
623
    PQclear(res);
 
624
    return true;
 
625
}
 
626
 
 
627
QStringList QPSQLDriver::tables(QSql::TableType type) const
 
628
{
 
629
    QStringList tl;
 
630
    if (!isOpen())
 
631
        return tl;
 
632
    QSqlQuery t(createResult());
 
633
    t.setForwardOnly(true);
 
634
 
 
635
    if (type & QSql::Tables)
 
636
        d->appendTables(tl, t, QLatin1Char('r'));
 
637
    if (type & QSql::Views)
 
638
        d->appendTables(tl, t, QLatin1Char('v'));
 
639
    if (type & QSql::SystemTables) {
 
640
        t.exec(QLatin1String("select relname from pg_class where (relkind = 'r') "
 
641
                "and (relname like 'pg_%') "));
 
642
        while (t.next())
 
643
            tl.append(t.value(0).toString());
 
644
    }
 
645
 
 
646
    return tl;
 
647
}
 
648
 
 
649
static void qSplitTableName(QString &tablename, QString &schema)
 
650
{
 
651
    int dot = tablename.indexOf(QLatin1Char('.'));
 
652
    if (dot == -1)
 
653
        return;
 
654
    schema = tablename.left(dot);
 
655
    tablename = tablename.mid(dot + 1);
 
656
}
 
657
 
 
658
QSqlIndex QPSQLDriver::primaryIndex(const QString& tablename) const
 
659
{
 
660
    QSqlIndex idx(tablename);
 
661
    if (!isOpen())
 
662
        return idx;
 
663
    QSqlQuery i(createResult());
 
664
    QString stmt;
 
665
 
 
666
    QString tbl = tablename;
 
667
    QString schema;
 
668
    qSplitTableName(tbl, schema);
 
669
 
 
670
    switch(d->pro) {
 
671
    case QPSQLDriver::Version6:
 
672
        stmt = QLatin1String("select pg_att1.attname, int(pg_att1.atttypid), pg_cl.relname "
 
673
                "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind "
 
674
                "where lower(pg_cl.relname) = '%1_pkey' "
 
675
                "and pg_cl.oid = pg_ind.indexrelid "
 
676
                "and pg_att2.attrelid = pg_ind.indexrelid "
 
677
                "and pg_att1.attrelid = pg_ind.indrelid "
 
678
                "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] "
 
679
                "order by pg_att2.attnum");
 
680
        break;
 
681
    case QPSQLDriver::Version7:
 
682
    case QPSQLDriver::Version71:
 
683
        stmt = QLatin1String("select pg_att1.attname, pg_att1.atttypid::int, pg_cl.relname "
 
684
                "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind "
 
685
                "where lower(pg_cl.relname) = '%1_pkey' "
 
686
                "and pg_cl.oid = pg_ind.indexrelid "
 
687
                "and pg_att2.attrelid = pg_ind.indexrelid "
 
688
                "and pg_att1.attrelid = pg_ind.indrelid "
 
689
                "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] "
 
690
                "order by pg_att2.attnum");
 
691
        break;
 
692
    case QPSQLDriver::Version73:
 
693
        stmt = QLatin1String("SELECT pg_attribute.attname, pg_attribute.atttypid::int, "
 
694
                "pg_class.relname "
 
695
                "FROM pg_attribute, pg_class "
 
696
                "WHERE %1 pg_class.oid = "
 
697
                "(SELECT indexrelid FROM pg_index WHERE indisprimary = true AND indrelid = "
 
698
                " (SELECT oid FROM pg_class WHERE lower(relname) = '%2')) "
 
699
                "AND pg_attribute.attrelid = pg_class.oid "
 
700
                "AND pg_attribute.attisdropped = false "
 
701
                "AND pg_table_is_visible(pg_class.oid) "
 
702
                "ORDER BY pg_attribute.attnum");
 
703
        if (schema.isEmpty())
 
704
            stmt = stmt.arg(QLatin1String(""));
 
705
        else
 
706
            stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from "
 
707
                   "pg_namespace where pg_namespace.nspname = '%1') AND ").arg(schema.toLower()));
 
708
        break;
 
709
    }
 
710
 
 
711
    i.exec(stmt.arg(tbl.toLower()));
 
712
    while (i.isActive() && i.next()) {
 
713
        QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt()));
 
714
        idx.append(f);
 
715
        idx.setName(i.value(2).toString());
 
716
    }
 
717
    return idx;
 
718
}
 
719
 
 
720
QSqlRecord QPSQLDriver::record(const QString& tablename) const
 
721
{
 
722
    QSqlRecord info;
 
723
    if (!isOpen())
 
724
        return info;
 
725
 
 
726
    QString tbl = tablename;
 
727
    QString schema;
 
728
    qSplitTableName(tbl, schema);
 
729
 
 
730
    QString stmt;
 
731
    switch(d->pro) {
 
732
    case QPSQLDriver::Version6:
 
733
        stmt = QLatin1String("select pg_attribute.attname, int(pg_attribute.atttypid), "
 
734
                "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
 
735
                "int(pg_attribute.attrelid), pg_attribute.attnum "
 
736
                "from pg_class, pg_attribute "
 
737
                "where lower(pg_class.relname) = '%1' "
 
738
                "and pg_attribute.attnum > 0 "
 
739
                "and pg_attribute.attrelid = pg_class.oid ");
 
740
        break;
 
741
    case QPSQLDriver::Version7:
 
742
        stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, "
 
743
                "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
 
744
                "pg_attribute.attrelid::int, pg_attribute.attnum "
 
745
                "from pg_class, pg_attribute "
 
746
                "where lower(pg_class.relname) = '%1' "
 
747
                "and pg_attribute.attnum > 0 "
 
748
                "and pg_attribute.attrelid = pg_class.oid ");
 
749
        break;
 
750
    case QPSQLDriver::Version71:
 
751
        stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, "
 
752
                "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
 
753
                "pg_attrdef.adsrc "
 
754
                "from pg_class, pg_attribute "
 
755
                "left join pg_attrdef on (pg_attrdef.adrelid = "
 
756
                "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) "
 
757
                "where lower(pg_class.relname) = '%1' "
 
758
                "and pg_attribute.attnum > 0 "
 
759
                "and pg_attribute.attrelid = pg_class.oid "
 
760
                "order by pg_attribute.attnum ");
 
761
        break;
 
762
    case QPSQLDriver::Version73:
 
763
        stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, "
 
764
                "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
 
765
                "pg_attrdef.adsrc "
 
766
                "from pg_class, pg_attribute "
 
767
                "left join pg_attrdef on (pg_attrdef.adrelid = "
 
768
                "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) "
 
769
                "where %1 "
 
770
                "lower(pg_class.relname) = '%2' "
 
771
                "and pg_attribute.attnum > 0 "
 
772
                "and pg_attribute.attrelid = pg_class.oid "
 
773
                "and pg_attribute.attisdropped = false "
 
774
                "and pg_table_is_visible(pg_class.oid) "
 
775
                "order by pg_attribute.attnum ");
 
776
        if (schema.isEmpty())
 
777
            stmt = stmt.arg(QLatin1String(""));
 
778
        else
 
779
            stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from "
 
780
                   "pg_namespace where pg_namespace.nspname = '%1') and ").arg(schema.toLower()));
 
781
        break;
 
782
    }
 
783
 
 
784
    QSqlQuery query(createResult());
 
785
    query.exec(stmt.arg(tbl.toLower()));
 
786
    if (d->pro >= QPSQLDriver::Version71) {
 
787
        while (query.next()) {
 
788
            int len = query.value(3).toInt();
 
789
            int precision = query.value(4).toInt();
 
790
            // swap length and precision if length == -1
 
791
            if (len == -1 && precision > -1) {
 
792
                len = precision - 4;
 
793
                precision = -1;
 
794
            }
 
795
            QString defVal = query.value(5).toString();
 
796
            if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\''))
 
797
                defVal = defVal.mid(1, defVal.length() - 2);
 
798
            QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()));
 
799
            f.setRequired(query.value(2).toBool());
 
800
            f.setLength(len);
 
801
            f.setPrecision(precision);
 
802
            f.setDefaultValue(defVal);
 
803
            f.setSqlType(query.value(1).toInt());
 
804
            info.append(f);
 
805
        }
 
806
    } else {
 
807
        // Postgres < 7.1 cannot handle outer joins
 
808
        while (query.next()) {
 
809
            QString defVal;
 
810
            QString stmt2 = QLatin1String("select pg_attrdef.adsrc from pg_attrdef where "
 
811
                            "pg_attrdef.adrelid = %1 and pg_attrdef.adnum = %2 ");
 
812
            QSqlQuery query2(createResult());
 
813
            query2.exec(stmt2.arg(query.value(5).toInt()).arg(query.value(6).toInt()));
 
814
            if (query2.isActive() && query2.next())
 
815
                defVal = query2.value(0).toString();
 
816
            if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\''))
 
817
                defVal = defVal.mid(1, defVal.length() - 2);
 
818
            int len = query.value(3).toInt();
 
819
            int precision = query.value(4).toInt();
 
820
            // swap length and precision if length == -1
 
821
            if (len == -1 && precision > -1) {
 
822
                len = precision - 4;
 
823
                precision = -1;
 
824
            }
 
825
            QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()));
 
826
            f.setRequired(query.value(2).toBool());
 
827
            f.setLength(len);
 
828
            f.setPrecision(precision);
 
829
            f.setDefaultValue(defVal);
 
830
            f.setSqlType(query.value(1).toInt());
 
831
            info.append(f);
 
832
        }
 
833
    }
 
834
 
 
835
    return info;
 
836
}
 
837
 
 
838
QString QPSQLDriver::formatValue(const QSqlField &field,
 
839
                                  bool) const
 
840
{
 
841
    QString r;
 
842
    if (field.isNull()) {
 
843
        r = QLatin1String("NULL");
 
844
    } else {
 
845
        switch (field.type()) {
 
846
        case QVariant::DateTime:
 
847
            if (field.value().toDateTime().isValid()) {
 
848
                QDate dt = field.value().toDateTime().date();
 
849
                QTime tm = field.value().toDateTime().time();
 
850
                // msecs need to be right aligned otherwise psql
 
851
                // interpretes them wrong
 
852
                r = QLatin1String("'") + QString::number(dt.year()) + QLatin1String("-")
 
853
                          + QString::number(dt.month()) + QLatin1String("-")
 
854
                          + QString::number(dt.day()) + QLatin1String(" ")
 
855
                          + tm.toString() + QLatin1String(".")
 
856
                          + QString::number(tm.msec()).rightJustified(3, QLatin1Char('0'))
 
857
                          + QLatin1String("'");
 
858
            } else {
 
859
                r = QLatin1String("NULL");
 
860
            }
 
861
            break;
 
862
        case QVariant::Time:
 
863
            if (field.value().toTime().isValid()) {
 
864
                r = field.value().toTime().toString(Qt::ISODate);
 
865
            } else {
 
866
                r = QLatin1String("NULL");
 
867
            }
 
868
        case QVariant::String:
 
869
        {
 
870
            // Escape '\' characters
 
871
            r = QSqlDriver::formatValue(field);
 
872
            r.replace(QLatin1String("\\"), QLatin1String("\\\\"));
 
873
            break;
 
874
        }
 
875
        case QVariant::Bool:
 
876
            if (field.value().toBool())
 
877
                r = QLatin1String("TRUE");
 
878
            else
 
879
                r = QLatin1String("FALSE");
 
880
            break;
 
881
        case QVariant::ByteArray: {
 
882
            QByteArray ba(field.value().toByteArray());
 
883
            QString res;
 
884
            r = QLatin1String("'");
 
885
            unsigned char uc;
 
886
            for (int i = 0; i < ba.size(); ++i) {
 
887
                uc = (unsigned char) ba[i];
 
888
                if (uc > 40 && uc < 92) {
 
889
                    r += QLatin1Char(uc);
 
890
                } else {
 
891
                    r += QLatin1String("\\\\");
 
892
                    r += QString::number((unsigned char) ba[i], 8).rightJustified(3,
 
893
                            QLatin1Char('0'), true);
 
894
                }
 
895
            }
 
896
            r += QLatin1String("'");
 
897
            break;
 
898
        }
 
899
        default:
 
900
            r = QSqlDriver::formatValue(field);
 
901
            break;
 
902
        }
 
903
    }
 
904
    return r;
 
905
}
 
906
 
 
907
QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
 
908
{
 
909
    QString res = identifier;
 
910
    res.replace(QLatin1Char('"'), QLatin1String("\"\""));
 
911
    res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
 
912
    int idx = identifier.indexOf(QLatin1Char('.'));
 
913
    if (idx != -1)
 
914
        res.replace(QLatin1Char('.'), QLatin1String("\".\""));
 
915
    return res;
 
916
}
 
917
 
 
918
bool QPSQLDriver::isOpen() const
 
919
{
 
920
    return PQstatus(d->connection) == CONNECTION_OK;
 
921
}
 
922
 
 
923
QPSQLDriver::Protocol QPSQLDriver::protocol() const
 
924
{
 
925
    return d->pro;
 
926
}
 
927