*/
/*
- SQLite format specification:
- - Initially only replaces sectors/ and sectors2/
-
- If map.sqlite does not exist in the save dir
- or the block was not found in the database
- the map will try to load from sectors folder.
- In either case, map.sqlite will be created
- and all future saves will save there.
-
- Structure of map.sqlite:
- Tables:
- blocks
- (PK) INT pos
- BLOB data
+SQLite format specification:
+ blocks:
+ (PK) INT id
+ BLOB data
*/
#include "database-sqlite3.h"
-#include "map.h"
-#include "mapsector.h"
-#include "mapblock.h"
-#include "serialization.h"
+#include "log.h"
+#include "filesys.h"
+#include "exceptions.h"
#include "main.h"
#include "settings.h"
-#include "log.h"
+#include "util/string.h"
+
+#include <cassert>
+
+
+#define SQLRES(s, r) \
+ if ((s) != (r)) { \
+ throw FileNotGoodException(std::string(\
+ "SQLite3 database error (" \
+ __FILE__ ":" TOSTRING(__LINE__) \
+ "): ") +\
+ sqlite3_errmsg(m_database)); \
+ }
+#define SQLOK(s) SQLRES(s, SQLITE_OK)
+
+#define PREPARE_STATEMENT(name, query) \
+ SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL))
+
+#define FINALIZE_STATEMENT(statement) \
+ if (sqlite3_finalize(statement) != SQLITE_OK) { \
+ throw FileNotGoodException(std::string( \
+ "SQLite3: Failed to finalize " #statement ": ") + \
+ sqlite3_errmsg(m_database)); \
+ }
-Database_SQLite3::Database_SQLite3(ServerMap *map, std::string savedir)
-{
- m_database = NULL;
- m_database_read = NULL;
- m_database_write = NULL;
- m_database_list = NULL;
- m_savedir = savedir;
- srvmap = map;
-}
-int Database_SQLite3::Initialized(void)
+Database_SQLite3::Database_SQLite3(const std::string &savedir) :
+ m_initialized(false),
+ m_savedir(savedir),
+ m_database(NULL),
+ m_stmt_read(NULL),
+ m_stmt_write(NULL),
+ m_stmt_list(NULL),
+ m_stmt_delete(NULL)
{
- return m_database ? 1 : 0;
}
void Database_SQLite3::beginSave() {
verifyDatabase();
- if(sqlite3_exec(m_database, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK)
- infostream<<"WARNING: beginSave() failed, saving might be slow.";
+ SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE);
+ sqlite3_reset(m_stmt_begin);
}
void Database_SQLite3::endSave() {
verifyDatabase();
- if(sqlite3_exec(m_database, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK)
- infostream<<"WARNING: endSave() failed, map might not have saved.";
+ SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE);
+ sqlite3_reset(m_stmt_end);
}
-void Database_SQLite3::createDirs(std::string path)
+void Database_SQLite3::openDatabase()
{
- if(fs::CreateAllDirs(path) == false)
- {
- infostream<<DTIME<<"Database_SQLite3: Failed to create directory "
- <<"\""<<path<<"\""<<std::endl;
- throw BaseException("Database_SQLite3 failed to create directory");
+ if (m_database) return;
+
+ std::string dbp = m_savedir + DIR_DELIM + "map.sqlite";
+
+ // Open the database connection
+
+ if (!fs::CreateAllDirs(m_savedir)) {
+ infostream << "Database_SQLite3: Failed to create directory \""
+ << m_savedir << "\"" << std::endl;
+ throw FileNotGoodException("Failed to create database "
+ "save directory");
}
-}
-void Database_SQLite3::verifyDatabase() {
- if(m_database)
- return;
-
- {
- std::string dbp = m_savedir + DIR_DELIM + "map.sqlite";
- bool needs_create = false;
- int d;
-
- /*
- Open the database connection
- */
-
- createDirs(m_savedir); // ?
-
- if(!fs::PathExists(dbp))
- needs_create = true;
-
- d = sqlite3_open_v2(dbp.c_str(), &m_database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
- if(d != SQLITE_OK) {
- infostream<<"WARNING: SQLite3 database failed to open: "<<sqlite3_errmsg(m_database)<<std::endl;
- throw FileNotGoodException("Cannot open database file");
- }
-
- if(needs_create)
- createDatabase();
-
- std::string querystr = std::string("PRAGMA synchronous = ")
- + itos(g_settings->getU16("sqlite_synchronous"));
- d = sqlite3_exec(m_database, querystr.c_str(), NULL, NULL, NULL);
- if(d != SQLITE_OK) {
- infostream<<"WARNING: Database pragma set failed: "
- <<sqlite3_errmsg(m_database)<<std::endl;
- throw FileNotGoodException("Cannot set pragma");
- }
-
- d = sqlite3_prepare(m_database, "SELECT `data` FROM `blocks` WHERE `pos`=? LIMIT 1", -1, &m_database_read, NULL);
- if(d != SQLITE_OK) {
- infostream<<"WARNING: SQLite3 database read statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
- throw FileNotGoodException("Cannot prepare read statement");
- }
-
- d = sqlite3_prepare(m_database, "REPLACE INTO `blocks` VALUES(?, ?)", -1, &m_database_write, NULL);
- if(d != SQLITE_OK) {
- infostream<<"WARNING: SQLite3 database write statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
- throw FileNotGoodException("Cannot prepare write statement");
- }
-
- d = sqlite3_prepare(m_database, "SELECT `pos` FROM `blocks`", -1, &m_database_list, NULL);
- if(d != SQLITE_OK) {
- infostream<<"WARNING: SQLite3 database list statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
- throw FileNotGoodException("Cannot prepare read statement");
- }
-
- infostream<<"ServerMap: SQLite3 database opened"<<std::endl;
+ bool needs_create = !fs::PathExists(dbp);
+
+ if (sqlite3_open_v2(dbp.c_str(), &m_database,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+ NULL) != SQLITE_OK) {
+ errorstream << "SQLite3 database failed to open: "
+ << sqlite3_errmsg(m_database) << std::endl;
+ throw FileNotGoodException("Cannot open database file");
}
+
+ if (needs_create) {
+ createDatabase();
+ }
+
+ std::string query_str = std::string("PRAGMA synchronous = ")
+ + itos(g_settings->getU16("sqlite_synchronous"));
+ SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL));
}
-void Database_SQLite3::saveBlock(MapBlock *block)
+void Database_SQLite3::verifyDatabase()
{
- DSTACK(__FUNCTION_NAME);
- /*
- Dummy blocks are not written
- */
- if(block->isDummy())
- {
- /*v3s16 p = block->getPos();
- infostream<<"Database_SQLite3::saveBlock(): WARNING: Not writing dummy block "
- <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
- return;
- }
+ if (m_initialized) return;
- // Format used for writing
- u8 version = SER_FMT_VER_HIGHEST_WRITE;
- // Get destination
- v3s16 p3d = block->getPos();
+ openDatabase();
+ PREPARE_STATEMENT(begin, "BEGIN");
+ PREPARE_STATEMENT(end, "COMMIT");
+ PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1");
+#ifdef __ANDROID__
+ PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
+#else
+ PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
+#endif
+ PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
+ PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
-#if 0
- v2s16 p2d(p3d.X, p3d.Z);
- std::string sectordir = getSectorDir(p2d);
+ m_initialized = true;
- createDirs(sectordir);
+ verbosestream << "ServerMap: SQLite3 database opened." << std::endl;
+}
- std::string fullpath = sectordir+DIR_DELIM+getBlockFilename(p3d);
- std::ofstream o(fullpath.c_str(), std::ios_base::binary);
- if(o.good() == false)
- throw FileNotGoodException("Cannot open block data");
-#endif
- /*
- [0] u8 serialization version
- [1] data
- */
+inline void Database_SQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index)
+{
+ SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)));
+}
+bool Database_SQLite3::deleteBlock(const v3s16 &pos)
+{
verifyDatabase();
- std::ostringstream o(std::ios_base::binary);
+ bindPos(m_stmt_delete, pos);
- o.write((char*)&version, 1);
+ bool good = sqlite3_step(m_stmt_delete) == SQLITE_DONE;
+ sqlite3_reset(m_stmt_delete);
- // Write basic data
- block->serialize(o, version, true);
+ if (!good) {
+ errorstream << "WARNING: deleteBlock: Block failed to delete "
+ << PP(pos) << ": " << sqlite3_errmsg(m_database) << std::endl;
+ }
+ return good;
+}
- // Write block to database
+bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data)
+{
+ verifyDatabase();
+
+#ifdef __ANDROID__
+ /**
+ * Note: For some unknown reason SQLite3 fails to REPLACE blocks on Android,
+ * deleting them and then inserting works.
+ */
+ bindPos(m_stmt_read, pos);
+
+ if (sqlite3_step(m_stmt_read) == SQLITE_ROW) {
+ deleteBlock(pos);
+ }
+ sqlite3_reset(m_stmt_read);
+#endif
- std::string tmp = o.str();
- const char *bytes = tmp.c_str();
+ bindPos(m_stmt_write, pos);
+ SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL));
- if(sqlite3_bind_int64(m_database_write, 1, getBlockAsInteger(p3d)) != SQLITE_OK)
- infostream<<"WARNING: Block position failed to bind: "<<sqlite3_errmsg(m_database)<<std::endl;
- if(sqlite3_bind_blob(m_database_write, 2, (void *)bytes, o.tellp(), NULL) != SQLITE_OK) // TODO this mught not be the right length
- infostream<<"WARNING: Block data failed to bind: "<<sqlite3_errmsg(m_database)<<std::endl;
- int written = sqlite3_step(m_database_write);
- if(written != SQLITE_DONE)
- infostream<<"WARNING: Block failed to save ("<<p3d.X<<", "<<p3d.Y<<", "<<p3d.Z<<") "
- <<sqlite3_errmsg(m_database)<<std::endl;
- // Make ready for later reuse
- sqlite3_reset(m_database_write);
+ SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE)
+ sqlite3_reset(m_stmt_write);
- // We just wrote it to the disk so clear modified flag
- block->resetModified();
+ return true;
}
-MapBlock* Database_SQLite3::loadBlock(v3s16 blockpos)
+std::string Database_SQLite3::loadBlock(const v3s16 &pos)
{
- v2s16 p2d(blockpos.X, blockpos.Z);
verifyDatabase();
- if (sqlite3_bind_int64(m_database_read, 1, getBlockAsInteger(blockpos)) != SQLITE_OK) {
- infostream << "WARNING: Could not bind block position for load: "
- << sqlite3_errmsg(m_database)<<std::endl;
- }
+ bindPos(m_stmt_read, pos);
- if (sqlite3_step(m_database_read) == SQLITE_ROW) {
- /*
- Make sure sector is loaded
- */
- MapSector *sector = srvmap->createSector(p2d);
-
- /*
- Load block
- */
- const char *data = (const char *)sqlite3_column_blob(m_database_read, 0);
- size_t len = sqlite3_column_bytes(m_database_read, 0);
- if (data == NULL || len == 0) {
- errorstream << "Blank block data in database (data == NULL || len"
- " == 0) (" << blockpos.X << "," << blockpos.Y << ","
- << blockpos.Z << ")" << std::endl;
-
- if (g_settings->getBool("ignore_world_load_errors")) {
- errorstream << "Ignoring block load error. Duck and cover! "
- << "(ignore_world_load_errors)" << std::endl;
- } else {
- throw SerializationError("Blank block data in database");
- }
- return NULL;
- }
-
- std::string datastr(data, len);
-
- //srvmap->loadBlock(&datastr, blockpos, sector, false);
-
- try {
- std::istringstream is(datastr, std::ios_base::binary);
-
- u8 version = SER_FMT_VER_INVALID;
- is.read((char *)&version, 1);
-
- if (is.fail())
- throw SerializationError("ServerMap::loadBlock(): Failed"
- " to read MapBlock version");
-
- MapBlock *block = NULL;
- bool created_new = false;
- block = sector->getBlockNoCreateNoEx(blockpos.Y);
- if (block == NULL)
- {
- block = sector->createBlankBlockNoInsert(blockpos.Y);
- created_new = true;
- }
-
- // Read basic data
- block->deSerialize(is, version, true);
-
- // If it's a new block, insert it to the map
- if (created_new)
- sector->insertBlock(block);
-
- /*
- Save blocks loaded in old format in new format
- */
- //if(version < SER_FMT_VER_HIGHEST || save_after_load)
- // Only save if asked to; no need to update version
- //if(save_after_load)
- // saveBlock(block);
-
- // We just loaded it from, so it's up-to-date.
- block->resetModified();
- }
- catch (SerializationError &e)
- {
- errorstream << "Invalid block data in database"
- << " (" << blockpos.X << "," << blockpos.Y << "," << blockpos.Z << ")"
- << " (SerializationError): " << e.what() << std::endl;
-
- // TODO: Block should be marked as invalid in memory so that it is
- // not touched but the game can run
-
- if (g_settings->getBool("ignore_world_load_errors")) {
- errorstream << "Ignoring block load error. Duck and cover! "
- << "(ignore_world_load_errors)" << std::endl;
- } else {
- throw SerializationError("Invalid block data in database");
- //assert(0);
- }
- }
-
- sqlite3_step(m_database_read);
- // We should never get more than 1 row, so ok to reset
- sqlite3_reset(m_database_read);
-
- return srvmap->getBlockNoCreateNoEx(blockpos); // should not be using this here
+ if (sqlite3_step(m_stmt_read) != SQLITE_ROW) {
+ sqlite3_reset(m_stmt_read);
+ return "";
}
- sqlite3_reset(m_database_read);
- return NULL;
+ const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0);
+ size_t len = sqlite3_column_bytes(m_stmt_read, 0);
+
+ std::string s;
+ if (data)
+ s = std::string(data, len);
+
+ sqlite3_step(m_stmt_read);
+ // We should never get more than 1 row, so ok to reset
+ sqlite3_reset(m_stmt_read);
+
+ return s;
}
void Database_SQLite3::createDatabase()
{
- int e;
- assert(m_database);
- e = sqlite3_exec(m_database,
- "CREATE TABLE IF NOT EXISTS `blocks` ("
- "`pos` INT NOT NULL PRIMARY KEY,"
- "`data` BLOB"
- ");"
- , NULL, NULL, NULL);
- if(e == SQLITE_ABORT)
- throw FileNotGoodException("Could not create sqlite3 database structure");
- else
- infostream<<"ServerMap: SQLite3 database structure was created";
-
+ assert(m_database); // Pre-condition
+ SQLOK(sqlite3_exec(m_database,
+ "CREATE TABLE IF NOT EXISTS `blocks` (\n"
+ " `pos` INT PRIMARY KEY,\n"
+ " `data` BLOB\n"
+ ");\n",
+ NULL, NULL, NULL));
}
-void Database_SQLite3::listAllLoadableBlocks(std::list<v3s16> &dst)
+void Database_SQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
verifyDatabase();
- while(sqlite3_step(m_database_list) == SQLITE_ROW)
- {
- sqlite3_int64 block_i = sqlite3_column_int64(m_database_list, 0);
- v3s16 p = getIntegerAsBlock(block_i);
- //dstream<<"block_i="<<block_i<<" p="<<PP(p)<<std::endl;
- dst.push_back(p);
+ while (sqlite3_step(m_stmt_list) == SQLITE_ROW) {
+ dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0)));
}
+ sqlite3_reset(m_stmt_list);
}
-
-#define FINALIZE_STATEMENT(statement) \
- if ( statement ) \
- rc = sqlite3_finalize(statement); \
- if ( rc != SQLITE_OK ) \
- errorstream << "Database_SQLite3::~Database_SQLite3():" \
- << "Failed to finalize: " << #statement << ": rc=" << rc << std::endl;
-
Database_SQLite3::~Database_SQLite3()
{
- int rc = SQLITE_OK;
-
- FINALIZE_STATEMENT(m_database_read)
- FINALIZE_STATEMENT(m_database_write)
- FINALIZE_STATEMENT(m_database_list)
-
- if(m_database)
- rc = sqlite3_close(m_database);
-
- if (rc != SQLITE_OK) {
+ FINALIZE_STATEMENT(m_stmt_read)
+ FINALIZE_STATEMENT(m_stmt_write)
+ FINALIZE_STATEMENT(m_stmt_list)
+ FINALIZE_STATEMENT(m_stmt_begin)
+ FINALIZE_STATEMENT(m_stmt_end)
+ FINALIZE_STATEMENT(m_stmt_delete)
+
+ if (sqlite3_close(m_database) != SQLITE_OK) {
errorstream << "Database_SQLite3::~Database_SQLite3(): "
- << "Failed to close database: rc=" << rc << std::endl;
+ << "Failed to close database: "
+ << sqlite3_errmsg(m_database) << std::endl;
}
}