From d4596cf570dac98e13640d7ff63dba932a5bfd0b Mon Sep 17 00:00:00 2001
From: Dan Goodliffe <dan@randomdan.homeip.net>
Date: Thu, 15 Jan 2015 20:36:27 +0000
Subject: Add the SqlSelectDeserializer for Slicer objects and covering unit
 tests

---
 p2pvr/daemon/Jamfile.jam                           |   3 +
 p2pvr/daemon/sqlSelectDeserializer.cpp             | 171 +++++++++++++++++++++
 p2pvr/daemon/sqlSelectDeserializer.h               |  26 ++++
 p2pvr/daemon/unittests/Jamfile.jam                 |  15 ++
 .../daemon/unittests/testSqlSelectDeserializer.cpp |  81 ++++++++++
 5 files changed, 296 insertions(+)
 create mode 100644 p2pvr/daemon/sqlSelectDeserializer.cpp
 create mode 100644 p2pvr/daemon/sqlSelectDeserializer.h
 create mode 100644 p2pvr/daemon/unittests/testSqlSelectDeserializer.cpp

diff --git a/p2pvr/daemon/Jamfile.jam b/p2pvr/daemon/Jamfile.jam
index d9894d3..5913af1 100644
--- a/p2pvr/daemon/Jamfile.jam
+++ b/p2pvr/daemon/Jamfile.jam
@@ -1,4 +1,5 @@
 import testing ;
+lib slicer : : <name>slicer : : <include>/usr/include/slicer ;
 
 cpp-pch pch : pch.hpp :
 	<library>../ice//p2pvrice
@@ -21,9 +22,11 @@ lib p2pvrdaemon :
 	<library>../devices//p2pvrdevices
 	<library>../daemonbase//p2pvrdaemonbase
 	<implicit-dependency>../../libtmdb//tmdb
+	<library>slicer
 	: :
 	<include>.
 	<library>../ice//p2pvrice
+	<library>slicer
 	;
 
 unit-test testEmbedding :
