Builds on windows again

This commit is contained in:
eelke 2017-11-26 13:07:21 +01:00
parent 33cf39b799
commit bebb3391c3
160 changed files with 138 additions and 117 deletions

7
pglab/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
debug/
release/
DIST/
Makefile
Makefile.Debug
Makefile.Release
desktop.ini

173
pglab/ASyncDBConnection.cpp Normal file
View file

@ -0,0 +1,173 @@
#include "ASyncDBConnection.h"
#include "ScopeGuard.h"
#include <chrono>
using namespace boost::asio;
namespace {
class registerMetaTypes {
public:
registerMetaTypes()
{
qRegisterMetaType<ASyncDBConnection::State>();
qRegisterMetaType<Pgsql::ErrorDetails>();
}
} registerMetaTypes_instance;
}
ASyncDBConnection::ASyncDBConnection(boost::asio::io_service &ios)
: m_asioSock(ios)
{}
ASyncDBConnection::~ASyncDBConnection() = default;
ASyncDBConnection::State ASyncDBConnection::state() const
{
return m_state;
}
void ASyncDBConnection::setupConnection(const ConnectionConfig &config)
{
m_config = config;
auto keywords = m_config.getKeywords();
auto values = m_config.getValues();
bool ok = m_connection.connectStart(keywords, values);
// auto start = std::chrono::steady_clock::now();
if (ok && m_connection.status() != CONNECTION_BAD) {
auto sock_handle = m_connection.socket();
m_asioSock.assign(ip::tcp::v4(), sock_handle);
m_asioSock.non_blocking(true);
m_asioSock.async_write_some(null_buffers(),
[this] (boost::system::error_code ec, std::size_t s)
{ async_connect_handler(ec, s); }
);
}
}
void ASyncDBConnection::async_connect_handler(boost::system::error_code ec, std::size_t s)
{
// boost::asio::error::operation_aborted
if (ec == boost::system::errc::success) {
auto poll_state = m_connection.connectPoll();
if (poll_state == PGRES_POLLING_OK) {
// if connected return true
doStateCallback(State::Connected);
}
else if (poll_state == PGRES_POLLING_FAILED) {
doStateCallback(State::NotConnected);
}
else if (poll_state == PGRES_POLLING_READING) {
doStateCallback(State::Connecting);
m_asioSock.async_read_some(null_buffers(),
[this] (boost::system::error_code ec, std::size_t s)
{ async_connect_handler(ec, s); }
);
}
else if (poll_state == PGRES_POLLING_WRITING) {
doStateCallback(State::Connecting);
m_asioSock.async_write_some(null_buffers(),
[this] (boost::system::error_code ec, std::size_t s)
{ async_connect_handler(ec, s); }
);
}
}
}
void ASyncDBConnection::doStateCallback(State state)
{
m_state = state;
if (state == State::Connected) {
m_canceller = m_connection.getCancel();
m_connection.setNoticeReceiver(
[this](const PGresult *result) { processNotice(result); });
}
emit onStateChanged(state);
}
void ASyncDBConnection::closeConnection()
{
// SHould this be async too????
if (m_state == State::QuerySend) {
m_canceller.cancel(nullptr);
}
if (m_state != State::NotConnected) {
m_asioSock.close();
m_connection.close();
}
doStateCallback(State::NotConnected);
}
bool ASyncDBConnection::send(const std::string &command, on_result_callback on_result)
{
m_connection.sendQuery(command);
m_timer.start();
doStateCallback(State::QuerySend);
m_asioSock.async_read_some(null_buffers(),
[this, on_result] (boost::system::error_code ec, std::size_t s)
{ async_query_handler(ec, s, on_result); }
);
return true;
}
bool ASyncDBConnection::send(const std::string &command, Pgsql::Params params, on_result_callback on_result)
{
m_connection.sendQueryParams(command.c_str(), params);
m_timer.start();
doStateCallback(State::QuerySend);
m_asioSock.async_read_some(null_buffers(),
[this, on_result] (boost::system::error_code ec, std::size_t s)
{ async_query_handler(ec, s, on_result); }
);
return true;
}
void ASyncDBConnection::async_query_handler(boost::system::error_code ec, std::size_t s, on_result_callback on_result)
{
if (ec == boost::system::errc::success) {
bool finished = false;
if (m_connection.consumeInput()) {
while ( ! finished && ! m_connection.isBusy()) {
auto res = m_connection.getResult();
qint64 ms = m_timer.restart();
on_result(res, ms);
if (res == nullptr) {
m_timer.invalidate();
doStateCallback(State::Connected);
finished = true;
}
}
// else is still waiting for more data
}
else {
// error during consume
auto error_msg = m_connection.getErrorMessage();
}
//return finished;
if (!finished) {
// wait for more
m_asioSock.async_read_some(null_buffers(),
[this, on_result] (boost::system::error_code ec, std::size_t s)
{ async_query_handler(ec, s, on_result); }
);
}
}
}
bool ASyncDBConnection::cancel()
{
return m_canceller.cancel(nullptr);
}
void ASyncDBConnection::processNotice(const PGresult *result)
{
Pgsql::ErrorDetails details = Pgsql::ErrorDetails::createErrorDetailsFromPGresult(result);
emit onNotice(details);
}

76
pglab/ASyncDBConnection.h Normal file
View file

@ -0,0 +1,76 @@
#ifndef ASYNCDBCONNECTION_H
#define ASYNCDBCONNECTION_H
#include <QObject>
#include "Pgsql_Connection.h"
#include "Pgsql_Params.h"
#include "Pgsql_Result.h"
#include "Expected.h"
#include "ConnectionConfig.h"
#include <QElapsedTimer>
#include <mutex>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/io_service.hpp>
/** \brief Class that handles asynchronous execution of queries.
*
* Queries are passed to this class with a routine to call on completion
* when the result is on that routine is called.
*/
class ASyncDBConnection: public QObject {
Q_OBJECT
public:
enum class State {
NotConnected,
Connecting,
Connected, ///< connected and idle
QuerySend, ///< connected query send expecting result
CancelSend, ///< cancel send expecting result
Terminating ///< shutting down
};
using on_result_callback = std::function<void(Expected<std::shared_ptr<Pgsql::Result>>, qint64)>;
explicit ASyncDBConnection(boost::asio::io_service &ios);
~ASyncDBConnection();
State state() const;
void setupConnection(const ConnectionConfig &config);
void closeConnection();
/** Sends command to the server.
When the result is in on_result will be called directly within the thread.
If the command gives multiple results on_result will be called for each result.
*/
bool send(const std::string &command, on_result_callback on_result);
bool send(const std::string &command, Pgsql::Params params, on_result_callback on_result);
bool cancel();
signals:
void onStateChanged(ASyncDBConnection::State state);
void onNotice(Pgsql::ErrorDetails notice);
private:
Pgsql::Connection m_connection;
boost::asio::ip::tcp::socket m_asioSock;
ConnectionConfig m_config;
State m_state = State::NotConnected;
Pgsql::Canceller m_canceller;
QElapsedTimer m_timer;
void async_connect_handler(boost::system::error_code ec, std::size_t s);
void async_query_handler(boost::system::error_code ec, std::size_t s, on_result_callback on_result);
void doStateCallback(State state);
void processNotice(const PGresult *result);
};
Q_DECLARE_METATYPE(ASyncDBConnection::State);
Q_DECLARE_METATYPE(Pgsql::ErrorDetails);
#endif // ASYNCDBCONNECTION_H

25
pglab/ASyncWindow.cpp Normal file
View file

@ -0,0 +1,25 @@
#include "ASyncWindow.h"
#include <QTimer>
ASyncWindow::ASyncWindow(QWidget *parent)
: QMainWindow(parent)
{}
void ASyncWindow::QueueTask(TSQueue::t_Callable c)
{
m_taskQueue.add(c);
// Theoretically this needs to be only called if the queue was empty because otherwise it already would
// be busy emptying the queue. For now however I think it is safer to call it just to make sure.
QMetaObject::invokeMethod(this, "processCallableQueue", Qt::QueuedConnection); // queues on main thread
}
void ASyncWindow::processCallableQueue()
{
if (!m_taskQueue.empty()) {
auto c = m_taskQueue.pop();
c();
if (!m_taskQueue.empty()) {
QTimer::singleShot(0, this, SLOT(processCallableQueue()));
}
}
}

24
pglab/ASyncWindow.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef ASYNCWINDOW_H
#define ASYNCWINDOW_H
#include <QMainWindow>
#include "tsqueue.h"
class ASyncWindow : public QMainWindow {
Q_OBJECT
public:
ASyncWindow(QWidget *parent);
/* Meant to be called from other threads to pass a code block
* that has to be executed in the context of the thread of the window.
*/
void QueueTask(TSQueue::t_Callable c);
private:
TSQueue m_taskQueue;
private slots:
void processCallableQueue();
};
#endif // ASYNCWINDOW_H

281
pglab/BackupDialog.cpp Normal file
View file

@ -0,0 +1,281 @@
#include "BackupDialog.h"
#include "ui_BackupDialog.h"
#include "BackupFormatModel.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QProcess>
#include <QScrollBar>
#include <QStandardPaths>
#ifdef WIN32
# include <windows.h> // for CreateProcess flags
#endif
BackupDialog::BackupDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::BackupDialog)
{
ui->setupUi(this);
auto format_model = new BackupFormatModel(this);
ui->backupFormat->setModel(format_model);
}
BackupDialog::~BackupDialog()
{
delete ui;
}
void BackupDialog::setConfig(const ConnectionConfig &cfg)
{
m_config = cfg;
}
void BackupDialog::ConnectTo(QProcess *process)
{
disconnectCurrentProcess();
m_process = process;
if (process) {
process->setProcessChannelMode(QProcess::MergedChannels);
process->setReadChannel(QProcess::StandardOutput);
connect(process, SIGNAL(readyRead()), this, SLOT(on_process_readyRead()));
connect(process, SIGNAL(errorOccurred(QProcess::ProcessError)), this,
SLOT(on_process_errorOccurred(QProcess::ProcessError)));
connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this,
SLOT(on_process_finished(int, QProcess::ExitStatus)));
}
}
void BackupDialog::disconnectCurrentProcess()
{
if (m_process) {
disconnect(m_process, SIGNAL(readyRead()), this, SLOT(on_process_readyRead()));
disconnect(m_process, SIGNAL(errorOccurred(QProcess::ProcessError)), this,
SLOT(on_process_errorOccurred(QProcess::ProcessError)));
disconnect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this,
SLOT(on_process_finished(int, QProcess::ExitStatus)));
m_process = nullptr;
}
}
void BackupDialog::writeOutput(QString s)
{
ui->stdOutput->appendPlainText(s);
QScrollBar *bar = ui->stdOutput->verticalScrollBar();
bar->setValue(bar->maximum());
}
void BackupDialog::on_process_readyRead()
{
QByteArray data = m_process->readAllStandardOutput();
writeOutput(QString::fromUtf8(data));
}
void BackupDialog::on_process_errorOccurred(QProcess::ProcessError error)
{
QString msg;
switch (error) {
case 0:
msg = tr("Failed to start");
break;
case 1:
msg = tr("Crashed");
break;
case 2:
msg = tr("Timedout");
break;
case 3:
msg = tr("Read error");
break;
case 4:
msg = tr("Write error");
break;
case 5:
msg = tr("Unknown error");
break;
default:
msg = tr("Unexpected error");
}
auto res = QMessageBox::critical(this, "pglab", msg, QMessageBox::Close);
if (res == QMessageBox::Yes) {
QApplication::quit();
}
}
void BackupDialog::on_process_finished(int exitCode, QProcess::ExitStatus )
{
writeOutput(tr("Completed, with exitcode %1\n").arg(exitCode));
}
void BackupDialog::setParams(QStringList &args)
{
QString short_args("-");
if (ui->chkbxVerbose->checkState() == Qt::Checked)
short_args += "v";
if (ui->chkbxClean->checkState() == Qt::Checked)
short_args += "c";
if (ui->chkbxCreate->checkState() == Qt::Checked)
short_args += "C";
if (ui->chkbxIncludeBlobs->checkState() == Qt::Checked)
short_args += "b";
switch (ui->what->currentIndex()) {
case 1:
short_args += "a";
break;
case 2:
short_args += "s";
break;
}
if (ui->oids->checkState() == Qt::Checked)
short_args += "o";
if (ui->noAcl->checkState() == Qt::Checked)
short_args += "x";
if (short_args.length() > 1) // larger then one because always includes '-'
args << short_args;
// Todo check path exists and name is valid?
QFileInfo fi(QDir::fromNativeSeparators(ui->editFilename->text()));
QDir dir(fi.absolutePath());
if (!dir.exists())
dir.mkdir(fi.absolutePath());
args << "-f" << ui->editFilename->text(); // R"-(c:\temp\backup.sql)-";
int format_index = ui->backupFormat->currentIndex();
auto format_model_base = ui->backupFormat->model();
BackupFormatModel *bfm = dynamic_cast<BackupFormatModel *>(format_model_base);
if (bfm) {
QVariant v = bfm->data(bfm->index(format_index, 1));
QString format("-F");
format += v.toString();
args << format;
}
int jobs = ui->jobs->value();
if (jobs > 0)
args << "-j" << QString::number(jobs);
int compression = ui->compression->value();
if (compression >= 0)
args << "-Z" << QString::number(compression);
}
void BackupDialog::on_btnStart_clicked()
{
ui->stdOutput->clear();
//QString program = R"-(C:\Prog\bigsql\pg96\bin\pg_dump.exe)-";
QString program = "/usr/bin/pg_dump";
QStringList arguments;
setParams(arguments);
// BOOL res = AllocConsole();
// if (!res) {
// DWORD error = GetLastError();
// QMessageBox::critical(this, "pglab", tr("AllocConsole failed %1").arg(error), QMessageBox::Close);
// }
// HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
// DWORD written;
// res = WriteConsoleOutputCharacter(out, L"Hello, world!\n", 14, {0, 0}, &written);
// PROCESS_INFORMATION proc_info;
// STARTUPINFO startup_info;
// memset(&startup_info, 0, sizeof(startup_info));
// startup_info.cb = sizeof(startup_info);
// startup_info.lpReserved;
// startup_info.lpDesktop;
// startup_info.lpTitle;
// startup_info.dwX;
// startup_info.dwY;
// startup_info.dwXSize;
// startup_info.dwYSize;
// startup_info.dwXCountChars;
// startup_info.dwYCountChars;
// startup_info.dwFillAttribute;
// startup_info.dwFlags = STARTF_USESTDHANDLES;
// startup_info.wShowWindow;
// startup_info.cbReserved2;
// startup_info.lpReserved2;
// startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
// startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
// startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
// res = CreateProcess(
// LR"_(C:\Prog\build-conoutputtest-Desktop_Qt_5_8_0_MSVC2015_32bit2-Debug\debug\conoutputtest.exe)_",
// NULL, // _Inout_opt_ LPTSTR lpCommandLine,
// NULL, // _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
// NULL, // _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
// TRUE,
// 0, // _In_ DWORD dwCreationFlags,
// NULL, // _In_opt_ LPVOID lpEnvironment,
// NULL, // _In_opt_ LPCTSTR lpCurrentDirectory,
// &startup_info, // _In_ LPSTARTUPINFO lpStartupInfo,
// &proc_info // _Out_ LPPROCESS_INFORMATION lpProcessInformation
// );
// QString program = R"_(C:\prog\build-conoutputtest-Desktop_Qt_5_8_0_MSVC2015_32bit2-Debug\debug\conoutputtest.exe)_";
// QStringList arguments;
// arguments << "/C" << "DIR /S c:\\";
// Database connection paramters are passed through the environment as this is far less visible to others. Commandline
// parameters can often be viewed even if the user is not the owner of the process.
// We use the systemEnvironment as a sane default. Then we let the connection overwrite all PG variables in it.
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
m_config.writeToEnvironment(env);
//env.insert("SESSIONNAME", "Console");
auto p = new QProcess(this);
ConnectTo(p);
p->setProcessEnvironment(env);
#ifdef WIN32
p->setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args)
{
args->flags |= CREATE_NEW_CONSOLE;
args->flags &= ~DETACHED_PROCESS;
args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES;
});
#endif
p->start(program, arguments);
}
void BackupDialog::on_backupFormat_currentIndexChanged(int index)
{
int format_index = ui->backupFormat->currentIndex();
auto format_model_base = ui->backupFormat->model();
BackupFormatModel *bfm = dynamic_cast<BackupFormatModel *>(format_model_base);
if (bfm) {
QVariant v = bfm->data(bfm->index(format_index, 1));
QString format = v.toString();
bool comp_enable = (format == "c" || format == "d");
ui->compression->setEnabled(comp_enable);
if (!comp_enable)
ui->compression->setValue(-1);
bool jobs_enable = (format == "d");
ui->jobs->setEnabled(jobs_enable);
if (!jobs_enable)
ui->jobs->setValue(0);
}
}
void BackupDialog::on_selectDestination_clicked()
{
QString home_dir = QStandardPaths::locate(QStandardPaths::HomeLocation, "", QStandardPaths::LocateDirectory);
QString fn = QFileDialog::getSaveFileName(this, tr("Save backup"), home_dir,
tr("Backup file (*.backup)"));
if (!fn.isEmpty()) {
ui->editFilename->setText(QDir::toNativeSeparators(fn));
}
}

44
pglab/BackupDialog.h Normal file
View file

@ -0,0 +1,44 @@
#ifndef BACKUPDIALOG_H
#define BACKUPDIALOG_H
#include <QDialog>
#include "ConnectionConfig.h"
#include <QProcess>
namespace Ui {
class BackupDialog;
}
class QStringList;
class BackupDialog : public QDialog
{
Q_OBJECT
public:
explicit BackupDialog(QWidget *parent = 0);
~BackupDialog();
void ConnectTo(QProcess *process);
void disconnectCurrentProcess();
void setConfig(const ConnectionConfig &cfg);
private:
Ui::BackupDialog *ui;
QProcess *m_process = nullptr;
ConnectionConfig m_config;
void writeOutput(QString s);
void setParams(QStringList &args);
private slots:
void on_process_readyRead();
void on_process_errorOccurred(QProcess::ProcessError error);
void on_process_finished(int exitCode, QProcess::ExitStatus exitStatus);
void on_btnStart_clicked();
void on_backupFormat_currentIndexChanged(int index);
void on_selectDestination_clicked();
};
#endif // BACKUPDIALOG_H

306
pglab/BackupDialog.ui Normal file
View file

@ -0,0 +1,306 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BackupDialog</class>
<widget class="QDialog" name="BackupDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<height>745</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<widget class="QPushButton" name="btnStart">
<property name="geometry">
<rect>
<x>270</x>
<y>630</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>START</string>
</property>
</widget>
<widget class="QTabWidget" name="tabWidget">
<property name="geometry">
<rect>
<x>30</x>
<y>90</y>
<width>601</width>
<height>501</height>
</rect>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Tab 1</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Filename</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="editFilename"/>
</item>
<item>
<widget class="QPushButton" name="selectDestination">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Format</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="backupFormat">
<property name="modelColumn">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Jobs:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="jobs"/>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="chkbxVerbose">
<property name="text">
<string>Verbose</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Compression</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="compression">
<property name="toolTip">
<string>-1 means default, 0-9 is no compression to max compression</string>
</property>
<property name="minimum">
<number>-1</number>
</property>
<property name="maximum">
<number>9</number>
</property>
<property name="value">
<number>-1</number>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="chkbxIncludeBlobs">
<property name="text">
<string>Include blobs</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="chkbxClean">
<property name="text">
<string>Clean</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="chkbxCreate">
<property name="text">
<string>Create</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="noOwner">
<property name="text">
<string>No owner</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QCheckBox" name="oids">
<property name="text">
<string>Oids</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="what">
<item>
<property name="text">
<string>data + schema</string>
</property>
</item>
<item>
<property name="text">
<string>data only (-a)</string>
</property>
</item>
<item>
<property name="text">
<string>schema-only (-s)</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>What</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QCheckBox" name="noAcl">
<property name="text">
<string>No privileges/acl</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Tab 2</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPlainTextEdit" name="stdOutput">
<property name="palette">
<palette>
<active>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>120</red>
<green>120</green>
<blue>120</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>240</red>
<green>240</green>
<blue>240</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<family>Source Code Pro</family>
<pointsize>10</pointsize>
</font>
</property>
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="plainText">
<string>Test text.</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
<resources/>
<connections/>
</ui>

