aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJude Melton-Houghton <jwmhjwmh@gmail.com>2022-10-19 14:16:57 -0400
committerLoïc Blot <nerzhul@users.noreply.github.com>2022-11-10 18:56:48 +0100
commitaaa05f901adddebf2435890edcbe03fe3e501771 (patch)
tree187afc29f1c3bc36bb1e7ddc9b95c8e46d48c200 /src
parent9dbac989bd0fb22de8bd882a788c827a7a3bc2e1 (diff)
downloadminetest-aaa05f901adddebf2435890edcbe03fe3e501771.tar.xz
Add mod storage PostgreSQL backend
Diffstat (limited to 'src')
-rw-r--r--src/database/database-postgresql.cpp207
-rw-r--r--src/database/database-postgresql.h24
-rw-r--r--src/server.cpp11
-rw-r--r--src/unittest/test_modmetadatadatabase.cpp73
4 files changed, 315 insertions, 0 deletions
diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp
index a84c89fa8..85beff995 100644
--- a/src/database/database-postgresql.cpp
+++ b/src/database/database-postgresql.cpp
@@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "settings.h"
#include "remoteplayer.h"
#include "server/player_sao.h"
+#include <cstdlib>
Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string,
const char *type) :
@@ -812,5 +813,211 @@ void AuthDatabasePostgreSQL::writePrivileges(const AuthEntry &authEntry)
}
}
+ModMetadataDatabasePostgreSQL::ModMetadataDatabasePostgreSQL(const std::string &connect_string):
+ Database_PostgreSQL(connect_string, "_mod_storage"),
+ ModMetadataDatabase()
+{
+ connectToDatabase();
+}
+
+void ModMetadataDatabasePostgreSQL::createDatabase()
+{
+ createTableIfNotExists("mod_storage",
+ "CREATE TABLE mod_storage ("
+ "modname TEXT NOT NULL,"
+ "key BYTEA NOT NULL,"
+ "value BYTEA NOT NULL,"
+ "PRIMARY KEY (modname, key)"
+ ");");
+
+ infostream << "PostgreSQL: Mod Storage Database was initialized." << std::endl;
+}
+
+void ModMetadataDatabasePostgreSQL::initStatements()
+{
+ prepareStatement("get_all",
+ "SELECT key, value FROM mod_storage WHERE modname = $1");
+ prepareStatement("get_all_keys",
+ "SELECT key FROM mod_storage WHERE modname = $1");
+ prepareStatement("get",
+ "SELECT value FROM mod_storage WHERE modname = $1 AND key = $2::bytea");
+ prepareStatement("has",
+ "SELECT true FROM mod_storage WHERE modname = $1 AND key = $2::bytea");
+ if (getPGVersion() < 90500) {
+ prepareStatement("set_insert",
+ "INSERT INTO mod_storage (modname, key, value) "
+ "SELECT $1, $2::bytea, $3::bytea "
+ "WHERE NOT EXISTS ("
+ "SELECT true FROM mod_storage WHERE modname = $1 AND key = $2::bytea"
+ ")");
+ prepareStatement("set_update",
+ "UPDATE mod_storage SET value = $3::bytea WHERE modname = $1 AND key = $2::bytea");
+ } else {
+ prepareStatement("set",
+ "INSERT INTO mod_storage (modname, key, value) VALUES ($1, $2::bytea, $3::bytea) "
+ "ON CONFLICT ON CONSTRAINT mod_storage_pkey DO "
+ "UPDATE SET value = $3::bytea");
+ }
+ prepareStatement("remove",
+ "DELETE FROM mod_storage WHERE modname = $1 AND key = $2::bytea");
+ prepareStatement("remove_all",
+ "DELETE FROM mod_storage WHERE modname = $1");
+ prepareStatement("list",
+ "SELECT DISTINCT modname FROM mod_storage");
+}
+
+bool ModMetadataDatabasePostgreSQL::getModEntries(const std::string &modname, StringMap *storage)
+{
+ verifyDatabase();
+
+ const void *args[] = { modname.c_str() };
+ const int argLen[] = { -1 };
+ const int argFmt[] = { 0 };
+ PGresult *results = execPrepared("get_all", ARRLEN(args),
+ args, argLen, argFmt, false);
+
+ int numrows = PQntuples(results);
+
+ for (int row = 0; row < numrows; ++row) {
+ std::string key(PQgetvalue(results, row, 0), PQgetlength(results, row, 0));
+ std::string value(PQgetvalue(results, row, 1), PQgetlength(results, row, 1));
+ storage->emplace(std::move(key), std::move(value));
+ }
+
+ PQclear(results);
+
+ return true;
+}
+
+bool ModMetadataDatabasePostgreSQL::getModKeys(const std::string &modname,
+ std::vector<std::string> *storage)
+{
+ verifyDatabase();
+
+ const void *args[] = { modname.c_str() };
+ const int argLen[] = { -1 };
+ const int argFmt[] = { 0 };
+ PGresult *results = execPrepared("get_all_keys", ARRLEN(args),
+ args, argLen, argFmt, false);
+
+ int numrows = PQntuples(results);
+
+ storage->reserve(storage->size() + numrows);
+ for (int row = 0; row < numrows; ++row)
+ storage->emplace_back(PQgetvalue(results, row, 0), PQgetlength(results, row, 0));
+
+ PQclear(results);
+
+ return true;
+}
+
+bool ModMetadataDatabasePostgreSQL::getModEntry(const std::string &modname,
+ const std::string &key, std::string *value)
+{
+ verifyDatabase();
+
+ const void *args[] = { modname.c_str(), key.c_str() };
+ const int argLen[] = { -1, (int)MYMIN(key.size(), INT_MAX) };
+ const int argFmt[] = { 0, 1 };
+ PGresult *results = execPrepared("get", ARRLEN(args), args, argLen, argFmt, false);
+
+ int numrows = PQntuples(results);
+ bool found = numrows > 0;
+
+ if (found)
+ value->assign(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0));
+
+ PQclear(results);
+
+ return found;
+}
+
+bool ModMetadataDatabasePostgreSQL::hasModEntry(const std::string &modname,
+ const std::string &key)
+{
+ verifyDatabase();
+
+ const void *args[] = { modname.c_str(), key.c_str() };
+ const int argLen[] = { -1, (int)MYMIN(key.size(), INT_MAX) };
+ const int argFmt[] = { 0, 1 };
+ PGresult *results = execPrepared("has", ARRLEN(args), args, argLen, argFmt, false);
+
+ int numrows = PQntuples(results);
+ bool found = numrows > 0;
+
+ PQclear(results);
+
+ return found;
+}
+
+bool ModMetadataDatabasePostgreSQL::setModEntry(const std::string &modname,
+ const std::string &key, const std::string &value)
+{
+ verifyDatabase();
+
+ const void *args[] = { modname.c_str(), key.c_str(), value.c_str() };
+ const int argLen[] = {
+ -1,
+ (int)MYMIN(key.size(), INT_MAX),
+ (int)MYMIN(value.size(), INT_MAX),
+ };
+ const int argFmt[] = { 0, 1, 1 };
+ if (getPGVersion() < 90500) {
+ execPrepared("set_insert", ARRLEN(args), args, argLen, argFmt);
+ execPrepared("set_update", ARRLEN(args), args, argLen, argFmt);
+ } else {
+ execPrepared("set", ARRLEN(args), args, argLen, argFmt);
+ }
+
+ return true;
+}
+
+bool ModMetadataDatabasePostgreSQL::removeModEntry(const std::string &modname,
+ const std::string &key)
+{
+ verifyDatabase();
+
+ const void *args[] = { modname.c_str(), key.c_str() };
+ const int argLen[] = { -1, (int)MYMIN(key.size(), INT_MAX) };
+ const int argFmt[] = { 0, 1 };
+ PGresult *results = execPrepared("remove", ARRLEN(args), args, argLen, argFmt, false);
+
+ int affected = atoi(PQcmdTuples(results));
+
+ PQclear(results);
+
+ return affected > 0;
+}
+
+bool ModMetadataDatabasePostgreSQL::removeModEntries(const std::string &modname)
+{
+ verifyDatabase();
+
+ const void *args[] = { modname.c_str() };
+ const int argLen[] = { -1 };
+ const int argFmt[] = { 0 };
+ PGresult *results = execPrepared("remove_all", ARRLEN(args), args, argLen, argFmt, false);
+
+ int affected = atoi(PQcmdTuples(results));
+
+ PQclear(results);
+
+ return affected > 0;
+}
+
+void ModMetadataDatabasePostgreSQL::listMods(std::vector<std::string> *res)
+{
+ verifyDatabase();
+
+ PGresult *results = execPrepared("list", 0, NULL, false);
+
+ int numrows = PQntuples(results);
+
+ for (int row = 0; row < numrows; ++row)
+ res->emplace_back(PQgetvalue(results, row, 0), PQgetlength(results, row, 0));
+
+ PQclear(results);
+}
+
#endif // USE_POSTGRESQL
diff --git a/src/database/database-postgresql.h b/src/database/database-postgresql.h
index 0a9ead01e..22269d362 100644
--- a/src/database/database-postgresql.h
+++ b/src/database/database-postgresql.h
@@ -169,3 +169,27 @@ protected:
private:
virtual void writePrivileges(const AuthEntry &authEntry);
};
+
+class ModMetadataDatabasePostgreSQL : private Database_PostgreSQL, public ModMetadataDatabase
+{
+public:
+ ModMetadataDatabasePostgreSQL(const std::string &connect_string);
+ ~ModMetadataDatabasePostgreSQL() = default;
+
+ bool getModEntries(const std::string &modname, StringMap *storage);
+ bool getModKeys(const std::string &modname, std::vector<std::string> *storage);
+ bool getModEntry(const std::string &modname, const std::string &key, std::string *value);
+ bool hasModEntry(const std::string &modname, const std::string &key);
+ bool setModEntry(const std::string &modname,
+ const std::string &key, const std::string &value);
+ bool removeModEntry(const std::string &modname, const std::string &key);
+ bool removeModEntries(const std::string &modname);
+ void listMods(std::vector<std::string> *res);
+
+ void beginSave() { Database_PostgreSQL::beginSave(); }
+ void endSave() { Database_PostgreSQL::endSave(); }
+
+protected:
+ virtual void createDatabase();
+ virtual void initStatements();
+};
diff --git a/src/server.cpp b/src/server.cpp
index 762450a75..416e51f30 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -67,6 +67,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "server/serverinventorymgr.h"
#include "translation.h"
#include "database/database-sqlite3.h"
+#if USE_POSTGRESQL
+#include "database/database-postgresql.h"
+#endif
#include "database/database-files.h"
#include "database/database-dummy.h"
#include "gameparams.h"
@@ -4025,6 +4028,14 @@ ModMetadataDatabase *Server::openModStorageDatabase(const std::string &backend,
if (backend == "sqlite3")
return new ModMetadataDatabaseSQLite3(world_path);
+#if USE_POSTGRESQL
+ if (backend == "postgresql") {
+ std::string connect_string;
+ world_mt.getNoEx("pgsql_mod_storage_connection", connect_string);
+ return new ModMetadataDatabasePostgreSQL(connect_string);
+ }
+#endif // USE_POSTGRESQL
+
if (backend == "files")
return new ModMetadataDatabaseFiles(world_path);
diff --git a/src/unittest/test_modmetadatadatabase.cpp b/src/unittest/test_modmetadatadatabase.cpp
index b93ff7e3b..b46feb884 100644
--- a/src/unittest/test_modmetadatadatabase.cpp
+++ b/src/unittest/test_modmetadatadatabase.cpp
@@ -20,12 +20,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
// This file is an edited copy of test_authdatabase.cpp
+#include "cmake_config.h"
+
#include "test.h"
#include <algorithm>
+#include <cstdlib>
#include "database/database-dummy.h"
#include "database/database-files.h"
#include "database/database-sqlite3.h"
+#if USE_POSTGRESQL
+#include "database/database-postgresql.h"
+#endif
#include "filesys.h"
namespace
@@ -104,6 +110,46 @@ private:
std::string dir;
ModMetadataDatabase *mod_meta_db = nullptr;
};
+
+#if USE_POSTGRESQL
+void clearPostgreSQLDatabase(const std::string &connect_string)
+{
+ ModMetadataDatabasePostgreSQL db(connect_string);
+ std::vector<std::string> modnames;
+ db.beginSave();
+ db.listMods(&modnames);
+ for (const std::string &modname : modnames)
+ db.removeModEntries(modname);
+ db.endSave();
+}
+
+class PostgreSQLProvider : public ModMetadataDatabaseProvider
+{
+public:
+ PostgreSQLProvider(const std::string &connect_string): m_connect_string(connect_string) {}
+
+ ~PostgreSQLProvider()
+ {
+ if (m_db)
+ m_db->endSave();
+ delete m_db;
+ }
+
+ ModMetadataDatabase *getModMetadataDatabase() override
+ {
+ if (m_db)
+ m_db->endSave();
+ delete m_db;
+ m_db = new ModMetadataDatabasePostgreSQL(m_connect_string);
+ m_db->beginSave();
+ return m_db;
+ };
+
+private:
+ std::string m_connect_string;
+ ModMetadataDatabase *m_db = nullptr;
+};
+#endif // USE_POSTGRESQL
}
class TestModMetadataDatabase : public TestBase
@@ -193,6 +239,33 @@ void TestModMetadataDatabase::runTests(IGameDef *gamedef)
runTestsForCurrentDB();
delete mod_meta_provider;
+
+#if USE_POSTGRESQL
+ const char *env_postgresql_connect_string = getenv("MINETEST_POSTGRESQL_CONNECT_STRING");
+ if (env_postgresql_connect_string) {
+ std::string connect_string(env_postgresql_connect_string);
+
+ rawstream << "-------- PostgreSQL database (same object)" << std::endl;
+
+ clearPostgreSQLDatabase(connect_string);
+ mod_meta_db = new ModMetadataDatabasePostgreSQL(connect_string);
+ mod_meta_provider = new FixedProvider(mod_meta_db);
+
+ runTestsForCurrentDB();
+
+ delete mod_meta_db;
+ delete mod_meta_provider;
+
+ rawstream << "-------- PostgreSQL database (new objects)" << std::endl;
+
+ clearPostgreSQLDatabase(connect_string);
+ mod_meta_provider = new PostgreSQLProvider(connect_string);
+
+ runTestsForCurrentDB();
+
+ delete mod_meta_provider;
+ }
+#endif // USE_POSTGRESQL
}
////////////////////////////////////////////////////////////////////////////////