Skip to content

Commit

Permalink
PostgreSQL database support
Browse files Browse the repository at this point in the history
  • Loading branch information
zeuner authored and sfan5 committed Jan 8, 2017
1 parent d490cf0 commit af502f3
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 0 deletions.
38 changes: 38 additions & 0 deletions CMakeLists.txt
Expand Up @@ -78,6 +78,39 @@ if(NOT SQLITE3_LIBRARY OR NOT SQLITE3_INCLUDE_DIR)
message(FATAL_ERROR "sqlite3 not found!")
endif(NOT SQLITE3_LIBRARY OR NOT SQLITE3_INCLUDE_DIR)

# Libraries: postgresql

option(ENABLE_POSTGRESQL "Enable PostgreSQL backend" TRUE)
set(USE_POSTGRESQL 0)

if(ENABLE_POSTGRESQL)
find_program(POSTGRESQL_CONFIG_EXECUTABLE pg_config DOC "pg_config")
find_library(POSTGRESQL_LIBRARY pq)
if(POSTGRESQL_CONFIG_EXECUTABLE)
execute_process(COMMAND ${POSTGRESQL_CONFIG_EXECUTABLE} --includedir-server
OUTPUT_VARIABLE POSTGRESQL_SERVER_INCLUDE_DIRS
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${POSTGRESQL_CONFIG_EXECUTABLE}
OUTPUT_VARIABLE POSTGRESQL_CLIENT_INCLUDE_DIRS
OUTPUT_STRIP_TRAILING_WHITESPACE)
# This variable is case sensitive for the cmake PostgreSQL module
set(PostgreSQL_ADDITIONAL_SEARCH_PATHS ${POSTGRESQL_SERVER_INCLUDE_DIRS} ${POSTGRESQL_CLIENT_INCLUDE_DIRS})
endif()

find_package("PostgreSQL")

if(POSTGRESQL_FOUND)
set(USE_POSTGRESQL 1)
message(STATUS "PostgreSQL backend enabled")
# This variable is case sensitive, don't try to change it to POSTGRESQL_INCLUDE_DIR
message(STATUS "PostgreSQL includes: ${PostgreSQL_INCLUDE_DIR}")
include_directories(${PostgreSQL_INCLUDE_DIR})
set(POSTGRESQL_LIBRARY ${PostgreSQL_LIBRARIES})
else()
message(STATUS "PostgreSQL not found!")
endif()
endif(ENABLE_POSTGRESQL)

# Libraries: leveldb

set(USE_LEVELDB 0)
Expand Down Expand Up @@ -148,6 +181,10 @@ set(mapper_SRCS
db-sqlite3.cpp
)

if(USE_POSTGRESQL)
set(mapper_SRCS ${mapper_SRCS} db-postgresql.cpp)
endif(USE_POSTGRESQL)

