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

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