QxOrm Windows Linux Macintosh C++

Home Download Quick sample Tutorial (4)
Faq (36) Forum

QxOrm >> Faq
Current version :  QxOrm 1.2.9 - QxOrm library online documentation
QxEntityEditor 1.1.6
Version française du site Web site english version
qt_ambassador
QxOrm library has been accepted into the Qt Ambassador Program

What is QxOrm ?

QxOrm is a C++ library designed to provide Object Relational Mapping (ORM) feature to C++ users.
QxOrm is developed by Lionel Marty, a software development engineer since 2003.

QxOrm provides many functionalities starting from a simple C++ setting function by class :
  • persistence : communication with a lot of databases (with 1-1, 1-n, n-1 and n-n relationships)
  • serialization : binary and xml format
  • reflection (or introspection) : access to classes definitions, retrieve properties and call classes methods

What is QxEntityEditor ?

QxEntityEditor is a graphic editor for QxOrm library : QxEntityEditor provides a graphic way to manage the data model.
QxEntityEditor is multi-platform (available for Windows, Linux and Mac OS X) and generate native code for all environments : desktop (Windows, Linux, Mac OS X), embedded and mobile (Android, iOS, Raspberry Pi, etc.).
A video presentation of QxEntityEditor application is available.

QxEntityEditor is based on plugins and provides many ways to import/export your data model :
  • generate C++ persistent classes automatically (registered into QxOrm context) ;
  • generate DDL SQL script automatically (database schema) for SQLite, MySQL, PostgreSQL, Oracle and MS SQL Server ;
  • manage schema evolution for each project version (ALTER TABLE, ADD COLUMN, DROP INDEX, etc.) ;
  • transfer your data model over network and create quickly client/server applications, using QxService module ;
  • import existing database structure (using ODBC connection) for SQLite, MySQL, PostgreSQL, Oracle and MS SQL Server databases ;
  • because each project is different, QxEntityEditor provides several ways to customize generated files (especially a javascript engine and an integrated debugger).
QxEntityEditor

QxEntityEditor is developed by Lionel Marty, a software development engineer since 2003.



How to contact QxOrm to report a bug or ask a question ?

If you find a bug or if you have a question about QxOrm library, you can send an e-mail to : support@qxorm.com.
A forum dedicated to QxOrm is available here.


How to install and build QxOrm library ?

A tutorial to install a development environment with QxOrm library on Windows is available.

QxOrm uses qmake process from Qt library to create makefile and build the project.
qmake is portable and multi-platform, so it works perfectly on Windows, Linux (Unix) and Mac.
To build QxOrm library, just execute following commands :
qmake
make debug
make release

On Windows, *.vcproj and *.sln files are available for Visual C++ 2008 and Visual C++ 2010.
*.pro files are readable by Qt Creator, and some plugins are available to interface to other C++ IDE.
mingw_build_all_debug.bat and mingw_build_all_release.bat scripts in the directory ./tools/ can quickly built QxOrm library and all tests with MinGW compiler on Windows.
gcc_build_all_debug.sh and gcc_build_all_release.sh scripts in the directory ./tools/ can quickly built QxOrm library and all tests with GCC compiler on Linux.
osx_build_all_debug.sh and osx_build_all_release.sh scripts in the directory ./tools/ can quickly built QxOrm library and all tests on Mac (thanks very much to Dominique Billet).

Note : depending on your development environment, it may be necessary to modify QxOrm.pri file to set boost package configuration :
QX_BOOST_INCLUDE_PATH = $$quote(D:/Dvlp/_Libs/Boost/1_42/include)
QX_BOOST_LIB_PATH = $$quote(D:/Dvlp/_Libs/Boost/1_42/lib_shared)
QX_BOOST_LIB_SERIALIZATION_DEBUG = "boost_serialization-vc90-mt-gd-1_42"
QX_BOOST_LIB_SERIALIZATION_RELEASE = "boost_serialization-vc90-mt-1_42"


What are the databases supported by QxOrm ?

QxOrm uses the engine QtSql of Qt based on a system of plug-in.
A detailed list of supported databases is available on the website of Qt here.
The plug-in ODBC (QODBC) ensures compatibility with many databases.
For optimal performances, it is possible to use a plug-in specific to a database :
  • QMYSQL : MySQL
  • QPSQL : PostgreSQL (versions 7.3 and above)
  • QOCI : Oracle Call Interfaces Driver
  • QSQLITE : SQLite version 3
  • QDB2 : IBM DB2 (version 7.1 and above)
  • QIBASE : Borland InterBase
  • QTDS : Sybase Adaptive Server

Why QxOrm is dependent on two libraries : boost and Qt ?

QxOrm uses many functionalities available in excellent libraries : boost and Qt.
In addition, these two libraries are used in many projects both professional and open source.
A large number of forums, tutorials, and a whole community are available to answer any issue that could arise.
The QxOrm objective is not to redevelop features that already exist but to provide a powerful tool for access to databases such as it exists in other languages (Java with Hibernate, .Net with NHibernate, Ruby, Python, etc...).

boost boost : many of boost's founders are on the C++ standard committee and several boost libraries have been accepted for incorporation into C++1x (new standard for the C++ programming language). The boost's libraries are aimed at a wide range of C++ users and application domains.
QxOrm uses the following features of boost : smart_pointer, serialization, type_traits, multi_index_container, unordered_container, any, tuple, foreach, function.
It is recommended to install the latest version of boost available at the following address : http://www.boost.org/

Qt Qt : cross-platform application development framework : ihm (QtGui), network (QtNetwork), xml (QtXml), database (QtSql)...
Qt provides excellent support and documentation. Using Qt, you can write simple and powerful C++ code.
Qt is produced by Digia's Qt Development Frameworks division and is available under LGPL license.
QxOrm is compatible with a lot of Qt's objects : QObject, QString, QDate, QTime, QDateTime, QList, QHash, QSharedPointer, QScopedPointer...
It is recommended to install the latest version of Qt available at the following address : http://qt.digia.com/


Why does QxOrm require a precompiled header to be used ?

QxOrm uses the techniques of C++ meta-programming to provide most of its functionalities.
You do not need to know how to use meta-programming to work with QxOrm library.
Indeed, QxOrm is simple to use and the C++ code written with Qt and QxOrm is easy to read, therefore easy to develop and to maintain.

However, meta-programming is costly in compilation times.
By using a precompiled.h file, your project will be compiled much more quickly.
Last but not least, another advantage is that the file QxOrm.h includes the basic functionalities of libraries boost and Qt.
It is thus not necessary anymore to write #include <QtCore/QString.h> to use the class QString of Qt for example.
In the same way, there is no need anymore to write #include <boost/shared_ptr.hpp> to use smart pointers of boost library.


Is it possible to reduce compilation times of my project ?

Yes, if the serialization of your data in xml format is not used in your project, you can disable this functionality.
The compilation times will be then reduced but you will not have anymore access to the namespace qx::serialization:xml.
To disable xml serialization, it is necessary to open the QxConfig.h file and to modify the constant _QX_SERIALIZE_XML.
A recompilation of QxOrm library is necessary to take into account this modification.

Another possibility is to use the polymorphic classes of the library boost::serialization (instead of template).
This feature reduces compilation times and the size of the executable that is generated.
However, the speed of execution of your program will be reduced since part of the work carried out during compilation will be done during the execution of your application.
To activate this feature in QxOrm, you must modify the constant _QX_SERIALIZE_POLYMORPHIC of the QxConfig.h file.
Warning : the serialization functions will be then accessible from the following namespace : qx::serialization::polymorphic_binary, qx::serialization::polymorphic_text and qx::serialization::polymorphic_xml.
A recompilation of QxOrm library is necessary to take into account this modification.

It is also possible to use Q_PROPERTY macro to define properties for a class inherited from QObject type.
In this case, there is two different ways to register properties into QxOrm context and you can reduce noticeably compilation times of your project.
For more details about this feature, click here.


What are all types of serialization available ?

QxOrm is based on boost serialization library.
There are several types of serialization available : binary, xml, text, etc...
QxConfig.h file can enable and/or disable some types of serialization.

Each type of serialization has its own characteristics :
* binary : smallest, fastest, non-portable
* text : larger, slower, portable
* xml : largest, slowest, portable

Note : binary type is not portable, so you can't transfer data between Windows and Unix for example.
If you need to transfer data over network between different platforms, you have to use text or xml serialization.
QxOrm provides another solution : portable_binary serialization.
portable_binary has the same characteristics as binary type and can serialize data in a portable way.
However, portable_binary is not provided officially by boost library, so it's necessary to test before using in a production software.


Why does QxOrm provide a new type of container qx::QxCollection<Key, Value> ?

There are many containers in stl, boost and Qt libraries.
It is therefore legitimate to ask this question : what is qx::QxCollection<Key, Value> ?
qx::QxCollection<Key, Value> is a new container (based on the excellent library boost::multi_index_container) which has the following functionalities :
  • preserves the insertion order of elements in the list
  • quick access to an element by its index : is equivalent to std::vector<T> or QList<T> for example
  • quick access to an element by a key (hash-map) : is equivalent to QHash<Key, Value> or boost::unordered_map<Key, Value> for example
  • sort by Key type and by Value type
Note : qx::QxCollection<Key, Value> is compatible with the foreach macro provided by Qt library and the BOOST_FOREACH macro provided by boost library.
However, each element returned by these 2 macros corresponds to an object of type std::pair<Key, Value>.
To obtain a more natural and more readable result, it is advised to use the _foreach macro : this macro uses BOOST_FOREACH for all the containers except for qx::QxCollection<Key, Value>.
In this case, the returned element corresponds to the Value type (cf. sample).
The macro _foreach is compatible with all containers (stl, Qt, boost...) since it uses the macro BOOST_FOREACH.

Additional note : qx::QxCollection<Key, Value> is particularly suited to receive data resulting from a database.
Indeed, these data can be sorted (by using ORDER BY in a sql request for example), it is thus important to preserve the insertion order of the elements in the list.
Furthermore, each data resulting from a database has a unique id. It is thus important to be able to access quickly to an element based on this single identifier (hash-map).

Sample :
/* definition of drug class with 3 properties : code, name, description */
class drug { public: QString code; QString name; QString desc; };

/* smart pointer of drug */
typedef boost::shared_ptr<drug> drug_ptr;

/* collection of drugs by code */
qx::QxCollection<QString, drug_ptr> lstDrugs;

/* create 3 new drugs */
drug_ptr d1; d1.reset(new drug()); d1->code = "code1"; d1->name = "name1"; d1->desc = "desc1";
drug_ptr d2; d2.reset(new drug()); d2->code = "code2"; d2->name = "name2"; d2->desc = "desc2";
drug_ptr d3; d3.reset(new drug()); d3->code = "code3"; d3->name = "name3"; d3->desc = "desc3";

/* insert drugs into the collection */
lstDrugs.insert(d1->code, d1);
lstDrugs.insert(d2->code, d2);
lstDrugs.insert(d3->code, d3);

/* iterate with '_foreach' keyword */
_foreach(drug_ptr p, lstDrugs)
{ qDebug() << qPrintable(p->name) << " " << qPrintable(p->desc); }

/* iterate with 'for' keyword */
for (long l = 0; l < lstDrugs.count(); ++l)
{
   drug_ptr p = lstDrugs.getByIndex(l);
   QString code = lstDrugs.getKeyByIndex(l);
   qDebug() << qPrintable(p->name) << " " << qPrintable(p->desc);
}

/* iterate with 'QxCollectionIterator' java style */
qx::QxCollectionIterator<QString, drug_ptr> itr(lstDrugs);
while (itr.next())
{
   QString code = itr.key();
   qDebug() << qPrintable(itr.value()->name) << " " << qPrintable(itr.value()->desc);
}

