diff options
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/common')
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 |
