From feda8a7db8d1d7c5439aa8f8feef7cc0dd2b59a0 Mon Sep 17 00:00:00 2001 From: Unit 193 Date: Fri, 27 Jul 2018 23:51:12 -0400 Subject: New upstream version 3.2.1+dfsg1 --- SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp | 50 ++++++- SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h | 4 + SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h | 69 +++++++--- SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h | 40 +++++- SQLiteStudio3/coreSQLiteStudio/db/db.cpp | 2 + SQLiteStudio3/coreSQLiteStudio/db/db.h | 35 ++++- SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp | 5 + SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h | 2 + SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp | 12 ++ SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h | 2 + .../coreSQLiteStudio/db/queryexecutor.cpp | 145 ++++++++++++++++----- SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h | 144 ++++++++++++++++++++ .../queryexecutorsteps/queryexecutoraddrowids.cpp | 32 ++++- .../db/queryexecutorsteps/queryexecutoraddrowids.h | 2 + .../queryexecutorsteps/queryexecutorcellsize.cpp | 8 +- .../db/queryexecutorsteps/queryexecutorcolumns.cpp | 5 +- .../queryexecutordatasources.cpp | 2 +- .../db/queryexecutorsteps/queryexecutorexecute.cpp | 6 +- .../db/queryexecutorsteps/queryexecutorstep.cpp | 2 +- .../db/queryexecutorsteps/queryexecutorstep.h | 2 +- SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp | 5 +- SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h | 2 +- .../coreSQLiteStudio/db/stdsqlite3driver.h | 5 +- 23 files changed, 497 insertions(+), 84 deletions(-) (limited to 'SQLiteStudio3/coreSQLiteStudio/db') diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp index cd9b972..70037b0 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp @@ -8,6 +8,7 @@ #include "sqlerrorresults.h" #include "sqlerrorcodes.h" #include "services/notifymanager.h" +#include "services/sqliteextensionmanager.h" #include "log.h" #include "parser/lexer.h" #include @@ -120,7 +121,7 @@ void AbstractDb::registerAllFunctions() void AbstractDb::registerAllCollations() { - foreach (const QString& name, registeredCollations) + for (const QString& name : registeredCollations) { if (!deregisterCollation(name)) qWarning() << "Failed to deregister custom collation:" << name; @@ -128,13 +129,49 @@ void AbstractDb::registerAllCollations() registeredCollations.clear(); - foreach (const CollationManager::CollationPtr& collPtr, COLLATIONS->getCollationsForDatabase(getName())) + for (const CollationManager::CollationPtr& collPtr : COLLATIONS->getCollationsForDatabase(getName())) registerCollation(collPtr->name); disconnect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerAllCollations())); connect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerAllCollations())); } +void AbstractDb::loadExtensions() +{ + for (const SqliteExtensionManager::ExtensionPtr& extPtr : SQLITE_EXTENSIONS->getExtensionForDatabase(getName())) + loadedExtensionCount += loadExtension(extPtr->filePath, extPtr->initFunc) ? 1 : 0; + + connect(SQLITE_EXTENSIONS, SIGNAL(extensionListChanged()), this, SLOT(reloadExtensions())); +} + +void AbstractDb::reloadExtensions() +{ + if (!isOpen()) + return; + + bool doOpen = false; + if (loadedExtensionCount > 0) + { + if (!closeQuiet()) + { + qWarning() << "Failed to close database for extension reloading."; + return; + } + + doOpen = true; + loadedExtensionCount = 0; + disconnect(SQLITE_EXTENSIONS, SIGNAL(extensionListChanged()), this, SLOT(reloadExtensions())); + } + + if (doOpen && !openQuiet()) + { + qCritical() << "Failed to re-open database for extension reloading."; + return; + } + + loadExtensions(); +} + bool AbstractDb::isOpen() { // We use separate mutex for connection state to avoid situations, when some query is being executed, @@ -166,7 +203,7 @@ QString AbstractDb::generateUniqueDbNameNoLock() } QStringList existingDatabases; - foreach (SqlResultsRowPtr row, results->getAll()) + for (SqlResultsRowPtr row : results->getAll()) existingDatabases << row->value("name").toString(); return generateUniqueName("attached", existingDatabases); @@ -347,6 +384,9 @@ bool AbstractDb::openAndSetup() // Implementation specific initialization initAfterOpen(); + // Load extension + loadExtensions(); + // Custom SQL functions registerAllFunctions(); @@ -658,7 +698,7 @@ void AbstractDb::detachAll() if (!isOpenInternal()) return; - foreach (Db* db, attachedDbMap.rightValues()) + for (Db* db : attachedDbMap.rightValues()) detachInternal(db); } @@ -683,7 +723,7 @@ QString AbstractDb::getUniqueNewObjectName(const QString &attachedDbName) QSet existingNames; SqlQueryPtr results = exec(QString("SELECT name FROM %1.sqlite_master").arg(dbName)); - foreach (SqlResultsRowPtr row, results->getAll()) + for (SqlResultsRowPtr row : results->getAll()) existingNames << row->value(0).toString(); return randStrNotIn(16, existingNames, false); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h index 3f2b4c0..c69c9f2 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h @@ -86,6 +86,7 @@ class API_EXPORT AbstractDb : public Db void setTimeout(int secs); int getTimeout() const; bool isValid() const; + void loadExtensions(); protected: struct FunctionUserData @@ -445,6 +446,8 @@ class API_EXPORT AbstractDb : public Db */ QStringList registeredCollations; + int loadedExtensionCount = 0; + private slots: /** * @brief Handles asynchronous execution results. @@ -464,6 +467,7 @@ class API_EXPORT AbstractDb : public Db bool openForProbing(); void registerAllFunctions(); void registerAllCollations(); + void reloadExtensions(); }; /** diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h index 7419f8c..51465f9 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include /** * @brief Complete implementation of SQLite 2 driver for SQLiteStudio. @@ -42,9 +44,11 @@ class AbstractDb2 : public AbstractDb * All values from this constructor are just passed to AbstractDb constructor. */ AbstractDb2(const QString& name, const QString& path, const QHash& connOptions); - ~AbstractDb2(); + bool loadExtension(const QString& filePath, const QString& initFunc = QString()); + bool isComplete(const QString& sql) const; + protected: bool isOpenInternal(); void interruptExecution(); @@ -131,6 +135,7 @@ class AbstractDb2 : public AbstractDb int dbErrorCode = SQLITE_OK; QList userDataList; QList queries; + QMutex* dbOperMutex = nullptr; }; //------------------------------------------------------------------------------------ @@ -141,15 +146,31 @@ template AbstractDb2::AbstractDb2(const QString& name, const QString& path, const QHash& connOptions) : AbstractDb(name, path, connOptions) { + dbOperMutex = new QMutex(QMutex::Recursive); } template AbstractDb2::~AbstractDb2() { + safe_delete(dbOperMutex); if (isOpenInternal()) closeInternal(); } +template +bool AbstractDb2::loadExtension(const QString& filePath, const QString& initFunc) +{ + UNUSED(filePath); + UNUSED(initFunc); + return false; +} + +template +bool AbstractDb2::isComplete(const QString& sql) const +{ + return sqlite_complete(sql.toUtf8().constData()); +} + template bool AbstractDb2::isOpenInternal() { @@ -168,6 +189,7 @@ void AbstractDb2::interruptExecution() if (!isOpenInternal()) return; + QMutexLocker mutexLocker(dbOperMutex); sqlite_interrupt(dbHandle); } @@ -189,6 +211,7 @@ bool AbstractDb2::openInternal() resetError(); sqlite* handle = nullptr; char* errMsg = nullptr; + QMutexLocker mutexLocker(dbOperMutex); handle = sqlite_open(path.toUtf8().constData(), 0, &errMsg); if (!handle) { @@ -214,6 +237,7 @@ bool AbstractDb2::closeInternal() cleanUp(); + QMutexLocker mutexLocker(dbOperMutex); sqlite_close(dbHandle); dbHandle = nullptr; return true; @@ -243,6 +267,7 @@ bool AbstractDb2::deregisterFunction(const QString& name, int argCount) if (!dbHandle) return false; + QMutexLocker mutexLocker(dbOperMutex); sqlite_create_function(dbHandle, name.toLatin1().data(), argCount, nullptr, nullptr); sqlite_create_aggregate(dbHandle, name.toLatin1().data(), argCount, nullptr, nullptr, nullptr); @@ -273,6 +298,7 @@ bool AbstractDb2::registerScalarFunction(const QString& name, int argCount) userData->argCount = argCount; userDataList << userData; + QMutexLocker mutexLocker(dbOperMutex); int res = sqlite_create_function(dbHandle, name.toUtf8().constData(), argCount, &AbstractDb2::evaluateScalar, userData); @@ -291,6 +317,7 @@ bool AbstractDb2::registerAggregateFunction(const QString& name, int argCount userData->argCount = argCount; userDataList << userData; + QMutexLocker mutexLocker(dbOperMutex); int res = sqlite_create_aggregate(dbHandle, name.toUtf8().constData(), argCount, &AbstractDb2::evaluateAggregateStep, &AbstractDb2::evaluateAggregateFinal, @@ -527,6 +554,7 @@ int AbstractDb2::Query::prepareStmt(const QString& processedQuery) char* errMsg = nullptr; const char* tail; QByteArray queryBytes = processedQuery.toUtf8(); + QMutexLocker mutexLocker(db->dbOperMutex); int res = sqlite_compile(db->dbHandle, queryBytes.constData(), &tail, &stmt, &errMsg); if (res != SQLITE_OK) { @@ -556,6 +584,7 @@ int AbstractDb2::Query::resetStmt() nextRowValues.clear(); char* errMsg = nullptr; + QMutexLocker mutexLocker(db->dbOperMutex); int res = sqlite_reset(stmt, &errMsg); if (res != SQLITE_OK) { @@ -576,23 +605,27 @@ bool AbstractDb2::Query::execInternal(const QList& args) if (!checkDbState()) return false; - ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite2, flags.testFlag(Db::Flag::NO_LOCK)); + QMutexLocker mutexLocker(db->dbOperMutex); logSql(db.data(), query, args, flags); - QueryWithParamCount queryWithParams = getQueryWithParamCount(query, Dialect::Sqlite2); - QString singleStr = replaceNamedParams(queryWithParams.first); - int res; if (stmt) res = resetStmt(); else - res = prepareStmt(singleStr); + res = prepareStmt(query); if (res != SQLITE_OK) return false; - for (int paramIdx = 1; paramIdx <= queryWithParams.second; paramIdx++) + int maxParamIdx = args.size(); + if (!flags.testFlag(Db::Flag::SKIP_PARAM_COUNTING)) + { + QueryWithParamCount queryWithParams = getQueryWithParamCount(query, Dialect::Sqlite2); + maxParamIdx = qMin(maxParamIdx, queryWithParams.second); + } + + for (int paramIdx = 1; paramIdx <= maxParamIdx; paramIdx++) { res = bindParam(paramIdx, args[paramIdx-1]); if (res != SQLITE_OK) @@ -612,7 +645,7 @@ bool AbstractDb2::Query::execInternal(const QHash& args) if (!checkDbState()) return false; - ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite2, flags.testFlag(Db::Flag::NO_LOCK)); + QMutexLocker mutexLocker(db->dbOperMutex); logSql(db.data(), query, args, flags); @@ -629,7 +662,7 @@ bool AbstractDb2::Query::execInternal(const QHash& args) return false; int paramIdx = 1; - foreach (const QString& paramName, queryWithParams.second) + for (const QString& paramName : queryWithParams.second) { if (!args.contains(paramName)) { @@ -665,9 +698,7 @@ template int AbstractDb2::Query::bindParam(int paramIdx, const QVariant& value) { if (value.isNull()) - { return sqlite_bind(stmt, paramIdx, nullptr, 0, 0); - } switch (value.type()) { @@ -686,6 +717,7 @@ int AbstractDb2::Query::bindParam(int paramIdx, const QVariant& value) } } + qWarning() << "sqlite_bind() MISUSE"; return SQLITE_MISUSE; // not going to happen } template @@ -747,19 +779,14 @@ bool AbstractDb2::Query::hasNextInternal() template int AbstractDb2::Query::fetchFirst() { + QMutexLocker mutexLocker(db->dbOperMutex); rowAvailable = true; int res = fetchNext(); + affected = 0; if (res == SQLITE_OK) { - if (colCount == 0) - { - affected = 0; - } - else - { - affected = sqlite_changes(db->dbHandle); - insertRowId["ROWID"] = sqlite_last_insert_rowid(db->dbHandle); - } + affected = sqlite_changes(db->dbHandle); + insertRowId["ROWID"] = sqlite_last_insert_rowid(db->dbHandle); } return res; } @@ -783,6 +810,7 @@ QString AbstractDb2::Query::finalize() if (stmt) { char* errMsg = nullptr; + QMutexLocker mutexLocker(db->dbOperMutex); sqlite_finalize(stmt, &errMsg); stmt = nullptr; if (errMsg) @@ -814,6 +842,7 @@ int AbstractDb2::Query::fetchNext() int res; int secondsSpent = 0; + QMutexLocker mutexLocker(db->dbOperMutex); while ((res = sqlite_step(stmt, &columnsCount, &values, &columns)) == SQLITE_BUSY && secondsSpent < db->getTimeout()) { QThread::sleep(1); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h index db9bc02..f45e475 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h @@ -44,6 +44,9 @@ class AbstractDb3 : public AbstractDb AbstractDb3(const QString& name, const QString& path, const QHash& connOptions); ~AbstractDb3(); + bool loadExtension(const QString& filePath, const QString& initFunc = QString()); + bool isComplete(const QString& sql) const; + protected: bool isOpenInternal(); void interruptExecution(); @@ -315,6 +318,31 @@ AbstractDb3::~AbstractDb3() closeInternal(); } +template +bool AbstractDb3::loadExtension(const QString& filePath, const QString& initFunc) +{ + char* errMsg = nullptr; + int res = T::load_extension(dbHandle, filePath.toUtf8().constData(), initFunc.isEmpty() ? nullptr : initFunc.toUtf8().constData(), &errMsg); + if (res != T::OK) + { + dbErrorMessage = QObject::tr("Could not load extension %1: %2").arg(filePath, extractLastError()); + dbErrorCode = res; + if (errMsg) + { + dbErrorMessage = QObject::tr("Could not load extension %1: %2").arg(filePath, QString::fromUtf8(errMsg)); + T::free(errMsg); + } + return false; + } + return true; +} + +template +bool AbstractDb3::isComplete(const QString& sql) const +{ + return T::complete(sql.toUtf8().constData()); +} + template bool AbstractDb3::isOpenInternal() { @@ -358,6 +386,7 @@ bool AbstractDb3::openInternal() return false; } dbHandle = handle; + T::enable_load_extension(dbHandle, 1); return true; } @@ -392,7 +421,6 @@ bool AbstractDb3::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); @@ -864,8 +892,6 @@ bool AbstractDb3::Query::execInternal(const QList& args) 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(); @@ -875,8 +901,14 @@ bool AbstractDb3::Query::execInternal(const QList& args) if (res != T::OK) return false; + int maxParamIdx = args.size(); + if (!flags.testFlag(Db::Flag::SKIP_PARAM_COUNTING)) + { + QueryWithParamCount queryWithParams = getQueryWithParamCount(query, Dialect::Sqlite3); + maxParamIdx = qMin(maxParamIdx, queryWithParams.second); + } - for (int paramIdx = 1, argCount = args.size(); paramIdx <= queryWithParams.second && paramIdx <= argCount; paramIdx++) + for (int paramIdx = 1; paramIdx <= maxParamIdx; paramIdx++) { res = bindParam(paramIdx, args[paramIdx-1]); if (res != T::OK) diff --git a/SQLiteStudio3/coreSQLiteStudio/db/db.cpp b/SQLiteStudio3/coreSQLiteStudio/db/db.cpp index 977812a..ebe6731 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/db.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/db.cpp @@ -5,10 +5,12 @@ Db::Db() { +// qDebug() << "Db::Db()" << this; } Db::~Db() { +// qDebug() << "Db::~Db()" << this; } void Db::metaInit() diff --git a/SQLiteStudio3/coreSQLiteStudio/db/db.h b/SQLiteStudio3/coreSQLiteStudio/db/db.h index 4a9e80a..2ee79aa 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/db.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/db.h @@ -1,6 +1,7 @@ #ifndef DB_H #define DB_H +#include #include "returncode.h" #include "dialect.h" #include "services/functionmanager.h" @@ -10,13 +11,13 @@ #include "interruptable.h" #include "dbobjecttype.h" #include -#include #include #include #include #include #include #include +#include /** @file */ @@ -147,8 +148,13 @@ class API_EXPORT Db : public QObject, public Interruptable * of code, where the lock on Db was already set. Never (!) use this to ommit lock from different * threads. Justified situation is when you implement Db::initialDbSetup() in the derived class, * or when you implement SqlFunctionPlugin. Don't use it for the usual cases. + * This flag is ignored by SQLite2 plugin, because SQLite2 is not prepared for multithreaded + * processing, therefore all operations must be synchronized. */ - SKIP_DROP_DETECTION = 0x4, /**< Query execution will not notify about any detected objects dropped by the query. */ + SKIP_DROP_DETECTION = 0x4, /**< Query execution will not notify about any detected objects dropped by the query. + * Benefit is that it speeds up execution. */ + SKIP_PARAM_COUNTING = 0x8, /**< During execution with arguments as list the number of bind parameters will not be verified. + * This speeds up execution at cost of possible error if bind params in query don't match number of args. */ }; Q_DECLARE_FLAGS(Flags, Flag) @@ -478,6 +484,12 @@ class API_EXPORT Db : public QObject, public Interruptable */ virtual bool isReadable() = 0; + /** + * @brief Tells whether given SQL is a complete statement or not. + * @return true if given SQL is complete SQL (or more), or it misses some part. + */ + virtual bool isComplete(const QString& sql) const = 0; + /** * @brief Checks if the database is writable at the moment. * @return true if the database is writable, or false otherwise. @@ -623,7 +635,7 @@ class API_EXPORT Db : public QObject, public Interruptable * This method is used only to let the database know, that the given function exists in FunctionManager and we want it to be visible * in this database's context. When the function is called from SQL query, then the function execution is delegated to the FunctionManager. * - * For details about usage of custom SQL functions see http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_SQL_functions + * For details about usage of custom SQL functions see https://github.com/pawelsalawa/sqlitestudio/wiki/User_Manual#custom-sql-functions * * @see FunctionManager */ @@ -646,7 +658,7 @@ class API_EXPORT Db : public QObject, public Interruptable * This method is used only to let the database know, that the given function exists in FunctionManager and we want it to be visible * in this database's context. When the function is called from SQL query, then the function execution is delegated to the FunctionManager. * - * For details about usage of custom SQL functions see http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_SQL_functions + * For details about usage of custom SQL functions see https://github.com/pawelsalawa/sqlitestudio/wiki/User_Manual#custom-sql-functions * * @see FunctionManager */ @@ -663,7 +675,7 @@ class API_EXPORT Db : public QObject, public Interruptable * when comparing 2 values in the database in order to sort query results. The name passed to this method is a name of the collation * as it is used in SQL queries and also the same name must be used when defining collation in Collations editor window. * - * For details about usage of custom collations see http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_collations + * For details about usage of custom collations see https://github.com/pawelsalawa/sqlitestudio/wiki/User_Manual#custom-sql-functions * * @see CollationManager */ @@ -678,6 +690,19 @@ class API_EXPORT Db : public QObject, public Interruptable */ virtual bool deregisterCollation(const QString& name) = 0; + /** + * @brief Loads a SQLite extension. + * @param filePath Absolute path to the extension file (dll/so/dylib). + * @param initFunc Optional entry point function. If empty, SQLite's default will be used. + * @return true on success, or false on failure. + * + * This function works only on SQLite 3 drivers, as SQLite 2 does not support extensions. + * More details can be found at https://sqlite.org/c3ref/load_extension.html + * + * If function returns false, use getErrorText() to discover details. + */ + virtual bool loadExtension(const QString& filePath, const QString& initFunc = QString()) = 0; + signals: /** * @brief Emitted when the connection to the database was established. diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp index a4a8b73..4778bb2 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp @@ -9,3 +9,8 @@ DbSqlite3::DbSqlite3(const QString& name, const QString& path) : DbSqlite3(name, path, QHash()) { } + +bool DbSqlite3::complete(const QString& sql) +{ + return Sqlite3::complete(sql.toUtf8().constData()); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h index 29db5a8..1509dff 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h @@ -28,6 +28,8 @@ class API_EXPORT DbSqlite3 : public AbstractDb3 * @overload */ DbSqlite3(const QString& name, const QString& path); + + static bool complete(const QString& sql); }; #endif // DBSQLITE3_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp index bdf3ef6..5e210f2 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp @@ -325,6 +325,18 @@ void InvalidDb::setError(const QString& value) error = value; } +bool InvalidDb::loadExtension(const QString& filePath, const QString& initFunc) +{ + UNUSED(filePath); + UNUSED(initFunc); + return false; +} + +bool InvalidDb::isComplete(const QString& sql) const +{ + UNUSED(sql); + return false; +} void InvalidDb::interrupt() { diff --git a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h index a9d58e0..3bc91a4 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h @@ -59,6 +59,8 @@ class API_EXPORT InvalidDb : public Db bool isValid() const; QString getError() const; void setError(const QString& value); + bool loadExtension(const QString& filePath, const QString& initFunc); + bool isComplete(const QString& sql) const; public slots: bool open(); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp index 4a1c2f6..4160e41 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp @@ -33,6 +33,10 @@ // TODO modify all executor steps to use rebuildTokensFromContents() method, instead of replacing tokens manually. +QHash> QueryExecutor::additionalStatelessSteps; +QList QueryExecutor::allAdditionalStatelsssSteps; +QHash> QueryExecutor::additionalStatefulStepFactories; + QueryExecutor::QueryExecutor(Db* db, const QString& query, QObject *parent) : QObject(parent) { @@ -46,8 +50,7 @@ QueryExecutor::QueryExecutor(Db* db, const QString& query, QObject *parent) : connect(this, SIGNAL(executionFailed(int,QString)), this, SLOT(cleanupAfterExecFailed(int,QString))); connect(DBLIST, SIGNAL(dbAboutToBeUnloaded(Db*, DbPlugin*)), this, SLOT(cleanupBeforeDbDestroy(Db*))); connect(DBLIST, SIGNAL(dbRemoved(Db*)), this, SLOT(cleanupBeforeDbDestroy(Db*))); - connect(simpleExecutor, SIGNAL(finished(SqlQueryPtr)), this, SLOT(simpleExecutionFinished(SqlQueryPtr))); - + connect(simpleExecutor, &ChainExecutor::finished, this, &QueryExecutor::simpleExecutionFinished, Qt::DirectConnection); } QueryExecutor::~QueryExecutor() @@ -58,38 +61,79 @@ QueryExecutor::~QueryExecutor() void QueryExecutor::setupExecutionChain() { + executionChain.append(additionalStatelessSteps[FIRST]); + executionChain.append(createSteps(FIRST)); + executionChain << new QueryExecutorParseQuery("initial") << new QueryExecutorDetectSchemaAlter() << new QueryExecutorExplainMode() << new QueryExecutorValuesMode() << new QueryExecutorAttaches() // needs to be at the begining, because columns needs to know real databases - << new QueryExecutorParseQuery("after Attaches") - << new QueryExecutorDataSources() + << new QueryExecutorParseQuery("after Attaches"); + + executionChain.append(additionalStatelessSteps[AFTER_ATTACHES]); + executionChain.append(createSteps(AFTER_ATTACHES)); + + executionChain << new QueryExecutorDataSources() << new QueryExecutorReplaceViews() - << new QueryExecutorParseQuery("after ReplaceViews") - << new QueryExecutorAddRowIds() - << new QueryExecutorParseQuery("after AddRowIds") - << new QueryExecutorColumns() - << new QueryExecutorParseQuery("after Columns") - //<< new QueryExecutorColumnAliases() - << new QueryExecutorOrder() - << new QueryExecutorWrapDistinctResults() - << new QueryExecutorParseQuery("after WrapDistinctResults") - << new QueryExecutorCellSize() + << new QueryExecutorParseQuery("after ReplaceViews"); + + executionChain.append(additionalStatelessSteps[AFTER_REPLACED_VIEWS]); + executionChain.append(createSteps(AFTER_REPLACED_VIEWS)); + + executionChain << new QueryExecutorAddRowIds() + << new QueryExecutorParseQuery("after AddRowIds"); + + executionChain.append(additionalStatelessSteps[AFTER_ROW_IDS]); + executionChain.append(createSteps(AFTER_ROW_IDS)); + + executionChain << new QueryExecutorColumns() + << new QueryExecutorParseQuery("after Columns"); + + executionChain.append(additionalStatelessSteps[AFTER_REPLACED_COLUMNS]); + executionChain.append(createSteps(AFTER_REPLACED_COLUMNS)); + + executionChain << new QueryExecutorOrder(); + + executionChain.append(additionalStatelessSteps[AFTER_ORDER]); + executionChain.append(createSteps(AFTER_ORDER)); + + executionChain << new QueryExecutorWrapDistinctResults() + << new QueryExecutorParseQuery("after WrapDistinctResults"); + + executionChain.append(additionalStatelessSteps[AFTER_DISTINCT_WRAP]); + executionChain.append(createSteps(AFTER_DISTINCT_WRAP)); + + executionChain << new QueryExecutorCellSize() << new QueryExecutorCountResults() - << new QueryExecutorParseQuery("after Order") - << new QueryExecutorLimit() - << new QueryExecutorParseQuery("after Limit") - << new QueryExecutorExecute(); + << new QueryExecutorParseQuery("after CellSize"); + + executionChain.append(additionalStatelessSteps[AFTER_CELL_SIZE_LIMIT]); + executionChain.append(createSteps(AFTER_CELL_SIZE_LIMIT)); - foreach (QueryExecutorStep* step, executionChain) + executionChain << new QueryExecutorLimit() + << new QueryExecutorParseQuery("after Limit"); + + executionChain.append(additionalStatelessSteps[AFTER_ROW_LIMIT_AND_OFFSET]); + executionChain.append(createSteps(AFTER_ROW_LIMIT_AND_OFFSET)); + executionChain.append(additionalStatelessSteps[JUST_BEFORE_EXECUTION]); + executionChain.append(createSteps(JUST_BEFORE_EXECUTION)); + executionChain.append(additionalStatelessSteps[LAST]); + executionChain.append(createSteps(LAST)); + + executionChain << new QueryExecutorExecute(); + + for (QueryExecutorStep* step : executionChain) step->init(this, context); } void QueryExecutor::clearChain() { - foreach (QueryExecutorStep* step, executionChain) - delete step; + for (QueryExecutorStep* step : executionChain) + { + if (!allAdditionalStatelsssSteps.contains(step)) + delete step; + } executionChain.clear(); } @@ -98,7 +142,7 @@ void QueryExecutor::executeChain() { // Go through all remaining steps bool result; - foreach (QueryExecutorStep* currentStep, executionChain) + for (QueryExecutorStep* currentStep : executionChain) { if (isInterrupted()) { @@ -249,6 +293,7 @@ void QueryExecutor::execInternal() context->noMetaColumns = noMetaColumns; context->resultsHandler = resultsHandler; context->preloadResults = preloadResults; + context->queryParameters = queryParameters; // Start the execution setupExecutionChain(); @@ -372,7 +417,12 @@ QSet QueryExecutor::getEditionForbiddenGl void QueryExecutor::setParam(const QString& name, const QVariant& value) { - context->queryParameters[name] = value; + queryParameters[name] = value; +} + +void QueryExecutor::setParams(const QHash& params) +{ + queryParameters = params; } void QueryExecutor::arg(const QVariant& value) @@ -454,7 +504,7 @@ void QueryExecutor::simpleExecutionFinished(SqlQueryPtr results) ResultColumnPtr resCol; context->resultColumns.clear(); - foreach (const QString& colName, results->getColumnNames()) + for (const QString& colName : results->getColumnNames()) { resCol = ResultColumnPtr::create(); resCol->displayName = colName; @@ -487,19 +537,19 @@ bool QueryExecutor::simpleExecIsSelect() tokens.trim(); // First check if it's explicit "SELECT" or "VALUES" (the latter one added in SQLite 3.8.4). - TokenPtr token = tokens.first(); - QString upper = token->value.toUpper(); - if (token->type == Token::KEYWORD && (upper == "SELECT" || upper == "VALUES")) + TokenPtr firstToken = tokens.first(); + QString upper = firstToken->value.toUpper(); + if (firstToken->type == Token::KEYWORD && (upper == "SELECT" || upper == "VALUES")) return true; // Now it's only possible to be a SELECT if it starts with "WITH" statement. - if (token->type != Token::KEYWORD || upper != "WITH") + if (firstToken->type != Token::KEYWORD || upper != "WITH") return false; // Go through all tokens and find which one appears first (exclude contents indise parenthesis, // cause there will always be a SELECT for Common Table Expression). int depth = 0; - foreach (token, tokens) + for (const TokenPtr& token : tokens) { switch (token->type) { @@ -533,7 +583,7 @@ bool QueryExecutor::simpleExecIsSelect() void QueryExecutor::cleanup() { Db* attDb = nullptr; - foreach (const QString& attDbName, context->dbNameToAttach.leftValues()) + for (const QString& attDbName : context->dbNameToAttach.leftValues()) { attDb = DBLIST->getByName(attDbName, Qt::CaseInsensitive); if (attDbName.isNull()) @@ -575,8 +625,10 @@ bool QueryExecutor::handleRowCountingResults(quint32 asyncId, SqlQueryPtr result QStringList QueryExecutor::applyLimitForSimpleMethod(const QStringList &queries) { static_qstring(tpl, "SELECT * FROM (%1) LIMIT %2 OFFSET %3"); - QStringList result = queries; + if (page < 0) + return queries; // no paging requested + QStringList result = queries; QString lastQuery = queries.last(); bool isSelect = false; @@ -589,6 +641,15 @@ QStringList QueryExecutor::applyLimitForSimpleMethod(const QStringList &queries) return result; } +QList QueryExecutor::createSteps(QueryExecutor::StepPosition position) +{ + QList steps; + for (StepFactory* factory : additionalStatefulStepFactories[position]) + steps << factory->produceQueryExecutorStep(); + + return steps; +} + int QueryExecutor::getQueryCountLimitForSmartMode() const { return queryCountLimitForSmartMode; @@ -599,6 +660,28 @@ void QueryExecutor::setQueryCountLimitForSmartMode(int value) queryCountLimitForSmartMode = value; } +void QueryExecutor::registerStep(StepPosition position, QueryExecutorStep *step) +{ + additionalStatelessSteps[position] += step; + allAdditionalStatelsssSteps += step; +} + +void QueryExecutor::registerStep(QueryExecutor::StepPosition position, QueryExecutor::StepFactory* stepFactory) +{ + additionalStatefulStepFactories[position] += stepFactory; +} + +void QueryExecutor::deregisterStep(StepPosition position, QueryExecutorStep *step) +{ + additionalStatelessSteps[position].removeOne(step); + allAdditionalStatelsssSteps.removeOne(step); +} + +void QueryExecutor::deregisterStep(QueryExecutor::StepPosition position, QueryExecutor::StepFactory* stepFactory) +{ + additionalStatefulStepFactories[position].removeOne(stepFactory); +} + bool QueryExecutor::getForceSimpleMode() const { return forceSimpleMode; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h index 4830e36..85c39bf 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h @@ -646,6 +646,53 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable QString errorMessageFromSmartExecution; }; + /** + * @brief Position for custom executor steps. + * + * These position define where in query executor chain of steps the new, registered step will be placed. + * It's used in arguments of registerStep() method. + * + * If multiple steps are registered at same position, they will be executed in order they were registered. + * + * Values of positions determin what actions have been already taken by the executor, so for example + * AFTER_ATTACHES means, that executor already attached referenced databases and replaced occurrences of objects + * from that databases. + * + * Enumerations are in order as the steps are executed. + * + * If you need more detailed description about certain steps performed by query executor, see documentation + * of their classes - all these classes name start with QueryExecutor prefix. + */ + enum StepPosition { + FIRST, /**< As first step */ + AFTER_ATTACHES, /**< After transparent attaching is applied (detected tables are defined to be attached first). */ + AFTER_REPLACED_VIEWS, /**< After referenced views have been replaced with subqueries */ + AFTER_ROW_IDS, /**< After ROWID columns have been added to result columns */ + AFTER_REPLACED_COLUMNS, /**< After all columns have been explicitly listed in result list, together with unique alias names */ + AFTER_ORDER, /**< After order clause was applied/modified */ + AFTER_DISTINCT_WRAP, /**< After wrapping SELECT was added in case of DISTINCT or GROUP BY clauses were used */ + AFTER_CELL_SIZE_LIMIT, /**< After cell result size was limited to save memory usage */ + AFTER_ROW_LIMIT_AND_OFFSET, /**< After LIMIT and ORDER clauses were added/modified. This is the last possible moment, directly ahead of final query execution */ + JUST_BEFORE_EXECUTION, /**< Same as AFTER_ROW_LIMIT_AND_OFFSET */ + LAST /**< Same as AFTER_ROW_LIMIT_AND_OFFSET */ + }; + + /** + * @brief Interface for producing query executor steps. + * + * It can be used for overloaded version of registerStep() method, + * in case a step is stateful and needs to be created/deleted for every query executor processing. + * + * If step is stateless, it can be registered directly as an instance, using the other version of registerStep() method. + * + * This is an abstract class and not pointer to function, because it has to be comparable (and std::function is not), + * so it's possible to deregister the factory later on. + */ + class StepFactory { + public: + virtual QueryExecutorStep* produceQueryExecutorStep() = 0; + }; + /** * @brief Creates query executor, initializes internal context object. * @param db Optional database. If not provided, it has to be defined later with setDb(). @@ -808,6 +855,15 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable */ void setParam(const QString& name, const QVariant& value); + /** + * @brief Assigns all query parameters at once. + * @param params All bind parameters for the query. + * + * This is same as setParam(), except it overrides all current params with given ones. + * It allows for setting all params at once. + */ + void setParams(const QHash& params); + /** * @brief Replaces placeholder in the query. * @param value Value to replace placeholder with. @@ -1038,6 +1094,49 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable int getQueryCountLimitForSmartMode() const; void setQueryCountLimitForSmartMode(int value); + /** + * @brief Adds new step to the chain of execution + * @param position Where in chain should the step be placed. + * @param step Step implementation instance. + * + * If multiple steps are registered for the same position, they will be executed in the order they were registered. + * + * If step is registered with a plugin, remember to deregister the step upon plugin unload. + * Best place for that is in Plugin::deinit(). + */ + static void registerStep(StepPosition position, QueryExecutorStep* step); + + /** + * @brief Adds new step to chain of execution + * @param position Where in chain should the step be placed. + * @param stepFactory Factory for creating instance of the step. + * + * This is overloaded method for cases when the step is stateful and needs to be recreated for every invokation of query executor. + */ + static void registerStep(StepPosition position, StepFactory* stepFactory); + + /** + * @brief Removes extra step from the execution chain. + * @param position Position from which the step should be removed. + * @param step Step implementation instance. + * + * Removes step from list of additional steps to be performed. If such step was not on the list, this method does nothing. + * + * There is a position parameter to this method, becuase a step could be added to multiple different positions + * and you decide from which you want to deregister it. + */ + static void deregisterStep(StepPosition position, QueryExecutorStep* step); + + /** + * @brief Removes extra step from the execution chain. + * @param position Position from which the step should be removed. + * @param stepFactory Factory to deregister. + * + * This is overloaded method to remove factory instead of instance of a step. To be used together with registerStep() + * that also takes factory in parameters. + */ + static void deregisterStep(StepPosition position, StepFactory* stepFactory); + private: /** * @brief Executes query. @@ -1125,6 +1224,13 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable QStringList applyLimitForSimpleMethod(const QStringList &queries); + /** + * @brief Creates instances of steps for all registered factories for given position. + * @param position Position for which factories will be used. + * @return List of instances of steps from given factories. + */ + QList createSteps(StepPosition position); + /** * @brief Query executor context object. * @@ -1316,6 +1422,37 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable */ QList executionChain; + /** + * @brief List of registered additional steps to be performed + * + * It's a map, where keys describe where in chain should the steps be placed + * and values are list of steps to be inserted at that position. + * + * They are added/removed by methods: registerStep() and deregisterStep(). + */ + static QHash> additionalStatelessSteps; + + /** + * @brief List of all registered additional steps + * + * This is a list of all registered additional steps (registered instances of steps), regardless of their position. + * This is used to identify registered stateless steps, so they are not deleted on query executor cleanup. + * + * Only steps created with a factory are deleted upon executor cleanup (and of course all internal steps created by the executor itself). + */ + static QList allAdditionalStatelsssSteps; + + /** + * @brief List of registered factories that create additional steps to be performed + * + * This is similar to additionalSteps, except it holds factories, instead of step instances. + * + * Steps created with a factory are appended after stateless steps registered as direct + * instances (held by additionalSteps) - that is for case when both instance and factory + * are registered for the same step position. + */ + static QHash> additionalStatefulStepFactories; + /** * @brief Execution results handler. * @@ -1327,6 +1464,13 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable */ Db::QueryResultsHandler resultsHandler = nullptr; + /** + * @brief Parameters for query execution. + * + * It's defined by setParam(). + */ + QHash queryParameters; + bool forceSimpleMode = false; ChainExecutor* simpleExecutor = nullptr; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp index d417072..d59d73c 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp @@ -54,7 +54,7 @@ QHash> QueryExecutorAddRowIds::addR return rowIdColsMap; // Go trough subselects to add ROWID result columns there and collect rowId mapping to use here. - foreach (SqliteSelect* subSelect, getSubSelects(core)) + for (SqliteSelect* subSelect : getSubSelects(core)) { rowIdColsMap.unite(addRowIdForTables(subSelect, ok, false)); if (!ok) @@ -66,11 +66,14 @@ QHash> QueryExecutorAddRowIds::addR resolver.resolveMultiCore = false; // multicore subselects result in not editable columns, skip them QSet tables = resolver.resolveTables(core); - foreach (const SelectResolver::Table& table, tables) + for (const SelectResolver::Table& table : tables) { if (table.flags & (SelectResolver::FROM_COMPOUND_SELECT | SelectResolver::FROM_DISTINCT_SELECT | SelectResolver::FROM_GROUPED_SELECT)) continue; // we don't get ROWID from compound, distinct or aggregated subselects + if (checkInWithClause(table, select->with)) + continue; // we don't get ROWID from WITH clause, as it's likely to be recurrent and difficult. TODO: support columns from WITH clause + if (!addResultColumns(core, table, rowIdColsMap, isTopSelect)) { ok = false; @@ -89,7 +92,7 @@ QList QueryExecutorAddRowIds::getSubSelects(SqliteSelect::Core* c if (core->from->singleSource && core->from->singleSource->select) selects << core->from->singleSource->select; - foreach (SqliteSelect::Core::JoinSourceOther* otherSource, core->from->otherSources) + for (SqliteSelect::Core::JoinSourceOther* otherSource : core->from->otherSources) { if (!otherSource->singleSource->select) continue; @@ -137,7 +140,7 @@ QHash QueryExecutorAddRowIds::getNextColNames(const SelectResol SqliteCreateTable::Constraint* tableConstr = dynamic_cast(primaryKey); if (tableConstr) { - foreach (SqliteIndexedColumn* idxCol, tableConstr->indexedColumns) + for (SqliteIndexedColumn* idxCol : tableConstr->indexedColumns) colNames[getNextColName()] = idxCol->name; return colNames; @@ -205,6 +208,27 @@ bool QueryExecutorAddRowIds::addResultColumns(SqliteSelect::Core* core, const Se return true; } +bool QueryExecutorAddRowIds::checkInWithClause(const SelectResolver::Table &table, SqliteWith *with) +{ + if (!table.database.isNull() || !with) + return false; + + SqliteWith::CommonTableExpression* cte = nullptr; + QString nameToCompareWith = table.tableAlias.isNull() ? table.table : table.tableAlias; + for (SqliteWith::CommonTableExpression* cteItem : with->cteList) + { + if (cteItem->table == nameToCompareWith) + { + cte = cteItem; + break; + } + } + if (!cte) + return false; + + return true; +} + bool QueryExecutorAddRowIds::addResultColumns(SqliteSelect::Core* core, const SelectResolver::Table& table, const QString& queryExecutorColumn, const QString& realColumn, bool aliasOnlyAsSelectColumn) { diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h index 61bc302..1669542 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h @@ -76,6 +76,8 @@ class QueryExecutorAddRowIds : public QueryExecutorStep * @return Map of query executor alias to real database column name. */ QHash getNextColNames(const SelectResolver::Table& table); + + bool checkInWithClause(const SelectResolver::Table& table, SqliteWith *with); }; #endif // QUERYEXECUTORADDROWIDS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp index 54bd35a..352c74b 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp @@ -10,7 +10,7 @@ bool QueryExecutorCellSize::exec() if (!select || select->explain) return true; - foreach (SqliteSelect::Core* core, select->coreSelects) + for (SqliteSelect::Core* core : select->coreSelects) { if (!applyDataLimit(select.data(), core)) return false; @@ -31,7 +31,7 @@ bool QueryExecutorCellSize::applyDataLimit(SqliteSelect* select, SqliteSelect::C bool first = true; TokenList tokens; - foreach (const QueryExecutor::ResultColumnPtr& col, context->resultColumns) + for (const QueryExecutor::ResultColumnPtr& col : context->resultColumns) { if (!first) tokens += getSeparatorTokens(); @@ -40,7 +40,7 @@ bool QueryExecutorCellSize::applyDataLimit(SqliteSelect* select, SqliteSelect::C first = false; } - foreach (const QueryExecutor::ResultRowIdColumnPtr& col, context->rowIdColumns) + for (const QueryExecutor::ResultRowIdColumnPtr& col : context->rowIdColumns) { if (!first) tokens += getSeparatorTokens(); @@ -112,7 +112,7 @@ TokenList QueryExecutorCellSize::getNoLimitTokens(const QueryExecutor::ResultRow { TokenList newTokens; bool first = true; - foreach (const QString& col, resCol->queryExecutorAliasToColumn.keys()) + for (const QString& col : resCol->queryExecutorAliasToColumn.keys()) { if (!first) newTokens += getSeparatorTokens(); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp index 30a4f5d..0cb6b7a 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp @@ -108,6 +108,9 @@ QueryExecutor::ResultColumnPtr QueryExecutorColumns::getResultColumn(const Selec if (resolvedColumn.flags & SelectResolver::FROM_DISTINCT_SELECT) resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::DISTINCT_RESULTS; + if (resolvedColumn.flags & SelectResolver::FROM_CTE_SELECT) + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::COMM_TAB_EXPR; + resultColumn->database = resolvedColumn.originalDatabase; resultColumn->table = resolvedColumn.table; resultColumn->column = resolvedColumn.column; @@ -200,7 +203,7 @@ QString QueryExecutorColumns::resolveAttachedDatabases(const QString &dbName) bool QueryExecutorColumns::isRowIdColumnAlias(const QString& alias) { - foreach (QueryExecutor::ResultRowIdColumnPtr rowIdColumn, context->rowIdColumns) + for (QueryExecutor::ResultRowIdColumnPtr rowIdColumn : context->rowIdColumns) { if (rowIdColumn->queryExecutorAliasToColumn.keys().contains(alias)) return true; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp index 4a422d3..88c957b 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp @@ -19,7 +19,7 @@ bool QueryExecutorDataSources::exec() SqliteSelect::Core* core = select->coreSelects.first(); QSet tables = resolver.resolveTables(core); - foreach (SelectResolver::Table resolvedTable, tables) + for (SelectResolver::Table resolvedTable : tables) { QueryExecutor::SourceTablePtr table = QueryExecutor::SourceTablePtr::create(); table->database = resolvedTable.database; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp index aaa8014..0a97c1e 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp @@ -23,7 +23,7 @@ bool QueryExecutorExecute::exec() void QueryExecutorExecute::provideResultColumns(SqlQueryPtr results) { QueryExecutor::ResultColumnPtr resCol; - foreach (const QString& colName, results->getColumnNames()) + for (const QString& colName : results->getColumnNames()) { resCol = QueryExecutor::ResultColumnPtr::create(); resCol->displayName = colName; @@ -124,8 +124,8 @@ void QueryExecutorExecute::handleFailResult(SqlQueryPtr results) QHash QueryExecutorExecute::getBindParamsForQuery(SqliteQueryPtr query) { QHash queryParams; - QStringList bindParams = query->tokens.filter(Token::BIND_PARAM).toStringList(); - foreach (const QString& bindParam, bindParams) + QStringList bindParams = query->tokens.filter(Token::BIND_PARAM).toValueList(); + for (const QString& bindParam : bindParams) { if (context->queryParameters.contains(bindParam)) queryParams.insert(bindParam, context->queryParameters[bindParam]); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp index d64ea2e..8fbf026 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp @@ -18,7 +18,7 @@ void QueryExecutorStep::init(QueryExecutor *queryExecutor, QueryExecutor::Contex void QueryExecutorStep::updateQueries() { QString newQuery; - foreach (SqliteQueryPtr query, context->parsedQueries) + for (SqliteQueryPtr query : context->parsedQueries) { newQuery += query->detokenize(); newQuery += "\n"; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h index a15ad9c..9a89b26 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h @@ -37,7 +37,7 @@ * * To access database object, that the query is executed on, use QueryExecutor::getDb(). */ -class QueryExecutorStep : public QObject +class API_EXPORT QueryExecutorStep : public QObject { Q_OBJECT diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp index 4217711..94799d9 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp @@ -1,5 +1,6 @@ #include "sqlquery.h" #include "db/sqlerrorcodes.h" +#include "common/utils_sql.h" SqlQuery::~SqlQuery() { @@ -115,7 +116,7 @@ void SqlQuery::setArgs(const QHash& args) } -void RowIdConditionBuilder::setRowId(const RowId& rowId) +void RowIdConditionBuilder::setRowId(const RowId& rowId, Dialect dialect) { static const QString argTempalate = QStringLiteral(":rowIdArg%1"); @@ -127,7 +128,7 @@ void RowIdConditionBuilder::setRowId(const RowId& rowId) it.next(); arg = argTempalate.arg(i++); queryArgs[arg] = it.value(); - conditions << it.key() + " = " + arg; + conditions << wrapObjIfNeeded(it.key(), dialect) + " = " + arg; } } diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h index 4e0d9cf..ed78d80 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h @@ -304,7 +304,7 @@ class API_EXPORT SqlQuery class API_EXPORT RowIdConditionBuilder { public: - void setRowId(const RowId& rowId); + void setRowId(const RowId& rowId, Dialect dialect); const QHash& getQueryArgs(); QString build(); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h b/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h index 6b0c422..94a0413 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h @@ -2,7 +2,7 @@ #define STDSQLITE3DRIVER_H #define STD_SQLITE3_DRIVER(Name, Label, Prefix, UppercasePrefix) \ - struct Name \ + struct API_EXPORT Name \ { \ static_char* label = Label; \ \ @@ -69,7 +69,9 @@ static int step(stmt* arg) {return Prefix##sqlite3_step(arg);} \ static int reset(stmt* arg) {return Prefix##sqlite3_reset(arg);} \ static int close(handle* arg) {return Prefix##sqlite3_close(arg);} \ + static void free(void* arg) {return Prefix##sqlite3_free(arg);} \ static int enable_load_extension(handle* arg1, int arg2) {return Prefix##sqlite3_enable_load_extension(arg1, arg2);} \ + static int load_extension(handle *arg1, const char *arg2, const char *arg3, char **arg4) {return Prefix##sqlite3_load_extension(arg1, arg2, arg3, arg4);} \ static void* user_data(context* arg) {return Prefix##sqlite3_user_data(arg);} \ static void* aggregate_context(context* arg1, int arg2) {return Prefix##sqlite3_aggregate_context(arg1, arg2);} \ static int collation_needed(handle* a1, void* a2, void(*a3)(void*,handle*,int eTextRep,const char*)) {return Prefix##sqlite3_collation_needed(a1, a2, a3);} \ @@ -80,6 +82,7 @@ {return Prefix##sqlite3_create_function_v2(a1, a2, a3, a4, a5, a6, a7, a8, a9);} \ static int create_collation_v2(handle* a1, const char *a2, int a3, void *a4, int(*a5)(void*,int,const void*,int,const void*), void(*a6)(void*)) \ {return Prefix##sqlite3_create_collation_v2(a1, a2, a3, a4, a5, a6);} \ + static int complete(const char* arg) {return Prefix##sqlite3_complete(arg);} \ }; #endif // STDSQLITE3DRIVER_H -- cgit v1.2.3