66
pglab/BackupRestore.cpp Normal file
View file

@ -0,0 +1,66 @@
#include <QProcess>
#include <QProcessEnvironment>
#include "ConnectionConfig.h"
void setupEnvironment(const ConnectionConfig &cc)
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert("PGHOST", cc.host().c_str());
env.insert("PGPORT", QString::number(cc.port()));
env.insert("PGDATABASE", cc.dbname().c_str());
env.insert("PGUSER", cc.user().c_str());
env.insert("PGPASSWORD", cc.password().c_str());
// QProcess process;
// process.setProcessEnvironment(env);
// process.start("myapp");
}
// QString toolpath;
// Executable = toolpath + "\\pg_restore.exe";
// ExecutableDump = toolpath + "\\pg_dump.exe";
//---------------------------------------------------------------------------
void Backup(QString dest_file_name)
{
// QString command_line = "\"" + ExecutableDump + "\" ";
// // Add commandline options
// command_line += "--verbose ";
// command_line += "--format=c "; // user option
// command_line += " --file=\"" + dest_file_name + "\"";
QStringList args;
args << "--verbose";
args << "--format=c";
args << "--file=\"" + dest_file_name + "\"";
QProcess process;
process.setProgram("pg_dump.exe");
process.setArguments(args);
process.start();
// process.setProcessEnvironment(env);
// process.start("myapp");
}
//---------------------------------------------------------------------------
void Restore(QString bron_file_name)
{
QProcess process;
process.setProgram("pg_restore.exe");
// QString command_line = "\"" + ExecutableRestore + "\" ";
// command_line += "--verbose --no-password --no-privileges --no-owner --dbname=\"" + DbConfig.DbNaam + "\" \"" + bron_file_name + "\"";
// ExecProg = new ExecuteProgram;
// ExecProg->Startup(command_line, Env.GetRawData(), Handle, WM_USER);
}

69
pglab/CMakeLists.txt Normal file
View file

@ -0,0 +1,69 @@
cmake_minimum_required(VERSION 3.2)
project(pglab)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
add_executable(pglab
ASyncDBConnection.cpp
ASyncWindow.cpp
BackupDialog.cpp
BackupRestore.cpp
CodeBuilderConfiguration.cpp
ConnectionConfig.cpp
ConnectionList.cpp
ConnectionListModel.cpp
ConnectionManagerWindow.cpp
DatabaseInspectorWidget.cpp
DatabasesTableModel.cpp
DatabaseWindow.cpp
ExplainTreeModelItem.cpp
GlobalIoService.cpp
jsoncpp.cpp
main.cpp
MainWindow.cpp
MasterController.cpp
OpenDatabase.cpp
ParamListModel.cpp
ParamTypeDelegate.cpp
PgAuthIdContainer.cpp
PgAuthId.cpp
PgClass.cpp
PgDatabaseCatalogue.cpp
PgDatabaseContainer.cpp
PgDatabase.cpp
PgNamespace.cpp
PgTypeContainer.cpp
PgType.cpp
ProcessStdioWidget.cpp
QueryExplainModel.cpp
QueryResultModel.cpp
QueryTab.cpp
RolesTableModel.cpp
ServerWindow.cpp
SqlSyntaxHighlighter.cpp
stopwatch.cpp
tsqueue.cpp
tuplesresultwidget.cpp
TypeSelectionItemModel.cpp
util.cpp
resources.qrc
)
set_target_properties(pglab PROPERTIES
CXX_STANDARD 14
CXX_STANDARD_REQUIRED ON)
if(CMAKE_COMPILER_IS_GNUCXX)
# target_compile_options(pglab PRIVATE -Wall -fpic -march=native )
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og")
endif()
target_link_libraries( pglab
core
pgsql
boost-system
Qt5::Widgets
pthread
)

View file

@ -0,0 +1,8 @@
#include "CodeBuilderConfiguration.h"
#include "Pgsql_Result.h"
QString CodeBuilder::GenClassDefinition(const Pgsql::Result &result) const
{
return QString();
}

View file

@ -0,0 +1,120 @@
#pragma once
#include <QString>
#include <QStringBuilder>
#include <libpq-fe.h>
namespace Pgsql { class Result; }
/*
class PgAuthId {
public:
PgAuthId();
Oid oid = InvalidOid;
QString name;
bool super;
bool inherit;
bool createRole;
bool createDB;
bool canlogin;
bool replication;
bool bypassRls;
int connLimit;
QDateTime validUntil;*/
/** Defines how a database result fieldname should be converted into a variable
* name in the target language.
*
*/
class VarNameManglingRules {
public:
enum class CollisionHandling {
Restrict, ///< An error will be reported and no code generated
Fqn, ///< Turn into fully qualified name (table_column)
Number ///< A number will be appended to fields that have the same name
};
enum class CaseConversion {
AsIs,
Upper,
Lower
};
QString replaceSpaceWith; ///< default is empty string which means remove spaces
CollisionHandling CollisionHandling = CollisionHandling::Restrict;
CaseConversion caseConversion = CaseConversion::AsIs; ///< overall case conversion rule
CaseConversion caseFirstChar = CaseConversion::AsIs; ///< case of the first char
bool underscoreToCamel = false; ///< remove underscores and make first char after underscore uppercase
};
/**
*
*/
class LanguageConfig {
public:
/** Default template for declaring a variable of the correct type.
* exmaple: "{$type} {$varname};"
*/
QString varDeclTemplate;
VarNameManglingRules varNaming;
enum class VariableStrategy {
UseLocalVariables,
DeclareClass
};
QString classStartTemplate;
QString classEndTemplate;
QString classFieldTemplate;
};
/**
*
* There are some default fallbacks in place
* - smallint > integer
* - integer > bigint
* - int2 > smallint
* - int4 > integer
* - int8 > bigint
*
* - float > double
* - text > varchar
*/
class TypeConfig {
public:
/** The following template allows you to derive the variable name from the result fieldname.
*
* {$} in the suplied value will be replaced with the name of the result field.
*
* example: {$}
*/
QString varNameTemplate;
QString typeIdent;
QString varDeclTemplate; ///< Overrules the default template in the language config
// Mapping() = default;
// Mapping(Oid oid, QString lang_type_ident)
// : db_oid(oid), lang_type(lang_type_ident)
// {}
//
// bool operator < (const Mapping &rhs) const
// {
// return db_oid < rhs.db_oid;
// }
};
class TypeMappings {
public:
private:
};
class CodeBuilderConfiguration {
public:
};
class CodeBuilder {
public:
QString GenClassDefinition(const Pgsql::Result &result) const;
};

320
pglab/ConnectionConfig.cpp Normal file
View file

@ -0,0 +1,320 @@
#include "ConnectionConfig.h"
#include "util.h"
#include <QCoreApplication>
#include <QProcessEnvironment>
namespace {
struct {
SslMode mode;
const char* string;
} SslModeStringTable[] = {
{ SslMode::disable, "disable" },
{ SslMode::allow, "allow" },
{ SslMode::prefer, "prefer" },
{ SslMode::require, "require" },
{ SslMode::verify_ca, "verify-ca" },
{ SslMode::verify_full, "verify-full" }
};
inline const char *valuePtr(const std::string &v)
{
return v.empty() ? nullptr : v.c_str();
}
} // end unnamed namespace
std::string SslModeToString(SslMode sm)
{
std::string result;
for (auto e : SslModeStringTable) {
if (e.mode == sm) {
result = e.string;
}
}
return result;
}
SslMode StringToSslMode(std::string s)
{
SslMode result = SslMode::allow;
for (auto e : SslModeStringTable) {
if (e.string == s) {
result = e.mode;
}
}
return result;
}
std::vector<const char*> ConnectionConfig::s_keywords = {
"host", "hostaddr", "port", "user", "password", "dbname",
"sslmode", "sslcert", "sslkey", "sslrootcrt", "sslcrl",
"client_encoding", "application_name", nullptr };
ConnectionConfig::ConnectionConfig()
: m_applicationName(QCoreApplication::applicationName().toUtf8().data())
{}
void ConnectionConfig::setName(std::string desc)
{
if (m_name != desc) {
m_dirty = true;
m_name = std::move(desc);
}
}
const std::string& ConnectionConfig::name() const
{
return m_name;
}
void ConnectionConfig::setHost(std::string host)
{
if (m_host != host) {
m_dirty = true;
m_host = std::move(host);
}
}
const std::string& ConnectionConfig::host() const
{
return m_host;
}
void ConnectionConfig::setHostAddr(std::string v)
{
if (m_hostaddr != v) {
m_dirty = true;
m_hostaddr = std::move(v);
}
}
const std::string& ConnectionConfig::hostAddr() const
{
return m_hostaddr;
}
void ConnectionConfig::setPort(unsigned short port)
{
auto p = std::to_string(port);
if (m_port != p) {
m_dirty = true;
m_port = p;
}
}
unsigned short ConnectionConfig::port() const
{
return static_cast<unsigned short>(std::stoi(m_port));
}
void ConnectionConfig::setUser(std::string v)
{
if (m_user != v) {
m_dirty = true;
m_user = std::move(v);
}
}
const std::string& ConnectionConfig::user() const
{
return m_user;
}
void ConnectionConfig::setPassword(std::string v)
{
if (m_password != v) {
m_dirty = true;
m_password = std::move(v);
}
}
const std::string& ConnectionConfig::password() const
{
return m_password;
}
void ConnectionConfig::setDbname(std::string v)
{
if (m_dbname != v) {
m_dirty = true;
m_dbname = std::move(v);
}
}
const std::string& ConnectionConfig::dbname() const
{
return m_dbname;
}
void ConnectionConfig::setSslMode(SslMode m)
{
auto v = SslModeToString(m);
if (m_sslMode != v) {
m_dirty = true;
m_sslMode = v;
}
}
SslMode ConnectionConfig::sslMode() const
{
return StringToSslMode(m_sslMode);
}
void ConnectionConfig::setSslCert(std::string v)
{
if (m_sslCert != v) {
m_dirty = true;
m_sslCert = std::move(v);
}
}
const std::string& ConnectionConfig::sslCert() const
{
return m_sslCert;
}
void ConnectionConfig::setSslKey(std::string v)
{
if (m_sslKey != v) {
m_dirty = true;
m_sslKey = std::move(v);
}
}
const std::string& ConnectionConfig::sslKey() const
{
return m_sslKey;
}
void ConnectionConfig::setSslRootCert(std::string v)
{
if (m_sslRootCert != v) {
m_dirty = true;
m_sslRootCert = std::move(v);
}
}
const std::string& ConnectionConfig::sslRootCert() const
{
return m_sslRootCert;
}
void ConnectionConfig::setSslCrl(std::string v)
{
if (m_sslCrl != v) {
m_dirty = true;
m_sslCrl = std::move(v);
}
}
const std::string& ConnectionConfig::sslCrl() const
{
return m_sslCrl;
}
const char * const * ConnectionConfig::getKeywords() const
{
return s_keywords.data();
}
const char * const * ConnectionConfig::getValues() const
{
m_values.resize(s_keywords.size(), nullptr);
m_values[0] = valuePtr(m_host);
m_values[1] = valuePtr(m_hostaddr);
m_values[2] = valuePtr(m_port);
m_values[3] = valuePtr(m_user);
m_values[4] = valuePtr(m_password);
m_values[5] = valuePtr(m_dbname);
m_values[6] = valuePtr(m_sslMode);
m_values[7] = valuePtr(m_sslCert);
m_values[8] = valuePtr(m_sslKey);
m_values[9] = valuePtr(m_sslRootCert);
m_values[10] = valuePtr(m_sslCrl);
m_values[11] = "utf8";
m_values[12] = valuePtr(m_applicationName);
return m_values.data();
}
bool ConnectionConfig::isSameDatabase(const ConnectionConfig &rhs) const
{
return m_host == rhs.m_host
&& m_hostaddr == rhs.m_hostaddr
&& m_port == rhs.m_port
&& m_user == rhs.m_user
&& m_password == rhs.m_password
&& m_dbname == rhs.m_dbname;
}
bool ConnectionConfig::dirty() const
{
return m_dirty;
}
void ConnectionConfig::clean()
{
m_dirty = false;
}
/*
PGHOST behaves the same as the host connection parameter.
PGHOSTADDR behaves the same as the hostaddr connection parameter. This can be set instead of or in addition to PGHOST to avoid DNS lookup overhead.
PGPORT behaves the same as the port connection parameter.
PGDATABASE behaves the same as the dbname connection parameter.
PGUSER behaves the same as the user connection parameter.
PGPASSWORD behaves the same as the password connection parameter. Use of this environment variable is not recommended for security reasons, as some operating systems allow non-root users to see process environment variables via ps; instead consider using the ~/.pgpass file (see Section 31.15).
PGPASSFILE specifies the name of the password file to use for lookups. If not set, it defaults to ~/.pgpass (see Section 31.15).
PGSERVICE behaves the same as the service connection parameter.
PGSERVICEFILE specifies the name of the per-user connection service file. If not set, it defaults to ~/.pg_service.conf (see Section 31.16).
PGREALM sets the Kerberos realm to use with PostgreSQL, if it is different from the local realm. If PGREALM is set, libpq applications will attempt authentication with servers for this realm and use separate ticket files to avoid conflicts with local ticket files. This environment variable is only used if GSSAPI authentication is selected by the server.
PGOPTIONS behaves the same as the options connection parameter.
PGAPPNAME behaves the same as the application_name connection parameter.
PGSSLMODE behaves the same as the sslmode connection parameter.
PGREQUIRESSL behaves the same as the requiressl connection parameter.
PGSSLCOMPRESSION behaves the same as the sslcompression connection parameter.
PGSSLCERT behaves the same as the sslcert connection parameter.
PGSSLKEY behaves the same as the sslkey connection parameter.
PGSSLROOTCERT behaves the same as the sslrootcert connection parameter.
PGSSLCRL behaves the same as the sslcrl connection parameter.
PGREQUIREPEER behaves the same as the requirepeer connection parameter.
PGKRBSRVNAME behaves the same as the krbsrvname connection parameter.
PGGSSLIB behaves the same as the gsslib connection parameter.
PGCONNECT_TIMEOUT behaves the same as the connect_timeout connection parameter.
PGCLIENTENCODING behaves the same as the client_encoding connection parameter.
*/
void ConnectionConfig::writeToEnvironment(QProcessEnvironment &env) const
{
strToEnv(env, "PGHOST", m_host);
strToEnv(env, "PGHOSTADDR", m_hostaddr);
strToEnv(env, "PGPORT", m_port);
strToEnv(env, "PGDATABASE", m_dbname);
strToEnv(env, "PGUSER", m_user);
strToEnv(env, "PGPASSWORD", m_password);
strToEnv(env, "PGSSLMODE", m_sslMode);
strToEnv(env, "PGSSLCERT", m_sslCert);
strToEnv(env, "PGSSLKEY", m_sslKey);
strToEnv(env, "PGSSLROOTCERT", m_sslRootCert);
strToEnv(env, "PGSSLCRL", m_sslCrl);
strToEnv(env, "PGSSLCOMPRESSION", "0");
strToEnv(env, "PGCONNECT_TIMEOUT", "10");
env.insert("PGCLIENTENCODING", "utf8");
env.remove("PGREQUIRESSL");
}
void ConnectionConfig::strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val)
{
if (val.empty())
env.remove(var);
else
env.insert(var, stdStrToQ(val));
}

102
pglab/ConnectionConfig.h Normal file
View file

@ -0,0 +1,102 @@
#ifndef CONNECTION_H
#define CONNECTION_H
#include <vector>
#include <string>
enum class SslMode {
disable=0,
allow=1,
prefer=2,
require=3,
verify_ca=4,
verify_full=5
};
enum class PasswordMode {
Unsave,
Encrypted,
DontSave
};
class QProcessEnvironment;
class QString;
class ConnectionConfig {
public:
ConnectionConfig();
void setName(std::string desc);
const std::string& name() const;
void setHost(std::string host);
const std::string& host() const;
void setHostAddr(std::string v);
const std::string& hostAddr() const;
void setPort(unsigned short port);
unsigned short port() const;
void setUser(std::string v);
const std::string& user() const;
void setPassword(std::string v);
const std::string& password() const;
void setDbname(std::string v);
const std::string& dbname() const;
void setSslMode(SslMode m);
SslMode sslMode() const;
void setSslCert(std::string v);
const std::string& sslCert() const;
void setSslKey(std::string v);
const std::string& sslKey() const;
void setSslRootCert(std::string v);
const std::string& sslRootCert() const;
void setSslCrl(std::string v);
const std::string& sslCrl() const;
const char * const * getKeywords() const;
const char * const * getValues() const;
bool isSameDatabase(const ConnectionConfig &rhs) const;
void writeToEnvironment(QProcessEnvironment &env) const;
bool dirty() const;
void clean();
private:
std::string m_name;
std::string m_host;
std::string m_hostaddr;
std::string m_port = "5432";
std::string m_user;
std::string m_password;
std::string m_dbname;
std::string m_sslMode;
std::string m_sslCert;
std::string m_sslKey;
std::string m_sslRootCert;
std::string m_sslCrl;
std::string m_applicationName;
bool m_dirty = false;
static void strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val);
static std::vector<const char*> s_keywords;
mutable std::vector<const char*> m_values;
};
#endif // CONNECTION_H

148
pglab/ConnectionList.cpp Normal file
View file

@ -0,0 +1,148 @@
#include "ConnectionList.h"
#include "ScopeGuard.h"
#include "util.h"
#include <QDir>
#include <QStandardPaths>
#include <QSettings>
namespace {
/** Saves a connection configuration.
Before calling this you may want to call beginGroup.
*/
void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc)
{
settings.setValue("name", stdStrToQ(cc.name()));
settings.setValue("host", stdStrToQ(cc.host()));
settings.setValue("hostaddr", stdStrToQ(cc.hostAddr()));
settings.setValue("port", cc.port());
settings.setValue("user", stdStrToQ(cc.user()));
settings.setValue("password", stdStrToQ(cc.password()));
settings.setValue("dbname", stdStrToQ(cc.dbname()));
settings.setValue("sslmode", (int)cc.sslMode());
settings.setValue("sslcert", stdStrToQ(cc.sslCert()));
settings.setValue("sslkey", stdStrToQ(cc.sslKey()));
settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert()));
settings.setValue("sslcrl", stdStrToQ(cc.sslCrl()));
}
void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc)
{
cc.setName(qvarToStdStr(settings.value("name")));
cc.setHost(qvarToStdStr(settings.value("host")));
cc.setHostAddr(qvarToStdStr(settings.value("hostaddr")));
cc.setPort(settings.value("port", 5432).toInt());
cc.setUser(qvarToStdStr(settings.value("user")));
// std::string encpw = qvarToStdStr(settings.value("encryptedpw"));
// if (encpw.empty()) {
cc.setPassword(qvarToStdStr(settings.value("password")));
// }
// else {
// cc.setEncryptedPassword(encpw);
// }
cc.setDbname(qvarToStdStr(settings.value("dbname")));
cc.setSslMode((SslMode)settings.value("sslmode").toInt());
cc.setSslCert(qvarToStdStr(settings.value("sslcert")));
cc.setSslKey(qvarToStdStr(settings.value("sslkey")));
cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert")));
cc.setSslCrl(qvarToStdStr(settings.value("sslcrl")));
}
} // end of unnamed namespace
/// \todo should return an expected as creation of the folder can fail
QString ConnectionList::iniFileName()
{
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
path += "/connections.ini";
return path;
}
ConnectionList::ConnectionList()
{
}
int ConnectionList::createNew()
{
m_connections.emplace_back(QUuid::createUuid(), ConnectionConfig());
return m_connections.size()-1;
}
void ConnectionList::remove(int idx, int count)
{
auto f = m_connections.begin() + idx;
auto l = f + count;
deleteFromIni(f, l);
m_connections.erase(f, l);
}
void ConnectionList::deleteFromIni(t_Connections::iterator begin, t_Connections::iterator end)
{
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
for (auto i = begin; i != end; ++i) {
settings.remove(i->m_uuid.toString());
}
}
void ConnectionList::load()
{
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
auto groups = settings.childGroups();
for (auto grp : groups) {
if (grp == "c_IniGroupSecurity") {
// Read security settings
} else {
QUuid uuid(grp);
if ( ! uuid.isNull() ) {
settings.beginGroup(grp);
SCOPE_EXIT { settings.endGroup(); };
ConnectionConfig cc;
LoadConnectionConfig(settings, cc);
m_connections.emplace_back(uuid, cc);
}
}
}
}
void ConnectionList::save()
{
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
for (auto& e : m_connections) {
settings.beginGroup(e.m_uuid.toString());
SCOPE_EXIT { settings.endGroup(); };
SaveConnectionConfig(settings, e.m_config);
e.m_config.clean();
}
settings.sync();
}
void ConnectionList::save(int index)
{
if (index >= 0 && index < (int)m_connections.size()) {
auto& e = m_connections[index];
if (e.m_config.dirty()) {
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
settings.beginGroup(e.m_uuid.toString());
SaveConnectionConfig(settings, e.m_config);
e.m_config.clean();
settings.sync();
}
}
}

