File indexing completed on 2024-04-21 05:49:01

0001 /*
0002    SPDX-FileCopyrightText: 2010 Marco Mentasti <marcomentasti@gmail.com>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "sqlmanager.h"
0008 #include "connectionmodel.h"
0009 
0010 #include <KConfig>
0011 #include <KConfigGroup>
0012 #include <KLocalizedString>
0013 
0014 #include <QDebug>
0015 #include <QSqlDatabase>
0016 #include <QSqlDriver>
0017 #include <QSqlError>
0018 
0019 using KWallet::Wallet;
0020 
0021 SQLManager::SQLManager(QObject *parent)
0022     : QObject(parent)
0023     , m_model(new ConnectionModel(this))
0024 {
0025 }
0026 
0027 SQLManager::~SQLManager()
0028 {
0029     for (int i = 0; i < m_model->rowCount(); i++) {
0030         QString connection = m_model->data(m_model->index(i), Qt::DisplayRole).toString();
0031         QSqlDatabase::removeDatabase(connection);
0032     }
0033 
0034     delete m_model;
0035     delete m_wallet;
0036 }
0037 
0038 void SQLManager::createConnection(const Connection &conn)
0039 {
0040     if (QSqlDatabase::contains(conn.name)) {
0041         qDebug() << "connection" << conn.name << "already exist";
0042         QSqlDatabase::removeDatabase(conn.name);
0043     }
0044 
0045     QSqlDatabase db = QSqlDatabase::addDatabase(conn.driver, conn.name);
0046 
0047     if (!db.isValid()) {
0048         Q_EMIT error(db.lastError().text());
0049         QSqlDatabase::removeDatabase(conn.name);
0050         return;
0051     }
0052 
0053     db.setHostName(conn.hostname);
0054     db.setUserName(conn.username);
0055     db.setPassword(conn.password);
0056     db.setDatabaseName(conn.database);
0057     db.setConnectOptions(conn.options);
0058 
0059     if (conn.port > 0) {
0060         db.setPort(conn.port);
0061     }
0062 
0063     m_model->addConnection(conn);
0064 
0065     // try to open connection, with or without password
0066     if (db.open()) {
0067         m_model->setStatus(conn.name, Connection::ONLINE);
0068     } else {
0069         if (conn.status != Connection::REQUIRE_PASSWORD) {
0070             m_model->setStatus(conn.name, Connection::OFFLINE);
0071             Q_EMIT error(db.lastError().text());
0072         }
0073     }
0074 
0075     Q_EMIT connectionCreated(conn.name);
0076 }
0077 
0078 bool SQLManager::testConnection(const Connection &conn, QSqlError &error)
0079 {
0080     QString connectionName = (conn.name.isEmpty()) ? QStringLiteral("katesql-test") : conn.name;
0081 
0082     QSqlDatabase db = QSqlDatabase::addDatabase(conn.driver, connectionName);
0083 
0084     if (!db.isValid()) {
0085         error = db.lastError();
0086         QSqlDatabase::removeDatabase(connectionName);
0087         return false;
0088     }
0089 
0090     db.setHostName(conn.hostname);
0091     db.setUserName(conn.username);
0092     db.setPassword(conn.password);
0093     db.setDatabaseName(conn.database);
0094     db.setConnectOptions(conn.options);
0095 
0096     if (conn.port > 0) {
0097         db.setPort(conn.port);
0098     }
0099 
0100     if (!db.open()) {
0101         error = db.lastError();
0102         QSqlDatabase::removeDatabase(connectionName);
0103         return false;
0104     }
0105 
0106     QSqlDatabase::removeDatabase(connectionName);
0107     return true;
0108 }
0109 
0110 bool SQLManager::isValidAndOpen(const QString &connection)
0111 {
0112     QSqlDatabase db = QSqlDatabase::database(connection);
0113 
0114     if (!db.isValid()) {
0115         m_model->setStatus(connection, Connection::OFFLINE);
0116         Q_EMIT error(db.lastError().text());
0117         return false;
0118     }
0119 
0120     if (!db.isOpen()) {
0121         qDebug() << "database connection is not open. trying to open it...";
0122 
0123         if (m_model->status(connection) == Connection::REQUIRE_PASSWORD) {
0124             QString password;
0125             int ret = readCredentials(connection, password);
0126 
0127             if (ret != SQLManager::K_WALLET_CONNECTION_SUCCESSFUL) {
0128                 qDebug() << "Can't retrieve password from kwallet. returned code" << ret;
0129             } else {
0130                 db.setPassword(password);
0131                 m_model->setPassword(connection, password);
0132             }
0133         }
0134 
0135         if (!db.open()) {
0136             m_model->setStatus(connection, Connection::OFFLINE);
0137             Q_EMIT error(db.lastError().text());
0138             return false;
0139         }
0140     }
0141 
0142     m_model->setStatus(connection, Connection::ONLINE);
0143 
0144     return true;
0145 }
0146 
0147 void SQLManager::reopenConnection(const QString &name)
0148 {
0149     Q_EMIT connectionAboutToBeClosed(name);
0150 
0151     QSqlDatabase db = QSqlDatabase::database(name);
0152 
0153     db.close();
0154     isValidAndOpen(name);
0155 }
0156 
0157 Wallet *SQLManager::openWallet()
0158 {
0159     if (!m_wallet) {
0160         /// FIXME get kate window id...
0161         m_wallet = Wallet::openWallet(KWallet::Wallet::NetworkWallet(), 0);
0162     }
0163 
0164     if (!m_wallet) {
0165         return nullptr;
0166     }
0167 
0168     QString folder(QStringLiteral("SQL Connections"));
0169 
0170     if (!m_wallet->hasFolder(folder)) {
0171         m_wallet->createFolder(folder);
0172     }
0173 
0174     m_wallet->setFolder(folder);
0175 
0176     return m_wallet;
0177 }
0178 
0179 int SQLManager::storeCredentials(const Connection &conn)
0180 {
0181     Wallet *wallet = openWallet();
0182 
0183     if (!wallet) { // user reject
0184         return SQLManager::K_WALLET_CONNECTION_REJECTED_BY_USER;
0185     }
0186 
0187     QMap<QString, QString> map;
0188 
0189     map[QStringLiteral("driver")] = conn.driver.toUpper();
0190     map[QStringLiteral("options")] = conn.options;
0191 
0192     // Sqlite is without password
0193     if (conn.driver.contains(QLatin1String("QSQLITE"))) {
0194         map[QStringLiteral("database")] = conn.database;
0195     } else {
0196         map[QStringLiteral("database")] = conn.database.toUpper();
0197         map[QStringLiteral("username")] = conn.username;
0198         map[QStringLiteral("password")] = conn.password;
0199         map[QStringLiteral("hostname")] = conn.hostname.toUpper();
0200         map[QStringLiteral("port")] = QString::number(conn.port);
0201     }
0202     const int result = (wallet->writeMap(conn.name, map) == SQLManager::K_WALLET_CONNECTION_SUCCESSFUL) ? SQLManager::K_WALLET_CONNECTION_SUCCESSFUL
0203                                                                                                         : SQLManager::K_WALLET_CONNECTION_ERROR;
0204     return result;
0205 }
0206 
0207 // if success, password contain the password
0208 int SQLManager::readCredentials(const QString &name, QString &password)
0209 {
0210     Wallet *wallet = openWallet();
0211 
0212     if (!wallet) { // user reject
0213         return SQLManager::K_WALLET_CONNECTION_REJECTED_BY_USER;
0214     }
0215 
0216     QMap<QString, QString> map;
0217 
0218     if (wallet->readMap(name, map) == 0) {
0219         if (!map.isEmpty()) {
0220             password = map.value(QStringLiteral("password"));
0221             return SQLManager::K_WALLET_CONNECTION_SUCCESSFUL;
0222         }
0223     }
0224 
0225     return SQLManager::K_WALLET_CONNECTION_ERROR;
0226 }
0227 
0228 ConnectionModel *SQLManager::connectionModel()
0229 {
0230     return m_model;
0231 }
0232 
0233 void SQLManager::removeConnection(const QString &name)
0234 {
0235     Q_EMIT connectionAboutToBeClosed(name);
0236 
0237     m_model->removeConnection(name);
0238 
0239     QSqlDatabase::removeDatabase(name);
0240 
0241     Q_EMIT connectionRemoved(name);
0242 }
0243 
0244 void SQLManager::loadConnections(const KConfigGroup &connectionsGroup)
0245 {
0246     Connection c;
0247     const auto groupList = connectionsGroup.groupList();
0248 
0249     for (const QString &groupName : groupList) {
0250         qDebug() << "reading group:" << groupName;
0251 
0252         KConfigGroup group = connectionsGroup.group(groupName);
0253 
0254         c.name = groupName;
0255         c.driver = group.readEntry("driver");
0256         c.options = group.readEntry("options");
0257 
0258         if (c.driver.contains(QLatin1String("QSQLITE"))) {
0259             c.database = QUrl(group.readEntry("database")).path();
0260         } else {
0261             c.database = group.readEntry("database");
0262             c.hostname = group.readEntry("hostname");
0263             c.username = group.readEntry("username");
0264             c.port = group.readEntry("port", 0);
0265 
0266             // for compatibility with version 0.2, when passwords
0267             // were stored in config file instead of kwallet
0268             c.password = group.readEntry("password");
0269 
0270             if (!c.password.isEmpty()) {
0271                 c.status = Connection::ONLINE;
0272             } else {
0273                 c.status = Connection::REQUIRE_PASSWORD;
0274             }
0275         }
0276         createConnection(c);
0277     }
0278 }
0279 
0280 void SQLManager::saveConnections(KConfigGroup *connectionsGroup)
0281 {
0282     //    qDebug() << "Saving " << m_model->rowCount() << " groups";
0283     for (int i = 0; i < m_model->rowCount(); i++) {
0284         saveConnection(connectionsGroup, m_model->data(m_model->index(i), Qt::UserRole).value<Connection>());
0285     }
0286 }
0287 
0288 void SQLManager::saveConnection(KConfigGroup *connectionsGroup, const Connection &conn)
0289 {
0290     //    qDebug() << "saving connection " << conn.name;
0291     KConfigGroup group = connectionsGroup->group(conn.name);
0292 
0293     group.writeEntry("driver", conn.driver);
0294     group.writeEntry("options", conn.options);
0295 
0296     if (conn.driver.contains(QLatin1String("QSQLITE"))) {
0297         group.writeEntry("database", QUrl::fromLocalFile(conn.database));
0298         return;
0299     }
0300     group.writeEntry("database", conn.database);
0301     group.writeEntry("hostname", conn.hostname);
0302     group.writeEntry("username", conn.username);
0303     group.writeEntry("port", conn.port);
0304 }
0305 
0306 void SQLManager::runQuery(const QString &text, const QString &connection)
0307 {
0308     //    qDebug() << "connection:" << connection;
0309     //    qDebug() << "text:" << text;
0310 
0311     if (text.isEmpty()) {
0312         return;
0313     }
0314 
0315     if (!isValidAndOpen(connection)) {
0316         return;
0317     }
0318 
0319     QSqlDatabase db = QSqlDatabase::database(connection);
0320     QSqlQuery query(db);
0321 
0322     if (!query.prepare(text)) {
0323         QSqlError err = query.lastError();
0324 
0325         if (err.type() == QSqlError::ConnectionError) {
0326             m_model->setStatus(connection, Connection::OFFLINE);
0327         }
0328 
0329         Q_EMIT error(err.text());
0330         return;
0331     }
0332 
0333     if (!query.exec()) {
0334         QSqlError err = query.lastError();
0335 
0336         if (err.type() == QSqlError::ConnectionError) {
0337             m_model->setStatus(connection, Connection::OFFLINE);
0338         }
0339 
0340         Q_EMIT error(err.text());
0341         return;
0342     }
0343 
0344     QString message;
0345 
0346     /// TODO: improve messages
0347     if (query.isSelect()) {
0348         if (!query.driver()->hasFeature(QSqlDriver::QuerySize)) {
0349             message = i18nc("@info", "Query completed successfully");
0350         } else {
0351             int nRowsSelected = query.size();
0352             message = i18ncp("@info", "%1 record selected", "%1 records selected", nRowsSelected);
0353         }
0354     } else {
0355         int nRowsAffected = query.numRowsAffected();
0356         message = i18ncp("@info", "%1 row affected", "%1 rows affected", nRowsAffected);
0357     }
0358 
0359     Q_EMIT success(message);
0360     Q_EMIT queryActivated(query, connection);
0361 }
0362 
0363 #include "moc_sqlmanager.cpp"