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

source code of qtbase/src/plugins/sqldrivers/psql/qsql_psql.cpp