From af502f3ac2e04c111c34d9091bcd53bdb0c86bea Mon Sep 17 00:00:00 2001 From: zeuner Date: Sun, 8 Jan 2017 23:24:09 +0100 Subject: [PATCH] PostgreSQL database support --- CMakeLists.txt | 38 +++++++++++ TileGenerator.cpp | 7 ++ cmake_config.h.in | 1 + config.h | 1 + db-postgresql.cpp | 167 ++++++++++++++++++++++++++++++++++++++++++++++ db-postgresql.h | 29 ++++++++ 6 files changed, 243 insertions(+) create mode 100644 db-postgresql.cpp create mode 100644 db-postgresql.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cbd462..f473e41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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) @@ -163,6 +200,7 @@ add_executable(minetestmapper target_link_libraries( minetestmapper ${SQLITE3_LIBRARY} + ${POSTGRESQL_LIBRARY} ${LEVELDB_LIBRARY} ${REDIS_LIBRARY} ${LIBGD_LIBRARY} diff --git a/TileGenerator.cpp b/TileGenerator.cpp index f5d9574..b0577ca 100644 --- a/TileGenerator.cpp +++ b/TileGenerator.cpp @@ -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 @@ -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); diff --git a/cmake_config.h.in b/cmake_config.h.in index 2711a45..3b74bb4 100644 --- a/cmake_config.h.in +++ b/cmake_config.h.in @@ -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@ diff --git a/config.h b/config.h index cc09a29..a54018d 100644 --- a/config.h +++ b/config.h @@ -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 diff --git a/db-postgresql.cpp b/db-postgresql.cpp new file mode 100644 index 0000000..834d1bc --- /dev/null +++ b/db-postgresql.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#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 DBPostgreSQL::getBlockPos() +{ + std::vector 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 &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( + 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(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; +} diff --git a/db-postgresql.h b/db-postgresql.h new file mode 100644 index 0000000..8692694 --- /dev/null +++ b/db-postgresql.h @@ -0,0 +1,29 @@ +#ifndef _DB_POSTGRESQL_H +#define _DB_POSTGRESQL_H + +#include "db.h" +#include + +class DBPostgreSQL : public DB { +public: + DBPostgreSQL(const std::string &mapdir); + virtual std::vector getBlockPos(); + virtual void getBlocksOnZ(std::map &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