Initial implementation

master
Mateusz Zalega 2015-11-07 20:14:47 +01:00
parent 880f447196
commit dd11464124
47 changed files with 2162 additions and 224 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.swo
*.swp
*.db-journal
*.gdb_history
*.peda-session*

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "googletest"]
path = googletest
url = https://github.com/google/googletest.git

110
Makefile
View File

@ -1,5 +1,7 @@
INC = -Isource/include -I/usr/include/zdb
LIBS = -lcairo -lqrencode -lzdb -lpthread
INC = -Ibuild -Isource/include -I/usr/include/zdb -Igoogletest/googletest/include
LIB = -Lbuild -Lgoogletest/googletest
UNITTESTLIBS = -lzdb -lpthread -lgtest_main -lgtest
SOLIBS = -lzdb -lpthread
CXXSOURCE = $(wildcard source/*.cc)
CSOURCE = $(wildcard source/*.c)
@ -10,46 +12,104 @@ COBJ = $(CSOURCE:source/%.c=build/%.o)
CXXOBJ = $(CXXSOURCE:source/%.cc=build/%.o)
OBJ = $(COBJ) $(CXXOBJ)
CDEPEND = $(CSOURCE:source/%.c=build/%.c.d)
CXXDEPEND = $(CXXSOURCE:source/%.cc=build/%.cc.d)
DEPEND = $(CDEPEND) $(CXXDEPEND)
.PRECIOUS: $(DEPEND) $(OBJ)
.SECONDARY: $(DEPEND) $(OBJ)
UNITTEST_CXXSOURCE = $(wildcard unittest/*.cc)
UNITTEST_CXXOBJ = $(UNITTEST_CXXSOURCE:unittest/%.cc=build/unittest/%.o)
UNITTEST_TARGETS = $(UNITTEST_CXXOBJ:%.o=%)
UNITTEST_LOGS = $(UNITTEST_TARGETS:%=%.xml)
PRECOMPILED_HEADERS = $(HEADERS:source/include/%.hh=build/%.hh.gch)
TESTDBS = $(UNITTEST_TARGETS:build/unittest/%=build/unittest/%.db)
TARGETS = build/libinvdb.so
LIBTARGETS = build/libinvdb.so
CC = gcc
CXX = g++
LD = $(CXX)
CXXFLAGS = -std=c++14 -g -fPIC
SOFLAGS = -shared
CXXDEPEND = $(CXXSOURCE:source/%.cc=build/%.cc.d) \
$(UNITTEST_CXXSOURCE:unittest/%.cc=build/unittest/%.cc.d)
HEADERDEPEND = $(HEADERS:source/include/%.hh=build/%.hh.d)
DEPEND = $(CXXDEPEND) $(HEADERDEPEND)
.PHONY: all depend clean mrproper
TARGETS = $(LIBTARGETS) $(UNITTEST_TARGETS)
all: depend $(TARGETS)
UNITTEST_LD_LIBRARY_PATH = build
.PRECIOUS: $(DEPEND) $(OBJ) $(UNITTEST_CXXOBJ) $(UNITTEST_LOGS) \
$(PRECOMPILED_HEADERS)
.SECONDARY: $(DEPEND) $(OBJ) $(UNITTEST_CXXOBJ) $(UNITTEST_LOGS) \
$(PRECOMPILED_HEADERS)
CC := gcc
CXX := g++
LD := g++
CXXFLAGS := -std=c++14 -g -fPIC -Wno-virtual-move-assign -Wno-format-security
SOFLAGS := -shared
.PHONY: all depend clean mrproper googletest submodules test \
clean_testresults directories
all: directories depend $(TARGETS) test
depend: $(DEPEND)
clean:
clean: clean_testresults
rm -f build/*.o
rm -f build/unittest/*.o
rm -f $(TARGETS)
rm -f $(UNITTEST_LOGS)
rm -f build/unittest/*.db
rm -f $(PRECOMPILED_HEADERS)
clean_testresults:
rm -f $(UNITTEST_TARGETS)
mrproper: clean
rm -f build/*.d
rm -f build/unittest/*.d
cd googletest && make clean
cd googletest/googletest && make clean
googletest:
git submodule init
git submodule update googletest
cd googletest && cmake . && make
cd googletest/googletest && cmake . && make
submodules: googletest
directories:
mkdir -p build/unittest
mkdir -p googletest
test: $(UNITTEST_LOGS)
build/unittest/%.db: data/clean.db
cp $< $@
build/unittest/%.xml: build/unittest/% build/unittest/%.db
LD_LIBRARY_PATH=$(UNITTEST_LD_LIBRARY_PATH) $< --gtest_output=\"xml:$@\"\
\sqlite://$(CURDIR)/$<.db 2>&1
build/%.cc.d: source/%.cc
$(CXX) -M $< -o $@ $(INC) $(CXXFLAGS)
$(CXX) -MM $< -o $@ $(INC) $(CXXFLAGS)
build/%.c.d: source/%.c
$(CXX) -M $< -o $@ $(INC) $(CXXFLAGS)
build/%.o: source/%.cc build/%.cc.d
build/%.o: source/%.cc build/%.cc.d $(PRECOMPILED_HEADERS)
$(CXX) -c $< -o $@ $(INC) $(CXXFLAGS)
build/%.o: source/%.c build/%.c.d
$(CC) -c $< -o $@ $(INC) $(CFLAGS)
build/%.so: $(OBJ)
$(LD) $(SOFLAGS) $(OBJ) -o $@
$(LD) $(SOFLAGS) $(OBJ) $(SOLIBS) -o $@
build/unittest/%.cc.d: unittest/%.cc
$(CXX) -MM $< -o $@ $(INC) $(CXXFLAGS)
build/unittest/%.o: unittest/%.cc build/unittest/*.cc.d $(PRECOMPILED_HEADERS)
$(CXX) $(INC) -c $(CXXFLAGS) $< -o $@
build/unittest/%: build/unittest/%.o $(LIBTARGETS)
$(LD) $(LIB) $< $(LIBTARGETS:build/lib%.so=-l%) \
$(UNITTESTLIBS) -o $@
build/%.hh.d: source/include/%.hh
$(CXX) $(INC) -MM $(CXXFLAGS) $< -o $@
build/%.hh.gch: source/include/%.hh build/%.hh.d
$(CXX) $(INC) -c $(CXXFLAGS) $< -o $@
include $(wildcard build/*.d)
include $(wildcard build/unittest/*.d)

BIN
data/clean.db Normal file

Binary file not shown.

BIN
data/test.db Normal file

Binary file not shown.

1
googletest Submodule

@ -0,0 +1 @@
Subproject commit 82b11b8cfcca464c2ac74b623d04e74452e74f32

View File

@ -1,8 +1,10 @@
#include "dbobjectptr.hh"
#include "dbobjectmap.hh"
#include "category.hh"
#include "session.hh"
#include "dbobject.hh"
#include "item.hh"
#include "dbsession.hh"
#include <stdexcept>
#include <vector>
#include <algorithm>
#include <zdb.h>
// refer to libzdb docs
@ -12,36 +14,53 @@ namespace inventory {
namespace datamodel {
const char *Category::sc_table = "categories";
const char *Category::sc_view = "category_view";
const char *Category::sc_membership_table = "category_membership";
const char *Category::sc_dbclass = "category";
Category Category::from_query(ResultSet_T r) {
Category ret;
const struct DBRecordElementDesc Category::desc_desc = {
"description",
"",
""
};
TRY {
ret.id = ResultSet_getIntByName(r, "id");
ret.parent = ResultSet_getIntByName(r, "parent");
const struct DBRecordElementDesc Category::symbol_desc = {
"symbol",
"",
""
};
const char *pname = ResultSet_getStringByName(r, "name");
if (pname != NULL)
ret.name = pname;
void Category::create_tables(db::DBSession &session) {
const char *create_table =
"CREATE TABLE \"categories\" ( \n\
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, \n\
`sup` INTEGER NOT NULL DEFAULT -1, \n\
`name` TEXT, \n\
`description` TEXT, \n\
`symbol` BLOB, \n\
FOREIGN KEY(`sup`) REFERENCES categories ( id ) );";
const char *pdescription = ResultSet_getStringByName(r, "description");
if (pdescription != NULL)
ret.description = pdescription;
db::DBOperations::execute<void *>(session, NULL, create_table,
[](void *, PreparedStatement_T s) {});
int symbol_size;
// returns void* by default, and std::copy needs type size info
const char *psymbol =
(const char *)(ResultSet_getBlobByName(r, "symbol", &symbol_size));
if (psymbol != NULL) {
ret.symbol.resize(symbol_size);
std::copy(psymbol, psymbol + symbol_size, std::back_inserter(ret.symbol));
}
} CATCH(SQLException) {
}
END_TRY;
DBHierarchicalObject<Category>::create_sub_view(session);
DBHierarchicalObject<Category>::create_sup_view(session);
DBAssocHierarchicalObject<Item, Category>::create_membership_table(session);
const char *category_view =
"CREATE VIEW category_view AS SELECT * FROM category_sub \n\
JOIN categories ON categories.id = category_sub.id;";
db::DBOperations::execute<void *>(session, NULL, category_view,
[](void *, PreparedStatement_T s) {});
}
void Category::validate_tables(db::DBSession &session) {
}
void Category::remove_tables(db::DBSession &session) {
return ret;
}
}

15
source/dbfield.cc Normal file
View File

@ -0,0 +1,15 @@
#include "dbfield.hh"
#include "dbobject.hh"
#include <vector>
#include <algorithm>
#include <zdb.h>
// refer to libzdb docs
#include <Exception.h>
namespace inventory {
namespace datamodel {
}
}

41
source/dbobject.cc Normal file
View File

@ -0,0 +1,41 @@
#include "dbobject.hh"
#include "dbfield.hh"
#include "dbsession.hh"
#include "dboperations.hh"
#include <map>
#include <utility>
#include <string>
namespace inventory {
namespace datamodel {
const struct DBRecordElementDesc
DBRecordDescTempl<DBObject>::id_desc = {
"id",
"ID",
"Unique identification number"
};
const struct DBRecordElementDesc
DBRecordDescTempl<DBNamedObject>::name_desc = {
"name",
"Name",
""
};
const struct DBRecordElementDesc
DBRecordDescTempl<DBHierarchicalObject>::sup_desc = {
"sup",
"Stored in",
""
};
const struct DBRecordElementDesc
DBRecordDescTempl<DBHierarchicalObject>::nsub_desc = {
"sub",
"Children count",
""
};
}
}

View File

@ -1,4 +1,4 @@
#include "session.hh"
#include "dbsession.hh"
#include <zdb.h>
#include <stdexcept>
@ -9,6 +9,16 @@ DBSession::DBSession(const char *url)
: m_url(url), m_pool(NULL) {
}
DBSession::~DBSession() {
destroy_connpool();
}
void DBSession::reset(const char *url) {
destroy_connpool();
m_url = url;
create_connpool();
}
void DBSession::create_connpool() {
if (m_pool == NULL) {
URL_T url = URL_new(m_url.c_str());
@ -17,6 +27,14 @@ void DBSession::create_connpool() {
}
}
void DBSession::destroy_connpool() {
if (m_pool == NULL)
return;
ConnectionPool_stop(m_pool);
ConnectionPool_free(&m_pool);
}
Connection_T DBSession::get_connection() {
create_connpool();
Connection_T conn = ConnectionPool_getConnection(m_pool);
@ -25,5 +43,9 @@ Connection_T DBSession::get_connection() {
return conn;
}
void DBSession::return_connection(Connection_T conn) {
ConnectionPool_returnConnection(m_pool, conn);
}
}
}

View File

@ -1,25 +1,44 @@
#ifndef INVENTORY_CATEGORY_HH_
#define INVENTORY_CATEGORY_HH_
#include <string>
#include <vector>
#include <zdb.h>
#include "dbcontainer.hh"
#include "dbobjectvec.hh"
#include "dbobjectptr.hh"
#include "dbobject.hh"
#include "dbfield.hh"
#include "dbassocobject.hh"
namespace inventory {
namespace db {
class DBSession;
}
namespace datamodel {
class Category {
class Item;
class Category : public DBAssocHierarchicalObject<Item, Category> {
public:
int id;
int parent;
std::string name;
std::string description;
std::vector<char> symbol;
static const char *sc_table;
static const char *sc_view;
static const char *sc_membership_table;
static const char *sc_dbclass;
static const char *sc_table;
DBString<Category> description;
DBBlob<Category> symbol;
Category()
: description(this, &desc_desc),
symbol(this, &symbol_desc) {}
static Category from_query(ResultSet_T);
static void create_tables(db::DBSession &session);
static void validate_tables(db::DBSession &session);
static void remove_tables(db::DBSession &session);
private:
static const struct DBRecordElementDesc desc_desc;
static const struct DBRecordElementDesc symbol_desc;
};
}
}
#include "category_impl.hh"
#endif

View File

@ -0,0 +1,11 @@
#ifndef INVENTORY_CATEGORY_IMPL_HH_
#define INVENTORY_CATEGORY_IMPL_HH_
#include "dbassocobject_impl.hh"
namespace inventory {
namespace datamodel {
}
}
#endif

View File

@ -0,0 +1,38 @@
#ifndef INVENTORY_CXX_SEMANTICS_HH_
#define INVENTORY_CXX_SEMANTICS_HH_
#include <cstddef>
namespace inventory {
namespace semantics {
class non_constructible {
non_constructible() {}
};
template<class Derived>
class singleton {
public:
static Derived &instance() {
if (s_instance == NULL)
s_instance = new Derived;
return *s_instance;
}
protected:
singleton() {}
private:
static Derived *s_instance;
};
template<typename... Args>
class visitor {
public:
virtual void apply(Args... args) = 0;
};
}
}
#include "cxx-semantics_impl.hh"
#endif

View File

@ -0,0 +1,15 @@
#ifndef INVENTORY_CXX_SEMANTICS_IMPL_HH_
#define INVENTORY_CXX_SEMANTICS_IMPL_HH_
#include "cxx-semantics.hh"
#include <cstddef>
namespace inventory {
namespace semantics {
template<class Derived>
Derived *singleton<Derived>::s_instance = NULL;
}
}
#endif

View File

@ -0,0 +1,43 @@
#ifndef INVENTORY_DBASSOCHIERARCHY_HH_
#define INVENTORY_DBASSOCHIERARCHY_HH_
#include "dbcontainer.hh"
#include "dbobjectvec.hh"
#include "dbobjectptr.hh"
#include "dbobject.hh"
namespace inventory {
namespace datamodel {
template<class Object, class Derived>
class DBAssocObject : public DBNamedObject<Derived> {
public:
template<template<class> class Container = datamodel::DBObjectVector>
datamodel::DBContainerPtr<Object, Container> get_objects(
db::DBSession &session,
datamodel::DBObjectInserter<Object, Container> inserter =
datamodel::DBObjectInserterImpl<Object, Container>::inserter,
datamodel::DBContainerPtr<Object, Container> pcontainer = {},
int offset = 0, int limit = -1) const;
template<template<class> class Container = datamodel::DBObjectVector>
static datamodel::DBContainerPtr<Derived, Container> get_assoc(
db::DBSession &session, const Object& object,
datamodel::DBObjectInserter<Derived, Container> inserter =
datamodel::DBObjectInserterImpl<Derived, Container>::inserter,
datamodel::DBContainerPtr<Derived, Container> pcontainer = {},
int offset = 0, int limit = -1);
protected:
static void create_membership_table(db::DBSession &session);
};
template<class Object, class Derived>
class DBAssocHierarchicalObject : public DBAssocObject<Object, Derived>,
public DBHierarchicalObject<Derived> {
public:
};
}
}
#endif

View File

@ -0,0 +1,81 @@
#ifndef INVENTORY_DBASSOCHIERARCHY_IMPL_HH_
#define INVENTORY_DBASSOCHIERARCHY_IMPL_HH_
#include "category.hh"
#include "dbcontainer.hh"
#include "dboperations.hh"
#include "dbsession.hh"
namespace inventory {
namespace datamodel {
template<class Object, class Derived>
template<template<class> class Container>
DBContainerPtr<Object, Container> DBAssocObject<Object, Derived>::get_objects(
db::DBSession &session,
DBObjectInserter<Object, Container> inserter,
DBContainerPtr<Object, Container> pcontainer, int offset, int limit) const {
const std::string prepared_stmt =
std::string("SELECT * FROM ") +
Object::sc_view +
std::string(" AS object JOIN ") + Derived::sc_membership_table +
std::string(" AS membr") +
std::string(" WHERE object.id = membr.object_id AND") +
std::string(" membr.assoc_id = ?") +
std::string(" ORDER BY object.name ASC LIMIT ? OFFSET ?");
auto stmt_cons =
[this](void *, PreparedStatement_T s, int offset, int limit) {
PreparedStatement_setInt(s, 1, (int)(DBObject<Derived>::id));
PreparedStatement_setInt(s, 2, limit);
PreparedStatement_setInt(s, 3, offset);
};
return db::DBOperations::get_all<Object, void *, Container>(session, NULL,
prepared_stmt.c_str(), stmt_cons, inserter, pcontainer, offset, limit);
}
template<class Object, class Derived>
template<template<class> class Container>
DBContainerPtr<Derived, Container> DBAssocObject<Object, Derived>::get_assoc(
db::DBSession &session, const Object& object,
DBObjectInserter<Derived, Container> inserter,
DBContainerPtr<Derived, Container> pcontainer, int offset, int limit) {
const std::string prepared_stmt =
std::string("SELECT * FROM ") +
Derived::sc_view +
std::string(" AS assoc JOIN ") + Derived::sc_membership_table +
std::string(" AS membr") +
std::string(" WHERE assoc.id = membr.object_id AND") +
std::string(" membr.assoc_id = ?") +
std::string(" ORDER BY assoc.name ASC LIMIT ? OFFSET ?");
auto stmt_cons =
[object](void *, PreparedStatement_T s, int offset, int limit) {
PreparedStatement_setInt(s, 1, (int)(object.id));
PreparedStatement_setInt(s, 2, limit);
PreparedStatement_setInt(s, 3, offset);
};
return db::DBOperations::get_all<Derived, void *, Container>(session, NULL,
prepared_stmt.c_str(), stmt_cons, inserter, pcontainer, offset, limit);
}
template<class Object, class Derived>
void DBAssocObject<Object, Derived>::create_membership_table(
db::DBSession &session) {
std::string membership_table =
std::string("CREATE TABLE \"") + Derived::sc_dbclass + "_membership\" ( \n\
`object_id` INTEGER NOT NULL, \n\
`assoc_id` INTEGER NOT NULL, \n\
FOREIGN KEY(`object_id`) REFERENCES " + Object::sc_table + " ( id ), \n\
FOREIGN KEY(`assoc_id`) REFERENCES " + Derived::sc_table + "( id ) );";
db::DBOperations::execute<void *>(session, NULL, membership_table.c_str(),
[](void *, PreparedStatement_T s) {});
}
}
}
#include "dbobject_impl.hh"
#endif

View File

@ -0,0 +1,41 @@
#ifndef INVENTORY_DBCONTAINER_HH_
#define INVENTORY_DBCONTAINER_HH_
#include "dbobjectvec.hh"
#include "dbobjectmap.hh"
#include "dbobjectptr.hh"
#include <memory>
#include <algorithm>
namespace inventory {
namespace datamodel {
template<class T, template<class> class Container>
using DBObjectInserter =
std::function<void(datamodel::DBObjectPtr<T>, Container<T> &)>;
template<class T, template<class> class Container>
class DBObjectInserterImpl {};
template<class T>
class DBObjectInserterImpl<T, DBObjectVector> {
public:
static void inserter(DBObjectPtr<T> el, DBObjectVector<T> &c) {
c.push_back(el);
}
};
template<class T>
class DBObjectInserterImpl<T, DBObjectMap> {
public:
static void inserter(DBObjectPtr<T> el, DBObjectMap<T> &c) {
c.insert(std::make_pair((int)(el->id), el));
}
};
template<class T, template<class> class Container = DBObjectVector>
using DBContainerPtr = std::shared_ptr<Container<T>>;
}
}
#endif

View File

@ -0,0 +1,15 @@
#ifndef INVENTORY_DBCONTAINERPTR_HH_
#define INVENTORY_DBCONTAINERPTR_HH_
#include "dbobjectvec.hh"
#include <memory>
namespace inventory {
namespace datamodel {
template<class T, template<class> class Container>
using DBContainerPtr = std::shared_ptr<Container<T>>;
}
}
#endif

134
source/include/dbfield.hh Normal file
View File

@ -0,0 +1,134 @@
#ifndef INVENTORY_DBFIELD_HH
#define INVENTORY_DBFIELD_HH
#include <string>
#include <vector>
#include <zdb.h>
#include "cxx-semantics.hh"
namespace inventory {
namespace datamodel {
template<class Derived>
class DBObject;
enum class DBRecordElementType {
UNKNOWN,
INTEGER,
UNSIGNED_INTEGER,
TEXT,
BLOB
};
struct DBRecordElementDesc {
const char *column_name;
const char *name;
const char *description;
};
// specialized for each class in DBObject tree
template<template<class> class Object>
class DBRecordDescTempl : semantics::non_constructible {};
template<class Object>
class DBRecordDesc : semantics::non_constructible {};
template<class Derived>
class DBRecordElement {
public:
DBRecordElement(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc);
const struct DBRecordElementDesc *element_description() {
return mcp_elem_desc;
}
DBRecordElementType type() const {
return m_type;
}
virtual void from_query(ResultSet_T) {};
virtual void insert_value(PreparedStatement_T stmt, int pos) const {};
protected:
const DBObject<Derived> * const m_parent;
const struct DBRecordElementDesc * const mcp_elem_desc;
DBRecordElementType m_type;
};
template<class Data, class Derived>
class DBField : public DBRecordElement<Derived> {
public:
DBField(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc);
DBField operator=(const DBField<Data, Derived> &assigned) {
m_value = assigned.m_value;
return *this;
}
Data operator=(const Data &assigned) {
return m_value = assigned;
}
operator Data() const {
return m_value;
}
virtual void from_query(ResultSet_T r) {};
virtual void insert_value(PreparedStatement_T stmt, int pos) const {};
protected:
Data m_value;
};
template<class Derived>
class DBInt : public DBField<int, Derived> {
public:
DBInt(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc);
using DBField<int, Derived>::operator =;
void from_query(ResultSet_T r);
void insert_value(PreparedStatement_T stmt, int pos) const;
};
template<class Derived>
class DBUnsignedInt : public DBField<unsigned int, Derived> {
public:
DBUnsignedInt(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc);
using DBField<unsigned int, Derived>::operator =;
void from_query(ResultSet_T r);
void insert_value(PreparedStatement_T stmt, int pos) const;
};
template<class Derived>
class DBString : public DBField<std::string, Derived> {
public:
DBString(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc);
using DBField<std::string, Derived>::operator =;
void from_query(ResultSet_T r);
void insert_value(PreparedStatement_T stmt, int pos) const;
const char *c_str() const;
};
template<class Derived>
class DBBlob : public DBField<std::vector<char>, Derived> {
public:
DBBlob(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc);
using DBField<std::vector<char>, Derived>::operator =;
void from_query(ResultSet_T r);
void insert_value(PreparedStatement_T stmt, int pos) const;
};
}
}
#endif

View File

@ -0,0 +1,126 @@
#ifndef INVENTORY_DBFIELD_IMPL_HH_
#define INVENTORY_DBFIELD_IMPL_HH_
#include "dbobject.hh"
#include "dbobject_impl.hh"
#include "dbfield.hh"
#include <vector>
#include <string>
namespace inventory {
namespace datamodel {
template<class Data, class Derived>
DBField<Data, Derived>::DBField(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc)
: DBRecordElement<Derived>(parent, elem_desc) {}
template<class Derived>
DBInt<Derived>::DBInt(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc)
: DBField<int, Derived>(parent, elem_desc) {
// neither clang nor gcc would recognize m_value
// w/o explicit class path, not sure why
DBField<int, Derived>::m_value = -1;
DBRecordElement<Derived>::m_type = DBRecordElementType::INTEGER;
}
template<class Derived>
DBUnsignedInt<Derived>::DBUnsignedInt(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc)
: DBField<unsigned int, Derived>(parent, elem_desc) {
DBField<unsigned int, Derived>::m_value = 0;
DBRecordElement<Derived>::m_type = DBRecordElementType::UNSIGNED_INTEGER;
}
template<class Derived>
DBString<Derived>::DBString(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc)
: DBField<std::string, Derived>(parent, elem_desc) {
DBRecordElement<Derived>::m_type = DBRecordElementType::TEXT;
}
template<class Derived>
DBBlob<Derived>::DBBlob(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc)
: DBField<std::vector<char>, Derived>(parent, elem_desc) {
DBRecordElement<Derived>::m_type = DBRecordElementType::BLOB;
}
template<class Derived>
const char *DBString<Derived>::c_str() const {
return DBField<std::string, Derived>::m_value.c_str();
}
template<class Derived>
DBRecordElement<Derived>::DBRecordElement(const DBObject<Derived> *parent,
const struct DBRecordElementDesc *elem_desc)
: m_parent(parent), mcp_elem_desc(elem_desc) {
if (parent != NULL)
parent->register_element(this);
}
template<class Derived>
void DBInt<Derived>::from_query(ResultSet_T r) {
DBField<int, Derived>::m_value =
ResultSet_getIntByName(r,
DBRecordElement<Derived>::mcp_elem_desc->column_name);
}
template<class Derived>
void DBInt<Derived>::insert_value(PreparedStatement_T stmt, int pos) const {
PreparedStatement_setInt(stmt, pos, DBField<int, Derived>::m_value);
}
template<class Derived>
void DBUnsignedInt<Derived>::from_query(ResultSet_T r) {
DBField<unsigned int, Derived>::m_value =
ResultSet_getIntByName(r,
DBRecordElement<Derived>::mcp_elem_desc->column_name);
}
template<class Derived>
void DBUnsignedInt<Derived>::insert_value(PreparedStatement_T stmt, int pos) const {
PreparedStatement_setInt(stmt, pos, DBField<unsigned int, Derived>::m_value);
}
template<class Derived>
void DBString<Derived>::from_query(ResultSet_T r) {
const char * pname =
ResultSet_getStringByName(r,
DBRecordElement<Derived>::mcp_elem_desc->column_name);
if (pname != NULL)
DBField<std::string, Derived>::m_value = pname;
}
template<class Derived>
void DBString<Derived>::insert_value(PreparedStatement_T stmt, int pos) const {
PreparedStatement_setString(stmt, pos,
DBField<std::string, Derived>::m_value.c_str());
}
template<class Derived>
void DBBlob<Derived>::from_query(ResultSet_T r) {
int symbol_size;
// returns void* by default, and std::copy needs type size info
const char *psymbol = (const char *)(ResultSet_getBlobByName(r,
DBRecordElement<Derived>::mcp_elem_desc->column_name,
&symbol_size));
if (psymbol != NULL) {
DBField<std::vector<char>, Derived>::m_value.resize(symbol_size);
std::copy(psymbol, psymbol + symbol_size,
std::back_inserter(DBField<std::vector<char>, Derived>::m_value));
}
}
template<class Derived>
void DBBlob<Derived>::insert_value(PreparedStatement_T stmt, int pos) const {
PreparedStatement_setBlob(stmt, pos,
&DBField<std::vector<char>, Derived>::m_value.front(),
DBField<std::vector<char>, Derived>::m_value.size());
}
}
}
#endif

164
source/include/dbobject.hh Normal file
View File

@ -0,0 +1,164 @@
#ifndef INVENTORY_DBOBJECT_HH_
#define INVENTORY_DBOBJECT_HH_
#include <string>
#include <map>
#include <zdb.h>
#include "dbcontainer.hh"
#include "dbobjectptr.hh"
#include "dbcontainerptr.hh"
#include "dbfield.hh"
#include "cxx-semantics.hh"
namespace inventory {
namespace db {
class DBSession;
}
namespace datamodel {
template<class Derived>
class DBObject;
template<>
class DBRecordDescTempl<DBObject> : semantics::non_constructible {
public:
static const struct DBRecordElementDesc id_desc;
};
template<class Derived>
class DBObject {
template<class T>
friend class DBRecordElement;
public:
typedef std::map<std::string, DBRecordElement<Derived> *> RecordElementMap;
protected:
void register_element(DBRecordElement<Derived> *el) const;
// must be initialized before table elements
mutable RecordElementMap m_record_elements;
public:
enum class Id {
ROOT = -1,
UNDEFINED = -2
};
DBInt<Derived> id;
DBObject();
virtual ~DBObject() {}
static datamodel::DBObjectPtr<Derived> get(db::DBSession &session, int id);
template<template<class> class Container = datamodel::DBObjectVector>
static datamodel::DBContainerPtr<Derived, Container> get_all(
db::DBSession &session,
datamodel::DBObjectInserter<Derived, Container> inserter =
datamodel::DBObjectInserterImpl<Derived, Container>::inserter,
datamodel::DBContainerPtr<Derived, Container> pcontainer = {},
int offset = 0, int limit = -1);
// Table fields are initialized only in DBObject class,
// but there may be some additional information to be
// taken care of lower in the hierarchy, like children
// count in DBHierarchicalObject.
// Implementations still must call DBObject function.
virtual void from_query(ResultSet_T);
void insert(db::DBSession &sess) const;
void update(db::DBSession &sess) const;
void remove(db::DBSession &sess) const;
void refresh(db::DBSession &sess);
DBRecordElement<Derived> *operator[](const char *column_name) {
return m_record_elements[column_name];
}
const RecordElementMap &record_element_map() {
return m_record_elements;
}
std::string prepared_statement_columns() const;
std::string prepared_statement_value_placeholders() const;
bool is_root() const {
return id == (int)(Id::ROOT);
}
private:
void insert_values(PreparedStatement_T s) const;
};
template<class Derived>
class DBNamedObject;
template<>
class DBRecordDescTempl<DBNamedObject> : semantics::non_constructible {
public:
static const struct DBRecordElementDesc name_desc;
};
template<class Derived>
class DBNamedObject : public virtual DBObject<Derived> {
public:
DBString<Derived> name;
DBNamedObject();
virtual ~DBNamedObject() {}
static datamodel::DBObjectPtr<Derived> get(db::DBSession &session,
const char *name);
};
template<class Derived>
class DBHierarchicalObject;
template<>
class DBRecordDescTempl<DBHierarchicalObject> : semantics::non_constructible {
public:
static const struct DBRecordElementDesc sup_desc;
static const struct DBRecordElementDesc nsub_desc;
};
template<class Derived>
class DBHierarchicalObject : public virtual DBObject<Derived> {
public:
DBInt<Derived> sup;
// generated, not part of the table
DBInt<Derived> nsub;
DBHierarchicalObject();
virtual ~DBHierarchicalObject() {}
template<template<class> class Container = datamodel::DBObjectVector>
static datamodel::DBContainerPtr<Derived, Container> get_children(
db::DBSession &session, int id,
datamodel::DBObjectInserter<Derived, Container> inserter =
datamodel::DBObjectInserterImpl<Derived, Container>::inserter,
datamodel::DBContainerPtr<Derived, Container> pcontainer = {},
int offset = 0, int limit = -1);
template<template<class> class Container = datamodel::DBObjectVector>
datamodel::DBContainerPtr<Derived, Container> get_children(
db::DBSession &session,
datamodel::DBObjectInserter<Derived, Container> inserter =
datamodel::DBObjectInserterImpl<Derived, Container>::inserter,
datamodel::DBContainerPtr<Derived, Container> pcontainer = {},
int offset = 0, int limit = -1);
datamodel::DBObjectPtr<Derived> get_parent(db::DBSession &session);
virtual void from_query(ResultSet_T);
protected:
static void create_sub_view(db::DBSession &session);
static void create_sup_view(db::DBSession &session);
};
}
}
#endif

View File

@ -0,0 +1,280 @@
#ifndef INVENTORY_DBOBJECT_IMPL_HH_
#define INVENTORY_DBOBJECT_IMPL_HH_
#include <string>
#include "dbfield.hh"
#include "dbfield_impl.hh"
#include "dbobject.hh"
#include "dboperations.hh"
namespace inventory {
namespace datamodel {
template<class Derived>
DBObject<Derived>::DBObject()
: m_record_elements(),
// used in SQL statements, left unregistered
id(NULL, &DBRecordDescTempl<DBObject>::id_desc) {
id = (int)(DBObject<Derived>::Id::UNDEFINED);
}
template<class Derived>
DBNamedObject<Derived>::DBNamedObject()
: name(this, &DBRecordDescTempl<DBNamedObject>::name_desc) {}
template<class Derived>
DBHierarchicalObject<Derived>::DBHierarchicalObject()
: sup(this, &DBRecordDescTempl<DBHierarchicalObject>::sup_desc),
nsub(NULL, &DBRecordDescTempl<DBHierarchicalObject>::nsub_desc) {
sup = (int)(DBHierarchicalObject<Derived>::Id::UNDEFINED);
nsub = -1;
}
template<class Derived>
void DBObject<Derived>::register_element(DBRecordElement<Derived> *el) const {
m_record_elements.insert(
std::make_pair(
std::string(el->element_description()->column_name), el
));
}
template<class Derived>
void DBObject<Derived>::from_query(ResultSet_T r) {
TRY {
id = ResultSet_getIntByName(r,
DBRecordDescTempl<DBObject>::id_desc.column_name);
for (const auto &el : m_record_elements)
(el.second)->from_query(r);
} CATCH(SQLException) {
}
END_TRY;
}
template<class Derived>
void DBHierarchicalObject<Derived>::from_query(ResultSet_T r) {
nsub = ResultSet_getIntByName(r,
DBRecordDescTempl<DBHierarchicalObject>::nsub_desc.column_name);
DBObject<Derived>::from_query(r);
}
template<class Derived>
std::string DBObject<Derived>::prepared_statement_columns() const {
std::string ret;
for (const auto &el : m_record_elements)
ret += el.first + ", ";
ret.pop_back();
ret.pop_back();
return ret;
}
template<class Derived>
std::string DBObject<Derived>::prepared_statement_value_placeholders() const {
std::string ret;
for (const auto &el : m_record_elements)
ret += "?, ";
ret.pop_back();
ret.pop_back();
return ret;
}
template<class Derived>
void DBObject<Derived>::insert(db::DBSession &session) const {
const std::string prepared_stmt =
std::string("INSERT INTO ") +
Derived::sc_table + std::string("(") +
prepared_statement_columns() +
std::string(") VALUES(") +
prepared_statement_value_placeholders() +
std::string(")");
auto stmt_cons =
[this](void *, PreparedStatement_T s) {
insert_values(s);
};
db::DBOperations::execute<void *>(session, NULL, prepared_stmt.c_str(),
stmt_cons);
}
template<class Derived>
void DBObject<Derived>::update(db::DBSession &session) const {
const std::string prepared_stmt =
std::string("UPDATE ") +
Derived::sc_table +
std::string(" SET (") +
prepared_statement_columns() +
std::string(") = (") +
prepared_statement_value_placeholders() +
std::string(") WHERE id = ?");
auto stmt_cons =
[this](void *, PreparedStatement_T s) {
insert_values(s);
};
db::DBOperations::execute<void *>(session, NULL, prepared_stmt.c_str(),
stmt_cons);
}
template<class Derived>
void DBObject<Derived>::remove(db::DBSession &session) const {
const std::string prepared_stmt =
std::string("DELETE FROM ") +
Derived::sc_table +
std::string("WHERE item_id = ?");
auto stmt_cons =
[this](void *, PreparedStatement_T s) {
PreparedStatement_setInt(s, 1, (int)(id));
};
db::DBOperations::execute<void *>(session, NULL, prepared_stmt.c_str(),
stmt_cons);
}
template<class Derived>
void DBObject<Derived>::refresh(db::DBSession &session) {
*this = *DBObject<Derived>::get(session, id);
}
template<class Derived>
void DBObject<Derived>::insert_values(PreparedStatement_T s) const {
int pos = 1;
for (const auto &el : m_record_elements) {
el.second->insert_value(s, pos++);
}
}
template<class Derived>
DBObjectPtr<Derived> DBObject<Derived>::get(db::DBSession &session, int id) {
if (id < 0)
return DBObjectPtr<Derived>(); // TODO
// I want to use underlying database's prepared
// statement system to sanititze inputs, but it's
// not possible to specify tables this way.
const std::string prepared_stmt =
std::string("SELECT * FROM ") +
Derived::sc_view +
std::string(" WHERE id=?");
auto stmt_cons =
[](int id, PreparedStatement_T s) {
PreparedStatement_setInt(s, 1, id);
};
return db::DBOperations::get_single<Derived, int>(session, id,
prepared_stmt.c_str(), stmt_cons);
}
template<class Derived>
DBObjectPtr<Derived> DBNamedObject<Derived>::
get(db::DBSession &session, const char *name) {
const std::string prepared_stmt =
std::string("SELECT * FROM ") +
Derived::sc_view +
std::string(" WHERE name=?");
auto stmt_cons =
[](const char *name, PreparedStatement_T s) {
PreparedStatement_setString(s, 1, name);
};
return db::DBOperations::get_single<Derived, const char *>(session,
name, prepared_stmt.c_str(), stmt_cons);
}
template<class Derived>
template<template<class> class Container>
DBContainerPtr<Derived, Container> DBObject<Derived>::get_all(
db::DBSession &session,
DBObjectInserter<Derived, Container> inserter,
DBContainerPtr<Derived, Container> pcontainer, int offset, int limit) {
const std::string prepared_stmt =
std::string("SELECT * FROM ") +
Derived::sc_view +
std::string(" ORDER BY name ASC LIMIT ? OFFSET ?");
auto stmt_cons =
[](void *, PreparedStatement_T s, int offset, int limit) {
PreparedStatement_setInt(s, 1, limit);
PreparedStatement_setInt(s, 2, offset);
};
return db::DBOperations::get_all<Derived, void *, Container>(session, NULL,
prepared_stmt.c_str(), stmt_cons, inserter, pcontainer, offset, limit);
}
template<class Derived>
template<template<class> class Container>
DBContainerPtr<Derived, Container> DBHierarchicalObject<Derived>::get_children(
db::DBSession &session, int id,
DBObjectInserter<Derived, Container> inserter,
DBContainerPtr<Derived, Container> pcontainer, int offset, int limit) {
const std::string prepared_stmt =
std::string("SELECT * FROM ") +
Derived::sc_view +
std::string(" WHERE parent = ?") +
std::string(" ORDER BY name ASC LIMIT ? OFFSET ?");
auto stmt_cons =
[](int id, PreparedStatement_T s, int offset, int limit) {
PreparedStatement_setInt(s, 1, id);
PreparedStatement_setInt(s, 2, limit);
PreparedStatement_setInt(s, 3, offset);
};
return db::DBOperations::get_all<Derived, int, Container>(session, id,
prepared_stmt.c_str(), stmt_cons, inserter, pcontainer,
offset, limit);
}
template<class Derived>
template<template<class> class Container>
DBContainerPtr<Derived, Container> DBHierarchicalObject<Derived>::get_children(
db::DBSession &session,
DBObjectInserter<Derived, Container> inserter,
DBContainerPtr<Derived, Container> pcontainer, int offset, int limit) {
return get_children<Container>(session, (int)(DBObject<Derived>::id), inserter,
pcontainer, offset, limit);
}
template<class Derived>
DBObjectPtr<Derived> DBHierarchicalObject<Derived>::get_parent(
db::DBSession &session) {
return get(session, sup);
}
template<class Derived>
void DBHierarchicalObject<Derived>::create_sub_view(db::DBSession &session) {
std::string sub_view = std::string("CREATE VIEW ") + Derived::sc_dbclass + "_sub AS \n\
SELECT id, 0 AS sub FROM " + Derived::sc_table + " WHERE id NOT IN (SELECT id FROM " + Derived::sc_dbclass + "_sup) \n\
UNION \n\
SELECT id, sub FROM " + Derived::sc_dbclass + "_sup;";
db::DBOperations::execute<void *>(session, NULL, sub_view.c_str(),
[](void *, PreparedStatement_T s) {});
}
template<class Derived>
void DBHierarchicalObject<Derived>::create_sup_view(db::DBSession &session) {
std::string sup_view = std::string("CREATE VIEW ") + Derived::sc_dbclass + "_sup \
AS SELECT sup AS id, COUNT(*) AS sub FROM " + Derived::sc_table + " GROUP BY sup;";
db::DBOperations::execute<void *>(session, NULL, sup_view.c_str(),
[](void *, PreparedStatement_T s) {});
}
}
}
#endif

View File

@ -0,0 +1,15 @@
#ifndef INVENTORY_OBJECT_MAP_
#define INVENTORY_OBJECT_MAP_
#include <map>
#include "dbobjectptr.hh"
namespace inventory {
namespace datamodel {
template<class T>
using DBObjectMap = std::map<int, DBObjectPtr<T>>;
}
}
#endif

View File

@ -0,0 +1,11 @@
#ifndef INVENTORY_DBOBJECTPTR_HH
#include <memory>
namespace inventory {
namespace datamodel {
template <class T>
using DBObjectPtr = std::shared_ptr<T>;
}
}
#endif

View File

@ -0,0 +1,67 @@
#ifndef INVENTORY_OBJECT_TREE_HH_
#define INVENTORY_OBJECT_TREE_HH_
#include "dbobjectptr.hh"
#include "dbobjecttreeptr.hh"
#include "dbcontainer.hh"
#include "cxx-semantics.hh"
#include <vector>
#include <functional>
namespace inventory {
namespace db {
class DBSession;
}
namespace datamodel {
template <class Object>
class DBObjectTree : public DBObjectTreePtrBase<Object> {
public:
template <class T>
using SubtreeContainer = std::vector<DBObjectTreePtr<T>>;
typedef DBContainerPtr<Object, SubtreeContainer>
SubtreeContainerPtr;
class SubtreeVisitor :
public semantics::visitor<DBObjectPtr<Object>, int, int> {};
// visibility outside of DBObjectTree
typedef SubtreeVisitor Visitor;
DBObjectTree() {
m_object.reset(new Object);
m_object->id = (int)(Object::Id::ROOT);
}
DBObjectTree(DBObjectPtr<Object> obj)
: m_object(obj) {}
DBObjectTree(DBObjectPtr<Object> obj, DBObjectTreePtr<Object> suptree)
: m_object(obj), m_suptree(suptree) {}
DBObjectPtr<Object> object() {
return m_object;
}
SubtreeContainerPtr subtree() {
return m_subtree;
}
void fetch_object(db::DBSession &);
void fetch_subtree(db::DBSession &);
void recurse_subtrees(db::DBSession &session, Visitor &visitor,
int max_level = -1, int level = 0);
private:
DBObjectPtr<Object> m_object;
DBObjectTreePtr<Object> m_suptree;
SubtreeContainerPtr m_subtree;
};
}
}
#endif

View File

@ -0,0 +1,59 @@
#ifndef INVENTORY_OBJECT_TREE_IMPL_HH_
#define INVENTORY_OBJECT_TREE_IMPL_HH_
#include "dbobjecttree.hh"
#include "dbobject.hh"
#include "dbsession.hh"
#include "dbcontainer.hh"
#include "dbcontainerptr.hh"
#include <vector>
namespace inventory {
namespace datamodel {
template<class Object>
void DBObjectTree<Object>::fetch_object(db::DBSession &session) {
m_object->refresh(session);
}
template<class Object>
void DBObjectTree<Object>::fetch_subtree(db::DBSession &session) {
if (!m_object)
fetch_object(session);
if (!m_subtree)
m_subtree.reset(new SubtreeContainer<Object>);
auto inserter =
[this](DBObjectPtr<Object> el, SubtreeContainer<Object> &c) {
DBObjectTreePtr<Object> subtree(new DBObjectTree<Object>(el,
DBObjectTree<Object>::shared_from_this()));
c.push_back(subtree);
};
(*m_object).template get_children<SubtreeContainer>(session,
inserter, m_subtree);
}
template<class Object>
void DBObjectTree<Object>::recurse_subtrees(db::DBSession &session,
DBObjectTree<Object>::Visitor &visitor, int max_level,
int level) {
if (!m_subtree)
fetch_subtree(session);
visitor.apply(m_object, max_level, level);
if (max_level != -1 && level >= max_level)
return;
if (m_subtree) {
for (DBObjectTreePtr<Object> subtree : *m_subtree) {
subtree->recurse_subtrees(session, visitor, max_level, level + 1);
}
}
}
}
}
#endif

View File

@ -0,0 +1,20 @@
#ifndef INVENTORY_DBOBJECTTREEPTR_HH_
#define INVENTORY_DBOBJECTTREEPTR_HH_
#include <memory>
namespace inventory {
namespace datamodel {
template <class T>
class DBObjectTree;
template <class T>
using DBObjectTreePtr = std::shared_ptr<DBObjectTree<T>>;
template <class T>
using DBObjectTreePtrBase = std::enable_shared_from_this<DBObjectTree<T>>;
}
}
#endif

View File

@ -0,0 +1,15 @@
#ifndef INVENTORY_DBOBJECTVEC_HH_
#define INVENTORY_DBOBJECTVEC_HH_
#include "dbobjectptr.hh"
#include <vector>
namespace inventory {
namespace datamodel {
template <class T>
using DBObjectVector = std::vector<DBObjectPtr<T>>;
}
}
#endif

View File

@ -0,0 +1,60 @@
#ifndef INVENTORY_DBOPERATIONS_HH
#define INVENTORY_DBOPERATIONS_HH
#include <string>
#include <stdexcept>
#include <functional>
#include <zdb.h>
#include "dbobjectptr.hh"
#include "dbobjectvec.hh"
#include "dbcontainer.hh"
namespace inventory {
namespace datamodel {
template<class Derived>
class DBObject;
}
namespace db {
class DBSession;
class DBOperations {
DBOperations() {}
public:
template<class K>
using StatementConstructorExecute = std::function<void(K, PreparedStatement_T)>;
template<class K>
using StatementConstructorSingle = std::function<void(K, PreparedStatement_T)>;
// + offset, limit
template<class K>
using StatementConstructor = std::function<void(K, PreparedStatement_T, int, int)>;
// only a statement constructor argument class here, operation is the same
// for whole database object hierarchy
template<class K>
static void execute(
DBSession &session, const K &stmt_arg, const char *prepared_stmt,
StatementConstructorExecute<K> cons);
template<class T, class K>
static datamodel::DBObjectPtr<T> get_single(
DBSession &session, const K &stmt_arg, const char *prepared_stmt,
StatementConstructorSingle<K> cons);
template<class T, class K,
template<class> class Container = datamodel::DBObjectVector>
static datamodel::DBContainerPtr<T, Container> get_all(
DBSession& session, const K &stmt_arg,
const char *prepared_stmt, StatementConstructor<K> cons,
datamodel::DBObjectInserter<T, Container> inserter,
datamodel::DBContainerPtr<T, Container> pcontainer,
int offset, int limit);
};
}
}
#endif

View File

@ -0,0 +1,92 @@
#ifndef INVENTORY_DBOPERATIONS_IMPL_HH_
#define INVENTORY_DBOPERATIONS_IMPL_HH_
#include "dboperations.hh"
#include "dbsession.hh"
#include "dbobject.hh"
#include "log.hh"
#include <zdb.h>
// refer to libzdb docs
#include <Exception.h>
namespace inventory {
namespace db {
template<class K>
void DBOperations::execute(
DBSession &session, const K &stmt_arg, const char *prepared_stmt,
StatementConstructorSingle<K> cons) {
LIBINVDB_LOG(Loglevel::DEBUG, std::string("[QUERY] ") + prepared_stmt);
Connection_T conn = session.get_connection();
PreparedStatement_T s = Connection_prepareStatement(conn, prepared_stmt);
cons(stmt_arg, s);
TRY {
PreparedStatement_execute(s);
} CATCH(SQLException) {
// TODO exception wrapper
}
END_TRY;
session.return_connection(conn);
}
template<class T, class K>
datamodel::DBObjectPtr<T> DBOperations::get_single(
DBSession &session, const K &stmt_arg, const char *prepared_stmt,
StatementConstructorSingle<K> cons) {
LIBINVDB_LOG(Loglevel::DEBUG, std::string("[QUERY] ") + prepared_stmt);
Connection_T conn = session.get_connection();
PreparedStatement_T s = Connection_prepareStatement(conn, prepared_stmt);
cons(stmt_arg, s);
ResultSet_T r = PreparedStatement_executeQuery(s);
ResultSet_next(r); // with unique key, only one item is expected
if (r == NULL)
throw std::runtime_error("No such item");
datamodel::DBObjectPtr<T> ret(new T);
ret->from_query(r);
session.return_connection(conn);
return ret;
}
template<class T, class K, template<class> class Container>
datamodel::DBContainerPtr<T, Container> DBOperations::get_all(
DBSession &session, const K &stmt_arg,
const char *prepared_stmt, StatementConstructor<K> cons,
datamodel::DBObjectInserter<T, Container> inserter,
datamodel::DBContainerPtr<T, Container> pcontainer,
int offset, int limit) {
LIBINVDB_LOG(Loglevel::DEBUG, std::string("[QUERY] ") + prepared_stmt);
if (!pcontainer)
pcontainer.reset(new Container<T>);
Connection_T conn = session.get_connection();
PreparedStatement_T s = Connection_prepareStatement(conn,
prepared_stmt);
cons(stmt_arg, s, offset, limit);
ResultSet_T r = PreparedStatement_executeQuery(s);
while (ResultSet_next(r)) {
datamodel::DBObjectPtr<T> el(new T);
el->from_query(r);
inserter(el, *pcontainer);
}
session.return_connection(conn);
return pcontainer;
}
}
}
#include "dbobject_impl.hh"
#endif

View File

@ -0,0 +1,38 @@
#ifndef INVENTORY_DBSESSION_HH_
#define INVENTORY_DBSESSION_HH_
#include <string>
#include <stdexcept>
#include <functional>
#include <zdb.h>
#include "dbobjectptr.hh"
#include "dbobjectvec.hh"
#include "dbcontainer.hh"
#include "dboperations.hh"
namespace inventory {
namespace db {
class DBSession {
public:
DBSession() {}
DBSession(const char *url);
~DBSession();
Connection_T get_connection();
void return_connection(Connection_T);
void reset(const char *url);
private:
void create_connpool();
void destroy_connpool();
std::string m_url;
ConnectionPool_T m_pool;
};
}
}
#include "dboperations_impl.hh"
#endif

View File

@ -2,6 +2,7 @@
#define INVENTORY_GROUP_HH_
#include <string>
#include <vector>
#include "category.hh"
namespace inventory {
namespace datamodel {

View File

@ -1,31 +1,67 @@
#ifndef INVENTORY_ITEM_HH_
#define INVENTORY_ITEM_HH_
#include "session.hh"
#include <zdb.h>
#include <string>
#include <vector>
#include "dbobjectptr.hh"
#include "dbcontainer.hh"
#include "dbsession.hh"
#include "dbobject.hh"
#include "dbfield.hh"
#include "cxx-semantics.hh"
namespace inventory {
namespace datamodel {
class Item {
class Item;
template<>
class DBRecordDesc<Item> : semantics::non_constructible {
public:
// raw data in table order
// no need for deeper abstraction here
int id;
int parent;
std::string name;
std::string description;
unsigned int time_added;
unsigned int time_spoiled;
std::string cash_worth;
int currency_id;
static const struct DBRecordElementDesc desc_desc;
static const struct DBRecordElementDesc time_added_desc;
static const struct DBRecordElementDesc time_spoiled_desc;
static const struct DBRecordElementDesc cash_worth_desc;
static const struct DBRecordElementDesc currency_desc;
static const struct DBRecordElementDesc long_axis_desc;
static const struct DBRecordElementDesc short_axis_desc;
static const struct DBRecordElementDesc height_desc;
static const struct DBRecordElementDesc mass_desc;
static const struct DBRecordElementDesc url_desc;
};
static const char *sc_table;
class Item : public DBNamedObject<Item>, public DBHierarchicalObject<Item> {
public:
static const char *sc_table;
static const char *sc_view;
static const char *sc_dbclass;
// these may throw, can't do it in copy constructor
static Item from_query(ResultSet_T);
DBString<Item> description;
DBUnsignedInt<Item> time_added;
DBUnsignedInt<Item> time_spoiled;
DBInt<Item> cash_worth;
DBInt<Item> currency_id;
DBInt<Item> long_axis;
DBInt<Item> short_axis;
DBInt<Item> height;
DBInt<Item> mass;
DBString<Item> url;
Item()
: description(this, &DBRecordDesc<Item>::desc_desc),
time_added(this, &DBRecordDesc<Item>::time_added_desc),
time_spoiled(this, &DBRecordDesc<Item>::time_spoiled_desc),
cash_worth(this, &DBRecordDesc<Item>::cash_worth_desc),
currency_id(this, &DBRecordDesc<Item>::currency_desc),
long_axis(this, &DBRecordDesc<Item>::long_axis_desc),
short_axis(this, &DBRecordDesc<Item>::short_axis_desc),
height(this, &DBRecordDesc<Item>::height_desc),
mass(this, &DBRecordDesc<Item>::mass_desc),
url(this, &DBRecordDesc<Item>::url_desc) {}
static void create_tables(db::DBSession &session);
static void validate_tables(db::DBSession &session);
static void remove_tables(db::DBSession &session);
};
}
}
#endif

View File

@ -0,0 +1,6 @@
#ifndef INVENTORY_ITEM_IMPL_HH_
#define INVENTORY_ITEM_IMPL_HH_
#include "item.hh"
#include "dbobject_impl.hh"
#endif

39
source/include/log.hh Normal file
View File

@ -0,0 +1,39 @@
#ifndef INVENTORY_LOG_HH_
#define INVENTORY_LOG_HH_
#include "logstream.hh"
#include "cxx-semantics.hh"
#include <memory>
#define LIBINVDB_LOG(lvl, str) Log::instance()(lvl, "[" + \
std::string(__func__) + " @ " + std::string(__FILE__) + ":" + \
std::to_string(__LINE__) + "] " + std::string(str))
namespace inventory {
class Log : public semantics::singleton<Log> {
public:
Log()
: m_loglevel(Loglevel::WARNING) {}
void set_stream(std::shared_ptr<Logstream> stream) {
mp_logstream = stream;
}
void set_loglevel(Loglevel lvl) {
m_loglevel = lvl;
}
void operator()(Loglevel lvl, const std::string &msg) {
if ((int)(lvl) <= (int)(m_loglevel))
(*mp_logstream)(lvl, msg);
}
private:
static std::shared_ptr<Logstream> mp_logstream;
Loglevel m_loglevel;
};
}
#endif

View File

@ -0,0 +1,33 @@
#ifndef INVENTORY_LOGSTREAM_HH_
#define INVENTORY_LOGSTREAM_HH_
#include <cstddef>
#include <string>
#include <iostream>
namespace inventory {
enum class Loglevel {
CRITICAL,
WARNING,
NOTICE,
INFO,
DEBUG,
DEBUG2
};
class Logstream {
public:
virtual void operator()(Loglevel lvl, const std::string &msg) = 0;
};
class StdLogstream : public Logstream {
public:
virtual void operator()(Loglevel lvl, const std::string &msg);
private:
static const char *sc_loglevel[];
};
}
#endif

View File

@ -1,118 +0,0 @@
#ifndef INVENTORY_DBSESSION_HH
#define INVENTORY_DBSESSION_HH
#include <string>
#include <vector>
#include <stdexcept>
#include <zdb.h>
namespace inventory {
namespace db {
class DBSession {
public:
DBSession(const char *url);
Connection_T get_connection();
// database accessors for use w/ datamodel types
template<class T>
T get(int id);
template<class T>
T get(const char *name);
template<class T>
std::vector<T> get_all(int offset = 0, int limit = -1);
template<class T>
T get_parent(const T &);
private:
void create_connpool();
const std::string m_url;
ConnectionPool_T m_pool;
};
template<class T>
T DBSession::get(int id) {
// We want to use underlying database's prepared
// statement system to sanititze inputs, but it's
// not possible to specify tables this way.
const std::string prepared_stmt =
std::string("SELECT * FROM ") +
T::sc_table +
std::string(" WHERE id=?");
Connection_T conn = get_connection();
PreparedStatement_T s = Connection_prepareStatement(conn,
prepared_stmt.c_str());
PreparedStatement_setInt(s, 1, id);
ResultSet_T r = PreparedStatement_executeQuery(s);
ResultSet_next(r); // with unique key, only one item is expected
if (r == NULL)
throw std::runtime_error("No such item");
T &&ret = T::from_query(r);
Connection_close(conn);
return ret;
}
template<class T>
T DBSession::get(const char *name) {
const std::string prepared_stmt =
std::string("SELECT * FROM ") +
T::sc_table +
std::string(" WHERE name=?");
Connection_T conn = get_connection();
PreparedStatement_T s = Connection_prepareStatement(conn,
prepared_stmt.c_str());
PreparedStatement_setString(s, 1, name);
ResultSet_T r = PreparedStatement_executeQuery(s);
ResultSet_next(r);
if (r == NULL)
throw std::runtime_error("No such item");
T &&ret = T::from_query(r);
Connection_close(conn);
return ret;
}
template<class T>
std::vector<T> DBSession::get_all(int offset, int limit) {
const std::string prepared_stmt =
std::string("SELECT * FROM ") +
T::sc_table +
std::string(" ORDER BY name ASC LIMIT ? OFFSET ?");
Connection_T conn = get_connection();
PreparedStatement_T s = Connection_prepareStatement(conn,
prepared_stmt.c_str());
PreparedStatement_setInt(s, 1, limit);
PreparedStatement_setInt(s, 2, offset);
ResultSet_T r = PreparedStatement_executeQuery(s);
std::vector<T> ret;
while (ResultSet_next(r)) {
T &&it = T::from_query(r);
ret.push_back(it);
}
return ret;
}
template<class T>
T DBSession::get_parent(const T &t) {
return (T&&)(get(t.id));
}
}
}
#endif

View File

@ -1,5 +1,9 @@
#include "dbcontainer.hh"
#include "dbobjectptr.hh"
#include "item.hh"
#include "session.hh"
#include "category.hh"
#include "dboperations.hh"
#include "dbsession.hh"
#include <stdexcept>
#include <zdb.h>
@ -10,38 +14,155 @@ namespace inventory {
namespace datamodel {
const char *Item::sc_table = "items";
const char *Item::sc_view = "item_view";
const char *Item::sc_dbclass = "item";
Item Item::from_query(ResultSet_T r) {
Item ret;
const struct DBRecordElementDesc DBRecordDesc<Item>::desc_desc = {
"description",
"Description",
""
};
TRY {
ret.id = ResultSet_getIntByName(r, "id");
ret.parent = ResultSet_getIntByName(r, "parent");
const struct DBRecordElementDesc DBRecordDesc<Item>::time_added_desc = {
"time_added",
"Time added",
""
};
// SQL NULL case
const char *pname = ResultSet_getStringByName(r, "name");
if (pname != NULL)
ret.name = pname;
const struct DBRecordElementDesc DBRecordDesc<Item>::time_spoiled_desc = {
"time_spoiled",
"Time spoiled",
""
};
const char *pdescription = ResultSet_getStringByName(r, "description");
if (pdescription != NULL)
ret.description = pdescription;
const struct DBRecordElementDesc DBRecordDesc<Item>::cash_worth_desc = {
"cash_worth",
"",
""
};
ret.time_added = ResultSet_getIntByName(r, "time_added");
ret.time_spoiled = ResultSet_getIntByName(r, "time_spoiled");
const struct DBRecordElementDesc DBRecordDesc<Item>::currency_desc = {
"currency_id",
"",
""
};
const char *pcashworth = ResultSet_getStringByName(r, "cash_worth");
if (pcashworth != NULL)
ret.cash_worth = pcashworth;
const struct DBRecordElementDesc DBRecordDesc<Item>::long_axis_desc = {
"long_axis",
"",
""
};
ret.currency_id = ResultSet_getIntByName(r, "currency_id");
} CATCH(SQLException) {
}
END_TRY;
const struct DBRecordElementDesc DBRecordDesc<Item>::short_axis_desc = {
"short_axis",
"",
""
};
return ret;
const struct DBRecordElementDesc DBRecordDesc<Item>::height_desc = {
"height",
"",
""
};
const struct DBRecordElementDesc DBRecordDesc<Item>::mass_desc = {
"mass",
"",
""
};
const struct DBRecordElementDesc DBRecordDesc<Item>::url_desc = {
"url",
"",
""
};
void Item::create_tables(db::DBSession &session) {
const char *create_table =
"CREATE TABLE \"items\" ( \n\
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, \n\
`name` TEXT NOT NULL, \n\
`sup` INTEGER NOT NULL DEFAULT -1, \n\
`description` TEXT, \n\
`time_added` INTEGER DEFAULT -1, \n\
`time_spoiled` INTEGER DEFAULT -1, \n\
`cash_worth` INTEGER DEFAULT -1, \n\
`currency_id` INTEGER DEFAULT -1, \n\
`long_axis` INTEGER DEFAULT -1, \n\
`short_axis` INTEGER DEFAULT -1, \n\
`height` INTEGER DEFAULT -1, \n\
`mass` INTEGER DEFAULT -1, \n\
`url` TEXT, \n\
FOREIGN KEY(`sup`) REFERENCES items ( id ) );";
db::DBOperations::execute<void *>(session, NULL, create_table,
[](void *, PreparedStatement_T s) {});
DBHierarchicalObject<Item>::create_sub_view(session);
DBHierarchicalObject<Item>::create_sup_view(session);
const char *item_view =
"CREATE VIEW item_view AS SELECT * FROM item_sub JOIN items ON items.id = item_sub.id;";
db::DBOperations::execute<void *>(session, NULL, item_view,
[](void *, PreparedStatement_T s) {});
}
void Item::validate_tables(db::DBSession &session) {
}
void Item::remove_tables(db::DBSession &session) {
const char *drop_table = "DROP TABLE items";
db::DBOperations::execute<void *>(session, NULL, drop_table,
[](void *, PreparedStatement_T s) {});
const char *drop_item_sub = "DROP VIEW item_sub";
db::DBOperations::execute<void *>(session, NULL, drop_item_sub,
[](void *, PreparedStatement_T s) {});
const char *drop_item_sup = "DROP VIEW item_sup";
db::DBOperations::execute<void *>(session, NULL, drop_item_sup,
[](void *, PreparedStatement_T s) {});
const char *drop_item_view = "DROP VIEW item_view;";
db::DBOperations::execute<void *>(session, NULL, drop_item_view,
[](void *, PreparedStatement_T s) {});
}
/*
void Item::add_category(db::DBSession &session, const Category& category) const {
const std::string prepared_stmt =
std::string("INSERT INTO ") +
Category::sc_membership_table +
std::string(" (item_id, category_id) VALUES (?, ?)");
auto stmt_cons =
[this, category](void *, PreparedStatement_T s) {
PreparedStatement_setInt(s, 1, id);
PreparedStatement_setInt(s, 2, category.id);
};
db::DBOperations::execute<void *>(session, NULL, prepared_stmt.c_str(),
stmt_cons);
}
void Item::remove_category(db::DBSession &session, const Category& category) const {
const std::string prepared_stmt =
std::string("DELETE FROM ") +
Category::sc_membership_table +
std::string("WHERE item_id = ?");
auto stmt_cons =
[this](void *, PreparedStatement_T s) {
PreparedStatement_setInt(s, 1, id);
};
db::DBOperations::execute<void *>(session, NULL, prepared_stmt.c_str(),
stmt_cons);
}
*/
}
}

