diff options
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/db')
29 files changed, 585 insertions, 381 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp index 9bfb46f..b137c1f 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp @@ -1,15 +1,13 @@ #include "abstractdb.h" -#include "services/dbmanager.h" +#include "services/collationmanager.h" #include "common/utils.h" #include "asyncqueryrunner.h" #include "sqlresultsrow.h" #include "common/utils_sql.h" -#include "services/config.h" #include "sqlerrorresults.h" #include "sqlerrorcodes.h" #include "services/notifymanager.h" #include "services/sqliteextensionmanager.h" -#include "log.h" #include "parser/lexer.h" #include "common/compatibility.h" #include <QDebug> @@ -25,10 +23,12 @@ quint32 AbstractDb::asyncId = 1; AbstractDb::AbstractDb(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) : name(name), path(path), connOptions(connOptions) { + connect(SQLITESTUDIO, SIGNAL(aboutToQuit()), this, SLOT(appIsAboutToQuit())); } AbstractDb::~AbstractDb() { + disconnect(SQLITESTUDIO, SIGNAL(aboutToQuit()), this, SLOT(appIsAboutToQuit())); } bool AbstractDb::open() @@ -47,7 +47,11 @@ bool AbstractDb::close() if (deny) return false; - bool res = !isOpen() || closeQuiet(); + bool open = isOpen(); + if (open) + flushWal(); + + bool res = !open || closeQuiet(); if (res) emit disconnected(); @@ -71,7 +75,7 @@ bool AbstractDb::closeQuiet() registeredFunctions.clear(); registeredCollations.clear(); if (FUNCTIONS) // FUNCTIONS is already null when closing db while closing entire app - disconnect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions())); + disconnect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerUserFunctions())); return res; } @@ -89,40 +93,49 @@ bool AbstractDb::openForProbing() return res; } -void AbstractDb::registerAllFunctions() +void AbstractDb::registerUserFunctions() { - for (const RegisteredFunction& regFn : registeredFunctions) + QMutableSetIterator<RegisteredFunction> it(registeredFunctions); + while (it.hasNext()) { + const RegisteredFunction& regFn = it.next(); + if (regFn.builtIn) + continue; + if (!deregisterFunction(regFn.name, regFn.argCount)) qWarning() << "Failed to deregister custom SQL function:" << regFn.name; - } - registeredFunctions.clear(); + it.remove(); + } RegisteredFunction regFn; - for (FunctionManager::ScriptFunction* fnPtr : FUNCTIONS->getScriptFunctionsForDatabase(getName())) + for (FunctionManager::ScriptFunction*& fnPtr : FUNCTIONS->getScriptFunctionsForDatabase(getName())) { regFn.argCount = fnPtr->undefinedArgs ? -1 : fnPtr->arguments.count(); regFn.name = fnPtr->name; regFn.type = fnPtr->type; + regFn.deterministic = fnPtr->deterministic; registerFunction(regFn); } +} - for (FunctionManager::NativeFunction* fnPtr : FUNCTIONS->getAllNativeFunctions()) +void AbstractDb::registerBuiltInFunctions() +{ + RegisteredFunction regFn; + for (FunctionManager::NativeFunction*& fnPtr : FUNCTIONS->getAllNativeFunctions()) { regFn.argCount = fnPtr->undefinedArgs ? -1 : fnPtr->arguments.count(); regFn.name = fnPtr->name; regFn.type = fnPtr->type; + regFn.builtIn = true; + regFn.deterministic = fnPtr->deterministic; registerFunction(regFn); } - - disconnect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions())); - connect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions())); } -void AbstractDb::registerAllCollations() +void AbstractDb::registerUserCollations() { - for (const QString& name : registeredCollations) + for (QString& name : registeredCollations) { if (!deregisterCollation(name)) qWarning() << "Failed to deregister custom collation:" << name; @@ -130,16 +143,16 @@ void AbstractDb::registerAllCollations() registeredCollations.clear(); - for (const CollationManager::CollationPtr& collPtr : COLLATIONS->getCollationsForDatabase(getName())) + for (CollationManager::CollationPtr& collPtr : COLLATIONS->getCollationsForDatabase(getName())) registerCollation(collPtr->name); - disconnect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerAllCollations())); - connect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerAllCollations())); + disconnect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerUserCollations())); + connect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerUserCollations())); } void AbstractDb::loadExtensions() { - for (const SqliteExtensionManager::ExtensionPtr& extPtr : SQLITE_EXTENSIONS->getExtensionForDatabase(getName())) + for (SqliteExtensionManager::ExtensionPtr& extPtr : SQLITE_EXTENSIONS->getExtensionForDatabase(getName())) loadedExtensionCount += loadExtension(extPtr->filePath, extPtr->initFunc) ? 1 : 0; connect(SQLITE_EXTENSIONS, SIGNAL(extensionListChanged()), this, SLOT(reloadExtensions())); @@ -377,14 +390,20 @@ bool AbstractDb::openAndSetup() // Implementation specific initialization initAfterOpen(); + // Built-in SQL functions + registerBuiltInFunctions(); + // Load extension loadExtensions(); // Custom SQL functions - registerAllFunctions(); + registerUserFunctions(); // Custom collations - registerAllCollations(); + registerUserCollations(); + + disconnect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerUserFunctions())); + connect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerUserFunctions())); return result; } @@ -616,6 +635,12 @@ void AbstractDb::asyncQueryFinished(AsyncQueryRunner *runner) emit idle(); } +void AbstractDb::appIsAboutToQuit() +{ + if (isOpen()) + flushWal(); +} + QString AbstractDb::attach(Db* otherDb, bool silent) { QWriteLocker locker(&dbOperLock); @@ -716,7 +741,7 @@ QString AbstractDb::getUniqueNewObjectName(const QString &attachedDbName) QSet<QString> existingNames; SqlQueryPtr results = exec(QString("SELECT name FROM %1.sqlite_master").arg(dbName)); - for (SqlResultsRowPtr row : results->getAll()) + for (SqlResultsRowPtr& row : results->getAll()) existingNames << row->value(0).toString(); return randStrNotIn(16, existingNames, false); @@ -875,10 +900,10 @@ void AbstractDb::registerFunction(const AbstractDb::RegisteredFunction& function switch (function.type) { case FunctionManager::ScriptFunction::SCALAR: - successful = registerScalarFunction(function.name, function.argCount); + successful = registerScalarFunction(function.name, function.argCount, function.deterministic); break; case FunctionManager::ScriptFunction::AGGREGATE: - successful = registerAggregateFunction(function.name, function.argCount); + successful = registerAggregateFunction(function.name, function.argCount, function.deterministic); break; } @@ -888,6 +913,12 @@ void AbstractDb::registerFunction(const AbstractDb::RegisteredFunction& function qCritical() << "Could not register SQL function:" << function.name << function.argCount << function.type; } +void AbstractDb::flushWal() +{ + if (!flushWalInternal()) + notifyWarn(tr("Failed to make full WAL checkpoint on database '%1'. Error returned from SQLite engine: %2").arg(name, getErrorTextInternal())); +} + int qHash(const AbstractDb::RegisteredFunction& fn) { return qHash(fn.name) ^ fn.argCount ^ fn.type; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h index a465679..193f173 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h @@ -203,6 +203,8 @@ class API_EXPORT AbstractDb : public Db virtual void initAfterOpen(); + virtual bool flushWalInternal() = 0; + void checkForDroppedObject(const QString& query); bool registerCollation(const QString& name); bool deregisterCollation(const QString& name); @@ -341,6 +343,16 @@ class API_EXPORT AbstractDb : public Db * @brief Function type. */ FunctionManager::ScriptFunction::Type type; + + /** + * @brief The deterministic flag used for function registration. + */ + bool deterministic; + + /** + * @brief Flag indicating if this function is SQLiteStudio's built-in function or user's custom function. + */ + bool builtIn = false; }; friend int qHash(const AbstractDb::RegisteredFunction& fn); @@ -415,6 +427,23 @@ class API_EXPORT AbstractDb : public Db void registerFunction(const RegisteredFunction& function); /** + * @brief Flushes any pending WAL log files into the db file. + * + * It actually makes a 'PRAGMA wal_checkpoint(FULL)' call on the database. + * It's called automatically upon closing the database. + */ + void flushWal(); + + /** + * @brief Registers SQLiteStudio's built-in functions in the db. + * + * This function is called once during opening the db. + * + * @see FunctionManager + */ + void registerBuiltInFunctions(); + + /** * @brief Connection state lock. * * It's locked whenever the connection state is changed or tested. @@ -457,14 +486,16 @@ class API_EXPORT AbstractDb : public Db */ void asyncQueryFinished(AsyncQueryRunner* runner); + void appIsAboutToQuit(); + public slots: bool open(); bool close(); bool openQuiet(); bool closeQuiet(); bool openForProbing(); - void registerAllFunctions(); - void registerAllCollations(); + void registerUserFunctions(); + void registerUserCollations(); void reloadExtensions(); }; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h index 72c1614..f8812e9 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h @@ -58,10 +58,11 @@ class AbstractDb3 : public AbstractDb bool initAfterCreated(); void initAfterOpen(); SqlQueryPtr prepare(const QString& query); - QString getTypeLabel(); + bool flushWalInternal(); + QString getTypeLabel() const; bool deregisterFunction(const QString& name, int argCount); - bool registerScalarFunction(const QString& name, int argCount); - bool registerAggregateFunction(const QString& name, int argCount); + bool registerScalarFunction(const QString& name, int argCount, bool deterministic); + bool registerAggregateFunction(const QString& name, int argCount, bool deterministic); bool registerCollationInternal(const QString& name); bool deregisterCollationInternal(const QString& name); @@ -121,6 +122,7 @@ class AbstractDb3 : public AbstractDb }; QString extractLastError(); + QString extractLastError(typename T::handle* handle); void cleanUp(); void resetError(); @@ -407,6 +409,22 @@ int AbstractDb3<T>::getErrorCodeInternal() } template <class T> +bool AbstractDb3<T>::flushWalInternal() +{ + resetError(); + if (!dbHandle) + return false; + + int res = T::wal_checkpoint_v2(dbHandle, nullptr, T::CHECKPOINT_FULL, nullptr, nullptr); + if (res != T::OK) + { + dbErrorMessage = QObject::tr("Could not run WAL checkpoint: %1").arg(extractLastError()); + dbErrorCode = res; + } + return res == T::OK; +} + +template <class T> bool AbstractDb3<T>::openInternal() { resetError(); @@ -414,11 +432,11 @@ bool AbstractDb3<T>::openInternal() int res = T::open_v2(path.toUtf8().constData(), &handle, T::OPEN_READWRITE|T::OPEN_CREATE, nullptr); if (res != T::OK) { + dbErrorMessage = QObject::tr("Could not open database: %1").arg(extractLastError(handle)); + dbErrorCode = res; if (handle) T::close(handle); - dbErrorMessage = QObject::tr("Could not open database: %1").arg(extractLastError()); - dbErrorCode = res; return false; } dbHandle = handle; @@ -469,7 +487,7 @@ SqlQueryPtr AbstractDb3<T>::prepare(const QString& query) } template <class T> -QString AbstractDb3<T>::getTypeLabel() +QString AbstractDb3<T>::getTypeLabel() const { return T::label; } @@ -485,7 +503,7 @@ bool AbstractDb3<T>::deregisterFunction(const QString& name, int argCount) } template <class T> -bool AbstractDb3<T>::registerScalarFunction(const QString& name, int argCount) +bool AbstractDb3<T>::registerScalarFunction(const QString& name, int argCount, bool deterministic) { if (!dbHandle) return false; @@ -495,7 +513,11 @@ bool AbstractDb3<T>::registerScalarFunction(const QString& name, int argCount) userData->name = name; userData->argCount = argCount; - int res = T::create_function_v2(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, userData, + int opts = T::UTF8; + if (deterministic) + opts |= T::DETERMINISTIC; + + int res = T::create_function_v2(dbHandle, name.toUtf8().constData(), argCount, opts, userData, &AbstractDb3<T>::evaluateScalar, nullptr, nullptr, @@ -505,7 +527,7 @@ bool AbstractDb3<T>::registerScalarFunction(const QString& name, int argCount) } template <class T> -bool AbstractDb3<T>::registerAggregateFunction(const QString& name, int argCount) +bool AbstractDb3<T>::registerAggregateFunction(const QString& name, int argCount, bool deterministic) { if (!dbHandle) return false; @@ -515,7 +537,11 @@ bool AbstractDb3<T>::registerAggregateFunction(const QString& name, int argCount userData->name = name; userData->argCount = argCount; - int res = T::create_function_v2(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, userData, + int opts = T::UTF8; + if (deterministic) + opts |= T::DETERMINISTIC; + + int res = T::create_function_v2(dbHandle, name.toUtf8().constData(), argCount, opts, userData, nullptr, &AbstractDb3<T>::evaluateAggregateStep, &AbstractDb3<T>::evaluateAggregateFinal, @@ -552,8 +578,14 @@ bool AbstractDb3<T>::deregisterCollationInternal(const QString& name) template <class T> QString AbstractDb3<T>::extractLastError() { - dbErrorCode = T::extended_errcode(dbHandle); - dbErrorMessage = QString::fromUtf8(T::errmsg(dbHandle)); + return extractLastError(dbHandle); +} + +template<class T> +QString AbstractDb3<T>::extractLastError(typename T::handle* handle) +{ + dbErrorCode = T::extended_errcode(handle); + dbErrorMessage = QString::fromUtf8(T::errmsg(handle)); return dbErrorMessage; } @@ -799,11 +831,25 @@ void AbstractDb3<T>::registerDefaultCollation(void* fnUserData, typename T::hand return; } + SqlQueryPtr results = db->exec("PRAGMA collation_list", Db::Flag::NO_LOCK|Db::Flag::SKIP_DROP_DETECTION); + if (results->isError()) + qWarning() << "Unable to query existing collations while registering needed collation" << collationName << ":" << db->getErrorText(); + + QStringList existingCollations = results->columnAsList<QString>("name"); + if (existingCollations.contains(collationName)) + { + qDebug() << "Requested collation" << collationName << "already exists. Probably different input encoding was expected," + << "but SQLite should deal with it. Skipping default collation registration."; + return; + } + int res = T::create_collation_v2(fnDbHandle, collationName, T::UTF8, nullptr, &AbstractDb3<T>::evaluateDefaultCollation, nullptr); if (res != T::OK) qWarning() << "Could not register default collation in AbstractDb3<T>::registerDefaultCollation()."; + else + qDebug() << "Registered default collation on demand, under name:" << collationName; } template <class T> diff --git a/SQLiteStudio3/coreSQLiteStudio/db/db.cpp b/SQLiteStudio3/coreSQLiteStudio/db/db.cpp index b3ad91b..11e47f2 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/db.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/db.cpp @@ -46,6 +46,6 @@ QDataStream &operator >>(QDataStream &in, Db*& myObj) QDebug operator<<(QDebug dbg, const Db* db) { - dbg.nospace() << "<DB:" << db->getName() << ">"; + dbg.nospace() << "<DB:" << (db ? db->getName() : 0x0) << ">"; return dbg.space(); } diff --git a/SQLiteStudio3/coreSQLiteStudio/db/db.h b/SQLiteStudio3/coreSQLiteStudio/db/db.h index d937fb7..1331fff 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/db.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/db.h @@ -2,9 +2,7 @@ #define DB_H #include <QVariant> -#include "returncode.h" -#include "services/functionmanager.h" -#include "common/readwritelocker.h" +#include "common/global.h" #include "coreSQLiteStudio_global.h" #include "db/attachguard.h" #include "interruptable.h" @@ -604,7 +602,16 @@ class API_EXPORT Db : public QObject, public Interruptable * This is usually the same as DbPlugin::getTitle(), but getTitle() is used in list of plugins in configuration dialog, * while getTypeLabel() is used on databases list. */ - virtual QString getTypeLabel() = 0; + virtual QString getTypeLabel() const = 0; + + /** + * @brief Gets C++ class name implementing this particular Db instance. + * @return Class name. + * + * It can be used to distinguish between different drivers of Db instances. While getTypeLabel() can theoretically return + * same labels for two different drivers, this method will always return distinct class name. + */ + virtual QString getTypeClassName() const = 0; /** * @brief Initializes resources once the all derived Db classes are constructed. @@ -631,6 +638,7 @@ class API_EXPORT Db : public QObject, public Interruptable * @brief Registers scalar custom SQL function. * @param name Name of the function. * @param argCount Number of arguments accepted by the function (-1 for undefined). + * @param deterministic The deterministic function flag used when registering the function. * @return true on success, false on failure. * * Scalar functions are evaluated for each row and their result is used in place of function invokation. @@ -643,12 +651,13 @@ class API_EXPORT Db : public QObject, public Interruptable * * @see FunctionManager */ - virtual bool registerScalarFunction(const QString& name, int argCount) = 0; + virtual bool registerScalarFunction(const QString& name, int argCount, bool deterministic) = 0; /** * @brief Registers aggregate custom SQL function. * @param name Name of the function. * @param argCount Number of arguments accepted by the function (-1 for undefined). + * @param deterministic The deterministic function flag used when registering the function. * @return true on success, false on failure. * * Aggregate functions are used to aggregate many rows into single row. They are common in queries with GROUP BY statements. @@ -666,7 +675,7 @@ class API_EXPORT Db : public QObject, public Interruptable * * @see FunctionManager */ - virtual bool registerAggregateFunction(const QString& name, int argCount) = 0; + virtual bool registerAggregateFunction(const QString& name, int argCount, bool deterministic) = 0; /** * @brief Registers a collation sequence implementation in the database. @@ -707,6 +716,15 @@ class API_EXPORT Db : public QObject, public Interruptable */ virtual bool loadExtension(const QString& filePath, const QString& initFunc = QString()) = 0; + /** + * @brief Creates instance of same (derived) class with same construction parameters passed. + * @return Created instance. + * + * This is useful when one needs to operate on this database out of DbManager context, + * so ownership, connection/disconnection, deletion, etc. all belongs to the caller of this method. + */ + virtual Db* clone() const = 0; + signals: /** * @brief Emitted when the connection to the database was established. @@ -825,22 +843,20 @@ class API_EXPORT Db : public QObject, public Interruptable virtual bool closeQuiet() = 0; /** - * @brief Deregisters all funtions registered in the database and registers new (possibly the same) functions. + * @brief Deregisters previously registered user-defined functions and registers their fresh definition in the db. * * This slot is called from openAndSetup() and then every time user modifies custom SQL functions and commits changes to them. - * It deregisters all functions registered before in this database and registers new functions, currently defined for - * this database. * * @see FunctionManager */ - virtual void registerAllFunctions() = 0; + virtual void registerUserFunctions() = 0; /** * @brief Deregisters all collations registered in the database and registers new (possibly the same) collations. * * This slot is called from openAndsetup() and then every time user modifies custom collations and commits changes to them. */ - virtual void registerAllCollations() = 0; + virtual void registerUserCollations() = 0; }; QDataStream &operator<<(QDataStream &out, const Db* myObj); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp index 4778bb2..76ddd20 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp @@ -14,3 +14,13 @@ bool DbSqlite3::complete(const QString& sql) { return Sqlite3::complete(sql.toUtf8().constData()); } + +Db* DbSqlite3::clone() const +{ + return new DbSqlite3(name, path, connOptions); +} + +QString DbSqlite3::getTypeClassName() const +{ + return "DbSqlite3"; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h index 1e7f6e2..54a4230 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h @@ -2,7 +2,6 @@ #define DBSQLITE3_H #include "abstractdb3.h" -#include "common/global.h" #include "stdsqlite3driver.h" #include "db/sqlite3.h" @@ -30,6 +29,9 @@ class API_EXPORT DbSqlite3 : public AbstractDb3<Sqlite3> DbSqlite3(const QString& name, const QString& path); static bool complete(const QString& sql); + + Db* clone() const; + QString getTypeClassName() const; }; #endif // DBSQLITE3_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp index e7acb7b..e001017 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp @@ -241,11 +241,16 @@ int InvalidDb::getErrorCode() return 0; } -QString InvalidDb::getTypeLabel() +QString InvalidDb::getTypeLabel() const { return QStringLiteral("INVALID"); } +QString InvalidDb::getTypeClassName() const +{ + return "InvalidDb"; +} + bool InvalidDb::initAfterCreated() { return false; @@ -258,17 +263,19 @@ bool InvalidDb::deregisterFunction(const QString& name, int argCount) return false; } -bool InvalidDb::registerScalarFunction(const QString& name, int argCount) +bool InvalidDb::registerScalarFunction(const QString& name, int argCount, bool deterministic) { UNUSED(name); UNUSED(argCount); + UNUSED(deterministic); return false; } -bool InvalidDb::registerAggregateFunction(const QString& name, int argCount) +bool InvalidDb::registerAggregateFunction(const QString& name, int argCount, bool deterministic) { UNUSED(name); UNUSED(argCount); + UNUSED(deterministic); return false; } @@ -309,11 +316,11 @@ bool InvalidDb::closeQuiet() return false; } -void InvalidDb::registerAllFunctions() +void InvalidDb::registerUserFunctions() { } -void InvalidDb::registerAllCollations() +void InvalidDb::registerUserCollations() { } QString InvalidDb::getError() const @@ -339,6 +346,11 @@ bool InvalidDb::isComplete(const QString& sql) const return false; } +Db* InvalidDb::clone() const +{ + return new InvalidDb(name, path, connOptions); +} + void InvalidDb::interrupt() { } diff --git a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h index c56da2e..2505079 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h @@ -48,11 +48,12 @@ class API_EXPORT InvalidDb : public Db QString getUniqueNewObjectName(const QString& attachedDbName); QString getErrorText(); int getErrorCode(); - QString getTypeLabel(); + QString getTypeLabel() const; + QString getTypeClassName() const; bool initAfterCreated(); bool deregisterFunction(const QString& name, int argCount); - bool registerScalarFunction(const QString& name, int argCount); - bool registerAggregateFunction(const QString& name, int argCount); + bool registerScalarFunction(const QString& name, int argCount, bool deterministic); + bool registerAggregateFunction(const QString& name, int argCount, bool deterministic); bool registerCollation(const QString& name); bool deregisterCollation(const QString& name); void interrupt(); @@ -61,6 +62,7 @@ class API_EXPORT InvalidDb : public Db void setError(const QString& value); bool loadExtension(const QString& filePath, const QString& initFunc); bool isComplete(const QString& sql) const; + Db* clone() const; public slots: bool open(); @@ -68,8 +70,8 @@ class API_EXPORT InvalidDb : public Db bool openQuiet(); bool openForProbing(); bool closeQuiet(); - void registerAllFunctions(); - void registerAllCollations(); + void registerUserFunctions(); + void registerUserCollations(); private: QString name; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp index 83dae5d..022bf47 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp @@ -1,5 +1,5 @@ #include "queryexecutor.h" -#include "sqlerrorresults.h" +#include "db/queryexecutorsteps/queryexecutorfilter.h" #include "sqlerrorcodes.h" #include "services/dbmanager.h" #include "db/sqlerrorcodes.h" @@ -10,7 +10,6 @@ #include "queryexecutorsteps/queryexecutorattaches.h" #include "queryexecutorsteps/queryexecutorcountresults.h" #include "queryexecutorsteps/queryexecutorexecute.h" -#include "queryexecutorsteps/queryexecutorcellsize.h" #include "queryexecutorsteps/queryexecutorlimit.h" #include "queryexecutorsteps/queryexecutororder.h" #include "queryexecutorsteps/queryexecutorwrapdistinctresults.h" @@ -49,7 +48,7 @@ QueryExecutor::QueryExecutor(Db* db, const QString& query, QObject *parent) : setAutoDelete(false); connect(this, SIGNAL(executionFailed(int,QString)), this, SLOT(cleanupAfterExecFailed(int,QString))); - connect(DBLIST, SIGNAL(dbAboutToBeUnloaded(Db*, DbPlugin*)), this, SLOT(cleanupBeforeDbDestroy(Db*))); + connect(DBLIST, SIGNAL(dbAboutToBeUnloaded(Db*,DbPlugin*)), this, SLOT(cleanupBeforeDbDestroy(Db*))); connect(DBLIST, SIGNAL(dbRemoved(Db*)), this, SLOT(cleanupBeforeDbDestroy(Db*))); connect(simpleExecutor, &ChainExecutor::finished, this, &QueryExecutor::simpleExecutionFinished, Qt::DirectConnection); } @@ -82,6 +81,12 @@ void QueryExecutor::setupExecutionChain() executionChain.append(additionalStatelessSteps[AFTER_REPLACED_VIEWS]); executionChain.append(createSteps(AFTER_REPLACED_VIEWS)); + executionChain << new QueryExecutorFilter() + << new QueryExecutorParseQuery("after Filter"); + + executionChain.append(additionalStatelessSteps[AFTER_COLUMN_FILTERS]); + executionChain.append(createSteps(AFTER_COLUMN_FILTERS)); + executionChain << new QueryExecutorAddRowIds() << new QueryExecutorParseQuery("after AddRowIds"); @@ -105,14 +110,8 @@ void QueryExecutor::setupExecutionChain() executionChain.append(additionalStatelessSteps[AFTER_DISTINCT_WRAP]); executionChain.append(createSteps(AFTER_DISTINCT_WRAP)); - executionChain << new QueryExecutorCellSize() - << new QueryExecutorCountResults() - << new QueryExecutorParseQuery("after CellSize"); - - executionChain.append(additionalStatelessSteps[AFTER_CELL_SIZE_LIMIT]); - executionChain.append(createSteps(AFTER_CELL_SIZE_LIMIT)); - - executionChain << new QueryExecutorColumnType() + executionChain << new QueryExecutorCountResults() + << new QueryExecutorColumnType() << new QueryExecutorParseQuery("after ColumnType"); executionChain.append(additionalStatelessSteps[AFTER_COLUMN_TYPES]); @@ -130,13 +129,13 @@ void QueryExecutor::setupExecutionChain() executionChain << new QueryExecutorExecute(); - for (QueryExecutorStep* step : executionChain) + for (QueryExecutorStep*& step : executionChain) step->init(this, context); } void QueryExecutor::clearChain() { - for (QueryExecutorStep* step : executionChain) + for (QueryExecutorStep*& step : executionChain) { if (!allAdditionalStatelsssSteps.contains(step)) delete step; @@ -149,7 +148,7 @@ void QueryExecutor::executeChain() { // Go through all remaining steps bool result; - for (QueryExecutorStep* currentStep : executionChain) + for (QueryExecutorStep*& currentStep : executionChain) { if (isInterrupted()) { @@ -189,7 +188,9 @@ void QueryExecutor::stepFailed(QueryExecutorStep* currentStep) if (isInterrupted()) { + executionMutex.lock(); executionInProgress = false; + executionMutex.unlock(); emit executionFailed(SqlErrorCode::INTERRUPTED, tr("Execution interrupted.")); return; } @@ -416,7 +417,7 @@ QList<QueryExecutor::ResultRowIdColumnPtr> QueryExecutor::getRowIdResultColumns( int QueryExecutor::getMetaColumnCount() const { int count = 0; - for (ResultRowIdColumnPtr rowIdCol : context->rowIdColumns) + for (ResultRowIdColumnPtr& rowIdCol : context->rowIdColumns) count += rowIdCol->queryExecutorAliasToColumn.size(); return count; @@ -450,7 +451,9 @@ void QueryExecutor::executeSimpleMethod() if (queriesForSimpleExecution.isEmpty()) queriesForSimpleExecution = quickSplitQueries(originalQuery, false, true); - QStringList queriesWithPagination = applyLimitForSimpleMethod(queriesForSimpleExecution); + QStringList queriesWithPagination = applyFiltersAndLimitAndOrderForSimpleMethod(queriesForSimpleExecution); + if (isExecutorLoggingEnabled()) + qDebug() << "Simple Execution Method query:" << queriesWithPagination.join("; "); simpleExecutor->setQueries(queriesWithPagination); simpleExecutor->setDb(db); @@ -479,7 +482,7 @@ void QueryExecutor::simpleExecutionFinished(SqlQueryPtr results) ResultColumnPtr resCol; context->resultColumns.clear(); - for (const QString& colName : results->getColumnNames()) + for (QString& colName : results->getColumnNames()) { resCol = ResultColumnPtr::create(); resCol->displayName = colName; @@ -558,7 +561,7 @@ bool QueryExecutor::simpleExecIsSelect() void QueryExecutor::cleanup() { Db* attDb = nullptr; - for (const QString& attDbName : context->dbNameToAttach.leftValues()) + for (QString& attDbName : context->dbNameToAttach.leftValues()) { attDb = DBLIST->getByName(attDbName, Qt::CaseInsensitive); if (attDbName.isNull()) @@ -597,29 +600,65 @@ bool QueryExecutor::handleRowCountingResults(quint32 asyncId, SqlQueryPtr result return true; } -QStringList QueryExecutor::applyLimitForSimpleMethod(const QStringList &queries) +QStringList QueryExecutor::applyFiltersAndLimitAndOrderForSimpleMethod(const QStringList &queries) { + static_qstring(filtersTpl, "SELECT * FROM (%1) WHERE %2"); static_qstring(tpl, "SELECT * FROM (%1) LIMIT %2 OFFSET %3"); - if (page < 0) - return queries; // no paging requested + static_qstring(sortTpl, "SELECT * FROM (%1) ORDER BY %2"); + static_qstring(sortColTpl, "%1 %2"); + + if (page < 0 && sortOrder.isEmpty()) + return queries; QStringList result = queries; QString lastQuery = queries.last(); bool isSelect = false; getQueryAccessMode(lastQuery, &isSelect); - if (isSelect) + + // FILTERS + QString filters = getFilters(); + if (isSelect && !filters.isEmpty()) { - result.removeLast(); - result << tpl.arg(trimQueryEnd(lastQuery), QString::number(resultsPerPage), QString::number(page * resultsPerPage)); + lastQuery = filtersTpl.arg( + trimQueryEnd(lastQuery), + filters + ); } + + // ORDER BY + if (!sortOrder.isEmpty()) + { + QStringList cols; + for (QueryExecutor::Sort& sort : sortOrder) + { + cols << sortColTpl.arg( + QString::number(sort.column + 1), // in ORDER BY column indexes are 1-based + (sort.order == QueryExecutor::Sort::DESC) ? "DESC" : "ASC" + ); + } + lastQuery = sortTpl.arg(trimQueryEnd(lastQuery), cols.join(", ")); + } + + // LIMIT + if (page >= 0 && isSelect) + { + lastQuery = tpl.arg( + trimQueryEnd(lastQuery), + QString::number(resultsPerPage), + QString::number(page * resultsPerPage) + ); + } + + result.removeLast(); + result << lastQuery; return result; } QList<QueryExecutorStep*> QueryExecutor::createSteps(QueryExecutor::StepPosition position) { QList<QueryExecutorStep*> steps; - for (StepFactory* factory : additionalStatefulStepFactories[position]) + for (StepFactory*& factory : additionalStatefulStepFactories[position]) steps << factory->produceQueryExecutorStep(); return steps; @@ -714,7 +753,7 @@ void QueryExecutor::handleErrorsFromSmartAndSimpleMethods(SqlQueryPtr results) { QString match; QString replaceName; - for (const QString& attachName : context->dbNameToAttach.rightValues()) + for (QString& attachName : context->dbNameToAttach.rightValues()) { match = attachName + "."; replaceName = wrapObjIfNeeded(context->dbNameToAttach.valueByRight(attachName)) + "."; @@ -752,7 +791,7 @@ bool QueryExecutor::wasDataModifyingQuery() const QList<DataType> QueryExecutor::resolveColumnTypes(Db* db, QList<QueryExecutor::ResultColumnPtr>& columns, bool noDbLocking) { QSet<Table> tables; - for (ResultColumnPtr col : columns) + for (ResultColumnPtr& col : columns) tables << Table(col->database, col->table); SchemaResolver resolver(db); @@ -774,7 +813,7 @@ QList<DataType> QueryExecutor::resolveColumnTypes(Db* db, QList<QueryExecutor::R QList<DataType> datatypeList; Table t; SqliteCreateTable::Column* parsedCol = nullptr; - for (ResultColumnPtr col : columns) + for (ResultColumnPtr& col : columns) { t = Table(col->database, col->table); if (!parsedTables.contains(t)) @@ -969,3 +1008,13 @@ int qHash(QueryExecutor::SourceTable sourceTable) return qHash(sourceTable.database + "." + sourceTable.table + "/" + sourceTable.alias); } + +QString QueryExecutor::getFilters() const +{ + return filters; +} + +void QueryExecutor::setFilters(const QString& newFilters) +{ + filters = newFilters; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h index ffbcb7b..c8d0b00 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h @@ -2,10 +2,10 @@ #define QUERYEXECUTOR_H #include "db/db.h" -#include "parser/token.h" -#include "selectresolver.h" #include "coreSQLiteStudio_global.h" #include "common/bistrhash.h" +#include "parser/ast/sqlitequery.h" +#include "parser/ast/sqlitequerytype.h" #include "datatype.h" #include <QObject> #include <QHash> @@ -15,7 +15,6 @@ /** @file */ class Parser; -class SqliteQuery; class QueryExecutorStep; class DbPlugin; class ChainExecutor; @@ -174,6 +173,10 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable * in COMPOUND_SELECT case. To learn about common table expression statement, * see http://sqlite.org/lang_with.html */ + VIEW_NOT_EXPANDED,/**< + * The data cell comes from a VIEW that was not expanded (because there were + * multi-level views), therefore it was impossible to get ROWID for the cell. + */ }; /** @@ -652,6 +655,14 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable * message from smart execution. */ QString errorMessageFromSmartExecution; + + /** + * @brief Flag indicating whether views were replaced/expanded. + * + * In other words, this flag tells whether the ReplaceViews step of query executor + * was executed, or skipped (due to many levels of views). False = skipped. + */ + bool viewsExpanded = false; }; /** @@ -679,8 +690,8 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable 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_COLUMN_TYPES, /**< After typeof() result meta columns were added */ + AFTER_COLUMN_FILTERS, /**< After WHERE filters applied */ 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 */ @@ -1077,6 +1088,9 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable bool getNoMetaColumns() const; void setNoMetaColumns(bool value); + void setFilters(const QString& newFilters); + QString getFilters() const; + void handleErrorsFromSmartAndSimpleMethods(SqlQueryPtr results); /** @@ -1222,7 +1236,7 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable */ bool handleRowCountingResults(quint32 asyncId, SqlQueryPtr results); - QStringList applyLimitForSimpleMethod(const QStringList &queries); + QStringList applyFiltersAndLimitAndOrderForSimpleMethod(const QStringList &queries); /** * @brief Creates instances of steps for all registered factories for given position. @@ -1348,6 +1362,13 @@ class API_EXPORT QueryExecutor : public QObject, public QRunnable int dataLengthLimit = -1; /** + * @brief Optional filters to apply to the query. + * If not empty, it will be appended to the WHERE clause at the very end of execution chain, + * skipping complex result colum analysis, etc. + */ + QString filters; + + /** * @brief Limit of queries, after which simple mode is used. * * Up to the defined limit the smart execution will be used (unless #forceSimpleMode was set). diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp index a487623..d294b2c 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp @@ -1,7 +1,6 @@ #include "queryexecutoraddrowids.h" #include "parser/ast/sqliteselect.h" #include "selectresolver.h" -#include "common/utils_sql.h" #include "parser/ast/sqlitecreatetable.h" #include "schemaresolver.h" #include "common/compatibility.h" @@ -55,12 +54,14 @@ QHash<SelectResolver::Table,QHash<QString,QString>> QueryExecutorAddRowIds::addR return rowIdColsMap; // Go trough subselects to add ROWID result columns there and collect rowId mapping to use here. - for (SqliteSelect* subSelect : getSubSelects(core)) + for (SqliteSelect*& subSelect : getSubSelects(core)) { unite(rowIdColsMap, addRowIdForTables(subSelect, ok, false)); if (!ok) return rowIdColsMap; + } + core->rebuildTokens(); // Getting all tables we need to get ROWID for SelectResolver resolver(db, select->tokens.detokenize(), context->dbNameToAttach); @@ -71,7 +72,11 @@ QHash<SelectResolver::Table,QHash<QString,QString>> QueryExecutorAddRowIds::addR { if (table.flags & (SelectResolver::FROM_COMPOUND_SELECT | SelectResolver::FROM_DISTINCT_SELECT | SelectResolver::FROM_GROUPED_SELECT | SelectResolver::FROM_CTE_SELECT)) - continue; // we don't get ROWID from compound, distinct or aggregated subselects + continue; // we don't get ROWID from compound, distinct or aggregated subselects. + + // Tables from inside of view don't provide ROWID, if views were not expanded. + if (!context->viewsExpanded && table.flags & SelectResolver::FROM_VIEW) + continue; 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 @@ -94,7 +99,7 @@ QList<SqliteSelect*> QueryExecutorAddRowIds::getSubSelects(SqliteSelect::Core* c if (core->from->singleSource && core->from->singleSource->select) selects << core->from->singleSource->select; - for (SqliteSelect::Core::JoinSourceOther* otherSource : core->from->otherSources) + for (SqliteSelect::Core::JoinSourceOther*& otherSource : core->from->otherSources) { if (!otherSource->singleSource->select) continue; @@ -118,7 +123,7 @@ QHash<QString,QString> QueryExecutorAddRowIds::getNextColNames(const SelectResol return colNames; } - if (createTable->withOutRowId.isNull()) + if (!createTable->withOutRowId) { // It's a regular ROWID table colNames[getNextColName()] = "ROWID"; @@ -142,7 +147,7 @@ QHash<QString,QString> QueryExecutorAddRowIds::getNextColNames(const SelectResol SqliteCreateTable::Constraint* tableConstr = dynamic_cast<SqliteCreateTable::Constraint*>(primaryKey); if (tableConstr) { - for (SqliteIndexedColumn* idxCol : tableConstr->indexedColumns) + for (SqliteIndexedColumn*& idxCol : tableConstr->indexedColumns) colNames[getNextColName()] = idxCol->name; return colNames; @@ -155,7 +160,11 @@ QHash<QString,QString> QueryExecutorAddRowIds::getNextColNames(const SelectResol bool QueryExecutorAddRowIds::addResultColumns(SqliteSelect::Core* core, const SelectResolver::Table& table, QHash<SelectResolver::Table,QHash<QString,QString>>& rowIdColsMap, bool isTopSelect) { - SelectResolver::Table keyTable = table; + SelectResolver::Table destilledTable = table; + if (destilledTable.database == "main" && destilledTable.originalDatabase.isNull()) + destilledTable.database = QString(); + + SelectResolver::Table keyTable = destilledTable; // If selecting from named subselect, where table in that subselect has no alias, we need to match // Table by table&database, but excluding alias. @@ -163,18 +172,20 @@ bool QueryExecutorAddRowIds::addResultColumns(SqliteSelect::Core* core, const Se { keyTable.tableAlias = QString(); if (!rowIdColsMap.contains(keyTable)) - keyTable = table; + { + keyTable = destilledTable; + } } // Aliased matching should be performed also against pushed (to old) aliases, due to multi-level subselects. if (!rowIdColsMap.contains(keyTable)) { - for (const SelectResolver::Table& rowIdColsMapTable : rowIdColsMap.keys()) + for (auto rowIdColsMapTable = rowIdColsMap.keyBegin(), end = rowIdColsMap.keyEnd(); rowIdColsMapTable != end; ++rowIdColsMapTable) { - if (!table.oldTableAliases.contains(rowIdColsMapTable.tableAlias, Qt::CaseInsensitive)) + if (!table.oldTableAliases.contains(rowIdColsMapTable->tableAlias, Qt::CaseInsensitive)) continue; - keyTable = rowIdColsMapTable; + keyTable = *rowIdColsMapTable; } } @@ -202,7 +213,7 @@ bool QueryExecutorAddRowIds::addResultColumns(SqliteSelect::Core* core, const Se while (it.hasNext()) { it.next(); - if (!addResultColumns(core, table, it.key(), it.value(), aliasOnlyAsSelectColumn)) + if (!addResultColumns(core, destilledTable, it.key(), it.value(), aliasOnlyAsSelectColumn)) return false; } @@ -228,7 +239,7 @@ bool QueryExecutorAddRowIds::checkInWithClause(const SelectResolver::Table &tabl SqliteWith::CommonTableExpression* cte = nullptr; QString nameToCompareWith = table.tableAlias.isNull() ? table.table : table.tableAlias; - for (SqliteWith::CommonTableExpression* cteItem : with->cteList) + for (SqliteWith::CommonTableExpression*& cteItem : with->cteList) { if (cteItem->table == nameToCompareWith) { diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h index 1669542..bf4d263 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h @@ -2,7 +2,7 @@ #define QUERYEXECUTORADDROWIDS_H #include "queryexecutorstep.h" -#include "parser/token.h" +#include "selectresolver.h" /** * @brief Adds ROWID to result columns. diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp deleted file mode 100644 index 352c74b..0000000 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "queryexecutorcellsize.h" -#include <QDebug> - -bool QueryExecutorCellSize::exec() -{ - if (queryExecutor->getDataLengthLimit() < 0) - return true; - - SqliteSelectPtr select = getSelect(); - if (!select || select->explain) - return true; - - for (SqliteSelect::Core* core : select->coreSelects) - { - if (!applyDataLimit(select.data(), core)) - return false; - } - - updateQueries(); - return true; -} - -bool QueryExecutorCellSize::applyDataLimit(SqliteSelect* select, SqliteSelect::Core* core) -{ - if (core->tokensMap["selcollist"].size() == 0) - { - qCritical() << "No 'selcollist' in Select::Core. Cannot apply cell size limits."; - return false; - } - - bool first = true; - TokenList tokens; - - for (const QueryExecutor::ResultColumnPtr& col : context->resultColumns) - { - if (!first) - tokens += getSeparatorTokens(); - - tokens += getLimitTokens(col); - first = false; - } - - for (const QueryExecutor::ResultRowIdColumnPtr& col : context->rowIdColumns) - { - if (!first) - tokens += getSeparatorTokens(); - - tokens += getNoLimitTokens(col); - first = false; - } - - // Wrapping original select with new select with limited columns - select->tokens = wrapSelect(select->tokens, tokens); - - return true; -} - -TokenList QueryExecutorCellSize::getLimitTokens(const QueryExecutor::ResultColumnPtr& resCol) -{ - // CASE WHEN typeof(alias) IN ('real', 'integer', 'numeric', 'null') THEN alias ELSE substr(alias, 1, limit) END - TokenList newTokens; - newTokens << TokenPtr::create(Token::KEYWORD, "CASE") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::KEYWORD, "WHEN") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::OTHER, "typeof") - << TokenPtr::create(Token::PAR_LEFT, "(") - << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias) - << TokenPtr::create(Token::PAR_RIGHT, ")") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::KEYWORD, "IN") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::PAR_LEFT, "(") - << TokenPtr::create(Token::STRING, "'real'") - << TokenPtr::create(Token::OPERATOR, ",") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::STRING, "'integer'") - << TokenPtr::create(Token::OPERATOR, ",") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::STRING, "'numeric'") - << TokenPtr::create(Token::OPERATOR, ",") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::STRING, "'null'") - << TokenPtr::create(Token::PAR_RIGHT, ")") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::KEYWORD, "THEN") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias) - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::KEYWORD, "ELSE") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::OTHER, "substr") - << TokenPtr::create(Token::PAR_LEFT, "(") - << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias) - << TokenPtr::create(Token::OPERATOR, ",") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::INTEGER, "1") - << TokenPtr::create(Token::OPERATOR, ",") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::INTEGER, QString::number(queryExecutor->getDataLengthLimit())) - << TokenPtr::create(Token::PAR_RIGHT, ")") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::KEYWORD, "END") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::KEYWORD, "AS") - << TokenPtr::create(Token::SPACE, " ") - << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias); - return newTokens; -} - -TokenList QueryExecutorCellSize::getNoLimitTokens(const QueryExecutor::ResultRowIdColumnPtr& resCol) -{ - TokenList newTokens; - bool first = true; - for (const QString& col : resCol->queryExecutorAliasToColumn.keys()) - { - if (!first) - newTokens += getSeparatorTokens(); - - newTokens << TokenPtr::create(Token::OTHER, col); - first = false; - } - return newTokens; -} - -TokenList QueryExecutorCellSize::getSeparatorTokens() -{ - TokenList newTokens; - newTokens << TokenPtr::create(Token::OPERATOR, ","); - newTokens << TokenPtr::create(Token::SPACE, " "); - return newTokens; -} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h deleted file mode 100644 index c174c69..0000000 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef QUERYEXECUTORCELLSIZE_H -#define QUERYEXECUTORCELLSIZE_H - -#include "queryexecutorstep.h" - -/** - * @brief Applies per-cell byte size limit to the query. - * - * Size of data extracted for each cell is limited in order to avoid huge memory use - * when the database contains column with like 500MB values per row and the query - * returns for example 100 rows. - * - * This is accomplished by wrapping all result columns (except ROWID columns) with substr() SQL function. - * - * SQLiteStudio limits each column to SqlQueryModel::cellDataLengthLimit when displaying - * data in SqlQueryView. - * - * This feature is disabled by default in QueryExecutor and has to be enabled by defining - * QueryExecutor::setDataLengthLimit(). - */ -class QueryExecutorCellSize : public QueryExecutorStep -{ - Q_OBJECT - - public: - bool exec(); - - private: - /** - * @brief Applies limit function to all result columns in given SELECT. - * @param select Select that we want to limit. - * @param core Select's core that we want to limit. - * @return true on success, false on failure. - * - * This method is called for each core in the \p select. - */ - bool applyDataLimit(SqliteSelect* select, SqliteSelect::Core* core); - - /** - * @brief Generates tokens that will return limited value of the result column. - * @param resCol Result column to wrap. - * @return List of tokens. - */ - TokenList getLimitTokens(const QueryExecutor::ResultColumnPtr& resCol); - - /** - * @brief Generates tokens that will return unlimited value of the ROWID result column. - * @param resCol ROWID result column. - * @return List of tokens. - */ - TokenList getNoLimitTokens(const QueryExecutor::ResultRowIdColumnPtr& resCol); - - /** - * @brief Generates tokens representing result columns separator. - * @return List of tokens. - * - * Result columns separator tokens are just a period and a space. - */ - TokenList getSeparatorTokens(); -}; - -#endif // QUERYEXECUTORCELLSIZE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp index 5ababcd..d3f2eea 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp @@ -18,7 +18,7 @@ bool QueryExecutorColumns::exec() // Resolving result columns of the select SelectResolver resolver(db, queryExecutor->getOriginalQuery(), context->dbNameToAttach); resolver.resolveMultiCore = true; - QList<SelectResolver::Column> columns = resolver.resolve(select.data()).first(); + QList<SelectResolver::Column> columns = resolver.resolve(select->coreSelects.first()); if (resolver.hasErrors()) { @@ -34,22 +34,21 @@ bool QueryExecutorColumns::exec() // Deleting old result columns and defining new ones SqliteSelect::Core* core = select->coreSelects.first(); - for (SqliteSelect::Core::ResultColumn* resCol : core->resultColumns) + for (SqliteSelect::Core::ResultColumn*& resCol : core->resultColumns) delete resCol; core->resultColumns.clear(); // Count total rowId columns - for (const QueryExecutor::ResultRowIdColumnPtr& rowIdCol : context->rowIdColumns) + for (QueryExecutor::ResultRowIdColumnPtr& rowIdCol : context->rowIdColumns) rowIdColNames += rowIdCol->queryExecutorAliasToColumn.keys(); // Defining result columns QueryExecutor::ResultColumnPtr resultColumn; SqliteSelect::Core::ResultColumn* resultColumnForSelect = nullptr; bool rowIdColumn = false; - int i = 0; QSet<QString> usedAliases; - for (const SelectResolver::Column& col : columns) + for (SelectResolver::Column& col : columns) { // Convert column to QueryExecutor result column resultColumn = getResultColumn(col); @@ -68,8 +67,6 @@ bool QueryExecutorColumns::exec() if (!rowIdColumn) context->resultColumns << resultColumn; // store it in context for later usage by any step - - i++; } // qDebug() << "before: " << context->processedQuery; @@ -111,6 +108,9 @@ QueryExecutor::ResultColumnPtr QueryExecutorColumns::getResultColumn(const Selec if (resolvedColumn.flags & SelectResolver::FROM_CTE_SELECT) resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::COMM_TAB_EXPR; + if (resolvedColumn.flags & SelectResolver::FROM_VIEW) + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::VIEW_NOT_EXPANDED; + resultColumn->database = resolvedColumn.originalDatabase; resultColumn->table = resolvedColumn.table; resultColumn->column = resolvedColumn.column; @@ -120,13 +120,10 @@ QueryExecutor::ResultColumnPtr QueryExecutorColumns::getResultColumn(const Selec } if (isRowIdColumnAlias(resultColumn->alias)) - { resultColumn->queryExecutorAlias = resultColumn->alias; - } else - { resultColumn->queryExecutorAlias = getNextColName(); - } + return resultColumn; } @@ -135,10 +132,10 @@ SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect SqliteSelect::Core::ResultColumn* selectResultColumn = new SqliteSelect::Core::ResultColumn(); QString colString = resultColumn->column; - if (col.aliasDefinedInSubQuery) // #2931 - colString = col.alias; + if (col.aliasDefinedInSubQuery) // #2819 (id from old tracker was 2931) + colString = wrapObjIfNeeded(col.alias); - if (!resultColumn->expression) + if (!resultColumn->expression && !col.aliasDefinedInSubQuery) // if alias defined in subquery, it's already wrapped colString = wrapObjIfNeeded(colString); Parser parser; @@ -149,6 +146,7 @@ SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect if (parser.getErrors().size() > 0) qWarning() << "The error was:" << parser.getErrors().first()->getFrom() << ":" << parser.getErrors().first()->getMessage(); + delete selectResultColumn; return nullptr; } @@ -189,7 +187,6 @@ SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect selectResultColumn->alias = aliasTpl.arg(aliasBase, QString::number(nextAliasCounter++)); usedAliases += selectResultColumn->alias; - selectResultColumn->alias = wrapObjIfNeeded(selectResultColumn->alias); return selectResultColumn; } @@ -204,7 +201,7 @@ QString QueryExecutorColumns::resolveAttachedDatabases(const QString &dbName) bool QueryExecutorColumns::isRowIdColumnAlias(const QString& alias) { - for (QueryExecutor::ResultRowIdColumnPtr rowIdColumn : context->rowIdColumns) + for (QueryExecutor::ResultRowIdColumnPtr& rowIdColumn : context->rowIdColumns) { if (rowIdColumn->queryExecutorAliasToColumn.keys().contains(alias)) return true; @@ -224,7 +221,7 @@ void QueryExecutorColumns::wrapWithAliasedColumns(SqliteSelect* select) QString baseColName; QString colName; static_qstring(colNameTpl, "%1:%2"); - for (const QueryExecutor::ResultColumnPtr& resCol : context->resultColumns) + for (QueryExecutor::ResultColumnPtr& resCol : context->resultColumns) { if (!first) outerColumns += sepTokens; @@ -253,9 +250,9 @@ void QueryExecutorColumns::wrapWithAliasedColumns(SqliteSelect* select) first = false; } - for (const QueryExecutor::ResultRowIdColumnPtr& rowIdColumn : context->rowIdColumns) + for (QueryExecutor::ResultRowIdColumnPtr& rowIdColumn : context->rowIdColumns) { - for (const QString& alias : rowIdColumn->queryExecutorAliasToColumn.keys()) + for (QString& alias : rowIdColumn->queryExecutorAliasToColumn.keys()) { if (!first) outerColumns += sepTokens; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumntype.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumntype.cpp index fb788f4..d0f9387 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumntype.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumntype.cpp @@ -1,4 +1,7 @@ #include "queryexecutorcolumntype.h" +#include "parser/parser.h" +#include <QDebug> +#include <QStringList> bool QueryExecutorColumnType::exec() { @@ -9,7 +12,22 @@ bool QueryExecutorColumnType::exec() if (!select || select->explain) return true; - addTypeColumns(select.data()); + static_qstring(selectTpl, "SELECT *, %1 FROM (%2)"); + + QStringList columns = addTypeColumns(); + QString newSelect = selectTpl.arg(columns.join(", "), select->detokenize()); + + Parser parser; + if (!parser.parse(newSelect) || parser.getQueries().size() == 0) + { + qWarning() << "Could not parse SELECT after applying typeof(). Tried to parse query:\n" << newSelect; + return false; + } + + context->parsedQueries.removeLast(); + context->parsedQueries << parser.getQueries().first(); + + updateQueries(); select->rebuildTokens(); updateQueries(); @@ -17,31 +35,16 @@ bool QueryExecutorColumnType::exec() return true; } -void QueryExecutorColumnType::addTypeColumns(SqliteSelect* select) +QStringList QueryExecutorColumnType::addTypeColumns() { - for (const QueryExecutor::ResultColumnPtr& resCol : context->resultColumns) + static_qstring(typeOfColTpl, "typeof(%1) AS %2"); + QStringList typeColumns; + for (QueryExecutor::ResultColumnPtr& resCol : context->resultColumns) { QString nextCol = getNextColName(); QString targetCol = resCol->queryExecutorAlias; - - for (SqliteSelect::Core* core : select->coreSelects) - { - SqliteSelect::Core::ResultColumn* realResCol = createRealTypeOfResCol(targetCol, nextCol); - core->resultColumns << realResCol; - realResCol->setParent(core); - } - + typeColumns << typeOfColTpl.arg(targetCol, nextCol); context->typeColumnToResultColumnAlias[nextCol] = targetCol; } -} - -SqliteSelect::Core::ResultColumn* QueryExecutorColumnType::createRealTypeOfResCol(const QString& targetCol, const QString& alias) -{ - SqliteExpr* targetColExpr = new SqliteExpr(); - targetColExpr->initId(targetCol); - - SqliteExpr* expr = new SqliteExpr(); - expr->initFunction("typeof", false, {targetColExpr}); - - return new SqliteSelect::Core::ResultColumn(expr, true, alias); + return typeColumns; } diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumntype.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumntype.h index f492313..92c6324 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumntype.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumntype.h @@ -2,7 +2,7 @@ #define QUERYEXECUTORCOLUMNTYPE_H #include "queryexecutorstep.h" -#include "parser/ast/sqliteselect.h" +//#include "parser/ast/sqliteselect.h" class QueryExecutorColumnType : public QueryExecutorStep { @@ -12,8 +12,8 @@ class QueryExecutorColumnType : public QueryExecutorStep bool exec(); private: - void addTypeColumns(SqliteSelect* select); - SqliteSelect::Core::ResultColumn* createRealTypeOfResCol(const QString& targetCol, const QString& alias); + QStringList addTypeColumns(); +// SqliteSelect::Core::ResultColumn* createRealTypeOfResCol(const QString& targetCol, const QString& alias); }; #endif // QUERYEXECUTORCOLUMNTYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp index be7a873..9ff8026 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp @@ -1,6 +1,7 @@ #include "queryexecutordatasources.h" #include "parser/ast/sqliteselect.h" #include "selectresolver.h" +#include <QDebug> bool QueryExecutorDataSources::exec() { @@ -19,7 +20,7 @@ bool QueryExecutorDataSources::exec() SqliteSelect::Core* core = select->coreSelects.first(); QSet<SelectResolver::Table> tables = resolver.resolveTables(core); - for (SelectResolver::Table resolvedTable : tables) + for (const SelectResolver::Table& resolvedTable : tables) { if (resolvedTable.flags & SelectResolver::FROM_CTE_SELECT) continue; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorfilter.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorfilter.cpp new file mode 100644 index 0000000..bcc9ce4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorfilter.cpp @@ -0,0 +1,26 @@ +#include "queryexecutorfilter.h" +#include <QDebug> + +bool QueryExecutorFilter::exec() +{ +// qDebug() << "filters:" << queryExecutor->getFilters(); +// qDebug() << "q1:" << context->processedQuery; + if (queryExecutor->getFilters().trimmed().isEmpty()) + return true; + + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + if (select->tokens.size() < 1) + return true; // shouldn't happen, but if happens, quit gracefully + + static_qstring(selectTpl, "SELECT * FROM (%1) WHERE %2"); + QString newSelect = selectTpl.arg(select->detokenize(), queryExecutor->getFilters()); + + int begin = select->tokens.first()->start; + int length = select->tokens.last()->end - select->tokens.first()->start + 1; + context->processedQuery = context->processedQuery.replace(begin, length, newSelect); +// qDebug() << "q2:" << context->processedQuery; + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorfilter.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorfilter.h new file mode 100644 index 0000000..0bb8459 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorfilter.h @@ -0,0 +1,20 @@ +#ifndef QUERYEXECUTORFILTER_H +#define QUERYEXECUTORFILTER_H + +#include "queryexecutorstep.h" + +/** + * @brief Applies WHERE filtering to the query. + * + * This step is executed late in the execution chain. It is useful, when one wants to apply filtering + * without involving whole column/rowid analysis that is done in earlier executor steps. + */ +class QueryExecutorFilter : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORFILTER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp index af1d7a6..afa1297 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp @@ -1,5 +1,4 @@ #include "queryexecutorlimit.h" -#include "parser/ast/sqlitelimit.h" #include <QDebug> bool QueryExecutorLimit::exec() diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp index 8195322..b6db4b2 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp @@ -1,5 +1,6 @@ #include "queryexecutorreplaceviews.h" #include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqliteselect.h" #include "schemaresolver.h" #include <QDebug> @@ -68,39 +69,20 @@ SqliteCreateViewPtr QueryExecutorReplaceViews::getView(const QString& database, void QueryExecutorReplaceViews::replaceViews(SqliteSelect* select) { SqliteSelect::Core* core = select->coreSelects.first(); - - QStringList viewsInDatabase; - SqliteCreateViewPtr view; - QList<SqliteSelect::Core::SingleSource*> sources = core->getAllTypedStatements<SqliteSelect::Core::SingleSource>(); - QList<SqliteSelect::Core::SingleSource*> viewSources; - QSet<SqliteStatement*> parents; + typedef QPair<SqliteSelect::Core::SingleSource*, SqliteCreateViewPtr> SourceViewPair; + SqliteCreateViewPtr view; + QList<SourceViewPair> sourceViewPairs; for (SqliteSelect::Core::SingleSource* src : sources) { if (src->table.isNull()) continue; - viewsInDatabase = getViews(src->database); + QStringList viewsInDatabase = getViews(src->database); if (!viewsInDatabase.contains(src->table, Qt::CaseInsensitive)) continue; - parents << src->parentStatement(); - viewSources << src; - } - - if (parents.size() > 1) - { - // Multi-level views (view selecting from view, selecting from view...). - // Such constructs build up easily to huge, non-optimized queries. - // For performance reasons, we won't expand such views. - qDebug() << "Multi-level views. Skipping view expanding feature of query executor. Some columns won't be editable due to that. Number of different view parents:" - << parents.size(); - return; - } - - for (SqliteSelect::Core::SingleSource* src : viewSources) - { view = getView(src->database, src->table); if (!view) { @@ -109,15 +91,50 @@ void QueryExecutorReplaceViews::replaceViews(SqliteSelect* select) continue; } - QString alias = src->alias.isNull() ? view->view : src->alias; + if (usesAnyView(view->select, viewsInDatabase)) + { + // Multi-level views (view selecting from view, selecting from view...). + // Such constructs build up easily to huge, non-optimized queries. + // For performance reasons, we won't expand such views. + qDebug() << "Multi-level views. Skipping view expanding feature of query executor. Some columns won't be editable due to that."; + return; + } + + sourceViewPairs << SourceViewPair(src, view); + } + + for (SourceViewPair& pair : sourceViewPairs) + { + view = pair.second; + + QString alias = pair.first->alias.isNull() ? view->view : pair.first->alias; - src->select = view->select; - src->alias = alias; - src->database = QString(); - src->table = QString(); + pair.first->select = view->select; + pair.first->alias = alias; + pair.first->database = QString(); + pair.first->table = QString(); - replaceViews(src->select); + // replaceViews(pair.first->select); // No recursion, as we avoid multi-level expanding. + } + + context->viewsExpanded = true; +} + +bool QueryExecutorReplaceViews::usesAnyView(SqliteSelect* select, const QStringList& viewsInDatabase) +{ + for (SqliteSelect::Core*& core : select->coreSelects) + { + QList<SqliteSelect::Core::SingleSource*> sources = core->getAllTypedStatements<SqliteSelect::Core::SingleSource>(); + for (SqliteSelect::Core::SingleSource* src : sources) + { + if (src->table.isNull()) + continue; + + if (viewsInDatabase.contains(src->table, Qt::CaseInsensitive)) + return true; + } } + return false; } uint qHash(const QueryExecutorReplaceViews::View& view) diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h index 633b0bc..345934d 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h @@ -4,6 +4,8 @@ #include "queryexecutorstep.h" #include "parser/ast/sqlitecreateview.h" +class SchemaResolver; + /** * @brief Replaces all references to views in query with SELECTs from those views. * @@ -86,6 +88,14 @@ class QueryExecutorReplaceViews : public QueryExecutorStep void replaceViews(SqliteSelect* select); /** + * @brief Tells whether particular SELECT statement has any View as a data source. + * @param select Parsed SELECT statement. + * @param viewsInDatabase Prepared list of views existing in the database. + * @return true if the SELECT uses at least one existing View. + */ + bool usesAnyView(SqliteSelect* select, const QStringList& viewsInDatabase); + + /** * @brief Used for caching view list per database. */ QHash<QString,QStringList> views; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp index 72dbeec..26b137a 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp @@ -1,6 +1,5 @@ #include "queryexecutorstep.h" #include "db/queryexecutor.h" -#include "common/unused.h" QueryExecutorStep::~QueryExecutorStep() { @@ -17,7 +16,7 @@ void QueryExecutorStep::init(QueryExecutor *queryExecutor, QueryExecutor::Contex void QueryExecutorStep::updateQueries() { QString newQuery; - for (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 fcc0cf6..cc89212 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h @@ -1,7 +1,6 @@ #ifndef QUERYEXECUTORSTEP_H #define QUERYEXECUTORSTEP_H -#include "db/sqlquery.h" #include "parser/ast/sqliteselect.h" #include "db/queryexecutor.h" #include <QObject> diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp index bef3ed8..0880344 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp @@ -1,6 +1,7 @@ #include "sqlquery.h" #include "db/sqlerrorcodes.h" #include "common/utils_sql.h" +#include "common/unused.h" SqlQuery::~SqlQuery() { @@ -141,3 +142,83 @@ QString RowIdConditionBuilder::build() { return conditions.join(" AND "); } + +/********************** SqlQueryError ************************/ + +class API_EXPORT SqlQueryError : public SqlQuery +{ + public: + SqlQueryError(const QString& errorText, int errorCode); + virtual ~SqlQueryError(); + + QString getErrorText(); + int getErrorCode(); + QStringList getColumnNames(); + int columnCount(); + + protected: + SqlResultsRowPtr nextInternal(); + bool hasNextInternal(); + bool execInternal(const QList<QVariant>& args); + bool execInternal(const QHash<QString, QVariant>& args); + + private: + QString errorText; + int errorCode = 0; +}; + +SqlQueryPtr SqlQuery::error(const QString& errorText, int errorCode) +{ + return SqlQueryPtr(new SqlQueryError(errorText, errorCode)); +} + +SqlQueryError::SqlQueryError(const QString& errorText, int errorCode) : + errorText(errorText), errorCode(errorCode) +{ +} + +SqlQueryError::~SqlQueryError() +{ +} + +QString SqlQueryError::getErrorText() +{ + return errorText; +} + +int SqlQueryError::getErrorCode() +{ + return errorCode; +} + +QStringList SqlQueryError::getColumnNames() +{ + return QStringList(); +} + +int SqlQueryError::columnCount() +{ + return 0; +} + +SqlResultsRowPtr SqlQueryError::nextInternal() +{ + return SqlResultsRowPtr(); +} + +bool SqlQueryError::hasNextInternal() +{ + return false; +} + +bool SqlQueryError::execInternal(const QList<QVariant>& args) +{ + UNUSED(args); + return false; +} + +bool SqlQueryError::execInternal(const QHash<QString, QVariant>& args) +{ + UNUSED(args); + return false; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h index 17bb464..1eea25c 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h @@ -66,6 +66,14 @@ class API_EXPORT SqlQuery { public: /** + * @brief Produces empty, erronous result. + * @param errorText Error message returned with #getErrorText() of the returned object. + * @param errorCode Error code returned with #getErrorText() of the returned object. + * @return SqlQuery object shared pointer with no results, but with error details populated. + */ + static SqlQueryPtr error(const QString& errorText, int errorCode); + + /** * @brief Releases result resources. */ virtual ~SqlQuery(); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h b/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h index 01894c2..c8a3ba3 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h @@ -11,6 +11,7 @@ static const int OPEN_READWRITE = UppercasePrefix##SQLITE_OPEN_READWRITE; \ static const int OPEN_CREATE = UppercasePrefix##SQLITE_OPEN_CREATE; \ static const int UTF8 = UppercasePrefix##SQLITE_UTF8; \ + static const int DETERMINISTIC = UppercasePrefix##SQLITE_DETERMINISTIC; \ static const int INTEGER = UppercasePrefix##SQLITE_INTEGER; \ static const int FLOAT = UppercasePrefix##SQLITE_FLOAT; \ static const int NULL_TYPE = UppercasePrefix##SQLITE_NULL; \ @@ -19,6 +20,10 @@ static const int BUSY = UppercasePrefix##SQLITE_BUSY; \ static const int ROW = UppercasePrefix##SQLITE_ROW; \ static const int DONE = UppercasePrefix##SQLITE_DONE; \ + static const int CHECKPOINT_PASSIVE = UppercasePrefix##SQLITE_CHECKPOINT_PASSIVE; \ + static const int CHECKPOINT_FULL = UppercasePrefix##SQLITE_CHECKPOINT_FULL; \ + static const int CHECKPOINT_RESTART = UppercasePrefix##SQLITE_CHECKPOINT_RESTART; \ + static const int CHECKPOINT_TRUNCATE = UppercasePrefix##SQLITE_CHECKPOINT_TRUNCATE; \ \ typedef Prefix##sqlite3 handle; \ typedef Prefix##sqlite3_stmt stmt; \ @@ -74,6 +79,8 @@ 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 wal_checkpoint(handle* arg1, const char* arg2) {return Prefix##sqlite3_wal_checkpoint(arg1, arg2);} \ + static int wal_checkpoint_v2(handle* a1, const char* a2, int a3, int* a4, int* a5) {return Prefix##sqlite3_wal_checkpoint_v2(a1, a2, a3, a4, a5);} \ 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);} \ |
