File indexing completed on 2024-04-28 15:58:53

0001 /* This file is part of the KDE project
0002    Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org>
0003 
0004    This program is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This program is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this program; see the file COPYING.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "SqliteConnection.h"
0021 #include "SqliteConnection_p.h"
0022 #include "SqliteCursor.h"
0023 #include "SqlitePreparedStatement.h"
0024 #include "SqliteFunctions.h"
0025 #include "sqlite_debug.h"
0026 
0027 #include <sqlite3.h>
0028 
0029 #include "KDbConnectionData.h"
0030 #include "KDbConnectionOptions.h"
0031 #include "KDbUtils.h"
0032 #include "KDbUtils_p.h"
0033 #include "KDbVersionInfo.h"
0034 
0035 #include <QFile>
0036 #include <QDir>
0037 #include <QRegularExpression>
0038 
0039 SqliteConnection::SqliteConnection(KDbDriver *driver, const KDbConnectionData& connData,
0040                                    const KDbConnectionOptions &options)
0041         : KDbConnection(driver, connData, options)
0042         , d(new SqliteConnectionInternal(this))
0043 {
0044     QByteArray propertyName = "extraSqliteExtensionPaths";
0045     KDbUtils::Property extraSqliteExtensionPathsProperty = this->options()->property(propertyName);
0046     if (extraSqliteExtensionPathsProperty.isNull()) {
0047         this->options()->insert(propertyName, QStringList());
0048     }
0049     this->options()->setCaption(propertyName, SqliteConnection::tr("Extra paths for SQLite plugins"));
0050 }
0051 
0052 SqliteConnection::~SqliteConnection()
0053 {
0054     destroy();
0055     delete d;
0056 }
0057 
0058 void SqliteConnection::storeResult()
0059 {
0060     d->storeResult(&m_result);
0061 }
0062 
0063 bool SqliteConnection::drv_connect()
0064 {
0065     return true;
0066 }
0067 
0068 bool SqliteConnection::drv_getServerVersion(KDbServerVersionInfo* version)
0069 {
0070     version->setString(QLatin1String(SQLITE_VERSION)); //defined in sqlite3.h
0071     static const QRegularExpression re(QLatin1String("^(\\d+)\\.(\\d+)\\.(\\d+)$"));
0072     QRegularExpressionMatch match  = re.match(version->string());
0073     if (match.hasMatch()) {
0074         version->setMajor(match.captured(1).toInt());
0075         version->setMinor(match.captured(2).toInt());
0076         version->setRelease(match.captured(3).toInt());
0077     }
0078     return true;
0079 }
0080 
0081 bool SqliteConnection::drv_disconnect()
0082 {
0083     return true;
0084 }
0085 
0086 bool SqliteConnection::drv_getDatabasesList(QStringList* list)
0087 {
0088     //this is one-db-per-file database
0089     list->append(data().databaseName());
0090     return true;
0091 }
0092 
0093 tristate SqliteConnection::drv_containsTable(const QString &tableName)
0094 {
0095     return resultExists(KDbEscapedString("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE %1")
0096                             .arg(escapeString(tableName)));
0097 }
0098 
0099 #if 0 // TODO
0100 bool SqliteConnection::drv_getTablesList(QStringList* list)
0101 {
0102     KDbCursor *cursor;
0103     if (!(cursor = executeQuery(KDbEscapedString("SELECT name FROM sqlite_master WHERE type='table'")))) {
0104         sqliteWarning() << "!executeQuery()";
0105         return false;
0106     }
0107     list->clear();
0108     cursor->moveFirst();
0109     while (!cursor->eof() && !cursor->result().isError()) {
0110         *list += cursor->value(0).toString();
0111         cursor->moveNext();
0112     }
0113     if (cursor->result().isError()) {
0114         deleteCursor(cursor);
0115         return false;
0116     }
0117     return deleteCursor(cursor);
0118 }
0119 #endif
0120 
0121 bool SqliteConnection::drv_createDatabase(const QString &dbName)
0122 {
0123     Q_UNUSED(dbName);
0124     return drv_useDatabaseInternal(nullptr, nullptr, true/*create if missing*/);
0125 }
0126 
0127 bool SqliteConnection::drv_useDatabase(const QString &dbName, bool *cancelled,
0128                                        KDbMessageHandler* msgHandler)
0129 {
0130     Q_UNUSED(dbName);
0131     return drv_useDatabaseInternal(cancelled, msgHandler, false/*do not create if missing*/);
0132 }
0133 
0134 bool SqliteConnection::drv_useDatabaseInternal(bool *cancelled,
0135                                                KDbMessageHandler* msgHandler, bool createIfMissing)
0136 {
0137 //! @todo add option (command line or in kdbrc?)
0138 //! @todo   int exclusiveFlag = KDbConnection::isReadOnly() ? SQLITE_OPEN_READONLY : SQLITE_OPEN_WRITE_LOCKED; // <-- shared read + (if !r/o): exclusive write
0139     int openFlags = 0;
0140     if (options()->isReadOnly()) {
0141         openFlags |= SQLITE_OPEN_READONLY;
0142     }
0143     else {
0144         openFlags |= SQLITE_OPEN_READWRITE;
0145         if (createIfMissing) {
0146             openFlags |= SQLITE_OPEN_CREATE;
0147         }
0148     }
0149 
0150 //! @todo add option
0151 //    int allowReadonly = 1;
0152 //    const bool wasReadOnly = KDbConnection::isReadOnly();
0153 
0154     //sqliteDebug() << data().databaseName();
0155     int res = sqlite3_open_v2(
0156                  /* unicode expected since SQLite 3.1 */
0157                  QDir::toNativeSeparators(data().databaseName()).toUtf8().constData(),
0158                  &d->data,
0159                  openFlags, /*exclusiveFlag,
0160                  allowReadonly *//* If 1 and locking fails, try opening in read-only mode */
0161                  nullptr
0162              );
0163     if (res != SQLITE_OK) {
0164         m_result.setServerErrorCode(res);
0165     }
0166     storeResult();
0167 
0168     if (!m_result.isError()) {
0169         // Set the secure-delete on, so SQLite overwrites deleted content with zeros.
0170         // The default setting is determined by the SQLITE_SECURE_DELETE compile-time option but we overwrite it here.
0171         // Works with 3.6.23. Earlier version just ignore this pragma.
0172         // See https://www.sqlite.org/pragma.html#pragma_secure_delete
0173 //! @todo add connection flags to the driver and global setting to control the "secure delete" pragma
0174         if (!drv_executeSql(KDbEscapedString("PRAGMA secure_delete = on"))) {
0175             drv_closeDatabaseSilently();
0176             return false;
0177         }
0178         // Load ICU extension for unicode collations
0179         if (!findAndLoadExtension(QLatin1String("kdb_sqlite_icu"))) {
0180             drv_closeDatabaseSilently();
0181             return false;
0182         }
0183         // load ROOT collation for use as default collation
0184         if (!drv_executeSql(KDbEscapedString("SELECT icu_load_collation('', '')"))) {
0185             drv_closeDatabaseSilently();
0186             return false;
0187         }
0188         if (!createCustomSQLiteFunctions(d->data)) {
0189             drv_closeDatabaseSilently();
0190             return false;
0191         }
0192     }
0193 
0194 //! @todo check exclusive status
0195     Q_UNUSED(cancelled);
0196     Q_UNUSED(msgHandler);
0197 //! @todo removed in kdb - reenable?
0198 /*
0199     if (d->res == SQLITE_OK && cancelled && !wasReadOnly && allowReadonly && isReadOnly()) {
0200         //opened as read only, ask
0201         if (KDbMessageHandler::Continue !=
0202                 askQuestion(
0203                     KDbMessageHandler::WarningContinueCancel,
0204                     futureTr("Do you want to open file \"%1\" as read-only?\n\n"
0205                         "The file is probably already open on this or another computer. "
0206                         "Could not gain exclusive access for writing the file.")
0207                     .arg(QDir::fromNativeSeparators(data()->databaseName())),
0208                     futureTr("Opening As Read-Only"),
0209                     KDbMessageHandler::Continue,
0210                     KDbMessageHandler::KDbGuiItem()
0211                             .setProperty("text", futureTr("Open As Read-Only"))
0212                             .setProperty("icon", "document-open"),
0213                     KDbMessageHandler::KDbGuiItem(),
0214                     "askBeforeOpeningFileReadOnly",
0215                     KDbMessageHandler::Notify,
0216                     msgHandler)
0217         {
0218             clearError();
0219             if (!drv_closeDatabase())
0220                 return false;
0221             *cancelled = true;
0222             return false;
0223         }
0224     }
0225 
0226     if (d->res == SQLITE_CANTOPEN_WITH_LOCKED_READWRITE) {
0227         setError(ERR_ACCESS_RIGHTS,
0228                  futureTr("The file is probably already open on this or another computer.\n\n"
0229                           "Could not gain exclusive access for reading and writing the file. "
0230                           "Check the file's permissions and whether it is already opened and locked by another application."));
0231     } else if (d->res == SQLITE_CANTOPEN_WITH_LOCKED_WRITE) {
0232         setError(ERR_ACCESS_RIGHTS,
0233                  futureTr("The file is probably already open on this or another computer.\n\n"
0234                           "Could not gain exclusive access for writing the file. "
0235                           "Check the file's permissions and whether it is already opened and locked by another application."));
0236     }*/
0237     return res == SQLITE_OK;
0238 }
0239 
0240 void SqliteConnection::drv_closeDatabaseSilently()
0241 {
0242     KDbResult result = this->result(); // save
0243     drv_closeDatabase();
0244     m_result = result;
0245 }
0246 
0247 bool SqliteConnection::drv_closeDatabase()
0248 {
0249     if (!d->data)
0250         return false;
0251 
0252     const int res = sqlite3_close(d->data);
0253     if (SQLITE_OK == res) {
0254         d->data = nullptr;
0255         return true;
0256     }
0257     if (SQLITE_BUSY == res) {
0258 #if 0 //this is ANNOYING, needs fixing (by closing cursors or waiting)
0259         setError(ERR_CLOSE_FAILED, futureTr("Could not close busy database."));
0260 #else
0261         return true;
0262 #endif
0263     }
0264     return false;
0265 }
0266 
0267 bool SqliteConnection::drv_dropDatabase(const QString &dbName)
0268 {
0269     Q_UNUSED(dbName); // Each database is one single SQLite file.
0270     const QString filename = data().databaseName();
0271     if (QFile::exists(filename) && !QFile::remove(filename)) {
0272         m_result = KDbResult(ERR_ACCESS_RIGHTS,
0273                              SqliteConnection::tr("Could not delete file \"%1\". "
0274                              "Check the file's permissions and whether it is already "
0275                              "opened and locked by another application.")
0276                    .arg(QDir::fromNativeSeparators(filename)));
0277         return false;
0278     }
0279     return true;
0280 }
0281 
0282 KDbCursor* SqliteConnection::prepareQuery(const KDbEscapedString& sql, KDbCursor::Options options)
0283 {
0284     return new SqliteCursor(this, sql, options);
0285 }
0286 
0287 KDbCursor* SqliteConnection::prepareQuery(KDbQuerySchema* query, KDbCursor::Options options)
0288 {
0289     return new SqliteCursor(this, query, options);
0290 }
0291 
0292 KDbSqlResult* SqliteConnection::drv_prepareSql(const KDbEscapedString& sql)
0293 {
0294 #ifdef KDB_DEBUG_GUI
0295     KDb::debugGUI(QLatin1String("PrepareSQL (SQLite): ") + sql.toString());
0296 #endif
0297 
0298     sqlite3_stmt *prepared_st = nullptr;
0299     const int res = sqlite3_prepare(
0300                  d->data,            /* Database handle */
0301                  sql.constData(),    /* SQL statement, UTF-8 encoded */
0302                  sql.length(),       /* Length of zSql in bytes. */
0303                  &prepared_st,       /* OUT: Statement handle */
0304                  nullptr/*const char **pzTail*/     /* OUT: Pointer to unused portion of zSql */
0305              );
0306     if (res != SQLITE_OK) {
0307         m_result.setServerErrorCode(res);
0308         storeResult();
0309 #ifdef KDB_DEBUG_GUI
0310         KDb::debugGUI(QLatin1String("  Failure"));
0311 #endif
0312         return nullptr;
0313     }
0314 
0315 #ifdef KDB_DEBUG_GUI
0316     KDb::debugGUI(QLatin1String("  Success"));
0317 #endif
0318     return new SqliteSqlResult(this, prepared_st);
0319 }
0320 
0321 bool SqliteConnection::drv_executeSql(const KDbEscapedString& sql)
0322 {
0323 #ifdef KDB_DEBUG_GUI
0324     KDb::debugGUI(QLatin1String("ExecuteSQL (SQLite): ") + sql.toString());
0325 #endif
0326 
0327     char *errmsg_p = nullptr;
0328     const int res = sqlite3_exec(
0329                  d->data,
0330                  sql.constData(),
0331                  nullptr/*callback*/,
0332                  nullptr,
0333                  &errmsg_p);
0334     if (res != SQLITE_OK) {
0335         m_result.setServerErrorCode(res);
0336     }
0337     if (errmsg_p) {
0338         clearResult();
0339         m_result.setServerMessage(QLatin1String(errmsg_p));
0340         sqlite3_free(errmsg_p);
0341     } else {
0342         storeResult();
0343     }
0344 
0345 #ifdef KDB_DEBUG_GUI
0346     KDb::debugGUI(QLatin1String( res == SQLITE_OK ? "  Success" : "  Failure"));
0347 #endif
0348     return res == SQLITE_OK;
0349 }
0350 
0351 QString SqliteConnection::serverResultName() const
0352 {
0353     return SqliteConnectionInternal::serverResultName(m_result.serverErrorCode());
0354 }
0355 
0356 KDbPreparedStatementInterface* SqliteConnection::prepareStatementInternal()
0357 {
0358     return new SqlitePreparedStatement(d);
0359 }
0360 
0361 bool SqliteConnection::findAndLoadExtension(const QString & name)
0362 {
0363     QStringList pluginPaths;
0364     foreach (const QString& path, KDb::libraryPaths()) {
0365         pluginPaths += path + QLatin1String("/sqlite3");
0366     }
0367     pluginPaths += options()->property("extraSqliteExtensionPaths").value().toStringList();
0368     foreach (const QString& path, pluginPaths) {
0369         if (loadExtension(path + QLatin1Char('/') + name + QLatin1String(KDB_SHARED_LIB_EXTENSION))) {
0370             return true;
0371         }
0372     }
0373     clearResult();
0374     m_result = KDbResult(ERR_CANNOT_LOAD_OBJECT,
0375                          SqliteConnection::tr("Could not load SQLite plugin \"%1\".").arg(name));
0376     return false;
0377 }
0378 
0379 bool SqliteConnection::loadExtension(const QString& path)
0380 {
0381     bool tempEnable = false;
0382     clearResult();
0383     QFileInfo fileInfo(path);
0384     if (!fileInfo.exists()) {
0385         m_result = KDbResult(ERR_OBJECT_NOT_FOUND,
0386                              SqliteConnection::tr("Could not find SQLite plugin file \"%1\".").arg(path));
0387         //sqliteWarning() << "Could not find SQLite extension";
0388         return false;
0389     }
0390     if (!d->extensionsLoadingEnabled()) {
0391         tempEnable = true;
0392         d->setExtensionsLoadingEnabled(true);
0393     }
0394     char *errmsg_p = nullptr;
0395     int res = sqlite3_load_extension(d->data, QDir::toNativeSeparators(path).toUtf8().constData(),
0396                                      nullptr, &errmsg_p);
0397     bool ok = res == SQLITE_OK;
0398     if (!ok) {
0399         m_result.setServerErrorCode(res);
0400         m_result = KDbResult(ERR_CANNOT_LOAD_OBJECT,
0401                              SqliteConnection::tr("Could not load SQLite extension \"%1\".").arg(path));
0402         sqliteWarning() << "SqliteConnection::loadExtension(): Could not load SQLite extension"
0403                 << path << ":" << errmsg_p;
0404         if (errmsg_p) {
0405             m_result.setServerMessage(QLatin1String(errmsg_p));
0406             sqlite3_free(errmsg_p);
0407         }
0408     }
0409     if (tempEnable) {
0410         d->setExtensionsLoadingEnabled(false);
0411     }
0412     return ok;
0413 }