aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/db
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/db')
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp81
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h35
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h70
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/db.cpp2
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/db.h38
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp10
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h4
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp22
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h12
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp105
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h31
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp37
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h2
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp132
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h62
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp35
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumntype.cpp47
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumntype.h6
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp3
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorfilter.cpp26
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorfilter.h20
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp1
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp75
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h10
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp3
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h1
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp81
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h8
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h7
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);} \