Switch to cmake + vcpkg, builds on linux, windows needs verification.

Code has been modified to be portable.
This commit is contained in:
eelke 2026-10-17 16:21:22 +02:00
parent c1a5ac1aad
commit 49f6619a4e
34 changed files with 678 additions and 327 deletions

30
CMakeLists.txt Normal file
View file

@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.21)
project(pgLab LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
add_compile_definitions(BOOST_ENABLE_ASSERT_HANDLER QT_DEPRECATED_WARNINGS)
if(WIN32)
add_compile_definitions(WIN32_LEAN_AND_MEAN NOMINMAX)
endif()
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Sql Concurrent)
option(BUILD_TESTS "Build the gtest executables and register them with ctest" ON)
add_subdirectory(core)
add_subdirectory(pgsql)
add_subdirectory(pglablib)
add_subdirectory(pglab)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

View file

@ -11,3 +11,41 @@ The release notes use collected from git using [reno](https://docs.openstack.org
- googletest
- libpq
Boost, Botan, libpq and googletest are provided by vcpkg through the manifest in `vcpkg.json`, so you do not install them manually.
## Required tools
- CMake 3.21 or newer
- A C++20 compiler (GCC or Clang on Linux, Visual Studio 2022 on Windows)
- Qt 6 with the Core, Gui, Widgets, Sql and Concurrent components
- vcpkg, with the `VCPKG_ROOT` environment variable pointing to its checkout
- Ninja or make (optional, for faster builds)
- A PostgreSQL server (only needed to run the application against a database)
## Building
Configure with the vcpkg toolchain file. The first configure downloads and builds the dependencies, which takes a while:
```
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
cmake --build build
```
To skip the test executables, add `-DBUILD_TESTS=OFF` to the configure command.
## Running the tests
From the build directory, run:
```
ctest --output-on-failure
```
## Running the application
The executable is built to `build/pglab/pglab`. On a machine without a display, set `QT_QPA_PLATFORM=offscreen`:
```
./build/pglab/pglab
```

View file

@ -1,41 +1,31 @@
set(CMAKE_AUTOMOC ON)
add_library(core STATIC
BackupFormatModel.cpp
CsvWriter.cpp
my_boost_assert_handler.cpp
PasswordManager.cpp
SqlLexer.cpp)
SqlLexer.cpp
CsvWriter.cpp
BackupFormatModel.cpp
ExplainTreeModelItem.cpp
jsoncpp.cpp
SqlLexer.h
ScopeGuard.h
CsvWriter.h
BackupFormatModel.h
Expected.h
ExplainTreeModelItem.h
json/json.h
std_utils.h
IntegerRange.h
)
target_link_libraries(core PUBLIC
boost
botan
Qt5::Core
Qt6::Core
)
target_include_directories(core INTERFACE
target_include_directories(core PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
)
set_target_properties(core PROPERTIES
CXX_STANDARD 14
set_target_properties(core PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE True
POSITION_INDEPENDENT_CODE ON
)
add_executable(runtests
test/main.cpp
test/tst_CsvWriter.cpp
test/tst_expected.cpp
test/tst_PasswordManager.cpp
test/tst_scopeguard.cpp
test/tst_SqlLexer.cpp
)
target_link_libraries(runtests
core
gtest
Threads::Threads
)
add_test(tests runtests)

View file

@ -1,69 +1,115 @@
cmake_minimum_required(VERSION 3.2)
project(pglab)
set(PGLAB_SOURCES
main.cpp
About.cpp
catalog/delegates/IconColumnDelegate.cpp
catalog/models/BaseTableModel.cpp
catalog/models/ColumnTableModel.cpp
catalog/models/ConstraintModel.cpp
catalog/models/DatabasesTableModel.cpp
catalog/models/DependantsTableModel.cpp
catalog/models/IndexModel.cpp
catalog/models/ProcTableModel.cpp
catalog/models/RolesTableModel.cpp
catalog/models/SequenceModel.cpp
catalog/models/TriggerTableModel.cpp
catalog/tables/TablesTableModel.cpp
catalog/widgets/CatalogConstraintPage.cpp
catalog/widgets/CatalogFunctionsPage.cpp
catalog/widgets/CatalogIndexPage.cpp
catalog/widgets/CatalogInspector.cpp
catalog/widgets/CatalogNamespacePage.cpp
catalog/widgets/CatalogPageBase.cpp
catalog/widgets/CatalogSequencesPage.cpp
catalog/widgets/CatalogTablesPage.cpp
catalog/widgets/CatalogTypesPage.cpp
catalog/widgets/ColumnPage.cpp
catalog/widgets/DependantsPage.cpp
catalog/widgets/TriggerPage.cpp
codeeditor/CodeEditor.cpp
codeeditor/EditorGutter.cpp
codeeditor/GutterPainter.cpp
crud/CrudModel.cpp
crud/CrudTab.cpp
querytool/QueryExplainModel.cpp
querytool/QueryParamListController.cpp
querytool/QueryResultModel.cpp
querytool/QueryTool.cpp
serverinspector/DatabasesPage.cpp
serverinspector/RolesPage.cpp
serverinspector/ServerInspector.cpp
util/Colors.cpp
util/PgLabItemDelegate.cpp
util/PgLabTableView.cpp
util/SqlSyntaxHighlighter.cpp
ConnectionConfigurationWidget.cpp
ConnectionController.cpp
ManagedPage.cpp
PgDumpOutputHighlighter.cpp
CreateDatabaseDialog.cpp
ConnectionManagerWindow.cpp
ConnectionListModel.cpp
SslModeModel.cpp
stopwatch.cpp
tuplesresultwidget.cpp
BackupDialog.cpp
MasterController.cpp
ParamTypeDelegate.cpp
OpenDatabase.cpp
ProcessStdioWidget.cpp
ResultTableModelUtil.cpp
NamespaceItemModel.cpp
PropertyProxyModel.cpp
CodeGenerator.cpp
UserConfiguration.cpp
widgets/SqlCodePreview.cpp
CustomFilterSortModel.cpp
PropertiesPage.cpp
PasswordPromptDialog.cpp
EditTableWidget.cpp
EditColumnTableModel.cpp
DatabaseWindow.cpp
widgets/SingleRecordModel.cpp
widgets/SingleRecordWidget.cpp
)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(PGLAB_FORMS
ConnectionManagerWindow.ui
CreateDatabaseDialog.ui
DatabaseWindow.ui
tuplesresultwidget.ui
querytool/QueryTab.ui
ProcessStdioWidget.ui
crud/CrudTab.ui
CodeGenerator.ui
widgets/SingleRecordWidget.ui
)
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
${PGLAB_SOURCES}
${PGLAB_FORMS}
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")
if(WIN32)
set_target_properties(pglab PROPERTIES WIN32_EXECUTABLE ON)
endif()
target_link_libraries( pglab
core
target_link_libraries(pglab PRIVATE
pglablib
pgsql
boost-system
Qt5::Widgets
pthread
core
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Sql
Qt6::Concurrent
)
target_include_directories(pglab PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
set_target_properties(pglab PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
)

View file

@ -3,7 +3,7 @@
#include <QDialog>
#include "catalog\PgDatabase.h"
#include "catalog/PgDatabase.h"
class PgDatabaseCatalog;

View file

@ -148,7 +148,7 @@
<customwidget>
<class>CodeEditor</class>
<extends>QPlainTextEdit</extends>
<header>codeeditor\CodeEditor.h</header>
<header>codeeditor/CodeEditor.h</header>
</customwidget>
</customwidgets>
<resources/>

View file

@ -169,7 +169,7 @@ QVariant DatabasesTableModel::getData(const QModelIndex &index) const
break;
case SizeCol:
if (d.size.totalBytes >= 0)
v = d.size.totalBytes;
v = QVariant::fromValue(d.size.totalBytes);
break;
case AclCol:
v = db.aclString();

View file

@ -114,7 +114,7 @@ QVariant IndexModel::getData(const QModelIndex &index) const
break;
case SizeCol:
v = dat.sizeBytes;
v = QVariant::fromValue(dat.sizeBytes);
break;
case ExplicitIndexCol:
v = !dat.isSupportingIndex();

View file

@ -102,12 +102,12 @@ QVariant SequenceModel::data(const QModelIndex &index, int role) const
case NameCol: return seq.objectName();
case SchemaCol: return seq.nsName();
case OwnerCol: return seq.ownerName();
case LastCol: return seq.last;
case StartCol: return seq.start;
case MinCol: return seq.min;
case MaxCol: return seq.max;
case IncrementCol: return seq.increment;
case CacheCol: return seq.cache;
case LastCol: return QVariant::fromValue(seq.last);
case StartCol: return QVariant::fromValue(seq.start);
case MinCol: return QVariant::fromValue(seq.min);
case MaxCol: return QVariant::fromValue(seq.max);
case IncrementCol: return QVariant::fromValue(seq.increment);
case CacheCol: return QVariant::fromValue(seq.cache);
case CycledCol: return seq.cycled;
case CalledCol: return seq.called;
case AclCol: return seq.aclString();

View file

@ -178,10 +178,10 @@ QVariant TablesTableModel::getData(const QModelIndex &index) const
case OptionsCol: break;
case AclCol: return t.aclString();
case CommentCol: return t.description;
case TotalSizeCol: return s.oid == 0 ? QVariant() : s.totalBytes;
case TableSizeCol: return s.oid == 0 ? QVariant() : s.totalBytes - s.indexBytes - s.toastBytes;
case IndexSizeCol: return s.oid == 0 ? QVariant() : s.indexBytes;
case ToastSizeCol: return s.oid == 0 ? QVariant() : s.toastBytes;
case TotalSizeCol: return s.oid == 0 ? QVariant() : QVariant::fromValue(s.totalBytes);
case TableSizeCol: return s.oid == 0 ? QVariant() : QVariant::fromValue(s.totalBytes - s.indexBytes - s.toastBytes);
case IndexSizeCol: return s.oid == 0 ? QVariant() : QVariant::fromValue(s.indexBytes);
case ToastSizeCol: return s.oid == 0 ? QVariant() : QVariant::fromValue(s.toastBytes);
}
return QVariant();

View file

@ -150,7 +150,7 @@ QVariant StringToVariant(const std::string &str, Oid oid)
case Pgsql::oid_oid:
case Pgsql::int8_oid:
return v.operator int64_t();
return QVariant::fromValue(static_cast<int64_t>(v));
case Pgsql::bool_oid:
return v.operator bool();

View file

@ -40,7 +40,7 @@ if (role == Qt::DisplayRole) {
result = item->estimateError();
break;
case c_ColumnRowCount:
result = item->actualRows;
result = QVariant::fromValue(item->actualRows);
break;
case c_ColumnLoops:
result = item->actualLoops;

View file

@ -1,13 +1,14 @@
#include "ASyncDBConnection.h"
#include "ASyncDBConnection.h"
#include "ScopeGuard.h"
#include "util.h"
#include "Pgsql_PgException.h"
#include "Win32Event.h"
#include "WaitHandleList.h"
#include <atomic>
#include <chrono>
#include <queue>
#include <WinSock2.h>
#include <QDebug>
#include <QEventLoop>
#include <QSocketNotifier>
#include <QTimer>
namespace {
@ -45,10 +46,6 @@ public:
struct t_Command {
std::mutex m_mutex;
t_CommandQueue m_queue;
Win32Event m_newEvent;
t_Command()
: m_newEvent(Win32Event::Reset::Auto, Win32Event::Initial::Clear)
{}
} m_commandQueue;
ConnectionConfig m_config;
@ -64,18 +61,29 @@ public:
void stop();
/// Wakes the worker thread when it is waiting in its event loop
void wake();
private:
enum class WaitResult {
Socket, ///< the socket became ready
Wakeup, ///< woken up by stop() or by a new command
Timeout ///< the timeout expired
};
ASyncDBConnection *asyncConnObject = nullptr;
/// \todo Implement new method to stop the thread
Win32Event m_stopEvent;
std::atomic<bool> terminateRequested{false}; ///< is set when the thread should stop
std::mutex m_wakeMutex;
QEventLoop *m_loop = nullptr; ///< loop the worker blocks in, guarded by m_wakeMutex
Pgsql::Connection m_connection;
bool terminateRequested = false; ///< is set when the thread should stop
bool m_terminated = true;
Pgsql::Canceller m_canceller;
QElapsedTimer m_timer;
bool hasQueuedCommand();
WaitResult waitForSocket(QSocketNotifier::Type type, int timeout_ms, bool wakeOnNewCommand);
bool makeConnection();
void communicate();
@ -97,7 +105,6 @@ private:
ASyncDBConnectionThread::ASyncDBConnectionThread(ASyncDBConnection *asco)
: asyncConnObject(asco)
, m_stopEvent(Win32Event::Reset::Manual, Win32Event::Initial::Clear)
{}
void ASyncDBConnectionThread::run()
@ -154,6 +161,59 @@ bool ASyncDBConnectionThread::cancel()
return m_canceller.cancel(nullptr);
}
bool ASyncDBConnectionThread::hasQueuedCommand()
{
std::lock_guard<std::mutex> lg(m_commandQueue.m_mutex);
return !m_commandQueue.m_queue.empty();
}
ASyncDBConnectionThread::WaitResult ASyncDBConnectionThread::waitForSocket(QSocketNotifier::Type type, int timeout_ms, bool wakeOnNewCommand)
{
QEventLoop loop;
WaitResult result = WaitResult::Wakeup;
QSocketNotifier notifier(m_connection.socket(), type);
QObject::connect(&notifier, &QSocketNotifier::activated, &loop, [&result, &loop] {
result = WaitResult::Socket;
loop.quit();
});
QTimer timer;
timer.setSingleShot(true);
if (timeout_ms >= 0) {
QObject::connect(&timer, &QTimer::timeout, &loop, [&result, &loop] {
result = WaitResult::Timeout;
loop.quit();
});
timer.start(timeout_ms);
}
{
std::lock_guard<std::mutex> lg(m_wakeMutex);
m_loop = &loop;
}
// Checked after publishing the loop, so a wake-up that happened before is not lost
bool pending = terminateRequested || (wakeOnNewCommand && hasQueuedCommand());
if (!pending) {
loop.exec();
}
{
std::lock_guard<std::mutex> lg(m_wakeMutex);
m_loop = nullptr;
}
return result;
}
void ASyncDBConnectionThread::wake()
{
std::lock_guard<std::mutex> lg(m_wakeMutex);
if (m_loop) {
QMetaObject::invokeMethod(m_loop, [loop = m_loop] { loop->quit(); }, Qt::QueuedConnection);
}
}
bool ASyncDBConnectionThread::makeConnection()
{
using namespace std::literals::chrono_literals;
@ -165,30 +225,14 @@ bool ASyncDBConnectionThread::makeConnection()
bool ok = m_connection.connectStart(conn_string); //keywords, values);
auto start = std::chrono::steady_clock::now();
if (ok && m_connection.status() != CONNECTION_BAD) {
Win32Event socket_event(Win32Event::Reset::Auto, Win32Event::Initial::Clear);
long fd = FD_WRITE;
QSocketNotifier::Type type = QSocketNotifier::Write;
while (true) {
// poll till complete or failed (we can get an abort command)
int sock = m_connection.socket();
WSAEventSelect(sock, socket_event.handle(), fd);
WaitHandleList whl;
auto wait_result_socket_event = whl.add(socket_event);
auto wait_result_stop = whl.add(m_stopEvent);
auto elapsed = std::chrono::steady_clock::now() - start;
auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(30s - elapsed).count();
WaitResult res = waitForSocket(type, remaining > 0 ? static_cast<int>(remaining) : 0, false);
auto nu = std::chrono::steady_clock::now();
std::chrono::duration<float, std::milli> diff = -(nu-start);
diff += 30s;
DWORD timeout = diff.count();
DWORD res = MsgWaitForMultipleObjectsEx(
whl.count(), // _In_ DWORD nCount,
whl, // _In_ const HANDLE *pHandles,
timeout, // _In_ DWORD dwMilliseconds,
0, // _In_ DWORD dwWakeMask,
0 // _In_ DWORD dwFlags
);
if (res == wait_result_socket_event) {
if (res == WaitResult::Socket) {
auto poll_state = m_connection.connectPoll();
if (poll_state == PGRES_POLLING_OK) {
qDebug() << "ASyncDBConnection Connected";
@ -202,17 +246,21 @@ bool ASyncDBConnectionThread::makeConnection()
}
else if (poll_state == PGRES_POLLING_READING) {
doStateCallback({ ASyncDBConnection::State::Connecting, "Negotiating" });
fd = FD_READ;
type = QSocketNotifier::Read;
}
else if (poll_state == PGRES_POLLING_WRITING) {
doStateCallback({ ASyncDBConnection::State::Connecting, "Negotiating" });
fd = FD_WRITE;
type = QSocketNotifier::Write;
}
}
else if (res == wait_result_stop) {
else if (terminateRequested) {
m_connection.close();
return false;
}
else if (res == WaitResult::Timeout) {
doStateCallback({ ASyncDBConnection::State::NotConnected, "Connection timed out" });
m_connection.close();
return false;
}
} // end while (true)
}
@ -252,7 +300,7 @@ void ASyncDBConnectionThread::communicate()
void ASyncDBConnectionThread::stop()
{
terminateRequested = true;
m_stopEvent.set();
wake();
}
void ASyncDBConnectionThread::doStateCallback(ASyncDBConnection::StateData state)
@ -264,34 +312,18 @@ void ASyncDBConnectionThread::doStateCallback(ASyncDBConnection::StateData state
bool ASyncDBConnectionThread::waitForAndSendCommand()
{
SOCKET sock = static_cast<SOCKET>(m_connection.socket());
Win32Event socket_event(Win32Event::Reset::Manual, Win32Event::Initial::Clear);
long fd = FD_READ | FD_CLOSE;
WSAEventSelect(sock, socket_event.handle(), fd);
WaitHandleList whl;
auto wait_result_new_command = whl.add(m_commandQueue.m_newEvent);
auto wait_result_socket = whl.add(socket_event);
whl.add(m_stopEvent);
DWORD res = MsgWaitForMultipleObjectsEx(
whl.count(), // _In_ DWORD nCount,
whl, // _In_ const HANDLE *pHandles,
INFINITE, // _In_ DWORD dwMilliseconds,
0, // _In_ DWORD dwWakeMask,
0 // _In_ DWORD dwFlags
);
if (res == wait_result_new_command) {
if (hasQueuedCommand()) {
doNewCommand();
return true;
}
// Idle connection: a readable socket carries notifies or notices, or signals a lost connection
if (waitForSocket(QSocketNotifier::Read, -1, true) == WaitResult::Socket) {
if (!m_connection.consumeInput()) {
doStateCallback({ ASyncDBConnection::State::NotConnected, "Connection lost" });
return false;
}
}
else if (res == wait_result_socket) {
WSANETWORKEVENTS net_events;
WSAEnumNetworkEvents(sock, socket_event.handle(), &net_events);
if (net_events.lNetworkEvents & FD_CLOSE) {
doStateCallback({ ASyncDBConnection::State::NotConnected, "Connection lost" });
return false;
}
}
// Note if it was stop we can just return and function
// above will stop looping because terminateRequested has been set too by stop
return true;
@ -347,42 +379,16 @@ bool ASyncDBConnectionThread::consumeResultInput()
void ASyncDBConnectionThread::waitForResult()
{
SOCKET sock = static_cast<SOCKET>(m_connection.socket());
Win32Event socket_event(Win32Event::Reset::Manual, Win32Event::Initial::Clear);
long fd = FD_READ | FD_CLOSE;
bool finished = false;
while ( ! finished) {
WSAEventSelect(sock, socket_event.handle(), fd);
WaitResult res = waitForSocket(QSocketNotifier::Read, -1, false);
WaitHandleList whl;
auto wait_result_socket = whl.add(socket_event);
auto wait_result_stop = whl.add(m_stopEvent);
DWORD res = MsgWaitForMultipleObjectsEx(
whl.count(), // _In_ DWORD nCount,
whl, // _In_ const HANDLE *pHandles,
INFINITE, // _In_ DWORD dwMilliseconds,
0, // _In_ DWORD dwWakeMask,
0 // _In_ DWORD dwFlags
);
if (res == wait_result_socket) {
WSANETWORKEVENTS net_events;
WSAEnumNetworkEvents(sock, socket_event.handle(), &net_events);
if (net_events.lNetworkEvents & FD_READ) {
if (consumeResultInput()) {
finished = true;
}
if (res == WaitResult::Socket) {
if (consumeResultInput()) {
finished = true;
}
else if (net_events.lNetworkEvents & FD_CLOSE) {
doStateCallback({ ASyncDBConnection::State::NotConnected, "Close while waiting for result" });
finished = true;
stop();
}
}
if (res == wait_result_stop) {
else if (terminateRequested) {
// Send cancel, close connection and terminate thread
cancel();
doStateCallback(ASyncDBConnection::State::Terminating);
@ -455,8 +461,8 @@ bool ASyncDBConnection::send(const std::string &command, on_result_callback on_r
{
std::lock_guard<std::mutex> lg(m_threadData->m_commandQueue.m_mutex);
m_threadData->m_commandQueue.m_queue.emplace(command, on_result);
m_threadData->m_commandQueue.m_newEvent.set();
}
m_threadData->wake();
return true;
}
@ -468,8 +474,8 @@ bool ASyncDBConnection::send(const std::string &command, Pgsql::Params params, o
{
std::lock_guard<std::mutex> lg(m_threadData->m_commandQueue.m_mutex);
m_threadData->m_commandQueue.m_queue.emplace(command, std::move(params), on_result);
m_threadData->m_commandQueue.m_newEvent.set();
}
m_threadData->wake();
return true;
}

View file

@ -11,6 +11,7 @@
#include <QElapsedTimer>
#include <mutex>
#include <memory>
#include <thread>
class ASyncDBConnectionThread;
/** \brief Class that handles asynchronous execution of queries.
@ -33,7 +34,7 @@ public:
class StateData {
public:
State State;
enum State State;
QString Message;
StateData(enum State state, QString message = QString())

187
pglablib/CMakeLists.txt Normal file
View file

@ -0,0 +1,187 @@
add_library(pglablib STATIC
Pglablib.cpp
ASyncDBConnection.cpp
ConnectionConfig.cpp
TestConnection.cpp
catalog/PgType.cpp
catalog/PgTypeContainer.cpp
catalog/PgNamespace.cpp
catalog/PgClass.cpp
catalog/PgDatabase.cpp
catalog/PgDatabaseContainer.cpp
catalog/PgAuthId.cpp
catalog/PgAuthIdContainer.cpp
catalog/PgClassContainer.cpp
catalog/PgDatabaseCatalog.cpp
catalog/PgNamespaceContainer.cpp
catalog/PgAttribute.cpp
catalog/PgContainer.cpp
catalog/PgAttributeContainer.cpp
catalog/PgIndex.cpp
catalog/PgIndexContainer.cpp
catalog/PgConstraint.cpp
catalog/PgConstraintContainer.cpp
ParamListJson.cpp
ParamListModel.cpp
sqlite/SQLiteConnection.cpp
sqlite/sqlite3.c
ui/catalog/tables/TableNode.cpp
ui/catalog/tables/TableSize.cpp
ui/catalog/tables/TableTreeBuilder.cpp
util.cpp
SqlFormattingUtils.cpp
catalog/PgKeywordList.cpp
QueryGenerator.cpp
catalog/PgObject.cpp
catalog/PgTablespace.cpp
catalog/PgTablespaceContainer.cpp
codebuilder/LanguageConfig.cpp
codebuilder/CodeBuilder.cpp
codebuilder/NameManglingRules.cpp
codebuilder/DefaultConfigs.cpp
codebuilder/TypeMappings.cpp
codebuilder/IndentationConfig.cpp
codebuilder/StructureTemplate.cpp
FormatToStream.cpp
codebuilder/StringLiteralRules.cpp
codebuilder/StringEscapeRule.cpp
catalog/PgTrigger.cpp
catalog/PgTriggerContainer.cpp
catalog/PgProc.cpp
catalog/PgProcContainer.cpp
catalog/PgDatabaseObject.cpp
catalog/PgServerObject.cpp
catalog/PgNamespaceObject.cpp
catalog/PgCollation.cpp
catalog/PgCollationContainer.cpp
catalog/PgInherits.cpp
catalog/PgInheritsContainer.cpp
SelectionEditorFactory.cpp
catalog/PgAm.cpp
model/TypeModelFactory.cpp
model/TypeSelectionItemModel.cpp
catalog/PgAmContainer.cpp
model/CollationModel.cpp
model/CollationModelFactory.cpp
catalog/PgLanguageContainer.cpp
catalog/PgLanguage.cpp
catalog/PgAcl.cpp
catalog/PgSequence.cpp
catalog/PgSequenceContainer.cpp
utils/HumanReadableBytes.cpp
utils/KeyStrengthener.cpp
utils/PasswordManager.cpp
utils/PostgresqlUrlParser.cpp
Pglablib.h
ASyncDBConnection.h
ConnectionConfig.h
TestConnection.h
catalog/PgType.h
catalog/PgTypeContainer.h
catalog/PgNamespace.h
catalog/PgClass.h
catalog/PgDatabase.h
catalog/PgDatabaseContainer.h
catalog/PgContainer.h
catalog/PgAuthId.h
catalog/PgAuthIdContainer.h
catalog/PgClassContainer.h
catalog/PgDatabaseCatalog.h
catalog/PgNamespaceContainer.h
catalog/PgAttribute.h
catalog/PgAttributeContainer.h
catalog/PgIndex.h
catalog/PgIndexContainer.h
catalog/PgConstraint.h
catalog/PgConstraintContainer.h
ParamListJson.h
ParamListModel.h
sqlite/SQLiteConnection.h
sqlite/sqlite3.h
sqlite/sqlite3ext.h
ui/catalog/tables/TableNode.h
ui/catalog/tables/TableSize.h
ui/catalog/tables/TableTreeBuilder.h
util.h
SqlFormattingUtils.h
catalog/PgCatalogTypes.h
catalog/PgKeywordList.h
QueryGenerator.h
catalog/PgObject.h
catalog/PgTablespace.h
catalog/PgTablespaceContainer.h
codebuilder/LanguageConfig.h
codebuilder/CodeBuilder.h
codebuilder/NameManglingRules.h
codebuilder/DefaultConfigs.h
codebuilder/TypeMappings.h
codebuilder/IndentationConfig.h
codebuilder/StructureTemplate.h
FormatToStream.h
codebuilder/ResultLoopTemplate.h
codebuilder/StringEscapeRule.h
codebuilder/StringLiteralRules.h
catalog/PgTrigger.h
catalog/PgTriggerContainer.h
catalog/PgProc.h
catalog/PgProcContainer.h
catalog/PgDatabaseObject.h
catalog/PgServerObject.h
catalog/PgNamespaceObject.h
catalog/PgCollation.h
catalog/PgCollationContainer.h
AbstractModelFactory.h
AbstractEditorFactory.h
catalog/PgInherits.h
catalog/PgInheritsContainer.h
SelectionEditorFactory.h
model/TypeSelectionItemModel.h
model/TypeModelFactory.h
catalog/PgAm.h
catalog/PgAmContainer.h
model/CollationModel.h
model/CollationModelFactory.h
catalog/PgLanguageContainer.h
catalog/PgLanguage.h
catalog/PgAcl.h
catalog/PgSequence.h
catalog/PgSequenceContainer.h
utils/HumanReadableBytes.h
utils/KeyStrengthener.h
utils/PasswordManager.h
utils/PostgresqlUrlParser.h
)
find_package(Boost REQUIRED COMPONENTS container timer chrono assert)
find_path(BOTAN_INCLUDE_DIR botan/hash.h REQUIRED)
find_library(BOTAN_LIBRARY NAMES botan-3 botan REQUIRED)
if(NOT TARGET Botan::Botan)
add_library(Botan::Botan STATIC IMPORTED)
set_target_properties(Botan::Botan PROPERTIES
IMPORTED_LOCATION "${BOTAN_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${BOTAN_INCLUDE_DIR}"
)
endif()
target_link_libraries(pglablib PUBLIC
core
pgsql
Boost::container
Boost::timer
Boost::chrono
Boost::assert
Botan::Botan
Qt6::Core
Qt6::Widgets
)
target_include_directories(pglablib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
set_target_properties(pglablib PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE ON
)

View file

@ -200,7 +200,7 @@ QString quoteIdent(QString ident)
{
assert(ident.length() > 0);
static const wchar_t dquote = L'"';
static const QChar dquote = u'"';
if (identNeedsQuotes(ident)) {
QString out;

View file

@ -1,31 +0,0 @@
#include "WaitHandleList.h"
#include "win32event.h"
WaitHandleList::WaitHandleList() = default;
WaitHandleList::~WaitHandleList() = default;
WaitResultValue WaitHandleList::add(HANDLE h)
{
m_waitHandles.push_back(h);
return WAIT_OBJECT_0 + static_cast<DWORD>(m_waitHandles.size() - 1);
}
WaitResultValue WaitHandleList::add(Win32Event &e)
{
return add(e.handle());
}
DWORD WaitHandleList::count() const
{
return static_cast<DWORD>(m_waitHandles.size());
}
void WaitHandleList::clear()
{
m_waitHandles.clear();
}
WaitHandleList::operator const HANDLE*() const
{
return m_waitHandles.data();
}

View file

@ -1,26 +0,0 @@
#ifndef WAITHANDLELIST_H
#define WAITHANDLELIST_H
#include <windows.h>
#include <vector>
class Win32Event;
using WaitResultValue = DWORD;
class WaitHandleList {
public:
WaitHandleList();
~WaitHandleList();
WaitResultValue add(HANDLE h);
WaitResultValue add(Win32Event &e);
DWORD count() const;
void clear();
operator const HANDLE*() const;
private:
std::vector<HANDLE> m_waitHandles;
};
#endif // WAITHANDLELIST_H

View file

@ -1,47 +0,0 @@
#ifndef WIN32EVENT_H
#define WIN32EVENT_H
#include <WinSock2.h>
#include <windows.h>
/** Simpel wrapper around a Win32 Event object.
Mostly to make cleanup automatic.*/
class Win32Event {
public:
enum class Reset { Auto=0, Manual=1 };
enum class Initial { Clear=0, Set=1 };
Win32Event(Reset r, Initial is)
: hEvent(CreateEvent(
nullptr, // _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL(r), // _In_ BOOL bManualReset,
BOOL(is), // _In_ BOOL bInitialState,
nullptr //_In_opt_ LPCTSTR lpName
))
{}
Win32Event(Reset r, Initial is, int sock, long net_events)
: Win32Event(r, is)
{
WSAEventSelect(sock, hEvent, net_events);
}
~Win32Event()
{
CloseHandle(hEvent);
}
Win32Event(const Win32Event &) = delete;
Win32Event &operator=(const Win32Event &) = delete;
void set() { SetEvent(hEvent); }
void reset() { ResetEvent(hEvent); }
HANDLE handle() { return hEvent; }
private:
HANDLE hEvent;
};
#endif // WIN32EVENT_H

View file

@ -1,13 +1,15 @@
#include "PgLanguage.h"
#include <stdexcept>
QString PgLanguage::createSql() const
{
throw std::exception("PgLanguage::createSql() not implemented");
throw std::logic_error("PgLanguage::createSql() not implemented");
}
QString PgLanguage::dropSql() const
{
throw std::exception("PgLanguage::dropSql() not implemented");
throw std::logic_error("PgLanguage::dropSql() not implemented");
}
QString PgLanguage::typeName() const

View file

@ -48,15 +48,15 @@ namespace {
for(; index < len; index++) {
QChar ch = str[index];
if (!double_quote && ch == L'\'')
if (!double_quote && ch == u'\'')
single_quote = !single_quote;
else if (!single_quote && ch == L'"')
else if (!single_quote && ch == u'"')
double_quote = !double_quote;
else if (!double_quote && !single_quote && ch == L'(')
else if (!double_quote && !single_quote && ch == u'(')
brackets++;
else if (!double_quote && !single_quote && ch == L')')
else if (!double_quote && !single_quote && ch == u')')
brackets--;
else if (!double_quote && !single_quote && brackets == 0 && ch == L',') {
else if (!double_quote && !single_quote && brackets == 0 && ch == u',') {
if (index != start_array)
res.push_back(str.mid(start_array, index - 1).trimmed());
else

View file

@ -38,7 +38,7 @@ QString PgServerObject::alterOwnerSql(const QString& ident) const
}
void PgServerObject::setAcls(boost::optional<AclList> acls)
void PgServerObject::setAcls(std::optional<AclList> acls)
{
m_acls = std::move(acls);
}

View file

@ -4,7 +4,7 @@
#include <QString>
#include "PgObject.h"
#include "PgAcl.h"
#include <boost/optional.hpp>
#include <optional>
class PgAuthId;
@ -26,7 +26,7 @@ public:
* @brief setAcls
* @param acls Important: pass empty optional when acl IS NULL, pass empty list for empty array
*/
void setAcls(boost::optional<AclList> acls);
void setAcls(std::optional<AclList> acls);
QString aclString() const;
/**
* @brief grantSql
@ -53,7 +53,7 @@ protected:
private:
Oid m_ownerOid = InvalidOid;
const PgAuthId * m_owner = nullptr;
boost::optional<AclList> m_acls;
std::optional<AclList> m_acls;
};
#endif // PGSERVEROBJECT_H

View file

@ -2,7 +2,6 @@
#define STRINGESCAPERULE_H
#include <QString>
#include <boost/optional.hpp>
enum class ConvertToNumericEscape {
Utf8, ///< Generates upto four escapes for 4 utf8 chars

View file

@ -1,5 +1,6 @@
#pragma once
#include <cstdint>
#include <string>
std::string HumanReadableBytes(uint64_t bytes);

View file

@ -9,7 +9,7 @@
#include <botan/base64.h>
#include <botan/mac.h>
#include <botan/block_cipher.h>
#include <boost/lexical_cast.hpp>
#include <botan/exceptn.h>
using namespace Botan;
@ -33,7 +33,7 @@ public:
* distinct key which is derived by hashing the wrapped key name with HMAC.
*/
const std::vector<uint8_t> wrapped_name =
nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()),
nist_key_wrap_padded(reinterpret_cast<const uint8_t*>(name.data()),
name.size(),
*m_cipher);
@ -47,7 +47,7 @@ public:
secure_vector<uint8_t> get(const std::string& name, const std::string_view &wrapped_key) const
{
const std::vector<uint8_t> wrapped_name =
nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()),
nist_key_wrap_padded(reinterpret_cast<const uint8_t*>(name.data()),
name.size(),
*m_cipher);

View file

@ -19,7 +19,11 @@ namespace Botan {
class PasswordManagerException: public std::exception {
public:
using std::exception::exception; //(char const* const _Message);
PasswordManagerException() = default;
explicit PasswordManagerException(const char *message) : m_message(message) {}
const char *what() const noexcept override { return m_message.c_str(); }
private:
std::string m_message;
};
class PasswordManagerLockedException: public PasswordManagerException {

View file

@ -1,4 +1,5 @@
#include "ArrayParser.h"
#include <cstring>
#include <stdexcept>
using namespace Pgsql;

48
pgsql/CMakeLists.txt Normal file
View file

@ -0,0 +1,48 @@
find_package(PostgreSQL REQUIRED)
add_library(pgsql STATIC
Pgsql_Connection.cpp
Pgsql_Params.cpp
Pgsql_PgException.cpp
Pgsql_Result.cpp
Pgsql_Row.cpp
Pgsql_Value.cpp
ArrayParser.cpp
Pgsql_oids.cpp
Pgsql_Transaction.cpp
Pgsql_ErrorDetails.cpp
Pgsql_Canceller.cpp
Pgsql_Connection.h
Pgsql_IResult.h
Pgsql_Params.h
Pgsql_PgException.h
Pgsql_Result.h
Pgsql_ResultConstIterator.h
Pgsql_Row.h
Pgsql_Value.h
Pgsql_declare.h
Pgsql_Col.h
ArrayParser.h
Pgsql_oids.h
SqlGenerator.h
Pgsql_Transaction.h
Pgsql_ErrorDetails.h
Pgsql_Canceller.h
)
target_link_libraries(pgsql PUBLIC
core
PostgreSQL::PostgreSQL
Qt6::Core
Qt6::Widgets
)
target_include_directories(pgsql PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
set_target_properties(pgsql PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE ON
)

5
tests/CMakeLists.txt Normal file
View file

@ -0,0 +1,5 @@
find_package(GTest CONFIG REQUIRED)
add_subdirectory(mygtestutils)
add_subdirectory(PgsqlTests)
add_subdirectory(pglabtests)

View file

@ -0,0 +1,24 @@
add_executable(PgsqlTests
main.cpp
tst_Value.cpp
tst_ArrayParser.cpp
tst_Pgsql_oids.cpp
)
target_link_libraries(PgsqlTests PRIVATE
pgsql
mygtestutils
GTest::gtest
Qt6::Core
)
target_include_directories(PgsqlTests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
set_target_properties(PgsqlTests PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
)
add_test(NAME PgsqlTests COMMAND PgsqlTests)

View file

@ -0,0 +1,19 @@
add_library(mygtestutils STATIC
PrintTo_Qt.cpp
PrintTo_Qt.h
)
target_link_libraries(mygtestutils PUBLIC
GTest::gtest
Qt6::Core
)
target_include_directories(mygtestutils PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
set_target_properties(mygtestutils PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE ON
)

View file

@ -0,0 +1,41 @@
add_executable(pglabtests
main.cpp
TableTreeBuilderTests.cpp
tst_ConvertLangToSqlString.cpp
tst_ConvertToMultiLineCString.cpp
tst_ExplainJsonParser.cpp
tst_HumanReadableBytes.cpp
tst_TryParsePostgresqlUrl.cpp
tst_escapeConnectionStringValue.cpp
tst_expected.cpp
tst_SqlLexer.cpp
tst_scopeguard.cpp
tst_CsvWriter.cpp
tst_PasswordManager.cpp
tst_ParamJson.cpp
tst_CodeBuilder.cpp
tst_NameManglingRules.cpp
tst_TypeMappings.cpp
tst_Range.cpp
)
target_link_libraries(pglabtests PRIVATE
pglablib
pgsql
core
mygtestutils
GTest::gtest
Qt6::Core
Qt6::Widgets
)
target_include_directories(pglabtests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
set_target_properties(pglabtests PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
)
add_test(NAME pglabtests COMMAND pglabtests)

13
vcpkg.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "pglab",
"version-string": "0.1.0",
"dependencies": [
"boost-container",
"boost-timer",
"boost-chrono",
"boost-assert",
"botan",
"libpq",
"gtest"
]
}