/* sort ascending by key and sort descending by value */
lstDrugs.sortByKey(true);
lstDrugs.sortByValue(false);

/* access drug by code */
drug_ptr p = lstDrugs.getByKey("code2");

/* access drug by index */
drug_ptr p = lstDrugs.getByIndex(2);

/* test if drug exists and if collection is empty */
bool bExist = lstDrugs.exist("code3");
bool bEmpty = lstDrugs.empty();

/* remove the second drug from collection */
lstDrugs.removeByIndex(2);

/* remove the drug with "code3" */
lstDrugs.removeByKey("code3");

/* clear the collection */
lstDrugs.clear();


Why does QxOrm provide a new smart-pointer qx::dao::ptr<T> ?

QxOrm can be used with smart-pointers of boost and Qt libraries.
QxOrm smart-pointer is based on QSharedPointer and provides new features with 'qx::dao::...' functions.
qx::dao::ptr<T> keeps automatically values from database.
So it's possible to detect if an instance has been modified using the method 'isDirty()' : this method can return list of properties changed.
qx::dao::ptr<T> can also be used with the function 'qx::dao::update_optimized()' to update in database only properties changed.
qx::dao::ptr<T> can be used with a simple object and with many containers : stl, boost, Qt and qx::QxCollection<Key, Value>.

Sample :
   // Test 'isDirty()' method
   qx::dao::ptr<blog> blog_isdirty = qx::dao::ptr<blog>(new blog());
   blog_isdirty->m_id = blog_1->m_id;
   daoError = qx::dao::fetch_by_id(blog_isdirty);
   qAssert(! daoError.isValid() && ! blog_isdirty.isDirty());

   blog_isdirty->m_text = "blog property 'text' modified => blog is dirty !!!";
   QStringList lstDiff; bool bDirty = blog_isdirty.isDirty(lstDiff);
   qAssert(bDirty && (lstDiff.count() == 1) && (lstDiff.at(0) == "blog_text"));
   if (bDirty) { qDebug("[QxOrm] test dirty 1 : blog is dirty => '%s'", qPrintable(lstDiff.join("|"))); }

   // Update only property 'm_text' of 'blog_isdirty'
   daoError = qx::dao::update_optimized(blog_isdirty);
   qAssert(! daoError.isValid() && ! blog_isdirty.isDirty());
   qx::dump(blog_isdirty);

   // Test 'isDirty()' method with a container
   typedef qx::dao::ptr< QList<author_ptr> > type_lst_author_test_is_dirty;

   type_lst_author_test_is_dirty container_isdirty = type_lst_author_test_is_dirty(new QList<author_ptr>());
   daoError = qx::dao::fetch_all(container_isdirty);
   qAssert(! daoError.isValid() && ! container_isdirty.isDirty() && (container_isdirty->count() == 3));

   author_ptr author_ptr_dirty = container_isdirty->at(1);
   author_ptr_dirty->m_name = "author name modified at index 1 => container is dirty !!!";
   bDirty = container_isdirty.isDirty(lstDiff);
   qAssert(bDirty && (lstDiff.count() == 1));
   if (bDirty) { qDebug("[QxOrm] test dirty 2 : container is dirty => '%s'", qPrintable(lstDiff.join("|"))); }

   author_ptr_dirty = container_isdirty->at(2);
   author_ptr_dirty->m_birthdate = QDate(1998, 03, 06);
   bDirty = container_isdirty.isDirty(lstDiff);
   qAssert(bDirty && (lstDiff.count() == 2));
   if (bDirty) { qDebug("[QxOrm] test dirty 3 : container is dirty => '%s'", qPrintable(lstDiff.join("|"))); }

   // Update only property 'm_name' at position 1, only property 'm_birthdate' at position 2 and nothing at position 0
   daoError = qx::dao::update_optimized(container_isdirty);
   qAssert(! daoError.isValid() && ! container_isdirty.isDirty());
   qx::dump(container_isdirty);

   // Fetch only property 'm_dt_creation' of blog
   QStringList lstColumns = QStringList() << "date_creation";
   list_blog lst_blog_with_only_date_creation;
   daoError = qx::dao::fetch_all(lst_blog_with_only_date_creation, NULL, lstColumns);
   qAssert(! daoError.isValid() && (lst_blog_with_only_date_creation.size() > 0));

   if ((lst_blog_with_only_date_creation.size() > 0) && (lst_blog_with_only_date_creation[0] != NULL))
   { qAssert(lst_blog_with_only_date_creation[0]->m_text.isEmpty()); }

   qx::dump(lst_blog_with_only_date_creation);


Should I use QString or std::string ?

QxOrm advises to use the QString class for the management of the character strings.
Even if boost provides many functionalities with its module boost::string_algo, the QString class is easier to use and supports many formats : ASCII, Utf8, Utf16...
However, QxOrm is compatible with std::string and std::wstring if you prefer to use this kind of character strings.


Is it necessary to use smart-pointers ?

QxOrm strongly advises to use boost or Qt smart-pointers.
The C++ language does not have Garbage Collector like Java or C# for example.
The use of smart-pointers simplifies the memory management in C++.
The ideal in a C++ program is not to have any call to delete or delete[].
Furthermore, smart-pointer is a new functionality of the new C++ standard : C++1x.
It is thus essential to know the following classes today :


The primary key is long type by default. Is it possible to use a key of QString type or other ?

It is possible to define a unique id of QString type or other with QxOrm library.
By default, the unique id is long type.
To indicate that a class has a single identifier of QString type or other, it is necessary to specialize the template qx::trait::get_primary_key.
To simplify, you can use the macro : QX_REGISTER_PRIMARY_KEY(myClass, QString).

Warning : the macro QX_REGISTER_PRIMARY_KEY must be used before the macro QX_REGISTER_HPP_... in the definition of your class, otherwise a compilation error occurs.

Here is an example with author class of qxBlog tutorial and a QString primary key :

#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_
 
class author
{
public:
// -- properties
   QString  m_id;
   QString  m_name;
// -- constructor, virtual destructor
   author() { ; }
   virtual ~author() { ; }
};

QX_REGISTER_PRIMARY_KEY(author, QString)
QX_REGISTER_HPP_QX_BLOG(author, qx::trait::no_base_class_defined, 0)

#endif // _QX_BLOG_AUTHOR_H_


How to define a 'multi-columns primary key' (composite key) ?

QxOrm supports 'multi-columns primary key'.
The class id must be defined with following type :
* QPair or std::pair to define 2 columns
* boost::tuple to define from 2 columns to 9 columns

It is necessary to use the macro QX_REGISTER_PRIMARY_KEY() to specialize the template and to map class id with multi-columns in database.
The list of multi-columns names must be defined with '|' character : 'column1|column2|column3|etc...'.

Sample with class 'author' from project 'qxBlogCompositeKey', this class has an id mapped to 3 columns in database :

#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_

class blog;

class QX_BLOG_DLL_EXPORT author
{

   QX_REGISTER_FRIEND_CLASS(author)

public:

// -- composite key (multi-column primary key in database)
   typedef boost::tuple<QString, long, QString> type_composite_key;
   static QString str_composite_key() { return "author_id_0|author_id_1|author_id_2"; }

// -- typedef
   typedef boost::shared_ptr<blog> blog_ptr;
   typedef std::vector<blog_ptr> list_blog;

// -- enum
   enum enum_sex { male, female, unknown };

// -- properties
   type_composite_key   m_id;
   QString              m_name;
   QDate                m_birthdate;
   enum_sex             m_sex;
   list_blog            m_blogX;

// -- contructor, virtual destructor
   author() : m_id("", 0, ""), m_sex(unknown) { ; }
   virtual ~author() { ; }

// -- methods
   int age() const;

// -- methods "get" to composite key
   type_composite_key getId() const    { return m_id; }
   QString getId_0() const             { return boost::tuples::get<0>(m_id); }
   long getId_1() const                { return boost::tuples::get<1>(m_id); }
   QString getId_2() const             { return boost::tuples::get<2>(m_id); }

// -- methods "set" to composite key
   void setId_0(const QString & s)     { boost::tuples::get<0>(m_id) = s; }
   void setId_1(long l)                { boost::tuples::get<1>(m_id) = l; }
   void setId_2(const QString & s)     { boost::tuples::get<2>(m_id) = s; }

};

QX_REGISTER_PRIMARY_KEY(author, author::type_composite_key)
QX_REGISTER_HPP_QX_BLOG(author, qx::trait::no_base_class_defined, 0)

typedef boost::shared_ptr<author> author_ptr;
typedef qx::QxCollection<author::type_composite_key, author_ptr> list_author;

#endif // _QX_BLOG_AUTHOR_H_

#include "../include/precompiled.h"
#include "../include/author.h"
#include "../include/blog.h"
#include <QxMemLeak.h>

QX_REGISTER_CPP_QX_BLOG(author)

namespace qx {
template <> void register_class(QxClass<author> & t)
{
   t.id(& author::m_id, author::str_composite_key());

   t.data(& author::m_name, "name");
   t.data(& author::m_birthdate, "birthdate");
   t.data(& author::m_sex, "sex");

   t.relationOneToMany(& author::m_blogX, blog::str_composite_key(), author::str_composite_key());

   t.fct_0<int>(& author::age, "age");
}}

int author::age() const
{
   if (! m_birthdate.isValid()) { return -1; }
   return (QDate::currentDate().year() - m_birthdate.year());
}


How to register private or protected members into QxOrm context ?

To register private or protected members into QxOrm context (qx::register_class<T> function), it's necessary to declare some friend class.
To simplify writing C++ template, QxOrm library provides this macro : QX_REGISTER_FRIEND_CLASS(myClass).
An example using this macro can be found in ./test/qxDllSample/dll1/ directory of QxOrm package with CPerson class :

namespace qx {
namespace test {

class QX_DLL1_EXPORT CPerson : public QObject
{

   Q_OBJECT
   QX_REGISTER_FRIEND_CLASS(qx::test::CPerson)

   // etc...

};

} // namespace test
} // namespace qx



How to enable/disable the module QxMemLeak for automatic detection of memory leaks ?

QxMemLeak module provides a fast detection of memory leaks in Debug mode once the execution of the program is finished (with indication of the file and the line => style MFC from Microsoft).
This module is developed by Wu Yongwei and has undergone some modifications to be integrated in QxOrm.
If another tool is already used in your projects (Valgrind for example), this functionality should not be activated.
To enable/disable QxMemLeak module, all is needed is to modify the constant _QX_USE_MEM_LEAK_DETECTION defined in the QxConfig.h. file.
A recompilation of QxOrm library is necessary to take into account this modification.


How to manage inheritance and database ?

With ORM tools, there is usually 3 strategies to manage inheritance and database : QxOrm works by default with Concrete Table Inheritance strategy (others are not supported yet).
Many tutorials and forums are available on internet to more details about ORM inheritance and database.
You can find a sample in the directory ./test/qxDllSample/dll2/ with the class BaseClassTrigger.


How to define a 'Trigger' with QxOrm ?

With QxOrm Trigger, it is possible to execute process before and/or after an insert, update or delete query in the database.
You can find a sample in the directory ./test/qxDllSample/dll2/ with the class BaseClassTrigger.
The class BaseClassTrigger contains 5 properties : m_id, m_dateCreation, m_dateModification, m_userCreation and m_userModification.
Each property will be automatically auto-updated for all derived classes from BaseClassTrigger (see Foo class and Bar class in the same project).
It is necessary to specialize 'QxDao_Trigger' template to work with this feature.

#ifndef _QX_BASE_CLASS_TRIGGER_H_
#define _QX_BASE_CLASS_TRIGGER_H_