10
source/log.cc Normal file
View File

@ -0,0 +1,10 @@
#include "log.hh"
#include "logstream.hh"
#include <memory>
namespace inventory {
std::shared_ptr<Logstream> Log::mp_logstream(new StdLogstream);
}

19
source/logstream.cc Normal file
View File

@ -0,0 +1,19 @@
#include "logstream.hh"
#include <cstddef>
#include <iostream>
namespace inventory {
const char *StdLogstream::sc_loglevel[] = {
"CRITICAL",
"WARNING",
"NOTICE",
"INFO",
"DEBUG"
};
void StdLogstream::operator()(Loglevel lvl, const std::string &msg) {
std::cerr << "[" << sc_loglevel[(int)(lvl)] << "] " << msg << std::endl;
}
}

42
unittest/category_test.cc Normal file
View File

@ -0,0 +1,42 @@
#include "category.hh"
#include "category_impl.hh"
#include "item.hh"
#include "item_impl.hh"
#include "dbsession.hh"
#include "dbobjectvec.hh"
#include <iostream>
using namespace inventory;
using namespace datamodel;
using namespace inventory::db;
int main(int argc, char *argv[]) {
DBSession session(argv[1]);
return 0;
std::cout << "Categories' items" << std::endl;
DBContainerPtr<Category> cats = Category::get_all(session);
for (DBObjectPtr<Category> cat : *cats) {
std::cout << (std::string)(cat->name) << std::endl;
DBContainerPtr<Item> items = cat->get_objects(session);
for (DBObjectPtr<Item> it : *items) {
std::cout << "\t" << (std::string)(it->name) << std::endl;
}
}
std::cout << "Items' categories" << std::endl;
DBContainerPtr<Item> items = Item::get_all(session);
for (DBObjectPtr<Item> item : *items) {
std::cout << (std::string)(item->name) << std::endl;
DBContainerPtr<Category> cats = Category::get_assoc(session, *item);
for (DBObjectPtr<Category> cat : *cats) {
std::cout << "\t" << (std::string)(cat->name) << std::endl;
}
}
return 0;
}

