diff options
| author | 2015-04-19 22:30:21 -0400 | |
|---|---|---|
| committer | 2015-04-19 22:30:21 -0400 | |
| commit | a308f430f694423064ebc86fd0506c8c6fdb3d93 (patch) | |
| tree | ceacd24fecf92f40980f8d8f3fd169e317c886af /SQLiteStudio3/coreSQLiteStudio | |
| parent | a5b034d4a9c44f9bc1e83b01de82530f8fc63013 (diff) | |
Imported Upstream version 3.0.5upstream/3.0.5
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio')
37 files changed, 1032 insertions, 137 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/ChangeLog.txt b/SQLiteStudio3/coreSQLiteStudio/ChangeLog.txt index f795356..384fd8a 100644 --- a/SQLiteStudio3/coreSQLiteStudio/ChangeLog.txt +++ b/SQLiteStudio3/coreSQLiteStudio/ChangeLog.txt @@ -1,3 +1,18 @@ +[3.0.5] + * [ADDED]: #2831 Ported 'Erase table data' feature (in table's context menu) from version 2.1.5. + * [CHANGE]: Data view has now a 'cover' with progress bar when commiting more changes at once. This eliminates weird GUI freeze when commiting lots of new/deleted rows. + * [CHANGE]: Populate dialog has now progress bar when working. + * [CHANGE]: Enhanced SQL formatter to format CREATE TRIGGER statements in a bit more readable way. + * [BUGFIX]: #2838 Fixed outstanding bug causing database file to be deleted if a user tried "Test connection" on the database under Windows. + * [BUGFIX]: Fixed crash when closing Populate Dialog while populating was in progress. + * [BUGFIX]: Fixed warning messages when there was a problem with restoring windows from last session. A message contains now window name that caused problem. + * [BUGFIX]: #2830 Closing database list made impossible. + * [BUGFIX]: #2829 Fixed support for DEFERRABLE (and related) keywords in foreign keys. + * [BUGFIX]: #2827 Release a file after import. + * [BUGFIX]: #2826 Fixed bug in SQL formatter causing 'expr ISNULL' expressions to disappear. + * [BUGFIX]: #2834 Removed './lib' form rpath for linux non-portable compilation. + * [BUGFIX]: #2836 Removed termcap dependency, as it was deprecated and no longer required. + [3.0.4] * [ADDED]: Import dialog has now "Ignore errors" option, to skip rows that causes problems (constraint violation, wrong column count, etc). * [CHANGE]: Enhanced Copy&Paste capabilities of data grid, so copying and pasting internally in SQLiteStudio will not be limited by TSV format (NULL values will be distinguished, tab characters will be allowed in values), only pasting from outside will be affected by TSV limitations. diff --git a/SQLiteStudio3/coreSQLiteStudio/TODO.txt b/SQLiteStudio3/coreSQLiteStudio/TODO.txt index de84b10..d18c16a 100644 --- a/SQLiteStudio3/coreSQLiteStudio/TODO.txt +++ b/SQLiteStudio3/coreSQLiteStudio/TODO.txt @@ -1,3 +1,12 @@ +* Outstanding features for 3.1: +- BLOB preview engine based on plugins +- ERD plugin +- DB compare plugin +- Foreign Key value suggestions in GridView +- code templates +- executing query with bind params +- migrate updates engine to Qt Install Framework + * Next versions: - object names (columns, tables, etc) in dialogs should be validated against suffix/prefix whitespaces and if they appear, user should be asked for confirmation - small useful features: generating template queries from context menu for table/view, from data view. 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 diff --git a/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro b/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro index 0a7eb60..e21ee8b 100644 --- a/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro +++ b/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro @@ -217,7 +217,10 @@ SOURCES += sqlitestudio.cpp \ rsa/PrimeGenerator.cpp \ rsa/RSA.cpp \ translations.cpp \ - common/signalwait.cpp + common/signalwait.cpp \ + common/blockingsocket.cpp \ + common/threadwitheventloop.cpp \ + common/private/blockingsocketprivate.cpp HEADERS += sqlitestudio.h\ coreSQLiteStudio_global.h \ @@ -405,7 +408,11 @@ HEADERS += sqlitestudio.h\ rsa/PrimeGenerator.h \ rsa/RSA.h \ translations.h \ - common/signalwait.h + common/signalwait.h \ + common/blockingsocket.h \ + common/threadwitheventloop.h \ + common/private/blockingsocketprivate.h \ + common/expiringcache.h unix: { target.path = $$LIBDIR diff --git a/SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp b/SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp index 3672527..7d7d20b 100644 --- a/SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp @@ -35,16 +35,17 @@ QString CsvSerializer::serialize(const QStringList& data, const CsvFormat& forma return outputCells.join(format.columnSeparator); } -QList<QStringList> CsvSerializer::deserialize(const QString& data, const CsvFormat& format) +template <class T> +QList<QList<T>> typedDeserialize(const T& data, const CsvFormat& format) { - QList<QStringList> rows; - QStringList cells; + QList<QList<T>> rows; + QList<T> cells; int pos = 0; int lgt = data.length(); bool quotes = false; bool sepAsLast = false; - QString field = ""; + T field = ""; QChar c; while (pos < lgt) @@ -95,3 +96,81 @@ QList<QStringList> CsvSerializer::deserialize(const QString& data, const CsvForm return rows; } + +QList<QList<QByteArray> > CsvSerializer::deserialize(const QByteArray& data, const CsvFormat& format) +{ + return typedDeserialize<QByteArray>(data, format); +} + +QList<QStringList> CsvSerializer::deserialize(const QString& data, const CsvFormat& format) +{ + QList<QList<QString>> deserialized = typedDeserialize<QString>(data, format); + + QList<QStringList> finalList; + for (const QList<QString>& resPart : deserialized) + finalList << QStringList(resPart); + + return finalList; +} + + +//QList<QStringList> CsvSerializer::deserialize(const QByteArray& data, const CsvFormat& format) +//{ +// QList<QStringList> rows; +// QStringList cells; + +// int pos = 0; +// int lgt = data.length(); +// bool quotes = false; +// bool sepAsLast = false; +// QString field = ""; +// QChar c; + +// while (pos < lgt) +// { +// c = data[pos]; +// sepAsLast = false; +// if (!quotes && c == '"' ) +// { +// quotes = true; +// } +// else if (quotes && c == '"' ) +// { +// if (pos + 1 < data.length() && data[pos+1] == '"' ) +// { +// field += c; +// pos++; +// } +// else +// { +// quotes = false; +// } +// } +// else if (!quotes && format.columnSeparator.contains(c)) +// { +// cells << field; +// field.clear(); +// sepAsLast = true; +// } +// else if (!quotes && format.rowSeparator.contains(c)) +// { +// cells << field; +// rows << cells; +// cells.clear(); +// field.clear(); +// } +// else +// { +// field += c; +// } +// pos++; +// } + +// if (field.size() > 0 || sepAsLast) +// cells << field; + +// if (cells.size() > 0) +// rows << cells; + +// return rows; +//} diff --git a/SQLiteStudio3/coreSQLiteStudio/csvserializer.h b/SQLiteStudio3/coreSQLiteStudio/csvserializer.h index 3217203..1ff6ee9 100644 --- a/SQLiteStudio3/coreSQLiteStudio/csvserializer.h +++ b/SQLiteStudio3/coreSQLiteStudio/csvserializer.h @@ -10,6 +10,7 @@ class API_EXPORT CsvSerializer static QString serialize(const QList<QStringList>& data, const CsvFormat& format); static QString serialize(const QStringList& data, const CsvFormat& format); static QList<QStringList> deserialize(const QString& data, const CsvFormat& format); + static QList<QList<QByteArray>> deserialize(const QByteArray& data, const CsvFormat& format); }; #endif // CSVSERIALIZER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp index 68c6ad7..8c08a51 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp @@ -703,23 +703,6 @@ int AbstractDb::getErrorCode() bool AbstractDb::initAfterCreated() { - bool isOpenBefore = isOpen(); - if (!isOpenBefore) - { - if (!openForProbing()) - { - qWarning() << "Could not open database for initAfterCreated(). Database:" << name; - return false; - } - } - - // SQLite version - QVariant value = exec("SELECT sqlite_version()")->getSingleCell(); - version = value.toString().mid(0, 1).toUInt(); - - if (!isOpenBefore) - closeQuiet(); - return true; } diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h index 9b27dc3..0620a7d 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h @@ -52,6 +52,7 @@ class AbstractDb2 : public AbstractDb int getErrorCodeInternal(); bool openInternal(); bool closeInternal(); + bool initAfterCreated(); void initAfterOpen(); SqlQueryPtr prepare(const QString& query); QString getTypeLabel(); @@ -220,6 +221,13 @@ bool AbstractDb2<T>::closeInternal() } template <class T> +bool AbstractDb2<T>::initAfterCreated() +{ + version = 2; + return AbstractDb::initAfterCreated(); +} + +template <class T> void AbstractDb2<T>::initAfterOpen() { } diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h index 9cb58aa..fe37d5e 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h @@ -51,6 +51,7 @@ class AbstractDb3 : public AbstractDb int getErrorCodeInternal(); bool openInternal(); bool closeInternal(); + bool initAfterCreated(); void initAfterOpen(); SqlQueryPtr prepare(const QString& query); QString getTypeLabel(); @@ -383,6 +384,13 @@ bool AbstractDb3<T>::closeInternal() } template <class T> +bool AbstractDb3<T>::initAfterCreated() +{ + version = 3; + return AbstractDb::initAfterCreated(); +} + +template <class T> void AbstractDb3<T>::initAfterOpen() { T::enable_load_extension(dbHandle, true); diff --git a/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp b/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp index d31d536..676009a 100644 --- a/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp @@ -929,10 +929,10 @@ void DbVersionConverter::fullConvertStep2() for (DbPlugin* plugin : PLUGINS->getLoadedPlugins<DbPlugin>()) { tmpDb = plugin->getInstance("", ":memory:", QHash<QString,QVariant>()); - if (tmpDb->initAfterCreated() && tmpDb->getDialect() == fullConversionConfig->to) + if (tmpDb && tmpDb->initAfterCreated() && tmpDb->getDialect() == fullConversionConfig->to) db = plugin->getInstance(fullConversionConfig->targetName, fullConversionConfig->targetFile, QHash<QString,QVariant>()); - delete tmpDb; + safe_delete(tmpDb); if (db) break; } @@ -1105,7 +1105,7 @@ QList<Db*> DbVersionConverter::getAllPossibleDbInstances() const for (DbPlugin* plugin : PLUGINS->getLoadedPlugins<DbPlugin>()) { db = plugin->getInstance("", ":memory:", QHash<QString,QVariant>()); - if (!db->initAfterCreated()) + if (!db || !db->initAfterCreated()) continue; dbList << db; diff --git a/SQLiteStudio3/coreSQLiteStudio/importworker.cpp b/SQLiteStudio3/coreSQLiteStudio/importworker.cpp index 2cdeeb0..cf62a98 100644 --- a/SQLiteStudio3/coreSQLiteStudio/importworker.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/importworker.cpp @@ -53,6 +53,7 @@ void ImportWorker::run() if (tableCreated) emit createdTable(db, table); + plugin->afterImport(); emit finished(true); } diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp index 454c0e3..d21578e 100644 --- a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp @@ -640,8 +640,8 @@ SqliteCreateTable::Column::Column(const QString &name, SqliteColumnType *type, c this->constraints.last()->type != SqliteCreateTable::Column::Constraint::NAME_ONLY) { SqliteCreateTable::Column::Constraint* last = this->constraints.last(); - last->deferrable = constr->deferrable; - last->initially = constr->initially; + last->foreignKey->deferrable = constr->deferrable; + last->foreignKey->initially = constr->initially; delete constr; // We don't want deleted constr to be added to list. We finish this now. diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h index f3be244..877d0fa 100644 --- a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h @@ -21,6 +21,8 @@ class API_EXPORT SqliteCreateTable : public SqliteQuery public: class API_EXPORT Constraint : public SqliteStatement { + friend class Column; + public: enum Type { @@ -70,11 +72,14 @@ class API_EXPORT SqliteCreateTable : public SqliteQuery QString id; QString collationName = QString::null; SqliteForeignKey* foreignKey = nullptr; - SqliteDeferrable deferrable = SqliteDeferrable::null; - SqliteInitially initially = SqliteInitially::null; protected: TokenList rebuildTokensFromContents(); + + private: + SqliteDeferrable deferrable = SqliteDeferrable::null; // only a temporary field for parse time, before merging with actual FK + SqliteInitially initially = SqliteInitially::null; // only a temporary field for parse time, before merging with actual FK + }; typedef QSharedPointer<Constraint> ConstraintPtr; diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp index 1429cef..12adf80 100644 --- a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp @@ -65,11 +65,12 @@ QString SqliteExpr::likeOp(SqliteExpr::LikeOp value) SqliteExpr::NotNull SqliteExpr::notNullOp(const QString &value) { - if (value == "ISNULL") + QString upper = value.toUpper(); + if (upper == "ISNULL") return SqliteExpr::NotNull::ISNULL; - else if (value == "NOTNULL") + else if (upper == "NOTNULL") return SqliteExpr::NotNull::NOTNULL; - else if (value == "NOT NULL") + else if (upper == "NOT NULL") return SqliteExpr::NotNull::NOT_NULL; else return SqliteExpr::NotNull::null; diff --git a/SQLiteStudio3/coreSQLiteStudio/populateworker.cpp b/SQLiteStudio3/coreSQLiteStudio/populateworker.cpp index 0b08526..f25a6ac 100644 --- a/SQLiteStudio3/coreSQLiteStudio/populateworker.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/populateworker.cpp @@ -46,6 +46,13 @@ void PopulateWorker::run() if (i == 0 && !beforePopulating()) return; + if (isInterrupted()) + { + db->rollback(); + emit finished(false); + return; + } + args.clear(); for (PopulateEngine* engine : engines) args << engine->nextValue(nextValueError); @@ -58,6 +65,8 @@ void PopulateWorker::run() emit finished(false); return; } + + emit finishedStep(i + 1); } if (!db->commit()) diff --git a/SQLiteStudio3/coreSQLiteStudio/populateworker.h b/SQLiteStudio3/coreSQLiteStudio/populateworker.h index f0a39f8..d253a79 100644 --- a/SQLiteStudio3/coreSQLiteStudio/populateworker.h +++ b/SQLiteStudio3/coreSQLiteStudio/populateworker.h @@ -36,6 +36,7 @@ class PopulateWorker : public QObject, public QRunnable signals: void finished(bool result); + void finishedStep(int step); }; #endif // POPULATEWORKER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp index f14d23d..7988748 100644 --- a/SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp @@ -14,6 +14,8 @@ const char* sqliteMasterDdl = const char* sqliteTempMasterDdl = "CREATE TABLE sqlite_temp_master (type text, name text, tbl_name text, rootpage integer, sql text)"; +ExpiringCache<SchemaResolver::ObjectCacheKey,QVariant> SchemaResolver::cache; + SchemaResolver::SchemaResolver(Db *db) : db(db) { @@ -286,12 +288,19 @@ QString SchemaResolver::getObjectDdl(const QString &database, const QString &nam // Prepare db prefix. QString dbName = getPrefixDb(database, dialect); + // Cache + QString typeStr = objectTypeToString(type); + bool useCache = usesCache(); + ObjectCacheKey key(ObjectCacheKey::OBJECT_DDL, db, dbName, lowerName, typeStr); + if (useCache && cache.contains(key)) + return cache.object(key, true)->toString(); + // Get the DDL QVariant results; if (type != ANY) { results = db->exec(QString( - "SELECT sql FROM %1.sqlite_master WHERE lower(name) = '%2' AND type = '%3';").arg(dbName, escapeString(lowerName), objectTypeToString(type)), + "SELECT sql FROM %1.sqlite_master WHERE lower(name) = '%2' AND type = '%3';").arg(dbName, escapeString(lowerName), typeStr), dbFlags )->getSingleCell(); } @@ -317,6 +326,9 @@ QString SchemaResolver::getObjectDdl(const QString &database, const QString &nam if (!resStr.trimmed().endsWith(";")) resStr += ";"; + if (useCache) + cache.insert(key, new QVariant(resStr)); + // Return the DDL return resStr; } @@ -417,6 +429,11 @@ QStringList SchemaResolver::getObjects(const QString &type) QStringList SchemaResolver::getObjects(const QString &database, const QString &type) { + bool useCache = usesCache(); + ObjectCacheKey key(ObjectCacheKey::OBJECT_NAMES, db, database, type); + if (useCache && cache.contains(key)) + return cache.object(key, true)->toStringList(); + QStringList resList; QString dbName = getPrefixDb(database, db->getDialect()); @@ -430,6 +447,9 @@ QStringList SchemaResolver::getObjects(const QString &database, const QString &t resList << value; } + if (useCache) + cache.insert(key, new QVariant(resList)); + return resList; } @@ -440,6 +460,11 @@ QStringList SchemaResolver::getAllObjects() QStringList SchemaResolver::getAllObjects(const QString& database) { + bool useCache = usesCache(); + ObjectCacheKey key(ObjectCacheKey::OBJECT_NAMES, db, database); + if (useCache && cache.contains(key)) + return cache.object(key, true)->toStringList(); + QStringList resList; QString dbName = getPrefixDb(database, db->getDialect()); @@ -455,6 +480,9 @@ QStringList SchemaResolver::getAllObjects(const QString& database) resList << value; } + if (useCache) + cache.insert(key, new QVariant(resList)); + return resList; } @@ -605,25 +633,42 @@ StrHash<SchemaResolver::ObjectDetails> SchemaResolver::getAllObjectDetails(const ObjectDetails detail; QString type; - SqlQueryPtr results = db->exec(QString("SELECT name, type, sql FROM %1.sqlite_master").arg(getPrefixDb(database, db->getDialect())), dbFlags); - if (results->isError()) + QList<QVariant> rows; + bool useCache = usesCache(); + ObjectCacheKey key(ObjectCacheKey::OBJECT_DETAILS, db, database); + if (useCache && cache.contains(key)) + { + rows = cache.object(key, true)->toList(); + } + else { - qCritical() << "Error while getting all object details in SchemaResolver:" << results->getErrorCode(); - return details; + SqlQueryPtr results = db->exec(QString("SELECT name, type, sql FROM %1.sqlite_master").arg(getPrefixDb(database, db->getDialect())), dbFlags); + if (results->isError()) + { + qCritical() << "Error while getting all object details in SchemaResolver:" << results->getErrorCode(); + return details; + } + + for (const SqlResultsRowPtr& row : results->getAll()) + rows << row->valueMap(); + + if (useCache) + cache.insert(key, new QVariant(rows)); } - SqlResultsRowPtr row; - while (results->hasNext()) + QHash<QString, QVariant> row; + for (const QVariant& rowVariant : rows) { - row = results->next(); - type = row->value("type").toString(); + row = rowVariant.toHash(); + type = row["type"].toString(); detail.type = stringToObjectType(type); if (detail.type == ANY) qCritical() << "Unhlandled db object type:" << type; - detail.ddl = row->value("sql").toString(); - details[row->value("name").toString()] = detail; + detail.ddl = row["sql"].toString(); + details[row["name"].toString()] = detail; } + return details; } @@ -747,6 +792,16 @@ SchemaResolver::ObjectType SchemaResolver::stringToObjectType(const QString& typ return SchemaResolver::ANY; } +void SchemaResolver::staticInit() +{ + cache.setExpireTime(3000); +} + +bool SchemaResolver::usesCache() +{ + return db->getConnectionOptions().contains(USE_SCHEMA_CACHING) && db->getConnectionOptions()[USE_SCHEMA_CACHING].toBool(); +} + QList<SqliteCreateViewPtr> SchemaResolver::getParsedViewsForTable(const QString& database, const QString& table) { QList<SqliteCreateViewPtr> createViewList; @@ -909,3 +964,18 @@ void SchemaResolver::setNoDbLocking(bool value) dbFlags ^= Db::Flag::NO_LOCK; } + +SchemaResolver::ObjectCacheKey::ObjectCacheKey(Type type, Db* db, const QString& value1, const QString& value2, const QString& value3) : + type(type), db(db), value1(value1), value2(value2), value3(value3) +{ +} + +int qHash(const SchemaResolver::ObjectCacheKey& key) +{ + return qHash(key.type) ^ qHash(key.db) ^ qHash(key.value1) ^ qHash(key.value2) ^ qHash(key.value3); +} + +int operator==(const SchemaResolver::ObjectCacheKey& k1, const SchemaResolver::ObjectCacheKey& k2) +{ + return (k1.type == k2.type && k1.db == k2.db && k1.value1 == k2.value1 && k1.value2 == k2.value2 && k1.value3 == k2.value3); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/schemaresolver.h b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.h index 5316f5a..5d325d8 100644 --- a/SQLiteStudio3/coreSQLiteStudio/schemaresolver.h +++ b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.h @@ -14,6 +14,7 @@ #include "db/sqlquery.h" #include "db/db.h" #include "common/strhash.h" +#include "common/expiringcache.h" #include <QStringList> class SqliteCreateTable; @@ -38,6 +39,24 @@ class API_EXPORT SchemaResolver QString ddl; }; + struct ObjectCacheKey + { + enum Type + { + OBJECT_NAMES, + OBJECT_DETAILS, + OBJECT_DDL + }; + + ObjectCacheKey(Type type, Db* db, const QString& value1 = QString(), const QString& value2 = QString(), const QString& value3 = QString()); + + Type type; + Db* db; + QString value1; + QString value2; + QString value3; + }; + explicit SchemaResolver(Db* db); virtual ~SchemaResolver(); @@ -164,8 +183,12 @@ class API_EXPORT SchemaResolver static QString objectTypeToString(ObjectType type); static ObjectType stringToObjectType(const QString& type); + static void staticInit(); + + static_char* USE_SCHEMA_CACHING = "useSchemaCaching"; private: + bool usesCache(); SqliteQueryPtr getParsedDdl(const QString& ddl); SqliteCreateTablePtr virtualTableAsRegularTable(const QString& database, const QString& table); StrHash< QStringList> getGroupedObjects(const QString &database, const QStringList& inputList, SqliteQueryType type); @@ -180,8 +203,13 @@ class API_EXPORT SchemaResolver Parser* parser = nullptr; bool ignoreSystemObjects = false; Db::Flags dbFlags; + + static ExpiringCache<ObjectCacheKey,QVariant> cache; }; +int qHash(const SchemaResolver::ObjectCacheKey& key); +int operator==(const SchemaResolver::ObjectCacheKey& k1, const SchemaResolver::ObjectCacheKey& k2); + template <class T> StrHash<QSharedPointer<T>> SchemaResolver::getAllParsedObjectsForType(const QString& database, const QString& type) { diff --git a/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h index 5a56151..66bbf08 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h +++ b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h @@ -189,6 +189,8 @@ class API_EXPORT DbManager : public QObject */ virtual void notifyDatabasesAreLoaded() = 0; + virtual void rescanInvalidDatabasesForPlugin(DbPlugin* dbPlugin) = 0; + signals: /** * @brief Application just connected to the database. diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp index e96e181..a7bff0d 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp @@ -81,8 +81,12 @@ bool DbManagerImpl::updateDb(Db* db, const QString &name, const QString &path, c return false; } - QDir pathDir(path); - QString normalizedPath = pathDir.absolutePath(); + QString normalizedPath; + QUrl url(path); + if (url.scheme().isEmpty() || url.scheme() == "file") + normalizedPath = QDir(path).absolutePath(); + else + normalizedPath = path; listLock.lockForWrite(); nameToDb.remove(db->getName(), Qt::CaseInsensitive); @@ -343,6 +347,66 @@ void DbManagerImpl::scanForNewDatabasesInConfig() } } +void DbManagerImpl::rescanInvalidDatabasesForPlugin(DbPlugin* dbPlugin) +{ + if (!dbPlugin) + { + qWarning() << "Call to DbManagerImpl::rescanInvalidDatabasesForPlugin() with null plugin."; + return; + } + + Db* db = nullptr; + + QUrl url; + QString errorMessages; + for (Db* invalidDb : getInvalidDatabases()) + { + if (invalidDb->getConnectionOptions().contains(DB_PLUGIN) && invalidDb->getConnectionOptions()[DB_PLUGIN].toString() != dbPlugin->getName()) + continue; + + url = QUrl::fromUserInput(invalidDb->getPath()); + if (url.isLocalFile() && !QFile::exists(invalidDb->getPath())) + continue; + + errorMessages = QString(); + db = createDb(invalidDb->getName(), invalidDb->getPath(), invalidDb->getConnectionOptions(), &errorMessages); + if (!db) + { + if (!errorMessages.isNull()) + { + dynamic_cast<InvalidDb*>(invalidDb)->setError(errorMessages); + } + continue; // For this db driver was not loaded yet. + } + + if (!dbPlugin->checkIfDbServedByPlugin(db)) + { + qDebug() << "Managed to load database" << db->getPath() << " (" << db->getName() << ")" + << "but it doesn't use DbPlugin that was just loaded, so it will not be loaded to the db manager"; + + delete db; + continue; + } + + removeDbInternal(invalidDb, false); + delete invalidDb; + + addDbInternal(db, false); + + if (!db->getConnectionOptions().contains(DB_PLUGIN)) + { + db->getConnectionOptions()[DB_PLUGIN] = dbPlugin->getName(); + if (!CFG->updateDb(db->getName(), db->getName(), db->getPath(), db->getConnectionOptions())) + qWarning() << "Could not store handling plugin in options for database" << db->getName(); + } + + if (CFG->getDbGroup(db->getName())->open) + db->open(); + + emit dbLoaded(db); + } +} + void DbManagerImpl::addDbInternal(Db* db, bool alsoToConfig) { if (alsoToConfig) @@ -391,17 +455,23 @@ Db* DbManagerImpl::tryToLoadDb(InvalidDb* invalidDb, bool emitNotifySignal) Db* DbManagerImpl::createDb(const QString &name, const QString &path, const QHash<QString,QVariant> &options, QString* errorMessages) { QList<DbPlugin*> dbPlugins = PLUGINS->getLoadedPlugins<DbPlugin>(); - DbPlugin* dbPlugin = nullptr; Db* db = nullptr; QStringList messages; QString message; - QDir pathDir(path); // Using QDir to normalize separator - foreach (dbPlugin, dbPlugins) + + QString normalizedPath; + QUrl url(path); + if (url.scheme().isEmpty() || url.scheme() == "file") + normalizedPath = QDir(path).absolutePath(); + else + normalizedPath = path; + + for (DbPlugin* dbPlugin : dbPlugins) { if (options.contains("plugin") && options["plugin"] != dbPlugin->getName()) continue; - db = dbPlugin->getInstance(name, pathDir.absolutePath(), options, &message); + db = dbPlugin->getInstance(name, normalizedPath, options, &message); if (!db) { messages << message; @@ -410,6 +480,7 @@ Db* DbManagerImpl::createDb(const QString &name, const QString &path, const QHas if (!db->initAfterCreated()) { + safe_delete(db); messages << tr("Database could not be initialized."); continue; } @@ -506,46 +577,47 @@ void DbManagerImpl::loaded(Plugin* plugin, PluginType* type) return; DbPlugin* dbPlugin = dynamic_cast<DbPlugin*>(plugin); - Db* db = nullptr; + rescanInvalidDatabasesForPlugin(dbPlugin); +// Db* db = nullptr; - QUrl url; - for (Db* invalidDb : getInvalidDatabases()) - { - if (invalidDb->getConnectionOptions().contains(DB_PLUGIN) && invalidDb->getConnectionOptions()[DB_PLUGIN].toString() != dbPlugin->getName()) - continue; +// QUrl url; +// for (Db* invalidDb : getInvalidDatabases()) +// { +// if (invalidDb->getConnectionOptions().contains(DB_PLUGIN) && invalidDb->getConnectionOptions()[DB_PLUGIN].toString() != dbPlugin->getName()) +// continue; - url = QUrl::fromUserInput(invalidDb->getPath()); - if (url.isLocalFile() && !QFile::exists(invalidDb->getPath())) - continue; +// url = QUrl::fromUserInput(invalidDb->getPath()); +// if (url.isLocalFile() && !QFile::exists(invalidDb->getPath())) +// continue; - db = createDb(invalidDb->getName(), invalidDb->getPath(), invalidDb->getConnectionOptions()); - if (!db) - continue; // For this db driver was not loaded yet. +// db = createDb(invalidDb->getName(), invalidDb->getPath(), invalidDb->getConnectionOptions()); +// if (!db) +// continue; // For this db driver was not loaded yet. - if (!dbPlugin->checkIfDbServedByPlugin(db)) - { - qDebug() << "Managed to load database" << db->getPath() << " (" << db->getName() << ")" - << "but it doesn't use DbPlugin that was just loaded, so it will not be loaded to the db manager"; +// if (!dbPlugin->checkIfDbServedByPlugin(db)) +// { +// qDebug() << "Managed to load database" << db->getPath() << " (" << db->getName() << ")" +// << "but it doesn't use DbPlugin that was just loaded, so it will not be loaded to the db manager"; - delete db; - continue; - } +// delete db; +// continue; +// } - removeDbInternal(invalidDb, false); - delete invalidDb; +// removeDbInternal(invalidDb, false); +// delete invalidDb; - addDbInternal(db, false); +// addDbInternal(db, false); - if (!db->getConnectionOptions().contains(DB_PLUGIN)) - { - db->getConnectionOptions()[DB_PLUGIN] = dbPlugin->getName(); - if (!CFG->updateDb(db->getName(), db->getName(), db->getPath(), db->getConnectionOptions())) - qWarning() << "Could not store handling plugin in options for database" << db->getName(); - } +// if (!db->getConnectionOptions().contains(DB_PLUGIN)) +// { +// db->getConnectionOptions()[DB_PLUGIN] = dbPlugin->getName(); +// if (!CFG->updateDb(db->getName(), db->getName(), db->getPath(), db->getConnectionOptions())) +// qWarning() << "Could not store handling plugin in options for database" << db->getName(); +// } - if (CFG->getDbGroup(db->getName())->open) - db->open(); +// if (CFG->getDbGroup(db->getName())->open) +// db->open(); - emit dbLoaded(db); - } +// emit dbLoaded(db); +// } } diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h index 5797ac6..67a1335 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h @@ -178,6 +178,7 @@ class API_EXPORT DbManagerImpl : public DbManager public slots: void notifyDatabasesAreLoaded(); void scanForNewDatabasesInConfig(); + void rescanInvalidDatabasesForPlugin(DbPlugin* dbPlugin); }; #endif // DBMANAGERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp index 237667d..02ba549 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp @@ -55,6 +55,7 @@ void PopulateManager::populate(Db* db, const QString& table, const QHash<QString PopulateWorker* worker = new PopulateWorker(db, table, columns, engineList, rows); connect(worker, SIGNAL(finished(bool)), this, SLOT(finalizePopulating(bool))); + connect(worker, SIGNAL(finishedStep(int)), this, SIGNAL(finishedStep(int))); connect(this, SIGNAL(orderWorkerToInterrupt()), worker, SLOT(interrupt())); QThreadPool::globalInstance()->start(worker); diff --git a/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h index 05b1f82..b445a9a 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h +++ b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h @@ -41,6 +41,7 @@ class API_EXPORT PopulateManager : public PluginServiceBase void populatingSuccessful(); void populatingFailed(); void orderWorkerToInterrupt(); + void finishedStep(int step); }; #define POPULATE_MANAGER SQLITESTUDIO->getPopulateManager() diff --git a/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp index 361b2e4..963ba7d 100644 --- a/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp @@ -39,7 +39,7 @@ DEFINE_SINGLETON(SQLiteStudio) -static const int sqlitestudioVersion = 30004; +static const int sqlitestudioVersion = 30005; SQLiteStudio::SQLiteStudio() { @@ -266,6 +266,7 @@ void SQLiteStudio::init(const QStringList& cmdListArguments, bool guiAvailable) CfgMain::staticInit(); Db::metaInit(); initUtilsSql(); + SchemaResolver::staticInit(); initKeywords(); Lexer::staticInit(); CompletionHelper::init(); diff --git a/SQLiteStudio3/coreSQLiteStudio/translations/coreSQLiteStudio_sk.ts b/SQLiteStudio3/coreSQLiteStudio/translations/coreSQLiteStudio_sk.ts index 24da18a..74fb84c 100644 --- a/SQLiteStudio3/coreSQLiteStudio/translations/coreSQLiteStudio_sk.ts +++ b/SQLiteStudio3/coreSQLiteStudio/translations/coreSQLiteStudio_sk.ts @@ -7,7 +7,7 @@ <location filename="../db/abstractdb.cpp" line="306"/> <location filename="../db/abstractdb.cpp" line="323"/> <source>Cannot execute query on closed database.</source> - <translation type="unfinished"></translation> + <translation>Nemôžem spustiť dotaz na uzatvorenej databáze.</translation> </message> <message> <location filename="../db/abstractdb.cpp" line="603"/> @@ -466,22 +466,22 @@ Tables, indexes, triggers and views copied to database %3 will remain.</source> <message> <location filename="../services/impl/pluginmanagerimpl.cpp" line="541"/> <source>Cannot load plugin %1, because it's in conflict with plugin %2.</source> - <translation type="unfinished"></translation> + <translation>Nemôžem načítať plugin %1, pretože je v konflikte s pluginom %2.</translation> </message> <message> <location filename="../services/impl/pluginmanagerimpl.cpp" line="552"/> <source>Cannot load plugin %1, because its dependency was not loaded: %2.</source> - <translation type="unfinished"></translation> + <translation>Nemôžem načítať plugin %1, pretože neboli načítané jeho závislosti %2.</translation> </message> <message> <location filename="../services/impl/pluginmanagerimpl.cpp" line="561"/> <source>Cannot load plugin %1. Error details: %2</source> - <translation type="unfinished"></translation> + <translation>Nemôžem načítať plugin %1. Detaily chyby %2</translation> </message> <message> <location filename="../services/impl/pluginmanagerimpl.cpp" line="577"/> <source>Cannot load plugin %1 (error while initializing plugin).</source> - <translation type="unfinished"></translation> + <translation>Nemôžem načítať plugin %1 (nastala chyba pri jeho inicializácii).</translation> </message> <message> <location filename="../services/impl/pluginmanagerimpl.cpp" line="730"/> |
