diff --git a/asyncdbconnection.cpp b/asyncdbconnection.cpp index 2bc6c30..0eb8b0a 100644 --- a/asyncdbconnection.cpp +++ b/asyncdbconnection.cpp @@ -250,6 +250,7 @@ void ASyncDBConnection::Thread::doNewCommand() } } if (!command.empty() && m_connection.sendQuery(command)) { + m_timer.start(); doStateCallback(State::QuerySend); } else { @@ -290,9 +291,11 @@ void ASyncDBConnection::Thread::waitForResult() while ( ! finished && ! m_connection.isBusy()) { auto res(m_connection.getResult()); { + qint64 ms = m_timer.restart(); std::lock_guard lg(m_commandQueue.m_mutex); - m_commandQueue.m_queue.front().on_result(res); + m_commandQueue.m_queue.front().on_result(res, ms); if (res == nullptr) { + m_timer.invalidate(); m_commandQueue.m_queue.pop(); doStateCallback(State::Connected); finished = true; diff --git a/asyncdbconnection.h b/asyncdbconnection.h index e147dfa..03a6162 100644 --- a/asyncdbconnection.h +++ b/asyncdbconnection.h @@ -4,6 +4,7 @@ #include "PgsqlConn.h" #include "win32event.h" #include "connectionconfig.h" +#include #include #include #include @@ -21,7 +22,7 @@ public: Terminating }; - using on_result_callback = std::function)>; + using on_result_callback = std::function, qint64)>; using on_state_callback = std::function; using on_notice_callback = std::function; @@ -102,6 +103,7 @@ private: bool terminateRequested = false; ///< is set when the thread should stop bool m_terminated = true; Pgsql::Canceller m_canceller; + QElapsedTimer m_timer; bool makeConnection(); diff --git a/databaseinspectorwidget.cpp b/databaseinspectorwidget.cpp new file mode 100644 index 0000000..93d7f38 --- /dev/null +++ b/databaseinspectorwidget.cpp @@ -0,0 +1,14 @@ +#include "databaseinspectorwidget.h" +#include "ui_databaseinspectorwidget.h" + +DatabaseInspectorWidget::DatabaseInspectorWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::DatabaseInspectorWidget) +{ + ui->setupUi(this); +} + +DatabaseInspectorWidget::~DatabaseInspectorWidget() +{ + delete ui; +} diff --git a/databaseinspectorwidget.h b/databaseinspectorwidget.h new file mode 100644 index 0000000..98f810f --- /dev/null +++ b/databaseinspectorwidget.h @@ -0,0 +1,22 @@ +#ifndef DATABASEINSPECTORWIDGET_H +#define DATABASEINSPECTORWIDGET_H + +#include + +namespace Ui { +class DatabaseInspectorWidget; +} + +class DatabaseInspectorWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DatabaseInspectorWidget(QWidget *parent = 0); + ~DatabaseInspectorWidget(); + +private: + Ui::DatabaseInspectorWidget *ui; +}; + +#endif // DATABASEINSPECTORWIDGET_H diff --git a/databaseoverviewform.ui b/databaseinspectorwidget.ui similarity index 93% rename from databaseoverviewform.ui rename to databaseinspectorwidget.ui index 8e1fcf6..74e83a1 100644 --- a/databaseoverviewform.ui +++ b/databaseinspectorwidget.ui @@ -1,13 +1,13 @@ - DatabaseOverviewForm - + DatabaseInspectorWidget + 0 0 - 733 - 618 + 599 + 536 diff --git a/databaseoverviewform.cpp b/databaseoverviewform.cpp deleted file mode 100644 index cbc4c58..0000000 --- a/databaseoverviewform.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "databaseoverviewform.h" -#include "ui_databaseoverviewform.h" - -DatabaseOverviewForm::DatabaseOverviewForm(QWidget *parent) : - QWidget(parent), - ui(new Ui::DatabaseOverviewForm) -{ - ui->setupUi(this); -} - -DatabaseOverviewForm::~DatabaseOverviewForm() -{ - delete ui; -} diff --git a/icons/desktop.ini b/icons/desktop.ini index a0f2f95..17ade64 100644 --- a/icons/desktop.ini +++ b/icons/desktop.ini @@ -11,3 +11,4 @@ folder.png=@folder.png,0 script_save.png=@script_save.png,0 lightbulb.png=@lightbulb.png,0 table_save.png=@table_save.png,0 +lightbulb_off.png=@lightbulb_off.png,0 diff --git a/icons/information.png b/icons/information.png new file mode 100644 index 0000000..93c67f2 Binary files /dev/null and b/icons/information.png differ diff --git a/icons/lightbulb_off.png b/icons/lightbulb_off.png new file mode 100644 index 0000000..9516ef3 Binary files /dev/null and b/icons/lightbulb_off.png differ diff --git a/icons/page_white_delete.png b/icons/page_white_delete.png new file mode 100644 index 0000000..6c9f525 Binary files /dev/null and b/icons/page_white_delete.png differ diff --git a/mainwindow.cpp b/mainwindow.cpp index a84761b..962a536 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -26,7 +26,7 @@ MainWindow::MainWindow(QWidget *parent) ui->setupUi(this); ui->tabWidget->setDocumentMode(true); - ui->tabWidget->setTabsClosable(true); + //ui->tabWidget->setTabsClosable(true); } MainWindow::~MainWindow() @@ -40,6 +40,7 @@ QueryTab* MainWindow::newSqlPage() qt->setConfig(m_config); ui->tabWidget->addTab(qt, "Tab"); ui->tabWidget->setCurrentWidget(qt); + qt->newdoc(); return qt; } @@ -125,7 +126,8 @@ void MainWindow::on_actionExport_data_triggered() void MainWindow::on_actionClose_triggered() { - close(); + //close(); + on_tabWidget_tabCloseRequested(ui->tabWidget->currentIndex()); } void MainWindow::on_actionAbout_triggered() @@ -172,11 +174,20 @@ void MainWindow::on_actionExecute_SQL_triggered() } } +void MainWindow::on_actionExplain_triggered() +{ + QueryTab *tab = GetActiveQueryTab(); + if (tab) { + tab->explain(false); + } +} + + void MainWindow::on_actionExplain_Analyze_triggered() { QueryTab *tab = GetActiveQueryTab(); if (tab) { - tab->explainAnalyze(); + tab->explain(true); } } @@ -221,3 +232,4 @@ void MainWindow::on_tabWidget_tabCloseRequested(int index) ui->tabWidget->removeTab(index); } } + diff --git a/mainwindow.h b/mainwindow.h index 57f6fa1..230ce7c 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -45,6 +45,7 @@ public: private: + Ui::MainWindow *ui; TSQueue m_taskQueue; @@ -56,6 +57,7 @@ private: void closeEvent(QCloseEvent *event); void showEvent(QShowEvent *event); QueryTab *newSqlPage(); + private slots: void processCallableQueue(); @@ -72,6 +74,7 @@ private slots: void on_actionSave_copy_of_SQL_as_triggered(); void on_actionNew_SQL_triggered(); void on_tabWidget_tabCloseRequested(int index); + void on_actionExplain_triggered(); }; #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui index d773752..9e066de 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -45,7 +45,7 @@ 0 0 993 - 25 + 22 @@ -67,7 +67,18 @@ + + + Query + + + + + + + + @@ -81,10 +92,14 @@ + + + + @@ -121,11 +136,24 @@ + + + :/icons/page_white_delete.png + + Close + + Ctrl+F4 + + + + :/icons/information.png + + About @@ -137,7 +165,10 @@ - Execute SQL + Execute queries + + + Execute the (selected) queries F5 @@ -191,6 +222,22 @@ Ctrl+N + + + + :/icons/lightbulb_off.png + + + + Explain + + + Explain the (selected) query + + + F7 + + diff --git a/pglab.pro b/pglab.pro index af58e7e..88a3345 100644 --- a/pglab.pro +++ b/pglab.pro @@ -35,11 +35,15 @@ SOURCES += main.cpp\ connectionlistmodel.cpp \ connectionconfig.cpp \ backuprestore.cpp \ - databaseoverviewform.cpp \ dbschema_database.cpp \ querytab.cpp \ stopwatch.cpp \ - util.cpp + util.cpp \ + databaseinspectorwidget.cpp \ + pgtype.cpp \ + pgsqldatabasecatalogue.cpp \ + pgtypecontainer.cpp \ + tuplesresultwidget.cpp HEADERS += mainwindow.h \ serverproperties.h \ @@ -60,17 +64,22 @@ HEADERS += mainwindow.h \ connectionconfig.h \ scopeguard.h \ expected.h \ - databaseoverviewform.h \ dbschema_database.h \ querytab.h \ stopwatch.h \ - util.h + util.h \ + databaseinspectorwidget.h \ + pgtype.h \ + pgsqldatabasecatalogue.h \ + pgtypecontainer.h \ + tuplesresultwidget.h FORMS += mainwindow.ui \ serverproperties.ui \ databasewindow.ui \ connectionmanagerwindow.ui \ - databaseoverviewform.ui \ + databaseinspectorwidget.ui \ + tuplesresultwidget.ui \ querytab.ui RESOURCES += \ diff --git a/querytab.cpp b/querytab.cpp index b52da2a..e23af9d 100644 --- a/querytab.cpp +++ b/querytab.cpp @@ -40,9 +40,9 @@ QueryTab::QueryTab(MainWindow *win, QWidget *parent) : highlighter.reset(new SqlHighlighter(ui->queryEdit->document())); connect(ui->queryEdit, &QPlainTextEdit::textChanged, this, &QueryTab::queryTextChanged); - m_stopwatch.setOutputLabel(ui->lblElapsedTime); - ui->lblElapsedTime->clear(); - ui->lblRowCount->clear(); +// m_stopwatch.setOutputLabel(ui->lblElapsedTime); +// ui->lblElapsedTime->clear(); +// ui->lblRowCount->clear(); } QueryTab::~QueryTab() @@ -167,14 +167,14 @@ void QueryTab::execute() std::string cmd = getCommand(); m_stopwatch.start(); m_dbConnection.send(cmd, - [this](std::shared_ptr res) + [this](std::shared_ptr res, qint64 elapsedms) { - m_win->QueueTask([this, res]() { query_ready(res); }); + m_win->QueueTask([this, res, elapsedms]() { query_ready(res, elapsedms); }); }); } } -void QueryTab::explainAnalyze() +void QueryTab::explain(bool analyze) { ui->explainTreeView->setModel(nullptr); explainModel.reset(); @@ -182,10 +182,14 @@ void QueryTab::explainAnalyze() addLog("Explain clicked"); + std::string analyze_str; + if (analyze) { + analyze_str = "ANALYZE, "; + } m_stopwatch.start(); - std::string cmd = "EXPLAIN (ANALYZE, VERBOSE, BUFFERS, FORMAT JSON) " + getCommand(); + std::string cmd = "EXPLAIN (" + analyze_str + "VERBOSE, BUFFERS, FORMAT JSON) " + getCommand(); m_dbConnection.send(cmd, - [this](std::shared_ptr res) + [this](std::shared_ptr res, qint64 ) { if (res) { // Process explain data seperately @@ -216,7 +220,9 @@ void QueryTab::cancel() void QueryTab::setFileName(const QString &filename) { m_fileName = filename; - setTabCaption(m_fileName); + QFileInfo fileInfo(filename); + QString fn(fileInfo.fileName()); + setTabCaption(fn, m_fileName); } bool QueryTab::continueWithoutSavingWarning() @@ -403,7 +409,7 @@ std::string QueryTab::getCommand() const return command.toUtf8().data(); } -void QueryTab::setTabCaption(const QString &caption) +void QueryTab::setTabCaption(const QString &caption, const QString &tooltip) { QWidget * w = parentWidget(); QWidget * p = w->parentWidget(); @@ -412,32 +418,45 @@ void QueryTab::setTabCaption(const QString &caption) int i = tabwidget->indexOf(this); if (i >= 0) { tabwidget->setTabText(i, caption); + tabwidget->setTabToolTip(i, tooltip); } } } -void QueryTab::query_ready(std::shared_ptr dbres) +void QueryTab::query_ready(std::shared_ptr dbres, qint64 elapsedms) { - m_stopwatch.stop(); if (dbres) { addLog("query_ready with result"); auto st = dbres->resultStatus(); if (st == PGRES_TUPLES_OK) { //int n_rows = dbres->getRows(); - QString rowcount_str = QString("rows: %1").arg(dbres->getRows()); - ui->lblRowCount->setText(rowcount_str); - resultModel.reset(new QueryResultModel(nullptr , dbres)); - ui->ResultView->setModel(resultModel.get()); - ui->tabWidget->setCurrentWidget(ui->dataTab); + //QString rowcount_str = QString("rows: %1").arg(dbres->getRows()); + + auto result_model = std::make_shared(nullptr , dbres); + TuplesResultWidget *trw = new TuplesResultWidget; + trw->setResult(result_model, elapsedms); + resultList.push_back(trw); + ui->tabWidget->addTab(trw, "Data"); +// ui->lblRowCount->setText(rowcount_str); +// resultModel.reset(new QueryResultModel(nullptr , dbres)); +// ui->ResultView->setModel(resultModel.get()); +// ui->tabWidget->setCurrentWidget(ui->dataTab); //statusBar()->showMessage(tr("Query ready.")); } else { if (st == PGRES_COMMAND_OK) { // statusBar()->showMessage(tr("Command OK.")); - QString msg = tr("Query returned succesfully: %1 rows affected, %2 execution time.") - .arg(QString::number(dbres->tuplesAffected())) - .arg(m_stopwatch.elapsed()); //msfloatToHumanReadableString(elapsedTime.count())); + int tuples_affected = dbres->tuplesAffected(); + QString msg; + if (tuples_affected >= 0) + msg = tr("Query returned succesfully: %1 rows affected, execution time %2") + .arg(QString::number(tuples_affected)) + .arg(msfloatToHumanReadableString(elapsedms)); + else + msg = tr("Query returned succesfully, execution time %1") + .arg(msfloatToHumanReadableString(elapsedms)); + ui->messagesEdit->append(msg); ui->tabWidget->setCurrentWidget(ui->messageTab); @@ -476,6 +495,7 @@ void QueryTab::query_ready(std::shared_ptr dbres) } } else { + m_stopwatch.stop(); addLog("query_ready with NO result"); // statusBar()->showMessage(tr("Query cancelled.")); } @@ -483,7 +503,10 @@ void QueryTab::query_ready(std::shared_ptr dbres) void QueryTab::clearResult() { - ui->ResultView->setModel(nullptr); - resultModel.reset(); - ui->lblRowCount->clear(); +// ui->ResultView->setModel(nullptr); +// resultModel.reset(); + for (auto e : resultList) + delete e; + resultList.clear(); +// ui->lblRowCount->clear(); } diff --git a/querytab.h b/querytab.h index a57c8a9..402d244 100644 --- a/querytab.h +++ b/querytab.h @@ -5,12 +5,13 @@ #include "queryresultmodel.h" #include "queryexplainmodel.h" #include "stopwatch.h" +#include "tuplesresultwidget.h" #include #include namespace Ui { -class QueryTab; + class QueryTab; } class MainWindow; @@ -38,11 +39,22 @@ public: void saveCopyAs(); void execute(); - void explainAnalyze(); + void explain(bool analyze); void cancel(); bool canClose(); private: + +// struct ResultTab { +// public: +// std::shared_ptr resultModel; +// std::shared_ptr tuplesResult; +//// ResultTab(std::shared_ptr rm, Ui::TuplesResult *tr) +//// : resultModel(rm), tuplesResult(tr) +//// {} +// }; + using ResultTabContainer = std::vector; + Ui::QueryTab *ui; MainWindow *m_win; std::unique_ptr highlighter; @@ -58,17 +70,20 @@ private: QString promptUserForSaveSqlFilename(); + + ASyncDBConnection m_dbConnection; - std::unique_ptr resultModel; + std::unique_ptr explainModel; + ResultTabContainer resultList; void addLog(QString s); std::string getCommand() const; void explain_ready(ExplainRoot::SPtr explain); - void query_ready(std::shared_ptr dbres); + void query_ready(std::shared_ptr dbres, qint64 elapsedms); - void setTabCaption(const QString &caption); + void setTabCaption(const QString &caption, const QString &tooltip); void clearResult(); private slots: diff --git a/querytab.ui b/querytab.ui index e8f75fe..101518b 100644 --- a/querytab.ui +++ b/querytab.ui @@ -65,61 +65,6 @@ - - - Data - - - - - - - Source Sans Pro - 10 - - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::ScrollPerPixel - - - QAbstractItemView::ScrollPerPixel - - - false - - - 20 - - - - - - - - - - 0ms - - - - - - - TextLabel - - - - - - - - Explain diff --git a/resources.qrc b/resources.qrc index 4d3c217..e1c3cf0 100644 --- a/resources.qrc +++ b/resources.qrc @@ -10,5 +10,8 @@ icons/lightbulb.png icons/table_save.png icons/page_white_add.png + icons/page_white_delete.png + icons/lightbulb_off.png + icons/information.png diff --git a/tuplesresultwidget.cpp b/tuplesresultwidget.cpp new file mode 100644 index 0000000..4cf1582 --- /dev/null +++ b/tuplesresultwidget.cpp @@ -0,0 +1,26 @@ +#include "tuplesresultwidget.h" +#include "ui_tuplesresultwidget.h" +#include "util.h" + +TuplesResultWidget::TuplesResultWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::TuplesResultWidget) +{ + ui->setupUi(this); + ui->lblRowCount->setText(QString()); +} + +TuplesResultWidget::~TuplesResultWidget() +{ + delete ui; +} + +void TuplesResultWidget::setResult(std::shared_ptr res, float ms) +{ + resultModel = res; + ui->ResultView->setModel(resultModel.get()); + + QString rowcount_str = QString("rows: %1").arg(resultModel->rowCount()); + ui->lblRowCount->setText(rowcount_str); + ui->lblElapsedTime->setText(msfloatToHumanReadableString(ms)); +} diff --git a/tuplesresultwidget.h b/tuplesresultwidget.h new file mode 100644 index 0000000..15cd447 --- /dev/null +++ b/tuplesresultwidget.h @@ -0,0 +1,26 @@ +#ifndef TUPLESRESULTWIDGET_H +#define TUPLESRESULTWIDGET_H + +#include "queryresultmodel.h" +#include + +namespace Ui { +class TuplesResultWidget; +} + +class TuplesResultWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TuplesResultWidget(QWidget *parent = 0); + ~TuplesResultWidget(); + + void setResult(std::shared_ptr res, float ms); +private: + Ui::TuplesResultWidget *ui; + + std::shared_ptr resultModel; +}; + +#endif // TUPLESRESULTWIDGET_H diff --git a/tuplesresultwidget.ui b/tuplesresultwidget.ui new file mode 100644 index 0000000..34c08e2 --- /dev/null +++ b/tuplesresultwidget.ui @@ -0,0 +1,99 @@ + + + TuplesResultWidget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 4 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + Source Sans Pro + 10 + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + 20 + + + 16 + + + + + + + + 4 + + + 0 + + + 4 + + + 0 + + + + + 0ms + + + + + + + TextLabel + + + + + + + + + + +