View File

@ -0,0 +1,28 @@
#include "dbobjectptr.hh"
#include "dbobjectvec.hh"
#include "dbcontainer.hh"
#include "item.hh"
#include "item_impl.hh"
#include "dbsession.hh"
#include <iostream>
using namespace inventory;
using namespace datamodel;
using namespace inventory::db;
using namespace std;
int main(int argc, char *argv[]) {
DBSession session(argv[1]);
return 0;
{
DBContainerPtr<Item> items = Item::get_all(session);
for (DBObjectPtr<Item> &it : *items) {
cout << it->name.c_str() << "\t" << it->nsub << endl;
}
}
return 0;
}

39
unittest/get.cc Normal file
View File

@ -0,0 +1,39 @@
#include "dbobjectptr.hh"
#include "dbobjectvec.hh"
#include "dbcontainer.hh"
#include "item.hh"
#include "item_impl.hh"
#include "dbsession.hh"
#include <iostream>
using namespace inventory;
using namespace datamodel;
using namespace inventory::db;
using namespace std;
int main(int argc, char *argv[]) {
DBSession session(argv[1]);
return 0;
std::cout << "Items via vector: " << std::endl;
{
DBContainerPtr<Item> items = Item::get_all(session);
for (DBObjectPtr<Item> &it : *items) {
cout << it->name.c_str() << endl;
}
}
std::cout << "Items via map: " << std::endl;
{
DBContainerPtr<Item, DBObjectMap> items
= Item::get_all<DBObjectMap>(session);
for (auto &map_pair : *items) {
DBObjectPtr<Item> it = map_pair.second;
cout << it->name.c_str() << endl;
}
}
return 0;
}