54
pglab/ConnectionList.h Normal file
View file

@ -0,0 +1,54 @@
#ifndef CONNECTIONLIST_H
#define CONNECTIONLIST_H
#include "ConnectionConfig.h"
#include <QString>
#include <QUuid>
#include <vector>
#include "Expected.h"
class ConnectionList {
private:
static QString iniFileName();
public:
ConnectionList();
int size() const { return m_connections.size(); }
ConnectionConfig& getConfigByIdx(int idx)
{
return m_connections.at(idx).m_config;
}
int createNew();
void remove(int idx, int count);
void load();
void save();
void save(int index);
private:
class LijstElem {
public:
QUuid m_uuid;
ConnectionConfig m_config;
LijstElem(const QUuid id, const ConnectionConfig &cfg)
: m_uuid(id), m_config(cfg)
{}
};
using t_Connections = std::vector<LijstElem>;
t_Connections m_connections;
void deleteFromIni(t_Connections::iterator begin, t_Connections::iterator end);
};
#endif // CONNECTIONLIST_H

View file

@ -0,0 +1,182 @@
#include "ConnectionListModel.h"
#include "ConnectionList.h"
#include "ScopeGuard.h"
#include "util.h"
#include <botan/cryptobox.h>
ConnectionListModel::ConnectionListModel(ConnectionList *conns, QObject *parent)
: QAbstractListModel(parent)
, m_connections(conns)
{
}
ConnectionListModel::~ConnectionListModel()
{
}
int ConnectionListModel::rowCount(const QModelIndex &parent) const
{
int result = 0;
if (parent == QModelIndex()) {
result = m_connections->size();
}
return result;
}
int ConnectionListModel::columnCount(const QModelIndex &/*parent*/) const
{
return 7;
}
QVariant ConnectionListModel::data(const QModelIndex &index, int role) const
{
QVariant result;
if (role == Qt::DisplayRole || role == Qt::EditRole) {
int row = index.row();
int col = index.column();
const ConnectionConfig& cfg = m_connections->getConfigByIdx(row);
switch (col) {
case 0:
result = makeLongDescription(cfg);
break;
case 1:
result = stdStrToQ(cfg.name());
break;
case 2:
result = stdStrToQ(cfg.host());
break;
case 3:
result = cfg.port();
break;
case 4:
result = stdStrToQ(cfg.user());
break;
case 5:
result = stdStrToQ(cfg.password());
break;
case 6:
result = stdStrToQ(cfg.dbname());
break;
}
}
return result;
}
bool ConnectionListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
bool result = false;
if (role == Qt::EditRole) {
int row = index.row();
int col = index.column();
// auto& elem = m_connections.at(row);
// elem.m_dirty = true;
// ConnectionConfig& cfg = elem.m_config;
ConnectionConfig& cfg = m_connections->getConfigByIdx(row);
if (col > 0) {
result = true;
}
switch (col) {
case 0:
break;
case 1:
cfg.setName( qStrToStd(value.toString()) );
break;
case 2:
cfg.setHost( qStrToStd(value.toString()) );
break;
case 3:
cfg.setPort( value.toInt() );
break;
case 4:
cfg.setUser( qStrToStd(value.toString()) );
break;
case 5:
cfg.setPassword( qStrToStd(value.toString()) );
break;
case 6:
cfg.setDbname( qStrToStd(value.toString()) );
break;
}
}
if (result) {
emit dataChanged(index, index);
}
return result;
}
Qt::ItemFlags ConnectionListModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags result;
int row = index.row();
if (row >= 0 && row < (int)m_connections->size()) {
result = Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
}
return result;
}
QString ConnectionListModel::makeLongDescription(const ConnectionConfig &cfg)
{
std::string result(cfg.name());
result += " (";
result += cfg.user();
result += "@";
result += cfg.host();
result += ":";
result += std::to_string(cfg.port());
result += "/";
result += cfg.dbname();
result += ")";
return stdStrToQ(result);
}
void ConnectionListModel::newItem()
{
int i = m_connections->createNew();
auto idx = createIndex(i, 0);
emit dataChanged(idx, idx);
}
Expected<ConnectionConfig> ConnectionListModel::get(int row)
{
if (row >= 0 && row < (int)m_connections->size()) {
return m_connections->getConfigByIdx(row);
}
else {
return Expected<ConnectionConfig>::fromException(std::out_of_range("Invalid row"));
}
}
//void ConnectionListModel::del(const int idx)
bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent)
{
bool result = false;
if (row >= 0 && row < (int)m_connections->size()) {
beginRemoveRows(parent, row, row + count -1);
SCOPE_EXIT { endRemoveRows(); };
m_connections->remove(row, count);
result = true;
}
return result;
}
//void ConnectionListModel::load()
//{
// m_connections->load();
//}
void ConnectionListModel::save()
{
m_connections->save();
}
void ConnectionListModel::save(int index)
{
m_connections->save(index);
}

View file

@ -0,0 +1,46 @@
#ifndef CONNECTIONLISTMODEL_H
#define CONNECTIONLISTMODEL_H
#include <vector>
#include <memory>
#include <QAbstractListModel>
#include "ConnectionConfig.h"
#include "Expected.h"
class ConnectionList;
/** \brief Model class for the list of connections.
*
* This class also allows for the editing of the list.
*/
class ConnectionListModel : public QAbstractListModel {
Q_OBJECT
public:
ConnectionListModel(ConnectionList *conns, QObject *parent);
ConnectionListModel(const ConnectionListModel&) = delete;
~ConnectionListModel();
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
virtual int columnCount(const QModelIndex &/*parent*/) const override;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
void newItem();
Expected<ConnectionConfig> get(int row);
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
void save();
void save(int index);
private:
ConnectionList *m_connections;
static QString makeLongDescription(const ConnectionConfig &cfg);
};
#endif // CONNECTIONLISTMODEL_H

View file

@ -0,0 +1,179 @@
#include "ConnectionManagerWindow.h"
#include "ui_ConnectionManagerWindow.h"
//#include "mainwindow.h"
#include "MasterController.h"
#include <QDataWidgetMapper>
#include <QMessageBox>
#include <QStandardItemModel>
#include "ConnectionListModel.h"
ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::ConnectionManagerWindow)
// , m_listModel(new ConnectionListModel(this))
, m_masterController(master)
{
ui->setupUi(this);
ui->listView->setModel(m_masterController->getConnectionListModel());
setupWidgetMappings();
connect(ui->listView->selectionModel(),
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
this, SLOT(on_currentChanged(QModelIndex,QModelIndex)));
}
ConnectionManagerWindow::~ConnectionManagerWindow()
{
// m_listModel->save();
delete ui;
// delete m_listModel;
delete m_mapper;
}
void ConnectionManagerWindow::on_actionAdd_Connection_triggered()
{
// ConnectionConfig c;
// c.setName("new");
//m_listModel->add(c);
auto clm = m_masterController->getConnectionListModel();
clm->newItem();
// Select the new row
auto idx = clm->index(clm->rowCount() - 1, 0);
ui->listView->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Select);
}
void ConnectionManagerWindow::on_currentChanged(const QModelIndex &current,
const QModelIndex &)
{
int currow = current.row();
auto clm = m_masterController->getConnectionListModel();
clm->save(prevSelection);
m_mapper->setCurrentIndex(currow);
prevSelection = currow;
}
void ConnectionManagerWindow::on_actionDelete_connection_triggered()
{
auto ci = ui->listView->selectionModel()->currentIndex();
if (ci.isValid()) {
auto clm = m_masterController->getConnectionListModel();
clm->removeRow(ci.row());
}
}
void ConnectionManagerWindow::setupWidgetMappings()
{
auto clm = m_masterController->getConnectionListModel();
m_mapper = new QDataWidgetMapper(this);
m_mapper->setModel(clm);
m_mapper->addMapping(ui->edtName, 1);
m_mapper->addMapping(ui->edtHost, 2);
m_mapper->addMapping(ui->spinPort, 3);
m_mapper->addMapping(ui->edtUser, 4);
m_mapper->addMapping(ui->edtPassword, 5);
m_mapper->addMapping(ui->edtDbname, 6);
m_mapper->toFirst();
}
void ConnectionManagerWindow::on_actionConnect_triggered()
{
auto ci = ui->listView->selectionModel()->currentIndex();
m_masterController->openSqlWindowForConnection(ci.row());
}
void ConnectionManagerWindow::on_actionQuit_application_triggered()
{
auto res = QMessageBox::question(this, "pglab",
tr("Close ALL windows?"), QMessageBox::Yes, QMessageBox::No);
if (res == QMessageBox::Yes) {
QApplication::quit();
}
//closeAllWindows();
}
void ConnectionManagerWindow::on_actionBackup_database_triggered()
{
auto ci = ui->listView->selectionModel()->currentIndex();
m_masterController->openBackupDlgForConnection(ci.row());
}
void ConnectionManagerWindow::on_actionManage_server_triggered()
{
auto ci = ui->listView->selectionModel()->currentIndex();
m_masterController->openServerWindowForConnection(ci.row());
}
#include <botan/botan.h>
//#include <botan/base64.h>
//#include <botan/pbkdf.h>
//#include <botan/block_cipher.h>
//#include <botan/hex.h>
#include <botan/cryptobox.h>
void ConnectionManagerWindow::on_testButton_clicked()
{
std::string error = Botan::runtime_version_check(BOTAN_VERSION_MAJOR,
BOTAN_VERSION_MINOR,
BOTAN_VERSION_PATCH);
if (error.empty()) {
// Botan::AutoSeeded_RNG rng;
// Botan::secure_vector<Botan::byte> salt =
// //{ 0x3f, 0x0a, 0xb0, 0x11, 0x44, 0xfe, 0x9d, 0xf7, 0x85, 0xd3, 0x11, 0x38, 0xe2, 0xdf, 0x31, 0x42 };
// rng.random_vec(16);
// // salt should be random and saved with encrypted data so it can be used when we decrypt
// std::string password = "Hello kitty";
// std::unique_ptr<Botan::PBKDF> pbkdf(Botan::get_pbkdf("PBKDF2(SHA-256)"));
// Botan::OctetString aes256_key = pbkdf->derive_key(32, password, salt.data(), salt.size(), 10000);
// std::string plaintext("Your great-grandfather gave this watch to your granddad for good luck. Unfortunately, Dane's luck wasn't as good as his old man's.");
// Botan::secure_vector<uint8_t> pt(plaintext.data(),plaintext.data()+plaintext.length());
// std::unique_ptr<Botan::Cipher_Mode> enc(Botan::get_cipher_mode("AES-256/CBC/PKCS7", Botan::ENCRYPTION));
// enc->set_key(aes256_key);
// //generate fresh nonce (IV)
// //std::unique_ptr<Botan::RandomNumberGenerator> rng(new Botan::AutoSeeded_RNG);
// std::vector<uint8_t> iv(enc->default_nonce_length());
// rng.randomize(iv.data(), iv.size());
// enc->start(iv);
// enc->finish(pt);
// //std::cout << std::endl << enc->name() << " with iv " << Botan::hex_encode(iv) << std::endl << Botan::hex_encode(pt);
//std::string s = aes256_key.as_string();// + "\n" + t.format_string();
std::string passphrase = "my passphrase";
std::string plaintext("password1234");
try {
Botan::AutoSeeded_RNG rng;
std::string encrypted = Botan::CryptoBox::encrypt((const uint8_t*)plaintext.data(), plaintext.length(), passphrase, rng);
std::string decrypted = Botan::CryptoBox::decrypt(encrypted, passphrase);
std::string s = encrypted + "\n" + decrypted;
QMessageBox::information(this, "pglab",
QString::fromUtf8(s.c_str()), QMessageBox::Yes);
}
catch (Botan::Decoding_Error &e) {
QMessageBox::information(this, "pglab",
tr("Failure to decrypt"), QMessageBox::Yes);
}
}
else {
QMessageBox ::information(this, "pglab",
QString::fromUtf8(error.c_str()), QMessageBox::Yes);
}
}

View file

@ -0,0 +1,45 @@
#ifndef CONNECTIONMANAGERWINDOW_H
#define CONNECTIONMANAGERWINDOW_H
#include <QMainWindow>
namespace Ui {
class ConnectionManagerWindow;
}
class ConnectionConfig;
class MasterController;
class QDataWidgetMapper;
class QStandardItemModel;
/** \brief Class that holds glue code for the ConnectionManager UI.
*
*/
class ConnectionManagerWindow : public QMainWindow {
Q_OBJECT
public:
explicit ConnectionManagerWindow(MasterController *master, QWidget *parent = 0);
~ConnectionManagerWindow();
private slots:
void on_actionAdd_Connection_triggered();
void on_currentChanged(const QModelIndex &current, const QModelIndex &previous);
void on_actionDelete_connection_triggered();
void on_actionConnect_triggered();
void on_actionQuit_application_triggered();
void on_actionBackup_database_triggered();
void on_actionManage_server_triggered();
void on_testButton_clicked();
private:
Ui::ConnectionManagerWindow *ui;
QDataWidgetMapper *m_mapper = nullptr;
MasterController *m_masterController;
int prevSelection = -1;
void setupWidgetMappings();
};
#endif // CONNECTIONMANAGERWINDOW_H

View file

@ -0,0 +1,302 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConnectionManagerWindow</class>
<widget class="QMainWindow" name="ConnectionManagerWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>pglab - Connection Manager</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListView" name="listView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="edtName"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Host</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="edtHost"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Port</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spinPort">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="edtUser"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="edtPassword">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Database</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="edtDbname"/>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="cmbbxSsl">
<property name="currentIndex">
<number>2</number>
</property>
<item>
<property name="text">
<string>reject</string>
</property>
</item>
<item>
<property name="text">
<string>allow</string>
</property>
</item>
<item>
<property name="text">
<string>prefer</string>
</property>
</item>
<item>
<property name="text">
<string>require</string>
</property>
</item>
<item>
<property name="text">
<string>verify-ca</string>
</property>
</item>
<item>
<property name="text">
<string>verify-full</string>
</property>
</item>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>SSL</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Certificate</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="edtCert"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Key</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="edtKey"/>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Root cert.</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="edtRootCert"/>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Revocation list</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLineEdit" name="edtCrl"/>
</item>
<item row="11" column="0">
<widget class="QPushButton" name="testButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>24</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>Fi&amp;le</string>
</property>
<addaction name="actionQuit_application"/>
</widget>
<addaction name="menuFile"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionConnect"/>
<addaction name="actionManage_server"/>
<addaction name="separator"/>
<addaction name="actionAdd_Connection"/>
<addaction name="actionDelete_connection"/>
<addaction name="separator"/>
<addaction name="actionBackup_database"/>
</widget>
<action name="actionAdd_Connection">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/add_connection.png</normaloff>:/icons/add_connection.png</iconset>
</property>
<property name="text">
<string>Add Connection</string>
</property>
</action>
<action name="actionDelete_connection">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/delete_connection.png</normaloff>
<normalon>:/icons/delete_connection.png</normalon>:/icons/delete_connection.png</iconset>
</property>
<property name="text">
<string>Delete connection</string>
</property>
<property name="iconText">
<string>Delete</string>
</property>
</action>
<action name="actionConnect">
<property name="icon">
<iconset>
<normalon>:/icons/open_query_window.png</normalon>
</iconset>
</property>
<property name="text">
<string>Connect</string>
</property>
</action>
<action name="actionQuit_application">
<property name="text">
<string>&amp;Quit application</string>
</property>
</action>
<action name="actionBackup_database">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/backup_database.png</normaloff>
<normalon>:/icons/backups.png</normalon>:/icons/backup_database.png</iconset>
</property>
<property name="text">
<string>Backup database</string>
</property>
</action>
<action name="actionManage_server">
<property name="icon">
<iconset>
<normalon>:/icons/manage_server.png</normalon>
</iconset>
</property>
<property name="text">
<string>Manage server</string>
</property>
</action>
</widget>
<resources>
<include location="resources.qrc"/>
</resources>
<connections/>
</ui>

View file

@ -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;
}

View file

@ -0,0 +1,22 @@
#ifndef DATABASEINSPECTORWIDGET_H
#define DATABASEINSPECTORWIDGET_H
#include <QWidget>
namespace Ui {
class DatabaseInspectorWidget;
}
class DatabaseInspectorWidget : public QWidget
{
Q_OBJECT
public:
explicit DatabaseInspectorWidget(QWidget *parent = 0);
~DatabaseInspectorWidget();
private:
Ui::DatabaseInspectorWidget *ui;
};
#endif // DATABASEINSPECTORWIDGET_H

View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DatabaseInspectorWidget</class>
<widget class="QWidget" name="DatabaseInspectorWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>599</width>
<height>536</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Schema filter</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabTables">
<attribute name="title">
<string>Tables</string>
</attribute>
</widget>
<widget class="QWidget" name="tabSequences">
<attribute name="title">
<string>Sequences</string>
</attribute>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Functions</string>
</attribute>
</widget>
<widget class="QWidget" name="tabDomains">
<attribute name="title">
<string>Domains</string>
</attribute>
</widget>
<widget class="QWidget" name="tabCollations">
<attribute name="title">
<string>Collations</string>
</attribute>
</widget>
<widget class="QWidget" name="tabFTS">
<attribute name="title">
<string>FTS</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

100
pglab/DatabaseWindow.cpp Normal file
View file

@ -0,0 +1,100 @@
#include "DatabaseWindow.h"
#include "ui_DatabaseWindow.h"
#include <QTimer>
#include "GlobalIoService.h"
DatabaseWindow::DatabaseWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::DatabaseWindow),
m_dbConnection(*getGlobalAsioIoService())
{
ui->setupUi(this);
connect(&m_dbConnection, &ASyncDBConnection::onStateChanged, this, &DatabaseWindow::connectionStateChanged);
connect(&m_dbConnection, &ASyncDBConnection::onNotice, this, &DatabaseWindow::receiveNotice);
}
DatabaseWindow::~DatabaseWindow()
{
m_dbConnection.closeConnection();
delete ui;
}
void DatabaseWindow::setConfig(const ConnectionConfig &config)
{
m_config = config;
QString title = "pglab - ";
title += m_config.name().c_str();
setWindowTitle(title);
QueueTask([this]() { startConnect(); });
}
void DatabaseWindow::QueueTask(TSQueue::t_Callable c)
{
m_taskQueue.add(c);
// Theoretically this needs to be only called if the queue was empty because otherwise it already would
// be busy emptying the queue. For now however I think it is safer to call it just to make sure.
QMetaObject::invokeMethod(this, "processCallableQueue", Qt::QueuedConnection); // queues on main thread
}
void DatabaseWindow::processCallableQueue()
{
if (!m_taskQueue.empty()) {
auto c = m_taskQueue.pop();
c();
if (!m_taskQueue.empty()) {
QTimer::singleShot(0, this, SLOT(processCallableQueue()));
}
}
}
void DatabaseWindow::startConnect()
{
m_dbConnection.setupConnection(m_config);
}
void DatabaseWindow::connectionStateChanged(ASyncDBConnection::State state)
{
QString status_str;
switch (state) {
case ASyncDBConnection::State::NotConnected:
status_str = tr("Geen verbinding");
break;
case ASyncDBConnection::State::Connecting:
status_str = tr("Verbinden");
break;
case ASyncDBConnection::State::Connected:
status_str = tr("Verbonden");
break;
case ASyncDBConnection::State::QuerySend:
status_str = tr("Query verstuurd");
break;
case ASyncDBConnection::State::CancelSend:
status_str = tr("Query geannuleerd");
break;
case ASyncDBConnection::State::Terminating:
break;
}
// addLog(status_str);
statusBar()->showMessage(status_str);
}
void DatabaseWindow::receiveNotice(Pgsql::ErrorDetails notice)
{
// 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));
// }
}

43
pglab/DatabaseWindow.h Normal file
View file

