diff --git a/pglab/ConnectionController.cpp b/pglab/ConnectionController.cpp index f8815dd..5bc2753 100644 --- a/pglab/ConnectionController.cpp +++ b/pglab/ConnectionController.cpp @@ -40,7 +40,6 @@ void ConnectionController::init() m_connectionTreeModel->load(); m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr); - m_connectionManagerWindow->show(); } void ConnectionController::showConnectionManager() @@ -55,16 +54,21 @@ void ConnectionController::openSqlWindowForConnection(QModelIndex index) if (retrieveConnectionPassword(*config)) { m_connectionTreeModel->save(*config); - // TODO instead of directly openening the mainwindow - // do async connect and only open window when we have - // working connection - auto w = new DatabaseWindow(m_masterController, nullptr); - w->setAttribute( Qt::WA_DeleteOnClose ); - w->setConfig(*config); - w->showMaximized(); + + openSqlWindowForConnection(*config); } } +} +void ConnectionController::openSqlWindowForConnection(const ConnectionConfig &config) +{ + // TODO instead of directly openening the mainwindow + // do async connect and only open window when we have + // working connection + auto w = new DatabaseWindow(m_masterController, nullptr); + w->setAttribute( Qt::WA_DeleteOnClose ); + w->setConfig(config); + w->showMaximized(); } void ConnectionController::openBackupDlgForConnection(QModelIndex index) diff --git a/pglab/ConnectionController.h b/pglab/ConnectionController.h index b0ebc99..0a71bd7 100644 --- a/pglab/ConnectionController.h +++ b/pglab/ConnectionController.h @@ -38,7 +38,8 @@ public: void showConnectionManager(); void openSqlWindowForConnection(QModelIndex index); - void openBackupDlgForConnection(QModelIndex index); + void openSqlWindowForConnection(const ConnectionConfig &cfg); + void openBackupDlgForConnection(QModelIndex index); /// Starts the form for creating a new conncetion. /// This function returns immidiatly! diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index 1b8c892..2528f6f 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -1,16 +1,19 @@ #include "ConnectionListModel.h" #include "ScopeGuard.h" -#include "util.h" #include #include +#include #include #include #include #include #include +#include +#include #include #include +#include namespace { @@ -41,6 +44,16 @@ CREATE TABLE IF NOT EXISTS connection ( password TEXT );)__"; + const char * const q_create_table_migrations = + R"__( +CREATE TABLE IF NOT EXISTS _migration ( + migration_id TEXT PRIMARY KEY +);)__"; + + const char * const q_load_migrations_present = + R"__( +SELECT migration_id +FROM _migration;)__"; const char * const q_insert_or_replace_into_connection = @@ -49,21 +62,136 @@ R"__(INSERT OR REPLACE INTO connection :sslmode, :sslcert, :sslkey, :sslrootcert, :sslcrl, :password); )__" ; - std::tuple InitConnectionTables(QSqlDatabase &db) - { - QSqlQuery q_create_table(db); - q_create_table.prepare(q_create_table_conngroup); - if (!q_create_table.exec()) { - auto err = q_create_table.lastError(); - return { false, err }; - } - q_create_table.prepare(q_create_table_connection); - if (!q_create_table.exec()) { - auto err = q_create_table.lastError(); - return { false, err }; - } - return {true, {}}; - } + +// Keeping migration function name and id DRY +#define APPLY_MIGRATION(id) ApplyMigration(#id, &MigrationDirector::id) + + class MigrationDirector { + public: + explicit MigrationDirector(QSqlDatabase &db) + : db(db) + { + } + + void Execute() + { + InitConnectionTables(); + present = LoadMigrations(); + APPLY_MIGRATION(M20250215_0933_Parameters); + } + + private: + QSqlDatabase &db; + std::unordered_set present; + + void M20250215_0933_Parameters() + { + Exec(R"__( +CREATE TABLE connection_parameter ( + connection_uuid TEXT, + pname TEXT, + pvalue TEXT NOT NULL, + + PRIMARY KEY(connection_uuid, pname) +);)__"); + + + for (QString key : { "host", "hostaddr", "user", "dbname", "sslmode", "sslcert", "sslkey", "sslrootcert", "sslcrl" }) + { + Exec( + "INSERT INTO connection_parameter (connection_uuid, pname, pvalue)" + " SELECT uuid, '" % key % "', " % key % "\n" + " FROM connection\n" + " WHERE " % key % " IS NOT NULL and " % key % " <> '';"); + } + Exec(R"__( +INSERT INTO connection_parameter (connection_uuid, pname, pvalue) + SELECT uuid, 'port', port + FROM connection + WHERE port IS NOT NULL; +)__"); + + for (QString column : { "host", "hostaddr", "user", "dbname", "sslmode", "sslcert", "sslkey", "sslrootcert", "sslcrl", "port" }) + { + // sqlite does not seem to support dropping more then one column per alter table + Exec("ALTER TABLE connection DROP COLUMN " % column % ";"); + } + } + + void Exec(QString query) + { + QSqlQuery q(query, db); + Verify(q); + } + + void ApplyMigration(QString migration_id, void (MigrationDirector::*func)()) + { + if (!present.contains(migration_id)) + { + if (!db.transaction()) + { + throw std::runtime_error("Failed to start transaction on user configuration database"); + } + (this->*func)(); + RegisterMigration(migration_id); + if (!db.commit()) + { + db.rollback(); + throw std::runtime_error("Failed to commit transaction on user configuration database"); + } + } + } + + std::unordered_set LoadMigrations() + { + std::unordered_set result; + QSqlQuery q(q_load_migrations_present, db); + Verify(q); + while (q.next()) + { + result.insert(q.value(0).toString()); + } + return result; + } + + void RegisterMigration(QString migrationId) + { + const char * const q_register_migration = + R"__(INSERT INTO _migration VALUES (:id);)__" ; + + QSqlQuery q(db); + q.prepare(q_register_migration); + q.bindValue(":id", migrationId); + if (!q.exec()) { + Verify(q); + } + } + + void Verify(QSqlQuery &q) + { + auto err = q.lastError(); + if (err.type() == QSqlError::NoError) + return; + + db.rollback(); + QString errString = err.text(); + throw std::runtime_error(errString.toStdString()); + } + + void InitConnectionTables() + { + // Original schema + QSqlQuery q_create_table(db); + q_create_table.exec(q_create_table_conngroup); + Verify(q_create_table); + q_create_table.exec(q_create_table_connection); + Verify(q_create_table); + // Start using migrations + q_create_table.exec(q_create_table_migrations); + Verify(q_create_table); + } + }; + std::optional SaveConnectionConfig(QSqlDatabase &db, const ConnectionConfig &cc, int conngroup_id) { @@ -105,7 +233,9 @@ ConnectionTreeModel::ConnectionTreeModel(QObject *parent, QSqlDatabase &db) void ConnectionTreeModel::load() { - InitConnectionTables(m_db); + //InitConnectionTables(m_db); + MigrationDirector md(m_db); + md.Execute(); QSqlQuery q(m_db); q.prepare("SELECT conngroup_id, gname FROM conngroup;"); @@ -124,9 +254,7 @@ void ConnectionTreeModel::load() m_groups.push_back(g); } - q.prepare("SELECT uuid, cname, conngroup_id, host, hostaddr, port, " - " user, dbname, sslmode, sslcert, sslkey, sslrootcert, sslcrl, " - " password " + q.prepare("SELECT uuid, cname, conngroup_id, password " "FROM connection ORDER BY conngroup_id, cname;"); if (!q.exec()) { // auto err = q_create_table.lastError(); diff --git a/pglab/MasterController.cpp b/pglab/MasterController.cpp index a851e9f..ff80988 100644 --- a/pglab/MasterController.cpp +++ b/pglab/MasterController.cpp @@ -1,5 +1,8 @@ #include "MasterController.h" #include "ConnectionController.h" +#include "utils/PostgresqlUrlParser.h" +#include +#include #include #include #include @@ -38,7 +41,20 @@ void MasterController::init() } m_connectionController = new ConnectionController(this); - m_connectionController->init(); + m_connectionController->init(); + + + QStringList arguments = QCoreApplication::arguments(); + for (auto && arg : arguments) + { + ConnectionConfig cfg; + if (TryParsePostgresqlUrl(arg, cfg)) { + m_connectionController->openSqlWindowForConnection(cfg); + return; + } + } + + m_connectionController->showConnectionManager(); } ConnectionController *MasterController::connectionController() diff --git a/pglablib/ConnectionConfig.cpp b/pglablib/ConnectionConfig.cpp index f324c24..6495785 100644 --- a/pglablib/ConnectionConfig.cpp +++ b/pglablib/ConnectionConfig.cpp @@ -24,6 +24,20 @@ namespace { return v.empty() ? nullptr : v.c_str(); } + struct { + const char * host = "host"; + const char * hostaddr = "hostaddr"; + const char * port = "port"; + const char * dbname = "dbname"; + const char * user = "user"; + const char * password = "password"; + const char * sslmode = "sslmode"; + const char * sslcert = "sslcert"; + const char * sslkey = "sslkey"; + const char * sslrootcert = "sslrootcert"; + const char * sslcrl = "sslcrl"; + } keywords; + } // end unnamed namespace QString SslModeToString(SslMode sm) @@ -46,7 +60,7 @@ SslMode StringToSslMode(QString s) } ConnectionConfig::ConnectionConfig() - : m_applicationName(QCoreApplication::applicationName().toUtf8().data()) + : m_parameters{{"application_name", QCoreApplication::applicationName()}} {} const ConnectionGroup *ConnectionConfig::parent() const @@ -92,168 +106,131 @@ const QString& ConnectionConfig::name() const void ConnectionConfig::setHost(const QString& host) { - if (m_host != host) - { - m_dirty = true; - m_host = std::move(host); - } + setParameter(keywords.host, host); } -const QString& ConnectionConfig::host() const +QString ConnectionConfig::host() const { - return m_host; + return getParameter(keywords.host); } void ConnectionConfig::setHostAddr(const QString &v) { - if (m_hostaddr != v) - { - m_dirty = true; - m_hostaddr = std::move(v); - } + setParameter(keywords.hostaddr, v); } -const QString& ConnectionConfig::hostAddr() const +QString ConnectionConfig::hostAddr() const { - return m_hostaddr; + return getParameter(keywords.hostaddr); } void ConnectionConfig::setPort(unsigned short port) { - if (m_port != port) - { - m_dirty = true; - m_port = port; - } + setParameter(keywords.port, QString::number(port)); } unsigned short ConnectionConfig::port() const { - return m_port; + QString s = getParameter(keywords.port); + if (s.isEmpty()) + return 5432; + + unsigned short port = static_cast(s.toInt()); + return port; } void ConnectionConfig::setUser(const QString& v) { - if (m_user != v) - { - m_dirty = true; - m_user = v; - } - + setParameter(keywords.user, v); } -const QString& ConnectionConfig::user() const +QString ConnectionConfig::user() const { - return m_user; + return getParameter(keywords.user); } void ConnectionConfig::setPassword(const QString& v) { - if (m_password != v) - { - m_dirty = true; - m_password = v; - } + setParameter(keywords.password, v); } -const QString& ConnectionConfig::password() const +QString ConnectionConfig::password() const { - return m_password; + return getParameter(keywords.password); } void ConnectionConfig::setDbname(const QString& v) { - if (m_dbname != v) - { - m_dirty = true; - m_dbname = v; - } + setParameter(keywords.dbname, v); } -const QString& ConnectionConfig::dbname() const +QString ConnectionConfig::dbname() const { - return m_dbname; + return getParameter(keywords.dbname); } void ConnectionConfig::setSslMode(SslMode m) { - if (m_sslMode != m) - { - m_dirty = true; - m_sslMode = m; - } + setParameter(keywords.sslmode, SslModeToString(m)); } SslMode ConnectionConfig::sslMode() const { - return m_sslMode; + QString s = getParameter(keywords.sslmode); + return StringToSslMode(s); } void ConnectionConfig::setSslCert(const QString& v) { - if (m_sslCert != v) - { - m_dirty = true; - m_sslCert = std::move(v); - } + setParameter(keywords.sslcert, v); } -const QString& ConnectionConfig::sslCert() const +QString ConnectionConfig::sslCert() const { - return m_sslCert; + return getParameter(keywords.sslcert); } void ConnectionConfig::setSslKey(const QString& v) { - if (m_sslKey != v) - { - m_dirty = true; - m_sslKey = std::move(v); - } + setParameter(keywords.sslkey, v); } -const QString& ConnectionConfig::sslKey() const +QString ConnectionConfig::sslKey() const { - return m_sslKey; + return getParameter(keywords.sslkey); } void ConnectionConfig::setSslRootCert(const QString& v) { - if (m_sslRootCert != v) - { - m_dirty = true; - m_sslRootCert = std::move(v); - } + setParameter(keywords.sslrootcert, v); } -const QString& ConnectionConfig::sslRootCert() const +QString ConnectionConfig::sslRootCert() const { - return m_sslRootCert; + return getParameter(keywords.sslrootcert); } void ConnectionConfig::setSslCrl(const QString& v) { - if (m_sslCrl != v) - { - m_dirty = true; - m_sslCrl = std::move(v); - } + setParameter(keywords.sslcrl, v); } -const QString& ConnectionConfig::sslCrl() const +QString ConnectionConfig::sslCrl() const { - return m_sslCrl; + return getParameter(keywords.sslcrl); } -bool ConnectionConfig::isSameDatabase(const ConnectionConfig &rhs) const -{ - return m_host == rhs.m_host - && m_hostaddr == rhs.m_hostaddr - && m_port == rhs.m_port - && m_user == rhs.m_user - && m_password == rhs.m_password - && m_dbname == rhs.m_dbname; -} +// bool ConnectionConfig::isSameDatabase(const ConnectionConfig &rhs) const +// { +// return host() == rhs.host() +// // && m_hostaddr == rhs.m_hostaddr +// // && m_port == rhs.m_port +// // && m_user == rhs.m_user +// // && m_password == rhs.m_password +// // && m_dbname == rhs.m_dbname +// ; + +// } bool ConnectionConfig::dirty() const { @@ -314,58 +291,111 @@ QString ConnectionConfig::escapeConnectionStringValue(const QString &value) QString ConnectionConfig::connectionString() const { QString s; - s += "host=" - % escapeConnectionStringValue(m_host) - % " port=" - % QString::number(m_port) - % " user=" - % escapeConnectionStringValue(m_user); - s += " password="; - s += escapeConnectionStringValue(m_password); - s += " dbname="; - s += escapeConnectionStringValue(m_dbname); - s += " sslmode="; - s += SslModeToString(m_sslMode); - if (!m_sslCert.isEmpty()) - { - s += " sslcert="; - s += escapeConnectionStringValue(m_sslCert); - } - if (!m_sslKey.isEmpty()) - { - s += " sslkey="; - s += escapeConnectionStringValue(m_sslKey); - } - if (!m_sslRootCert.isEmpty()) - { - s += " sslrootcrt="; - s += escapeConnectionStringValue(m_sslRootCert); - } - if (!m_sslCrl.isEmpty()) - { - s += " sslCrl="; - s += escapeConnectionStringValue(m_sslCrl); - } - s += " client_encoding=utf8"; - s += " application_name="; - s += escapeConnectionStringValue(m_applicationName); - return s; + for (auto && param : m_parameters) + { + // maybe we should prevent empty parameters from staying in the map? + if (!param.second.isEmpty()) + { + s += param.first % "=" % escapeConnectionStringValue(param.second); + } + } + + // s += "host=" + // % escapeConnectionStringValue(m_host) + // % " port=" + // % QString::number(m_port) + // % " user=" + // % escapeConnectionStringValue(m_user); + // s += " password="; + // s += escapeConnectionStringValue(m_password); + // s += " dbname="; + // s += escapeConnectionStringValue(m_dbname); + // s += " sslmode="; + // s += SslModeToString(m_sslMode); + // if (!m_sslCert.isEmpty()) + // { + // s += " sslcert="; + // s += escapeConnectionStringValue(m_sslCert); + // } + // if (!m_sslKey.isEmpty()) + // { + // s += " sslkey="; + // s += escapeConnectionStringValue(m_sslKey); + // } + // if (!m_sslRootCert.isEmpty()) + // { + // s += " sslrootcrt="; + // s += escapeConnectionStringValue(m_sslRootCert); + // } + // if (!m_sslCrl.isEmpty()) + // { + // s += " sslCrl="; + // s += escapeConnectionStringValue(m_sslCrl); + // } + // s += " client_encoding=utf8"; + // s += " application_name="; + // s += escapeConnectionStringValue(m_applicationName); + + return s; +} + +void ConnectionConfig::setParameter(const QString &name, const QString &value) +{ + if (value.isEmpty()) + { + if (m_parameters.erase(name) > 0) + { + m_dirty = true; + } + } + else + { + auto findResult = m_parameters.find(name); + if (findResult == m_parameters.end()) + { + m_parameters.insert({name, value}); + m_dirty = true; + } + else if (findResult->second != value) + { + findResult->second = value; + m_dirty = true; + } + } + + //if (name == "sslMode") + // m_sslMode = StringToSslMode(value); + + // Would it better to store everything in the map or keep the specific + // fields for common keywords? + // Map over fields + // + can use foreach + // - not strongly typed, but we should be carefull not to restrict ourselves + // the specific fields are more something that helps in the UI +} + +QString ConnectionConfig::getParameter(const QString &name) const +{ + auto findResult = m_parameters.find(name); + if (findResult == m_parameters.end()) + return {}; + return findResult->second; } void ConnectionConfig::writeToEnvironment(QProcessEnvironment &env) const { - strToEnv(env, "PGHOST", m_host); - strToEnv(env, "PGHOSTADDR", m_hostaddr); - strToEnv(env, "PGPORT", QString::number(m_port)); - strToEnv(env, "PGDATABASE", m_dbname); - strToEnv(env, "PGUSER", m_user); - strToEnv(env, "PGPASSWORD", m_password); - strToEnv(env, "PGSSLMODE", SslModeToString(m_sslMode)); - strToEnv(env, "PGSSLCERT", m_sslCert); - strToEnv(env, "PGSSLKEY", m_sslKey); - strToEnv(env, "PGSSLROOTCERT", m_sslRootCert); - strToEnv(env, "PGSSLCRL", m_sslCrl); + strToEnv(env, "PGHOST", getParameter(keywords.host)); + strToEnv(env, "PGHOSTADDR", getParameter(keywords.hostaddr)); + strToEnv(env, "PGPORT", getParameter(keywords.port)); + strToEnv(env, "PGDATABASE", getParameter(keywords.dbname)); + strToEnv(env, "PGUSER", getParameter(keywords.user)); + strToEnv(env, "PGPASSWORD", getParameter(keywords.password)); + strToEnv(env, "PGSSLMODE", getParameter(keywords.sslmode)); + // strToEnv(env, "PGSSLCERT", m_sslCert); + // strToEnv(env, "PGSSLKEY", m_sslKey); + // strToEnv(env, "PGSSLROOTCERT", m_sslRootCert); + // strToEnv(env, "PGSSLCRL", m_sslCrl); strToEnv(env, "PGSSLCOMPRESSION", "0"); strToEnv(env, "PGCONNECT_TIMEOUT", "10"); env.insert("PGCLIENTENCODING", "utf8"); diff --git a/pglablib/ConnectionConfig.h b/pglablib/ConnectionConfig.h index 390202a..e7d1817 100644 --- a/pglablib/ConnectionConfig.h +++ b/pglablib/ConnectionConfig.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -74,42 +75,42 @@ public: const QString& name() const; void setHost(const QString& host); - const QString& host() const; + QString host() const; void setHostAddr(const QString& v); - const QString& hostAddr() const; + QString hostAddr() const; void setPort(unsigned short port); unsigned short port() const; void setUser(const QString& v); - const QString& user() const; + QString user() const; void setPassword(const QString& v); - const QString& password() const; + QString password() const; void setDbname(const QString& v); - const QString& dbname() const; + QString dbname() const; void setSslMode(SslMode m); SslMode sslMode() const; - void setSslCert(const QString& v); - const QString& sslCert() const; + void setSslCert(const QString& v); + QString sslCert() const; - void setSslKey(const QString& v); - const QString& sslKey() const; + void setSslKey(const QString& v); + QString sslKey() const; - void setSslRootCert(const QString& v); - const QString& sslRootCert() const; + void setSslRootCert(const QString& v); + QString sslRootCert() const; - void setSslCrl(const QString& v); - const QString& sslCrl() const; + void setSslCrl(const QString& v); + QString sslCrl() const; // const char * const * getKeywords() const; // const char * const * getValues() const; - bool isSameDatabase(const ConnectionConfig &rhs) const; + // bool isSameDatabase(const ConnectionConfig &rhs) const; void writeToEnvironment(QProcessEnvironment &env) const; @@ -131,26 +132,31 @@ public: */ static QString escapeConnectionStringValue(const QString &value); QString connectionString() const; + + void setParameter(const QString &name, const QString &value); + QString getParameter(const QString &name) const; private: QUuid m_uuid; - QString m_name; - QString m_host; - QString m_hostaddr; - uint16_t m_port = 5432; + QString m_name; + // QString m_host; + // QString m_hostaddr; + // uint16_t m_port = 5432; - QString m_user; - QString m_password; ///< Note this is not saved in the DB only the m_encodedPassword is safed. - QString m_dbname; + // QString m_user; + // QString m_password; ///< Note this is not saved in the DB only the m_encodedPassword is safed. + // QString m_dbname; - SslMode m_sslMode = SslMode::prefer; - QString m_sslCert; - QString m_sslKey; - QString m_sslRootCert; - QString m_sslCrl; + // SslMode m_sslMode = SslMode::prefer; + // QString m_sslCert; + // QString m_sslKey; + // QString m_sslRootCert; + // QString m_sslCrl; - QString m_applicationName; + // QString m_applicationName; QByteArray m_encodedPassword; + std::unordered_map m_parameters; + bool m_dirty = false; ConnectionGroup* m_group = nullptr; diff --git a/pglablib/pglablib.pro b/pglablib/pglablib.pro index fd0c53a..6fc008f 100644 --- a/pglablib/pglablib.pro +++ b/pglablib/pglablib.pro @@ -88,7 +88,8 @@ SOURCES += \ catalog/PgAcl.cpp \ catalog/PgSequence.cpp \ catalog/PgSequenceContainer.cpp \ - utils/HumanReadableBytes.cpp + utils/HumanReadableBytes.cpp \ + utils/PostgresqlUrlParser.cpp HEADERS += \ Pglablib.h \ @@ -164,7 +165,8 @@ HEADERS += \ catalog/PgAcl.h \ catalog/PgSequence.h \ catalog/PgSequenceContainer.h \ - utils/HumanReadableBytes.h + utils/HumanReadableBytes.h \ + utils/PostgresqlUrlParser.h unix { target.path = /usr/lib diff --git a/pglablib/utils/PostgresqlUrlParser.cpp b/pglablib/utils/PostgresqlUrlParser.cpp new file mode 100644 index 0000000..e2d9883 --- /dev/null +++ b/pglablib/utils/PostgresqlUrlParser.cpp @@ -0,0 +1,38 @@ +#include "PostgresqlUrlParser.h" + +#include +#include +#include + +//PostgresqlUrlParser::PostgresqlUrlParser() {} + +bool TryParsePostgresqlUrl(const QString &urlString, ConnectionConfig &out) +{ + const char* shortPrefix = "postgres"; + const char* longPrefix = "postgresql"; + + auto url = QUrl(urlString, QUrl::StrictMode); + if (url.scheme() != shortPrefix && url.scheme() != longPrefix) + return false; + + out.setUser(url.userName()); + out.setPassword(url.password()); + out.setHost(url.host()); + out.setDbname(url.fileName()); + out.setPort(url.port()); + + QUrlQuery query(url.query()); + for (auto && param : query.queryItems()) + { + if (param.first == "ssl" && param.second == "true") { + out.setSslMode(SslMode::require); + } + else + { + out.setParameter(param.first, param.second); + } + } + + return true; +} + diff --git a/pglablib/utils/PostgresqlUrlParser.h b/pglablib/utils/PostgresqlUrlParser.h new file mode 100644 index 0000000..520fb7a --- /dev/null +++ b/pglablib/utils/PostgresqlUrlParser.h @@ -0,0 +1,15 @@ +#pragma once + +#include + + +class ConnectionConfig; + +bool TryParsePostgresqlUrl(const QString &urlString, ConnectionConfig &out); + +// class PostgresqlUrlParser +// { +// public: +// PostgresqlUrlParser(); + +//}; diff --git a/tests/pglabtests/pglabtests.pro b/tests/pglabtests/pglabtests.pro index ae63f49..a666430 100644 --- a/tests/pglabtests/pglabtests.pro +++ b/tests/pglabtests/pglabtests.pro @@ -19,6 +19,7 @@ SOURCES += main.cpp \ tst_ConvertToMultiLineCString.cpp \ tst_ExplainJsonParser.cpp \ tst_HumanReadableBytes.cpp \ + tst_TryParsePostgresqlUrl.cpp \ tst_escapeConnectionStringValue.cpp \ tst_expected.cpp \ tst_SqlLexer.cpp \ diff --git a/tests/pglabtests/tst_TryParsePostgresqlUrl.cpp b/tests/pglabtests/tst_TryParsePostgresqlUrl.cpp new file mode 100644 index 0000000..e10ff48 --- /dev/null +++ b/tests/pglabtests/tst_TryParsePostgresqlUrl.cpp @@ -0,0 +1,20 @@ +#include +#include +#include +#include "utils/PostgresqlUrlParser.h" +#include "PrintTo_Qt.h" + +using namespace testing; + +TEST(TryParsePostgresqlUrl, emptyInput) +{ + QString urlString = "postgresql://user:secret@localhost:5433/otherdb?connect_timeout=10&application_name=myapp"; + ConnectionConfig config; + bool result = TryParsePostgresqlUrl(urlString, config); + ASSERT_TRUE(result); + ASSERT_THAT(config.user(), Eq("user")); + ASSERT_THAT(config.password(), Eq("secret")); + ASSERT_THAT(config.host(), Eq("localhost")); + ASSERT_THAT(config.dbname(), Eq("otherdb")); + ASSERT_THAT(config.port(), Eq(5433)); +}