taocpp / taopq

C++ client library for PostgreSQL
Boost Software License 1.0
264 stars 40 forks source link

Unpacking Rows into structs #56

Closed ninkibah closed 2 years ago

ninkibah commented 2 years ago

I was just looking at some code using the official postgres C++ library, and we have code like:

using PersonTuple = std::tuple<std::string, std::string, int>;
struct Person {
  std::string firstName;
  std::string lastName;
  int dob;
};

Person readFromDB() {
  PersonTuple pt = readRowFromDB();
  return Person{pt.get<0>(), pt.get<1>(), pt.get<2>()};
}

This is bad enough when there are only 3 columns/fields to fill, but in production code we have tables with 10-20 columns.

I would love if there was a clever way of unpacking a tuple into a struct. The best I can think of is to use std::tie:

Person readFromDB() {
  PersonTuple pt = readRowFromDB();
  Person p;
  std::tie(p.firstName, p.lastName, p.dob) = pt;
  return p;
}

If you could make a suggestion in the documentation on how to write such unpacking, I think that would be a help to all users of taopq. Even better would be to have a function that does the unpacking for you, but that's something I can't figure out yet. Possibly some code using Antony Polyukhin's reflection library from Boost: https://apolukhin.github.io/pfr_non_boost/

d-frey commented 2 years ago

I'll have a look after the weekend, I'm currently on a small vacation.

d-frey commented 2 years ago

It's interesting and I managed to add a first attempt, which allows aggregates to be used as parameters and results. For now, you'll have to mark a type as an aggregate for taoPQ explicitly, see https://github.com/taocpp/taopq/blob/96ad4d6af990f260a075c715fb786ef5e9f8abeb/src/test/pq/aggregate.cpp#L21-L22

Obviously, limitations and caveats apply, so please be careful when integrating it in any production code. Also, any feedback is appreciated.

ninkibah commented 2 years ago

That's cool. Now if only we could all use the new branch of Clang that supports reflection! BTW, available on godbolt since this morning.

d-frey commented 2 years ago

Just added another (experimental) trick building on top of the existing conversions. A row will now automatically convert to any registered aggregate type T via row::as<T>(). This allows for a registered aggregate type user:

for( const user u : connection->execute( "SELECT * FROM users" ) ) {
   std::cout << u.name << " is " << u.age << " years old.\n";
}

And the users are only constructed (and destructed) one at a time, so no large temporary arrays/vectors/... of users, only the data that was received by libpq for the result set.