if(USE_LEVELDB)
set(mapper_SRCS ${mapper_SRCS} db-leveldb.cpp)
endif(USE_LEVELDB)
Expand All @@ -163,6 +200,7 @@ add_executable(minetestmapper
target_link_libraries(
minetestmapper
${SQLITE3_LIBRARY}
${POSTGRESQL_LIBRARY}
${LEVELDB_LIBRARY}
${REDIS_LIBRARY}
${LIBGD_LIBRARY}
Expand Down
7 changes: 7 additions & 0 deletions TileGenerator.cpp
Expand Up @@ -13,6 +13,9 @@
#include "ZlibDecompressor.h"
#include "util.h"
#include "db-sqlite3.h"
#if USE_POSTGRESQL
#include "db-postgresql.h"
#endif
#if USE_LEVELDB
#include "db-leveldb.h"
#endif
Expand Down Expand Up @@ -283,6 +286,10 @@ void TileGenerator::openDb(const std::string &input)

if(backend == "sqlite3")
m_db = new DBSQLite3(input);
#if USE_POSTGRESQL
else if(backend == "postgresql")
m_db = new DBPostgreSQL(input);
#endif
#if USE_LEVELDB
else if(backend == "leveldb")
m_db = new DBLevelDB(input);
Expand Down
1 change: 1 addition & 0 deletions cmake_config.h.in
Expand Up @@ -3,6 +3,7 @@
#ifndef CMAKE_CONFIG_H
#define CMAKE_CONFIG_H

#define USE_POSTGRESQL @USE_POSTGRESQL@
#define USE_LEVELDB @USE_LEVELDB@
#define USE_REDIS @USE_REDIS@

Expand Down
1 change: 1 addition & 0 deletions config.h
Expand Up @@ -9,6 +9,7 @@
#ifdef USE_CMAKE_CONFIG_H
#include "cmake_config.h"
#else
#define USE_POSTGRESQL 0
#define USE_LEVELDB 0
#define USE_REDIS 0

Expand Down
167 changes: 167 additions & 0 deletions db-postgresql.cpp
@@ -0,0 +1,167 @@
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <arpa/inet.h>
#include "db-postgresql.h"
#include "util.h"
#include "types.h"

#define ARRLEN(x) (sizeof(x) / sizeof((x)[0]))

DBPostgreSQL::DBPostgreSQL(const std::string &mapdir)
{
std::ifstream ifs((mapdir + "/world.mt").c_str());
if(!ifs.good())
throw std::runtime_error("Failed to read world.mt");
std::string const connect_string = get_setting("pgsql_connection", ifs);
ifs.close();
db = PQconnectdb(connect_string.c_str());

if (PQstatus(db) != CONNECTION_OK) {
throw std::runtime_error(std::string(
"PostgreSQL database error: ") +
PQerrorMessage(db)
);
}

prepareStatement(
"get_block_pos",
"SELECT posX, posY, posZ FROM blocks"
);
prepareStatement(
"get_blocks_z",
"SELECT posX, posY, data FROM blocks WHERE posZ = $1::int4"
);

checkResults(PQexec(db, "START TRANSACTION;"));
checkResults(PQexec(db, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;"));
}


DBPostgreSQL::~DBPostgreSQL()
{
try {
checkResults(PQexec(db, "COMMIT;"));
} catch (std::exception& caught) {
std::cerr << "could not finalize: " << caught.what() << std::endl;
}
PQfinish(db);
}

std::vector<BlockPos> DBPostgreSQL::getBlockPos()
{
std::vector<BlockPos> positions;

PGresult *results = execPrepared(
"get_block_pos", 0,
NULL, NULL, NULL, false, false
);

int numrows = PQntuples(results);

for (int row = 0; row < numrows; ++row)
positions.push_back(pg_to_blockpos(results, row, 0));

PQclear(results);

return positions;
}


void DBPostgreSQL::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos)
{
int32_t const z = htonl(zPos);

const void *args[] = { &z };
const int argLen[] = { sizeof(z) };
const int argFmt[] = { 1 };

PGresult *results = execPrepared(
"get_blocks_z", ARRLEN(args), args,
argLen, argFmt, false
);

int numrows = PQntuples(results);

for (int row = 0; row < numrows; ++row) {
BlockPos position;
position.x = pg_binary_to_int(results, row, 0);
position.y = pg_binary_to_int(results, row, 1);
position.z = zPos;
Block const b(
position,
ustring(
reinterpret_cast<unsigned char*>(
PQgetvalue(results, row, 2)
),
PQgetlength(results, row, 2)
)
);
blocks[position.x].push_back(b);
}

PQclear(results);
}

PGresult *DBPostgreSQL::checkResults(PGresult *res, bool clear)
{
ExecStatusType statusType = PQresultStatus(res);

switch (statusType) {
case PGRES_COMMAND_OK:
case PGRES_TUPLES_OK:
break;
case PGRES_FATAL_ERROR:
throw std::runtime_error(
std::string("PostgreSQL database error: ") +
PQresultErrorMessage(res)
);
default:
throw std::runtime_error(
"Unhandled PostgreSQL result code"
);
}

if (clear)
PQclear(res);

return res;
}

void DBPostgreSQL::prepareStatement(const std::string &name, const std::string &sql)
{
checkResults(PQprepare(db, name.c_str(), sql.c_str(), 0, NULL));
}

PGresult *DBPostgreSQL::execPrepared(
const char *stmtName, const int paramsNumber,
const void **params,
const int *paramsLengths, const int *paramsFormats,
bool clear, bool nobinary
)
{
return checkResults(PQexecPrepared(db, stmtName, paramsNumber,
(const char* const*) params, paramsLengths, paramsFormats,
nobinary ? 1 : 0), clear
);
}

int DBPostgreSQL::pg_to_int(PGresult *res, int row, int col)
{
return atoi(PQgetvalue(res, row, col));
}

int DBPostgreSQL::pg_binary_to_int(PGresult *res, int row, int col)
{
int32_t* raw = reinterpret_cast<int32_t*>(PQgetvalue(res, row, col));
return ntohl(*raw);
}

BlockPos DBPostgreSQL::pg_to_blockpos(PGresult *res, int row, int col)
{
BlockPos result;
result.x = pg_to_int(res, row, col);
result.y = pg_to_int(res, row, col + 1);
result.z = pg_to_int(res, row, col + 2);
return result;
}
29 changes: 29 additions & 0 deletions db-postgresql.h
@@ -0,0 +1,29 @@
#ifndef _DB_POSTGRESQL_H
#define _DB_POSTGRESQL_H

#include "db.h"
#include <libpq-fe.h>

class DBPostgreSQL : public DB {
public:
DBPostgreSQL(const std::string &mapdir);
virtual std::vector<BlockPos> getBlockPos();
virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos);
virtual ~DBPostgreSQL();
protected:
PGresult *checkResults(PGresult *res, bool clear = true);
void prepareStatement(const std::string &name, const std::string &sql);
PGresult *execPrepared(
const char *stmtName, const int paramsNumber,
const void **params,
const int *paramsLengths = NULL, const int *paramsFormats = NULL,
bool clear = true, bool nobinary = true
);
int pg_to_int(PGresult *res, int row, int col);
int pg_binary_to_int(PGresult *res, int row, int col);
BlockPos pg_to_blockpos(PGresult *res, int row, int col);
private:
PGconn *db;
};

#endif // _DB_POSTGRESQL_H

0 comments on commit af502f3

Please sign in to comment.