diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 30cf57e..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Ignored default folder with query files -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/QtSettings.xml b/.idea/QtSettings.xml deleted file mode 100644 index 0248d0c..0000000 --- a/.idea/QtSettings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml deleted file mode 100644 index a3aeb4c..0000000 --- a/.idea/editor.xml +++ /dev/null @@ -1,367 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index df87cf9..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 0b76fe5..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 498ae80..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/pgLab.iml b/.idea/pgLab.iml deleted file mode 100644 index 4c94235..0000000 --- a/.idea/pgLab.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.junie/skills/reno-release-notes/SKILL.md b/.junie/skills/reno-release-notes/SKILL.md deleted file mode 100644 index 2a1779f..0000000 --- a/.junie/skills/reno-release-notes/SKILL.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -name: reno-release-notes -description: Create, review and lint pgLab release notes with reno (releasenotes/notes YAML fragments). Use whenever the user mentions reno, release notes, releasenotes/notes, "reno new", "reno lint", or asks to add or check a note for a user-visible change in pgLab, even if reno is not named explicitly. ---- - -# reno release notes in pgLab - -pgLab uses [reno](https://docs.openstack.org/reno/latest/) for release notes. -Each user-visible change adds its own small YAML file in `releasenotes/notes/`, -committed together with the change. reno derives the release from git tags, so -never move or rename notes to change releases. Content is reStructuredText, not -Markdown. - -The notes are rendered in the Sphinx user docs through the -`.. release-notes::` directive in `docs/releasenotes.rst` (docs build: -`pip install -r docs/requirements.txt && sphinx-build -b html docs userdocs`). -The About dialog links to the generated `releasenotes.html`. - -## When to write a note - -Write one for: new features, behaviour changes users will notice (UI, menus, -shortcuts, query/code generation output, connection handling), bug fixes, -known issues, and upgrade impact (e.g. changed stored settings or connection -config format). - -Skip: pure refactors, build-system/CI changes, test-only changes, internal -code cleanup, docs-only edits. - -## Sections configured for this project - -Defined in `releasenotes/config.yaml` (render order). Only these keys are valid; -others (`deprecations`, `critical`) are NOT configured here and will not render. - -| Key | Rendered title | Use for | -|------------|------------------|--------------------------------------------| -| `prelude` | (implicit) | Release-wide summary, single string | -| `features` | New Features | New functionality | -| `other` | Changes | Changes to existing behaviour, minor items | -| `issues` | Known Issues | Known problems / limitations | -| `upgrade` | Upgrade Notes | What users must do or know when upgrading | -| `security` | Security Issues | Security fixes | -| `fixes` | Bug Fixes | Ordinary bug fixes | - -Every section except `prelude` is a list of strings. Encoding is utf8. - -## Creating a note - -From the repo root (`pip install reno` if missing): - -```bash -reno new short-descriptive-slug -``` - -This creates `releasenotes/notes/short-descriptive-slug-<16 hex>.yaml`. Keep the -random suffix. Existing examples: `f5-bindings-…`, -`connection-details-in-window-title-…`, `dollar-quoted-string-…`. -Delete all unused template sections, keep only what you need. - -```yaml ---- -features: - - | - The query tool now executes the current statement with - :kbd:`F5`. -fixes: - - | - Fixed reconnect dialog that kept popping up after the connection was lost. -``` - -Short single-line items may be plain (`- text`), longer ones use `- |`. -Wrap at roughly 80 columns. - -## Writing style - -- Write for pgLab users, not patch reviewers: describe the visible effect, not - the implementation (no class or function names unless users see them). -- Past or present tense, full sentences, one bullet per distinct change. -- RST markup: inline literals with double backticks (``` ``pg_catalog`` ```), - links as `` `text `_ ``, GUI menu paths with - `:menuselection:` (e.g. `:menuselection:`Help --> About``), keys with - `:kbd:`. No Markdown, no single-backtick code. -- Mention the PostgreSQL feature or object type involved when relevant. -- For `upgrade`, state the action required. - -## Validate before committing - -```bash -reno lint -reno list # note must be tracked by git to appear for tagged history -``` - -Common failures: YAML indentation/tabs, misspelled section key, a string where -a list is required (or a list for `prelude`), an unconfigured section, bad RST. -Optionally build the docs (command above) to check rendering. - -## Commit rules - -- `git add releasenotes/notes/` and commit it in the same commit as the - change, not a follow-up. -- Commit messages are plain imperative sentences (e.g. "Fix reconnect dialog - popping up repeatedly."). -- To correct a note, edit the same file; don't add a second one. - -## Troubleshooting - -- Note missing in report: not committed, or shallow clone without tags - (`git fetch --tags --unshallow`). -- Wrong release: decided by git history/tags; check `reno list`. -- Broken rendering: almost always RST (single backticks, Markdown links, - indentation). diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index b9c08b0..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,32 +0,0 @@ -# AGENTS.md - -pgLab: Qt 6 / C++20 desktop GUI for PostgreSQL. - -## Layout (link order: core, pgsql -> pglablib -> pglab) -- `core/` – generic utilities (SQL lexer, CSV writer, `Expected`, JSON); no DB access. -- `pgsql/` – thin C++ wrapper around libpq (connection, result, params, oids). -- `pglablib/` – catalog models, code builders, query generation, connection config. -- `pglab/` – the Qt Widgets application (executable). -- `tests/` – gtest executables `PgsqlTests` and `pglabtests`, plus `mygtestutils` (Qt `PrintTo` helpers). -- `docs/` – Sphinx user docs; `releasenotes/notes` – `reno` notes. - -## Build and test -CMake + vcpkg is the active build system (`VCPKG_ROOT` must be set). Qt 6 is not provided by vcpkg; it must be installed on the system. -``` -cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake -cmake --build build -ctest --test-dir build --output-on-failure -``` -- Run one suite: `./build/tests/pglabtests/pglabtests --gtest_filter='SqlLexer*'` (same for `PgsqlTests`). -- `-DBUILD_TESTS=OFF` skips tests. `build/` is git-ignored; the first configure builds dependencies and is slow. -- Run the app headless with `QT_QPA_PLATFORM=offscreen ./build/pglab/pglab`. It needs a PostgreSQL server for anything useful. - -## Gotchas -- Tests are registered per executable, so each `ctest` entry covers a whole suite. -- A new test file must be added to the `add_executable` list in `tests//CMakeLists.txt`. Sources are listed explicitly, with no globbing. The same goes for new sources in each library's `CMakeLists.txt`. -- `core/test/` is not referenced by any CMake file (its tests are built from `tests/pglabtests`). Don't expect changes there to be compiled. -- `AUTOMOC`/`AUTOUIC`/`AUTORCC` are on globally, so `.ui` and `.qrc` files only need to be listed as sources. -- `BOOST_ENABLE_ASSERT_HANDLER` is defined; the handler lives in `core/my_boost_assert_handler.cpp`. -- Commit messages are plain imperative sentences. User-visible changes need a reno note (`releasenotes/notes`). -- Docs build: `pip install -r docs/requirements.txt && sphinx-build -b html docs userdocs`. -- Windows (VS 2022) is also a supported platform. Avoid Linux-only code, and don't break `WIN32_LEAN_AND_MEAN`/`NOMINMAX` assumptions. diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index a172074..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -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() diff --git a/README.md b/README.md index 7c2eb7e..bdf5ea0 100644 --- a/README.md +++ b/README.md @@ -11,41 +11,3 @@ 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 -``` - diff --git a/common.pri b/common.pri new file mode 100644 index 0000000..dcff145 --- /dev/null +++ b/common.pri @@ -0,0 +1,15 @@ +! include( ./local.pri ) { +error( "Use local.pri.sample to create your own local.pri" ) +} + +LIBS += -lUser32 -lws2_32 -llibpq + +CONFIG += c++20 + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which as been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS +DEFINES += BOOST_ENABLE_ASSERT_HANDLER +DEFINES += WIN32_LEAN_AND_MEAN NOMINMAX _WIN32_WINNT=0x0501 diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index ec63cf3..95b09fc 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,31 +1,41 @@ +set(CMAKE_AUTOMOC ON) + add_library(core STATIC - my_boost_assert_handler.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 - ) - + CsvWriter.cpp + my_boost_assert_handler.cpp + PasswordManager.cpp + SqlLexer.cpp) + target_link_libraries(core PUBLIC - Qt6::Core + boost + botan + Qt5::Core ) - -target_include_directories(core PUBLIC + +target_include_directories(core INTERFACE $ +) + +set_target_properties(core PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON + POSITION_INDEPENDENT_CODE True ) -set_target_properties(core PROPERTIES - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED ON - 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) diff --git a/core/core.pro b/core/core.pro new file mode 100644 index 0000000..0b424f6 --- /dev/null +++ b/core/core.pro @@ -0,0 +1,43 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-02-26T10:51:14 +# +#------------------------------------------------- + +QT -= gui + +TARGET = core +TEMPLATE = lib +CONFIG += staticlib + +! include( ../common.pri ) { +error( "Couldn't find the common.pri file!" ) +} + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += my_boost_assert_handler.cpp \ + SqlLexer.cpp \ + CsvWriter.cpp \ + BackupFormatModel.cpp \ + ExplainTreeModelItem.cpp \ + jsoncpp.cpp + +HEADERS += \ + SqlLexer.h \ + ScopeGuard.h \ + CsvWriter.h \ + BackupFormatModel.h \ + Expected.h \ + ExplainTreeModelItem.h \ + json/json.h \ + std_utils.h \ + IntegerRange.h + +unix { + target.path = /usr/lib + INSTALLS += target +} diff --git a/local.pri.sample b/local.pri.sample new file mode 100644 index 0000000..44b8f1e --- /dev/null +++ b/local.pri.sample @@ -0,0 +1,17 @@ +# Need paths here to find following library headers +# - botan +# - boost +# - libpq +# ??? +INCLUDEPATH += c:/prog/include +INCLUDEPATH += c:/prog/include/pgsql +INCLUDEPATH += c:/VSproj/boost/include/boost-1_68 + +# Need paths here to find library files for +# - botan +# - boost +# - libpq +# +LIBS += -L\VSproj\boost\lib -L/PROG/LIB + +GOOGLETEST_DIR = c:/prog/googletest diff --git a/pglab/CMakeLists.txt b/pglab/CMakeLists.txt index fb3d3f2..d6a1072 100644 --- a/pglab/CMakeLists.txt +++ b/pglab/CMakeLists.txt @@ -1,115 +1,69 @@ -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 - crud/DataGridWidget.cpp - querytool/QueryExplainModel.cpp - querytool/QueryParamListController.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 - ) +cmake_minimum_required(VERSION 3.2) +project(pglab) -set(PGLAB_FORMS - ConnectionManagerWindow.ui - CreateDatabaseDialog.ui - DatabaseWindow.ui - tuplesresultwidget.ui - querytool/QueryTab.ui - ProcessStdioWidget.ui - crud/CrudTab.ui - CodeGenerator.ui - widgets/SingleRecordWidget.ui - ) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) add_executable(pglab - ${PGLAB_SOURCES} - ${PGLAB_FORMS} + 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 ) -if(WIN32) - set_target_properties(pglab PROPERTIES WIN32_EXECUTABLE ON) +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 PRIVATE - pglablib +target_link_libraries( pglab + core pgsql - 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 + boost-system + Qt5::Widgets + pthread ) diff --git a/pglab/CreateDatabaseDialog.h b/pglab/CreateDatabaseDialog.h index 74cb209..bcc00af 100644 --- a/pglab/CreateDatabaseDialog.h +++ b/pglab/CreateDatabaseDialog.h @@ -3,7 +3,7 @@ #include -#include "catalog/PgDatabase.h" +#include "catalog\PgDatabase.h" class PgDatabaseCatalog; diff --git a/pglab/CreateDatabaseDialog.ui b/pglab/CreateDatabaseDialog.ui index a16e077..f17dbac 100644 --- a/pglab/CreateDatabaseDialog.ui +++ b/pglab/CreateDatabaseDialog.ui @@ -148,7 +148,7 @@ CodeEditor QPlainTextEdit -
codeeditor/CodeEditor.h
+
codeeditor\CodeEditor.h
diff --git a/pglab/catalog/models/DatabasesTableModel.cpp b/pglab/catalog/models/DatabasesTableModel.cpp index 289eea2..ac10ec5 100644 --- a/pglab/catalog/models/DatabasesTableModel.cpp +++ b/pglab/catalog/models/DatabasesTableModel.cpp @@ -169,7 +169,7 @@ QVariant DatabasesTableModel::getData(const QModelIndex &index) const break; case SizeCol: if (d.size.totalBytes >= 0) - v = QVariant::fromValue(d.size.totalBytes); + v = d.size.totalBytes; break; case AclCol: v = db.aclString(); diff --git a/pglab/catalog/models/IndexModel.cpp b/pglab/catalog/models/IndexModel.cpp index e04f715..7cee4e4 100644 --- a/pglab/catalog/models/IndexModel.cpp +++ b/pglab/catalog/models/IndexModel.cpp @@ -114,7 +114,7 @@ QVariant IndexModel::getData(const QModelIndex &index) const break; case SizeCol: - v = QVariant::fromValue(dat.sizeBytes); + v = dat.sizeBytes; break; case ExplicitIndexCol: v = !dat.isSupportingIndex(); diff --git a/pglab/catalog/models/SequenceModel.cpp b/pglab/catalog/models/SequenceModel.cpp index 640b50c..659348f 100644 --- a/pglab/catalog/models/SequenceModel.cpp +++ b/pglab/catalog/models/SequenceModel.cpp @@ -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 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 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 CycledCol: return seq.cycled; case CalledCol: return seq.called; case AclCol: return seq.aclString(); diff --git a/pglab/catalog/tables/TablesTableModel.cpp b/pglab/catalog/tables/TablesTableModel.cpp index d6a4e8b..0df8ff2 100644 --- a/pglab/catalog/tables/TablesTableModel.cpp +++ b/pglab/catalog/tables/TablesTableModel.cpp @@ -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() : 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); + 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; } return QVariant(); diff --git a/pglab/crud/CrudModel.cpp b/pglab/crud/CrudModel.cpp index c30a1bc..d688bd6 100644 --- a/pglab/crud/CrudModel.cpp +++ b/pglab/crud/CrudModel.cpp @@ -4,13 +4,11 @@ #include "catalog/PgAttribute.h" #include "catalog/PgAttributeContainer.h" #include "catalog/PgConstraintContainer.h" -#include "catalog/PgClassContainer.h" #include "SqlFormattingUtils.h" #include "Pgsql_oids.h" #include "CustomDataRole.h" #include #include -#include #include #include "Pgsql_oids.h" #include "Pgsql_PgException.h" @@ -41,23 +39,12 @@ void CrudModel::setConfig(std::shared_ptr db, const PgClass &table { m_database = db; m_table = table; - m_wholeTable = true; + m_primaryKey = db->catalog()->constraints()->getPrimaryForRelation(table.oid()); callLoadData = true; auto dbconfig = m_database->config(); m_dbConn.setupConnection(dbconfig); } -void CrudModel::setResult(std::shared_ptr db, std::shared_ptr result) -{ - m_database = db; - m_table.reset(); - m_wholeTable = false; - callLoadData = false; - auto dbconfig = m_database->config(); - m_dbConn.setupConnection(dbconfig); - loadIntoModel(result); -} - QVariant CrudModel::headerData(int section, Qt::Orientation orientation, int role) const { if (section == 0) @@ -163,7 +150,7 @@ QVariant StringToVariant(const std::string &str, Oid oid) case Pgsql::oid_oid: case Pgsql::int8_oid: - return QVariant::fromValue(static_cast(v)); + return v.operator int64_t(); case Pgsql::bool_oid: return v.operator bool(); @@ -218,8 +205,6 @@ QVariant CrudModel::data(const QModelIndex &index, int role) const if (value) return StringToVariant(value->c_str(), typ); } - else if (role == Qt::ForegroundRole && !m_wholeTable && columnIsReadOnly(index.column())) - return QBrush(Qt::gray); return {}; } @@ -244,18 +229,13 @@ void CrudModel::loadData() void CrudModel::loadIntoModel(std::shared_ptr data) { beginResetModel(); - bool was_dirty = hasPendingChanges(); m_pendingRowList.clear(); m_roData = data; initializeColumnList(); - m_mapping.emplace(createResultSourceMapping(*m_roData, *m_database->catalog())); lastRowKey = data->rows() - 1; initRowMapping(); - if (m_wholeTable) - appendNewRowInternal(); + appendNewRowInternal(); endResetModel(); - if (was_dirty) - emit pendingChangesChanged(false); } void CrudModel::initRowMapping() @@ -297,7 +277,7 @@ Qt::ItemFlags CrudModel::flags(const QModelIndex &index) const if (index.column() < PreColumnCount) return flags; - if (!columnIsReadOnly(index.column())) + if (m_primaryKey && !columnIsReadOnly(index.column())) flags |= Qt::ItemIsEditable; return flags; @@ -324,10 +304,7 @@ bool CrudModel::setData(const QModelIndex &index, const QVariant &value, int rol s.clear(); val = s; } - bool was_dirty = hasPendingChanges(); m_pendingRowList.setValue(col, row_mapping.rowKey, val); - if (!was_dirty) - emit pendingChangesChanged(true); emit dataChanged(index, index, QVector() << role); return true; @@ -335,17 +312,22 @@ bool CrudModel::setData(const QModelIndex &index, const QVariant &value, int rol return false; } -std::tuple> CrudModel::saveRow(const PendingRow &pending_row, const RowMapping &row_mapping) +std::tuple> CrudModel::saveRow(const PendingRow &pending_row) { auto data = pending_row.data(); - if (data.empty()) - return { true, {} }; + RowMapping& rowmapping = m_rowMapping[pending_row.row()]; - if (row_mapping.isNew()) + if (!data.empty()) { QString buffer; Pgsql::Params params; - std::tie(buffer, params) = createInsertQuery(pending_row); + if (rowmapping.isNew()) + std::tie(buffer, params) = createInsertQuery(pending_row); + else + { + Pgsql::Params pkey_params = getPKeyParamsForRow(pending_row.row()); + std::tie(buffer, params) = createUpdateQuery(pkey_params, pending_row); + } Pgsql::Connection db_update_conn; auto dbconfig = m_database->config(); @@ -374,120 +356,59 @@ std::tuple> CrudModel::saveRow(const Pending msgBox.setText(ex.what()); msgBox.exec(); } - return { false, {} }; - } - - return saveUpdate(pending_row, row_mapping); -} - -std::tuple> CrudModel::saveUpdate(const PendingRow &pending_row, const RowMapping &row_mapping) -{ - std::set tables; - for (const auto &e : pending_row.data()) - tables.insert(m_mapping->tableOid(e.first)); - - try - { - Pgsql::Connection db_update_conn; - auto dbconfig = m_database->config(); - db_update_conn.connect(dbconfig.connectionString()); - auto tx = Pgsql::Transaction::startTransaction(db_update_conn); - for (Oid table_oid : tables) - { - QString buffer; - Pgsql::Params params; - std::tie(buffer, params) = createUpdateQuery(table_oid, pending_row, row_mapping); - auto result = tx.queryParam(buffer, params); - if (!result || result.tuplesAffected() != 1) - { - QMessageBox::warning(nullptr, tr("Update failed"), tr("The change did not update exactly one row in %1, the row may have changed or is no longer unique.").arg(tableName(table_oid))); - return { false, {} }; - } - } - tx.commit(); - - std::vector values; - values.reserve(m_roData->cols()); - for (int col = 0; col < m_roData->cols(); ++col) - { - auto &&pending = pending_row.data(); - auto found = pending.find(col); - values.push_back(found != pending.end() ? found->second : getSavedData(row_mapping, col)); - } - return { true, values }; - } - catch (const Pgsql::PgResultError &ex) - { - QMessageBox msgBox; - msgBox.setText(ex.what()); - msgBox.exec(); } return { false, {} }; } -QString CrudModel::tableName(Oid table_oid) const +Pgsql::Params CrudModel::getPKeyParamsForRow(int row) const { - auto table = m_database->catalog()->classes()->getByKey(table_oid); - return table ? table->fullyQualifiedQuotedObjectName() : QString(); -} - -std::vector CrudModel::resultColumnsOfTable(Oid table_oid) const -{ - std::vector cols; - for (int col = 0; col < m_roData->cols(); ++col) - if (m_roData->ftable(col) == table_oid && m_roData->ftableCol(col) > 0) - cols.push_back(col); - return cols; -} - -QString CrudModel::attributeName(int col) const -{ - if (col < static_cast(columnList.size()) && columnList[col]) - return columnList[col]->name; - return QString::fromUtf8(m_roData->getColName(col)); -} - -Pgsql::Params CrudModel::getPKeyParamsForRow(const RowMapping &row_mapping, Oid table_oid) const -{ - Pgsql::Params params; - for (int col : m_mapping->primaryKeyColumns(table_oid)) + Pgsql::Params params; + for (auto attnum : m_primaryKey->key) { - auto s = getSavedData(row_mapping, col); - params.add(s, getType(col)); - } - return params; + const int col = attNumToCol(attnum); + Oid t = getType(col); + auto s = getSavedData(col, row); + params.add(s, t); + } + return params; } -std::tuple CrudModel::createUpdateQuery(Oid table_oid, const PendingRow &pending_row, const RowMapping &row_mapping) +QString CrudModel::columnName(int col) const { - Pgsql::Params params; - QString table_name = tableName(table_oid); - QString buffer; - QTextStream q(&buffer); - q << "UPDATE " << table_name << " AS d\n SET "; - int param = 0; - for (auto& e : pending_row.data()) - { - if (m_mapping->tableOid(e.first) != table_oid) - continue; - if (param > 0) - q << ","; - q << quoteIdent(attributeName(e.first)) << "=$" << ++param; - params.add(e.second, getType(e.first)); - } + return m_roData->getColName(col); +} - q << "\nWHERE "; - int i = 0; - for (int col : m_mapping->primaryKeyColumns(table_oid)) +std::tuple CrudModel::createUpdateQuery(const Pgsql::Params &pkey_params, const PendingRow &pending_row) +{ + Pgsql::Params params; + auto data = pending_row.data(); + QString table_name = m_table->fullyQualifiedQuotedObjectName(); + QString buffer; + QTextStream q(&buffer); + q << "UPDATE " << table_name << " AS d\n SET "; + int param = 0; + for (auto& e : data) { - if (i > 0) - q << " AND "; - q << quoteIdent(attributeName(col)) << "=$" << ++param; - ++i; - } - params.addParams(getPKeyParamsForRow(row_mapping, table_oid)); - q.flush(); - return { buffer, params }; + if (param > 0) + q << ","; + q << quoteIdent(columnName(e.first)) << "=$" << ++param; + params.add(e.second, getType(e.first)); + } + + q << "\nWHERE "; + int i = 0; + for (auto attnum : m_primaryKey->key) + { + int col = attNumToCol(attnum); + if (i > 0) + q << " AND "; + q << quoteIdent(columnName(col)) << "=$" << ++param; + ++i; + } + params.addParams(pkey_params); + q << "\nRETURNING *"; + q.flush(); + return { buffer, params }; } std::tuple CrudModel::createInsertQuery(const PendingRow &pending_row) @@ -503,11 +424,12 @@ std::tuple CrudModel::createInsertQuery(const PendingRow for (const auto& e : data) { int num = e.first; + auto&& column = columnList[num]; if (first) first = false; else q << ","; - q << quoteIdent(attributeName(num)); + q << quoteIdent(column.name); } q << ") VALUES ($1"; for (size_t p = 2; p <= data.size(); ++p) @@ -520,6 +442,19 @@ std::tuple CrudModel::createInsertQuery(const PendingRow return { buffer, params }; } +std::tuple CrudModel::createDeleteStatement(const PKeyValues &pkey_values) +{ + Pgsql::Params params; + size_t i = 0; + for (auto attnum : m_primaryKey->key) + { + const int col = attNumToCol(attnum); + params.add(pkey_values[i].c_str(), getType(col)); + ++i; + } + + return { createDeleteStatement(), params }; +} QString CrudModel::createDeleteStatement() const { @@ -530,11 +465,12 @@ QString CrudModel::createDeleteStatement() const q << "DELETE FROM " << table_name; q << "\nWHERE "; int i = 0; - for (int col : m_mapping->primaryKeyColumns(m_table->oid())) + for (auto attnum : m_primaryKey->key) { + const int col = attNumToCol(attnum); if (i > 0) q << " AND "; - q << quoteIdent(attributeName(col)) << "=$" << ++i; + q << quoteIdent(columnName(col)) << "=$" << ++i; } q.flush(); @@ -547,13 +483,7 @@ bool CrudModel::savePendingChanges() while (!m_pendingRowList.m_rows.empty()) { auto iter = m_pendingRowList.m_rows.begin(); - int pendingKey = iter->first; - auto mapping_iter = std::find_if(m_rowMapping.begin(), m_rowMapping.end(), - [pendingKey](const RowMapping &rhs) -> bool { return rhs.rowKey == pendingKey; }); - if (mapping_iter == m_rowMapping.end()) - return false; - - auto [ok, modified_row] = saveRow(iter->second, *mapping_iter); + auto [ok, modified_row] = saveRow(iter->second); if (ok) { int rowKey = iter->first; @@ -586,13 +516,9 @@ bool CrudModel::columnIsReadOnly(int column_index) const if (m_roData == nullptr) return true; - int col = column_index - PreColumnCount; - if (!m_mapping || !m_mapping->isEditable(col)) - return true; - - const auto &column = columnList[col]; - return !column || column->getIdentity() == PgAttribute::Identity::Always - || column->getGenerated() != PgAttribute::Generated::None; + auto&& column = columnList[column_index - PreColumnCount]; + return column.getIdentity() == PgAttribute::Identity::Always + || column.getGenerated() != PgAttribute::Generated::None; } void CrudModel::initializeColumnList() @@ -600,51 +526,28 @@ void CrudModel::initializeColumnList() columnList.clear(); columnList.reserve(m_roData->cols()); auto atts = m_database->catalog()->attributes(); + auto columns = atts->getColumnsForRelation(m_table->oid()); for (int col = 0; col < m_roData->cols(); ++col) { int attnum = m_roData->ftableCol(col); - if (attnum <= 0) - { - columnList.emplace_back(); - continue; - } - auto columns = atts->getColumnsForRelation(m_roData->ftable(col)); auto find_result = std::find_if(columns.begin(), columns.end(), [attnum](const PgAttribute &att) { return att.num == attnum; }); - if (find_result != columns.end()) - columnList.emplace_back(*find_result); - else - columnList.emplace_back(); + assert(find_result != columns.end()); + columnList.push_back(*find_result); } } -bool CrudModel::saveChanges() +bool CrudModel::submit() { - bool was_dirty = hasPendingChanges(); - bool ok = savePendingChanges(); - if (was_dirty != hasPendingChanges()) - emit pendingChangesChanged(hasPendingChanges()); - return ok; + return savePendingChanges(); } -void CrudModel::revertChanges() +void CrudModel::revert() { - if (!hasPendingChanges()) - return; - m_pendingRowList.clear(); - for (auto &row_mapping : m_rowMapping) - row_mapping.pending = false; - - if (rowCount() > 0) - emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); - emit pendingChangesChanged(false); } void CrudModel::appendNewRow() { - if (!m_wholeTable) - return; - int row = static_cast(m_rowMapping.size()); beginInsertRows(QModelIndex(), row, row); appendNewRowInternal(); @@ -662,8 +565,6 @@ std::tuple CrudModel::removeRows(const std::set return { true, "" }; if (row_ranges.rbegin()->end() > static_cast(m_rowMapping.size())) return { false, "Range error" }; - if (!m_wholeTable || !m_table || m_mapping->primaryKeyColumns(m_table->oid()).empty()) - return { false, "Rows can only be removed from a table with a primary key" }; // When removing rows there is no direct mapping anymore between the rows in the grid // and the rows in m_roData @@ -688,7 +589,7 @@ std::tuple CrudModel::removeRows(const std::set for (int current_row = range.start(); current_row < range.end(); ++current_row) { auto&& mapping = m_rowMapping[static_cast(current_row)]; - auto params = getPKeyParamsForRow(mapping, m_table->oid()); + auto params = getPKeyParamsForRow(mapping.rowKey); if (!params.empty()) db_update_conn.queryParam(delete_statement, params); } @@ -697,10 +598,7 @@ std::tuple CrudModel::removeRows(const std::set // If something goes wrong after this commit we should reload contents of model } // Then from model - bool was_dirty = hasPendingChanges(); RemoveRangesOfRowsFromModel(row_ranges); - if (was_dirty != hasPendingChanges()) - emit pendingChangesChanged(hasPendingChanges()); return { true, "" }; } catch (const Pgsql::PgResultError &error) @@ -735,8 +633,6 @@ void CrudModel::RemoveRangeOfRowsFromModel(IntegerRange range) beginRemoveRows(QModelIndex(), range.start(), range.end() - 1); SCOPE_EXIT { endRemoveRows(); }; auto first = m_rowMapping.begin() + range.start(); - for (auto iter = first; iter != first + range.length(); ++iter) - m_pendingRowList.m_rows.erase(iter->rowKey); m_rowMapping.erase(first, first + range.length()); } diff --git a/pglab/crud/CrudModel.h b/pglab/crud/CrudModel.h index 485b6c2..5a6beb3 100644 --- a/pglab/crud/CrudModel.h +++ b/pglab/crud/CrudModel.h @@ -5,10 +5,8 @@ #include "Pgsql_Connection.h" #include "IntegerRange.h" -#include "ResultSourceMapping.h" #include "catalog/PgClass.h" #include "catalog/PgConstraint.h" -#include "catalog/PgAttribute.h" #include #include #include @@ -28,8 +26,6 @@ public: ~CrudModel() override; void setConfig(std::shared_ptr db, const PgClass &table); - /// Show an arbitrary query result, cells are editable where the result maps them to a table with a primary key - void setResult(std::shared_ptr db, std::shared_ptr result); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; @@ -46,23 +42,15 @@ public: void loadData(); - /// True when there are edits that have not been saved to the database - bool hasPendingChanges() const { return !m_pendingRowList.m_rows.empty(); } - /// Writes all pending rows to the database, rows that fail stay pending - bool saveChanges(); - /// Discards all pending edits, the grid shows the last saved values again - void revertChanges(); - std::tuple removeRows(const std::set> &row_ranges); bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; -signals: - /// Emitted when the model goes from clean to having pending changes or back - void pendingChangesChanged(bool hasChanges); - public slots: + virtual bool submit() override; + virtual void revert() override; private: + using PKeyValues = std::vector; const int PreColumnCount = 1; @@ -88,7 +76,6 @@ private: res += " NULLS FIRST"; else if (nulls == NullSorting::Last) res += " NULLS LAST"; - return res; } }; @@ -218,11 +205,10 @@ private: std::shared_ptr m_database; std::optional m_table; - std::optional m_mapping; + std::optional m_primaryKey; ASyncDBConnection m_dbConn; bool callLoadData = false; - bool m_wholeTable = false; ///< true when showing a whole table (CRUD), false for an arbitrary query result std::shared_ptr m_roData; @@ -232,7 +218,7 @@ private: // identity allways or generated }; - std::vector> columnList; // attribute per column in m_roData, empty for expression columns + std::vector columnList; // list of columnMeta 1 to 1 with columns in m_roData. PendingRowList m_pendingRowList; @@ -252,30 +238,29 @@ private: Oid getType(int column) const; + QString columnName(int col) const; /// Get the PKey values from when the row was last restored or retrieved. - Pgsql::Params getPKeyParamsForRow(const RowMapping &row_mapping, Oid table_oid) const; + Pgsql::Params getPKeyParamsForRow(int row) const; bool savePendingChanges(); - std::tuple createUpdateQuery(Oid table_oid, const PendingRow &pending_row, const RowMapping &row_mapping); + std::tuple createUpdateQuery(const Pgsql::Params &pkey_params, const PendingRow &pending_row); std::tuple createInsertQuery(const PendingRow &pending_row); + std::tuple createDeleteStatement(const PKeyValues &pkey_values); QString createDeleteStatement() const; - std::tuple> saveRow(const PendingRow &pending_row, const RowMapping &row_mapping); - std::tuple> saveUpdate(const PendingRow &pending_row, const RowMapping &row_mapping); + std::tuple> saveRow(const PendingRow &pending_row); void appendNewRow(); void appendNewRowInternal(); int lastRowKey = -1; int allocNewRowKey() { return ++lastRowKey; } - /// Name of the table with the given oid, quoted and schema qualified - QString tableName(Oid table_oid) const; - /// Result columns that are user columns (attnum > 0) of the given table, in result order - std::vector resultColumnsOfTable(Oid table_oid) const; - /// Catalog name of the attribute behind a result column, falls back to the result column name - QString attributeName(int col) const; + /// Convert an attnum from the database catalog to the corresponding column in the model + /// + /// \todo still assumes columns are in order, all being shown and no special column like oid shown. + int attNumToCol(int attnum) const { return attnum - 1; } void RemoveRangesOfRowsFromModel(const std::set> &row_ranges); void RemoveRangeOfRowsFromModel(IntegerRange row_range); diff --git a/pglab/crud/CrudTab.cpp b/pglab/crud/CrudTab.cpp index 8bb44b0..a776879 100644 --- a/pglab/crud/CrudTab.cpp +++ b/pglab/crud/CrudTab.cpp @@ -3,19 +3,16 @@ #include "CrudModel.h" #include "CustomDataRole.h" #include "ResultTableModelUtil.h" -#include "DataGridWidget.h" +#include "util/PgLabItemDelegate.h" #include "IntegerRange.h" #include "OpenDatabase.h" #include "catalog/PgClassContainer.h" #include "catalog/PgDatabaseCatalog.h" #include -#include #include -#include #include #include #include -#include CrudTab::CrudTab(IDatabaseWindow *context, QWidget *parent) @@ -27,18 +24,25 @@ CrudTab::CrudTab(IDatabaseWindow *context, QWidget *parent) m_db = context->openDatabase(); + SetTableViewDefault(ui->tableView); + + auto delegate = new PgLabItemDelegate(ui->tableView); + ui->tableView->setItemDelegate(delegate); + m_crudModel = new CrudModel(nullptr); - m_grid = new DataGridWidget(this); - ui->verticalLayout->addWidget(m_grid); - m_grid->setSourceModel(m_crudModel); - m_grid->view()->verticalHeader()->setVisible(false); + m_SortFilterProxy = new QSortFilterProxyModel(this); + m_SortFilterProxy->setSourceModel(m_crudModel); + m_SortFilterProxy->setSortRole(CustomSortRole); + ui->tableView->setModel(m_SortFilterProxy); + ui->tableView->setSortingEnabled(true); + m_SortFilterProxy->sort(0, Qt::AscendingOrder); - m_grid->view()->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_grid->view()->setSelectionBehavior(QAbstractItemView::SelectRows); - m_grid->view()->addAction(ui->actionRemove_rows); + ui->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); + ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->tableView->addAction(ui->actionRemove_rows); - auto horizontal_header = m_grid->view()->horizontalHeader(); + auto horizontal_header = ui->tableView->horizontalHeader(); horizontal_header->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); connect(horizontal_header, &QHeaderView::customContextMenuRequested, this, &CrudTab::headerCustomContextMenu); @@ -60,20 +64,13 @@ void CrudTab::setConfig(Oid oid) //std::shared_ptr db, const PgCla void CrudTab::refresh() { - if (m_crudModel->hasPendingChanges()) { - auto res = QMessageBox::question(this, "pgLab", - tr("There are unsaved changes. Refreshing discards them. Do you want to continue?"), - QMessageBox::Yes, QMessageBox::No); - if (res != QMessageBox::Yes) - return; - } m_crudModel->loadData(); } void CrudTab::on_actionRemove_rows_triggered() { std::set> row_ranges; - auto selection = m_grid->proxy()->mapSelectionToSource(m_grid->view()->selectionModel()->selection()); + auto selection = m_SortFilterProxy->mapSelectionToSource(ui->tableView->selectionModel()->selection()); for (auto& range : selection) { row_ranges.emplace(range.top(), range.height()); } @@ -113,7 +110,7 @@ void CrudTab::headerCustomContextMenu(const QPoint &pos) if (headerContextMenu) { - auto horizontal_header = m_grid->view()->horizontalHeader(); + auto horizontal_header = ui->tableView->horizontalHeader(); headerContextMenu->popup(horizontal_header->mapToGlobal(pos)); } } @@ -125,9 +122,9 @@ void CrudTab::gotoColumn(QString column) auto name = m_crudModel->headerData(i, Qt::Horizontal).toString(); if (name == column) { - auto index = m_grid->view()->currentIndex(); - index = m_grid->view()->model()->index(index.row(), i); - m_grid->view()->setCurrentIndex(index); + auto index = ui->tableView->currentIndex(); + index = ui->tableView->model()->index(index.row(), i); + ui->tableView->setCurrentIndex(index); break; } } diff --git a/pglab/crud/CrudTab.h b/pglab/crud/CrudTab.h index a6bf0fc..ef91325 100644 --- a/pglab/crud/CrudTab.h +++ b/pglab/crud/CrudTab.h @@ -1,6 +1,7 @@ #ifndef CRUDTAB_H #define CRUDTAB_H +#include #include "catalog/PgClass.h" #include "IDatabaseWindow.h" #include @@ -13,7 +14,6 @@ namespace Ui { class OpenDatabase; class CrudModel; -class DataGridWidget; class QMenu; class CrudTab : public QWidget { @@ -33,7 +33,7 @@ private: std::optional m_table; CrudModel *m_crudModel = nullptr; - DataGridWidget *m_grid = nullptr; + QSortFilterProxyModel *m_SortFilterProxy = nullptr; QMenu *headerContextMenu = nullptr; diff --git a/pglab/crud/CrudTab.ui b/pglab/crud/CrudTab.ui index f3118a4..8c58abd 100644 --- a/pglab/crud/CrudTab.ui +++ b/pglab/crud/CrudTab.ui @@ -13,7 +13,15 @@ Form - + + + + + false + + + + Remove rows diff --git a/pglab/crud/DataGridWidget.cpp b/pglab/crud/DataGridWidget.cpp deleted file mode 100644 index 65708f7..0000000 --- a/pglab/crud/DataGridWidget.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "DataGridWidget.h" -#include "CrudModel.h" -#include "CustomDataRole.h" -#include "ResultTableModelUtil.h" -#include "util/PgLabItemDelegate.h" -#include -#include -#include -#include -#include -#include - - -DataGridWidget::DataGridWidget(QWidget *parent) - : QWidget(parent) -{ - auto layout = new QVBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - - m_toolbar = new QToolBar(this); - m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - layout->addWidget(m_toolbar); - - m_actSave = new QAction(style()->standardIcon(QStyle::SP_DialogSaveButton), tr("Save"), this); - m_actSave->setShortcut(QKeySequence::Save); - m_actSave->setShortcutContext(Qt::WidgetWithChildrenShortcut); - m_actSave->setToolTip(tr("Save all pending changes to the database (Ctrl+S)")); - m_actSave->setEnabled(false); - addAction(m_actSave); - m_toolbar->addAction(m_actSave); - connect(m_actSave, &QAction::triggered, this, &DataGridWidget::onSave); - - m_actRevert = new QAction(style()->standardIcon(QStyle::SP_DialogResetButton), tr("Revert"), this); - m_actRevert->setToolTip(tr("Discard all pending changes")); - m_actRevert->setEnabled(false); - m_toolbar->addAction(m_actRevert); - connect(m_actRevert, &QAction::triggered, this, &DataGridWidget::onRevert); - - m_view = new QTableView(this); - layout->addWidget(m_view); - - SetTableViewDefault(m_view); - auto delegate = new PgLabItemDelegate(m_view); - m_view->setItemDelegate(delegate); -} - -void DataGridWidget::setSourceModel(QAbstractItemModel *model) -{ - if (!m_proxy) { - m_proxy = new QSortFilterProxyModel(this); - m_proxy->setSortRole(CustomSortRole); - } - disconnect(m_pendingChangesConnection); - m_proxy->setSourceModel(model); - m_view->setModel(m_proxy); - m_view->setSortingEnabled(true); - m_proxy->sort(0, Qt::AscendingOrder); - - auto crud = qobject_cast(model); - m_toolbar->setVisible(crud != nullptr); - if (crud) { - m_pendingChangesConnection = connect(crud, &CrudModel::pendingChangesChanged, this, &DataGridWidget::onPendingChangesChanged); - onPendingChangesChanged(crud->hasPendingChanges()); - } - else { - onPendingChangesChanged(false); - } -} - -void DataGridWidget::onPendingChangesChanged(bool hasChanges) -{ - m_actSave->setEnabled(hasChanges); - m_actRevert->setEnabled(hasChanges); -} - -void DataGridWidget::onSave() -{ - auto crud = qobject_cast(m_proxy->sourceModel()); - if (!crud) - return; - - commitOpenEditor(); - crud->saveChanges(); -} - -void DataGridWidget::onRevert() -{ - auto crud = qobject_cast(m_proxy->sourceModel()); - if (!crud) - return; - - commitOpenEditor(); - crud->revertChanges(); -} - -void DataGridWidget::commitOpenEditor() -{ - // Moving focus away from the editor makes the delegate commit its value, a toolbar click does not take focus - if (m_view->indexWidget(m_view->currentIndex())) - m_view->setFocus(); -} diff --git a/pglab/crud/DataGridWidget.h b/pglab/crud/DataGridWidget.h deleted file mode 100644 index d605053..0000000 --- a/pglab/crud/DataGridWidget.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef DATAGRIDWIDGET_H -#define DATAGRIDWIDGET_H - -#include - -class QAbstractItemModel; -class QAction; -class QSortFilterProxyModel; -class QTableView; -class QToolBar; - -/** Sortable and editable grid shared by the CRUD tab and the query result tab. - */ -class DataGridWidget : public QWidget { - Q_OBJECT -public: - explicit DataGridWidget(QWidget *parent = nullptr); - - /// Sets the model to show, sorting is done by a proxy that uses CustomSortRole - void setSourceModel(QAbstractItemModel *model); - - QTableView *view() const { return m_view; } - QSortFilterProxyModel *proxy() const { return m_proxy; } - -private slots: - void onPendingChangesChanged(bool hasChanges); - void onSave(); - void onRevert(); - -private: - /// Closes an open cell editor so its value is written to the model before saving or reverting - void commitOpenEditor(); - - QTableView *m_view = nullptr; - QSortFilterProxyModel *m_proxy = nullptr; - QToolBar *m_toolbar = nullptr; - QAction *m_actSave = nullptr; - QAction *m_actRevert = nullptr; - QMetaObject::Connection m_pendingChangesConnection; -}; - -#endif // DATAGRIDWIDGET_H diff --git a/pglab/pglab.pro b/pglab/pglab.pro new file mode 100644 index 0000000..93a9186 --- /dev/null +++ b/pglab/pglab.pro @@ -0,0 +1,229 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2016-11-11T09:17:13 +# +#------------------------------------------------- + +QT += core gui concurrent + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets sql + + +TARGET = pglab +TEMPLATE = app + +! include( ../common.pri ) { +error( "Couldn't find the common.pri file!" ) +} + +DEFINES += _WIN32_WINNT=0x0501 + +win32:RC_ICONS += pglab.ico + +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 + +HEADERS += \ + About.h \ + Config.h \ + catalog/delegates/IconColumnDelegate.h \ + catalog/models/BaseTableModel.h \ + catalog/models/ColumnTableModel.h \ + catalog/models/ConstraintModel.h \ + catalog/models/DatabasesTableModel.h \ + catalog/models/DependantsTableModel.h \ + catalog/models/IndexModel.h \ + catalog/models/ProcTableModel.h \ + catalog/models/RolesTableModel.h \ + catalog/models/SequenceModel.h \ + catalog/models/TriggerTableModel.h \ + catalog/tables/TablesTableModel.h \ + catalog/widgets/CatalogConstraintPage.h \ + catalog/widgets/CatalogFunctionsPage.h \ + catalog/widgets/CatalogIndexPage.h \ + catalog/widgets/CatalogInspector.h \ + catalog/widgets/CatalogNamespacePage.h \ + catalog/widgets/CatalogPageBase.h \ + catalog/widgets/CatalogSequencesPage.h \ + catalog/widgets/CatalogTablesPage.h \ + catalog/widgets/CatalogTypesPage.h \ + catalog/widgets/ColumnPage.h \ + catalog/widgets/DependantsPage.h \ + catalog/widgets/TriggerPage.h \ + codeeditor/CodeEditor.h \ + codeeditor/EditorGutter.h \ + codeeditor/GutterPainter.h \ + crud/CrudModel.h \ + crud/CrudTab.h \ + querytool/QueryExplainModel.h \ + querytool/QueryParamListController.h \ + querytool/QueryResultModel.h \ + querytool/QueryTool.h \ + serverinspector/DatabasesPage.h \ + serverinspector/RolesPage.h \ + serverinspector/ServerInspector.h \ + util/Colors.h \ + util/PgLabItemDelegate.h \ + util/PgLabTableView.h \ + util/PgLabTableViewHelper.h \ + util/SqlSyntaxHighlighter.h \ + ConnectionConfigurationWidget.h \ + ConnectionController.h \ + IDatabaseWindow.h \ + ManagedPage.h \ + PgDumpOutputHighlighter.h \ + CreateDatabaseDialog.h \ + ConnectionManagerWindow.h \ + ConnectionListModel.h \ + SslModeModel.h \ + stopwatch.h \ + TuplesResultWidget.h \ + BackupDialog.h \ + MasterController.h \ + ParamTypeDelegate.h \ + OpenDatabase.h \ + ProcessStdioWidget.h \ + ResultTableModelUtil.h \ + NamespaceItemModel.h \ + AbstractCommand.h \ + PropertyProxyModel.h \ + CustomDataRole.h \ + CodeGenerator.h \ + UserConfiguration.h \ + widgets/SqlCodePreview.h \ + CustomFilterSortModel.h \ + PropertiesPage.h \ + PasswordPromptDialog.h \ + EditTableWidget.h \ + EditColumnTableModel.h \ + DatabaseWindow.h \ + NamespaceFilter.h \ + widgets/SingleRecordModel.h \ + widgets/SingleRecordWidget.h + +FORMS += \ + ConnectionManagerWindow.ui \ + CreateDatabaseDialog.ui \ + DatabaseWindow.ui \ + TuplesResultWidget.ui \ + querytool/QueryTab.ui \ + ProcessStdioWidget.ui \ + crud/CrudTab.ui \ + CodeGenerator.ui \ + widgets/SingleRecordWidget.ui + +RESOURCES += \ + resources.qrc + +#QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS,5.01 + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../core/release/ -lcore +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../core/debug/ -lcore + +INCLUDEPATH += $$PWD/../core +DEPENDPATH += $$PWD/../core + +win32:CONFIG(debug, debug|release): LIBS += -lbotand +else:win32:CONFIG(release, debug|release): LIBS += -lbotan + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/libcore.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/libcore.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/core.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/core.lib + + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../pgsql/release/ -lpgsql +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../pgsql/debug/ -lpgsql +else:unix:!macx: LIBS += -L$$OUT_PWD/../pgsql/ -lpgsql + +INCLUDEPATH += $$PWD/../pgsql +DEPENDPATH += $$PWD/../pgsql + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/release/libpgsql.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/debug/libpgsql.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/release/pgsql.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/debug/pgsql.lib +else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../pgsql/libpgsql.a + + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../pglablib/release/ -lpglablib +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../pglablib/debug/ -lpglablib +else:unix:!macx: LIBS += -L$$OUT_PWD/../pglablib/ -lpglablib + +INCLUDEPATH += $$PWD/../pglablib +DEPENDPATH += $$PWD/../pglablib + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pglablib/release/libpglablib.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pglablib/debug/libpglablib.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pglablib/release/pglablib.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pglablib/debug/pglablib.lib +else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../pglablib/libpglablib.a diff --git a/pglab/querytool/QueryExplainModel.cpp b/pglab/querytool/QueryExplainModel.cpp index bcce73b..51381ec 100644 --- a/pglab/querytool/QueryExplainModel.cpp +++ b/pglab/querytool/QueryExplainModel.cpp @@ -40,7 +40,7 @@ if (role == Qt::DisplayRole) { result = item->estimateError(); break; case c_ColumnRowCount: - result = QVariant::fromValue(item->actualRows); + result = item->actualRows; break; case c_ColumnLoops: result = item->actualLoops; diff --git a/pglab/querytool/QueryResultModel.cpp b/pglab/querytool/QueryResultModel.cpp new file mode 100644 index 0000000..360a00c --- /dev/null +++ b/pglab/querytool/QueryResultModel.cpp @@ -0,0 +1,85 @@ +#include "QueryResultModel.h" +#include "ResultTableModelUtil.h" +#include "Pgsql_declare.h" +#include "Pgsql_oids.h" +#include "catalog/PgDatabaseCatalog.h" +#include "CustomDataRole.h" +#include +#include + +using namespace Pgsql; + +QueryResultModel::QueryResultModel(QObject *parent, std::shared_ptr r + , std::shared_ptr cat) + : QAbstractTableModel(parent) + , result(std::move(r)) + , catalog(cat) +{} + + +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 +{ +// static const QString null_str("null"); + QVariant v; + + const int col = index.column(); + const int rij = index.row(); + const Oid oid = result->type(col); + if (role == Qt::DisplayRole || role == Qt::EditRole) { + if (result->null(col, rij)) { + //v = nullptr; + } + else { + Value value = result->get(col, rij); + if (oid == bool_oid) { + bool b = value; + v = b; + } + else { + QString s = value; + v = s; + } + } + } + else if (role == CustomDataTypeRole) { + v = oid; + } + + + return v; +} + +QVariant QueryResultModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant r; + if (role == Qt::DisplayRole) { + if (orientation == Qt::Horizontal) { + QString s(result->getColName(section)); + s += "\n"; + s += getTypeDisplayString(*catalog, result->type(section)); + r = s; + } + else { + r = QString::number(section + 1); + } + } + Qt::ItemFlags f; + return r; +} + +Qt::ItemFlags QueryResultModel::flags(const QModelIndex &) const +{ + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; +} diff --git a/pglab/querytool/QueryResultModel.h b/pglab/querytool/QueryResultModel.h new file mode 100644 index 0000000..7de2f70 --- /dev/null +++ b/pglab/querytool/QueryResultModel.h @@ -0,0 +1,31 @@ +#ifndef QUERYRESULTMODEL_H +#define QUERYRESULTMODEL_H + +#include +#include "catalog/models/BaseTableModel.h" +#include "Pgsql_Connection.h" + +class PgDatabaseCatalog; + +class QueryResultModel : public QAbstractTableModel +{ + Q_OBJECT +public: + QueryResultModel(QObject *parent, std::shared_ptr r, std::shared_ptr cat); + + 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; + + std::shared_ptr GetPgsqlResult() const { return result; } +protected: +// virtual Oid getType(int column) const override; +// virtual QVariant getData(const QModelIndex &index) const override; +private: + std::shared_ptr result; + std::shared_ptr catalog; +}; + +#endif // QUERYRESULTMODEL_H diff --git a/pglab/querytool/QueryTool.cpp b/pglab/querytool/QueryTool.cpp index f808335..153b19c 100644 --- a/pglab/querytool/QueryTool.cpp +++ b/pglab/querytool/QueryTool.cpp @@ -476,8 +476,10 @@ void QueryTool::query_ready(std::shared_ptr dbres, qint64 elapsed //int n_rows = dbres->getRows(); //QString rowcount_str = QString("rows: %1").arg(dbres->getRows()); + auto result_model = std::make_shared(nullptr , dbres, + m_catalog); TuplesResultWidget *trw = new TuplesResultWidget; - trw->setResult(m_context->openDatabase(), dbres, elapsedms); + trw->setResult(result_model, elapsedms); resultList.push_back(trw); ui->tabWidget->addTab(trw, "Data"); if (resultList.size() == 1) diff --git a/pglab/querytool/QueryTool.h b/pglab/querytool/QueryTool.h index 1274642..5cb98f0 100644 --- a/pglab/querytool/QueryTool.h +++ b/pglab/querytool/QueryTool.h @@ -3,6 +3,7 @@ #include "ManagedPage.h" #include "ASyncDBConnection.h" +#include "QueryResultModel.h" #include "QueryExplainModel.h" #include "stopwatch.h" #include "tuplesresultwidget.h" @@ -25,6 +26,7 @@ class QTableView; class QTabWidget; class SqlSyntaxHighlighter; class ExplainRoot; +class QueryResultModel; class QueryExplainModel; class PgTypeContainer; class OpenDatabase; diff --git a/pglab/tuplesresultwidget.cpp b/pglab/tuplesresultwidget.cpp index 21b090a..1e2dd5b 100644 --- a/pglab/tuplesresultwidget.cpp +++ b/pglab/tuplesresultwidget.cpp @@ -1,15 +1,11 @@ -#include "tuplesresultwidget.h" +#include "tuplesresultwidget.h" #include "ui_tuplesresultwidget.h" #include "ResultTableModelUtil.h" #include "util.h" -#include "crud/CrudModel.h" -#include "crud/DataGridWidget.h" -#include "OpenDatabase.h" #include #include -#include -#include +#include "util/PgLabItemDelegate.h" TuplesResultWidget::TuplesResultWidget(QWidget *parent) : @@ -19,13 +15,9 @@ TuplesResultWidget::TuplesResultWidget(QWidget *parent) : ui->setupUi(this); ui->lblRowCount->setText(QString()); - m_grid = new DataGridWidget(this); - ui->verticalLayout->insertWidget(0, m_grid); - m_grid->view()->setFont(QFont("Source Sans Pro", 10)); - m_grid->view()->setAlternatingRowColors(true); - m_grid->view()->setWordWrap(false); - m_grid->view()->verticalHeader()->setDefaultSectionSize(21); - m_grid->view()->verticalHeader()->setMinimumSectionSize(16); + SetTableViewDefault(ui->ResultView); + auto delegate = new PgLabItemDelegate(ui->ResultView); + ui->ResultView->setItemDelegate(delegate); } TuplesResultWidget::~TuplesResultWidget() @@ -33,17 +25,14 @@ TuplesResultWidget::~TuplesResultWidget() delete ui; } -void TuplesResultWidget::setResult(std::shared_ptr db, std::shared_ptr res, float ms) +void TuplesResultWidget::setResult(std::shared_ptr res, float ms) { - m_result = res; + resultModel = res; + ui->ResultView->setModel(resultModel.get()); - m_model = new CrudModel(m_grid); - m_model->setResult(db, res); - m_grid->setSourceModel(m_model); + ui->ResultView->resizeColumnsToContents(); - m_grid->view()->resizeColumnsToContents(); - - QString rowcount_str = QString("rows: %1").arg(m_model->rowCount()); + QString rowcount_str = QString("rows: %1").arg(resultModel->rowCount()); ui->lblRowCount->setText(rowcount_str); ui->lblElapsedTime->setText(msfloatToHumanReadableString(ms)); } @@ -53,6 +42,8 @@ void TuplesResultWidget::exportData(const QString &file_name) const QFile file(file_name); if (file.open(QIODevice::WriteOnly)) { QTextStream out(&file); - ::exportTable(m_grid->view(), out); + ::exportTable(ui->ResultView, out); } } + + diff --git a/pglab/tuplesresultwidget.h b/pglab/tuplesresultwidget.h index 19fc083..89a55d9 100644 --- a/pglab/tuplesresultwidget.h +++ b/pglab/tuplesresultwidget.h @@ -1,21 +1,13 @@ #ifndef TUPLESRESULTWIDGET_H #define TUPLESRESULTWIDGET_H +#include "querytool/QueryResultModel.h" #include -#include namespace Ui { class TuplesResultWidget; } -namespace Pgsql { - class Result; -} - -class OpenDatabase; -class CrudModel; -class DataGridWidget; - class TuplesResultWidget : public QWidget { Q_OBJECT @@ -24,15 +16,14 @@ public: explicit TuplesResultWidget(QWidget *parent = 0); ~TuplesResultWidget(); - void setResult(std::shared_ptr db, std::shared_ptr res, float ms); + void setResult(std::shared_ptr res, float ms); void exportData(const QString &file_name) const; - std::shared_ptr GetPgsqlResult() const { return m_result; } + std::shared_ptr GetPgsqlResult() const { return resultModel->GetPgsqlResult(); } private: Ui::TuplesResultWidget *ui; - DataGridWidget *m_grid = nullptr; - CrudModel *m_model = nullptr; - std::shared_ptr m_result; + + std::shared_ptr resultModel; }; #endif // TUPLESRESULTWIDGET_H diff --git a/pglab/tuplesresultwidget.ui b/pglab/tuplesresultwidget.ui index b13909e..677a464 100644 --- a/pglab/tuplesresultwidget.ui +++ b/pglab/tuplesresultwidget.ui @@ -29,6 +29,37 @@ 2 + + + + + Source Sans Pro + 10 + + + + QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed + + + true + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + 21 + + + 16 + + + diff --git a/pglabAll.pro b/pglabAll.pro new file mode 100644 index 0000000..8631a5e --- /dev/null +++ b/pglabAll.pro @@ -0,0 +1,18 @@ +TEMPLATE = subdirs + +DEFINES += BOOST_ENABLE_ASSERT_HANDLER + +SUBDIRS += core \ + pgsql \ + pglablib \ + pglab + +pglab.depends = core pgsql pglablib +pgsql.depends = core +pglablib.depends = core pgsql +tests.depends = core pgsql pglablib + + +CONFIG(debug, debug|release) { +SUBDIRS += tests +} diff --git a/pglablib/ASyncDBConnection.cpp b/pglablib/ASyncDBConnection.cpp index 2b5c2fb..1c27fb9 100644 --- a/pglablib/ASyncDBConnection.cpp +++ b/pglablib/ASyncDBConnection.cpp @@ -1,14 +1,13 @@ -#include "ASyncDBConnection.h" +#include "ASyncDBConnection.h" #include "ScopeGuard.h" #include "util.h" #include "Pgsql_PgException.h" -#include +#include "Win32Event.h" +#include "WaitHandleList.h" #include #include +#include #include -#include -#include -#include namespace { @@ -46,6 +45,10 @@ 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; @@ -61,29 +64,18 @@ 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; - std::atomic 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 + /// \todo Implement new method to stop the thread + Win32Event m_stopEvent; 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(); @@ -105,6 +97,7 @@ private: ASyncDBConnectionThread::ASyncDBConnectionThread(ASyncDBConnection *asco) : asyncConnObject(asco) + , m_stopEvent(Win32Event::Reset::Manual, Win32Event::Initial::Clear) {} void ASyncDBConnectionThread::run() @@ -161,59 +154,6 @@ bool ASyncDBConnectionThread::cancel() return m_canceller.cancel(nullptr); } -bool ASyncDBConnectionThread::hasQueuedCommand() -{ - std::lock_guard 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(¬ifier, &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 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 lg(m_wakeMutex); - m_loop = nullptr; - } - return result; -} - -void ASyncDBConnectionThread::wake() -{ - std::lock_guard 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; @@ -225,14 +165,30 @@ 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) { - QSocketNotifier::Type type = QSocketNotifier::Write; + Win32Event socket_event(Win32Event::Reset::Auto, Win32Event::Initial::Clear); + + long fd = FD_WRITE; while (true) { // poll till complete or failed (we can get an abort command) - auto elapsed = std::chrono::steady_clock::now() - start; - auto remaining = std::chrono::duration_cast(30s - elapsed).count(); - WaitResult res = waitForSocket(type, remaining > 0 ? static_cast(remaining) : 0, false); + 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); - if (res == WaitResult::Socket) { + auto nu = std::chrono::steady_clock::now(); + std::chrono::duration 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) { auto poll_state = m_connection.connectPoll(); if (poll_state == PGRES_POLLING_OK) { qDebug() << "ASyncDBConnection Connected"; @@ -246,21 +202,17 @@ bool ASyncDBConnectionThread::makeConnection() } else if (poll_state == PGRES_POLLING_READING) { doStateCallback({ ASyncDBConnection::State::Connecting, "Negotiating" }); - type = QSocketNotifier::Read; + fd = FD_READ; } else if (poll_state == PGRES_POLLING_WRITING) { doStateCallback({ ASyncDBConnection::State::Connecting, "Negotiating" }); - type = QSocketNotifier::Write; + fd = FD_WRITE; } } - else if (terminateRequested) { - m_connection.close(); - return false; - } - else if (res == WaitResult::Timeout) { - doStateCallback({ ASyncDBConnection::State::NotConnected, "Connection timed out" }); + else if (res == wait_result_stop) { m_connection.close(); return false; + } } // end while (true) } @@ -300,7 +252,7 @@ void ASyncDBConnectionThread::communicate() void ASyncDBConnectionThread::stop() { terminateRequested = true; - wake(); + m_stopEvent.set(); } void ASyncDBConnectionThread::doStateCallback(ASyncDBConnection::StateData state) @@ -312,18 +264,34 @@ void ASyncDBConnectionThread::doStateCallback(ASyncDBConnection::StateData state bool ASyncDBConnectionThread::waitForAndSendCommand() { - if (hasQueuedCommand()) { - doNewCommand(); - return true; - } + SOCKET sock = static_cast(m_connection.socket()); + Win32Event socket_event(Win32Event::Reset::Manual, Win32Event::Initial::Clear); + long fd = FD_READ | FD_CLOSE; + WSAEventSelect(sock, socket_event.handle(), fd); - // 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; - } + 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) { + doNewCommand(); } + 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; @@ -379,16 +347,42 @@ bool ASyncDBConnectionThread::consumeResultInput() void ASyncDBConnectionThread::waitForResult() { + SOCKET sock = static_cast(m_connection.socket()); + Win32Event socket_event(Win32Event::Reset::Manual, Win32Event::Initial::Clear); + + long fd = FD_READ | FD_CLOSE; + bool finished = false; while ( ! finished) { - WaitResult res = waitForSocket(QSocketNotifier::Read, -1, false); + WSAEventSelect(sock, socket_event.handle(), fd); - if (res == WaitResult::Socket) { - if (consumeResultInput()) { - finished = true; + 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; + } } + else if (net_events.lNetworkEvents & FD_CLOSE) { + doStateCallback({ ASyncDBConnection::State::NotConnected, "Close while waiting for result" }); + finished = true; + stop(); + } } - else if (terminateRequested) { + if (res == wait_result_stop) { // Send cancel, close connection and terminate thread cancel(); doStateCallback(ASyncDBConnection::State::Terminating); @@ -461,8 +455,8 @@ bool ASyncDBConnection::send(const std::string &command, on_result_callback on_r { std::lock_guard 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; } @@ -474,8 +468,8 @@ bool ASyncDBConnection::send(const std::string &command, Pgsql::Params params, o { std::lock_guard 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; } diff --git a/pglablib/ASyncDBConnection.h b/pglablib/ASyncDBConnection.h index 0479f5f..02f5310 100644 --- a/pglablib/ASyncDBConnection.h +++ b/pglablib/ASyncDBConnection.h @@ -11,7 +11,6 @@ #include #include #include -#include class ASyncDBConnectionThread; /** \brief Class that handles asynchronous execution of queries. @@ -34,7 +33,7 @@ public: class StateData { public: - enum State State; + State State; QString Message; StateData(enum State state, QString message = QString()) diff --git a/pglablib/CMakeLists.txt b/pglablib/CMakeLists.txt deleted file mode 100644 index 1734d09..0000000 --- a/pglablib/CMakeLists.txt +++ /dev/null @@ -1,189 +0,0 @@ -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 - ResultSourceMapping.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 - ResultSourceMapping.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 - $ - ) - -set_target_properties(pglablib PROPERTIES - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON - ) diff --git a/pglablib/ResultSourceMapping.cpp b/pglablib/ResultSourceMapping.cpp deleted file mode 100644 index 8197133..0000000 --- a/pglablib/ResultSourceMapping.cpp +++ /dev/null @@ -1,159 +0,0 @@ -#include "ResultSourceMapping.h" - -#include "Pgsql_Result.h" -#include "catalog/PgClass.h" -#include "catalog/PgDatabaseCatalog.h" -#include "catalog/PgConstraintContainer.h" -#include "catalog/PgNamespace.h" -#include "catalog/PgNamespaceContainer.h" -#include "catalog/PgClassContainer.h" - -#include -#include - -namespace { - -/// Whether a non-key column of the table is selected more than once. -/// A table that appears twice in a join (through aliases) shows up like that, and libpq can not tell the instances apart. -bool hasAmbiguousColumn(const std::vector &columns, Oid tableOid, const std::vector &pkAttnums) -{ - for (const auto &col : columns) { - if (col.tableOid != tableOid || std::find(pkAttnums.begin(), pkAttnums.end(), col.attnum) != pkAttnums.end()) - continue; - auto count = std::count_if(columns.begin(), columns.end(), [&](const ResultColumnSource &other) { - return other.tableOid == tableOid && other.attnum == col.attnum; - }); - if (count > 1) - return true; - } - return false; -} - -} - -ResultSourceMapping::ResultSourceMapping(std::vector columns, const std::vector &primaryKeys) - : m_columns(std::move(columns)) -{ - for (const auto &col : m_columns) { - if (col.tableOid == InvalidOid || findTable(col.tableOid)) - continue; - - TableInfo info; - info.tableOid = col.tableOid; - - auto pk = std::find_if(primaryKeys.begin(), primaryKeys.end(), [&](const TablePrimaryKey &key) { - return key.tableOid == col.tableOid; - }); - if (pk != primaryKeys.end() && !pk->attnums.empty()) { - bool complete = true; - for (int attnum : pk->attnums) { - auto found = std::find_if(m_columns.begin(), m_columns.end(), [&](const ResultColumnSource &source) { - return source.tableOid == col.tableOid && source.attnum == attnum; - }); - if (found == m_columns.end()) { - complete = false; - break; - } - info.pkColumns.push_back(static_cast(std::distance(m_columns.begin(), found))); - } - info.editable = complete && !hasAmbiguousColumn(m_columns, col.tableOid, pk->attnums); - } - if (!info.editable) - info.pkColumns.clear(); - - m_tables.push_back(info); - } -} - -int ResultSourceMapping::columnCount() const -{ - return static_cast(m_columns.size()); -} - -Oid ResultSourceMapping::tableOid(int col) const -{ - if (col < 0 || col >= columnCount()) - throw std::range_error("column index out of range"); - return m_columns[col].tableOid; -} - -int ResultSourceMapping::attnum(int col) const -{ - if (col < 0 || col >= columnCount()) - throw std::range_error("column index out of range"); - return m_columns[col].attnum; -} - -bool ResultSourceMapping::isEditable(int col) const -{ - if (col < 0 || col >= columnCount()) - throw std::range_error("column index out of range"); - - const auto &source = m_columns[col]; - if (source.attnum <= 0) - return false; - - const TableInfo *info = findTable(source.tableOid); - return info && info->editable; -} - -std::vector ResultSourceMapping::primaryKeyColumns(Oid tableOid) const -{ - const TableInfo *info = findTable(tableOid); - return info ? info->pkColumns : std::vector{}; -} - -const ResultSourceMapping::TableInfo *ResultSourceMapping::findTable(Oid tableOid) const -{ - auto found = std::find_if(m_tables.begin(), m_tables.end(), [tableOid](const TableInfo &info) { - return info.tableOid == tableOid; - }); - return found == m_tables.end() ? nullptr : &*found; -} - -namespace { - -/// Catalog tables (pg_catalog, information_schema, ...) are views or system tables and must never be edited. -bool isSystemCatalogTable(const PgDatabaseCatalog &catalog, Oid tableOid) -{ - const PgClass *cls = catalog.classes()->getByKey(tableOid); - if (!cls) - return false; - const PgNamespace *ns = catalog.namespaces()->getByKey(cls->nsOid()); - return ns && ns->isSystemCatalog(); -} - -} - -ResultSourceMapping createResultSourceMapping(const Pgsql::Result &result, const PgDatabaseCatalog &catalog) -{ - std::vector columns; - std::vector primaryKeys; - - for (int col = 0; col < result.cols(); ++col) { - ResultColumnSource source; - source.tableOid = result.ftable(col); - source.attnum = result.ftableCol(col); - columns.push_back(source); - - if (source.tableOid == InvalidOid) - continue; - - bool known = std::any_of(primaryKeys.begin(), primaryKeys.end(), [&](const TablePrimaryKey &key) { - return key.tableOid == source.tableOid; - }); - if (known) - continue; - - TablePrimaryKey pk; - pk.tableOid = source.tableOid; - if (!isSystemCatalogTable(catalog, source.tableOid)) { - auto constraint = catalog.constraints()->getPrimaryForRelation(source.tableOid); - if (constraint) - pk.attnums.assign(constraint->key.begin(), constraint->key.end()); - } - primaryKeys.push_back(pk); - } - - return ResultSourceMapping(std::move(columns), primaryKeys); -} diff --git a/pglablib/ResultSourceMapping.h b/pglablib/ResultSourceMapping.h deleted file mode 100644 index b12f162..0000000 --- a/pglablib/ResultSourceMapping.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef RESULTSOURCEMAPPING_H -#define RESULTSOURCEMAPPING_H - -#include - -#include - -class PgDatabaseCatalog; - -namespace Pgsql { - class Result; -} - -/// The table column a result column is read from, as reported by libpq (PQftable/PQftablecol). -/// tableOid is InvalidOid and attnum is 0 for expression columns. -struct ResultColumnSource { - Oid tableOid = InvalidOid; - int attnum = 0; -}; - -/// The primary key of a table, attnums in key order. -struct TablePrimaryKey { - Oid tableOid = InvalidOid; - std::vector attnums; -}; - -/// Maps the columns of a query result to the table columns they come from and decides which -/// cells can be edited. A cell is editable when it maps to a user column of a table whose full -/// primary key is present in the result and which is not ambiguous. -class ResultSourceMapping { -public: - ResultSourceMapping(std::vector columns, const std::vector &primaryKeys); - - int columnCount() const; - /// Table the column is read from, InvalidOid for expression columns. - Oid tableOid(int col) const; - /// attnum of the column in its table, 0 for expression columns. - int attnum(int col) const; - bool isEditable(int col) const; - /// Result column indices holding the primary key of the table, in key order. - /// Empty when the table is not editable. - std::vector primaryKeyColumns(Oid tableOid) const; - -private: - struct TableInfo { - Oid tableOid = InvalidOid; - bool editable = false; - std::vector pkColumns; - }; - - std::vector m_columns; - std::vector m_tables; - - const TableInfo *findTable(Oid tableOid) const; -}; - -/// Builds the mapping for a query result, looking up the primary keys of the source tables in the catalog. -ResultSourceMapping createResultSourceMapping(const Pgsql::Result &result, const PgDatabaseCatalog &catalog); - - -#endif // RESULTSOURCEMAPPING_H diff --git a/pglablib/SqlFormattingUtils.cpp b/pglablib/SqlFormattingUtils.cpp index ff7f1d3..a75620e 100644 --- a/pglablib/SqlFormattingUtils.cpp +++ b/pglablib/SqlFormattingUtils.cpp @@ -200,7 +200,7 @@ QString quoteIdent(QString ident) { assert(ident.length() > 0); - static const QChar dquote = u'"'; + static const wchar_t dquote = L'"'; if (identNeedsQuotes(ident)) { QString out; diff --git a/pglablib/WaitHandleList.cpp b/pglablib/WaitHandleList.cpp new file mode 100644 index 0000000..d219610 --- /dev/null +++ b/pglablib/WaitHandleList.cpp @@ -0,0 +1,31 @@ +#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(m_waitHandles.size() - 1); +} + +WaitResultValue WaitHandleList::add(Win32Event &e) +{ + return add(e.handle()); +} + +DWORD WaitHandleList::count() const +{ + return static_cast(m_waitHandles.size()); +} + +void WaitHandleList::clear() +{ + m_waitHandles.clear(); +} + +WaitHandleList::operator const HANDLE*() const +{ + return m_waitHandles.data(); +} diff --git a/pglablib/WaitHandleList.h b/pglablib/WaitHandleList.h new file mode 100644 index 0000000..376e817 --- /dev/null +++ b/pglablib/WaitHandleList.h @@ -0,0 +1,26 @@ +#ifndef WAITHANDLELIST_H +#define WAITHANDLELIST_H + +#include +#include + +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 m_waitHandles; +}; + +#endif // WAITHANDLELIST_H diff --git a/pglablib/Win32Event.h b/pglablib/Win32Event.h new file mode 100644 index 0000000..684d13f --- /dev/null +++ b/pglablib/Win32Event.h @@ -0,0 +1,47 @@ +#ifndef WIN32EVENT_H +#define WIN32EVENT_H + +#include + +#include +/** 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 diff --git a/pglablib/catalog/PgLanguage.cpp b/pglablib/catalog/PgLanguage.cpp index c224d23..cd9dd4f 100644 --- a/pglablib/catalog/PgLanguage.cpp +++ b/pglablib/catalog/PgLanguage.cpp @@ -1,15 +1,13 @@ #include "PgLanguage.h" -#include - QString PgLanguage::createSql() const { - throw std::logic_error("PgLanguage::createSql() not implemented"); + throw std::exception("PgLanguage::createSql() not implemented"); } QString PgLanguage::dropSql() const { - throw std::logic_error("PgLanguage::dropSql() not implemented"); + throw std::exception("PgLanguage::dropSql() not implemented"); } QString PgLanguage::typeName() const diff --git a/pglablib/catalog/PgProc.cpp b/pglablib/catalog/PgProc.cpp index 02cf6d0..83fe734 100644 --- a/pglablib/catalog/PgProc.cpp +++ b/pglablib/catalog/PgProc.cpp @@ -48,15 +48,15 @@ namespace { for(; index < len; index++) { QChar ch = str[index]; - if (!double_quote && ch == u'\'') + if (!double_quote && ch == L'\'') single_quote = !single_quote; - else if (!single_quote && ch == u'"') + else if (!single_quote && ch == L'"') double_quote = !double_quote; - else if (!double_quote && !single_quote && ch == u'(') + else if (!double_quote && !single_quote && ch == L'(') brackets++; - else if (!double_quote && !single_quote && ch == u')') + else if (!double_quote && !single_quote && ch == L')') brackets--; - else if (!double_quote && !single_quote && brackets == 0 && ch == u',') { + else if (!double_quote && !single_quote && brackets == 0 && ch == L',') { if (index != start_array) res.push_back(str.mid(start_array, index - 1).trimmed()); else diff --git a/pglablib/catalog/PgServerObject.cpp b/pglablib/catalog/PgServerObject.cpp index 1108917..f85bd1f 100644 --- a/pglablib/catalog/PgServerObject.cpp +++ b/pglablib/catalog/PgServerObject.cpp @@ -38,7 +38,7 @@ QString PgServerObject::alterOwnerSql(const QString& ident) const } -void PgServerObject::setAcls(std::optional acls) +void PgServerObject::setAcls(boost::optional acls) { m_acls = std::move(acls); } diff --git a/pglablib/catalog/PgServerObject.h b/pglablib/catalog/PgServerObject.h index b1520a1..5c08fd3 100644 --- a/pglablib/catalog/PgServerObject.h +++ b/pglablib/catalog/PgServerObject.h @@ -4,7 +4,7 @@ #include #include "PgObject.h" #include "PgAcl.h" -#include +#include 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(std::optional acls); + void setAcls(boost::optional acls); QString aclString() const; /** * @brief grantSql @@ -53,7 +53,7 @@ protected: private: Oid m_ownerOid = InvalidOid; const PgAuthId * m_owner = nullptr; - std::optional m_acls; + boost::optional m_acls; }; #endif // PGSERVEROBJECT_H diff --git a/pglablib/codebuilder/StringEscapeRule.h b/pglablib/codebuilder/StringEscapeRule.h index fb01dd8..a8c9822 100644 --- a/pglablib/codebuilder/StringEscapeRule.h +++ b/pglablib/codebuilder/StringEscapeRule.h @@ -2,6 +2,7 @@ #define STRINGESCAPERULE_H #include +#include enum class ConvertToNumericEscape { Utf8, ///< Generates upto four escapes for 4 utf8 chars diff --git a/pglablib/pglablib.pro b/pglablib/pglablib.pro new file mode 100644 index 0000000..c41f973 --- /dev/null +++ b/pglablib/pglablib.pro @@ -0,0 +1,222 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-12-13T17:36:43 +# +#------------------------------------------------- + +QT += widgets core + +TARGET = pglablib +TEMPLATE = lib +CONFIG += staticlib + +! include( ../common.pri ) { +error( "Couldn't find the common.pri file!" ) +} + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + Pglablib.cpp \ + ASyncDBConnection.cpp \ + ConnectionConfig.cpp \ + TestConnection.cpp \ + WaitHandleList.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 + +HEADERS += \ + Pglablib.h \ + ASyncDBConnection.h \ + ConnectionConfig.h \ + TestConnection.h \ + WaitHandleList.h \ + Win32Event.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 + +unix { + target.path = /usr/lib + INSTALLS += target +} + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../core/release/ -lcore +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../core/debug/ -lcore +else:unix:!macx: LIBS += -L$$OUT_PWD/../core/ -lcore + +INCLUDEPATH += $$PWD/../core +DEPENDPATH += $$PWD/../core + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/libcore.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/libcore.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/core.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/core.lib +else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../core/libcore.a + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../pgsql/release/ -lpgsql +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../pgsql/debug/ -lpgsql +else:unix:!macx: LIBS += -L$$OUT_PWD/../pgsql/ -lpgsql + +INCLUDEPATH += $$PWD/../pgsql +DEPENDPATH += $$PWD/../pgsql + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/release/libpgsql.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/debug/libpgsql.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/release/pgsql.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/debug/pgsql.lib +else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../pgsql/libpgsql.a + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../core/release/ -lcore +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../core/debug/ -lcore +else:unix: LIBS += -L$$OUT_PWD/../core/ -lcore + +INCLUDEPATH += $$PWD/../core +DEPENDPATH += $$PWD/../core + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/libcore.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/libcore.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/core.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/core.lib +else:unix: PRE_TARGETDEPS += $$OUT_PWD/../core/libcore.a diff --git a/pglablib/utils/HumanReadableBytes.h b/pglablib/utils/HumanReadableBytes.h index b17ec0f..b76de05 100644 --- a/pglablib/utils/HumanReadableBytes.h +++ b/pglablib/utils/HumanReadableBytes.h @@ -1,6 +1,5 @@ #pragma once -#include #include std::string HumanReadableBytes(uint64_t bytes); diff --git a/pglablib/utils/PasswordManager.cpp b/pglablib/utils/PasswordManager.cpp index dfa0476..b0a71de 100644 --- a/pglablib/utils/PasswordManager.cpp +++ b/pglablib/utils/PasswordManager.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include using namespace Botan; @@ -33,7 +33,7 @@ public: * distinct key which is derived by hashing the wrapped key name with HMAC. */ const std::vector wrapped_name = - nist_key_wrap_padded(reinterpret_cast(name.data()), + nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()), name.size(), *m_cipher); @@ -47,7 +47,7 @@ public: secure_vector get(const std::string& name, const std::string_view &wrapped_key) const { const std::vector wrapped_name = - nist_key_wrap_padded(reinterpret_cast(name.data()), + nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()), name.size(), *m_cipher); diff --git a/pglablib/utils/PasswordManager.h b/pglablib/utils/PasswordManager.h index 8fb99d1..4bba574 100644 --- a/pglablib/utils/PasswordManager.h +++ b/pglablib/utils/PasswordManager.h @@ -19,11 +19,7 @@ namespace Botan { class PasswordManagerException: public std::exception { public: - 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; + using std::exception::exception; //(char const* const _Message); }; class PasswordManagerLockedException: public PasswordManagerException { diff --git a/pgsql/ArrayParser.cpp b/pgsql/ArrayParser.cpp index cdb5999..1f29a61 100644 --- a/pgsql/ArrayParser.cpp +++ b/pgsql/ArrayParser.cpp @@ -1,5 +1,4 @@ #include "ArrayParser.h" -#include #include using namespace Pgsql; diff --git a/pgsql/CMakeLists.txt b/pgsql/CMakeLists.txt deleted file mode 100644 index f6b0f39..0000000 --- a/pgsql/CMakeLists.txt +++ /dev/null @@ -1,48 +0,0 @@ -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 - $ - ) - -set_target_properties(pgsql PROPERTIES - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON - ) diff --git a/pgsql/pgsql.pro b/pgsql/pgsql.pro new file mode 100644 index 0000000..02c21a5 --- /dev/null +++ b/pgsql/pgsql.pro @@ -0,0 +1,60 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2016-11-11T09:17:13 +# +#------------------------------------------------- + +CONFIG += staticlib +QT += core + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = pgsql +TEMPLATE = lib + +! include( ../common.pri ) { +error( "Couldn't find the common.pri file!" ) +} + +LIBS += -lws2_32 -llibpq + +SOURCES += 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 + +HEADERS += 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 + + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../core/release/ -lcore +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../core/debug/ -lcore + +INCLUDEPATH += $$PWD/../core +DEPENDPATH += $$PWD/../core + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/libcore.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/libcore.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/core.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/core.lib diff --git a/releasenotes/notes/datagrid-save-revert-toolbar-3f9c1a7e5b2d8604.yaml b/releasenotes/notes/datagrid-save-revert-toolbar-3f9c1a7e5b2d8604.yaml deleted file mode 100644 index ebfdb20..0000000 --- a/releasenotes/notes/datagrid-save-revert-toolbar-3f9c1a7e5b2d8604.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -features: - - | - Data grids in the table editor and in query results now have a toolbar - with :menuselection:`Save` and :menuselection:`Revert` buttons, Save can - also be triggered with :kbd:`Ctrl+S`. Both buttons are only enabled while - there are unsaved changes. -other: - - | - Edits in data grids are no longer saved automatically when a cell editor - is closed or when :kbd:`Escape` is pressed. Use the :menuselection:`Save` - button to write changes to the database, or :menuselection:`Revert` to - discard them. Refreshing a table with unsaved changes asks for confirmation. diff --git a/releasenotes/notes/query-results-editable-grid-b265a193a39f3245.yaml b/releasenotes/notes/query-results-editable-grid-b265a193a39f3245.yaml deleted file mode 100644 index a54b9f5..0000000 --- a/releasenotes/notes/query-results-editable-grid-b265a193a39f3245.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -features: - - | - Query results are now shown in the same grid as the table data tab. Columns - can be sorted by clicking their headers, and cells that belong to a table - with a primary key present in the result can be edited directly. Changes - are saved with an ``UPDATE`` by primary key. Tables in system catalog - schemas such as ``pg_catalog`` and ``information_schema`` are never editable. -other: - - | - Read-only cells in query results, such as expression columns, columns of - tables without a primary key in the result, or columns of system catalog - tables, are now shown in grey so they can be told apart from editable cells. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt deleted file mode 100644 index a02ee3c..0000000 --- a/tests/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -find_package(GTest CONFIG REQUIRED) - -add_subdirectory(mygtestutils) -add_subdirectory(PgsqlTests) -add_subdirectory(pglabtests) diff --git a/tests/PgsqlTests/CMakeLists.txt b/tests/PgsqlTests/CMakeLists.txt deleted file mode 100644 index 34071a1..0000000 --- a/tests/PgsqlTests/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -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) diff --git a/tests/PgsqlTests/PgsqlTests.pro b/tests/PgsqlTests/PgsqlTests.pro new file mode 100644 index 0000000..cb1d574 --- /dev/null +++ b/tests/PgsqlTests/PgsqlTests.pro @@ -0,0 +1,46 @@ +! include( ../../common.pri ) { +error( "Couldn't find the common.pri file!" ) +} +include(gtest_dependency.pri) + +TEMPLATE = app +CONFIG += console +CONFIG -= app_bundle +CONFIG += thread +CONFIG += qt + +QT += core + +HEADERS += + +SOURCES += main.cpp \ + tst_Value.cpp \ + tst_ArrayParser.cpp \ + tst_Pgsql_oids.cpp + + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../pgsql/release/ -lpgsql +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../pgsql/debug/ -lpgsql +else:unix: LIBS += -L$$OUT_PWD/../../pgsql/ -lpgsql + +INCLUDEPATH += $$PWD/../../pgsql +DEPENDPATH += $$PWD/../../pgsql + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/release/libpgsql.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/debug/libpgsql.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/release/pgsql.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/debug/pgsql.lib +else:unix: PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/libpgsql.a + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../mygtestutils/release/ -lmygtestutils +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../mygtestutils/debug/ -lmygtestutils +else:unix:!macx: LIBS += -L$$OUT_PWD/../mygtestutils/ -lmygtestutils + +INCLUDEPATH += $$PWD/../mygtestutils +DEPENDPATH += $$PWD/../mygtestutils + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/release/libmygtestutils.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/debug/libmygtestutils.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/release/mygtestutils.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/debug/mygtestutils.lib +else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/libmygtestutils.a diff --git a/tests/PgsqlTests/gtest_dependency.pri b/tests/PgsqlTests/gtest_dependency.pri new file mode 100644 index 0000000..d01d7f0 --- /dev/null +++ b/tests/PgsqlTests/gtest_dependency.pri @@ -0,0 +1,29 @@ +isEmpty(GOOGLETEST_DIR):GOOGLETEST_DIR=$$(GOOGLETEST_DIR) + +isEmpty(GOOGLETEST_DIR) { + warning("Using googletest src dir specified at Qt Creator wizard") + message("set GOOGLETEST_DIR as environment variable or qmake variable to get rid of this message") + GOOGLETEST_DIR = C:/Prog/googletest +} + +!isEmpty(GOOGLETEST_DIR): { + GTEST_SRCDIR = $$GOOGLETEST_DIR/googletest + GMOCK_SRCDIR = $$GOOGLETEST_DIR/googlemock +} + +requires(exists($$GTEST_SRCDIR):exists($$GMOCK_SRCDIR)) + +!exists($$GOOGLETEST_DIR):message("No googletest src dir found - set GOOGLETEST_DIR to enable.") + +DEFINES += \ + GTEST_LANG_CXX11 + +INCLUDEPATH *= \ + $$GTEST_SRCDIR \ + $$GTEST_SRCDIR/include \ + $$GMOCK_SRCDIR \ + $$GMOCK_SRCDIR/include + +SOURCES += \ + $$GTEST_SRCDIR/src/gtest-all.cc \ + $$GMOCK_SRCDIR/src/gmock-all.cc diff --git a/tests/mygtestutils/CMakeLists.txt b/tests/mygtestutils/CMakeLists.txt deleted file mode 100644 index 930a0ad..0000000 --- a/tests/mygtestutils/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -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 - ) diff --git a/tests/mygtestutils/mygtestutils.pro b/tests/mygtestutils/mygtestutils.pro new file mode 100644 index 0000000..a56a359 --- /dev/null +++ b/tests/mygtestutils/mygtestutils.pro @@ -0,0 +1,32 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-12-01T10:07:31 +# +#------------------------------------------------- + +QT -= gui + +TARGET = mygtestutils +TEMPLATE = lib +CONFIG += staticlib + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + PrintTo_Qt.cpp + +HEADERS += \ + PrintTo_Qt.h +unix { + target.path = /usr/lib + INSTALLS += target +} diff --git a/tests/pglabtests/CMakeLists.txt b/tests/pglabtests/CMakeLists.txt deleted file mode 100644 index d9f30de..0000000 --- a/tests/pglabtests/CMakeLists.txt +++ /dev/null @@ -1,42 +0,0 @@ -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 - ResultSourceMappingTest.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) diff --git a/tests/pglabtests/ResultSourceMappingTest.cpp b/tests/pglabtests/ResultSourceMappingTest.cpp deleted file mode 100644 index 74565e8..0000000 --- a/tests/pglabtests/ResultSourceMappingTest.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include -#include - -#include "ResultSourceMapping.h" - - -namespace { - -constexpr Oid tableA = 1001; -constexpr Oid tableB = 1002; - -ResultColumnSource src(Oid table, int attnum) -{ - ResultColumnSource s; - s.tableOid = table; - s.attnum = attnum; - return s; -} - -ResultColumnSource expression() -{ - return ResultColumnSource{}; -} - -TablePrimaryKey pk(Oid table, std::vector attnums) -{ - TablePrimaryKey key; - key.tableOid = table; - key.attnums = std::move(attnums); - return key; -} - -} - - -TEST(ResultSourceMappingTest, single_table_with_pk_all_columns_editable) -{ - ResultSourceMapping m({src(tableA, 1), src(tableA, 2), src(tableA, 3)}, {pk(tableA, {1})}); - - EXPECT_EQ(m.columnCount(), 3); - EXPECT_TRUE(m.isEditable(0)); - EXPECT_TRUE(m.isEditable(1)); - EXPECT_TRUE(m.isEditable(2)); - EXPECT_EQ(m.tableOid(1), tableA); - EXPECT_EQ(m.attnum(2), 3); - EXPECT_THAT(m.primaryKeyColumns(tableA), ::testing::ElementsAre(0)); -} - -TEST(ResultSourceMappingTest, composite_pk_maps_key_columns_in_key_order) -{ - ResultSourceMapping m({src(tableA, 3), src(tableA, 1), src(tableA, 2)}, {pk(tableA, {1, 2})}); - - EXPECT_TRUE(m.isEditable(0)); - EXPECT_THAT(m.primaryKeyColumns(tableA), ::testing::ElementsAre(1, 2)); -} - -TEST(ResultSourceMappingTest, join_with_both_pks_both_tables_editable) -{ - ResultSourceMapping m({src(tableA, 1), src(tableA, 2), src(tableB, 1), src(tableB, 2)}, - {pk(tableA, {1}), pk(tableB, {1})}); - - for (int col = 0; col < m.columnCount(); ++col) { - EXPECT_TRUE(m.isEditable(col)) << "column " << col; - } - EXPECT_THAT(m.primaryKeyColumns(tableA), ::testing::ElementsAre(0)); - EXPECT_THAT(m.primaryKeyColumns(tableB), ::testing::ElementsAre(2)); -} - -TEST(ResultSourceMappingTest, join_where_only_one_table_has_pk_in_result) -{ - ResultSourceMapping m({src(tableA, 1), src(tableA, 2), src(tableB, 2)}, - {pk(tableA, {1}), pk(tableB, {1})}); - - EXPECT_TRUE(m.isEditable(0)); - EXPECT_TRUE(m.isEditable(1)); - EXPECT_FALSE(m.isEditable(2)); - EXPECT_TRUE(m.primaryKeyColumns(tableB).empty()); -} - -TEST(ResultSourceMappingTest, pk_missing_from_result_is_read_only) -{ - ResultSourceMapping m({src(tableA, 2), src(tableA, 3)}, {pk(tableA, {1})}); - - EXPECT_FALSE(m.isEditable(0)); - EXPECT_FALSE(m.isEditable(1)); - EXPECT_TRUE(m.primaryKeyColumns(tableA).empty()); -} - -TEST(ResultSourceMappingTest, composite_pk_partially_present_is_read_only) -{ - ResultSourceMapping m({src(tableA, 1), src(tableA, 3)}, {pk(tableA, {1, 2})}); - - EXPECT_FALSE(m.isEditable(0)); - EXPECT_FALSE(m.isEditable(1)); -} - -TEST(ResultSourceMappingTest, table_without_pk_is_read_only) -{ - ResultSourceMapping m({src(tableA, 1), src(tableA, 2)}, {}); - - EXPECT_FALSE(m.isEditable(0)); - EXPECT_FALSE(m.isEditable(1)); -} - -TEST(ResultSourceMappingTest, expression_column_is_read_only) -{ - ResultSourceMapping m({src(tableA, 1), expression(), src(tableA, 2)}, {pk(tableA, {1})}); - - EXPECT_TRUE(m.isEditable(0)); - EXPECT_FALSE(m.isEditable(1)); - EXPECT_EQ(m.tableOid(1), InvalidOid); - EXPECT_EQ(m.attnum(1), 0); - EXPECT_TRUE(m.isEditable(2)); -} - -TEST(ResultSourceMappingTest, system_column_is_read_only) -{ - ResultSourceMapping m({src(tableA, 1), src(tableA, -2)}, {pk(tableA, {1})}); - - EXPECT_TRUE(m.isEditable(0)); - EXPECT_FALSE(m.isEditable(1)); -} - -TEST(ResultSourceMappingTest, duplicate_pk_column_still_editable) -{ - ResultSourceMapping m({src(tableA, 1), src(tableA, 1), src(tableA, 2)}, {pk(tableA, {1})}); - - EXPECT_TRUE(m.isEditable(0)); - EXPECT_TRUE(m.isEditable(1)); - EXPECT_TRUE(m.isEditable(2)); - EXPECT_THAT(m.primaryKeyColumns(tableA), ::testing::ElementsAre(0)); -} - -TEST(ResultSourceMappingTest, same_non_pk_column_twice_makes_table_read_only) -{ - ResultSourceMapping m({src(tableA, 1), src(tableA, 2), src(tableA, 2)}, {pk(tableA, {1})}); - - EXPECT_FALSE(m.isEditable(0)); - EXPECT_FALSE(m.isEditable(1)); - EXPECT_FALSE(m.isEditable(2)); - EXPECT_TRUE(m.primaryKeyColumns(tableA).empty()); -} - -TEST(ResultSourceMappingTest, self_join_with_aliased_non_pk_columns_is_read_only) -{ - ResultSourceMapping m({src(tableA, 1), src(tableA, 1), src(tableA, 2), src(tableA, 2)}, {pk(tableA, {1})}); - - EXPECT_FALSE(m.isEditable(0)); - EXPECT_FALSE(m.isEditable(2)); -} - -TEST(ResultSourceMappingTest, out_of_range_column_throws) -{ - ResultSourceMapping m({src(tableA, 1)}, {pk(tableA, {1})}); - - EXPECT_THROW(m.isEditable(1), std::range_error); - EXPECT_THROW(m.isEditable(-1), std::range_error); - EXPECT_THROW(m.tableOid(1), std::range_error); - EXPECT_THROW(m.attnum(-1), std::range_error); -} - -TEST(ResultSourceMappingTest, empty_result_has_no_columns) -{ - ResultSourceMapping m({}, {}); - - EXPECT_EQ(m.columnCount(), 0); - EXPECT_TRUE(m.primaryKeyColumns(tableA).empty()); -} diff --git a/tests/pglabtests/pglabtests.pro b/tests/pglabtests/pglabtests.pro new file mode 100644 index 0000000..a666430 --- /dev/null +++ b/tests/pglabtests/pglabtests.pro @@ -0,0 +1,86 @@ +! include( ../../common.pri ) { +error( "Couldn't find the common.pri file!" ) +} +include(gtest_dependency.pri) + +TEMPLATE = app +CONFIG += console +CONFIG -= app_bundle +CONFIG += thread +CONFIG += qt + +QT += core widgets + +HEADERS += + +SOURCES += 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 + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../core/release/ -lcore +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../core/debug/ -lcore + +INCLUDEPATH += $$PWD/../../core +DEPENDPATH += $$PWD/../../core + +win32:CONFIG(debug, debug|release): LIBS += -lbotand +else:win32:CONFIG(release, debug|release): LIBS += -lbotan + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../core/release/libcore.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../core/debug/libcore.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../core/release/core.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../core/debug/core.lib + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../pgsql/release/ -lpgsql +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../pgsql/debug/ -lpgsql +else:unix:!macx: LIBS += -L$$OUT_PWD/../../pgsql/ -lpgsql + +INCLUDEPATH += $$PWD/../../pgsql +DEPENDPATH += $$PWD/../../pgsql + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/release/libpgsql.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/debug/libpgsql.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/release/pgsql.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/debug/pgsql.lib +else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/libpgsql.a + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../pglablib/release/ -lpglablib +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../pglablib/debug/ -lpglablib +else:unix:!macx: LIBS += -L$$OUT_PWD/../../pglablib/ -lpglablib + +INCLUDEPATH += $$PWD/../../pglablib +DEPENDPATH += $$PWD/../../pglablib + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pglablib/release/libpglablib.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pglablib/debug/libpglablib.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pglablib/release/pglablib.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pglablib/debug/pglablib.lib +else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../../pglablib/libpglablib.a + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../mygtestutils/release/ -lmygtestutils +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../mygtestutils/debug/ -lmygtestutils +else:unix:!macx: LIBS += -L$$OUT_PWD/../mygtestutils/ -lmygtestutils + +INCLUDEPATH += $$PWD/../mygtestutils +DEPENDPATH += $$PWD/../mygtestutils + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/release/libmygtestutils.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/debug/libmygtestutils.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/release/mygtestutils.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/debug/mygtestutils.lib +else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/libmygtestutils.a diff --git a/tests/tests.pro b/tests/tests.pro new file mode 100644 index 0000000..ee23c08 --- /dev/null +++ b/tests/tests.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +SUBDIRS += PgsqlTests \ + pglabtests \ + mygtestutils diff --git a/vcpkg.json b/vcpkg.json deleted file mode 100644 index 4283df6..0000000 --- a/vcpkg.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "pglab", - "version-string": "0.1.0", - "dependencies": [ - "boost-container", - "boost-timer", - "boost-chrono", - "boost-assert", - "botan", - "libpq", - "gtest" - ] -}