Following on from my earlier question: Very simple PostgreSQL ORM in C++ using libpq (tldr; I'm creating a set of helper classes for working with libpq)
I've decided to have another attack at the datatypes which represent postgresql data types so some sort of ORM can occur with my classes. Following on from the advice in the question, this is what I've come up with. I'm trying to make the base class a bit more powerful and to save having to repeat code for each derived class.
class BaseDataType
{
public:
BaseDataType(bool isCompound) : isCompound(isCompound) {}
virtual ~BaseDataType() {}
virtual std::string toString() const = 0;
// return the amount of params that should be in the string for this data type
// this is used in the fromString method to check that the correct # of params are included in the string
virtual int getParamCount() = 0;
protected:
bool isCompound = false;
void fromString(std::string s)
{
// remove all parenthesis and split on ','
spg::convert::ReplaceAll(s, "(", "");
spg::convert::ReplaceAll(s, ")", "");
std::vector<std::string> splitted = split(s, ',');
if (splitted.size() != getParamCount()) throw "Input string doesn't have enough params";
// each data type will implement its own parseString method that uses the strings in the splitted array
// therefore it's fairly essential that each datatype has a constructor that allows each param to be a string
// TODO this kinda feels a bit messy?
parseString(splitted);
}
// the method that would take the array of strings and populate local members
virtual void parseString(std::vector<std::string>& splitted) = 0;
};
class Point : public BaseDataType
{
public:
Point() : BaseDataType(false) {}
Point(double x, double y) : BaseDataType(false), x(x), y(y) {}
Point(const std::string& s) : BaseDataType(false) { fromString(s); }
Point(std::string& x, std::string& y) : BaseDataType(false) { fromString(x + "," + y); }
std::string toString() const { return std::to_string(x) + "," + std::to_string(y); }
int getParamCount() { return 2; }
double x, y;
protected:
void parseString(std::vector<std::string>& splitted)
{
x = std::stod(splitted[0]);
y = std::stod(splitted[1]);
}
};
class Box : public BaseDataType
{
public:
Box() : BaseDataType(false) {}
Box(double x1, double y1, double x2, double y2) : BaseDataType(false), corner1(x1, y1), corner2(x2, y2) {}
Box(Point corner1, Point corner2) : BaseDataType(false), corner1(corner1), corner2(corner2) {}
Box(const std::string& s) : BaseDataType(false) { fromString(s); }
Box(const std::string& corner1, const std::string& corner2) : BaseDataType(false) { fromString(corner1+","+corner2); }
std::string toString()const { return corner1.toString() + "," + corner2.toString(); }
int getParamCount() { return 4; }
Point corner1, corner2;
protected:
void parseString(std::vector<std::string>& splitted)
{
corner1 = Point(splitted[0],splitted[1]);
corner2 = Point(splitted[2],splitted[3]);
}
};
// a postgres compound data type: CREATE TYPE FOO AS (angle REAL, a_box Box);
class SomeCompoundType : public BaseDataType
{
public:
SomeCompoundType() : BaseDataType(true){}
SomeCompoundType(double angle, Box box) : BaseDataType(true), angle(angle), box(box){}
SomeCompoundType(const std::string& s) : BaseDataType(true) { fromString(s); }
SomeCompoundType(const std::string& angle, std::string& box) : BaseDataType(true) { fromString(angle + "," + box); }
// this needs parenthesis around the outer type as it's a compound string
// TODO I need to work this into the base maybe
std::string toString() const { return "(" + std::to_string(angle) + "\(" + box.toString() + "\))"; }
int getParamCount() { return 5; }
double angle;
Box box;
protected:
// example "(2.35, (10,10,20,25))"
void parseString(std::vector<std::string>& splitted)
{
angle = std::stod(splitted[0]);
// inside a compound datatype, postgres needs parenthesis around data types that have more than one value
box = Box(Point(splitted[1], splitted[2]), Point(splitted[3], splitted[4]));
}
};
The split methods:
std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems) {
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
if (!item.empty())
elems.push_back(item);
}
return elems;
}
std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> elems;
split(s, delim, elems);
return elems;
}
and a basic implementation of the data types:
Point p("10,20");
Box b("1,2,3,4");
SomeCompoundType sct1(2.5, Box(1, 2, 3, 4));
SomeCompoundType sct2("(2.5, \(1,2,3,4\))");
I'm a bit concerned about the growth of constructors for each data type, with a minimum 4 necessary for each type (I'm including a default constructor as, I assume, good practice). Are there any ways around this growth?
libpqtypes
andlibpqxx
? \$\endgroup\$pg_catalog.pg_cast
,pg_catalog.pg_types
, etc, or the underlying header files they're generated from. Hand-coding it all is going to suck when you're working with user-defined types (CREATE TYPE ... AS
), domains, extension types, etc. \$\endgroup\$