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

@ -1,16 +1,19 @@
#include "ConnectionListModel.h"
#include "ScopeGuard.h"
#include "util.h"
#include <botan/cryptobox.h>
#include <QDir>
#include <QException>
#include <QMimeData>
#include <QSettings>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QString>
#include <QStringBuilder>
#include <QStandardPaths>
#include <QStringBuilder>
#include <unordered_set>
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<bool, QSqlError> 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<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);
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<QSqlError> 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();