1
/****************************************************************************
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
5
** This file is part of the sql module of the Qt Toolkit.
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.
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.
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.
21
** Contact info@trolltech.com if any conditions of this licensing are
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.
27
****************************************************************************/
29
#include "qsql_psql.h"
33
#include <qcoreapplication.h>
35
#include <qdatetime.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>
48
// workaround for postgres defining their OIDs in a private header file
53
#define QNUMERICOID 1700
54
#define QFLOAT4OID 700
55
#define QFLOAT8OID 701
56
#define QABSTIMEOID 702
57
#define QRELTIMEOID 703
60
#define QTIMETZOID 1266
61
#define QTIMESTAMPOID 1114
62
#define QTIMESTAMPTZOID 1184
65
#define QREGPROCOID 24
69
Q_DECLARE_METATYPE(PGconn*)
70
Q_DECLARE_METATYPE(PGresult*)
72
class QPSQLDriverPrivate
75
QPSQLDriverPrivate(): connection(0), isUtf8(false), pro(QPSQLDriver::Version6) {}
78
QPSQLDriver::Protocol pro;
80
void appendTables(QStringList &tl, QSqlQuery &t, QChar type);
83
void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type)
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);
94
query = QString::fromLatin1("select relname, null from pg_class where (relkind = 'r') "
95
"and (relname !~ '^Inv') "
96
"and (relname !~ '^pg_') ");
100
QString schema = t.value(1).toString();
101
if (schema.isEmpty() || schema == QLatin1String("public"))
102
tl.append(t.value(0).toString());
104
tl.append(t.value(0).toString().prepend(QLatin1Char('.')).prepend(schema));
108
class QPSQLResultPrivate
111
QPSQLResultPrivate(QPSQLResult *qq): q(qq), driver(0), result(0), currentSize(-1) {}
114
const QPSQLDriverPrivate *driver;
118
bool processResults();
121
static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
122
const QPSQLDriverPrivate *p)
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);
129
bool QPSQLResultPrivate::processResults()
134
int status = PQresultStatus(result);
135
if (status == PGRES_TUPLES_OK) {
138
currentSize = PQntuples(result);
140
} else if (status == PGRES_COMMAND_OK) {
146
q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
147
"Unable to create query"), QSqlError::StatementError, driver));
151
static QVariant::Type qDecodePSQLType(int t)
153
QVariant::Type type = QVariant::Invalid;
156
type = QVariant::Bool;
159
type = QVariant::LongLong;
167
type = QVariant::Int;
172
type = QVariant::Double;
177
type = QVariant::Date;
181
type = QVariant::Time;
184
case QTIMESTAMPTZOID:
185
type = QVariant::DateTime;
188
type = QVariant::ByteArray;
191
type = QVariant::String;
197
QPSQLResult::QPSQLResult(const QPSQLDriver* db, const QPSQLDriverPrivate* p)
200
d = new QPSQLResultPrivate(this);
204
QPSQLResult::~QPSQLResult()
210
QVariant QPSQLResult::handle() const
212
return qVariantFromValue(d->result);
215
void QPSQLResult::cleanup()
220
setAt(QSql::BeforeFirstRow);
225
bool QPSQLResult::fetch(int i)
231
if (i >= d->currentSize)
239
bool QPSQLResult::fetchFirst()
244
bool QPSQLResult::fetchLast()
246
return fetch(PQntuples(d->result) - 1);
249
QVariant QPSQLResult::data(int i)
251
if (i >= PQnfields(d->result)) {
252
qWarning("QPSQLResult::data: column %d out of range", i);
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);
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:
267
return QString::fromLatin1(val).toLongLong();
269
return QString::fromLatin1(val).toULongLong();
272
case QVariant::Double:
273
if (ptype == QNUMERICOID)
274
return QString::fromAscii(val);
275
return strtod(val, 0);
277
if (val[0] == '\0') {
278
return QVariant(QDate());
280
return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate));
282
case QVariant::Time: {
283
const QString str = QString::fromLatin1(val);
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));
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('+'))
298
// milliseconds are sometimes returned with 2 digits only
299
if (dtval.at(dtval.length() - 3).isPunct())
300
dtval += QLatin1Char('0');
302
return QVariant(QDateTime());
304
return QVariant(QDateTime::fromString(dtval, Qt::ISODate));
306
case QVariant::ByteArray: {
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));
316
ba[i] = ba.at(i + 1);
325
case QVariant::Invalid:
326
qWarning("QPSQLResult::data: unknown data type");
331
bool QPSQLResult::isNull(int field)
333
PQgetvalue(d->result, at(), field);
334
return PQgetisnull(d->result, at(), field);
337
bool QPSQLResult::reset (const QString& query)
342
if (!driver()->isOpen() || driver()->isOpenError())
344
d->result = PQexec(d->driver->connection,
345
d->driver->isUtf8 ? query.toUtf8().constData()
346
: query.toLocal8Bit().constData());
347
return d->processResults();
350
int QPSQLResult::size()
352
return d->currentSize;
355
int QPSQLResult::numRowsAffected()
357
return QString::fromLatin1(PQcmdTuples(d->result)).toInt();
360
QVariant QPSQLResult::lastInsertId() const
363
Oid id = PQoidValue(d->result);
364
if (id != InvalidOid)
370
QSqlRecord QPSQLResult::record() const
373
if (!isActive() || !isSelect())
376
int count = PQnfields(d->result);
377
for (int i = 0; i < count; ++i) {
379
if (d->driver->isUtf8)
380
f.setName(QString::fromUtf8(PQfname(d->result, i)));
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) {
392
f.setPrecision(precision);
393
f.setSqlType(PQftype(d->result, i));
399
///////////////////////////////////////////////////////////////////
401
static bool setEncodingUtf8(PGconn* connection)
403
PGresult* result = PQexec(connection, "SET CLIENT_ENCODING TO 'UNICODE'");
404
int status = PQresultStatus(result);
406
return status == PGRES_COMMAND_OK;
409
static void setDatestyle(PGconn* connection)
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));
418
static QPSQLDriver::Protocol getPSQLVersion(PGconn* connection)
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));
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();
431
qWarning("This version of PostgreSQL is not supported and may not work.");
432
return QPSQLDriver::Version6;
435
return QPSQLDriver::Version6;
436
} else if (vMaj == 7) {
438
return QPSQLDriver::Version7;
440
return QPSQLDriver::Version71;
442
return QPSQLDriver::Version73;
445
qWarning("This version of PostgreSQL is not supported and may not work.");
448
return QPSQLDriver::Version6;
451
QPSQLDriver::QPSQLDriver(QObject *parent)
457
QPSQLDriver::QPSQLDriver(PGconn * conn, QObject * parent)
461
d->connection = conn;
463
d->pro = getPSQLVersion(d->connection);
469
void QPSQLDriver::init()
471
d = new QPSQLDriverPrivate();
474
QPSQLDriver::~QPSQLDriver()
477
PQfinish(d->connection);
481
QVariant QPSQLDriver::handle() const
483
return qVariantFromValue(d->connection);
486
bool QPSQLDriver::hasFeature(DriverFeature f) const
494
return d->pro >= QPSQLDriver::Version71;
503
Quote a string for inclusion into the connection string
506
surround string by single quotes
508
static QString qQuote(QString s)
510
s.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
511
s.replace(QLatin1Char('\''), QLatin1String("\\'"));
512
s.append(QLatin1Char('\'')).prepend(QLatin1Char('\''));
516
bool QPSQLDriver::open(const QString & db,
517
const QString & user,
518
const QString & password,
519
const QString & host,
521
const QString& connOpts)
525
QString connectString;
527
connectString.append(QLatin1String("host=")).append(qQuote(host));
529
connectString.append(QLatin1String(" dbname=")).append(qQuote(db));
531
connectString.append(QLatin1String(" user=")).append(qQuote(user));
532
if (!password.isEmpty())
533
connectString.append(QLatin1String(" password=")).append(qQuote(password));
535
connectString.append(QLatin1String(" port=")).append(qQuote(QString::number(port)));
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);
544
d->connection = PQconnectdb(connectString.toLocal8Bit().constData());
545
if (PQstatus(d->connection) == CONNECTION_BAD) {
546
setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
551
d->pro = getPSQLVersion(d->connection);
552
d->isUtf8 = setEncodingUtf8(d->connection);
553
setDatestyle(d->connection);
560
void QPSQLDriver::close()
564
PQfinish(d->connection);
571
QSqlResult *QPSQLDriver::createResult() const
573
return new QPSQLResult(this, d);
576
bool QPSQLDriver::beginTransaction()
579
qWarning("QPSQLDriver::beginTransaction: Database not open");
582
PGresult* res = PQexec(d->connection, "BEGIN");
583
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
585
setLastError(qMakeError(tr("Could not begin transaction"),
586
QSqlError::TransactionError, d));
593
bool QPSQLDriver::commitTransaction()
596
qWarning("QPSQLDriver::commitTransaction: Database not open");
599
PGresult* res = PQexec(d->connection, "COMMIT");
600
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
602
setLastError(qMakeError(tr("Could not commit transaction"),
603
QSqlError::TransactionError, d));
610
bool QPSQLDriver::rollbackTransaction()
613
qWarning("QPSQLDriver::rollbackTransaction: Database not open");
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));
627
QStringList QPSQLDriver::tables(QSql::TableType type) const
632
QSqlQuery t(createResult());
633
t.setForwardOnly(true);
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_%') "));
643
tl.append(t.value(0).toString());
649
static void qSplitTableName(QString &tablename, QString &schema)
651
int dot = tablename.indexOf(QLatin1Char('.'));
654
schema = tablename.left(dot);
655
tablename = tablename.mid(dot + 1);
658
QSqlIndex QPSQLDriver::primaryIndex(const QString& tablename) const
660
QSqlIndex idx(tablename);
663
QSqlQuery i(createResult());
666
QString tbl = tablename;
668
qSplitTableName(tbl, schema);
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");
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");
692
case QPSQLDriver::Version73:
693
stmt = QLatin1String("SELECT pg_attribute.attname, pg_attribute.atttypid::int, "
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(""));
706
stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from "
707
"pg_namespace where pg_namespace.nspname = '%1') AND ").arg(schema.toLower()));
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()));
715
idx.setName(i.value(2).toString());
720
QSqlRecord QPSQLDriver::record(const QString& tablename) const
726
QString tbl = tablename;
728
qSplitTableName(tbl, schema);
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 ");
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 ");
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, "
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 ");
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, "
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) "
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(""));
779
stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from "
780
"pg_namespace where pg_namespace.nspname = '%1') and ").arg(schema.toLower()));
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) {
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());
801
f.setPrecision(precision);
802
f.setDefaultValue(defVal);
803
f.setSqlType(query.value(1).toInt());
807
// Postgres < 7.1 cannot handle outer joins
808
while (query.next()) {
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) {
825
QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()));
826
f.setRequired(query.value(2).toBool());
828
f.setPrecision(precision);
829
f.setDefaultValue(defVal);
830
f.setSqlType(query.value(1).toInt());
838
QString QPSQLDriver::formatValue(const QSqlField &field,
842
if (field.isNull()) {
843
r = QLatin1String("NULL");
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("'");
859
r = QLatin1String("NULL");
863
if (field.value().toTime().isValid()) {
864
r = field.value().toTime().toString(Qt::ISODate);
866
r = QLatin1String("NULL");
868
case QVariant::String:
870
// Escape '\' characters
871
r = QSqlDriver::formatValue(field);
872
r.replace(QLatin1String("\\"), QLatin1String("\\\\"));
876
if (field.value().toBool())
877
r = QLatin1String("TRUE");
879
r = QLatin1String("FALSE");
881
case QVariant::ByteArray: {
882
QByteArray ba(field.value().toByteArray());
884
r = QLatin1String("'");
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);
891
r += QLatin1String("\\\\");
892
r += QString::number((unsigned char) ba[i], 8).rightJustified(3,
893
QLatin1Char('0'), true);
896
r += QLatin1String("'");
900
r = QSqlDriver::formatValue(field);
907
QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
909
QString res = identifier;
910
res.replace(QLatin1Char('"'), QLatin1String("\"\""));
911
res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
912
int idx = identifier.indexOf(QLatin1Char('.'));
914
res.replace(QLatin1Char('.'), QLatin1String("\".\""));
918
bool QPSQLDriver::isOpen() const
920
return PQstatus(d->connection) == CONNECTION_OK;
923
QPSQLDriver::Protocol QPSQLDriver::protocol() const