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
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);
261 Q_DECLARE_PUBLIC(QPSQLResult)
284static QSqlError
qMakeError(
const QString &err, QSqlError::ErrorType type,
288 QString msg = QString::fromUtf8(s);
291 errorCode = QString::fromLatin1(PQresultErrorField(result, PG_DIAG_SQLSTATE));
292 msg += QString::fromLatin1(
"(%1)").arg(errorCode);
294 return QSqlError(
"QPSQL: "_L1 + err, msg, type, errorCode);
305 if (
stmtId != drv_d_func()->currentStmtId) {
306 q->setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
307 "Query results lost - probably discarded on executing "
308 "another SQL query."), QSqlError::StatementError, drv_d_func(), result));
312 int status = PQresultStatus(
result);
314 case PGRES_TUPLES_OK:
317 currentSize = q->isForwardOnly() ? -1 : PQntuples(result);
320 case PGRES_SINGLE_TUPLE:
326 case PGRES_COMMAND_OK:
339 q->setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
340 "Unable to create query"), QSqlError::StatementError, drv_d_func(), result));
346 int type = QMetaType::UnknownType;
349 type = QMetaType::Bool;
352 type = QMetaType::LongLong;
360 type = QMetaType::Int;
365 type = QMetaType::Double;
370 type = QMetaType::QDate;
374 type = QMetaType::QTime;
378 type = QMetaType::QDateTime;
381 type = QMetaType::QByteArray;
384 type = QMetaType::QUuid;
387 type = QMetaType::QString;
390 return QMetaType(type);
395 if (drv_d_func() && !preparedStmtId.isEmpty()) {
396#if defined(LIBPQ_HAS_CLOSE_PREPARED)
397 PGresult *result = PQclosePrepared(drv_d_func()->connection, preparedStmtId.constData());
399 const QByteArray stmt = QByteArrayView(
"DEALLOCATE ") + preparedStmtId;
400 PGresult *result = drv_d_func()->exec(stmt.constData());
403 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
404 const QString msg = QString::fromUtf8(PQerrorMessage(drv_d_func()->connection));
405 qCWarning(lcPsql,
"Unable to free statement: %ls.", qUtf16Printable(msg));
409 preparedStmtId.clear();
413 : QSqlResult(*
new QPSQLResultPrivate(
this, db))
416 d->preparedQueriesEnabled = db->hasFeature(QSqlDriver::PreparedQueries);
424 d->deallocatePreparedStmt();
429 Q_D(
const QPSQLResult);
430 return QVariant::fromValue(d->result);
439 while (!d->nextResultSets.empty()) {
440 PQclear(d->nextResultSets.front());
441 d->nextResultSets.pop();
443 if (d->stmtId != InvalidStatementId) {
445 d->drv_d_func()->finishQuery(d->stmtId);
447 d->stmtId = InvalidStatementId;
448 setAt(QSql::BeforeFirstRow);
450 d->canFetchMoreRows =
false;
456 Q_D(
const QPSQLResult);
464 if (isForwardOnly()) {
468 while (ok && i > at())
473 if (i >= d->currentSize)
481 Q_D(
const QPSQLResult);
487 if (isForwardOnly()) {
488 if (at() == QSql::BeforeFirstRow) {
491 if (d->result && PQntuples(d->result) > 0) {
504 Q_D(
const QPSQLResult);
508 if (isForwardOnly()) {
511 if (i == QSql::AfterLastRow)
513 if (i == QSql::BeforeFirstRow)
521 return fetch(d->currentSize - 1);
530 const int currentRow = at();
531 if (currentRow == QSql::BeforeFirstRow)
533 if (currentRow == QSql::AfterLastRow)
536 if (isForwardOnly()) {
537 if (!d->canFetchMoreRows)
540 d->result = d->drv_d_func()->getResult(d->stmtId);
542 setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
543 "Unable to get result"), QSqlError::StatementError, d->drv_d_func(), d->result));
544 d->canFetchMoreRows =
false;
547 int status = PQresultStatus(d->result);
551 Q_ASSERT(PQntuples(d->result) == 1);
552 Q_ASSERT(d->canFetchMoreRows);
553 setAt(currentRow + 1);
555 case PGRES_TUPLES_OK:
557 Q_ASSERT(PQntuples(d->result) == 0);
558 d->canFetchMoreRows =
false;
561 setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
562 "Unable to get result"), QSqlError::StatementError, d->drv_d_func(), d->result));
563 d->canFetchMoreRows =
false;
568 if (currentRow + 1 >= d->currentSize)
570 setAt(currentRow + 1);
580 setAt(QSql::BeforeFirstRow);
582 if (isForwardOnly()) {
583 if (d->canFetchMoreRows) {
585 while (d->result && PQresultStatus(d->result) == PGRES_SINGLE_TUPLE) {
587 d->result = d->drv_d_func()->getResult(d->stmtId);
589 d->canFetchMoreRows =
false;
591 if (d->result && PQresultStatus(d->result) == PGRES_FATAL_ERROR)
592 return d->processResults();
597 d->result = d->drv_d_func()->getResult(d->stmtId);
598 return d->processResults();
604 if (!d->nextResultSets.empty()) {
605 d->result = d->nextResultSets.front();
606 d->nextResultSets.pop();
608 return d->processResults();
613 Q_D(
const QPSQLResult);
614 if (i >= PQnfields(d->result)) {
615 qCWarning(lcPsql,
"QPSQLResult::data: column %d out of range.", i);
618 const int currentRow = isForwardOnly() ? 0 : at();
619 int ptype = PQftype(d->result, i);
620 QMetaType type = qDecodePSQLType(ptype);
621 if (PQgetisnull(d->result, currentRow, i))
622 return QVariant(type,
nullptr);
623 const char *val = PQgetvalue(d->result, currentRow, i);
625 case QMetaType::Bool:
626 return QVariant((
bool)(val[0] ==
't'));
627 case QMetaType::QString:
628 return QString::fromUtf8(val);
629 case QMetaType::LongLong:
631 return QByteArray::fromRawData(val, qstrlen(val)).toLongLong();
633 return QByteArray::fromRawData(val, qstrlen(val)).toULongLong();
636 case QMetaType::Double: {
638 if (numericalPrecisionPolicy() == QSql::HighPrecision)
639 return QString::fromLatin1(val);
642 double dbl = qstrtod(val,
nullptr, &ok);
644 if (qstricmp(val,
"NaN") == 0)
646 else if (qstricmp(val,
"Infinity") == 0)
648 else if (qstricmp(val,
"-Infinity") == 0)
654 if (numericalPrecisionPolicy() == QSql::LowPrecisionInt64)
655 return QVariant((qlonglong)dbl);
656 else if (numericalPrecisionPolicy() == QSql::LowPrecisionInt32)
657 return QVariant((
int)dbl);
658 else if (numericalPrecisionPolicy() == QSql::LowPrecisionDouble)
659 return QVariant(dbl);
663#if QT_CONFIG(datestring)
664 case QMetaType::QDate:
665 return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate));
666 case QMetaType::QTime:
667 return QVariant(QTime::fromString(QString::fromLatin1(val), Qt::ISODate));
668 case QMetaType::QDateTime: {
669 const QLatin1StringView tzString(val);
670 const auto timeString(tzString.sliced(11));
671 if (timeString.contains(u'-') || timeString.contains(u'+') || timeString.endsWith(u'Z'))
672 return QDateTime::fromString(tzString, Qt::ISODate);
673 const QString utc = tzString.toString() + u'Z';
674 return QVariant(QDateTime::fromString(utc, Qt::ISODate));
677 case QMetaType::QDate:
678 case QMetaType::QTime:
679 case QMetaType::QDateTime:
680 return QVariant(QString::fromLatin1(val));
682 case QMetaType::QByteArray: {
684 unsigned char *data = PQunescapeBytea(
reinterpret_cast<
const unsigned char *>(val), &len);
685 QByteArray ba(
reinterpret_cast<
const char *>(data), len);
689 case QMetaType::QUuid:
690 return QUuid::fromString(val);
692 qCWarning(lcPsql,
"QPSQLResult::data: unhandled data type %d.", type.id());
699 Q_D(
const QPSQLResult);
700 const int currentRow = isForwardOnly() ? 0 : at();
701 return PQgetisnull(d->result, currentRow, field);
710 if (!driver()->isOpen() || driver()->isOpenError())
713 d->stmtId = d->drv_d_func()->sendQuery(query);
714 if (d->stmtId == InvalidStatementId) {
715 setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
716 "Unable to send query"), QSqlError::StatementError, d->drv_d_func()));
721 setForwardOnly(d->drv_d_func()->setSingleRowMode());
723 d->result = d->drv_d_func()->getResult(d->stmtId);
724 if (!isForwardOnly()) {
726 while (PGresult *nextResultSet = d->drv_d_func()->getResult(d->stmtId))
727 d->nextResultSets.push(nextResultSet);
729 return d->processResults();
734 Q_D(
const QPSQLResult);
735 return d->currentSize;
740 Q_D(
const QPSQLResult);
741 const char *tuples = PQcmdTuples(d->result);
742 return QByteArray::fromRawData(tuples, qstrlen(tuples)).toInt();
747 Q_D(
const QPSQLResult);
748 if (d->drv_d_func()->pro >= QPSQLDriver::Version8_1) {
751 if (qry.exec(QStringLiteral(
"SELECT lastval();")) && qry.next())
753 }
else if (isActive()) {
754 Oid id = PQoidValue(d->result);
755 if (id != InvalidOid)
763 Q_D(
const QPSQLResult);
765 if (!isActive() || !isSelect())
768 int count = PQnfields(d->result);
770 for (
int i = 0; i < count; ++i) {
771 f.setName(QString::fromUtf8(PQfname(d->result, i)));
772 const int tableOid = PQftable(d->result, i);
777 if (tableOid != InvalidOid && !isForwardOnly()) {
778 auto &tableName = d->drv_d_func()->oidToTable[tableOid];
779 if (tableName.isEmpty()) {
781 if (qry.exec(QStringLiteral(
"SELECT relname FROM pg_class WHERE pg_class.oid = %1")
782 .arg(tableOid)) && qry.next()) {
783 tableName = qry.value(0).toString();
786 f.setTableName(tableName);
788 f.setTableName(QString());
790 int ptype = PQftype(d->result, i);
791 f.setMetaType(qDecodePSQLType(ptype));
792 f.setValue(QVariant(f.metaType()));
793 int len = PQfsize(d->result, i);
794 int precision = PQfmod(d->result, i);
803 if (precision != -1) {
804 len = (precision >> 16);
805 precision = ((precision -
VARHDRSZ) & 0xffff);
814 if (len == -1 && precision >=
VARHDRSZ) {
821 f.setPrecision(precision);
830 QSqlResult::virtual_hook(id, data);
833static auto qCreateParam(QSqlField &f,
const QVariant &boundValue,
const QPSQLDriver *driver)
835 std::pair<QByteArray,
bool > param;
836 if (!QSqlResultPrivate::isVariantNull(boundValue)) {
838 switch (boundValue.metaType().id()) {
839 case QMetaType::QByteArray:
840 param = {boundValue.toByteArray(),
true};
843 f.setMetaType(boundValue.metaType());
844 f.setValue(boundValue);
845 const QString strval = driver->formatValue<
true>(f);
846 param = {strval.isNull() ? QByteArray{} : strval.toUtf8(),
false};
856 Q_CONSTINIT
static QBasicAtomicInt qPreparedStmtCount = Q_BASIC_ATOMIC_INITIALIZER(0);
857 return QByteArrayView(
"qpsqlpstmt_") + QByteArray::number(qPreparedStmtCount.fetchAndAddRelaxed(1) + 1, 16);
863 if (!d->preparedQueriesEnabled)
864 return QSqlResult::prepare(query);
868 d->deallocatePreparedStmt();
870 const QByteArray stmtId = qMakePreparedStmtId();
871 PGresult *result = PQprepare(d->drv_d_func()->connection, stmtId.constData(),
872 d->positionalToNamedBinding(query).toUtf8(), 0,
nullptr);
874 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
875 setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
876 "Unable to prepare statement"), QSqlError::StatementError, d->drv_d_func(), result));
878 d->preparedStmtId.clear();
883 d->preparedStmtId = stmtId;
890 if (!d->preparedQueriesEnabled)
891 return QSqlResult::exec();
895 QVarLengthArray<
const char *> pgParams;
896 QVarLengthArray<
int> pgParamLengths;
897 QVarLengthArray<
int> pgParamFormats;
898 QVarLengthArray<QByteArray> _refsToKeep;
900 if (
const QVariantList values = boundValues(); !values.isEmpty()) {
902 for (
const QVariant &value : values) {
903 auto [param, binary] = qCreateParam(f, value,
static_cast<
const QPSQLDriver *>(driver()));
904 pgParams.emplace_back(param.constBegin());
905 pgParamLengths.emplace_back(param.size());
906 pgParamFormats.emplace_back(binary);
908 _refsToKeep.emplace_back(std::move(param));
912 d->result = PQexecPrepared(d->drv_d_func()->connection, d->preparedStmtId.constData(), pgParams.size(),
913 pgParams.data(), pgParamLengths.data(), pgParamFormats.data(), 0);
915 const auto status = PQresultStatus(d->result);
916 if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) {
917 d->stmtId = InvalidStatementId;
918 setLastError(qMakeError(QCoreApplication::translate(
"QPSQLResult",
919 "Unable to send query"), QSqlError::StatementError, d->drv_d_func(), d->result));
922 d->stmtId = d->drv_d_func()->currentStmtId = d->drv_d_func()->generateStatementId();
925 setForwardOnly(d->drv_d_func()->setSingleRowMode());
927 if (!isForwardOnly()) {
929 while (PGresult *nextResultSet = d->drv_d_func()->getResult(d->stmtId))
930 d->nextResultSets.push(nextResultSet);
932 return d->processResults();
940 int status = PQresultStatus(result);
942 return status == PGRES_COMMAND_OK;
948 int status = PQresultStatus(result);
949 if (status != PGRES_COMMAND_OK)
950 qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
956 if (pro >= QPSQLDriver::Version9) {
961 int status = PQresultStatus(result);
962 if (status != PGRES_COMMAND_OK)
963 qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
971 int status = PQresultStatus(result);
972 if (status != PGRES_COMMAND_OK)
973 qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
981 if (pro < QPSQLDriver::Version8_2) {
986 int status = PQresultStatus(result);
987 if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK)
988 if (QString::fromLatin1(PQgetvalue(result, 0, 0)) ==
"\\"_L1)
998 return QPSQLDriver::Version6;
1003 return QPSQLDriver::Version7_1;
1005 return QPSQLDriver::Version7_3;
1007 return QPSQLDriver::Version7_4;
1009 return QPSQLDriver::Version7;
1017 return QPSQLDriver::Version8_1;
1019 return QPSQLDriver::Version8_2;
1021 return QPSQLDriver::Version8_3;
1023 return QPSQLDriver::Version8_4;
1025 return QPSQLDriver::Version8;
1033 return QPSQLDriver::Version9_1;
1035 return QPSQLDriver::Version9_2;
1037 return QPSQLDriver::Version9_3;
1039 return QPSQLDriver::Version9_4;
1041 return QPSQLDriver::Version9_5;
1043 return QPSQLDriver::Version9_6;
1045 return QPSQLDriver::Version9;
1050 return QPSQLDriver::Version10;
1052 return QPSQLDriver::Version11;
1054 return QPSQLDriver::Version12;
1057 return QPSQLDriver::UnknownLaterVersion;
1060 return QPSQLDriver::VersionUnknown;
1065 const QRegularExpression rx(QStringLiteral(
"(\\d+)(?:\\.(\\d+))?"));
1066 const QRegularExpressionMatch match = rx.match(versionString);
1067 if (match.hasMatch()) {
1072 int vMaj = match.capturedView(1).toInt();
1077 if (match.capturedView(2).isEmpty())
1078 return QPSQLDriver::VersionUnknown;
1079 vMin = match.capturedView(2).toInt();
1081 return qMakePSQLVersion(vMaj, vMin);
1084 return QPSQLDriver::VersionUnknown;
1089 QPSQLDriver::Protocol serverVersion = QPSQLDriver::Version6;
1091 int status = PQresultStatus(result);
1092 if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) {
1093 serverVersion = qFindPSQLVersion(
1094 QString::fromLatin1(PQgetvalue(result, 0, 0)));
1098 QPSQLDriver::Protocol clientVersion =
1099#if defined(PG_MAJORVERSION)
1100 qFindPSQLVersion(PG_MAJORVERSION
""_L1);
1101#elif defined(PG_VERSION)
1102 qFindPSQLVersion(PG_VERSION
""_L1);
1104 QPSQLDriver::VersionUnknown;
1107 if (serverVersion == QPSQLDriver::VersionUnknown) {
1108 serverVersion = clientVersion;
1109 if (serverVersion != QPSQLDriver::VersionUnknown)
1110 qCWarning(lcPsql,
"The server version of this PostgreSQL is unknown, "
1111 "falling back to the client version.");
1115 if (serverVersion == QPSQLDriver::VersionUnknown)
1116 serverVersion = QPSQLDriver::Version6;
1118 if (serverVersion < QPSQLDriver::Version7_3)
1119 qCWarning(lcPsql,
"This version of PostgreSQL is not supported and may not work.");
1121 return serverVersion;
1124QPSQLDriver::QPSQLDriver(QObject *parent)
1125 : QSqlDriver(*
new QPSQLDriverPrivate, parent)
1129QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent)
1130 : QSqlDriver(*
new QPSQLDriverPrivate, parent)
1133 d->connection = conn;
1135 d->pro = d->getPSQLVersion();
1136 d->detectBackslashEscape();
1138 setOpenError(
false);
1142QPSQLDriver::~QPSQLDriver()
1145 PQfinish(d->connection);
1148QVariant QPSQLDriver::handle()
const
1150 Q_D(
const QPSQLDriver);
1151 return QVariant::fromValue(d->connection);
1154bool QPSQLDriver::hasFeature(DriverFeature f)
const
1156 Q_D(
const QPSQLDriver);
1161 case LowPrecisionNumbers:
1162 case EventNotifications:
1163 case MultipleResultSets:
1167 case PreparedQueries:
1168 case PositionalPlaceholders:
1169 return d->pro >= QPSQLDriver::Version8_2;
1170 case BatchOperations:
1171 case NamedPlaceholders:
1181
1182
1183
1184
1185
1188 s.replace(u'\\',
"\\\\"_L1);
1189 s.replace(u'\'',
"\\'"_L1);
1190 s.append(u'\'').prepend(u'\'');
1194bool QPSQLDriver::open(
const QString &db,
1195 const QString &user,
1196 const QString &password,
1197 const QString &host,
1199 const QString &connOpts)
1203 QString connectString;
1204 if (!host.isEmpty())
1205 connectString.append(
"host="_L1).append(qQuote(host));
1207 connectString.append(
" dbname="_L1).append(qQuote(db));
1208 if (!user.isEmpty())
1209 connectString.append(
" user="_L1).append(qQuote(user));
1210 if (!password.isEmpty())
1211 connectString.append(
" password="_L1).append(qQuote(password));
1213 connectString.append(
" port="_L1).append(qQuote(QString::number(port)));
1216 if (!connOpts.isEmpty()) {
1217 QString opt = connOpts;
1218 opt.replace(
';'_L1,
' '_L1, Qt::CaseInsensitive);
1219 connectString.append(u' ').append(opt);
1222 d->connection = PQconnectdb(std::move(connectString).toLocal8Bit().constData());
1223 if (PQstatus(d->connection) == CONNECTION_BAD) {
1224 setLastError(qMakeError(tr(
"Unable to connect"), QSqlError::ConnectionError, d));
1226 PQfinish(d->connection);
1227 d->connection =
nullptr;
1231 d->pro = d->getPSQLVersion();
1232 d->detectBackslashEscape();
1233 if (!d->setEncodingUtf8()) {
1234 setLastError(qMakeError(tr(
"Unable to set client encoding to 'UNICODE'"), QSqlError::ConnectionError, d));
1236 PQfinish(d->connection);
1237 d->connection =
nullptr;
1241 d->setByteaOutput();
1242 d->setUtcTimeZone();
1245 setOpenError(
false);
1249void QPSQLDriver::close()
1255 disconnect(d->sn, &QSocketNotifier::activated,
this, &QPSQLDriver::_q_handleNotification);
1260 PQfinish(d->connection);
1261 d->connection =
nullptr;
1263 setOpenError(
false);
1266QSqlResult *QPSQLDriver::createResult()
const
1268 return new QPSQLResult(
this);
1271bool QPSQLDriver::beginTransaction()
1275 qCWarning(lcPsql,
"QPSQLDriver::beginTransaction: Database not open.");
1278 PGresult *res = d->exec(
"BEGIN");
1279 if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
1280 setLastError(qMakeError(tr(
"Could not begin transaction"),
1281 QSqlError::TransactionError, d, res));
1289bool QPSQLDriver::commitTransaction()
1293 qCWarning(lcPsql,
"QPSQLDriver::commitTransaction: Database not open.");
1296 PGresult *res = d->exec(
"COMMIT");
1298 bool transaction_failed =
false;
1304 if (d->pro >= QPSQLDriver::Version8) {
1305 transaction_failed = qstrcmp(PQcmdStatus(res),
"ROLLBACK") == 0;
1308 if (!res || PQresultStatus(res) != PGRES_COMMAND_OK || transaction_failed) {
1309 setLastError(qMakeError(tr(
"Could not commit transaction"),
1310 QSqlError::TransactionError, d, res));
1318bool QPSQLDriver::rollbackTransaction()
1322 qCWarning(lcPsql,
"QPSQLDriver::rollbackTransaction: Database not open.");
1325 PGresult *res = d->exec(
"ROLLBACK");
1326 if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
1327 setLastError(qMakeError(tr(
"Could not rollback transaction"),
1328 QSqlError::TransactionError, d, res));
1336QStringList QPSQLDriver::tables(QSql::TableType type)
const
1338 Q_D(
const QPSQLDriver);
1342 QSqlQuery t(createResult());
1343 t.setForwardOnly(
true);
1345 if (type & QSql::Tables)
1346 const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, u'r');
1347 if (type & QSql::Views)
1348 const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, u'v');
1349 if (type & QSql::SystemTables) {
1350 t.exec(QStringLiteral(
"SELECT relname FROM pg_class WHERE (relkind = 'r') "
1351 "AND (relname LIKE 'pg_%') "));
1353 tl.append(t.value(0).toString());
1361 qsizetype dot = tablename.indexOf(u'.');
1364 schema = tablename.left(dot);
1365 tablename = tablename.mid(dot + 1);
1368QSqlIndex QPSQLDriver::primaryIndex(
const QString &tablename)
const
1370 QSqlIndex idx(tablename);
1373 QSqlQuery i(createResult());
1375 QString tbl = tablename;
1377 qSplitTableName(tbl, schema);
1378 schema = stripDelimiters(schema, QSqlDriver::TableName);
1379 tbl = stripDelimiters(tbl, QSqlDriver::TableName);
1381 QString stmt = QStringLiteral(
"SELECT pg_attribute.attname, pg_attribute.atttypid::int, "
1383 "FROM pg_attribute, pg_class "
1384 "WHERE %1 pg_class.oid IN "
1385 "(SELECT indexrelid FROM pg_index WHERE indisprimary = true AND indrelid IN "
1386 "(SELECT oid FROM pg_class WHERE relname = '%2')) "
1387 "AND pg_attribute.attrelid = pg_class.oid "
1388 "AND pg_attribute.attisdropped = false "
1389 "ORDER BY pg_attribute.attnum");
1390 if (schema.isEmpty())
1391 stmt = stmt.arg(QStringLiteral(
"pg_table_is_visible(pg_class.oid) AND"));
1393 stmt = stmt.arg(QStringLiteral(
"pg_class.relnamespace = (SELECT oid FROM "
1394 "pg_namespace WHERE pg_namespace.nspname = '%1') AND").arg(schema));
1396 i.exec(stmt.arg(tbl));
1397 while (i.isActive() && i.next()) {
1398 QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt()), tablename);
1400 idx.setName(i.value(2).toString());
1405QSqlRecord QPSQLDriver::record(
const QString &tablename)
const
1411 QString tbl = tablename;
1413 qSplitTableName(tbl, schema);
1414 schema = stripDelimiters(schema, QSqlDriver::TableName);
1415 tbl = stripDelimiters(tbl, QSqlDriver::TableName);
1417 const QString adsrc = protocol() < Version8
1418 ? QStringLiteral(
"pg_attrdef.adsrc")
1419 : QStringLiteral(
"pg_get_expr(pg_attrdef.adbin, pg_attrdef.adrelid)");
1420 const QString nspname = schema.isEmpty()
1421 ? QStringLiteral(
"pg_table_is_visible(pg_class.oid)")
1422 : QStringLiteral(
"pg_class.relnamespace = (SELECT oid FROM "
1423 "pg_namespace WHERE pg_namespace.nspname = '%1')").arg(schema);
1424 const QString stmt =
1425 QStringLiteral(
"SELECT pg_attribute.attname, pg_attribute.atttypid::int, "
1426 "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
1428 "FROM pg_class, pg_attribute "
1429 "LEFT JOIN pg_attrdef ON (pg_attrdef.adrelid = "
1430 "pg_attribute.attrelid AND pg_attrdef.adnum = pg_attribute.attnum) "
1432 "AND pg_class.relname = '%3' "
1433 "AND pg_attribute.attnum > 0 "
1434 "AND pg_attribute.attrelid = pg_class.oid "
1435 "AND pg_attribute.attisdropped = false "
1436 "ORDER BY pg_attribute.attnum").arg(adsrc, nspname, tbl);
1438 QSqlQuery query(createResult());
1440 while (query.next()) {
1441 int len = query.value(3).toInt();
1442 int precision = query.value(4).toInt();
1444 if (len == -1 && precision > -1) {
1445 len = precision - 4;
1448 QString defVal = query.value(5).toString();
1449 if (!defVal.isEmpty() && defVal.at(0) == u'\'') {
1450 const qsizetype end = defVal.lastIndexOf(u'\'');
1452 defVal = defVal.mid(1, end - 1);
1454 QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()), tablename);
1455 f.setRequired(query.value(2).toBool());
1457 f.setPrecision(precision);
1458 f.setDefaultValue(defVal);
1466template <
bool forPreparedStatement>
1469 if constexpr (forPreparedStatement)
1470 return str.toString();
1472 return u'\'' + str.toString() + u'\'';
1475template <
bool forPreparedStatement,
class FloatType>
1479 *target = autoQuoteResult<forPreparedStatement>(u"NaN");
1480 else if (qIsInf(val))
1481 *target = autoQuoteResult<forPreparedStatement>((val < 0) ? u"-Infinity" : u"Infinity");
1484QString QPSQLDriver::formatValue(
const QSqlField &field,
bool trimStrings)
const
1486 return formatValue<
false>(field, trimStrings);
1489template<
bool forPreparedStatement>
1490QString QPSQLDriver::formatValue(
const QSqlField &field,
bool trimStrings)
const
1492 Q_D(
const QPSQLDriver);
1493 const auto nullStr = [](){
return forPreparedStatement ?
1494 QString{} : QStringLiteral(
"NULL"); };
1497 if (field.isNull()) {
1500 switch (field.metaType().id()) {
1501 case QMetaType::QDateTime: {
1502 const auto dt = field.value().toDateTime();
1507 r = autoQuoteResult<forPreparedStatement>(dt.toUTC().toString(Qt::ISODateWithMs));
1513 case QMetaType::QTime: {
1514 const auto t = field.value().toTime();
1516 r = autoQuoteResult<forPreparedStatement>(t.toString(Qt::ISODateWithMs));
1521 case QMetaType::QString:
1522 if constexpr (forPreparedStatement) {
1523 r = field.value().toString();
1525 r = QSqlDriver::formatValue(field, trimStrings);
1526 if (d->hasBackslashEscape)
1527 r.replace(u'\\',
"\\\\"_L1);
1530 case QMetaType::Bool:
1531 if (field.value().toBool())
1532 r = QStringLiteral(
"TRUE");
1534 r = QStringLiteral(
"FALSE");
1536 case QMetaType::QByteArray: {
1537 QByteArray ba(field.value().toByteArray());
1539#if defined PG_VERSION_NUM && PG_VERSION_NUM-0
>= 80200
1540 unsigned char *data = PQescapeByteaConn(d->connection, (
const unsigned char*)ba.constData(), ba.size(), &len);
1542 unsigned char *data = PQescapeBytea((
const unsigned char*)ba.constData(), ba.size(), &len);
1544 r = autoQuoteResult<forPreparedStatement>(QLatin1StringView((
const char*)data));
1548 case QMetaType::Float:
1549 assignSpecialPsqlFloatValue<forPreparedStatement>(field.value().toFloat(), &r);
1551 r = QSqlDriver::formatValue(field, trimStrings);
1553 case QMetaType::Double:
1554 assignSpecialPsqlFloatValue<forPreparedStatement>(field.value().toDouble(), &r);
1556 r = QSqlDriver::formatValue(field, trimStrings);
1558 case QMetaType::QUuid:
1559 r = autoQuoteResult<forPreparedStatement>(field.value().toString());
1562 r = QSqlDriver::formatValue(field, trimStrings);
1569QString QPSQLDriver::escapeIdentifier(
const QString &identifier, IdentifierType)
const
1571 QString res = identifier;
1572 if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) {
1573 res.replace(u'"',
"\"\""_L1);
1574 res.replace(u'.',
"\".\""_L1);
1575 res = u'"' + res + u'"';
1580bool QPSQLDriver::isOpen()
const
1582 Q_D(
const QPSQLDriver);
1583 return PQstatus(d->connection) == CONNECTION_OK;
1586QPSQLDriver::Protocol QPSQLDriver::protocol()
const
1588 Q_D(
const QPSQLDriver);
1592bool QPSQLDriver::subscribeToNotification(
const QString &name)
1596 qCWarning(lcPsql,
"QPSQLDriver::subscribeToNotification: Database not open.");
1600 const bool alreadyContained = d->seid.contains(name);
1601 int socket = PQsocket(d->connection);
1607 if (!alreadyContained)
1609 QString query = QStringLiteral(
"LISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
1610 PGresult *result = d->exec(query);
1611 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
1612 if (!alreadyContained)
1613 d->seid.removeLast();
1614 setLastError(qMakeError(tr(
"Unable to subscribe"), QSqlError::StatementError, d, result));
1621 d->sn =
new QSocketNotifier(socket, QSocketNotifier::Read,
this);
1622 connect(d->sn, &QSocketNotifier::activated,
this, &QPSQLDriver::_q_handleNotification);
1625 qCWarning(lcPsql,
"QPSQLDriver::subscribeToNotificationImplementation: "
1626 "PQsocket didn't return a valid socket to listen on.");
1633bool QPSQLDriver::unsubscribeFromNotification(
const QString &name)
1637 qCWarning(lcPsql,
"QPSQLDriver::unsubscribeFromNotification: Database not open.");
1641 if (!d->seid.contains(name)) {
1642 qCWarning(lcPsql,
"QPSQLDriver::unsubscribeFromNotification: not subscribed to '%ls'.",
1643 qUtf16Printable(name));
1647 QString query = QStringLiteral(
"UNLISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
1648 PGresult *result = d->exec(query);
1649 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
1650 setLastError(qMakeError(tr(
"Unable to unsubscribe"), QSqlError::StatementError, d, result));
1656 d->seid.removeAll(name);
1658 if (d->seid.isEmpty()) {
1659 disconnect(d->sn, &QSocketNotifier::activated,
this, &QPSQLDriver::_q_handleNotification);
1667QStringList QPSQLDriver::subscribedToNotifications()
const
1669 Q_D(
const QPSQLDriver);
1673void QPSQLDriver::_q_handleNotification()
1676 d->pendingNotifyCheck =
false;
1677 PQconsumeInput(d->connection);
1679 PGnotify *notify =
nullptr;
1680 while ((notify = PQnotifies(d->connection)) !=
nullptr) {
1681 QString name(QLatin1StringView(notify->relname));
1682 if (d->seid.contains(name)) {
1684#if defined PG_VERSION_NUM && PG_VERSION_NUM-0
>= 70400
1686 payload = QString::fromUtf8(notify->extra);
1688 QSqlDriver::NotificationSource source = (notify->be_pid == PQbackendPID(d->connection)) ? QSqlDriver::SelfSource : QSqlDriver::OtherSource;
1689 emit notification(name, source, payload);
1692 qCWarning(lcPsql,
"QPSQLDriver: received notification for '%ls' which isn't subscribed to.",
1693 qUtf16Printable(name));
1701#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
QByteArray preparedStmtId
QPSQLResultPrivate(QSqlResult *q, const QSqlDriver *drv)
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.
\macro QT_RESTRICTED_CAST_FROM_ASCII
#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