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