aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/common
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/common')
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/blockingsocket.cpp97
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/blockingsocket.h44
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/expiringcache.h158
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/private/blockingsocketprivate.cpp130
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/private/blockingsocketprivate.h38
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp55
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/signalwait.cpp17
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/signalwait.h3
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/threadwitheventloop.cpp17
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/threadwitheventloop.h19
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/utils.cpp36
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp69
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h7
13 files changed, 641 insertions, 49 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/blockingsocket.cpp b/SQLiteStudio3/coreSQLiteStudio/common/blockingsocket.cpp
new file mode 100644
index 0000000..da725c8
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/common/blockingsocket.cpp
@@ -0,0 +1,97 @@
+#include "blockingsocket.h"
+#include "common/global.h"
+#include "common/threadwitheventloop.h"
+#include "common/private/blockingsocketprivate.h"
+#include <QMutexLocker>
+
+BlockingSocket::BlockingSocket(QObject* parent) :
+ QObject(parent)
+{
+ socketThread = new ThreadWithEventLoop;
+ socket = new BlockingSocketPrivate;
+ socket->moveToThread(socketThread);
+
+ connect(socketThread, &QThread::finished, socket, &QObject::deleteLater);
+ connect(socketThread, &QThread::finished, socketThread, &QObject::deleteLater);
+ connect(this, SIGNAL(callForSend(QByteArray,bool&)), socket, SLOT(handleSendCall(QByteArray,bool&)), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(callForRead(qint64,int,QByteArray&,bool&)), socket, SLOT(handleReadCall(qint64,int,QByteArray&,bool&)), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(callForConnect(QString,int,bool&)), socket, SLOT(handleConnectCall(QString,int,bool&)), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(callForDisconnect()), socket, SLOT(handleDisconnectCall()), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(callForIsConnected(bool&)), socket, SLOT(handleIsConnectedCall(bool&)), Qt::BlockingQueuedConnection);
+ connect(socket, SIGNAL(disconnected()), this, SIGNAL(disconnected()));
+
+ socketThread->start();
+}
+
+BlockingSocket::~BlockingSocket()
+{
+ QMutexLocker lock(&socketOperationMutex);
+ emit callForDisconnect();
+ socketThread->quit();
+}
+
+QAbstractSocket::SocketError BlockingSocket::getErrorCode()
+{
+ QMutexLocker lock(&socketOperationMutex);
+ return socket->getErrorCode();
+}
+
+QString BlockingSocket::getErrorText()
+{
+ QMutexLocker lock(&socketOperationMutex);
+ return socket->getErrorText();
+}
+
+bool BlockingSocket::connectToHost(const QString& host, int port)
+{
+ QMutexLocker lock(&socketOperationMutex);
+ bool res = false;
+ emit callForConnect(host, port, res);
+ return res;
+}
+
+void BlockingSocket::disconnectFromHost()
+{
+ QMutexLocker lock(&socketOperationMutex);
+ emit callForDisconnect();
+}
+
+bool BlockingSocket::isConnected()
+{
+ QMutexLocker lock(&socketOperationMutex);
+ bool res = false;
+ emit callForIsConnected(res);
+ return res;
+}
+
+bool BlockingSocket::send(const QByteArray& bytes)
+{
+ QMutexLocker lock(&socketOperationMutex);
+ bool res = false;
+ emit callForSend(bytes, res);
+ return res;
+}
+
+QByteArray BlockingSocket::read(qint64 count, int timeout, bool* ok)
+{
+ QMutexLocker lock(&socketOperationMutex);
+ bool res = false;
+ QByteArray bytes;
+ emit callForRead(count, timeout, bytes, res);
+ if (ok)
+ *ok = res;
+
+ return bytes;
+}
+
+void BlockingSocket::quit()
+{
+ QMutexLocker lock(&socketOperationMutex);
+ socketThread->quit();
+}
+
+void BlockingSocket::exit()
+{
+ QMutexLocker lock(&socketOperationMutex);
+ socketThread->quit();
+}
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/blockingsocket.h b/SQLiteStudio3/coreSQLiteStudio/common/blockingsocket.h
new file mode 100644
index 0000000..2a42df7
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/common/blockingsocket.h
@@ -0,0 +1,44 @@
+#ifndef THREADEDSOCKET_H
+#define THREADEDSOCKET_H
+
+#include "coreSQLiteStudio_global.h"
+#include <QObject>
+#include <QAbstractSocket>
+#include <QMutex>
+
+class BlockingSocketPrivate;
+class ThreadWithEventLoop;
+
+class API_EXPORT BlockingSocket : public QObject
+{
+ Q_OBJECT
+
+ public:
+ BlockingSocket(QObject* parent = nullptr);
+ ~BlockingSocket();
+
+ QString getErrorText();
+ QAbstractSocket::SocketError getErrorCode();
+ bool connectToHost(const QString& host, int port);
+ void disconnectFromHost();
+ bool isConnected();
+ bool send(const QByteArray& bytes);
+ QByteArray read(qint64 count, int timeout = 30000, bool* ok = nullptr);
+ void quit();
+ void exit();
+
+ private:
+ ThreadWithEventLoop* socketThread = nullptr;
+ BlockingSocketPrivate* socket = nullptr;
+ QMutex socketOperationMutex;
+
+ signals:
+ void callForConnect(const QString& host, int port, bool& result);
+ void callForDisconnect();
+ void callForIsConnected(bool& connected);
+ void callForSend(const QByteArray& bytes, bool& result);
+ void callForRead(qint64 count, int timeout, QByteArray& resultBytes, bool& result);
+ void disconnected();
+};
+
+#endif // THREADEDSOCKET_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/expiringcache.h b/SQLiteStudio3/coreSQLiteStudio/common/expiringcache.h
new file mode 100644
index 0000000..d9bd483
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/common/expiringcache.h
@@ -0,0 +1,158 @@
+#ifndef EXPIRINGCACHE_H
+#define EXPIRINGCACHE_H
+
+#include <QCache>
+#include <QDateTime>
+#include <QTimer>
+#include <QDebug>
+
+template <class K, class V>
+class ExpiringCache : public QCache<K, V>
+{
+ public:
+ ExpiringCache(int maxCost = 100, int expireMs = 1000);
+ ~ExpiringCache();
+
+ bool insert(const K& key, V* object, int cost = 1);
+ bool contains(const K& key) const;
+ V* object(const K& key, bool noExpireCheck = false) const;
+ V* operator[](const K& key) const;
+ V* take(const K& key);
+ QList<K> keys() const;
+ bool remove(const K& key);
+ int count() const;
+ void clear();
+ bool isEmpty() const;
+ void setExpireTime(int ms);
+
+ private:
+ bool expired(const K& key) const;
+
+ mutable QHash<K, qint64> expires;
+ int expireMs;
+};
+
+template <class K, class V>
+ExpiringCache<K, V>::ExpiringCache(int maxCost, int expireMs) :
+ QCache<K, V>(maxCost), expireMs(expireMs)
+{
+}
+
+template <class K, class V>
+ExpiringCache<K, V>::~ExpiringCache()
+{
+}
+
+template <class K, class V>
+bool ExpiringCache<K, V>::insert(const K& key, V* object, int cost)
+{
+ QList<K> keysBefore = QCache<K, V>::keys();
+ bool result = QCache<K, V>::insert(key, object, cost);
+ if (!result)
+ return false;
+
+ QList<K> keysAfter = QCache<K, V>::keys();
+
+ for (const K& keyBefore : keysBefore)
+ {
+ if (!keysAfter.contains(keyBefore))
+ expires.remove(keyBefore);
+ }
+
+ expires[key] = QDateTime::currentMSecsSinceEpoch() + expireMs;
+ return true;
+}
+
+template <class K, class V>
+bool ExpiringCache<K, V>::contains(const K& key) const
+{
+ if (expired(key))
+ return false;
+
+ return QCache<K, V>::contains(key);
+}
+
+template <class K, class V>
+V* ExpiringCache<K, V>::object(const K& key, bool noExpireCheck) const
+{
+ if (!noExpireCheck && expired(key))
+ return nullptr;
+
+ return QCache<K, V>::object(key);
+}
+
+template <class K, class V>
+V* ExpiringCache<K, V>::operator[](const K& key) const
+{
+ if (expired(key))
+ return nullptr;
+
+ return QCache<K, V>::operator[](key);
+}
+
+template <class K, class V>
+V* ExpiringCache<K, V>::take(const K& key)
+{
+ if (expired(key))
+ return nullptr;
+
+ expires.remove(key);
+ return QCache<K, V>::take(key);
+}
+
+template <class K, class V>
+QList<K> ExpiringCache<K, V>::keys() const
+{
+ QList<K> keyList;
+ for (const K& key : QCache<K, V>::keys())
+ {
+ if (!expired(key))
+ keyList << key;
+ }
+ return keyList;
+}
+
+template <class K, class V>
+bool ExpiringCache<K, V>::remove(const K& key)
+{
+ expires.remove(key);
+ return QCache<K, V>::remove(key);
+}
+
+template <class K, class V>
+int ExpiringCache<K, V>::count() const
+{
+ return keys().count();
+}
+
+template <class K, class V>
+void ExpiringCache<K, V>::clear()
+{
+ expires.clear();
+ QCache<K, V>::clear();
+}
+
+template <class K, class V>
+bool ExpiringCache<K, V>::isEmpty() const
+{
+ return keys().isEmpty();
+}
+
+template <class K, class V>
+void ExpiringCache<K, V>::setExpireTime(int ms)
+{
+ expireMs = ms;
+}
+
+template <class K, class V>
+bool ExpiringCache<K, V>::expired(const K& key) const
+{
+ if (expires.contains(key) && QDateTime::currentMSecsSinceEpoch() > expires[key])
+ {
+ expires.remove(key);
+ return true;
+ }
+ return false;
+}
+
+#endif // EXPIRINGCACHE_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/private/blockingsocketprivate.cpp b/SQLiteStudio3/coreSQLiteStudio/common/private/blockingsocketprivate.cpp
new file mode 100644
index 0000000..a3d696e
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/common/private/blockingsocketprivate.cpp
@@ -0,0 +1,130 @@
+#include "blockingsocketprivate.h"
+#include <QTcpSocket>
+#include <QCoreApplication>
+#include <QTimer>
+#include <QThread>
+
+BlockingSocketPrivate::BlockingSocketPrivate()
+{
+}
+
+BlockingSocketPrivate::~BlockingSocketPrivate()
+{
+}
+
+void BlockingSocketPrivate::setError(QAbstractSocket::SocketError errorCode, const QString& errMsg)
+{
+ this->errorCode = errorCode;
+ this->errorText = errMsg;
+}
+
+bool BlockingSocketPrivate::isConnected()
+{
+ return (socket && socket->isOpen() && socket->state() == QAbstractSocket::ConnectedState);
+}
+
+QAbstractSocket::SocketError BlockingSocketPrivate::getErrorCode()
+{
+ return errorCode;
+}
+
+void BlockingSocketPrivate::createSocketIfNecessary()
+{
+ // This method is called only when the socket is already called,
+ // so we're sure the socket is created in the target thread.
+ if (socket)
+ return;
+
+ socket = new QTcpSocket(this);
+ connect(socket, SIGNAL(disconnected()), this, SIGNAL(disconnected()));
+}
+
+QString BlockingSocketPrivate::getErrorText()
+{
+ return errorText;
+}
+
+void BlockingSocketPrivate::handleSendCall(const QByteArray& bytes, bool& result)
+{
+ createSocketIfNecessary();
+ result = true;
+ qint64 size = bytes.size();
+ qint64 totalBytesSent = 0;
+ qint64 bytesSent = 0;
+ while (totalBytesSent < size)
+ {
+ bytesSent = socket->write(totalBytesSent == 0 ? bytes : bytes.mid(totalBytesSent));
+ if (bytesSent < 0)
+ {
+ result = false;
+ setError(socket->error(), socket->errorString());
+ return;
+ }
+ totalBytesSent += bytesSent;
+ }
+}
+
+void BlockingSocketPrivate::handleReadCall(qint64 count, int timeout, QByteArray& resultBytes, bool& result)
+{
+ createSocketIfNecessary();
+ resultBytes.clear();
+ QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+
+ QTimer timer;
+ timer.setSingleShot(true);
+ timer.setInterval(timeout);
+ timer.start();
+
+ while (resultBytes.size() < count && timer.isActive())
+ {
+ if (!isConnected())
+ {
+ qWarning() << "Blocking socket closed in the middle of reading.";
+ result = false;
+ setError(socket->error(), socket->errorString());
+ return;
+ }
+
+ if (socket->bytesAvailable() == 0)
+ {
+ QThread::msleep(1);
+ QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+ continue;
+ }
+
+ resultBytes += socket->read(qMin(socket->bytesAvailable(), count));
+ }
+
+ result = (resultBytes.size() >= count && timer.isActive());
+}
+
+void BlockingSocketPrivate::handleConnectCall(const QString& host, int port, bool& result)
+{
+ result = true;
+ if (isConnected())
+ return;
+
+ createSocketIfNecessary();
+ socket->connectToHost(host, port);
+ if (!socket->waitForConnected())
+ {
+ result = false;
+ setError(socket->error(), socket->errorString());
+ }
+}
+
+void BlockingSocketPrivate::handleDisconnectCall()
+{
+ if (!isConnected())
+ return;
+
+ createSocketIfNecessary();
+ socket->abort();
+ socket->close();
+
+}
+
+void BlockingSocketPrivate::handleIsConnectedCall(bool& connected)
+{
+ connected = isConnected();
+}
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/private/blockingsocketprivate.h b/SQLiteStudio3/coreSQLiteStudio/common/private/blockingsocketprivate.h
new file mode 100644
index 0000000..8d7b994
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/common/private/blockingsocketprivate.h
@@ -0,0 +1,38 @@
+#ifndef BLOCKINGSOCKETPRIVATE_H
+#define BLOCKINGSOCKETPRIVATE_H
+
+#include <QObject>
+#include <QAbstractSocket>
+
+class BlockingSocketPrivate : public QObject
+{
+ Q_OBJECT
+
+ public:
+ BlockingSocketPrivate();
+ ~BlockingSocketPrivate();
+
+ QString getErrorText();
+ QAbstractSocket::SocketError getErrorCode();
+
+ private:
+ void createSocketIfNecessary();
+ void setError(QAbstractSocket::SocketError errorCode, const QString& errMsg);
+ bool isConnected();
+
+ QAbstractSocket* socket = nullptr;
+ QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError;
+ QString errorText;
+
+ private slots:
+ void handleSendCall(const QByteArray& bytes, bool& result);
+ void handleReadCall(qint64 count, int timeout, QByteArray& resultBytes, bool& result);
+ void handleConnectCall(const QString& host, int port, bool& result);
+ void handleDisconnectCall();
+ void handleIsConnectedCall(bool& connected);
+
+ signals:
+ void disconnected();
+};
+
+#endif // BLOCKINGSOCKETPRIVATE_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp b/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp
index 0eaca75..cdf8110 100644
--- a/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp
+++ b/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp
@@ -1,8 +1,10 @@
#include "readwritelocker.h"
#include "parser/lexer.h"
+#include "common/utils_sql.h"
#include <QReadWriteLock>
#include <QReadLocker>
#include <QWriteLocker>
+#include <QDebug>
ReadWriteLocker::ReadWriteLocker(QReadWriteLock* lock, Mode mode)
{
@@ -47,57 +49,18 @@ void ReadWriteLocker::init(QReadWriteLock* lock, ReadWriteLocker::Mode mode)
ReadWriteLocker::Mode ReadWriteLocker::getMode(const QString &query, Dialect dialect, bool noLock)
{
- static QStringList readOnlyCommands = {"ANALYZE", "EXPLAIN", "PRAGMA"};
-
if (noLock)
return ReadWriteLocker::NONE;
- TokenList tokens = Lexer::tokenize(query, dialect);
- int keywordIdx = tokens.indexOf(Token::KEYWORD);
-
- if (keywordIdx > -1 && readOnlyCommands.contains(tokens[keywordIdx]->value.toUpper()))
- return ReadWriteLocker::READ;
-
- if (keywordIdx > -1 && tokens[keywordIdx]->value.toUpper() == "WITH")
+ QueryAccessMode queryMode = getQueryAccessMode(query, dialect);
+ switch (queryMode)
{
- bool matched = false;
- bool isSelect = false;
- int depth = 0;
- for (TokenPtr token : tokens)
- {
- switch (token->type)
- {
- case Token::PAR_LEFT:
- depth++;
- break;
- case Token::PAR_RIGHT:
- depth--;
- break;
- case Token::KEYWORD:
- if (depth == 0)
- {
- QString val = token->value.toUpper();
- if (val == "SELECT")
- {
- matched = true;
- isSelect = true;
- }
- else if (val == "DELETE" || val == "UPDATE" || val == "INSERT")
- {
- matched = true;
- }
- }
- break;
- default:
- break;
- }
-
- if (matched)
- break;
- }
- if (isSelect)
+ case QueryAccessMode::READ:
return ReadWriteLocker::READ;
+ case QueryAccessMode::WRITE:
+ return ReadWriteLocker::WRITE;
}
- return ReadWriteLocker::WRITE;
+ qCritical() << "Unhandled query access mode:" << static_cast<int>(queryMode);
+ return ReadWriteLocker::NONE;
}
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/signalwait.cpp b/SQLiteStudio3/coreSQLiteStudio/common/signalwait.cpp
index 90f9075..4a22e83 100644
--- a/SQLiteStudio3/coreSQLiteStudio/common/signalwait.cpp
+++ b/SQLiteStudio3/coreSQLiteStudio/common/signalwait.cpp
@@ -12,8 +12,11 @@ bool SignalWait::wait(int msTimeout)
{
QTime timer(0, 0, 0, msTimeout);
timer.start();
- while (!called && timer.elapsed() < msTimeout)
- qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
+ while (!called && !failed && timer.elapsed() < msTimeout)
+ QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+
+ if (failed)
+ return false;
return called;
}
@@ -23,7 +26,17 @@ void SignalWait::reset()
called = false;
}
+void SignalWait::addFailSignal(QObject* object, const char* signal)
+{
+ connect(object, signal, this, SLOT(handleFailSignal()));
+}
+
void SignalWait::handleSignal()
{
called = true;
}
+
+void SignalWait::handleFailSignal()
+{
+ failed = true;
+}
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/signalwait.h b/SQLiteStudio3/coreSQLiteStudio/common/signalwait.h
index 7b7b903..57759be 100644
--- a/SQLiteStudio3/coreSQLiteStudio/common/signalwait.h
+++ b/SQLiteStudio3/coreSQLiteStudio/common/signalwait.h
@@ -12,12 +12,15 @@ class SignalWait : public QObject
bool wait(int msTimeout);
void reset();
+ void addFailSignal(QObject *object, const char *signal);
private:
bool called = false;
+ bool failed = false;
private slots:
void handleSignal();
+ void handleFailSignal();
};
#endif // SIGNALWAIT_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/threadwitheventloop.cpp b/SQLiteStudio3/coreSQLiteStudio/common/threadwitheventloop.cpp
new file mode 100644
index 0000000..c05eb1e
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/common/threadwitheventloop.cpp
@@ -0,0 +1,17 @@
+#include "threadwitheventloop.h"
+#include <QDebug>
+
+ThreadWithEventLoop::ThreadWithEventLoop(QObject* parent) :
+ QThread(parent)
+{
+}
+
+ThreadWithEventLoop::~ThreadWithEventLoop()
+{
+}
+
+void ThreadWithEventLoop::run()
+{
+ exec();
+}
+
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/threadwitheventloop.h b/SQLiteStudio3/coreSQLiteStudio/common/threadwitheventloop.h
new file mode 100644
index 0000000..ef0629a
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/common/threadwitheventloop.h
@@ -0,0 +1,19 @@
+#ifndef THREADWITHEVENTLOOP_H
+#define THREADWITHEVENTLOOP_H
+
+#include <QObject>
+#include <QThread>
+
+class ThreadWithEventLoop : public QThread
+{
+ Q_OBJECT
+
+ public:
+ ThreadWithEventLoop(QObject* parent = nullptr);
+ ~ThreadWithEventLoop();
+
+ protected:
+ void run() Q_DECL_OVERRIDE;
+};
+
+#endif // THREADWITHEVENTLOOP_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils.cpp b/SQLiteStudio3/coreSQLiteStudio/common/utils.cpp
index d56d838..f2b3d1c 100644
--- a/SQLiteStudio3/coreSQLiteStudio/common/utils.cpp
+++ b/SQLiteStudio3/coreSQLiteStudio/common/utils.cpp
@@ -18,6 +18,33 @@
#include <QFileInfo>
#endif
+#ifdef Q_OS_WIN
+#include <windows.h>
+#include <tchar.h>
+
+typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
+bool is64BitWindows()
+{
+#if defined(_WIN64)
+ return true; // 64-bit programs run only on Win64
+#elif defined(_WIN32)
+ // 32-bit programs run on both 32-bit and 64-bit Windows
+ // so must sniff
+ BOOL f64 = false;
+ LPFN_ISWOW64PROCESS fnIsWow64Process;
+
+ fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
+ if (fnIsWow64Process)
+ {
+ return fnIsWow64Process(GetCurrentProcess(), &f64) && f64;
+ }
+ return false;
+#else
+ return true; // Win64 does not support Win16
+#endif
+}
+#endif
+
void initUtils()
{
qRegisterMetaType<QList<int>>("QList<int>");
@@ -735,7 +762,14 @@ QString getOsString()
QString os = "Unknown";
#endif
- os += ", " + QString::number(QSysInfo::WordSize) + "bit";
+ os += ", ";
+#ifdef Q_OS_WIN
+ os += (is64BitWindows() ? "64" : "32");
+#else
+ os += QString::number(QSysInfo::WordSize);
+#endif
+ os += "bit";
+
return os;
}
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp
index fcf49af..20ac736 100644
--- a/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp
+++ b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp
@@ -550,3 +550,72 @@ QString getBindTokenName(const TokenPtr& token)
return token->value.mid(1);
}
+
+QueryAccessMode getQueryAccessMode(const QString& query, Dialect dialect, bool* isSelect)
+{
+ static QStringList readOnlyCommands = {"ANALYZE", "EXPLAIN", "PRAGMA", "SELECT"};
+
+ if (isSelect)
+ *isSelect = false;
+
+ TokenList tokens = Lexer::tokenize(query, dialect);
+ int keywordIdx = tokens.indexOf(Token::KEYWORD);
+
+ int cmdIdx = readOnlyCommands.indexOf(tokens[keywordIdx]->value.toUpper());
+ if (keywordIdx > -1 && cmdIdx > -1)
+ {
+ if (cmdIdx == 3 && isSelect)
+ *isSelect = true;
+
+ return QueryAccessMode::READ;
+ }
+
+ if (keywordIdx > -1 && tokens[keywordIdx]->value.toUpper() == "WITH")
+ {
+ bool matched = false;
+ bool queryIsSelect = false;
+ int depth = 0;
+ for (TokenPtr token : tokens)
+ {
+ switch (token->type)
+ {
+ case Token::PAR_LEFT:
+ depth++;
+ break;
+ case Token::PAR_RIGHT:
+ depth--;
+ break;
+ case Token::KEYWORD:
+ if (depth == 0)
+ {
+ QString val = token->value.toUpper();
+ if (val == "SELECT")
+ {
+ matched = true;
+ queryIsSelect = true;
+ }
+ else if (val == "DELETE" || val == "UPDATE" || val == "INSERT")
+ {
+ matched = true;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (matched)
+ break;
+ }
+
+ if (queryIsSelect)
+ {
+ if (isSelect)
+ *isSelect = true;
+
+ return QueryAccessMode::READ;
+ }
+ }
+
+ return QueryAccessMode::WRITE;
+}
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h
index 8e1f3ff..945b7cc 100644
--- a/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h
+++ b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h
@@ -19,6 +19,12 @@ enum class NameWrapper
null
};
+enum class QueryAccessMode
+{
+ READ,
+ WRITE
+};
+
typedef QPair<QString,QStringList> QueryWithParamNames;
typedef QPair<QString,int> QueryWithParamCount;
@@ -66,6 +72,7 @@ API_EXPORT QueryWithParamCount getQueryWithParamCount(const QString& query, Dial
API_EXPORT QString trimBindParamPrefix(const QString& param);
API_EXPORT QString commentAllSqlLines(const QString& sql);
API_EXPORT QString getBindTokenName(const TokenPtr& token);
+API_EXPORT QueryAccessMode getQueryAccessMode(const QString& query, Dialect dialect, bool* isSelect = nullptr);
#endif // UTILS_SQL_H