diff --git a/p2pvr/daemon/sqlSelectDeserializer.cpp b/p2pvr/daemon/sqlSelectDeserializer.cpp
new file mode 100644
index 0000000..0e87a64
--- /dev/null
+++ b/p2pvr/daemon/sqlSelectDeserializer.cpp
@@ -0,0 +1,171 @@
+#include "sqlSelectDeserializer.h"
+#include <sqlHandleAsVariableType.h>
+#include <logger.h>
+#include <boost/algorithm/string/predicate.hpp>
+#include <stdexcept>
+
+VariableType
+operator/(DB::SelectCommand & cmd, unsigned int col)
+{
+	HandleAsVariableType vt;
+	cmd[col].apply(vt);
+	return vt.variable;
+}
+
+class SqlSource : public DB::HandleField, public Slicer::ValueSource,
+		public Slicer::TValueSource<boost::posix_time::time_duration>,
+		public Slicer::TValueSource<boost::posix_time::ptime> {
+	public:
+		bool isNull() const
+		{
+			return vt.isNull();
+		}
+
+		void interval(const boost::posix_time::time_duration & d) override
+		{
+			vt = d;
+		}
+		void null() override
+		{
+			vt = Null();
+		}
+		void string(const char * s, size_t l) override
+		{
+			vt = Glib::ustring(s, l);
+		}
+		void integer(int64_t i) override
+		{
+			vt = i;
+		}
+		void floatingpoint(double fp) override
+		{
+			vt = fp;
+		}
+		void timestamp(const boost::posix_time::ptime & t)
+		{
+			vt = t;
+		}
+#define SET(Type) \
+    void set(Type & b) const override { \
+			b = vt.as<Type>(); \
+		}
+#define SETNCONV(Type, CType) \
+    void set(Type & b) const override { \
+			b = boost::numeric_cast<Type>(vt.as<CType>()); \
+		}
+		SET(bool);
+		SET(std::string);
+		SETNCONV(Ice::Byte, int64_t);
+		SETNCONV(Ice::Short, int64_t);
+		SET(Ice::Int);
+		SET(Ice::Long);
+		SETNCONV(Ice::Float, double);
+		SET(Ice::Double);
+		SET(boost::posix_time::ptime);
+		SET(boost::posix_time::time_duration);
+
+	private:
+		VariableType vt;
+};
+typedef IceUtil::Handle<SqlSource> SqlSourcePtr;
+
+SqlSelectDeserializer::SqlSelectDeserializer(DB::SelectCommand & c, IceUtil::Optional<std::string> tc) :
+	cmd(c),
+	typeIdColName(tc)
+{
+}
+
+void
+SqlSelectDeserializer::Deserialize(Slicer::ModelPartPtr mp)
+{
+	cmd.execute();
+	columnCount = cmd.columnCount();
+	if (typeIdColName) {
+		typeIdColIdx = cmd.getOrdinal(*typeIdColName);
+	}
+	switch (mp->GetType()) {
+		case Slicer::mpt_Sequence:
+			DeserializeSequence(mp);
+			return;
+		case Slicer::mpt_Complex:
+			DeserializeObject(mp);
+			return;
+		case Slicer::mpt_Simple:
+			DeserializeSimple(mp);
+			return;
+		default:
+			throw std::invalid_argument("Unspported model type");
+	}
+}
+
+void
+SqlSelectDeserializer::DeserializeSimple(Slicer::ModelPartPtr mp)
+{
+	auto fmp = mp->GetAnonChild();
+	if (!cmd.fetch()) {
+		std::invalid_argument("No rows returned");
+	}
+	SqlSourcePtr h = new SqlSource();
+	const DB::Column & c = cmd[0];
+	c.apply(*h);
+	if (!h->isNull()) {
+		fmp->Create();
+		fmp->SetValue(h);
+		fmp->Complete();
+	}
+	if (cmd.fetch()) {
+		std::invalid_argument("Too many rows returned");
+	}
+}
+
+void
+SqlSelectDeserializer::DeserializeSequence(Slicer::ModelPartPtr mp)
+{
+	mp = mp->GetAnonChild();
+	SqlSourcePtr h = new SqlSource();
+	while (cmd.fetch()) {
+		DeserializeRow(mp);
+	}
+}
+
+void
+SqlSelectDeserializer::DeserializeObject(Slicer::ModelPartPtr mp)
+{
+	if (!cmd.fetch()) {
+		std::invalid_argument("No rows returned");
+	}
+	DeserializeRow(mp);
+	if (cmd.fetch()) {
+		std::invalid_argument("Too many rows returned");
+	}
+}
+
+void
+SqlSelectDeserializer::DeserializeRow(Slicer::ModelPartPtr mp)
+{
+	SqlSourcePtr h = new SqlSource();
+	auto rmp = mp->GetAnonChild();
+	if (rmp) {
+		if (typeIdColIdx) {
+			rmp = rmp->GetSubclassModelPart(cmd / *typeIdColIdx);
+		}
+		rmp->Create();
+		for (auto col = 0u; col < columnCount; col += 1) {
+			const DB::Column & c = cmd[col];
+			auto fmpr = rmp->GetAnonChildRef([&c](Slicer::HookCommonPtr h) {
+					return boost::iequals(c.name.raw(), h->PartName());
+					});
+			if (fmpr) {
+				auto fmp = fmpr->Child();
+				c.apply(*h);
+				if (!h->isNull()) {
+					fmp->Create();
+					fmp->SetValue(h);
+					fmp->Complete();
+				}
+			}
+		}
+		rmp->Complete();
+	}
+}
+
diff --git a/p2pvr/daemon/sqlSelectDeserializer.h b/p2pvr/daemon/sqlSelectDeserializer.h
new file mode 100644
index 0000000..008a87c
--- /dev/null
+++ b/p2pvr/daemon/sqlSelectDeserializer.h
@@ -0,0 +1,26 @@
+#ifndef P2PVR_SQL_DESERIALIZER_H
+#define P2PVR_SQL_DESERIALIZER_H
+
+#include <slicer/serializer.h>
+#include <selectcommand.h>
+
+class SqlSelectDeserializer : public Slicer::Deserializer {
+	public:
+		SqlSelectDeserializer(DB::SelectCommand &, IceUtil::Optional<std::string> typeIdCol = IceUtil::Optional<std::string>());
+
+		virtual void Deserialize(Slicer::ModelPartPtr) override;
+
+	protected:
+		void DeserializeSimple(Slicer::ModelPartPtr);
+		void DeserializeObject(Slicer::ModelPartPtr);
+		void DeserializeSequence(Slicer::ModelPartPtr);
+		void DeserializeRow(Slicer::ModelPartPtr);
+
+		DB::SelectCommand & cmd;
+		unsigned int columnCount;
+		IceUtil::Optional<std::string> typeIdColName;
+		IceUtil::Optional<unsigned int> typeIdColIdx;
+};
+
+#endif
+
diff --git a/p2pvr/daemon/unittests/Jamfile.jam b/p2pvr/daemon/unittests/Jamfile.jam
index f07338f..c9ebc5a 100644
--- a/p2pvr/daemon/unittests/Jamfile.jam
+++ b/p2pvr/daemon/unittests/Jamfile.jam
@@ -121,3 +121,18 @@ unit-test testStorage :
 	<define>ROOT=\"$(me)\"
 	;
 
