Practices regarding wrapper for setDataBuffer (OCCI)
I have an OracleConnection class that uses the OCCI Oracle API to access the database. I now need to go fetch multiple rows of records from the database and this is done with the ResultSet::getDataBuffer(...) function of the API. This function takes a series of arguments, one of them being a big enum which defines the types of data can contain.
Obviously I don't want to strew my application code with Oracle API types, so other API's could be interchanged with this one. So my question is how would I best take this Type parameter in my function wrapper? Should I just create an enum and take only the types I will need or could templates help me here to map to the enum of OCCI in the OracleConnection class I have?
Occi setDataBuffer function:
void setDataBuffer(
unsigned int colIndex,
void *buffer,
Type type,
sb4 s开发者_JS百科ize = 0,
ub2 *length = NULL,
sb2 *ind = NULL,
ub2 *rc = NULL);
Type
here is an enum that looks like this:
enum Type
{
OCCI_SQLT_CHR=SQLT_CHR,
OCCI_SQLT_NUM=SQLT_NUM,
OCCIINT = SQLT_INT,
OCCIFLOAT = SQLT_FLT,
OCCIBFLOAT = SQLT_BFLOAT,
OCCIBDOUBLE = SQLT_BDOUBLE,
OCCIIBFLOAT = SQLT_IBFLOAT,
OCCIIBDOUBLE = SQLT_IBDOUBLE,
OCCI_SQLT_STR=SQLT_STR,
OCCI_SQLT_VNU=SQLT_VNU,
OCCI_SQLT_PDN=SQLT_PDN,
OCCI_SQLT_LNG=SQLT_LNG,
OCCI_SQLT_VCS=SQLT_VCS,
.... (about 2x as many to go)
my wrapper looks as follows:
void setDataBuffer(unsigned int colIndex, void * buffer, unsigned long size = 0, int type /*use int or template or redefine own Type Enum?*/, unsigned short * length = NULL, signed short * ind = NULL, unsigned short * rc = NULL)
One option could be to make your function a template, and then use a traits class to convert the template type to the values representing the various Oracle types.
The traits class could look like this:
template <typename T>
struct oracle_type_traits;
template <> // create a specialization for each relevant type
struct oracle_type_traits<double> {
static const value = OCCIBDOUBLE // its value member should be the value you want to map to
};
Now, the following will give you the Oracle type id for a double
:
oracle_type_traits<double>::value
and inside setDataBuffer<T>(...)
, you just check oracle_type_traits<T>::value
to get the corresponding Oracle type ID.
From the POV of the users of your wrapper, the best would be if they would call either an overloaded function or a function (member) template that they pass an object to of the appropriate type and which will then magically do the right thing for that type. That is, the best would be to have a function getData(unsigned int colIndex, T&)
for any type T
your class (or the Oracle API) supports, which will find out the necessary buffer size, allocate the buffer, determine the right enum, and call the Oracle API function.
I'm sure you can work out most of the details, probably with the exception of how to map a type to the enum
, so this is what I'll try to line out.
Basically, I see two possibilities for this, one of which (employing a compile-time list) is better suited if you have lots of types to support, while the other one (employing traits) needs to be used if there's more type-specific to this than just mapping a type to an enum
.
The traits method is quite simple to use, but tedious if you have many types:
template<typename T>
struct get_data_buffer_traits;
template<>
struct get_data_buffer_traits<int> {
Type type OCCIINT;
};
template<>
struct get_data_buffer_traits<float> {
Type type OCCIBFLOAT;
};
You can then map the type passed to your template as T
into the right enum
value using get_data_buffer_traits<T>::type
.
This traits template is also the place where you can put any other type-specific operation your generic data retrieval function might need (like converting between what's in the buffer and the actual type, if that isn't a straight-forward cast). If you don't have anything else to put into these traits, you could use a macro to make defining these easier:
#define DEFINE_GET_DATA_BUFFER_TRAITS(Type_,Enum_) \
template<> struct get_data_buffer_traits<Type_> { Type type Enum_; };
DEFINE_GET_DATA_BUFFER_TRAITS(int , OCCIINT );
DEFINE_GET_DATA_BUFFER_TRAITS(float, OCCIBFLOAT);
...
#undef DEFINE_GET_DATA_BUFFER_TRAITS
However, if that's the case, you might as well create a compile-time map that maps the two and search that (at compile-time) for the right enum
value. If you don't have a template meta library at hand that provides this, here's the outline for an idea how to do that yourself:
// Beware, brain-compiled code ahead!
struct nil {};
template< typename HType
, Type HEnum
, class T >
struct set_data_buffer_type_map_node {
typedef HType head_type
enum { head_enum = HEnum };
typedef T tail_type;
};
typedef
set_data_buffer_type_map_node< int , OCCIINT
set_data_buffer_type_map_node< float, OCCIBFLOAT
...
nil
> > // either count or keep adding these until compiler accepts :)
set_data_buffer_type_map;
template< typename T, class Map >
struct getter {
// recurse towards tail
Type get_enum() { return getter<T,typename Map::tail_type>::get_enum(); }
};
template< typename T, Type HEnum, class Tail >
struct getter< T, set_data_buffer_type_map<T,HEnum,Tail> > {
// current node has T as HType
Type get_enum() { return set_data_buffer_type_map<T,HEnum,Tail>::head_enum; }
};
template< typename T, typename HType, Type HEnum, >
struct getter< T, set_data_buffer_type_map<T,HEnum,nil> > {
// no function here, so compile-time error
};
template< typename T>
Type get_type_enum()
{
return getter<T, set_data_buffer_type_map>::get_enum();
}
(Note: This is just an outline. I have not even attempted to compile it. )
I will suggest to go with enum option. Using it as template means your API users should have idea about all the types before which can be bit difficult. Using it as enum also give them as option to refer the enum and decide which SQL types suits the requirement.
精彩评论