Modern C++ vs ODBC API
Our C++ microservices platform includes a header-only library that encapsulates ODBC API in a very pragmatic way, just like we like it, and also coherent with C++ high-level style, like RAII concepts.
ODBC API is old stuff, it feels somewhat anachronic these days, completely C-oriented, but on the other hand, works very fast and gives you generic database access to most SQL databases, so it looks like a smart choice to use in your C++ projects, plus, you can write portable C++ database code that compiles on Windows, Linux, and Unix, a big plus.
We had to struggle with these old APIs that receive pointers and SQLCHAR arrays, in order to create the illusion of simplicity, in this case, to retrieve a resultset as a JSON std::string...
This library (dblib.h) was created to be used in a multithreaded HTTPS server, it considers topics like connection pooling and reducing heap allocations, it does retrieve all data as SQLCHAR type because it will generate a JSON string, and here starts the fun with C++.
Since it is pretty generic, every data retrieval microservice must execute a SQL query and then retrieve some metadata to read column names, data types, and the data itself converted to an SQLCHAR array (unsigned char).
We could go the C-way, with some native C-style arrays, but there is no need for that, and we won't gain any speed but we will sacrifice safety and flexibility.
Modern C++ to the rescue, std::vector
We need a container for the columns' metadata and the columns' data too, after executing the query we retrieve the number of columns of the resultset, and then std::vector serves the purpose of a fast and flexible container:
As a good practice, we reserve enough space in the vector to avoid repeated allocations, since we know the number of columns this is no challenge. We proceed to add colInfo objects to the vector using an efficient method, and then proceed to bind the column's data buffer (which is of the required size for the data type). All these low-level ODBC mechanics gets hidden from the user of the library. We will come back to the SQLBindCol() line in a minute because something interesting happened in that line.
SQLBind() expects an SQLCHAR buffer to store the retrieved data once we start fetching rows, but we can use another high-level modern C++ abstraction to solve it...
This struct represents a column's metadata and its data, the std::vector<SQLCHAR> field does the magic, no need for dynamic heap allocation with new/free by ourselves, C++ STL will do all the work thanks to the RAII mechanism inherent to the vector, all memory will be properly managed by the abstraction, we just make sure there will be enough space in the container for each column, using the struct's constructor.
Back to the SQLBindCol() line, ODBC API expects a pointer to the first element of an SQLCHAR buffer, we solve this using:
&col.data[0]
col represents a colInfo object, and data is a vector of SQLCHAR, so data[0] is the first element of the vector, since the vector is very efficient when storing its data, like a C-array, we can use it in this way for this particular case, so &col.data[0] gives to ODBC the pointer to the start of the vector's internal buffer that has enough space for this column represented as a null-terminated string.
Reading the data is also a task enhanced by the use of a modern C++ FOR loop and compile-time type deduction:
For each row, we use our vector to read each column's data and name.
That's it. Modern C++ saves the day, by providing high-level abstractions, type safety, and efficient native code. Don't try to be smarter than the STL, that was a lesson we learned in the process, even when using stack instead of the heap and native C-arrays, we did not enhance the performance under load in a multithreaded process but we did introduce safety concerns like buffer overflows and lack of flexibility, among others, and for what? more fun less pain, we prefer the modern C++ way, elegant simplicity.
At the end of the day, uncle Bjarne was right, you are not smarter than an optimizing compiler, and you won't beat the high-level abstractions of the standard library, we should have listened!
Long live Bjarne Stroustrup, the father of C++.
Are you curious about our C++ microservices platform? please read our presentation, "Anatomy of a C++ microservice"