+unit-test testSqlSelectDeserializer :
+	testSqlSelectDeserializer.cpp
+	:
+	<define>BOOST_TEST_DYN_LINK
+	<library>../..//p2common
+	<library>../..//p2sql
+	<library>../..//p2ut
+	<library>../..//p2xml
+	<library>..//p2pvrdaemon
+	<library>IceUtil
+	<library>Ice
+	<library>../..//boost_utf
+	<define>ROOT=\"$(me)\"
+	;
+
diff --git a/p2pvr/daemon/unittests/testSqlSelectDeserializer.cpp b/p2pvr/daemon/unittests/testSqlSelectDeserializer.cpp
new file mode 100644
index 0000000..79990cb
--- /dev/null
+++ b/p2pvr/daemon/unittests/testSqlSelectDeserializer.cpp
@@ -0,0 +1,81 @@
+#define BOOST_TEST_MODULE SqlSelectDeserializer
+#include <boost/test/unit_test.hpp>
+#include <sqlSelectDeserializer.h>
+#include <slicer/slicer.h>
+#include <connection.h>
+#include <p2pvr.h>
+#include <rdbmsDataSource.h>
+#include <commonObjects.h>
+#include <testOptionsSource.h>
+#include <boost/filesystem/operations.hpp>
+#include <definedDirs.h>
+#include <commonHelpers.h>
+
+typedef boost::shared_ptr<DB::SelectCommand> SelectPtr;
+
+class TestCommonObjects : public CommonObjects {
+	public:
+		TestCommonObjects()
+		{
+			TestOptionsSource::LoadTestOptions({
+					{ "common.datasourceRoot", (RootDir / "datasources").string() },
+				});
+		}
+};
+BOOST_FIXTURE_TEST_SUITE ( Sql, TestCommonObjects );
+
+BOOST_AUTO_TEST_CASE( listOfEvents )
+{
+	auto db = dataSource<RdbmsDataSource>("postgres")->getReadonly();
+	auto sel = SelectPtr(db->newSelectCommand("SELECT * FROM events ORDER BY serviceId, eventId LIMIT 100"));
+	auto res = Slicer::DeserializeAny<SqlSelectDeserializer, P2PVR::Events>(*sel);
+	BOOST_REQUIRE_EQUAL(res.size(), 100);
+	BOOST_REQUIRE_EQUAL(res[0]->ServiceId, 4166);
+	BOOST_REQUIRE_EQUAL(res[0]->EventId, 49741);
+	BOOST_REQUIRE_EQUAL(res[0]->Title, "Skiing Weatherview");
+	BOOST_REQUIRE(!res[0]->Subtitle);
+	BOOST_REQUIRE_EQUAL(res[0]->Description, "Detailed weather forecast.");
+	BOOST_REQUIRE_EQUAL(res[0]->StartTime, Common::DateTime({2014, 12, 19, 0, 25}));
+	BOOST_REQUIRE_EQUAL(res[0]->StopTime, Common::DateTime({2014, 12, 19, 0, 30}));
+	BOOST_REQUIRE(res[0]->Current);
+	BOOST_REQUIRE_EQUAL(res[99]->ServiceId, 4166);
+	BOOST_REQUIRE_EQUAL(res[99]->EventId, 52448);
+	BOOST_REQUIRE_EQUAL(res[99]->Title, "East Midlands Today");
+}
+
+BOOST_AUTO_TEST_CASE( singleField )
+{
+	auto db = dataSource<RdbmsDataSource>("postgres")->getReadonly();
+	auto sel = SelectPtr(db->newSelectCommand("SELECT EventId FROM events ORDER BY serviceId, eventId LIMIT 1"));
+	auto res = Slicer::DeserializeAny<SqlSelectDeserializer, int>(*sel);
+	BOOST_REQUIRE_EQUAL(res, 49741);
+}
+
+BOOST_AUTO_TEST_CASE( singleEvent )
+{
+	auto db = dataSource<RdbmsDataSource>("postgres")->getReadonly();
+	auto sel = SelectPtr(db->newSelectCommand("SELECT * FROM events ORDER BY serviceId, eventId LIMIT 1"));
+	auto res = Slicer::DeserializeAny<SqlSelectDeserializer, P2PVR::EventPtr>(*sel);
+	BOOST_REQUIRE_EQUAL(res->ServiceId, 4166);
+	BOOST_REQUIRE_EQUAL(res->EventId, 49741);
+	BOOST_REQUIRE_EQUAL(res->Title, "Skiing Weatherview");
+	BOOST_REQUIRE(!res->Subtitle);
+	BOOST_REQUIRE_EQUAL(res->Description, "Detailed weather forecast.");
+	BOOST_REQUIRE_EQUAL(res->StartTime, Common::DateTime({2014, 12, 19, 0, 25}));
+	BOOST_REQUIRE_EQUAL(res->StopTime, Common::DateTime({2014, 12, 19, 0, 30}));
+}
+
+BOOST_AUTO_TEST_CASE( dynamicTypes )
+{
+	auto db = dataSource<RdbmsDataSource>("postgres")->getReadonly();
+	auto sel = SelectPtr(db->newSelectCommand("SELECT d.*, '::DVBSI::TerrestrialDelivery' \"typeid\" FROM delivery_dvbt d ORDER BY TransportStreamId"));
+	auto res = Slicer::DeserializeAny<SqlSelectDeserializer, P2PVR::Deliveries>(*sel, "typeid");
+	BOOST_REQUIRE_EQUAL(res.size(), 6);
+	auto dvbt = DVBSI::TerrestrialDeliveryPtr::dynamicCast(res[0]);
+	BOOST_REQUIRE_EQUAL(dvbt->Frequency, 682000000);
+	BOOST_REQUIRE_EQUAL(dvbt->TransportStreamId, 4170);
+	BOOST_REQUIRE_EQUAL(dvbt->CodeRateHP, 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END();
+
-- 
cgit v1.2.3