2 Copyright (C) 2016 Loic Blot <loic.blot@unix-experience.fr>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as published by
6 the Free Software Foundation; either version 2.1 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "database-postgresql.h"
26 // Without this some of the network functions are not found on mingw
28 #define _WIN32_WINNT 0x0501
33 #include <netinet/in.h>
37 #include "exceptions.h"
39 #include "remoteplayer.h"
40 #include "server/player_sao.h"
42 Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string,
44 m_connect_string(connect_string)
46 if (m_connect_string.empty()) {
47 // Use given type to reference the exact setting in the error message
50 "Set pgsql" + s + "_connection string in world.mt to "
51 "use the postgresql backend\n"
53 "pgsql" + s + "_connection has the following form: \n"
54 "\tpgsql" + s + "_connection = host=127.0.0.1 port=5432 "
55 "user=mt_user password=mt_password dbname=minetest" + s + "\n"
56 "mt_user should have CREATE TABLE, INSERT, SELECT, UPDATE and "
57 "DELETE rights on the database. "
58 "Don't create mt_user as a SUPERUSER!";
59 throw SettingNotFoundException(msg);
63 Database_PostgreSQL::~Database_PostgreSQL()
68 void Database_PostgreSQL::connectToDatabase()
70 m_conn = PQconnectdb(m_connect_string.c_str());
72 if (PQstatus(m_conn) != CONNECTION_OK) {
73 throw DatabaseException(std::string(
74 "PostgreSQL database error: ") +
75 PQerrorMessage(m_conn));
78 m_pgversion = PQserverVersion(m_conn);
81 * We are using UPSERT feature from PostgreSQL 9.5
82 * to have the better performance where possible.
84 if (m_pgversion < 90500) {
85 warningstream << "Your PostgreSQL server lacks UPSERT "
86 << "support. Use version 9.5 or better if possible."
90 infostream << "PostgreSQL Database: Version " << m_pgversion
91 << " Connection made." << std::endl;
97 void Database_PostgreSQL::verifyDatabase()
99 if (PQstatus(m_conn) == CONNECTION_OK)
106 void Database_PostgreSQL::ping()
108 if (PQping(m_connect_string.c_str()) != PQPING_OK) {
109 throw DatabaseException(std::string(
110 "PostgreSQL database error: ") +
111 PQerrorMessage(m_conn));
115 bool Database_PostgreSQL::initialized() const
117 return (PQstatus(m_conn) == CONNECTION_OK);
120 PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
122 ExecStatusType statusType = PQresultStatus(result);
124 switch (statusType) {
125 case PGRES_COMMAND_OK:
126 case PGRES_TUPLES_OK:
128 case PGRES_FATAL_ERROR:
130 throw DatabaseException(
131 std::string("PostgreSQL database error: ") +
132 PQresultErrorMessage(result));
141 void Database_PostgreSQL::createTableIfNotExists(const std::string &table_name,
142 const std::string &definition)
144 std::string sql_check_table = "SELECT relname FROM pg_class WHERE relname='" +
146 PGresult *result = checkResults(PQexec(m_conn, sql_check_table.c_str()), false);
148 // If table doesn't exist, create it
149 if (!PQntuples(result)) {
150 checkResults(PQexec(m_conn, definition.c_str()));
156 void Database_PostgreSQL::beginSave()
159 checkResults(PQexec(m_conn, "BEGIN;"));
162 void Database_PostgreSQL::endSave()
164 checkResults(PQexec(m_conn, "COMMIT;"));
167 void Database_PostgreSQL::rollback()
169 checkResults(PQexec(m_conn, "ROLLBACK;"));
172 MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string):
173 Database_PostgreSQL(connect_string, ""),
180 void MapDatabasePostgreSQL::createDatabase()
182 createTableIfNotExists("blocks",
183 "CREATE TABLE blocks ("
188 "PRIMARY KEY (posX,posY,posZ)"
192 infostream << "PostgreSQL: Map Database was initialized." << std::endl;
195 void MapDatabasePostgreSQL::initStatements()
197 prepareStatement("read_block",
198 "SELECT data FROM blocks "
199 "WHERE posX = $1::int4 AND posY = $2::int4 AND "
202 if (getPGVersion() < 90500) {
203 prepareStatement("write_block_insert",
204 "INSERT INTO blocks (posX, posY, posZ, data) SELECT "
205 "$1::int4, $2::int4, $3::int4, $4::bytea "
206 "WHERE NOT EXISTS (SELECT true FROM blocks "
207 "WHERE posX = $1::int4 AND posY = $2::int4 AND "
210 prepareStatement("write_block_update",
211 "UPDATE blocks SET data = $4::bytea "
212 "WHERE posX = $1::int4 AND posY = $2::int4 AND "
215 prepareStatement("write_block",
216 "INSERT INTO blocks (posX, posY, posZ, data) VALUES "
217 "($1::int4, $2::int4, $3::int4, $4::bytea) "
218 "ON CONFLICT ON CONSTRAINT blocks_pkey DO "
219 "UPDATE SET data = $4::bytea");
222 prepareStatement("delete_block", "DELETE FROM blocks WHERE "
223 "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4");
225 prepareStatement("list_all_loadable_blocks",
226 "SELECT posX, posY, posZ FROM blocks");
229 bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data)
231 // Verify if we don't overflow the platform integer with the mapblock size
232 if (data.size() > INT_MAX) {
233 errorstream << "Database_PostgreSQL::saveBlock: Data truncation! "
234 << "data.size() over 0xFFFFFFFF (== " << data.size()
246 const void *args[] = { &x, &y, &z, data.c_str() };
247 const int argLen[] = {
248 sizeof(x), sizeof(y), sizeof(z), (int)data.size()
250 const int argFmt[] = { 1, 1, 1, 1 };
252 if (getPGVersion() < 90500) {
253 execPrepared("write_block_update", ARRLEN(args), args, argLen, argFmt);
254 execPrepared("write_block_insert", ARRLEN(args), args, argLen, argFmt);
256 execPrepared("write_block", ARRLEN(args), args, argLen, argFmt);
261 void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block)
270 const void *args[] = { &x, &y, &z };
271 const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
272 const int argFmt[] = { 1, 1, 1 };
274 PGresult *results = execPrepared("read_block", ARRLEN(args), args,
275 argLen, argFmt, false);
277 if (PQntuples(results))
278 block->assign(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0));
285 bool MapDatabasePostgreSQL::deleteBlock(const v3s16 &pos)
294 const void *args[] = { &x, &y, &z };
295 const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
296 const int argFmt[] = { 1, 1, 1 };
298 execPrepared("delete_block", ARRLEN(args), args, argLen, argFmt);
303 void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
307 PGresult *results = execPrepared("list_all_loadable_blocks", 0,
308 NULL, NULL, NULL, false, false);
310 int numrows = PQntuples(results);
312 for (int row = 0; row < numrows; ++row)
313 dst.push_back(pg_to_v3s16(results, row, 0));
321 PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string):
322 Database_PostgreSQL(connect_string, "_player"),
329 void PlayerDatabasePostgreSQL::createDatabase()
331 createTableIfNotExists("player",
332 "CREATE TABLE player ("
333 "name VARCHAR(60) NOT NULL,"
334 "pitch NUMERIC(15, 7) NOT NULL,"
335 "yaw NUMERIC(15, 7) NOT NULL,"
336 "posX NUMERIC(15, 7) NOT NULL,"
337 "posY NUMERIC(15, 7) NOT NULL,"
338 "posZ NUMERIC(15, 7) NOT NULL,"
340 "breath INT NOT NULL,"
341 "creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
342 "modification_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
347 createTableIfNotExists("player_inventories",
348 "CREATE TABLE player_inventories ("
349 "player VARCHAR(60) NOT NULL,"
350 "inv_id INT NOT NULL,"
351 "inv_width INT NOT NULL,"
352 "inv_name TEXT NOT NULL DEFAULT '',"
353 "inv_size INT NOT NULL,"
354 "PRIMARY KEY(player, inv_id),"
355 "CONSTRAINT player_inventories_fkey FOREIGN KEY (player) REFERENCES "
356 "player (name) ON DELETE CASCADE"
360 createTableIfNotExists("player_inventory_items",
361 "CREATE TABLE player_inventory_items ("
362 "player VARCHAR(60) NOT NULL,"
363 "inv_id INT NOT NULL,"
364 "slot_id INT NOT NULL,"
365 "item TEXT NOT NULL DEFAULT '',"
366 "PRIMARY KEY(player, inv_id, slot_id),"
367 "CONSTRAINT player_inventory_items_fkey FOREIGN KEY (player) REFERENCES "
368 "player (name) ON DELETE CASCADE"
372 createTableIfNotExists("player_metadata",
373 "CREATE TABLE player_metadata ("
374 "player VARCHAR(60) NOT NULL,"
375 "attr VARCHAR(256) NOT NULL,"
377 "PRIMARY KEY(player, attr),"
378 "CONSTRAINT player_metadata_fkey FOREIGN KEY (player) REFERENCES "
379 "player (name) ON DELETE CASCADE"
383 infostream << "PostgreSQL: Player Database was inited." << std::endl;
386 void PlayerDatabasePostgreSQL::initStatements()
388 if (getPGVersion() < 90500) {
389 prepareStatement("create_player",
390 "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
391 "($1, $2, $3, $4, $5, $6, $7::int, $8::int)");
393 prepareStatement("update_player",
394 "UPDATE SET pitch = $2, yaw = $3, posX = $4, posY = $5, posZ = $6, hp = $7::int, "
395 "breath = $8::int, modification_date = NOW() WHERE name = $1");
397 prepareStatement("save_player",
398 "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
399 "($1, $2, $3, $4, $5, $6, $7::int, $8::int)"
400 "ON CONFLICT ON CONSTRAINT player_pkey DO UPDATE SET pitch = $2, yaw = $3, "
401 "posX = $4, posY = $5, posZ = $6, hp = $7::int, breath = $8::int, "
402 "modification_date = NOW()");
405 prepareStatement("remove_player", "DELETE FROM player WHERE name = $1");
407 prepareStatement("load_player_list", "SELECT name FROM player");
409 prepareStatement("remove_player_inventories",
410 "DELETE FROM player_inventories WHERE player = $1");
412 prepareStatement("remove_player_inventory_items",
413 "DELETE FROM player_inventory_items WHERE player = $1");
415 prepareStatement("add_player_inventory",
416 "INSERT INTO player_inventories (player, inv_id, inv_width, inv_name, inv_size) VALUES "
417 "($1, $2::int, $3::int, $4, $5::int)");
419 prepareStatement("add_player_inventory_item",
420 "INSERT INTO player_inventory_items (player, inv_id, slot_id, item) VALUES "
421 "($1, $2::int, $3::int, $4)");
423 prepareStatement("load_player_inventories",
424 "SELECT inv_id, inv_width, inv_name, inv_size FROM player_inventories "
425 "WHERE player = $1 ORDER BY inv_id");
427 prepareStatement("load_player_inventory_items",
428 "SELECT slot_id, item FROM player_inventory_items WHERE "
429 "player = $1 AND inv_id = $2::int");
431 prepareStatement("load_player",
432 "SELECT pitch, yaw, posX, posY, posZ, hp, breath FROM player WHERE name = $1");
434 prepareStatement("remove_player_metadata",
435 "DELETE FROM player_metadata WHERE player = $1");
437 prepareStatement("save_player_metadata",
438 "INSERT INTO player_metadata (player, attr, value) VALUES ($1, $2, $3)");
440 prepareStatement("load_player_metadata",
441 "SELECT attr, value FROM player_metadata WHERE player = $1");
445 bool PlayerDatabasePostgreSQL::playerDataExists(const std::string &playername)
449 const char *values[] = { playername.c_str() };
450 PGresult *results = execPrepared("load_player", 1, values, false);
452 bool res = (PQntuples(results) > 0);
457 void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player)
459 PlayerSAO* sao = player->getPlayerSAO();
465 v3f pos = sao->getBasePosition();
466 std::string pitch = ftos(sao->getLookPitch());
467 std::string yaw = ftos(sao->getRotation().Y);
468 std::string posx = ftos(pos.X);
469 std::string posy = ftos(pos.Y);
470 std::string posz = ftos(pos.Z);
471 std::string hp = itos(sao->getHP());
472 std::string breath = itos(sao->getBreath());
473 const char *values[] = {
477 posx.c_str(), posy.c_str(), posz.c_str(),
482 const char* rmvalues[] = { player->getName() };
485 if (getPGVersion() < 90500) {
486 if (!playerDataExists(player->getName()))
487 execPrepared("create_player", 8, values, true, false);
489 execPrepared("update_player", 8, values, true, false);
492 execPrepared("save_player", 8, values, true, false);
494 // Write player inventories
495 execPrepared("remove_player_inventories", 1, rmvalues);
496 execPrepared("remove_player_inventory_items", 1, rmvalues);
498 const auto &inventory_lists = sao->getInventory()->getLists();
499 std::ostringstream oss;
500 for (u16 i = 0; i < inventory_lists.size(); i++) {
501 const InventoryList* list = inventory_lists[i];
502 const std::string &name = list->getName();
503 std::string width = itos(list->getWidth()),
504 inv_id = itos(i), lsize = itos(list->getSize());
506 const char* inv_values[] = {
513 execPrepared("add_player_inventory", 5, inv_values);
515 for (u32 j = 0; j < list->getSize(); j++) {
518 list->getItem(j).serialize(oss);
519 std::string itemStr = oss.str(), slotId = itos(j);
521 const char* invitem_values[] = {
527 execPrepared("add_player_inventory_item", 4, invitem_values);
531 execPrepared("remove_player_metadata", 1, rmvalues);
532 const StringMap &attrs = sao->getMeta().getStrings();
533 for (const auto &attr : attrs) {
534 const char *meta_values[] = {
539 execPrepared("save_player_metadata", 3, meta_values);
543 player->onSuccessfulSave();
546 bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
551 const char *values[] = { player->getName() };
552 PGresult *results = execPrepared("load_player", 1, values, false, false);
554 // Player not found, return not found
555 if (!PQntuples(results)) {
560 sao->setLookPitch(pg_to_float(results, 0, 0));
561 sao->setRotation(v3f(0, pg_to_float(results, 0, 1), 0));
562 sao->setBasePosition(v3f(
563 pg_to_float(results, 0, 2),
564 pg_to_float(results, 0, 3),
565 pg_to_float(results, 0, 4))
567 sao->setHPRaw((u16) pg_to_int(results, 0, 5));
568 sao->setBreath((u16) pg_to_int(results, 0, 6), false);
573 results = execPrepared("load_player_inventories", 1, values, false, false);
575 int resultCount = PQntuples(results);
577 for (int row = 0; row < resultCount; ++row) {
578 InventoryList* invList = player->inventory.
579 addList(PQgetvalue(results, row, 2), pg_to_uint(results, row, 3));
580 invList->setWidth(pg_to_uint(results, row, 1));
582 u32 invId = pg_to_uint(results, row, 0);
583 std::string invIdStr = itos(invId);
585 const char* values2[] = {
589 PGresult *results2 = execPrepared("load_player_inventory_items", 2,
590 values2, false, false);
592 int resultCount2 = PQntuples(results2);
593 for (int row2 = 0; row2 < resultCount2; row2++) {
594 const std::string itemStr = PQgetvalue(results2, row2, 1);
595 if (itemStr.length() > 0) {
597 stack.deSerialize(itemStr);
598 invList->changeItem(pg_to_uint(results2, row2, 0), stack);
606 results = execPrepared("load_player_metadata", 1, values, false);
608 int numrows = PQntuples(results);
609 for (int row = 0; row < numrows; row++) {
610 sao->getMeta().setString(PQgetvalue(results, row, 0), PQgetvalue(results, row, 1));
612 sao->getMeta().setModified(false);
619 bool PlayerDatabasePostgreSQL::removePlayer(const std::string &name)
621 if (!playerDataExists(name))
626 const char *values[] = { name.c_str() };
627 execPrepared("remove_player", 1, values);
632 void PlayerDatabasePostgreSQL::listPlayers(std::vector<std::string> &res)
636 PGresult *results = execPrepared("load_player_list", 0, NULL, false);
638 int numrows = PQntuples(results);
639 for (int row = 0; row < numrows; row++)
640 res.emplace_back(PQgetvalue(results, row, 0));
645 AuthDatabasePostgreSQL::AuthDatabasePostgreSQL(const std::string &connect_string) :
646 Database_PostgreSQL(connect_string, "_auth"),
652 void AuthDatabasePostgreSQL::createDatabase()
654 createTableIfNotExists("auth",
655 "CREATE TABLE auth ("
659 "last_login INT NOT NULL DEFAULT 0,"
663 createTableIfNotExists("user_privileges",
664 "CREATE TABLE user_privileges ("
667 "PRIMARY KEY (id, privilege),"
668 "CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON DELETE CASCADE"
672 void AuthDatabasePostgreSQL::initStatements()
674 prepareStatement("auth_read", "SELECT id, name, password, last_login FROM auth WHERE name = $1");
675 prepareStatement("auth_write", "UPDATE auth SET name = $1, password = $2, last_login = $3 WHERE id = $4");
676 prepareStatement("auth_create", "INSERT INTO auth (name, password, last_login) VALUES ($1, $2, $3) RETURNING id");
677 prepareStatement("auth_delete", "DELETE FROM auth WHERE name = $1");
679 prepareStatement("auth_list_names", "SELECT name FROM auth ORDER BY name DESC");
681 prepareStatement("auth_read_privs", "SELECT privilege FROM user_privileges WHERE id = $1");
682 prepareStatement("auth_write_privs", "INSERT INTO user_privileges (id, privilege) VALUES ($1, $2)");
683 prepareStatement("auth_delete_privs", "DELETE FROM user_privileges WHERE id = $1");
686 bool AuthDatabasePostgreSQL::getAuth(const std::string &name, AuthEntry &res)
690 const char *values[] = { name.c_str() };
691 PGresult *result = execPrepared("auth_read", 1, values, false, false);
692 int numrows = PQntuples(result);
698 res.id = pg_to_uint(result, 0, 0);
699 res.name = std::string(PQgetvalue(result, 0, 1), PQgetlength(result, 0, 1));
700 res.password = std::string(PQgetvalue(result, 0, 2), PQgetlength(result, 0, 2));
701 res.last_login = pg_to_int(result, 0, 3);
705 std::string playerIdStr = itos(res.id);
706 const char *privsValues[] = { playerIdStr.c_str() };
707 PGresult *results = execPrepared("auth_read_privs", 1, privsValues, false);
709 numrows = PQntuples(results);
710 for (int row = 0; row < numrows; row++)
711 res.privileges.emplace_back(PQgetvalue(results, row, 0));
718 bool AuthDatabasePostgreSQL::saveAuth(const AuthEntry &authEntry)
724 std::string lastLoginStr = itos(authEntry.last_login);
725 std::string idStr = itos(authEntry.id);
726 const char *values[] = {
727 authEntry.name.c_str() ,
728 authEntry.password.c_str(),
729 lastLoginStr.c_str(),
732 execPrepared("auth_write", 4, values);
734 writePrivileges(authEntry);
740 bool AuthDatabasePostgreSQL::createAuth(AuthEntry &authEntry)
744 std::string lastLoginStr = itos(authEntry.last_login);
745 const char *values[] = {
746 authEntry.name.c_str() ,
747 authEntry.password.c_str(),
753 PGresult *result = execPrepared("auth_create", 3, values, false, false);
755 int numrows = PQntuples(result);
757 errorstream << "Strange behaviour on auth creation, no ID returned." << std::endl;
763 authEntry.id = pg_to_uint(result, 0, 0);
766 writePrivileges(authEntry);
772 bool AuthDatabasePostgreSQL::deleteAuth(const std::string &name)
776 const char *values[] = { name.c_str() };
777 execPrepared("auth_delete", 1, values);
779 // privileges deleted by foreign key on delete cascade
783 void AuthDatabasePostgreSQL::listNames(std::vector<std::string> &res)
787 PGresult *results = execPrepared("auth_list_names", 0,
788 NULL, NULL, NULL, false, false);
790 int numrows = PQntuples(results);
792 for (int row = 0; row < numrows; ++row)
793 res.emplace_back(PQgetvalue(results, row, 0));
798 void AuthDatabasePostgreSQL::reload()
803 void AuthDatabasePostgreSQL::writePrivileges(const AuthEntry &authEntry)
805 std::string authIdStr = itos(authEntry.id);
806 const char *values[] = { authIdStr.c_str() };
807 execPrepared("auth_delete_privs", 1, values);
809 for (const std::string &privilege : authEntry.privileges) {
810 const char *values[] = { authIdStr.c_str(), privilege.c_str() };
811 execPrepared("auth_write_privs", 2, values);
816 #endif // USE_POSTGRESQL