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