Tolerate packet reordering in the early init process
[oweals/minetest.git] / src / database-postgresql.cpp
1 /*
2 Copyright (C) 2016 Loic Blot <loic.blot@unix-experience.fr>
3
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.
8
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.
13
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.
17 */
18
19 #include "config.h"
20
21 #if USE_POSTGRESQL
22
23 #include "database-postgresql.h"
24
25 #ifdef _WIN32
26         #ifndef WIN32_LEAN_AND_MEAN
27                 #define WIN32_LEAN_AND_MEAN
28         #endif
29         // Without this some of the network functions are not found on mingw
30         #ifndef _WIN32_WINNT
31                 #define _WIN32_WINNT 0x0501
32         #endif
33         #include <windows.h>
34         #include <winsock2.h>
35 #else
36 #include <netinet/in.h>
37 #endif
38
39 #include "log.h"
40 #include "exceptions.h"
41 #include "settings.h"
42
43 Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) :
44         m_connect_string(""),
45         m_conn(NULL),
46         m_pgversion(0)
47 {
48         if (!conf.getNoEx("pgsql_connection", m_connect_string)) {
49                 throw SettingNotFoundException(
50                         "Set pgsql_connection string in world.mt to "
51                         "use the postgresql backend\n"
52                         "Notes:\n"
53                         "pgsql_connection has the following form: \n"
54                         "\tpgsql_connection = host=127.0.0.1 port=5432 user=mt_user "
55                         "password=mt_password dbname=minetest_world\n"
56                         "mt_user should have CREATE TABLE, INSERT, SELECT, UPDATE and "
57                         "DELETE rights on the database.\n"
58                         "Don't create mt_user as a SUPERUSER!");
59         }
60
61         connectToDatabase();
62 }
63
64 Database_PostgreSQL::~Database_PostgreSQL()
65 {
66         PQfinish(m_conn);
67 }
68
69 void Database_PostgreSQL::connectToDatabase()
70 {
71         m_conn = PQconnectdb(m_connect_string.c_str());
72
73         if (PQstatus(m_conn) != CONNECTION_OK) {
74                 throw DatabaseException(std::string(
75                         "PostgreSQL database error: ") +
76                         PQerrorMessage(m_conn));
77         }
78
79         m_pgversion = PQserverVersion(m_conn);
80
81         /*
82         * We are using UPSERT feature from PostgreSQL 9.5
83         * to have the better performance,
84         * set the minimum version to 90500
85         */
86         if (m_pgversion < 90500) {
87                 throw DatabaseException("PostgreSQL database error: "
88                         "Server version 9.5 or greater required.");
89         }
90
91         infostream << "PostgreSQL Database: Version " << m_pgversion
92                         << " Connection made." << std::endl;
93
94         createDatabase();
95         initStatements();
96 }
97
98 void Database_PostgreSQL::verifyDatabase()
99 {
100         if (PQstatus(m_conn) == CONNECTION_OK)
101                 return;
102
103         PQreset(m_conn);
104         ping();
105 }
106
107 void Database_PostgreSQL::ping()
108 {
109         if (PQping(m_connect_string.c_str()) != PQPING_OK) {
110                 throw DatabaseException(std::string(
111                         "PostgreSQL database error: ") +
112                         PQerrorMessage(m_conn));
113         }
114 }
115
116 bool Database_PostgreSQL::initialized() const
117 {
118         return (PQstatus(m_conn) == CONNECTION_OK);
119 }
120
121 void Database_PostgreSQL::initStatements()
122 {
123         prepareStatement("read_block",
124                         "SELECT data FROM blocks "
125                         "WHERE posX = $1::int4 AND posY = $2::int4 AND "
126                         "posZ = $3::int4");
127
128         prepareStatement("write_block",
129                         "INSERT INTO blocks (posX, posY, posZ, data) VALUES "
130                         "($1::int4, $2::int4, $3::int4, $4::bytea) "
131                         "ON CONFLICT ON CONSTRAINT blocks_pkey DO "
132                         "UPDATE SET data = $4::bytea");
133
134         prepareStatement("delete_block", "DELETE FROM blocks WHERE "
135                         "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4");
136
137         prepareStatement("list_all_loadable_blocks",
138                         "SELECT posX, posY, posZ FROM blocks");
139 }
140
141 PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
142 {
143         ExecStatusType statusType = PQresultStatus(result);
144
145         switch (statusType) {
146         case PGRES_COMMAND_OK:
147         case PGRES_TUPLES_OK:
148                 break;
149         case PGRES_FATAL_ERROR:
150         default:
151                 throw DatabaseException(
152                         std::string("PostgreSQL database error: ") +
153                         PQresultErrorMessage(result));
154         }
155
156         if (clear)
157                 PQclear(result);
158
159         return result;
160 }
161
162 void Database_PostgreSQL::createDatabase()
163 {
164         PGresult *result = checkResults(PQexec(m_conn,
165                 "SELECT relname FROM pg_class WHERE relname='blocks';"),
166                 false);
167
168         // If table doesn't exist, create it
169         if (!PQntuples(result)) {
170                 static const char* dbcreate_sql = "CREATE TABLE blocks ("
171                         "posX INT NOT NULL,"
172                         "posY INT NOT NULL,"
173                         "posZ INT NOT NULL,"
174                         "data BYTEA,"
175                         "PRIMARY KEY (posX,posY,posZ)"
176                 ");";
177                 checkResults(PQexec(m_conn, dbcreate_sql));
178         }
179
180         PQclear(result);
181
182         infostream << "PostgreSQL: Game Database was inited." << std::endl;
183 }
184
185
186 void Database_PostgreSQL::beginSave()
187 {
188         verifyDatabase();
189         checkResults(PQexec(m_conn, "BEGIN;"));
190 }
191
192 void Database_PostgreSQL::endSave()
193 {
194         checkResults(PQexec(m_conn, "COMMIT;"));
195 }
196
197 bool Database_PostgreSQL::saveBlock(const v3s16 &pos,
198                 const std::string &data)
199 {
200         // Verify if we don't overflow the platform integer with the mapblock size
201         if (data.size() > INT_MAX) {
202                 errorstream << "Database_PostgreSQL::saveBlock: Data truncation! "
203                                 << "data.size() over 0xFFFF (== " << data.size()
204                                 << ")" << std::endl;
205                 return false;
206         }
207
208         verifyDatabase();
209
210         s32 x, y, z;
211         x = htonl(pos.X);
212         y = htonl(pos.Y);
213         z = htonl(pos.Z);
214
215         const void *args[] = { &x, &y, &z, data.c_str() };
216         const int argLen[] = {
217                 sizeof(x), sizeof(y), sizeof(z), (int)data.size()
218         };
219         const int argFmt[] = { 1, 1, 1, 1 };
220
221         execPrepared("write_block", ARRLEN(args), args, argLen, argFmt);
222         return true;
223 }
224
225 void Database_PostgreSQL::loadBlock(const v3s16 &pos,
226                 std::string *block)
227 {
228         verifyDatabase();
229
230         s32 x, y, z;
231         x = htonl(pos.X);
232         y = htonl(pos.Y);
233         z = htonl(pos.Z);
234
235         const void *args[] = { &x, &y, &z };
236         const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
237         const int argFmt[] = { 1, 1, 1 };
238
239         PGresult *results = execPrepared("read_block", ARRLEN(args), args,
240                         argLen, argFmt, false);
241
242         *block = "";
243
244         if (PQntuples(results)) {
245                 *block = std::string(PQgetvalue(results, 0, 0),
246                                 PQgetlength(results, 0, 0));
247         }
248
249         PQclear(results);
250 }
251
252 bool Database_PostgreSQL::deleteBlock(const v3s16 &pos)
253 {
254         verifyDatabase();
255
256         s32 x, y, z;
257         x = htonl(pos.X);
258         y = htonl(pos.Y);
259         z = htonl(pos.Z);
260
261         const void *args[] = { &x, &y, &z };
262         const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
263         const int argFmt[] = { 1, 1, 1 };
264
265         execPrepared("read_block", ARRLEN(args), args, argLen, argFmt);
266
267         return true;
268 }
269
270 void Database_PostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
271 {
272         verifyDatabase();
273
274         PGresult *results = execPrepared("list_all_loadable_blocks", 0,
275                         NULL, NULL, NULL, false, false);
276
277         int numrows = PQntuples(results);
278
279         for (int row = 0; row < numrows; ++row) {
280                 dst.push_back(pg_to_v3s16(results, 0, 0));
281         }
282
283         PQclear(results);
284 }
285
286 #endif // USE_POSTGRESQL