@ -0,0 +1,43 @@
#ifndef DATABASEWINDOW_H
#define DATABASEWINDOW_H
#include "ASyncDBConnection.h"
#include "tsqueue.h"
#include <QMainWindow>
namespace Ui {
class DatabaseWindow;
}
class DatabaseWindow : public QMainWindow
{
Q_OBJECT
public:
explicit DatabaseWindow(QWidget *parent = 0);
~DatabaseWindow();
void setConfig(const ConnectionConfig &config);
/* Meant to be called from other threads to pass a code block
* that has to be executed in the context of the thread of the window.
*/
void QueueTask(TSQueue::t_Callable c);
private:
Ui::DatabaseWindow *ui;
TSQueue m_taskQueue;
ASyncDBConnection m_dbConnection;
ConnectionConfig m_config;
void connectionStateChanged(ASyncDBConnection::State state);
void receiveNotice(Pgsql::ErrorDetails notice);
void startConnect();
private slots:
void processCallableQueue();
};
#endif // DATABASEWINDOW_H

56
pglab/DatabaseWindow.ui Normal file
View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DatabaseWindow</class>
<widget class="QMainWindow" name="DatabaseWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Tab 1</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTableView" name="tableView"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Tab 2</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>25</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,129 @@
#include "DatabasesTableModel.h"
#include "PgDatabaseCatalogue.h"
#include "PgDatabaseContainer.h"
#include "PgAuthIdContainer.h"
DatabasesTableModel::DatabasesTableModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
void DatabasesTableModel::setDatabaseList(const PgDatabaseCatalogue* cat)
{
beginResetModel();
m_catalog = cat;
m_databases = cat->databases();
endResetModel();
}
QVariant DatabasesTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
QVariant v;
if (orientation == Qt::Horizontal) {
if (role == Qt::DisplayRole) {
switch (section) {
case NameCol:
v = tr("Name");
break;
case DbaCol:
v = tr("Owner");
break;
case EncodingCol:
v = tr("Encoding");
break;
case CollateCol:
v = tr("Collation");
break;
case CTypeCol:
v = tr("CType");
break;
case IsTemplateCol:
v = tr("Is template");
break;
case AllowConnCol:
v = tr("Can connect");
break;
case ConnLimitCol:
v = tr("Conn. limit");
break;
case TablespaceCol:
v = tr("Tablespace");
break;
case AclCol:
v = tr("ACL");
break;
}
}
}
return v;
}
int DatabasesTableModel::rowCount(const QModelIndex &parent) const
{
int result = 0;
if (m_databases) {
result = m_databases->count();
}
return result;
}
int DatabasesTableModel::columnCount(const QModelIndex &parent) const
{
int result = 10;
// if (parent.isValid())
// return 10;
return result;
}
QVariant DatabasesTableModel::data(const QModelIndex &index, int role) const
{
QVariant v;
//if (!index.isValid())
if (m_databases) {
const PgDatabase &db = m_databases->getByIdx(index.row());
if (role == Qt::DisplayRole) {
switch (index.column()) {
case NameCol:
v = db.name;
break;
case DbaCol:
// todo lookup role name
{
const auto& roles = m_catalog->authIds();
v = QString("%1 (%2)").arg(roles->getByOid(db.dba).name).arg(db.dba);
}
break;
case EncodingCol:
// todo lookup encoding name
v = db.encoding;
break;
case CollateCol:
v = db.collate;
break;
case CTypeCol:
v = db.ctype;
break;
case IsTemplateCol:
v = db.isTemplate;
break;
case AllowConnCol:
v = db.allowConn;
break;
case ConnLimitCol:
v = db.connLimit;
break;
case TablespaceCol:
// todo lookup tablespace name
v = db.tablespace;
break;
case AclCol:
v = db.acl;
break;
}
}
}
return v;
}

View file

@ -0,0 +1,41 @@
#ifndef DATABASESTABLEMODEL_H
#define DATABASESTABLEMODEL_H
#include <QAbstractTableModel>
class PgDatabaseContainer;
class PgDatabaseCatalogue;
/** Class for displaying the list of databases of a server in a QTableView
*
*/
class DatabasesTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum e_Columns : int { NameCol, DbaCol, EncodingCol, CollateCol,
CTypeCol, IsTemplateCol, AllowConnCol, ConnLimitCol,
TablespaceCol, AclCol };
explicit DatabasesTableModel(QObject *parent);
void setDatabaseList(const PgDatabaseCatalogue* cat);
// Header:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
const PgDatabaseCatalogue *m_catalog = nullptr;
const PgDatabaseContainer *m_databases = nullptr;
};
#endif // DATABASESTABLEMODEL_H

2454
pglab/Doxyfile Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,384 @@
#include "ExplainTreeModelItem.h"
#include "json/json.h"
#include <limits>
namespace {
ExplainTreeModelItemPtr createPlanItemFromJson(Json::Value &plan)
{
ExplainTreeModelItemPtr result = std::make_shared<ExplainTreeModelItem>();
result->setNodeType(QString::fromStdString(plan["Node Type"].asString()));
result->setStrategy(QString::fromStdString(plan["Strategy"].asString()));
result->setJoinType(QString::fromStdString(plan["Join Type"].asString()));
result->setStartupCost(plan["Startup Cost"].asFloat());
result->setTotalCost(plan["Total Cost"].asFloat());
result->setEstimatedRows(plan["Plan Rows"].asInt());
result->setPlanWidth(plan["Plan Width"].asInt());
result->setActualStartupTime(plan["Actual Startup Time"].asFloat());
result->setActualTotalTime(plan["Actual Total Time"].asFloat());
result->setActualRows(plan["Actual Rows"].asInt());
result->setActualLoops(plan["Actual Loops"].asInt());
result->setRelationName(QString::fromStdString(plan["Relation Name"].asString()));
result->setAlias(QString::fromStdString(plan["Alias"].asString()));
result->setScanDirection(QString::fromStdString(plan["Scan Direction"].asString()));
result->setIndexName(QString::fromStdString(plan["Index Name"].asString()));
result->setIndexCondition(QString::fromStdString(plan["Index Cond"].asString()));
result->setIndexRecheck(QString::fromStdString(plan["Rows Removed by Index Recheck"].asString()));
result->setFilter(QString::fromStdString(plan["Filter"].asString()));
result->setHashCondition(QString::fromStdString(plan["Hash Cond"].asString()));
result->setSortKey(QString::fromStdString(plan["Sort Key"].toStyledString()));
result->setSortMethod(QString::fromStdString(plan["Sort Method"].asString()));
if (plan.isMember("Sort Space Used")) {
const Json::Value& sm = plan["Sort Space Used"];
if (sm.isInt()) {
result->setSortSpaceUsed(sm.asInt());
}
}
result->setSortSpaceType(QString::fromStdString(plan["Sort Space Type"].asString()));
Json::Value &plans = plan["Plans"];
if (plans.isArray()) {
for (auto p : plans) {
result->appendChild(
createPlanItemFromJson(p));
}
}
return result;
}
} // END of unnamed namespace
ExplainRoot::SPtr ExplainRoot::createFromJson(Json::Value &json)
{
auto res = std::make_shared<ExplainRoot>();
// Explain always seems to be an array with one element
if (json.isArray()) {
if (json.size() > 0) {
Json::Value &explain = json[0];
Json::Value &plan = explain["Plan"];
res->plan = createPlanItemFromJson(plan);
res->planningTime = explain["Planning Time"].asFloat();
res->executionTime = explain["Execution Time"].asFloat();
res->totalRuntime = explain["Total Runtime"].asFloat();
}
}
return res;
}
ExplainTreeModelItem::ExplainTreeModelItem() = default;
ExplainTreeModelItem::~ExplainTreeModelItem() = default;
void ExplainTreeModelItem::appendChild(ItemPtr child)
{
child->setParent(shared_from_this());
m_childItems.push_back(child);
}
ExplainTreeModelItemPtr ExplainTreeModelItem::child(int row)
{
return m_childItems.at(row);
}
int ExplainTreeModelItem::childCount() const
{
return m_childItems.size();
}
//int ExplainTreeModelItem::columnCount() const
//{
// return 6;
//}
//QVariant ExplainTreeModelItem::data(int column) const
//{
// QVariant r;
// if (column == 0) {
// r = nodeType;
// }
// else if (column == 1) {
// }
// return r;
//}
int ExplainTreeModelItem::row() const
{
int idx = 0;
auto p = m_parentItem.lock();
if (p) {
idx = std::find(p->m_childItems.begin(), p->m_childItems.end(), shared_from_this()) - p->m_childItems.begin();
}
return idx;
}
void ExplainTreeModelItem::setParent(ItemPtr parent)
{
m_parentItem = parent;
}
ExplainTreeModelItemPtr ExplainTreeModelItem::parent()
{
auto p = m_parentItem.lock();
return p;
}
void ExplainTreeModelItem::setNodeType(QString nt)
{
m_nodeType = std::move(nt);
}
const QString& ExplainTreeModelItem::nodeType() const
{
return m_nodeType;
}
void ExplainTreeModelItem::setStrategy(QString strat)
{
m_strategy = std::move(strat);
}
const QString& ExplainTreeModelItem::strategy() const
{
return m_strategy;
}
void ExplainTreeModelItem::setJoinType(QString jointype)
{
m_joinType = jointype;
}
QString ExplainTreeModelItem::joinType() const
{
return m_joinType;
}
void ExplainTreeModelItem::setStartupCost(float cost)
{
m_startupCost = cost;
}
void ExplainTreeModelItem::setTotalCost(float cost)
{
m_totalCost = cost;
}
void ExplainTreeModelItem::setEstimatedRows(long long estimated)
{
m_estimatedRows = estimated;
}
long long ExplainTreeModelItem::estimatedRows() const
{
return m_estimatedRows;
}
void ExplainTreeModelItem::setPlanWidth(int width)
{
m_planWidth = width;
}
void ExplainTreeModelItem::setActualStartupTime(float timems)
{
m_actualStartupTime = timems;
}
void ExplainTreeModelItem::setActualTotalTime(float timems)
{
m_actualTotalTime = timems;
}
float ExplainTreeModelItem::actualTotalTime() const
{
return m_actualTotalTime;
}
void ExplainTreeModelItem::setActualRows(long long rowcount)
{
m_actualRows = rowcount;
}
long long ExplainTreeModelItem::actualRows() const
{
return m_actualRows;
}
void ExplainTreeModelItem::setActualLoops(int loopcount)
{
m_actualLoops = loopcount;
}
int ExplainTreeModelItem::actualLoops() const
{
return m_actualLoops;
}
void ExplainTreeModelItem::setRelationName(QString n)
{
m_relationName = std::move(n);
}
void ExplainTreeModelItem::setAlias(QString a)
{
m_alias = std::move(a);
}
void ExplainTreeModelItem::setScanDirection(QString dir)
{
m_scanDirection = std::move(dir);
}
void ExplainTreeModelItem::setIndexName(QString idxname)
{
m_indexName = std::move(idxname);
}
void ExplainTreeModelItem::setIndexCondition(QString idxcond)
{
m_indexCondition = std::move(idxcond);
}
void ExplainTreeModelItem::setIndexRecheck(QString idxrecheck)
{
m_indexRecheck = std::move(idxrecheck);
}
void ExplainTreeModelItem::setFilter(QString filter)
{
m_filter = std::move(filter);
}
void ExplainTreeModelItem::setHashCondition(QString condition)
{
m_hashCondition = std::move(condition);
}
void ExplainTreeModelItem::setSortKey(QString key)
{
m_sortKey = std::move(key);
}
void ExplainTreeModelItem::setSortMethod(QString method)
{
m_sortMethod = std::move(method);
}
void ExplainTreeModelItem::setSortSpaceUsed(int space)
{
m_sortSpaceUsed = space;
}
void ExplainTreeModelItem::setSortSpaceType(QString type)
{
m_sortSpaceType = std::move(type);
}
float ExplainTreeModelItem::exclusiveTime() const
{
float tt = inclusiveTime();
for (auto c : m_childItems) {
tt -= c->inclusiveTime();
}
return tt;
}
float ExplainTreeModelItem::inclusiveTime() const
{
float t = m_actualTotalTime * m_actualLoops;
return t;
}
float ExplainTreeModelItem::estimateError() const
{
float res = 1.0;
if (m_estimatedRows > m_actualRows) {
if (m_actualRows > 0) {
res = float(m_estimatedRows) / m_actualRows;
}
else {
res = std::numeric_limits<float>::infinity();
}
}
else if (m_actualRows > m_estimatedRows) {
if (m_estimatedRows > 0) {
res = float(m_actualRows) / m_estimatedRows;
}
else {
res = std::numeric_limits<float>::infinity();
}
res = -res;
}
return res;
}
QString ExplainTreeModelItem::detailString() const
{
QString s;
if (!m_joinType.isEmpty()) {
s += m_joinType + " " + m_nodeType + " ";
}
if (!m_strategy.isEmpty()) {
s += m_strategy + " " + m_nodeType + " ";
}
if (!m_indexName.isEmpty()) {
s+= m_scanDirection + " "
+ m_nodeType + "\n";
if (!m_indexCondition.isEmpty()) {
s += "cond: " + m_indexCondition + " ";
}
if (!m_filter.isEmpty()) {
s += "filter: " + m_filter + "\n";
}
if (!m_indexRecheck.isEmpty()) {
s += "removed by recheck: " + m_indexRecheck + "\n";
}
s += "idx: " + m_indexName + " rel: " + m_alias + " ";
}
else {
if (!m_alias.isEmpty()) {
s += m_nodeType + " rel: " + m_alias + " ";
}
}
if (!m_hashCondition.isEmpty()) {
s += m_hashCondition + " ";
}
if (!m_sortMethod.isEmpty()) {
s += m_sortMethod + " " + m_sortSpaceType + " "
+ QString::number(m_sortSpaceUsed) + "kB "
+ m_sortKey + " ";
}
return s.trimmed();
}
//"Sort Key": ["pg_attribute.attname"],
//"Sort Method": "quicksort",
//"Sort Space Used": 1426,
//"Sort Space Type": "Memory",
//{
// "Node Type": "Index Scan",
// "Parent Relationship": "Inner",
// "Scan Direction": "Forward",
// "Index Name": "pg_type_oid_index",
// "Relation Name": "pg_type",
// "Alias": "pg_type",
// "Startup Cost": 0.15,
// "Total Cost": 0.18,
// "Plan Rows": 1,
// "Plan Width": 758,
// "Actual Startup Time": 0.003,
// "Actual Total Time": 0.004,
// "Actual Rows": 1,
// "Actual Loops": 100,
// "Index Cond": "(oid = pg_attribute.atttypid)",
// "Rows Removed by Index Recheck": 0
// "Filter": "actief"
//}

View file

@ -0,0 +1,144 @@
#pragma once
#include <QList>
//#include <QVariant>
#include <vector>
#include <memory>
namespace Json {
class Value;
}
class ExplainTreeModelItem;
typedef std::shared_ptr<ExplainTreeModelItem> ExplainTreeModelItemPtr;
/* Columns for tree
* 0. explain text
* 1. exclusive times
* 2. inclusive
* 3. rows x
* 4. rows
* 5. loops
*/
/** \brief Class for the nodes in the QueryExplainModel
*/
class ExplainTreeModelItem: public std::enable_shared_from_this<ExplainTreeModelItem> {
public:
typedef std::shared_ptr<ExplainTreeModelItem> ItemPtr;
ExplainTreeModelItem();
~ExplainTreeModelItem();
ExplainTreeModelItem(const ExplainTreeModelItem &rhs) = delete;
ExplainTreeModelItem &operator=(const ExplainTreeModelItem &rhs) = delete;
void appendChild(ItemPtr child);
ExplainTreeModelItemPtr child(int row);
int childCount() const;
// int columnCount() const;
// QVariant data(int column) const;
int row() const;
void setParent(ItemPtr parent);
ItemPtr parent();
void setNodeType(QString nt);
const QString& nodeType() const;
void setStrategy(QString strat);
const QString& strategy() const;
void setJoinType(QString jointype);
QString joinType() const;
void setStartupCost(float cost);
void setTotalCost(float cost);
void setEstimatedRows(long long estimated);
long long estimatedRows() const;
void setPlanWidth(int width);
void setActualStartupTime(float timems);
void setActualTotalTime(float timems);
float actualTotalTime() const;
void setActualRows(long long rowcount);
long long actualRows() const;
void setActualLoops(int loopcount);
int actualLoops() const;
void setRelationName(QString n);
void setAlias(QString a);
void setScanDirection(QString dir);
void setIndexName(QString idxname);
void setIndexCondition(QString idxcond);
void setIndexRecheck(QString idxrecheck);
void setFilter(QString filter);
void setHashCondition(QString condition);
void setSortKey(QString key);
void setSortMethod(QString method);
void setSortSpaceUsed(int space);
void setSortSpaceType(QString type);
/** ActualTotalTime minus the actual total time of it's children */
float exclusiveTime() const;
float inclusiveTime() const;
float estimateError() const;
QString detailString() const;
private:
std::vector<ItemPtr> m_childItems;
std::weak_ptr<ExplainTreeModelItem> m_parentItem;
QString m_nodeType;
QString m_strategy;
QString m_joinType;
float m_startupCost = 0.f;
float m_totalCost = 0.f;
long long m_estimatedRows = 0;
int m_planWidth = 0;
float m_actualStartupTime = 0.f;
float m_actualTotalTime = 0.f;
long long m_actualRows = 0;
int m_actualLoops = 0;
QString m_relationName;
QString m_alias;
QString m_scanDirection;
QString m_indexName;
QString m_indexCondition;
QString m_indexRecheck;
QString m_filter;
QString m_hashCondition;
QString m_sortKey;
QString m_sortMethod;
int m_sortSpaceUsed = -1;
QString m_sortSpaceType;
// "Output": ["f1.id", "f1.program", "f1.version", "f1.lic_number", "f1.callstack_crc_1", "f1.callstack_crc_2", "array_agg(f2.id)"],
// "Group Key": ["f1.id"],
// "Shared Hit Blocks": 694427,
// "Shared Read Blocks": 0,
// "Shared Dirtied Blocks": 0,
// "Shared Written Blocks": 0,
// "Local Hit Blocks": 0,
// "Local Read Blocks": 0,
// "Local Dirtied Blocks": 0,
// "Local Written Blocks": 0,
// "Temp Read Blocks": 0,
// "Temp Written Blocks": 0
// "Parent Relationship": "Outer",
};
class ExplainRoot {
public:
using SPtr = std::shared_ptr<ExplainRoot>;
static SPtr createFromJson(Json::Value &json);
ExplainTreeModelItemPtr plan;
float planningTime = 0.f;
// Triggers???
float executionTime = 0.f;
float totalRuntime = 0.f;
};

View file

@ -0,0 +1,7 @@
#include "GlobalIoService.h"
std::shared_ptr<boost::asio::io_service> getGlobalAsioIoService()
{
static auto ios = std::make_shared<boost::asio::io_service>();
return ios;
}

6
pglab/GlobalIoService.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include <memory>
#include <boost/asio.hpp>
std::shared_ptr<boost::asio::io_service> getGlobalAsioIoService();

274
pglab/MainWindow.cpp Normal file
View file

