1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtSql module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qsql_psql_p.h"
41
42#include <qcoreapplication.h>
43#include <qvariant.h>
44#include <qdatetime.h>
45#include <qregularexpression.h>
46#include <qsqlerror.h>
47#include <qsqlfield.h>
48#include <qsqlindex.h>
49#include <qsqlrecord.h>
50#include <qsqlquery.h>
51#include <qsocketnotifier.h>
52#include <qstringlist.h>
53#include <qlocale.h>
54#include <QtSql/private/qsqlresult_p.h>
55#include <QtSql/private/qsqldriver_p.h>
56#include <QtCore/private/qlocale_tools_p.h>
57
58#include <queue>
59
60#include <libpq-fe.h>
61#include <pg_config.h>
62
63#include <cmath>
64
65// workaround for postgres defining their OIDs in a private header file
66#define QBOOLOID 16
67#define QINT8OID 20
68#define QINT2OID 21
69#define QINT4OID 23
70#define QNUMERICOID 1700
71#define QFLOAT4OID 700
72#define QFLOAT8OID 701
73#define QABSTIMEOID 702
74#define QRELTIMEOID 703
75#define QDATEOID 1082
76#define QTIMEOID 1083
77#define QTIMETZOID 1266
78#define QTIMESTAMPOID 1114
79#define QTIMESTAMPTZOID 1184
80#define QOIDOID 2278
81#define QBYTEAOID 17
82#define QREGPROCOID 24
83#define QXIDOID 28
84#define QCIDOID 29
85
86#define QBITOID 1560
87#define QVARBITOID 1562
88
89#define VARHDRSZ 4
90
91/* This is a compile time switch - if PQfreemem is declared, the compiler will use that one,
92 otherwise it'll run in this template */
93template <typename T>
94inline void PQfreemem(T *t, int = 0) { free(t); }
95
96Q_DECLARE_OPAQUE_POINTER(PGconn*)
97Q_DECLARE_METATYPE(PGconn*)
98
99Q_DECLARE_OPAQUE_POINTER(PGresult*)
100Q_DECLARE_METATYPE(PGresult*)
101
102QT_BEGIN_NAMESPACE
103
104inline void qPQfreemem(void *buffer)
105{
106 PQfreemem(buffer);
107}
108
109/* Missing declaration of PGRES_SINGLE_TUPLE for PSQL below 9.2 */
110#if !defined PG_VERSION_NUM || PG_VERSION_NUM-0 < 90200
111static const int PGRES_SINGLE_TUPLE = 9;
112#endif
113
114typedef int StatementId;
115static const StatementId InvalidStatementId = 0;
116
117class QPSQLResultPrivate;
118
119class QPSQLResult final : public QSqlResult
120{
121 Q_DECLARE_PRIVATE(QPSQLResult)
122
123public:
124 QPSQLResult(const QPSQLDriver *db);
125 ~QPSQLResult();
126
127 QVariant handle() const override;
128 void virtual_hook(int id, void *data) override;
129
130protected:
131 void cleanup();
132 bool fetch(int i) override;
133 bool fetchFirst() override;
134 bool fetchLast() override;
135 bool fetchNext() override;
136 bool nextResult() override;
137 QVariant data(int i) override;
138 bool isNull(int field) override;
139 bool reset(const QString &query) override;
140 int size() override;
141 int numRowsAffected() override;
142 QSqlRecord record() const override;
143 QVariant lastInsertId() const override;
144 bool prepare(const QString &query) override;
145 bool exec() override;
146};
147
148class QPSQLDriverPrivate final : public QSqlDriverPrivate
149{
150 Q_DECLARE_PUBLIC(QPSQLDriver)
151public:
152 QPSQLDriverPrivate() : QSqlDriverPrivate(),
153 connection(nullptr),
154 isUtf8(false),
155 pro(QPSQLDriver::Version6),
156 sn(nullptr),
157 pendingNotifyCheck(false),
158 hasBackslashEscape(false),
159 stmtCount(0),
160 currentStmtId(InvalidStatementId)
161 { dbmsType = QSqlDriver::PostgreSQL; }
162
163 PGconn *connection;
164 bool isUtf8;
165 QPSQLDriver::Protocol pro;
166 QSocketNotifier *sn;
167 QStringList seid;
168 mutable bool pendingNotifyCheck;
169 bool hasBackslashEscape;
170 int stmtCount;
171 StatementId currentStmtId;
172
173 void appendTables(QStringList &tl, QSqlQuery &t, QChar type);
174 PGresult *exec(const char *stmt);
175 PGresult *exec(const QString &stmt);
176 StatementId sendQuery(const QString &stmt);
177 bool setSingleRowMode() const;
178 PGresult *getResult(StatementId stmtId) const;
179 void finishQuery(StatementId stmtId);
180 void discardResults() const;
181 StatementId generateStatementId();
182 void checkPendingNotifications() const;
183 QPSQLDriver::Protocol getPSQLVersion();
184 bool setEncodingUtf8();
185 void setDatestyle();
186 void setByteaOutput();
187 void detectBackslashEscape();
188 mutable QHash<int, QString> oidToTable;
189};
190
191void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type)
192{
193 const QString query =
194 QStringLiteral("SELECT pg_class.relname, pg_namespace.nspname FROM pg_class "
195 "LEFT JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid) "
196 "WHERE (pg_class.relkind = '") + type +
197 QStringLiteral("') AND (pg_class.relname !~ '^Inv') "
198 "AND (pg_class.relname !~ '^pg_') "
199 "AND (pg_namespace.nspname != 'information_schema')");
200 t.exec(query);
201 while (t.next()) {
202 QString schema = t.value(1).toString();
203 if (schema.isEmpty() || schema == QLatin1String("public"))
204 tl.append(t.value(0).toString());
205 else
206 tl.append(t.value(0).toString().prepend(QLatin1Char('.')).prepend(schema));
207 }
208}
209
210PGresult *QPSQLDriverPrivate::exec(const char *stmt)
211{
212 // PQexec() silently discards any prior query results that the application didn't eat.
213 PGresult *result = PQexec(connection, stmt);
214 currentStmtId = result ? generateStatementId() : InvalidStatementId;
215 checkPendingNotifications();
216 return result;
217}
218
219PGresult *QPSQLDriverPrivate::exec(const QString &stmt)
220{
221 return exec((isUtf8 ? stmt.toUtf8() : stmt.toLocal8Bit()).constData());
222}
223
224StatementId QPSQLDriverPrivate::sendQuery(const QString &stmt)
225{
226 // Discard any prior query results that the application didn't eat.
227 // This is required for PQsendQuery()
228 discardResults();
229 const int result = PQsendQuery(connection,
230 (isUtf8 ? stmt.toUtf8() : stmt.toLocal8Bit()).constData());
231 currentStmtId = result ? generateStatementId() : InvalidStatementId;
232 return currentStmtId;
233}
234
235bool QPSQLDriverPrivate::setSingleRowMode() const
236{
237 // Activates single-row mode for last sent query, see:
238 // https://www.postgresql.org/docs/9.2/static/libpq-single-row-mode.html
239 // This method should be called immediately after the sendQuery() call.
240#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 90200
241 return PQsetSingleRowMode(connection) > 0;
242#else
243 return false;
244#endif
245}
246
247PGresult *QPSQLDriverPrivate::getResult(StatementId stmtId) const
248{
249 // Make sure the results of stmtId weren't discaded. This might
250 // happen for forward-only queries if somebody executed another
251 // SQL query on the same db connection.
252 if (stmtId != currentStmtId) {
253 // If you change the following warning, remember to update it
254 // on sql-driver.html page too.
255 qWarning("QPSQLDriver::getResult: Query results lost - "
256 "probably discarded on executing another SQL query.");
257 return nullptr;
258 }
259 PGresult *result = PQgetResult(connection);
260 checkPendingNotifications();
261 return result;
262}
263
264void QPSQLDriverPrivate::finishQuery(StatementId stmtId)
265{
266 if (stmtId != InvalidStatementId && stmtId == currentStmtId) {
267 discardResults();
268 currentStmtId = InvalidStatementId;
269 }
270}
271
272void QPSQLDriverPrivate::discardResults() const
273{
274 while (PGresult *result = PQgetResult(connection))
275 PQclear(result);
276}
277
278StatementId QPSQLDriverPrivate::generateStatementId()
279{
280 int stmtId = ++stmtCount;
281 if (stmtId <= 0)
282 stmtId = stmtCount = 1;
283 return stmtId;
284}
285
286void QPSQLDriverPrivate::checkPendingNotifications() const
287{
288 Q_Q(const QPSQLDriver);
289 if (seid.size() && !pendingNotifyCheck) {
290 pendingNotifyCheck = true;
291 QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), "_q_handleNotification", Qt::QueuedConnection, Q_ARG(int,0));
292 }
293}
294
295class QPSQLResultPrivate : public QSqlResultPrivate
296{
297 Q_DECLARE_PUBLIC(QPSQLResult)
298public:
299 Q_DECLARE_SQLDRIVER_PRIVATE(QPSQLDriver)
300 QPSQLResultPrivate(QPSQLResult *q, const QPSQLDriver *drv)
301 : QSqlResultPrivate(q, drv),
302 result(nullptr),
303 stmtId(InvalidStatementId),
304 currentSize(-1),
305 canFetchMoreRows(false),
306 preparedQueriesEnabled(false)
307 { }
308
309 QString fieldSerial(int i) const override { return QLatin1Char('$') + QString::number(i + 1); }
310 void deallocatePreparedStmt();
311
312 PGresult *result;
313 std::queue<PGresult*> nextResultSets;
314 QString preparedStmtId;
315 StatementId stmtId;
316 int currentSize;
317 bool canFetchMoreRows;
318 bool preparedQueriesEnabled;
319
320 bool processResults();
321};
322
323static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type,
324 const QPSQLDriverPrivate *p, PGresult *result = nullptr)
325{
326 const char *s = PQerrorMessage(p->connection);
327 QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
328 QString errorCode;
329 if (result) {
330 errorCode = QString::fromLatin1(PQresultErrorField(result, PG_DIAG_SQLSTATE));
331 msg += QString::fromLatin1("(%1)").arg(errorCode);
332 }
333 return QSqlError(QLatin1String("QPSQL: ") + err, msg, type, errorCode);
334}
335
336bool QPSQLResultPrivate::processResults()
337{
338 Q_Q(QPSQLResult);
339 if (!result) {
340 q->setSelect(false);
341 q->setActive(false);
342 currentSize = -1;
343 canFetchMoreRows = false;
344 if (stmtId != drv_d_func()->currentStmtId) {
345 q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
346 "Query results lost - probably discarded on executing "
347 "another SQL query."), QSqlError::StatementError, drv_d_func(), result));
348 }
349 return false;
350 }
351 int status = PQresultStatus(result);
352 switch (status) {
353 case PGRES_TUPLES_OK:
354 q->setSelect(true);
355 q->setActive(true);
356 currentSize = q->isForwardOnly() ? -1 : PQntuples(result);
357 canFetchMoreRows = false;
358 return true;
359 case PGRES_SINGLE_TUPLE:
360 q->setSelect(true);
361 q->setActive(true);
362 currentSize = -1;
363 canFetchMoreRows = true;
364 return true;
365 case PGRES_COMMAND_OK:
366 q->setSelect(false);
367 q->setActive(true);
368 currentSize = -1;
369 canFetchMoreRows = false;
370 return true;
371 default:
372 break;
373 }
374 q->setSelect(false);
375 q->setActive(false);
376 currentSize = -1;
377 canFetchMoreRows = false;
378 q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
379 "Unable to create query"), QSqlError::StatementError, drv_d_func(), result));
380 return false;
381}
382
383static QVariant::Type qDecodePSQLType(int t)
384{
385 QVariant::Type type = QVariant::Invalid;
386 switch (t) {
387 case QBOOLOID:
388 type = QVariant::Bool;
389 break;
390 case QINT8OID:
391 type = QVariant::LongLong;
392 break;
393 case QINT2OID:
394 case QINT4OID:
395 case QOIDOID:
396 case QREGPROCOID:
397 case QXIDOID:
398 case QCIDOID:
399 type = QVariant::Int;
400 break;
401 case QNUMERICOID:
402 case QFLOAT4OID:
403 case QFLOAT8OID:
404 type = QVariant::Double;
405 break;
406 case QABSTIMEOID:
407 case QRELTIMEOID:
408 case QDATEOID:
409 type = QVariant::Date;
410 break;
411 case QTIMEOID:
412 case QTIMETZOID:
413 type = QVariant::Time;
414 break;
415 case QTIMESTAMPOID:
416 case QTIMESTAMPTZOID:
417 type = QVariant::DateTime;
418 break;
419 case QBYTEAOID:
420 type = QVariant::ByteArray;
421 break;
422 default:
423 type = QVariant::String;
424 break;
425 }
426 return type;
427}
428
429void QPSQLResultPrivate::deallocatePreparedStmt()
430{
431 const QString stmt = QStringLiteral("DEALLOCATE ") + preparedStmtId;
432 PGresult *result = drv_d_func()->exec(stmt);
433
434 if (PQresultStatus(result) != PGRES_COMMAND_OK)
435 qWarning("Unable to free statement: %s", PQerrorMessage(drv_d_func()->connection));
436 PQclear(result);
437 preparedStmtId.clear();
438}
439
440QPSQLResult::QPSQLResult(const QPSQLDriver *db)
441 : QSqlResult(*new QPSQLResultPrivate(this, db))
442{
443 Q_D(QPSQLResult);
444 d->preparedQueriesEnabled = db->hasFeature(QSqlDriver::PreparedQueries);
445}
446
447QPSQLResult::~QPSQLResult()
448{
449 Q_D(QPSQLResult);
450 cleanup();
451
452 if (d->preparedQueriesEnabled && !d->preparedStmtId.isNull())
453 d->deallocatePreparedStmt();
454}
455
456QVariant QPSQLResult::handle() const
457{
458 Q_D(const QPSQLResult);
459 return QVariant::fromValue(d->result);
460}
461
462void QPSQLResult::cleanup()
463{
464 Q_D(QPSQLResult);
465 if (d->result)
466 PQclear(d->result);
467 d->result = nullptr;
468 while (!d->nextResultSets.empty()) {
469 PQclear(d->nextResultSets.front());
470 d->nextResultSets.pop();
471 }
472 if (d->stmtId != InvalidStatementId) {
473 if (d->drv_d_func())
474 d->drv_d_func()->finishQuery(d->stmtId);
475 }
476 d->stmtId = InvalidStatementId;
477 setAt(QSql::BeforeFirstRow);
478 d->currentSize = -1;
479 d->canFetchMoreRows = false;
480 setActive(false);
481}
482
483bool QPSQLResult::fetch(int i)
484{
485 Q_D(const QPSQLResult);
486 if (!isActive())
487 return false;
488 if (i < 0)
489 return false;
490 if (at() == i)
491 return true;
492
493 if (isForwardOnly()) {
494 if (i < at())
495 return false;
496 bool ok = true;
497 while (ok && i > at())
498 ok = fetchNext();
499 return ok;
500 }
501
502 if (i >= d->currentSize)
503 return false;
504 setAt(i);
505 return true;
506}
507
508bool QPSQLResult::fetchFirst()
509{
510 Q_D(const QPSQLResult);
511 if (!isActive())
512 return false;
513 if (at() == 0)
514 return true;
515
516 if (isForwardOnly()) {
517 if (at() == QSql::BeforeFirstRow) {
518 // First result has been already fetched by exec() or
519 // nextResult(), just check it has at least one row.
520 if (d->result && PQntuples(d->result) > 0) {
521 setAt(0);
522 return true;
523 }
524 }
525 return false;
526 }
527
528 return fetch(0);
529}
530
531bool QPSQLResult::fetchLast()
532{
533 Q_D(const QPSQLResult);
534 if (!isActive())
535 return false;
536
537 if (isForwardOnly()) {
538 // Cannot seek to last row in forwardOnly mode, so we have to use brute force
539 int i = at();
540 if (i == QSql::AfterLastRow)
541 return false;
542 if (i == QSql::BeforeFirstRow)
543 i = 0;
544 while (fetchNext())
545 ++i;
546 setAt(i);
547 return true;
548 }
549
550 return fetch(d->currentSize - 1);
551}
552
553bool QPSQLResult::fetchNext()
554{
555 Q_D(QPSQLResult);
556 if (!isActive())
557 return false;
558
559 const int currentRow = at(); // Small optimalization
560 if (currentRow == QSql::BeforeFirstRow)
561 return fetchFirst();
562 if (currentRow == QSql::AfterLastRow)
563 return false;
564
565 if (isForwardOnly()) {
566 if (!d->canFetchMoreRows)
567 return false;
568 PQclear(d->result);
569 d->result = d->drv_d_func()->getResult(d->stmtId);
570 if (!d->result) {
571 setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
572 "Unable to get result"), QSqlError::StatementError, d->drv_d_func(), d->result));
573 d->canFetchMoreRows = false;
574 return false;
575 }
576 int status = PQresultStatus(d->result);
577 switch (status) {
578 case PGRES_SINGLE_TUPLE:
579 // Fetched next row of current result set
580 Q_ASSERT(PQntuples(d->result) == 1);
581 Q_ASSERT(d->canFetchMoreRows);
582 setAt(currentRow + 1);
583 return true;
584 case PGRES_TUPLES_OK:
585 // In single-row mode PGRES_TUPLES_OK means end of current result set
586 Q_ASSERT(PQntuples(d->result) == 0);
587 d->canFetchMoreRows = false;
588 return false;
589 default:
590 setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
591 "Unable to get result"), QSqlError::StatementError, d->drv_d_func(), d->result));
592 d->canFetchMoreRows = false;
593 return false;
594 }
595 }
596
597 if (currentRow + 1 >= d->currentSize)
598 return false;
599 setAt(currentRow + 1);
600 return true;
601}
602
603bool QPSQLResult::nextResult()
604{
605 Q_D(QPSQLResult);
606 if (!isActive())
607 return false;
608
609 setAt(QSql::BeforeFirstRow);
610
611 if (isForwardOnly()) {
612 if (d->canFetchMoreRows) {
613 // Skip all rows from current result set
614 while (d->result && PQresultStatus(d->result) == PGRES_SINGLE_TUPLE) {
615 PQclear(d->result);
616 d->result = d->drv_d_func()->getResult(d->stmtId);
617 }
618 d->canFetchMoreRows = false;
619 // Check for unexpected errors
620 if (d->result && PQresultStatus(d->result) == PGRES_FATAL_ERROR)
621 return d->processResults();
622 }
623 // Fetch first result from next result set
624 if (d->result)
625 PQclear(d->result);
626 d->result = d->drv_d_func()->getResult(d->stmtId);
627 return d->processResults();
628 }
629
630 if (d->result)
631 PQclear(d->result);
632 d->result = nullptr;
633 if (!d->nextResultSets.empty()) {
634 d->result = d->nextResultSets.front();
635 d->nextResultSets.pop();
636 }
637 return d->processResults();
638}
639
640QVariant QPSQLResult::data(int i)
641{
642 Q_D(const QPSQLResult);
643 if (i >= PQnfields(d->result)) {
644 qWarning("QPSQLResult::data: column %d out of range", i);
645 return QVariant();
646 }
647 const int currentRow = isForwardOnly() ? 0 : at();
648 int ptype = PQftype(d->result, i);
649 QVariant::Type type = qDecodePSQLType(ptype);
650 if (PQgetisnull(d->result, currentRow, i))
651 return QVariant(type);
652 const char *val = PQgetvalue(d->result, currentRow, i);
653 switch (type) {
654 case QVariant::Bool:
655 return QVariant((bool)(val[0] == 't'));
656 case QVariant::String:
657 return d->drv_d_func()->isUtf8 ? QString::fromUtf8(val) : QString::fromLatin1(val);
658 case QVariant::LongLong:
659 if (val[0] == '-')
660 return QByteArray::fromRawData(val, qstrlen(val)).toLongLong();
661 else
662 return QByteArray::fromRawData(val, qstrlen(val)).toULongLong();
663 case QVariant::Int:
664 return atoi(val);
665 case QVariant::Double: {
666 if (ptype == QNUMERICOID) {
667 if (numericalPrecisionPolicy() == QSql::HighPrecision)
668 return QString::fromLatin1(val);
669 }
670 bool ok;
671 double dbl = qstrtod(val, nullptr, &ok);
672 if (!ok) {
673 if (qstricmp(val, "NaN") == 0)
674 dbl = qQNaN();
675 else if (qstricmp(val, "Infinity") == 0)
676 dbl = qInf();
677 else if (qstricmp(val, "-Infinity") == 0)
678 dbl = -qInf();
679 else
680 return QVariant();
681 }
682 if (ptype == QNUMERICOID) {
683 if (numericalPrecisionPolicy() == QSql::LowPrecisionInt64)
684 return QVariant((qlonglong)dbl);
685 else if (numericalPrecisionPolicy() == QSql::LowPrecisionInt32)
686 return QVariant((int)dbl);
687 else if (numericalPrecisionPolicy() == QSql::LowPrecisionDouble)
688 return QVariant(dbl);
689 }
690 return dbl;
691 }
692 case QVariant::Date:
693 if (val[0] == '\0') {
694 return QVariant(QDate());
695 } else {
696#if QT_CONFIG(datestring)
697 return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate));
698#else
699 return QVariant(QString::fromLatin1(val));
700#endif
701 }
702 case QVariant::Time: {
703 const QString str = QString::fromLatin1(val);
704#if QT_CONFIG(datestring)
705 if (str.isEmpty())
706 return QVariant(QTime());
707 else
708 return QVariant(QTime::fromString(str, Qt::ISODate));
709#else
710 return QVariant(str);
711#endif
712 }
713 case QVariant::DateTime: {
714 QString dtval = QString::fromLatin1(val);
715#if QT_CONFIG(datestring)
716 if (dtval.length() < 10) {
717 return QVariant(QDateTime());
718 } else {
719 QChar sign = dtval[dtval.size() - 3];
720 if (sign == QLatin1Char('-') || sign == QLatin1Char('+')) dtval += QLatin1String(":00");
721 return QVariant(QDateTime::fromString(dtval, Qt::ISODate).toLocalTime());
722 }
723#else
724 return QVariant(dtval);
725#endif
726 }
727 case QVariant::ByteArray: {
728 size_t len;
729 unsigned char *data = PQunescapeBytea((const unsigned char*)val, &len);
730 QByteArray ba(reinterpret_cast<const char *>(data), int(len));
731 qPQfreemem(data);
732 return QVariant(ba);
733 }
734 default:
735 case QVariant::Invalid:
736 qWarning("QPSQLResult::data: unknown data type");
737 }
738 return QVariant();
739}
740
741bool QPSQLResult::isNull(int field)
742{
743 Q_D(const QPSQLResult);
744 const int currentRow = isForwardOnly() ? 0 : at();
745 return PQgetisnull(d->result, currentRow, field);
746}
747
748bool QPSQLResult::reset(const QString &query)
749{
750 Q_D(QPSQLResult);
751 cleanup();
752 if (!driver())
753 return false;
754 if (!driver()->isOpen() || driver()->isOpenError())
755 return false;
756
757 d->stmtId = d->drv_d_func()->sendQuery(query);
758 if (d->stmtId == InvalidStatementId) {
759 setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
760 "Unable to send query"), QSqlError::StatementError, d->drv_d_func()));
761 return false;
762 }
763
764 if (isForwardOnly())
765 setForwardOnly(d->drv_d_func()->setSingleRowMode());
766
767 d->result = d->drv_d_func()->getResult(d->stmtId);
768 if (!isForwardOnly()) {
769 // Fetch all result sets right away
770 while (PGresult *nextResultSet = d->drv_d_func()->getResult(d->stmtId))
771 d->nextResultSets.push(nextResultSet);
772 }
773 return d->processResults();
774}
775
776int QPSQLResult::size()
777{
778 Q_D(const QPSQLResult);
779 return d->currentSize;
780}
781
782int QPSQLResult::numRowsAffected()
783{
784 Q_D(const QPSQLResult);
785 const char *tuples = PQcmdTuples(d->result);
786 return QByteArray::fromRawData(tuples, qstrlen(tuples)).toInt();
787}
788
789QVariant QPSQLResult::lastInsertId() const
790{
791 Q_D(const QPSQLResult);
792 if (d->drv_d_func()->pro >= QPSQLDriver::Version8_1) {
793 QSqlQuery qry(driver()->createResult());
794 // Most recent sequence value obtained from nextval
795 if (qry.exec(QStringLiteral("SELECT lastval();")) && qry.next())
796 return qry.value(0);
797 } else if (isActive()) {
798 Oid id = PQoidValue(d->result);
799 if (id != InvalidOid)
800 return QVariant(id);
801 }
802 return QVariant();
803}
804
805QSqlRecord QPSQLResult::record() const
806{
807 Q_D(const QPSQLResult);
808 QSqlRecord info;
809 if (!isActive() || !isSelect())
810 return info;
811
812 int count = PQnfields(d->result);
813 for (int i = 0; i < count; ++i) {
814 QSqlField f;
815 if (d->drv_d_func()->isUtf8)
816 f.setName(QString::fromUtf8(PQfname(d->result, i)));
817 else
818 f.setName(QString::fromLocal8Bit(PQfname(d->result, i)));
819 const int tableOid = PQftable(d->result, i);
820 // WARNING: We cannot execute any other SQL queries on
821 // the same db connection while forward-only mode is active
822 // (this would discard all results of forward-only query).
823 // So we just skip this...
824 if (tableOid != InvalidOid && !isForwardOnly()) {
825 auto &tableName = d->drv_d_func()->oidToTable[tableOid];
826 if (tableName.isEmpty()) {
827 QSqlQuery qry(driver()->createResult());
828 if (qry.exec(QStringLiteral("SELECT relname FROM pg_class WHERE pg_class.oid = %1")
829 .arg(tableOid)) && qry.next()) {
830 tableName = qry.value(0).toString();
831 }
832 }
833 f.setTableName(tableName);
834 }
835 int ptype = PQftype(d->result, i);
836 f.setType(qDecodePSQLType(ptype));
837 int len = PQfsize(d->result, i);
838 int precision = PQfmod(d->result, i);
839
840 switch (ptype) {
841 case QTIMESTAMPOID:
842 case QTIMESTAMPTZOID:
843 precision = 3;
844 break;
845
846 case QNUMERICOID:
847 if (precision != -1) {
848 len = (precision >> 16);
849 precision = ((precision - VARHDRSZ) & 0xffff);
850 }
851 break;
852 case QBITOID:
853 case QVARBITOID:
854 len = precision;
855 precision = -1;
856 break;
857 default:
858 if (len == -1 && precision >= VARHDRSZ) {
859 len = precision - VARHDRSZ;
860 precision = -1;
861 }
862 }
863
864 f.setLength(len);
865 f.setPrecision(precision);
866 f.setSqlType(ptype);
867 info.append(f);
868 }
869 return info;
870}
871
872void QPSQLResult::virtual_hook(int id, void *data)
873{
874 Q_ASSERT(data);
875 QSqlResult::virtual_hook(id, data);
876}
877
878static QString qCreateParamString(const QVector<QVariant> &boundValues, const QSqlDriver *driver)
879{
880 if (boundValues.isEmpty())
881 return QString();
882
883 QString params;
884 QSqlField f;
885 for (const QVariant &val : boundValues) {
886 f.setType(val.type());
887 if (val.isNull())
888 f.clear();
889 else
890 f.setValue(val);
891 if (!params.isNull())
892 params.append(QLatin1String(", "));
893 params.append(driver->formatValue(f));
894 }
895 return params;
896}
897
898QString qMakePreparedStmtId()
899{
900 static QBasicAtomicInt qPreparedStmtCount = Q_BASIC_ATOMIC_INITIALIZER(0);
901 QString id = QStringLiteral("qpsqlpstmt_") + QString::number(qPreparedStmtCount.fetchAndAddRelaxed(1) + 1, 16);
902 return id;
903}
904
905bool QPSQLResult::prepare(const QString &query)
906{
907 Q_D(QPSQLResult);
908 if (!d->preparedQueriesEnabled)
909 return QSqlResult::prepare(query);
910
911 cleanup();
912
913 if (!d->preparedStmtId.isEmpty())
914 d->deallocatePreparedStmt();
915
916 const QString stmtId = qMakePreparedStmtId();
917 const QString stmt = QStringLiteral("PREPARE %1 AS ").arg(stmtId).append(d->positionalToNamedBinding(query));
918
919 PGresult *result = d->drv_d_func()->exec(stmt);
920
921 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
922 setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
923 "Unable to prepare statement"), QSqlError::StatementError, d->drv_d_func(), result));
924 PQclear(result);
925 d->preparedStmtId.clear();
926 return false;
927 }
928
929 PQclear(result);
930 d->preparedStmtId = stmtId;
931 return true;
932}
933
934bool QPSQLResult::exec()
935{
936 Q_D(QPSQLResult);
937 if (!d->preparedQueriesEnabled)
938 return QSqlResult::exec();
939
940 cleanup();
941
942 QString stmt;
943 const QString params = qCreateParamString(boundValues(), driver());
944 if (params.isEmpty())
945 stmt = QStringLiteral("EXECUTE %1").arg(d->preparedStmtId);
946 else
947 stmt = QStringLiteral("EXECUTE %1 (%2)").arg(d->preparedStmtId, params);
948
949 d->stmtId = d->drv_d_func()->sendQuery(stmt);
950 if (d->stmtId == InvalidStatementId) {
951 setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
952 "Unable to send query"), QSqlError::StatementError, d->drv_d_func()));
953 return false;
954 }
955
956 if (isForwardOnly())
957 setForwardOnly(d->drv_d_func()->setSingleRowMode());
958
959 d->result = d->drv_d_func()->getResult(d->stmtId);
960 if (!isForwardOnly()) {
961 // Fetch all result sets right away
962 while (PGresult *nextResultSet = d->drv_d_func()->getResult(d->stmtId))
963 d->nextResultSets.push(nextResultSet);
964 }
965 return d->processResults();
966}
967
968///////////////////////////////////////////////////////////////////
969
970bool QPSQLDriverPrivate::setEncodingUtf8()
971{
972 PGresult *result = exec("SET CLIENT_ENCODING TO 'UNICODE'");
973 int status = PQresultStatus(result);
974 PQclear(result);
975 return status == PGRES_COMMAND_OK;
976}
977
978void QPSQLDriverPrivate::setDatestyle()
979{
980 PGresult *result = exec("SET DATESTYLE TO 'ISO'");
981 int status = PQresultStatus(result);
982 if (status != PGRES_COMMAND_OK)
983 qWarning("%s", PQerrorMessage(connection));
984 PQclear(result);
985}
986
987void QPSQLDriverPrivate::setByteaOutput()
988{
989 if (pro >= QPSQLDriver::Version9) {
990 // Server version before QPSQLDriver::Version9 only supports escape mode for bytea type,
991 // but bytea format is set to hex by default in PSQL 9 and above. So need to force the
992 // server to use the old escape mode when connects to the new server.
993 PGresult *result = exec("SET bytea_output TO escape");
994 int status = PQresultStatus(result);
995 if (status != PGRES_COMMAND_OK)
996 qWarning("%s", PQerrorMessage(connection));
997 PQclear(result);
998 }
999}
1000
1001void QPSQLDriverPrivate::detectBackslashEscape()
1002{
1003 // standard_conforming_strings option introduced in 8.2
1004 // http://www.postgresql.org/docs/8.2/static/runtime-config-compatible.html
1005 if (pro < QPSQLDriver::Version8_2) {
1006 hasBackslashEscape = true;
1007 } else {
1008 hasBackslashEscape = false;
1009 PGresult *result = exec(QStringLiteral("SELECT '\\\\' x"));
1010 int status = PQresultStatus(result);
1011 if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK)
1012 if (QString::fromLatin1(PQgetvalue(result, 0, 0)) == QLatin1String("\\"))
1013 hasBackslashEscape = true;
1014 PQclear(result);
1015 }
1016}
1017
1018static QPSQLDriver::Protocol qMakePSQLVersion(int vMaj, int vMin)
1019{
1020 switch (vMaj) {
1021 case 6:
1022 return QPSQLDriver::Version6;
1023 case 7:
1024 {
1025 switch (vMin) {
1026 case 1:
1027 return QPSQLDriver::Version7_1;
1028 case 3:
1029 return QPSQLDriver::Version7_3;
1030 case 4:
1031 return QPSQLDriver::Version7_4;
1032 default:
1033 return QPSQLDriver::Version7;
1034 }
1035 break;
1036 }
1037 case 8:
1038 {
1039 switch (vMin) {
1040 case 1:
1041 return QPSQLDriver::Version8_1;
1042 case 2:
1043 return QPSQLDriver::Version8_2;
1044 case 3:
1045 return QPSQLDriver::Version8_3;
1046 case 4:
1047 return QPSQLDriver::Version8_4;
1048 default:
1049 return QPSQLDriver::Version8;
1050 }
1051 break;
1052 }
1053 case 9:
1054 {
1055 switch (vMin) {
1056 case 1:
1057 return QPSQLDriver::Version9_1;
1058 case 2:
1059 return QPSQLDriver::Version9_2;
1060 case 3:
1061 return QPSQLDriver::Version9_3;
1062 case 4:
1063 return QPSQLDriver::Version9_4;
1064 case 5:
1065 return QPSQLDriver::Version9_5;
1066 case 6:
1067 return QPSQLDriver::Version9_6;
1068 default:
1069 return QPSQLDriver::Version9;
1070 }
1071 break;
1072 }
1073 case 10:
1074 return QPSQLDriver::Version10;
1075 case 11:
1076 return QPSQLDriver::Version11;
1077 default:
1078 if (vMaj > 11)
1079 return QPSQLDriver::UnknownLaterVersion;
1080 break;
1081 }
1082 return QPSQLDriver::VersionUnknown;
1083}
1084
1085static QPSQLDriver::Protocol qFindPSQLVersion(const QString &versionString)
1086{
1087 const QRegularExpression rx(QStringLiteral("(\\d+)(?:\\.(\\d+))?"));
1088 const QRegularExpressionMatch match = rx.match(versionString);
1089 if (match.hasMatch()) {
1090 // Beginning with PostgreSQL version 10, a major release is indicated by
1091 // increasing the first part of the version, e.g. 10 to 11.
1092 // Before version 10, a major release was indicated by increasing either
1093 // the first or second part of the version number, e.g. 9.5 to 9.6.
1094 int vMaj = match.capturedRef(1).toInt();
1095 int vMin;
1096 if (vMaj >= 10) {
1097 vMin = 0;
1098 } else {
1099 if (match.capturedRef(2).isEmpty())
1100 return QPSQLDriver::VersionUnknown;
1101 vMin = match.capturedRef(2).toInt();
1102 }
1103 return qMakePSQLVersion(vMaj, vMin);
1104 }
1105
1106 return QPSQLDriver::VersionUnknown;
1107}
1108
1109QPSQLDriver::Protocol QPSQLDriverPrivate::getPSQLVersion()
1110{
1111 QPSQLDriver::Protocol serverVersion = QPSQLDriver::Version6;
1112 PGresult *result = exec("SELECT version()");
1113 int status = PQresultStatus(result);
1114 if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) {
1115 serverVersion = qFindPSQLVersion(
1116 QString::fromLatin1(PQgetvalue(result, 0, 0)));
1117 }
1118 PQclear(result);
1119
1120 QPSQLDriver::Protocol clientVersion =
1121#if defined(PG_MAJORVERSION)
1122 qFindPSQLVersion(QLatin1String(PG_MAJORVERSION));
1123#elif defined(PG_VERSION)
1124 qFindPSQLVersion(QLatin1String(PG_VERSION));
1125#else
1126 QPSQLDriver::VersionUnknown;
1127#endif
1128
1129 if (serverVersion == QPSQLDriver::VersionUnknown) {
1130 serverVersion = clientVersion;
1131 if (serverVersion != QPSQLDriver::VersionUnknown)
1132 qWarning("The server version of this PostgreSQL is unknown, falling back to the client version.");
1133 }
1134
1135 // Keep the old behavior unchanged
1136 if (serverVersion == QPSQLDriver::VersionUnknown)
1137 serverVersion = QPSQLDriver::Version6;
1138
1139 if (serverVersion < QPSQLDriver::Version7_3) {
1140 qWarning("This version of PostgreSQL is not supported and may not work.");
1141 }
1142
1143 return serverVersion;
1144}
1145
1146QPSQLDriver::QPSQLDriver(QObject *parent)
1147 : QSqlDriver(*new QPSQLDriverPrivate, parent)
1148{
1149}
1150
1151QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent)
1152 : QSqlDriver(*new QPSQLDriverPrivate, parent)
1153{
1154 Q_D(QPSQLDriver);
1155 d->connection = conn;
1156 if (conn) {
1157 d->pro = d->getPSQLVersion();
1158 d->detectBackslashEscape();
1159 setOpen(true);
1160 setOpenError(false);
1161 }
1162}
1163
1164QPSQLDriver::~QPSQLDriver()
1165{
1166 Q_D(QPSQLDriver);
1167 if (d->connection)
1168 PQfinish(d->connection);
1169}
1170
1171QVariant QPSQLDriver::handle() const
1172{
1173 Q_D(const QPSQLDriver);
1174 return QVariant::fromValue(d->connection);
1175}
1176
1177bool QPSQLDriver::hasFeature(DriverFeature f) const
1178{
1179 Q_D(const QPSQLDriver);
1180 switch (f) {
1181 case Transactions:
1182 case QuerySize:
1183 case LastInsertId:
1184 case LowPrecisionNumbers:
1185 case EventNotifications:
1186 case MultipleResultSets:
1187 case BLOB:
1188 return true;
1189 case PreparedQueries:
1190 case PositionalPlaceholders:
1191 return d->pro >= QPSQLDriver::Version8_2;
1192 case BatchOperations:
1193 case NamedPlaceholders:
1194 case SimpleLocking:
1195 case FinishQuery:
1196 case CancelQuery:
1197 return false;
1198 case Unicode:
1199 return d->isUtf8;
1200 }
1201 return false;
1202}
1203
1204/*
1205 Quote a string for inclusion into the connection string
1206 \ -> \\
1207 ' -> \'
1208 surround string by single quotes
1209 */
1210static QString qQuote(QString s)
1211{
1212 s.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
1213 s.replace(QLatin1Char('\''), QLatin1String("\\'"));
1214 s.append(QLatin1Char('\'')).prepend(QLatin1Char('\''));
1215 return s;
1216}
1217
1218bool QPSQLDriver::open(const QString &db,
1219 const QString &user,
1220 const QString &password,
1221 const QString &host,
1222 int port,
1223 const QString &connOpts)
1224{
1225 Q_D(QPSQLDriver);
1226 if (isOpen())
1227 close();
1228 QString connectString;
1229 if (!host.isEmpty())
1230 connectString.append(QLatin1String("host=")).append(qQuote(host));
1231 if (!db.isEmpty())
1232 connectString.append(QLatin1String(" dbname=")).append(qQuote(db));
1233 if (!user.isEmpty())
1234 connectString.append(QLatin1String(" user=")).append(qQuote(user));
1235 if (!password.isEmpty())
1236 connectString.append(QLatin1String(" password=")).append(qQuote(password));
1237 if (port != -1)
1238 connectString.append(QLatin1String(" port=")).append(qQuote(QString::number(port)));
1239
1240 // add any connect options - the server will handle error detection
1241 if (!connOpts.isEmpty()) {
1242 QString opt = connOpts;
1243 opt.replace(QLatin1Char(';'), QLatin1Char(' '), Qt::CaseInsensitive);
1244 connectString.append(QLatin1Char(' ')).append(opt);
1245 }
1246
1247 d->connection = PQconnectdb(std::move(connectString).toLocal8Bit().constData());
1248 if (PQstatus(d->connection) == CONNECTION_BAD) {
1249 setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
1250 setOpenError(true);
1251 PQfinish(d->connection);
1252 d->connection = nullptr;
1253 return false;
1254 }
1255
1256 d->pro = d->getPSQLVersion();
1257 d->detectBackslashEscape();
1258 d->isUtf8 = d->setEncodingUtf8();
1259 d->setDatestyle();
1260 d->setByteaOutput();
1261
1262 setOpen(true);
1263 setOpenError(false);
1264 return true;
1265}
1266
1267void QPSQLDriver::close()
1268{
1269 Q_D(QPSQLDriver);
1270 if (isOpen()) {
1271
1272 d->seid.clear();
1273 if (d->sn) {
1274 disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
1275 delete d->sn;
1276 d->sn = nullptr;
1277 }
1278
1279 if (d->connection)
1280 PQfinish(d->connection);
1281 d->connection = nullptr;
1282 setOpen(false);
1283 setOpenError(false);
1284 }
1285}
1286
1287QSqlResult *QPSQLDriver::createResult() const
1288{
1289 return new QPSQLResult(this);
1290}
1291
1292bool QPSQLDriver::beginTransaction()
1293{
1294 Q_D(QPSQLDriver);
1295 if (!isOpen()) {
1296 qWarning("QPSQLDriver::beginTransaction: Database not open");
1297 return false;
1298 }
1299 PGresult *res = d->exec("BEGIN");
1300 if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
1301 setLastError(qMakeError(tr("Could not begin transaction"),
1302 QSqlError::TransactionError, d, res));
1303 PQclear(res);
1304 return false;
1305 }
1306 PQclear(res);
1307 return true;
1308}
1309
1310bool QPSQLDriver::commitTransaction()
1311{
1312 Q_D(QPSQLDriver);
1313 if (!isOpen()) {
1314 qWarning("QPSQLDriver::commitTransaction: Database not open");
1315 return false;
1316 }
1317 PGresult *res = d->exec("COMMIT");
1318
1319 bool transaction_failed = false;
1320
1321 // XXX
1322 // This hack is used to tell if the transaction has succeeded for the protocol versions of
1323 // PostgreSQL below. For 7.x and other protocol versions we are left in the dark.
1324 // This hack can dissapear once there is an API to query this sort of information.
1325 if (d->pro >= QPSQLDriver::Version8) {
1326 transaction_failed = qstrcmp(PQcmdStatus(res), "ROLLBACK") == 0;
1327 }
1328
1329 if (!res || PQresultStatus(res) != PGRES_COMMAND_OK || transaction_failed) {
1330 setLastError(qMakeError(tr("Could not commit transaction"),
1331 QSqlError::TransactionError, d, res));
1332 PQclear(res);
1333 return false;
1334 }
1335 PQclear(res);
1336 return true;
1337}
1338
1339bool QPSQLDriver::rollbackTransaction()
1340{
1341 Q_D(QPSQLDriver);
1342 if (!isOpen()) {
1343 qWarning("QPSQLDriver::rollbackTransaction: Database not open");
1344 return false;
1345 }
1346 PGresult *res = d->exec("ROLLBACK");
1347 if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
1348 setLastError(qMakeError(tr("Could not rollback transaction"),
1349 QSqlError::TransactionError, d, res));
1350 PQclear(res);
1351 return false;
1352 }
1353 PQclear(res);
1354 return true;
1355}
1356
1357QStringList QPSQLDriver::tables(QSql::TableType type) const
1358{
1359 Q_D(const QPSQLDriver);
1360 QStringList tl;
1361 if (!isOpen())
1362 return tl;
1363 QSqlQuery t(createResult());
1364 t.setForwardOnly(true);
1365
1366 if (type & QSql::Tables)
1367 const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, QLatin1Char('r'));
1368 if (type & QSql::Views)
1369 const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, QLatin1Char('v'));
1370 if (type & QSql::SystemTables) {
1371 t.exec(QStringLiteral("SELECT relname FROM pg_class WHERE (relkind = 'r') "
1372 "AND (relname LIKE 'pg_%') "));
1373 while (t.next())
1374 tl.append(t.value(0).toString());
1375 }
1376
1377 return tl;
1378}
1379
1380static void qSplitTableName(QString &tablename, QString &schema)
1381{
1382 int dot = tablename.indexOf(QLatin1Char('.'));
1383 if (dot == -1)
1384 return;
1385 schema = tablename.left(dot);
1386 tablename = tablename.mid(dot + 1);
1387}
1388
1389QSqlIndex QPSQLDriver::primaryIndex(const QString &tablename) const
1390{
1391 QSqlIndex idx(tablename);
1392 if (!isOpen())
1393 return idx;
1394 QSqlQuery i(createResult());
1395
1396 QString tbl = tablename;
1397 QString schema;
1398 qSplitTableName(tbl, schema);
1399 schema = stripDelimiters(schema, QSqlDriver::TableName);
1400 tbl = stripDelimiters(tbl, QSqlDriver::TableName);
1401
1402 QString stmt = QStringLiteral("SELECT pg_attribute.attname, pg_attribute.atttypid::int, "
1403 "pg_class.relname "
1404 "FROM pg_attribute, pg_class "
1405 "WHERE %1 pg_class.oid IN "
1406 "(SELECT indexrelid FROM pg_index WHERE indisprimary = true AND indrelid IN "
1407 "(SELECT oid FROM pg_class WHERE relname = '%2')) "
1408 "AND pg_attribute.attrelid = pg_class.oid "
1409 "AND pg_attribute.attisdropped = false "
1410 "ORDER BY pg_attribute.attnum");
1411 if (schema.isEmpty())
1412 stmt = stmt.arg(QStringLiteral("pg_table_is_visible(pg_class.oid) AND"));
1413 else
1414 stmt = stmt.arg(QStringLiteral("pg_class.relnamespace = (SELECT oid FROM "
1415 "pg_namespace WHERE pg_namespace.nspname = '%1') AND").arg(schema));
1416
1417 i.exec(stmt.arg(tbl));
1418 while (i.isActive() && i.next()) {
1419 QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt()), tablename);
1420 idx.append(f);
1421 idx.setName(i.value(2).toString());
1422 }
1423 return idx;
1424}
1425
1426QSqlRecord QPSQLDriver::record(const QString &tablename) const
1427{
1428 QSqlRecord info;
1429 if (!isOpen())
1430 return info;
1431
1432 QString tbl = tablename;
1433 QString schema;
1434 qSplitTableName(tbl, schema);
1435 schema = stripDelimiters(schema, QSqlDriver::TableName);
1436 tbl = stripDelimiters(tbl, QSqlDriver::TableName);
1437
1438 QString stmt = QStringLiteral("SELECT pg_attribute.attname, pg_attribute.atttypid::int, "
1439 "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
1440 "pg_attrdef.adsrc "
1441 "FROM pg_class, pg_attribute "
1442 "LEFT JOIN pg_attrdef ON (pg_attrdef.adrelid = "
1443 "pg_attribute.attrelid AND pg_attrdef.adnum = pg_attribute.attnum) "
1444 "WHERE %1 "
1445 "AND pg_class.relname = '%2' "
1446 "AND pg_attribute.attnum > 0 "
1447 "AND pg_attribute.attrelid = pg_class.oid "
1448 "AND pg_attribute.attisdropped = false "
1449 "ORDER BY pg_attribute.attnum");
1450 if (schema.isEmpty())
1451 stmt = stmt.arg(QStringLiteral("pg_table_is_visible(pg_class.oid)"));
1452 else
1453 stmt = stmt.arg(QStringLiteral("pg_class.relnamespace = (SELECT oid FROM "
1454 "pg_namespace WHERE pg_namespace.nspname = '%1')").arg(schema));
1455
1456 QSqlQuery query(createResult());
1457 query.exec(stmt.arg(tbl));
1458 while (query.next()) {
1459 int len = query.value(3).toInt();
1460 int precision = query.value(4).toInt();
1461 // swap length and precision if length == -1
1462 if (len == -1 && precision > -1) {
1463 len = precision - 4;
1464 precision = -1;
1465 }
1466 QString defVal = query.value(5).toString();
1467 if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\'')) {
1468 const int end = defVal.lastIndexOf(QLatin1Char('\''));
1469 if (end > 0)
1470 defVal = defVal.mid(1, end - 1);
1471 }
1472 QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()), tablename);
1473 f.setRequired(query.value(2).toBool());
1474 f.setLength(len);
1475 f.setPrecision(precision);
1476 f.setDefaultValue(defVal);
1477 f.setSqlType(query.value(1).toInt());
1478 info.append(f);
1479 }
1480
1481 return info;
1482}
1483
1484template <class FloatType>
1485inline void assignSpecialPsqlFloatValue(FloatType val, QString *target)
1486{
1487 if (qIsNaN(val))
1488 *target = QStringLiteral("'NaN'");
1489 else if (qIsInf(val))
1490 *target = (val < 0) ? QStringLiteral("'-Infinity'") : QStringLiteral("'Infinity'");
1491}
1492
1493QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
1494{
1495 Q_D(const QPSQLDriver);
1496 const auto nullStr = [](){ return QStringLiteral("NULL"); };
1497 QString r;
1498 if (field.isNull()) {
1499 r = nullStr();
1500 } else {
1501 switch (int(field.type())) {
1502 case QVariant::DateTime:
1503#if QT_CONFIG(datestring)
1504 if (field.value().toDateTime().isValid()) {
1505 // we force the value to be considered with a timezone information, and we force it to be UTC
1506 // this is safe since postgresql stores only the UTC value and not the timezone offset (only used
1507 // while parsing), so we have correct behavior in both case of with timezone and without tz
1508 r = QStringLiteral("TIMESTAMP WITH TIME ZONE ") + QLatin1Char('\'') +
1509 QLocale::c().toString(field.value().toDateTime().toUTC(), u"yyyy-MM-ddThh:mm:ss.zzz") +
1510 QLatin1Char('Z') + QLatin1Char('\'');
1511 } else {
1512 r = nullStr();
1513 }
1514#else
1515 r = nullStr();
1516#endif // datestring
1517 break;
1518 case QVariant::Time:
1519#if QT_CONFIG(datestring)
1520 if (field.value().toTime().isValid()) {
1521 r = QLatin1Char('\'') + field.value().toTime().toString(u"hh:mm:ss.zzz") + QLatin1Char('\'');
1522 } else
1523#endif
1524 {
1525 r = nullStr();
1526 }
1527 break;
1528 case QVariant::String:
1529 r = QSqlDriver::formatValue(field, trimStrings);
1530 if (d->hasBackslashEscape)
1531 r.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
1532 break;
1533 case QVariant::Bool:
1534 if (field.value().toBool())
1535 r = QStringLiteral("TRUE");
1536 else
1537 r = QStringLiteral("FALSE");
1538 break;
1539 case QVariant::ByteArray: {
1540 QByteArray ba(field.value().toByteArray());
1541 size_t len;
1542#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 80200
1543 unsigned char *data = PQescapeByteaConn(d->connection, (const unsigned char*)ba.constData(), ba.size(), &len);
1544#else
1545 unsigned char *data = PQescapeBytea((const unsigned char*)ba.constData(), ba.size(), &len);
1546#endif
1547 r += QLatin1Char('\'');
1548 r += QLatin1String((const char*)data);
1549 r += QLatin1Char('\'');
1550 qPQfreemem(data);
1551 break;
1552 }
1553 case QMetaType::Float:
1554 assignSpecialPsqlFloatValue(field.value().toFloat(), &r);
1555 if (r.isEmpty())
1556 r = QSqlDriver::formatValue(field, trimStrings);
1557 break;
1558 case QVariant::Double:
1559 assignSpecialPsqlFloatValue(field.value().toDouble(), &r);
1560 if (r.isEmpty())
1561 r = QSqlDriver::formatValue(field, trimStrings);
1562 break;
1563 case QVariant::Uuid:
1564 r = QLatin1Char('\'') + field.value().toString() + QLatin1Char('\'');
1565 break;
1566 default:
1567 r = QSqlDriver::formatValue(field, trimStrings);
1568 break;
1569 }
1570 }
1571 return r;
1572}
1573
1574QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
1575{
1576 QString res = identifier;
1577 if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) {
1578 res.replace(QLatin1Char('"'), QLatin1String("\"\""));
1579 res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
1580 res.replace(QLatin1Char('.'), QLatin1String("\".\""));
1581 }
1582 return res;
1583}
1584
1585bool QPSQLDriver::isOpen() const
1586{
1587 Q_D(const QPSQLDriver);
1588 return PQstatus(d->connection) == CONNECTION_OK;
1589}
1590
1591QPSQLDriver::Protocol QPSQLDriver::protocol() const
1592{
1593 Q_D(const QPSQLDriver);
1594 return d->pro;
1595}
1596
1597bool QPSQLDriver::subscribeToNotification(const QString &name)
1598{
1599 Q_D(QPSQLDriver);
1600 if (!isOpen()) {
1601 qWarning("QPSQLDriver::subscribeToNotificationImplementation: database not open.");
1602 return false;
1603 }
1604
1605 if (d->seid.contains(name)) {
1606 qWarning("QPSQLDriver::subscribeToNotificationImplementation: already subscribing to '%s'.",
1607 qPrintable(name));
1608 return false;
1609 }
1610
1611 int socket = PQsocket(d->connection);
1612 if (socket) {
1613 // Add the name to the list of subscriptions here so that QSQLDriverPrivate::exec knows
1614 // to check for notifications immediately after executing the LISTEN
1615 d->seid << name;
1616 QString query = QStringLiteral("LISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
1617 PGresult *result = d->exec(query);
1618 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
1619 d->seid.removeLast();
1620 setLastError(qMakeError(tr("Unable to subscribe"), QSqlError::StatementError, d, result));
1621 PQclear(result);
1622 return false;
1623 }
1624 PQclear(result);
1625
1626 if (!d->sn) {
1627 d->sn = new QSocketNotifier(socket, QSocketNotifier::Read);
1628 connect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
1629 }
1630 } else {
1631 qWarning("QPSQLDriver::subscribeToNotificationImplementation: PQsocket didn't return a valid socket to listen on");
1632 return false;
1633 }
1634
1635 return true;
1636}
1637
1638bool QPSQLDriver::unsubscribeFromNotification(const QString &name)
1639{
1640 Q_D(QPSQLDriver);
1641 if (!isOpen()) {
1642 qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: database not open.");
1643 return false;
1644 }
1645
1646 if (!d->seid.contains(name)) {
1647 qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: not subscribed to '%s'.",
1648 qPrintable(name));
1649 return false;
1650 }
1651
1652 QString query = QStringLiteral("UNLISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
1653 PGresult *result = d->exec(query);
1654 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
1655 setLastError(qMakeError(tr("Unable to unsubscribe"), QSqlError::StatementError, d, result));
1656 PQclear(result);
1657 return false;
1658 }
1659 PQclear(result);
1660
1661 d->seid.removeAll(name);
1662
1663 if (d->seid.isEmpty()) {
1664 disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
1665 delete d->sn;
1666 d->sn = nullptr;
1667 }
1668
1669 return true;
1670}
1671
1672QStringList QPSQLDriver::subscribedToNotifications() const
1673{
1674 Q_D(const QPSQLDriver);
1675 return d->seid;
1676}
1677
1678void QPSQLDriver::_q_handleNotification(int)
1679{
1680 Q_D(QPSQLDriver);
1681 d->pendingNotifyCheck = false;
1682 PQconsumeInput(d->connection);
1683
1684 PGnotify *notify = nullptr;
1685 while ((notify = PQnotifies(d->connection)) != nullptr) {
1686 QString name(QLatin1String(notify->relname));
1687 if (d->seid.contains(name)) {
1688 QString payload;
1689#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 70400
1690 if (notify->extra)
1691 payload = d->isUtf8 ? QString::fromUtf8(notify->extra) : QString::fromLatin1(notify->extra);
1692#endif
1693 emit notification(name);
1694 QSqlDriver::NotificationSource source = (notify->be_pid == PQbackendPID(d->connection)) ? QSqlDriver::SelfSource : QSqlDriver::OtherSource;
1695 emit notification(name, source, payload);
1696 }
1697 else
1698 qWarning("QPSQLDriver: received notification for '%s' which isn't subscribed to.",
1699 qPrintable(name));
1700
1701 qPQfreemem(notify);
1702 }
1703}
1704
1705QT_END_NAMESPACE
1706