Simple, unified database interface

master
jachoo 2012-02-16 16:19:45 +01:00
parent 355b5648d7
commit 5aa18256bc
4 changed files with 744 additions and 0 deletions

View File

@ -0,0 +1,2 @@
default

121
data/mods/jachoo/init.lua Normal file
View File

@ -0,0 +1,121 @@
minetest.register_on_chat_message( function(name, message)
local meta_name = "lastmessage"
if message:sub(1,1) == '/' then
local cmd = "/lastmessage"
local s
if message:sub(0, #cmd) == cmd then
local v = minetest.get_player_meta(name,meta_name,"string")
if v then
minetest.chat_send_player(name, 'Your last message: '..v)
end
return true
end
else
minetest.set_player_meta(name,meta_name,"string",message)
end
end)
-- minetest.register_on_chat_message( function(name, message)
-- print("test-jachoo: name="..dump(name).." message="..dump(message))
-- -- types: string, int, double, bool, v3s16, v3f
-- -- local t = {string="a", int=0, double=0.0, bool=true, v3s16={x=1,y=10,z=100}, v3f={x=1.1,y=2.2,z=3.3}}
-- -- for s,def in pairs(t) do
-- -- print("wczytuje ["..s.."]")
-- -- local v = minetest.get_player_meta(name,"test-"..s,s)
-- -- print(v)
-- -- end
-- -- for s,def in pairs(t) do
-- -- print("zapisuje ["..s.."]")
-- -- minetest.set_player_meta(name,"test-"..s,s,def)
-- -- end
-- local v = nil
-- local t = nil
-- t = "string"
-- v = minetest.get_player_meta(name,"test-"..t,t)
-- if v == nil
-- then v = "a"
-- else
-- print("odczytano ["..t.."] = "..v)
-- v = v .. "a"
-- end
-- minetest.set_player_meta(name,"test-"..t,t,v)
-- t = "int"
-- v = minetest.get_player_meta(name,"test-"..t,t)
-- if v == nil
-- then v = 0
-- else
-- print("odczytano ["..t.."] = "..v)
-- v = v + 1
-- end
-- minetest.set_player_meta(name,"test-"..t,t,v)
-- t = "double"
-- v = minetest.get_player_meta(name,"test-"..t,t)
-- if v == nil
-- then v = 0.1
-- else
-- print("odczytano ["..t.."] = "..v)
-- v = v + 1.1
-- end
-- minetest.set_player_meta(name,"test-"..t,t,v)
-- t = "bool"
-- v = minetest.get_player_meta(name,"test-"..t,t)
-- if v == nil
-- then v = true
-- else
-- local x
-- if v then x = "T" else x = "F" end
-- print("odczytano ["..t.."] = "..x)
-- v = not v
-- end
-- minetest.set_player_meta(name,"test-"..t,t,v)
-- t = "v3s16"
-- v = minetest.get_player_meta(name,"test-"..t,t)
-- if v == nil
-- then v = {x=1,y=2,z=3}
-- else
-- print("odczytano ["..t.."] = "..v.x..","..v.y..","..v.z)
-- v = {x=v.x+1,y=v.y+1,z=v.z+1}
-- end
-- minetest.set_player_meta(name,"test-"..t,t,v)
-- t = "v3f"
-- v = minetest.get_player_meta(name,"test-"..t,t)
-- if v == nil
-- then v = {x=1.1,y=2.2,z=3.3}
-- else
-- print("odczytano ["..t.."] = "..v.x..","..v.y..","..v.z)
-- v = {x=v.x+1.1,y=v.y+1.1,z=v.z+1.1}
-- end
-- minetest.set_player_meta(name,"test-"..t,t,v)
-- t = "v3fpos"
-- v = minetest.get_player_meta(name,"test-"..t,t)
-- if v == nil
-- then v = {x=1.1,y=-20000.2,z=30000.3}
-- else
-- print("odczytano ["..t.."] = "..v.x..","..v.y..","..v.z)
-- v = {x=v.x+1.1,y=v.y+1.1,z=v.z+1.1}
-- end
-- minetest.set_player_meta(name,"test-"..t,t,v)
-- end)

162
src/db.cpp Normal file
View File

@ -0,0 +1,162 @@
/*
Minetest-c55
Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Author: Jan Cychnerski */
#include "db.h"
using namespace std;
/* bool */
template<> const std::string DBTypeTraits<bool>::name = "INT";
template<> void DBTypeTraits<bool>::getColumn(sqlite3_stmt *stmt, int iCol, bool& val)
{
val = sqlite3_column_int(stmt,iCol) != 0;
}
template<> void DBTypeTraits<bool>::bind(sqlite3_stmt* stmt, int num, const bool& val)
{
bool d = sqlite3_bind_int(stmt,num,val?1:0);
if(d != SQLITE_OK) throw DatabaseException("Bind error");
}
/* int */
template<> const std::string DBTypeTraits<int>::name = "INT";
template<> void DBTypeTraits<int>::getColumn(sqlite3_stmt *stmt, int iCol, int& val)
{
val = sqlite3_column_int(stmt,iCol);
}
template<> void DBTypeTraits<int>::bind(sqlite3_stmt* stmt, int num, const int& val)
{
int d = sqlite3_bind_int(stmt,num,val);
if(d != SQLITE_OK) throw DatabaseException("Bind error");
}
/* u64, db_key */
template<> const std::string DBTypeTraits<u64>::name = "INT";
template<> void DBTypeTraits<u64>::getColumn(sqlite3_stmt *stmt, int iCol, u64& val)
{
val = sqlite3_column_int64(stmt,iCol);
}
template<> void DBTypeTraits<u64>::bind(sqlite3_stmt* stmt, int num, const u64& val)
{
u64 d = sqlite3_bind_int64(stmt,num,val);
if(d != SQLITE_OK) throw DatabaseException("Bind error");
}
template<> const std::string DBTypeTraits<db_key>::name = "INT";
template<> void DBTypeTraits<db_key>::getColumn(sqlite3_stmt *stmt, int iCol, db_key& val)
{
val = sqlite3_column_int64(stmt,iCol);
}
template<> void DBTypeTraits<db_key>::bind(sqlite3_stmt* stmt, int num, const db_key& val)
{
db_key d = sqlite3_bind_int64(stmt,num,val);
if(d != SQLITE_OK) throw DatabaseException("Bind error");
}
/* v3s16 */
template<> const std::string DBTypeTraits<v3s16>::name = "INT";
template<> void DBTypeTraits<v3s16>::getColumn(sqlite3_stmt *stmt, int iCol, v3s16& val)
{
val = DBKey( sqlite3_column_int64(stmt,iCol) );
}
template<> void DBTypeTraits<v3s16>::bind(sqlite3_stmt* stmt, int num, const v3s16& val)
{
int d = sqlite3_bind_int64(stmt,num,DBKey(val));
if(d != SQLITE_OK) throw DatabaseException("Bind error");
}
/* v2s16 */
template<> const std::string DBTypeTraits<v2s16>::name = "INT";
template<> void DBTypeTraits<v2s16>::getColumn(sqlite3_stmt *stmt, int iCol, v2s16 & val)
{
val = DBKey( sqlite3_column_int64(stmt,iCol) );
}
template<> void DBTypeTraits<v2s16>::bind(sqlite3_stmt* stmt, int num, const v2s16& val)
{
int d = sqlite3_bind_int64(stmt,num,DBKey(val));
if(d != SQLITE_OK) throw DatabaseException("Bind error");
}
/* v3f */
template<> const std::string DBTypeTraits<v3f>::name = "BLOB";
template<> void DBTypeTraits<v3f>::getColumn(sqlite3_stmt *stmt, int iCol, v3f& val)
{
if(sqlite3_column_bytes(stmt,iCol) != 12) throw DatabaseException("Value is not v3f");
const f32* data = (const f32*)sqlite3_column_blob(stmt,iCol);
val.X = data[0];
val.Y = data[1];
val.Z = data[2];
}
template<> void DBTypeTraits<v3f>::bind(sqlite3_stmt* stmt, int num, const v3f& val)
{
const float data[3] = {val.X, val.Y, val.Z};
int d = sqlite3_bind_blob(stmt,num,data,12,SQLITE_TRANSIENT);
if(d != SQLITE_OK) throw DatabaseException("Bind error");
}
/* string - stored as BLOB (!) */
template<> const std::string DBTypeTraits<std::string>::name = "BLOB";
template<> void DBTypeTraits<std::string>::getColumn(sqlite3_stmt *stmt, int iCol, std::string& val)
{
const char * data = (const char *)sqlite3_column_blob(stmt,iCol);
size_t len = sqlite3_column_bytes(stmt,iCol);
val.assign(data,len);
}
template<> void DBTypeTraits<std::string>::bind(sqlite3_stmt* stmt, int num, const std::string& val)
{
int d = sqlite3_bind_blob(stmt,num,val.c_str(),val.length(),NULL);
if(d != SQLITE_OK) throw DatabaseException("Bind error");
}
/* float */
template<> const std::string DBTypeTraits<float>::name = "REAL";
template<> void DBTypeTraits<float>::getColumn(sqlite3_stmt *stmt, int iCol, float& val)
{
val = (float)sqlite3_column_double(stmt,iCol);
}
template<> void DBTypeTraits<float>::bind(sqlite3_stmt* stmt, int num, const float& val)
{
float d = sqlite3_bind_double(stmt,num,val);
if(d != SQLITE_OK) throw DatabaseException("Bind error");
}
/* double */
template<> const std::string DBTypeTraits<double>::name = "REAL";
template<> void DBTypeTraits<double>::getColumn(sqlite3_stmt *stmt, int iCol, double& val)
{
val = sqlite3_column_double(stmt,iCol);
}
template<> void DBTypeTraits<double>::bind(sqlite3_stmt* stmt, int num, const double& val)
{
double d = sqlite3_bind_double(stmt,num,val);
if(d != SQLITE_OK) throw DatabaseException("Bind error");
}

459
src/db.h Normal file
View File

@ -0,0 +1,459 @@
/*
Minetest-c55
Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Author: Jan Cychnerski */
#ifndef DB_HEADER
#define DB_HEADER
#include <jmutex.h>
#include <jmutexautolock.h>
#include <jthread.h>
#include <iostream>
#include <sstream>
#include <map>
#include "common_irrlicht.h"
#include "exceptions.h"
#include "utility.h"
extern "C" {
#include "sqlite3.h"
}
#define DBTYPE_BASE 0
#define DBTYPE_SERVER 1
#define DBTYPE_CLIENT 2
class DatabaseException : public BaseException {
public:
DatabaseException() : BaseException("Database access error") {}
DatabaseException(const char* msg) : BaseException(msg) {}
};
//type of 64-bit database key
typedef sqlite3_int64 db_key;
/* Some helper functions */
inline s32 unsignedToSigned(s32 i, s32 max_positive)
{
if(i < max_positive)
return i;
else
return i - 2*max_positive;
}
// modulo of a negative number does not work consistently in C
inline db_key pythonmodulo(db_key i, db_key mod)
{
if(i >= 0)
return i % mod;
return mod - ((-i) % mod);
}
inline v3s16 getIntegerAsBlock(db_key i)
{
s32 x = unsignedToSigned(pythonmodulo(i, 4096), 2048);
i = (i - x) / 4096;
s32 y = unsignedToSigned(pythonmodulo(i, 4096), 2048);
i = (i - y) / 4096;
s32 z = unsignedToSigned(pythonmodulo(i, 4096), 2048);
return v3s16(x,y,z);
}
inline v2s16 getIntegerAsSector(db_key i)
{
v3s16 v = getIntegerAsBlock(i);
return v2s16(v.X,v.Z);
}
inline db_key getBlockAsInteger(const v3s16& pos) {
return (db_key)pos.Z*16777216 +
(db_key)pos.Y*4096 + (db_key)pos.X;
}
inline db_key getSectorAsInteger(const v2s16& pos) {
return getBlockAsInteger(v3s16(pos.X,0,pos.Y));
}
//database INT key type with implicit constructors
struct DBKey {
db_key i;
DBKey(const db_key& i):i(i){}
DBKey(const u64& i):i(i){}
DBKey(const v3s16& i):i(getBlockAsInteger(i)){}
DBKey(const v2s16& i):i(getSectorAsInteger(i)){}
operator db_key() const { return i; }
operator u64() const { return i; }
operator v3s16() const { return getIntegerAsBlock(i); }
operator v2s16() const { return getIntegerAsSector(i); }
};
//for future use - BLOB data is stored in minetest in std::string for now
typedef std::string binary_t;
//traits for types stored in databases
//available: int, u64, db_key, v3s16, v2s16, std::string (maybe more in db.cpp)
template<class T>
struct DBTypeTraits {
static const std::string name;
typedef T t;
static inline T getColumn(sqlite3_stmt* stmt, int iCol) { T v; getColumn(stmt,iCol,v); return v; }
static void getColumn(sqlite3_stmt* stmt, int iCol, T& val);
static void bind(sqlite3_stmt* stmt, int num, const T& val);
};
//traits for database _key_ types (for future use)
template<class T>
struct DBKeyTypeTraits : public DBTypeTraits<T> {};
//traits for database _data_ types (for future use)
template<class T>
struct DBDataTypeTraits : public DBTypeTraits<T> {};
//base class for tables in db
class ITable {
public:
ITable(sqlite3* database, const std::string& name, const std::string& key, const std::string& data, bool old_names = false):
m_database(database),
name(name),
key_name(key),
data_name(data),
old_names(old_names)
{
assert(database);
create();
int d;
std::string q;
std::string id_name = old_names ? "pos" : "id";
q = "SELECT `data` FROM `"+name+"` WHERE `"+id_name+"`=? LIMIT 1";
d = sqlite3_prepare_v2(m_database,q.c_str(), -1, &m_read, NULL);
if(d != SQLITE_OK) {
//infostream<<"WARNING: Database read statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
throw FileNotGoodException("Cannot prepare read statement");
}
q = "REPLACE INTO `"+name+"` ("+id_name+",data) VALUES(?, ?)";
d = sqlite3_prepare_v2(m_database,q.c_str(), -1, &m_write, NULL);
if(d != SQLITE_OK) {
//infostream<<"WARNING: Database write statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
throw FileNotGoodException("Cannot prepare write statement");
}
q = "SELECT `"+id_name+"` FROM `"+name+"`";
d = sqlite3_prepare_v2(m_database,q.c_str(), -1, &m_list, NULL);
if(d != SQLITE_OK) {
//infostream<<"WARNING: Database list statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
throw FileNotGoodException("Cannot prepare read statement");
}
}
virtual ~ITable() {
if(m_read)
sqlite3_finalize(m_read);
if(m_write)
sqlite3_finalize(m_write);
if(m_list)
sqlite3_finalize(m_list);
}
//creates the table or returns false if failed
virtual bool createNoEx()
{
std::string id_name = old_names ? "pos" : "id";
std::string query =
"CREATE TABLE IF NOT EXISTS `" + name + "` ("
"`"+id_name+"` " + key_name + " NOT NULL PRIMARY KEY,"
"`data` " + data_name +
");";
return exec(query);
}
//creates the table or throws DatabaseException
void create()
{
if(!createNoEx()) throw DatabaseException("Cannot create table!");
}
//inserts or replaces data in row with given key
//if failed, returns false
template<class Key, class Data>
bool put(const Key& key, const Data& data)
{
DBKeyTypeTraits<Key>::bind(m_write,1,key);
DBDataTypeTraits<Data>::bind(m_write,2,data);
int d = sqlite3_step(m_write);
sqlite3_reset(m_write);
return d == SQLITE_DONE;
}
//loads data from row with given key to buffer 'data'
//if failed, returns false ('data' will not be modified!)
template<class Key, class Data>
bool getNoEx(const Key& key, Data& data)
{
DBKeyTypeTraits<Key>::bind(m_read,1,key);
if(sqlite3_step(m_read) != SQLITE_ROW){
sqlite3_reset(m_read);
return false;
}
DBDataTypeTraits<Data>::getColumn(m_read,0,data);
sqlite3_reset(m_read);
return true;
}
//loads and returns data from row with given key
//if failed (i.e. key doesn't exist) throws DatabaseException
//NOTE: reversed template arguments!
template<class Data,class Key>
Data get(const Key& key)
{
Data d;
if(!getNoEx(key,d)) throw DatabaseException("Database read error");
return d;
}
//inserts all ids from tabale to given list
template<class Key>
bool getKeys(core::list<Key>& list)
{
while(sqlite3_step(m_list) == SQLITE_ROW)
{
Key key = DBKeyTypeTraits<Key>::getColumn(m_list,0);
list.push_back(key);
}
sqlite3_reset(m_list);
return true;
}
const std::string name; //name of the table
const std::string key_name; //name of sqlite key type
const std::string data_name; //name of sqlite data type
const bool old_names; //if true, primary key has name 'pos' and not 'id'
protected:
sqlite3* m_database;
sqlite3_stmt *m_read;
sqlite3_stmt *m_write;
sqlite3_stmt *m_list;
bool exec(const std::string& query)
{
assert(m_database);
int e = sqlite3_exec(m_database,query.c_str(), NULL, NULL, NULL);
return e == SQLITE_OK;
}
private:
ITable(const ITable&); //disable copy constructor
};
//template class for tables in database
template<class Key, class Data = void>
class Table : protected ITable {
public:
Table(sqlite3* database, const std::string& name, bool old_names = false):
ITable(database, name, key_traits::name, data_traits::name, old_names)
{}
//inserts or replaces data in row with given key
//if failed, returns false
bool put(const Key& key, const Data& data)
{
return ITable::put(key,data);
}
//loads data from row with given key to buffer 'data'
//if failed, returns false ('data' will not be modified!)
bool getNoEx(const Key& key, Data& data)
{
return ITable::getNoEx(key,data);
}
//loads and returns data from row with given key
//if failed (i.e. key doesn't exist) throws DatabaseException
inline Data get(const Key& key)
{
return ITable::get<Data>(key);
}
//inserts all ids from tabale to given list
bool getKeys(core::list<Key>& list)
{
return ITable::getKeys(list);
}
protected:
typedef DBKeyTypeTraits<Key> key_traits;
typedef DBDataTypeTraits<Data> data_traits;
};
//template class for tables in database
template<class Key>
class Table<Key,void> : protected ITable {
public:
Table(sqlite3* database, const std::string& name, const std::string& data_type, bool old_names = false):
ITable(database, name, key_traits::name, data_type, old_names)
{}
//inserts or replaces data in row with given key
//if failed, returns false
template<class Data>
bool put(const Key& key, const Data& data)
{
return ITable::put(key,data);
}
//loads data from row with given key to buffer 'data'
//if failed, returns false ('data' will not be modified!)
template<class Data>
bool getNoEx(const Key& key, Data& data)
{
return ITable::getNoEx(key,data);
}
//loads and returns data from row with given key
//if failed (i.e. key doesn't exist) throws DatabaseException
template<class Data>
inline Data get(const Key& key)
{
return ITable::get<Data>(key);
}
//inserts all ids from tabale to given list
bool getKeys(core::list<Key>& list)
{
return ITable::getKeys(list);
}
protected:
typedef DBKeyTypeTraits<Key> key_traits;
};
//database interface
class Database {
public:
Database(const std::string& file)
{
m_is_new = false;
int d = sqlite3_open_v2(file.c_str(), &m_database, SQLITE_OPEN_READWRITE, NULL);
if(d != SQLITE_OK) {
//can't open a file. try to create it.
m_is_new = true;
d = sqlite3_open_v2(file.c_str(), &m_database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
}
if(d != SQLITE_OK) {
//infostream<<"WARNING: Database failed to open: "<<sqlite3_errmsg(m_database)<<std::endl;
throw FileNotGoodException("Cannot create/open database file");
}
}
~Database()
{
tables.clear(); //finalize all queries to tables
if(m_database)
sqlite3_close(m_database);
}
//creates or loads a table with given key type, data type and name
//if old_names=true, then primary key will have name 'pos' instead of 'id'
//BE CAREFULL! if in the database exists a table with another key/data types - result is unpredictable!
//sometimes it may then throw DatabaseException, but don't rely on this!
template<class Key, class Data>
Table<Key,Data>& getTable(const std::string& name, bool old_names = false)
{
SharedPtr<ITable>& ptr = tables[name];
if(ptr==NULL)
ptr = (ITable*) new Table<Key,Data>(m_database,name,old_names);
if(typeid(Table<Key,Data>) != typeid(*ptr)) throw DatabaseException("Wrong key/data type(s)!");
return (Table<Key,Data>&)*ptr;
}
//creates or loads a table with given key type, data type and name
//if old_names=true, then primary key will have name 'pos' instead of 'id'
//BE CAREFULL! if in the database exists a table with another key/data types - result is unpredictable!
//sometimes it may then throw DatabaseException, but don't rely on this!
template<class Key>
Table<Key>& getTable(const std::string& name, bool old_names = false)
{
SharedPtr<ITable>& ptr = tables[name];
if(ptr==NULL)
ptr = (ITable*) new Table<Key>(m_database,name,"BLOB",old_names);
if(typeid(Table<Key>) != typeid(*ptr)) throw DatabaseException("Wrong key/data type(s)!");
return (Table<Key>&)*ptr;
}
//creates or loads a typeless table
//if old_names=true, then primary key will have name 'pos' instead of 'id'
ITable& getTable(const std::string& name, bool old_names = false)
{
SharedPtr<ITable>& ptr = tables[name];
if(ptr==NULL)
ptr = new ITable(m_database,name,"BLOB","BLOB",old_names);
return *ptr;
}
//begins a transaction
void begin()
{
sqlite3_exec(m_database,"BEGIN;", NULL, NULL, NULL);
}
//commits a transaction
void commit()
{
sqlite3_exec(m_database,"COMMIT;", NULL, NULL, NULL);
}
//returns true if database was created from scratch (i.e. no database file existed before)
inline bool isNew() const
{
return m_is_new;
}
private:
sqlite3* m_database;
std::map<std::string,SharedPtr<ITable> > tables;
bool m_is_new;
};
#endif