@ -0,0 +1,274 @@
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QStandardPaths>
#include <QFileDialog>
#include <QMessageBox>
#include <QTextTable>
#include <QElapsedTimer>
#include <algorithm>
#include <QCloseEvent>
#include <QMetaObject>
#include <QMetaMethod>
#include "QueryTab.h"
#include "util.h"
#include "MasterController.h"
#include "OpenDatabase.h"
namespace pg = Pgsql;
MainWindow::MainWindow(MasterController *master, QWidget *parent)
: ASyncWindow(parent)
, ui(new Ui::MainWindow)
, m_masterController(master)
{
ui->setupUi(this);
ui->tabWidget->setDocumentMode(true);
}
MainWindow::~MainWindow()
{
delete ui;
}
QueryTab* MainWindow::newSqlPage()
{
QueryTab *qt = new QueryTab(this);
qt->setConfig(m_config);
ui->tabWidget->addTab(qt, "Tab");
ui->tabWidget->setCurrentWidget(qt);
qt->newdoc();
return qt;
}
QueryTab *MainWindow::GetActiveQueryTab()
{
QWidget *widget = ui->tabWidget->currentWidget();
QueryTab *qt = dynamic_cast<QueryTab*>(widget);
return qt;
}
void MainWindow::setConfig(const ConnectionConfig &config)
{
m_config = config;
auto res = OpenDatabase::createOpenDatabase(config);
if (res.valid()) {
m_database = res.get();
}
QString title = "pglab - ";
title += m_config.name().c_str();
setWindowTitle(title);
newSqlPage();
}
void MainWindow::on_actionLoad_SQL_triggered()
{
QString home_dir = QStandardPaths::locate(QStandardPaths::HomeLocation, "", QStandardPaths::LocateDirectory);
QString file_name = QFileDialog::getOpenFileName(this,
tr("Open sql query"), home_dir, tr("SQL files (*.sql *.txt)"));
if ( ! file_name.isEmpty()) {
QueryTab* qt = newSqlPage();
qt->load(file_name);
}
}
void MainWindow::on_actionSave_SQL_triggered()
{
QueryTab *tab = GetActiveQueryTab();
if (tab) {
tab->save();
}
}
void MainWindow::on_actionSave_SQL_as_triggered()
{
QueryTab *tab = GetActiveQueryTab();
if (tab) {
tab->saveAs();
}
}
void MainWindow::on_actionSave_copy_of_SQL_as_triggered()
{
QueryTab *tab = GetActiveQueryTab();
if (tab) {
tab->saveCopyAs();
}
}
void MainWindow::on_actionExport_data_triggered()
{
QueryTab *tab = GetActiveQueryTab();
if (tab) {
QString home_dir = QStandardPaths::locate(QStandardPaths::HomeLocation, "", QStandardPaths::LocateDirectory);
QString file_name = QFileDialog::getSaveFileName(this,
tr("Export data"), home_dir, tr("CSV file (*.csv)"));
tab->exportData(file_name);
}
}
void MainWindow::on_actionClose_triggered()
{
//close();
on_tabWidget_tabCloseRequested(ui->tabWidget->currentIndex());
}
void MainWindow::on_actionAbout_triggered()
{
QMessageBox::about(this, "pgLab 0.1", tr(
"Copyrights 2016-2017, Eelke Klein, All Rights Reserved.\n"
"\n"
"The program is provided AS IS with NO WARRANTY OF ANY KIND,"
" INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS "
"FOR A PARTICULAR PURPOSE.\n"
"\n"
"This program is dynamically linked with Qt 5.8 Copyright (C) 2016 "
"The Qt Company Ltd. https://www.qt.io/licensing/. \n"
"\n"
"Icons by fatcow http://www.fatcow.com/free-icons provided under Creative Commons "
"attribution 3.0 license\n"
"\n"
"More icons by https://icons8.com/ under Creative Commons Attribution-NoDerivs 3.0 Unported "
"license."
));
}
void MainWindow::on_actionExecute_SQL_triggered()
{
QueryTab *tab = GetActiveQueryTab();
if (tab) {
tab->execute();
}
}
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->explain(true);
}
}
void MainWindow::on_actionCancel_triggered()
{
QueryTab *tab = GetActiveQueryTab();
if (tab) {
tab->cancel();
}
}
void MainWindow::closeEvent(QCloseEvent *event)
{
// TODO collect which files need saving
// std::vector<QString> files_to_save;
// int n = ui->tabWidget->count();
// for (int i = 0; i < n; ++i) {
// QWidget *w = ui->tabWidget->widget(i);
// QueryTab *qt = dynamic_cast<QueryTab*>(w);
// if (qt) {
// if (qt->isChanged()) {
// files_to_save.push_back(qt->fileName());
// }
// }
// }
// QString s;
// for (const auto& e : files_to_save) {
// s += e + "\n";
// }
// QMessageBox msgBox;
// msgBox.setIcon(QMessageBox::Warning);
// msgBox.setText("The following documents need to be saved");
// msgBox.setInformativeText(s);
// msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
// msgBox.setDefaultButton(QMessageBox::Cancel);
// //int ret =
// msgBox.exec();
}
void MainWindow::showEvent(QShowEvent *event)
{
if (!event->spontaneous()) {
// m_queryTextChanged = false;
}
event->accept();
}
void MainWindow::on_actionNew_SQL_triggered()
{
newSqlPage()->newdoc();
}
void MainWindow::on_tabWidget_tabCloseRequested(int index)
{
QWidget *widget = ui->tabWidget->widget(index);
QueryTab *qt = dynamic_cast<QueryTab*>(widget);
if (qt && qt->canClose()) {
ui->tabWidget->removeTab(index);
}
}
void MainWindow::on_actionShow_connection_manager_triggered()
{
m_masterController->showConnectionManager();
}
void MainWindow::on_actionCopy_triggered()
{
// What should be copied?
QWidget *w = QApplication::focusWidget();
QTableView *tv = dynamic_cast<QTableView*>(w);
if (tv) {
copySelectionToClipboard(tv);
}
else {
const QMetaObject *meta = w->metaObject();
int i = meta->indexOfSlot("copy");
if (i != -1) {
QMetaMethod method = meta->method(i);
method.invoke(w, Qt::AutoConnection);
}
}
//this->ui->
}
void MainWindow::on_actionCopy_as_C_string_triggered()
{
// Find which edit is active, copy the selected text or all text if no selection present
// Put quote's around each line and add escapes.
QueryTab *tab = GetActiveQueryTab();
if (tab) {
tab->copyQueryAsCString();
}
}
void MainWindow::on_actionCopy_as_raw_Cpp_string_triggered()
{
QueryTab *tab = GetActiveQueryTab();
if (tab) {
tab->copyQueryAsRawCppString();
}
}

88
pglab/MainWindow.h Normal file
View file

@ -0,0 +1,88 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "ASyncDBConnection.h"
#include "ConnectionConfig.h"
#include "tsqueue.h"
#include <QLabel>
#include "ASyncWindow.h"
#include <QSocketNotifier>
#include <memory>
#include <future>
#include "Pgsql_Connection.h"
#include <chrono>
#include <deque>
#include <mutex>
namespace Ui {
class MainWindow;
}
namespace Pgsql {
class Connection;
}
class QueryTab;
class MasterController;
class QCloseEvent;
class OpenDatabase;
class MainWindow : public ASyncWindow {
Q_OBJECT
public:
explicit MainWindow(MasterController *master, QWidget *parent);
~MainWindow();
void setConfig(const ConnectionConfig &config);
OpenDatabase* getDatabase() { return m_database; }
private:
Ui::MainWindow *ui;
ConnectionConfig m_config;
OpenDatabase *m_database = nullptr;
MasterController *m_masterController;
// class OpenDocumentController {
// public:
// private:
// QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
// QDir dir(path);
// if (!dir.exists()) {
// dir.mkpath(".");
// }
// };
QueryTab *GetActiveQueryTab();
void closeEvent(QCloseEvent *event);
void showEvent(QShowEvent *event);
QueryTab *newSqlPage();
private slots:
void on_actionLoad_SQL_triggered();
void on_actionSave_SQL_triggered();
void on_actionExport_data_triggered();
void on_actionClose_triggered();
void on_actionAbout_triggered();
void on_actionExecute_SQL_triggered();
void on_actionExplain_Analyze_triggered();
void on_actionCancel_triggered();
void on_actionSave_SQL_as_triggered();
void on_actionSave_copy_of_SQL_as_triggered();
void on_actionNew_SQL_triggered();
void on_tabWidget_tabCloseRequested(int index);
void on_actionExplain_triggered();
void on_actionShow_connection_manager_triggered();
void on_actionCopy_triggered();
void on_actionCopy_as_C_string_triggered();
void on_actionCopy_as_raw_Cpp_string_triggered();
};
#endif // MAINWINDOW_H

310
pglab/MainWindow.ui Normal file
View file

@ -0,0 +1,310 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>993</width>
<height>804</height>
</rect>
</property>
<property name="windowTitle">
<string>pglab - database</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>7</number>
</property>
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>993</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuTest">
<property name="title">
<string>Fi&amp;le</string>
</property>
<addaction name="actionNew_SQL"/>
<addaction name="actionLoad_SQL"/>
<addaction name="actionSave_SQL"/>
<addaction name="actionSave_SQL_as"/>
<addaction name="actionSave_copy_of_SQL_as"/>
<addaction name="actionExport_data"/>
<addaction name="separator"/>
<addaction name="actionClose"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
</widget>
<widget class="QMenu" name="menuQuery">
<property name="title">
<string>&amp;Query</string>
</property>
<addaction name="actionExecute_SQL"/>
<addaction name="actionExplain"/>
<addaction name="actionExplain_Analyze"/>
<addaction name="separator"/>
<addaction name="actionCancel"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>Wi&amp;ndow</string>
</property>
<addaction name="actionShow_connection_manager"/>
</widget>
<widget class="QMenu" name="menuEdit">
<property name="title">
<string>Edit</string>
</property>
<addaction name="actionCopy"/>
<addaction name="actionCopy_as_C_string"/>
<addaction name="actionCopy_as_raw_Cpp_string"/>
</widget>
<addaction name="menuTest"/>
<addaction name="menuEdit"/>
<addaction name="menuQuery"/>
<addaction name="menuView"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionNew_SQL"/>
<addaction name="actionLoad_SQL"/>
<addaction name="actionSave_SQL"/>
<addaction name="actionExport_data"/>
<addaction name="actionClose"/>
<addaction name="separator"/>
<addaction name="actionCopy"/>
<addaction name="actionCopy_as_C_string"/>
<addaction name="separator"/>
<addaction name="actionExecute_SQL"/>
<addaction name="actionExplain"/>
<addaction name="actionExplain_Analyze"/>
<addaction name="actionCancel"/>
<addaction name="separator"/>
<addaction name="actionAbout"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionLoad_SQL">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/folder.png</normaloff>:/icons/folder.png</iconset>
</property>
<property name="text">
<string>&amp;Load SQL</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
</action>
<action name="actionSave_SQL">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/script_save.png</normaloff>:/icons/script_save.png</iconset>
</property>
<property name="text">
<string>&amp;Save SQL</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="actionExport_data">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/table_save.png</normaloff>:/icons/table_save.png</iconset>
</property>
<property name="text">
<string>&amp;Export data</string>
</property>
</action>
<action name="actionClose">
<property name="icon">
<iconset>
<normalon>:/icons/page_white_delete.png</normalon>
</iconset>
</property>
<property name="text">
<string>&amp;Close</string>
</property>
<property name="shortcut">
<string>Ctrl+F4</string>
</property>
</action>
<action name="actionAbout">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/about.png</normaloff>
<normalon>:/icons/information.png</normalon>:/icons/about.png</iconset>
</property>
<property name="text">
<string>&amp;About</string>
</property>
</action>
<action name="actionExecute_SQL">
<property name="icon">
<iconset>
<normalon>:/icons/script_go.png</normalon>
</iconset>
</property>
<property name="text">
<string>&amp;Execute queries</string>
</property>
<property name="toolTip">
<string>Execute the (selected) queries</string>
</property>
<property name="shortcut">
<string>F5</string>
</property>
</action>
<action name="actionCancel">
<property name="icon">
<iconset>
<normalon>:/icons/script_delete.png</normalon>
</iconset>
</property>
<property name="text">
<string>&amp;Cancel</string>
</property>
<property name="shortcut">
<string>Alt+Pause</string>
</property>
</action>
<action name="actionExplain_Analyze">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/lightbulb.png</normaloff>:/icons/lightbulb.png</iconset>
</property>
<property name="text">
<string>Ex&amp;plain Analyze</string>
</property>
<property name="shortcut">
<string>Shift+F7</string>
</property>
</action>
<action name="actionSave_SQL_as">
<property name="text">
<string>Sa&amp;ve SQL as</string>
</property>
</action>
<action name="actionSave_copy_of_SQL_as">
<property name="text">
<string>Save copy &amp;of SQL as</string>
</property>
</action>
<action name="actionNew_SQL">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/new_query_tab.png</normaloff>
<normalon>:/icons/page_white_add.png</normalon>:/icons/new_query_tab.png</iconset>
</property>
<property name="text">
<string>&amp;New SQL</string>
</property>
<property name="shortcut">
<string>Ctrl+N</string>
</property>
</action>
<action name="actionExplain">
<property name="icon">
<iconset>
<normalon>:/icons/lightbulb_off.png</normalon>
</iconset>
</property>
<property name="text">
<string>E&amp;xplain</string>
</property>
<property name="toolTip">
<string>Explain the (selected) query</string>
</property>
<property name="shortcut">
<string>F7</string>
</property>
</action>
<action name="actionShow_connection_manager">
<property name="text">
<string>&amp;Show connection manager</string>
</property>
</action>
<action name="actionCopy">
<property name="icon">
<iconset>
<normalon>:/icons/page_white_copy.png</normalon>
</iconset>
</property>
<property name="text">
<string>&amp;Copy</string>
</property>
<property name="shortcut">
<string>Ctrl+C</string>
</property>
</action>
<action name="actionCopy_as_C_string">
<property name="icon">
<iconset>
<normalon>:/icons/token_shortland_character.png</normalon>
</iconset>
</property>
<property name="text">
<string>Copy as C-&amp;string</string>
</property>
<property name="shortcut">
<string>Ctrl+Alt+C</string>
</property>
</action>
<action name="actionCopy_as_raw_Cpp_string">
<property name="icon">
<iconset>
<normalon>:/icons/token_shortland_character.png</normalon>
</iconset>
</property>
<property name="text">
<string>Copy as raw C++-string</string>
</property>
<property name="shortcut">
<string>Ctrl+Alt+C</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
<include location="resources.qrc"/>
</resources>
<connections/>
</ui>

View file

@ -0,0 +1,71 @@
#include "MasterController.h"
#include "ConnectionManagerWindow.h"
#include "ConnectionList.h"
#include "ConnectionListModel.h"
#include "MainWindow.h"
#include "ServerWindow.h"
#include "BackupDialog.h"
MasterController::MasterController(QObject *parent) : QObject(parent)
{}
MasterController::~MasterController()
{
delete m_connectionManagerWindow;
delete m_connectionListModel;
delete m_connectionList;
}
void MasterController::init()
{
m_connectionList = new ConnectionList;
m_connectionList->load();
m_connectionListModel = new ConnectionListModel(m_connectionList, this);
m_connectionManagerWindow = new ConnectionManagerWindow(this, nullptr);
m_connectionManagerWindow->show();
}
void MasterController::showConnectionManager()
{
m_connectionManagerWindow->show();
}
void MasterController::openSqlWindowForConnection(int connection_index)
{
auto cc = m_connectionListModel->get(connection_index);
m_connectionListModel->save(connection_index);
if (cc.valid()) {
auto w = new MainWindow(this, nullptr);
w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(cc.get());
w->show();
}
}
void MasterController::openBackupDlgForConnection(int connection_index)
{
auto cc = m_connectionListModel->get(connection_index);
m_connectionListModel->save(connection_index);
if (cc.valid()) {
auto w = new BackupDialog(nullptr); //new ServerWindow(this, nullptr);
w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(cc.get());
w->show();
}
}
void MasterController::openServerWindowForConnection(int connection_index)
{
auto cc = m_connectionListModel->get(connection_index);
m_connectionListModel->save(connection_index);
if (cc.valid()) {
auto w = new ServerWindow(this, nullptr);
w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(cc.get());
w->show();
}
}

47
pglab/MasterController.h Normal file
View file

@ -0,0 +1,47 @@
#ifndef MASTERCONTROLLER_H
#define MASTERCONTROLLER_H
#include <QObject>
#include <future>
#include <map>
class ConnectionConfig;
class ConnectionList;
class ConnectionListModel;
class ConnectionManagerWindow;
/** \brief Controller class responsible for all things global.
*/
class MasterController : public QObject {
Q_OBJECT
public:
explicit MasterController(QObject *parent = 0);
MasterController(const MasterController&) = delete;
MasterController &operator=(const MasterController&) = delete;
~MasterController();
void init();
ConnectionListModel *getConnectionListModel()
{
return m_connectionListModel;
}
void showConnectionManager();
void openSqlWindowForConnection(int connection_index);
void openServerWindowForConnection(int connection_index);
void openBackupDlgForConnection(int connection_index);
signals:
public slots:
private:
ConnectionList *m_connectionList = nullptr;
ConnectionListModel *m_connectionListModel = nullptr;
ConnectionManagerWindow *m_connectionManagerWindow = nullptr;
};
#endif // MASTERCONTROLLER_H

56
pglab/OpenDatabase.cpp Normal file
View file

@ -0,0 +1,56 @@
#include "OpenDatabase.h"
#include "PgDatabaseCatalogue.h"
#include "Pgsql_Connection.h"
#include "TypeSelectionItemModel.h"
Expected<OpenDatabase*> OpenDatabase::createOpenDatabase(const ConnectionConfig &cfg)
{
OpenDatabase *odb = new OpenDatabase(cfg, nullptr);
if (odb->Init()) {
return odb;
}
//return Expected<ConnectionConfig>::fromException(std::out_of_range("Invalid row"));
return Expected<OpenDatabase*>::fromException(
std::runtime_error("Failed to get database information"));
}
OpenDatabase::OpenDatabase(const ConnectionConfig& cfg, QObject *parent)
: QObject(parent)
, m_config(cfg)
, m_catalogue(new PgDatabaseCatalogue)
{
}
OpenDatabase::~OpenDatabase()
{
delete m_catalogue;
}
bool OpenDatabase::Init()
{
Pgsql::Connection conn;
auto kw = m_config.getKeywords();
auto vals = m_config.getValues();
if (conn.connect(kw, vals, 0)) {
m_catalogue->loadAll(conn);
return true;
}
return false;
}
PgDatabaseCatalogue* OpenDatabase::catalogue()
{
return m_catalogue;
}
TypeSelectionItemModel* OpenDatabase::typeSelectionModel()
{
if (m_typeSelectionModel == nullptr) {
m_typeSelectionModel = new TypeSelectionItemModel(nullptr);
m_typeSelectionModel->setTypeList(m_catalogue->types());
}
return m_typeSelectionModel;
}

40
pglab/OpenDatabase.h Normal file
View file

@ -0,0 +1,40 @@
#ifndef OPENDATABASE_H
#define OPENDATABASE_H
#include <QObject>
#include "ConnectionConfig.h"
#include "Expected.h"
class PgDatabaseCatalogue;
class TypeSelectionItemModel;
/** Instances of this class represent a single database on which atleast one
* window is opened. This class is used to track details about that database.
*/
class OpenDatabase : public QObject
{
Q_OBJECT
public:
static Expected<OpenDatabase*> createOpenDatabase(const ConnectionConfig &cfg);
OpenDatabase(const OpenDatabase &) = delete;
OpenDatabase& operator=(const OpenDatabase &) = delete;
~OpenDatabase();
PgDatabaseCatalogue* catalogue();
TypeSelectionItemModel* typeSelectionModel();
signals:
public slots:
private:
ConnectionConfig m_config;
PgDatabaseCatalogue *m_catalogue;
TypeSelectionItemModel *m_typeSelectionModel = nullptr;
OpenDatabase(const ConnectionConfig& cfg, QObject *parent = 0);
bool Init();
};
#endif // OPENDATABASE_H

137
pglab/ParamListModel.cpp Normal file
View file

@ -0,0 +1,137 @@
#include "ParamListModel.h"
ParamListModel::ParamListModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
QVariant ParamListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
// FIXME: Implement me!
QVariant result;
if (orientation == Qt::Horizontal) {
if (role == Qt::DisplayRole) {
switch (section) {
case ColValue:
result = tr("Value");
break;
case ColType:
result = tr("Type");
break;
}
}
}
else if (orientation == Qt::Vertical) {
if (role == Qt::DisplayRole) {
result = tr("$%1").arg(section + 1);
}
}
return result;
}
//bool ParamListModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
//{
// if (value != headerData(section, orientation, role)) {
// // FIXME: Implement me!
// emit headerDataChanged(orientation, section, section);
// return true;
// }
// return false;
//}
int ParamListModel::rowCount(const QModelIndex &) const
{
return m_paramList.size();
}
int ParamListModel::columnCount(const QModelIndex &) const
{
return ColumnCount;
}
QVariant ParamListModel::data(const QModelIndex &index, int role) const
{
QVariant result;
if (index.isValid()) {
int row = index.row();
int col = index.column();
if (role == Qt::DisplayRole) {
const auto& record = m_paramList[row];
switch (col) {
case ColValue: // value column
result = record.value; // tr("val, %1").arg(row);
break;
case ColType: // type column
result = record.type; // tr("type, %1").arg(row);
break;
}
}
}
return result;
}
bool ParamListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (data(index, role) != value) {
if (role == Qt::EditRole) {
int row = index.row();
int col = index.column();
auto& record = m_paramList[row];
switch (col) {
case ColValue:
record.value = value.toString();
break;
case ColType:
record.type = value.toString();
break;
}
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
}
return false;
}
Qt::ItemFlags ParamListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
bool ParamListModel::insertRows(int row, int count, const QModelIndex &parent)
{
beginInsertRows(parent, row, row + count - 1);
// FIXME: Implement me!
auto iter = m_paramList.begin() + row;
m_paramList.insert(iter, count, Param());
endInsertRows();
return true;
}
//bool ParamListModel::insertColumns(int column, int count, const QModelIndex &parent)
//{
// beginInsertColumns(parent, column, column + count - 1);
// // FIXME: Implement me!
// endInsertColumns();
//}
bool ParamListModel::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(parent, row, row + count - 1);
auto iter = m_paramList.begin() + row;
m_paramList.erase(iter, iter + count);
endRemoveRows();
return true;
}
//bool ParamListModel::removeColumns(int column, int count, const QModelIndex &parent)
//{
// beginRemoveColumns(parent, column, column + count - 1);
// // FIXME: Implement me!
// endRemoveColumns();
//}

