7#include <qcoreapplication.h>
8#include <qanystringview.h>
11#include <qloggingcategory.h>
12#include <qregularexpression.h>
16#include <qsqlrecord.h>
18#include <qsocketnotifier.h>
19#include <qstringlist.h>
22#include <qvarlengtharray.h>
23#include <QtSql/private/qsqlresult_p.h>
24#include <QtSql/private/qsqldriver_p.h>
25#include <QtCore/private/qlocale_tools_p.h>
39#define QNUMERICOID 1700
42#define QABSTIMEOID 702
43#define QRELTIMEOID 703
46#define QTIMETZOID 1266
47#define QTIMESTAMPOID 1114
48#define QTIMESTAMPTZOID 1184
56#define QVARBITOID 1562
62
66Q_DECLARE_OPAQUE_POINTER(PGconn*)
67Q_DECLARE_METATYPE(PGconn*)
69Q_DECLARE_OPAQUE_POINTER(PGresult*)
70Q_DECLARE_METATYPE(PGresult*)
76using namespace Qt::StringLiterals;
84#if !defined PG_VERSION_NUM || PG_VERSION_NUM-0
< 90200
91class QPSQLResultPrivate;
113 bool reset(
const QString &query)
override;
124 Q_DECLARE_PUBLIC(QPSQLDriver)
158 const QString query =
159 QStringLiteral(
"SELECT pg_class.relname, pg_namespace.nspname FROM pg_class "
160 "LEFT JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid) "
161 "WHERE (pg_class.relkind = '") + type +
162 QStringLiteral(
"') AND (pg_class.relname !~ '^Inv') "
163 "AND (pg_class.relname !~ '^pg_') "
164 "AND (pg_namespace.nspname != 'information_schema')");
167 QString schema = t.value(1).toString();
168 if (schema.isEmpty() || schema ==
"public"_L1)
169 tl.append(t.value(0).toString());
171 tl.append(t.value(0).toString().prepend(u'.').prepend(schema));
186 return exec(stmt.toUtf8().constData()
);
194 const int result = PQsendQuery(
connection, stmt.toUtf8().constData());
204#if defined PG_VERSION_NUM && PG_VERSION_NUM-0
>= 90200
205 return PQsetSingleRowMode(connection) > 0;
219 qCWarning(lcPsql,
"QPSQLDriver::getResult: Query results lost - "
220 "probably discarded on executing another SQL query.");
252 Q_Q(
const QPSQLDriver);
253 if (seid.size() && !pendingNotifyCheck) {
255 QMetaObject::invokeMethod(
const_cast<QPSQLDriver*>(q), &QPSQLDriver::_q_handleNotification, Qt::QueuedConnection);
259class QPSQLResultPrivate
final :
public QSqlResultPrivate
261 Q_DECLARE_PUBLIC(QPSQLResult)
263 Q_DECLARE_SQLDRIVER_PRIVATE(QPSQLDriver)
264 using QSqlResultPrivate::QSqlResultPrivate;
280static QSqlError
qMakeError(
const QString &err, QSqlError::ErrorType type,
284 QString msg = QString::fromUtf8(s);
287 errorCode = QString::fromLatin1(PQresultErrorField(result, PG_DIAG_SQLSTATE));
288 msg += QString::fromLatin1(
"(%1)").arg(errorCode);
290 return QSqlError(
"QPSQL: "_L1 + err, msg, type, errorCode);
301 if (
stmtId != drv_d_func()->currentStmtId) {
302 q->setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
303 "Query results lost - probably discarded on executing "
304 "another SQL query."), QSqlError::StatementError, drv_d_func(), result));
308 int status = PQresultStatus(
result);
310 case PGRES_TUPLES_OK:
313 currentSize = q->isForwardOnly() ? -1 : PQntuples(result);
316 case PGRES_SINGLE_TUPLE:
322 case PGRES_COMMAND_OK:
335 q->setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
336 "Unable to create query"), QSqlError::StatementError, drv_d_func(), result));
342 int type = QMetaType::UnknownType;
345 type = QMetaType::Bool;
348 type = QMetaType::LongLong;
356 type = QMetaType::Int;
361 type = QMetaType::Double;
366 type = QMetaType::QDate;
370 type = QMetaType::QTime;
374 type = QMetaType::QDateTime;
377 type = QMetaType::QByteArray;
380 type = QMetaType::QUuid;
383 type = QMetaType::QString;
386 return QMetaType(type);
391 if (drv_d_func() && !preparedStmtId.isEmpty()) {
392#if defined(LIBPQ_HAS_CLOSE_PREPARED)
393 PGresult *result = PQclosePrepared(drv_d_func()->connection, preparedStmtId.constData());
395 const QByteArray stmt = QByteArrayView(
"DEALLOCATE ") + preparedStmtId;
396 PGresult *result = drv_d_func()->exec(stmt.constData());
399 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
400 const QString msg = QString::fromUtf8(PQerrorMessage(drv_d_func()->connection));
401 qCWarning(lcPsql,
"Unable to free statement: %ls.", qUtf16Printable(msg));
405 preparedStmtId.clear();
409 : QSqlResult(*
new QPSQLResultPrivate(
this, db))
412 d->preparedQueriesEnabled = db->hasFeature(QSqlDriver::PreparedQueries);
420 d->deallocatePreparedStmt();
425 Q_D(
const QPSQLResult);
426 return QVariant::fromValue(d->result);
435 while (!d->nextResultSets.empty()) {
436 PQclear(d->nextResultSets.front());
437 d->nextResultSets.pop();
439 if (d->stmtId != InvalidStatementId) {
441 d->drv_d_func()->finishQuery(d->stmtId);
443 d->stmtId = InvalidStatementId;
444 setAt(QSql::BeforeFirstRow);
446 d->canFetchMoreRows =
false;
452 Q_D(
const QPSQLResult);
460 if (isForwardOnly()) {
464 while (ok && i > at())
469 if (i >= d->currentSize)
477 Q_D(
const QPSQLResult);
483 if (isForwardOnly()) {
484 if (at() == QSql::BeforeFirstRow) {
487 if (d->result && PQntuples(d->result) > 0) {
500 Q_D(
const QPSQLResult);
504 if (isForwardOnly()) {
507 if (i == QSql::AfterLastRow)
509 if (i == QSql::BeforeFirstRow)
517 return fetch(d->currentSize - 1);
526 const int currentRow = at();
527 if (currentRow == QSql::BeforeFirstRow)
529 if (currentRow == QSql::AfterLastRow)
532 if (isForwardOnly()) {
533 if (!d->canFetchMoreRows)
536 d->result = d->drv_d_func()->getResult(d->stmtId);
538 setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
539 "Unable to get result"), QSqlError::StatementError, d->drv_d_func(), d->result));
540 d->canFetchMoreRows =
false;
543 int status = PQresultStatus(d->result);
545 case PGRES_SINGLE_TUPLE:
547 Q_ASSERT(PQntuples(d->result) == 1);
548 Q_ASSERT(d->canFetchMoreRows);
549 setAt(currentRow + 1);
551 case PGRES_TUPLES_OK:
553 Q_ASSERT(PQntuples(d->result) == 0);
554 d->canFetchMoreRows =
false;
557 setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
558 "Unable to get result"), QSqlError::StatementError, d->drv_d_func(), d->result));
559 d->canFetchMoreRows =
false;
564 if (currentRow + 1 >= d->currentSize)
566 setAt(currentRow + 1);
576 setAt(QSql::BeforeFirstRow);
578 if (isForwardOnly()) {
579 if (d->canFetchMoreRows) {
581 while (d->result && PQresultStatus(d->result) == PGRES_SINGLE_TUPLE) {
583 d->result = d->drv_d_func()->getResult(d->stmtId);
585 d->canFetchMoreRows =
false;
587 if (d->result && PQresultStatus(d->result) == PGRES_FATAL_ERROR)
588 return d->processResults();
593 d->result = d->drv_d_func()->getResult(d->stmtId);
594 return d->processResults();
600 if (!d->nextResultSets.empty()) {
601 d->result = d->nextResultSets.front();
602 d->nextResultSets.pop();
604 return d->processResults();
609 Q_D(
const QPSQLResult);
610 if (i >= PQnfields(d->result)) {
611 qCWarning(lcPsql,
"QPSQLResult::data: column %d out of range.", i);
614 const int currentRow = isForwardOnly() ? 0 : at();
615 int ptype = PQftype(d->result, i);
616 QMetaType type = qDecodePSQLType(ptype);
617 if (PQgetisnull(d->result, currentRow, i))
618 return QVariant(type,
nullptr);
619 const char *val = PQgetvalue(d->result, currentRow, i);
621 case QMetaType::Bool:
622 return QVariant((
bool)(val[0] ==
't'));
623 case QMetaType::QString:
624 return QString::fromUtf8(val);
625 case QMetaType::LongLong:
627 return QByteArray::fromRawData(val, qstrlen(val)).toLongLong();
629 return QByteArray::fromRawData(val, qstrlen(val)).toULongLong();
632 case QMetaType::Double: {
634 if (numericalPrecisionPolicy() == QSql::HighPrecision)
635 return QString::fromLatin1(val);
638 double dbl = qstrtod(val,
nullptr, &ok);
640 if (qstricmp(val,
"NaN") == 0)
642 else if (qstricmp(val,
"Infinity") == 0)
644 else if (qstricmp(val,
"-Infinity") == 0)
650 if (numericalPrecisionPolicy() == QSql::LowPrecisionInt64)
651 return QVariant((qlonglong)dbl);
652 else if (numericalPrecisionPolicy() == QSql::LowPrecisionInt32)
653 return QVariant((
int)dbl);
654 else if (numericalPrecisionPolicy() == QSql::LowPrecisionDouble)
655 return QVariant(dbl);
659#if QT_CONFIG(datestring)
660 case QMetaType::QDate:
661 return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate));
662 case QMetaType::QTime:
663 return QVariant(QTime::fromString(QString::fromLatin1(val), Qt::ISODate));
664 case QMetaType::QDateTime: {
665 const QLatin1StringView tzString(val);
666 const auto timeString(tzString.sliced(11));
667 if (timeString.contains(u'-') || timeString.contains(u'+') || timeString.endsWith(u'Z'))
668 return QDateTime::fromString(tzString, Qt::ISODate);
669 const auto utc = tzString.toString() + u'Z';
670 return QVariant(QDateTime::fromString(utc, Qt::ISODate));
673 case QMetaType::QDate:
674 case QMetaType::QTime:
675 case QMetaType::QDateTime:
676 return QVariant(QString::fromLatin1(val));
678 case QMetaType::QByteArray: {
680 unsigned char *data = PQunescapeBytea(
reinterpret_cast<
const unsigned char *>(val), &len);
681 QByteArray ba(
reinterpret_cast<
const char *>(data), len);
685 case QMetaType::QUuid:
686 return QUuid::fromString(val);
688 qCWarning(lcPsql,
"QPSQLResult::data: unhandled data type %d.", type.id());
695 Q_D(
const QPSQLResult);
696 const int currentRow = isForwardOnly() ? 0 : at();
697 return PQgetisnull(d->result, currentRow, field);
706 if (!driver()->isOpen() || driver()->isOpenError())
709 d->stmtId = d->drv_d_func()->sendQuery(query);
710 if (d->stmtId == InvalidStatementId) {
711 setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
712 "Unable to send query"), QSqlError::StatementError, d->drv_d_func()));
717 setForwardOnly(d->drv_d_func()->setSingleRowMode());
719 d->result = d->drv_d_func()->getResult(d->stmtId);
720 if (!isForwardOnly()) {
722 while (PGresult *nextResultSet = d->drv_d_func()->getResult(d->stmtId))
723 d->nextResultSets.push(nextResultSet);
725 return d->processResults();
730 Q_D(
const QPSQLResult);
731 return d->currentSize;
736 Q_D(
const QPSQLResult);
737 const char *tuples = PQcmdTuples(d->result);
738 return QByteArray::fromRawData(tuples, qstrlen(tuples)).toInt();
743 Q_D(
const QPSQLResult);
744 if (d->drv_d_func()->pro >= QPSQLDriver::Version8_1) {
747 if (qry.exec(QStringLiteral(
"SELECT lastval();")) && qry.next())
749 }
else if (isActive()) {
750 Oid id = PQoidValue(d->result);
751 if (id != InvalidOid)
759 Q_D(
const QPSQLResult);
761 if (!isActive() || !isSelect())
764 int count = PQnfields(d->result);
766 for (
int i = 0; i < count; ++i) {
767 f.setName(QString::fromUtf8(PQfname(d->result, i)));
768 const int tableOid = PQftable(d->result, i);
773 if (tableOid != InvalidOid && !isForwardOnly()) {
774 auto &tableName = d->drv_d_func()->oidToTable[tableOid];
775 if (tableName.isEmpty()) {
777 if (qry.exec(QStringLiteral(
"SELECT relname FROM pg_class WHERE pg_class.oid = %1")
778 .arg(tableOid)) && qry.next()) {
779 tableName = qry.value(0).toString();
782 f.setTableName(tableName);
784 f.setTableName(QString());
786 int ptype = PQftype(d->result, i);
787 f.setMetaType(qDecodePSQLType(ptype));
788 f.setValue(QVariant(f.metaType()));
789 int len = PQfsize(d->result, i);
790 int precision = PQfmod(d->result, i);
799 if (precision != -1) {
800 len = (precision >> 16);
801 precision = ((precision -
VARHDRSZ) & 0xffff);
810 if (len == -1 && precision >=
VARHDRSZ) {
817 f.setPrecision(precision);
826 QSqlResult::virtual_hook(id, data);
829static auto qCreateParam(QSqlField &f,
const QVariant &boundValue,
const QPSQLDriver *driver)
831 std::pair<QByteArray,
bool > param;
832 if (!QSqlResultPrivate::isVariantNull(boundValue)) {
834 switch (boundValue.metaType().id()) {
835 case QMetaType::QByteArray:
836 param = {boundValue.toByteArray(),
true};
839 f.setMetaType(boundValue.metaType());
840 f.setValue(boundValue);
841 const QString strval = driver->formatValue<
true>(f);
842 param = {strval.isNull() ? QByteArray{} : strval.toUtf8(),
false};
852 Q_CONSTINIT
static QBasicAtomicInt qPreparedStmtCount = Q_BASIC_ATOMIC_INITIALIZER(0);
853 return QByteArrayView(
"qpsqlpstmt_") + QByteArray::number(qPreparedStmtCount.fetchAndAddRelaxed(1) + 1, 16);
859 if (!d->preparedQueriesEnabled)
860 return QSqlResult::prepare(query);
864 d->deallocatePreparedStmt();
866 const QByteArray stmtId = qMakePreparedStmtId();
867 PGresult *result = PQprepare(d->drv_d_func()->connection, stmtId.constData(),
868 d->positionalToNamedBinding(query).toUtf8(), 0,
nullptr);
870 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
871 setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
872 "Unable to prepare statement"), QSqlError::StatementError, d->drv_d_func(), result));
874 d->preparedStmtId.clear();
879 d->preparedStmtId = stmtId;
886 if (!d->preparedQueriesEnabled)
887 return QSqlResult::exec();
891 QVarLengthArray<
const char *> pgParams;
892 QVarLengthArray<
int> pgParamLengths;
893 QVarLengthArray<
int> pgParamFormats;
894 QVarLengthArray<QByteArray> _refsToKeep;
896 if (
const QVariantList values = boundValues(); !values.isEmpty()) {
898 for (
const QVariant &value : values) {
899 auto [param, binary] = qCreateParam(f, value,
static_cast<
const QPSQLDriver *>(driver()));
900 pgParams.emplace_back(param.constBegin());
901 pgParamLengths.emplace_back(param.size());
902 pgParamFormats.emplace_back(binary);
904 _refsToKeep.emplace_back(std::move(param));
908 d->result = PQexecPrepared(d->drv_d_func()->connection, d->preparedStmtId.constData(), pgParams.size(),
909 pgParams.data(), pgParamLengths.data(), pgParamFormats.data(), 0);
911 const auto status = PQresultStatus(d->result);
912 if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) {
913 d->stmtId = InvalidStatementId;
914 setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
915 "Unable to send query"), QSqlError::StatementError, d->drv_d_func(), d->result));
918 d->stmtId = d->drv_d_func()->currentStmtId = d->drv_d_func()->generateStatementId();
921 setForwardOnly(d->drv_d_func()->setSingleRowMode());
923 if (!isForwardOnly()) {
925 while (PGresult *nextResultSet = d->drv_d_func()->getResult(d->stmtId))
926 d->nextResultSets.push(nextResultSet);
928 return d->processResults();
936 int status = PQresultStatus(result);
938 return status == PGRES_COMMAND_OK;
944 int status = PQresultStatus(result);
945 if (status != PGRES_COMMAND_OK)
946 qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
952 if (pro >= QPSQLDriver::Version9) {
957 int status = PQresultStatus(result);
958 if (status != PGRES_COMMAND_OK)
959 qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
967 int status = PQresultStatus(result);
968 if (status != PGRES_COMMAND_OK)
969 qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
977 if (pro < QPSQLDriver::Version8_2) {
982 int status = PQresultStatus(result);
983 if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK)
984 if (QString::fromLatin1(PQgetvalue(result, 0, 0)) ==
"\\"_L1)
994 return QPSQLDriver::Version6;
999 return QPSQLDriver::Version7_1;
1001 return QPSQLDriver::Version7_3;
1003 return QPSQLDriver::Version7_4;
1005 return QPSQLDriver::Version7;
1013 return QPSQLDriver::Version8_1;
1015 return QPSQLDriver::Version8_2;
1017 return QPSQLDriver::Version8_3;
1019 return QPSQLDriver::Version8_4;
1021 return QPSQLDriver::Version8;
1029 return QPSQLDriver::Version9_1;
1031 return QPSQLDriver::Version9_2;
1033 return QPSQLDriver::Version9_3;
1035 return QPSQLDriver::Version9_4;
1037 return QPSQLDriver::Version9_5;
1039 return QPSQLDriver::Version9_6;
1041 return QPSQLDriver::Version9;
1046 return QPSQLDriver::Version10;
1048 return QPSQLDriver::Version11;
1050 return QPSQLDriver::Version12;
1053 return QPSQLDriver::UnknownLaterVersion;
1056 return QPSQLDriver::VersionUnknown;
1061 const QRegularExpression rx(QStringLiteral(
"(\\d+)(?:\\.(\\d+))?"));
1062 const QRegularExpressionMatch match = rx.match(versionString);
1063 if (match.hasMatch()) {
1068 int vMaj = match.capturedView(1).toInt();
1073 if (match.capturedView(2).isEmpty())
1074 return QPSQLDriver::VersionUnknown;
1075 vMin = match.capturedView(2).toInt();
1077 return qMakePSQLVersion(vMaj, vMin);
1080 return QPSQLDriver::VersionUnknown;
1085 QPSQLDriver::Protocol serverVersion = QPSQLDriver::Version6;
1087 int status = PQresultStatus(result);
1088 if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) {
1089 serverVersion = qFindPSQLVersion(
1090 QString::fromLatin1(PQgetvalue(result, 0, 0)));
1094 QPSQLDriver::Protocol clientVersion =
1095#if defined(PG_MAJORVERSION)
1096 qFindPSQLVersion(PG_MAJORVERSION
""_L1);
1097#elif defined(PG_VERSION)
1098 qFindPSQLVersion(PG_VERSION
""_L1);
1100 QPSQLDriver::VersionUnknown;
1103 if (serverVersion == QPSQLDriver::VersionUnknown) {
1104 serverVersion = clientVersion;
1105 if (serverVersion != QPSQLDriver::VersionUnknown)
1106 qCWarning(lcPsql,
"The server version of this PostgreSQL is unknown, "
1107 "falling back to the client version.");
1111 if (serverVersion == QPSQLDriver::VersionUnknown)
1112 serverVersion = QPSQLDriver::Version6;
1114 if (serverVersion < QPSQLDriver::Version7_3)
1115 qCWarning(lcPsql,
"This version of PostgreSQL is not supported and may not work.");
1117 return serverVersion;
1120QPSQLDriver::QPSQLDriver(QObject *parent)
1121 : QSqlDriver(*
new QPSQLDriverPrivate, parent)
1125QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent)
1126 : QSqlDriver(*
new QPSQLDriverPrivate, parent)
1129 d->connection = conn;
1131 d->pro = d->getPSQLVersion();
1132 d->detectBackslashEscape();
1134 setOpenError(
false);
1138QPSQLDriver::~QPSQLDriver()
1141 PQfinish(d->connection);
1144QVariant QPSQLDriver::handle()
const
1146 Q_D(
const QPSQLDriver);
1147 return QVariant::fromValue(d->connection);
1150bool QPSQLDriver::hasFeature(DriverFeature f)
const
1152 Q_D(
const QPSQLDriver);
1157 case LowPrecisionNumbers:
1158 case EventNotifications:
1159 case MultipleResultSets:
1163 case PreparedQueries:
1164 case PositionalPlaceholders:
1165 return d->pro >= QPSQLDriver::Version8_2;
1166 case BatchOperations:
1167 case NamedPlaceholders:
1177
1178
1179
1180
1181
1184 s.replace(u'\\',
"\\\\"_L1);
1185 s.replace(u'\'',
"\\'"_L1);
1186 s.append(u'\'').prepend(u'\'');
1190bool QPSQLDriver::open(
const QString &db,
1191 const QString &user,
1192 const QString &password,
1193 const QString &host,
1195 const QString &connOpts)
1199 QString connectString;
1200 if (!host.isEmpty())
1201 connectString.append(
"host="_L1).append(qQuote(host));
1203 connectString.append(
" dbname="_L1).append(qQuote(db));
1204 if (!user.isEmpty())
1205 connectString.append(
" user="_L1).append(qQuote(user));
1206 if (!password.isEmpty())
1207 connectString.append(
" password="_L1).append(qQuote(password));
1209 connectString.append(
" port="_L1).append(qQuote(QString::number(port)));
1212 if (!connOpts.isEmpty()) {
1213 QString opt = connOpts;
1214 opt.replace(
';'_L1,
' '_L1, Qt::CaseInsensitive);
1215 connectString.append(u' ').append(opt);
1218 d->connection = PQconnectdb(std::move(connectString).toLocal8Bit().constData());
1219 if (PQstatus(d->connection) == CONNECTION_BAD) {
1220 setLastError(qMakeError(tr(
"Unable to connect"), QSqlError::ConnectionError, d));
1222 PQfinish(d->connection);
1223 d->connection =
nullptr;
1227 d->pro = d->getPSQLVersion();
1228 d->detectBackslashEscape();
1229 if (!d->setEncodingUtf8()) {
1230 setLastError(qMakeError(tr(
"Unable to set client encoding to 'UNICODE'"), QSqlError::ConnectionError, d));
1232 PQfinish(d->connection);
1233 d->connection =
nullptr;
1237 d->setByteaOutput();
1238 d->setUtcTimeZone();
1241 setOpenError(
false);
1245void QPSQLDriver::close()
1251 disconnect(d->sn, &QSocketNotifier::activated,
this, &QPSQLDriver::_q_handleNotification);
1256 PQfinish(d->connection);
1257 d->connection =
nullptr;
1259 setOpenError(
false);
1262QSqlResult *QPSQLDriver::createResult()
const
1264 return new QPSQLResult(
this);
1267bool QPSQLDriver::beginTransaction()
1271 qCWarning(lcPsql,
"QPSQLDriver::beginTransaction: Database not open.");
1274 PGresult *res = d->exec(
"BEGIN");
1275 if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
1276 setLastError(qMakeError(tr(
"Could not begin transaction"),
1277 QSqlError::TransactionError, d, res));
1285bool QPSQLDriver::commitTransaction()
1289 qCWarning(lcPsql,
"QPSQLDriver::commitTransaction: Database not open.");
1292 PGresult *res = d->exec(
"COMMIT");
1294 bool transaction_failed =
false;
1300 if (d->pro >= QPSQLDriver::Version8) {
1301 transaction_failed = qstrcmp(PQcmdStatus(res),
"ROLLBACK") == 0;
1304 if (!res || PQresultStatus(res) != PGRES_COMMAND_OK || transaction_failed) {
1305 setLastError(qMakeError(tr(
"Could not commit transaction"),
1306 QSqlError::TransactionError, d, res));
1314bool QPSQLDriver::rollbackTransaction()
1318 qCWarning(lcPsql,
"QPSQLDriver::rollbackTransaction: Database not open.");
1321 PGresult *res = d->exec(
"ROLLBACK");
1322 if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
1323 setLastError(qMakeError(tr(
"Could not rollback transaction"),
1324 QSqlError::TransactionError, d, res));
1332QStringList QPSQLDriver::tables(QSql::TableType type)
const
1334 Q_D(
const QPSQLDriver);
1338 QSqlQuery t(createResult());
1339 t.setForwardOnly(
true);
1341 if (type & QSql::Tables)
1342 const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, u'r');
1343 if (type & QSql::Views)
1344 const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, u'v');
1345 if (type & QSql::SystemTables) {
1346 t.exec(QStringLiteral(
"SELECT relname FROM pg_class WHERE (relkind = 'r') "
1347 "AND (relname LIKE 'pg_%') "));
1349 tl.append(t.value(0).toString());
1357 qsizetype dot = tablename.indexOf(u'.');
1360 schema = tablename.left(dot);
1361 tablename = tablename.mid(dot + 1);
1364QSqlIndex QPSQLDriver::primaryIndex(
const QString &tablename)
const
1366 QSqlIndex idx(tablename);
1369 QSqlQuery i(createResult());
1371 QString tbl = tablename;
1373 qSplitTableName(tbl, schema);
1374 schema = stripDelimiters(schema, QSqlDriver::TableName);
1375 tbl = stripDelimiters(tbl, QSqlDriver::TableName);
1377 QString stmt = QStringLiteral(
"SELECT pg_attribute.attname, pg_attribute.atttypid::int, "
1379 "FROM pg_attribute, pg_class "
1380 "WHERE %1 pg_class.oid IN "
1381 "(SELECT indexrelid FROM pg_index WHERE indisprimary = true AND indrelid IN "
1382 "(SELECT oid FROM pg_class WHERE relname = '%2')) "
1383 "AND pg_attribute.attrelid = pg_class.oid "
1384 "AND pg_attribute.attisdropped = false "
1385 "ORDER BY pg_attribute.attnum");
1386 if (schema.isEmpty())
1387 stmt = stmt.arg(QStringLiteral(
"pg_table_is_visible(pg_class.oid) AND"));
1389 stmt = stmt.arg(QStringLiteral(
"pg_class.relnamespace = (SELECT oid FROM "
1390 "pg_namespace WHERE pg_namespace.nspname = '%1') AND").arg(schema));
1392 i.exec(stmt.arg(tbl));
1393 while (i.isActive() && i.next()) {
1394 QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt()), tablename);
1396 idx.setName(i.value(2).toString());
1401QSqlRecord QPSQLDriver::record(
const QString &tablename)
const
1407 QString tbl = tablename;
1409 qSplitTableName(tbl, schema);
1410 schema = stripDelimiters(schema, QSqlDriver::TableName);
1411 tbl = stripDelimiters(tbl, QSqlDriver::TableName);
1413 const QString adsrc = protocol() < Version8
1414 ? QStringLiteral(
"pg_attrdef.adsrc")
1415 : QStringLiteral(
"pg_get_expr(pg_attrdef.adbin, pg_attrdef.adrelid)");
1416 const QString nspname = schema.isEmpty()
1417 ? QStringLiteral(
"pg_table_is_visible(pg_class.oid)")
1418 : QStringLiteral(
"pg_class.relnamespace = (SELECT oid FROM "
1419 "pg_namespace WHERE pg_namespace.nspname = '%1')").arg(schema);
1420 const QString stmt =
1421 QStringLiteral(
"SELECT pg_attribute.attname, pg_attribute.atttypid::int, "
1422 "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
1424 "FROM pg_class, pg_attribute "
1425 "LEFT JOIN pg_attrdef ON (pg_attrdef.adrelid = "
1426 "pg_attribute.attrelid AND pg_attrdef.adnum = pg_attribute.attnum) "
1428 "AND pg_class.relname = '%3' "
1429 "AND pg_attribute.attnum > 0 "
1430 "AND pg_attribute.attrelid = pg_class.oid "
1431 "AND pg_attribute.attisdropped = false "
1432 "ORDER BY pg_attribute.attnum").arg(adsrc, nspname, tbl);
1434 QSqlQuery query(createResult());
1436 while (query.next()) {
1437 int len = query.value(3).toInt();
1438 int precision = query.value(4).toInt();
1440 if (len == -1 && precision > -1) {
1441 len = precision - 4;
1444 QString defVal = query.value(5).toString();
1445 if (!defVal.isEmpty() && defVal.at(0) == u'\'') {
1446 const qsizetype end = defVal.lastIndexOf(u'\'');
1448 defVal = defVal.mid(1, end - 1);
1450 QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()), tablename);
1451 f.setRequired(query.value(2).toBool());
1453 f.setPrecision(precision);
1454 f.setDefaultValue(defVal);
1462template <
bool forPreparedStatement>
1465 if constexpr (forPreparedStatement)
1466 return str.toString();
1468 return u'\'' + str.toString() + u'\'';
1471template <
bool forPreparedStatement,
class FloatType>
1475 *target = autoQuoteResult<forPreparedStatement>(u"NaN");
1476 else if (qIsInf(val))
1477 *target = autoQuoteResult<forPreparedStatement>((val < 0) ? u"-Infinity" : u"Infinity");
1480QString QPSQLDriver::formatValue(
const QSqlField &field,
bool trimStrings)
const
1482 return formatValue<
false>(field, trimStrings);
1485template<
bool forPreparedStatement>
1486QString QPSQLDriver::formatValue(
const QSqlField &field,
bool trimStrings)
const
1488 Q_D(
const QPSQLDriver);
1489 const auto nullStr = [](){
return forPreparedStatement ?
1490 QString{} : QStringLiteral(
"NULL"); };
1493 if (field.isNull()) {
1496 switch (field.metaType().id()) {
1497 case QMetaType::QDateTime: {
1498 const auto dt = field.value().toDateTime();
1503 r = autoQuoteResult<forPreparedStatement>(dt.toUTC().toString(Qt::ISODateWithMs));
1509 case QMetaType::QTime: {
1510 const auto t = field.value().toTime();
1512 r = autoQuoteResult<forPreparedStatement>(t.toString(Qt::ISODateWithMs));
1517 case QMetaType::QString:
1518 if constexpr (forPreparedStatement) {
1519 r = field.value().toString();
1521 r = QSqlDriver::formatValue(field, trimStrings);
1522 if (d->hasBackslashEscape)
1523 r.replace(u'\\',
"\\\\"_L1);
1526 case QMetaType::Bool:
1527 if (field.value().toBool())
1528 r = QStringLiteral(
"TRUE");
1530 r = QStringLiteral(
"FALSE");
1532 case QMetaType::QByteArray: {
1533 QByteArray ba(field.value().toByteArray());
1535#if defined PG_VERSION_NUM && PG_VERSION_NUM-0
>= 80200
1536 unsigned char *data = PQescapeByteaConn(d->connection, (
const unsigned char*)ba.constData(), ba.size(), &len);
1538 unsigned char *data = PQescapeBytea((
const unsigned char*)ba.constData(), ba.size(), &len);
1540 r = autoQuoteResult<forPreparedStatement>(QLatin1StringView((
const char*)data));
1544 case QMetaType::Float:
1545 assignSpecialPsqlFloatValue<forPreparedStatement>(field.value().toFloat(), &r);
1547 r = QSqlDriver::formatValue(field, trimStrings);
1549 case QMetaType::Double:
1550 assignSpecialPsqlFloatValue<forPreparedStatement>(field.value().toDouble(), &r);
1552 r = QSqlDriver::formatValue(field, trimStrings);
1554 case QMetaType::QUuid:
1555 r = autoQuoteResult<forPreparedStatement>(field.value().toString());
1558 r = QSqlDriver::formatValue(field, trimStrings);
1565QString QPSQLDriver::escapeIdentifier(
const QString &identifier, IdentifierType)
const
1567 QString res = identifier;
1568 if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) {
1569 res.replace(u'"',
"\"\""_L1);
1570 res.replace(u'.',
"\".\""_L1);
1571 res = u'"' + res + u'"';
1576bool QPSQLDriver::isOpen()
const
1578 Q_D(
const QPSQLDriver);
1579 return PQstatus(d->connection) == CONNECTION_OK;
1582QPSQLDriver::Protocol QPSQLDriver::protocol()
const
1584 Q_D(
const QPSQLDriver);
1588bool QPSQLDriver::subscribeToNotification(
const QString &name)
1592 qCWarning(lcPsql,
"QPSQLDriver::subscribeToNotification: Database not open.");
1596 const bool alreadyContained = d->seid.contains(name);
1597 int socket = PQsocket(d->connection);
1603 if (!alreadyContained)
1605 QString query = QStringLiteral(
"LISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
1606 PGresult *result = d->exec(query);
1607 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
1608 if (!alreadyContained)
1609 d->seid.removeLast();
1610 setLastError(qMakeError(tr(
"Unable to subscribe"), QSqlError::StatementError, d, result));
1617 d->sn =
new QSocketNotifier(socket, QSocketNotifier::Read,
this);
1618 connect(d->sn, &QSocketNotifier::activated,
this, &QPSQLDriver::_q_handleNotification);
1621 qCWarning(lcPsql,
"QPSQLDriver::subscribeToNotificationImplementation: "
1622 "PQsocket didn't return a valid socket to listen on.");
1629bool QPSQLDriver::unsubscribeFromNotification(
const QString &name)
1633 qCWarning(lcPsql,
"QPSQLDriver::unsubscribeFromNotification: Database not open.");
1637 if (!d->seid.contains(name)) {
1638 qCWarning(lcPsql,
"QPSQLDriver::unsubscribeFromNotification: not subscribed to '%ls'.",
1639 qUtf16Printable(name));
1643 QString query = QStringLiteral(
"UNLISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
1644 PGresult *result = d->exec(query);
1645 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
1646 setLastError(qMakeError(tr(
"Unable to unsubscribe"), QSqlError::StatementError, d, result));
1652 d->seid.removeAll(name);
1654 if (d->seid.isEmpty()) {
1655 disconnect(d->sn, &QSocketNotifier::activated,
this, &QPSQLDriver::_q_handleNotification);
1663QStringList QPSQLDriver::subscribedToNotifications()
const
1665 Q_D(
const QPSQLDriver);
1669void QPSQLDriver::_q_handleNotification()
1672 d->pendingNotifyCheck =
false;
1673 PQconsumeInput(d->connection);
1675 PGnotify *notify =
nullptr;
1676 while ((notify = PQnotifies(d->connection)) !=
nullptr) {
1677 QString name(QLatin1StringView(notify->relname));
1678 if (d->seid.contains(name)) {
1680#if defined PG_VERSION_NUM && PG_VERSION_NUM-0
>= 70400
1682 payload = QString::fromUtf8(notify->extra);
1684 QSqlDriver::NotificationSource source = (notify->be_pid == PQbackendPID(d->connection)) ? QSqlDriver::SelfSource : QSqlDriver::OtherSource;
1685 emit notification(name, source, payload);
1688 qCWarning(lcPsql,
"QPSQLDriver: received notification for '%ls' which isn't subscribed to.",
1689 qUtf16Printable(name));
1697#include "moc_qsql_psql_p.cpp"
void appendTables(QStringList &tl, QSqlQuery &t, QChar type)
void discardResults() const
PGresult * getResult(StatementId stmtId) const
void detectBackslashEscape()
PGresult * exec(const char *stmt)
void finishQuery(StatementId stmtId)
PGresult * exec(const QString &stmt)
QHash< int, QString > oidToTable
StatementId generateStatementId()
StatementId sendQuery(const QString &stmt)
bool setSingleRowMode() const
void checkPendingNotifications() const
StatementId currentStmtId
QString fieldSerial(qsizetype i) const override
QByteArray preparedStmtId
void deallocatePreparedStmt()
bool preparedQueriesEnabled
std::queue< PGresult * > nextResultSets
bool reset(const QString &query) override
Sets the result to use the SQL statement query for subsequent data retrieval.
int size() override
Returns the size of the SELECT result, or -1 if it cannot be determined or if the query is not a SELE...
QVariant lastInsertId() const override
Returns the object ID of the most recent inserted row if the database supports it.
bool nextResult() override
bool isNull(int field) override
Returns true if the field at position index in the current row is null; otherwise returns false.
QPSQLResult(const QPSQLDriver *db)
bool fetch(int i) override
Positions the result to an arbitrary (zero-based) row index.
bool fetchLast() override
Positions the result to the last record (last row) in the result.
QSqlRecord record() const override
Returns the current record if the query is active; otherwise returns an empty QSqlRecord.
bool fetchNext() override
Positions the result to the next available record (row) in the result.
QVariant data(int i) override
Returns the data for field index in the current row as a QVariant.
bool prepare(const QString &query) override
Prepares the given query for execution; the query will normally use placeholders so that it can be ex...
QVariant handle() const override
Returns the low-level database handle for this result set wrapped in a QVariant or an invalid QVarian...
int numRowsAffected() override
Returns the number of rows affected by the last query executed, or -1 if it cannot be determined or i...
bool fetchFirst() override
Positions the result to the first record (row 0) in the result.
void virtual_hook(int id, void *data) override
bool exec() override
Executes the query, returning true if successful; otherwise returns false.
The QSqlField class manipulates the fields in SQL database tables and views.
The QSqlQuery class provides a means of executing and manipulating SQL statements.
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
static auto qCreateParam(QSqlField &f, const QVariant &boundValue, const QPSQLDriver *driver)
void assignSpecialPsqlFloatValue(FloatType val, QString *target)
void PQfreemem(T *t, int=0)
static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type, const QPSQLDriverPrivate *p, PGresult *result=nullptr)
static constexpr int PGRES_SINGLE_TUPLE
static QPSQLDriver::Protocol qFindPSQLVersion(const QString &versionString)
QString autoQuoteResult(QAnyStringView str)
static QMetaType qDecodePSQLType(int t)
void qPQfreemem(void *buffer)
static QPSQLDriver::Protocol qMakePSQLVersion(int vMaj, int vMin)
static QByteArray qMakePreparedStmtId()
static void qSplitTableName(QString &tablename, QString &schema)
static QString qQuote(QString s)
static constexpr StatementId InvalidStatementId
struct pg_result PGresult