Skip to content

Instantly share code, notes, and snippets.

@Kludgy
Last active May 8, 2016 04:21
Show Gist options
  • Save Kludgy/99b6b30b0fb12cc19c9f559a1ef784d6 to your computer and use it in GitHub Desktop.
Save Kludgy/99b6b30b0fb12cc19c9f559a1ef784d6 to your computer and use it in GitHub Desktop.
One approach to the typing row data in C++11.
/*
One possible approach to typing SQL result sets in C++11.
TODO: Nominal row type APIs to disambiguate different row schemas that happen to have the same field descriptions.
*/
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using std::cout;
using std::endl;
using std::make_pair;
using std::ostream;
using std::pair;
using std::string;
using std::stringstream;
using std::tuple;
using std::vector;
/*
Some mock data to play with, mimicking naive SQL result set APIs. Same principles apply to any,
- Input data that is poorly typed (here, vector<vector<string>>).
- A data schema (here, vector<sql_field_desc>) that provides the context for interpreting data.
*/
enum sql_field_type
{
sql_field_type_datetime,
sql_field_type_int,
sql_field_type_varchar
};
struct sql_field_type_desc
{
sql_field_type type;
bool is_signed;
};
struct sql_field_desc
{
string name;
sql_field_type_desc type_desc;
};
struct sql_queryresult
{
vector<sql_field_desc> fields;
vector< vector<string> > data; // data[row][col] yields field data
};
static sql_queryresult sql_mock_queryresult_Customer()
{
return
{
{
{ "CustomerID", sql_field_type_int, true },
{ "CustomerName", sql_field_type_varchar, false },
{ "BirthDate", sql_field_type_datetime, false }
},
{
{ "1001", "Joe Bluff", "1543-01-07 00:00:00" },
{ "1002", "Jenny Guff", "3232-32-32 00:00:00" },
{ "1003", "Guy Fluff", "1988-12-31 00:00:00" },
{ "1004", "Matilda Huff", "1989-01-01 00:00:00" }
}
};
}
static sql_queryresult sql_mock_queryresult_Purchase()
{
return
{
{
{ "PurchaseID", sql_field_type_int, true },
{ "PurchaseDate", sql_field_type_datetime, false }
},
{
{ "9991", "2016-01-13 23:33:12" },
{ "9992", "2016-01-13 23:38:03" }
}
};
}
/* unsaturated row */
template <class...> struct DBRowType {};
/* field labels */
class BirthDate{};
class CustomerID{};
class CustomerName{};
class PurchaseID{};
class PurchaseDate{};
/* saturated rows */
typedef DBRowType< CustomerID, CustomerName, BirthDate > CustomerRow;
typedef DBRowType< PurchaseID, PurchaseDate > PurchaseRow;
/* field type labels */
struct DBDate{};
struct DBInt{};
struct DBString{};
/* field -> type relation */
template <class> class DBFieldType{};
template <> struct DBFieldType<BirthDate > { typedef DBDate type; };
template <> struct DBFieldType<CustomerID > { typedef DBInt type; };
template <> struct DBFieldType<CustomerName> { typedef DBString type; };
template <> struct DBFieldType<PurchaseID > { typedef DBInt type; };
template <> struct DBFieldType<PurchaseDate> { typedef DBDate type; };
/* field type -> native representation */
template <class> struct DBNativeType{};
template <> struct DBNativeType<DBDate > { typedef string type; };
template <> struct DBNativeType<DBInt > { typedef int type; };
template <> struct DBNativeType<DBString> { typedef string type; };
/* injecting sql_field_type_desc -> field type */
template <class> bool DBImpliesType(sql_field_type_desc);
template <> bool DBImpliesType<DBDate >(sql_field_type_desc d) { return d.type == sql_field_type_datetime; }
template <> bool DBImpliesType<DBInt >(sql_field_type_desc d) { return d.type == sql_field_type_int && d.is_signed; }
template <> bool DBImpliesType<DBString>(sql_field_type_desc d) { return d.type == sql_field_type_varchar; }
/* field names */
template <class> struct DBFieldName{};
template <> struct DBFieldName<BirthDate > { static constexpr char const * value = "BirthDate"; };
template <> struct DBFieldName<CustomerID > { static constexpr char const * value = "CustomerID"; };
template <> struct DBFieldName<CustomerName> { static constexpr char const * value = "CustomerName"; };
template <> struct DBFieldName<PurchaseID > { static constexpr char const * value = "PurchaseID"; };
template <> struct DBFieldName<PurchaseDate> { static constexpr char const * value = "PurchaseDate"; };
/* inductive row value */
template <class> struct DBRowValue{};
template <class field, class... fields> struct DBRowValue< DBRowType<field, fields...> >
{
typename DBNativeType< typename DBFieldType<field>::type >::type head;
DBRowValue< DBRowType<fields...> > tail;
};
template <> struct DBRowValue< DBRowType<> > {};
/* field -> value relation */
template <class, class> struct DBGetFieldValue;
template <class target, class field, class... fields> struct DBGetFieldValue< target, DBRowType<field, fields...> >
{
static constexpr auto get(DBRowValue< DBRowType<field, fields...> > row) -> decltype(DBGetFieldValue< target, DBRowType<fields...> >::get(row.tail))
{
return DBGetFieldValue< target, DBRowType<fields...> >::get(row.tail);
}
};
template <class target, class... fields> struct DBGetFieldValue< target, DBRowType<target, fields...> >
{
static constexpr typename DBNativeType< typename DBFieldType<target>::type >::type get(DBRowValue< DBRowType<target, fields...> > row)
{
return row.head;
}
};
template <class target> struct DBGetFieldValue< target, DBRowType<> >
{
};
/* nicer api */
template <class rowtype> struct DBRow
{
DBRowValue<rowtype> value;
DBRow(DBRowValue<rowtype> value) : value(value) {}
template <class field> auto get() const -> decltype(DBGetFieldValue<field, rowtype>::get(value))
{
return DBGetFieldValue<field, rowtype>::get(value);
}
};
/* row constructor with type deduction */
template <class... fields> DBRow< DBRowType<fields...> > DBMakeRow(DBRowValue< DBRowType<fields...> > value)
{
return DBRow< DBRowType<fields...> >(value);
}
/* (toy example) field value parsing */
template <class fieldtype> pair<bool, typename DBNativeType<fieldtype>::type> DBParseSQLFieldData(string value);
template <> pair<bool, int> DBParseSQLFieldData<DBInt>(string s)
{
return make_pair(true, atoi(s.c_str()));
}
template <> pair<bool, string> DBParseSQLFieldData<DBDate>(string s)
{
return make_pair(true, s);
}
template <> pair<bool, string> DBParseSQLFieldData<DBString>(string s)
{
return make_pair(true, s);
}
/* inductive row type (schema) validation */
template <class> struct DBValidateRowType;
template <class field, class... fields> struct DBValidateRowType< DBRowType<field, fields...> >
{
static bool validate(vector<sql_field_desc> field_descs, size_t curfield)
{
if (curfield >= field_descs.size())
{
return false;
// out of range-- row type larger than given row description
}
auto field_desc = field_descs[curfield];
if (field_desc.name != DBFieldName<field>::value)
{
return false;
// column name mismatch-- expected DBFieldName<field>::value, but got field_desc.name
}
if (!DBImpliesType<typename DBFieldType<field>::type>(field_desc.type_desc))
{
return false;
// column type mismatch-- field_desc does not imply mydesc
}
return DBValidateRowType< DBRowType<fields...> >::validate(field_descs, curfield+1);
}
};
template <> struct DBValidateRowType< DBRowType<> >
{
static bool validate(vector<sql_field_desc> field_descs, size_t curfield)
{
if (curfield != field_descs.size())
{
return false;
// out of range-- row type smaller than given row description
}
return true;
}
};
/* inductive row value parsing */
template <class> struct DBParseRow;
template <class field, class... fields> struct DBParseRow< DBRowType<field, fields...> >
{
static pair< bool, DBRowValue< DBRowType<field, fields...> > > parse(vector<string> data, size_t curcol)
{
if (curcol >= data.size())
{
return make_pair(false, DBRowValue< DBRowType<field, fields...> >());
// out of range-- row type larger than given row data-- result is garbage
}
auto head = DBParseSQLFieldData< typename DBFieldType<field>::type >(data[curcol]);
if (!head.first)
{
return make_pair(false, DBRowValue< DBRowType<field, fields...> >());
// parsing error in this value-- result is garbage
}
auto tail = DBParseRow< DBRowType<fields...> >::parse(data, curcol+1);
if (!tail.first)
{
return make_pair(false, DBRowValue< DBRowType<field, fields...> >());
// some parsing error occurred in our tail-- result is garbage
}
return make_pair(true, DBRowValue< DBRowType<field, fields...> >{ head.second, tail.second });
}
};
template <> struct DBParseRow< DBRowType<> >
{
static pair< bool, DBRowValue< DBRowType<> > > parse(vector<string> data, size_t curcol)
{
if (curcol != data.size())
{
return make_pair(false, DBRowValue< DBRowType<> >());
// out of range-- row type smaller than given row data-- result is garbage
}
return make_pair(true, DBRowValue< DBRowType<> >{});
}
};
/* example api for row validate+parse */
template <class ROWTYPE> pair< bool, vector< DBRow<ROWTYPE> > > DBBindQueryResult(sql_queryresult raw)
{
typedef DBRow<ROWTYPE> ROW;
auto valid = DBValidateRowType<ROWTYPE>::validate(raw.fields, 0);
if (!valid)
{
return make_pair(false, vector<ROW>{});
// validation failure-- result is garbage
}
auto result = vector<ROW>();
for (auto line : raw.data)
{
auto bound = DBParseRow<ROWTYPE>::parse(line, 0);
if (!bound.first)
{
return make_pair(false, vector<ROW>{});
// row parse failure-- result is garbage
}
else
{
result.push_back(DBMakeRow(bound.second));
}
}
return make_pair(true, result);
}
/* demo ostream */
template <class> struct DBFieldTypeName{};
template <> struct DBFieldTypeName<DBDate > { static constexpr char const * value = "DBDate"; };
template <> struct DBFieldTypeName<DBInt > { static constexpr char const * value = "DBInt"; };
template <> struct DBFieldTypeName<DBString> { static constexpr char const * value = "DBString"; };
template <class rowtype> struct DBPrintRowValue;
template <class field, class... fields> struct DBPrintRowValue< DBRowType<field, fields...> >
{
static ostream& print(ostream& os, DBRowValue< DBRowType<field, fields...> > rowvalue)
{
char const * const fieldname = DBFieldName<field>::value;
char const * const fieldtname = DBFieldTypeName< typename DBFieldType<field>::type >::value;
os << " " << fieldname << ": " << rowvalue.head << " (" << fieldtname << ")" << endl;
return DBPrintRowValue< DBRowType<fields...> >::print(os, rowvalue.tail);
}
};
template <> struct DBPrintRowValue< DBRowType<> >
{
static ostream& print(ostream& os, DBRowValue< DBRowType<> > rowvalue)
{
return os;
}
};
template <class... fields> ostream& operator <<(ostream& os, DBRow< DBRowType<fields...> > row)
{
return DBPrintRowValue< DBRowType<fields...> >::print(os, row.value);
}
/* demonstration of binding */
template <class ROWTYPE, class MAPROW>
void bind_demo(sql_queryresult data, MAPROW maprow)
{
cout << "=============" << endl;
auto result = DBBindQueryResult<ROWTYPE>(data);
if (!result.first)
{
// TODO: Implement error details. :)
cout << "Parse error" << endl;
return;
}
cout << "Parse OK" << endl;
if (result.second.empty())
{
cout << "(no row data available)" << endl;
return;
}
// Print all row data (field name, value, field type)
for (auto row : result.second)
{
cout << "row:" << endl << row << endl;
}
cout << "Transform first row:" << endl;
cout << maprow(result.second[0]);
cout << endl;
}
int main()
{
// Compile error: native / SQL row mismatch
// auto bad_result_1 = DBBindQueryResult<PurchaseRow>(sql_mock_queryresult_Customer());
// Compile error: native row is smaller than SQL row
// typedef DBRowType<CustomerID, CustomerName> CustomerNameRow;
// auto bad_result_2 = DBBindQueryResult<CustomerNameRow>(sql_mock_queryresult_Customer());
// Compile succeeds (generally sloppy admission of equivalence) because row type is structurally
// equivalent.
{
typedef DBRowType<CustomerID, CustomerName, BirthDate> CustomerRow2;
auto ok_result_2 = DBBindQueryResult<CustomerRow2>(sql_mock_queryresult_Customer());
}
bind_demo<CustomerRow>(sql_mock_queryresult_Customer(), [](DBRow<CustomerRow> row) -> string {
stringstream out;
out << row.get<CustomerName>() << " has a birthday on " << row.get<BirthDate>() << "." << endl;
return out.str();
});
bind_demo<PurchaseRow>(sql_mock_queryresult_Purchase(), [](DBRow<PurchaseRow> row) -> string {
stringstream out;
out << "Item " << row.get<PurchaseID>() << " purchased on " << row.get<PurchaseDate>() << "." << endl;
return out.str();
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment