diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,8 @@ include(GenerateExportHeader) include(KMyMoneyMacros) +find_package(PkgConfig) + set (OPT_KF5_COMPONENTS DocTools Holidays Contacts Akonadi IdentityManagement Activities) find_package(Gpgmepp) if (Gpgmepp_FOUND) @@ -50,6 +52,10 @@ list(APPEND OPT_KF5_COMPONENTS Gpgmepp) endif() +if (PkgConfig_FOUND) + pkg_check_modules(SQLCIPHER sqlcipher) +endif() + find_package(Qt5 5.6 REQUIRED COMPONENTS Core DBus Widgets Svg Xml Test PrintSupport OPTIONAL_COMPONENTS Sql Concurrent QuickWidgets) @@ -139,7 +145,12 @@ cmake_dependent_option(ENABLE_SQLSTORAGE "Enable SQL storage support." ON "Qt5Sql_FOUND" OFF) -add_feature_info("SQL Storage" ENABLE_SQLSTORAGE "It allows storing your financial data in SQL database (note: no encryption yet supported).") +add_feature_info("SQL Storage" ENABLE_SQLSTORAGE "It allows storing your financial data in SQL database.") + +cmake_dependent_option(ENABLE_SQLCIPHER "Enable SQLCipher support." ON + "SQLCIPHER_FOUND" OFF) + +add_feature_info("SQLCipher" ENABLE_SQLCIPHER "It allows encrypting your SQLite3 database.") cmake_dependent_option(ENABLE_IBANBICDATA "Enable IBAN/BIC data support." OFF "Qt5Sql_FOUND" OFF) diff --git a/config-kmymoney.h.cmake b/config-kmymoney.h.cmake --- a/config-kmymoney.h.cmake +++ b/config-kmymoney.h.cmake @@ -18,3 +18,5 @@ #cmakedefine ENABLE_WEBENGINE 1 #cmakedefine ENABLE_UNFINISHEDFEATURES 1 + +#cmakedefine ENABLE_SQLCIPHER 1 diff --git a/kmymoney/CMakeLists.txt b/kmymoney/CMakeLists.txt --- a/kmymoney/CMakeLists.txt +++ b/kmymoney/CMakeLists.txt @@ -116,7 +116,17 @@ ecm_add_app_icon(kmymoney_SRCS ICONS ${KMYMONEY_APP_ICONS}) add_executable( kmymoney ${kmymoney_SRCS} ) + +if (ENABLE_SQLCIPHER) + target_link_libraries(kmymoney + PRIVATE + sqlcipher + ) + add_definitions( -DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2 ) +endif() + target_link_libraries(kmymoney + PUBLIC views kmymoney_base kmymoney_common diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -181,6 +181,10 @@ #include "misc/platformtools.h" +#ifdef ENABLE_SQLCIPHER +#include "sqlcipher/sqlite3.h" +#endif + #ifdef KMM_DEBUG #include "mymoney/storage/mymoneystoragedump.h" #include "mymoneytracer.h" @@ -1122,6 +1126,26 @@ qRegisterMetaType("MyMoneyMoney"); qRegisterMetaType("MyMoneySecurity"); +#ifdef ENABLE_SQLCIPHER + /* Issues: + * 1) libsqlite3 loads implicitly before libsqlcipher + * thus making the second one loaded but non-functional, + * 2) libsqlite3 gets linked into kmymoney target implicitly + * and it's not possible to unload or unlink it explicitly + * + * Solution: + * Use e.g. dummy sqlite3_key call, so that libsqlcipher gets loaded implicitly before libsqlite3 + * thus making the first one functional. + * + * Additional info: + * 1) loading libsqlcipher explicitly doesn't solve the issue, + * 2) using sqlite3_key only in sqlstorage plugin doesn't solve the issue, + * 3) in a separate, minimal test case, loading libsqlite3 explicitly + * with QLibrary::ExportExternalSymbolsHint makes libsqlcipher non-functional + */ + sqlite3_key(nullptr, nullptr, 0); +#endif + // preset the pointer because we need it during the course of this constructor kmymoney = this; d->m_config = KSharedConfig::openConfig(); @@ -3455,7 +3479,7 @@ break; } } catch (const MyMoneyException &e) { - KMessageBox::sorry(this, i18n("Cannot open file as requested. Error was: %1", QString::fromLatin1(e.what()))); + KMessageBox::detailedError(this, i18n("Cannot open file as requested."), QString::fromLatin1(e.what())); return false; } } diff --git a/kmymoney/plugins/CMakeLists.txt b/kmymoney/plugins/CMakeLists.txt --- a/kmymoney/plugins/CMakeLists.txt +++ b/kmymoney/plugins/CMakeLists.txt @@ -25,10 +25,9 @@ add_subdirectory( kbanking ) endif (ENABLE_KBANKING) -option(ENABLE_SQLCIPHER "Enable SQLCipher plugin" OFF) if (ENABLE_SQLCIPHER) add_subdirectory(sqlcipher) -endif(ENABLE_SQLCIPHER) +endif() option(ENABLE_ONLINEJOBPLUGINMOCKUP "Enable onlineJob-plugin mockup (only for developers)" OFF) if (ENABLE_ONLINEJOBPLUGINMOCKUP) diff --git a/kmymoney/plugins/sql/mymoneydbdriver.cpp b/kmymoney/plugins/sql/mymoneydbdriver.cpp --- a/kmymoney/plugins/sql/mymoneydbdriver.cpp +++ b/kmymoney/plugins/sql/mymoneydbdriver.cpp @@ -17,6 +17,7 @@ ***************************************************************************/ #include "mymoneydbdriver.h" +#include "config-kmymoney.h" // ---------------------------------------------------------------------------- // QT Includes @@ -150,8 +151,11 @@ map["QODBC"] = QString("Open Database Connectivity"); map["QPSQL"] = QString("PostgreSQL v7.3 and up"); map["QTDS"] = QString("Sybase Adaptive Server and Microsoft SQL Server"); - map["QSQLITE"] = QString("SQLite Version 3"); - map["SQLCIPHER"] = QString("SQLCipher Version 3 (encrypted SQLite)"); +#ifndef ENABLE_SQLCIPHER + map["QSQLITE"] = QString("SQLite Version 3"); // QSQLITE is overridden with QSQLCIPHER and won't work properly, so disable it +#else + map["QSQLCIPHER"] = QString("SQLCipher Version 3 (encrypted SQLite)"); +#endif return map; } @@ -174,7 +178,7 @@ return QExplicitlySharedDataPointer (new MyMoneySybaseDriver()); else if (type == "QSQLITE") return QExplicitlySharedDataPointer (new MyMoneySqlite3Driver()); - else if (type == "SQLCIPHER") + else if (type == "QSQLCIPHER") return QExplicitlySharedDataPointer (new MyMoneySqlCipher3Driver()); else throw MYMONEYEXCEPTION_CSTRING("Unknown database driver type."); } diff --git a/kmymoney/plugins/sql/mymoneystoragesql.cpp b/kmymoney/plugins/sql/mymoneystoragesql.cpp --- a/kmymoney/plugins/sql/mymoneystoragesql.cpp +++ b/kmymoney/plugins/sql/mymoneystoragesql.cpp @@ -26,9 +26,13 @@ // ---------------------------------------------------------------------------- // QT Includes +#include + // ---------------------------------------------------------------------------- // KDE Includes +#include "KMessageBox" + // ---------------------------------------------------------------------------- // Project Includes @@ -93,6 +97,8 @@ if (QUrlQuery(url).queryItemValue("driver").contains("QMYSQL")) { setConnectOptions("MYSQL_OPT_RECONNECT=1"); } + + QSqlQuery query(*this); switch (openMode) { case QIODevice::ReadOnly: // OpenDatabase menu entry (or open last file) case QIODevice::ReadWrite: // Save menu entry with database open @@ -109,7 +115,33 @@ d->buildError(QSqlQuery(*this), Q_FUNC_INFO, "opening database"); rc = 1; } else { - rc = d->createTables(); // check all tables are present, create if not + if (driverName().compare(QLatin1String("QSQLCIPHER")) == 0) { + auto passphrase = password(); + while (true) { + if (!passphrase.isEmpty()) { + query.exec(QString::fromLatin1("PRAGMA cipher_version")); + if(!query.next()) + throw MYMONEYEXCEPTION_CSTRING("Based on empty cipher_version, libsqlcipher is not in use."); + query.exec(QString::fromLatin1("PRAGMA key = '%1'").arg(passphrase)); // SQLCipher feature to decrypt a database + } + query.exec(QStringLiteral("SELECT count(*) FROM sqlite_master")); // SQLCipher recommended way to check if password is correct + if (query.next()) { + rc = d->createTables(); // check all tables are present, create if not + break; + } + auto ok = false; + passphrase = QInputDialog::getText(nullptr, i18n("Password"), + i18n("You're trying to open an encrypted database.\n" + "Please provide a password in order to open it."), + QLineEdit::Password, QString(), &ok); + if (!ok) { + QSqlDatabase::close(); + throw MYMONEYEXCEPTION_CSTRING("Bad password."); + } + } + } else { + rc = d->createTables(); // check all tables are present, create if not + } } break; case QIODevice::WriteOnly: // SaveAs Database - if exists, must be empty, if not will create @@ -124,10 +156,19 @@ d->buildError(QSqlQuery(*this), Q_FUNC_INFO, "opening new database"); rc = 1; } else { + query.exec(QString::fromLatin1("PRAGMA key = '%1'").arg(password())); rc = d->createTables(); } } } else { + if (driverName().compare(QLatin1String("QSQLCIPHER")) == 0 && + !password().isEmpty()) { + KMessageBox::information(nullptr, i18n("Overwriting an existing database with an encrypted database is not yet supported.\n" + "Please save your database under a new name.")); + QSqlDatabase::close(); + rc = 3; + return rc; + } rc = d->createTables(); if (rc == 0) { if (clear) { @@ -252,7 +293,9 @@ d->m_onlineJobs = d->m_payeeIdentifier = 0; d->m_displayStatus = true; try { - if (this->driverName().compare(QLatin1String("QSQLITE")) == 0) { + const auto driverName = this->driverName(); + if (driverName.compare(QLatin1String("QSQLITE")) == 0 || + driverName.compare(QLatin1String("QSQLCIPHER")) == 0) { QSqlQuery query(*this); query.exec("PRAGMA foreign_keys = ON"); // this is needed for "ON UPDATE" and "ON DELETE" to work } diff --git a/kmymoney/plugins/sql/mymoneystoragesql_p.h b/kmymoney/plugins/sql/mymoneystoragesql_p.h --- a/kmymoney/plugins/sql/mymoneystoragesql_p.h +++ b/kmymoney/plugins/sql/mymoneystoragesql_p.h @@ -2013,6 +2013,7 @@ .arg(maindb.databaseName()).arg(Q_FUNC_INFO)); } else { QSqlQuery qm(maindb); + qm.exec(QString::fromLatin1("PRAGMA key = '%1'").arg(q->password())); QString qs = m_driver->createDbString(dbName) + ';'; if (!qm.exec(qs)) { // krazy:exclude=crashy buildError(qm, Q_FUNC_INFO, diff --git a/kmymoney/plugins/sql/sqlstorage.cpp b/kmymoney/plugins/sql/sqlstorage.cpp --- a/kmymoney/plugins/sql/sqlstorage.cpp +++ b/kmymoney/plugins/sql/sqlstorage.cpp @@ -24,7 +24,10 @@ // QT Includes #include +#include #include +#include +#include // ---------------------------------------------------------------------------- // KDE Includes @@ -105,6 +108,11 @@ query.addQueryItem(optionKey, options); dbURL.setQuery(query); } + break; + case 2: // bad password + case 3: // unsupported operation + delete storage; + return nullptr; } } // single user mode; read some of the data into memory @@ -227,17 +235,26 @@ auto url = dialog->selectedURL(); QUrl newurl = url; if ((newurl.scheme() == QLatin1String("sql"))) { - const QString key = QLatin1String("driver"); + const auto key = QLatin1String("driver"); // take care and convert some old url to their new counterpart QUrlQuery query(newurl); if (query.queryItemValue(key) == QLatin1String("QMYSQL3")) { // fix any old urls query.removeQueryItem(key); query.addQueryItem(key, QLatin1String("QMYSQL")); - } - if (query.queryItemValue(key) == QLatin1String("QSQLITE3")) { + } else if (query.queryItemValue(key) == QLatin1String("QSQLITE3")) { query.removeQueryItem(key); query.addQueryItem(key, QLatin1String("QSQLITE")); } +#ifdef ENABLE_SQLCIPHER + // Reading unencrypted database with QSQLITE + // while QSQLCIPHER is available causes crash. + // QSQLCIPHER can read QSQLITE + if (query.queryItemValue(key) == QLatin1String("QSQLITE")) { + query.removeQueryItem(key); + query.addQueryItem(key, QLatin1String("QSQLCIPHER")); + } +#endif + newurl.setQuery(query); // check if a password is needed. it may be if the URL came from the last/recent file list @@ -291,6 +308,10 @@ return false; } break; + case 2: // bad password + case 3: // unsupported operation + delete writer; + return false; } if (canWrite) { delete writer; diff --git a/kmymoney/plugins/sqlcipher/CMakeLists.txt b/kmymoney/plugins/sqlcipher/CMakeLists.txt --- a/kmymoney/plugins/sqlcipher/CMakeLists.txt +++ b/kmymoney/plugins/sqlcipher/CMakeLists.txt @@ -1,69 +1,47 @@ -# Copyright (c) 2014, Christian Dávid, -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# qsql_sqlite between Qt versions aren't necessarily compatible and can fail to compile +# qsql_sqlite is copied from https://github.com/sjemens/qsqlcipher-qt5 +if (Qt5Core_VERSION VERSION_GREATER 5.10.99) + set (QSQLITE_VERSION 5.11) #untested -project(QSqlcipherDriverPlugin) +elseif (Qt5Core_VERSION VERSION_GREATER 5.9.99) + set (QSQLITE_VERSION 5.10) #tested -add_definitions(${QT_DEFINITIONS}) +elseif (Qt5Core_VERSION VERSION_GREATER 5.7.99) + set (QSQLITE_VERSION 5.9) #untested -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) +else() + set (QSQLITE_VERSION 5.7) #untested -find_package(SQLCipher REQUIRED) -find_package(QSQLiteSource REQUIRED) +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/QSQLite/${QSQLITE_VERSION}/qsql_sqlite_p.h ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/QSQLite/${QSQLITE_VERSION}/qsql_sqlite.cpp ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) -set ( sqlcipherdriver_srcs - qsqlcipherdriverplugin.cpp - sqlcipherdriver.cpp - ${QSQLITESOURCE_SRCS_DIRS}/qsql_sqlite.cpp +set ( qsqlcipher_SOURCES + smain.cpp + qsql_sqlite.cpp ) -set ( sqlcipherdriver_header - qsqlcipherdriverplugin.h - sqlcipherdriver.h - ${QSQLITESOURCE_SRCS_DIRS}/qsql_sqlite.h +set ( qsqlcipher_HEADERS + qsql_sqlite_p.h ) -include_directories(${QSQLITESOURCE_INCLUDE_DIRS}) -include_directories(${SQLCIPHER_INCLUDE_DIR}) +kcoreaddons_add_plugin(qsqlcipher + SOURCES ${qsqlcipher_SOURCES} + JSON "${CMAKE_CURRENT_SOURCE_DIR}/sqlcipher.json" + INSTALL_NAMESPACE "sqldrivers") -qt4_automoc(${sqlcipherdriver_srcs}) -qt4_wrap_cpp(sqlcipherdriver_moc ${sqlcipherdriver_header}) -add_library(sqlcipherdriver SHARED ${sqlcipherdriver_header} ${sqlcipherdriver_srcs} ${sqlcipherdriver_moc}) +#kcoreaddons_add_plugin sets LIBRARY_OUTPUT_DIRECTORY to ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${INSTALL_NAMESPACE} +set_target_properties(qsqlcipher + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") -target_link_libraries(sqlcipherdriver PUBLIC - ${QT_LIBRARIES} - ${QT_QTCORE_LIBRARIES} - Qt5::Sql - ${SQLCIPHER_LIBRARIES} +target_link_libraries(qsqlcipher + PRIVATE + sqlcipher + Qt5::SqlPrivate ) if(BUILD_TESTING) - add_subdirectory(tests) + add_subdirectory(tests) endif() - -#### Install files #### - -install(TARGETS sqlcipherdriver - DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/sqldrivers/ -) diff --git a/kmymoney/plugins/sqlcipher/QSQLite/5.10/qsql_sqlite.cpp b/kmymoney/plugins/sqlcipher/QSQLite/5.10/qsql_sqlite.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/QSQLite/5.10/qsql_sqlite.cpp @@ -0,0 +1,1037 @@ +//krazy:skip +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_sqlite_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef QT_NO_REGULAREXPRESSION +#include +#include +#endif +#include + +#if defined Q_OS_WIN +# include +#else +# include +#endif + +#include +#include + +Q_DECLARE_OPAQUE_POINTER(sqlite3*) +Q_DECLARE_METATYPE(sqlite3*) + +Q_DECLARE_OPAQUE_POINTER(sqlite3_stmt*) +Q_DECLARE_METATYPE(sqlite3_stmt*) + +QT_BEGIN_NAMESPACE + +static QString _q_escapeIdentifier(const QString &identifier) +{ + QString res = identifier; + if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"'))) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +static QVariant::Type qGetColumnType(const QString &tpName) +{ + const QString typeName = tpName.toLower(); + + if (typeName == QLatin1String("integer") + || typeName == QLatin1String("int")) + return QVariant::Int; + if (typeName == QLatin1String("double") + || typeName == QLatin1String("float") + || typeName == QLatin1String("real") + || typeName.startsWith(QLatin1String("numeric"))) + return QVariant::Double; + if (typeName == QLatin1String("blob")) + return QVariant::ByteArray; + if (typeName == QLatin1String("boolean") + || typeName == QLatin1String("bool")) + return QVariant::Bool; + return QVariant::String; +} + +static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, + int errorCode = -1) +{ + return QSqlError(descr, + QString(reinterpret_cast(sqlite3_errmsg16(access))), + type, QString::number(errorCode)); +} + +class QSQLiteResultPrivate; + +class QSQLiteResult : public QSqlCachedResult +{ + Q_DECLARE_PRIVATE(QSQLiteResult) + friend class QSQLiteDriver; + +public: + explicit QSQLiteResult(const QSQLiteDriver* db); + ~QSQLiteResult(); + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache& row, int idx) Q_DECL_OVERRIDE; + bool reset(const QString &query) Q_DECL_OVERRIDE; + bool prepare(const QString &query) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QVariant lastInsertId() const Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + void detachFromResultSet() Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; +}; + +class QSQLiteDriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QSQLiteDriver) + +public: + inline QSQLiteDriverPrivate() : QSqlDriverPrivate(), access(0) { dbmsType = QSqlDriver::SQLite; } + sqlite3 *access; + QList results; + QStringList notificationid; +}; + + +class QSQLiteResultPrivate: public QSqlCachedResultPrivate +{ + Q_DECLARE_PUBLIC(QSQLiteResult) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QSQLiteDriver) + QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv); + void cleanup(); + bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); + // initializes the recordInfo and the cache + void initColumns(bool emptyResultset); + void finalize(); + + sqlite3_stmt *stmt; + + bool skippedStatus; // the status of the fetchNext() that's skipped + bool skipRow; // skip the next fetchNext()? + QSqlRecord rInf; + QVector firstRow; +}; + +QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv) + : QSqlCachedResultPrivate(q, drv), + stmt(0), + skippedStatus(false), + skipRow(false) +{ +} + +void QSQLiteResultPrivate::cleanup() +{ + Q_Q(QSQLiteResult); + finalize(); + rInf.clear(); + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirstRow); + q->setActive(false); + q->cleanup(); +} + +void QSQLiteResultPrivate::finalize() +{ + if (!stmt) + return; + + sqlite3_finalize(stmt); + stmt = 0; +} + +void QSQLiteResultPrivate::initColumns(bool emptyResultset) +{ + Q_Q(QSQLiteResult); + int nCols = sqlite3_column_count(stmt); + if (nCols <= 0) + return; + + q->init(nCols); + + for (int i = 0; i < nCols; ++i) { + QString colName = QString(reinterpret_cast( + sqlite3_column_name16(stmt, i)) + ).remove(QLatin1Char('"')); + + // must use typeName for resolving the type to match QSqliteDriver::record + QString typeName = QString(reinterpret_cast( + sqlite3_column_decltype16(stmt, i))); + // sqlite3_column_type is documented to have undefined behavior if the result set is empty + int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); + + QVariant::Type fieldType; + + if (!typeName.isEmpty()) { + fieldType = qGetColumnType(typeName); + } else { + // Get the proper type for the field based on stp value + switch (stp) { + case SQLITE_INTEGER: + fieldType = QVariant::Int; + break; + case SQLITE_FLOAT: + fieldType = QVariant::Double; + break; + case SQLITE_BLOB: + fieldType = QVariant::ByteArray; + break; + case SQLITE_TEXT: + fieldType = QVariant::String; + break; + case SQLITE_NULL: + default: + fieldType = QVariant::Invalid; + break; + } + } + + QSqlField fld(colName, fieldType); + fld.setSqlType(stp); + rInf.append(fld); + } +} + +bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + Q_Q(QSQLiteResult); + int res; + int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + for(int i=0;isetLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), + QCoreApplication::translate("QSQLiteResult", "No query"), QSqlError::ConnectionError)); + q->setAt(QSql::AfterLastRow); + return false; + } + res = sqlite3_step(stmt); + + switch(res) { + case SQLITE_ROW: + // check to see if should fill out columns + if (rInf.isEmpty()) + // must be first call. + initColumns(false); + if (idx < 0 && !initialFetch) + return true; + for (i = 0; i < rInf.count(); ++i) { + switch (sqlite3_column_type(stmt, i)) { + case SQLITE_BLOB: + values[i + idx] = QByteArray(static_cast( + sqlite3_column_blob(stmt, i)), + sqlite3_column_bytes(stmt, i)); + break; + case SQLITE_INTEGER: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case SQLITE_FLOAT: + switch(q->numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + values[i + idx] = sqlite3_column_int(stmt, i); + break; + case QSql::LowPrecisionInt64: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case QSql::LowPrecisionDouble: + case QSql::HighPrecision: + default: + values[i + idx] = sqlite3_column_double(stmt, i); + break; + }; + break; + case SQLITE_NULL: + values[i + idx] = QVariant(QVariant::String); + break; + default: + values[i + idx] = QString(reinterpret_cast( + sqlite3_column_text16(stmt, i)), + sqlite3_column_bytes16(stmt, i) / sizeof(QChar)); + break; + } + } + return true; + case SQLITE_DONE: + if (rInf.isEmpty()) + // must be first call. + initColumns(true); + q->setAt(QSql::AfterLastRow); + sqlite3_reset(stmt); + return false; + case SQLITE_CONSTRAINT: + case SQLITE_ERROR: + // SQLITE_ERROR is a generic error code and we must call sqlite3_reset() + // to get the specific error message. + res = sqlite3_reset(stmt); + q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + q->setAt(QSql::AfterLastRow); + return false; + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + // something wrong, don't get col info, but still return false + q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + sqlite3_reset(stmt); + q->setAt(QSql::AfterLastRow); + return false; + } + return false; +} + +QSQLiteResult::QSQLiteResult(const QSQLiteDriver* db) + : QSqlCachedResult(*new QSQLiteResultPrivate(this, db)) +{ + Q_D(QSQLiteResult); + const_cast(d->drv_d_func())->results.append(this); +} + +QSQLiteResult::~QSQLiteResult() +{ + Q_D(QSQLiteResult); + if (d->drv_d_func()) + const_cast(d->drv_d_func())->results.removeOne(this); + d->cleanup(); +} + +void QSQLiteResult::virtual_hook(int id, void *data) +{ + QSqlCachedResult::virtual_hook(id, data); +} + +bool QSQLiteResult::reset(const QString &query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QSQLiteResult::prepare(const QString &query) +{ + Q_D(QSQLiteResult); + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + + d->cleanup(); + + setSelect(false); + + const void *pzTail = NULL; + +#if (SQLITE_VERSION_NUMBER >= 3003011) + int res = sqlite3_prepare16_v2(d->drv_d_func()->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#else + int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#endif + + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } else if (pzTail && !QString(reinterpret_cast(pzTail)).trimmed().isEmpty()) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute multiple statements at a time"), QSqlError::StatementError, SQLITE_MISUSE)); + d->finalize(); + return false; + } + return true; +} + +static QString secondsToOffset(int seconds) +{ + const QChar sign = ushort(seconds < 0 ? '-' : '+'); + seconds = qAbs(seconds); + const int hours = seconds / 3600; + const int minutes = (seconds % 3600) / 60; + + return QString(QStringLiteral("%1%2:%3")).arg(sign).arg(hours, 2, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')); +} + +static QString timespecToString(const QDateTime &dateTime) +{ + switch (dateTime.timeSpec()) { + case Qt::LocalTime: + return QString(); + case Qt::UTC: + return QStringLiteral("Z"); + case Qt::OffsetFromUTC: + return secondsToOffset(dateTime.offsetFromUtc()); + case Qt::TimeZone: + return secondsToOffset(dateTime.timeZone().offsetFromUtc(dateTime)); + default: + return QString(); + } +} + +bool QSQLiteResult::exec() +{ + Q_D(QSQLiteResult); + QVector values = boundValues(); + + d->skippedStatus = false; + d->skipRow = false; + d->rInf.clear(); + clearValues(); + setLastError(QSqlError()); + + int res = sqlite3_reset(d->stmt); + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to reset statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + + int paramCount = sqlite3_bind_parameter_count(d->stmt); + bool paramCountIsValid = paramCount == values.count(); + +#if (SQLITE_VERSION_NUMBER >= 3003011) + // In the case of the reuse of a named placeholder + if (paramCount < values.count()) { + const auto countIndexes = [](int counter, const QList& indexList) { + return counter + indexList.length(); + }; + + const int bindParamCount = std::accumulate(d->indexes.cbegin(), + d->indexes.cend(), + 0, + countIndexes); + + paramCountIsValid = bindParamCount == values.count(); + // When using named placeholders, it will reuse the index for duplicated + // placeholders. So we need to ensure the QVector has only one instance of + // each value as SQLite will do the rest for us. + QVector prunedValues; + QList handledIndexes; + for (int i = 0, currentIndex = 0; i < values.size(); ++i) { + if (handledIndexes.contains(i)) + continue; + const auto placeHolder = QString::fromUtf8(sqlite3_bind_parameter_name(d->stmt, currentIndex + 1)); + handledIndexes << d->indexes[placeHolder]; + prunedValues << values.at(d->indexes[placeHolder].first()); + ++currentIndex; + } + values = prunedValues; + } +#endif + + if (paramCountIsValid) { + for (int i = 0; i < paramCount; ++i) { + res = SQLITE_OK; + const QVariant value = values.at(i); + + if (value.isNull()) { + res = sqlite3_bind_null(d->stmt, i + 1); + } else { + switch (value.type()) { + case QVariant::ByteArray: { + const QByteArray *ba = static_cast(value.constData()); + res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(), + ba->size(), SQLITE_STATIC); + break; } + case QVariant::Int: + case QVariant::Bool: + res = sqlite3_bind_int(d->stmt, i + 1, value.toInt()); + break; + case QVariant::Double: + res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble()); + break; + case QVariant::UInt: + case QVariant::LongLong: + res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong()); + break; + case QVariant::DateTime: { + const QDateTime dateTime = value.toDateTime(); + const QString str = dateTime.toString(QLatin1String("yyyy-MM-ddThh:mm:ss.zzz") + timespecToString(dateTime)); + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + str.size() * sizeof(ushort), SQLITE_TRANSIENT); + break; + } + case QVariant::Time: { + const QTime time = value.toTime(); + const QString str = time.toString(QStringViewLiteral("hh:mm:ss.zzz")); + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + str.size() * sizeof(ushort), SQLITE_TRANSIENT); + break; + } + case QVariant::String: { + // lifetime of string == lifetime of its qvariant + const QString *str = static_cast(value.constData()); + res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(), + (str->size()) * sizeof(QChar), SQLITE_STATIC); + break; } + default: { + QString str = value.toString(); + // SQLITE_TRANSIENT makes sure that sqlite buffers the data + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); + break; } + } + } + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to bind parameters"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + } + } else { + setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", + "Parameter count mismatch"), QString(), QSqlError::StatementError)); + return false; + } + d->skippedStatus = d->fetchNext(d->firstRow, 0, true); + if (lastError().isValid()) { + setSelect(false); + setActive(false); + return false; + } + setSelect(!d->rInf.isEmpty()); + setActive(true); + return true; +} + +bool QSQLiteResult::gotoNext(QSqlCachedResult::ValueCache& row, int idx) +{ + Q_D(QSQLiteResult); + return d->fetchNext(row, idx, false); +} + +int QSQLiteResult::size() +{ + return -1; +} + +int QSQLiteResult::numRowsAffected() +{ + Q_D(const QSQLiteResult); + return sqlite3_changes(d->drv_d_func()->access); +} + +QVariant QSQLiteResult::lastInsertId() const +{ + Q_D(const QSQLiteResult); + if (isActive()) { + qint64 id = sqlite3_last_insert_rowid(d->drv_d_func()->access); + if (id) + return id; + } + return QVariant(); +} + +QSqlRecord QSQLiteResult::record() const +{ + Q_D(const QSQLiteResult); + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +void QSQLiteResult::detachFromResultSet() +{ + Q_D(QSQLiteResult); + if (d->stmt) + sqlite3_reset(d->stmt); +} + +QVariant QSQLiteResult::handle() const +{ + Q_D(const QSQLiteResult); + return QVariant::fromValue(d->stmt); +} + +///////////////////////////////////////////////////////// + +#ifndef QT_NO_REGULAREXPRESSION +static void _q_regexp(sqlite3_context* context, int argc, sqlite3_value** argv) +{ + if (Q_UNLIKELY(argc != 2)) { + sqlite3_result_int(context, 0); + return; + } + + const QString pattern = QString::fromUtf8( + reinterpret_cast(sqlite3_value_text(argv[0]))); + const QString subject = QString::fromUtf8( + reinterpret_cast(sqlite3_value_text(argv[1]))); + + auto cache = static_cast*>(sqlite3_user_data(context)); + auto regexp = cache->object(pattern); + const bool wasCached = regexp; + + if (!wasCached) + regexp = new QRegularExpression(pattern, QRegularExpression::DontCaptureOption | QRegularExpression::OptimizeOnFirstUsageOption); + + const bool found = subject.contains(*regexp); + + if (!wasCached) + cache->insert(pattern, regexp); + + sqlite3_result_int(context, int(found)); +} + +static void _q_regexp_cleanup(void *cache) +{ + delete static_cast*>(cache); +} +#endif + +QSQLiteDriver::QSQLiteDriver(QObject * parent) + : QSqlDriver(*new QSQLiteDriverPrivate, parent) +{ +} + +QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent) + : QSqlDriver(*new QSQLiteDriverPrivate, parent) +{ + Q_D(QSQLiteDriver); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLiteDriver::~QSQLiteDriver() +{ + close(); +} + +bool QSQLiteDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case BLOB: + case Transactions: + case Unicode: + case LastInsertId: + case PreparedQueries: + case PositionalPlaceholders: + case SimpleLocking: + case FinishQuery: + case LowPrecisionNumbers: + case EventNotifications: + return true; + case QuerySize: + case BatchOperations: + case MultipleResultSets: + case CancelQuery: + return false; + case NamedPlaceholders: +#if (SQLITE_VERSION_NUMBER < 3003011) + return false; +#else + return true; +#endif + + } + return false; +} + +/* + SQLite dbs have no user name, passwords, hosts or ports. + just file names. +*/ +bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts) +{ + Q_D(QSQLiteDriver); + if (isOpen()) + close(); + + + int timeOut = 5000; + bool sharedCache = false; + bool openReadOnlyOption = false; + bool openUriOption = false; +#ifndef QT_NO_REGULAREXPRESSION + static const QLatin1String regexpConnectOption = QLatin1String("QSQLITE_ENABLE_REGEXP"); + bool defineRegexp = false; + int regexpCacheSize = 25; +#endif + + const auto opts = conOpts.splitRef(QLatin1Char(';')); + for (auto option : opts) { + option = option.trimmed(); + if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT"))) { + option = option.mid(20).trimmed(); + if (option.startsWith(QLatin1Char('='))) { + bool ok; + const int nt = option.mid(1).trimmed().toInt(&ok); + if (ok) + timeOut = nt; + } + } else if (option == QLatin1String("QSQLITE_OPEN_READONLY")) { + openReadOnlyOption = true; + } else if (option == QLatin1String("QSQLITE_OPEN_URI")) { + openUriOption = true; + } else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) { + sharedCache = true; + } +#ifndef QT_NO_REGULAREXPRESSION + else if (option.startsWith(regexpConnectOption)) { + option = option.mid(regexpConnectOption.size()).trimmed(); + if (option.isEmpty()) { + defineRegexp = true; + } else if (option.startsWith(QLatin1Char('='))) { + bool ok = false; + const int cacheSize = option.mid(1).trimmed().toInt(&ok); + if (ok) { + defineRegexp = true; + if (cacheSize > 0) + regexpCacheSize = cacheSize; + } + } + } +#endif + } + + int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); + openMode |= (sharedCache ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE); + if (openUriOption) + openMode |= SQLITE_OPEN_URI; + + openMode |= SQLITE_OPEN_NOMUTEX; + + if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL) == SQLITE_OK) { + sqlite3_busy_timeout(d->access, timeOut); + setOpen(true); + setOpenError(false); +#ifndef QT_NO_REGULAREXPRESSION + if (defineRegexp) { + auto cache = new QCache(regexpCacheSize); + sqlite3_create_function_v2(d->access, "regexp", 2, SQLITE_UTF8, cache, &_q_regexp, NULL, + NULL, &_q_regexp_cleanup); + } +#endif + return true; + } else { + if (d->access) { + sqlite3_close(d->access); + d->access = 0; + } + + setLastError(qMakeError(d->access, tr("Error opening database"), + QSqlError::ConnectionError)); + setOpenError(true); + return false; + } +} + +void QSQLiteDriver::close() +{ + Q_D(QSQLiteDriver); + if (isOpen()) { + for (QSQLiteResult *result : qAsConst(d->results)) + result->d_func()->finalize(); + + if (d->access && (d->notificationid.count() > 0)) { + d->notificationid.clear(); + sqlite3_update_hook(d->access, NULL, NULL); + } + + if (sqlite3_close(d->access) != SQLITE_OK) + setLastError(qMakeError(d->access, tr("Error closing database"), QSqlError::ConnectionError)); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QSQLiteDriver::createResult() const +{ + return new QSQLiteResult(this); +} + +bool QSQLiteDriver::beginTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("BEGIN"))) { + setLastError(QSqlError(tr("Unable to begin transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::commitTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("COMMIT"))) { + setLastError(QSqlError(tr("Unable to commit transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::rollbackTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("ROLLBACK"))) { + setLastError(QSqlError(tr("Unable to rollback transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +QStringList QSQLiteDriver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + + QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 " + "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"); + if ((type & QSql::Tables) && (type & QSql::Views)) + sql = sql.arg(QLatin1String("type='table' OR type='view'")); + else if (type & QSql::Tables) + sql = sql.arg(QLatin1String("type='table'")); + else if (type & QSql::Views) + sql = sql.arg(QLatin1String("type='view'")); + else + sql.clear(); + + if (!sql.isEmpty() && q.exec(sql)) { + while(q.next()) + res.append(q.value(0).toString()); + } + + if (type & QSql::SystemTables) { + // there are no internal tables beside this one: + res.append(QLatin1String("sqlite_master")); + } + + return res; +} + +static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) +{ + QString schema; + QString table(tableName); + int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); + if (indexOfSeparator > -1) { + schema = tableName.left(indexOfSeparator).append(QLatin1Char('.')); + table = tableName.mid(indexOfSeparator + 1); + } + q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(table) + QLatin1Char(')')); + + QSqlIndex ind; + while (q.next()) { + bool isPk = q.value(5).toInt(); + if (onlyPIndex && !isPk) + continue; + QString typeName = q.value(2).toString().toLower(); + QSqlField fld(q.value(1).toString(), qGetColumnType(typeName), tableName); + if (isPk && (typeName == QLatin1String("integer"))) + // INTEGER PRIMARY KEY fields are auto-generated in sqlite + // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY! + fld.setAutoValue(true); + fld.setRequired(q.value(3).toInt() != 0); + fld.setDefaultValue(q.value(4)); + ind.append(fld); + } + return ind; +} + +QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const +{ + if (!isOpen()) + return QSqlIndex(); + + QString table = tblname; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, table, true); +} + +QSqlRecord QSQLiteDriver::record(const QString &tbl) const +{ + if (!isOpen()) + return QSqlRecord(); + + QString table = tbl; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, table); +} + +QVariant QSQLiteDriver::handle() const +{ + Q_D(const QSQLiteDriver); + return QVariant::fromValue(d->access); +} + +QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const +{ + Q_UNUSED(type); + return _q_escapeIdentifier(identifier); +} + +static void handle_sqlite_callback(void *qobj,int aoperation, char const *adbname, char const *atablename, + sqlite3_int64 arowid) +{ + Q_UNUSED(aoperation); + Q_UNUSED(adbname); + QSQLiteDriver *driver = static_cast(qobj); + if (driver) { + QMetaObject::invokeMethod(driver, "handleNotification", Qt::QueuedConnection, + Q_ARG(QString, QString::fromUtf8(atablename)), Q_ARG(qint64, arowid)); + } +} + +bool QSQLiteDriver::subscribeToNotification(const QString &name) +{ + Q_D(QSQLiteDriver); + if (!isOpen()) { + qWarning("Database not open."); + return false; + } + + if (d->notificationid.contains(name)) { + qWarning("Already subscribing to '%s'.", qPrintable(name)); + return false; + } + + //sqlite supports only one notification callback, so only the first is registered + d->notificationid << name; + if (d->notificationid.count() == 1) + sqlite3_update_hook(d->access, &handle_sqlite_callback, reinterpret_cast (this)); + + return true; +} + +bool QSQLiteDriver::unsubscribeFromNotification(const QString &name) +{ + Q_D(QSQLiteDriver); + if (!isOpen()) { + qWarning("Database not open."); + return false; + } + + if (!d->notificationid.contains(name)) { + qWarning("Not subscribed to '%s'.", qPrintable(name)); + return false; + } + + d->notificationid.removeAll(name); + if (d->notificationid.isEmpty()) + sqlite3_update_hook(d->access, NULL, NULL); + + return true; +} + +QStringList QSQLiteDriver::subscribedToNotifications() const +{ + Q_D(const QSQLiteDriver); + return d->notificationid; +} + +void QSQLiteDriver::handleNotification(const QString &tableName, qint64 rowid) +{ + Q_D(const QSQLiteDriver); + if (d->notificationid.contains(tableName)) { + emit notification(tableName); + emit notification(tableName, QSqlDriver::UnknownSource, QVariant(rowid)); + } +} + +QT_END_NAMESPACE diff --git a/kmymoney/plugins/sqlcipher/QSQLite/5.10/qsql_sqlite_p.h b/kmymoney/plugins/sqlcipher/QSQLite/5.10/qsql_sqlite_p.h new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/QSQLite/5.10/qsql_sqlite_p.h @@ -0,0 +1,107 @@ +//krazy:skip +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_SQLITE_H +#define QSQL_SQLITE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +struct sqlite3; + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_SQLITE +#else +#define Q_EXPORT_SQLDRIVER_SQLITE Q_SQL_EXPORT +#endif + +QT_BEGIN_NAMESPACE + +class QSqlResult; +class QSQLiteDriverPrivate; + +class Q_EXPORT_SQLDRIVER_SQLITE QSQLiteDriver : public QSqlDriver +{ + Q_DECLARE_PRIVATE(QSQLiteDriver) + Q_OBJECT + friend class QSQLiteResultPrivate; +public: + explicit QSQLiteDriver(QObject *parent = 0); + explicit QSQLiteDriver(sqlite3 *connection, QObject *parent = 0); + ~QSQLiteDriver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString & connOpts) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + + QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString &table) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE; + + bool subscribeToNotification(const QString &name) Q_DECL_OVERRIDE; + bool unsubscribeFromNotification(const QString &name) Q_DECL_OVERRIDE; + QStringList subscribedToNotifications() const Q_DECL_OVERRIDE; +private Q_SLOTS: + void handleNotification(const QString &tableName, qint64 rowid); +}; + +QT_END_NAMESPACE + +#endif // QSQL_SQLITE_H diff --git a/kmymoney/plugins/sqlcipher/QSQLite/5.11/qsql_sqlite.cpp b/kmymoney/plugins/sqlcipher/QSQLite/5.11/qsql_sqlite.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/QSQLite/5.11/qsql_sqlite.cpp @@ -0,0 +1,1081 @@ +//krazy:skip +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_sqlite_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_CONFIG(regularexpression) +#include +#include +#endif +#if QT_CONFIG(timezone) +#include +#endif +#include + +#if defined Q_OS_WIN +# include +#else +# include +#endif + +#include +#include + +Q_DECLARE_OPAQUE_POINTER(sqlite3*) +Q_DECLARE_METATYPE(sqlite3*) + +Q_DECLARE_OPAQUE_POINTER(sqlite3_stmt*) +Q_DECLARE_METATYPE(sqlite3_stmt*) + +QT_BEGIN_NAMESPACE + +static QString _q_escapeIdentifier(const QString &identifier) +{ + QString res = identifier; + if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"'))) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +static QVariant::Type qGetColumnType(const QString &tpName) +{ + const QString typeName = tpName.toLower(); + + if (typeName == QLatin1String("integer") + || typeName == QLatin1String("int")) + return QVariant::Int; + if (typeName == QLatin1String("double") + || typeName == QLatin1String("float") + || typeName == QLatin1String("real") + || typeName.startsWith(QLatin1String("numeric"))) + return QVariant::Double; + if (typeName == QLatin1String("blob")) + return QVariant::ByteArray; + if (typeName == QLatin1String("boolean") + || typeName == QLatin1String("bool")) + return QVariant::Bool; + return QVariant::String; +} + +static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, + int errorCode = -1) +{ + return QSqlError(descr, + QString(reinterpret_cast(sqlite3_errmsg16(access))), + type, QString::number(errorCode)); +} + +class QSQLiteResultPrivate; + +class QSQLiteResult : public QSqlCachedResult +{ + Q_DECLARE_PRIVATE(QSQLiteResult) + friend class QSQLiteDriver; + +public: + explicit QSQLiteResult(const QSQLiteDriver* db); + ~QSQLiteResult(); + QVariant handle() const override; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache& row, int idx) override; + bool reset(const QString &query) override; + bool prepare(const QString &query) override; + bool execBatch(bool arrayBind) override; + bool exec() override; + int size() override; + int numRowsAffected() override; + QVariant lastInsertId() const override; + QSqlRecord record() const override; + void detachFromResultSet() override; + void virtual_hook(int id, void *data) override; +}; + +class QSQLiteDriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QSQLiteDriver) + +public: + inline QSQLiteDriverPrivate() : QSqlDriverPrivate(), access(0) { dbmsType = QSqlDriver::SQLite; } + sqlite3 *access; + QList results; + QStringList notificationid; +}; + + +class QSQLiteResultPrivate: public QSqlCachedResultPrivate +{ + Q_DECLARE_PUBLIC(QSQLiteResult) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QSQLiteDriver) + QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv); + void cleanup(); + bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); + // initializes the recordInfo and the cache + void initColumns(bool emptyResultset); + void finalize(); + + sqlite3_stmt *stmt; + + bool skippedStatus; // the status of the fetchNext() that's skipped + bool skipRow; // skip the next fetchNext()? + QSqlRecord rInf; + QVector firstRow; +}; + +QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv) + : QSqlCachedResultPrivate(q, drv), + stmt(0), + skippedStatus(false), + skipRow(false) +{ +} + +void QSQLiteResultPrivate::cleanup() +{ + Q_Q(QSQLiteResult); + finalize(); + rInf.clear(); + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirstRow); + q->setActive(false); + q->cleanup(); +} + +void QSQLiteResultPrivate::finalize() +{ + if (!stmt) + return; + + sqlite3_finalize(stmt); + stmt = 0; +} + +void QSQLiteResultPrivate::initColumns(bool emptyResultset) +{ + Q_Q(QSQLiteResult); + int nCols = sqlite3_column_count(stmt); + if (nCols <= 0) + return; + + q->init(nCols); + + for (int i = 0; i < nCols; ++i) { + QString colName = QString(reinterpret_cast( + sqlite3_column_name16(stmt, i)) + ).remove(QLatin1Char('"')); +#ifdef ENABLE_COLUMN_METADATA + const QString tableName = QString(reinterpret_cast( + sqlite3_column_table_name16(stmt, i)) + ).remove(QLatin1Char('"')); +#endif + // must use typeName for resolving the type to match QSqliteDriver::record + QString typeName = QString(reinterpret_cast( + sqlite3_column_decltype16(stmt, i))); + // sqlite3_column_type is documented to have undefined behavior if the result set is empty + int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); + + QVariant::Type fieldType; + + if (!typeName.isEmpty()) { + fieldType = qGetColumnType(typeName); + } else { + // Get the proper type for the field based on stp value + switch (stp) { + case SQLITE_INTEGER: + fieldType = QVariant::Int; + break; + case SQLITE_FLOAT: + fieldType = QVariant::Double; + break; + case SQLITE_BLOB: + fieldType = QVariant::ByteArray; + break; + case SQLITE_TEXT: + fieldType = QVariant::String; + break; + case SQLITE_NULL: + default: + fieldType = QVariant::Invalid; + break; + } + } +#ifdef ENABLE_COLUMN_METADATA + QSqlField fld(colName, fieldType, tableName); +#else + QSqlField fld(colName, fieldType); +#endif + fld.setSqlType(stp); + rInf.append(fld); + } +} + +bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + Q_Q(QSQLiteResult); + int res; + int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + for(int i=0;isetLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), + QCoreApplication::translate("QSQLiteResult", "No query"), QSqlError::ConnectionError)); + q->setAt(QSql::AfterLastRow); + return false; + } + res = sqlite3_step(stmt); + + switch(res) { + case SQLITE_ROW: + // check to see if should fill out columns + if (rInf.isEmpty()) + // must be first call. + initColumns(false); + if (idx < 0 && !initialFetch) + return true; + for (i = 0; i < rInf.count(); ++i) { + switch (sqlite3_column_type(stmt, i)) { + case SQLITE_BLOB: + values[i + idx] = QByteArray(static_cast( + sqlite3_column_blob(stmt, i)), + sqlite3_column_bytes(stmt, i)); + break; + case SQLITE_INTEGER: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case SQLITE_FLOAT: + switch(q->numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + values[i + idx] = sqlite3_column_int(stmt, i); + break; + case QSql::LowPrecisionInt64: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case QSql::LowPrecisionDouble: + case QSql::HighPrecision: + default: + values[i + idx] = sqlite3_column_double(stmt, i); + break; + }; + break; + case SQLITE_NULL: + values[i + idx] = QVariant(QVariant::String); + break; + default: + values[i + idx] = QString(reinterpret_cast( + sqlite3_column_text16(stmt, i)), + sqlite3_column_bytes16(stmt, i) / sizeof(QChar)); + break; + } + } + return true; + case SQLITE_DONE: + if (rInf.isEmpty()) + // must be first call. + initColumns(true); + q->setAt(QSql::AfterLastRow); + sqlite3_reset(stmt); + return false; + case SQLITE_CONSTRAINT: + case SQLITE_ERROR: + // SQLITE_ERROR is a generic error code and we must call sqlite3_reset() + // to get the specific error message. + res = sqlite3_reset(stmt); + q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + q->setAt(QSql::AfterLastRow); + return false; + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + // something wrong, don't get col info, but still return false + q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + sqlite3_reset(stmt); + q->setAt(QSql::AfterLastRow); + return false; + } + return false; +} + +QSQLiteResult::QSQLiteResult(const QSQLiteDriver* db) + : QSqlCachedResult(*new QSQLiteResultPrivate(this, db)) +{ + Q_D(QSQLiteResult); + const_cast(d->drv_d_func())->results.append(this); +} + +QSQLiteResult::~QSQLiteResult() +{ + Q_D(QSQLiteResult); + if (d->drv_d_func()) + const_cast(d->drv_d_func())->results.removeOne(this); + d->cleanup(); +} + +void QSQLiteResult::virtual_hook(int id, void *data) +{ + QSqlCachedResult::virtual_hook(id, data); +} + +bool QSQLiteResult::reset(const QString &query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QSQLiteResult::prepare(const QString &query) +{ + Q_D(QSQLiteResult); + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + + d->cleanup(); + + setSelect(false); + + const void *pzTail = NULL; + +#if (SQLITE_VERSION_NUMBER >= 3003011) + int res = sqlite3_prepare16_v2(d->drv_d_func()->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#else + int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#endif + + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } else if (pzTail && !QString(reinterpret_cast(pzTail)).trimmed().isEmpty()) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute multiple statements at a time"), QSqlError::StatementError, SQLITE_MISUSE)); + d->finalize(); + return false; + } + return true; +} + +static QString secondsToOffset(int seconds) +{ + const QChar sign = ushort(seconds < 0 ? '-' : '+'); + seconds = qAbs(seconds); + const int hours = seconds / 3600; + const int minutes = (seconds % 3600) / 60; + + return QString(QStringLiteral("%1%2:%3")).arg(sign).arg(hours, 2, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')); +} + +static QString timespecToString(const QDateTime &dateTime) +{ + switch (dateTime.timeSpec()) { + case Qt::LocalTime: + return QString(); + case Qt::UTC: + return QStringLiteral("Z"); + case Qt::OffsetFromUTC: + return secondsToOffset(dateTime.offsetFromUtc()); +#if QT_CONFIG(timezone) + case Qt::TimeZone: + return secondsToOffset(dateTime.timeZone().offsetFromUtc(dateTime)); +#endif + default: + return QString(); + } +} + +bool QSQLiteResult::execBatch(bool arrayBind) +{ + Q_UNUSED(arrayBind); + Q_D(QSqlResult); + QScopedValueRollback> valuesScope(d->values); + QVector values = d->values; + if (values.count() == 0) + return false; + + for (int i = 0; i < values.at(0).toList().count(); ++i) { + d->values.clear(); + QScopedValueRollback>> indexesScope(d->indexes); + QHash>::const_iterator it = d->indexes.constBegin(); + while (it != d->indexes.constEnd()) { + bindValue(it.key(), values.at(it.value().first()).toList().at(i), QSql::In); + ++it; + } + if (!exec()) + return false; + } + return true; +} + +bool QSQLiteResult::exec() +{ + Q_D(QSQLiteResult); + QVector values = boundValues(); + + d->skippedStatus = false; + d->skipRow = false; + d->rInf.clear(); + clearValues(); + setLastError(QSqlError()); + + int res = sqlite3_reset(d->stmt); + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to reset statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + + int paramCount = sqlite3_bind_parameter_count(d->stmt); + bool paramCountIsValid = paramCount == values.count(); + +#if (SQLITE_VERSION_NUMBER >= 3003011) + // In the case of the reuse of a named placeholder + // We need to check explicitly that paramCount is greater than or equal to 1, as sqlite + // can end up in a case where for virtual tables it returns 0 even though it + // has parameters + if (paramCount >= 1 && paramCount < values.count()) { + const auto countIndexes = [](int counter, const QVector &indexList) { + return counter + indexList.length(); + }; + + const int bindParamCount = std::accumulate(d->indexes.cbegin(), + d->indexes.cend(), + 0, + countIndexes); + + paramCountIsValid = bindParamCount == values.count(); + // When using named placeholders, it will reuse the index for duplicated + // placeholders. So we need to ensure the QVector has only one instance of + // each value as SQLite will do the rest for us. + QVector prunedValues; + QVector handledIndexes; + for (int i = 0, currentIndex = 0; i < values.size(); ++i) { + if (handledIndexes.contains(i)) + continue; + const auto placeHolder = QString::fromUtf8(sqlite3_bind_parameter_name(d->stmt, currentIndex + 1)); + const auto &indexes = d->indexes.value(placeHolder); + handledIndexes << indexes; + prunedValues << values.at(indexes.first()); + ++currentIndex; + } + values = prunedValues; + } +#endif + + if (paramCountIsValid) { + for (int i = 0; i < paramCount; ++i) { + res = SQLITE_OK; + const QVariant value = values.at(i); + + if (value.isNull()) { + res = sqlite3_bind_null(d->stmt, i + 1); + } else { + switch (value.type()) { + case QVariant::ByteArray: { + const QByteArray *ba = static_cast(value.constData()); + res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(), + ba->size(), SQLITE_STATIC); + break; } + case QVariant::Int: + case QVariant::Bool: + res = sqlite3_bind_int(d->stmt, i + 1, value.toInt()); + break; + case QVariant::Double: + res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble()); + break; + case QVariant::UInt: + case QVariant::LongLong: + res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong()); + break; + case QVariant::DateTime: { + const QDateTime dateTime = value.toDateTime(); + const QString str = dateTime.toString(QLatin1String("yyyy-MM-ddThh:mm:ss.zzz") + timespecToString(dateTime)); + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + str.size() * sizeof(ushort), SQLITE_TRANSIENT); + break; + } + case QVariant::Time: { + const QTime time = value.toTime(); + const QString str = time.toString(QStringViewLiteral("hh:mm:ss.zzz")); + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + str.size() * sizeof(ushort), SQLITE_TRANSIENT); + break; + } + case QVariant::String: { + // lifetime of string == lifetime of its qvariant + const QString *str = static_cast(value.constData()); + res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(), + (str->size()) * sizeof(QChar), SQLITE_STATIC); + break; } + default: { + QString str = value.toString(); + // SQLITE_TRANSIENT makes sure that sqlite buffers the data + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); + break; } + } + } + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to bind parameters"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + } + } else { + setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", + "Parameter count mismatch"), QString(), QSqlError::StatementError)); + return false; + } + d->skippedStatus = d->fetchNext(d->firstRow, 0, true); + if (lastError().isValid()) { + setSelect(false); + setActive(false); + return false; + } + setSelect(!d->rInf.isEmpty()); + setActive(true); + return true; +} + +bool QSQLiteResult::gotoNext(QSqlCachedResult::ValueCache& row, int idx) +{ + Q_D(QSQLiteResult); + return d->fetchNext(row, idx, false); +} + +int QSQLiteResult::size() +{ + return -1; +} + +int QSQLiteResult::numRowsAffected() +{ + Q_D(const QSQLiteResult); + return sqlite3_changes(d->drv_d_func()->access); +} + +QVariant QSQLiteResult::lastInsertId() const +{ + Q_D(const QSQLiteResult); + if (isActive()) { + qint64 id = sqlite3_last_insert_rowid(d->drv_d_func()->access); + if (id) + return id; + } + return QVariant(); +} + +QSqlRecord QSQLiteResult::record() const +{ + Q_D(const QSQLiteResult); + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +void QSQLiteResult::detachFromResultSet() +{ + Q_D(QSQLiteResult); + if (d->stmt) + sqlite3_reset(d->stmt); +} + +QVariant QSQLiteResult::handle() const +{ + Q_D(const QSQLiteResult); + return QVariant::fromValue(d->stmt); +} + +///////////////////////////////////////////////////////// + +#if QT_CONFIG(regularexpression) +static void _q_regexp(sqlite3_context* context, int argc, sqlite3_value** argv) +{ + if (Q_UNLIKELY(argc != 2)) { + sqlite3_result_int(context, 0); + return; + } + + const QString pattern = QString::fromUtf8( + reinterpret_cast(sqlite3_value_text(argv[0]))); + const QString subject = QString::fromUtf8( + reinterpret_cast(sqlite3_value_text(argv[1]))); + + auto cache = static_cast*>(sqlite3_user_data(context)); + auto regexp = cache->object(pattern); + const bool wasCached = regexp; + + if (!wasCached) + regexp = new QRegularExpression(pattern, QRegularExpression::DontCaptureOption | QRegularExpression::OptimizeOnFirstUsageOption); + + const bool found = subject.contains(*regexp); + + if (!wasCached) + cache->insert(pattern, regexp); + + sqlite3_result_int(context, int(found)); +} + +static void _q_regexp_cleanup(void *cache) +{ + delete static_cast*>(cache); +} +#endif + +QSQLiteDriver::QSQLiteDriver(QObject * parent) + : QSqlDriver(*new QSQLiteDriverPrivate, parent) +{ +} + +QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent) + : QSqlDriver(*new QSQLiteDriverPrivate, parent) +{ + Q_D(QSQLiteDriver); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLiteDriver::~QSQLiteDriver() +{ + close(); +} + +bool QSQLiteDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case BLOB: + case Transactions: + case Unicode: + case LastInsertId: + case PreparedQueries: + case PositionalPlaceholders: + case SimpleLocking: + case FinishQuery: + case LowPrecisionNumbers: + case EventNotifications: + return true; + case QuerySize: + case BatchOperations: + case MultipleResultSets: + case CancelQuery: + return false; + case NamedPlaceholders: +#if (SQLITE_VERSION_NUMBER < 3003011) + return false; +#else + return true; +#endif + + } + return false; +} + +/* + SQLite dbs have no user name, passwords, hosts or ports. + just file names. +*/ +bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts) +{ + Q_D(QSQLiteDriver); + if (isOpen()) + close(); + + + int timeOut = 5000; + bool sharedCache = false; + bool openReadOnlyOption = false; + bool openUriOption = false; +#if QT_CONFIG(regularexpression) + static const QLatin1String regexpConnectOption = QLatin1String("QSQLITE_ENABLE_REGEXP"); + bool defineRegexp = false; + int regexpCacheSize = 25; +#endif + + const auto opts = conOpts.splitRef(QLatin1Char(';')); + for (auto option : opts) { + option = option.trimmed(); + if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT"))) { + option = option.mid(20).trimmed(); + if (option.startsWith(QLatin1Char('='))) { + bool ok; + const int nt = option.mid(1).trimmed().toInt(&ok); + if (ok) + timeOut = nt; + } + } else if (option == QLatin1String("QSQLITE_OPEN_READONLY")) { + openReadOnlyOption = true; + } else if (option == QLatin1String("QSQLITE_OPEN_URI")) { + openUriOption = true; + } else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) { + sharedCache = true; + } +#if QT_CONFIG(regularexpression) + else if (option.startsWith(regexpConnectOption)) { + option = option.mid(regexpConnectOption.size()).trimmed(); + if (option.isEmpty()) { + defineRegexp = true; + } else if (option.startsWith(QLatin1Char('='))) { + bool ok = false; + const int cacheSize = option.mid(1).trimmed().toInt(&ok); + if (ok) { + defineRegexp = true; + if (cacheSize > 0) + regexpCacheSize = cacheSize; + } + } + } +#endif + } + + int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); + openMode |= (sharedCache ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE); + if (openUriOption) + openMode |= SQLITE_OPEN_URI; + + openMode |= SQLITE_OPEN_NOMUTEX; + + if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL) == SQLITE_OK) { + sqlite3_busy_timeout(d->access, timeOut); + setOpen(true); + setOpenError(false); +#if QT_CONFIG(regularexpression) + if (defineRegexp) { + auto cache = new QCache(regexpCacheSize); + sqlite3_create_function_v2(d->access, "regexp", 2, SQLITE_UTF8, cache, &_q_regexp, NULL, + NULL, &_q_regexp_cleanup); + } +#endif + return true; + } else { + if (d->access) { + sqlite3_close(d->access); + d->access = 0; + } + + setLastError(qMakeError(d->access, tr("Error opening database"), + QSqlError::ConnectionError)); + setOpenError(true); + return false; + } +} + +void QSQLiteDriver::close() +{ + Q_D(QSQLiteDriver); + if (isOpen()) { + for (QSQLiteResult *result : qAsConst(d->results)) + result->d_func()->finalize(); + + if (d->access && (d->notificationid.count() > 0)) { + d->notificationid.clear(); + sqlite3_update_hook(d->access, NULL, NULL); + } + + if (sqlite3_close(d->access) != SQLITE_OK) + setLastError(qMakeError(d->access, tr("Error closing database"), QSqlError::ConnectionError)); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QSQLiteDriver::createResult() const +{ + return new QSQLiteResult(this); +} + +bool QSQLiteDriver::beginTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("BEGIN"))) { + setLastError(QSqlError(tr("Unable to begin transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::commitTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("COMMIT"))) { + setLastError(QSqlError(tr("Unable to commit transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::rollbackTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("ROLLBACK"))) { + setLastError(QSqlError(tr("Unable to rollback transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +QStringList QSQLiteDriver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + + QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 " + "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"); + if ((type & QSql::Tables) && (type & QSql::Views)) + sql = sql.arg(QLatin1String("type='table' OR type='view'")); + else if (type & QSql::Tables) + sql = sql.arg(QLatin1String("type='table'")); + else if (type & QSql::Views) + sql = sql.arg(QLatin1String("type='view'")); + else + sql.clear(); + + if (!sql.isEmpty() && q.exec(sql)) { + while(q.next()) + res.append(q.value(0).toString()); + } + + if (type & QSql::SystemTables) { + // there are no internal tables beside this one: + res.append(QLatin1String("sqlite_master")); + } + + return res; +} + +static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) +{ + QString schema; + QString table(tableName); + int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); + if (indexOfSeparator > -1) { + schema = tableName.left(indexOfSeparator).append(QLatin1Char('.')); + table = tableName.mid(indexOfSeparator + 1); + } + q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(table) + QLatin1Char(')')); + + QSqlIndex ind; + while (q.next()) { + bool isPk = q.value(5).toInt(); + if (onlyPIndex && !isPk) + continue; + QString typeName = q.value(2).toString().toLower(); +#ifdef ENABLE_COLUMN_METADATA + QSqlField fld(q.value(1).toString(), qGetColumnType(typeName), tableName); +#else + QSqlField fld(q.value(1).toString(), qGetColumnType(typeName)); +#endif + if (isPk && (typeName == QLatin1String("integer"))) + // INTEGER PRIMARY KEY fields are auto-generated in sqlite + // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY! + fld.setAutoValue(true); + fld.setRequired(q.value(3).toInt() != 0); + fld.setDefaultValue(q.value(4)); + ind.append(fld); + } + return ind; +} + +QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const +{ + if (!isOpen()) + return QSqlIndex(); + + QString table = tblname; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, table, true); +} + +QSqlRecord QSQLiteDriver::record(const QString &tbl) const +{ + if (!isOpen()) + return QSqlRecord(); + + QString table = tbl; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, table); +} + +QVariant QSQLiteDriver::handle() const +{ + Q_D(const QSQLiteDriver); + return QVariant::fromValue(d->access); +} + +QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const +{ + Q_UNUSED(type); + return _q_escapeIdentifier(identifier); +} + +static void handle_sqlite_callback(void *qobj,int aoperation, char const *adbname, char const *atablename, + sqlite3_int64 arowid) +{ + Q_UNUSED(aoperation); + Q_UNUSED(adbname); + QSQLiteDriver *driver = static_cast(qobj); + if (driver) { + QMetaObject::invokeMethod(driver, "handleNotification", Qt::QueuedConnection, + Q_ARG(QString, QString::fromUtf8(atablename)), Q_ARG(qint64, arowid)); + } +} + +bool QSQLiteDriver::subscribeToNotification(const QString &name) +{ + Q_D(QSQLiteDriver); + if (!isOpen()) { + qWarning("Database not open."); + return false; + } + + if (d->notificationid.contains(name)) { + qWarning("Already subscribing to '%s'.", qPrintable(name)); + return false; + } + + //sqlite supports only one notification callback, so only the first is registered + d->notificationid << name; + if (d->notificationid.count() == 1) + sqlite3_update_hook(d->access, &handle_sqlite_callback, reinterpret_cast (this)); + + return true; +} + +bool QSQLiteDriver::unsubscribeFromNotification(const QString &name) +{ + Q_D(QSQLiteDriver); + if (!isOpen()) { + qWarning("Database not open."); + return false; + } + + if (!d->notificationid.contains(name)) { + qWarning("Not subscribed to '%s'.", qPrintable(name)); + return false; + } + + d->notificationid.removeAll(name); + if (d->notificationid.isEmpty()) + sqlite3_update_hook(d->access, NULL, NULL); + + return true; +} + +QStringList QSQLiteDriver::subscribedToNotifications() const +{ + Q_D(const QSQLiteDriver); + return d->notificationid; +} + +void QSQLiteDriver::handleNotification(const QString &tableName, qint64 rowid) +{ + Q_D(const QSQLiteDriver); + if (d->notificationid.contains(tableName)) { + emit notification(tableName); + emit notification(tableName, QSqlDriver::UnknownSource, QVariant(rowid)); + } +} + +QT_END_NAMESPACE diff --git a/kmymoney/plugins/sqlcipher/QSQLite/5.11/qsql_sqlite_p.h b/kmymoney/plugins/sqlcipher/QSQLite/5.11/qsql_sqlite_p.h new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/QSQLite/5.11/qsql_sqlite_p.h @@ -0,0 +1,107 @@ +//krazy:skip +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_SQLITE_H +#define QSQL_SQLITE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +struct sqlite3; + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_SQLITE +#else +#define Q_EXPORT_SQLDRIVER_SQLITE Q_SQL_EXPORT +#endif + +QT_BEGIN_NAMESPACE + +class QSqlResult; +class QSQLiteDriverPrivate; + +class Q_EXPORT_SQLDRIVER_SQLITE QSQLiteDriver : public QSqlDriver +{ + Q_DECLARE_PRIVATE(QSQLiteDriver) + Q_OBJECT + friend class QSQLiteResultPrivate; +public: + explicit QSQLiteDriver(QObject *parent = 0); + explicit QSQLiteDriver(sqlite3 *connection, QObject *parent = 0); + ~QSQLiteDriver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString & connOpts) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + + QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString &table) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE; + + bool subscribeToNotification(const QString &name) Q_DECL_OVERRIDE; + bool unsubscribeFromNotification(const QString &name) Q_DECL_OVERRIDE; + QStringList subscribedToNotifications() const Q_DECL_OVERRIDE; +private Q_SLOTS: + void handleNotification(const QString &tableName, qint64 rowid); +}; + +QT_END_NAMESPACE + +#endif // QSQL_SQLITE_H diff --git a/kmymoney/plugins/sqlcipher/QSQLite/5.7/qsql_sqlite.cpp b/kmymoney/plugins/sqlcipher/QSQLite/5.7/qsql_sqlite.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/QSQLite/5.7/qsql_sqlite.cpp @@ -0,0 +1,808 @@ +//krazy:skip +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** 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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_sqlite_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined Q_OS_WIN +# include +#else +# include +#endif + +#include + +Q_DECLARE_OPAQUE_POINTER(sqlite3*) +Q_DECLARE_METATYPE(sqlite3*) + +Q_DECLARE_OPAQUE_POINTER(sqlite3_stmt*) +Q_DECLARE_METATYPE(sqlite3_stmt*) + +QT_BEGIN_NAMESPACE + +static QString _q_escapeIdentifier(const QString &identifier) +{ + 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; +} + +static QVariant::Type qGetColumnType(const QString &tpName) +{ + const QString typeName = tpName.toLower(); + + if (typeName == QLatin1String("integer") + || typeName == QLatin1String("int")) + return QVariant::Int; + if (typeName == QLatin1String("double") + || typeName == QLatin1String("float") + || typeName == QLatin1String("real") + || typeName.startsWith(QLatin1String("numeric"))) + return QVariant::Double; + if (typeName == QLatin1String("blob")) + return QVariant::ByteArray; + if (typeName == QLatin1String("boolean") + || typeName == QLatin1String("bool")) + return QVariant::Bool; + return QVariant::String; +} + +static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, + int errorCode = -1) +{ + return QSqlError(descr, + QString(reinterpret_cast(sqlite3_errmsg16(access))), + type, QString::number(errorCode)); +} + +class QSQLiteResultPrivate; + +class QSQLiteResult : public QSqlCachedResult +{ + friend class QSQLiteDriver; + friend class QSQLiteResultPrivate; +public: + explicit QSQLiteResult(const QSQLiteDriver* db); + ~QSQLiteResult(); + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache& row, int idx) Q_DECL_OVERRIDE; + bool reset(const QString &query) Q_DECL_OVERRIDE; + bool prepare(const QString &query) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QVariant lastInsertId() const Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + void detachFromResultSet() Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; + +private: + QSQLiteResultPrivate* d; +}; + +class QSQLiteDriverPrivate : public QSqlDriverPrivate +{ +public: + inline QSQLiteDriverPrivate() : QSqlDriverPrivate(), access(0) { dbmsType = QSqlDriver::SQLite; } + sqlite3 *access; + QList results; +}; + + +class QSQLiteResultPrivate +{ +public: + QSQLiteResultPrivate(QSQLiteResult *res); + void cleanup(); + bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); + // initializes the recordInfo and the cache + void initColumns(bool emptyResultset); + void finalize(); + + QSQLiteResult* q; + sqlite3 *access; + + sqlite3_stmt *stmt; + + bool skippedStatus; // the status of the fetchNext() that's skipped + bool skipRow; // skip the next fetchNext()? + QSqlRecord rInf; + QVector firstRow; +}; + +QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult* res) : q(res), access(0), + stmt(0), skippedStatus(false), skipRow(false) +{ +} + +void QSQLiteResultPrivate::cleanup() +{ + finalize(); + rInf.clear(); + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirstRow); + q->setActive(false); + q->cleanup(); +} + +void QSQLiteResultPrivate::finalize() +{ + if (!stmt) + return; + + sqlite3_finalize(stmt); + stmt = 0; +} + +void QSQLiteResultPrivate::initColumns(bool emptyResultset) +{ + int nCols = sqlite3_column_count(stmt); + if (nCols <= 0) + return; + + q->init(nCols); + + for (int i = 0; i < nCols; ++i) { + QString colName = QString(reinterpret_cast( + sqlite3_column_name16(stmt, i)) + ).remove(QLatin1Char('"')); + + // must use typeName for resolving the type to match QSqliteDriver::record + QString typeName = QString(reinterpret_cast( + sqlite3_column_decltype16(stmt, i))); + // sqlite3_column_type is documented to have undefined behavior if the result set is empty + int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); + + QVariant::Type fieldType; + + if (!typeName.isEmpty()) { + fieldType = qGetColumnType(typeName); + } else { + // Get the proper type for the field based on stp value + switch (stp) { + case SQLITE_INTEGER: + fieldType = QVariant::Int; + break; + case SQLITE_FLOAT: + fieldType = QVariant::Double; + break; + case SQLITE_BLOB: + fieldType = QVariant::ByteArray; + break; + case SQLITE_TEXT: + fieldType = QVariant::String; + break; + case SQLITE_NULL: + default: + fieldType = QVariant::Invalid; + break; + } + } + + QSqlField fld(colName, fieldType); + fld.setSqlType(stp); + rInf.append(fld); + } +} + +bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + int res; + int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + for(int i=0;isetLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), + QCoreApplication::translate("QSQLiteResult", "No query"), QSqlError::ConnectionError)); + q->setAt(QSql::AfterLastRow); + return false; + } + res = sqlite3_step(stmt); + + switch(res) { + case SQLITE_ROW: + // check to see if should fill out columns + if (rInf.isEmpty()) + // must be first call. + initColumns(false); + if (idx < 0 && !initialFetch) + return true; + for (i = 0; i < rInf.count(); ++i) { + switch (sqlite3_column_type(stmt, i)) { + case SQLITE_BLOB: + values[i + idx] = QByteArray(static_cast( + sqlite3_column_blob(stmt, i)), + sqlite3_column_bytes(stmt, i)); + break; + case SQLITE_INTEGER: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case SQLITE_FLOAT: + switch(q->numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + values[i + idx] = sqlite3_column_int(stmt, i); + break; + case QSql::LowPrecisionInt64: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case QSql::LowPrecisionDouble: + case QSql::HighPrecision: + default: + values[i + idx] = sqlite3_column_double(stmt, i); + break; + }; + break; + case SQLITE_NULL: + values[i + idx] = QVariant(QVariant::String); + break; + default: + values[i + idx] = QString(reinterpret_cast( + sqlite3_column_text16(stmt, i)), + sqlite3_column_bytes16(stmt, i) / sizeof(QChar)); + break; + } + } + return true; + case SQLITE_DONE: + if (rInf.isEmpty()) + // must be first call. + initColumns(true); + q->setAt(QSql::AfterLastRow); + sqlite3_reset(stmt); + return false; + case SQLITE_CONSTRAINT: + case SQLITE_ERROR: + // SQLITE_ERROR is a generic error code and we must call sqlite3_reset() + // to get the specific error message. + res = sqlite3_reset(stmt); + q->setLastError(qMakeError(access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + q->setAt(QSql::AfterLastRow); + return false; + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + // something wrong, don't get col info, but still return false + q->setLastError(qMakeError(access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + sqlite3_reset(stmt); + q->setAt(QSql::AfterLastRow); + return false; + } + return false; +} + +QSQLiteResult::QSQLiteResult(const QSQLiteDriver* db) + : QSqlCachedResult(db) +{ + d = new QSQLiteResultPrivate(this); + d->access = db->d_func()->access; + const_cast(db->d_func())->results.append(this); +} + +QSQLiteResult::~QSQLiteResult() +{ + const QSqlDriver *sqlDriver = driver(); + if (sqlDriver) + const_cast(qobject_cast(sqlDriver)->d_func())->results.removeOne(this); + d->cleanup(); + delete d; +} + +void QSQLiteResult::virtual_hook(int id, void *data) +{ + QSqlCachedResult::virtual_hook(id, data); +} + +bool QSQLiteResult::reset(const QString &query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QSQLiteResult::prepare(const QString &query) +{ + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + + d->cleanup(); + + setSelect(false); + + const void *pzTail = NULL; + +#if (SQLITE_VERSION_NUMBER >= 3003011) + int res = sqlite3_prepare16_v2(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#else + int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#endif + + if (res != SQLITE_OK) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } else if (pzTail && !QString(reinterpret_cast(pzTail)).trimmed().isEmpty()) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute multiple statements at a time"), QSqlError::StatementError, SQLITE_MISUSE)); + d->finalize(); + return false; + } + return true; +} + +bool QSQLiteResult::exec() +{ + const QVector values = boundValues(); + + d->skippedStatus = false; + d->skipRow = false; + d->rInf.clear(); + clearValues(); + setLastError(QSqlError()); + + int res = sqlite3_reset(d->stmt); + if (res != SQLITE_OK) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to reset statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + int paramCount = sqlite3_bind_parameter_count(d->stmt); + if (paramCount == values.count()) { + for (int i = 0; i < paramCount; ++i) { + res = SQLITE_OK; + const QVariant value = values.at(i); + + if (value.isNull()) { + res = sqlite3_bind_null(d->stmt, i + 1); + } else { + switch (value.type()) { + case QVariant::ByteArray: { + const QByteArray *ba = static_cast(value.constData()); + res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(), + ba->size(), SQLITE_STATIC); + break; } + case QVariant::Int: + case QVariant::Bool: + res = sqlite3_bind_int(d->stmt, i + 1, value.toInt()); + break; + case QVariant::Double: + res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble()); + break; + case QVariant::UInt: + case QVariant::LongLong: + res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong()); + break; + case QVariant::DateTime: { + const QDateTime dateTime = value.toDateTime(); + const QString str = dateTime.toString(QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz")); + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + str.size() * sizeof(ushort), SQLITE_TRANSIENT); + break; + } + case QVariant::Time: { + const QTime time = value.toTime(); + const QString str = time.toString(QStringLiteral("hh:mm:ss.zzz")); + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + str.size() * sizeof(ushort), SQLITE_TRANSIENT); + break; + } + case QVariant::String: { + // lifetime of string == lifetime of its qvariant + const QString *str = static_cast(value.constData()); + res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(), + (str->size()) * sizeof(QChar), SQLITE_STATIC); + break; } + default: { + QString str = value.toString(); + // SQLITE_TRANSIENT makes sure that sqlite buffers the data + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); + break; } + } + } + if (res != SQLITE_OK) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to bind parameters"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + } + } else { + setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", + "Parameter count mismatch"), QString(), QSqlError::StatementError)); + return false; + } + d->skippedStatus = d->fetchNext(d->firstRow, 0, true); + if (lastError().isValid()) { + setSelect(false); + setActive(false); + return false; + } + setSelect(!d->rInf.isEmpty()); + setActive(true); + return true; +} + +bool QSQLiteResult::gotoNext(QSqlCachedResult::ValueCache& row, int idx) +{ + return d->fetchNext(row, idx, false); +} + +int QSQLiteResult::size() +{ + return -1; +} + +int QSQLiteResult::numRowsAffected() +{ + return sqlite3_changes(d->access); +} + +QVariant QSQLiteResult::lastInsertId() const +{ + if (isActive()) { + qint64 id = sqlite3_last_insert_rowid(d->access); + if (id) + return id; + } + return QVariant(); +} + +QSqlRecord QSQLiteResult::record() const +{ + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +void QSQLiteResult::detachFromResultSet() +{ + if (d->stmt) + sqlite3_reset(d->stmt); +} + +QVariant QSQLiteResult::handle() const +{ + return QVariant::fromValue(d->stmt); +} + +///////////////////////////////////////////////////////// + +QSQLiteDriver::QSQLiteDriver(QObject * parent) + : QSqlDriver(*new QSQLiteDriverPrivate, parent) +{ +} + +QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent) + : QSqlDriver(*new QSQLiteDriverPrivate, parent) +{ + Q_D(QSQLiteDriver); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLiteDriver::~QSQLiteDriver() +{ +} + +bool QSQLiteDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case BLOB: + case Transactions: + case Unicode: + case LastInsertId: + case PreparedQueries: + case PositionalPlaceholders: + case SimpleLocking: + case FinishQuery: + case LowPrecisionNumbers: + return true; + case QuerySize: + case NamedPlaceholders: + case BatchOperations: + case EventNotifications: + case MultipleResultSets: + case CancelQuery: + return false; + } + return false; +} + +/* + SQLite dbs have no user name, passwords, hosts or ports. + just file names. +*/ +bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts) +{ + Q_D(QSQLiteDriver); + if (isOpen()) + close(); + + + int timeOut = 5000; + bool sharedCache = false; + bool openReadOnlyOption = false; + bool openUriOption = false; + + const QStringList opts = QString(conOpts).remove(QLatin1Char(' ')).split(QLatin1Char(';')); + foreach (const QString &option, opts) { + if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT="))) { + bool ok; + const int nt = option.midRef(21).toInt(&ok); + if (ok) + timeOut = nt; + } else if (option == QLatin1String("QSQLITE_OPEN_READONLY")) { + openReadOnlyOption = true; + } else if (option == QLatin1String("QSQLITE_OPEN_URI")) { + openUriOption = true; + } else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) { + sharedCache = true; + } + } + + int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); + if (openUriOption) + openMode |= SQLITE_OPEN_URI; + + sqlite3_enable_shared_cache(sharedCache); + + if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL) == SQLITE_OK) { + sqlite3_busy_timeout(d->access, timeOut); + setOpen(true); + setOpenError(false); + return true; + } else { + if (d->access) { + sqlite3_close(d->access); + d->access = 0; + } + + setLastError(qMakeError(d->access, tr("Error opening database"), + QSqlError::ConnectionError)); + setOpenError(true); + return false; + } +} + +void QSQLiteDriver::close() +{ + Q_D(QSQLiteDriver); + if (isOpen()) { + foreach (QSQLiteResult *result, d->results) { + result->d->finalize(); + } + + if (sqlite3_close(d->access) != SQLITE_OK) + setLastError(qMakeError(d->access, tr("Error closing database"), + QSqlError::ConnectionError)); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QSQLiteDriver::createResult() const +{ + return new QSQLiteResult(this); +} + +bool QSQLiteDriver::beginTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("BEGIN"))) { + setLastError(QSqlError(tr("Unable to begin transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::commitTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("COMMIT"))) { + setLastError(QSqlError(tr("Unable to commit transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::rollbackTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("ROLLBACK"))) { + setLastError(QSqlError(tr("Unable to rollback transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +QStringList QSQLiteDriver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + + QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 " + "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"); + if ((type & QSql::Tables) && (type & QSql::Views)) + sql = sql.arg(QLatin1String("type='table' OR type='view'")); + else if (type & QSql::Tables) + sql = sql.arg(QLatin1String("type='table'")); + else if (type & QSql::Views) + sql = sql.arg(QLatin1String("type='view'")); + else + sql.clear(); + + if (!sql.isEmpty() && q.exec(sql)) { + while(q.next()) + res.append(q.value(0).toString()); + } + + if (type & QSql::SystemTables) { + // there are no internal tables beside this one: + res.append(QLatin1String("sqlite_master")); + } + + return res; +} + +static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) +{ + QString schema; + QString table(tableName); + int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); + if (indexOfSeparator > -1) { + schema = tableName.left(indexOfSeparator).append(QLatin1Char('.')); + table = tableName.mid(indexOfSeparator + 1); + } + q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(table) + QLatin1String(")")); + + QSqlIndex ind; + while (q.next()) { + bool isPk = q.value(5).toInt(); + if (onlyPIndex && !isPk) + continue; + QString typeName = q.value(2).toString().toLower(); + QSqlField fld(q.value(1).toString(), qGetColumnType(typeName)); + if (isPk && (typeName == QLatin1String("integer"))) + // INTEGER PRIMARY KEY fields are auto-generated in sqlite + // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY! + fld.setAutoValue(true); + fld.setRequired(q.value(3).toInt() != 0); + fld.setDefaultValue(q.value(4)); + ind.append(fld); + } + return ind; +} + +QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const +{ + if (!isOpen()) + return QSqlIndex(); + + QString table = tblname; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, table, true); +} + +QSqlRecord QSQLiteDriver::record(const QString &tbl) const +{ + if (!isOpen()) + return QSqlRecord(); + + QString table = tbl; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, table); +} + +QVariant QSQLiteDriver::handle() const +{ + Q_D(const QSQLiteDriver); + return QVariant::fromValue(d->access); +} + +QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const +{ + Q_UNUSED(type); + return _q_escapeIdentifier(identifier); +} + +QT_END_NAMESPACE diff --git a/kmymoney/plugins/sqlcipher/QSQLite/5.7/qsql_sqlite_p.h b/kmymoney/plugins/sqlcipher/QSQLite/5.7/qsql_sqlite_p.h new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/QSQLite/5.7/qsql_sqlite_p.h @@ -0,0 +1,96 @@ +//krazy:skip +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** 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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_SQLITE_H +#define QSQL_SQLITE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +struct sqlite3; + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_SQLITE +#else +#define Q_EXPORT_SQLDRIVER_SQLITE Q_SQL_EXPORT +#endif + +QT_BEGIN_NAMESPACE + +class QSQLiteDriverPrivate; +class QSQLiteDriver; + +class Q_EXPORT_SQLDRIVER_SQLITE QSQLiteDriver : public QSqlDriver +{ + Q_DECLARE_PRIVATE(QSQLiteDriver) + Q_OBJECT + friend class QSQLiteResult; +public: + explicit QSQLiteDriver(QObject *parent = 0); + explicit QSQLiteDriver(sqlite3 *connection, QObject *parent = 0); + ~QSQLiteDriver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString & connOpts) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + + QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString &table) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif // QSQL_SQLITE_H diff --git a/kmymoney/plugins/sqlcipher/QSQLite/5.9/qsql_sqlite.cpp b/kmymoney/plugins/sqlcipher/QSQLite/5.9/qsql_sqlite.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/QSQLite/5.9/qsql_sqlite.cpp @@ -0,0 +1,935 @@ +//krazy:skip +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_sqlite_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_CONFIG(timezone) +#include +#endif + +#if defined Q_OS_WIN +# include +#else +# include +#endif + +#include +#include + +Q_DECLARE_OPAQUE_POINTER(sqlite3*) +Q_DECLARE_METATYPE(sqlite3*) + +Q_DECLARE_OPAQUE_POINTER(sqlite3_stmt*) +Q_DECLARE_METATYPE(sqlite3_stmt*) + +QT_BEGIN_NAMESPACE + +static QString _q_escapeIdentifier(const QString &identifier) +{ + QString res = identifier; + if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"'))) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +static QVariant::Type qGetColumnType(const QString &tpName) +{ + const QString typeName = tpName.toLower(); + + if (typeName == QLatin1String("integer") + || typeName == QLatin1String("int")) + return QVariant::Int; + if (typeName == QLatin1String("double") + || typeName == QLatin1String("float") + || typeName == QLatin1String("real") + || typeName.startsWith(QLatin1String("numeric"))) + return QVariant::Double; + if (typeName == QLatin1String("blob")) + return QVariant::ByteArray; + if (typeName == QLatin1String("boolean") + || typeName == QLatin1String("bool")) + return QVariant::Bool; + return QVariant::String; +} + +static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, + int errorCode = -1) +{ + return QSqlError(descr, + QString(reinterpret_cast(sqlite3_errmsg16(access))), + type, QString::number(errorCode)); +} + +class QSQLiteResultPrivate; + +class QSQLiteResult : public QSqlCachedResult +{ + Q_DECLARE_PRIVATE(QSQLiteResult) + friend class QSQLiteDriver; + +public: + explicit QSQLiteResult(const QSQLiteDriver* db); + ~QSQLiteResult(); + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache& row, int idx) Q_DECL_OVERRIDE; + bool reset(const QString &query) Q_DECL_OVERRIDE; + bool prepare(const QString &query) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QVariant lastInsertId() const Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + void detachFromResultSet() Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; +}; + +class QSQLiteDriverPrivate : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QSQLiteDriver) + +public: + inline QSQLiteDriverPrivate() : QSqlDriverPrivate(), access(0) { dbmsType = QSqlDriver::SQLite; } + sqlite3 *access; + QList results; + QStringList notificationid; +}; + + +class QSQLiteResultPrivate: public QSqlCachedResultPrivate +{ + Q_DECLARE_PUBLIC(QSQLiteResult) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QSQLiteDriver) + QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv); + void cleanup(); + bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); + // initializes the recordInfo and the cache + void initColumns(bool emptyResultset); + void finalize(); + + sqlite3_stmt *stmt; + + bool skippedStatus; // the status of the fetchNext() that's skipped + bool skipRow; // skip the next fetchNext()? + QSqlRecord rInf; + QVector firstRow; +}; + +QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv) + : QSqlCachedResultPrivate(q, drv), + stmt(0), + skippedStatus(false), + skipRow(false) +{ +} + +void QSQLiteResultPrivate::cleanup() +{ + Q_Q(QSQLiteResult); + finalize(); + rInf.clear(); + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirstRow); + q->setActive(false); + q->cleanup(); +} + +void QSQLiteResultPrivate::finalize() +{ + if (!stmt) + return; + + sqlite3_finalize(stmt); + stmt = 0; +} + +void QSQLiteResultPrivate::initColumns(bool emptyResultset) +{ + Q_Q(QSQLiteResult); + int nCols = sqlite3_column_count(stmt); + if (nCols <= 0) + return; + + q->init(nCols); + + for (int i = 0; i < nCols; ++i) { + QString colName = QString(reinterpret_cast( + sqlite3_column_name16(stmt, i)) + ).remove(QLatin1Char('"')); + + // must use typeName for resolving the type to match QSqliteDriver::record + QString typeName = QString(reinterpret_cast( + sqlite3_column_decltype16(stmt, i))); + // sqlite3_column_type is documented to have undefined behavior if the result set is empty + int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); + + QVariant::Type fieldType; + + if (!typeName.isEmpty()) { + fieldType = qGetColumnType(typeName); + } else { + // Get the proper type for the field based on stp value + switch (stp) { + case SQLITE_INTEGER: + fieldType = QVariant::Int; + break; + case SQLITE_FLOAT: + fieldType = QVariant::Double; + break; + case SQLITE_BLOB: + fieldType = QVariant::ByteArray; + break; + case SQLITE_TEXT: + fieldType = QVariant::String; + break; + case SQLITE_NULL: + default: + fieldType = QVariant::Invalid; + break; + } + } + + QSqlField fld(colName, fieldType); + fld.setSqlType(stp); + rInf.append(fld); + } +} + +bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + Q_Q(QSQLiteResult); + int res; + int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + for(int i=0;isetLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), + QCoreApplication::translate("QSQLiteResult", "No query"), QSqlError::ConnectionError)); + q->setAt(QSql::AfterLastRow); + return false; + } + res = sqlite3_step(stmt); + + switch(res) { + case SQLITE_ROW: + // check to see if should fill out columns + if (rInf.isEmpty()) + // must be first call. + initColumns(false); + if (idx < 0 && !initialFetch) + return true; + for (i = 0; i < rInf.count(); ++i) { + switch (sqlite3_column_type(stmt, i)) { + case SQLITE_BLOB: + values[i + idx] = QByteArray(static_cast( + sqlite3_column_blob(stmt, i)), + sqlite3_column_bytes(stmt, i)); + break; + case SQLITE_INTEGER: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case SQLITE_FLOAT: + switch(q->numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + values[i + idx] = sqlite3_column_int(stmt, i); + break; + case QSql::LowPrecisionInt64: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case QSql::LowPrecisionDouble: + case QSql::HighPrecision: + default: + values[i + idx] = sqlite3_column_double(stmt, i); + break; + }; + break; + case SQLITE_NULL: + values[i + idx] = QVariant(QVariant::String); + break; + default: + values[i + idx] = QString(reinterpret_cast( + sqlite3_column_text16(stmt, i)), + sqlite3_column_bytes16(stmt, i) / sizeof(QChar)); + break; + } + } + return true; + case SQLITE_DONE: + if (rInf.isEmpty()) + // must be first call. + initColumns(true); + q->setAt(QSql::AfterLastRow); + sqlite3_reset(stmt); + return false; + case SQLITE_CONSTRAINT: + case SQLITE_ERROR: + // SQLITE_ERROR is a generic error code and we must call sqlite3_reset() + // to get the specific error message. + res = sqlite3_reset(stmt); + q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + q->setAt(QSql::AfterLastRow); + return false; + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + // something wrong, don't get col info, but still return false + q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + sqlite3_reset(stmt); + q->setAt(QSql::AfterLastRow); + return false; + } + return false; +} + +QSQLiteResult::QSQLiteResult(const QSQLiteDriver* db) + : QSqlCachedResult(*new QSQLiteResultPrivate(this, db)) +{ + Q_D(QSQLiteResult); + const_cast(d->drv_d_func())->results.append(this); +} + +QSQLiteResult::~QSQLiteResult() +{ + Q_D(QSQLiteResult); + if (d->drv_d_func()) + const_cast(d->drv_d_func())->results.removeOne(this); + d->cleanup(); +} + +void QSQLiteResult::virtual_hook(int id, void *data) +{ + QSqlCachedResult::virtual_hook(id, data); +} + +bool QSQLiteResult::reset(const QString &query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QSQLiteResult::prepare(const QString &query) +{ + Q_D(QSQLiteResult); + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + + d->cleanup(); + + setSelect(false); + + const void *pzTail = NULL; + +#if (SQLITE_VERSION_NUMBER >= 3003011) + int res = sqlite3_prepare16_v2(d->drv_d_func()->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#else + int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#endif + + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } else if (pzTail && !QString(reinterpret_cast(pzTail)).trimmed().isEmpty()) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute multiple statements at a time"), QSqlError::StatementError, SQLITE_MISUSE)); + d->finalize(); + return false; + } + return true; +} + +static QString secondsToOffset(int seconds) +{ + const QChar sign = ushort(seconds < 0 ? '-' : '+'); + seconds = qAbs(seconds); + const int hours = seconds / 3600; + const int minutes = (seconds % 3600) / 60; + + return QString(QStringLiteral("%1%2:%3")).arg(sign).arg(hours, 2, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')); +} + +static QString timespecToString(const QDateTime &dateTime) +{ + switch (dateTime.timeSpec()) { + case Qt::LocalTime: + return QString(); + case Qt::UTC: + return QStringLiteral("Z"); + case Qt::OffsetFromUTC: + return secondsToOffset(dateTime.offsetFromUtc()); +#if QT_CONFIG(timezone) + case Qt::TimeZone: + return secondsToOffset(dateTime.timeZone().offsetFromUtc(dateTime)); +#endif + default: + return QString(); + } +} + +bool QSQLiteResult::exec() +{ + Q_D(QSQLiteResult); + const QVector values = boundValues(); + + d->skippedStatus = false; + d->skipRow = false; + d->rInf.clear(); + clearValues(); + setLastError(QSqlError()); + + int res = sqlite3_reset(d->stmt); + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to reset statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + int paramCount = sqlite3_bind_parameter_count(d->stmt); + if (paramCount == values.count()) { + for (int i = 0; i < paramCount; ++i) { + res = SQLITE_OK; + const QVariant value = values.at(i); + + if (value.isNull()) { + res = sqlite3_bind_null(d->stmt, i + 1); + } else { + switch (value.type()) { + case QVariant::ByteArray: { + const QByteArray *ba = static_cast(value.constData()); + res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(), + ba->size(), SQLITE_STATIC); + break; } + case QVariant::Int: + case QVariant::Bool: + res = sqlite3_bind_int(d->stmt, i + 1, value.toInt()); + break; + case QVariant::Double: + res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble()); + break; + case QVariant::UInt: + case QVariant::LongLong: + res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong()); + break; + case QVariant::DateTime: { + const QDateTime dateTime = value.toDateTime(); + const QString str = dateTime.toString(QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz") + timespecToString(dateTime)); + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + str.size() * sizeof(ushort), SQLITE_TRANSIENT); + break; + } + case QVariant::Time: { + const QTime time = value.toTime(); + const QString str = time.toString(QStringLiteral("hh:mm:ss.zzz")); + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + str.size() * sizeof(ushort), SQLITE_TRANSIENT); + break; + } + case QVariant::String: { + // lifetime of string == lifetime of its qvariant + const QString *str = static_cast(value.constData()); + res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(), + (str->size()) * sizeof(QChar), SQLITE_STATIC); + break; } + default: { + QString str = value.toString(); + // SQLITE_TRANSIENT makes sure that sqlite buffers the data + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); + break; } + } + } + if (res != SQLITE_OK) { + setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", + "Unable to bind parameters"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + } + } else { + setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", + "Parameter count mismatch"), QString(), QSqlError::StatementError)); + return false; + } + d->skippedStatus = d->fetchNext(d->firstRow, 0, true); + if (lastError().isValid()) { + setSelect(false); + setActive(false); + return false; + } + setSelect(!d->rInf.isEmpty()); + setActive(true); + return true; +} + +bool QSQLiteResult::gotoNext(QSqlCachedResult::ValueCache& row, int idx) +{ + Q_D(QSQLiteResult); + return d->fetchNext(row, idx, false); +} + +int QSQLiteResult::size() +{ + return -1; +} + +int QSQLiteResult::numRowsAffected() +{ + Q_D(const QSQLiteResult); + return sqlite3_changes(d->drv_d_func()->access); +} + +QVariant QSQLiteResult::lastInsertId() const +{ + Q_D(const QSQLiteResult); + if (isActive()) { + qint64 id = sqlite3_last_insert_rowid(d->drv_d_func()->access); + if (id) + return id; + } + return QVariant(); +} + +QSqlRecord QSQLiteResult::record() const +{ + Q_D(const QSQLiteResult); + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +void QSQLiteResult::detachFromResultSet() +{ + Q_D(QSQLiteResult); + if (d->stmt) + sqlite3_reset(d->stmt); +} + +QVariant QSQLiteResult::handle() const +{ + Q_D(const QSQLiteResult); + return QVariant::fromValue(d->stmt); +} + +///////////////////////////////////////////////////////// + +QSQLiteDriver::QSQLiteDriver(QObject * parent) + : QSqlDriver(*new QSQLiteDriverPrivate, parent) +{ +} + +QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent) + : QSqlDriver(*new QSQLiteDriverPrivate, parent) +{ + Q_D(QSQLiteDriver); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLiteDriver::~QSQLiteDriver() +{ + close(); +} + +bool QSQLiteDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case BLOB: + case Transactions: + case Unicode: + case LastInsertId: + case PreparedQueries: + case PositionalPlaceholders: + case SimpleLocking: + case FinishQuery: + case LowPrecisionNumbers: + case EventNotifications: + return true; + case QuerySize: + case NamedPlaceholders: + case BatchOperations: + case MultipleResultSets: + case CancelQuery: + return false; + } + return false; +} + +/* + SQLite dbs have no user name, passwords, hosts or ports. + just file names. +*/ +bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts) +{ + Q_D(QSQLiteDriver); + if (isOpen()) + close(); + + + int timeOut = 5000; + bool sharedCache = false; + bool openReadOnlyOption = false; + bool openUriOption = false; + + const auto opts = conOpts.splitRef(QLatin1Char(';')); + for (auto option : opts) { + option = option.trimmed(); + if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT"))) { + option = option.mid(20).trimmed(); + if (option.startsWith(QLatin1Char('='))) { + bool ok; + const int nt = option.mid(1).trimmed().toInt(&ok); + if (ok) + timeOut = nt; + } + } else if (option == QLatin1String("QSQLITE_OPEN_READONLY")) { + openReadOnlyOption = true; + } else if (option == QLatin1String("QSQLITE_OPEN_URI")) { + openUriOption = true; + } else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) { + sharedCache = true; + } + } + + int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); + if (openUriOption) + openMode |= SQLITE_OPEN_URI; + + sqlite3_enable_shared_cache(sharedCache); + + if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL) == SQLITE_OK) { + sqlite3_busy_timeout(d->access, timeOut); + setOpen(true); + setOpenError(false); + return true; + } else { + if (d->access) { + sqlite3_close(d->access); + d->access = 0; + } + + setLastError(qMakeError(d->access, tr("Error opening database"), + QSqlError::ConnectionError)); + setOpenError(true); + return false; + } +} + +void QSQLiteDriver::close() +{ + Q_D(QSQLiteDriver); + if (isOpen()) { + for (QSQLiteResult *result : qAsConst(d->results)) + result->d_func()->finalize(); + + if (d->access && (d->notificationid.count() > 0)) { + d->notificationid.clear(); + sqlite3_update_hook(d->access, NULL, NULL); + } + + if (sqlite3_close(d->access) != SQLITE_OK) + setLastError(qMakeError(d->access, tr("Error closing database"), QSqlError::ConnectionError)); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QSQLiteDriver::createResult() const +{ + return new QSQLiteResult(this); +} + +bool QSQLiteDriver::beginTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("BEGIN"))) { + setLastError(QSqlError(tr("Unable to begin transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::commitTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("COMMIT"))) { + setLastError(QSqlError(tr("Unable to commit transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::rollbackTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("ROLLBACK"))) { + setLastError(QSqlError(tr("Unable to rollback transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +QStringList QSQLiteDriver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + + QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 " + "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"); + if ((type & QSql::Tables) && (type & QSql::Views)) + sql = sql.arg(QLatin1String("type='table' OR type='view'")); + else if (type & QSql::Tables) + sql = sql.arg(QLatin1String("type='table'")); + else if (type & QSql::Views) + sql = sql.arg(QLatin1String("type='view'")); + else + sql.clear(); + + if (!sql.isEmpty() && q.exec(sql)) { + while(q.next()) + res.append(q.value(0).toString()); + } + + if (type & QSql::SystemTables) { + // there are no internal tables beside this one: + res.append(QLatin1String("sqlite_master")); + } + + return res; +} + +static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) +{ + QString schema; + QString table(tableName); + int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); + if (indexOfSeparator > -1) { + schema = tableName.left(indexOfSeparator).append(QLatin1Char('.')); + table = tableName.mid(indexOfSeparator + 1); + } + q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(table) + QLatin1Char(')')); + + QSqlIndex ind; + while (q.next()) { + bool isPk = q.value(5).toInt(); + if (onlyPIndex && !isPk) + continue; + QString typeName = q.value(2).toString().toLower(); + QSqlField fld(q.value(1).toString(), qGetColumnType(typeName)); + if (isPk && (typeName == QLatin1String("integer"))) + // INTEGER PRIMARY KEY fields are auto-generated in sqlite + // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY! + fld.setAutoValue(true); + fld.setRequired(q.value(3).toInt() != 0); + fld.setDefaultValue(q.value(4)); + ind.append(fld); + } + return ind; +} + +QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const +{ + if (!isOpen()) + return QSqlIndex(); + + QString table = tblname; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, table, true); +} + +QSqlRecord QSQLiteDriver::record(const QString &tbl) const +{ + if (!isOpen()) + return QSqlRecord(); + + QString table = tbl; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, table); +} + +QVariant QSQLiteDriver::handle() const +{ + Q_D(const QSQLiteDriver); + return QVariant::fromValue(d->access); +} + +QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const +{ + Q_UNUSED(type); + return _q_escapeIdentifier(identifier); +} + +static void handle_sqlite_callback(void *qobj,int aoperation, char const *adbname, char const *atablename, + sqlite3_int64 arowid) +{ + Q_UNUSED(aoperation); + Q_UNUSED(adbname); + QSQLiteDriver *driver = static_cast(qobj); + if (driver) { + QMetaObject::invokeMethod(driver, "handleNotification", Qt::QueuedConnection, + Q_ARG(QString, QString::fromUtf8(atablename)), Q_ARG(qint64, arowid)); + } +} + +bool QSQLiteDriver::subscribeToNotification(const QString &name) +{ + Q_D(QSQLiteDriver); + if (!isOpen()) { + qWarning("Database not open."); + return false; + } + + if (d->notificationid.contains(name)) { + qWarning("Already subscribing to '%s'.", qPrintable(name)); + return false; + } + + //sqlite supports only one notification callback, so only the first is registered + d->notificationid << name; + if (d->notificationid.count() == 1) + sqlite3_update_hook(d->access, &handle_sqlite_callback, reinterpret_cast (this)); + + return true; +} + +bool QSQLiteDriver::unsubscribeFromNotification(const QString &name) +{ + Q_D(QSQLiteDriver); + if (!isOpen()) { + qWarning("Database not open."); + return false; + } + + if (!d->notificationid.contains(name)) { + qWarning("Not subscribed to '%s'.", qPrintable(name)); + return false; + } + + d->notificationid.removeAll(name); + if (d->notificationid.isEmpty()) + sqlite3_update_hook(d->access, NULL, NULL); + + return true; +} + +QStringList QSQLiteDriver::subscribedToNotifications() const +{ + Q_D(const QSQLiteDriver); + return d->notificationid; +} + +void QSQLiteDriver::handleNotification(const QString &tableName, qint64 rowid) +{ + Q_D(const QSQLiteDriver); + if (d->notificationid.contains(tableName)) { + emit notification(tableName); + emit notification(tableName, QSqlDriver::UnknownSource, QVariant(rowid)); + } +} + +QT_END_NAMESPACE diff --git a/kmymoney/plugins/sqlcipher/QSQLite/5.9/qsql_sqlite_p.h b/kmymoney/plugins/sqlcipher/QSQLite/5.9/qsql_sqlite_p.h new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/QSQLite/5.9/qsql_sqlite_p.h @@ -0,0 +1,107 @@ +//krazy:skip +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_SQLITE_H +#define QSQL_SQLITE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +struct sqlite3; + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_SQLITE +#else +#define Q_EXPORT_SQLDRIVER_SQLITE Q_SQL_EXPORT +#endif + +QT_BEGIN_NAMESPACE + +class QSqlResult; +class QSQLiteDriverPrivate; + +class Q_EXPORT_SQLDRIVER_SQLITE QSQLiteDriver : public QSqlDriver +{ + Q_DECLARE_PRIVATE(QSQLiteDriver) + Q_OBJECT + friend class QSQLiteResultPrivate; +public: + explicit QSQLiteDriver(QObject *parent = 0); + explicit QSQLiteDriver(sqlite3 *connection, QObject *parent = 0); + ~QSQLiteDriver(); + bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString & connOpts) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + + QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString &table) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE; + + bool subscribeToNotification(const QString &name) Q_DECL_OVERRIDE; + bool unsubscribeFromNotification(const QString &name) Q_DECL_OVERRIDE; + QStringList subscribedToNotifications() const Q_DECL_OVERRIDE; +private Q_SLOTS: + void handleNotification(const QString &tableName, qint64 rowid); +}; + +QT_END_NAMESPACE + +#endif // QSQL_SQLITE_H diff --git a/kmymoney/plugins/sqlcipher/cmake/modules/FindQSQLiteSource.cmake b/kmymoney/plugins/sqlcipher/cmake/modules/FindQSQLiteSource.cmake deleted file mode 100644 --- a/kmymoney/plugins/sqlcipher/cmake/modules/FindQSQLiteSource.cmake +++ /dev/null @@ -1,55 +0,0 @@ -# Try to find qsql_sqlite.cpp and qsql_sqlite.h -# Once done this will define -# -# QSQLITESOURCE_FOUND - system has qt sources with qslqlite -# QSQLITESOURCE_SRCS_DIR - full path to qsql_sqlite.cpp -# QSQLITESOURCE_INCLUDE_DIR - header files - -# Copyright (c) 2014, Christian Dávid, -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# This file is usually in the source code you have to get from git -find_path(QSQLITESOURCE_SRCS_DIR qsql_sqlite.cpp - DOC "Path to source dir of QSQLiteDriver" - PATH_SUFFIXES - src/sql/drivers/sqlite/ - PATHS - ${QT_INCLUDE_DIR} -) - -# To find this file you usually need to make the sources from git (this will create the necessary folder hierarchy) -find_path(QSQLITESOURCE_INCLUDE_DIR QtSql/private/qsqlcachedresult_p.h - DOC "Path to qt include files with private headers" - PATH_SUFFIXES - include - PATHS - ${QT_INCLUDE_DIR} -) - -set(QSQLITESOURCE_SRCS_DIRS ${QSQLITESOURCE_SRCS_DIR}) -set(QSQLITESOURCE_INCLUDE_DIRS ${QSQLITESOURCE_INCLUDE_DIR}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(QSQLiteSource "Could not find QSQLite source." QSQLITESOURCE_SRCS_DIR QSQLITESOURCE_INCLUDE_DIR) - -mark_as_advanced(QSQLITESOURCE_SRCS_DIR QSQLITESOURCE_INCLUDE_DIR) diff --git a/kmymoney/plugins/sqlcipher/cmake/modules/FindSQLCipher.cmake b/kmymoney/plugins/sqlcipher/cmake/modules/FindSQLCipher.cmake deleted file mode 100644 --- a/kmymoney/plugins/sqlcipher/cmake/modules/FindSQLCipher.cmake +++ /dev/null @@ -1,66 +0,0 @@ -# - Try to find SqlCipher -# Once done this will define -# -# SQLCIPHER_FOUND - system has SqlCipher -# SQLCIPHER_INCLUDE_DIR - the SqlCipher include directory -# SQLCIPHER_LIBRARIES - Link these to use SqlCipher -# SQLCIPHER_DEFINITIONS - Compiler switches required for using SqlCipher -# Redistribution and use is allowed according to the terms of the BSD license. - -# Copyright (c) 2008, Gilles Caulier, -# Copyright (c) 2014, Christian Dávid, -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# use pkg-config to get the directories and then use these values -# in the FIND_PATH() and FIND_LIBRARY() calls -if( NOT WIN32 ) - find_package(PkgConfig) - - pkg_check_modules(PC_SQLCIPHER QUIET sqlcipher) - - set(SQLCIPHER_DEFINITIONS ${PC_SQLCIPHER_CFLAGS_OTHER}) -endif( NOT WIN32 ) - -find_path(SQLCIPHER_INCLUDE_DIR NAMES sqlcipher/sqlite3.h - PATHS - ${PC_SQLCIPHER_INCLUDEDIR} - ${PC_SQLCIPHER_INCLUDE_DIRS} - ${CMAKE_INCLUDE_PATH} -) - -find_library(SQLCIPHER_LIBRARIES NAMES sqlcipher - PATHS - ${PC_SQLCIPHER_LIBDIR} - ${PC_SQLCIPHER_LIBRARY_DIRS} -) - -add_definitions(-DSQLITE_HAS_CODEC) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SqlCipher DEFAULT_MSG SQLCIPHER_INCLUDE_DIR SQLCIPHER_LIBRARIES ) - -# show the SQLCIPHER_INCLUDE_DIR and SQLCIPHER_LIBRARIES variables only in the advanced view -mark_as_advanced(SQLCIPHER_INCLUDE_DIR SQLCIPHER_LIBRARIES ) - diff --git a/kmymoney/plugins/sqlcipher/qsqlcipherdriverplugin.h b/kmymoney/plugins/sqlcipher/qsqlcipherdriverplugin.h deleted file mode 100644 --- a/kmymoney/plugins/sqlcipher/qsqlcipherdriverplugin.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * QSqlDriver for SQLCipher - * Copyright 2014 Christian David - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef QSQLCIPHERDRIVERPLUGIN_H -#define QSQLCIPHERDRIVERPLUGIN_H - -#include -#include - -#ifdef QT_PLUGIN -#define Q_EXPORT_SQLDRIVER_SQLCIPHER -#else -#define Q_EXPORT_SQLDRIVER_SQLCIPHER Q_SQL_EXPORT -#endif - -QT_BEGIN_NAMESPACE - -class Q_EXPORT_SQLDRIVER_SQLCIPHER QSqlcipherDriverPlugin : public QSqlDriverPlugin -{ - Q_OBJECT - -public: - virtual QSqlDriver* create(const QString&); - virtual QStringList keys() const; -}; - -QT_END_NAMESPACE - -#endif // QSQLCIPHERDRIVERPLUGIN_H diff --git a/kmymoney/plugins/sqlcipher/qsqlcipherdriverplugin.cpp b/kmymoney/plugins/sqlcipher/qsqlcipherdriverplugin.cpp deleted file mode 100644 --- a/kmymoney/plugins/sqlcipher/qsqlcipherdriverplugin.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * QSqlDriver for SQLCipher - * Copyright 2014 Christian David - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "qsqlcipherdriverplugin.h" - -#include "sqlcipherdriver.h" - -QT_BEGIN_NAMESPACE - -QSqlDriver* QSqlcipherDriverPlugin::create(const QString& name) -{ - if (name == QLatin1String("SQLCIPHER")) { - QSQLiteDriver* driver = new SQLCipherDriver(); - return driver; - } - return 0; -} - -QStringList QSqlcipherDriverPlugin::keys() const -{ - return QStringList("SQLCIPHER"); -} - -Q_EXPORT_PLUGIN2(qsqlcipherdriverplugin, QSqlcipherDriverPlugin); - -QT_END_NAMESPACE diff --git a/kmymoney/plugins/sqlcipher/smain.cpp b/kmymoney/plugins/sqlcipher/smain.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/smain.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include "qsql_sqlite_p.h" + +QT_BEGIN_NAMESPACE + +class QSQLCipherDriverPlugin : public QSqlDriverPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QSqlDriverFactoryInterface" FILE "sqlcipher.json") + +public: + QSQLCipherDriverPlugin(); + + QSqlDriver* create(const QString &) Q_DECL_OVERRIDE; +}; + +QSQLCipherDriverPlugin::QSQLCipherDriverPlugin() + : QSqlDriverPlugin() +{ +} + +QSqlDriver* QSQLCipherDriverPlugin::create(const QString &name) +{ + if (name == QLatin1String("QSQLCIPHER")) { + QSQLiteDriver* driver = new QSQLiteDriver(); + return driver; + } + return 0; +} + +QT_END_NAMESPACE + +#include "smain.moc" diff --git a/kmymoney/plugins/sqlcipher/sqlcipher.json b/kmymoney/plugins/sqlcipher/sqlcipher.json new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/sqlcipher.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "QSQLCIPHER" ] +} diff --git a/kmymoney/plugins/sqlcipher/sqlcipherdriver.h b/kmymoney/plugins/sqlcipher/sqlcipherdriver.h deleted file mode 100644 --- a/kmymoney/plugins/sqlcipher/sqlcipherdriver.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * QSqlDriver for SQLCipher - * Copyright 2014 Christian David - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#ifndef SQLCIPHERDRIVER_H -#define SQLCIPHERDRIVER_H - -#include // krazy:exclude=includes - -class SQLCipherDriver : public QSQLiteDriver -{ - Q_OBJECT - -public: - virtual bool open(const QString& db, const QString& user, const QString& password, const QString& host, int port, const QString& connOpts); - -}; - -#endif // SQLCIPHERDRIVER_H diff --git a/kmymoney/plugins/sqlcipher/sqlcipherdriver.cpp b/kmymoney/plugins/sqlcipher/sqlcipherdriver.cpp deleted file mode 100644 --- a/kmymoney/plugins/sqlcipher/sqlcipherdriver.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * QSqlDriver for SQLCipher - * Copyright 2014 Christian David - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "sqlcipherdriver.h" -#include -#include -#include - -bool SQLCipherDriver::open(const QString& db, const QString& user, const QString& password, const QString& host, int port, const QString& connOpts) -{ - if (QSQLiteDriver::open(db, user, password, host, port, connOpts)) { - // SQLCipher does not accept empty passwords - if (password.isEmpty()) - return true; - - // This is ugly but needed as the handle is stored in the private section of QSQLiteDriver - sqlite3* handle = *static_cast(QSQLiteDriver::handle().data()); - if (sqlite3_key(handle, password.toUtf8().constData(), password.length()) == SQLITE_OK) { - return true; - } else { - setLastError(QSqlError(QLatin1String("Error while setting passphrase for database."), QString(), QSqlError::ConnectionError, -1)); - QSQLiteDriver::setOpen(false); - setOpenError(true); - } - } - - return false; -} diff --git a/kmymoney/plugins/sqlcipher/tests/CMakeLists.txt b/kmymoney/plugins/sqlcipher/tests/CMakeLists.txt --- a/kmymoney/plugins/sqlcipher/tests/CMakeLists.txt +++ b/kmymoney/plugins/sqlcipher/tests/CMakeLists.txt @@ -1,57 +1,8 @@ -# Copyright (c) 2014, Christian Dávid, -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +include(ECMAddTests) -set (LIBRARIES - Qt5::Core - Qt5::Test - Qt5::Sql -) - -set( sqlcipherdriverinstalltest_SRCS - sqlcipherdriverinstalltest.cpp -) - -kde4_add_unit_test( sqlcipherdriverinstalltest - TESTNAME sqlcipherdriver-installed-test - ${sqlcipherdriverinstalltest_SRCS} -) - -target_link_libraries( sqlcipherdriverinstalltest - ${LIBRARIES} -) - - -set( sqlcipherdrivertest_SRCS - sqlcipherdrivertest.cpp -) - -kde4_add_unit_test( sqlcipherdrivertest - TESTNAME sqlcipherdriver - ${sqlcipherdrivertest_SRCS} -) - -target_link_libraries( sqlcipherdrivertest - ${LIBRARIES} +file(GLOB tests_sources "*-test.cpp") +ecm_add_tests(${tests_sources} + LINK_LIBRARIES + Qt5::Test + Qt5::Sql ) diff --git a/kmymoney/plugins/sqlcipher/tests/qsqlcipher-test.h b/kmymoney/plugins/sqlcipher/tests/qsqlcipher-test.h new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/tests/qsqlcipher-test.h @@ -0,0 +1,51 @@ +/* + * Copyright 2014 Christian Dávid + * Copyright 2018 Łukasz Wojniłowicz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SQLCIPHERDRIVERTEST_H +#define SQLCIPHERDRIVERTEST_H + +#include +#include +#include + +class qsqlciphertest : public QObject +{ + Q_OBJECT +private: + int data(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void isSQLCipherUsed(); + void createEncryptedDatabase(); + void createTable(); + void writeData_data(); + void writeData(); + void reopenDatabase(); + void countData(); + void readData_data(); + void readData(); + +private: + QTemporaryFile m_file; + QSqlDatabase m_db; +}; + +#endif // SQLCIPHERDRIVERTEST_H diff --git a/kmymoney/plugins/sqlcipher/tests/qsqlcipher-test.cpp b/kmymoney/plugins/sqlcipher/tests/qsqlcipher-test.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/sqlcipher/tests/qsqlcipher-test.cpp @@ -0,0 +1,178 @@ +/* + * Copyright 2014 Christian Dávid + * Copyright 2018 Łukasz Wojniłowicz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "qsqlcipher-test.h" + +#include +#include +#include +#include +#include + +QTEST_GUILESS_MAIN(qsqlciphertest) + +static const auto passphrase(QStringLiteral("blue valentines")); +static const auto sqlDriverName(QStringLiteral("QSQLCIPHER")); + +void qsqlciphertest::initTestCase() +{ + // Called before the first testfunction is executed + auto drivers = QSqlDatabase::drivers(); + QVERIFY(drivers.contains(sqlDriverName)); + QVERIFY(m_file.open()); // open new temporary file just to get it's filename + m_file.resize(0); // it's important for the file to be empty during creation of encrypted database + m_file.close(); // only filename was needed, so close it + m_db = QSqlDatabase::addDatabase(sqlDriverName); + QVERIFY(m_db.isValid()); + m_db.setDatabaseName(m_file.fileName()); + m_db.setPassword(passphrase); + QVERIFY(m_db.open()); +} + +void qsqlciphertest::cleanupTestCase() +{ + m_db.close(); + m_db = QSqlDatabase(); // otherwise QSqlDatabase::removeDatabase says that m_db is still in use + m_db.removeDatabase(QSqlDatabase::defaultConnection); +} + +void qsqlciphertest::isSQLCipherUsed() +{ + QSqlQuery query(m_db); + QVERIFY(query.exec("PRAGMA cipher_version")); + QVERIFY(query.next()); // if that fails, then probably libsqlite3 instead libsqlcipher is in use + qInfo() << "Cipher version: " << query.value(0).toString(); +} + +/** + * taken from @url http://www.keenlogics.com/qt-sqlite-driver-plugin-for-encrypted-databases-using-sqlcipher-windows/ + * thank you! + */ +void qsqlciphertest::createEncryptedDatabase() +{ + QSqlQuery query(m_db); + QVERIFY(query.exec(QString::fromLatin1("PRAGMA key = '%1'").arg(passphrase))); // this should happen immediately after opening the database, otherwise it cannot be encrypted + m_file.open(); + // http://www.sqlite.org/fileformat.html#database_header + auto header = QString(m_file.read(16)); + QVERIFY(header != QLatin1String("SQLite format 3\000")); // encrypted database has scrambled content in contrast to its regular SQLite counterpart + m_file.close(); +} + +void qsqlciphertest::createTable() +{ + QSqlQuery query(m_db); + QVERIFY(query.exec("CREATE TABLE test (" + "id int PRIMARY_KEY," + "text varchar(20)" + ");" + )); + QVERIFY(!query.lastError().isValid()); +} + +void qsqlciphertest::writeData_data() +{ + data(); +} + +void qsqlciphertest::writeData() +{ + QFETCH(int, id); + QFETCH(QString, text); + + QSqlQuery query(m_db); + query.prepare("INSERT INTO test (id, text) " + "VALUES (:id, :text);" + ); + query.bindValue(":id", id); + query.bindValue(":text", text); + QVERIFY(query.exec()); + QVERIFY(!query.lastError().isValid()); + QCOMPARE(query.numRowsAffected(), 1); +} + +void qsqlciphertest::reopenDatabase() +{ + // close the encrypted database... + m_db.close(); + m_db = QSqlDatabase(); + m_db.removeDatabase(QSqlDatabase::defaultConnection); + + // ... and open it anew + m_db = QSqlDatabase::addDatabase(sqlDriverName); + QVERIFY(m_db.isValid()); + m_db.setDatabaseName(m_file.fileName()); + m_db.setPassword(passphrase); + QVERIFY(m_db.open()); + + QSqlQuery query(m_db); + QVERIFY(query.exec(QString::fromLatin1("PRAGMA key = '%1'").arg(passphrase))); // this should happen immediately after opening the database, to decrypt it properly + + QVERIFY(query.exec("SELECT count(*) FROM sqlite_master;")); // this is a check if the password correctly decrypts the database + QVERIFY(!query.lastError().isValid()); +} + +void qsqlciphertest::countData() +{ + QSqlQuery query(m_db); + QVERIFY(query.exec("SELECT count(id) FROM test;")); + QVERIFY(query.next()); + QCOMPARE(query.value(0).toInt(), data()); +} + +void qsqlciphertest::readData_data() +{ + data(); +} + +void qsqlciphertest::readData() +{ + QFETCH(int, id); + QFETCH(QString, text); + + QSqlQuery query(m_db); + query.prepare("SELECT id, text FROM test WHERE id = :id;"); + query.bindValue(":id", QVariant::fromValue(id)); + QVERIFY(query.exec()); + + QVERIFY(!query.lastError().isValid()); + QVERIFY(query.next()); + + QSqlRecord record = query.record(); + int idIndex = record.indexOf("id"); + QVERIFY(idIndex != -1); + int textIndex = record.indexOf("text"); + QVERIFY(textIndex != -1); + + QCOMPARE(query.value(0).toInt(), id); + QCOMPARE(query.value(1).toString(), text); + QVERIFY(!query.next()); +} + +int qsqlciphertest::data() +{ + QTest::addColumn("id"); + QTest::addColumn("text"); + + int i = 0; + QTest::newRow("string Hello World") << ++i << "Hello World"; + QTest::newRow("string 20 characters") << ++i << "ABCDEFGHIJKLMNOPQRST"; + QTest::newRow("simple string") << ++i << "simple"; + + return i; // return number of rows! +} diff --git a/kmymoney/plugins/sqlcipher/tests/sqlcipherdriverinstalltest.h b/kmymoney/plugins/sqlcipher/tests/sqlcipherdriverinstalltest.h deleted file mode 100644 --- a/kmymoney/plugins/sqlcipher/tests/sqlcipherdriverinstalltest.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * QSqlDriver for SQLCipher - * Copyright 2014 Christian David - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef SQLCIPHERDRIVERINSTALLTEST_H -#define SQLCIPHERDRIVERINSTALLTEST_H - -#include - -class SqlCipherDriverInstallTest : public QObject -{ - Q_OBJECT -private Q_SLOTS: - void findDriver(); - void createDatabase(); -}; - -#endif // SQLCIPHERDRIVERINSTALLTEST_H diff --git a/kmymoney/plugins/sqlcipher/tests/sqlcipherdriverinstalltest.cpp b/kmymoney/plugins/sqlcipher/tests/sqlcipherdriverinstalltest.cpp deleted file mode 100644 --- a/kmymoney/plugins/sqlcipher/tests/sqlcipherdriverinstalltest.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * QSqlDriver for SQLCipher - * Copyright 2014 Christian David - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "sqlcipherdriverinstalltest.h" - -#include -#include - -QTEST_GUILESS_MAIN(SqlCipherDriverInstallTest); - -void SqlCipherDriverInstallTest::findDriver() -{ - QStringList drivers = QSqlDatabase::drivers(); - QVERIFY(drivers.contains("SQLCIPHER")); -} - -void SqlCipherDriverInstallTest::createDatabase() -{ - QSqlDatabase db = QSqlDatabase::addDatabase("SQLCIPHER"); - QVERIFY(db.isValid()); - db.setDatabaseName(":memory:"); - QVERIFY(db.open()); - db.close(); -} diff --git a/kmymoney/plugins/sqlcipher/tests/sqlcipherdrivertest.h b/kmymoney/plugins/sqlcipher/tests/sqlcipherdrivertest.h deleted file mode 100644 --- a/kmymoney/plugins/sqlcipher/tests/sqlcipherdrivertest.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * QSqlDriver for SQLCipher - * Copyright 2014 Christian David - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef SQLCIPHERDRIVERTEST_H -#define SQLCIPHERDRIVERTEST_H - -#include -#include -#include - -class sqlcipherdrivertest : public QObject -{ - Q_OBJECT -private: - int data(); - -private Q_SLOTS: - void initTestCase(); - void cleanupTestCase(); - - void init(); - void cleanup(); - - void createUnencryptedDatabase(); - - void createDatabase(); - void checkReadAccess(); - void createTable(); - void write_data(); - void write(); - void count(); - void readData_data(); - void readData(); - - void isFileEncrpyted(); - - void checkForEncryption_data(); - void checkForEncryption(); - - void reopenDatabase(); - void readData2_data(); - void readData2(); - -private: - QTemporaryFile file; - QSqlDatabase db; -}; - -#endif // SQLCIPHERDRIVERTEST_H diff --git a/kmymoney/plugins/sqlcipher/tests/sqlcipherdrivertest.cpp b/kmymoney/plugins/sqlcipher/tests/sqlcipherdrivertest.cpp deleted file mode 100644 --- a/kmymoney/plugins/sqlcipher/tests/sqlcipherdrivertest.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/* - * QSqlDriver for SQLCipher - * Copyright 2014 Christian David - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "sqlcipherdrivertest.h" - -#include -#include -#include -#include -#include -#include - -QTEST_GUILESS_MAIN(sqlcipherdrivertest); - -static const QString passphrase = QLatin1String("blue valentines"); - -void sqlcipherdrivertest::initTestCase() -{ - // Called before the first testfunction is executed - QVERIFY(file.open()); - file.close(); -} - -void sqlcipherdrivertest::cleanupTestCase() -{ - // Called after the last testfunction was executed - db.close(); -} - -void sqlcipherdrivertest::init() -{ - // Called before each testfunction is executed -} - -void sqlcipherdrivertest::cleanup() -{ - // Called after every testfunction -} - -void sqlcipherdrivertest::createUnencryptedDatabase() -{ - QTemporaryFile file; - file.open(); - file.close(); - - QSqlDatabase db = QSqlDatabase::addDatabase("SQLCIPHER"); - QVERIFY(db.isValid()); - db.setDatabaseName(file.fileName()); - QVERIFY(db.open()); - - QSqlQuery query; - QVERIFY(query.exec("SELECT count(*) FROM sqlite_master;")); - QVERIFY(!query.lastError().isValid()); - QVERIFY(query.next()); - QCOMPARE(query.value(0), QVariant(0)); - db.close(); -} - -void sqlcipherdrivertest::createDatabase() -{ - QSqlDatabase db = QSqlDatabase::addDatabase("SQLCIPHER"); - QVERIFY(db.isValid()); - db.setDatabaseName(file.fileName()); - db.setPassword(passphrase); - QVERIFY(db.open()); -} - -void sqlcipherdrivertest::checkReadAccess() -{ - QSqlQuery query; - QVERIFY(query.exec("SELECT count(*) FROM sqlite_master;")); - QVERIFY(!query.lastError().isValid()); -} - -void sqlcipherdrivertest::createTable() -{ - QSqlQuery query; - QVERIFY(query.exec("CREATE TABLE test (" - "id int PRIMARY_KEY," - "text varchar(20)" - ");" - )); - QVERIFY(!query.lastError().isValid()); -} - -int sqlcipherdrivertest::data() -{ - QTest::addColumn("id"); - QTest::addColumn("text"); - - QTest::newRow("string Hello World") << 1 << "Hello World"; - QTest::newRow("string 20 characters") << 2 << "ABCDEFGHIJKLMNOPQRST"; - QTest::newRow("simple string") << 3 << "simple"; - - return 3; // return number of rows! -} - -void sqlcipherdrivertest::write_data() -{ - data(); -} - -void sqlcipherdrivertest::write() -{ - QFETCH(int, id); - QFETCH(QString, text); - - QSqlQuery query; - query.prepare("INSERT INTO test (id, text) " - "VALUES (:id, :text);" - ); - query.bindValue(":id", id); - query.bindValue(":text", text); - QVERIFY(query.exec()); - QVERIFY(!query.lastError().isValid()); - QCOMPARE(query.numRowsAffected(), 1); -} - -void sqlcipherdrivertest::count() -{ - QSqlQuery query; - QVERIFY(query.exec("SELECT count(id) FROM test;")); - QVERIFY(query.next()); - QCOMPARE(query.value(0).toInt(), data()); -} - -void sqlcipherdrivertest::readData_data() -{ - data(); -} - -void sqlcipherdrivertest::readData() -{ - QFETCH(int, id); - QFETCH(QString, text); - - QSqlQuery query; - query.prepare("SELECT id, text FROM test WHERE id = :id;"); - query.bindValue(":id", QVariant::fromValue(id)); - QVERIFY(query.exec()); - - QVERIFY(!query.lastError().isValid()); - QVERIFY(query.next()); - - QSqlRecord record = query.record(); - int idIndex = record.indexOf("id"); - QVERIFY(idIndex != -1); - int textIndex = record.indexOf("text"); - QVERIFY(textIndex != -1); - - QCOMPARE(query.value(0).toInt(), id); - QCOMPARE(query.value(1).toString(), text); - QVERIFY(!query.next()); -} - -/** - * taken from @url http://www.keenlogics.com/qt-sqlite-driver-plugin-for-encrypted-databases-using-sqlcipher-windows/ - * thank you! - */ -void sqlcipherdrivertest::isFileEncrpyted() -{ - file.open(); - // http://www.sqlite.org/fileformat.html#database_header - QString header = file.read(16); - QVERIFY(header != "SQLite format 3\000"); - file.close(); -} - -void sqlcipherdrivertest::checkForEncryption_data() -{ - data(); -} - -void sqlcipherdrivertest::checkForEncryption() -{ - QFETCH(QString, text); - - QVERIFY(file.open()); - - QString line; - QTextStream in(&file); - - while (!in.atEnd()) { - line = in.readLine(); - if (line.contains(text)) { - QFAIL("Found unencrypted text in database file"); - } - } - - file.close(); -} - -void sqlcipherdrivertest::reopenDatabase() -{ - db.close(); - - db = QSqlDatabase::addDatabase("SQLCIPHER"); - QVERIFY(db.isValid()); - db.setDatabaseName(file.fileName()); - QVERIFY(db.open()); - - QSqlQuery query; - query.prepare("PRAGMA key = '" + passphrase + '\''); - QVERIFY(query.exec()); - - QVERIFY(!query.lastError().isValid()); - - checkReadAccess(); -} - -void sqlcipherdrivertest::readData2_data() -{ - data(); -} - -void sqlcipherdrivertest::readData2() -{ - readData(); -}