Make getters of the Settings class const
[oweals/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                 errorstream<<"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                 errorstream<<"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                 errorstream<<"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                 errorstream<<"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                 errorstream<<"SQLite3 read statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
121                 throw FileNotGoodException("Cannot prepare read statement");
122         }
123 #ifdef __ANDROID__
124         d = sqlite3_prepare(m_database, "INSERT INTO `blocks` VALUES(?, ?);", -1, &m_database_write, NULL);
125 #else
126         d = sqlite3_prepare(m_database, "REPLACE INTO `blocks` VALUES(?, ?);", -1, &m_database_write, NULL);
127 #endif
128         if(d != SQLITE_OK) {
129                 errorstream<<"SQLite3 write statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
130                 throw FileNotGoodException("Cannot prepare write statement");
131         }
132
133 #ifdef __ANDROID__
134         d = sqlite3_prepare(m_database, "DELETE FROM `blocks` WHERE `pos`=?;", -1, &m_database_delete, NULL);
135         if(d != SQLITE_OK) {
136                 infostream<<"WARNING: SQLite3 database delete statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
137                 throw FileNotGoodException("Cannot prepare delete statement");
138         }
139 #endif
140
141         d = sqlite3_prepare(m_database, "SELECT `pos` FROM `blocks`", -1, &m_database_list, NULL);
142         if(d != SQLITE_OK) {
143                 infostream<<"SQLite3 list statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
144                 throw FileNotGoodException("Cannot prepare read statement");
145         }
146
147         infostream<<"ServerMap: SQLite3 database opened"<<std::endl;
148 }
149
150 bool Database_SQLite3::saveBlock(v3s16 blockpos, std::string &data)
151 {
152         verifyDatabase();
153
154 #ifdef __ANDROID__
155         /**
156          * Note: For some unknown reason sqlite3 fails to REPLACE blocks on android,
157          * deleting them and inserting first works.
158          */
159         if (sqlite3_bind_int64(m_database_read, 1, getBlockAsInteger(blockpos)) != SQLITE_OK) {
160                 infostream << "WARNING: Could not bind block position for load: "
161                         << sqlite3_errmsg(m_database)<<std::endl;
162         }
163
164         if (sqlite3_step(m_database_read) == SQLITE_ROW) {
165                 if (sqlite3_bind_int64(m_database_delete, 1, getBlockAsInteger(blockpos)) != SQLITE_OK) {
166                         infostream << "WARNING: Could not bind block position for delete: "
167                                 << sqlite3_errmsg(m_database)<<std::endl;
168                 }
169
170                 if (sqlite3_step(m_database_delete) != SQLITE_DONE) {
171                         errorstream << "WARNING: saveBlock: Block failed to delete "
172                                 << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
173                         return false;
174                 }
175                 sqlite3_reset(m_database_delete);
176         }
177         sqlite3_reset(m_database_read);
178 #endif
179
180         if (sqlite3_bind_int64(m_database_write, 1, getBlockAsInteger(blockpos)) != SQLITE_OK) {
181                 errorstream << "WARNING: saveBlock: Block position failed to bind: "
182                         << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
183                 sqlite3_reset(m_database_write);
184                 return false;
185         }
186
187         if (sqlite3_bind_blob(m_database_write, 2, (void *) data.c_str(), data.size(), NULL) != SQLITE_OK) {
188                 errorstream << "WARNING: saveBlock: Block data failed to bind: "
189                         << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
190                 sqlite3_reset(m_database_write);
191                 return false;
192         }
193
194         if (sqlite3_step(m_database_write) != SQLITE_DONE) {
195                 errorstream << "WARNING: saveBlock: Block failed to save "
196                         << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
197                 sqlite3_reset(m_database_write);
198                 return false;
199         }
200
201         sqlite3_reset(m_database_write);
202
203         return true;
204 }
205
206 std::string Database_SQLite3::loadBlock(v3s16 blockpos)
207 {
208         verifyDatabase();
209
210         if (sqlite3_bind_int64(m_database_read, 1, getBlockAsInteger(blockpos)) != SQLITE_OK) {
211                 errorstream << "Could not bind block position for load: "
212                         << sqlite3_errmsg(m_database)<<std::endl;
213         }
214
215         if (sqlite3_step(m_database_read) == SQLITE_ROW) {
216                 const char *data = (const char *) sqlite3_column_blob(m_database_read, 0);
217                 size_t len = sqlite3_column_bytes(m_database_read, 0);
218
219                 std::string s = "";
220                 if(data)
221                         s = std::string(data, len);
222
223                 sqlite3_step(m_database_read);
224                 // We should never get more than 1 row, so ok to reset
225                 sqlite3_reset(m_database_read);
226
227                 return s;
228         }
229
230         sqlite3_reset(m_database_read);
231         return "";
232 }
233
234 void Database_SQLite3::createDatabase()
235 {
236         int e;
237         assert(m_database);
238         e = sqlite3_exec(m_database,
239                 "CREATE TABLE IF NOT EXISTS `blocks` ("
240                         "`pos` INT NOT NULL PRIMARY KEY,"
241                         "`data` BLOB"
242                 ");"
243         , NULL, NULL, NULL);
244         if(e != SQLITE_OK)
245                 throw FileNotGoodException("Could not create sqlite3 database structure");
246         else
247                 infostream<<"ServerMap: SQLite3 database structure was created";
248
249 }
250
251 void Database_SQLite3::listAllLoadableBlocks(std::list<v3s16> &dst)
252 {
253         verifyDatabase();
254
255         while(sqlite3_step(m_database_list) == SQLITE_ROW)
256         {
257                 sqlite3_int64 block_i = sqlite3_column_int64(m_database_list, 0);
258                 v3s16 p = getIntegerAsBlock(block_i);
259                 //dstream<<"block_i="<<block_i<<" p="<<PP(p)<<std::endl;
260                 dst.push_back(p);
261         }
262 }
263
264
265 #define FINALIZE_STATEMENT(statement)                                          \
266         if ( statement )                                                           \
267                 rc = sqlite3_finalize(statement);                                      \
268         if ( rc != SQLITE_OK )                                                     \
269                 errorstream << "Database_SQLite3::~Database_SQLite3():"                \
270                         << "Failed to finalize: " << #statement << ": rc=" << rc << std::endl;
271
272 Database_SQLite3::~Database_SQLite3()
273 {
274         int rc = SQLITE_OK;
275
276         FINALIZE_STATEMENT(m_database_read)
277         FINALIZE_STATEMENT(m_database_write)
278         FINALIZE_STATEMENT(m_database_list)
279
280         if(m_database)
281                 rc = sqlite3_close(m_database);
282
283         if (rc != SQLITE_OK) {
284                 errorstream << "Database_SQLite3::~Database_SQLite3(): "
285                                 << "Failed to close database: rc=" << rc << std::endl;
286         }
287 }