minetest-mapper-cpp/Minetestmapper/db-postgresql.cpp

177 lines
6.0 KiB
C++

#if USE_POSTGRESQL
#include "db-postgresql.h"
#include <stdexcept>
#include <unistd.h> // for usleep
#include <arpa/inet.h>
#include "Settings.h"
#include "types.h"
#define BLOCKPOSLIST_QUERY_COMPAT "SELECT x, y, z FROM blocks"
#define BLOCKPOSLISTBOUNDED_QUERY_COMPAT "SELECT x, y, z FROM blocks WHERE x BETWEEN $1 AND $2 AND y BETWEEN $3 AND $4 AND z BETWEEN $5 AND $6"
#define BLOCK_QUERY_COMPAT "SELECT data FROM blocks WHERE x = $1 AND y = $2 AND z = $3"
#define BLOCKPOSLIST_QUERY "SELECT posX, posY, posZ FROM blocks"
#define BLOCKPOSLISTBOUNDED_QUERY "SELECT posX, posY, posZ FROM blocks WHERE posX BETWEEN $1 AND $2 AND posY BETWEEN $3 AND $4 AND posZ BETWEEN $5 AND $6"
#define BLOCK_QUERY "SELECT data FROM blocks WHERE posX = $1 AND posY = $2 AND posZ = $3"
// From pg_type.h
#define PG_INT4OID 23
DBPostgreSQL::DBPostgreSQL(const std::string &mapdir) :
m_blocksQueriedCount(0),
m_blocksReadCount(0)
{
Settings world_mt(mapdir + "/world.mt");
std::string connection_info;
bool info_found = false;
bool compat_mode = false;
// Official postgresql connection info string
info_found = world_mt.check("pgsql_connection", connection_info);
compat_mode = !info_found;
// ShadowNinja's implementation (historical)
if (!info_found)
info_found = world_mt.check("postgresql_connection_info", connection_info);
// johnnyjoy's implementation (historical)
if (!info_found)
info_found = world_mt.check("pg_connection_info", connection_info);
if (!info_found)
throw std::runtime_error("Set pgsql_connection in world.mt to use the postgresql backend");
connection_info += "fallback_application_name=minetestmapper " + connection_info;
m_connection = PQconnectdb(connection_info.c_str());
if (PQstatus(m_connection) != CONNECTION_OK) {
throw std::runtime_error(std::string("Failed to connect to postgresql database: ")
+ PQerrorMessage(m_connection));
}
const char *blockposlist_query = BLOCKPOSLIST_QUERY;
const char *blockposlistbounded_query = BLOCKPOSLISTBOUNDED_QUERY;
const char *block_query = BLOCK_QUERY;
if (compat_mode) {
blockposlist_query = BLOCKPOSLIST_QUERY_COMPAT;
blockposlistbounded_query = BLOCKPOSLISTBOUNDED_QUERY_COMPAT;
block_query = BLOCK_QUERY_COMPAT;
}
PGresult *result;
result = PQprepare(m_connection, "GetBlockPosList", blockposlist_query, 0, NULL);
if (!result || PQresultStatus(result) != PGRES_COMMAND_OK)
throw std::runtime_error(std::string("Failed to prepare PostgreSQL statement (GetBlockPosList): ")
+ (result ? PQresultErrorMessage(result) : "(result was NULL)"));
PQclear(result);
result = PQprepare(m_connection, "GetBlockPosListBounded", blockposlistbounded_query, 0, NULL);
if (!result || PQresultStatus(result) != PGRES_COMMAND_OK)
throw std::runtime_error(std::string("Failed to prepare PostgreSQL statement (GetBlockPosListBounded): ")
+ (result ? PQresultErrorMessage(result) : "(result was NULL)"));
PQclear(result);
result = PQprepare(m_connection, "GetBlock", block_query, 0, NULL);
if (!result || PQresultStatus(result) != PGRES_COMMAND_OK)
throw std::runtime_error(std::string("Failed to prepare PostgreSQL statement (GetBlock): ")
+ (result ? PQresultErrorMessage(result) : "(result was NULL)"));
PQclear(result);
for (int i = 0; i < POSTGRESQL_MAXPARAMS; i++) {
m_getBlockParamList[i] = reinterpret_cast<char const *>(m_getBlockParams + i);
m_getBlockParamLengths[i] = sizeof(int32_t);
m_getBlockParamFormats[i] = 1;
}
}
DBPostgreSQL::~DBPostgreSQL()
{
PQfinish(m_connection);
}
int DBPostgreSQL::getBlocksReadCount(void)
{
return m_blocksReadCount;
}
int DBPostgreSQL::getBlocksQueriedCount(void)
{
return m_blocksQueriedCount;
}
const DB::BlockPosList &DBPostgreSQL::getBlockPosList()
{
PGresult *result = PQexecPrepared(m_connection, "GetBlockPosList", 0, NULL, NULL, NULL, 1);
return processBlockPosListQueryResult(result);
}
const DB::BlockPosList &DBPostgreSQL::getBlockPosList(BlockPos minPos, BlockPos maxPos)
{
for (int i = 0; i < 3; i++) {
m_getBlockParams[2*i] = htonl(minPos.dimension[i]);
m_getBlockParams[2*i+1] = htonl(maxPos.dimension[i]);
}
PGresult *result = PQexecPrepared(m_connection, "GetBlockPosListBounded", 6, m_getBlockParamList, m_getBlockParamLengths, m_getBlockParamFormats, 1);
return processBlockPosListQueryResult(result);
}
const DB::BlockPosList &DBPostgreSQL::processBlockPosListQueryResult(PGresult *result) {
m_blockPosList.clear();
if (!result || PQresultStatus(result) != PGRES_TUPLES_OK)
throw std::runtime_error(std::string("Failed to read block-pos list from database: ")
+ (result ? PQresultErrorMessage(result) : "(result was NULL)"));
int rows = PQntuples(result);
// Make sure that we got the right data types
if (rows &&
( PQftype(result, 0) != PG_INT4OID
|| PQftype(result, 1) != PG_INT4OID
|| PQftype(result, 2) != PG_INT4OID)) {
throw std::runtime_error(std::string("Unexpected data type of block coordinate in database query result."));
}
for (int i = 0; i < rows; i++) {
int32_t x = ntohl(*reinterpret_cast<uint32_t *>(PQgetvalue(result, i, 0)));
int32_t y = ntohl(*reinterpret_cast<uint32_t *>(PQgetvalue(result, i, 1)));
int32_t z = ntohl(*reinterpret_cast<uint32_t *>(PQgetvalue(result, i, 2)));
m_blockPosList.push_back(BlockPos(x, y, z, BlockPos::XYZ));
}
PQclear(result);
return m_blockPosList;
}
DB::Block DBPostgreSQL::getBlockOnPos(const BlockPos &pos)
{
Block block(pos,reinterpret_cast<const unsigned char *>(""));
m_blocksQueriedCount++;
for (int i = 0; i < 3; i++) {
m_getBlockParams[i] = htonl(pos.dimension[i]);
}
PGresult *result = PQexecPrepared(m_connection, "GetBlock", 3, m_getBlockParamList, m_getBlockParamLengths, m_getBlockParamFormats, 1);
if (!result || PQresultStatus(result) != PGRES_TUPLES_OK)
throw std::runtime_error(std::string("Failed to read block from database: ")
+ (result ? PQresultErrorMessage(result) : "(result was NULL)"));
if (PQntuples(result) != 0) {
block = Block(pos, ustring(reinterpret_cast<unsigned char *>(PQgetvalue(result, 0, 0)), PQgetlength(result, 0, 0)));
m_blocksReadCount++;
}
PQclear(result);
return block;
}
#endif // USE_POSTGRESQL