From a308f430f694423064ebc86fd0506c8c6fdb3d93 Mon Sep 17 00:00:00 2001 From: Unit 193 Date: Sun, 19 Apr 2015 22:30:21 -0400 Subject: Imported Upstream version 3.0.5 --- Plugins/DbSqlite2/dbsqlite2.json | 2 +- .../SqlEnterpriseFormatter/formatcreatetrigger.cpp | 60 +++++--- .../SqlEnterpriseFormatter/formatcreatetrigger.h | 7 + Plugins/SqlEnterpriseFormatter/formatexpr.cpp | 1 + Plugins/SqlEnterpriseFormatter/formatstatement.cpp | 2 +- .../sqlenterpriseformatter.json | 2 +- SQLiteStudio3/coreSQLiteStudio/ChangeLog.txt | 15 ++ SQLiteStudio3/coreSQLiteStudio/TODO.txt | 9 ++ .../coreSQLiteStudio/common/blockingsocket.cpp | 97 +++++++++++++ .../coreSQLiteStudio/common/blockingsocket.h | 44 ++++++ .../coreSQLiteStudio/common/expiringcache.h | 158 +++++++++++++++++++++ .../common/private/blockingsocketprivate.cpp | 130 +++++++++++++++++ .../common/private/blockingsocketprivate.h | 38 +++++ .../coreSQLiteStudio/common/readwritelocker.cpp | 55 ++----- .../coreSQLiteStudio/common/signalwait.cpp | 17 ++- SQLiteStudio3/coreSQLiteStudio/common/signalwait.h | 3 + .../common/threadwitheventloop.cpp | 17 +++ .../coreSQLiteStudio/common/threadwitheventloop.h | 19 +++ SQLiteStudio3/coreSQLiteStudio/common/utils.cpp | 36 ++++- .../coreSQLiteStudio/common/utils_sql.cpp | 69 +++++++++ SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h | 7 + .../coreSQLiteStudio/coreSQLiteStudio.pro | 11 +- SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp | 87 +++++++++++- SQLiteStudio3/coreSQLiteStudio/csvserializer.h | 1 + SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp | 17 --- SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h | 8 ++ SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h | 8 ++ .../coreSQLiteStudio/dbversionconverter.cpp | 6 +- SQLiteStudio3/coreSQLiteStudio/importworker.cpp | 1 + .../parser/ast/sqlitecreatetable.cpp | 4 +- .../parser/ast/sqlitecreatetable.h | 9 +- .../coreSQLiteStudio/parser/ast/sqliteexpr.cpp | 7 +- SQLiteStudio3/coreSQLiteStudio/populateworker.cpp | 9 ++ SQLiteStudio3/coreSQLiteStudio/populateworker.h | 1 + SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp | 92 ++++++++++-- SQLiteStudio3/coreSQLiteStudio/schemaresolver.h | 28 ++++ .../coreSQLiteStudio/services/dbmanager.h | 2 + .../services/impl/dbmanagerimpl.cpp | 148 ++++++++++++++----- .../coreSQLiteStudio/services/impl/dbmanagerimpl.h | 1 + .../coreSQLiteStudio/services/populatemanager.cpp | 1 + .../coreSQLiteStudio/services/populatemanager.h | 1 + SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp | 3 +- .../translations/coreSQLiteStudio_sk.ts | 10 +- SQLiteStudio3/guiSQLiteStudio/common/ipvalidator.h | 3 +- .../guiSQLiteStudio/common/widgetcover.cpp | 36 +++++ SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h | 8 +- .../guiSQLiteStudio/datagrid/sqlquerymodel.cpp | 20 ++- .../guiSQLiteStudio/datagrid/sqlquerymodel.h | 6 + SQLiteStudio3/guiSQLiteStudio/dataview.cpp | 39 +++++ SQLiteStudio3/guiSQLiteStudio/dataview.h | 6 + SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp | 38 ++++- SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h | 3 + .../guiSQLiteStudio/dbtree/dbtreemodel.cpp | 29 +++- SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp | 22 +-- .../guiSQLiteStudio/dialogs/populatedialog.cpp | 13 ++ .../guiSQLiteStudio/dialogs/populatedialog.h | 2 + SQLiteStudio3/guiSQLiteStudio/iconmanager.h | 2 + SQLiteStudio3/guiSQLiteStudio/icons.qrc | 2 + SQLiteStudio3/guiSQLiteStudio/img/delete.png | Bin 0 -> 729 bytes .../guiSQLiteStudio/img/erase_table_data.png | Bin 0 -> 885 bytes .../translations/guiSQLiteStudio_sk.ts | 86 +++++------ SQLiteStudio3/guiSQLiteStudio/uiconfig.h | 6 +- SQLiteStudio3/guiSQLiteStudio/uidebug.cpp | 8 ++ SQLiteStudio3/guiSQLiteStudio/uidebug.h | 2 + .../guiSQLiteStudio/windows/tablewindow.cpp | 8 +- .../guiSQLiteStudio/windows/viewwindow.cpp | 10 +- SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro | 8 +- 67 files changed, 1359 insertions(+), 241 deletions(-) create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/blockingsocket.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/blockingsocket.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/expiringcache.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/private/blockingsocketprivate.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/private/blockingsocketprivate.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/threadwitheventloop.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/threadwitheventloop.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/delete.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/erase_table_data.png diff --git a/Plugins/DbSqlite2/dbsqlite2.json b/Plugins/DbSqlite2/dbsqlite2.json index 6761faf..c1e1817 100644 --- a/Plugins/DbSqlite2/dbsqlite2.json +++ b/Plugins/DbSqlite2/dbsqlite2.json @@ -2,6 +2,6 @@ "type": "DbPlugin", "title": "SQLite 2", "description": "Provides support for SQLite 2.* databases", - "version": 10002, + "version": 10003, "author": "SalSoft" } diff --git a/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.cpp b/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.cpp index 01351e6..6eb8b21 100644 --- a/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.cpp +++ b/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.cpp @@ -9,55 +9,71 @@ FormatCreateTrigger::FormatCreateTrigger(SqliteCreateTrigger* createTrig) : void FormatCreateTrigger::formatInternal() { handleExplainQuery(createTrig); - withKeyword("CREATE"); + + QStringList keywords; + + keywords << "CREATE"; if (createTrig->tempKw) - withKeyword("TEMP"); + keywords << "TEMP"; else if (createTrig->temporaryKw) - withKeyword("TEMPORARY"); + keywords << "TEMPORARY"; - withKeyword("TRIGGER"); + keywords << "TRIGGER"; if (createTrig->ifNotExistsKw) - withKeyword("IF").withKeyword("NOT").withKeyword("EXISTS"); + keywords << "IF" << "NOT" << "EXISTS"; + + QString kwLineUp = keywords.join(" "); + markKeywordLineUp(kwLineUp, TRIGGER_MARK); + + for (const QString& kw : keywords) + withKeyword(kw); if (dialect == Dialect::Sqlite3 && !createTrig->database.isNull()) withId(createTrig->database).withIdDot(); - withId(createTrig->trigger); + withId(createTrig->trigger).withNewLine(); + + FormatStatementEnricher eventStmtEnricher = nullptr; switch (createTrig->eventTime) { case SqliteCreateTrigger::Time::BEFORE: - withKeyword("BEFORE"); + withLinedUpKeyword("BEFORE", TRIGGER_MARK); break; case SqliteCreateTrigger::Time::AFTER: - withKeyword("AFTER"); + withLinedUpKeyword("AFTER", TRIGGER_MARK); break; case SqliteCreateTrigger::Time::INSTEAD_OF: - withKeyword("INSTEAD").withKeyword("OF"); + withLinedUpKeyword("INSTEAD OF", TRIGGER_MARK); break; case SqliteCreateTrigger::Time::null: + eventStmtEnricher = [kwLineUp](FormatStatement* stmt) + { + dynamic_cast(stmt)->setLineUpKeyword(kwLineUp); + }; break; } - withStatement(createTrig->event).withKeyword("ON"); + withStatement(createTrig->event, QString(), eventStmtEnricher).withNewLine(); + withLinedUpKeyword("ON", TRIGGER_MARK); if (dialect == Dialect::Sqlite2 && !createTrig->database.isNull()) withId(createTrig->database).withIdDot(); - withId(createTrig->table); + withId(createTrig->table).withNewLine(); switch (createTrig->scope) { case SqliteCreateTrigger::Scope::FOR_EACH_ROW: - withKeyword("FOR").withKeyword("EACH").withKeyword("ROW"); + withLinedUpKeyword("FOR EACH", TRIGGER_MARK).withKeyword("ROW").withNewLine(); break; case SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT: - withKeyword("FOR").withKeyword("EACH").withKeyword("STATEMENT"); + withLinedUpKeyword("FOR EACH", TRIGGER_MARK).withKeyword("STATEMENT").withNewLine(); break; case SqliteCreateTrigger::Scope::null: break; } if (createTrig->precondition) - withKeyword("WHEN").withStatement(createTrig->precondition); + withLinedUpKeyword("WHEN", TRIGGER_MARK).withStatement(createTrig->precondition); withNewLine().withKeyword("BEGIN").withNewLine().withIncrIndent().withStatementList(createTrig->queries, QString(), ListSeparator::SEMICOLON).withSemicolon(); withDecrIndent().withKeyword("END").withSemicolon(); @@ -69,21 +85,29 @@ FormatCreateTriggerEvent::FormatCreateTriggerEvent(SqliteCreateTrigger::Event* e { } +void FormatCreateTriggerEvent::setLineUpKeyword(const QString& lineUpKw) +{ + this->lineUpKw = lineUpKw; +} + void FormatCreateTriggerEvent::formatInternal() { + if (!lineUpKw.isNull()) + markKeywordLineUp(lineUpKw, TRIGGER_MARK); + switch (ev->type) { case SqliteCreateTrigger::Event::INSERT: - withKeyword("INSERT"); + withLinedUpKeyword("INSERT", TRIGGER_MARK); break; case SqliteCreateTrigger::Event::UPDATE: - withKeyword("UPDATE"); + withLinedUpKeyword("UPDATE", TRIGGER_MARK); break; case SqliteCreateTrigger::Event::DELETE: - withKeyword("DELETE"); + withLinedUpKeyword("DELETE", TRIGGER_MARK); break; case SqliteCreateTrigger::Event::UPDATE_OF: - withKeyword("UPDATE").withKeyword("OF").withIdList(ev->columnNames); + withLinedUpKeyword("UPDATE OF", TRIGGER_MARK).withIdList(ev->columnNames); break; case SqliteCreateTrigger::Event::null: break; diff --git a/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.h b/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.h index 795c2c7..108a31e 100644 --- a/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.h +++ b/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.h @@ -3,6 +3,7 @@ #include "formatstatement.h" #include "parser/ast/sqlitecreatetrigger.h" +#include "common/global.h" class FormatCreateTrigger : public FormatStatement { @@ -14,18 +15,24 @@ class FormatCreateTrigger : public FormatStatement private: SqliteCreateTrigger* createTrig = nullptr; + + static_char* TRIGGER_MARK = "TRIGGER"; }; class FormatCreateTriggerEvent : public FormatStatement { public: FormatCreateTriggerEvent(SqliteCreateTrigger::Event* ev); + void setLineUpKeyword(const QString& lineUpKw); protected: void formatInternal(); private: SqliteCreateTrigger::Event* ev = nullptr; + QString lineUpKw; + + static_char* TRIGGER_MARK = "TRIGGER"; }; #endif // FORMATCREATETRIGGER_H diff --git a/Plugins/SqlEnterpriseFormatter/formatexpr.cpp b/Plugins/SqlEnterpriseFormatter/formatexpr.cpp index da51ae6..ec5d4d6 100644 --- a/Plugins/SqlEnterpriseFormatter/formatexpr.cpp +++ b/Plugins/SqlEnterpriseFormatter/formatexpr.cpp @@ -107,6 +107,7 @@ void FormatExpr::formatInternal() break; case SqliteExpr::Mode::NOTNULL: { + withStatement(expr->expr1); switch (expr->notNull) { case SqliteExpr::NotNull::ISNULL: diff --git a/Plugins/SqlEnterpriseFormatter/formatstatement.cpp b/Plugins/SqlEnterpriseFormatter/formatstatement.cpp index dfdbb14..e5eaed4 100644 --- a/Plugins/SqlEnterpriseFormatter/formatstatement.cpp +++ b/Plugins/SqlEnterpriseFormatter/formatstatement.cpp @@ -459,7 +459,7 @@ void FormatStatement::handleExplainQuery(SqliteQuery* query) { withKeyword("EXPLAIN"); if (query->queryPlan) - withKeyword("QUERY").withKeyword("PLAN"); + withKeyword("QUERY").withKeyword("PLAN").withNewLine(); } } diff --git a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.json b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.json index 85d6414..c0ba2df 100644 --- a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.json +++ b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.json @@ -2,6 +2,6 @@ "type": "CodeFormatterPlugin", "title": "SQL Enterprise", "description": "Advanced SQL formatter.", - "version": 10005, + "version": 10006, "author": "SalSoft" } 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 + +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 +#include +#include + +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 +#include +#include +#include + +template +class ExpiringCache : public QCache +{ + 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 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 expires; + int expireMs; +}; + +template +ExpiringCache::ExpiringCache(int maxCost, int expireMs) : + QCache(maxCost), expireMs(expireMs) +{ +} + +template +ExpiringCache::~ExpiringCache() +{ +} + +template +bool ExpiringCache::insert(const K& key, V* object, int cost) +{ + QList keysBefore = QCache::keys(); + bool result = QCache::insert(key, object, cost); + if (!result) + return false; + + QList keysAfter = QCache::keys(); + + for (const K& keyBefore : keysBefore) + { + if (!keysAfter.contains(keyBefore)) + expires.remove(keyBefore); + } + + expires[key] = QDateTime::currentMSecsSinceEpoch() + expireMs; + return true; +} + +template +bool ExpiringCache::contains(const K& key) const +{ + if (expired(key)) + return false; + + return QCache::contains(key); +} + +template +V* ExpiringCache::object(const K& key, bool noExpireCheck) const +{ + if (!noExpireCheck && expired(key)) + return nullptr; + + return QCache::object(key); +} + +template +V* ExpiringCache::operator[](const K& key) const +{ + if (expired(key)) + return nullptr; + + return QCache::operator[](key); +} + +template +V* ExpiringCache::take(const K& key) +{ + if (expired(key)) + return nullptr; + + expires.remove(key); + return QCache::take(key); +} + +template +QList ExpiringCache::keys() const +{ + QList keyList; + for (const K& key : QCache::keys()) + { + if (!expired(key)) + keyList << key; + } + return keyList; +} + +template +bool ExpiringCache::remove(const K& key) +{ + expires.remove(key); + return QCache::remove(key); +} + +template +int ExpiringCache::count() const +{ + return keys().count(); +} + +template +void ExpiringCache::clear() +{ + expires.clear(); + QCache::clear(); +} + +template +bool ExpiringCache::isEmpty() const +{ + return keys().isEmpty(); +} + +template +void ExpiringCache::setExpireTime(int ms) +{ + expireMs = ms; +} + +template +bool ExpiringCache::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 +#include +#include +#include + +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 +#include + +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 #include #include +#include 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(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 + +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 +#include + +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 #endif +#ifdef Q_OS_WIN +#include +#include + +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"); @@ -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 QueryWithParamNames; typedef QPair 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 CsvSerializer::deserialize(const QString& data, const CsvFormat& format) +template +QList> typedDeserialize(const T& data, const CsvFormat& format) { - QList rows; - QStringList cells; + QList> rows; + QList 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 CsvSerializer::deserialize(const QString& data, const CsvForm return rows; } + +QList > CsvSerializer::deserialize(const QByteArray& data, const CsvFormat& format) +{ + return typedDeserialize(data, format); +} + +QList CsvSerializer::deserialize(const QString& data, const CsvFormat& format) +{ + QList> deserialized = typedDeserialize(data, format); + + QList finalList; + for (const QList& resPart : deserialized) + finalList << QStringList(resPart); + + return finalList; +} + + +//QList CsvSerializer::deserialize(const QByteArray& data, const CsvFormat& format) +//{ +// QList 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& data, const CsvFormat& format); static QString serialize(const QStringList& data, const CsvFormat& format); static QList deserialize(const QString& data, const CsvFormat& format); + static QList> 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(); @@ -219,6 +220,13 @@ bool AbstractDb2::closeInternal() return true; } +template +bool AbstractDb2::initAfterCreated() +{ + version = 2; + return AbstractDb::initAfterCreated(); +} + template void AbstractDb2::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(); @@ -382,6 +383,13 @@ bool AbstractDb3::closeInternal() return true; } +template +bool AbstractDb3::initAfterCreated() +{ + version = 3; + return AbstractDb::initAfterCreated(); +} + template void AbstractDb3::initAfterOpen() { 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()) { tmpDb = plugin->getInstance("", ":memory:", QHash()); - if (tmpDb->initAfterCreated() && tmpDb->getDialect() == fullConversionConfig->to) + if (tmpDb && tmpDb->initAfterCreated() && tmpDb->getDialect() == fullConversionConfig->to) db = plugin->getInstance(fullConversionConfig->targetName, fullConversionConfig->targetFile, QHash()); - delete tmpDb; + safe_delete(tmpDb); if (db) break; } @@ -1105,7 +1105,7 @@ QList DbVersionConverter::getAllPossibleDbInstances() const for (DbPlugin* plugin : PLUGINS->getLoadedPlugins()) { db = plugin->getInstance("", ":memory:", QHash()); - 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 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::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::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 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 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 SchemaResolver::getParsedViewsForTable(const QString& database, const QString& table) { QList 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 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 cache; }; +int qHash(const SchemaResolver::ObjectCacheKey& key); +int operator==(const SchemaResolver::ObjectCacheKey& k1, const SchemaResolver::ObjectCacheKey& k2); + template StrHash> 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)->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 &options, QString* errorMessages) { QList dbPlugins = PLUGINS->getLoadedPlugins(); - 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(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 QHashstart(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 @@ Cannot execute query on closed database. - + Nemôžem spustiť dotaz na uzatvorenej databáze. @@ -466,22 +466,22 @@ Tables, indexes, triggers and views copied to database %3 will remain. Cannot load plugin %1, because it's in conflict with plugin %2. - + Nemôžem načítať plugin %1, pretože je v konflikte s pluginom %2. Cannot load plugin %1, because its dependency was not loaded: %2. - + Nemôžem načítať plugin %1, pretože neboli načítané jeho závislosti %2. Cannot load plugin %1. Error details: %2 - + Nemôžem načítať plugin %1. Detaily chyby %2 Cannot load plugin %1 (error while initializing plugin). - + Nemôžem načítať plugin %1 (nastala chyba pri jeho inicializácii). diff --git a/SQLiteStudio3/guiSQLiteStudio/common/ipvalidator.h b/SQLiteStudio3/guiSQLiteStudio/common/ipvalidator.h index 1c9ca4d..2cda2d7 100644 --- a/SQLiteStudio3/guiSQLiteStudio/common/ipvalidator.h +++ b/SQLiteStudio3/guiSQLiteStudio/common/ipvalidator.h @@ -1,9 +1,10 @@ #ifndef IPVALIDATOR_H #define IPVALIDATOR_H +#include "guiSQLiteStudio_global.h" #include -class IpValidator : public QValidator +class GUI_API_EXPORT IpValidator : public QValidator { public: IpValidator(QObject* parent = 0); diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp index 7cc6a4e..168c7f9 100644 --- a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp @@ -140,6 +140,11 @@ void WidgetCover::hide() animation->start(); } +void WidgetCover::setProgress(int value) +{ + busyBar->setValue(value); +} + QEasingCurve WidgetCover::getEasingCurve() const { return easingCurve; @@ -192,6 +197,37 @@ bool WidgetCover::eventFilter(QObject* obj, QEvent* e) return false; } +void WidgetCover::displayProgress(int maxValue, const QString& format) +{ + if (!busyBar) + return; + + busyBar->setRange(0, maxValue); + if (!format.isNull()) + busyBar->setFormat(format); + + busyBar->setTextVisible(true); +} + +void WidgetCover::noDisplayProgress() +{ + if (!busyBar) + return; + + busyBar->setRange(0, 0); + busyBar->setTextVisible(true); +} + +void WidgetCover::initWithProgressBarOnly(const QString& format) +{ + busyBar = new QProgressBar(); + busyBar->setRange(0, 100); + busyBar->setFormat(format); + busyBar->setTextVisible(true); + + containerLayout->addWidget(busyBar, 0, 0); +} + void WidgetCover::initWithInterruptContainer(const QString& interruptButtonText) { cancelButton = new QPushButton(); diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h index d0ccef7..0b7a2f5 100644 --- a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h +++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h @@ -22,16 +22,15 @@ class GUI_API_EXPORT WidgetCover : public QWidget QEasingCurve getEasingCurve() const; void setEasingCurve(const QEasingCurve& value); - int getDuration() const; void setDuration(int value); - int getTransparency() const; void setTransparency(int value); - QGridLayout* getContainerLayout(); bool eventFilter(QObject* obj, QEvent* e); - + void displayProgress(int maxValue, const QString& format = QString()); + void noDisplayProgress(); + void initWithProgressBarOnly(const QString& format); void initWithInterruptContainer(const QString& interruptButtonText = QString()); private: @@ -67,6 +66,7 @@ class GUI_API_EXPORT WidgetCover : public QWidget public slots: void show(); void hide(); + void setProgress(int value); }; #endif // WIDGETCOVER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp index 8024abf..ac97283 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp @@ -385,14 +385,19 @@ void SqlQueryModel::commitInternal(const QList& items) // Grouping by row and commiting QList> groupedItems = groupItemsByRows(items); + emit aboutToCommit(groupedItems.size()); + + int step = 1; + rowsDeletedSuccessfullyInTheCommit.clear(); bool ok = true; - foreach (const QList& itemsInRow, groupedItems) + for (const QList& itemsInRow : groupedItems) { if (!commitRow(itemsInRow)) { ok = false; break; } + emit commitingStepFinished(step++); } // Getting current uncommited list (after rows deletion it may be different) @@ -417,15 +422,21 @@ void SqlQueryModel::commitInternal(const QList& items) else { // Commited successfully - foreach (SqlQueryItem* item, itemsLeft) + for (SqlQueryItem* item : itemsLeft) { item->setUncommited(false); item->setNewRow(false); } + qSort(rowsDeletedSuccessfullyInTheCommit); + int removeOffset = 0; + for (int row : rowsDeletedSuccessfullyInTheCommit) + removeRow(row - removeOffset++); // deleting row decrements all rows below + emit commitStatusChanged(getUncommitedItems().size() > 0); } } + rowsDeletedSuccessfullyInTheCommit.clear(); if (!ok) { @@ -447,6 +458,8 @@ void SqlQueryModel::commitInternal(const QList& items) int itemsAddedDeletedDelta = numberOfItemsAdded - numberOfItemsDeleted; recalculateRowsAndPages(itemsAddedDeletedDelta); + + emit commitFinished(); } void SqlQueryModel::rollbackInternal(const QList& items) @@ -604,7 +617,8 @@ bool SqlQueryModel::commitDeletedRow(const QList& itemsInRow) } int row = itemsInRow[0]->index().row(); - return removeRow(row); + rowsDeletedSuccessfullyInTheCommit << row; + return true; } void SqlQueryModel::rollbackAddedRow(const QList& itemsInRow) diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h index 3e92bb2..062af95 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h @@ -351,6 +351,8 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel */ QList columnEditionStatus; + QList rowsDeletedSuccessfullyInTheCommit; + private slots: void handleExecFinished(SqlQueryPtr results); void handleExecFailed(int code, QString errorMessage); @@ -446,6 +448,10 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel * Emitted after columns header sorting has been changed. */ void sortingUpdated(const QueryExecutor::SortList& sortOrder); + + void aboutToCommit(int totalSteps); + void commitingStepFinished(int step); + void commitFinished(); }; Q_DECLARE_OPERATORS_FOR_FLAGS(SqlQueryModel::Features) diff --git a/SQLiteStudio3/guiSQLiteStudio/dataview.cpp b/SQLiteStudio3/guiSQLiteStudio/dataview.cpp index e99b9b8..32efae4 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dataview.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dataview.cpp @@ -11,6 +11,7 @@ #include "iconmanager.h" #include "uiconfig.h" #include "datagrid/sqlqueryitem.h" +#include "common/widgetcover.h" #include #include #include @@ -42,6 +43,7 @@ void DataView::init(SqlQueryModel* model) formViewRowCountLabel = new QLabel(); formViewCurrentRowLabel = new QLabel(); + initWidgetCover(); initFormView(); initPageEdit(); initFilter(); @@ -139,6 +141,15 @@ void DataView::initPageEdit() connect(pageEdit, SIGNAL(editingFinished()), this, SLOT(pageEntered())); } +void DataView::initWidgetCover() +{ + widgetCover = new WidgetCover(this); + widgetCover->initWithProgressBarOnly("%v / %m"); + connect(model, SIGNAL(aboutToCommit(int)), this, SLOT(coverForGridCommit(int))); + connect(model, SIGNAL(commitingStepFinished(int)), this, SLOT(updateGridCommitCover(int))); + connect(model, SIGNAL(commitFinished()), this, SLOT(hideGridCommitCover())); +} + void DataView::createActions() { bool rowInserting = model->features().testFlag(SqlQueryModel::INSERT_ROW); @@ -438,6 +449,34 @@ void DataView::filterModeSelected() actionMap[FILTER]->setIcon(modeAction->icon()); } +void DataView::coverForGridCommit(int total) +{ + if (total <= 3) + return; + + widgetCover->displayProgress(total); + widgetCover->show(); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); +} + +void DataView::updateGridCommitCover(int value) +{ + if (!widgetCover->isVisible()) + return; + + widgetCover->setProgress(value); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); +} + +void DataView::hideGridCommitCover() +{ + if (!widgetCover->isVisible()) + return; + + widgetCover->hide(); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); +} + void DataView::updateCommitRollbackActions(bool enabled) { gridView->getAction(SqlQueryView::COMMIT)->setEnabled(enabled); diff --git a/SQLiteStudio3/guiSQLiteStudio/dataview.h b/SQLiteStudio3/guiSQLiteStudio/dataview.h index 5207fdb..64bbb07 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dataview.h +++ b/SQLiteStudio3/guiSQLiteStudio/dataview.h @@ -13,6 +13,7 @@ class FormView; class ExtLineEdit; class QLabel; class IntValidator; +class WidgetCover; CFG_KEY_LIST(DataView, QObject::tr("Data view (both grid and form)"), CFG_KEY_ENTRY(REFRESH_DATA, Qt::Key_F5, QObject::tr("Refresh data")) @@ -115,6 +116,7 @@ class GUI_API_EXPORT DataView : public QTabWidget, public ExtActionContainer void initUpdates(); void initSlots(); void initPageEdit(); + void initWidgetCover(); void createContents(); void goToFormRow(IndexModifier idxMod); void setNavigationState(bool enabled); @@ -153,6 +155,7 @@ class GUI_API_EXPORT DataView : public QTabWidget, public ExtActionContainer QMutex manualPageChangeMutex; bool uncommittedGrid = false; bool uncommittedForm = false; + WidgetCover* widgetCover = nullptr; signals: @@ -193,6 +196,9 @@ class GUI_API_EXPORT DataView : public QTabWidget, public ExtActionContainer void showFormView(); void updateTabsMode(); void filterModeSelected(); + void coverForGridCommit(int total); + void updateGridCommitCover(int value); + void hideGridCommitCover(); }; int qHash(DataView::ActionGroup action); diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp index 509594d..1aeff0f 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp @@ -136,6 +136,7 @@ void DbTree::createActions() createAction(CLEAR_FILTER, tr("Clear filter"), ui->nameFilter, SLOT(clear()), this); createAction(REFRESH_SCHEMAS, ICONS.DATABASE_RELOAD, tr("Refresh all database schemas"), this, SLOT(refreshSchemas()), this); createAction(REFRESH_SCHEMA, ICONS.DATABASE_RELOAD, tr("Refresh selected database schema"), this, SLOT(refreshSchema()), this); + createAction(ERASE_TABLE_DATA, ICONS.ERASE_TABLE_DATA, tr("Erase table data"), this, SLOT(eraseTableData()), this); } void DbTree::updateActionStates(const QStandardItem *item) @@ -189,7 +190,7 @@ void DbTree::updateActionStates(const QStandardItem *item) break; case DbTreeItem::Type::TABLE: enabled << EDIT_TABLE << DEL_TABLE << EXPORT_TABLE << IMPORT_TABLE << POPULATE_TABLE << ADD_COLUMN << CREATE_SIMILAR_TABLE; - enabled << RESET_AUTOINCREMENT << ADD_INDEX << ADD_TRIGGER; + enabled << RESET_AUTOINCREMENT << ADD_INDEX << ADD_TRIGGER << ERASE_TABLE_DATA; break; case DbTreeItem::Type::VIRTUAL_TABLE: // TODO change below when virtual tables can be edited @@ -393,6 +394,7 @@ void DbTree::setupActionsForMenu(DbTreeItem* currItem, QMenu* contextMenu) actions += ActionEntry(POPULATE_TABLE); actions += ActionEntry(CREATE_SIMILAR_TABLE); actions += ActionEntry(RESET_AUTOINCREMENT); + actions += ActionEntry(ERASE_TABLE_DATA); actions += ActionEntry(_separator); actions += dbEntryExt; break; @@ -1399,6 +1401,35 @@ void DbTree::resetAutoincrement() notifyInfo(tr("Autoincrement value for table '%1' has been reset successfly.").arg(table)); } +void DbTree::eraseTableData() +{ + Db* db = getSelectedDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString table = item->getTable(); + if (table.isNull()) + { + qWarning() << "Tried to erase table data, while table wasn't selected in DbTree."; + return; + } + + QMessageBox::StandardButton btn = QMessageBox::question(this, tr("Erase table data"), tr("Are you sure you want to delete all data from table '%1'?") + .arg(table)); + if (btn != QMessageBox::Yes) + return; + + SqlQueryPtr res = db->exec(QString("DELETE FROM %1;").arg(wrapObjIfNeeded(table, db->getDialect()))); + if (res->isError()) + { + notifyError(tr("An error occurred while trying to delete data from table '%1': %2").arg(table, res->getErrorText())); + return; + } + + notifyInfo(tr("All data has been deleted for table '%1'.").arg(table)); +} + void DbTree::addColumn(DbTreeItem* item) { Db* db = getSelectedOpenDb(); @@ -1591,6 +1622,11 @@ void DbTree::setupDefShortcuts() BIND_SHORTCUTS(DbTree, Action); } +void DbTree::closeEvent(QCloseEvent *e) +{ + e->ignore(); +} + int qHash(DbTree::Action action) { return static_cast(action); diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h index 60b8dd5..2f5583e 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h @@ -84,6 +84,7 @@ class GUI_API_EXPORT DbTree : public QDockWidget, public ExtActionContainer REFRESH_SCHEMA, CREATE_SIMILAR_TABLE, RESET_AUTOINCREMENT, + ERASE_TABLE_DATA, _separator // Never use it directly, it's just for menu setup }; @@ -116,6 +117,7 @@ class GUI_API_EXPORT DbTree : public QDockWidget, public ExtActionContainer protected: void createActions(); void setupDefShortcuts(); + void closeEvent(QCloseEvent* e); private: void setActionEnabled(int action, bool enabled); @@ -187,6 +189,7 @@ class GUI_API_EXPORT DbTree : public QDockWidget, public ExtActionContainer void integrityCheck(); void createSimilarTable(); void resetAutoincrement(); + void eraseTableData(); void addColumn(DbTreeItem* item); void editColumn(DbTreeItem* item); void delColumn(DbTreeItem* item); diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp index a4e736f..78f0db8 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp @@ -250,8 +250,19 @@ void DbTreeModel::restoreGroup(const Config::DbGroupPtr& group, QList* dbLi { if (db) { - if (db->open()) + // If the db was stored in cfg as open, it was already open by DbManager. + // Now the DbTreeModel didn't catch that (as it didn't exist yet), so we need to + // call handler for 'connected' event, instead of forcing another open call. + // Otherwise the database that could not be open would be requested to open twice: + // 1. when restoring DbManager + // 2. here + // Instead of that, we just check if the database is already open (by DbManager) + // and call proper handler to refresh database's schema and create tree nodes. + if (db->isOpen()) + { + dbConnected(db); treeView->expand(item->index()); + } } else { @@ -401,8 +412,12 @@ QString DbTreeModel::getDbToolTip(DbTreeItem* item) const QStringList rows; Db* db = item->getDb(); - QFile dbFile(db->getPath()); QString iconPath = db->isValid() ? ICONS.DATABASE.toImgSrc() : ICONS.DATABASE_INVALID.toImgSrc(); + int fileSize = -1; + + QUrl url(db->getPath()); + if (url.scheme().isEmpty() || url.scheme() == "file") + fileSize = QFile(db->getPath()).size(); rows << toolTipHdrRowTmp.arg(iconPath).arg(tr("Database: %1", "dbtree tooltip").arg(db->getName())); rows << toolTipRowTmp.arg("URI:").arg(db->getPath()); @@ -410,13 +425,17 @@ QString DbTreeModel::getDbToolTip(DbTreeItem* item) const if (db->isValid()) { rows << toolTipRowTmp.arg(tr("Version:", "dbtree tooltip")).arg(QString("SQLite %1").arg(db->getVersion())); - rows << toolTipRowTmp.arg(tr("File size:", "dbtree tooltip")).arg(formatFileSize(dbFile.size())); - rows << toolTipRowTmp.arg(tr("Encoding:", "dbtree tooltip")).arg(db->getEncoding()); + + if (fileSize > -1) + rows << toolTipRowTmp.arg(tr("File size:", "dbtree tooltip")).arg(formatFileSize(fileSize)); + + if (db->isOpen()) + rows << toolTipRowTmp.arg(tr("Encoding:", "dbtree tooltip")).arg(db->getEncoding()); } else { InvalidDb* idb = dynamic_cast(db); - rows << toolTipRowTmp.arg(tr("Error details:", "dbtree tooltip")).arg(idb->getError()); + rows << toolTipRowTmp.arg(tr("Error:", "dbtree tooltip")).arg(idb->getError()); } return toolTipTableTmp.arg(rows.join("")); diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp index 05d50af..ac7cd8a 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp @@ -455,10 +455,6 @@ bool DbDialog::testDatabase() if (url.scheme().isEmpty()) url.setScheme("file"); - bool existed = false; - if (url.isLocalFile() && QFile::exists(path)) - existed = QFile::exists(path); - QHash options = collectOptions(); DbPlugin* plugin = dbPlugins[ui->typeCombo->currentText()]; Db* testDb = plugin->getInstance("", path, options); @@ -466,15 +462,14 @@ bool DbDialog::testDatabase() bool res = false; if (testDb) { - res = true; + if (testDb->openForProbing()) + { + res = !testDb->getEncoding().isEmpty(); + testDb->closeQuiet(); + } delete testDb; } - if (!existed) - { - QFile file(path); - file.remove(); - } return res; } @@ -605,7 +600,12 @@ void DbDialog::browseClicked() { if (customBrowseHandler) { - customBrowseHandler(ui->fileEdit->text()); + QString newUrl = customBrowseHandler(ui->fileEdit->text()); + if (!newUrl.isNull()) + { + ui->fileEdit->setText(newUrl); + updateState(); + } return; } diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp index ca3fd31..7861ff0 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp @@ -51,7 +51,9 @@ void PopulateDialog::init() pluginTitles << plugin->getTitle(); widgetCover = new WidgetCover(this); + widgetCover->initWithInterruptContainer(tr("Abort")); widgetCover->setVisible(false); + connect(widgetCover, SIGNAL(cancelClicked()), POPULATE_MANAGER, SLOT(interrupt())); ui->scrollArea->setAutoFillBackground(false); ui->scrollArea->viewport()->setAutoFillBackground(false); @@ -71,6 +73,7 @@ void PopulateDialog::init() connect(ui->databaseCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshTables())); connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshColumns())); connect(POPULATE_MANAGER, SIGNAL(populatingFinished()), widgetCover, SLOT(hide())); + connect(POPULATE_MANAGER, SIGNAL(finishedStep(int)), widgetCover, SLOT(setProgress(int))); connect(POPULATE_MANAGER, SIGNAL(populatingSuccessful()), this, SLOT(finished())); } @@ -317,10 +320,20 @@ void PopulateDialog::accept() QString table = ui->tableCombo->currentText(); qint64 rows = ui->rowsSpin->value(); + started = true; + widgetCover->displayProgress(rows, "%v / %m"); widgetCover->show(); POPULATE_MANAGER->populate(db, table, engines, rows); } +void PopulateDialog::reject() +{ + if (started) + POPULATE_MANAGER->interrupt(); + + QDialog::reject(); +} + PopulateDialog::ColumnEntry::ColumnEntry(QCheckBox* check, QComboBox* combo, QToolButton* button) : check(check), combo(combo), button(button) { diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h index 0ecc318..948d6ce 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h @@ -58,6 +58,7 @@ class GUI_API_EXPORT PopulateDialog : public QDialog QSignalMapper* buttonMapper = nullptr; QHash columnsValid; WidgetCover* widgetCover = nullptr; + bool started = false; private slots: void refreshTables(); @@ -71,6 +72,7 @@ class GUI_API_EXPORT PopulateDialog : public QDialog public: void accept(); + void reject(); }; #endif // POPULATEDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/iconmanager.h b/SQLiteStudio3/guiSQLiteStudio/iconmanager.h index cf4f2ec..940c869 100644 --- a/SQLiteStudio3/guiSQLiteStudio/iconmanager.h +++ b/SQLiteStudio3/guiSQLiteStudio/iconmanager.h @@ -90,6 +90,7 @@ class GUI_API_EXPORT IconManager : public QObject DEF_ICON(DATABASE_ONLINE, "database_online") DEF_ICON(DATABASE_RELOAD, "database_reload") DEF_ICON(DDL_HISTORY, "ddl_history") + DEF_ICON(DELETE, "delete") DEF_ICON(DELETE_ROW, "delete_row") DEF_ICO3(DELETE_COLLATION, DELETE_ROW) DEF_ICO3(DELETE_DATATYPE, DELETE_ROW) @@ -104,6 +105,7 @@ class GUI_API_EXPORT IconManager : public QObject DEF_ICON(DIRECTORY_OPEN_WITH_DB, "directory_open_with_db") DEF_ICON(DIRECTORY_WITH_DB, "directory_with_db") DEF_ICON(ERASE, "erase") + DEF_ICON(ERASE_TABLE_DATA, "erase_table_data") DEF_ICON(EXEC_QUERY, "exec_query") DEF_ICON(EXPLAIN_QUERY, "explain_query") DEF_ICON(EXPORT, "export") diff --git a/SQLiteStudio3/guiSQLiteStudio/icons.qrc b/SQLiteStudio3/guiSQLiteStudio/icons.qrc index 6612814..9cf981d 100644 --- a/SQLiteStudio3/guiSQLiteStudio/icons.qrc +++ b/SQLiteStudio3/guiSQLiteStudio/icons.qrc @@ -191,5 +191,7 @@ img/go_back.png img/reset_autoincrement.png img/plus.png + img/erase_table_data.png + img/delete.png diff --git a/SQLiteStudio3/guiSQLiteStudio/img/delete.png b/SQLiteStudio3/guiSQLiteStudio/img/delete.png new file mode 100644 index 0000000..20d6f5e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/delete.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/erase_table_data.png b/SQLiteStudio3/guiSQLiteStudio/img/erase_table_data.png new file mode 100644 index 0000000..a07585a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/erase_table_data.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/translations/guiSQLiteStudio_sk.ts b/SQLiteStudio3/guiSQLiteStudio/translations/guiSQLiteStudio_sk.ts index 26e0fb1..42a735f 100644 --- a/SQLiteStudio3/guiSQLiteStudio/translations/guiSQLiteStudio_sk.ts +++ b/SQLiteStudio3/guiSQLiteStudio/translations/guiSQLiteStudio_sk.ts @@ -1028,7 +1028,7 @@ but it's okay to use it. Execute only the query under the cursor - + Vykonať len dotaz, na ktorom stojí kurzor @@ -1089,12 +1089,12 @@ but it's okay to use it. Sort table columns alphabetically - + Zoradiť stĺpce tabuľky abecedne Expand tables node when connected to a database - + Rozbaliť zoznam tabuliek po pripojení k databáze @@ -1104,7 +1104,7 @@ but it's okay to use it. Display additional labels on the list - + Zobraziť doplnkové popisky v zozname @@ -1114,7 +1114,7 @@ but it's okay to use it. Display labels for regular tables - + Zobraziť popisky pre regulárne tabuľky @@ -1124,12 +1124,12 @@ but it's okay to use it. Display labels for virtual tables - + Zobraziť popisky pre virtuálne tabuľky Expand views node when connected to a database - + Rozbaliť zoznam pohľadov po pripojení k databáze @@ -1139,42 +1139,42 @@ but it's okay to use it. Sort objects (tables, indexes, triggers and views) alphabetically - + Zoradiť objekty (tabuľky, indexy, spúšťače a pohľady) abecedne Display system tables and indexes on the list - + Zobraziť systémové tabuľky a indexy v zozname Table windows - + Okná tabuľky When enabled, Table Windows will show up with the data tab, instead of the structure tab. - + Ak je táto možnosť zaškrtnutá, tak sa v okne zobrazia dáta a nie štruktúra tabuľky. Open Table Windows with the data tab for start - + Zobraziť dáta po otvorení tabuľky View windows - + Okná pohľadov When enabled, View Windows will show up with the data tab, instead of the structure tab. - + Ak je táto možnosť zaškrtnutá, tak sa v okne zobrazia dáta a nie SQL dotaz. Open View Windows with the data tab for start - + Zobraziť dáta po otvorení pohľadu @@ -1184,22 +1184,22 @@ but it's okay to use it. Current style: - + Aktuálny štýl: Preview - + Náhľad Enabled - + Zapnutý Disabled - + Vypnutý @@ -1209,17 +1209,17 @@ but it's okay to use it. SQL editor font - + Písmo SQL editora Database list font - + Font zoznamu databáz Database list additional label font - + Font doplnkového popisku @@ -1835,7 +1835,7 @@ Prezeranie ďalších strán bude možné až po dokončení spočítavania. Database type - + Typ databázy @@ -1845,22 +1845,22 @@ Prezeranie ďalších strán bude možné až po dokončení spočítavania. Generate automatically - + Generovať automaticky Options - Voľby + Voľby Permanent (keep it in configuration) - + Zapamätať si databázu Test connection - + Test spojenia Name @@ -1877,7 +1877,7 @@ Prezeranie ďalších strán bude možné až po dokončení spočítavania. Create new database file - + Vytvoriť nový databázový súbor @@ -1888,7 +1888,7 @@ Prezeranie ďalších strán bude možné až po dokončení spočítavania. Name (on the list) - + Názov (v zozname) @@ -1912,7 +1912,7 @@ Prezeranie ďalších strán bude možné až po dokončení spočítavania. Browse for existing database file on local computer - + Hľadať databázový súbor na lokálnom počítači @@ -1947,7 +1947,7 @@ Prezeranie ďalších strán bude možné až po dokončení spočítavania. Auto-generated - + Automaticky vygenerovaný The name will be auto-generated @@ -3325,7 +3325,7 @@ Please enter new, unique name, or press '%1' to abort the operation: Cannot create unique index, because values in selected columns are not unique. Would you like to execute SELECT query to see problematic values? - + Nemôžem vytvoriť jedinečný index, pretože hodnoty vo vybraných stĺpcoch nie sú jedinečné. Chcete spustiť dotaz SELECT na zobrazenie problematických hodnôt? @@ -4265,7 +4265,7 @@ Please enter new, unique name, or press '%1' to abort the operation: Cell text value editor - + Úprava hodnôt v bunkách @@ -4401,7 +4401,7 @@ Please enter new, unique name, or press '%1' to abort the operation: SQL editor window - + Okno SQL editora @@ -4411,42 +4411,42 @@ Please enter new, unique name, or press '%1' to abort the operation: Execute "%1" query - + Vykonať "%1" dotaz Switch current working database to previous on the list - + Prepnúť sa na predchádzajúcu databázu v zozname Switch current working database to next on the list - + Prepnúť sa na nasledujúcu databázu v zozname Go to next editor tab - + Prechod na nasledujúcu záložku editora Go to previous editor tab - + Prechod na predchádzajúcu záložku editora Move keyboard input focus to the results view below - + Prepnúť kurzor na výsledky Move keyboard input focus to the SQL editor above - + Prepnúť kurzor do editora Table window - + Okno tabuľky @@ -4491,7 +4491,7 @@ Please enter new, unique name, or press '%1' to abort the operation: Delete selected table constraint - Vymazaťˇvybrané obmedzenie + Vymazať vybrané obmedzenie diff --git a/SQLiteStudio3/guiSQLiteStudio/uiconfig.h b/SQLiteStudio3/guiSQLiteStudio/uiconfig.h index 3372979..b434dc8 100644 --- a/SQLiteStudio3/guiSQLiteStudio/uiconfig.h +++ b/SQLiteStudio3/guiSQLiteStudio/uiconfig.h @@ -85,9 +85,9 @@ CFG_CATEGORIES(Ui, ) ) -QString getFileDialogInitPath(); -void setFileDialogInitPath(const QString& path); -void setFileDialogInitPathByFile(const QString& filePath); +GUI_API_EXPORT QString getFileDialogInitPath(); +GUI_API_EXPORT void setFileDialogInitPath(const QString& path); +GUI_API_EXPORT void setFileDialogInitPathByFile(const QString& filePath); #define CFG_UI CFG_INSTANCE(Ui) diff --git a/SQLiteStudio3/guiSQLiteStudio/uidebug.cpp b/SQLiteStudio3/guiSQLiteStudio/uidebug.cpp index 00aaac4..a2ce9f8 100644 --- a/SQLiteStudio3/guiSQLiteStudio/uidebug.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/uidebug.cpp @@ -12,6 +12,8 @@ bool UI_DEBUG_ENABLED = false; bool UI_DEBUG_CONSOLE = true; QString UI_DEBUG_FILE; +QStringList MsgHandlerThreadProxy::ignoredWarnings; + void uiMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { if (!UI_DEBUG_ENABLED) @@ -100,6 +102,9 @@ MsgHandlerThreadProxy::~MsgHandlerThreadProxy() void MsgHandlerThreadProxy::init() { + ignoredWarnings << QStringLiteral("libpng warning: Unknown iTXt compression type or method"); + ignoredWarnings << QStringLiteral("QPainter::font: Painter not active"); + if (sqliteStudioUiDebugConsole) { connect(this, SIGNAL(debugRequested(QString)), sqliteStudioUiDebugConsole, SLOT(debug(QString))); @@ -139,6 +144,9 @@ void MsgHandlerThreadProxy::debug(const QString &msg) void MsgHandlerThreadProxy::warn(const QString &msg) { + if (ignoredWarnings.contains(msg.mid(25))) + return; + emit warnRequested(msg); } diff --git a/SQLiteStudio3/guiSQLiteStudio/uidebug.h b/SQLiteStudio3/guiSQLiteStudio/uidebug.h index d2a2a51..449eb29 100644 --- a/SQLiteStudio3/guiSQLiteStudio/uidebug.h +++ b/SQLiteStudio3/guiSQLiteStudio/uidebug.h @@ -20,6 +20,8 @@ class GUI_API_EXPORT MsgHandlerThreadProxy : public QObject void init(); void initFile(const QString& fileName); + static QStringList ignoredWarnings; + QFile* outFile = nullptr; QTextStream outFileStream; diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp index 77a4adc..3a315db 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp @@ -621,20 +621,20 @@ bool TableWindow::restoreSession(const QVariant& sessionValue) QHash value = sessionValue.toHash(); if (value.size() == 0) { - notifyWarn("Could not restore window, because no database or table was stored in session for this window."); + notifyWarn(tr("Could not restore window %1, because no database or table was stored in session for this window.").arg(value["title"].toString())); return false; } if (!value.contains("db") || !value.contains("table")) { - notifyWarn("Could not restore window, because no database or table was stored in session for this window."); + notifyWarn(tr("Could not restore window '%1', because no database or table was stored in session for this window.").arg(value["title"].toString())); return false; } db = DBLIST->getByName(value["db"].toString()); if (!db || !db->isValid() || (!db->isOpen() && !db->open())) { - notifyWarn(tr("Could not restore window, because database %1 could not be resolved.").arg(value["db"].toString())); + notifyWarn(tr("Could not restore window '%1', because database %2 could not be resolved.").arg(value["title"].toString(), value["db"].toString())); return false; } @@ -643,7 +643,7 @@ bool TableWindow::restoreSession(const QVariant& sessionValue) SchemaResolver resolver(db); if (!resolver.getTables(database).contains(table, Qt::CaseInsensitive)) { - notifyWarn(tr("Could not restore window, because the table %1 doesn't exist in the database %2.").arg(table).arg(db->getName())); + notifyWarn(tr("Could not restore window '%1'', because the table %2 doesn't exist in the database %3.").arg(value["title"].toString(), table, db->getName())); return false; } diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp index a699801..9a30e1c 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp @@ -99,26 +99,26 @@ bool ViewWindow::restoreSession(const QVariant& sessionValue) QHash value = sessionValue.toHash(); if (value.size() == 0) { - notifyWarn("Could not restore window, because no database or view was stored in session for this window."); + notifyWarn(tr("Could not restore window '%1', because no database or view was stored in session for this window.").arg(value["title"].toString())); return false; } if (!value.contains("db") || !value.contains("view")) { - notifyWarn("Could not restore window, because no database or view was stored in session for this window."); + notifyWarn(tr("Could not restore window '%1', because no database or view was stored in session for this window.").arg(value["title"].toString())); return false; } db = DBLIST->getByName(value["db"].toString()); if (!db) { - notifyWarn(tr("Could not restore window, because database %1 could not be resolved.").arg(value["db"].toString())); + notifyWarn(tr("Could not restore window '%1', because database %2 could not be resolved.").arg(value["title"].toString(), value["db"].toString())); return false; } if (!db->isOpen() && !db->open()) { - notifyWarn(tr("Could not restore window, because database %1 could not be open.").arg(value["db"].toString())); + notifyWarn(tr("Could not restore window '%1', because database %2 could not be open.").arg(value["title"].toString(), value["db"].toString())); return false; } @@ -127,7 +127,7 @@ bool ViewWindow::restoreSession(const QVariant& sessionValue) SchemaResolver resolver(db); if (!resolver.getViews(database).contains(view, Qt::CaseInsensitive)) { - notifyWarn(tr("Could not restore window, because the view %1 doesn't exist in the database %2.").arg(view).arg(db->getName())); + notifyWarn(tr("Could not restore window '%1', because the view %2 doesn't exist in the database %3.").arg(value["title"].toString(), view, db->getName())); return false; } diff --git a/SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro b/SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro index 8ee61f9..4e3bae0 100644 --- a/SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro +++ b/SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro @@ -21,8 +21,10 @@ TEMPLATE = app CONFIG += c++11 QMAKE_CXXFLAGS += -pedantic -linux|portable { - QMAKE_LFLAGS += -Wl,-rpath,./lib +linux { + portable { + QMAKE_LFLAGS += -Wl,-rpath,./lib + } } TRANSLATIONS += translations/sqlitestudiocli_zh_CN.ts \ @@ -71,7 +73,7 @@ win32: { } unix: { - LIBS += -lreadline -ltermcap + LIBS += -lreadline -lcurses } HEADERS += \ -- cgit v1.2.3