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 }