56
unittest/initdb.cc Normal file
View File

@ -0,0 +1,56 @@
#include "item.hh"
#include "item_impl.hh"
#include "dbsession.hh"
#include "dbobjectvec.hh"
#include "dbobjecttree.hh"
#include "dbobjecttree_impl.hh"
#include "dbobjecttreeptr.hh"
#include "category.hh"
#include <gtest/gtest.h>
#include <iostream>
using namespace inventory;
using namespace datamodel;
using namespace inventory::db;
std::string g_test_db_url;
class DBInitTest : public ::testing::Test {
public:
DBInitTest()
: m_session(g_test_db_url.c_str()) {}
virtual void setUp() {}
virtual void tearDown() {}
protected:
DBSession m_session;
};
TEST_F(DBInitTest, ItemInit) {
Item::create_tables(m_session);
}
TEST_F(DBInitTest, CategoryInit) {
Category::create_tables(m_session);
}
TEST_F(DBInitTest, ItemInsert) {
Item root;
root.name = "Root object";
root.insert(m_session);
}
TEST_F(DBInitTest, ItemRetrieve) {
DBContainerPtr<Item> items = Item::get_all(m_session);
ASSERT_STREQ((*items)[0]->name.c_str(), "Root object");
}
int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);
g_test_db_url = argv[1];
return RUN_ALL_TESTS();
}

