]> git.lizzy.rs Git - minetest.git/blob - src/database-sqlite3.cpp
Make GUITable mouse wheel scrolling faster
[minetest.git] / src / database-sqlite3.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 /*
21         SQLite format specification:
22         - Initially only replaces sectors/ and sectors2/
23
24         If map.sqlite does not exist in the save dir
25         or the block was not found in the database
26         the map will try to load from sectors folder.
27         In either case, map.sqlite will be created
28         and all future saves will save there.
29
30         Structure of map.sqlite:
31         Tables:
32                 blocks
33                         (PK) INT pos
34                         BLOB data
35 */
36
37
38 #include "database-sqlite3.h"
39
40 #include "map.h"
41 #include "mapsector.h"
42 #include "mapblock.h"
43 #include "serialization.h"
44 #include "main.h"
45 #include "settings.h"
46 #include "log.h"
47
48 Database_SQLite3::Database_SQLite3(ServerMap *map, std::string savedir)
49 {
50         m_database = NULL;
51         m_database_read = NULL;
52         m_database_write = NULL;
53         m_database_list = NULL;
54         m_savedir = savedir;
55         srvmap = map;
56 }
57
58 int Database_SQLite3::Initialized(void)
59 {
60         return m_database ? 1 : 0;
61 }
62
63 void Database_SQLite3::beginSave() {
64         verifyDatabase();
65         if(sqlite3_exec(m_database, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK)
66                 infostream<<"WARNING: beginSave() failed, saving might be slow.";
67 }
68
69 void Database_SQLite3::endSave() {
70         verifyDatabase();
71         if(sqlite3_exec(m_database, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK)
72                 infostream<<"WARNING: endSave() failed, map might not have saved.";
73 }
74
75 void Database_SQLite3::createDirs(std::string path)
76 {
77         if(fs::CreateAllDirs(path) == false)
78         {
79                 infostream<<DTIME<<"Database_SQLite3: Failed to create directory "
80                                 <<"\""<<path<<"\""<<std::endl;
81                 throw BaseException("Database_SQLite3 failed to create directory");
82         }
83 }
84
85 void Database_SQLite3::verifyDatabase() {
86         if(m_database)
87                 return;
88
89         std::string dbp = m_savedir + DIR_DELIM "map.sqlite";
90         bool needs_create = false;
91         int d;
92
93         // Open the database connection
94
95         createDirs(m_savedir); // ?
96
97         if(!fs::PathExists(dbp))
98                 needs_create = true;
99
100         d = sqlite3_open_v2(dbp.c_str(), &m_database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
101         if(d != SQLITE_OK) {
102                 infostream<<"WARNING: SQLite3 database failed to open: "<<sqlite3_errmsg(m_database)<<std::endl;
103                 throw FileNotGoodException("Cannot open database file");
104         }
105
106         if(needs_create)
107                 createDatabase();
108
109         std::string querystr = std::string("PRAGMA synchronous = ")
110                          + itos(g_settings->getU16("sqlite_synchronous"));
111         d = sqlite3_exec(m_database, querystr.c_str(), NULL, NULL, NULL);
112         if(d != SQLITE_OK) {
113                 infostream<<"WARNING: Database pragma set failed: "
114                                 <<sqlite3_errmsg(m_database)<<std::endl;
115                 throw FileNotGoodException("Cannot set pragma");
116         }
117
118         d = sqlite3_prepare(m_database, "SELECT `data` FROM `blocks` WHERE `pos`=? LIMIT 1", -1, &m_database_read, NULL);
119         if(d != SQLITE_OK) {
120                 infostream<<"WARNING: SQLite3 database read statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
121                 throw FileNotGoodException("Cannot prepare read statement");
122         }
123
124         d = sqlite3_prepare(m_database, "REPLACE INTO `blocks` VALUES(?, ?)", -1, &m_database_write, NULL);
125         if(d != SQLITE_OK) {
126                 infostream<<"WARNING: SQLite3 database write statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
127                 throw FileNotGoodException("Cannot prepare write statement");
128         }
129
130         d = sqlite3_prepare(m_database, "SELECT `pos` FROM `blocks`", -1, &m_database_list, NULL);
131         if(d != SQLITE_OK) {
132                 infostream<<"WARNING: SQLite3 database list statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
133                 throw FileNotGoodException("Cannot prepare read statement");
134         }
135
136         infostream<<"ServerMap: SQLite3 database opened"<<std::endl;
137 }
138
139 void Database_SQLite3::saveBlock(MapBlock *block)
140 {
141         DSTACK(__FUNCTION_NAME);
142         /*
143                 Dummy blocks are not written
144         */
145         if(block->isDummy())
146         {
147                 /*v3s16 p = block->getPos();
148                 infostream<<"Database_SQLite3::saveBlock(): WARNING: Not writing dummy block "
149                                 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
150                 return;
151         }
152
153         // Format used for writing
154         u8 version = SER_FMT_VER_HIGHEST_WRITE;
155         // Get destination
156         v3s16 p3d = block->getPos();
157
158
159 #if 0
160         v2s16 p2d(p3d.X, p3d.Z);
161         std::string sectordir = getSectorDir(p2d);
162
163         createDirs(sectordir);
164
165         std::string fullpath = sectordir+DIR_DELIM+getBlockFilename(p3d);
166         std::ofstream o(fullpath.c_str(), std::ios_base::binary);
167         if(o.good() == false)
168                 throw FileNotGoodException("Cannot open block data");
169 #endif
170         /*
171                 [0] u8 serialization version
172                 [1] data
173         */
174
175         verifyDatabase();
176
177         std::ostringstream o(std::ios_base::binary);
178
179         o.write((char*)&version, 1);
180
181         // Write basic data
182         block->serialize(o, version, true);
183
184         // Write block to database
185
186         std::string tmp = o.str();
187         const char *bytes = tmp.c_str();
188
189         if(sqlite3_bind_int64(m_database_write, 1, getBlockAsInteger(p3d)) != SQLITE_OK)
190                 infostream<<"WARNING: Block position failed to bind: "<<sqlite3_errmsg(m_database)<<std::endl;
191         if(sqlite3_bind_blob(m_database_write, 2, (void *)bytes, o.tellp(), NULL) != SQLITE_OK) // TODO this mught not be the right length
192                 infostream<<"WARNING: Block data failed to bind: "<<sqlite3_errmsg(m_database)<<std::endl;
193         int written = sqlite3_step(m_database_write);
194         if(written != SQLITE_DONE)
195                 infostream<<"WARNING: Block failed to save ("<<p3d.X<<", "<<p3d.Y<<", "<<p3d.Z<<") "
196                 <<sqlite3_errmsg(m_database)<<std::endl;
197         // Make ready for later reuse
198         sqlite3_reset(m_database_write);
199
200         // We just wrote it to the disk so clear modified flag
201         block->resetModified();
202 }
203
204 MapBlock* Database_SQLite3::loadBlock(v3s16 blockpos)
205 {
206         v2s16 p2d(blockpos.X, blockpos.Z);
207         verifyDatabase();
208
209         if (sqlite3_bind_int64(m_database_read, 1, getBlockAsInteger(blockpos)) != SQLITE_OK) {
210                 infostream << "WARNING: Could not bind block position for load: "
211                         << sqlite3_errmsg(m_database)<<std::endl;
212         }
213
214         if (sqlite3_step(m_database_read) == SQLITE_ROW) {
215                 /*
216                         Make sure sector is loaded
217                 */
218                 MapSector *sector = srvmap->createSector(p2d);
219
220                 /*
221                         Load block
222                 */
223                 const char *data = (const char *)sqlite3_column_blob(m_database_read, 0);
224                 size_t len = sqlite3_column_bytes(m_database_read, 0);
225                 if (data == NULL || len == 0) {
226                         errorstream << "Blank block data in database (data == NULL || len"
227                                 " == 0) (" << blockpos.X << "," << blockpos.Y << ","
228                                 << blockpos.Z << ")" << std::endl;
229
230                         if (g_settings->getBool("ignore_world_load_errors")) {
231                                 errorstream << "Ignoring block load error. Duck and cover! "
232                                         << "(ignore_world_load_errors)" << std::endl;
233                         } else {
234                                 throw SerializationError("Blank block data in database");
235                         }
236                         return NULL;
237                 }
238
239                 std::string datastr(data, len);
240
241                 //srvmap->loadBlock(&datastr, blockpos, sector, false);
242
243                 try {
244                         std::istringstream is(datastr, std::ios_base::binary);
245
246                         u8 version = SER_FMT_VER_INVALID;
247                         is.read((char *)&version, 1);
248
249                         if (is.fail())
250                                 throw SerializationError("ServerMap::loadBlock(): Failed"
251                                          " to read MapBlock version");
252
253                         MapBlock *block = NULL;
254                         bool created_new = false;
255                         block = sector->getBlockNoCreateNoEx(blockpos.Y);
256                         if (block == NULL)
257                         {
258                                 block = sector->createBlankBlockNoInsert(blockpos.Y);
259                                 created_new = true;
260                         }
261
262                         // Read basic data
263                         block->deSerialize(is, version, true);
264
265                         // If it's a new block, insert it to the map
266                         if (created_new)
267                                 sector->insertBlock(block);
268
269                         /*
270                                 Save blocks loaded in old format in new format
271                         */
272                         //if(version < SER_FMT_VER_HIGHEST || save_after_load)
273                         // Only save if asked to; no need to update version
274                         //if(save_after_load)
275                         //      saveBlock(block);
276
277                         // We just loaded it from, so it's up-to-date.
278                         block->resetModified();
279                 }
280                 catch (SerializationError &e)
281                 {
282                         errorstream << "Invalid block data in database"
283                                 << " (" << blockpos.X << "," << blockpos.Y << "," << blockpos.Z << ")"
284                                 << " (SerializationError): " << e.what() << std::endl;
285
286                         // TODO: Block should be marked as invalid in memory so that it is
287                         // not touched but the game can run
288
289                         if (g_settings->getBool("ignore_world_load_errors")) {
290                                 errorstream << "Ignoring block load error. Duck and cover! "
291                                         << "(ignore_world_load_errors)" << std::endl;
292                         } else {
293                                 throw SerializationError("Invalid block data in database");
294                                 //assert(0);
295                         }
296                 }
297
298                 sqlite3_step(m_database_read);
299                 // We should never get more than 1 row, so ok to reset
300                 sqlite3_reset(m_database_read);
301
302                 return srvmap->getBlockNoCreateNoEx(blockpos);  // should not be using this here
303         }
304         sqlite3_reset(m_database_read);
305         return NULL;
306 }
307
308 void Database_SQLite3::createDatabase()
309 {
310         int e;
311         assert(m_database);
312         e = sqlite3_exec(m_database,
313                 "CREATE TABLE IF NOT EXISTS `blocks` ("
314                         "`pos` INT NOT NULL PRIMARY KEY,"
315                         "`data` BLOB"
316                 ");"
317         , NULL, NULL, NULL);
318         if(e == SQLITE_ABORT)
319                 throw FileNotGoodException("Could not create sqlite3 database structure");
320         else
321                 infostream<<"ServerMap: SQLite3 database structure was created";
322
323 }
324
325 void Database_SQLite3::listAllLoadableBlocks(std::list<v3s16> &dst)
326 {
327         verifyDatabase();
328
329         while(sqlite3_step(m_database_list) == SQLITE_ROW)
330         {
331                 sqlite3_int64 block_i = sqlite3_column_int64(m_database_list, 0);
332                 v3s16 p = getIntegerAsBlock(block_i);
333                 //dstream<<"block_i="<<block_i<<" p="<<PP(p)<<std::endl;
334                 dst.push_back(p);
335         }
336 }
337
338
339 #define FINALIZE_STATEMENT(statement)                                          \
340         if ( statement )                                                           \
341                 rc = sqlite3_finalize(statement);                                      \
342         if ( rc != SQLITE_OK )                                                     \
343                 errorstream << "Database_SQLite3::~Database_SQLite3():"                \
344                         << "Failed to finalize: " << #statement << ": rc=" << rc << std::endl;
345
346 Database_SQLite3::~Database_SQLite3()
347 {
348         int rc = SQLITE_OK;
349
350         FINALIZE_STATEMENT(m_database_read)
351         FINALIZE_STATEMENT(m_database_write)
352         FINALIZE_STATEMENT(m_database_list)
353
354         if(m_database)
355                 rc = sqlite3_close(m_database);
356
357         if (rc != SQLITE_OK) {
358                 errorstream << "Database_SQLite3::~Database_SQLite3(): "
359                                 << "Failed to close database: rc=" << rc << std::endl;
360         }
361 }
362