diff options
| author | 2014-12-06 17:33:25 -0500 | |
|---|---|---|
| committer | 2014-12-06 17:33:25 -0500 | |
| commit | 7167ce41b61d2ba2cdb526777a4233eb84a3b66a (patch) | |
| tree | a35c14143716e1f2c98f808c81f89426045a946f /SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h | |
Imported Upstream version 2.99.6upstream/2.99.6
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h')
| -rw-r--r-- | SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h | 1157 |
1 files changed, 1157 insertions, 0 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h new file mode 100644 index 0000000..d8e54b4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h @@ -0,0 +1,1157 @@ +#ifndef ABSTRACTDB3_H +#define ABSTRACTDB3_H + +#include "db/abstractdb.h" +#include "parser/lexer.h" +#include "common/utils_sql.h" +#include "common/unused.h" +#include "services/collationmanager.h" +#include "sqlitestudio.h" +#include "db/sqlerrorcodes.h" +#include <QThread> +#include <QPointer> +#include <QDebug> + +/** + * @brief Complete implementation of SQLite 3 driver for SQLiteStudio. + * + * Inherit this when implementing Db for SQLite 3. In most cases you will only need + * to create one public constructor, which forwards parameters to the AbstractDb constructor. + * This be sufficient to implement SQLite 3 database plugin. + * Just link it with proper SQLite library. + * + * The template parameter should provide all necessary SQLite symbols used by this implementation. + * This way every Db plugin can provide it's own symbols to work on SQLite and so it allows + * for loading multiple SQLite libraries into the same application, while symbols in each library + * can be different (and should be different, to avoid name conflicts and symbol overlapping). + * See how it's done in dbsqlite3.h. + * + * @see DbQt + */ +template <class T> +class AbstractDb3 : public AbstractDb +{ + public: + /** + * @brief Creates SQLite database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See AbstractDb for details. + * + * All values from this constructor are just passed to AbstractDb constructor. + */ + AbstractDb3(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions); + ~AbstractDb3(); + + protected: + bool isOpenInternal(); + void interruptExecution(); + QString getErrorTextInternal(); + int getErrorCodeInternal(); + bool openInternal(); + bool closeInternal(); + void initAfterOpen(); + SqlQueryPtr prepare(const QString& query); + QString getTypeLabel(); + bool deregisterFunction(const QString& name, int argCount); + bool registerScalarFunction(const QString& name, int argCount); + bool registerAggregateFunction(const QString& name, int argCount); + bool registerCollationInternal(const QString& name); + bool deregisterCollationInternal(const QString& name); + + private: + class Query : public SqlQuery + { + public: + class Row : public SqlResultsRow + { + public: + int init(const QStringList& columns, typename T::stmt* stmt); + + private: + int getValue(typename T::stmt* stmt, int col, QVariant& value); + }; + + Query(AbstractDb3<T>* db, const QString& query); + ~Query(); + + QString getErrorText(); + int getErrorCode(); + QStringList getColumnNames(); + int columnCount(); + qint64 rowsAffected(); + void finalize(); + + protected: + SqlResultsRowPtr nextInternal(); + bool hasNextInternal(); + bool execInternal(const QList<QVariant>& args); + bool execInternal(const QHash<QString, QVariant>& args); + + private: + int prepareStmt(); + int resetStmt(); + int bindParam(int paramIdx, const QVariant& value); + int fetchFirst(); + int fetchNext(); + bool checkDbState(); + void copyErrorFromDb(); + void copyErrorToDb(); + void setError(int code, const QString& msg); + + QPointer<AbstractDb3<T>> db; + typename T::stmt* stmt = nullptr; + int errorCode = T::OK; + QString errorMessage; + int colCount = 0; + QStringList colNames; + int affected = 0; + bool rowAvailable = false; + }; + + struct CollationUserData + { + QString name; + AbstractDb3<T>* db = nullptr; + }; + + QString extractLastError(); + void cleanUp(); + void resetError(); + + /** + * @brief Registers function to call when unknown collation was encountered by the SQLite. + * + * For unknown collations SQLite calls function registered by this method and expects it to register + * proper function handling that collation, otherwise the query will result with error. + * + * The default collation handler does a simple QString::compare(), case insensitive. + */ + void registerDefaultCollationRequestHandler(); + + /** + * @brief Stores given result in function's context. + * @param context Custom SQL function call context. + * @param result Value returned from function execution. + * @param ok true if the result is from a successful execution, or false if the result contains error message (QString). + * + * This method is called after custom implementation of the function was evaluated and it returned the result. + * It stores the result in function's context, so it becomes the result of the function call. + */ + static void storeResult(typename T::context* context, const QVariant& result, bool ok); + + /** + * @brief Converts SQLite arguments into the list of argument values. + * @param argCount Number of arguments. + * @param args SQLite argument values. + * @return Convenient Qt list with argument values as QVariant. + * + * This function does necessary conversions reflecting internal SQLite datatype, so if the type + * was for example BLOB, then the QVariant will be a QByteArray, etc. + */ + static QList<QVariant> getArgs(int argCount, typename T::value** args); + + /** + * @brief Evaluates requested function using defined implementation code and provides result. + * @param context SQL function call context. + * @param argCount Number of arguments passed to the function. + * @param args Arguments passed to the function. + * + * This method is aware of the implementation language and the code defined for it, + * so it delegates the execution to the proper plugin handling that language. Then it stores + * result returned from the plugin in function's context, so it becomes function's result. + * + * This method is called for scalar functions. + * + * @see DbQt::evaluateScalar() + */ + static void evaluateScalar(typename T::context* context, int argCount, typename T::value** args); + + /** + * @brief Evaluates requested function using defined implementation code and provides result. + * @param context SQL function call context. + * @param argCount Number of arguments passed to the function. + * @param args Arguments passed to the function. + * + * This method is called for aggregate functions. + * + * If this is the first call to this function using this context, then it will execute + * both "initial" and then "per step" code for this function implementation. + * + * @see DbQt3::evaluateScalar() + * @see DbQt::evaluateAggregateStep() + */ + static void evaluateAggregateStep(typename T::context* context, int argCount, typename T::value** args); + + /** + * @brief Evaluates "final" code for aggregate function. + * @param context SQL function call context. + * + * This method is called for aggregate functions. + * + * It's called once, at the end of aggregate function evaluation. + * It executes "final" code of the function implementation. + */ + static void evaluateAggregateFinal(typename T::context* context); + + /** + * @brief Evaluates code of the collation. + * @param userData Collation user data (name of the collation inside). + * @param length1 Number of characters in value1 (excluding \0). + * @param value1 First value to compare. + * @param length2 Number of characters in value2 (excluding \0). + * @param value2 Second value to compare. + * @return -1, 0, or 1, as SQLite's collation specification demands it. + */ + static int evaluateCollation(void* userData, int length1, const void* value1, int length2, const void* value2); + + /** + * @brief Cleans up collation user data when collation is deregistered. + * @param userData User data to delete. + */ + static void deleteCollationUserData(void* userData); + + /** + * @brief Destructor for function user data object. + * @param dataPtr Pointer to the user data object. + * + * This is called by SQLite when the function is deregistered. + */ + static void deleteUserData(void* dataPtr); + + /** + * @brief Allocates and/or returns shared memory for the aggregate SQL function call. + * @param context SQL function call context. + * @return Pointer to the memory. + * + * It allocates exactly the number of bytes required to store pointer to a QHash. + * The memory is released after the aggregate function is finished. + */ + static void* getContextMemPtr(typename T::context* context); + + /** + * @brief Allocates and/or returns QHash shared across all aggregate function steps. + * @param context SQL function call context. + * @return Shared hash table. + * + * The hash table is created before initial aggregate function step is made. + * Then it's shared across all further steps (using this method to get it) + * and then releases the memory after the last (final) step of the function call. + */ + static QHash<QString,QVariant> getAggregateContext(typename T::context* context); + + /** + * @brief Sets new value of the aggregate function shared hash table. + * @param context SQL function call context. + * @param aggregateContext New shared hash table value to store. + * + * This should be called after each time the context was requested with getAggregateContext() and then modified. + */ + static void setAggregateContext(typename T::context* context, const QHash<QString,QVariant>& aggregateContext); + + /** + * @brief Releases aggregate function shared hash table. + * @param context SQL function call context. + * + * This should be called from final aggregate function step to release the shared context (delete QHash). + * The memory used to store pointer to the shared context will be released by the SQLite itself. + */ + static void releaseAggregateContext(typename T::context* context); + + /** + * @brief Registers default collation for requested collation. + * @param fnUserData User data passed when registering collation request handling function. + * @param fnDbHandle Database handle for which this call is being made. + * @param eTextRep Text encoding (for now always T::UTF8). + * @param collationName Name of requested collation. + * + * This function is called by SQLite to order registering collation with given name. We register default collation, + * cause all known collations should already be registered. + * + * Default collation is implemented by evaluateDefaultCollation(). + */ + static void registerDefaultCollation(void* fnUserData, typename T::handle* fnDbHandle, int eTextRep, const char* collationName); + + /** + * @brief Called as a default collation implementation. + * @param userData Collation user data, not used. + * @param length1 Number of characters in value1 (excluding \0). + * @param value1 First value to compare. + * @param length2 Number of characters in value2 (excluding \0). + * @param value2 Second value to compare. + * @return -1, 0, or 1, as SQLite's collation specification demands it. + */ + static int evaluateDefaultCollation(void* userData, int length1, const void* value1, int length2, const void* value2); + + typename T::handle* dbHandle = nullptr; + QString dbErrorMessage; + int dbErrorCode = T::OK; + QList<Query*> queries; + + /** + * @brief User data for default collation request handling function. + * + * That function doesn't have destructor function pointer, so we need to keep track of that user data + * and delete it when database is closed. + */ + CollationUserData* defaultCollationUserData = nullptr; +}; + +//------------------------------------------------------------------------------------ +// AbstractDb3 +//------------------------------------------------------------------------------------ + +template <class T> +AbstractDb3<T>::AbstractDb3(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) : + AbstractDb(name, path, connOptions) +{ +} + +template <class T> +AbstractDb3<T>::~AbstractDb3() +{ + if (isOpenInternal()) + closeInternal(); +} + +template <class T> +bool AbstractDb3<T>::isOpenInternal() +{ + return dbHandle != nullptr; +} + +template <class T> +void AbstractDb3<T>::interruptExecution() +{ + if (!isOpenInternal()) + return; + + T::interrupt(dbHandle); +} + +template <class T> +QString AbstractDb3<T>::getErrorTextInternal() +{ + return dbErrorMessage; +} + +template <class T> +int AbstractDb3<T>::getErrorCodeInternal() +{ + return dbErrorCode; +} + +template <class T> +bool AbstractDb3<T>::openInternal() +{ + resetError(); + typename T::handle* handle = nullptr; + int res = T::open_v2(path.toUtf8().constData(), &handle, T::OPEN_READWRITE|T::OPEN_CREATE, nullptr); + if (res != T::OK) + { + if (handle) + T::close(handle); + + dbErrorMessage = tr("Could not open database: %1").arg(extractLastError()); + dbErrorCode = res; + return false; + } + dbHandle = handle; + return true; +} + +template <class T> +bool AbstractDb3<T>::closeInternal() +{ + resetError(); + if (!dbHandle) + return false; + + cleanUp(); + + int res = T::close(dbHandle); + if (res != T::OK) + { + dbErrorMessage = tr("Could not close database: %1").arg(extractLastError()); + dbErrorCode = res; + qWarning() << "Error closing database. That's weird:" << dbErrorMessage; + return false; + } + dbHandle = nullptr; + return true; +} + +template <class T> +void AbstractDb3<T>::initAfterOpen() +{ + T::enable_load_extension(dbHandle, true); + registerDefaultCollationRequestHandler();; + exec("PRAGMA foreign_keys = 1;", Flag::NO_LOCK); + exec("PRAGMA recursive_triggers = 1;", Flag::NO_LOCK); +} + +template <class T> +SqlQueryPtr AbstractDb3<T>::prepare(const QString& query) +{ + return SqlQueryPtr(new Query(this, query)); +} + +template <class T> +QString AbstractDb3<T>::getTypeLabel() +{ + return T::label; +} + +template <class T> +bool AbstractDb3<T>::deregisterFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + T::create_function(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, 0, nullptr, nullptr, nullptr); + return true; +} + +template <class T> +bool AbstractDb3<T>::registerScalarFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + FunctionUserData* userData = new FunctionUserData; + userData->db = this; + userData->name = name; + userData->argCount = argCount; + + int res = T::create_function_v2(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, userData, + &AbstractDb3<T>::evaluateScalar, + nullptr, + nullptr, + &AbstractDb3<T>::deleteUserData); + + return res == T::OK; +} + +template <class T> +bool AbstractDb3<T>::registerAggregateFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + FunctionUserData* userData = new FunctionUserData; + userData->db = this; + userData->name = name; + userData->argCount = argCount; + + int res = T::create_function_v2(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, userData, + nullptr, + &AbstractDb3<T>::evaluateAggregateStep, + &AbstractDb3<T>::evaluateAggregateFinal, + &AbstractDb3<T>::deleteUserData); + + return res == T::OK; +} + +template <class T> +bool AbstractDb3<T>::registerCollationInternal(const QString& name) +{ + if (!dbHandle) + return false; + + CollationUserData* userData = new CollationUserData; + userData->name = name; + + int res = T::create_collation_v2(dbHandle, name.toUtf8().constData(), T::UTF8, userData, + &AbstractDb3<T>::evaluateCollation, + &AbstractDb3<T>::deleteCollationUserData); + return res == T::OK; +} + +template <class T> +bool AbstractDb3<T>::deregisterCollationInternal(const QString& name) +{ + if (!dbHandle) + return false; + + T::create_collation_v2(dbHandle, name.toUtf8().constData(), T::UTF8, nullptr, nullptr, nullptr); + return true; +} + +template <class T> +QString AbstractDb3<T>::extractLastError() +{ + dbErrorCode = T::extended_errcode(dbHandle); + dbErrorMessage = QString::fromUtf8(T::errmsg(dbHandle)); + return dbErrorMessage; +} + +template <class T> +void AbstractDb3<T>::cleanUp() +{ + for (Query* q : queries) + q->finalize(); + + safe_delete(defaultCollationUserData); +} + +template <class T> +void AbstractDb3<T>::resetError() +{ + dbErrorCode = 0; + dbErrorMessage = QString::null; +} + +template <class T> +void AbstractDb3<T>::storeResult(typename T::context* context, const QVariant& result, bool ok) +{ + if (!ok) + { + QString str = result.toString(); + T::result_error16(context, str.utf16(), str.size() * sizeof(QChar)); + return; + } + + // Code below is a modified code from Qt (its SQLite plugin). + if (result.isNull()) + { + T::result_null(context); + return; + } + + switch (result.type()) + { + case QVariant::ByteArray: + { + QByteArray ba = result.toByteArray(); + T::result_blob(context, ba.constData(), ba.size(), T::TRANSIENT()); + break; + } + case QVariant::Int: + case QVariant::Bool: + { + T::result_int(context, result.toInt()); + break; + } + case QVariant::Double: + { + T::result_double(context, result.toDouble()); + break; + } + case QVariant::UInt: + case QVariant::LongLong: + { + T::result_int64(context, result.toLongLong()); + break; + } + case QVariant::List: + { + QList<QVariant> list = result.toList(); + QStringList strList; + for (const QVariant& v : list) + strList << v.toString(); + + QString str = strList.join(" "); + T::result_text16(context, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + break; + } + case QVariant::StringList: + { + QString str = result.toStringList().join(" "); + T::result_text16(context, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + break; + } + default: + { + // T::TRANSIENT makes sure that sqlite buffers the data + QString str = result.toString(); + T::result_text16(context, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + break; + } + } +} + +template <class T> +QList<QVariant> AbstractDb3<T>::getArgs(int argCount, typename T::value** args) +{ + int dataType; + QList<QVariant> results; + QVariant value; + + // The loop below uses slightly modified code from Qt (its SQLite plugin) to extract values. + for (int i = 0; i < argCount; i++) + { + dataType = T::value_type(args[i]); + switch (dataType) + { + case T::INTEGER: + value = T::value_int64(args[i]); + break; + case T::BLOB: + value = QByteArray( + static_cast<const char*>(T::value_blob(args[i])), + T::value_bytes(args[i]) + ); + break; + case T::FLOAT: + value = T::value_double(args[i]); + break; + case T::NULL_TYPE: + value = QVariant(QVariant::String); + break; + default: + value = QString( + reinterpret_cast<const QChar*>(T::value_text16(args[i])), + T::value_bytes16(args[i]) / sizeof(QChar) + ); + break; + } + results << value; + } + return results; +} + +template <class T> +void AbstractDb3<T>::evaluateScalar(typename T::context* context, int argCount, typename T::value** args) +{ + QList<QVariant> argList = getArgs(argCount, args); + bool ok = true; + QVariant result = AbstractDb::evaluateScalar(T::user_data(context), argList, ok); + storeResult(context, result, ok); +} + +template <class T> +void AbstractDb3<T>::evaluateAggregateStep(typename T::context* context, int argCount, typename T::value** args) +{ + void* dataPtr = T::user_data(context); + QList<QVariant> argList = getArgs(argCount, args); + QHash<QString,QVariant> aggregateContext = getAggregateContext(context); + + AbstractDb::evaluateAggregateStep(dataPtr, aggregateContext, argList); + + setAggregateContext(context, aggregateContext); +} + +template <class T> +void AbstractDb3<T>::evaluateAggregateFinal(typename T::context* context) +{ + void* dataPtr = T::user_data(context); + QHash<QString,QVariant> aggregateContext = getAggregateContext(context); + + bool ok = true; + QVariant result = AbstractDb::evaluateAggregateFinal(dataPtr, aggregateContext, ok); + + storeResult(context, result, ok); + releaseAggregateContext(context); +} + +template <class T> +int AbstractDb3<T>::evaluateCollation(void* userData, int length1, const void* value1, int length2, const void* value2) +{ + UNUSED(length1); + UNUSED(length2); + CollationUserData* collUserData = reinterpret_cast<CollationUserData*>(userData); + return COLLATIONS->evaluate(collUserData->name, QString::fromUtf8((const char*)value1), QString::fromUtf8((const char*)value2)); +} + +template <class T> +void AbstractDb3<T>::deleteCollationUserData(void* userData) +{ + if (!userData) + return; + + CollationUserData* collUserData = reinterpret_cast<CollationUserData*>(userData); + delete collUserData; +} + +template <class T> +void AbstractDb3<T>::deleteUserData(void* dataPtr) +{ + if (!dataPtr) + return; + + FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr); + delete userData; +} + +template <class T> +void* AbstractDb3<T>::getContextMemPtr(typename T::context* context) +{ + return T::aggregate_context(context, sizeof(QHash<QString,QVariant>**)); +} + +template <class T> +QHash<QString, QVariant> AbstractDb3<T>::getAggregateContext(typename T::context* context) +{ + return AbstractDb::getAggregateContext(getContextMemPtr(context)); +} + +template <class T> +void AbstractDb3<T>::setAggregateContext(typename T::context* context, const QHash<QString, QVariant>& aggregateContext) +{ + AbstractDb::setAggregateContext(getContextMemPtr(context), aggregateContext); +} + +template <class T> +void AbstractDb3<T>::releaseAggregateContext(typename T::context* context) +{ + AbstractDb::releaseAggregateContext(getContextMemPtr(context)); +} + +template <class T> +void AbstractDb3<T>::registerDefaultCollation(void* fnUserData, typename T::handle* fnDbHandle, int eTextRep, const char* collationName) +{ + CollationUserData* defUserData = reinterpret_cast<CollationUserData*>(fnUserData); + if (!defUserData) + { + qWarning() << "Null userData in AbstractDb3<T>::registerDefaultCollation()."; + return; + } + + AbstractDb3<T>* db = defUserData->db; + if (!db) + { + qWarning() << "No database defined in userData of AbstractDb3<T>::registerDefaultCollation()."; + return; + } + + // If SQLite seeks for collation implementation with different encoding, we force it to use existing one. + if (db->isCollationRegistered(QString::fromUtf8(collationName))) + return; + + // Check if dbHandle matches - just in case + if (db->dbHandle != fnDbHandle) + { + qWarning() << "Mismatch of dbHandle in AbstractDb3<T>::registerDefaultCollation()."; + return; + } + + int res = T::create_collation_v2(fnDbHandle, collationName, eTextRep, nullptr, + &AbstractDb3<T>::evaluateDefaultCollation, nullptr); + + if (res != T::OK) + qWarning() << "Could not register default collation in AbstractDb3<T>::registerDefaultCollation()."; +} + +template <class T> +int AbstractDb3<T>::evaluateDefaultCollation(void* userData, int length1, const void* value1, int length2, const void* value2) +{ + UNUSED(userData); + UNUSED(length1); + UNUSED(length2); + return COLLATIONS->evaluateDefault(QString::fromUtf8((const char*)value1), QString::fromUtf8((const char*)value2)); +} + +template <class T> +void AbstractDb3<T>::registerDefaultCollationRequestHandler() +{ + if (!dbHandle) + return; + + defaultCollationUserData = new CollationUserData; + defaultCollationUserData->db = this; + + int res = T::collation_needed(dbHandle, defaultCollationUserData, &AbstractDb3<T>::registerDefaultCollation); + if (res != T::OK) + qWarning() << "Could not register default collation request handler. Unknown collations will cause errors."; +} + +//------------------------------------------------------------------------------------ +// Results +//------------------------------------------------------------------------------------ + +template <class T> +AbstractDb3<T>::Query::Query(AbstractDb3<T>* db, const QString& query) : + db(db) +{ + this->query = query; + db->queries << this; +} + +template <class T> +AbstractDb3<T>::Query::~Query() +{ + if (db.isNull()) + return; + + finalize(); + db->queries.removeOne(this); +} + +template <class T> +void AbstractDb3<T>::Query::copyErrorFromDb() +{ + if (db->dbErrorCode != 0) + { + errorCode = db->dbErrorCode; + errorMessage = db->dbErrorMessage; + return; + } +} + +template <class T> +void AbstractDb3<T>::Query::copyErrorToDb() +{ + db->dbErrorCode = errorCode; + db->dbErrorMessage = errorMessage; +} + +template <class T> +void AbstractDb3<T>::Query::setError(int code, const QString& msg) +{ + if (errorCode != T::OK) + return; // don't overwrite first error + + errorCode = code; + errorMessage = msg; + copyErrorToDb(); +} + +template <class T> +int AbstractDb3<T>::Query::prepareStmt() +{ + const char* tail; + QByteArray queryBytes = query.toUtf8(); + int res = T::prepare_v2(db->dbHandle, queryBytes.constData(), queryBytes.size(), &stmt, &tail); + if (res != T::OK) + { + stmt = nullptr; + db->extractLastError(); + copyErrorFromDb(); + return res; + } + + if (tail && !QString::fromUtf8(tail).trimmed().isEmpty()) + qWarning() << "Executed query left with tailing contents:" << tail << ", while executing query:" << query; + + return T::OK; +} + +template <class T> +int AbstractDb3<T>::Query::resetStmt() +{ + errorCode = 0; + errorMessage = QString::null; + affected = 0; + colCount = -1; + rowAvailable = false; + + int res = T::reset(stmt); + if (res != T::OK) + { + stmt = nullptr; + setError(res, QString::fromUtf8(T::errmsg(db->dbHandle))); + return res; + } + return T::OK; +} + +template <class T> +bool AbstractDb3<T>::Query::execInternal(const QList<QVariant>& args) +{ + if (!checkDbState()) + return false; + + ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite3, flags.testFlag(Db::Flag::NO_LOCK)); + QueryWithParamCount queryWithParams = getQueryWithParamCount(query, Dialect::Sqlite3); + + int res; + if (stmt) + res = resetStmt(); + else + res = prepareStmt(); + + if (res != T::OK) + return false; + + for (int paramIdx = 1; paramIdx <= queryWithParams.second; paramIdx++) + { + res = bindParam(paramIdx, args[paramIdx-1]); + if (res != T::OK) + { + db->extractLastError(); + copyErrorFromDb(); + return false; + } + } + + bool ok = (fetchFirst() == T::OK); + if (ok) + db->checkForDroppedObject(query); + + return ok; +} + +template <class T> +bool AbstractDb3<T>::Query::execInternal(const QHash<QString, QVariant>& args) +{ + if (!checkDbState()) + return false; + + ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite3, flags.testFlag(Db::Flag::NO_LOCK)); + + QueryWithParamNames queryWithParams = getQueryWithParamNames(query, Dialect::Sqlite3); + + int res; + if (stmt) + res = resetStmt(); + else + res = prepareStmt(); + + if (res != T::OK) + return false; + + int paramIdx = 1; + foreach (const QString& paramName, queryWithParams.second) + { + if (!args.contains(paramName)) + { + setError(SqlErrorCode::OTHER_EXECUTION_ERROR, "Error while preparing statement: could not bind parameter " + paramName); + return false; + } + + res = bindParam(paramIdx++, args[paramName]); + if (res != T::OK) + { + db->extractLastError(); + copyErrorFromDb(); + return false; + } + } + + bool ok = (fetchFirst() == T::OK); + if (ok) + db->checkForDroppedObject(query); + + return ok; +} + +template <class T> +int AbstractDb3<T>::Query::bindParam(int paramIdx, const QVariant& value) +{ + if (value.isNull()) + { + return T::bind_null(stmt, paramIdx); + } + + switch (value.type()) + { + case QVariant::ByteArray: + { + QByteArray ba = value.toByteArray(); + return T::bind_blob(stmt, paramIdx, ba.constData(), ba.size(), T::TRANSIENT()); + } + case QVariant::Int: + case QVariant::Bool: + { + return T::bind_int(stmt, paramIdx, value.toInt()); + } + case QVariant::Double: + { + return T::bind_double(stmt, paramIdx, value.toDouble()); + } + case QVariant::UInt: + case QVariant::LongLong: + { + return T::bind_int64(stmt, paramIdx, value.toLongLong()); + } + default: + { + // T::TRANSIENT makes sure that sqlite buffers the data + QString str = value.toString(); + return T::bind_text16(stmt, paramIdx, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + } + } + + return T::MISUSE; // not going to happen +} + +template <class T> +bool AbstractDb3<T>::Query::checkDbState() +{ + if (db.isNull() || !db->dbHandle) + { + setError(SqlErrorCode::DB_NOT_DEFINED, "SqlQuery is no longer valid."); + return false; + } + + return true; +} + +template <class T> +void AbstractDb3<T>::Query::finalize() +{ + if (stmt) + { + T::finalize(stmt); + stmt = nullptr; + } +} + +template <class T> +QString AbstractDb3<T>::Query::getErrorText() +{ + return errorMessage; +} + +template <class T> +int AbstractDb3<T>::Query::getErrorCode() +{ + return errorCode; +} + +template <class T> +QStringList AbstractDb3<T>::Query::getColumnNames() +{ + return colNames; +} + +template <class T> +int AbstractDb3<T>::Query::columnCount() +{ + return colCount; +} + +template <class T> +qint64 AbstractDb3<T>::Query::rowsAffected() +{ + return affected; +} + +template <class T> +SqlResultsRowPtr AbstractDb3<T>::Query::nextInternal() +{ + Row* row = new Row; + int res = row->init(colNames, stmt); + if (res != T::OK) + { + delete row; + setError(res, QString::fromUtf8(T::errmsg(db->dbHandle))); + return SqlResultsRowPtr(); + } + + res = fetchNext(); + if (res != T::OK) + { + delete row; + return SqlResultsRowPtr(); + } + + return SqlResultsRowPtr(row); +} + +template <class T> +bool AbstractDb3<T>::Query::hasNextInternal() +{ + return rowAvailable && stmt && checkDbState(); +} + +template <class T> +int AbstractDb3<T>::Query::fetchFirst() +{ + colCount = T::column_count(stmt); + for (int i = 0; i < colCount; i++) + colNames << QString::fromUtf8(T::column_name(stmt, i)); + + rowAvailable = true; + int res = fetchNext(); + + affected = 0; + if (res == T::OK) + { + affected = T::changes(db->dbHandle); + insertRowId["ROWID"] = T::last_insert_rowid(db->dbHandle); + } + + return res; +} + +template <class T> +int AbstractDb3<T>::Query::fetchNext() +{ + if (!checkDbState()) + rowAvailable = false; + + if (!rowAvailable || !stmt) + { + setError(T::MISUSE, tr("Result set expired or no row available.")); + return T::MISUSE; + } + + rowAvailable = false; + int res; + int secondsSpent = 0; + while ((res = T::step(stmt)) == T::BUSY && secondsSpent < db->getTimeout()) + { + QThread::sleep(1); + if (db->getTimeout() >= 0) + secondsSpent++; + } + + switch (res) + { + case T::ROW: + rowAvailable = true; + break; + case T::DONE: + // Empty pointer as no more results are available. + break; + default: + setError(res, QString::fromUtf8(T::errmsg(db->dbHandle))); + return T::ERROR; + } + return T::OK; +} + +//------------------------------------------------------------------------------------ +// Row +//------------------------------------------------------------------------------------ + +template <class T> +int AbstractDb3<T>::Query::Row::init(const QStringList& columns, typename T::stmt* stmt) +{ + int res = T::OK; + QVariant value; + for (int i = 0; i < columns.size(); i++) + { + res = getValue(stmt, i, value); + if (res != T::OK) + return res; + + values << value; + valuesMap[columns[i]] = value; + } + return res; +} + +template <class T> +int AbstractDb3<T>::Query::Row::getValue(typename T::stmt* stmt, int col, QVariant& value) +{ + int dataType = T::column_type(stmt, col); + switch (dataType) + { + case T::INTEGER: + value = T::column_int64(stmt, col); + break; + case T::BLOB: + value = QByteArray( + static_cast<const char*>(T::column_blob(stmt, col)), + T::column_bytes(stmt, col) + ); + break; + case T::FLOAT: + value = T::column_double(stmt, col); + break; + case T::NULL_TYPE: + value = QVariant(QVariant::String); + break; + default: + value = QString( + reinterpret_cast<const QChar*>(T::column_text16(stmt, col)), + T::column_bytes16(stmt, col) / sizeof(QChar) + ); + break; + } + return T::OK; +} + +#endif // ABSTRACTDB3_H |
