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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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