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