Feature #10930
openSupport Polymorphism with Dbo Hierarchy
0%
Description
There have been questions about how to implement this in the Forum
https://redmine.emweb.be/boards/2/topics/18066
https://redmine.emweb.be/boards/2/topics/17642
https://redmine.emweb.be/boards/2/topics/298
I also would like to have the ability to inherit from a Dbo-Table-Class, save the base portion of data within that table and add another table for the derived portion of data.
At the same time, It would help to return a polymorphic pointer to the derived class, if the corresponding base-class was extracted from the db.
I did some thinking, and a lot of reading through the codebase, but a 1-1 relation is impossible without knowing the derived class in the base. At the moment.
What I'd like to propose is a relation schema that might be able to include those features, merely by expanding the types of relations...
This would enable the use of Base Dbo-Table-Classes by themselfes, as well as enable the reuse in other Dbo-Table-Classes, Data and all.
As this is purley theoretical and have not been able to test it, I hope I can convey my thoughts about a possible implementation well enough.
new Functions:
Wt::Dbo::baseClass(a);
-> to be added in every Class that wants to enable Polymorphism
Wt::Dbo::extends<BaseClass>(a);
-> to be added in Derived classes.
Derived from Multiple Dbo-Classes: add another extends()
.
Want to enable deriving from this: add baseClass()
In Effect:
Dbo::baseClass()
:- creates a table
<tableName>_Base
- creates a table
Dbo::extends()
- adds Fields and constraints to the extended
_Base
table - creates a table
<tableName>_Derived
- adds Fields and constraints for every extended
<BaseClass>
to_Derived
table
- adds Fields and constraints to the extended
The tables for the relations could look like this:
create table "<tableName>_Base" (
"base_id" bigint not null,
"derived_<derived_tablename>_id" bigint, /* add field and constraint for every derived class: */
constraint "fk_<tablename>_<derived_tablename>_key1" foreign key ("base_<tablename>_id") references "<tablename>" ("id") on delete cascade,
constraint "fk_<tablename>_<derived_tablename>_key2" foreign key ("derived_<derived_tablename>_id") references "<derived_tablename>" ("id") on delete cascade
/* Not sure on the primary key definition here */
);
create table "<tableName>_Derived" (
"derived_id" bigint not null,
"base_<base_tablename>_id" bigint, /*<add field and constraint for every base class: */
constraint "fk_<base_tablename>_<tablename>_key1" foreign key ("derived_<tablename>_id") references "<tablename>" ("id") on delete cascade,
constraint "fk_<base_tablename>_<tablename>_key2" foreign key ("base_<base_tablename>_id") references "<base_tablename>" ("id") on delete cascade
/* Not sure on the primary key definition here */
);
With an Example Structure like this:
class User
{
protected:
std::string email;
public:
template <class Action>
void persist(Action &a)
{
Wt::Dbo::field(a, email, "email");
Wt::Dbo::baseClass(a);
}
};
class Customer : public User
{
protected:
std::string address;
public:
template <class Action>
void persist(Action &a)
{
Wt::Dbo::field(a, address, "address");
Wt::Dbo::extends<User>(a);
}
};
class Manager : public User
{
protected:
std::string department;
public:
template <class Action>
void persist(Action &a)
{
Wt::Dbo::field(a, department, "department");
Wt::Dbo::extends<User>(a);
}
};
The Tables could look like that:
Table: User_Base
base_id | derived_Customer_id | derived_Manager_id |
---|---|---|
2 | 1 | null |
3 | null | 1 |
Table: Customer_Derived
derived_id | base_User_id |
---|---|
1 | 2 |
Table: Manager_Derived
derived_id | base_User_id |
---|---|
1 | 3 |
Having those extra Tables should help to get all the data together.
I think that might be relatively "easy" to implement. Unfortunately I don't see where or how this might be needed in the Source.
And it would help to have an adaption for Dbo::ptr<C>::get()
to return the polymorphic Element pointer.
Those tables should enable multiple parents and deriving over multiple steps as well, at least in theory.
Looking forward to a possible implementation, or some thoughts about how to do it differently.
Updated by Christian Meyer over 2 years ago
There are issues with the self() function if Inheriting from a Wt::Dbo::Dbo
class BaseClass : public Wt::Dbo::Dbo<BaseClass
{
};
class NextClass : public BaseClass
{
Wt::Dbo::ptr<NextClass> me(){ return self(); } // call to self is ambiguous
};
class NextDboClass : public BaseClass, public Wt::Dbo::Dbo<NextDboClass>
{
Wt::Dbo::ptr<NextDboClass> me(){ return self(); } // call to self is ambiguous // in both variations
};
error: reference to 'self' is ambiguous
/usr/local/include/Wt/Dbo/ptr_impl.h:692:8: note: candidates are: 'Wt::Dbo::ptr<C> Wt::Dbo::Dbo<C>::self() const [with C = NextClass]'
692 | ptr<C> Dbo<C>::self() const
| ^~~~~~
/usr/local/include/Wt/Dbo/ptr_impl.h:692:8: note: 'Wt::Dbo::ptr<C> Wt::Dbo::Dbo<C>::self() const [with C = BaseClass]'
error: request for member 'meta_' is ambiguous
502 | static void setMeta(C& obj, MetaDbo<C> *m) { obj.meta_ = m; }
| ~~~~^~~~~
/usr/local/include/Wt/Dbo/ptr.h:494:15: note: candidates are: 'Wt::Dbo::MetaDbo<NextClass>* Wt::Dbo::Dbo<NextClass>::meta_'
494 | MetaDbo<C> *meta_;
| ^~~~~
/usr/local/include/Wt/Dbo/ptr.h:494:15: note: 'Wt::Dbo::MetaDbo<BaseClass>* Wt::Dbo::Dbo<BaseClass>::meta_'
Updated by Christian Meyer over 2 years ago
I managed to implement something:
// ConnectorTable.h
template <class Base, class Derived>
class ConnectorTable
{
protected:
Wt::Dbo::ptr<Base> basePtr;
Wt::Dbo::ptr<Derived> derivedPtr;
public:
static std::string fieldName()
{
return Wt::WString("derived_{1}_to_{2}")
.arg(Base::refName())
.arg(Derived::refName())
.toUTF8();
}
static std::string refName()
{
return Wt::WString("{1}_{2}_Connector")
.arg(Base::refName())
.arg(Derived::refName())
.toUTF8();
}
ConnectorTable() { }
ConnectorTable(Wt::Dbo::ptr<Base> bPtr, Wt::Dbo::ptr<Derived> dPtr)
: basePtr(bPtr), derivedPtr(dPtr) {}
virtual ~ConnectorTable() {}
Wt::Dbo::ptr<Derived> getDerived() const { return derivedPtr; }
Wt::Dbo::ptr<Base> getBase() const { return basePtr; }
template <class Action>
void persist(Action &a)
{
Wt::Dbo::belongsTo(
a,
basePtr,
"base",
Wt::Dbo::OnDeleteCascade | Wt::Dbo::OnUpdateCascade
);
Wt::Dbo::belongsTo(
a,
derivedPtr,
fieldName(),
Wt::Dbo::OnDeleteCascade | Wt::Dbo::OnUpdateCascade
);
}
};
// Extra Functions to get direct Relation Objects
template <class Base, class Derived>
Wt::Dbo::ptr<Derived> getDerived(Wt::Dbo::ptr<Base> bPtr)
{
Wt::Dbo::Transaction t(dataSession());
auto query = t.session().find<ConnectorTable<Base, Derived>>().where("base_id = ?").bind(bPtr);
auto result = query.resultValue(); // either 1 or 0 results
if(!result)
return {};
return result->getDerived();
}
template <class Base, class Derived>
Wt::Dbo::ptr<Base> getBase(Wt::Dbo::ptr<Derived> dPtr)
{
Wt::Dbo::Transaction t(dataSession());
auto query =
t.session().find<ConnectorTable<Base, Derived>>().where(
ConnectorTable<Base,Derived>::fieldName() + "_id = ?"
).bind(dPtr);
auto result = query.resultValue();
if(!result)
return {};
return result->getBase();
}
This class can be defined for Dbo at the same time as the Derived Class and then added in the mapClass
calls
//Derived.h
#include "Base.h"
#include "ConnectorTable.h"
class Derived;
typedef ConnectorTable<Base, Derived> BaseDerivedConnector
class Derived : public Base
{
Derived() {}
static std::string refName() { return {"Derived"}; }
virtual Wt::Dbo::ptr<Base> getBase() const;
template <class Action>
void persist(Action &a);
private:
Wt::Dbo::weak_ptr<BaseDerivedConnector> baseConPtr;
Wt::Dbo::ptr<Base> basePtr;
protected:
bool has_derived = false;
void updateValues(const Derived* values);
};
DBO_EXTERN_TEMPLATES(BaseDerivedConnector)
DBO_EXTERN_TEMPLATES(Derived)
With a few tweaks in the persist function:
// Derived.cpp
template <class Action>
void Derived::persist(Action &a)
{
if (typeid(a) == typeid(Wt::Dbo::SessionAddAction))
{
// Insert action
// Add the current Object as a Base
basePtr = dataSession().addNew<Base>(this);
}
// Define the fields and connections
Wt::Dbo::hasOne(
a,
baseConPtr,
BaseSpecialConnector::fieldName()
);
Wt::Dbo::belongsTo(a, basePtr);
if (typeid(a) == typeid(Wt::Dbo::LoadDbAction<Derived>))
{
// Load action, after the fields Definition
// Fill the current Base Object with Values from DB
Base::updateValues(basePtr.get());
// like Copy Constructor, but just updating Values
}
}
The Only important functions within each Class that wants to enable the use of Inheritance with this ConnectTable Template are:
private:
Wt::Dbo::ptr<BaseClass> basePtr; // might be optional, but easier to set Internal Values
protected:
bool has_derived; // To be set from next Inheriting // enables looping through selection and looking for more
void updateValues(const Class* values); // called from next Inheriting
public:
static std::string refName(); //with the Name of The Class // Called for RelationNames