#include "QueryTab.h" #include "ui_QueryTab.h" #include "SqlSyntaxHighlighter.h" #include #include #include #include #include #include #include #include #include #include "ExplainTreeModelItem.h" #include "json/json.h" #include "MainWindow.h" #include "OpenDatabase.h" #include "PgTypeContainer.h" #include "PgDatabaseCatalogue.h" #include "util.h" #include "GlobalIoService.h" QueryParamListController::QueryParamListController(QTableView *tv, OpenDatabase *opendb, QWidget *parent) : QObject(parent) , paramTableView(tv) , m_openDatabase(opendb) { if (opendb) { m_typeDelegate.setTypeSelectionModel(opendb->typeSelectionModel()); } paramTableView->setModel(&m_paramList); paramTableView->setItemDelegateForColumn(1, &m_typeDelegate); } Pgsql::Params QueryParamListController::params() const { Pgsql::Params params; auto types = m_openDatabase->catalogue()->types(); for (auto e : m_paramList) { Oid oid = types->getByName(e.type).oid; params.add(e.value, oid); } return params; } bool QueryParamListController::empty() const { return m_paramList.rowCount() == 0; } void QueryParamListController::on_addParam() { m_paramList.insertRows(m_paramList.rowCount(), 1); } void QueryParamListController::on_removeParam() { auto rc = m_paramList.rowCount(); if (rc > 0) m_paramList.removeRows(rc-1, 1); } QueryTab::QueryTab(MainWindow *win, QWidget *parent) : QWidget(parent), ui(new Ui::QueryTab), m_win(win), m_dbConnection(*getGlobalAsioIoService()) { ui->setupUi(this); connect(&m_dbConnection, &ASyncDBConnection::onStateChanged, this, &QueryTab::connectionStateChanged); connect(&m_dbConnection, &ASyncDBConnection::onNotice, this, &QueryTab::receiveNotice); QFont font; font.setFamily("Source Code Pro"); font.setFixedPitch(true); font.setPointSize(10); ui->queryEdit->setFont(font); highlighter = new SqlSyntaxHighlighter(ui->queryEdit->document()); OpenDatabase* open_database = m_win->getDatabase(); if (open_database) { auto cat = open_database->catalogue(); highlighter->setTypes(cat->types()); } connect(ui->queryEdit, &QPlainTextEdit::textChanged, this, &QueryTab::queryTextChanged); m_queryParamListController = new QueryParamListController(ui->paramTableView, open_database, this); connect(ui->addButton, &QPushButton::clicked, m_queryParamListController, &QueryParamListController::on_addParam); connect(ui->removeButton, &QPushButton::clicked, m_queryParamListController, &QueryParamListController::on_removeParam); } QueryTab::~QueryTab() { m_dbConnection.closeConnection(); delete ui; } void QueryTab::setConfig(const ConnectionConfig &config) { m_config = config; m_win->QueueTask([this]() { startConnect(); }); } bool QueryTab::canClose() { bool can_close; if (m_queryTextChanged) { can_close = continueWithoutSavingWarning(); } else { can_close = true; } return can_close; } void QueryTab::newdoc() { ui->queryEdit->clear(); setFileName(tr("new")); m_queryTextChanged = false; m_new = true; } bool QueryTab::load(const QString &filename) { bool result = false; QFile file(filename); if (file.open(QIODevice::ReadOnly)) { QByteArray ba = file.readAll(); const char *ptr = ba.constData(); QTextCodec *codec = QTextCodec::codecForUtfText(ba, QTextCodec::codecForName("utf-8")); QTextCodec::ConverterState state; QString text = codec->toUnicode(ptr, ba.size(), &state); if (state.invalidChars > 0) { file.reset(); QTextStream stream(&file); text = stream.readAll(); } ui->queryEdit->setPlainText(text); m_queryTextChanged = false; setFileName(filename); m_new = false; result = true; } return result; } bool QueryTab::save() { bool result; if (m_fileName.isEmpty() || m_new) { result = saveAs(); } else { result = saveSqlTo(m_fileName); } return result; } bool QueryTab::saveAs() { bool result = false; QString filename = promptUserForSaveSqlFilename(); if (!filename.isEmpty()) { result = saveSqlTo(filename); if (result) { setFileName(filename); m_new = false; } } return result; } void QueryTab::saveCopyAs() { QString filename = promptUserForSaveSqlFilename(); if (!filename.isEmpty()) { saveSqlTo(filename); } } void QueryTab::execute() { if (m_dbConnection.state() == ASyncDBConnection::State::Connected) { addLog("Query clicked"); clearResult(); ui->messagesEdit->clear(); std::string cmd = getCommandUtf8(); m_stopwatch.start(); if (m_queryParamListController->empty()) m_dbConnection.send(cmd, [this](Expected> res, qint64 elapsedms) { m_win->QueueTask([this, res, elapsedms]() { query_ready(res, elapsedms); }); }); else m_dbConnection.send(cmd, m_queryParamListController->params(), [this](Expected> res, qint64 elapsedms) { m_win->QueueTask([this, res, elapsedms]() { query_ready(res, elapsedms); }); }); } } void QueryTab::explain(bool analyze) { ui->explainTreeView->setModel(nullptr); explainModel.reset(); ui->messagesEdit->clear(); addLog("Explain clicked"); std::string analyze_str; if (analyze) { analyze_str = "ANALYZE, "; } m_stopwatch.start(); std::string cmd = "EXPLAIN (" + analyze_str + "VERBOSE, BUFFERS, FORMAT JSON) " + getCommandUtf8(); m_dbConnection.send(cmd, [this](Expected> exp_res, qint64 ) { if (exp_res.valid()) { // Process explain data seperately auto res = exp_res.get(); std::thread([this,res]() { std::shared_ptr explain; if (res->cols() == 1 && res->rows() == 1) { std::string s = res->val(0, 0); Json::Value root; // will contains the root value after parsing. Json::Reader reader; bool parsingSuccessful = reader.parse(s, root); if (parsingSuccessful) { explain = ExplainRoot::createFromJson(root); } } m_win->QueueTask([this, explain]() { explain_ready(explain); }); }).detach(); } }); } void QueryTab::cancel() { m_dbConnection.cancel(); } void QueryTab::setFileName(const QString &filename) { m_fileName = filename; QFileInfo fileInfo(filename); QString fn(fileInfo.fileName()); setTabCaption(fn, m_fileName); } bool QueryTab::continueWithoutSavingWarning() { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); msgBox.setText(QString("Save changes in document \"%1\" before closing?").arg(m_fileName)); msgBox.setInformativeText("The changes will be lost when you choose Discard."); msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Cancel); int ret = msgBox.exec(); if (ret == QMessageBox::Save) { if (!save()) { // save failed or was a saveAs and was cancelled, don't close! ret = QMessageBox::Cancel; } } return ret != QMessageBox::Cancel; } bool QueryTab::saveSqlTo(const QString &filename) { bool result = false; QFile file(filename); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream.setCodec("utf-8"); QString text = ui->queryEdit->toPlainText(); stream << text; /* QTextDocument *doc = ui->queryEdit->document(); QTextBlock block = doc->firstBlock(); while (stream.status() == QTextStream::Ok && block.isValid()) { QString plain = block.text(); stream << plain << "\n"; block = block.next(); } */ stream.flush(); if (stream.status() == QTextStream::Ok) { m_queryTextChanged = false; result = true; } } return result; } QString QueryTab::promptUserForSaveSqlFilename() { QString home_dir = QStandardPaths::locate(QStandardPaths::HomeLocation, "", QStandardPaths::LocateDirectory); return QFileDialog::getSaveFileName(this, tr("Save query"), home_dir, tr("SQL file (*.sql)")); } void QueryTab::queryTextChanged() { m_queryTextChanged = true; } void QueryTab::connectionStateChanged(ASyncDBConnection::State state) { QTabWidget *tabwidget = getTabWidget(); if (tabwidget) { int i = tabwidget->indexOf(this); QString iconname; switch (state) { case ASyncDBConnection::State::NotConnected: case ASyncDBConnection::State::Connecting: iconname = ":/icons/16x16/document_red.png"; break; case ASyncDBConnection::State::Connected: iconname = ":/icons/16x16/document_green.png"; break; case ASyncDBConnection::State::QuerySend: case ASyncDBConnection::State::CancelSend: iconname = ":/icons/16x16/document_yellow.png"; break; case ASyncDBConnection::State::Terminating: break; } tabwidget->setTabIcon(i, QIcon(iconname)); } } void QueryTab::addLog(QString s) { QTextCursor text_cursor = QTextCursor(ui->edtLog->document()); text_cursor.movePosition(QTextCursor::End); text_cursor.insertText(s + "\r\n"); } void QueryTab::receiveNotice(Pgsql::ErrorDetails notice) { ui->messagesEdit->append(QString::fromStdString(notice.errorMessage)); ui->messagesEdit->append(QString::fromStdString(notice.severity)); ui->messagesEdit->append(QString("At position: %1").arg(notice.statementPosition)); ui->messagesEdit->append(QString::fromStdString("State: " + notice.state)); ui->messagesEdit->append(QString::fromStdString("Primary: " + notice.messagePrimary)); ui->messagesEdit->append(QString::fromStdString("Detail: " + notice.messageDetail)); ui->messagesEdit->append(QString::fromStdString("Hint: " + notice.messageHint)); ui->messagesEdit->append(QString::fromStdString("Context: " + notice.context)); // std::string state; ///< PG_DIAG_SQLSTATE Error code as listed in https://www.postgresql.org/docs/9.5/static/errcodes-appendix.html // std::string severity; // std::string messagePrimary; // std::string messageDetail; // std::string messageHint; // int statementPosition; ///< First character is one, measured in characters not bytes! // std::string context; // int internalPosition; // std::string internalQuery; // std::string schemaName; // std::string tableName; // std::string columnName; // std::string datatypeName; // std::string constraintName; // std::string sourceFile; // std::string sourceLine; // std::string sourceFunction; // QTextCursor cursor = ui->messagesEdit->textCursor(); // cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); // QTextTable *table = cursor.insertTable(4, 2); // if (table) { // table->cellAt(1, 0).firstCursorPosition().insertText("State"); // table->cellAt(1, 1).firstCursorPosition().insertText(QString::fromStdString(notice.state)); // table->cellAt(2, 0).firstCursorPosition().insertText("Primary"); // table->cellAt(2, 1).firstCursorPosition().insertText(QString::fromStdString(notice.messagePrimary)); // table->cellAt(3, 0).firstCursorPosition().insertText("Detail"); // table->cellAt(3, 1).firstCursorPosition().insertText(QString::fromStdString(notice.messageDetail)); // } // syntax error at or near "limit // statementPosition } void QueryTab::startConnect() { m_dbConnection.setupConnection(m_config); } void QueryTab::explain_ready(ExplainRoot::SPtr explain) { m_stopwatch.stop(); if (explain) { addLog("Explain ready"); QString times_str; if (explain->totalRuntime > 0.f) times_str = QString("Total time: %1").arg( msfloatToHumanReadableString(explain->totalRuntime)); else times_str = QString("Execution time: %1, Planning time: %2").arg( msfloatToHumanReadableString(explain->executionTime) , msfloatToHumanReadableString(explain->planningTime)); ui->lblTimes->setText(times_str); explainModel.reset(new QueryExplainModel(nullptr, explain)); ui->explainTreeView->setModel(explainModel.get()); ui->explainTreeView->expandAll(); ui->explainTreeView->setColumnWidth(0, 200); ui->explainTreeView->setColumnWidth(1, 80); ui->explainTreeView->setColumnWidth(2, 80); ui->explainTreeView->setColumnWidth(3, 80); ui->explainTreeView->setColumnWidth(4, 80); ui->explainTreeView->setColumnWidth(5, 80); ui->explainTreeView->setColumnWidth(6, 600); ui->tabWidget->setCurrentWidget(ui->explainTab); // statusBar()->showMessage(tr("Explain ready.")); } else { addLog("Explain no result"); ui->tabWidget->setCurrentWidget(ui->messageTab); // statusBar()->showMessage(tr("Explain failed.")); } } QString QueryTab::getCommand() const { QString command; QTextCursor cursor = ui->queryEdit->textCursor(); if (cursor.hasSelection()) { command = cursor.selection().toPlainText(); } else { command = ui->queryEdit->toPlainText(); } return command; } std::string QueryTab::getCommandUtf8() const { return getCommand().toUtf8().data(); } QTabWidget *QueryTab::getTabWidget() { QWidget * w = parentWidget(); QWidget * p = w->parentWidget(); QTabWidget *tw = dynamic_cast(p); return tw; } void QueryTab::setTabCaption(const QString &caption, const QString &tooltip) { QTabWidget *tabwidget = getTabWidget(); if (tabwidget) { int i = tabwidget->indexOf(this); if (i >= 0) { tabwidget->setTabText(i, caption); tabwidget->setTabToolTip(i, tooltip); } } } void QueryTab::query_ready(Expected> exp_res, qint64 elapsedms) { if (exp_res.valid()) { auto dbres = exp_res.get(); 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()); 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"); if (resultList.size() == 1) ui->tabWidget->setCurrentWidget(trw); } else { if (st == PGRES_COMMAND_OK) { 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); } else { // if (st == PGRES_EMPTY_QUERY) { // statusBar()->showMessage(tr("Empty query.")); // } // else if (st == PGRES_COPY_OUT) { // statusBar()->showMessage(tr("COPY OUT.")); // } // else if (st == PGRES_COPY_IN) { // statusBar()->showMessage(tr("COPY IN.")); // } // else if (st == PGRES_BAD_RESPONSE) { // statusBar()->showMessage(tr("BAD RESPONSE.")); // } // else if (st == PGRES_NONFATAL_ERROR) { // statusBar()->showMessage(tr("NON FATAL ERROR.")); // } // else if (st == PGRES_FATAL_ERROR) { // statusBar()->showMessage(tr("FATAL ERROR.")); // } // else if (st == PGRES_COPY_BOTH) { // statusBar()->showMessage(tr("COPY BOTH shouldn't happen is for replication.")); // } // else if (st == PGRES_SINGLE_TUPLE) { // statusBar()->showMessage(tr("SINGLE TUPLE result.")); // } // else { // statusBar()->showMessage(tr("No tuples returned, possibly an error...")); // } ui->tabWidget->setCurrentWidget(ui->messageTab); receiveNotice(dbres->diagDetails()); } } } else { m_stopwatch.stop(); addLog("query_ready with NO result"); } } else { // we have an error } } void QueryTab::clearResult() { for (auto e : resultList) delete e; resultList.clear(); } void QueryTab::copyQueryAsCString() { // QString command; // QTextCursor cursor = ui->queryEdit->textCursor(); // if (cursor.hasSelection()) { // command = cursor.selection().toPlainText(); // } // else { // command = ui->queryEdit->toPlainText(); // } QString command = getCommand(); QString cs = ConvertToMultiLineCString(command); QApplication::clipboard()->setText(cs); } void QueryTab::copyQueryAsRawCppString() { //auto sql = getAllOrSelectedSql(); QString command = getCommand(); QString cs = ConvertToMultiLineRawCppString(command); QApplication::clipboard()->setText(cs); } void QueryTab::exportData(const QString &file_name) { auto widget = ui->tabWidget->currentWidget(); auto fi = std::find(resultList.begin(), resultList.end(), widget); if (fi != resultList.end()) { TuplesResultWidget* rw = *fi; rw->exportData(file_name); } }