Move MapBlock (de)serializing code out of Database class
[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
124         d = sqlite3_prepare(m_database, "REPLACE INTO `blocks` VALUES(?, ?)", -1, &m_database_write, NULL);
125         if(d != SQLITE_OK) {
126                 errorstream<<"SQLite3 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<<"SQLite3 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 bool Database_SQLite3::saveBlock(v3s16 blockpos, std::string &data)
140 {
141         verifyDatabase();
142
143         if (sqlite3_bind_int64(m_database_write, 1, getBlockAsInteger(blockpos)) != SQLITE_OK) {
144                 errorstream << "WARNING: saveBlock: Block position failed to bind: "
145                         << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
146                 sqlite3_reset(m_database_write);
147                 return false;
148         }
149
150         if (sqlite3_bind_blob(m_database_write, 2, (void *) data.c_str(), data.size(), NULL) != SQLITE_OK) {
151                 errorstream << "WARNING: saveBlock: Block data failed to bind: "
152                         << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
153                 sqlite3_reset(m_database_write);
154                 return false;
155         }
156
157         if (sqlite3_step(m_database_write) != SQLITE_DONE) {
158                 errorstream << "WARNING: saveBlock: Block failed to save "
159                         << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
160                 sqlite3_reset(m_database_write);
161                 return false;
162         }
163
164         sqlite3_reset(m_database_write);
165         return true;
166 }
167
168 std::string Database_SQLite3::loadBlock(v3s16 blockpos)
169 {
170         verifyDatabase();
171
172         if (sqlite3_bind_int64(m_database_read, 1, getBlockAsInteger(blockpos)) != SQLITE_OK) {
173                 errorstream << "Could not bind block position for load: "
174                         << sqlite3_errmsg(m_database)<<std::endl;
175         }
176
177         if (sqlite3_step(m_database_read) == SQLITE_ROW) {
178                 const char *data = (const char *) sqlite3_column_blob(m_database_read, 0);
179                 size_t len = sqlite3_column_bytes(m_database_read, 0);
180
181                 std::string s = "";
182                 if(data)
183                         s = std::string(data, len);
184
185                 sqlite3_step(m_database_read);
186                 // We should never get more than 1 row, so ok to reset
187                 sqlite3_reset(m_database_read);
188
189                 return s;
190         }
191
192         sqlite3_reset(m_database_read);
193         return "";
194 }
195
196 void Database_SQLite3::createDatabase()
197 {
198         int e;
199         assert(m_database);
200         e = sqlite3_exec(m_database,
201                 "CREATE TABLE IF NOT EXISTS `blocks` ("
202                         "`pos` INT NOT NULL PRIMARY KEY,"
203                         "`data` BLOB"
204                 ");"
205         , NULL, NULL, NULL);
206         if(e == SQLITE_ABORT)
207                 throw FileNotGoodException("Could not create sqlite3 database structure");
208         else
209                 infostream<<"ServerMap: SQLite3 database structure was created";
210
211 }
212
213 void Database_SQLite3::listAllLoadableBlocks(std::list<v3s16> &dst)
214 {
215         verifyDatabase();
216
217         while(sqlite3_step(m_database_list) == SQLITE_ROW)
218         {
219                 sqlite3_int64 block_i = sqlite3_column_int64(m_database_list, 0);
220                 v3s16 p = getIntegerAsBlock(block_i);
221                 //dstream<<"block_i="<<block_i<<" p="<<PP(p)<<std::endl;
222                 dst.push_back(p);
223         }
224 }
225
226
227 #define FINALIZE_STATEMENT(statement)                                          \
228         if ( statement )                                                           \
229                 rc = sqlite3_finalize(statement);                                      \
230         if ( rc != SQLITE_OK )                                                     \
231                 errorstream << "Database_SQLite3::~Database_SQLite3():"                \
232                         << "Failed to finalize: " << #statement << ": rc=" << rc << std::endl;
233
234 Database_SQLite3::~Database_SQLite3()
235 {
236         int rc = SQLITE_OK;
237
238         FINALIZE_STATEMENT(m_database_read)
239         FINALIZE_STATEMENT(m_database_write)
240         FINALIZE_STATEMENT(m_database_list)
241
242         if(m_database)
243                 rc = sqlite3_close(m_database);
244
245         if (rc != SQLITE_OK) {
246                 errorstream << "Database_SQLite3::~Database_SQLite3(): "
247                                 << "Failed to close database: rc=" << rc << std::endl;
248         }
249 }