57
pglab/ParamListModel.h Normal file
View file

@ -0,0 +1,57 @@
#ifndef PARAMLISTMODEL_H
#define PARAMLISTMODEL_H
#include <QAbstractTableModel>
#include <vector>
#include "Pgsql_declare.h"
class ParamListModel : public QAbstractTableModel {
Q_OBJECT
public:
class Param {
public:
QString value; ///< the value of the parameter (currently this is passed directly)
QString type; ///< the type of the parameter
Param() = default;
Param(const QString &v, const QString t)
: value(v), type(t)
{}
};
enum e_Column {
ColValue = 0,
ColType,
ColumnCount // Keep last not a column just the count
};
explicit ParamListModel(QObject *parent = 0);
// Header:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override;
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// Editable:
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
auto begin() const { return m_paramList.begin(); }
auto end() const { return m_paramList.end(); }
private:
using t_ParamList = std::vector<Param>;
t_ParamList m_paramList;
};
#endif // PARAMLISTMODEL_H

100
pglab/ParamTypeDelegate.cpp Normal file
View file

@ -0,0 +1,100 @@
#include "ParamTypeDelegate.h"
#include <QComboBox>
#include "TypeSelectionItemModel.h"
ParamTypeDelegate::ParamTypeDelegate()
{}
ParamTypeDelegate::~ParamTypeDelegate()
{}
void ParamTypeDelegate::setTypeSelectionModel(TypeSelectionItemModel* model)
{
m_typeSelectionModel = model;
}
QWidget *ParamTypeDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QWidget *w = nullptr;
QComboBox *cmbbx = new QComboBox(parent);
cmbbx->setMaxVisibleItems(32);
cmbbx->setModel(m_typeSelectionModel);
w = cmbbx;
// ...
// m_ComboBox->setView(m_ColumnView);
// m_ComboBox->view()->setCornerWidget(new QSizeGrip(m_ColumnView));
// m_ComboBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
// ...
return w;
}
void ParamTypeDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
// if (index.data().canConvert<StarRating>()) {
// StarRating starRating = qvariant_cast<StarRating>(index.data());
// StarEditor *starEditor = qobject_cast<StarEditor *>(editor);
// starEditor->setStarRating(starRating);
// } else {
// QStyledItemDelegate::setEditorData(editor, index);
// }
if (index.column() == 1) {
QComboBox *cmbbx = dynamic_cast<QComboBox*>(editor);
if (cmbbx) {
auto data = index.data();
if (data.canConvert<QString>()) {
QModelIndexList indexes = m_typeSelectionModel->match(
m_typeSelectionModel->index(0, 1), Qt::DisplayRole, data, 1, Qt::MatchFlags( Qt::MatchExactly ));
if (!indexes.empty()) {
cmbbx->setCurrentIndex(indexes.at(0).row());
}
else {
cmbbx->setCurrentIndex(-1);
}
}
}
}
else {
QStyledItemDelegate::setEditorData(editor, index);
}
}
void ParamTypeDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
// if (index.data().canConvert<StarRating>()) {
// StarEditor *starEditor = qobject_cast<StarEditor *>(editor);
// model->setData(index, QVariant::fromValue(starEditor->starRating()));
// } else {
// QStyledItemDelegate::setModelData(editor, model, index);
// }
if (index.column() == 1) {
QComboBox *cmbbx = dynamic_cast<QComboBox*>(editor);
if (cmbbx) {
auto data = index.data();
if (data.canConvert<QString>()) {
QVariant d = m_typeSelectionModel->data(
m_typeSelectionModel->index(cmbbx->currentIndex(), 0));
model->setData(index, d);
}
}
} else {
QStyledItemDelegate::setModelData(editor, model, index);
}
}
// triggered by editing finished from editor
void ParamTypeDelegate::commitAndCloseEditor()
{
// StarEditor *editor = qobject_cast<StarEditor *>(sender());
// emit commitData(editor);
// emit closeEditor(editor);
}

30
pglab/ParamTypeDelegate.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef PARAMTYPEDELEGATE_H
#define PARAMTYPEDELEGATE_H
#include <QStyledItemDelegate>
class TypeSelectionItemModel;
/** Item delegate for supplying a combobox for selected the parameter type in
* the parameter list.
*/
class ParamTypeDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
ParamTypeDelegate();
~ParamTypeDelegate();
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setTypeSelectionModel(TypeSelectionItemModel* model);
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const override;
private:
TypeSelectionItemModel* m_typeSelectionModel = nullptr;
private slots:
void commitAndCloseEditor();
};
#endif // PARAMTYPEDELEGATE_H

3
pglab/PgAuthId.cpp Normal file
View file

@ -0,0 +1,3 @@
#include "PgAuthId.h"
PgAuthId::PgAuthId() = default;

32
pglab/PgAuthId.h Normal file
View file

@ -0,0 +1,32 @@
#ifndef PGAUTHID_H
#define PGAUTHID_H
#include <libpq-fe.h>
#include <QString>
#include <QDateTime>
class PgAuthId {
public:
PgAuthId();
Oid oid = InvalidOid;
QString name;
bool super;
bool inherit;
bool createRole;
bool createDB;
bool canlogin;
bool replication;
bool bypassRls;
int connLimit;
QDateTime validUntil;
bool valid() const { return oid != InvalidOid; }
bool operator==(Oid _oid) const { return oid == _oid; }
bool operator==(const QString &n) const { return name == n; }
bool operator<(Oid _oid) const { return oid < _oid; }
bool operator<(const PgAuthId &rhs) const { return oid < rhs.oid; }
};
#endif // PGAUTHID_H

View file

@ -0,0 +1,45 @@
#include "PgAuthIdContainer.h"
#include "Pgsql_Connection.h"
#include "PgDatabaseCatalogue.h"
PgAuthIdContainer::PgAuthIdContainer(PgDatabaseCatalogue *cat)
: PgContainer<PgAuthId>(cat)
{}
std::string PgAuthIdContainer::getLoadQuery() const
{
std::string result =
"SELECT oid, rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, "
" rolcanlogin, rolreplication, rolconnlimit, rolvaliduntil";
if (m_catalogue->serverVersion() >= 90500)
result += ", rolbypassrls";
result += "\n"
"FROM pg_authid";
return result;
}
void PgAuthIdContainer::load(const Pgsql::Result &res)
{
const int n_rows = res.rows();
m_container.clear();
m_container.reserve(n_rows);
bool with_rls = (m_catalogue->serverVersion() >= 90500);
for (auto row : res) {
PgAuthId v;
v.oid << row.get(0); // InvalidOid;
v.name << row.get(1);
v.super << row.get(2);
v.inherit << row.get(3);
v.createRole << row.get(4);
v.createDB << row.get(5);
v.canlogin << row.get(6);
v.replication << row.get(7);
v.connLimit << row.get(8);
v.validUntil << row.get(9);
v.bypassRls = with_rls ? (bool)row.get(10) : false;
// QDateTime
m_container.push_back(v);
}
std::sort(m_container.begin(), m_container.end());
}

26
pglab/PgAuthIdContainer.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef PGAUTHIDCONTAINER_H
#define PGAUTHIDCONTAINER_H
#include <vector>
#include "PgContainer.h"
#include "PgAuthId.h"
namespace Pgsql {
class Result;
}
class PgAuthIdContainer: public PgContainer<PgAuthId> {
public:
explicit PgAuthIdContainer(PgDatabaseCatalogue *cat);
std::string getLoadQuery() const;
void load(const Pgsql::Result &res);
private:
};
#endif // PGAUTHIDCONTAINER_H

3
pglab/PgClass.cpp Normal file
View file

@ -0,0 +1,3 @@
#include "PgClass.h"
PgClass::PgClass() = default;

26
pglab/PgClass.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef PGCLASS_H
#define PGCLASS_H
#include <QString>
#include <libpq-fe.h>
class PgClass {
public:
PgClass();
Oid oid = InvalidOid;
QString relname;
Oid relnamespace = InvalidOid;
Oid reltype = InvalidOid;
Oid reloftype = InvalidOid;
Oid relowner = InvalidOid;
Oid relam = InvalidOid;
Oid relfilename = InvalidOid;
Oid reltablespace = InvalidOid;
int relpages_est = 0;
float reltuples_est = 0;
int relallvisible = 0;
};
#endif // PGCLASS_H

70
pglab/PgContainer.h Normal file
View file

@ -0,0 +1,70 @@
#ifndef PGCONTAINER_H
#define PGCONTAINER_H
#include <QString>
#include <vector>
#include <libpq-fe.h>
class PgDatabaseCatalogue;
template<typename T>
class PgContainer {
public:
using t_Container = std::vector<T>; ///< Do not assume it will stay a vector only expect bidirectional access
explicit PgContainer(PgDatabaseCatalogue *cat)
: m_catalogue(cat)
{}
typename t_Container::const_iterator begin() const
{
return m_container.begin();
}
typename t_Container::const_iterator end() const
{
return m_container.end();
}
void clear()
{
m_container.clear();
}
int count() const
{
return (int)m_container.size();
}
const T& getByOid(Oid oid) const
{
auto lb_result = std::lower_bound(m_container.begin(), m_container.end(), oid);
if (lb_result != m_container.end() && lb_result->oid == oid)
return *lb_result;
return m_invalidInstance;
}
const T& getByName(const QString &name) const
{
auto find_res = std::find(m_container.begin(), m_container.end(), name);
if (find_res != m_container.end())
return *find_res;
return m_invalidInstance;
}
const T& getByIdx(int idx) const
{
return m_container.at(idx);
}
protected:
PgDatabaseCatalogue *m_catalogue;
t_Container m_container;
private:
T m_invalidInstance;
};
#endif // PGCONTAINER_H

6
pglab/PgDatabase.cpp Normal file
View file

@ -0,0 +1,6 @@
#include "PgDatabase.h"
PgDatabase::PgDatabase()
{
}

31
pglab/PgDatabase.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef PGDATABASE_H
#define PGDATABASE_H
#include <QString>
#include <libpq-fe.h>
class PgDatabase {
public:
PgDatabase();
Oid oid = InvalidOid;
QString name;
Oid dba; // owner?
int encoding;
QString collate;
QString ctype;
bool isTemplate;
bool allowConn;
int connLimit;
Oid tablespace;
QString acl;//"ARRAY";"YES"
bool isValid() const { return oid != InvalidOid; }
bool operator==(Oid _oid) const { return oid == _oid; }
bool operator==(const QString &n) const { return name == n; }
bool operator<(Oid _oid) const { return oid < _oid; }
bool operator<(const PgDatabase &rhs) const { return oid < rhs.oid; }
};
#endif // PGDATABASE_H

View file

@ -0,0 +1,114 @@
#include "PgDatabaseCatalogue.h"
#include "PgTypeContainer.h"
#include "PgDatabaseContainer.h"
#include "PgAuthIdContainer.h"
#include "Pgsql_Connection.h"
QString getRoleNameFromOid(const PgDatabaseCatalogue *cat, Oid oid)
{
QString name;
const PgAuthIdContainer *auth_ids = cat->authIds();
if (auth_ids) {
const PgAuthId& auth_id = auth_ids->getByOid(oid);
if (auth_id.valid()) {
name = auth_id.name;
}
}
return name;
}
PgDatabaseCatalogue::PgDatabaseCatalogue()
{
}
PgDatabaseCatalogue::~PgDatabaseCatalogue()
{
delete m_types;
}
void PgDatabaseCatalogue::loadAll(Pgsql::Connection &conn)
{
loadTypes(conn);
loadDatabases(conn);
loadAuthIds(conn);
}
void PgDatabaseCatalogue::loadInfo(Pgsql::Connection &conn)
{
Pgsql::Result r = conn.query("SHOW server_version_num");
if (r && r.resultStatus() == PGRES_TUPLES_OK)
if (r.rows() == 1)
m_serverVersion << r.get(0, 0);
r = conn.query("SELECT version()");
if (r && r.resultStatus() == PGRES_TUPLES_OK)
if (r.rows() == 1)
m_serverVersionString = r.get(0, 0).asQString();
}
void PgDatabaseCatalogue::loadTypes(Pgsql::Connection &conn)
{
if (m_types == nullptr)
m_types = new PgTypeContainer(this);
std::string q = m_types->getLoadQuery();
Pgsql::Result result = conn.query(q.c_str());
if (result && result.resultStatus() == PGRES_TUPLES_OK)
m_types->load(result);
else
throw std::runtime_error("Query failed");
}
void PgDatabaseCatalogue::loadDatabases(Pgsql::Connection &conn)
{
if (m_databases == nullptr)
m_databases = new PgDatabaseContainer(this);
std::string q = m_databases->getLoadQuery();
Pgsql::Result result = conn.query(q.c_str());
if (result && result.resultStatus() == PGRES_TUPLES_OK)
m_databases->load(result);
else
throw std::runtime_error("Query failed");
}
void PgDatabaseCatalogue::loadAuthIds(Pgsql::Connection &conn)
{
if (m_authIds == nullptr)
m_authIds = new PgAuthIdContainer(this);
std::string q = m_authIds->getLoadQuery();
Pgsql::Result result = conn.query(q.c_str());
if (result && result.resultStatus() == PGRES_TUPLES_OK)
m_authIds->load(result);
else
throw std::runtime_error("Query failed");
}
const QString& PgDatabaseCatalogue::serverVersionString() const
{
return m_serverVersionString;
}
int PgDatabaseCatalogue::serverVersion() const
{
return m_serverVersion;
}
const PgTypeContainer* PgDatabaseCatalogue::types() const
{
return m_types;
}
const PgDatabaseContainer *PgDatabaseCatalogue::databases() const
{
return m_databases;
}
const PgAuthIdContainer *PgDatabaseCatalogue::authIds() const
{
return m_authIds;
}

View file

@ -0,0 +1,49 @@
#ifndef PGSQLDATABASECATALOGUE_H
#define PGSQLDATABASECATALOGUE_H
#include <libpq-fe.h>
#include <QString>
#include <vector>
namespace Pgsql {
class Connection;
}
class PgTypeContainer;
class PgDatabaseContainer;
class PgAuthIdContainer;
class PgDatabaseCatalogue {
public:
PgDatabaseCatalogue();
PgDatabaseCatalogue(const PgDatabaseCatalogue&) = delete;
PgDatabaseCatalogue& operator = (const PgDatabaseCatalogue&) = delete;
~PgDatabaseCatalogue();
void loadAll(Pgsql::Connection &conn);
void loadInfo(Pgsql::Connection &conn);
void loadTypes(Pgsql::Connection &conn);
void loadDatabases(Pgsql::Connection &conn);
void loadAuthIds(Pgsql::Connection &conn);
const QString& serverVersionString() const;
int serverVersion() const;
const PgTypeContainer* types() const;
const PgDatabaseContainer *databases() const;
const PgAuthIdContainer *authIds() const;
private:
QString m_serverVersionString;
int m_serverVersion;
PgTypeContainer *m_types = nullptr;
PgDatabaseContainer *m_databases = nullptr;
PgAuthIdContainer *m_authIds = nullptr;
};
QString getRoleNameFromOid(const PgDatabaseCatalogue *cat, Oid oid);
#endif // PGSQLDATABASECATALOGUE_H

View file

@ -0,0 +1,36 @@
#include "PgDatabaseContainer.h"
#include "Pgsql_Connection.h"
PgDatabaseContainer::PgDatabaseContainer(PgDatabaseCatalogue *cat)
: PgContainer<PgDatabase>(cat)
{}
std::string PgDatabaseContainer::getLoadQuery() const
{
return "SELECT oid,datname,datdba,encoding,datcollate,datctype,datistemplate,datallowconn,"
"datconnlimit,dattablespace,datacl FROM pg_database";
}
void PgDatabaseContainer::load(const Pgsql::Result &res)
{
const int n_rows = res.rows();
m_container.clear();
m_container.reserve(n_rows);
for (auto row : res) {
PgDatabase v;
v.oid << row.get(0); // InvalidOid;
v.name << row.get(1);
v.dba << row.get(2); // owner?
v.encoding << row.get(3);
v.collate << row.get(4);
v.ctype << row.get(5);
v.isTemplate << row.get(6);
v.allowConn << row.get(7);
v.connLimit << row.get(8);
v.tablespace << row.get(9);
v.acl << row.get(10);
m_container.push_back(v);
}
std::sort(m_container.begin(), m_container.end());
}

View file

@ -0,0 +1,26 @@
#ifndef PGDATABASECONTAINER_H
#define PGDATABASECONTAINER_H
#include <vector>
#include "PgContainer.h"
#include "PgDatabase.h"
namespace Pgsql {
class Result;
}
class PgDatabaseContainer: public PgContainer<PgDatabase> {
public:
explicit PgDatabaseContainer(PgDatabaseCatalogue *cat);
std::string getLoadQuery() const;
void load(const Pgsql::Result &res);
private:
};
#endif // PGDATABASECONTAINER_H

4
pglab/PgNamespace.cpp Normal file
View file

@ -0,0 +1,4 @@
#include "PgNamespace.h"
PgNamespace::PgNamespace() = default;

17
pglab/PgNamespace.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef PGNAMESPACE_H
#define PGNAMESPACE_H
#include <QString>
#include <libpq-fe.h>
class PgNamespace
{
public:
PgNamespace();
QString name;
Oid owner = InvalidOid;
QString acl;
};
#endif // PGNAMESPACE_H

5
pglab/PgType.cpp Normal file
View file

@ -0,0 +1,5 @@
#include "PgType.h"
#include "Pgsql_Connection.h"
PgType::PgType() = default;

49
pglab/PgType.h Normal file
View file

@ -0,0 +1,49 @@
#ifndef PGTYPE_H
#define PGTYPE_H
#include <QString>
#include <libpq-fe.h>
class PgType {
public:
PgType();
Oid oid = InvalidOid;
QString typname;//"name";"NO"
Oid typnamespace = InvalidOid;//"oid";"NO"
Oid typowner = InvalidOid;//"oid";"NO"
short typlen = -1;//"smallint";"NO"
bool typbyval = false;//"boolean";"NO"
QString typtype;//""char"";"NO"
QString typcategory;//""char"";"NO"
bool typispreferred = false;//"boolean";"NO"
bool typisdefined = false;//"boolean";"NO"
QString typdelim;//""char"";"NO"
Oid typrelid = InvalidOid;//"oid";"NO"
Oid typelem = InvalidOid;//"oid";"NO"
Oid typarray = InvalidOid;//"oid";"NO"
QString typinput;//regproc";"NO"
QString typoutput;//"regproc";"NO"
QString typreceive;//"regproc";"NO"
QString typsend;//"regproc";"NO"
QString typmodin;//"regproc";"NO"
QString typmodout;//"regproc";"NO"
QString typanalyze;//"regproc";"NO"
QString typalign;//""char"";"NO"
QString typstorage;//""char"";"NO"
bool typnotnull = false;//"boolean";"NO"
Oid typbasetype = InvalidOid;//"oid";"NO"
int typtypmod = -1;//"integer";"NO"
int typndims = 0;//"integer";"NO"
Oid typcollation = InvalidOid;//"oid";"NO"
QString typdefaultbin;//"pg_node_tree";"YES"
QString typdefault;//"text";"YES"
QString typacl;//"ARRAY";"YES"
bool operator==(Oid _oid) const { return oid == _oid; }
bool operator==(const QString &n) const { return typname == n; }
bool operator<(Oid _oid) const { return oid < _oid; }
bool operator<(const PgType &rhs) const { return oid < rhs.oid; }
};
#endif // PGTYPE_H

