Basic support for passing postgresql uri on the commandline

This commit is contained in:
eelke 2025-02-17 18:09:19 +01:00
parent 4b4c95e57e
commit 4caccf1000
11 changed files with 453 additions and 192 deletions

View file

@ -40,7 +40,6 @@ void ConnectionController::init()
m_connectionTreeModel->load(); m_connectionTreeModel->load();
m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr); m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr);
m_connectionManagerWindow->show();
} }
void ConnectionController::showConnectionManager() void ConnectionController::showConnectionManager()
@ -55,17 +54,22 @@ void ConnectionController::openSqlWindowForConnection(QModelIndex index)
if (retrieveConnectionPassword(*config)) { if (retrieveConnectionPassword(*config)) {
m_connectionTreeModel->save(*config); m_connectionTreeModel->save(*config);
openSqlWindowForConnection(*config);
}
}
}
void ConnectionController::openSqlWindowForConnection(const ConnectionConfig &config)
{
// TODO instead of directly openening the mainwindow // TODO instead of directly openening the mainwindow
// do async connect and only open window when we have // do async connect and only open window when we have
// working connection // working connection
auto w = new DatabaseWindow(m_masterController, nullptr); auto w = new DatabaseWindow(m_masterController, nullptr);
w->setAttribute( Qt::WA_DeleteOnClose ); w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(*config); w->setConfig(config);
w->showMaximized(); w->showMaximized();
} }
}
}
void ConnectionController::openBackupDlgForConnection(QModelIndex index) void ConnectionController::openBackupDlgForConnection(QModelIndex index)
{ {

View file

@ -38,6 +38,7 @@ public:
void showConnectionManager(); void showConnectionManager();
void openSqlWindowForConnection(QModelIndex index); void openSqlWindowForConnection(QModelIndex index);
void openSqlWindowForConnection(const ConnectionConfig &cfg);
void openBackupDlgForConnection(QModelIndex index); void openBackupDlgForConnection(QModelIndex index);
/// Starts the form for creating a new conncetion. /// Starts the form for creating a new conncetion.

View file

@ -1,16 +1,19 @@
#include "ConnectionListModel.h" #include "ConnectionListModel.h"
#include "ScopeGuard.h" #include "ScopeGuard.h"
#include "util.h"
#include <botan/cryptobox.h> #include <botan/cryptobox.h>
#include <QDir> #include <QDir>
#include <QException>
#include <QMimeData> #include <QMimeData>
#include <QSettings> #include <QSettings>
#include <QSqlDatabase> #include <QSqlDatabase>
#include <QSqlError> #include <QSqlError>
#include <QSqlQuery> #include <QSqlQuery>
#include <QString>
#include <QStringBuilder>
#include <QStandardPaths> #include <QStandardPaths>
#include <QStringBuilder> #include <QStringBuilder>
#include <unordered_set>
namespace { namespace {
@ -41,6 +44,16 @@ CREATE TABLE IF NOT EXISTS connection (
password TEXT 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 = 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); :sslmode, :sslcert, :sslkey, :sslrootcert, :sslcrl, :password);
)__" ; )__" ;
std::tuple<bool, QSqlError> InitConnectionTables(QSqlDatabase &db)
// 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<QString> 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<QString> LoadMigrations()
{
std::unordered_set<QString> 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); QSqlQuery q_create_table(db);
q_create_table.prepare(q_create_table_conngroup); q_create_table.exec(q_create_table_conngroup);
if (!q_create_table.exec()) { Verify(q_create_table);
auto err = q_create_table.lastError(); q_create_table.exec(q_create_table_connection);
return { false, err }; Verify(q_create_table);
} // Start using migrations
q_create_table.prepare(q_create_table_connection); q_create_table.exec(q_create_table_migrations);
if (!q_create_table.exec()) { Verify(q_create_table);
auto err = q_create_table.lastError();
return { false, err };
}
return {true, {}};
} }
};
std::optional<QSqlError> SaveConnectionConfig(QSqlDatabase &db, const ConnectionConfig &cc, int conngroup_id) std::optional<QSqlError> SaveConnectionConfig(QSqlDatabase &db, const ConnectionConfig &cc, int conngroup_id)
{ {
@ -105,7 +233,9 @@ ConnectionTreeModel::ConnectionTreeModel(QObject *parent, QSqlDatabase &db)
void ConnectionTreeModel::load() void ConnectionTreeModel::load()
{ {
InitConnectionTables(m_db); //InitConnectionTables(m_db);
MigrationDirector md(m_db);
md.Execute();
QSqlQuery q(m_db); QSqlQuery q(m_db);
q.prepare("SELECT conngroup_id, gname FROM conngroup;"); q.prepare("SELECT conngroup_id, gname FROM conngroup;");
@ -124,9 +254,7 @@ void ConnectionTreeModel::load()
m_groups.push_back(g); m_groups.push_back(g);
} }
q.prepare("SELECT uuid, cname, conngroup_id, host, hostaddr, port, " q.prepare("SELECT uuid, cname, conngroup_id, password "
" user, dbname, sslmode, sslcert, sslkey, sslrootcert, sslcrl, "
" password "
"FROM connection ORDER BY conngroup_id, cname;"); "FROM connection ORDER BY conngroup_id, cname;");
if (!q.exec()) { if (!q.exec()) {
// auto err = q_create_table.lastError(); // auto err = q_create_table.lastError();

View file

@ -1,5 +1,8 @@
#include "MasterController.h" #include "MasterController.h"
#include "ConnectionController.h" #include "ConnectionController.h"
#include "utils/PostgresqlUrlParser.h"
#include <ConnectionConfig.h>
#include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QStandardPaths> #include <QStandardPaths>
@ -39,6 +42,19 @@ void MasterController::init()
m_connectionController = new ConnectionController(this); 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() ConnectionController *MasterController::connectionController()

View file

@ -24,6 +24,20 @@ namespace {
return v.empty() ? nullptr : v.c_str(); 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 } // end unnamed namespace
QString SslModeToString(SslMode sm) QString SslModeToString(SslMode sm)
@ -46,7 +60,7 @@ SslMode StringToSslMode(QString s)
} }
ConnectionConfig::ConnectionConfig() ConnectionConfig::ConnectionConfig()
: m_applicationName(QCoreApplication::applicationName().toUtf8().data()) : m_parameters{{"application_name", QCoreApplication::applicationName()}}
{} {}
const ConnectionGroup *ConnectionConfig::parent() const const ConnectionGroup *ConnectionConfig::parent() const
@ -92,168 +106,131 @@ const QString& ConnectionConfig::name() const
void ConnectionConfig::setHost(const QString& host) void ConnectionConfig::setHost(const QString& host)
{ {
if (m_host != host) setParameter(keywords.host, host);
{
m_dirty = true;
m_host = std::move(host);
}
} }
const QString& ConnectionConfig::host() const QString ConnectionConfig::host() const
{ {
return m_host; return getParameter(keywords.host);
} }
void ConnectionConfig::setHostAddr(const QString &v) void ConnectionConfig::setHostAddr(const QString &v)
{ {
if (m_hostaddr != v) setParameter(keywords.hostaddr, v);
{
m_dirty = true;
m_hostaddr = std::move(v);
}
} }
const QString& ConnectionConfig::hostAddr() const QString ConnectionConfig::hostAddr() const
{ {
return m_hostaddr; return getParameter(keywords.hostaddr);
} }
void ConnectionConfig::setPort(unsigned short port) void ConnectionConfig::setPort(unsigned short port)
{ {
if (m_port != port) setParameter(keywords.port, QString::number(port));
{
m_dirty = true;
m_port = port;
}
} }
unsigned short ConnectionConfig::port() const unsigned short ConnectionConfig::port() const
{ {
return m_port; QString s = getParameter(keywords.port);
if (s.isEmpty())
return 5432;
unsigned short port = static_cast<unsigned short>(s.toInt());
return port;
} }
void ConnectionConfig::setUser(const QString& v) void ConnectionConfig::setUser(const QString& v)
{ {
if (m_user != v) setParameter(keywords.user, v);
{
m_dirty = true;
m_user = v;
} }
} QString ConnectionConfig::user() const
const QString& ConnectionConfig::user() const
{ {
return m_user; return getParameter(keywords.user);
} }
void ConnectionConfig::setPassword(const QString& v) void ConnectionConfig::setPassword(const QString& v)
{ {
if (m_password != v) setParameter(keywords.password, v);
{
m_dirty = true;
m_password = v;
}
} }
const QString& ConnectionConfig::password() const QString ConnectionConfig::password() const
{ {
return m_password; return getParameter(keywords.password);
} }
void ConnectionConfig::setDbname(const QString& v) void ConnectionConfig::setDbname(const QString& v)
{ {
if (m_dbname != v) setParameter(keywords.dbname, v);
{
m_dirty = true;
m_dbname = v;
}
} }
const QString& ConnectionConfig::dbname() const QString ConnectionConfig::dbname() const
{ {
return m_dbname; return getParameter(keywords.dbname);
} }
void ConnectionConfig::setSslMode(SslMode m) void ConnectionConfig::setSslMode(SslMode m)
{ {
if (m_sslMode != m) setParameter(keywords.sslmode, SslModeToString(m));
{
m_dirty = true;
m_sslMode = m;
}
} }
SslMode ConnectionConfig::sslMode() const SslMode ConnectionConfig::sslMode() const
{ {
return m_sslMode; QString s = getParameter(keywords.sslmode);
return StringToSslMode(s);
} }
void ConnectionConfig::setSslCert(const QString& v) void ConnectionConfig::setSslCert(const QString& v)
{ {
if (m_sslCert != v) setParameter(keywords.sslcert, v);
{
m_dirty = true;
m_sslCert = std::move(v);
}
} }
const QString& ConnectionConfig::sslCert() const QString ConnectionConfig::sslCert() const
{ {
return m_sslCert; return getParameter(keywords.sslcert);
} }
void ConnectionConfig::setSslKey(const QString& v) void ConnectionConfig::setSslKey(const QString& v)
{ {
if (m_sslKey != v) setParameter(keywords.sslkey, v);
{
m_dirty = true;
m_sslKey = std::move(v);
}
} }
const QString& ConnectionConfig::sslKey() const QString ConnectionConfig::sslKey() const
{ {
return m_sslKey; return getParameter(keywords.sslkey);
} }
void ConnectionConfig::setSslRootCert(const QString& v) void ConnectionConfig::setSslRootCert(const QString& v)
{ {
if (m_sslRootCert != v) setParameter(keywords.sslrootcert, v);
{
m_dirty = true;
m_sslRootCert = std::move(v);
}
} }
const QString& ConnectionConfig::sslRootCert() const QString ConnectionConfig::sslRootCert() const
{ {
return m_sslRootCert; return getParameter(keywords.sslrootcert);
} }
void ConnectionConfig::setSslCrl(const QString& v) void ConnectionConfig::setSslCrl(const QString& v)
{ {
if (m_sslCrl != v) setParameter(keywords.sslcrl, v);
{
m_dirty = true;
m_sslCrl = std::move(v);
}
} }
const QString& ConnectionConfig::sslCrl() const QString ConnectionConfig::sslCrl() const
{ {
return m_sslCrl; return getParameter(keywords.sslcrl);
} }
bool ConnectionConfig::isSameDatabase(const ConnectionConfig &rhs) const // bool ConnectionConfig::isSameDatabase(const ConnectionConfig &rhs) const
{ // {
return m_host == rhs.m_host // return host() == rhs.host()
&& m_hostaddr == rhs.m_hostaddr // // && m_hostaddr == rhs.m_hostaddr
&& m_port == rhs.m_port // // && m_port == rhs.m_port
&& m_user == rhs.m_user // // && m_user == rhs.m_user
&& m_password == rhs.m_password // // && m_password == rhs.m_password
&& m_dbname == rhs.m_dbname; // // && m_dbname == rhs.m_dbname
} // ;
// }
bool ConnectionConfig::dirty() const bool ConnectionConfig::dirty() const
{ {
@ -314,58 +291,111 @@ QString ConnectionConfig::escapeConnectionStringValue(const QString &value)
QString ConnectionConfig::connectionString() const QString ConnectionConfig::connectionString() const
{ {
QString s; QString s;
s += "host="
% escapeConnectionStringValue(m_host) for (auto && param : m_parameters)
% " 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="; // maybe we should prevent empty parameters from staying in the map?
s += escapeConnectionStringValue(m_sslCert); if (!param.second.isEmpty())
}
if (!m_sslKey.isEmpty())
{ {
s += " sslkey="; s += param.first % "=" % escapeConnectionStringValue(param.second);
s += escapeConnectionStringValue(m_sslKey);
} }
if (!m_sslRootCert.isEmpty())
{
s += " sslrootcrt=";
s += escapeConnectionStringValue(m_sslRootCert);
} }
if (!m_sslCrl.isEmpty())
{ // s += "host="
s += " sslCrl="; // % escapeConnectionStringValue(m_host)
s += escapeConnectionStringValue(m_sslCrl); // % " port="
} // % QString::number(m_port)
s += " client_encoding=utf8"; // % " user="
s += " application_name="; // % escapeConnectionStringValue(m_user);
s += escapeConnectionStringValue(m_applicationName); // 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; 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 void ConnectionConfig::writeToEnvironment(QProcessEnvironment &env) const
{ {
strToEnv(env, "PGHOST", m_host); strToEnv(env, "PGHOST", getParameter(keywords.host));
strToEnv(env, "PGHOSTADDR", m_hostaddr); strToEnv(env, "PGHOSTADDR", getParameter(keywords.hostaddr));
strToEnv(env, "PGPORT", QString::number(m_port)); strToEnv(env, "PGPORT", getParameter(keywords.port));
strToEnv(env, "PGDATABASE", m_dbname); strToEnv(env, "PGDATABASE", getParameter(keywords.dbname));
strToEnv(env, "PGUSER", m_user); strToEnv(env, "PGUSER", getParameter(keywords.user));
strToEnv(env, "PGPASSWORD", m_password); strToEnv(env, "PGPASSWORD", getParameter(keywords.password));
strToEnv(env, "PGSSLMODE", SslModeToString(m_sslMode)); strToEnv(env, "PGSSLMODE", getParameter(keywords.sslmode));
strToEnv(env, "PGSSLCERT", m_sslCert); // strToEnv(env, "PGSSLCERT", m_sslCert);
strToEnv(env, "PGSSLKEY", m_sslKey); // strToEnv(env, "PGSSLKEY", m_sslKey);
strToEnv(env, "PGSSLROOTCERT", m_sslRootCert); // strToEnv(env, "PGSSLROOTCERT", m_sslRootCert);
strToEnv(env, "PGSSLCRL", m_sslCrl); // strToEnv(env, "PGSSLCRL", m_sslCrl);
strToEnv(env, "PGSSLCOMPRESSION", "0"); strToEnv(env, "PGSSLCOMPRESSION", "0");
strToEnv(env, "PGCONNECT_TIMEOUT", "10"); strToEnv(env, "PGCONNECT_TIMEOUT", "10");
env.insert("PGCLIENTENCODING", "utf8"); env.insert("PGCLIENTENCODING", "utf8");

View file

@ -6,6 +6,7 @@
#include <QMetaType> #include <QMetaType>
#include <QUuid> #include <QUuid>
#include <QVector> #include <QVector>
#include <map>
#include <vector> #include <vector>
#include <string> #include <string>
@ -74,42 +75,42 @@ public:
const QString& name() const; const QString& name() const;
void setHost(const QString& host); void setHost(const QString& host);
const QString& host() const; QString host() const;
void setHostAddr(const QString& v); void setHostAddr(const QString& v);
const QString& hostAddr() const; QString hostAddr() const;
void setPort(unsigned short port); void setPort(unsigned short port);
unsigned short port() const; unsigned short port() const;
void setUser(const QString& v); void setUser(const QString& v);
const QString& user() const; QString user() const;
void setPassword(const QString& v); void setPassword(const QString& v);
const QString& password() const; QString password() const;
void setDbname(const QString& v); void setDbname(const QString& v);
const QString& dbname() const; QString dbname() const;
void setSslMode(SslMode m); void setSslMode(SslMode m);
SslMode sslMode() const; SslMode sslMode() const;
void setSslCert(const QString& v); void setSslCert(const QString& v);
const QString& sslCert() const; QString sslCert() const;
void setSslKey(const QString& v); void setSslKey(const QString& v);
const QString& sslKey() const; QString sslKey() const;
void setSslRootCert(const QString& v); void setSslRootCert(const QString& v);
const QString& sslRootCert() const; QString sslRootCert() const;
void setSslCrl(const QString& v); void setSslCrl(const QString& v);
const QString& sslCrl() const; QString sslCrl() const;
// const char * const * getKeywords() const; // const char * const * getKeywords() const;
// const char * const * getValues() const; // const char * const * getValues() const;
bool isSameDatabase(const ConnectionConfig &rhs) const; // bool isSameDatabase(const ConnectionConfig &rhs) const;
void writeToEnvironment(QProcessEnvironment &env) const; void writeToEnvironment(QProcessEnvironment &env) const;
@ -131,26 +132,31 @@ public:
*/ */
static QString escapeConnectionStringValue(const QString &value); static QString escapeConnectionStringValue(const QString &value);
QString connectionString() const; QString connectionString() const;
void setParameter(const QString &name, const QString &value);
QString getParameter(const QString &name) const;
private: private:
QUuid m_uuid; QUuid m_uuid;
QString m_name; QString m_name;
QString m_host; // QString m_host;
QString m_hostaddr; // QString m_hostaddr;
uint16_t m_port = 5432; // uint16_t m_port = 5432;
QString m_user; // QString m_user;
QString m_password; ///< Note this is not saved in the DB only the m_encodedPassword is safed. // QString m_password; ///< Note this is not saved in the DB only the m_encodedPassword is safed.
QString m_dbname; // QString m_dbname;
SslMode m_sslMode = SslMode::prefer; // SslMode m_sslMode = SslMode::prefer;
QString m_sslCert; // QString m_sslCert;
QString m_sslKey; // QString m_sslKey;
QString m_sslRootCert; // QString m_sslRootCert;
QString m_sslCrl; // QString m_sslCrl;
QString m_applicationName; // QString m_applicationName;
QByteArray m_encodedPassword; QByteArray m_encodedPassword;
std::unordered_map<QString, QString> m_parameters;
bool m_dirty = false; bool m_dirty = false;
ConnectionGroup* m_group = nullptr; ConnectionGroup* m_group = nullptr;

View file

@ -88,7 +88,8 @@ SOURCES += \
catalog/PgAcl.cpp \ catalog/PgAcl.cpp \
catalog/PgSequence.cpp \ catalog/PgSequence.cpp \
catalog/PgSequenceContainer.cpp \ catalog/PgSequenceContainer.cpp \
utils/HumanReadableBytes.cpp utils/HumanReadableBytes.cpp \
utils/PostgresqlUrlParser.cpp
HEADERS += \ HEADERS += \
Pglablib.h \ Pglablib.h \
@ -164,7 +165,8 @@ HEADERS += \
catalog/PgAcl.h \ catalog/PgAcl.h \
catalog/PgSequence.h \ catalog/PgSequence.h \
catalog/PgSequenceContainer.h \ catalog/PgSequenceContainer.h \
utils/HumanReadableBytes.h utils/HumanReadableBytes.h \
utils/PostgresqlUrlParser.h
unix { unix {
target.path = /usr/lib target.path = /usr/lib

View file

@ -0,0 +1,38 @@
#include "PostgresqlUrlParser.h"
#include <ConnectionConfig.h>
#include <QUrl>
#include <QUrlQuery>
//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;
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <QString>
class ConnectionConfig;
bool TryParsePostgresqlUrl(const QString &urlString, ConnectionConfig &out);
// class PostgresqlUrlParser
// {
// public:
// PostgresqlUrlParser();
//};

View file

@ -19,6 +19,7 @@ SOURCES += main.cpp \
tst_ConvertToMultiLineCString.cpp \ tst_ConvertToMultiLineCString.cpp \
tst_ExplainJsonParser.cpp \ tst_ExplainJsonParser.cpp \
tst_HumanReadableBytes.cpp \ tst_HumanReadableBytes.cpp \
tst_TryParsePostgresqlUrl.cpp \
tst_escapeConnectionStringValue.cpp \ tst_escapeConnectionStringValue.cpp \
tst_expected.cpp \ tst_expected.cpp \
tst_SqlLexer.cpp \ tst_SqlLexer.cpp \

View file

@ -0,0 +1,20 @@
#include <gtest/gtest.h>
#include <gmock/gmock-matchers.h>
#include <ConnectionConfig.h>
#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));
}