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

@ -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<unsigned short>(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");

View file

@ -6,6 +6,7 @@
#include <QMetaType>
#include <QUuid>
#include <QVector>
#include <map>
#include <vector>
#include <string>
@ -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<QString, QString> m_parameters;
bool m_dirty = false;
ConnectionGroup* m_group = nullptr;

View file

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

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();
//};