From dd1146412448ca2ccdc5136ab2fe6c19f8e6f76a Mon Sep 17 00:00:00 2001 From: Mateusz Zalega Date: Sat, 7 Nov 2015 20:14:47 +0100 Subject: [PATCH] Initial implementation --- .gitignore | 5 + .gitmodules | 3 + Makefile | 110 ++++++++--- data/clean.db | Bin 0 -> 2048 bytes data/test.db | Bin 0 -> 15360 bytes googletest | 1 + source/category.cc | 73 ++++--- source/dbfield.cc | 15 ++ source/dbobject.cc | 41 ++++ source/dbsession.cc | 24 ++- source/include/category.hh | 41 ++-- source/include/category_impl.hh | 11 ++ source/include/cxx-semantics.hh | 38 ++++ source/include/cxx-semantics_impl.hh | 15 ++ source/include/dbassocobject.hh | 43 ++++ source/include/dbassocobject_impl.hh | 81 ++++++++ source/include/dbcontainer.hh | 41 ++++ source/include/dbcontainerptr.hh | 15 ++ source/include/dbfield.hh | 134 +++++++++++++ source/include/dbfield_impl.hh | 126 ++++++++++++ source/include/dbobject.hh | 164 ++++++++++++++++ source/include/dbobject_impl.hh | 280 +++++++++++++++++++++++++++ source/include/dbobjectmap.hh | 15 ++ source/include/dbobjectptr.hh | 11 ++ source/include/dbobjecttree.hh | 67 +++++++ source/include/dbobjecttree_impl.hh | 59 ++++++ source/include/dbobjecttreeptr.hh | 20 ++ source/include/dbobjectvec.hh | 15 ++ source/include/dboperations.hh | 60 ++++++ source/include/dboperations_impl.hh | 92 +++++++++ source/include/dbsession.hh | 38 ++++ source/include/group.hh | 1 + source/include/item.hh | 72 +++++-- source/include/item_impl.hh | 6 + source/include/log.hh | 39 ++++ source/include/logstream.hh | 33 ++++ source/include/session.hh | 118 ----------- source/item.cc | 169 +++++++++++++--- source/log.cc | 10 + source/logstream.cc | 19 ++ unittest/category_test.cc | 42 ++++ unittest/children_count.cc | 28 +++ unittest/get.cc | 39 ++++ unittest/initdb.cc | 56 ++++++ unittest/insert_test.cc | 29 +++ unittest/orm_test.cc | 20 ++ unittest/tree.cc | 67 +++++++ 47 files changed, 2162 insertions(+), 224 deletions(-) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 data/clean.db create mode 100644 data/test.db create mode 160000 googletest create mode 100644 source/dbfield.cc create mode 100644 source/dbobject.cc create mode 100644 source/include/category_impl.hh create mode 100644 source/include/cxx-semantics.hh create mode 100644 source/include/cxx-semantics_impl.hh create mode 100644 source/include/dbassocobject.hh create mode 100644 source/include/dbassocobject_impl.hh create mode 100644 source/include/dbcontainer.hh create mode 100644 source/include/dbcontainerptr.hh create mode 100644 source/include/dbfield.hh create mode 100644 source/include/dbfield_impl.hh create mode 100644 source/include/dbobject.hh create mode 100644 source/include/dbobject_impl.hh create mode 100644 source/include/dbobjectmap.hh create mode 100644 source/include/dbobjectptr.hh create mode 100644 source/include/dbobjecttree.hh create mode 100644 source/include/dbobjecttree_impl.hh create mode 100644 source/include/dbobjecttreeptr.hh create mode 100644 source/include/dbobjectvec.hh create mode 100644 source/include/dboperations.hh create mode 100644 source/include/dboperations_impl.hh create mode 100644 source/include/dbsession.hh create mode 100644 source/include/item_impl.hh create mode 100644 source/include/log.hh create mode 100644 source/include/logstream.hh delete mode 100644 source/include/session.hh create mode 100644 source/log.cc create mode 100644 source/logstream.cc create mode 100644 unittest/category_test.cc create mode 100644 unittest/children_count.cc create mode 100644 unittest/get.cc create mode 100644 unittest/initdb.cc create mode 100644 unittest/insert_test.cc create mode 100644 unittest/orm_test.cc create mode 100644 unittest/tree.cc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bcd6dc7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.swo +*.swp +*.db-journal +*.gdb_history +*.peda-session* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8cf8b5e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "googletest"] + path = googletest + url = https://github.com/google/googletest.git diff --git a/Makefile b/Makefile index db4fba6..bfb5f28 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/data/clean.db b/data/clean.db new file mode 100644 index 0000000000000000000000000000000000000000..cbb7875e7a86a3f9198037e0a21c52c4f82a9571 GIT binary patch literal 2048 zcmWFz^vNtqRY=P(%1ta$FlJz3U}R))P*7lCU|>SRj8HZUkcI(}7$LyKp!@O_FGv+o dC-W9U%17l#Ltr!nMnhmU1V%$(Gz91o0stW=2#o*$ literal 0 HcmV?d00001 diff --git a/data/test.db b/data/test.db new file mode 100644 index 0000000000000000000000000000000000000000..10d63d26ea9004de62ccaf00e42d56b12f2a9d5f GIT binary patch literal 15360 zcmeHNOKclO7@pa2{3>xJf^nVaIB&O2OBxBJiULX4q%Mw~IyNOjmE+jkcvHXXUDM`N zts}&>Cl09MPzgjYaDWq)5L^%k#Gyh6RR}H!(IO!ZrH29vGrMbh*JQ2AO-q%|B>T_K z%>U2hoB4n5e)iJIuqX@oOtG9#$#@Fb0EFNri~#`0$+eeUoOZ!$B)5^PmSF9sMSdJi z+`QF66gYI7s_jL0wVM_;tcfAO5O}Z%?5X<60B&VLABOreday&X-WdW60fqoWfFZyT zcvKMRBojgC20*va4fH4a=uw#$n+HRHA#gtkcotCrrc<)8RxFEx#JNJq?^-JtD<#QQ zK|V-c`}5Km^?!(d1LU72h5$og_ae{*J3tVCMWHMe3#lAd1Av>X7oxub`DckCzz}$h z5J1S~(mwz10(AE=S~y!OL*TJTzy=X?an%32z&il_g07=$=zUa1FCaJf9e0H*a7Q^i zybj-m>+o5~fp3V~Lrh1;JRkr&olZy-H@Q?2(qdjH;K?mDfv>W%TsnIAa7J7c&k7rd z(#8Cd(Kc*G+OQdJ!)B-rn?WlZ&KiS28#cHNn*l4E7W>oRhE1Q9O$!Wqt!!G%t;fp7 zW{q{Xl}(Fr{8l#B<8;|Pz-#!v{bfNqyX8e5;5Sq*q{^!~fqRnkfG$JzO!0zkAM}6$ zLmjopxm3QiMW6q>&?WNxkM5v92n+Z#`VoCkV!_wwOY}MV6n%m|L?56l=rVd6T_Vvz z|9gmmfDOeE*o_F3)kWo0c}A5h0CXS-$Hu4}9i?()gv#MzDu;%s92}%F5TFucDhCFr z?C+IuVZJiAkzf zNackUSDb$>PD>dDUC)uA^;;e0wP<}&0c*K)n%S3~?M zT&u&=OYS8;%+JQjc{_-oA>~HfTr9evv5olDadJlKk))m!is13O5mvP{pOJ!Y;zrSk zvCH~MM!n68RkCX&D7|%Bg+x6xs&JZ?Kd9F2*p}7f06pjT=;qrxwoG1VlBrBaFvJ*h zbWk;!edsHhI2Sma{AMNM-INT`AKI+%TRdY*xt?3v0>L1yQo2WHv)Q|MD zt!G9r(yOS`=>DM9&0Ax9h~;~4|V>ZLq7m?lL(d=0t|s&gTNln4xkr+E%I!5 z)(c62JS}aN{`37mhwiBS->z9WTL?pdAtIjs^h+K)Onl2WB4mjywm`|4%mt7UWBJ-uV<{c4nqflO?A&y=g(4sRgf zRGy==(q1dgcgC4C+6{BquaBWqO!x$0Qgx~*s^1(UY|*hgnM6ut2TttFE+`kPy$;{h z6x7zPW4PJDcswd3U(l#0z(NtVr7t*qca;fphls6)DNHJ~IY9c`z9a-`q%?xS1 MbCZQ(D{H*)zij_L9{>OV literal 0 HcmV?d00001 diff --git a/googletest b/googletest new file mode 160000 index 0000000..82b11b8 --- /dev/null +++ b/googletest @@ -0,0 +1 @@ +Subproject commit 82b11b8cfcca464c2ac74b623d04e74452e74f32 diff --git a/source/category.cc b/source/category.cc index bcd4615..10003f7 100644 --- a/source/category.cc +++ b/source/category.cc @@ -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 -#include -#include #include // 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(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::create_sub_view(session); + DBHierarchicalObject::create_sup_view(session); + DBAssocHierarchicalObject::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(session, NULL, category_view, + [](void *, PreparedStatement_T s) {}); +} + +void Category::validate_tables(db::DBSession &session) { + +} + +void Category::remove_tables(db::DBSession &session) { - return ret; } } diff --git a/source/dbfield.cc b/source/dbfield.cc new file mode 100644 index 0000000..ab67f83 --- /dev/null +++ b/source/dbfield.cc @@ -0,0 +1,15 @@ +#include "dbfield.hh" +#include "dbobject.hh" +#include +#include + +#include +// refer to libzdb docs +#include + +namespace inventory { +namespace datamodel { + +} +} + diff --git a/source/dbobject.cc b/source/dbobject.cc new file mode 100644 index 0000000..26feb9a --- /dev/null +++ b/source/dbobject.cc @@ -0,0 +1,41 @@ +#include "dbobject.hh" +#include "dbfield.hh" +#include "dbsession.hh" +#include "dboperations.hh" +#include +#include +#include + +namespace inventory { +namespace datamodel { + +const struct DBRecordElementDesc + DBRecordDescTempl::id_desc = { + "id", + "ID", + "Unique identification number" +}; + +const struct DBRecordElementDesc + DBRecordDescTempl::name_desc = { + "name", + "Name", + "" +}; + +const struct DBRecordElementDesc + DBRecordDescTempl::sup_desc = { + "sup", + "Stored in", + "" +}; + +const struct DBRecordElementDesc + DBRecordDescTempl::nsub_desc = { + "sub", + "Children count", + "" +}; + +} +} diff --git a/source/dbsession.cc b/source/dbsession.cc index df4205b..4259ab0 100644 --- a/source/dbsession.cc +++ b/source/dbsession.cc @@ -1,4 +1,4 @@ -#include "session.hh" +#include "dbsession.hh" #include #include @@ -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); +} + } } diff --git a/source/include/category.hh b/source/include/category.hh index c3fa9f3..c48c677 100644 --- a/source/include/category.hh +++ b/source/include/category.hh @@ -1,25 +1,44 @@ #ifndef INVENTORY_CATEGORY_HH_ #define INVENTORY_CATEGORY_HH_ -#include -#include -#include +#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 { public: - int id; - int parent; - std::string name; - std::string description; - std::vector 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 description; + DBBlob 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 diff --git a/source/include/category_impl.hh b/source/include/category_impl.hh new file mode 100644 index 0000000..174c9c7 --- /dev/null +++ b/source/include/category_impl.hh @@ -0,0 +1,11 @@ +#ifndef INVENTORY_CATEGORY_IMPL_HH_ +#define INVENTORY_CATEGORY_IMPL_HH_ +#include "dbassocobject_impl.hh" + +namespace inventory { +namespace datamodel { + +} +} + +#endif diff --git a/source/include/cxx-semantics.hh b/source/include/cxx-semantics.hh new file mode 100644 index 0000000..ee0dcc8 --- /dev/null +++ b/source/include/cxx-semantics.hh @@ -0,0 +1,38 @@ +#ifndef INVENTORY_CXX_SEMANTICS_HH_ +#define INVENTORY_CXX_SEMANTICS_HH_ +#include + +namespace inventory { +namespace semantics { + +class non_constructible { + non_constructible() {} +}; + +template +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 +class visitor { +public: + virtual void apply(Args... args) = 0; +}; + +} +} + +#include "cxx-semantics_impl.hh" +#endif diff --git a/source/include/cxx-semantics_impl.hh b/source/include/cxx-semantics_impl.hh new file mode 100644 index 0000000..a5e80e0 --- /dev/null +++ b/source/include/cxx-semantics_impl.hh @@ -0,0 +1,15 @@ +#ifndef INVENTORY_CXX_SEMANTICS_IMPL_HH_ +#define INVENTORY_CXX_SEMANTICS_IMPL_HH_ +#include "cxx-semantics.hh" +#include + +namespace inventory { +namespace semantics { + +template +Derived *singleton::s_instance = NULL; + +} +} + +#endif diff --git a/source/include/dbassocobject.hh b/source/include/dbassocobject.hh new file mode 100644 index 0000000..b7261f0 --- /dev/null +++ b/source/include/dbassocobject.hh @@ -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 DBAssocObject : public DBNamedObject { +public: + template class Container = datamodel::DBObjectVector> + datamodel::DBContainerPtr get_objects( + db::DBSession &session, + datamodel::DBObjectInserter inserter = + datamodel::DBObjectInserterImpl::inserter, + datamodel::DBContainerPtr pcontainer = {}, + int offset = 0, int limit = -1) const; + + template class Container = datamodel::DBObjectVector> + static datamodel::DBContainerPtr get_assoc( + db::DBSession &session, const Object& object, + datamodel::DBObjectInserter inserter = + datamodel::DBObjectInserterImpl::inserter, + datamodel::DBContainerPtr pcontainer = {}, + int offset = 0, int limit = -1); + +protected: + static void create_membership_table(db::DBSession &session); +}; + +template +class DBAssocHierarchicalObject : public DBAssocObject, + public DBHierarchicalObject { +public: +}; + +} +} + +#endif diff --git a/source/include/dbassocobject_impl.hh b/source/include/dbassocobject_impl.hh new file mode 100644 index 0000000..ab1f2c7 --- /dev/null +++ b/source/include/dbassocobject_impl.hh @@ -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 +template class Container> +DBContainerPtr DBAssocObject::get_objects( + db::DBSession &session, + DBObjectInserter inserter, + DBContainerPtr 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::id)); + PreparedStatement_setInt(s, 2, limit); + PreparedStatement_setInt(s, 3, offset); + }; + + return db::DBOperations::get_all(session, NULL, + prepared_stmt.c_str(), stmt_cons, inserter, pcontainer, offset, limit); +} + +template +template class Container> +DBContainerPtr DBAssocObject::get_assoc( + db::DBSession &session, const Object& object, + DBObjectInserter inserter, + DBContainerPtr 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(session, NULL, + prepared_stmt.c_str(), stmt_cons, inserter, pcontainer, offset, limit); +} + +template +void DBAssocObject::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(session, NULL, membership_table.c_str(), + [](void *, PreparedStatement_T s) {}); +} + +} +} + +#include "dbobject_impl.hh" +#endif diff --git a/source/include/dbcontainer.hh b/source/include/dbcontainer.hh new file mode 100644 index 0000000..28b9618 --- /dev/null +++ b/source/include/dbcontainer.hh @@ -0,0 +1,41 @@ +#ifndef INVENTORY_DBCONTAINER_HH_ +#define INVENTORY_DBCONTAINER_HH_ +#include "dbobjectvec.hh" +#include "dbobjectmap.hh" +#include "dbobjectptr.hh" +#include +#include + +namespace inventory { +namespace datamodel { + +template class Container> +using DBObjectInserter = + std::function, Container &)>; + +template class Container> +class DBObjectInserterImpl {}; + +template +class DBObjectInserterImpl { +public: + static void inserter(DBObjectPtr el, DBObjectVector &c) { + c.push_back(el); + } +}; + +template +class DBObjectInserterImpl { +public: + static void inserter(DBObjectPtr el, DBObjectMap &c) { + c.insert(std::make_pair((int)(el->id), el)); + } +}; + +template class Container = DBObjectVector> +using DBContainerPtr = std::shared_ptr>; + +} +} + +#endif diff --git a/source/include/dbcontainerptr.hh b/source/include/dbcontainerptr.hh new file mode 100644 index 0000000..7db022c --- /dev/null +++ b/source/include/dbcontainerptr.hh @@ -0,0 +1,15 @@ +#ifndef INVENTORY_DBCONTAINERPTR_HH_ +#define INVENTORY_DBCONTAINERPTR_HH_ +#include "dbobjectvec.hh" +#include + +namespace inventory { +namespace datamodel { + +template class Container> +using DBContainerPtr = std::shared_ptr>; + +} +} + +#endif diff --git a/source/include/dbfield.hh b/source/include/dbfield.hh new file mode 100644 index 0000000..224fb62 --- /dev/null +++ b/source/include/dbfield.hh @@ -0,0 +1,134 @@ +#ifndef INVENTORY_DBFIELD_HH +#define INVENTORY_DBFIELD_HH +#include +#include +#include +#include "cxx-semantics.hh" + +namespace inventory { +namespace datamodel { + +template +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 class Object> +class DBRecordDescTempl : semantics::non_constructible {}; + +template +class DBRecordDesc : semantics::non_constructible {}; + +template +class DBRecordElement { +public: + DBRecordElement(const DBObject *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 * const m_parent; + const struct DBRecordElementDesc * const mcp_elem_desc; + + DBRecordElementType m_type; +}; + +template +class DBField : public DBRecordElement { +public: + DBField(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc); + + DBField operator=(const DBField &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 DBInt : public DBField { +public: + DBInt(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc); + + using DBField::operator =; + void from_query(ResultSet_T r); + void insert_value(PreparedStatement_T stmt, int pos) const; +}; + +template +class DBUnsignedInt : public DBField { +public: + DBUnsignedInt(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc); + + using DBField::operator =; + void from_query(ResultSet_T r); + void insert_value(PreparedStatement_T stmt, int pos) const; +}; + +template +class DBString : public DBField { +public: + DBString(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc); + + using DBField::operator =; + void from_query(ResultSet_T r); + void insert_value(PreparedStatement_T stmt, int pos) const; + + const char *c_str() const; +}; + +template +class DBBlob : public DBField, Derived> { +public: + DBBlob(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc); + + using DBField, Derived>::operator =; + void from_query(ResultSet_T r); + void insert_value(PreparedStatement_T stmt, int pos) const; +}; + +} +} + +#endif diff --git a/source/include/dbfield_impl.hh b/source/include/dbfield_impl.hh new file mode 100644 index 0000000..c9eb298 --- /dev/null +++ b/source/include/dbfield_impl.hh @@ -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 +#include + +namespace inventory { +namespace datamodel { + +template +DBField::DBField(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc) +: DBRecordElement(parent, elem_desc) {} + +template +DBInt::DBInt(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc) +: DBField(parent, elem_desc) { + // neither clang nor gcc would recognize m_value + // w/o explicit class path, not sure why + DBField::m_value = -1; + DBRecordElement::m_type = DBRecordElementType::INTEGER; +} + +template +DBUnsignedInt::DBUnsignedInt(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc) +: DBField(parent, elem_desc) { + DBField::m_value = 0; + DBRecordElement::m_type = DBRecordElementType::UNSIGNED_INTEGER; +} + +template +DBString::DBString(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc) +: DBField(parent, elem_desc) { + DBRecordElement::m_type = DBRecordElementType::TEXT; +} + +template +DBBlob::DBBlob(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc) +: DBField, Derived>(parent, elem_desc) { + DBRecordElement::m_type = DBRecordElementType::BLOB; +} + +template +const char *DBString::c_str() const { + return DBField::m_value.c_str(); +} + +template +DBRecordElement::DBRecordElement(const DBObject *parent, + const struct DBRecordElementDesc *elem_desc) +: m_parent(parent), mcp_elem_desc(elem_desc) { + if (parent != NULL) + parent->register_element(this); +} + +template +void DBInt::from_query(ResultSet_T r) { + DBField::m_value = + ResultSet_getIntByName(r, + DBRecordElement::mcp_elem_desc->column_name); +} + +template +void DBInt::insert_value(PreparedStatement_T stmt, int pos) const { + PreparedStatement_setInt(stmt, pos, DBField::m_value); +} + +template +void DBUnsignedInt::from_query(ResultSet_T r) { + DBField::m_value = + ResultSet_getIntByName(r, + DBRecordElement::mcp_elem_desc->column_name); +} + +template +void DBUnsignedInt::insert_value(PreparedStatement_T stmt, int pos) const { + PreparedStatement_setInt(stmt, pos, DBField::m_value); +} + +template +void DBString::from_query(ResultSet_T r) { + const char * pname = + ResultSet_getStringByName(r, + DBRecordElement::mcp_elem_desc->column_name); + + if (pname != NULL) + DBField::m_value = pname; +} + +template +void DBString::insert_value(PreparedStatement_T stmt, int pos) const { + PreparedStatement_setString(stmt, pos, + DBField::m_value.c_str()); +} + +template +void DBBlob::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::mcp_elem_desc->column_name, + &symbol_size)); + if (psymbol != NULL) { + DBField, Derived>::m_value.resize(symbol_size); + std::copy(psymbol, psymbol + symbol_size, + std::back_inserter(DBField, Derived>::m_value)); + } +} + +template +void DBBlob::insert_value(PreparedStatement_T stmt, int pos) const { + PreparedStatement_setBlob(stmt, pos, + &DBField, Derived>::m_value.front(), + DBField, Derived>::m_value.size()); +} + +} +} + +#endif diff --git a/source/include/dbobject.hh b/source/include/dbobject.hh new file mode 100644 index 0000000..592a2e8 --- /dev/null +++ b/source/include/dbobject.hh @@ -0,0 +1,164 @@ +#ifndef INVENTORY_DBOBJECT_HH_ +#define INVENTORY_DBOBJECT_HH_ +#include +#include +#include +#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 DBObject; + + template<> + class DBRecordDescTempl : semantics::non_constructible { + public: + static const struct DBRecordElementDesc id_desc; + }; + + template + class DBObject { + template + friend class DBRecordElement; + + public: + typedef std::map *> RecordElementMap; + + protected: + void register_element(DBRecordElement *el) const; + + // must be initialized before table elements + mutable RecordElementMap m_record_elements; + + public: + enum class Id { + ROOT = -1, + UNDEFINED = -2 + }; + + DBInt id; + + DBObject(); + virtual ~DBObject() {} + + static datamodel::DBObjectPtr get(db::DBSession &session, int id); + + template class Container = datamodel::DBObjectVector> + static datamodel::DBContainerPtr get_all( + db::DBSession &session, + datamodel::DBObjectInserter inserter = + datamodel::DBObjectInserterImpl::inserter, + datamodel::DBContainerPtr 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 *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 DBNamedObject; + + template<> + class DBRecordDescTempl : semantics::non_constructible { + public: + static const struct DBRecordElementDesc name_desc; + }; + + template + class DBNamedObject : public virtual DBObject { + public: + DBString name; + + DBNamedObject(); + virtual ~DBNamedObject() {} + + static datamodel::DBObjectPtr get(db::DBSession &session, + const char *name); + }; + + template + class DBHierarchicalObject; + + template<> + class DBRecordDescTempl : semantics::non_constructible { + public: + static const struct DBRecordElementDesc sup_desc; + static const struct DBRecordElementDesc nsub_desc; + }; + + template + class DBHierarchicalObject : public virtual DBObject { + public: + DBInt sup; + + // generated, not part of the table + DBInt nsub; + + DBHierarchicalObject(); + virtual ~DBHierarchicalObject() {} + + template class Container = datamodel::DBObjectVector> + static datamodel::DBContainerPtr get_children( + db::DBSession &session, int id, + datamodel::DBObjectInserter inserter = + datamodel::DBObjectInserterImpl::inserter, + datamodel::DBContainerPtr pcontainer = {}, + int offset = 0, int limit = -1); + + template class Container = datamodel::DBObjectVector> + datamodel::DBContainerPtr get_children( + db::DBSession &session, + datamodel::DBObjectInserter inserter = + datamodel::DBObjectInserterImpl::inserter, + datamodel::DBContainerPtr pcontainer = {}, + int offset = 0, int limit = -1); + + datamodel::DBObjectPtr 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 diff --git a/source/include/dbobject_impl.hh b/source/include/dbobject_impl.hh new file mode 100644 index 0000000..5151eaf --- /dev/null +++ b/source/include/dbobject_impl.hh @@ -0,0 +1,280 @@ +#ifndef INVENTORY_DBOBJECT_IMPL_HH_ +#define INVENTORY_DBOBJECT_IMPL_HH_ +#include +#include "dbfield.hh" +#include "dbfield_impl.hh" +#include "dbobject.hh" +#include "dboperations.hh" + +namespace inventory { +namespace datamodel { + +template +DBObject::DBObject() +: m_record_elements(), + // used in SQL statements, left unregistered + id(NULL, &DBRecordDescTempl::id_desc) { + id = (int)(DBObject::Id::UNDEFINED); +} + +template +DBNamedObject::DBNamedObject() +: name(this, &DBRecordDescTempl::name_desc) {} + +template +DBHierarchicalObject::DBHierarchicalObject() +: sup(this, &DBRecordDescTempl::sup_desc), + nsub(NULL, &DBRecordDescTempl::nsub_desc) { + sup = (int)(DBHierarchicalObject::Id::UNDEFINED); + nsub = -1; +} + +template +void DBObject::register_element(DBRecordElement *el) const { + m_record_elements.insert( + std::make_pair( + std::string(el->element_description()->column_name), el + )); +} + +template +void DBObject::from_query(ResultSet_T r) { + TRY { + id = ResultSet_getIntByName(r, + DBRecordDescTempl::id_desc.column_name); + for (const auto &el : m_record_elements) + (el.second)->from_query(r); + } CATCH(SQLException) { + + } + END_TRY; +} + +template +void DBHierarchicalObject::from_query(ResultSet_T r) { + nsub = ResultSet_getIntByName(r, + DBRecordDescTempl::nsub_desc.column_name); + DBObject::from_query(r); +} + +template +std::string DBObject::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 +std::string DBObject::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 +void DBObject::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(session, NULL, prepared_stmt.c_str(), + stmt_cons); +} + +template +void DBObject::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(session, NULL, prepared_stmt.c_str(), + stmt_cons); +} + +template +void DBObject::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(session, NULL, prepared_stmt.c_str(), + stmt_cons); +} + +template +void DBObject::refresh(db::DBSession &session) { + *this = *DBObject::get(session, id); +} + +template +void DBObject::insert_values(PreparedStatement_T s) const { + int pos = 1; + for (const auto &el : m_record_elements) { + el.second->insert_value(s, pos++); + } +} + +template +DBObjectPtr DBObject::get(db::DBSession &session, int id) { + if (id < 0) + return DBObjectPtr(); // 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(session, id, + prepared_stmt.c_str(), stmt_cons); +} + +template +DBObjectPtr DBNamedObject:: + 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(session, + name, prepared_stmt.c_str(), stmt_cons); +} + +template +template class Container> +DBContainerPtr DBObject::get_all( + db::DBSession &session, + DBObjectInserter inserter, + DBContainerPtr 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(session, NULL, + prepared_stmt.c_str(), stmt_cons, inserter, pcontainer, offset, limit); +} + +template +template class Container> +DBContainerPtr DBHierarchicalObject::get_children( + db::DBSession &session, int id, + DBObjectInserter inserter, + DBContainerPtr 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(session, id, + prepared_stmt.c_str(), stmt_cons, inserter, pcontainer, + offset, limit); +} + +template +template class Container> +DBContainerPtr DBHierarchicalObject::get_children( + db::DBSession &session, + DBObjectInserter inserter, + DBContainerPtr pcontainer, int offset, int limit) { + return get_children(session, (int)(DBObject::id), inserter, + pcontainer, offset, limit); +} + +template +DBObjectPtr DBHierarchicalObject::get_parent( + db::DBSession &session) { + return get(session, sup); +} + +template +void DBHierarchicalObject::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(session, NULL, sub_view.c_str(), + [](void *, PreparedStatement_T s) {}); +} + +template +void DBHierarchicalObject::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(session, NULL, sup_view.c_str(), + [](void *, PreparedStatement_T s) {}); +} + + +} +} + +#endif diff --git a/source/include/dbobjectmap.hh b/source/include/dbobjectmap.hh new file mode 100644 index 0000000..d47eb92 --- /dev/null +++ b/source/include/dbobjectmap.hh @@ -0,0 +1,15 @@ +#ifndef INVENTORY_OBJECT_MAP_ +#define INVENTORY_OBJECT_MAP_ +#include +#include "dbobjectptr.hh" + +namespace inventory { +namespace datamodel { + +template +using DBObjectMap = std::map>; + +} +} + +#endif diff --git a/source/include/dbobjectptr.hh b/source/include/dbobjectptr.hh new file mode 100644 index 0000000..f9e3825 --- /dev/null +++ b/source/include/dbobjectptr.hh @@ -0,0 +1,11 @@ +#ifndef INVENTORY_DBOBJECTPTR_HH +#include + +namespace inventory { +namespace datamodel { + template + using DBObjectPtr = std::shared_ptr; +} +} + +#endif diff --git a/source/include/dbobjecttree.hh b/source/include/dbobjecttree.hh new file mode 100644 index 0000000..b496b2e --- /dev/null +++ b/source/include/dbobjecttree.hh @@ -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 +#include + +namespace inventory { +namespace db { + class DBSession; +} + +namespace datamodel { + +template +class DBObjectTree : public DBObjectTreePtrBase { +public: + template + using SubtreeContainer = std::vector>; + + typedef DBContainerPtr + SubtreeContainerPtr; + + class SubtreeVisitor : + public semantics::visitor, int, int> {}; + + // visibility outside of DBObjectTree + typedef SubtreeVisitor Visitor; + + DBObjectTree() { + m_object.reset(new Object); + m_object->id = (int)(Object::Id::ROOT); + } + + DBObjectTree(DBObjectPtr obj) + : m_object(obj) {} + + DBObjectTree(DBObjectPtr obj, DBObjectTreePtr suptree) + : m_object(obj), m_suptree(suptree) {} + + DBObjectPtr 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 m_object; + + DBObjectTreePtr m_suptree; + SubtreeContainerPtr m_subtree; +}; + +} +} + +#endif diff --git a/source/include/dbobjecttree_impl.hh b/source/include/dbobjecttree_impl.hh new file mode 100644 index 0000000..4276dea --- /dev/null +++ b/source/include/dbobjecttree_impl.hh @@ -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 + +namespace inventory { +namespace datamodel { + +template +void DBObjectTree::fetch_object(db::DBSession &session) { + m_object->refresh(session); +} + +template +void DBObjectTree::fetch_subtree(db::DBSession &session) { + if (!m_object) + fetch_object(session); + + if (!m_subtree) + m_subtree.reset(new SubtreeContainer); + + auto inserter = + [this](DBObjectPtr el, SubtreeContainer &c) { + DBObjectTreePtr subtree(new DBObjectTree(el, + DBObjectTree::shared_from_this())); + c.push_back(subtree); + }; + + (*m_object).template get_children(session, + inserter, m_subtree); +} + +template +void DBObjectTree::recurse_subtrees(db::DBSession &session, + DBObjectTree::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 subtree : *m_subtree) { + subtree->recurse_subtrees(session, visitor, max_level, level + 1); + } + } +} + +} +} + +#endif diff --git a/source/include/dbobjecttreeptr.hh b/source/include/dbobjecttreeptr.hh new file mode 100644 index 0000000..db49749 --- /dev/null +++ b/source/include/dbobjecttreeptr.hh @@ -0,0 +1,20 @@ +#ifndef INVENTORY_DBOBJECTTREEPTR_HH_ +#define INVENTORY_DBOBJECTTREEPTR_HH_ +#include + +namespace inventory { +namespace datamodel { + +template +class DBObjectTree; + +template +using DBObjectTreePtr = std::shared_ptr>; + +template +using DBObjectTreePtrBase = std::enable_shared_from_this>; + +} +} + +#endif diff --git a/source/include/dbobjectvec.hh b/source/include/dbobjectvec.hh new file mode 100644 index 0000000..46d5162 --- /dev/null +++ b/source/include/dbobjectvec.hh @@ -0,0 +1,15 @@ +#ifndef INVENTORY_DBOBJECTVEC_HH_ +#define INVENTORY_DBOBJECTVEC_HH_ +#include "dbobjectptr.hh" +#include + +namespace inventory { +namespace datamodel { + +template +using DBObjectVector = std::vector>; + +} +} + +#endif diff --git a/source/include/dboperations.hh b/source/include/dboperations.hh new file mode 100644 index 0000000..36e4988 --- /dev/null +++ b/source/include/dboperations.hh @@ -0,0 +1,60 @@ +#ifndef INVENTORY_DBOPERATIONS_HH +#define INVENTORY_DBOPERATIONS_HH +#include +#include +#include +#include +#include "dbobjectptr.hh" +#include "dbobjectvec.hh" +#include "dbcontainer.hh" + +namespace inventory { +namespace datamodel { + template + class DBObject; +} + +namespace db { + +class DBSession; + +class DBOperations { + DBOperations() {} + +public: + template + using StatementConstructorExecute = std::function; + + template + using StatementConstructorSingle = std::function; + + // + offset, limit + template + using StatementConstructor = std::function; + + // only a statement constructor argument class here, operation is the same + // for whole database object hierarchy + template + static void execute( + DBSession &session, const K &stmt_arg, const char *prepared_stmt, + StatementConstructorExecute cons); + + template + static datamodel::DBObjectPtr get_single( + DBSession &session, const K &stmt_arg, const char *prepared_stmt, + StatementConstructorSingle cons); + + template class Container = datamodel::DBObjectVector> + static datamodel::DBContainerPtr get_all( + DBSession& session, const K &stmt_arg, + const char *prepared_stmt, StatementConstructor cons, + datamodel::DBObjectInserter inserter, + datamodel::DBContainerPtr pcontainer, + int offset, int limit); +}; + +} +} + +#endif diff --git a/source/include/dboperations_impl.hh b/source/include/dboperations_impl.hh new file mode 100644 index 0000000..8974134 --- /dev/null +++ b/source/include/dboperations_impl.hh @@ -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 +// refer to libzdb docs +#include + +namespace inventory { +namespace db { + +template +void DBOperations::execute( + DBSession &session, const K &stmt_arg, const char *prepared_stmt, + StatementConstructorSingle 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 +datamodel::DBObjectPtr DBOperations::get_single( + DBSession &session, const K &stmt_arg, const char *prepared_stmt, + StatementConstructorSingle 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 ret(new T); + ret->from_query(r); + session.return_connection(conn); + + return ret; +} + +template class Container> +datamodel::DBContainerPtr DBOperations::get_all( + DBSession &session, const K &stmt_arg, + const char *prepared_stmt, StatementConstructor cons, + datamodel::DBObjectInserter inserter, + datamodel::DBContainerPtr pcontainer, + int offset, int limit) { + LIBINVDB_LOG(Loglevel::DEBUG, std::string("[QUERY] ") + prepared_stmt); + + if (!pcontainer) + pcontainer.reset(new Container); + + 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 el(new T); + el->from_query(r); + inserter(el, *pcontainer); + } + + session.return_connection(conn); + return pcontainer; +} + +} +} + +#include "dbobject_impl.hh" +#endif diff --git a/source/include/dbsession.hh b/source/include/dbsession.hh new file mode 100644 index 0000000..58c9f3a --- /dev/null +++ b/source/include/dbsession.hh @@ -0,0 +1,38 @@ +#ifndef INVENTORY_DBSESSION_HH_ +#define INVENTORY_DBSESSION_HH_ +#include +#include +#include +#include +#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 diff --git a/source/include/group.hh b/source/include/group.hh index bff548a..512cb9d 100644 --- a/source/include/group.hh +++ b/source/include/group.hh @@ -2,6 +2,7 @@ #define INVENTORY_GROUP_HH_ #include #include +#include "category.hh" namespace inventory { namespace datamodel { diff --git a/source/include/item.hh b/source/include/item.hh index d0762fc..999f442 100644 --- a/source/include/item.hh +++ b/source/include/item.hh @@ -1,31 +1,67 @@ #ifndef INVENTORY_ITEM_HH_ #define INVENTORY_ITEM_HH_ -#include "session.hh" -#include -#include -#include +#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 : 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, public DBHierarchicalObject { +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 description; + DBUnsignedInt time_added; + DBUnsignedInt time_spoiled; + DBInt cash_worth; + DBInt currency_id; + DBInt long_axis; + DBInt short_axis; + DBInt height; + DBInt mass; + DBString url; + + Item() + : description(this, &DBRecordDesc::desc_desc), + time_added(this, &DBRecordDesc::time_added_desc), + time_spoiled(this, &DBRecordDesc::time_spoiled_desc), + cash_worth(this, &DBRecordDesc::cash_worth_desc), + currency_id(this, &DBRecordDesc::currency_desc), + long_axis(this, &DBRecordDesc::long_axis_desc), + short_axis(this, &DBRecordDesc::short_axis_desc), + height(this, &DBRecordDesc::height_desc), + mass(this, &DBRecordDesc::mass_desc), + url(this, &DBRecordDesc::url_desc) {} + + static void create_tables(db::DBSession &session); + static void validate_tables(db::DBSession &session); + static void remove_tables(db::DBSession &session); }; } } + #endif diff --git a/source/include/item_impl.hh b/source/include/item_impl.hh new file mode 100644 index 0000000..fea1986 --- /dev/null +++ b/source/include/item_impl.hh @@ -0,0 +1,6 @@ +#ifndef INVENTORY_ITEM_IMPL_HH_ +#define INVENTORY_ITEM_IMPL_HH_ +#include "item.hh" +#include "dbobject_impl.hh" + +#endif diff --git a/source/include/log.hh b/source/include/log.hh new file mode 100644 index 0000000..40c5ba9 --- /dev/null +++ b/source/include/log.hh @@ -0,0 +1,39 @@ +#ifndef INVENTORY_LOG_HH_ +#define INVENTORY_LOG_HH_ +#include "logstream.hh" +#include "cxx-semantics.hh" +#include + +#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 { +public: + Log() + : m_loglevel(Loglevel::WARNING) {} + + void set_stream(std::shared_ptr 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 mp_logstream; + + Loglevel m_loglevel; +}; + +} + +#endif diff --git a/source/include/logstream.hh b/source/include/logstream.hh new file mode 100644 index 0000000..6bc8feb --- /dev/null +++ b/source/include/logstream.hh @@ -0,0 +1,33 @@ +#ifndef INVENTORY_LOGSTREAM_HH_ +#define INVENTORY_LOGSTREAM_HH_ +#include +#include +#include + +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 diff --git a/source/include/session.hh b/source/include/session.hh deleted file mode 100644 index 89c88b3..0000000 --- a/source/include/session.hh +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef INVENTORY_DBSESSION_HH -#define INVENTORY_DBSESSION_HH -#include -#include -#include -#include - -namespace inventory { -namespace db { - -class DBSession { -public: - DBSession(const char *url); - - Connection_T get_connection(); - - // database accessors for use w/ datamodel types - template - T get(int id); - - template - T get(const char *name); - - template - std::vector get_all(int offset = 0, int limit = -1); - - template - T get_parent(const T &); - -private: - void create_connpool(); - - const std::string m_url; - ConnectionPool_T m_pool; -}; - -template -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 -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 -std::vector 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 ret; - while (ResultSet_next(r)) { - T &&it = T::from_query(r); - ret.push_back(it); - } - - return ret; -} - -template -T DBSession::get_parent(const T &t) { - return (T&&)(get(t.id)); -} - -} -} -#endif diff --git a/source/item.cc b/source/item.cc index 9d83b9d..52411b7 100644 --- a/source/item.cc +++ b/source/item.cc @@ -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 #include @@ -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::desc_desc = { + "description", + "Description", + "" +}; - TRY { - ret.id = ResultSet_getIntByName(r, "id"); - ret.parent = ResultSet_getIntByName(r, "parent"); +const struct DBRecordElementDesc DBRecordDesc::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::time_spoiled_desc = { + "time_spoiled", + "Time spoiled", + "" +}; - const char *pdescription = ResultSet_getStringByName(r, "description"); - if (pdescription != NULL) - ret.description = pdescription; +const struct DBRecordElementDesc DBRecordDesc::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::currency_desc = { + "currency_id", + "", + "" +}; - const char *pcashworth = ResultSet_getStringByName(r, "cash_worth"); - if (pcashworth != NULL) - ret.cash_worth = pcashworth; +const struct DBRecordElementDesc DBRecordDesc::long_axis_desc = { + "long_axis", + "", + "" +}; - ret.currency_id = ResultSet_getIntByName(r, "currency_id"); - } CATCH(SQLException) { - - } - END_TRY; +const struct DBRecordElementDesc DBRecordDesc::short_axis_desc = { + "short_axis", + "", + "" +}; - return ret; +const struct DBRecordElementDesc DBRecordDesc::height_desc = { + "height", + "", + "" +}; + +const struct DBRecordElementDesc DBRecordDesc::mass_desc = { + "mass", + "", + "" +}; + +const struct DBRecordElementDesc DBRecordDesc::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(session, NULL, create_table, + [](void *, PreparedStatement_T s) {}); + + DBHierarchicalObject::create_sub_view(session); + DBHierarchicalObject::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(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(session, NULL, drop_table, + [](void *, PreparedStatement_T s) {}); + + const char *drop_item_sub = "DROP VIEW item_sub"; + db::DBOperations::execute(session, NULL, drop_item_sub, + [](void *, PreparedStatement_T s) {}); + + const char *drop_item_sup = "DROP VIEW item_sup"; + db::DBOperations::execute(session, NULL, drop_item_sup, + [](void *, PreparedStatement_T s) {}); + + const char *drop_item_view = "DROP VIEW item_view;"; + db::DBOperations::execute(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(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(session, NULL, prepared_stmt.c_str(), + stmt_cons); +} +*/ } } diff --git a/source/log.cc b/source/log.cc new file mode 100644 index 0000000..c96436f --- /dev/null +++ b/source/log.cc @@ -0,0 +1,10 @@ +#include "log.hh" +#include "logstream.hh" +#include + +namespace inventory { + +std::shared_ptr Log::mp_logstream(new StdLogstream); + +} + diff --git a/source/logstream.cc b/source/logstream.cc new file mode 100644 index 0000000..6fd1507 --- /dev/null +++ b/source/logstream.cc @@ -0,0 +1,19 @@ +#include "logstream.hh" +#include +#include + +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; +} + +} diff --git a/unittest/category_test.cc b/unittest/category_test.cc new file mode 100644 index 0000000..51a5bf7 --- /dev/null +++ b/unittest/category_test.cc @@ -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 + +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 cats = Category::get_all(session); + for (DBObjectPtr cat : *cats) { + std::cout << (std::string)(cat->name) << std::endl; + DBContainerPtr items = cat->get_objects(session); + for (DBObjectPtr it : *items) { + std::cout << "\t" << (std::string)(it->name) << std::endl; + } + } + + std::cout << "Items' categories" << std::endl; + + DBContainerPtr items = Item::get_all(session); + for (DBObjectPtr item : *items) { + std::cout << (std::string)(item->name) << std::endl; + DBContainerPtr cats = Category::get_assoc(session, *item); + for (DBObjectPtr cat : *cats) { + std::cout << "\t" << (std::string)(cat->name) << std::endl; + } + } + + return 0; +} diff --git a/unittest/children_count.cc b/unittest/children_count.cc new file mode 100644 index 0000000..1eec962 --- /dev/null +++ b/unittest/children_count.cc @@ -0,0 +1,28 @@ +#include "dbobjectptr.hh" +#include "dbobjectvec.hh" +#include "dbcontainer.hh" +#include "item.hh" +#include "item_impl.hh" +#include "dbsession.hh" + +#include + +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 items = Item::get_all(session); + for (DBObjectPtr &it : *items) { + cout << it->name.c_str() << "\t" << it->nsub << endl; + } + } + + return 0; +} diff --git a/unittest/get.cc b/unittest/get.cc new file mode 100644 index 0000000..b021441 --- /dev/null +++ b/unittest/get.cc @@ -0,0 +1,39 @@ +#include "dbobjectptr.hh" +#include "dbobjectvec.hh" +#include "dbcontainer.hh" +#include "item.hh" +#include "item_impl.hh" +#include "dbsession.hh" + +#include + +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 items = Item::get_all(session); + for (DBObjectPtr &it : *items) { + cout << it->name.c_str() << endl; + } + } + + std::cout << "Items via map: " << std::endl; + { + DBContainerPtr items + = Item::get_all(session); + for (auto &map_pair : *items) { + DBObjectPtr it = map_pair.second; + cout << it->name.c_str() << endl; + } + } + + return 0; +} diff --git a/unittest/initdb.cc b/unittest/initdb.cc new file mode 100644 index 0000000..ca0f77c --- /dev/null +++ b/unittest/initdb.cc @@ -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 + +#include + +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 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(); +} diff --git a/unittest/insert_test.cc b/unittest/insert_test.cc new file mode 100644 index 0000000..ed535c6 --- /dev/null +++ b/unittest/insert_test.cc @@ -0,0 +1,29 @@ +#include "category.hh" +#include "item.hh" +#include "item_impl.hh" +#include "dbsession.hh" + +#include +#include + +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; +} diff --git a/unittest/orm_test.cc b/unittest/orm_test.cc new file mode 100644 index 0000000..31eca8b --- /dev/null +++ b/unittest/orm_test.cc @@ -0,0 +1,20 @@ +#include "dbobject.hh" +#include "dbobject_impl.hh" +#include "item.hh" +#include "item_impl.hh" + +#include +#include + +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; +} diff --git a/unittest/tree.cc b/unittest/tree.cc new file mode 100644 index 0000000..da816fc --- /dev/null +++ b/unittest/tree.cc @@ -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 + +#include + +using namespace inventory; +using namespace datamodel; +using namespace inventory::db; + +std::string g_test_db_url; + +template +class TreeTest : public ::testing::Test { +protected: + class Visitor : public DBObjectTree::SubtreeVisitor { + public: + virtual void apply(DBObjectPtr 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 ItemTest; + +TEST_F(ItemTest, ItemTreeTest) { + Visitor visitor; + DBObjectTreePtr tree(new DBObjectTree); + 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(); +}