Introduction
Good afternoon, dear fellow programmers!
For a year now, very interesting events have taken place in the Qt world. Here you will find both the recent release of version 4.7, the concept of QML, and significant library integration into mobile platforms. Qt has the most correct trolls; I like what they do and where this library develops. I am even ready to argue that she is the best in her class, but those who write in Qt already know this.
')
There is something else that has changed over the year. For Qt, ORM libraries began to appear, one after another, like mushrooms. A holy place is never empty? There is a demand, here is your offer. Read what’s happening in the Qt ORM world in this article. I will try to give as much information as possible on the use and mechanisms used in the libraries reviewed; but none of them can be fully consecrated due to the fact that any ORM is a very complex set of programmer solutions.
(I will note right away that the article is to some extent an advertisement, because it appeared because of my own ORM; however, in fairness, I not only promote myself, but I also give a general assessment of what is on the topic now. Please treat with understanding: good intentions).
QxOrm, ver. 1.1.1
Author / owner : “QxOrm France”
Sites :
Official ,
at SourceForgeLicense : GPLv3 + commercial
Dependencies : Qt (4.5+), boost (1.38+)
Development period : beginning of 2010, last modified - April 2010
Documentation : incomplete, in french
Examples : yes, the most basic
The main goal of the development is to provide persistence (via QtSql), serialization (via boost :: serialization) and reflection.The library looks very strong and seems to be able to do a lot of things. Built on the principle of DAO (
Data Access Object ), when there is a class-displaying a row in a table, and a list of such rows is retrieved from some database using some methods. To make this possible, QxOrm uses very tricky mechanisms, including templates (a lot of templates), macros, simple and multiple inheritance. The code is very interesting for review if you are a fan of programmer tricks or, for example, you like Alexandrescu.
Judging by the examples, code and descriptions, the following features are implemented.
- Display table data in any stl / boost container. I assume that Qt containers will work too.
- Some complexity caching.
- Generating simplest queries: insert, update, delete, create table, and, of course, select.
- A wrapper over QSqlDatabase, your own QxSqlQuery.
- One-to-one, one-to-many, many-to-many links at the code level.
- Own stl-like QxCollection collection of key / value type with hashing, sorting by key and value.
- The template implementation of the foreach (!) Loop, the principles of which I can only guess.
- Implemented a template singleton.
Pros:
- The general orientation is not on databases, but on abstract storage, including XML. True, this is achieved at the expense of Boost - “on sparrows from a bazooka”.
- Good ORM capabilities, transferring selected values ​​directly to containers.
- A bunch of additional features: serialization, containers, caching, etc.
- Simple syntax for working with mappers. It can be seen that the author was guided by something that existed when designing interfaces. Everything is logical, neat, very similar to Boost and STL. Qt style - minimum.
- Manufacturability of the library; however, this is exactly the minus, because it is very difficult to study its internal cuisine.
Minuses:
- Overall focus is not on databases, but on abstract storage.
- Weak SQL support; in particular, there is no generation of WHERE, JOIN, GROUP BY, ORDER BY and much more. For example, to extract specific data by a filter, you must select them all, and then apply STL / Boost algorithms. (However, I’m not sure about everything; maybe I missed something. However, the source code doesn’t have a WHERE generation - this is a fact.)
- Weak documentation. Perhaps the author believes that examples and tutorials are enough, but I do not think so. We, Qt-programmers, are accustomed to good and complete documentation. There are no descriptions of classes, methods and constants. Well, what is - in French.
- The confusion of the library. Do not even hope that you can add there something of your own.
- Boost dependency.
- License. This is not to say that she is completely free; Want to sell a library based product? Pay the author.
- The main thing: the library does not develop, the documentation is not translated. The author first widely publicized QxOrm on foreign Internet, and then disappeared. Version 1.1.1 was the first and only.
Paradoxically, with all the drawbacks, QxOrm is almost the only full-fledged ORM solution compatible with Qt. And this is the only solution where there is caching, which is important for complex projects. You will see that this tiny review is still more than others, since other ORMs can hardly be compared to QxOrm. However, using the library in a large project, you may want some other opportunity, especially if you are working not with an abstract repository, but with a full-fledged DBMS - but it will not exist. You may want to fix a bug, but this is not so easy. You will have to reinvent a lot of bicycles and crutches. The project eventually will inevitably turn into a chimera. On the contrary, for a small project where only a few good functions are needed, the library can be useful, to the extent that you are not afraid to pull up and Boost.
Example of a mapper class (all code is taken from the documentation):
class drug
{
public :
long id ;
QString name ;
QString description ;
drug ( ) : id ( 0 ) { ; }
virtual ~ drug ( ) { ; }
} ;
QX_REGISTER_HPP_MY_TEST_EXE ( drug, qx :: trait :: no_base_class_defined , 1 )
QX_REGISTER_CPP_MY_TEST_EXE ( drug ) // QxOrm context
namespace qx {
template <> void register_class ( QxClass < drug > & t )
{
t. id ( & drug :: id , "id" ) ; // Register 'drug :: id' <=> primary key in your database
t. data ( & drug :: name , "name" , 1 ) ; // Register 'drug :: name' property with key 'name' and version '1'
t. data ( & drug :: description , "desc" ) ; // Register 'drug :: description' property with key 'desc'
} }
Example of use:
// Create table 'drug' into database to store drugs
QSqlError daoError = qx :: dao :: create_table < drug > ( ) ;
// Insert drugs from container to database
// 'id' property of 'd1', 'd2' and 'd3' are auto-updated
daoError = qx :: dao :: insert ( lst_drug ) ;
// Modify and update the second drug into database
d2 - > name = "name2 modified" ;
d2 - > description = "desc2 modified" ;
daoError = qx :: dao :: update ( d2 ) ;
// Delete the first drug from database
daoError = qx :: dao :: delete_by_id ( d1 ) ;
// Count drugs into database
long lDrugCount = qx :: dao :: count < drug > ( ) ;
// Fetch drug with id '3' into a new variable
drug_ptr d_tmp ; d_tmp. reset ( new drug ( ) ) ;
d_tmp - > id = 3 ;
daoError = qx :: dao :: fetch_by_id ( d_tmp ) ;
// Export Undergrounds
qx :: serialization :: xml :: to_file ( lst_drug, "./export_drugs.xml" ) ;
// Import drugs from xml file into a new container
type_lst_drug lst_drug_tmp ;
qx :: serialization :: xml :: from_file ( lst_drug_tmp, "./export_drugs.xml" ) ;
// Clone a drug
drug_ptr d_clone = qx :: clone ( * d1 ) ;
// Create a new drug by class name (factory)
boost :: any d_any = qx :: create ( "drug" ) ;
// Insert drugs container into 'qx :: cache'
qx :: cache :: set ( "drugs" , lst_drug ) ;
QDjango, ver. ???
Posted by : Jeremy Lainé, Bolloré telecom
Sites :
official ,
mailing listLicense : GPLv3, LGPLv3
Dependencies : Qt (4.5+)
In development since June 3, 2010
Documentation : complete, in English, doxygen-generated
Examples : there is, the most basic.
The main goal : to create a free ORM for Qt, the most similar to Django.It’s too early to talk about the advantages and disadvantages of this development, the library is still unable to do anything. Apparently, it will be DAO / Active Record-ORM. Already it is possible to generate SELECT, to retrieve data in the container, to create tables through generation CREATE TABLE. The author immediately began to prescribe the generation WHERE; the operators AND and OR are supported. That is, you can create a multi-level filter, and the syntax for specifying filters is also good. The development actively uses the same methods as in QxOrm: patterns, inheritance ... Based on them, I guess, the author is going to create huge farms of good OOP code.
But that is all. We will believe that in a year and a half a powerful ORM system will grow from QDjango, but for now it is not possible to talk about its use in projects.
Example of use taken from the author.
// all users
QDjangoQuerySet < User > users ;
// find all users whose password is "foo" and whose username is not "bar"
QDjangoQuerySet < User > someUsers ;
someUsers = users. filter ( QDjangoWhere ( "password" , QDjangoWhere :: Equals , "foo" ) &&
QDjangoWhere ( "username" , QDjangoWhere :: NotEquals , "bar" ) ) ;
// find all users whose username is "foo" or "bar"
someUsers = users. filter ( QDjangoWhere ( "username" , QDjangoWhere :: Equals , "foo" ) ||
QDjangoWhere ( "username" , QDjangoWhere :: Equals , "bar" ) ) ;
// find all users whose username starts with "f":
someUsers = users. filter ( QDjangoWhere ( "username" , QDjangoWhere :: StartsWith , "f" ) ) ;
// limit number of results
someUsers = users. limit ( 0 , 100 ) ;
// get number of matching users
int numberOfUsers = someUsers. size ( ) ;
// retrieve the first matching user
User * firstUser = someUsers. at ( 0 ) ;
// free memory
delete firstUser ;
// retrieve list of users and passwords for matching users
QList < QList < QVariant >> propertyLists = someUsers. valuesList ( QStringList ( ) << "username" << "password" ) ;
// delete all users in the queryset
someUsers. remove ( ) ;
User class:
class User : public QDjangoModel
{
Q_OBJECT
Q_PROPERTY ( QString username READ username WRITE setUsername )
Q_PROPERTY ( QString password READ password WRITE setPassword )
Q_CLASSINFO ( "username" , "max_length = 255" )
Q_CLASSINFO ( "password" , "max_length = 128" )
public :
QString username ( ) const ;
void setUsername ( const QString & username ) ;
QString password ( ) const ;
void setPassword ( const QString & password ) ;
private :
QString m_username ;
QString m_password ;
} ;
QtPersistence, ver. 0.1.1
By Matt Rogers
Sites :
SourceForgeLicense : LGPLv3
Dependencies : Qt (4.5+)
Development period : end of 2009 - beginning of 2010
Documentation : no
Examples : bad in unit tests
The main goal : to create an ORM for Qt, based on the Active Record approach, similar to some (?) ORM for Ruby.Another library that can do almost nothing. What is worse: and does not develop; It seems that the author abandoned this project. Actually, all that it can do is to write data to the database using a mapper class.
The only examples of use are found in unit tests (based on a self-written test module).
class FakeBook : public QPersistantObject
{
Q_OBJECT
Q_PROPERTY ( QString author READ author set setAuthor WRITE )
Q_PROPERTY ( int yearPublished READ yearPublished WRITE setYearPublished )
public :
Q_INVOKABLE FakeBook ( QObject * parent = 0 ) : QPersistantObject ( parent ) { }
virtual ~ FakeBook ( ) { }
void setAuthor ( const QString & a ) { m_author = a ; }
QString author ( ) const { return m_author ; }
void setYearPublished ( int year ) { m_yearPublished = year ; }
int yearPublished ( ) const { Return m_yearPublished ; }
private :
QString m_author ;
int m_yearPublished ;
} ;
FakeBook * b = new FakeBook ;
b - > setAuthor ( "Matt Rogers" ) ;
b - > setYearPublished ( 2009 ) ;
bool objectSaved = b - > save ( ) ;
delete b ;
ASSERT_TRUE ( objectSaved ) ;
b = new FakeBook ;
b - > setAuthor ( "Not Matt Rogers" ) ;
b - > setYearPublished ( 1999 ) ;
objectSaved = b - > save ( ) ;
delete b ;
ASSERT_TRUE ( objectSaved ) ;
QsT SQL Tools (QST), ver. 0.4.2a release
The author : Alexander Granin (I :))
Sites :
SourceForge ,
forumLicense : GPLv3, LGPLv3
Dependencies : Qt (4.5+)
In development since September 2009
Documentation : full, doxygen-generated, only in Russian
Examples : is, in the code, in unit tests; I also created special projects-examples TradeDB for versions 0.3 and 0.4 - full-fledged database applications.
The main goal is to facilitate the programming of database applications under Qt.Talking about your ORM is not easy. I got to Habr thanks to her, writing an article in the sandbox.
The article did not cause interest ... But it was version 0.3 - and not even release version, but pre-alpha. Now I have gone far in the development of QST, and 0.5.1 pre-alpha is already available; but there is still a lot of things ahead that need to be done.
First of all: this is not the usual ORM. I began to write the library without knowing this term; I needed a query generation tool in order not to write them, and so that they were concentrated in one layer: it was easier to keep track of them. I did not even know about such approaches as Active Record. The result was what turned out: the original ORM, which is not quite ORM. You cannot configure class fields in it that would be displayed on table fields; it is impossible to write (read) the data directly to (from) the database using only the assignment. But you can have a lot of other things.
Opportunities, they are also advantages of the library.
- Generation of simple (in the sense of - non-hierarchical, without gadgets) SQL queries of the type SELECT, INSERT, UPDATE, DELETE and EXECUTE (in the case of PostgreSQL it is SELECT).
- Concept DFD ¬– Declarative Field Descriptor. You use it to describe how to generate a query; additionally, for SELECT, you describe how to use the received data in Qt-views (QTableView, QListView, QComboBox).
- Integration with Qt Interview. What-no, but there is. In addition to the non-Active-Record approach, this is the main difference between QST and everything else. For example, you can specify how wide QTableView columns should be for a specific query, as they should be titled. Or you can select data values ​​associated with the current row in some view.
- Multiple named queries.
- For each request - the ability to connect many different views.
- Generate WHERE sections. Filters can be set very different; main disadvantage: conditions must be combined through the AND operator.
- Auto-retrieve the field name if it is specified as “max (field_name)”, “sum (price * count) as summa”. In the first case, the field can be accessed both completely (“max (field_name)”) and abbreviated (“field_name”). In the second case - only through “summa”.
- Multifunctional connection class - wrapper over QSqlDatabase. It can perform connection testing, store settings, connect with different names and delete connections.
- In general, it is not difficult to use; the main thing is to understand the meaning of how the library works.
- Tree data model. I really want to finally rewrite it, because I don’t want new and delete in my form in my library. This is more than possible and will lead to safer memory handling.
- Indirectly facilitates data conversion on the "Program - DB" path.
- Very extensive options for customizing the generation of SQL; for example, if you describe a future query by including filters in it, then when a filter is invalid, it is simply not generated. However, if valid, it is converted to the format of your SQL dialect and added to the WHERE clause. Comparison functors are also automatically placed; for strings this is LIKE, for numbers - “=”. However, they are easy to override.
- Other.
Minuses? Of course there is, and quite a lot!
- Unusual concept, originality. You have to do a lot of things with your hands; more precisely, create handler classes and prescribe DFD for different types of requests.
- SQL support, though more than in all the libraries reviewed, is still insufficient. Now I’m working on several tasks; probably in the next versions the generation engine will be rewritten.
- There is no caching, no serialization, no reflection, no real Object Relational Mapping. There is no possibility to create relationships (relations: 1: 1, 1: n, n: n). In this, I must admit, QxOrm ahead of the rest.
- No caching. Although I think how best to implement it.
- It is not as easy to extract data into structures, as was intended in all other ORMs. However, the approach in QST is such that it proposes not to think about individual data sets; instead, it is better to think at the level of models and representations, as well as individual values ​​of a particular record.
- The library is not as technological as others. Yes, there are inside both templates and inheritance, but this is nothing compared to the same QxOrm. The programmer, in any case, does not have to mess around with it.
- Some non-obviousness, in any case, first. A lot of things are done automatically (conversion, for example), and this can be immediately overlooked, even despite the complete documentation.
- Other.
In general, the library is still in development, but already can do a lot of things. I use it in my work; and as a freelancer I write another project on it. In general, much less work falls on the programmer than with the one with QxOrm or with QDjango - this is evident from the source code of the examples. Described handlers, loaded view into them - get features that almost all are located in the main class (QstAbstractModelHandler). I introduce everything I need on the sly, but I can always be contacted - I will definitely help. Unlike. Therefore, I immodestly propose to support me in this difficult endeavor. Although even a wish for good luck; and better by any review. I will be grateful.
An example of a handler class and a DFD descriptor for a SELECT query.
Please note that information for setting up the views is also transferred to the QstField fields: field display, header and column width.
// personshandler.h
const int PERSONS = 1 ;
const int PERSONS_FULL_NAME = 2 ;
class PersonsHandler : public QstAbstractModelHandler
{
private :
QstBatch _selector ( const int & queryNumber ) const ;
QstBatch _executor ( const int & queryNumber ) const ;
} ;
// personshandler.cpp
QstBatch PersonsHandler :: _selector ( const int & queryNumber ) const
{
QstBatch batch ;
if ( queryNumber == PERSONS )
{
batch. addSource ( "vPersons" ) ;
batch << QstField ( RolePrimaryKey, "ID" )
<< QstField ( "Address_ID" )
<< QstField ( "LastName" , FieldVisible, "Last Name" , 100 )
<< QstField ( "FirstName" , FieldVisible, "Name" , 100 )
<< QstField ( "ParentName" , FieldVisible, "Middle Name " , 100 )
<< QstField ( "vcBirthDate" , FieldVisible, "Date \ n of birth" , 90 )
<< QstField ( "Phone" , FieldVisible, "Contact Phone \ n " , 120 )
<< QstField ( "[E-Mail]" , FieldVisible, "e-mail" , 120 )
<< QstField ( "ID" , value ( ID_VALUE ) , PurposeWhere )
;
}
else
if ( queryNumber == PERSONS_FULL_NAME )
{
batch. addSource ( "vPersons" ) ;
batch
<< QstField ( "FullName" , FieldVisible, "Full Name" , 300 )
<< QstField ( "LastName" , FieldVisible, "Last Name" , 100 )
<< QstField ( "FirstName" , FieldVisible, "Name" , 100 )
<< QstField ( "ParentName" , FieldVisible, "Middle Name " , 100 )
<< QstField ( "vcBirthDate" , FieldVisible, "Date \ n of birth" , 90 )
<< QstField ( "Phone" , FieldVisible, "Contact Phone \ n " , 120 )
<< QstField ( "[E-Mail]" , FieldVisible, "e-mail" , 120 )
<< QstField ( "ID" , value ( ID_VALUE ) , PurposeWhere )
<< QstField ( RolePrimaryKey, "ID" )
<< QstField ( "Address_ID" )
;
}
else
{
Q_ASSERT ( false ) ;
}
return batch ;
}
View setting:
// PersonsHandler _personsHandler;
// QstPlainQueryModel _personsModel; // - described in the PersonsForm class.
void PersonsForm :: loadPersons ( )
{
_personsHandler. reload ( PERSONS, & _personsModel ) ;
_personsHandler. setTableView ( ui - > tv_PersonsTableView ) ;
}
QVariant PersonsForm :: personID ( ) const
{
return _personsHandler. keyValueOfView ( ) ;
}
Using:
void PersonsForm :: loadPersonalDocumentInfo ( )
{
PersonalDocumentsHandler th ;
th. setValue ( "Person_ID" , personID ( ) ) ;
QVariantMap valMap = th. SelectToMap ( PERSONAL_DOCUMENTS,
QStringList ( )
<< "DocTypeName"
<< "SerialNumber"
<< "Number"
<< "vcIssueDate"
<< "GivenBy" ) ;
ui - > le_DocumentTypeLineEdit - > setText ( valMap [ "DocTypeName" ] . toString ( ) ) ;
ui - > le_SerialNumberLineEdit - > setText ( valMap [ "SerialNumber" ] . toString ( ) ) ;
ui - > le_NumberLineEdit - > setText ( valMap [ "Number" ] . toString ( ) ) ;
ui - > le_IssueDateDateEdit - > setDate ( valMap [ "vcIssueDate" ] . toDate ( ) ) ;
ui - > le_GivenByLineEdit - > setText ( valMap [ "GivenBy" ] . toString ( ) ) ;
}