class QX_DLL2_EXPORT BaseClassTrigger
{

   QX_REGISTER_FRIEND_CLASS(BaseClassTrigger)

protected:

   long        m_id;
   QDateTime   m_dateCreation;
   QDateTime   m_dateModification;
   QString     m_userCreation;
   QString     m_userModification;

public:

   BaseClassTrigger() : m_id(0)  { ; }
   virtual ~BaseClassTrigger()   { ; }

   long getId() const                     { return m_id; }
   QDateTime getDateCreation() const      { return m_dateCreation; }
   QDateTime getDateModification() const  { return m_dateModification; }
   QString getUserCreation() const        { return m_userCreation; }
   QString getUserModification() const    { return m_userModification; }

   void setId(long l)                              { m_id = l; }
   void setDateCreation(const QDateTime & dt)      { m_dateCreation = dt; }
   void setDateModification(const QDateTime & dt)  { m_dateModification = dt; }
   void setUserCreation(const QString & s)         { m_userCreation = s; }
   void setUserModification(const QString & s)     { m_userModification = s; }

   void onBeforeInsert(qx::dao::detail::IxDao_Helper * dao);
   void onBeforeUpdate(qx::dao::detail::IxDao_Helper * dao);

};

QX_REGISTER_HPP_QX_DLL2(BaseClassTrigger, qx::trait::no_base_class_defined, 0)

namespace qx {
namespace dao {
namespace detail {

template <>
struct QxDao_Trigger<BaseClassTrigger>
{

