File indexing completed on 2024-05-05 16:47:13

0001 /* This file is part of the KDE project
0002    Copyright (C) 2003 Adam Pigg <adam@piggz.co.uk>
0003    Copyright (C) 2010-2016 Jarosław Staniek <staniek@kde.org>
0004 
0005    This program is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This program is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this program; see the file COPYING.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "PostgresqlConnection.h"
0022 #include "PostgresqlConnection_p.h"
0023 #include "PostgresqlPreparedStatement.h"
0024 #include "PostgresqlCursor.h"
0025 #include "postgresql_debug.h"
0026 #include "KDbConnectionData.h"
0027 #include "KDbError.h"
0028 #include "KDbGlobal.h"
0029 #include "KDbVersionInfo.h"
0030 
0031 #include <QFileInfo>
0032 #include <QHostAddress>
0033 
0034 #define MIN_SERVER_VERSION_MAJOR 7
0035 #define MIN_SERVER_VERSION_MINOR 1
0036 
0037 inline static QByteArray parameter(PGconn *conn, const char *paramName)
0038 {
0039     return PQparameterStatus(conn, paramName);
0040 }
0041 
0042 PostgresqlTransactionData::PostgresqlTransactionData(KDbConnection *conn)
0043         : KDbTransactionData(conn)
0044 {
0045 }
0046 
0047 PostgresqlTransactionData::~PostgresqlTransactionData()
0048 {
0049 }
0050 
0051 //==================================================================================
0052 
0053 PostgresqlConnection::PostgresqlConnection(KDbDriver *driver, const KDbConnectionData& connData,
0054                                            const KDbConnectionOptions &options)
0055         : KDbConnection(driver, connData, options)
0056         , d(new PostgresqlConnectionInternal(this))
0057 {
0058 }
0059 
0060 PostgresqlConnection::~PostgresqlConnection()
0061 {
0062     //delete m_trans;
0063     destroy();
0064     delete d;
0065 }
0066 
0067 KDbCursor* PostgresqlConnection::prepareQuery(const KDbEscapedString& sql, KDbCursor::Options options)
0068 {
0069     return new PostgresqlCursor(this, sql, options);
0070 }
0071 
0072 KDbCursor* PostgresqlConnection::prepareQuery(KDbQuerySchema* query, KDbCursor::Options options)
0073 {
0074     return new PostgresqlCursor(this, query, options);
0075 }
0076 
0077 bool PostgresqlConnection::drv_connect()
0078 {
0079     return true;
0080 }
0081 
0082 bool PostgresqlConnection::drv_getServerVersion(KDbServerVersionInfo* version)
0083 {
0084     // https://www.postgresql.org/docs/8.4/static/libpq-status.html
0085     //postgresqlDebug() << "server_version:" << d->parameter("server_version");
0086     version->setString(QString::fromLatin1(parameter(d->conn, "server_version")));
0087     const int versionNumber = PQserverVersion(d->conn);
0088     if (versionNumber > 0) {
0089         version->setMajor(versionNumber / 10000);
0090         version->setMinor((versionNumber % 1000) / 100);
0091         version->setRelease(versionNumber % 100);
0092     }
0093 
0094     if (   version->major() < MIN_SERVER_VERSION_MAJOR
0095         || (version->major() == MIN_SERVER_VERSION_MAJOR && version->minor() < MIN_SERVER_VERSION_MINOR))
0096     {
0097         postgresqlWarning()
0098             << QString::fromLatin1("PostgreSQL %d.%d is not supported and may not work. The minimum is %d.%d")
0099                .arg(version->major()).arg(version->minor()).arg(MIN_SERVER_VERSION_MAJOR).arg(MIN_SERVER_VERSION_MINOR);
0100     }
0101     return true;
0102 }
0103 
0104 bool PostgresqlConnection::drv_disconnect()
0105 {
0106     return true;
0107 }
0108 
0109 bool PostgresqlConnection::drv_getDatabasesList(QStringList* list)
0110 {
0111     return queryStringList(KDbEscapedString("SELECT datname FROM pg_database WHERE datallowconn = TRUE"), list);
0112 }
0113 
0114 bool PostgresqlConnection::drv_createDatabase(const QString &dbName)
0115 {
0116     return executeSql(KDbEscapedString("CREATE DATABASE ") + escapeIdentifier(dbName));
0117 }
0118 
0119 QByteArray buildConnParameter(const QByteArray& key, const QVariant& value)
0120 {
0121     QByteArray result = key;
0122 //! @todo optimize
0123     result.replace('\\', "\\\\").replace('\'', "\\'");
0124     return key + "='" + value.toString().toUtf8() + "' ";
0125 }
0126 
0127 bool PostgresqlConnection::drv_useDatabase(const QString &dbName, bool *cancelled,
0128                                            KDbMessageHandler* msgHandler)
0129 {
0130     Q_UNUSED(cancelled);
0131     Q_UNUSED(msgHandler);
0132 
0133     QByteArray conninfo;
0134 
0135     if (data().hostName().isEmpty()
0136         || 0 == QString::compare(data().hostName(), QLatin1String("localhost"), Qt::CaseInsensitive))
0137     {
0138         if (!data().localSocketFileName().isEmpty()) {
0139             QFileInfo fileInfo(data().localSocketFileName());
0140             if (fileInfo.exists()) {
0141                 conninfo += buildConnParameter("host", fileInfo.absolutePath());
0142             }
0143         }
0144     }
0145     else {
0146         const QHostAddress ip(data().hostName());
0147         if (ip.isNull()) {
0148             conninfo += buildConnParameter("host", data().hostName());
0149         }
0150         else {
0151             conninfo += buildConnParameter("hostaddr", ip.toString());
0152         }
0153     }
0154 
0155     //Build up the connection string
0156     if (data().port() > 0)
0157         conninfo += buildConnParameter("port", data().port());
0158 
0159     QString myDbName = dbName;
0160     if (myDbName.isEmpty())
0161         myDbName = data().databaseName();
0162     if (!myDbName.isEmpty())
0163         conninfo += buildConnParameter("dbname", myDbName);
0164 
0165     if (!data().userName().isEmpty())
0166         conninfo += buildConnParameter("user", data().userName());
0167 
0168     if (!data().password().isEmpty())
0169         conninfo += buildConnParameter("password", data().password());
0170 
0171     //postgresqlDebug() << conninfo;
0172 
0173     //! @todo other parameters: connect_timeout, options, options, sslmode, sslcert, sslkey, sslrootcert, sslcrl, krbsrvname, gsslib, service
0174     // https://www.postgresql.org/docs/8.4/interactive/libpq-connect.html
0175     d->conn = PQconnectdb(conninfo.constData());
0176 
0177     if (!d->connectionOK()) {
0178         PQfinish(d->conn);
0179         d->conn = nullptr;
0180         return false;
0181     }
0182 
0183     // pgsql 8.1 changed the default to no oids but we need them
0184     PGresult* result = PQexec(d->conn, "SET DEFAULT_WITH_OIDS TO ON");
0185     int status = PQresultStatus(result);
0186     Q_UNUSED(status)
0187     PQclear(result);
0188 
0189     // initialize encoding
0190     result = PQexec(d->conn, "SET CLIENT_ENCODING TO 'UNICODE'");
0191     status = PQresultStatus(result);
0192     PQclear(result);
0193     d->unicode = status == PGRES_COMMAND_OK;
0194 
0195     result = PQexec(d->conn, "SET DATESTYLE TO 'ISO'");
0196     status = PQresultStatus(result);
0197     if (status != PGRES_COMMAND_OK) {
0198         postgresqlWarning() << "Failed to set DATESTYLE to 'ISO':" << PQerrorMessage(d->conn);
0199     }
0200     //! @todo call on first use of SOUNDEX(), etc.;
0201     //!       it's not possible now because we don't have connection context in KDbFunctionExpressionData
0202     if (!d->fuzzystrmatchExtensionCreated) {
0203         d->fuzzystrmatchExtensionCreated
0204             = drv_executeSql(KDbEscapedString("CREATE EXTENSION IF NOT EXISTS fuzzystrmatch"));
0205     }
0206     PQclear(result);
0207     return true;
0208 }
0209 
0210 bool PostgresqlConnection::drv_closeDatabase()
0211 {
0212     PQfinish(d->conn);
0213     d->conn = nullptr;
0214     return true;
0215 }
0216 
0217 bool PostgresqlConnection::drv_dropDatabase(const QString &dbName)
0218 {
0219     //postgresqlDebug() << dbName;
0220 
0221     //! @todo Maybe should check that dbname is no the currentdb
0222     if (executeSql(KDbEscapedString("DROP DATABASE ") + escapeIdentifier(dbName)))
0223         return true;
0224 
0225     return false;
0226 }
0227 
0228 KDbSqlResult* PostgresqlConnection::drv_prepareSql(const KDbEscapedString& sql)
0229 {
0230     PGresult* result = d->executeSql(sql);
0231     const ExecStatusType status = PQresultStatus(result);
0232     if (status == PGRES_TUPLES_OK || status == PGRES_COMMAND_OK) {
0233         return new PostgresqlSqlResult(this, result, status);
0234     }
0235     storeResult(result, status);
0236     return nullptr;
0237 }
0238 
0239 bool PostgresqlConnection::drv_executeSql(const KDbEscapedString& sql)
0240 {
0241     PGresult* result = d->executeSql(sql);
0242     const ExecStatusType status = PQresultStatus(result);
0243     d->storeResultAndClear(&m_result, &result, status);
0244     return status == PGRES_TUPLES_OK || status == PGRES_COMMAND_OK;
0245 }
0246 
0247 bool PostgresqlConnection::drv_isDatabaseUsed() const
0248 {
0249     return d->conn;
0250 }
0251 
0252 tristate PostgresqlConnection::drv_containsTable(const QString &tableName)
0253 {
0254     return resultExists(KDbEscapedString("SELECT 1 FROM pg_class WHERE relkind='r' AND relname LIKE %1")
0255                         .arg(escapeString(tableName)));
0256 }
0257 
0258 QString PostgresqlConnection::serverResultName() const
0259 {
0260     if (m_result.code() >= 0 && m_result.code() <= PGRES_SINGLE_TUPLE) {
0261         return QString::fromLatin1(PQresStatus(ExecStatusType(m_result.code())));
0262     }
0263     return QString();
0264 }
0265 
0266 KDbPreparedStatementInterface* PostgresqlConnection::prepareStatementInternal()
0267 {
0268     return new PostgresqlPreparedStatement(d);
0269 }
0270 
0271 KDbEscapedString PostgresqlConnection::escapeString(const QByteArray& str) const
0272 {
0273     int error;
0274     d->escapingBuffer.resize(str.length() * 2 + 1);
0275     size_t count = PQescapeStringConn(d->conn,
0276                                       d->escapingBuffer.data(), str.constData(), str.length(),
0277                                       &error);
0278     d->escapingBuffer.resize(count);
0279 
0280     if (error != 0) {
0281         d->storeResult(const_cast<KDbResult*>(&m_result));
0282         const_cast<KDbResult&>(m_result) = KDbResult(ERR_INVALID_ENCODING,
0283                           PostgresqlConnection::tr("Escaping string failed. Invalid multibyte encoding."));
0284         return KDbEscapedString();
0285     }
0286     return KDbEscapedString("\'") + d->escapingBuffer + '\'';
0287 }
0288 
0289 KDbEscapedString PostgresqlConnection::escapeString(const QString& str) const
0290 {
0291     return escapeString(d->unicode ? str.toUtf8() : str.toLocal8Bit());
0292 }
0293 
0294 void PostgresqlConnection::storeResult(PGresult *pgResult, ExecStatusType execStatus)
0295 {
0296     d->storeResultAndClear(&m_result, &pgResult, execStatus);
0297 }