#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 "log.h" #include #include #include /** * @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 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& connOptions); ~AbstractDb3(); protected: bool isOpenInternal(); void interruptExecution(); QString getErrorTextInternal(); int getErrorCodeInternal(); bool openInternal(); bool closeInternal(); bool initAfterCreated(); 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* 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& args); bool execInternal(const QHash& 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> db; typename T::stmt* stmt = nullptr; int errorCode = T::OK; QString errorMessage; int colCount = 0; QStringList colNames; bool rowAvailable = false; }; struct CollationUserData { QString name; AbstractDb3* 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 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 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& 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 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 AbstractDb3::AbstractDb3(const QString& name, const QString& path, const QHash& connOptions) : AbstractDb(name, path, connOptions) { } template AbstractDb3::~AbstractDb3() { if (isOpenInternal()) closeInternal(); } template bool AbstractDb3::isOpenInternal() { return dbHandle != nullptr; } template void AbstractDb3::interruptExecution() { if (!isOpenInternal()) return; T::interrupt(dbHandle); } template QString AbstractDb3::getErrorTextInternal() { return dbErrorMessage; } template int AbstractDb3::getErrorCodeInternal() { return dbErrorCode; } template bool AbstractDb3::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 = QObject::tr("Could not open database: %1").arg(extractLastError()); dbErrorCode = res; return false; } dbHandle = handle; return true; } template bool AbstractDb3::closeInternal() { resetError(); if (!dbHandle) return false; cleanUp(); int res = T::close(dbHandle); if (res != T::OK) { dbErrorMessage = QObject::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 bool AbstractDb3::initAfterCreated() { version = 3; return AbstractDb::initAfterCreated(); } template void AbstractDb3::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 SqlQueryPtr AbstractDb3::prepare(const QString& query) { return SqlQueryPtr(new Query(this, query)); } template QString AbstractDb3::getTypeLabel() { return T::label; } template bool AbstractDb3::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 bool AbstractDb3::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::evaluateScalar, nullptr, nullptr, &AbstractDb3::deleteUserData); return res == T::OK; } template bool AbstractDb3::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::evaluateAggregateStep, &AbstractDb3::evaluateAggregateFinal, &AbstractDb3::deleteUserData); return res == T::OK; } template bool AbstractDb3::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::evaluateCollation, &AbstractDb3::deleteCollationUserData); return res == T::OK; } template bool AbstractDb3::deregisterCollationInternal(const QString& name) { if (!dbHandle) return false; T::create_collation_v2(dbHandle, name.toUtf8().constData(), T::UTF8, nullptr, nullptr, nullptr); return true; } template QString AbstractDb3::extractLastError() { dbErrorCode = T::extended_errcode(dbHandle); dbErrorMessage = QString::fromUtf8(T::errmsg(dbHandle)); return dbErrorMessage; } template void AbstractDb3::cleanUp() { for (Query* q : queries) q->finalize(); safe_delete(defaultCollationUserData); } template void AbstractDb3::resetError() { dbErrorCode = 0; dbErrorMessage = QString::null; } template void AbstractDb3::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 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 QList AbstractDb3::getArgs(int argCount, typename T::value** args) { int dataType; QList 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(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(T::value_text16(args[i])), T::value_bytes16(args[i]) / sizeof(QChar) ); break; } results << value; } return results; } template void AbstractDb3::evaluateScalar(typename T::context* context, int argCount, typename T::value** args) { QList argList = getArgs(argCount, args); bool ok = true; QVariant result = AbstractDb::evaluateScalar(T::user_data(context), argList, ok); storeResult(context, result, ok); } template void AbstractDb3::evaluateAggregateStep(typename T::context* context, int argCount, typename T::value** args) { void* dataPtr = T::user_data(context); QList argList = getArgs(argCount, args); QHash aggregateContext = getAggregateContext(context); AbstractDb::evaluateAggregateStep(dataPtr, aggregateContext, argList); setAggregateContext(context, aggregateContext); } template void AbstractDb3::evaluateAggregateFinal(typename T::context* context) { void* dataPtr = T::user_data(context); QHash aggregateContext = getAggregateContext(context); bool ok = true; QVariant result = AbstractDb::evaluateAggregateFinal(dataPtr, aggregateContext, ok); storeResult(context, result, ok); releaseAggregateContext(context); } template int AbstractDb3::evaluateCollation(void* userData, int length1, const void* value1, int length2, const void* value2) { UNUSED(length1); UNUSED(length2); CollationUserData* collUserData = reinterpret_cast(userData); return COLLATIONS->evaluate(collUserData->name, QString::fromUtf8((const char*)value1), QString::fromUtf8((const char*)value2)); } template void AbstractDb3::deleteCollationUserData(void* userData) { if (!userData) return; CollationUserData* collUserData = reinterpret_cast(userData); delete collUserData; } template void AbstractDb3::deleteUserData(void* dataPtr) { if (!dataPtr) return; FunctionUserData* userData = reinterpret_cast(dataPtr); delete userData; } template void* AbstractDb3::getContextMemPtr(typename T::context* context) { return T::aggregate_context(context, sizeof(QHash**)); } template QHash AbstractDb3::getAggregateContext(typename T::context* context) { return AbstractDb::getAggregateContext(getContextMemPtr(context)); } template void AbstractDb3::setAggregateContext(typename T::context* context, const QHash& aggregateContext) { AbstractDb::setAggregateContext(getContextMemPtr(context), aggregateContext); } template void AbstractDb3::releaseAggregateContext(typename T::context* context) { AbstractDb::releaseAggregateContext(getContextMemPtr(context)); } template void AbstractDb3::registerDefaultCollation(void* fnUserData, typename T::handle* fnDbHandle, int eTextRep, const char* collationName) { UNUSED(eTextRep); CollationUserData* defUserData = reinterpret_cast(fnUserData); if (!defUserData) { qWarning() << "Null userData in AbstractDb3::registerDefaultCollation()."; return; } AbstractDb3* db = defUserData->db; if (!db) { qWarning() << "No database defined in userData of AbstractDb3::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::registerDefaultCollation()."; return; } int res = T::create_collation_v2(fnDbHandle, collationName, T::UTF8, nullptr, &AbstractDb3::evaluateDefaultCollation, nullptr); if (res != T::OK) qWarning() << "Could not register default collation in AbstractDb3::registerDefaultCollation()."; } template int AbstractDb3::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, length1), QString::fromUtf8((const char*)value2, length2)); } template void AbstractDb3::registerDefaultCollationRequestHandler() { if (!dbHandle) return; defaultCollationUserData = new CollationUserData; defaultCollationUserData->db = this; int res = T::collation_needed(dbHandle, defaultCollationUserData, &AbstractDb3::registerDefaultCollation); if (res != T::OK) qWarning() << "Could not register default collation request handler. Unknown collations will cause errors."; } //------------------------------------------------------------------------------------ // Results //------------------------------------------------------------------------------------ template AbstractDb3::Query::Query(AbstractDb3* db, const QString& query) : db(db) { this->query = query; db->queries << this; } template AbstractDb3::Query::~Query() { if (db.isNull()) return; finalize(); db->queries.removeOne(this); } template void AbstractDb3::Query::copyErrorFromDb() { if (db->dbErrorCode != 0) { errorCode = db->dbErrorCode; errorMessage = db->dbErrorMessage; return; } } template void AbstractDb3::Query::copyErrorToDb() { db->dbErrorCode = errorCode; db->dbErrorMessage = errorMessage; } template void AbstractDb3::Query::setError(int code, const QString& msg) { if (errorCode != T::OK) return; // don't overwrite first error errorCode = code; errorMessage = msg; copyErrorToDb(); } template int AbstractDb3::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 int AbstractDb3::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 bool AbstractDb3::Query::execInternal(const QList& args) { if (!checkDbState()) return false; ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite3, flags.testFlag(Db::Flag::NO_LOCK)); logSql(db.data(), query, args, flags); 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, argCount = args.size(); paramIdx <= queryWithParams.second && paramIdx <= argCount; 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 bool AbstractDb3::Query::execInternal(const QHash& args) { if (!checkDbState()) return false; ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite3, flags.testFlag(Db::Flag::NO_LOCK)); logSql(db.data(), query, args, flags); QueryWithParamNames queryWithParams = getQueryWithParamNames(query, Dialect::Sqlite3); int res; if (stmt) res = resetStmt(); else res = prepareStmt(); if (res != T::OK) return false; int paramIdx = -1; for (const QString& paramName : queryWithParams.second) { if (!args.contains(paramName)) { qWarning() << "Could not bind parameter" << paramName << "because it was not found in passed arguments."; setError(SqlErrorCode::OTHER_EXECUTION_ERROR, "Error while preparing statement: could not bind parameter " + paramName); return false; } paramIdx = T::bind_parameter_index(stmt, paramName.toUtf8().constData()); if (paramIdx <= 0) { qWarning() << "Could not bind parameter" << paramName << "because it was not found in prepared statement."; 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 int AbstractDb3::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 bool AbstractDb3::Query::checkDbState() { if (db.isNull() || !db->dbHandle) { setError(SqlErrorCode::DB_NOT_DEFINED, "SqlQuery is no longer valid."); return false; } return true; } template void AbstractDb3::Query::finalize() { if (stmt) { T::finalize(stmt); stmt = nullptr; } } template QString AbstractDb3::Query::getErrorText() { return errorMessage; } template int AbstractDb3::Query::getErrorCode() { return errorCode; } template QStringList AbstractDb3::Query::getColumnNames() { return colNames; } template int AbstractDb3::Query::columnCount() { return colCount; } template qint64 AbstractDb3::Query::rowsAffected() { return affected; } template SqlResultsRowPtr AbstractDb3::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 bool AbstractDb3::Query::hasNextInternal() { return rowAvailable && stmt && checkDbState(); } template int AbstractDb3::Query::fetchFirst() { colCount = T::column_count(stmt); for (int i = 0; i < colCount; i++) colNames << QString::fromUtf8(T::column_name(stmt, i)); int changesBefore = T::total_changes(db->dbHandle); rowAvailable = true; int res = fetchNext(); affected = 0; if (res == T::OK) { affected = T::total_changes(db->dbHandle) - changesBefore; insertRowId["ROWID"] = T::last_insert_rowid(db->dbHandle); } return res; } template int AbstractDb3::Query::fetchNext() { if (!checkDbState()) rowAvailable = false; if (!rowAvailable || !stmt) { setError(T::MISUSE, QObject::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 int AbstractDb3::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 int AbstractDb3::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(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(T::column_text16(stmt, col)), T::column_bytes16(stmt, col) / sizeof(QChar) ); break; } return T::OK; } #endif // ABSTRACTDB3_H