   static inline void onBeforeInsert(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { if (t) { t->onBeforeInsert(dao); } }
   static inline void onBeforeUpdate(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { if (t) { t->onBeforeUpdate(dao); } }
   static inline void onBeforeDelete(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { Q_UNUSED(t); Q_UNUSED(dao); }
   static inline void onBeforeFetch(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { Q_UNUSED(t); Q_UNUSED(dao); }
   static inline void onAfterInsert(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { Q_UNUSED(t); Q_UNUSED(dao); }
   static inline void onAfterUpdate(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { Q_UNUSED(t); Q_UNUSED(dao); }
   static inline void onAfterDelete(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { Q_UNUSED(t); Q_UNUSED(dao); }
   static inline void onAfterFetch(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { Q_UNUSED(t); Q_UNUSED(dao); }

};

} // namespace detail
} // namespace dao
} // namespace qx

#endif // _QX_BASE_CLASS_TRIGGER_H_

#include "../include/precompiled.h"
#include "../include/BaseClassTrigger.h"
#include <QxMemLeak.h>

QX_REGISTER_CPP_QX_DLL2(BaseClassTrigger)

namespace qx {
template <> void register_class(QxClass<BaseClassTrigger> & t)
{
   IxDataMember * pData = NULL;

   pData = t.id(& BaseClassTrigger::m_id, "id");

   pData = t.data(& BaseClassTrigger::m_dateCreation, "date_creation");
   pData = t.data(& BaseClassTrigger::m_dateModification, "date_modification");
   pData = t.data(& BaseClassTrigger::m_userCreation, "user_creation");
   pData = t.data(& BaseClassTrigger::m_userModification, "user_modification");
}}

void BaseClassTrigger::onBeforeInsert(qx::dao::detail::IxDao_Helper * dao)
{
   Q_UNUSED(dao);
   m_dateCreation = QDateTime::currentDateTime();
   m_dateModification = QDateTime::currentDateTime();
   m_userCreation = "current_user_1";
   m_userModification = "current_user_1";
}

void BaseClassTrigger::onBeforeUpdate(qx::dao::detail::IxDao_Helper * dao)
{
   Q_UNUSED(dao);
   m_dateModification = QDateTime::currentDateTime();
   m_userModification = "current_user_2";
}


How to register an abstract class into QxOrm context ?

A C++ abstract class (with at least one pure virtual method) cannot be mapped to a table of a database (because it cannot be instantiated).
However, in some case, it can be interesting to define properties into abstract class used by a persistent object (by inheritance).
A sample of abstract class registered into QxOrm context is available in the directory ./test/qxDllSample/dll2/ of QxOrm package with the class BaseClassTrigger.
To register an abstract class into QxOrm context, you have to :
  • register the class with 'void register_class' like any other class ;
  • use macro QX_REGISTER_ABSTRACT_CLASS(className) just after the class definition.


How to register a class defined into a namespace into QxOrm context ?

If a class is defined into a namespace, a compilation error occurs using macros : QX_REGISTER_HPP and QX_REGISTER_CPP.
To avoid this compilation error, it is necessary to use followings macros : QX_REGISTER_COMPLEX_CLASS_NAME_HPP and QX_REGISTER_COMPLEX_CLASS_NAME_CPP.
You can find a sample in the directory ./test/qxDllSample/dll1/ of QxOrm package with the class CPerson defined into namespace qx::test :

* QX_REGISTER_COMPLEX_CLASS_NAME_HPP_QX_DLL1(qx::test::CPerson, QObject, 0, qx_test_CPerson)


How to define a soft delete behavior ?

A soft delete doesn't remove rows from database (this is not a physical delete) : a new column is added to the table definition to flag a row as deleted or not.
This column can contain a boolean (1 means row deleted, 0 or NULL means row not deleted), or can contain deletion date-time (if empty or NULL, row is not deleted).
So you can reactivate a deleted row by setting NULL or empty value into database.

To define a soft delete behavior with QxOrm library, you have to use the class qx::QxSoftDelete in function mapping by class qx::register_class<T>.
Here is an example with the class Bar containing 2 properties m_id and m_desc :

namespace qx {
template <> void register_class(QxClass<Bar> & t)
{
   t.setSoftDelete(qx::QxSoftDelete("deleted_at"));

   t.id(& Bar::m_id, "id");
   t.data(& Bar::m_desc, "desc");
}}

SQL queries builded by QxOrm library will take into account this soft delete parameter to add conditions (don't fetch deleted item, don't delete physically a row, etc.).
For example, if you execute this code with the class Bar :

Bar_ptr pBar; pBar.reset(new Bar());
pBar->setId(5);
QSqlError daoError = qx::dao::delete_by_id(pBar);     qAssert(! daoError.isValid());
qx_bool bDaoExist = qx::dao::exist(pBar);             qAssert(! bDaoExist);
daoError = qx::dao::delete_all<Bar>();                qAssert(! daoError.isValid());
long lBarCount = qx::dao::count<Bar>();               qAssert(lBarCount == 0);
daoError = qx::dao::destroy_all<Bar>();               qAssert(! daoError.isValid());

You will obtain following output trace :

[QxOrm] sql query (93 ms) : UPDATE Bar SET deleted_at = '20110617115148615' WHERE id = :id
[QxOrm] sql query (0 ms) : SELECT Bar.id AS Bar_id_0, Bar.deleted_at FROM Bar WHERE Bar.id = :id AND (Bar.deleted_at IS NULL OR Bar.deleted_at = '')
[QxOrm] sql query (78 ms) : UPDATE Bar SET deleted_at = '20110617115148724'
[QxOrm] sql query (0 ms) : SELECT COUNT(*) FROM Bar WHERE (Bar.deleted_at IS NULL OR Bar.deleted_at = '')
[QxOrm] sql query (110 ms) : DELETE FROM Bar

Note : to delete physically a row from database, you have to use followings functions : qx::dao::destroy_by_id() and qx::dao::destroy_all().

Other note : it is recommended to define into database an index on column deleted_at to optimize execution of SQL queries.


How to use a session (qx::QxSession class) to manage automatically database transactions (using C++ RAII) ?

A database transaction is a sequence of operations performed as a single logical unit of work.
If no errors occurred during the execution of the transaction then the system commits the transaction.
If an error occurs during the transaction, or if the user specifies a rollback operation, the data manipulations within the transaction are not persisted to the database.

The qx::QxSession class of QxOrm library is designed to manage automatically database transactions (using C++ RAII) :

{ // Start a scope where a new session is instantiated

  // Create a session : a valid database connexion by thread is automatically assigned to the session and a transaction is opened
  qx::QxSession session;

  // Execute some operations with database (using += operator of qx::QxSession class and session database connexion)
  session += qx::dao::insert(my_object, session.database());
  session += qx::dao::update(my_object, session.database());
  session += qx::dao::fetch_by_id(my_object, session.database());
  session += qx::dao::delete_by_id(my_object, session.database());

  // If the session is not valid (so an error occured) => display first error
  if (! session.isValid()) { qDebug("[QxOrm] session error : '%s'", qPrintable(session.firstError().text())); }

} // End of scope : session is destroyed (transaction => automatically commit or rollback if there is an error)

Note : a session can throw a qx::dao::sql_error exception when a SQL error occured (by default, there is no exception). You can setup this feature using :
* qx::QxSession constructor (for a specific session) ;
* qx::QxSqlDatabase::getSingleton()->setSessionThrowable(bool b) parameter (for all sessions).

Other note : don't forget to pass the session database connexion to each qx::dao::xxx functions (using session.database() method).
Moreover, you can manage your own database connexion (from a connexion pool for example) using constructor of qx::QxSession class.

qx::QxSession class provides also persistent methods (CRUD) to make easier to write C++ code.
Here is the same example using methods of qx::QxSession class instead of functions into namespace qx::dao :

{ // Start a scope where a new session is instantiated

  // Create a session : a valid database connexion by thread is automatically assigned to the session and a transaction is opened
  qx::QxSession session;

  // Execute some operations with database
  session.insert(my_object);
  session.update(my_object);
  session.fetchById(my_object);
  session.deleteById(my_object);

  // If the session is not valid (so an error occured) => display first error
  if (! session.isValid()) { qDebug("[QxOrm] session error : '%s'", qPrintable(session.firstError().text())); }

} // End of scope : session is destroyed (transaction => automatically commit or rollback if there is an error)



How to persist a type without its source code (class from an external library for example) ?

QxOrm library can persist every types, not only classes registered into QxOrm context using qx::register_class<T>().

It's necessary to write serialization functions from boost framework, using the non intrusive method (because source code is not available or is read-only). For more details on boost serialization module, goto official website.

For example, imagine that you have the class 'ExtObject3D' from an external library and the source code is not available or is read-only. Here is the code to can persist an instance of 'ExtObject3D' type into database :

#ifndef _PERSIST_EXTOBJECT3D_H_
#define _PERSIST_EXTOBJECT3D_H_

#include "ExtObject3D.h"

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/split_free.hpp>
#include <boost/serialization/nvp.hpp>
 
namespace boost {
namespace serialization {

template <class Archive>
void save(Archive & ar, const ExtObject3D & t, unsigned int version)
{
   Q_UNUSED(version);
   double x(t.getX()), y(t.getY()), z(t.getZ()), angle(t.getAngle());

   ar << boost::serialization::make_nvp("x", x);
   ar << boost::serialization::make_nvp("y", y);
   ar << boost::serialization::make_nvp("z", z);
   ar << boost::serialization::make_nvp("angle", angle);
}

template <class Archive>
void load(Archive & ar, ExtObject3D & t, unsigned int version)
{
   Q_UNUSED(version);
   double x(0.0), y(0.0), z(0.0), angle(0.0);

   ar >> boost::serialization::make_nvp("x", x);
   ar >> boost::serialization::make_nvp("y", y);
   ar >> boost::serialization::make_nvp("z", z);
   ar >> boost::serialization::make_nvp("angle", angle);

   t.setX(x);
   t.setY(y);
   t.setZ(z);
   t.setAngle(angle);
}

} // namespace serialization
} // namespace boost
 
BOOST_SERIALIZATION_SPLIT_FREE(ExtObject3D)

#endif // _PERSIST_EXTOBJECT3D_H_

Now you can persist an instance of 'ExtObject3D' type into database : so you can have a 'ExtObject3D' property in a persistent class registered into QxOrm context. This property can be mapped with a column of type TEXT or VARCHAR into database.

The default behaviour of QxOrm library is : the instance is serialized to XML format before to be inserted or updated into database. This default behaviour can be useful, for example if you want to save a collection of items without to make relation (so you don't have to manage another table into database). For example, with a property of type std::vector<mon_objet> in a persistent class without relation, the list of items will be saved into database under XML format.

Note : the default behaviour can be easily modified for a specific type. QtSql engine uses QVariant type to link C++ code and database. QVariant type can contain text, numeric, binary, etc. So it can be interesting to specialize the default behaviour (XML serialization) if you want to save datas under binary format or to optimize your application (XML serialization is not very fast). You just have to write (with boost serialization functions) a conversion into/from QVariant type, for example with 'ExtObject3D' class :

namespace qx {
namespace cvt {
namespace detail {

template <> struct QxStringCvt_ToVariant< ExtObject3D > {
static inline QVariant toVariant(const ExtObject3D & t, const QString & format, int index)
{ /* Ici je convertis ExtObject3D en QVariant */ } };

template <> struct QxStringCvt_FromVariant< ExtObject3D > {
static inline qx_bool fromVariant(const QVariant & v, ExtObject3D & t, const QString & format, int index)
{ /* Ici je convertis QVariant en ExtObject3D */; return qx_bool(true); } };

} // namespace detail
} // namespace cvt
} // namespace qx



How to use introspection engine (or reflection engine) of QxOrm library ?

All classes registered into QxOrm context using qx::register_class<T>() function can be used by introspection engine (or reflection engine) of QxOrm library. Introspection engine can provide dynamically (so during program execution) some informations about types. Those informations are called meta-datas and can list all classes characteristics (properties, methods, etc.). Many programming languages (for example Java or C#) have natively this mechanism, but not C++, that's why QxOrm library emulates an introspection engine.

Here is a list of QxOrm library classes to access to meta-datas : An instance of qx::IxClass type contains the list of class properties (qx::IxDataMemberX) and the list of class methods (qx::IxFunctionX).

Introspection engine of QxOrm library provides :
  • to create dynamically an instance of a class using class name under string format (qx::create()) ;
  • to access/modify dynamically the value of an object field (qx::IxDataMember::getValue() and qx::IxDataMember::setValue()) ;
  • to invoke dynamically a class method (qx::IxFunction::invoke()) ;
  • to access to the class hierarchy (qx::IxClass::getBaseClass()).
Note : QxService module of QxOrm library (click here to go to the tutorial) to create easily a C++ application server is based on introspection engine to call dynamically services methods (client request) on server side.

Here is a sample using introspection engine : how to dump all classes, properties and methods registered into QxOrm context ?

QString QxClassX::dumpAllClasses()
{
   QxClassX::registerAllClasses();
   QxCollection<QString, IxClass *> * pAllClasses = QxClassX::getAllClasses();
   if (! pAllClasses) { qAssert(false); return ""; }

   QString sDump;
   long lCount = pAllClasses->count();
   qDebug("[QxOrm] start dump all registered classes (%ld)", lCount);
   _foreach(IxClass * pClass, (* pAllClasses))
   { if (pClass) { sDump += pClass->dumpClass(); } }
   qDebug("[QxOrm] %s", "end dump all registered classes");

   return sDump;
}

QString IxClass::dumpClass() const
{
   QString sDump;
   sDump += "-- class '" + m_sKey + "' (name '" + m_sName + "', ";
   sDump += "description '" + m_sDescription + "', version '" + QString::number(m_lVersion) + "', ";
   sDump += "base class '" + (getBaseClass() ? getBaseClass()->getKey() : "") + "')\n";

   long lCount = (m_pDataMemberX ? m_pDataMemberX->count() : 0);
   sDump += "\t* list of registered properties (" + QString::number(lCount) + ")\n";
   if (m_pDataMemberX)
   {
      IxDataMember * pId = this->getId();
      for (long l = 0; l < lCount; l++)
      {
         IxDataMember * p = m_pDataMemberX->get(l); if (! p) { continue; }
         IxSqlRelation * pRelation = p->getSqlRelation();
         QString sInfos = p->getKey() + ((p == pId) ? QString(" (id)") : QString());
         sInfos += (pRelation ? (QString(" (") + pRelation->getDescription() + QString(")")) : QString());
         sDump += "\t\t" + sInfos + "\n";
      }
   }

   lCount = (m_pFctMemberX ? m_pFctMemberX->count() : 0);
   sDump += "\t* list of registered functions (" + QString::number(lCount) + ")\n";
   if (m_pFctMemberX)
   {
      _foreach_if(IxFunction_ptr p, (* m_pFctMemberX), (p))
      { QString sKey = p->getKey(); sDump += "\t\t" + sKey + "\n"; }
   }

   qDebug("%s", qPrintable(sDump));
   return sDump;
}

Using the function qx::QxClassX::dumpAllClasses() with qxBlog tutorial, you will obtain following output :

[QxOrm] start dump all registered classes (4)
-- class 'author' (name 'author', description '', version '0', base class '')
	* list of registered properties (5)
		author_id (id)
		name
		birthdate
		sex
		list_blog (relation one-to-many)
	* list of registered functions (1)
		age

-- class 'blog' (name 'blog', description '', version '0', base class '')
	* list of registered properties (6)
		blog_id (id)
		blog_text
		date_creation
		author_id (relation many-to-one)
		list_comment (relation one-to-many)
		list_category (relation many-to-many)
	* list of registered functions (0)

-- class 'comment' (name 'comment', description '', version '0', base class '')
	* list of registered properties (4)
		comment_id (id)
		comment_text
		date_creation
		blog_id (relation many-to-one)
	* list of registered functions (0)

-- class 'category' (name 'category', description '', version '0', base class '')
	* list of registered properties (4)
		category_id (id)
		name
		description
		list_blog (relation many-to-many)
	* list of registered functions (0)

[QxOrm] end dump all registered classes

Note : you can add some informations to introspection engine using property bag mechanism. Indeed, qx::IxClass, qx::IxDataMember and qx::IxFunction classes contain a list of QVariant items associated to a QString key (see qx::QxPropertyBag class for more details).



How to register automatically Qt meta-properties (using Q_PROPERTY macro) to QxOrm context ?

All classes inherited from QObject type can use Q_PROPERTY macro : those properties become meta-properties. This is how Qt framework provides an introspection engine using the moc process. Meta-properties can be used for example by QML engine, QtScript, etc.

QxOrm library needs to register each properties per class in the mapping function void qx::register_class<T>() to provide all features (persistence, XML and binary serialization, etc.). It's possible to register automatically all Qt meta-properties into QxOrm context without to manage any mapping function per class void qx::register_class<T>() : QX_REGISTER_ALL_QT_PROPERTIES() macro works with Qt introspection engine to iterate over all meta-properties.

Here is an example with TestQtProperty class into ./test/qxDllSample/dll1/include/ directory of QxOrm package :

#ifndef _QX_TEST_QT_META_PROPERTY_H_
#define _QX_TEST_QT_META_PROPERTY_H_
 
class QX_DLL1_EXPORT TestQtProperty : public QObject
{

   Q_OBJECT
   Q_PROPERTY(int id READ id WRITE setId)
   Q_PROPERTY(long number READ number WRITE setNumber)
   Q_PROPERTY(QString desc READ desc WRITE setDesc)
   Q_PROPERTY(QDateTime birthDate READ birthDate WRITE setBirthDate)
   Q_PROPERTY(QVariant photo READ photo WRITE setPhoto)

protected:

   int         m_id;
   long        m_number;
   QString     m_desc;
   QDateTime   m_birthDate;
   QVariant    m_photo;

public:

   TestQtProperty() : QObject(), m_id(0), m_number(0) { ; }
   virtual ~TestQtProperty() { ; }

   int id() const                { return m_id; }
   long number() const           { return m_number; }
   QString desc() const          { return m_desc; }
   QDateTime birthDate() const   { return m_birthDate; }
   QVariant photo() const        { return m_photo; }

   void setId(int i)                         { m_id = i; }
   void setNumber(long l)                    { m_number = l; }
   void setDesc(const QString & s)           { m_desc = s; }
   void setBirthDate(const QDateTime & dt)   { m_birthDate = dt; }
   void setPhoto(const QVariant & v)         { m_photo = v; }
 
};

QX_REGISTER_HPP_QX_DLL1(TestQtProperty, QObject, 0)

#endif // _QX_TEST_QT_META_PROPERTY_H_

#include "../include/precompiled.h"

#include "../include/TestQtProperty.h"

#include <QxMemLeak.h>
 
QX_REGISTER_CPP_QX_DLL1(TestQtProperty)
QX_REGISTER_ALL_QT_PROPERTIES(TestQtProperty, "id")

If you don't want to use QX_REGISTER_ALL_QT_PROPERTIES macro, you can write 4 lines of code :

namespace qx {
template <> void register_class(QxClass<TestQtProperty> & t)
{ qx::register_all_qt_properties<TestQtProperty>(t, "id"); }
} // namespace qx

Note : the second parameter of QX_REGISTER_ALL_QT_PROPERTIES macro is the name of the property mapped to the primary key into database. If this parameter is empty, then the class doesn't have any primary key or the primary key has been registered into a base class.

All properties defined with Q_PROPERTY macro can be registered into QxOrm context in two different ways :
1- with the classic method : t.data(& MyQObject::my_property, "my_property", 0);
2- or without writing the data-member pointer : t.data("my_property", 0);

You can use the first or the second method to register your properties into QxOrm context and access to the same functionalities using the common interface qx::IxDataMember. You can also mix Qt meta-properties and classic registration data-member into the same mapping function void qx::register_class<T>(). Each registration method has some advantages and disadvantages.

Here is the list of advantages using the second registration method into QxOrm context :
  • much more faster to compile ;
  • reduce exec size ;
  • strong integration with Qt introspection/moc engine ;
  • no need to manage any mapping function per class using QX_REGISTER_ALL_QT_PROPERTIES macro.
Here is the list of disadvantages compared to the classic registration method :
  • need to inherit from QObject class to use Q_PROPERTY macro ;
  • program execution more slower (QVariant type versus C++ template) ;
  • doesn't support relation between tables into database (one-to-one, one-to-many, many-to-one and many-to-many) ;
  • cannot access to the data-member pointer of a class (need to convert to QVariant type before to access or to modify a value).


How to build a query without writing SQL with the class qx::QxSqlQuery ?

The class qx::QxSqlQuery (or its typedef qx_query) is used to communicate with database (to filter, to sort, etc.) in two different ways :
  • writing manually SQL query ;
  • using C++ methods with a syntax similar to SQL (same concept than the great library SubSonic for .Net).
With the first method (writing manually SQL query), you can use some optimizations specific for each database.
The second method (using C++ code to build SQL query) binds automatically SQL parameters without using qx::QxSqlQuery::bind() function.

Here is an example with qx::QxSqlQuery class writing manually a SQL query :

// Build a SQL query to fetch only 'author' of type 'female'
qx::QxSqlQuery query("WHERE author.sex = :sex");
query.bind(":sex", author::female);

QList<author> list_of_female;
QSqlError daoError = qx::dao::fetch_by_query(query, list_of_female);
for (long l = 0; l < list_of_female.count(); l++)
{ /* here we can work with the collection provided by database */ }

QxOrm library provides 3 styles to write SQL parameters.
This style can be modified for a project using the following method qx::QxSqlDatabase::getSingleton()->setSqlPlaceHolderStyle() :
  • ph_style_2_point_name : "WHERE author.sex = :sex" (default style) ;
  • ph_style_at_name : "WHERE author.sex = @sex" ;
  • ph_style_question_mark : "WHERE author.sex = ?".
Here is the same example using C++ code of the class qx::QxSqlQuery (or its typedef qx_query) to build query automatically :

// Build a SQL query to fetch only 'author' of type 'female'
qx_query query;
query.where("author.sex").isEqualTo(author::female);

QList<author> list_of_female;
QSqlError daoError = qx::dao::fetch_by_query(query, list_of_female);
for (long l = 0; l < list_of_female.count(); l++)
{ /* here we can work with the collection provided by database */ }

With C++ methods of qx::QxSqlQuery class, you don't have to bind any SQL parameter, and the syntax is similar to real SQL.
All SQL parameters will be provided to database automatically with the following style : qx::QxSqlDatabase::getSingleton()->getSqlPlaceHolderStyle().

Here is an example with many methods of qx::QxSqlQuery class (or its typedef qx_query) :

qx_query query;
query.where("sex").isEqualTo(author::female)
     .and_("age").isGreaterThan(38)
     .or_("last_name").isNotEqualTo("Dupont")
     .or_("first_name").like("Alfred")
     .and_OpenParenthesis("id").isLessThanOrEqualTo(999)
     .and_("birth_date").isBetween(date1, date2)
     .closeParenthesis()
     .or_("id").in(50, 999, 11, 23, 78945)
     .and_("is_deleted").isNotNull()
     .orderAsc("last_name", "first_name", "sex")
     .limit(50, 150);

This code will produce following SQL for MySQL, PostgreSQL and SQLite databases (for Oracle and SQLServer, there is a specific process for limit() method) :

WHERE sex = :sex_1_0 
AND age > :age_3_0 
OR last_name <> :last_name_5_0 
OR first_name LIKE :first_name_7_0 
AND ( id <= :id_10_0 AND birth_date BETWEEN :birth_date_12_0_1 AND :birth_date_12_0_2 ) 
OR id IN (:id_15_0_0, :id_15_0_1, :id_15_0_2, :id_15_0_3, :id_15_0_4) 
AND is_deleted IS NOT NULL 
ORDER BY last_name ASC, first_name ASC, sex ASC 
LIMIT :limit_rows_count_19_0 OFFSET :offset_start_row_19_0

Here is the list of all functions available to use qx::QxSqlQuery class (or its typedef qx_query) :

// with functions into namespace qx::dao
qx::dao::count<T>()
qx::dao::fetch_by_query<T>()
qx::dao::update_by_query<T>()
qx::dao::delete_by_query<T>()
qx::dao::destroy_by_query<T>()
qx::dao::fetch_by_query_with_relation<T>()
qx::dao::fetch_by_query_with_all_relation<T>()
qx::dao::update_by_query_with_relation<T>()
qx::dao::update_by_query_with_all_relation<T>()
qx::dao::update_optimized_by_query<T>()

// with qx::QxSession class
qx::QxSession::count<T>()
qx::QxSession::fetchByQuery<T>()
qx::QxSession::update<T>()
qx::QxSession::deleteByQuery<T>()
qx::QxSession::destroyByQuery<T>()

// with qx::QxRepository<T> class
qx::QxRepository<T>::count()
qx::QxRepository<T>::fetchByQuery()
qx::QxRepository<T>::update()
qx::QxRepository<T>::deleteByQuery()
qx::QxRepository<T>::destroyByQuery()

Note : those functions have 2 other optionals parameters :
  • const QStringList & columns : to indicate columns to fetch (by default, all columns are fetched) ;
  • const QStringList & relation : to indicate relations to fetch (one-to-one, one-to-many, many-to-one and many-to-many defined into void qx::register_class<T>() mapping function by class), by default there is no relation fetched.


How to use the cache (functions into namespace qx::cache) of QxOrm library ?

Cache engine provided by QxOrm library (QxCache module) is thread-safe and can store easily any kind of objects.
Functions to access to the cache engine are inside namespace qx::cache.
qx::cache engine can provide a program optimization : you can for example store items fetched by a query to database.

Each item into the cache is associated with a key of type QString : this key provides a quick access to an item stored into the cache.
If a new item is inserted with a key already in the cache, then the old item associated with this key is removed automatically from the cache.

Cache engine of QxOrm library doesn't manage memory : there is no delete called by the cache engine.
This is why it's strongly recommended (but not an obligation) to store smart-pointers into the cache : for example, boost::shared_ptr<T> of boost library or QSharedPointer<T> of Qt library.

Cache engine can have a max cost to avoid too much memory usage : each item inserted to the cache can be associated with a cost (for example, element's count of a collection).
When the limit (max cost) of the cache engine is reached, first items inserted to the cache are automatically removed (insertion order) until limit of the cache is ok.

It's also possible to associate a date-time insertion when an item is added to the cache.
If there is no date-time, then the current date-time is taken into account.
This feature provides a way to verify that an item stored into the cache must be updated or not.

Here is an example using cache engine of QxOrm library (functions into namespace qx::cache) :

// Define max cost of cache engine to 500
qx::cache::max_cost(500);

// Fetch a list of 'author' from database
boost::shared_ptr< QList<author> > list_author;
QSqlError daoError = qx::dao::fetch_all(list_author);

// Insert the list of 'author' to the cache
qx::cache::set("list_author", list_author);

// Fetch a list of 'blog' from database
QSharedPointer< std::vector<blog> > list_blog;
daoError = qx::dao::fetch_all(list_blog);

// Insert the list of 'blog' to the cache (cost = 'blog' count)
qx::cache::set("list_blog", list_blog, list_blog.count());

// Pointer to an object of type 'comment'
comment_ptr my_comment;
my_comment.reset(new comment(50));
daoError = qx::dao::fetch_by_id(my_comment);

// Insert 'comment' to the cache with a date-time insertion
qx::cache::set("comment", my_comment, 1, my_comment->dateModif());

// Get the list of 'blog' stored into the cache
list_blog = qx::cache::get< QSharedPointer< std::vector<blog> > >("list_blog");

// Get the list of 'blog' without providing the type
qx_bool bGetOk = qx::cache::get("list_blog", list_blog);

// Remove list of 'author' from cache
bool bRemoveOk = qx::cache::remove("list_author");

// Get items count stored into the cache
long lCount = qx::cache::count();

// Get current cost of items stored into the cache
long lCurrentCost = qx::cache::current_cost();

// Verify that an element with the key "comment" exists into the cache
bool bExist = qx::cache::exist("comment");

// Get 'comment' stored into the cache with its date-time insertion
QDateTime dt;
bGetOk = qx::cache::get("comment", my_comment, dt);

// Clear the cache
qx::cache::clear();



How to build SQL schema (create and update tables) based on C++ persistents classes registered into QxOrm context ?

It's recommended to use QxEntityEditor application to manage SQL schema generation.

QxOrm library doesn't provide a generator to create and to update automatically tables into database.
Indeed, qx::dao::create_table<T> function must be used only to create prototypes or samples.
It's strongly recommended to work with a tool provided by each SGBD to design and to manage tables into database (for example Navicat with MySql, pgAdmin with PostgreSQL, SQLite Manager with SQLite, etc.).
Moreover, each tool provided by each SGBD can add some optimizations to the database (add some indexes for example).

But sometimes, it can be useful to not have to manage manually tables into database.
In this case, it's possible to create a C++ function to iterate over all persistents classes registered into QxOrm context (using introspection engine of QxOrm library) : so you can build a SQL script to create and to update tables into database.

QxOrm library provides an example of a C++ function : based on this function, you can create your own function to build SQL schema.
This QxOrm function is written in the file ./src/QxRegister/QxClassX.cpp and is called QString qx::QxClassX::dumpSqlSchema().
This QxOrm function builds a SQL script and returns a QString value : it's also possible to modify the function to generate a file with SQL script or to execute each SQL process directly to the SGBD.

Here is a sample implementation provided by dodobibi to manage a PostgreSQL database : this sample works with a version number to add columns to existing tables, to add some indexes to existing columns, etc.
When you start your application, a version number is provided and incremented when a new version of your application is released :

QApplication app(argc, argv);
app.setProperty("DomainVersion", 1);

A table into the database must be created to store this version number.
A C++ persistent class is mapped to this table :

#ifndef _DATABASE_VERSION_H_
#define _DATABASE_VERSION_H_
 
class MY_DLL_EXPORT DatabaseVersion
{
public:
  QString name;
  long version;
};

QX_REGISTER_HPP_MY_APP(DatabaseVersion, qx::trait::no_base_class_defined, 0)

#endif // _DATABASE_VERSION_H_

#include "../include/precompiled.h"
#include "../include/DatabaseVersion.h"
#include <QxMemLeak.h>
 
QX_REGISTER_CPP_MY_APP(DatabaseVersion)

namespace qx {
template <> void register_class(QxClass<DatabaseVersion> & t)
{
  t.id(& DatabaseVersion::name, "name");
  t.data(& DatabaseVersion::version, "version");
}}

With DatabaseVersion class, it's possible to verify that the database must be updated or not.
This is the goal of isDatabaseVersionOld() function :

bool isDatabaseVersionOld()
{
  DatabaseVersion dbVersion;
  dbVersion.name = "MyAppName";
  QSqlError err = qx::dao::fetch_by_id(dbVersion);
  if (err.isValid()) { qAssert(false); return false; }
  return (dbVersion.version < qApp->property("DomainVersion").toInt());
}

If isDatabaseVersionOld() function returns true when you start your application, then you must update your SQL schema :

void updateDatabaseVersion()
{
  try
  {
    int domainVersion = qApp->property("DomainVersion").toInt();

    // Connect to the database with a user with modifications rights on SQL schema
    QSqlDatabase db = qx::QxSqlDatabase::getSingleton()->getDatabaseCloned();
    db.setUserName("MyAdminLogin");
    db.setPassword("MyAdminPassword");

    // Create a session, open automatically a transaction and throw an exception when an error occured
    qx::QxSession session(db, true, true);

    // Fetch the database version with a lock to protect the database
    DatabaseVersion dbVersion;
    session.fetchByQuery(qx_query("WHERE name='MyAppName' FOR UPDATE"), dbVersion);

    // When unlocked for other users, verify that the database must be updated or not
    if (dbVersion.version >= domainVersion) { return; }

    // Execute each SQL process with "query" variable
    QSqlQuery query(db);

    // Fetch all C++ persistents classes registered into QxOrm context
    qx::QxCollection<QString, qx::IxClass *> * pAllClasses = qx::QxClassX::getAllClasses();
    if (! pAllClasses) { qAssert(false); return; }

    // Fetch all tables into database (this is a Qt function)
    QStringList tables = db.tables();

    for (long k = 0; k < pAllClasses->count(); k++)
    {
      qx::IxClass * pClass = pAllClasses->getByIndex(k);
      if (! pClass) { continue; }

      // Filter non persitents classes
      if (pClass->isKindOf("qx::service::IxParameter") || pClass->isKindOf("qx::service::IxService")) { continue; }

      // Filter already updated classes
      if (pClass->getVersion() <= dbVersion.version) { continue; }

      // If table doesn't exist, create it and set the owner
      if (! tables.contains(pClass->getName()))
      {
        query.exec("CREATE TABLE " + pClass->getName() + " ( ) WITH (OIDS = FALSE);"
                   "ALTER TABLE " + pClass->getName() + " OWNER TO \"MyAdminLogin\";");
        session += query.lastError();
      }

      // If a column doesn't exist, add it to the table
      qx::IxDataMemberX * pDataMemberX = pClass->getDataMemberX();
      for (long l = 0; (pDataMemberX && (l < pDataMemberX->count_WithDaoStrategy())); l++)
      {
        qx::IxDataMember * p = pDataMemberX->get_WithDaoStrategy(l);
        if (! p || (p->getVersion() <= dbVersion.version)) { continue; }

        query.exec("ALTER TABLE " + pClass->getName() + " ADD COLUMN " + p->getName() + " " + p->getSqlType() + ";");
        session += query.lastError();

        if (p->getIsPrimaryKey()) // PRIMARY KEY
        {
          query.exec("ALTER TABLE " + pClass->getName() + " ADD PRIMARY KEY (" + p->getName() + ");");
          session += query.lastError();
        }

        if (p->getAllPropertyBagKeys().contains("INDEX")) // INDEX
        {
          query.exec("CREATE INDEX " + pClass->getName() + "_" + p->getName() + "_idx" + 
                     " ON " + pClass->getName() + " USING " + p->getPropertyBag("INDEX").toString() + " (" + p->getName() + ");");
          session += query.lastError();
        }

        if (p->getNotNull()) // NOT NULL
        {
          query.exec("ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " SET NOT NULL;");
          session += query.lastError();
        }

        if (p->getAutoIncrement()) // AUTO INCREMENT
        {
          query.exec("CREATE SEQUENCE " + pClass->getName() + "_" + p->getName() + "_seq" + "; "
                     "ALTER TABLE " + pClass->getName() + "_" + p->getName() + "_seq" + " OWNER TO \"MyAdminLogin\"; "
                     "ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " " +
                     "SET DEFAULT nextval('" + pClass->getName() + "_" + p->getName() + "_seq" + "'::regclass);");
          session += query.lastError();
        }

        if (p->getDescription() != "") // DESCRIPTION
        {          query.exec("COMMENT ON COLUMN " + pClass->getName() + "." + p->getName() + " IS $$" + p->getDescription() + "$$ ;");
          session += query.lastError();
        }
      }
    }

    // Save current version of the database
    dbVersion.version = domainVersion;
    session.save(dbVersion);

    // End of "try" scope : session is destroyed => commit or rollback automatically
    // Moreover, a commit or a rollback unlock the process for all users
  }
  catch (const qx::dao::sql_error & err)
  {
    QSqlError sqlError = err.get();
    qDebug() << sqlError.databaseText();
    qDebug() << sqlError.driverText();
    qDebug() << sqlError.number();
    qDebug() << sqlError.type();
  }
}

Note : this code (like qx::QxClassX::dumpSqlSchema() function) can be modified to provide more features.
For example, it could be interesting to create by default another table (like DatabaseVersion table) to store the list of all persistents classes registered into QxOrm context : instead of using "db.tables()" Qt function, it could be possible to fetch all tables with more informations (version number for each table, columns count registered into QxOrm context, table description, etc.).


How to associate a SQL type to a C++ class ?

Each database provides its own SQL types to store datas.
QxOrm library associates by default some C++ classes frequently used in a program :

"bool" <-> "SMALLINT"
"qx_bool" <-> "SMALLINT"
"short" <-> "SMALLINT"
"int" <-> "INTEGER"
"long" <-> "INTEGER"
"long long" <-> "INTEGER"
"float" <-> "FLOAT"
"double" <-> "FLOAT"
"long double" <-> "FLOAT"
"unsigned short" <-> "SMALLINT"
"unsigned int" <-> "INTEGER"
"unsigned long" <-> "INTEGER"
"unsigned long long" <-> "INTEGER"
"std::string" <-> "TEXT"
"std::wstring" <-> "TEXT"
"QString" <-> "TEXT"
"QVariant" <-> "TEXT"
"QUuid" <-> "TEXT"
"QDate" <-> "DATE"
"QTime" <-> "TIME"
"QDateTime" <-> "TIMESTAMP"
"QByteArray" <-> "BLOB"
"qx::QxDateNeutral" <-> "TEXT"
"qx::QxTimeNeutral" <-> "TEXT"
"qx::QxDateTimeNeutral" <-> "TEXT"

If a SQL type provided by default by QxOrm library is not supported by the database, it can be easily modified (globally for all the application) using the following collection :

QHash<QString, QString> * lstSqlType = qx::QxClassX::getAllSqlTypeByClassName();
lstSqlType->insert("QString", "VARCHAR(255)");
lstSqlType->insert("std::string", "VARCHAR(255)");
// etc.

To modify a SQL type for a specific column of a table, you have to define the new SQL type in the mapping function of QxOrm library :

namespace qx {
template <> void register_class(QxClass<MyClass> & t)
{
  //...
  IxDataMember * p =  t.data(& MyClass::m_MyProperty, "my_property");
  p->setSqlType("VARCHAR(255)");
  //...
}}

For all classes not supported by default by QxOrm library (see the FAQ : How to persist a type without its source code (class from an external library for example) ?), it's possible to associate a default SQL type using the following macro (outside all namespace) :

QX_REGISTER_TRAIT_GET_SQL_TYPE(MyClass, "my_sql_type")



How to use QxValidator module to validate automatically an instance ?

QxValidator module of QxOrm library provides a validation engine for classes registered into QxOrm context.
To use this validation engine, you have to define your constraints into the mapping function per class : void qx::register_class.
If for an instance of class, at least one constraint violation is detected, then the instance is invalid : the object cannot be saved into database (INSERT or UPDATE).

It's also possible to use QxValidator module to validate an instance on the presentation layer : if some datas from a user are invalids, an error message can be displayed, and it's not necessary to try to send the instance to the data access layer.
The validation mechanism can be executed in different layers in your application without having to duplicate any of these rules (presentation layer, data access layer).

Here is a description of some classes defined into QxValidator module : QxValidator module manages automatically class inheritance : each constraint defined into a base class is checked during validation process of a derived class.

Here is an example using QxValidator module with a 'person' class :

* 'person.h' file :
#ifndef _CLASS_PERSON_H_
#define _CLASS_PERSON_H_
 
class person
{

public:

   enum sex { male, female, unknown };

   long        _id;
   QString     _firstName;
   QString     _lastName;
   QDateTime   _birthDate;
   sex         _sex;

   person() : _id(0), _sex(unknown) { ; }
   person(long id) : _id(id), _sex(unknown) { ; }
   virtual ~person() { ; }

private:

   void isValid(qx::QxInvalidValueX & invalidValues);

};

QX_REGISTER_HPP_MY_EXE(person, qx::trait::no_base_class_defined, 0)

#endif // _CLASS_PERSON_H_

* 'person.cpp' file :
#include "../include/precompiled.h"

#include "../include/person.h"
#include "../include/global_validator.h"

#include <QxMemLeak.h>

QX_REGISTER_CPP_MY_EXE(person)

namespace qx {
template <> void register_class(QxClass<person> & t)
{
   t.id(& person::_id, "id");

   t.data(& person::_firstName, "firstName");
   t.data(& person::_lastName, "lastName");
   t.data(& person::_birthDate, "birthDate");
   t.data(& person::_sex, "sex");

   QxValidatorX<person> * pAllValidator = t.getAllValidator();
   pAllValidator->add_NotEmpty("firstName");
   pAllValidator->add_NotEmpty("lastName", "a person must have a lastname");
   pAllValidator->add_CustomValidator(& person::isValid);
   pAllValidator->add_CustomValidator_QVariant(& validateFirstName, "firstName");
   pAllValidator->add_CustomValidator_DataType<QDateTime>(& validateDateTime, "birthDate");
}}

void person::isValid(qx::QxInvalidValueX & invalidValues)
{
   // This method is called automatically by 'QxValidator' module (validator engine of QxOrm library) :
   // - when you try to insert or update using 'qx::dao::xxx' functions
   // - when you call 'qx::validate()' function

   // For registration, see 'pAllValidator->add_CustomValidator(& person::isValid);' into 'qx::register_class' function

   // Here, you can verify some values of your instance
   // If a value is not valid, you must add an invalid value into the collection 'invalidValues'

   // For example, if we want to check property '_sex' of a person :
   if ((_sex != male) && (_sex != female))
   { invalidValues.insert("person sex must be defined : male or female"); }
}

* 'global_validator.h' file :
// Example of global functions 'validateFirstName' and 'validateDateTime' used by 'QxValidator' module
// Those functions will be called automatically by validator engine of QxOrm library :
// - when you try to insert or update using 'qx::dao::xxx' functions
// - when you call 'qx::validate()' function
 
void validateFirstName(const QVariant & value, const qx::IxValidator * validator, qx::QxInvalidValueX & invalidValues)
{
   // Here you can test the value (converted to QVariant type)
   // If an invalid value is detected, just add a message into 'invalidValues' collection

   // For example, if the value must be never equal to "admin" :
   if (value.toString() == "admin")
   { invalidValues.insert("value must not be equal to 'admin'"); }
}

void validateDateTime(const QDateTime & value, const qx::IxValidator * validator, qx::QxInvalidValueX & invalidValues)
{
   // Here you can test the value (with its real type, in this example, the data-member is a 'QDateTime' type)
   // If an invalid value is detected, just add a message into 'invalidValues' collection

   // For example, if the date-time must be valid :
   if (! value.isValid())
   { invalidValues.insert("date-time value must not be empty and must be valid"); }
}

* 'main.cpp' file :
person personValidate;
personValidate._lastName = "admin";
qx::QxInvalidValueX invalidValues = qx::validate(personValidate);
QString sInvalidValues = invalidValues.text();
qDebug("[QxOrm] test 'QxValidator' module :\n%s", qPrintable(sInvalidValues));

During program execution of this code, 'personValidate' instance is not valid : 'invalidValues' collection contains 4 items :
- "property 'firstName' must not be empty" ;
- "person sex must be defined : male or female" ;
- "value must not be equal to 'admin'" ;
- "date-time value must not be empty and must be valid".

QxValidator module provides some built-in constraints, which cover most of the basic data checks.
As we'll see later, you're not limited to them, you can literally in a minute write your own constraints :
  • add_NotNull() : checks if the value is not null ;
  • add_NotEmpty() : checks if the string is not empty ;
  • add_MinValue() : checks if the value is more than or equals to min ;
  • add_MaxValue() : checks if the value is less than or equals to max ;
  • add_Range() : checks if the value is between Min and Max (included) ;
  • add_MinDecimal() : checks if the decimal value is more than or equals to min ;
  • add_MaxDecimal() : checks if the decimal value is less than or equals to max ;
  • add_RangeDecimal() : checks if the decimal value is between Min and Max (included) ;
  • add_MinLength() : checks if the string length is more than or equals to min ;
  • add_MaxLength() : checks if the string length is less than or equals to max ;
  • add_Size() : checks if the string length is between the min-max range ;
  • add_DatePast() : checks if the date is in the past ;
  • add_DateFuture() : checks if the date is in the future ;
  • add_RegExp() : checks if the property matches the regular expression given a match flag ;
  • add_EMail() : checks whether the string conforms to the email address specification.
Like 'person' class example, it's possible to define a custom validator : it's a function or a class method called automatically by QxValidator module to validate a property or an instance of class.
There are 3 kinds of custom validator :
  • add_CustomValidator() : class method, method signature must be "void my_class::my_method(qx::QxInvalidValueX &)" ;
  • add_CustomValidator_QVariant() : global function with QVariant type (the property is converted into QVariant type), function signature must be "void my_validator(const QVariant &, const qx::IxValidator *, qx::QxInvalidValueX &)" ;
  • add_CustomValidator_DataType() : global function with real type, function signature must be "void my_validator(const T &, const qx::IxValidator *, qx::QxInvalidValueX &)" ;
Note : each validator can be associated with a group (optional parameter for each function add_XXX() of qx::IxValidatorX class).
So it's possible to create a context validation during program execution : for example, a person from IHM A can have different validation rules than a person from IHM B.
To execute a validation process by group (for example "myGroup"), you have to call the following function : "qx::QxInvalidValueX invalidValues = qx::validate(personValidate, "myGroup");".

Other note : QxValidator module provides default messages when a constraint violation is detected.
It's possible to modify those default messages (for example, a traduction) using the following collection : "QHash * lstMessage = QxClassX::getAllValidatorMessage();".
For example : "lstMessage->insert("min_value", "la valeur '%NAME%' doit être inférieure ou égale à '%CONSTRAINT%'");".
%NAME% and %CONSTRAINT% fields will be automatically replaced by the good value.
To modify a message for a specific validator (and not globally), you have to use the optional parameter provided by each function add_XXX() of qx::IxValidatorX class.


How to use qx::IxPersistable interface ?

qx::IxPersistable interface (or abstract class) provides only pure virtual methods.
Using qx::IxPersistable, you will have a common base class to call all persistents functions without knowing the real type of current instance (polymorphism concept).
QxOrm library doesn't force developers to work with a base class to register a persistent type into QxOrm context, however it's sometimes useful to have an interface to write some generic algorithms.

qx::IxPersistable class provides following virtual methods (for more details about those methods, goto QxOrm library online documentation) :

virtual long qxCount(const qx::QxSqlQuery & query = qx::QxSqlQuery(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxFetchById(const QVariant & id = QVariant(), const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxFetchAll(qx::IxCollection & list, const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxFetchByQuery(const qx::QxSqlQuery & query, qx::IxCollection & list, const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxInsert(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxUpdate(const qx::QxSqlQuery & query = qx::QxSqlQuery(), const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxSave(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDeleteById(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDeleteAll(QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDeleteByQuery(const qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDestroyById(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDestroyAll(QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDestroyByQuery(const qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL);
virtual qx_bool qxExist(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL);
virtual qx::QxInvalidValueX qxValidate(const QStringList & groups = QStringList());
virtual qx::IxCollection_ptr qxNewPersistableCollection() const;
virtual qx::IxClass * qxClass() const;

For example, working with a list of qx::IxPersistable pointers, it's possible to save all items into many tables of database, like this :

QList<qx::IxPersistable *> lst = ...;
foreach(qx::IxPersistable * p, lst)
{
   QSqlError daoError = p->qxSave();
   if (daoError.isValid()) { /* an error occured */ }
   // etc...
}

To implement qx::IxPersistable interface, it's necessary to :
  • inherit persistent class from qx::IxPersistable ;
  • into class definition (myClass.h for example), add QX_PERSISTABLE_HPP(myClass) macro ;
  • into class implementation (myClass.cpp for example), add QX_PERSISTABLE_CPP(myClass) macro.
For example, to implement qx::IxPersistable interface for author class from qxBlog tutorial, you have to write :

#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_

class blog;

class QX_BLOG_DLL_EXPORT author : public qx::IxPersistable
{
   QX_PERSISTABLE_HPP(author)
public:
// -- typedef
   typedef boost::shared_ptr<blog> blog_ptr;
   typedef std::vector<blog_ptr> list_blog;
// -- enum
   enum enum_sex { male, female, unknown };
// -- properties
   QString     m_id;
   QString     m_name;
   QDate       m_birthdate;
   enum_sex    m_sex;
   list_blog   m_blogX;
// -- contructor, virtual destructor
   author() : m_id(0), m_sex(unknown) { ; }
   virtual ~author() { ; }
// -- methods
   int age() const;
};

QX_REGISTER_PRIMARY_KEY(author, QString)
QX_REGISTER_HPP_QX_BLOG(author, qx::trait::no_base_class_defined, 0)

typedef boost::shared_ptr<author> author_ptr;
typedef qx::QxCollection<QString, author_ptr> list_author;

#endif // _QX_BLOG_AUTHOR_H_

#include "../include/precompiled.h"

#include "../include/author.h"
#include "../include/blog.h"

#include <QxMemLeak.h>

QX_REGISTER_CPP_QX_BLOG(author)
QX_PERSISTABLE_CPP(author)

namespace qx {
template <> void register_class(QxClass<author> & t)
{
   t.id(& author::m_id, "author_id");

   t.data(& author::m_name, "name");
   t.data(& author::m_birthdate, "birthdate");
   t.data(& author::m_sex, "sex");

   t.relationOneToMany(& author::m_blogX, "list_blog", "author_id");

   t.fct_0<int>(& author::age, "age");
}}

int author::age() const
{
   if (! m_birthdate.isValid()) { return -1; }
   return (QDate::currentDate().year() - m_birthdate.year());
}

Note : project test from ./test/qxDllSample/dll1/ directory provides a kind of 'super base class' : qx::QxPersistable class implements qx::IxPersistable interface and inherits from QObject.
So SIGNAL-SLOT concept from Qt library can be used with this class and could be an interesting way to use QxOrm triggers.
qx::QxPersistable class provides also some virtual methods to override to manage for example data validation process from QxValidator module.
For information, qx::QxPersistable class is not a part of QxOrm library, but you can copy-past it into your own project to use all its features :


How to use relationship engine to fetch datas from many tables ?

QxOrm library supports 4 kind of relationships to link C++ classes registered into QxOrm context : one-to-one, one-to-many, many-to-one and many-to-many.
For more details to define relationships, you can take a look at qxBlog tutorial.
We will explain here how to fetch datas from many tables (QxDao module, functions of qx::dao namespace) : The first parameter of fetch_by_id_with_relation, fetch_all_with_relation and fetch_by_query_with_relation functions is the list of relationships to fetch.
This list of relationships can contain those items :
  • relation key : each relation is associated to a key defined into qx::register_class<T> setting function ;
  • "*" keyword means "fetch all relationships defined into qx::register_class<T> function (1 level of relationships)" ;
  • "->" keyword means "LEFT OUTER JOIN" join type between 2 tables ;
  • ">>" keyword means "INNER JOIN" join type between 2 tables.
Note : using "*" keyword to indicate "fetch all relationships defined into qx::register_class<T> function", those calls are similar :
  • qx::dao::fetch_by_id_with_relation("*", ...) == qx::dao::fetch_by_id_with_all_relation(...) ;
  • qx::dao::fetch_by_query_with_relation("*", ...) == qx::dao::fetch_by_query_with_all_relation(...) ;
  • qx::dao::fetch_all_with_relation("*", ...) == qx::dao::fetch_all_with_all_relation(...).

Example : from qxBlog tutorial, it's possible to fetch all those datas with only 1 query :

1- fetch a blog and its author ;
2- for the author fetched, fetch all blog he wrote ;
3- for each blog written by author fetched, fetch all comment associated.

blog_ptr my_blog = blog_ptr(new blog(10));
QSqlError daoError = qx::dao::fetch_by_id_with_relation("author_id->list_blog->list_comment", my_blog);

This code builds the following SQL query :
SELECT blog.blog_id AS blog_blog_id_0, blog.blog_text AS blog_blog_text_0, blog.date_creation AS blog_date_creation_0, blog.author_id AS blog_author_id_0, 
       author_1.author_id AS author_1_author_id_0, author_1.name AS author_1_name_0, author_1.birthdate AS author_1_birthdate_0, author_1.sex AS author_1_sex_0, 
       blog_2.blog_id AS blog_2_blog_id_0, blog_2.author_id AS blog_2_author_id_0, blog_2.blog_text AS blog_2_blog_text_0, blog_2.date_creation AS blog_2_date_creation_0, 
       comment_4.comment_id AS comment_4_comment_id_0, comment_4.blog_id AS comment_4_blog_id_0, comment_4.comment_text AS comment_4_comment_text_0, comment_4.date_creation AS comment_4_date_creation_0 
FROM blog 
LEFT OUTER JOIN author author_1 ON author_1.author_id = blog.author_id 
LEFT OUTER JOIN blog blog_2 ON blog_2.author_id = author_1.author_id 
LEFT OUTER JOIN comment comment_4 ON comment_4.blog_id = blog_2.blog_id 
WHERE blog.blog_id = :blog_id


Another example : it's also possible to create a list of relationships to fetch, like this :

blog_ptr my_blog = blog_ptr(new blog(10));
QStringList relation;
relation << "author_id->list_blog->list_comment";
relation << "author_id->list_blog->list_category";
relation << "list_comment";
relation << "list_category";
QSqlError daoError = qx::dao::fetch_by_id_with_relation(relation, my_blog);

This code builds the following SQL query :
SELECT blog.blog_id AS blog_blog_id_0, blog.blog_text AS blog_blog_text_0, blog.date_creation AS blog_date_creation_0, blog.author_id AS blog_author_id_0, 
       author_1.author_id AS author_1_author_id_0, author_1.name AS author_1_name_0, author_1.birthdate AS author_1_birthdate_0, author_1.sex AS author_1_sex_0, 
       blog_2.blog_id AS blog_2_blog_id_0, blog_2.author_id AS blog_2_author_id_0, blog_2.blog_text AS blog_2_blog_text_0, blog_2.date_creation AS blog_2_date_creation_0, 
       category_5.category_id AS category_5_category_id_0, category_5.name AS category_5_name_0, category_5.description AS category_5_description_0, 
       comment_6.comment_id AS comment_6_comment_id_0, comment_6.blog_id AS comment_6_blog_id_0, comment_6.comment_text AS comment_6_comment_text_0, comment_6.date_creation AS comment_6_date_creation_0, 
       category_7.category_id AS category_7_category_id_0, category_7.name AS category_7_name_0, category_7.description AS category_7_description_0 
FROM blog 
LEFT OUTER JOIN author author_1 ON author_1.author_id = blog.author_id 
LEFT OUTER JOIN blog blog_2 ON blog_2.author_id = author_1.author_id 
LEFT OUTER JOIN category_blog category_blog_5 ON blog_2.blog_id = category_blog_5.blog_id 
LEFT OUTER JOIN category category_5 ON category_blog_5.category_id = category_5.category_id 
LEFT OUTER JOIN comment comment_6 ON comment_6.blog_id = blog.blog_id 
LEFT OUTER JOIN category_blog category_blog_7 ON blog.blog_id = category_blog_7.blog_id 
LEFT OUTER JOIN category category_7 ON category_blog_7.category_id = category_7.category_id 
WHERE blog.blog_id = :blog_id


Another example : to fetch all relationships per level, "*" keyword must be used.
So to fetch all relationships on 3 levels, we can write :

blog_ptr my_blog = blog_ptr(new blog(10));
QSqlError daoError = qx::dao::fetch_by_id_with_relation("*->*->*", my_blog);

This code builds the following SQL query :
SELECT blog.blog_id AS blog_blog_id_0, blog.blog_text AS blog_blog_text_0, blog.date_creation AS blog_date_creation_0, blog.author_id AS blog_author_id_0, 
       author_1.author_id AS author_1_author_id_0, author_1.name AS author_1_name_0, author_1.birthdate AS author_1_birthdate_0, author_1.sex AS author_1_sex_0, 
       blog_2.blog_id AS blog_2_blog_id_0, blog_2.author_id AS blog_2_author_id_0, blog_2.blog_text AS blog_2_blog_text_0, blog_2.date_creation AS blog_2_date_creation_0, blog_2.author_id AS blog_2_author_id_0_2, 
       author_3.author_id AS author_3_author_id_0, author_3.name AS author_3_name_0, author_3.birthdate AS author_3_birthdate_0, author_3.sex AS author_3_sex_0, 
       comment_4.comment_id AS comment_4_comment_id_0, comment_4.blog_id AS comment_4_blog_id_0, comment_4.comment_text AS comment_4_comment_text_0, comment_4.date_creation AS comment_4_date_creation_0, 
       category_5.category_id AS category_5_category_id_0, category_5.name AS category_5_name_0, category_5.description AS category_5_description_0, 
       comment_6.comment_id AS comment_6_comment_id_0, comment_6.blog_id AS comment_6_blog_id_0, comment_6.comment_text AS comment_6_comment_text_0, comment_6.date_creation AS comment_6_date_creation_0, comment_6.blog_id AS comment_6_blog_id_0_6, 
       blog_7.blog_id AS blog_7_blog_id_0, blog_7.blog_text AS blog_7_blog_text_0, blog_7.date_creation AS blog_7_date_creation_0, blog_7.author_id AS blog_7_author_id_0_7, 
       author_8.author_id AS author_8_author_id_0, author_8.name AS author_8_name_0, author_8.birthdate AS author_8_birthdate_0, author_8.sex AS author_8_sex_0, 
       comment_9.comment_id AS comment_9_comment_id_0, comment_9.blog_id AS comment_9_blog_id_0, comment_9.comment_text AS comment_9_comment_text_0, comment_9.date_creation AS comment_9_date_creation_0, 
       category_10.category_id AS category_10_category_id_0, category_10.name AS category_10_name_0, category_10.description AS category_10_description_0, 
       category_11.category_id AS category_11_category_id_0, category_11.name AS category_11_name_0, category_11.description AS category_11_description_0, 
       blog_12.blog_id AS blog_12_blog_id_0, blog_12.blog_text AS blog_12_blog_text_0, blog_12.date_creation AS blog_12_date_creation_0, blog_12.author_id AS blog_12_author_id_0_12, 
       author_13.author_id AS author_13_author_id_0, author_13.name AS author_13_name_0, author_13.birthdate AS author_13_birthdate_0, author_13.sex AS author_13_sex_0, 
       comment_14.comment_id AS comment_14_comment_id_0, comment_14.blog_id AS comment_14_blog_id_0, comment_14.comment_text AS comment_14_comment_text_0, comment_14.date_creation AS comment_14_date_creation_0, 
       category_15.category_id AS category_15_category_id_0, category_15.name AS category_15_name_0, category_15.description AS category_15_description_0 
FROM blog 
LEFT OUTER JOIN author author_1 ON author_1.author_id = blog.author_id 
LEFT OUTER JOIN blog blog_2 ON blog_2.author_id = author_1.author_id 
LEFT OUTER JOIN author author_3 ON author_3.author_id = blog_2.author_id 
LEFT OUTER JOIN comment comment_4 ON comment_4.blog_id = blog_2.blog_id 
LEFT OUTER JOIN category_blog category_blog_5 ON blog_2.blog_id = category_blog_5.blog_id 
LEFT OUTER JOIN category category_5 ON category_blog_5.category_id = category_5.category_id 
LEFT OUTER JOIN comment comment_6 ON comment_6.blog_id = blog.blog_id 
LEFT OUTER JOIN blog blog_7 ON blog_7.blog_id = comment_6.blog_id 
LEFT OUTER JOIN author author_8 ON author_8.author_id = blog_7.author_id 
LEFT OUTER JOIN comment comment_9 ON comment_9.blog_id = blog_7.blog_id 
LEFT OUTER JOIN category_blog category_blog_10 ON blog_7.blog_id = category_blog_10.blog_id 
LEFT OUTER JOIN category category_10 ON category_blog_10.category_id = category_10.category_id 
LEFT OUTER JOIN category_blog category_blog_11 ON blog.blog_id = category_blog_11.blog_id 
LEFT OUTER JOIN category category_11 ON category_blog_11.category_id = category_11.category_id 
LEFT OUTER JOIN category_blog category_blog_12 ON category_11.category_id = category_blog_12.category_id 
LEFT OUTER JOIN blog blog_12 ON category_blog_12.blog_id = blog_12.blog_id 
LEFT OUTER JOIN author author_13 ON author_13.author_id = blog_12.author_id 
LEFT OUTER JOIN comment comment_14 ON comment_14.blog_id = blog_12.blog_id 
LEFT OUTER JOIN category_blog category_blog_15 ON blog_12.blog_id = category_blog_15.blog_id 
LEFT OUTER JOIN category category_15 ON category_blog_15.category_id = category_15.category_id 
WHERE blog.blog_id = :blog_id



How to execute a stored procedure or a custom SQL query ?

QxOrm library provides 2 functions to execute a stored procedure or a custom SQL query : The first parameter of those functions, of qx::QxSqlQuery type (or qx_query), contains the stored procedure or the custom SQL query to execute.
For more informations about qx::QxSqlQuery class, goto here : How to build a query without writing SQL with the class qx::QxSqlQuery ?

qx::dao::execute_query<T>() function is a template function : T type must be registered into QxOrm context (qx::register_class<T> function).
All datas returned by the stored procedure or the custom SQL query which could be associated with members of the C++ class (of T type) will be fetched automatically.
An automatic search is done on the name of each fields returned by the query.
Here is an example from qxBlog project of QxOrm package :

// Call a custom SQL query or a stored procedure and fetch automatically properties (with a collection of items)
qx_query testStoredProcBis("SELECT * FROM author");
daoError = qx::dao::execute_query(testStoredProcBis, authorX);
qAssert(! daoError.isValid()); qAssert(authorX.count() > 0);
qx::dump(authorX);


qx::dao::call_query() function is not a template function : you have to iterate over each result using qx::QxSqlQuery class (or qx_query).
To get an output value parameter (must be pass as QSql::Out or QSql::InOut) returned by a stored procedure, just call the following method : QVariant qx::QxSqlQuery::boundValue(const QString & sKey) const;.

To iterate over all resultset, just use the following methods :
* long qx::QxSqlQuery::getSqlResultRowCount() const;
* long qx::QxSqlQuery::getSqlResultColumnCount() const;
* QVariant qx::QxSqlQuery::getSqlResultAt(long row, long column) const;
* QVariant qx::QxSqlQuery::getSqlResultAt(long row, const QString & column) const;
* QVector qx::QxSqlQuery::getSqlResultAllColumns() const;
* void qx::QxSqlQuery::dumpSqlResult();

Here is an example using qx::dao::call_query() function :

qx_query query("CALL MyStoredProc(:param1, :param2)");
query.bind(":param1", "myValue1");
query.bind(":param2", 5024, QSql::InOut);
QSqlError daoError = qx::dao::call_query(query);
QVariant vNewValue = query.boundValue(":param2");
query.dumpSqlResult();



How to use qx::QxDaoAsync class to execute queries in asynchronous way (multi-thread) ?

Sometimes, it's necessary to execute some queries to database in asynchronous way (multi-thread), for example to avoid to freeze a GUI if a query is too long to execute.
To make easier to work with asynchronous queries, QxOrm library provides qx::QxDaoAsync class.
This class executes a query in another thread and returns the queryFinished() SIGNAL when query is terminated.
To use qx::QxDaoAsync class, you just have to :
  • be careful to work only with classes implementing qx::IxPersistable interface ;
  • create an instance of qx::QxDaoAsync type (for example, a property of a QWidget derived class) ;
  • connect a SLOT to the qx::QxDaoAsync::queryFinished() SIGNAL (for example, a SLOT of a QWidget derived class) ;
  • run a query using one of qx::QxDaoAsync::asyncXXXX() methods.
Here is an example with a class called MyWidget :

class MyWidget : public QWidget
{ Q_OBJECT

   //...
   qx::QxDaoAsync m_daoAsync;
   //...
Q_SLOTS:
   void onQueryFinished(const QSqlError & daoError, qx::dao::detail::QxDaoAsyncParams_ptr pDaoParams);
   //...

};

And here is the implementation of MyWidget class :

MyWidget::MyWidget() : QObject()
{
   //...
   QObject::connect((& m_daoAsync), SIGNAL(queryFinished(const QSqlError &, qx::dao::detail::QxDaoAsyncParams_ptr)), 
                    this, SLOT(onQueryFinished(const QSqlError &, qx::dao::detail::QxDaoAsyncParams_ptr)));
   //...
}

void MyWidget::onQueryFinished(const QSqlError & daoError, qx::dao::detail::QxDaoAsyncParams_ptr pDaoParams)
{
   if (! pDaoParams) { return; }
   qx::QxSqlQuery query = pDaoParams->query;
   if (! daoError.isValid()) { ; }
   // If the async query is associated to a simple object, just use 'pDaoParams->pInstance' method
   qx::IxPersistable_ptr ptr = pDaoParams->pInstance;
   // If the async query is associated to a list of objects, just use 'pDaoParams->pListOfInstances' method
   qx::IxCollection_ptr lst = pDaoParams->pListOfInstances;
   //...
}



How to use QxModelView module to interact with Qt model/view architecture (Qt widgets and/or QML views) ?

QxModelView module provides an easy way to work with Qt model/view engine with all classes registered into QxOrm context :
  • Qt widgets : QTableView or QListView for example to display/modify a database table content ;
  • QML : each property defined in QxOrm context is exposed to QML engine : QxModelView module makes easier integration between QML and databases.
qx::IxModel interface provides a generic way for all models linked to persistents classes registered into QxOrm context. All methods of this class prefixed by 'qx' call functions from 'qx::dao' namespace and then communicate with database.

The qxBlogModelView sample project in ./test/ directory of QxOrm package shows how to create quickly a model and associate it to the Qt model/view engine (first with a Qt widget, then with a QML view).

1- Here is an example to display/modify data from 'author' table (go to qxBlog tutorial for 'author' class definition) in a QTableView :

// Create a model and fetch all data from database
qx::IxModel * pModel = new qx::QxModel<author>();
pModel->qxFetchAll();

// Associate the model to a QTableView and display it
QTableView tableView;
tableView.setModel(pModel);
tableView.show();

After executing this code, you will have the following window :

qx_model_view_01


2- Here is another example in QML (with Qt5, QxModelView module works fine with Qt4 too) :

// Create a model and fetch all data from database
qx::IxModel * pModel = new qx::QxModel<author>();
pModel->qxFetchAll();

// Associate the model to a QML view and display it
QQuickView qmlView;
qmlView.rootContext()->setContextProperty("myModel", pModel);
qmlView.setSource(QUrl("qrc:/documents/main.qml"));
qmlView.show();

And here is the 'main.qml' file content :

import QtQuick 2.1
import QtQuick.Controls 1.0

Item {
   width: 400
   height: 300
   Row {
      height: 20
      spacing: 20
      Button {
         text: "Clear"
         onClicked: myModel.clear()
      }
      Button {
         text: "Fetch All"
         onClicked: myModel.qxFetchAll_()
      }
      Button {
         text: "Save"
         onClicked: myModel.qxSave_()
      }
   }
   ListView {
      y: 30
      height: 270
      model: myModel
      delegate: Row {
         height: 20
         spacing: 10
         Text { text: "id: " + author_id }
         TextField {
            text: name
            onTextChanged: name = text
         }
      }
   }
}

After executing this code, you will have the following window :

qx_model_view_02

As you can see in the 'main.qml' file, 'author_id' and 'name' properties of 'author' model (myModel variable) can be automatically read and write (because they are registered into QxOrm context).
Moreover, qx::IxModel interface provides a list of methods for QML side (Q_INVOKABLE) to communicate with database : for example, the 'Save' button will save the model in database without having to write a C++ function.

Note : a QxEntityEditor plugin generates automatically the code to manage models with relationships. Then it is possible to work with nested C++ models.




QxOrm © 2014 Lionel Marty - contact@qxorm.com