In a application following an intelligent database design, calls to stored procedures happen very often and thus must be done with as little boilerplate as possible.
Usually frameworks abstracting calls to the database are just ORMs, ignoring completely stored procedures, making the database stupid and moving all the logic in the application.
A year ago, I read on planet PostgreSQL (http://tech.zalando.com/posts/zalando-stored-procedure-wrapper-part-i.html) about a simple system built using Java and the Java annotation and reflection system.
A stored procedure can be called using a few lines of interface :
@SProcService interface BasicExample { @SProcCall long computeProduct(@SProcParam int a, @SProcParam int b); }
Recently, I started planning the development on my spare time of a C++/Qt5 application using a PostgreSQL database and I realized I had just no way to easily call stored procedures. Doing a proper database for the application would thus be a huge pain from a C++ point of view, messing database calls in the middle of the application… Since my C++ skills needed an update (C++11 and C++14 are out in the wild since a few years and I never had an opportunity to use the new features they bring), I figured this would be the best time to do it.
C++ does not have (yet… C++17, I have faith in you) the attributes and introspection used in the stored procedure wrapper of Zalando. Instead, C++ has a great compilation-time processing system through the templates. Templates are not just meant for implementing generics, they are a turing-complete meta-programming language. You can really do a lot of things using them. A lot. For instance, a tuple type working just like a Python tuple, to store a few values of different types side by side. Or implementing a compile-time mathematical function. C++11 and C++14 brought variadic templates, auto and a few other tools
that seemed very powerful and could yield great solutions for my problem.
After a few hours of hacking, I had my first interesting results :
SqlBindingMapper<QDateTime> get_now("now"); qDebug() << get_now();
With a few lines to implement the database connection (using QtSql so far, because I plan to write Qt5 applications with it), these two lines are enough to call NOW() in the database and map the result to a QDateTime, the Qt Date-Time representation object.
Of course, returning a single value from an argument-less function, that’s not really interesting. Let’s sum two digits.
SqlBindingMapper<int, int, int> summer("sum"); qDebug() << summer(1, 2);
And this will display 3.
So that’s for one returned record with one field. What about calling generate_series ?
SqlBindingMapper<QList<int>, int, int> generateSeries("generate_series"); for (auto i: generateSeries(1, 10)) qDebug() << i;
Now, what about the following composite type :
CREATE TYPE card AS (value integer, suit text); CREATE FUNCTION test_card (a card ) RETURNS integer LANGUAGE SQL AS $function$ SELECT $1.value; $function$;
Calling that function in C++ is only requiring you to use std::tuple :
SqlBindingMapper<int, std::tuple<int, QString>> testCard("test_card"); int value = testCard(std::make_tuple(1, "test")); qDebug() << value;
Qt QObject introspection is also supported and during the FOSDEM I hacked support for arrays (ok, Qt vectors, but STL vectors are as easy to support) :
SqlBindingMapper<int, QVector<int>, int> array_length("array_length"); QVector<int> data; data << 1 << 2; qDebug() << "Our dims are :" << array_length(data, 1);
How does all this work behind the scene ? SqlBindingMapper is a template class that take a variadic number of parameters, the first one being the return type. It then implements the operator () returning the specified returning type and taking the specified parameters. A query is then built (at runtime so far, but this could evolve) with placeholders and the appropriate casting, still using templates. The placeholders are then filled, and after coming back from the database, a SqlQueryResultMapper<T> instance maps the rows to the required objects.
So far, the code is a crude hack, done for recreational purpose and for my own education. If there is any interest in a proper explanation of the code and people wanting to use it in real serious projects, I would be happy to help or write more documentation of course.
Right now, the code packaging suck. It’s only 3 headers you’d have to copy from the example on github : https://github.com/pinaraf/StoredProq/ (pg_types.h, queryresult.h and sqlmapper.h)
I hope you had fun reading this !