|
Boost : |
From: Corwin Joy (cjoy_at_[hidden])
Date: 2005-08-23 22:25:25
More thoughts after looking at the code:
You use std::string for exception messages, names of fields, etc.
I think this is a mistake.
Many databases return internationalized wide error messages and use
wide strings for table and column names. Also, you will ideally
want your library to return internationalized error messages.
In DTL we ended up converting halfway through to support this -
much better to get it right in the first place.
variant: I'm don't think the variant type will exactly fit what you want
here. I like the discriminated union which I would consider to be
a better choice than boost::any since row allocation efficiency is an issue.
One major difference, though, is that you're going to want "sticky types".
The idea here is that once a field is created with a particular type
(by reading what type it should be from the database) it needs to stay
that type. When a user assigns say a string into a date field the field
must not convert into a string type but instead must cast the field
to a date and throw an error if that type of conversion is not legal.
Ideally, this check gets done at compile time.
This "sticky type" logic is what we did in DTL when we did a variant field.
Also, I see that you are binding std::string as the type for character
data in your variant type. Be aware that this leads you down the slippery
slope of having to support arbitrary length strings with the associated
problems that I mentioned in my previous post.
You're also going to want to bind a boost date_time type (maybe the
ptime type), a wstring type, a blob type and possibly a long string type.
Adding new types shouldn't be a big deal though so this could easily
be skipped for the initial design. BUT, I think you will benefit from
including a date type early on since this gives an early example of what
I call "complex types", i.e. types that are held in your class but do not
map directly to a primitive database type but instead require reading into
some kind of intermediate buffer and then translation to give the final
type. In the case of dates, the primitive is (I think) an unsigned long
but ODBC reads dates as a big {YEAR, MONTH, DAY, HH, MM, SS}
type struct which has to be read and then translated. Take a look at
what we did here: http://dtemplatelib.sourceforge.net/fmtUserTypes.htm
to support the ability of users to write their own binding operators to
build
up complex field types as needed.
Your field_description only has about 1/3 of the information you
need to bind a field to a database column. Really, you need three
kinds of sections for a binding (maybe in 3 classes)
1. C++ data type.
2. Database data type and related information. You can't just assume this
from the C++ type. Sometimes you will need to specify extra information
about the SQL type to bind nondefault values for use in things like
mapping to larger strings, specifying non-default precision for dates etc.
The database datatype will be driver layer specific, i.e. different for
ODBC and various native driver layers.
3. Mapping between 1 <--> 2. Can include things like the address of
where to put the data, intermediate buffers that may be used to
manage the mappings, possibly any conversion policy. Here is
an example snippet from DTL where we ended up having all 3 of these
things in a single class:
void *addr; // absolute address of C++ member data
ptrdiff_t offset; // relative offset of this field from base address of its
Row
SDWORD sqlType;
SDWORD cType;
SDWORD paramType; // input, output or input/output type of field
tstring name; // column name
int typeId; // enum for type
bool IsPrimitive; // is this a simple POD type, or one where we first need
to fetch to an intermediate buffer then initialize such as a date or string
type
SDWORD size; // SQL size
SDWORD bufferLength; // how big a buffer do we need to transfer chunks,
this is a member here so that users can override and set it to be large for
big blobs
CountedPtr<SDWORD> bytesFetched;
static const SDWORD lenAtExec; // needed for PutData()
int VariantRowIdx; // -1: It isn't a variant row
BoundIOs_base *pBoundIOs; // refers to collection of bindings that this
object belongs to
BoundType bindingType; // column or param???
int colNo; // column number
MemPtr strbuf; // buffer needed for use with "complex types"
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk