diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:18:55 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:18:55 (GMT) |
commit | e5fcad302d86d316390c6b0f62759a067313e8a9 (patch) | |
tree | c2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/sql/drivers/psql | |
download | Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2 |
Long live Qt 4.5!
Diffstat (limited to 'src/sql/drivers/psql')
-rw-r--r-- | src/sql/drivers/psql/qsql_psql.cpp | 1250 | ||||
-rw-r--r-- | src/sql/drivers/psql/qsql_psql.h | 155 |
2 files changed, 1405 insertions, 0 deletions
diff --git a/src/sql/drivers/psql/qsql_psql.cpp b/src/sql/drivers/psql/qsql_psql.cpp new file mode 100644 index 0000000..e33dd95 --- /dev/null +++ b/src/sql/drivers/psql/qsql_psql.cpp @@ -0,0 +1,1250 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_psql.h" + +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qregexp.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlrecord.h> +#include <qsqlquery.h> +#include <qsocketnotifier.h> +#include <qstringlist.h> +#include <qmutex.h> + +#include <libpq-fe.h> +#include <pg_config.h> + +#include <stdlib.h> +#include <math.h> + +// workaround for postgres defining their OIDs in a private header file +#define QBOOLOID 16 +#define QINT8OID 20 +#define QINT2OID 21 +#define QINT4OID 23 +#define QNUMERICOID 1700 +#define QFLOAT4OID 700 +#define QFLOAT8OID 701 +#define QABSTIMEOID 702 +#define QRELTIMEOID 703 +#define QDATEOID 1082 +#define QTIMEOID 1083 +#define QTIMETZOID 1266 +#define QTIMESTAMPOID 1114 +#define QTIMESTAMPTZOID 1184 +#define QOIDOID 2278 +#define QBYTEAOID 17 +#define QREGPROCOID 24 +#define QXIDOID 28 +#define QCIDOID 29 + +/* This is a compile time switch - if PQfreemem is declared, the compiler will use that one, + otherwise it'll run in this template */ +template <typename T> +inline void PQfreemem(T *t, int = 0) { free(t); } + +Q_DECLARE_METATYPE(PGconn*) +Q_DECLARE_METATYPE(PGresult*) + +QT_BEGIN_NAMESPACE + +inline void qPQfreemem(void *buffer) +{ + PQfreemem(buffer); +} + +class QPSQLDriverPrivate +{ +public: + QPSQLDriverPrivate() : connection(0), isUtf8(false), pro(QPSQLDriver::Version6), sn(0) {} + PGconn *connection; + bool isUtf8; + QPSQLDriver::Protocol pro; + QSocketNotifier *sn; + QStringList seid; + + void appendTables(QStringList &tl, QSqlQuery &t, QChar type); +}; + +void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type) +{ + QString query; + if (pro >= QPSQLDriver::Version73) { + query = QString::fromLatin1("select pg_class.relname, pg_namespace.nspname from pg_class " + "left join pg_namespace on (pg_class.relnamespace = pg_namespace.oid) " + "where (pg_class.relkind = '%1') and (pg_class.relname !~ '^Inv') " + "and (pg_class.relname !~ '^pg_') " + "and (pg_namespace.nspname != 'information_schema') ").arg(type); + } else { + query = QString::fromLatin1("select relname, null from pg_class where (relkind = '%1') " + "and (relname !~ '^Inv') " + "and (relname !~ '^pg_') ").arg(type); + } + t.exec(query); + while (t.next()) { + QString schema = t.value(1).toString(); + if (schema.isEmpty() || schema == QLatin1String("public")) + tl.append(t.value(0).toString()); + else + tl.append(t.value(0).toString().prepend(QLatin1Char('.')).prepend(schema)); + } +} + +class QPSQLResultPrivate +{ +public: + QPSQLResultPrivate(QPSQLResult *qq): q(qq), driver(0), result(0), currentSize(-1), precisionPolicy(QSql::HighPrecision) {} + + QPSQLResult *q; + const QPSQLDriverPrivate *driver; + PGresult *result; + int currentSize; + QSql::NumericalPrecisionPolicy precisionPolicy; + bool preparedQueriesEnabled; + QString preparedStmtId; + + bool processResults(); +}; + +static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, + const QPSQLDriverPrivate *p) +{ + const char *s = PQerrorMessage(p->connection); + QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s); + return QSqlError(QLatin1String("QPSQL: ") + err, msg, type); +} + +bool QPSQLResultPrivate::processResults() +{ + if (!result) + return false; + + int status = PQresultStatus(result); + if (status == PGRES_TUPLES_OK) { + q->setSelect(true); + q->setActive(true); + currentSize = PQntuples(result); + return true; + } else if (status == PGRES_COMMAND_OK) { + q->setSelect(false); + q->setActive(true); + currentSize = -1; + return true; + } + q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult", + "Unable to create query"), QSqlError::StatementError, driver)); + return false; +} + +static QVariant::Type qDecodePSQLType(int t) +{ + QVariant::Type type = QVariant::Invalid; + switch (t) { + case QBOOLOID: + type = QVariant::Bool; + break; + case QINT8OID: + type = QVariant::LongLong; + break; + case QINT2OID: + case QINT4OID: + case QOIDOID: + case QREGPROCOID: + case QXIDOID: + case QCIDOID: + type = QVariant::Int; + break; + case QNUMERICOID: + case QFLOAT4OID: + case QFLOAT8OID: + type = QVariant::Double; + break; + case QABSTIMEOID: + case QRELTIMEOID: + case QDATEOID: + type = QVariant::Date; + break; + case QTIMEOID: + case QTIMETZOID: + type = QVariant::Time; + break; + case QTIMESTAMPOID: + case QTIMESTAMPTZOID: + type = QVariant::DateTime; + break; + case QBYTEAOID: + type = QVariant::ByteArray; + break; + default: + type = QVariant::String; + break; + } + return type; +} + +static void qDeallocatePreparedStmt(QPSQLResultPrivate *d) +{ + const QString stmt = QLatin1String("DEALLOCATE ") + d->preparedStmtId; + PGresult *result = PQexec(d->driver->connection, + d->driver->isUtf8 ? stmt.toUtf8().constData() + : stmt.toLocal8Bit().constData()); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) + qWarning("Unable to free statement: %s", PQerrorMessage(d->driver->connection)); + PQclear(result); + d->preparedStmtId.clear(); +} + +QPSQLResult::QPSQLResult(const QPSQLDriver* db, const QPSQLDriverPrivate* p) + : QSqlResult(db) +{ + d = new QPSQLResultPrivate(this); + d->driver = p; + d->preparedQueriesEnabled = db->hasFeature(QSqlDriver::PreparedQueries); +} + +QPSQLResult::~QPSQLResult() +{ + cleanup(); + + if (d->preparedQueriesEnabled && !d->preparedStmtId.isNull()) + qDeallocatePreparedStmt(d); + + delete d; +} + +QVariant QPSQLResult::handle() const +{ + return qVariantFromValue(d->result); +} + +void QPSQLResult::cleanup() +{ + if (d->result) + PQclear(d->result); + d->result = 0; + setAt(QSql::BeforeFirstRow); + d->currentSize = -1; + setActive(false); +} + +bool QPSQLResult::fetch(int i) +{ + if (!isActive()) + return false; + if (i < 0) + return false; + if (i >= d->currentSize) + return false; + if (at() == i) + return true; + setAt(i); + return true; +} + +bool QPSQLResult::fetchFirst() +{ + return fetch(0); +} + +bool QPSQLResult::fetchLast() +{ + return fetch(PQntuples(d->result) - 1); +} + +QVariant QPSQLResult::data(int i) +{ + if (i >= PQnfields(d->result)) { + qWarning("QPSQLResult::data: column %d out of range", i); + return QVariant(); + } + int ptype = PQftype(d->result, i); + QVariant::Type type = qDecodePSQLType(ptype); + const char *val = PQgetvalue(d->result, at(), i); + if (PQgetisnull(d->result, at(), i)) + return QVariant(type); + switch (type) { + case QVariant::Bool: + return QVariant((bool)(val[0] == 't')); + case QVariant::String: + return d->driver->isUtf8 ? QString::fromUtf8(val) : QString::fromAscii(val); + case QVariant::LongLong: + if (val[0] == '-') + return QString::fromLatin1(val).toLongLong(); + else + return QString::fromLatin1(val).toULongLong(); + case QVariant::Int: + return atoi(val); + case QVariant::Double: + if (ptype == QNUMERICOID) { + if (d->precisionPolicy != QSql::HighPrecision) { + QVariant retval; + bool convert; + if (d->precisionPolicy == QSql::LowPrecisionInt64) + retval = QString::fromAscii(val).toLongLong(&convert); + else if (d->precisionPolicy == QSql::LowPrecisionInt32) + retval = QString::fromAscii(val).toInt(&convert); + else if (d->precisionPolicy == QSql::LowPrecisionDouble) + retval = QString::fromAscii(val).toDouble(&convert); + if (!convert) + return QVariant(); + return retval; + } + return QString::fromAscii(val); + } + return strtod(val, 0); + case QVariant::Date: + if (val[0] == '\0') { + return QVariant(QDate()); + } else { +#ifndef QT_NO_DATESTRING + return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate)); +#else + return QVariant(QString::fromLatin1(val)); +#endif + } + case QVariant::Time: { + const QString str = QString::fromLatin1(val); +#ifndef QT_NO_DATESTRING + if (str.isEmpty()) + return QVariant(QTime()); + if (str.at(str.length() - 3) == QLatin1Char('+')) + // strip the timezone + return QVariant(QTime::fromString(str.left(str.length() - 3), Qt::ISODate)); + return QVariant(QTime::fromString(str, Qt::ISODate)); +#else + return QVariant(str); +#endif + } + case QVariant::DateTime: { + QString dtval = QString::fromLatin1(val); +#ifndef QT_NO_DATESTRING + if (dtval.length() < 10) + return QVariant(QDateTime()); + // remove the timezone + if (dtval.at(dtval.length() - 3) == QLatin1Char('+')) + dtval.chop(3); + // milliseconds are sometimes returned with 2 digits only + if (dtval.at(dtval.length() - 3).isPunct()) + dtval += QLatin1Char('0'); + if (dtval.isEmpty()) + return QVariant(QDateTime()); + else + return QVariant(QDateTime::fromString(dtval, Qt::ISODate)); +#else + return QVariant(dtval); +#endif + } + case QVariant::ByteArray: { + size_t len; + unsigned char *data = PQunescapeBytea((unsigned char*)val, &len); + QByteArray ba((const char*)data, len); + qPQfreemem(data); + return QVariant(ba); + } + default: + case QVariant::Invalid: + qWarning("QPSQLResult::data: unknown data type"); + } + return QVariant(); +} + +bool QPSQLResult::isNull(int field) +{ + PQgetvalue(d->result, at(), field); + return PQgetisnull(d->result, at(), field); +} + +bool QPSQLResult::reset (const QString& query) +{ + cleanup(); + if (!driver()) + return false; + if (!driver()->isOpen() || driver()->isOpenError()) + return false; + d->result = PQexec(d->driver->connection, + d->driver->isUtf8 ? query.toUtf8().constData() + : query.toLocal8Bit().constData()); + return d->processResults(); +} + +int QPSQLResult::size() +{ + return d->currentSize; +} + +int QPSQLResult::numRowsAffected() +{ + return QString::fromLatin1(PQcmdTuples(d->result)).toInt(); +} + +QVariant QPSQLResult::lastInsertId() const +{ + if (isActive()) { + Oid id = PQoidValue(d->result); + if (id != InvalidOid) + return QVariant(id); + } + return QVariant(); +} + +QSqlRecord QPSQLResult::record() const +{ + QSqlRecord info; + if (!isActive() || !isSelect()) + return info; + + int count = PQnfields(d->result); + for (int i = 0; i < count; ++i) { + QSqlField f; + if (d->driver->isUtf8) + f.setName(QString::fromUtf8(PQfname(d->result, i))); + else + f.setName(QString::fromLocal8Bit(PQfname(d->result, i))); + f.setType(qDecodePSQLType(PQftype(d->result, i))); + int len = PQfsize(d->result, i); + int precision = PQfmod(d->result, i); + // swap length and precision if length == -1 + if (len == -1 && precision > -1) { + len = precision - 4; + precision = -1; + } + f.setLength(len); + f.setPrecision(precision); + f.setSqlType(PQftype(d->result, i)); + info.append(f); + } + return info; +} + +void QPSQLResult::virtual_hook(int id, void *data) +{ + Q_ASSERT(data); + + switch (id) { + case QSqlResult::SetNumericalPrecision: + d->precisionPolicy = *reinterpret_cast<QSql::NumericalPrecisionPolicy *>(data); + break; + default: + QSqlResult::virtual_hook(id, data); + } +} + +static QString qReplacePlaceholderMarkers(const QString &query) +{ + const int originalLength = query.length(); + bool inQuote = false; + int markerIdx = 0; + QString result; + result.reserve(originalLength + 23); + for (int i = 0; i < originalLength; ++i) { + const QChar ch = query.at(i); + if (ch == QLatin1Char('?') && !inQuote) { + result += QLatin1Char('$'); + result += QString::number(++markerIdx); + } else { + if (ch == QLatin1Char('\'')) + inQuote = !inQuote; + result += ch; + } + } + + result.squeeze(); + return result; +} + +static QString qCreateParamString(const QVector<QVariant> boundValues, const QSqlDriver *driver) +{ + if (boundValues.isEmpty()) + return QString(); + + QString params; + QSqlField f; + for (int i = 0; i < boundValues.count(); ++i) { + const QVariant &val = boundValues.at(i); + + f.setType(val.type()); + if (val.isNull()) + f.clear(); + else + f.setValue(val); + if(!params.isNull()) + params.append(QLatin1String(", ")); + params.append(driver->formatValue(f)); + } + return params; +} + +Q_GLOBAL_STATIC(QMutex, qMutex) +QString qMakePreparedStmtId() +{ + qMutex()->lock(); + static unsigned int qPreparedStmtCount = 0; + QString id = QLatin1String("qpsqlpstmt_") + QString::number(++qPreparedStmtCount, 16); + qMutex()->unlock(); + return id; +} + +bool QPSQLResult::prepare(const QString &query) +{ + if (!d->preparedQueriesEnabled) + return QSqlResult::prepare(query); + + cleanup(); + + if (!d->preparedStmtId.isEmpty()) + qDeallocatePreparedStmt(d); + + const QString stmtId = qMakePreparedStmtId(); + const QString stmt = QString(QLatin1String("PREPARE %1 AS ")).arg(stmtId).append(qReplacePlaceholderMarkers(query)); + + PGresult *result = PQexec(d->driver->connection, + d->driver->isUtf8 ? stmt.toUtf8().constData() + : stmt.toLocal8Bit().constData()); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) { + setLastError(qMakeError(QCoreApplication::translate("QPSQLResult", + "Unable to prepare statement"), QSqlError::StatementError, d->driver)); + PQclear(result); + d->preparedStmtId.clear(); + return false; + } + + PQclear(result); + d->preparedStmtId = stmtId; + return true; +} + +bool QPSQLResult::exec() +{ + if (!d->preparedQueriesEnabled) + return QSqlResult::exec(); + + cleanup(); + + QString stmt; + const QString params = qCreateParamString(boundValues(), d->q->driver()); + if (params.isEmpty()) + stmt = QString(QLatin1String("EXECUTE %1")).arg(d->preparedStmtId); + else + stmt = QString(QLatin1String("EXECUTE %1 (%2)")).arg(d->preparedStmtId).arg(params); + + d->result = PQexec(d->driver->connection, + d->driver->isUtf8 ? stmt.toUtf8().constData() + : stmt.toLocal8Bit().constData()); + + return d->processResults(); +} + +/////////////////////////////////////////////////////////////////// + +static bool setEncodingUtf8(PGconn* connection) +{ + PGresult* result = PQexec(connection, "SET CLIENT_ENCODING TO 'UNICODE'"); + int status = PQresultStatus(result); + PQclear(result); + return status == PGRES_COMMAND_OK; +} + +static void setDatestyle(PGconn* connection) +{ + PGresult* result = PQexec(connection, "SET DATESTYLE TO 'ISO'"); + int status = PQresultStatus(result); + if (status != PGRES_COMMAND_OK) + qWarning("%s", PQerrorMessage(connection)); + PQclear(result); +} + +static QPSQLDriver::Protocol getPSQLVersion(PGconn* connection) +{ + QPSQLDriver::Protocol serverVersion = QPSQLDriver::Version6; + PGresult* result = PQexec(connection, "select version()"); + int status = PQresultStatus(result); + if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) { + QString val = QString::fromAscii(PQgetvalue(result, 0, 0)); + PQclear(result); + QRegExp rx(QLatin1String("(\\d+)\\.(\\d+)")); + rx.setMinimal(true); // enforce non-greedy RegExp + if (rx.indexIn(val) != -1) { + int vMaj = rx.cap(1).toInt(); + int vMin = rx.cap(2).toInt(); + + switch (vMaj) { + case 7: + switch (vMin) { + case 0: + serverVersion = QPSQLDriver::Version7; + break; + case 1: + case 2: + serverVersion = QPSQLDriver::Version71; + break; + default: + serverVersion = QPSQLDriver::Version73; + break; + } + break; + case 8: + switch (vMin) { + case 0: + serverVersion = QPSQLDriver::Version8; + break; + case 1: + serverVersion = QPSQLDriver::Version81; + break; + case 2: + default: + serverVersion = QPSQLDriver::Version82; + break; + } + break; + default: + break; + } + } + } + + if (serverVersion < QPSQLDriver::Version71) + qWarning("This version of PostgreSQL is not supported and may not work."); + + return serverVersion; +} + +QPSQLDriver::QPSQLDriver(QObject *parent) + : QSqlDriver(parent) +{ + init(); +} + +QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent) + : QSqlDriver(parent) +{ + init(); + d->connection = conn; + if (conn) { + d->pro = getPSQLVersion(d->connection); + setOpen(true); + setOpenError(false); + } +} + +void QPSQLDriver::init() +{ + d = new QPSQLDriverPrivate(); +} + +QPSQLDriver::~QPSQLDriver() +{ + if (d->connection) + PQfinish(d->connection); + delete d; +} + +QVariant QPSQLDriver::handle() const +{ + return qVariantFromValue(d->connection); +} + +bool QPSQLDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case Transactions: + case QuerySize: + case LastInsertId: + case LowPrecisionNumbers: + case EventNotifications: + return true; + case PreparedQueries: + case PositionalPlaceholders: + return d->pro >= QPSQLDriver::Version82; + case BatchOperations: + case NamedPlaceholders: + case SimpleLocking: + case FinishQuery: + case MultipleResultSets: + return false; + case BLOB: + return d->pro >= QPSQLDriver::Version71; + case Unicode: + return d->isUtf8; + } + return false; +} + +/* + Quote a string for inclusion into the connection string + \ -> \\ + ' -> \' + surround string by single quotes + */ +static QString qQuote(QString s) +{ + s.replace(QLatin1Char('\\'), QLatin1String("\\\\")); + s.replace(QLatin1Char('\''), QLatin1String("\\'")); + s.append(QLatin1Char('\'')).prepend(QLatin1Char('\'')); + return s; +} + +bool QPSQLDriver::open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts) +{ + if (isOpen()) + close(); + QString connectString; + if (!host.isEmpty()) + connectString.append(QLatin1String("host=")).append(qQuote(host)); + if (!db.isEmpty()) + connectString.append(QLatin1String(" dbname=")).append(qQuote(db)); + if (!user.isEmpty()) + connectString.append(QLatin1String(" user=")).append(qQuote(user)); + if (!password.isEmpty()) + connectString.append(QLatin1String(" password=")).append(qQuote(password)); + if (port != -1) + connectString.append(QLatin1String(" port=")).append(qQuote(QString::number(port))); + + // add any connect options - the server will handle error detection + if (!connOpts.isEmpty()) { + QString opt = connOpts; + opt.replace(QLatin1Char(';'), QLatin1Char(' '), Qt::CaseInsensitive); + connectString.append(QLatin1Char(' ')).append(opt); + } + + d->connection = PQconnectdb(connectString.toLocal8Bit().constData()); + if (PQstatus(d->connection) == CONNECTION_BAD) { + setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d)); + setOpenError(true); + PQfinish(d->connection); + d->connection = 0; + return false; + } + + d->pro = getPSQLVersion(d->connection); + d->isUtf8 = setEncodingUtf8(d->connection); + setDatestyle(d->connection); + + setOpen(true); + setOpenError(false); + return true; +} + +void QPSQLDriver::close() +{ + if (isOpen()) { + + d->seid.clear(); + if (d->sn) { + disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int))); + delete d->sn; + d->sn = 0; + } + + if (d->connection) + PQfinish(d->connection); + d->connection = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QPSQLDriver::createResult() const +{ + return new QPSQLResult(this, d); +} + +bool QPSQLDriver::beginTransaction() +{ + if (!isOpen()) { + qWarning("QPSQLDriver::beginTransaction: Database not open"); + return false; + } + PGresult* res = PQexec(d->connection, "BEGIN"); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + PQclear(res); + setLastError(qMakeError(tr("Could not begin transaction"), + QSqlError::TransactionError, d)); + return false; + } + PQclear(res); + return true; +} + +bool QPSQLDriver::commitTransaction() +{ + if (!isOpen()) { + qWarning("QPSQLDriver::commitTransaction: Database not open"); + return false; + } + PGresult* res = PQexec(d->connection, "COMMIT"); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + PQclear(res); + setLastError(qMakeError(tr("Could not commit transaction"), + QSqlError::TransactionError, d)); + return false; + } + PQclear(res); + return true; +} + +bool QPSQLDriver::rollbackTransaction() +{ + if (!isOpen()) { + qWarning("QPSQLDriver::rollbackTransaction: Database not open"); + return false; + } + PGresult* res = PQexec(d->connection, "ROLLBACK"); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + setLastError(qMakeError(tr("Could not rollback transaction"), + QSqlError::TransactionError, d)); + PQclear(res); + return false; + } + PQclear(res); + return true; +} + +QStringList QPSQLDriver::tables(QSql::TableType type) const +{ + QStringList tl; + if (!isOpen()) + return tl; + QSqlQuery t(createResult()); + t.setForwardOnly(true); + + if (type & QSql::Tables) + d->appendTables(tl, t, QLatin1Char('r')); + if (type & QSql::Views) + d->appendTables(tl, t, QLatin1Char('v')); + if (type & QSql::SystemTables) { + t.exec(QLatin1String("select relname from pg_class where (relkind = 'r') " + "and (relname like 'pg_%') ")); + while (t.next()) + tl.append(t.value(0).toString()); + } + + return tl; +} + +static void qSplitTableName(QString &tablename, QString &schema) +{ + int dot = tablename.indexOf(QLatin1Char('.')); + if (dot == -1) + return; + schema = tablename.left(dot); + tablename = tablename.mid(dot + 1); +} + +QSqlIndex QPSQLDriver::primaryIndex(const QString& tablename) const +{ + QSqlIndex idx(tablename); + if (!isOpen()) + return idx; + QSqlQuery i(createResult()); + QString stmt; + + QString tbl = tablename; + QString schema; + qSplitTableName(tbl, schema); + + switch(d->pro) { + case QPSQLDriver::Version6: + stmt = QLatin1String("select pg_att1.attname, int(pg_att1.atttypid), pg_cl.relname " + "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind " + "where lower(pg_cl.relname) = '%1_pkey' " + "and pg_cl.oid = pg_ind.indexrelid " + "and pg_att2.attrelid = pg_ind.indexrelid " + "and pg_att1.attrelid = pg_ind.indrelid " + "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] " + "order by pg_att2.attnum"); + break; + case QPSQLDriver::Version7: + case QPSQLDriver::Version71: + stmt = QLatin1String("select pg_att1.attname, pg_att1.atttypid::int, pg_cl.relname " + "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind " + "where lower(pg_cl.relname) = '%1_pkey' " + "and pg_cl.oid = pg_ind.indexrelid " + "and pg_att2.attrelid = pg_ind.indexrelid " + "and pg_att1.attrelid = pg_ind.indrelid " + "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] " + "order by pg_att2.attnum"); + break; + case QPSQLDriver::Version73: + case QPSQLDriver::Version74: + case QPSQLDriver::Version8: + case QPSQLDriver::Version81: + case QPSQLDriver::Version82: + stmt = QLatin1String("SELECT pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_class.relname " + "FROM pg_attribute, pg_class " + "WHERE %1 pg_class.oid IN " + "(SELECT indexrelid FROM pg_index WHERE indisprimary = true AND indrelid IN " + " (SELECT oid FROM pg_class WHERE lower(relname) = '%2')) " + "AND pg_attribute.attrelid = pg_class.oid " + "AND pg_attribute.attisdropped = false " + "ORDER BY pg_attribute.attnum"); + if (schema.isEmpty()) + stmt = stmt.arg(QLatin1String("pg_table_is_visible(pg_class.oid) AND")); + else + stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from " + "pg_namespace where pg_namespace.nspname = '%1') AND ").arg(schema.toLower())); + break; + } + + i.exec(stmt.arg(tbl.toLower())); + while (i.isActive() && i.next()) { + QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt())); + idx.append(f); + idx.setName(i.value(2).toString()); + } + return idx; +} + +QSqlRecord QPSQLDriver::record(const QString& tablename) const +{ + QSqlRecord info; + if (!isOpen()) + return info; + + QString tbl = tablename; + QString schema; + qSplitTableName(tbl, schema); + + QString stmt; + switch(d->pro) { + case QPSQLDriver::Version6: + stmt = QLatin1String("select pg_attribute.attname, int(pg_attribute.atttypid), " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "int(pg_attribute.attrelid), pg_attribute.attnum " + "from pg_class, pg_attribute " + "where lower(pg_class.relname) = '%1' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid "); + break; + case QPSQLDriver::Version7: + stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "pg_attribute.attrelid::int, pg_attribute.attnum " + "from pg_class, pg_attribute " + "where lower(pg_class.relname) = '%1' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid "); + break; + case QPSQLDriver::Version71: + stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "pg_attrdef.adsrc " + "from pg_class, pg_attribute " + "left join pg_attrdef on (pg_attrdef.adrelid = " + "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) " + "where lower(pg_class.relname) = '%1' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid " + "order by pg_attribute.attnum "); + break; + case QPSQLDriver::Version73: + case QPSQLDriver::Version74: + case QPSQLDriver::Version8: + case QPSQLDriver::Version81: + case QPSQLDriver::Version82: + stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "pg_attrdef.adsrc " + "from pg_class, pg_attribute " + "left join pg_attrdef on (pg_attrdef.adrelid = " + "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) " + "where %1 " + "and lower(pg_class.relname) = '%2' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid " + "and pg_attribute.attisdropped = false " + "order by pg_attribute.attnum "); + if (schema.isEmpty()) + stmt = stmt.arg(QLatin1String("pg_table_is_visible(pg_class.oid)")); + else + stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from " + "pg_namespace where pg_namespace.nspname = '%1')").arg(schema.toLower())); + break; + } + + QSqlQuery query(createResult()); + query.exec(stmt.arg(tbl.toLower())); + if (d->pro >= QPSQLDriver::Version71) { + while (query.next()) { + int len = query.value(3).toInt(); + int precision = query.value(4).toInt(); + // swap length and precision if length == -1 + if (len == -1 && precision > -1) { + len = precision - 4; + precision = -1; + } + QString defVal = query.value(5).toString(); + if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\'')) + defVal = defVal.mid(1, defVal.length() - 2); + QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt())); + f.setRequired(query.value(2).toBool()); + f.setLength(len); + f.setPrecision(precision); + f.setDefaultValue(defVal); + f.setSqlType(query.value(1).toInt()); + info.append(f); + } + } else { + // Postgres < 7.1 cannot handle outer joins + while (query.next()) { + QString defVal; + QString stmt2 = QLatin1String("select pg_attrdef.adsrc from pg_attrdef where " + "pg_attrdef.adrelid = %1 and pg_attrdef.adnum = %2 "); + QSqlQuery query2(createResult()); + query2.exec(stmt2.arg(query.value(5).toInt()).arg(query.value(6).toInt())); + if (query2.isActive() && query2.next()) + defVal = query2.value(0).toString(); + if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\'')) + defVal = defVal.mid(1, defVal.length() - 2); + int len = query.value(3).toInt(); + int precision = query.value(4).toInt(); + // swap length and precision if length == -1 + if (len == -1 && precision > -1) { + len = precision - 4; + precision = -1; + } + QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt())); + f.setRequired(query.value(2).toBool()); + f.setLength(len); + f.setPrecision(precision); + f.setDefaultValue(defVal); + f.setSqlType(query.value(1).toInt()); + info.append(f); + } + } + + return info; +} + +QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + QString r; + if (field.isNull()) { + r = QLatin1String("NULL"); + } else { + switch (field.type()) { + case QVariant::DateTime: +#ifndef QT_NO_DATESTRING + if (field.value().toDateTime().isValid()) { + QDate dt = field.value().toDateTime().date(); + QTime tm = field.value().toDateTime().time(); + // msecs need to be right aligned otherwise psql + // interpretes them wrong + r = QLatin1String("'") + QString::number(dt.year()) + QLatin1String("-") + + QString::number(dt.month()) + QLatin1String("-") + + QString::number(dt.day()) + QLatin1String(" ") + + tm.toString() + QLatin1String(".") + + QString::number(tm.msec()).rightJustified(3, QLatin1Char('0')) + + QLatin1String("'"); + } else { + r = QLatin1String("NULL"); + } +#else + r = QLatin1String("NULL"); +#endif // QT_NO_DATESTRING + break; + case QVariant::Time: +#ifndef QT_NO_DATESTRING + if (field.value().toTime().isValid()) { + r = field.value().toTime().toString(Qt::ISODate); + } else +#endif + { + r = QLatin1String("NULL"); + } + case QVariant::String: + { + // Escape '\' characters + r = QSqlDriver::formatValue(field, trimStrings); + r.replace(QLatin1String("\\"), QLatin1String("\\\\")); + break; + } + case QVariant::Bool: + if (field.value().toBool()) + r = QLatin1String("TRUE"); + else + r = QLatin1String("FALSE"); + break; + case QVariant::ByteArray: { + QByteArray ba(field.value().toByteArray()); + size_t len; +#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 80200 + unsigned char *data = PQescapeByteaConn(d->connection, (unsigned char*)ba.constData(), ba.size(), &len); +#else + unsigned char *data = PQescapeBytea((unsigned char*)ba.constData(), ba.size(), &len); +#endif + r += QLatin1Char('\''); + r += QLatin1String((const char*)data); + r += QLatin1Char('\''); + qPQfreemem(data); + break; + } + default: + r = QSqlDriver::formatValue(field, trimStrings); + break; + } + } + return r; +} + +QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + QString res = identifier; + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +bool QPSQLDriver::isOpen() const +{ + return PQstatus(d->connection) == CONNECTION_OK; +} + +QPSQLDriver::Protocol QPSQLDriver::protocol() const +{ + return d->pro; +} + +bool QPSQLDriver::subscribeToNotificationImplementation(const QString &name) +{ + if (!isOpen()) { + qWarning("QPSQLDriver::subscribeToNotificationImplementation: database not open."); + return false; + } + + if (d->seid.contains(name)) { + qWarning("QPSQLDriver::subscribeToNotificationImplementation: already subscribing to '%s'.", + qPrintable(name)); + return false; + } + + int socket = PQsocket(d->connection); + if (socket) { + QString query = QString(QLatin1String("LISTEN %1")).arg(escapeIdentifier(name, QSqlDriver::TableName)); + if (PQresultStatus(PQexec(d->connection, + d->isUtf8 ? query.toUtf8().constData() + : query.toLocal8Bit().constData()) + ) != PGRES_COMMAND_OK) { + setLastError(qMakeError(tr("Unable to subscribe"), QSqlError::StatementError, d)); + return false; + } + + if (!d->sn) { + d->sn = new QSocketNotifier(socket, QSocketNotifier::Read); + connect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int))); + } + } + + d->seid << name; + return true; +} + +bool QPSQLDriver::unsubscribeFromNotificationImplementation(const QString &name) +{ + if (!isOpen()) { + qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: database not open."); + return false; + } + + if (!d->seid.contains(name)) { + qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: not subscribed to '%s'.", + qPrintable(name)); + return false; + } + + QString query = QString(QLatin1String("UNLISTEN %1")).arg(escapeIdentifier(name, QSqlDriver::TableName)); + if (PQresultStatus(PQexec(d->connection, + d->isUtf8 ? query.toUtf8().constData() + : query.toLocal8Bit().constData()) + ) != PGRES_COMMAND_OK) { + setLastError(qMakeError(tr("Unable to unsubscribe"), QSqlError::StatementError, d)); + return false; + } + + d->seid.removeAll(name); + + if (d->seid.isEmpty()) { + disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int))); + delete d->sn; + d->sn = 0; + } + + return true; +} + +QStringList QPSQLDriver::subscribedToNotificationsImplementation() const +{ + return d->seid; +} + +void QPSQLDriver::_q_handleNotification(int) +{ + PQconsumeInput(d->connection); + PGnotify *notify = PQnotifies(d->connection); + if (notify) { + QString name(QLatin1String(notify->relname)); + + if (d->seid.contains(name)) + emit notification(name); + else + qWarning("QPSQLDriver: received notification for '%s' which isn't subscribed to.", + qPrintable(name)); + + qPQfreemem(notify); + } +} + +QT_END_NAMESPACE diff --git a/src/sql/drivers/psql/qsql_psql.h b/src/sql/drivers/psql/qsql_psql.h new file mode 100644 index 0000000..ca4dedf --- /dev/null +++ b/src/sql/drivers/psql/qsql_psql.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_PSQL_H +#define QSQL_PSQL_H + +#include <QtSql/qsqlresult.h> +#include <QtSql/qsqldriver.h> + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_PSQL +#else +#define Q_EXPORT_SQLDRIVER_PSQL Q_SQL_EXPORT +#endif + +QT_BEGIN_HEADER + +typedef struct pg_conn PGconn; +typedef struct pg_result PGresult; + +QT_BEGIN_NAMESPACE + +class QPSQLResultPrivate; +class QPSQLDriverPrivate; +class QPSQLDriver; +class QSqlRecordInfo; + +class QPSQLResult : public QSqlResult +{ + friend class QPSQLResultPrivate; +public: + QPSQLResult(const QPSQLDriver* db, const QPSQLDriverPrivate* p); + ~QPSQLResult(); + + QVariant handle() const; + void virtual_hook(int id, void *data); + +protected: + void cleanup(); + bool fetch(int i); + bool fetchFirst(); + bool fetchLast(); + QVariant data(int i); + bool isNull(int field); + bool reset (const QString& query); + int size(); + int numRowsAffected(); + QSqlRecord record() const; + QVariant lastInsertId() const; + bool prepare(const QString& query); + bool exec(); + +private: + QPSQLResultPrivate *d; +}; + +class Q_EXPORT_SQLDRIVER_PSQL QPSQLDriver : public QSqlDriver +{ + Q_OBJECT +public: + enum Protocol { + Version6 = 6, + Version7 = 7, + Version71 = 8, + Version73 = 9, + Version74 = 10, + Version8 = 11, + Version81 = 12, + Version82 = 13 + }; + + explicit QPSQLDriver(QObject *parent=0); + explicit QPSQLDriver(PGconn *conn, QObject *parent=0); + ~QPSQLDriver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts); + bool isOpen() const; + void close(); + QSqlResult *createResult() const; + QStringList tables(QSql::TableType) const; + QSqlIndex primaryIndex(const QString& tablename) const; + QSqlRecord record(const QString& tablename) const; + + Protocol protocol() const; + QVariant handle() const; + + QString escapeIdentifier(const QString &identifier, IdentifierType type) const; + QString formatValue(const QSqlField &field, bool trimStrings) const; + +protected: + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); + +protected Q_SLOTS: + bool subscribeToNotificationImplementation(const QString &name); + bool unsubscribeFromNotificationImplementation(const QString &name); + QStringList subscribedToNotificationsImplementation() const; + +private Q_SLOTS: + void _q_handleNotification(int); + +private: + void init(); + QPSQLDriverPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_PSQL_H |