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