29
unittest/insert_test.cc Normal file
View File

@ -0,0 +1,29 @@
#include "category.hh"
#include "item.hh"
#include "item_impl.hh"
#include "dbsession.hh"
#include <iostream>
#include <vector>
using namespace inventory;
using namespace datamodel;
using namespace inventory::db;
using namespace std;
int main(int argc, char *argv[]) {
DBSession session(argv[1]);
return 0;
Item i;
i.name = "Specimen 1";
i.mass = 200;
i.url = "http://digikey.com";
cout << "Inserting item\n";
i.insert(session);
return 0;
}

20
unittest/orm_test.cc Normal file
View File

@ -0,0 +1,20 @@
#include "dbobject.hh"
#include "dbobject_impl.hh"
#include "item.hh"
#include "item_impl.hh"
#include <iostream>
#include <vector>
using namespace inventory;
using namespace datamodel;
using namespace inventory::db;
using namespace std;
int main(int argc, char *argv[]) {
Item item;
cout << item.prepared_statement_columns() << endl;
cout << item.prepared_statement_value_placeholders() << endl;
return 0;
}

67
unittest/tree.cc Normal file
View File

@ -0,0 +1,67 @@
#include "item.hh"
#include "item_impl.hh"
#include "dbsession.hh"
#include "dbobjectvec.hh"
#include "dbobjecttree.hh"
#include "dbobjecttree_impl.hh"
#include "dbobjecttreeptr.hh"
#include "category.hh"
#include <gtest/gtest.h>
#include <iostream>
using namespace inventory;
using namespace datamodel;
using namespace inventory::db;
std::string g_test_db_url;
template<class Object>
class TreeTest : public ::testing::Test {
protected:
class Visitor : public DBObjectTree<Object>::SubtreeVisitor {
public:
virtual void apply(DBObjectPtr<Object> item, int max_lvl, int lvl) {
indent(lvl);
if (!item->is_root())
std::cout << (std::string)(item->name) << std::endl;
}
protected:
void indent(int level) {
for (int i = 0; i < level; i++)
std::cout << "\t";
}
};
public:
typedef Object TestedDBObject;
TreeTest()
: m_session(g_test_db_url.c_str()) {}
virtual void setUp() {}
virtual void tearDown() {}
protected:
DBSession m_session;
};
typedef TreeTest<Item> ItemTest;
TEST_F(ItemTest, ItemTreeTest) {
Visitor visitor;
DBObjectTreePtr<TestedDBObject> tree(new DBObjectTree<TestedDBObject>);
tree->recurse_subtrees(m_session, visitor);
EXPECT_EQ(0, 0);
}
int main(int argc, char *argv[]) {
return 0;
::testing::InitGoogleTest(&argc, argv);
g_test_db_url = argv[1];
return RUN_ALL_TESTS();
}