From 3a8cc3d7f0e05b82461fdd44878f1cc27506f379 Mon Sep 17 00:00:00 2001 From: Eelke Klein Date: Tue, 27 Dec 2016 15:41:11 +0100 Subject: [PATCH] Het maken van de DB connectie gebeurd nu asynchroon. --- PgsqlConn.cpp | 44 +++++++++++++++++++++++++-- PgsqlConn.h | 25 ++++++++++++++-- mainwindow.cpp | 64 +++++++++++++++++++++++++++++++++++---- mainwindow.h | 18 +++++++++++ sqlhighlighter.cpp | 74 +++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 207 insertions(+), 18 deletions(-) diff --git a/PgsqlConn.cpp b/PgsqlConn.cpp index 4573178..6e5fdaf 100644 --- a/PgsqlConn.cpp +++ b/PgsqlConn.cpp @@ -114,6 +114,26 @@ bool Connection::connect(const char *params) return result; } +bool Connection::connectStart(const char* params) +{ + conn = PQconnectStart(params); + return conn != nullptr; +} + +PostgresPollingStatusType Connection::connectPoll() +{ + return PQconnectPoll(conn); +} + +ConnStatusType Connection::status() +{ + return PQstatus(conn); +} + +int Connection::socket() +{ + return PQsocket(conn); +} std::string Connection::getErrorMessage() const { @@ -127,9 +147,9 @@ std::string Connection::getErrorMessage() const return result; } -Result Connection::Query(const char * query) +Result Connection::query(const char * command) { - PGresult *result = PQexec(conn, query); + PGresult *result = PQexec(conn, command); if (result) { return Result(result); } @@ -137,3 +157,23 @@ Result Connection::Query(const char * query) throw std::runtime_error("Failed to allocate result object"); } } + + + + +bool Connection::sendQuery(const char *query) +{ + int res = PQsendQuery(conn, query); + return res == 1; +} + +std::unique_ptr Connection::getResult() +{ + PGresult *r = PQgetResult(conn); + if (r) { + return std::make_unique(r); + } + else { + return nullptr; + } +} diff --git a/PgsqlConn.h b/PgsqlConn.h index e1d712f..99285fa 100644 --- a/PgsqlConn.h +++ b/PgsqlConn.h @@ -6,6 +6,8 @@ #include #include +#include + namespace Pgsql { /* @@ -106,16 +108,33 @@ namespace Pgsql { return connect(params.toUtf8().data()); } + bool connectStart(const char *params); + bool connectStart(const QString ¶ms) + { + return connectStart(params.toUtf8().data()); + } + + PostgresPollingStatusType connectPoll(); + ConnStatusType status(); + int socket(); + void close(); std::string getErrorMessage() const; - Result Query(const char * query); - Result Query(const QString &query) + Result query(const char * command); + Result query(const QString &command) { - return Query(query.toUtf8().data()); + return query(command.toUtf8().data()); } + bool sendQuery(const char * query); + std::unique_ptr getResult(); + + bool consumeInput(); + bool isBusy(); + + private: PGconn *conn = nullptr; }; diff --git a/mainwindow.cpp b/mainwindow.cpp index 46a30fc..b6760e8 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -4,6 +4,8 @@ #include "QueryResultModel.h" #include "sqlhighlighter.h" +#include + namespace pg = Pgsql; const char * test_query = @@ -29,22 +31,74 @@ MainWindow::MainWindow(QWidget *parent) : //performQuery(); - QAction *action = ui->mainToolBar->addAction("execute"); + QAction *action; + action = ui->mainToolBar->addAction("connect"); + connect(action, &QAction::triggered, this, &MainWindow::startConnect); + + action = ui->mainToolBar->addAction("execute"); connect(action, &QAction::triggered, this, &MainWindow::performQuery); + } MainWindow::~MainWindow() {} +void MainWindow::startConnect() +{ + if (connection == nullptr) { + connection = std::make_unique(); + } + QString connstr = ui->connectionStringEdit->text(); + bool ok = connection->connectStart(connstr + " application_name=Ivory client_encoding=utf8"); + if (ok && connection->status() != CONNECTION_BAD) { + // Start polling + int s = connection->socket(); + + connectingState.notifier = std::make_unique(s, QSocketNotifier::Write); + connect(connectingState.notifier.get(), &QSocketNotifier::activated, this, &MainWindow::socket_activate_connect); + + connectingState.poll_state = PGRES_POLLING_WRITING; + statusBar()->showMessage(tr("Connecting")); + } + else { + statusBar()->showMessage(tr("Connecting fail")); + } + +} + +void MainWindow::socket_activate_connect(int ) +{ + connectingState.poll_state = connection->connectPoll(); + + + if (connectingState.poll_state == PGRES_POLLING_OK) { + statusBar()->showMessage(tr("Connected")); + connectingState.notifier.reset(); + } + else if (connectingState.poll_state = PGRES_POLLING_FAILED) { + statusBar()->showMessage(tr("Connection failed")); + connectingState.notifier.reset(); + } + else if (connectingState.poll_state == PGRES_POLLING_READING) { + statusBar()->showMessage(tr("Connecting..")); + connectingState.notifier = std::make_unique(connection->socket(), QSocketNotifier::Read); + connect(connectingState.notifier.get(), &QSocketNotifier::activated, this, &MainWindow::socket_activate_connect); + } + else if (connectingState.poll_state == PGRES_POLLING_WRITING) { + statusBar()->showMessage(tr("Connecting...")); + connectingState.notifier = std::make_unique(connection->socket(), QSocketNotifier::Write); + connect(connectingState.notifier.get(), &QSocketNotifier::activated, this, &MainWindow::socket_activate_connect); + } +} + + void MainWindow::performQuery() { ui->ResultView->setModel(nullptr); resultModel.reset(); - QString connstr = ui->connectionStringEdit->text(); - pg::Connection conn; - if (conn.connect(connstr + " application_name=Ivory client_encoding=utf8")) { - pg::Result dbres = conn.Query(ui->queryEdit->toPlainText()); + if (connection->status() == CONNECTION_OK) { + pg::Result dbres = connection->query(ui->queryEdit->toPlainText()); resultModel.reset(new QueryResultModel(this , std::move(dbres))); ui->ResultView->setModel(resultModel.get()); } diff --git a/mainwindow.h b/mainwindow.h index c8ffca6..0498929 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -2,7 +2,9 @@ #define MAINWINDOW_H #include +#include #include +#include "PgsqlConn.h" class QueryResultModel; class SqlHighlighter; @@ -11,6 +13,12 @@ namespace Ui { class MainWindow; } +namespace Pgsql { + + class Connection; + +} + class MainWindow : public QMainWindow { Q_OBJECT @@ -23,10 +31,20 @@ private: std::unique_ptr ui; std::unique_ptr highlighter; + std::unique_ptr connection; std::unique_ptr resultModel; + struct { + std::unique_ptr notifier; + PostgresPollingStatusType poll_state; + } connectingState; + private slots: + + void startConnect(); + void performQuery(); + void socket_activate_connect(int socket); }; #endif // MAINWINDOW_H diff --git a/sqlhighlighter.cpp b/sqlhighlighter.cpp index 876ae74..e2d8e10 100644 --- a/sqlhighlighter.cpp +++ b/sqlhighlighter.cpp @@ -1,14 +1,21 @@ #include "SqlHighlighter.h" static const wchar_t *keywords[] = { - L"as", L"alter", L"all", L"and", L"any", L"by", L"column", L"create", L"database", L"from", L"full", L"group", L"having", - L"in", L"inner", L"join", L"left", L"not", L"or", L"order", L"outer", L"right", L"select", L"table", L"where" + L"as", L"alter", L"all", L"and", L"any", L"by", L"char", L"column", L"create", L"database", L"date", L"from", L"full", L"group", L"having", + L"in", L"inner", L"int", L"join", L"left", L"not", L"numeric", L"or", L"order", L"outer", L"right", L"select", L"smallint", L"table", L"time", + L"timestamp", L"timestamptz", L"varchar", L"where" }; + +static const wchar_t *operators[] = { + L"+", L"-", L"*", L"/", L"<", L">", L"<=", L">=", L"<>", L"!=", L"~" +}; + + //static auto types = R"-(bigint|boolean|char|character varying|date|int[248]|integer|numeric|smallint|time|timestamp(?:tz)?|timestamp(?:\s+with\s+timezone)?|varchar)-"; //static auto err = R"-(left|right|inner|outer)-"; - static_assert(sizeof(keywords)/4 == 25, - "sizeof keywords"); +// static_assert(sizeof(keywords)/4 == 25, +// "sizeof keywords"); SqlHighlighter::SqlHighlighter(QTextDocument *parent) @@ -37,10 +44,61 @@ SqlHighlighter::SqlHighlighter(QTextDocument *parent) // } } -void NextBasicToken(const QString &in, int ofs, int &start, int &length) -{ - // Basically parses for white space and chops based on white space - // it does also recognize comments and quoted strings/identifiers +namespace { + + // Advances ofs to first whitespace or end of string, returns false at end of string + void skipWhiteSpace(const QString &in, int &ofs) + { + const int l = in.length(); + while (ofs < l && in.at(ofs).isSpace()) ++ofs; + } + + enum class BasicTokenType { + None, + End, // End of input + Symbol, // can be many things, keyword, object name, operator, .. + Comment, + QuotedString, + DollarQuotedString, + QuotedIdentifier + }; + + /** + * @brief NextBasicToken + * @param in + * @param ofs + * @param start + * @param length + * @return false when input seems invalid, it will return what it did recognize but something wasn't right, parser should try to recover + */ + bool NextBasicToken(const QString &in, int ofs, int &start, int &length, BasicTokenType &tokentype) + { + // Basically chops based on white space + // it does also recognize comments and quoted strings/identifiers + bool result = false; + + skipWhiteSpace(in, ofs); + const int len = in.length(); + while (ofs < len) { + if (ofs+1 < len && in.at(ofs) == L'-' && in.at(ofs+1) == L'-') { + // Start of comment, end of line is end of comment + + } + else if (in.at(ofs) == L'\'') { + // Start of quoted string + + } + else if (in.at(ofs) == L'"') { + // Start of quoted identifier + + } + else if (in.at(ofs) == L'/' && ofs+1 < len && in.at(ofs+1) == L'*') { + // Start of C style comment, scan for end + } + + } + return result; + } }