A Better Qt + SQL Experience
Published:
When I was developing a match maker app for the local badminton club, I missed Java so much. Not that I miss the heavy JVM hoarding all your memory, but all the convenience Java brings when it comes to database access. The most crucial part of that convenience, in my opionion, is no need to bind variables to a result row manually.
Imagine this query:
SELECT id, name, age FROM users;
In Java you can pretty much do this;
public class User {
@Column("id")
String id;
@Column("name")
String name;
@Column("age")
int age;
}
List<Users> users = query(sql);
But in Qt, you will have to wire up all the binding yourself:
QSQLQuery query("SELECT * FROM Users");
while (query.next()) {
QString id = query.value(0).toString();
QString name = query.value(1).toString();
int age = query.value(2).toInt();
}
It’s really dumb that you have to repeat yourself like that for every query you make. And you have to be extra careful about the column order here as well. Wouldn’t it be nice to have the compiler does the binding for you?
So I come up with this
#include <QObject>
struct User {
Q_GADGET
DECLARE_PROPERTY(QString, id);
DECLARE_PROPERTY(QString, name);
DECLARE_PROPERTY(int, age);
};
auto rs = get_result_set();
auto user = map_result_set<User>(rs);
How do I do this?
Well the answer lies in Qt’s property system.
Apparently Qt uses moc
to add some basic reflection support
to C++. In the example above, I have added a Q_GADGET
macro,
this tells moc
this structure is a ‘gadget’ - a lightweight
version of QObject
. In Qt, QObject
supports properties and
meta functions - they can be queried and run in the runtime. It
also adds in some meta data about the class. Anyway, the most
important thing that enables us to automatically map database
rows into data structure, is the ability to read and update
the property dynamically. Consider this:
User user;
const QMetaObject &meta = User::staticMetaObject;
// Get property at index 0
QMetaProperty prop0 = meta.property(0);
// Write user's age
if (prop.name() == QStringLiteral("age")) {
prop0.writeOnGadget(reinterpret_cast<void *>(&user), 30);
}
Using this piece of code you can then iterate through dataset and set the value of given column to the gadget’s property.