summaryrefslogtreecommitdiff
path: root/src/database-sqlite3.cpp
diff options
context:
space:
mode:
authorIlya Zhuravlev <zhuravlevilya@ya.ru>2012-10-23 01:18:44 +0400
committerSfan5 <sfan5@live.de>2013-09-09 22:50:50 +0200
commit58841ef12f6cba1bb622353c1fcaa0e3c6fb46c9 (patch)
tree6012bbb1905231025dff89aa73782a7c38666839 /src/database-sqlite3.cpp
parent71a8769bb5ded4acb3f9e5a8502bb8af277f824d (diff)
downloadminetest-58841ef12f6cba1bb622353c1fcaa0e3c6fb46c9.tar.gz
minetest-58841ef12f6cba1bb622353c1fcaa0e3c6fb46c9.tar.bz2
minetest-58841ef12f6cba1bb622353c1fcaa0e3c6fb46c9.zip
Add dummy and LevelDB database backends
Diffstat (limited to 'src/database-sqlite3.cpp')
-rw-r--r--src/database-sqlite3.cpp319
1 files changed, 319 insertions, 0 deletions
diff --git a/src/database-sqlite3.cpp b/src/database-sqlite3.cpp
new file mode 100644
index 000000000..e15aa0c1c
--- /dev/null
+++ b/src/database-sqlite3.cpp
@@ -0,0 +1,319 @@
+/*
+ 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
+*/
+
+#include "map.h"
+#include "mapsector.h"
+#include "mapblock.h"
+#include "main.h"
+#include "filesys.h"
+#include "voxel.h"
+#include "porting.h"
+#include "mapgen.h"
+#include "nodemetadata.h"
+#include "settings.h"
+#include "log.h"
+#include "profiler.h"
+#include "nodedef.h"
+#include "gamedef.h"
+#include "util/directiontables.h"
+#include "rollback_interface.h"
+
+#include "database-sqlite3.h"
+
+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)
+{
+ 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.";
+}
+
+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.";
+}
+
+void Database_SQLite3::createDirs(std::string path)
+{
+ if(fs::CreateAllDirs(path) == false)
+ {
+ infostream<<DTIME<<"Database_SQLite3: Failed to create directory "
+ <<"\""<<path<<"\""<<std::endl;
+ throw BaseException("Database_SQLite3 failed to create 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();
+
+ 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;
+ }
+}
+
+void Database_SQLite3::saveBlock(MapBlock *block)
+{
+ 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;
+ }
+
+ // Format used for writing
+ u8 version = SER_FMT_VER_HIGHEST;
+ // Get destination
+ v3s16 p3d = block->getPos();
+
+
+#if 0
+ v2s16 p2d(p3d.X, p3d.Z);
+ std::string sectordir = getSectorDir(p2d);
+
+ createDirs(sectordir);
+
+ 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
+ */
+
+ verifyDatabase();
+
+ std::ostringstream o(std::ios_base::binary);
+
+ o.write((char*)&version, 1);
+
+ // Write basic data
+ block->serialize(o, version, true);
+
+ // Write block to database
+
+ std::string tmp = o.str();
+ const char *bytes = tmp.c_str();
+
+ 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);
+
+ // We just wrote it to the disk so clear modified flag
+ block->resetModified();
+}
+
+MapBlock* Database_SQLite3::loadBlock(v3s16 blockpos)
+{
+ 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;
+ 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);
+
+ 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
+ }
+ sqlite3_reset(m_database_read);
+ return(NULL);
+}
+
+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";
+
+}
+
+void Database_SQLite3::listAllLoadableBlocks(core::list<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);
+ }
+}
+
+Database_SQLite3::~Database_SQLite3()
+{
+ if(m_database_read)
+ sqlite3_finalize(m_database_read);
+ if(m_database_write)
+ sqlite3_finalize(m_database_write);
+ if(m_database)
+ sqlite3_close(m_database);
+}
+