85
pglab/PgTypeContainer.cpp Normal file
View file

@ -0,0 +1,85 @@
#include "PgTypeContainer.h"
#include "Pgsql_Connection.h"
#include <algorithm>
PgTypeContainer::PgTypeContainer(PgDatabaseCatalogue *cat)
: PgContainer<PgType>(cat)
{}
//const PgType& PgTypeContainer::getTypeByOid(Oid oid) const
//{
// auto lb_result = std::lower_bound(m_types.begin(), m_types.end(), oid);
// if (lb_result != m_types.end() && lb_result->oid == oid)
// return *lb_result;
// return m_invalidType;
//}
//const PgType& PgTypeContainer::getTypeByName(const QString &name) const
//{
// auto find_res = std::find(m_types.begin(), m_types.end(), name);
// if (find_res != m_types.end())
// return *find_res;
// return m_invalidType;
//}
//const PgType& PgTypeContainer::getTypeByIdx(int idx) const
//{
// return m_types.at(idx);
//}
std::string PgTypeContainer::getLoadQuery()
{
return
"SELECT oid, typname, typnamespace, typowner, typlen, typbyval, typtype, typcategory, \n"
" typispreferred, typisdefined, typdelim, typrelid, typelem, typarray, typinput, typoutput, \n"
" typreceive, typsend, typmodin, typmodout, typanalyze, typalign, typstorage, typnotnull, \n"
" typbasetype, typtypmod, typndims, typcollation, typdefaultbin, typdefault, typacl \n"
"FROM pg_type";
}
void PgTypeContainer::load(const Pgsql::Result &res)
{
const int n_rows = res.rows();
m_container.clear();
m_container.reserve(n_rows);
for (auto row : res) {
PgType v;
v.oid << row.get(0); // InvalidOid;
v.typname << row.get(1); //. operator QString(); // "name";"NO"
v.typnamespace << row.get(2); // InvalidOid;//"oid";"NO"
v.typowner << row.get(3); // InvalidOid;//"oid";"NO"
v.typlen << row.get(4); // -1;//"smallint";"NO"
v.typbyval << row.get(5); // false;//"boolean";"NO"
v.typtype << row.get(6);//""char"";"NO"
v.typcategory << row.get(7);//""char"";"NO"
v.typispreferred << row.get(8); //false;//"boolean";"NO"
v.typisdefined << row.get(9); //false;//"boolean";"NO"
v.typdelim << row.get(10); //""char"";"NO"
v.typrelid << row.get(11); // InvalidOid;//"oid";"NO"
v.typelem << row.get(12); // InvalidOid;//"oid";"NO"
v.typarray << row.get(13); // InvalidOid;//"oid";"NO"
v.typinput << row.get(14);//regproc";"NO"
v.typoutput << row.get(15);//"regproc";"NO"
v.typreceive << row.get(16);//"regproc";"NO"
v.typsend << row.get(17);//"regproc";"NO"
v.typmodin << row.get(18);//"regproc";"NO"
v.typmodout << row.get(19);//"regproc";"NO"
v.typanalyze << row.get(20);//"regproc";"NO"
v.typalign << row.get(21); // //""char"";"NO"
v.typstorage << row.get(22); //""char"";"NO"
v.typnotnull << row.get(23); //"boolean";"NO"
v.typbasetype << row.get(24); //"oid";"NO"
v.typtypmod << row.get(25); //-1;//"integer";"NO"
v.typndims << row.get(26); //"integer";"NO"
v.typcollation << row.get(27); //InvalidOid;//"oid";"NO"
v.typdefaultbin << row.get(28);//"pg_node_tree";"YES"
v.typdefault << row.get(29);//"text";"YES"
v.typacl << row.get(30);//"ARRAY";"YES"
m_container.push_back(v);
}
std::sort(m_container.begin(), m_container.end());
}

40
pglab/PgTypeContainer.h Normal file
View file

@ -0,0 +1,40 @@
#ifndef PGTYPECONTAINER_H
#define PGTYPECONTAINER_H
#include <vector>
#include "PgType.h"
#include "PgContainer.h"
namespace Pgsql {
class Result;
}
class PgTypeContainer: public PgContainer<PgType> {
public:
// using t_Types = std::vector<PgType>; ///< Do not assume it will stay a vector only expect bidirectional access
explicit PgTypeContainer(PgDatabaseCatalogue *cat);
// t_Types::const_iterator begin() const { return m_types.begin(); }
// t_Types::const_iterator end() const { return m_types.end(); }
// void clear();
// int count() const { return (int)m_types.size(); }
std::string getLoadQuery();
void load(const Pgsql::Result &res);
/** Searches for the type matching the specified oid.
*
* \return Returns the matching type or if it is not found a default constructed PgType (oid == InvalidOid).
*/
// const PgType& getTypeByOid(Oid oid) const;
// const PgType& getTypeByName(const QString &name) const;
// const PgType& getTypeByIdx(int idx) const;
private:
// PgType m_invalidType; ///< default constructed object for when a non existent type is being retrieved.
// t_Types m_types; // Keep sorted by Oid
};
#endif // PGTYPECONTAINER_H

View file

@ -0,0 +1,14 @@
#include "ProcessStdioWidget.h"
#include "ui_ProcessStdioWidget.h"
ProcessStdioWidget::ProcessStdioWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ProcessStdioWidget)
{
ui->setupUi(this);
}
ProcessStdioWidget::~ProcessStdioWidget()
{
delete ui;
}

View file

@ -0,0 +1,22 @@
#ifndef PROCESSSTDIOWIDGET_H
#define PROCESSSTDIOWIDGET_H
#include <QWidget>
namespace Ui {
class ProcessStdioWidget;
}
class ProcessStdioWidget : public QWidget
{
Q_OBJECT
public:
explicit ProcessStdioWidget(QWidget *parent = 0);
~ProcessStdioWidget();
private:
Ui::ProcessStdioWidget *ui;
};
#endif // PROCESSSTDIOWIDGET_H

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProcessStdioWidget</class>
<widget class="QWidget" name="ProcessStdioWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>750</width>
<height>556</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
</widget>
<resources/>
<connections/>
</ui>

219
pglab/QueryExplainModel.cpp Normal file
View file

@ -0,0 +1,219 @@
#include "QueryExplainModel.h"
#include <QColor>
#include <QSize>
#include <cmath>
const int c_ColumnNode = 0;
const int c_ColumnExclusive = 1;
const int c_ColumnInclusive = 2;
const int c_ColumnEstErr = 3;
const int c_ColumnRowCount = 4;
const int c_ColumnLoops = 5;
const int c_ColumnDetails = 6;
const int c_NumberOfColumns = 7;
QueryExplainModel::QueryExplainModel(QObject *parent, ExplainRoot::SPtr exp)
: QAbstractItemModel(parent)
, explain(std::move(exp))
{}
QVariant QueryExplainModel::data(const QModelIndex &index, int role) const
{
QVariant result;
if (index.isValid()) {
int col = index.column();
ExplainTreeModelItem *item = static_cast<ExplainTreeModelItem*>(index.internalPointer());
if (role == Qt::DisplayRole) {
switch (col) {
case c_ColumnNode:
result = item->nodeType();
break;
case c_ColumnExclusive:
result = item->exclusiveTime();
break;
case c_ColumnInclusive:
result = item->inclusiveTime();
break;
case c_ColumnEstErr:
result = item->estimateError();
break;
case c_ColumnRowCount:
result = item->actualRows();
break;
case c_ColumnLoops:
result = item->actualLoops();
break;
case c_ColumnDetails:
result = item->detailString();
break;
} // end switch column
}
else if (role == Qt::TextAlignmentRole) {
if (col == c_ColumnNode || col == c_ColumnDetails) {
result = Qt::AlignLeft + Qt::AlignVCenter;
}
else {
result = Qt::AlignRight + Qt::AlignVCenter;
}
}
else if (role == Qt::BackgroundColorRole) {
if (col == c_ColumnExclusive || col == c_ColumnInclusive) {
float t = col == 1 ? item->exclusiveTime() : item->inclusiveTime();
float tt = explain->plan->inclusiveTime();
if (tt > 0.000000001) {
float f = t / tt;
if (f > 0.9) {
result = QColor(255, 192, 192);
}
else if (f > 0.63) {
result = QColor(255, 224, 192);
}
else if (f > 0.36f) {
result = QColor(255, 255, 192);
}
else if (f > 0.09f) {
result = QColor(255, 255, 224);
}
else {
result = QColor(Qt::white);
}
}
}
if (col == c_ColumnEstErr) {
float e = std::fabs(item->estimateError());
if (e > 1000.0f) {
result = QColor(255, 192, 192);
}
else if (e > 100.0f) {
result = QColor(255, 224, 192);
}
else if (e > 10.0f) {
result = QColor(255, 255, 192);
}
else {
result = QColor(Qt::white);
}
}
}
}
return result;
}
//Qt::ItemFlags QueryExplainModel::flags(const QModelIndex &index) const
//{
//}
QVariant QueryExplainModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
QVariant v;
if (orientation == Qt::Horizontal) {
if (role == Qt::DisplayRole ) {
switch (section) {
case c_ColumnNode:
v = "Node";
break;
case c_ColumnExclusive:
v = "Exclusive";
break;
case c_ColumnInclusive:
v = "Inclusive";
break;
case c_ColumnEstErr:
v = "Est. Err";
break;
case c_ColumnRowCount:
v = "Rows";
break;
case c_ColumnLoops:
v = "Loops";
break;
case c_ColumnDetails:
v = "Details";
break;
}
}
// else if (role == Qt::SizeHintRole) {
// switch (section) {
// case 0:
// v = QSize();
// break;
// case 1:
// v = "Exclusive";
// break;
// case 2:
// v = "Inclusive";
// break;
// case 3:
// v = "Est. Err";
// break;
// }
// }
}
return v;
}
QModelIndex QueryExplainModel::index(int row, int column,
const QModelIndex &parent) const
{
QModelIndex result;
if (hasIndex(row, column, parent)) {
if (parent.isValid()) {
ExplainTreeModelItem *parentItem =
static_cast<ExplainTreeModelItem*>(parent.internalPointer());
if (parentItem) {
ExplainTreeModelItemPtr childItem = parentItem->child(row);
if (childItem) {
result = createIndex(row, column, childItem.get());
}
}
}
else {
result = createIndex(row, column, explain->plan.get());
}
}
return result;
}
QModelIndex QueryExplainModel::parent(const QModelIndex &index) const
{
QModelIndex result;
if (index.isValid()) {
ExplainTreeModelItem *childItem = static_cast<ExplainTreeModelItem*>(index.internalPointer());
auto parentItem = childItem->parent();
if (parentItem != nullptr) {
result = createIndex( parentItem->row(), 0, parentItem.get());
}
}
return result;
}
int QueryExplainModel::rowCount(const QModelIndex &parent) const
{
int result = 0;
if (parent.column() <= 0) {
if (parent.isValid()) {
auto item = static_cast<ExplainTreeModelItem*>(parent.internalPointer());
result = item->childCount();
}
else {
result = 1;
}
}
return result;
}
int QueryExplainModel::columnCount(const QModelIndex &) const
{
// if (parent.isValid()) {
// return 6;//static_cast<ExplainTreeModelItem*>(parent.internalPointer())->columnCount();
// }
// else {
// return 1;
// }
return c_NumberOfColumns;
}

33
pglab/QueryExplainModel.h Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#include <QAbstractItemModel>
#include <string>
#include "ExplainTreeModelItem.h"
/** \brief Model class for displaying the explain of a query in a tree like format.
*/
class QueryExplainModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit QueryExplainModel(QObject *parent, ExplainRoot::SPtr exp);
QVariant data(const QModelIndex &index, int role) const override;
// Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
private:
ExplainRoot::SPtr explain;
};

123
pglab/QueryResultModel.cpp Normal file
View file

@ -0,0 +1,123 @@
#include "QueryResultModel.h"
#include "Pgsql_declare.h"
#include <QBrush>
#include <QColor>
QueryResultModel::QueryResultModel(QObject *parent, std::shared_ptr<Pgsql::Result> r)
: QAbstractTableModel(parent)
, result(std::move(r))
{}
int QueryResultModel::rowCount(const QModelIndex &) const
{
int r = result->rows();
return r;
}
int QueryResultModel::columnCount(const QModelIndex &) const
{
int r = result->cols();
return r;
}
QVariant QueryResultModel::data(const QModelIndex &index, int role) const
{
using namespace Pgsql;
QVariant r;
int rij = index.row();
int col = index.column();
if (role == Qt::DisplayRole) {
if (result->null(col, rij)) {
r = "null";
}
else {
Oid o = result->type(col);
QString s(result->val(col, rij));
switch (o) {
case oid_bool:
s = (s == "t") ? "TRUE" : "FALSE";
// intentional fall through
default:
if (s.length() > 256) {
s.truncate(256);
}
r = s;
}
}
}
else if (role == Qt::TextAlignmentRole) {
Oid o = result->type(col);
switch (o) {
case oid_int2:
case oid_int4:
case oid_int8:
case oid_float4:
case oid_float8:
case oid_numeric:
case oid_oid:
r = Qt::AlignRight + Qt::AlignVCenter;
break;
case oid_bool:
r = Qt::AlignCenter;
break;
default:
r = Qt::AlignLeft + Qt::AlignVCenter;
}
}
else if (role == Qt::ForegroundRole) {
if (result->null(col, rij)) {
r = QBrush(Qt::gray);
}
else {
Oid o = result->type(col);
switch (o) {
case oid_int2:
case oid_int4:
case oid_int8:
r = QBrush(Qt::darkBlue);
break;
case oid_float4:
case oid_float8:
r = QBrush(Qt::darkCyan);
break;
case oid_numeric:
r = QBrush(Qt::darkGreen);
break;
case oid_bool:
if (strcmp(result->val(col, rij), "t") == 0) {
r = QBrush(Qt::darkGreen);
}
else {
r = QBrush(Qt::darkRed);
}
break;
default:
break;
}
}
}
return r;
}
QVariant QueryResultModel::headerData(int section, Qt::Orientation orientation, int role) const
{
QVariant r;
if (role == Qt::DisplayRole) {
if (orientation == Qt::Horizontal) {
r = QString(result->getColName(section));
}
else {
r = QString::number(section + 1);
}
}
return r;
}
//Qt::ItemFlags QueryResultModel::flags(const QModelIndex &) const
//{
// return Qt::ItemIsUserCheckable;
//}

23
pglab/QueryResultModel.h Normal file
View file

@ -0,0 +1,23 @@
#ifndef QUERYRESULTMODEL_H
#define QUERYRESULTMODEL_H
#include <QAbstractTableModel>
#include "Pgsql_Connection.h"
class QueryResultModel : public QAbstractTableModel
{
Q_OBJECT
public:
QueryResultModel(QObject *parent, std::shared_ptr<Pgsql::Result> r);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
std::shared_ptr<Pgsql::Result> result;
};
#endif // QUERYRESULTMODEL_H

608
pglab/QueryTab.cpp Normal file
View file

