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