Binding Non-primitive Types

>Question:
>1) We like to store non-primitive types in the database like
>complex-numbers. We have been sniffing around in the sourcecode of DTL
>(bind_basics, BoundsIO, etc) how to extend DTL, but there seem to be
>many places where we have to add some code. Do you have some kind of
>'manual' or guideline which routines we should extend/add to DTL in
>order to support 'foreign' types?

Answer:
There are two ways to do this.  The first method is to simply create
a binding operator for your non-primitive type.  The second method
is to add appropriate constructors and cast operators to
the class that uses the non-primitive type.

Method 1

Suppose we have a non primitive type called "cplx" used to store complex numbers. Suppose also that we have a simple table called multiplex that holds some complex numbers: /* create table multiplex ( AC1_re INTEGER NOT NULL, AC1_im INTEGER NOT NULL, AC2_re INTEGER NOT NULL, AC2_im INTEGER NOT NULL); insert into multiplex(AC1_re, AC1_im, AC2_re, AC2_im) VALUES(1, 2, 3,4); */ struct cplx { int re, im; }; ostream& operator << (ostream &o, const cplx &c) { o << "(" << c.re << ", " << c.im << ")"; return o; } // Here is the key step! // Override the binding operator for the cplex // class so that it will bind the individual // members in this class as needed. BoundIO operator >> (BoundIO &b, cplx &c) { BoundIOs *pCols = b.GetBoundIOsPtr(); tstring root_name = b.GetName(); // Remove base column. pCols->EraseColumn(root_name); // Bind individual columns from root_name. // If you don't have a naming convention you can just // pass in desired field names as a comma delimited list and // use vector<tstring> fields = ParseCommaDelimitedList(root_name) (*pCols)[root_name + "_re"] >> c.re; return (*pCols)[root_name + "_im"] >> c.im; } struct multiplex { cplx AC1, AC2; }; ostream& operator << (ostream &o, const multiplex &mp) { o << mp.AC1 << ", " << mp.AC2; return o; } BEGIN_DTL_NAMESPACE template<> class DefaultBCA<multiplex> { public: void operator()(BoundIOs &cols, multiplex &row) { // N.B.!! These binding operators generate two columns each as per // our specialization of the binding operator for cplx cols["AC1"] >> row.AC1; cols["AC2"] >> row.AC2; } }; END_DTL_NAMESPACE void test_non_primitive() { DBView<multiplex> view("multiplex"); copy(view.begin(), view.end(), ostream_iterator<multiplex>(cout, "\n")); }

Method 2

// Example of mapping a user defined type to a database. // In this case the database holds complex numbers as two fields, // but we want to map them into a single "complex" type in our class. // We can hanlde this by defining appropriate constructors and // cast operators for the class. // Define a struct and DBView to hold complex numbers from the table "complex_value" DTL_TABLE2(complex_value, int, real, int, imaginary ); // The above MACRO defines // struct complex_value_row {int real; int imaginary;} // DBView<complex_value_row> complex_value_view; // For our final class we want to represent items in the object/class // differently than they are held in the database so we define our class as class cplx_value { complex c; public: // construct from a database row format for read cplx_value(const complex_value_row &r) : c(r.real, r.imaginary) {} // convert to a database row format for write operator complex_value_row() {complex_value_row r; r.real = c.re(); r.imaginary = c.im(); return r;} } // This cleanly separates the database representation from the class representation, but we can still read/write quite transparently // to or from this class. e.g. void read_function() { // read from DB vector<cplx_value> values; // copy / read just as we normally would, the only difference is that the // iterator holds an representation for the database rows since they // don't exactly match the structure we want for our class copy(complex_value_view.begin(), complex_value_view.end(), back_inserter(values)); }