@ -0,0 +1,608 @@
#include "QueryTab.h"
#include "ui_QueryTab.h"
#include "SqlSyntaxHighlighter.h"
#include <QStandardPaths>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>
#include <QTabWidget>
#include <QTextCodec>
#include <QTextDocumentFragment>
#include <QTextStream>
#include <QClipboard>
#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<std::shared_ptr<Pgsql::Result>> res, qint64 elapsedms)
{
m_win->QueueTask([this, res, elapsedms]() { query_ready(res, elapsedms); });
});
else
m_dbConnection.send(cmd,
m_queryParamListController->params(),
[this](Expected<std::shared_ptr<Pgsql::Result>> 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<std::shared_ptr<Pgsql::Result>> exp_res, qint64 )
{
if (exp_res.valid()) {
// Process explain data seperately
auto res = exp_res.get();
std::thread([this,res]()
{
std::shared_ptr<ExplainRoot> 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<QTabWidget*>(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<std::shared_ptr<Pgsql::Result>> 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<QueryResultModel>(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);
}
}

125
pglab/QueryTab.h Normal file
View file

@ -0,0 +1,125 @@
#ifndef QUERYTAB_H
#define QUERYTAB_H
#include "ASyncDBConnection.h"
#include "ParamListModel.h"
#include "ParamTypeDelegate.h"
#include "QueryResultModel.h"
#include "QueryExplainModel.h"
#include "stopwatch.h"
#include "tuplesresultwidget.h"
#include <QWidget>
#include <memory>
namespace Ui {
class QueryTab;
}
class QTableView;
class QTabWidget;
class MainWindow;
class SqlSyntaxHighlighter;
class ExplainRoot;
class QueryResultModel;
class QueryExplainModel;
class PgTypeContainer;
class OpenDatabase;
class QueryParamListController : public QObject {
Q_OBJECT
public:
QueryParamListController(QTableView *tv, OpenDatabase *opendb, QWidget *parent);
Pgsql::Params params() const;
bool empty() const;
public slots:
void on_addParam();
void on_removeParam();
private:
QTableView *paramTableView;
OpenDatabase *m_openDatabase;
ParamListModel m_paramList;
ParamTypeDelegate m_typeDelegate;
};
class QueryTab : public QWidget {
Q_OBJECT
public:
QueryTab(MainWindow *win, QWidget *parent = nullptr);
~QueryTab();
void setConfig(const ConnectionConfig &config);
void newdoc();
// void open();
bool load(const QString &filename);
bool save();
bool saveAs();
void saveCopyAs();
void execute();
void explain(bool analyze);
void cancel();
bool canClose();
void copyQueryAsCString();
void copyQueryAsRawCppString();
void exportData(const QString &filename);
QString fileName() const { return m_fileName; }
bool isChanged() const { return m_queryTextChanged; }
bool isNew() const { return m_new; }
private:
using ResultTabContainer = std::vector<TuplesResultWidget*>;
Ui::QueryTab *ui;
MainWindow *m_win;
SqlSyntaxHighlighter* highlighter;
ConnectionConfig m_config;
StopWatch m_stopwatch;
QueryParamListController *m_queryParamListController = nullptr;
bool m_new = true;
QString m_fileName; ///< use setFileName function to set
bool m_queryTextChanged = false;
void setFileName(const QString &filename);
bool continueWithoutSavingWarning();
bool saveSqlTo(const QString &filename);
QString promptUserForSaveSqlFilename();
ASyncDBConnection m_dbConnection;
std::unique_ptr<QueryExplainModel> explainModel;
ResultTabContainer resultList;
void addLog(QString s);
QString getCommand() const;
std::string getCommandUtf8() const;
void explain_ready(ExplainRoot::SPtr explain);
void query_ready(Expected<std::shared_ptr<Pgsql::Result>> dbres, qint64 elapsedms);
QTabWidget *getTabWidget();
void setTabCaption(const QString &caption, const QString &tooltip);
void clearResult();
private slots:
void queryTextChanged();
void connectionStateChanged(ASyncDBConnection::State state);
void receiveNotice(Pgsql::ErrorDetails notice);
void startConnect();
};
#endif // QUERYTAB_H

218
pglab/QueryTab.ui Normal file
View file

@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QueryTab</class>
<widget class="QWidget" name="QueryTab">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>766</width>
<height>667</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QPlainTextEdit" name="queryEdit"/>
<widget class="QFrame" name="frameParams">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="addButton">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeButton">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTableView" name="paramTableView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="messageTab">
<attribute name="title">
<string>Messages</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item row="0" column="0">
<widget class="QTextEdit" name="messagesEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="explainTab">
<attribute name="title">
<string>Explain</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="lblTimes">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QTreeView" name="explainTreeView">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="indentation">
<number>10</number>
</property>
<property name="uniformRowHeights">
<bool>false</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="logTab">
<attribute name="title">
<string>Log</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QPlainTextEdit" name="edtLog"/>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

123
pglab/RolesTableModel.cpp Normal file
View file

@ -0,0 +1,123 @@
#include "RolesTableModel.h"
#include "PgAuthIdContainer.h"
RolesTableModel::RolesTableModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
void RolesTableModel::setRoleList(const PgAuthIdContainer* roles)
{
beginResetModel();
m_roles = roles;
endResetModel();
}
QVariant RolesTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
QVariant v;
if (orientation == Qt::Horizontal) {
if (role == Qt::DisplayRole) {
switch (section) {
case NameCol:
v = tr("Name");
break;
case SuperCol:
v = tr("Super");
break;
case InheritCol:
v = tr("Inherit");
break;
case CreateRoleCol:
v = tr("Create role");
break;
case CreateDBCol:
v = tr("Create DB");
break;
case CanLoginCol:
v = tr("Can login");
break;
case ReplicationCol:
v = tr("Replication");
break;
case BypassRlsCol:
v = tr("Bypass RLS");
break;
case ConnlimitCol:
v = tr("Connection limit");
break;
case ValidUntilCol:
v = tr("Valid until");
break;
}
}
}
return v;
}
int RolesTableModel::rowCount(const QModelIndex &parent) const
{
int result = 0;
if (m_roles) {
result = m_roles->count();
}
return result;
}
int RolesTableModel::columnCount(const QModelIndex &parent) const
{
int result = 10;
// if (parent.isValid())
// return 10;
return result;
}
QVariant RolesTableModel::data(const QModelIndex &index, int role) const
{
QVariant v;
//if (!index.isValid())
if (m_roles) {
const PgAuthId &authid = m_roles->getByIdx(index.row());
if (role == Qt::DisplayRole) {
switch (index.column()) {
case NameCol:
v = authid.name;
break;
case SuperCol:
// todo lookup role name
v = authid.super;
break;
case InheritCol:
// todo lookup encoding name
v = authid.inherit;
break;
case CreateRoleCol:
v = authid.createRole;
break;
case CreateDBCol:
v = authid.createDB;
break;
case CanLoginCol:
v = authid.canlogin;
break;
case ReplicationCol:
v = authid.replication;
break;
case BypassRlsCol:
v = authid.bypassRls;
break;
case ConnlimitCol:
// todo lookup tablespace name
v = authid.connLimit;
break;
case ValidUntilCol:
v = authid.validUntil;
break;
}
}
}
return v;
}

39
pglab/RolesTableModel.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef ROLESTABLEMODEL_H
#define ROLESTABLEMODEL_H
#include <QAbstractTableModel>
class PgAuthIdContainer;
/** Class for displaying the list of roles of a server in a QTableView
*
*/
class RolesTableModel : public QAbstractTableModel {
Q_OBJECT
public:
enum e_Columns : int { NameCol, SuperCol, InheritCol, CreateRoleCol,
CreateDBCol, CanLoginCol, ReplicationCol,
BypassRlsCol, ConnlimitCol, ValidUntilCol };
explicit RolesTableModel(QObject *parent);
void setRoleList(const PgAuthIdContainer* roles);
// Header:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
const PgAuthIdContainer *m_roles = nullptr;
};
#endif // ROLESTABLEMODEL_H

43
pglab/ServerWindow.cpp Normal file
View file

@ -0,0 +1,43 @@
#include "ServerWindow.h"
#include "ui_ServerWindow.h"
#include "OpenDatabase.h"
#include "DatabasesTableModel.h"
#include "RolesTableModel.h"
#include "PgDatabaseCatalogue.h"
ServerWindow::ServerWindow(MasterController *master, QWidget *parent)
: ASyncWindow(parent)
, ui(new Ui::ServerWindow)
, m_masterController(master)
{
ui->setupUi(this);
m_databasesModel = new DatabasesTableModel(this);
ui->databasesTableView->setModel(m_databasesModel);
m_rolesModel = new RolesTableModel(this);
ui->rolesTableView->setModel(m_rolesModel);
}
ServerWindow::~ServerWindow()
{
delete ui;
}
void ServerWindow::setConfig(const ConnectionConfig &config)
{
m_config = config;
auto res = OpenDatabase::createOpenDatabase(config);
if (res.valid()) {
m_database = res.get();
auto cat = m_database->catalogue();
if (cat) {
m_databasesModel->setDatabaseList(cat);
m_rolesModel->setRoleList(cat->authIds());
}
}
QString title = "pglab - ";
title += m_config.name().c_str();
setWindowTitle(title);
// newSqlPage();
}

33
pglab/ServerWindow.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef SERVERWINDOW_H
#define SERVERWINDOW_H
#include "ASyncWindow.h"
#include "ConnectionConfig.h"
namespace Ui {
class ServerWindow;
}
class MasterController;
class OpenDatabase;
class DatabasesTableModel;
class RolesTableModel;
class ServerWindow : public ASyncWindow {
Q_OBJECT
public:
explicit ServerWindow(MasterController *master, QWidget *parent );
~ServerWindow();
void setConfig(const ConnectionConfig &config);
private:
Ui::ServerWindow *ui;
MasterController *m_masterController = nullptr;
ConnectionConfig m_config;
OpenDatabase *m_database = nullptr;
DatabasesTableModel *m_databasesModel = nullptr;
RolesTableModel *m_rolesModel = nullptr;
};
#endif // SERVERWINDOW_H

149
pglab/ServerWindow.ui Normal file
View file

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ServerWindow</class>
<widget class="QMainWindow" name="ServerWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>2</number>
</property>
<widget class="QWidget" name="databasesTab">
<attribute name="title">
<string>Databases</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="databasesTableView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<attribute name="verticalHeaderDefaultSectionSize">
<number>20</number>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>16</number>
</attribute>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tablespacesTab">
<attribute name="title">
<string>Tablespaces</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="tablespacesTableView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<attribute name="verticalHeaderDefaultSectionSize">
<number>20</number>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>16</number>
</attribute>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="rolesTab">
<attribute name="title">
<string>Roles/users</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="rolesTableView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<attribute name="verticalHeaderDefaultSectionSize">
<number>20</number>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>16</number>
</attribute>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,177 @@
#include "SqlSyntaxHighlighter.h"
#include "PgTypeContainer.h"
#include "SqlLexer.h"
namespace {
t_SymbolSet g_Keywords = {
"a", "abort", "abs", "absent", "absolute", "access", "according", "action", "ada", "add",
"admin", "after", "aggregate", "all", "allocate", "also", "alter", "analyse", "analyze", "and",
"any", "are", "array", "array_agg", "array_max_cardinality", "as", "asc", "asensitive",
"assetion", "assignment", "asymmetric", "at", "atomic", "attribute", "attributes", "authorization", "avg",
"backward", "base64", "before", "begin", "begin_frame", "begin_partition", "bernoulli", "between", "bigint", "binary",
"bit", "bit_length", "blob", "blocked", "bom", "boolean", "both", "breadth", "buffer", "by",
"c", "cache", "call", "called", "cardinality", "cascade", "cascaded", "case", "cast",
"catalog", "catalog_name", "ceil", "ceiling", "chain", "char", "character", "characteristics",
"characters", "character_length", "character_set_catalog", "character_set_name", "character_set_schema",
"char_length", "check", "checkpoint", "class", "class_origin", "clob", "close", "cluster",
"coalesce", "cobol", "collate", "collation", "collation_catalog", "collation_name", "collation_schema",
"collect", "column", "columns", "column_name", "command_function", "command_function_code",
"comment", "comments", "commit", "committed", "concurrently", "condition", "condition_number",
"configuration", "conflict", "connect", "connection", "connection_name", "constraint", "constraints",
"constraint_catalog", "constraint_name", "constraint_schema", "constructor", "contains", "content",
"continue", "control", "conversion", "convert", "copy", "corr", "corresponding", "cost", "count",
"covar_pop", "covar_samp", "create", "cross", "csv", "cube", "cume_dist", "current", "current_catalog",
"current_date", "current_default_transform_group", "current_path", "current_role", "current_row",
"current_schema", "current_time", "current_timestamp", "current_transform_group_for_type",
"current_user", "cursor", "cursor_name", "cycle",
"data", "database", "datalink", "date", "datetime_interval_code", "datetime_interval_precision",
"day", "db", "deallocate", "dec", "decimal", "declare", "default", "defaults", "deferrable", "deferred",
"defined", "definer", "degree", "delete", "delimiter", "delimiters", "dense_rank", "depends", "depth",
"deref", "derived", "desc", "describe", "descriptor", "deterministic", "diagnostics", "dictionary",
"disable", "discard", "disconnect", "dispatch", "distinct", "dlnewcopy", "dlpreviouscopy", "dlurlcomplete",
"dlurlcompleteonly", "dlurlcompletewrite", "dlurlpatch", "dlurlpathonly", "dlurlpathwrite", "dlurlscheme",
"dlurlserver", "dlvalue", "do", "document", "domain", "double", "drop", "dynamic", "dynamic_function",
"dynamic_function_code",
"each", "element", "else", "empty", "enable", "encodign", "encrypted", "end", "end-exec", "end_frame",
"end_partition", "enforced", "enum", "equals", "escape", "event", "every", "except", "exception", "exclude",
"excluding", "exclusive", "exec", "execute", "exists", "exp", "explain", "expression", "extenstion",
"external", "extract", "false", "family", "fetch", "file", "filter", "final", "first", "first_value",
"flag", "float", "floor", "following", "for", "force", "foreign", "fortran", "forward", "found",
"frame_row", "free", "freeze", "from", "fs", "full", "function", "functions", "fusion",
"g", "general", "generated", "get", "global", "go" "goto", "grant", "granted", "greatest", "group",
"grouping", "groups", "handler", "having", "header", "hex", "hierarchy", "hold", "hour", "id", "identity",
"if", "ignore", "ilike", "immediate", "immediatly", "immutable", "implementation", "implicit", "import", "in",
"including", "increment", "indent", "index", "indexes", "indicator", "inherit", "inherits", "initially", "inline",
"inner", "inout", "input", "insensitive", "insert", "instance", "instantiable", "instead", "int", "integer",
"integrity", "intersect", "intersection", "interval", "into", "invoker", "is", "isnull", "isolation",
"join",
"k", "key", "key_member", "key_type",
"label", "lag", "language", "large", "last", "last_value", "lateral", "lead", "leading", "leakproof",
"least", "left", "length", "level", "library", "like", "like_regex", "limit", "link", "listen", "ln", "load", "local",
"localtime", "localtimestamp", "location", "locator", "lock", "locked", "logged", "lower",
"m", "map", "mapping", "match", "matched", "materialized", "max", "maxvalue", "max_cardinality", "member",
"merge", "message_length", "message_octet_length", "message_text", "method", "min", "minute", "minvalue",
"mod", "mode", "modifies", "module", "month", "more", "move", "multiset", "mumps",
"name", "namespace", "national", "natural", "nchar", "nclob", "nesting", "new", "next", "nfc", "nfd", "nfkc", "nkfd",
"nil", "no", "none", "normalize", "normalize", "not", "nothing", "notify", "notnull", "nowait", "nth_value", "ntile",
"null", "nullable", "nullif", "nulls", "number", "numeric",
"object", "occurrences_regex", "octets", "octet_length", "of", "off", "offset", "oids", "old", "on", "only", "open",
"operator", "option", "options", "or", "order", "ordering", "ordinality", "others", "out", "outer", "output", "over",
"overlaps", "overlay", "overriding", "owned", "owner",
"p", "pad", "parallel", "parameter", "parameter_mode", "parameter_name", "parameter_specific_catalog",
"parameter_specific_name", "parameter_specific_schema", "parser",
"partial", "partition", "pascal", "passing", "passthrough", "password", "path", "percent", "percentile_cont",
"percentile_disc", "percent_rank", "period", "permission", "placing", "plans", "pli", "policy", "portion",
"position", "position_regex", "power", "precedes", "preceding", "precision", "prepare", "prepared", "preserve",
"primary", "prior", "privileges", "procedural", "procedure", "program", "public",
"quote", "range", "rank", "read", "reads", "real", "reassign", "recheck", "recovery", "recursive", "ref",
"references", "referencing", "refresh", "regr_avgx", "regr_avgy", "regr_count", "regr_intercept", "regr_r2",
"regr_slope", "regr_sxx", "regr_sxy", "regr_syy", "reindex", "relative", "release", "rename", "repeatable",
"replace", "replica", "requiring", "reset", "respect", "restart", "restore", "restrict", "result", "return",
"returned_cardinality", "returned_length", "returned_octet_length", "returned_sqlstate", "returning", "returns",
"revoke", "right", "role", "rollback", "rollup", "routine", "routine_catalog", "routine_name", "routine_schema",
"row", "rows", "row_count", "row_number", "rule",
"savepoint", "scale", "schema", "schema_name", "scope", "scope_catalog", "scope_name", "scope_schema", "scroll",
"search", "second", "section", "security", "select", "selective", "self", "sensitive", "sequence", "sequences",
"serializable", "server", "server_name", "session", "session_user", "set", "setof", "sets", "share", "show",
"similar", "simple", "size", "skip", "smallint", "snapshot", "some", "source", "space", "specific", "specifictype",
"specific_name", "sql", "sqlcode", "sqlerror", "sqlexception", "sqlstate", "sqlwarning", "sqrt", "stable",
"standalone", "start", "state", "statement", "static", "statistics", "stddev_pop", "stddev_samp", "stdin", "stdout",
"storage", "strict", "strip", "structure", "style", "subclass_origin", "submultiset", "substring", "substring_regex",
"succeeds", "sum", "symmetric", "sysid", "system", "system_time", "system_user",
"t", "table", "tables", "tablesample", "tablespace", "table_name", "temp", "template", "temporary", "text", "then",
"ties", "time", "timestamp", "timezone_hour", "timezone_minute", "to", "token", "top_level_count", "trailing",
"transaction", "transaction_committed", "transaction_rolled_back", "transaction_active", "transform", "transforms",
"translate", "translate_regex", "translation", "treat", "trigger", "trigger_catalog", "trigger_name", "trigger_schema",
"trim", "trim_array", "true", "truncate", "trusted", "type", "types", "uescape", "unbounded", "uncommitted", "under",
"unencrypted", "union", "unique", "unknown", "unlink", "unlisten", "unlogged", "unnamed", "unnest", "until", "untyped",
"update", "upper", "uri", "usage", "user", "user_defined_type_catalog", "user_defined_type_code",
"user_defined_type_name", "user_defined_type_schema", "using",
"vacuum", "valid", "validate", "validator", "value", "values", "value_of", "varbinary", "varchar", "variadic",
"varying", "var_pop", "var_samp", "verbose", "version", "versioning", "view", "views", "volatile",
"when", "whenever", "where", "whitespace", "width_bucket", "window", "with", "within", "without", "work", "wrapper",
"write", "xml", "xmlagg", "xmlattributes", "xmlbinary", "xmlcast", "xmlcomment", "xmlconcat", "xmldeclaration",
"xmldocument", "xmlelement", "xmlexists", "xmlforest", "xmliterate", "xmlnamespaces", "xmlparse", "xmlpi",
"xmlquery", "xmlroot", "xmlschema", "xmlserialize", "xmltable", "xmltext", "xmlvalidate", "year", "yes", "zone"
};
//"bigint",
}
SqlSyntaxHighlighter::SqlSyntaxHighlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent)
{
m_keywordFormat.setForeground(QColor(32, 32, 192));
m_keywordFormat.setFontWeight(QFont::Bold);
m_commentFormat.setForeground(QColor(128, 128, 128));
m_quotedStringFormat.setForeground(QColor(192, 32, 192));
m_quotedIdentifierFormat.setForeground(QColor(192, 128, 32));
m_typeFormat.setForeground(QColor(32, 192, 32));
m_typeFormat.setFontWeight(QFont::Bold);
m_parameterFormat.setForeground(QColor(192, 32, 32));
m_parameterFormat.setFontWeight(QFont::Bold);
}
SqlSyntaxHighlighter::~SqlSyntaxHighlighter()
{
}
void SqlSyntaxHighlighter::setTypes(const PgTypeContainer *types)
{
m_typeNames.clear();
for (auto &e : *types) {
m_typeNames.insert(e.typname);
}
}
void SqlSyntaxHighlighter::highlightBlock(const QString &text)
{
int previous_state = previousBlockState();
if (previous_state <= 0)
previous_state = 0;
SqlLexer lexer(text, (LexerState)previous_state);
int startpos, length;
BasicTokenType tokentype;
QString s;
while (lexer.nextBasicToken(startpos, length, tokentype, s)) {
switch (tokentype) {
case BasicTokenType::None:
case BasicTokenType::End: // End of input
case BasicTokenType::DollarQuote:
break;
case BasicTokenType::Symbol: // can be many things, keyword, object name, operator, ..
if (g_Keywords.count(s.toLower()) > 0) {
setFormat(startpos, length, m_keywordFormat);
}
else if (m_typeNames.count(s.toLower()) > 0) {
setFormat(startpos, length, m_typeFormat);
}
break;
case BasicTokenType::OpenBlockComment:
setCurrentBlockState((int)lexer.currentState());
case BasicTokenType::BlockComment:
case BasicTokenType::Comment:
setFormat(startpos, length, m_commentFormat);
break;
case BasicTokenType::QuotedString:
setFormat(startpos, length, m_quotedStringFormat);
break;
case BasicTokenType::QuotedIdentifier:
setFormat(startpos, length, m_quotedIdentifierFormat);
break;
case BasicTokenType::Parameter:
setFormat(startpos, length, m_parameterFormat);
break;
}
}
}

View file

@ -0,0 +1,36 @@
#ifndef SQLSYNTAXHIGHLIGHTER_H
#define SQLSYNTAXHIGHLIGHTER_H
#include <QSyntaxHighlighter>
#include <QTextFormat>
#include <unordered_set>
#include "util.h"
using t_SymbolSet = std::unordered_set<QString>;
class PgTypeContainer;
class SqlSyntaxHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
public:
SqlSyntaxHighlighter(QTextDocument *parent = nullptr);
~SqlSyntaxHighlighter();
void setTypes(const PgTypeContainer *types);
protected:
void highlightBlock(const QString &text) override;
private:
QTextCharFormat m_keywordFormat;
QTextCharFormat m_commentFormat;
QTextCharFormat m_quotedStringFormat;
QTextCharFormat m_typeFormat;
QTextCharFormat m_quotedIdentifierFormat;
QTextCharFormat m_parameterFormat;
t_SymbolSet m_typeNames;
};
#endif // SQLSYNTAXHIGHLIGHTER_H

View file

@ -0,0 +1,72 @@
#include "TypeSelectionItemModel.h"
#include "PgTypeContainer.h"
#include <algorithm>
TypeSelectionItemModel::TypeSelectionItemModel(QObject *parent)
: QAbstractListModel(parent)
{
}
int TypeSelectionItemModel::rowCount(const QModelIndex &parent) const
{
int result = m_types.size();
// if (!parent.isValid()) {
// }
return result;
}
int TypeSelectionItemModel::columnCount(const QModelIndex &parent) const
{
int result = 1;
// if (parent.isValid())
// result = 1;
return result;
}
QVariant TypeSelectionItemModel::data(const QModelIndex &index, int role) const
{
QVariant result;
if (index.isValid()) {
int row = index.row();
int column = index.column();
if (role == Qt::DisplayRole) {
//const PgType &tp = m_types->getByIdx(row);
if (column == 0) {
result = m_types[row]; //tp.typname;
// switch (row) {
// case 0: result = "integer"; break;
// case 1: result = "numeric"; break;
// case 2: result = "timestamp"; break;
// case 3: result = "timestamptz"; break;
// case 4: result = "float"; break;
// case 5: result = "double"; break;
// case 6: result = "date"; break;
// case 7: result = "varchar"; break;
// case 8: result = "varchar"; break;
// case 9: result = "varchar"; break;
// }
}
}
}
return result;
}
void TypeSelectionItemModel::setTypeList(const PgTypeContainer* types)
{
beginResetModel();
m_types.clear();
for (const auto &e : *types) {
if (e.typcategory != "A"
&& e.typtype != "c") {
m_types.push_back(e.typname);
}
}
std::sort(m_types.begin(), m_types.end());
//emit dataChanged(this->createIndex(0, 0), this->createIndex(types->count(), 0), QVector<int>() << Qt::DisplayRole);
endResetModel();
}

View file

@ -0,0 +1,24 @@
#ifndef TYPESELECTIONITEMMODEL_H
#define TYPESELECTIONITEMMODEL_H
#include <QAbstractListModel>
#include <vector>
class PgTypeContainer;
class TypeSelectionItemModel : public QAbstractListModel {
Q_OBJECT
public:
explicit TypeSelectionItemModel(QObject *parent = 0);
void setTypeList(const PgTypeContainer* types);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
std::vector<QString> m_types;
};
#endif // TYPESELECTIONITEMMODEL_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
pglab/icons/about.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

BIN
pglab/icons/folder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
pglab/icons/lightbulb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Some files were not shown because too many files have changed in this diff Show more