diff --git a/src/plugins/sqldrivers/psql/qsql_psql.cpp b/src/plugins/sqldrivers/psql/qsql_psql.cpp index f0a730e614e..3d6027048e7 100644 --- a/src/plugins/sqldrivers/psql/qsql_psql.cpp +++ b/src/plugins/sqldrivers/psql/qsql_psql.cpp @@ -4,6 +4,7 @@ #include "qsql_psql_p.h" #include +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -386,8 +388,12 @@ static QMetaType qDecodePSQLType(int t) void QPSQLResultPrivate::deallocatePreparedStmt() { if (drv_d_func()) { +#if defined(LIBPQ_HAS_CLOSE_PREPARED) + PGresult *result = PQclosePrepared(drv_d_func()->connection, preparedStmtId.toUtf8()); +#else const QString stmt = QStringLiteral("DEALLOCATE ") + preparedStmtId; PGresult *result = drv_d_func()->exec(stmt); +#endif if (PQresultStatus(result) != PGRES_COMMAND_OK) { const QString msg = QString::fromUtf8(PQerrorMessage(drv_d_func()->connection)); @@ -820,22 +826,23 @@ void QPSQLResult::virtual_hook(int id, void *data) QSqlResult::virtual_hook(id, data); } -static QString qCreateParamString(const QList &boundValues, const QSqlDriver *driver) +static QList qCreateParamArray(const QList &boundValues, const QPSQLDriver *driver) { if (boundValues.isEmpty()) - return QString(); + return {}; - QString params; + QList params; + params.reserve(boundValues.size()); QSqlField f; for (const QVariant &val : boundValues) { - f.setMetaType(val.metaType()); - if (QSqlResultPrivate::isVariantNull(val)) - f.clear(); - else + QByteArray bval; + if (!QSqlResultPrivate::isVariantNull(val)) { + f.setMetaType(val.metaType()); f.setValue(val); - if (!params.isNull()) - params.append(", "_L1); - params.append(driver->formatValue(f)); + if (QString strval = driver->formatValue(f); !strval.isNull()) + bval = strval.toUtf8(); + } + params.append(bval); } return params; } @@ -859,9 +866,8 @@ bool QPSQLResult::prepare(const QString &query) d->deallocatePreparedStmt(); const QString stmtId = qMakePreparedStmtId(); - const QString stmt = QStringLiteral("PREPARE %1 AS ").arg(stmtId).append(d->positionalToNamedBinding(query)); - - PGresult *result = d->drv_d_func()->exec(stmt); + PGresult *result = PQprepare(d->drv_d_func()->connection, stmtId.toUtf8(), + d->positionalToNamedBinding(query).toUtf8(), 0, nullptr); if (PQresultStatus(result) != PGRES_COMMAND_OK) { setLastError(qMakeError(QCoreApplication::translate("QPSQLResult", @@ -884,24 +890,27 @@ bool QPSQLResult::exec() cleanup(); - QString stmt; - const QString params = qCreateParamString(boundValues(), driver()); - if (params.isEmpty()) - stmt = QStringLiteral("EXECUTE %1").arg(d->preparedStmtId); - else - stmt = QStringLiteral("EXECUTE %1 (%2)").arg(d->preparedStmtId, params); + const QList params = qCreateParamArray(boundValues(), static_cast(driver())); + QVarLengthArray pgParams; + pgParams.reserve(params.size()); + for (const QByteArray ¶m : params) + pgParams.emplace_back(param.constBegin()); - d->stmtId = d->drv_d_func()->sendQuery(stmt); - if (d->stmtId == InvalidStatementId) { + d->result = PQexecPrepared(d->drv_d_func()->connection, d->preparedStmtId.toUtf8(), pgParams.size(), + pgParams.data(), nullptr, nullptr, 0); + + const auto status = PQresultStatus(d->result); + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + d->stmtId = InvalidStatementId; setLastError(qMakeError(QCoreApplication::translate("QPSQLResult", - "Unable to send query"), QSqlError::StatementError, d->drv_d_func())); + "Unable to send query"), QSqlError::StatementError, d->drv_d_func(), d->result)); return false; } + d->stmtId = d->drv_d_func()->currentStmtId = d->drv_d_func()->generateStatementId(); if (isForwardOnly()) setForwardOnly(d->drv_d_func()->setSingleRowMode()); - d->result = d->drv_d_func()->getResult(d->stmtId); if (!isForwardOnly()) { // Fetch all result sets right away while (PGresult *nextResultSet = d->drv_d_func()->getResult(d->stmtId)) @@ -1440,19 +1449,34 @@ QSqlRecord QPSQLDriver::record(const QString &tablename) const return info; } -template + +template +inline QString autoQuoteResult(QAnyStringView str) +{ + return forPreparedStatement ? str.toString() : (u'\'' + str.toString() + u'\''); +} + +template inline void assignSpecialPsqlFloatValue(FloatType val, QString *target) { if (qIsNaN(val)) - *target = QStringLiteral("'NaN'"); + *target = autoQuoteResult(u"NaN"); else if (qIsInf(val)) - *target = (val < 0) ? QStringLiteral("'-Infinity'") : QStringLiteral("'Infinity'"); + *target = autoQuoteResult((val < 0) ? u"-Infinity" : u"Infinity"); } +QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + return formatValue(field, trimStrings); +} + +template QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const { Q_D(const QPSQLDriver); - const auto nullStr = [](){ return QStringLiteral("NULL"); }; + const auto nullStr = [](){ return forPreparedStatement ? + QString{} : QStringLiteral("NULL"); }; + QString r; if (field.isNull()) { r = nullStr(); @@ -1461,11 +1485,10 @@ QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const case QMetaType::QDateTime: { const auto dt = field.value().toDateTime(); if (dt.isValid()) { - // even though the documentation (https://www.postgresql.org/docs/current/datatype-datetime.html) - // states that any time zone indication for 'timestamp without tz' columns will be ignored, - // it is stored as the correct utc timestamp - so we can pass the utc offset here - r = QStringLiteral("TIMESTAMP WITH TIME ZONE ") + u'\'' + - dt.toOffsetFromUtc(dt.offsetFromUtc()).toString(Qt::ISODateWithMs) + u'\''; + // the datetime needs to be in UTC format + // Anyway the DB stores it that way for timestamptz + // for timestamp (without tz), we store as UTC too and the server will ignore the tz info + r = autoQuoteResult(dt.toUTC().toString(Qt::ISODateWithMs)); } else { r = nullStr(); } @@ -1474,15 +1497,19 @@ QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const case QMetaType::QTime: { const auto t = field.value().toTime(); if (t.isValid()) - r = u'\'' + QLocale::c().toString(t, u"hh:mm:ss.zzz") + u'\''; + r = autoQuoteResult(t.toString(Qt::ISODateWithMs)); else r = nullStr(); break; } case QMetaType::QString: - r = QSqlDriver::formatValue(field, trimStrings); - if (d->hasBackslashEscape) - r.replace(u'\\', "\\\\"_L1); + if (forPreparedStatement) { + r = field.value().toString(); + } else { + r = QSqlDriver::formatValue(field, trimStrings); + if (d->hasBackslashEscape) + r.replace(u'\\', "\\\\"_L1); + } break; case QMetaType::Bool: if (field.value().toBool()) @@ -1498,24 +1525,22 @@ QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const #else unsigned char *data = PQescapeBytea((const unsigned char*)ba.constData(), ba.size(), &len); #endif - r += u'\''; - r += QLatin1StringView((const char*)data); - r += u'\''; + r = autoQuoteResult(QLatin1StringView((const char*)data)); qPQfreemem(data); break; } case QMetaType::Float: - assignSpecialPsqlFloatValue(field.value().toFloat(), &r); + assignSpecialPsqlFloatValue(field.value().toFloat(), &r); if (r.isEmpty()) r = QSqlDriver::formatValue(field, trimStrings); break; case QMetaType::Double: - assignSpecialPsqlFloatValue(field.value().toDouble(), &r); + assignSpecialPsqlFloatValue(field.value().toDouble(), &r); if (r.isEmpty()) r = QSqlDriver::formatValue(field, trimStrings); break; case QMetaType::QUuid: - r = u'\'' + field.value().toString() + u'\''; + r = autoQuoteResult(field.value().toString()); break; default: r = QSqlDriver::formatValue(field, trimStrings); diff --git a/src/plugins/sqldrivers/psql/qsql_psql_p.h b/src/plugins/sqldrivers/psql/qsql_psql_p.h index 4ff83bee21c..0439c26ccce 100644 --- a/src/plugins/sqldrivers/psql/qsql_psql_p.h +++ b/src/plugins/sqldrivers/psql/qsql_psql_p.h @@ -84,6 +84,9 @@ public: QString escapeIdentifier(const QString &identifier, IdentifierType type) const override; QString formatValue(const QSqlField &field, bool trimStrings) const override; + template + QString formatValue(const QSqlField &field, bool trimStrings = false) const; + bool subscribeToNotification(const QString &name) override; bool unsubscribeFromNotification(const QString &name) override; QStringList subscribedToNotifications() const override;