2017-01-08 23:24:09 +01:00
|
|
|
#include <stdexcept>
|
|
|
|
#include <iostream>
|
|
|
|
#include <fstream>
|
2017-03-14 13:48:26 +01:00
|
|
|
#include <cstdlib>
|
2017-01-08 23:24:09 +01:00
|
|
|
#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");
|
2018-03-28 17:45:35 +02:00
|
|
|
std::string connect_string = read_setting("pgsql_connection", ifs);
|
2017-01-08 23:24:09 +01:00
|
|
|
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",
|
2020-03-27 16:12:26 +01:00
|
|
|
"SELECT posX::int4, posY::int4, posZ::int4 FROM blocks WHERE"
|
|
|
|
" (posX BETWEEN $1::int4 AND $2::int4) AND"
|
|
|
|
" (posY BETWEEN $3::int4 AND $4::int4) AND"
|
|
|
|
" (posZ BETWEEN $5::int4 AND $6::int4)"
|
2017-01-08 23:24:09 +01:00
|
|
|
);
|
|
|
|
prepareStatement(
|
2020-03-27 16:12:26 +01:00
|
|
|
"get_blocks",
|
|
|
|
"SELECT posY::int4, data FROM blocks WHERE"
|
|
|
|
" posX = $1::int4 AND posZ = $2::int4"
|
|
|
|
" AND (posY BETWEEN $3::int4 AND $4::int4)"
|
2017-01-08 23:24:09 +01:00
|
|
|
);
|
2020-03-27 21:10:00 +01:00
|
|
|
prepareStatement(
|
|
|
|
"get_block_exact",
|
|
|
|
"SELECT data FROM blocks WHERE"
|
|
|
|
" posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4"
|
|
|
|
);
|
2017-01-08 23:24:09 +01:00
|
|
|
|
|
|
|
checkResults(PQexec(db, "START TRANSACTION;"));
|
|
|
|
checkResults(PQexec(db, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;"));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DBPostgreSQL::~DBPostgreSQL()
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
checkResults(PQexec(db, "COMMIT;"));
|
2020-03-27 21:10:00 +01:00
|
|
|
} catch (const std::exception& caught) {
|
2017-01-08 23:24:09 +01:00
|
|
|
std::cerr << "could not finalize: " << caught.what() << std::endl;
|
|
|
|
}
|
|
|
|
PQfinish(db);
|
|
|
|
}
|
|
|
|
|
2020-03-27 21:10:00 +01:00
|
|
|
|
2020-03-27 16:12:26 +01:00
|
|
|
std::vector<BlockPos> DBPostgreSQL::getBlockPos(BlockPos min, BlockPos max)
|
2017-01-08 23:24:09 +01:00
|
|
|
{
|
2020-03-27 16:12:26 +01:00
|
|
|
int32_t const x1 = htonl(min.x);
|
|
|
|
int32_t const x2 = htonl(max.x - 1);
|
|
|
|
int32_t const y1 = htonl(min.y);
|
|
|
|
int32_t const y2 = htonl(max.y - 1);
|
|
|
|
int32_t const z1 = htonl(min.z);
|
|
|
|
int32_t const z2 = htonl(max.z - 1);
|
|
|
|
|
|
|
|
const void *args[] = { &x1, &x2, &y1, &y2, &z1, &z2 };
|
|
|
|
const int argLen[] = { 4, 4, 4, 4, 4, 4 };
|
|
|
|
const int argFmt[] = { 1, 1, 1, 1, 1, 1 };
|
2017-01-08 23:24:09 +01:00
|
|
|
|
|
|
|
PGresult *results = execPrepared(
|
2020-03-27 16:12:26 +01:00
|
|
|
"get_block_pos", ARRLEN(args), args,
|
|
|
|
argLen, argFmt, false
|
2017-01-08 23:24:09 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
int numrows = PQntuples(results);
|
|
|
|
|
2020-03-27 16:12:26 +01:00
|
|
|
std::vector<BlockPos> positions;
|
|
|
|
positions.reserve(numrows);
|
|
|
|
|
2017-01-08 23:24:09 +01:00
|
|
|
for (int row = 0; row < numrows; ++row)
|
2020-03-27 16:12:26 +01:00
|
|
|
positions.emplace_back(pg_to_blockpos(results, row, 0));
|
2017-01-08 23:24:09 +01:00
|
|
|
|
|
|
|
PQclear(results);
|
|
|
|
|
|
|
|
return positions;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-03-27 16:12:26 +01:00
|
|
|
void DBPostgreSQL::getBlocksOnXZ(BlockList &blocks, int16_t xPos, int16_t zPos,
|
|
|
|
int16_t min_y, int16_t max_y)
|
2017-01-08 23:24:09 +01:00
|
|
|
{
|
2020-03-27 16:12:26 +01:00
|
|
|
int32_t const x = htonl(xPos);
|
2017-01-08 23:24:09 +01:00
|
|
|
int32_t const z = htonl(zPos);
|
2020-03-27 16:12:26 +01:00
|
|
|
int32_t const y1 = htonl(min_y);
|
|
|
|
int32_t const y2 = htonl(max_y - 1);
|
2017-01-08 23:24:09 +01:00
|
|
|
|
2020-03-27 16:12:26 +01:00
|
|
|
const void *args[] = { &x, &z, &y1, &y2 };
|
|
|
|
const int argLen[] = { 4, 4, 4, 4 };
|
|
|
|
const int argFmt[] = { 1, 1, 1, 1 };
|
2017-01-08 23:24:09 +01:00
|
|
|
|
|
|
|
PGresult *results = execPrepared(
|
2020-03-27 16:12:26 +01:00
|
|
|
"get_blocks", ARRLEN(args), args,
|
2017-01-08 23:24:09 +01:00
|
|
|
argLen, argFmt, false
|
|
|
|
);
|
|
|
|
|
|
|
|
int numrows = PQntuples(results);
|
|
|
|
|
|
|
|
for (int row = 0; row < numrows; ++row) {
|
|
|
|
BlockPos position;
|
2020-03-27 16:12:26 +01:00
|
|
|
position.x = xPos;
|
|
|
|
position.y = pg_binary_to_int(results, row, 0);
|
2017-01-08 23:24:09 +01:00
|
|
|
position.z = zPos;
|
2020-03-27 16:12:26 +01:00
|
|
|
blocks.emplace_back(
|
2017-01-08 23:24:09 +01:00
|
|
|
position,
|
|
|
|
ustring(
|
|
|
|
reinterpret_cast<unsigned char*>(
|
2020-03-27 16:12:26 +01:00
|
|
|
PQgetvalue(results, row, 1)
|
2017-01-08 23:24:09 +01:00
|
|
|
),
|
2020-03-27 16:12:26 +01:00
|
|
|
PQgetlength(results, row, 1)
|
2017-01-08 23:24:09 +01:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
PQclear(results);
|
|
|
|
}
|
|
|
|
|
2020-03-27 21:10:00 +01:00
|
|
|
|
|
|
|
void DBPostgreSQL::getBlocksByPos(BlockList &blocks,
|
|
|
|
const std::vector<BlockPos> &positions)
|
|
|
|
{
|
|
|
|
int32_t x, y, z;
|
|
|
|
|
|
|
|
const void *args[] = { &x, &y, &z };
|
|
|
|
const int argLen[] = { 4, 4, 4 };
|
|
|
|
const int argFmt[] = { 1, 1, 1 };
|
|
|
|
|
|
|
|
for (auto pos : positions) {
|
|
|
|
x = htonl(pos.x);
|
|
|
|
y = htonl(pos.y);
|
|
|
|
z = htonl(pos.z);
|
|
|
|
|
|
|
|
PGresult *results = execPrepared(
|
|
|
|
"get_block_exact", ARRLEN(args), args,
|
|
|
|
argLen, argFmt, false
|
|
|
|
);
|
|
|
|
|
|
|
|
if (PQntuples(results) > 0) {
|
|
|
|
blocks.emplace_back(
|
|
|
|
pos,
|
|
|
|
ustring(
|
|
|
|
reinterpret_cast<unsigned char*>(
|
|
|
|
PQgetvalue(results, 0, 0)
|
|
|
|
),
|
|
|
|
PQgetlength(results, 0, 0)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
PQclear(results);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-01-08 23:24:09 +01:00
|
|
|
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,
|
2020-03-27 16:12:26 +01:00
|
|
|
bool clear
|
2017-01-08 23:24:09 +01:00
|
|
|
)
|
|
|
|
{
|
|
|
|
return checkResults(PQexecPrepared(db, stmtName, paramsNumber,
|
|
|
|
(const char* const*) params, paramsLengths, paramsFormats,
|
2020-03-27 16:12:26 +01:00
|
|
|
1 /* binary output */), clear
|
2017-01-08 23:24:09 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-03-27 16:12:26 +01:00
|
|
|
result.x = pg_binary_to_int(res, row, col);
|
|
|
|
result.y = pg_binary_to_int(res, row, col + 1);
|
|
|
|
result.z = pg_binary_to_int(res, row, col + 2);
|
2017-01-08 23:24:09 +01:00
|
|
|
return result;
|
|
|
|
}
|