patterncppMinor
Very simple PostgreSQL ORM in C++ using libpq
Viewed 0 times
postgresqlsimpleormlibpqusingvery
Problem
I'm working on a set of helper classes for working with libpq in C++ and I want to be able to pass in objects to the helper classes and have them internally represented as strings when sent to the db, and convert from string to objects when fetching data. I'm not particularly after a crazy ORM allowing custom objects, only the known PostgreSQL data types will suffice.
So, I'm thinking about a class for each data type, and each class managing the conversion to and from strings, using a method for each data type,
The problem I face now - which gets rather messy - is how to convert from string to these objects, when for example, a postgresql box is returned as
```
class IDataType
{
public:
virtual std::string toString() = 0;
std::string toEscapedString()
{
std::string s = toString();
spg::convert::ReplaceAll(s, "(", "\"(");
spg::convert::ReplaceAll(s, ")", ")\"");
// as replace all add's a backslash to the start and end of the string if compound type
if (isCompound)
s = s.substr(1, s.length() - 2);
// remove double \"(\"( and )\")\" caused by compound types
spg::convert::ReplaceAll(s, "\"(\"(", "\"(");
spg::convert::ReplaceAll(s, "\")\")", "\")");
return s;
}
protected:
IDataType(bool isCompound) : isCompound(isCompound) {}
bool isCompound = false;
};
// helper class for numeric types
// limit to int,float,double etc
template
class Number : public IDataType
{
public:
Number(T val) : IDataType(false), val(val)
So, I'm thinking about a class for each data type, and each class managing the conversion to and from strings, using a method for each data type,
toString() and fromString() because at the moment, data that is sent to the insert methods for inserting into the database is in the form of strings.The problem I face now - which gets rather messy - is how to convert from string to these objects, when for example, a postgresql box is returned as
(x1,y1),(x2,y2) but could be represented by the user as x1,y1,x1,y1, (x1,y1,x2,y2), (x1,y1),(x2,y2) or ((x1,y1),(x2,y2)) but I'm assuming that simple regex will suffice there?```
class IDataType
{
public:
virtual std::string toString() = 0;
std::string toEscapedString()
{
std::string s = toString();
spg::convert::ReplaceAll(s, "(", "\"(");
spg::convert::ReplaceAll(s, ")", ")\"");
// as replace all add's a backslash to the start and end of the string if compound type
if (isCompound)
s = s.substr(1, s.length() - 2);
// remove double \"(\"( and )\")\" caused by compound types
spg::convert::ReplaceAll(s, "\"(\"(", "\"(");
spg::convert::ReplaceAll(s, "\")\")", "\")");
return s;
}
protected:
IDataType(bool isCompound) : isCompound(isCompound) {}
bool isCompound = false;
};
// helper class for numeric types
// limit to int,float,double etc
template
class Number : public IDataType
{
public:
Number(T val) : IDataType(false), val(val)
Solution
Firstly, and most importantly, you're missing a virtual destructor for your
For 1, you could do this using templates. However, you don't have to, assuming you have access to C++11. There is a free function defined in `
IDataType definition. This is bad, and should be the first thing you fix:class IDataType
{
virtual ~IDataType() { }
}For 1, you could do this using templates. However, you don't have to, assuming you have access to C++11. There is a free function defined in `
called to_string that has an overload for most of the basic numeric types. Hence any place you're calling one of your numberToString methods, you could simply replace this with std::to_string(...) (see here for the documentation of to_string).
Converting back the other way is probably going to require a bit more effort, however. You could potentially do this with regex (but note that creating a regex that will accept only integers or doubles is somewhat difficult and error prone; cases like 1. or .4 or 1e5 are very easy to overlook and fiddly to get right). In this case, the only real difference seems to be that there are (multiple) parens or no parens. Perhaps a better way would be to reduce it to a format with no parens, and then parse that:
class Point : public IDataType
{
void fromString(std::string s)
{
// Remove parenthesis, leaving (hopefully) only "x,y"
s.erase(std::remove_if(s.begin(), s.end(),
[](char c) { return c == '(' || c == ')'; },
s.end());
// Split on ","
std::vector split;
boost::split(split, s, boost::is_any_of(","));
// Make sure there are only 2 values
// May also want to do some checking around lexical_cast in case it fails
x = Number(boost::lexical_cast(split[0]));
y = Number(boost::lexical_cast(split[1]));
}
....
}
A very similar algorithm can be used for Box (N.B. I haven't tried to compile the above code, so I apologize in advance if there are any errors in it. Hopefully you get the general idea, though).
On a separate note, I'm not a huge fan of the design. The idea seems to be you can create an IDataType object which is mutated by calling fromString. Given the small size of these in general (and unless you really need this to be high performance), you might want to consider making them immutable:
class Point : public IDataType
{
public:
Point(double x, double y)
: IDataType(false),
x(x),
y(y)
{ }
Point(const std::string& s)
{
fromString(s);
}
std::string toString(....) const { } // Note: your toString method should be const
private:
void fromString(std::string s)
{
// Logic
}
};
This has a few benefits:
- Easier to reason about; once you've instantiated it with given parameters, you don't need to worry about things changing through a call to
fromString.
- Easier to deal with exceptions: everything is done through the constructor, so you either get back a fully formed correct object, or the constructor throws. Currently, you need to make sure that your
fromString` function doesn't leave the object in an inconsistent state if it throws.Code Snippets
class IDataType
{
virtual ~IDataType() { }
}class Point : public IDataType
{
void fromString(std::string s)
{
// Remove parenthesis, leaving (hopefully) only "x,y"
s.erase(std::remove_if(s.begin(), s.end(),
[](char c) { return c == '(' || c == ')'; },
s.end());
// Split on ","
std::vector<std::string> split;
boost::split(split, s, boost::is_any_of(","));
// Make sure there are only 2 values
// May also want to do some checking around lexical_cast in case it fails
x = Number(boost::lexical_cast<double>(split[0]));
y = Number(boost::lexical_cast<double>(split[1]));
}
....
}class Point : public IDataType
{
public:
Point(double x, double y)
: IDataType(false),
x(x),
y(y)
{ }
Point(const std::string& s)
{
fromString(s);
}
std::string toString(....) const { } // Note: your toString method should be const
private:
void fromString(std::string s)
{
// Logic
}
};Context
StackExchange Code Review Q#59663, answer score: 3
Revisions (0)
No revisions yet.